aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.rubocop.yml42
-rw-r--r--.travis.yml28
-rw-r--r--CONTRIBUTING.md1
-rw-r--r--Gemfile9
-rw-r--r--Gemfile.lock92
-rw-r--r--Rakefile2
-rw-r--r--actioncable/CHANGELOG.md16
-rw-r--r--actioncable/README.md2
-rw-r--r--actioncable/Rakefile10
-rw-r--r--actioncable/actioncable.gemspec7
-rwxr-xr-xactioncable/bin/test2
-rw-r--r--actioncable/lib/action_cable.rb2
-rw-r--r--actioncable/lib/action_cable/channel/periodic_timers.rb3
-rw-r--r--actioncable/lib/action_cable/connection/base.rb3
-rw-r--r--actioncable/lib/action_cable/connection/identification.rb3
-rw-r--r--actioncable/lib/action_cable/connection/web_socket.rb2
-rw-r--r--actioncable/lib/action_cable/engine.rb2
-rw-r--r--actioncable/lib/action_cable/remote_connections.rb2
-rw-r--r--actioncable/lib/action_cable/server/base.rb2
-rw-r--r--actioncable/lib/action_cable/subscription_adapter/async.rb2
-rw-r--r--actioncable/lib/action_cable/subscription_adapter/evented_redis.rb4
-rw-r--r--actioncable/lib/action_cable/subscription_adapter/redis.rb4
-rw-r--r--actioncable/lib/rails/generators/channel/channel_generator.rb2
-rw-r--r--actioncable/test/client_test.rb12
-rw-r--r--actioncable/test/subscription_adapter/async_test.rb2
-rw-r--r--actioncable/test/subscription_adapter/base_test.rb16
-rw-r--r--actioncable/test/subscription_adapter/evented_redis_test.rb6
-rw-r--r--actioncable/test/subscription_adapter/inline_test.rb2
-rw-r--r--actioncable/test/subscription_adapter/postgresql_test.rb2
-rw-r--r--actioncable/test/subscription_adapter/redis_test.rb14
-rw-r--r--actioncable/test/test_helper.rb2
-rw-r--r--actionmailer/CHANGELOG.md11
-rw-r--r--actionmailer/actionmailer.gemspec7
-rwxr-xr-xactionmailer/bin/test2
-rw-r--r--actionmailer/lib/action_mailer.rb3
-rw-r--r--actionmailer/lib/action_mailer/base.rb10
-rw-r--r--actionmailer/lib/action_mailer/delivery_methods.rb17
-rw-r--r--actionmailer/lib/action_mailer/message_delivery.rb19
-rw-r--r--actionmailer/lib/action_mailer/preview.rb13
-rw-r--r--actionmailer/lib/rails/generators/mailer/mailer_generator.rb2
-rw-r--r--actionmailer/test/abstract_unit.rb4
-rw-r--r--actionmailer/test/base_test.rb20
-rw-r--r--actionmailer/test/caching_test.rb12
-rw-r--r--actionmailer/test/log_subscriber_test.rb4
-rw-r--r--actionmailer/test/message_delivery_test.rb13
-rw-r--r--actionmailer/test/parameterized_test.rb1
-rw-r--r--actionpack/CHANGELOG.md50
-rw-r--r--actionpack/Rakefile2
-rw-r--r--actionpack/actionpack.gemspec7
-rwxr-xr-xactionpack/bin/test2
-rw-r--r--actionpack/lib/abstract_controller/base.rb10
-rw-r--r--actionpack/lib/abstract_controller/caching.rb3
-rw-r--r--actionpack/lib/abstract_controller/caching/fragments.rb35
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb22
-rw-r--r--actionpack/lib/abstract_controller/helpers.rb7
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb2
-rw-r--r--actionpack/lib/abstract_controller/translation.rb2
-rw-r--r--actionpack/lib/action_controller.rb4
-rw-r--r--actionpack/lib/action_controller/api.rb3
-rw-r--r--actionpack/lib/action_controller/base.rb5
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb6
-rw-r--r--actionpack/lib/action_controller/metal.rb3
-rw-r--r--actionpack/lib/action_controller/metal/conditional_get.rb3
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb2
-rw-r--r--actionpack/lib/action_controller/metal/etag_with_template_digest.rb3
-rw-r--r--actionpack/lib/action_controller/metal/flash.rb3
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb5
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb12
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb3
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb8
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb140
-rw-r--r--actionpack/lib/action_controller/railtie.rb18
-rw-r--r--actionpack/lib/action_controller/test_case.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/filter_parameters.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb3
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb10
-rw-r--r--actionpack/lib/action_dispatch/http/parameter_filter.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb8
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb14
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb10
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb8
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb3
-rw-r--r--actionpack/lib/action_dispatch/journey.rb10
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/builder.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/simulator.rb8
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/transition_table.rb4
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/builder.rb4
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/transition_table.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/nodes/node.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/parser.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/parser.y2
-rw-r--r--actionpack/lib/action_dispatch/journey/parser_extras.rb4
-rw-r--r--actionpack/lib/action_dispatch/journey/route.rb10
-rw-r--r--actionpack/lib/action_dispatch/journey/router.rb12
-rw-r--r--actionpack/lib/action_dispatch/journey/router/utils.rb16
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb54
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb8
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb8
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cache_store.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb14
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb3
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb2
-rw-r--r--actionpack/lib/action_dispatch/request/utils.rb3
-rw-r--r--actionpack/lib/action_dispatch/routing.rb9
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb37
-rw-r--r--actionpack/lib/action_dispatch/routing/redirection.rb4
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb43
-rw-r--r--actionpack/lib/action_dispatch/routing/routes_proxy.rb5
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb13
-rw-r--r--actionpack/lib/action_dispatch/system_test_case.rb20
-rw-r--r--actionpack/lib/action_dispatch/system_testing/driver.rb29
-rw-r--r--actionpack/lib/action_dispatch/system_testing/server.rb13
-rw-r--r--actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb7
-rw-r--r--actionpack/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb24
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb16
-rw-r--r--actionpack/lib/action_dispatch/testing/test_process.rb4
-rw-r--r--actionpack/lib/action_dispatch/testing/test_response.rb2
-rw-r--r--actionpack/lib/action_pack.rb2
-rw-r--r--actionpack/test/abstract/translation_test.rb1
-rw-r--r--actionpack/test/abstract_unit.rb10
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb2
-rw-r--r--actionpack/test/controller/api/data_streaming_test.rb2
-rw-r--r--actionpack/test/controller/caching_test.rb71
-rw-r--r--actionpack/test/controller/helper_test.rb8
-rw-r--r--actionpack/test/controller/integration_test.rb14
-rw-r--r--actionpack/test/controller/live_stream_test.rb2
-rw-r--r--actionpack/test/controller/mime/accept_format_test.rb2
-rw-r--r--actionpack/test/controller/new_base/render_file_test.rb10
-rw-r--r--actionpack/test/controller/new_base/render_implicit_action_test.rb2
-rw-r--r--actionpack/test/controller/parameters/accessors_test.rb5
-rw-r--r--actionpack/test/controller/parameters/mutators_test.rb21
-rw-r--r--actionpack/test/controller/parameters/parameters_permit_test.rb72
-rw-r--r--actionpack/test/controller/params_wrapper_test.rb16
-rw-r--r--actionpack/test/controller/redirect_test.rb4
-rw-r--r--actionpack/test/controller/render_test.rb12
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb101
-rw-r--r--actionpack/test/controller/required_params_test.rb24
-rw-r--r--actionpack/test/controller/send_file_test.rb2
-rw-r--r--actionpack/test/controller/test_case_test.rb6
-rw-r--r--actionpack/test/controller/url_for_test.rb2
-rw-r--r--actionpack/test/dispatch/cookies_test.rb206
-rw-r--r--actionpack/test/dispatch/request/multipart_params_parsing_test.rb2
-rw-r--r--actionpack/test/dispatch/request/session_test.rb18
-rw-r--r--actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb4
-rw-r--r--actionpack/test/dispatch/request_test.rb17
-rw-r--r--actionpack/test/dispatch/routing/custom_url_helpers_test.rb14
-rw-r--r--actionpack/test/dispatch/routing/route_set_test.rb9
-rw-r--r--actionpack/test/dispatch/routing_test.rb32
-rw-r--r--actionpack/test/dispatch/system_testing/driver_test.rb18
-rw-r--r--actionpack/test/dispatch/system_testing/system_test_case_test.rb44
-rw-r--r--actionpack/test/dispatch/uploaded_file_test.rb6
-rw-r--r--actionpack/test/fixtures/functional_caching/formatted_fragment_cached.html.erb2
-rw-r--r--actionpack/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder2
-rw-r--r--actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb2
-rw-r--r--actionpack/test/fixtures/functional_caching/fragment_cached.html.erb2
-rw-r--r--actionpack/test/fixtures/layouts/builder.builder2
-rw-r--r--actionpack/test/fixtures/old_content_type/render_default_for_builder.builder2
-rw-r--r--actionpack/test/fixtures/respond_to/using_defaults.xml.builder2
-rw-r--r--actionpack/test/fixtures/respond_to/using_defaults_with_type_list.xml.builder2
-rw-r--r--actionpack/test/fixtures/test/formatted_xml_erb.builder2
-rw-r--r--actionpack/test/fixtures/test/hello_xml_world.builder2
-rw-r--r--actionpack/test/journey/gtg/transition_table_test.rb34
-rw-r--r--actionpack/test/journey/router/utils_test.rb5
-rw-r--r--actionpack/test/lib/controller/fake_models.rb4
-rw-r--r--actionview/CHANGELOG.md28
-rw-r--r--actionview/Rakefile12
-rw-r--r--actionview/actionview.gemspec7
-rw-r--r--actionview/app/assets/javascripts/README.md15
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/start.coffee2
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee9
-rwxr-xr-xactionview/bin/test2
-rw-r--r--actionview/lib/action_view.rb4
-rw-r--r--actionview/lib/action_view/base.rb26
-rw-r--r--actionview/lib/action_view/dependency_tracker.rb2
-rw-r--r--actionview/lib/action_view/digestor.rb4
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb34
-rw-r--r--actionview/lib/action_view/helpers/cache_helper.rb32
-rw-r--r--actionview/lib/action_view/helpers/date_helper.rb50
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb75
-rw-r--r--actionview/lib/action_view/helpers/form_options_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/javascript_helper.rb6
-rw-r--r--actionview/lib/action_view/helpers/tags/check_box.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_check_boxes.rb3
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb3
-rw-r--r--actionview/lib/action_view/helpers/tags/radio_button.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/select.rb4
-rw-r--r--actionview/lib/action_view/helpers/tags/text_area.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/text_field.rb2
-rw-r--r--actionview/lib/action_view/helpers/translation_helper.rb7
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb14
-rw-r--r--actionview/lib/action_view/layouts.rb8
-rw-r--r--actionview/lib/action_view/log_subscriber.rb11
-rw-r--r--actionview/lib/action_view/lookup_context.rb8
-rw-r--r--actionview/lib/action_view/railtie.rb11
-rw-r--r--actionview/lib/action_view/record_identifier.rb2
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer.rb4
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb4
-rw-r--r--actionview/lib/action_view/renderer/renderer.rb4
-rw-r--r--actionview/lib/action_view/rendering.rb2
-rw-r--r--actionview/lib/action_view/template.rb6
-rw-r--r--actionview/lib/action_view/template/handlers/builder.rb5
-rw-r--r--actionview/lib/action_view/template/handlers/erb.rb9
-rw-r--r--actionview/lib/action_view/template/resolver.rb7
-rw-r--r--actionview/lib/action_view/test_case.rb4
-rw-r--r--actionview/lib/action_view/testing/resolvers.rb4
-rw-r--r--actionview/lib/action_view/view_paths.rb4
-rw-r--r--actionview/test/abstract_unit.rb14
-rw-r--r--actionview/test/actionpack/abstract/abstract_controller_test.rb4
-rw-r--r--actionview/test/actionpack/abstract/helper_test.rb4
-rw-r--r--actionview/test/actionpack/controller/capture_test.rb2
-rw-r--r--actionview/test/actionpack/controller/layout_test.rb6
-rw-r--r--actionview/test/actionpack/controller/render_test.rb12
-rw-r--r--actionview/test/active_record_unit.rb6
-rw-r--r--actionview/test/activerecord/controller_runtime_test.rb2
-rw-r--r--actionview/test/activerecord/form_helper_activerecord_test.rb4
-rw-r--r--actionview/test/activerecord/relation_cache_test.rb8
-rw-r--r--actionview/test/fixtures/actionpack/layouts/builder.builder2
-rw-r--r--actionview/test/fixtures/actionpack/test/_hello.builder2
-rw-r--r--actionview/test/fixtures/actionpack/test/formatted_xml_erb.builder2
-rw-r--r--actionview/test/fixtures/actionpack/test/hello.builder4
-rw-r--r--actionview/test/fixtures/actionpack/test/hello_world_container.builder4
-rw-r--r--actionview/test/fixtures/actionpack/test/hello_xml_world.builder2
-rw-r--r--actionview/test/fixtures/actionpack/test/non_erb_block_content_for.builder2
-rw-r--r--actionview/test/fixtures/comments/empty.html+grid.erb1
-rw-r--r--actionview/test/fixtures/comments/empty.html.builder2
-rw-r--r--actionview/test/fixtures/ruby_template.ruby2
-rw-r--r--actionview/test/fixtures/test/_cached_nested_cached_customer.erb3
-rw-r--r--actionview/test/fixtures/test/_nested_cached_customer.erb1
-rw-r--r--actionview/test/fixtures/test/_partial_with_variants.html+grid.erb1
-rw-r--r--actionview/test/fixtures/test/hello.builder4
-rw-r--r--actionview/test/template/asset_tag_helper_test.rb12
-rw-r--r--actionview/test/template/atom_feed_helper_test.rb10
-rw-r--r--actionview/test/template/date_helper_test.rb398
-rw-r--r--actionview/test/template/digestor_test.rb2
-rw-r--r--actionview/test/template/form_helper/form_with_test.rb96
-rw-r--r--actionview/test/template/form_helper_test.rb14
-rw-r--r--actionview/test/template/form_options_helper_test.rb17
-rw-r--r--actionview/test/template/form_tag_helper_test.rb10
-rw-r--r--actionview/test/template/javascript_helper_test.rb4
-rw-r--r--actionview/test/template/log_subscriber_test.rb55
-rw-r--r--actionview/test/template/render_test.rb26
-rw-r--r--actionview/test/template/resolver_patterns_test.rb2
-rw-r--r--actionview/test/template/streaming_render_test.rb2
-rw-r--r--actionview/test/template/template_test.rb2
-rw-r--r--actionview/test/template/text_helper_test.rb8
-rw-r--r--actionview/test/template/url_helper_test.rb56
-rw-r--r--actionview/test/ujs/config.ru2
-rw-r--r--actionview/test/ujs/public/test/call-remote.js28
-rw-r--r--actionview/test/ujs/public/test/data-remote.js67
-rw-r--r--activejob/.gitignore1
-rw-r--r--activejob/CHANGELOG.md7
-rw-r--r--activejob/Rakefile8
-rw-r--r--activejob/activejob.gemspec7
-rwxr-xr-xactivejob/bin/test2
-rw-r--r--activejob/lib/active_job.rb2
-rw-r--r--activejob/lib/active_job/base.rb20
-rw-r--r--activejob/lib/active_job/callbacks.rb4
-rw-r--r--activejob/lib/active_job/core.rb1
-rw-r--r--activejob/lib/active_job/enqueuing.rb2
-rw-r--r--activejob/lib/active_job/execution.rb2
-rw-r--r--activejob/lib/active_job/logging.rb15
-rw-r--r--activejob/lib/active_job/queue_adapter.rb19
-rw-r--r--activejob/lib/active_job/queue_name.rb11
-rw-r--r--activejob/lib/active_job/queue_priority.rb6
-rw-r--r--activejob/lib/rails/generators/job/job_generator.rb2
-rw-r--r--activejob/test/adapters/delayed_job.rb2
-rw-r--r--activejob/test/cases/job_serialization_test.rb8
-rw-r--r--activejob/test/cases/logging_test.rb8
-rw-r--r--activejob/test/cases/queue_adapter_test.rb5
-rw-r--r--activejob/test/jobs/rescue_job.rb2
-rw-r--r--activejob/test/support/delayed_job/delayed/backend/test.rb3
-rw-r--r--activejob/test/support/integration/dummy_app_template.rb2
-rw-r--r--activejob/test/support/integration/helper.rb2
-rw-r--r--activemodel/CHANGELOG.md47
-rw-r--r--activemodel/Rakefile8
-rw-r--r--activemodel/activemodel.gemspec7
-rwxr-xr-xactivemodel/bin/test2
-rw-r--r--activemodel/lib/active_model.rb4
-rw-r--r--activemodel/lib/active_model/attribute_assignment.rb11
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb9
-rw-r--r--activemodel/lib/active_model/callbacks.rb3
-rw-r--r--activemodel/lib/active_model/dirty.rb8
-rw-r--r--activemodel/lib/active_model/errors.rb8
-rw-r--r--activemodel/lib/active_model/naming.rb2
-rw-r--r--activemodel/lib/active_model/serializers/json.rb3
-rw-r--r--activemodel/lib/active_model/type.rb28
-rw-r--r--activemodel/lib/active_model/type/big_integer.rb2
-rw-r--r--activemodel/lib/active_model/type/helpers.rb8
-rw-r--r--activemodel/lib/active_model/type/string.rb9
-rw-r--r--activemodel/lib/active_model/validations.rb8
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb2
-rw-r--r--activemodel/lib/active_model/validations/format.rb1
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb2
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb6
-rw-r--r--activemodel/lib/active_model/validations/presence.rb1
-rw-r--r--activemodel/lib/active_model/validations/validates.rb5
-rw-r--r--activemodel/lib/active_model/validator.rb8
-rw-r--r--activemodel/test/cases/dirty_test.rb2
-rw-r--r--activemodel/test/cases/type/string_test.rb13
-rw-r--r--activemodel/test/cases/validations/format_validation_test.rb6
-rw-r--r--activemodel/test/validators/email_validator.rb1
-rw-r--r--activerecord/CHANGELOG.md141
-rw-r--r--activerecord/Rakefile6
-rw-r--r--activerecord/activerecord.gemspec7
-rwxr-xr-xactiverecord/bin/test2
-rw-r--r--activerecord/lib/active_record.rb6
-rw-r--r--activerecord/lib/active_record/associations.rb27
-rw-r--r--activerecord/lib/active_record/associations/alias_tracker.rb26
-rw-r--r--activerecord/lib/active_record/associations/association.rb30
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb23
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/builder/collection_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb89
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb26
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb7
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb11
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb43
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb64
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_base.rb2
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_part.rb4
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb17
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb75
-rw-r--r--activerecord/lib/active_record/associations/preloader/belongs_to.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader/through_association.rb35
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb9
-rw-r--r--activerecord/lib/active_record/attribute.rb2
-rw-r--r--activerecord/lib/active_record/attribute/user_provided_default.rb2
-rw-r--r--activerecord/lib/active_record/attribute_decorators.rb3
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb1
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb5
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb17
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb10
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb21
-rw-r--r--activerecord/lib/active_record/attribute_mutation_tracker.rb5
-rw-r--r--activerecord/lib/active_record/attribute_set.rb6
-rw-r--r--activerecord/lib/active_record/attribute_set/builder.rb2
-rw-r--r--activerecord/lib/active_record/attributes.rb5
-rw-r--r--activerecord/lib/active_record/autosave_association.rb19
-rw-r--r--activerecord/lib/active_record/base.rb16
-rw-r--r--activerecord/lib/active_record/collection_cache_key.rb22
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb20
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb91
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb31
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb45
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb136
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb16
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb140
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb19
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb62
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb36
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb43
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb47
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb92
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb95
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb37
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb65
-rw-r--r--activerecord/lib/active_record/connection_handling.rb2
-rw-r--r--activerecord/lib/active_record/core.rb19
-rw-r--r--activerecord/lib/active_record/dynamic_matchers.rb18
-rw-r--r--activerecord/lib/active_record/enum.rb26
-rw-r--r--activerecord/lib/active_record/errors.rb2
-rw-r--r--activerecord/lib/active_record/explain.rb4
-rw-r--r--activerecord/lib/active_record/explain_subscriber.rb2
-rw-r--r--activerecord/lib/active_record/fixtures.rb60
-rw-r--r--activerecord/lib/active_record/inheritance.rb5
-rw-r--r--activerecord/lib/active_record/integration.rb75
-rw-r--r--activerecord/lib/active_record/internal_metadata.rb4
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb12
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb7
-rw-r--r--activerecord/lib/active_record/migration.rb34
-rw-r--r--activerecord/lib/active_record/migration/command_recorder.rb12
-rw-r--r--activerecord/lib/active_record/model_schema.rb55
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb5
-rw-r--r--activerecord/lib/active_record/persistence.rb28
-rw-r--r--activerecord/lib/active_record/query_cache.rb12
-rw-r--r--activerecord/lib/active_record/querying.rb2
-rw-r--r--activerecord/lib/active_record/railtie.rb10
-rw-r--r--activerecord/lib/active_record/railties/controller_runtime.rb2
-rw-r--r--activerecord/lib/active_record/railties/databases.rake10
-rw-r--r--activerecord/lib/active_record/readonly_attributes.rb3
-rw-r--r--activerecord/lib/active_record/reflection.rb105
-rw-r--r--activerecord/lib/active_record/relation.rb14
-rw-r--r--activerecord/lib/active_record/relation/batches.rb26
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb27
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb18
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb68
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb53
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/array_handler.rb19
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb88
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb44
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb59
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb52
-rw-r--r--activerecord/lib/active_record/relation/query_attribute.rb2
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb22
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb2
-rw-r--r--activerecord/lib/active_record/relation/where_clause_factory.rb2
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb20
-rw-r--r--activerecord/lib/active_record/schema_migration.rb4
-rw-r--r--activerecord/lib/active_record/scoping.rb7
-rw-r--r--activerecord/lib/active_record/scoping/default.rb13
-rw-r--r--activerecord/lib/active_record/scoping/named.rb24
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb14
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb16
-rw-r--r--activerecord/lib/active_record/tasks/postgresql_database_tasks.rb6
-rw-r--r--activerecord/lib/active_record/tasks/sqlite_database_tasks.rb26
-rw-r--r--activerecord/lib/active_record/timestamp.rb5
-rw-r--r--activerecord/lib/active_record/transactions.rb2
-rw-r--r--activerecord/lib/active_record/type.rb25
-rw-r--r--activerecord/lib/active_record/type/internal/abstract_json.rb33
-rw-r--r--activerecord/lib/active_record/type/json.rb28
-rw-r--r--activerecord/lib/active_record/type_caster.rb4
-rw-r--r--activerecord/lib/active_record/validations.rb10
-rw-r--r--activerecord/lib/rails/generators/active_record.rb2
-rw-r--r--activerecord/test/cases/adapter_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/boolean_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql2/charset_collation_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql2/json_test.rb191
-rw-r--r--activerecord/test/cases/adapters/mysql2/reserved_word_test.rb151
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/array_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/collation_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb16
-rw-r--r--activerecord/test/cases/adapters/postgresql/explain_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/geometric_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/json_test.rb209
-rw-r--r--activerecord/test/cases/adapters/postgresql/money_test.rb10
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb24
-rw-r--r--activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb175
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb17
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb10
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb23
-rw-r--r--activerecord/test/cases/adapters/sqlite3/collation_test.rb4
-rw-r--r--activerecord/test/cases/adapters/sqlite3/explain_test.rb4
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb14
-rw-r--r--activerecord/test/cases/associations/association_scope_test.rb3
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb19
-rw-r--r--activerecord/test/cases/associations/callbacks_test.rb2
-rw-r--r--activerecord/test/cases/associations/cascaded_eager_loading_test.rb6
-rw-r--r--activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb27
-rw-r--r--activerecord/test/cases/associations/eager_test.rb15
-rw-r--r--activerecord/test/cases/associations/extension_test.rb6
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb35
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb95
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb48
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb9
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb48
-rw-r--r--activerecord/test/cases/associations/left_outer_join_association_test.rb2
-rw-r--r--activerecord/test/cases/associations_test.rb12
-rw-r--r--activerecord/test/cases/attribute_test.rb4
-rw-r--r--activerecord/test/cases/base_test.rb85
-rw-r--r--activerecord/test/cases/batches_test.rb32
-rw-r--r--activerecord/test/cases/binary_test.rb2
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb14
-rw-r--r--activerecord/test/cases/cache_key_test.rb32
-rw-r--r--activerecord/test/cases/calculations_test.rb28
-rw-r--r--activerecord/test/cases/coders/yaml_column_test.rb1
-rw-r--r--activerecord/test/cases/collection_cache_key_test.rb46
-rw-r--r--activerecord/test/cases/comment_test.rb14
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb11
-rw-r--r--activerecord/test/cases/connection_pool_test.rb13
-rw-r--r--activerecord/test/cases/core_test.rb8
-rw-r--r--activerecord/test/cases/defaults_test.rb9
-rw-r--r--activerecord/test/cases/dirty_test.rb46
-rw-r--r--activerecord/test/cases/enum_test.rb7
-rw-r--r--activerecord/test/cases/errors_test.rb2
-rw-r--r--activerecord/test/cases/explain_test.rb6
-rw-r--r--activerecord/test/cases/finder_test.rb39
-rw-r--r--activerecord/test/cases/fixtures_test.rb50
-rw-r--r--activerecord/test/cases/inheritance_test.rb11
-rw-r--r--activerecord/test/cases/integration_test.rb63
-rw-r--r--activerecord/test/cases/json_attribute_test.rb33
-rw-r--r--activerecord/test/cases/json_shared_test_cases.rb221
-rw-r--r--activerecord/test/cases/locking_test.rb71
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb56
-rw-r--r--activerecord/test/cases/migration/command_recorder_test.rb5
-rw-r--r--activerecord/test/cases/migration/compatibility_test.rb15
-rw-r--r--activerecord/test/cases/migration/references_foreign_key_test.rb10
-rw-r--r--activerecord/test/cases/migration/references_statements_test.rb8
-rw-r--r--activerecord/test/cases/migration/rename_table_test.rb25
-rw-r--r--activerecord/test/cases/migration_test.rb41
-rw-r--r--activerecord/test/cases/migrator_test.rb2
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb4
-rw-r--r--activerecord/test/cases/numeric_data_test.rb71
-rw-r--r--activerecord/test/cases/persistence_test.rb2
-rw-r--r--activerecord/test/cases/primary_keys_test.rb23
-rw-r--r--activerecord/test/cases/query_cache_test.rb76
-rw-r--r--activerecord/test/cases/quoting_test.rb15
-rw-r--r--activerecord/test/cases/relation/delegation_test.rb49
-rw-r--r--activerecord/test/cases/relation/merging_test.rb6
-rw-r--r--activerecord/test/cases/relation/mutation_test.rb4
-rw-r--r--activerecord/test/cases/relation/or_test.rb25
-rw-r--r--activerecord/test/cases/relation/where_chain_test.rb2
-rw-r--r--activerecord/test/cases/relation/where_clause_test.rb26
-rw-r--r--activerecord/test/cases/relation/where_test.rb11
-rw-r--r--activerecord/test/cases/relation_test.rb23
-rw-r--r--activerecord/test/cases/relations_test.rb55
-rw-r--r--activerecord/test/cases/reload_models_test.rb2
-rw-r--r--activerecord/test/cases/reserved_word_test.rb132
-rw-r--r--activerecord/test/cases/result_test.rb6
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb32
-rw-r--r--activerecord/test/cases/scoping/named_scoping_test.rb6
-rw-r--r--activerecord/test/cases/scoping/relation_scoping_test.rb20
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb28
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb20
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb15
-rw-r--r--activerecord/test/cases/tasks/postgresql_rake_test.rb8
-rw-r--r--activerecord/test/cases/tasks/sqlite_rake_test.rb20
-rw-r--r--activerecord/test/cases/test_case.rb8
-rw-r--r--activerecord/test/cases/transactions_test.rb154
-rw-r--r--activerecord/test/cases/validations_test.rb14
-rw-r--r--activerecord/test/cases/view_test.rb4
-rw-r--r--activerecord/test/cases/yaml_serialization_test.rb12
-rw-r--r--activerecord/test/config.rb2
-rw-r--r--activerecord/test/fixtures/books.yml1
-rw-r--r--activerecord/test/fixtures/naked/yml/parrots.yml1
-rw-r--r--activerecord/test/migrations/magic/1_currencies_have_symbols.rb1
-rw-r--r--activerecord/test/models/author.rb1
-rw-r--r--activerecord/test/models/book.rb4
-rw-r--r--activerecord/test/models/category.rb9
-rw-r--r--activerecord/test/models/club.rb2
-rw-r--r--activerecord/test/models/comment.rb18
-rw-r--r--activerecord/test/models/company.rb9
-rw-r--r--activerecord/test/models/essay.rb1
-rw-r--r--activerecord/test/models/member.rb1
-rw-r--r--activerecord/test/models/membership.rb1
-rw-r--r--activerecord/test/models/numeric_data.rb8
-rw-r--r--activerecord/test/models/post.rb3
-rw-r--r--activerecord/test/models/subject.rb14
-rw-r--r--activerecord/test/models/topic.rb2
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb8
-rw-r--r--activerecord/test/schema/schema.rb28
-rw-r--r--activesupport/CHANGELOG.md105
-rw-r--r--activesupport/activesupport.gemspec7
-rwxr-xr-xactivesupport/bin/generate_tables2
-rwxr-xr-xactivesupport/bin/test2
-rw-r--r--activesupport/lib/active_support.rb11
-rw-r--r--activesupport/lib/active_support/all.rb4
-rw-r--r--activesupport/lib/active_support/benchmarkable.rb4
-rw-r--r--activesupport/lib/active_support/cache.rb135
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb8
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb12
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache.rb21
-rw-r--r--activesupport/lib/active_support/callbacks.rb28
-rw-r--r--activesupport/lib/active_support/configurable.rb8
-rw-r--r--activesupport/lib/active_support/core_ext.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/array.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/array/conversions.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/array/inquiry.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/array/prepend_and_append.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/big_decimal.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/class.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute.rb19
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute_accessors.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/class/subclasses.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/date.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/date/acts_like.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date/calculations.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/date/conversions.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/date/zones.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/calculations.rb18
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/date_time.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/acts_like.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/compatibility.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/conversions.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb68
-rw-r--r--activesupport/lib/active_support/core_ext/file.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash.rb18
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb18
-rw-r--r--activesupport/lib/active_support/core_ext/hash/indifferent_access.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/integer.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/integer/inflections.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/integer/time.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/kernel.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/concern.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module.rb22
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb47
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/module/concerning.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb48
-rw-r--r--activesupport/lib/active_support/core_ext/module/introspection.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/reachable.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/numeric.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/conversions.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/time.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/object.rb24
-rw-r--r--activesupport/lib/active_support/core_ext/object/blank.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/conversions.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/object/deep_dup.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/json.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_param.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/with_options.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/range.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/string.rb26
-rw-r--r--activesupport/lib/active_support/core_ext/string/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/string/inquiry.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/multibyte.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/string/zones.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/time.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/time/acts_like.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb48
-rw-r--r--activesupport/lib/active_support/core_ext/time/compatibility.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/time/conversions.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/time/zones.rb6
-rw-r--r--activesupport/lib/active_support/current_attributes.rb193
-rw-r--r--activesupport/lib/active_support/dependencies.rb55
-rw-r--r--activesupport/lib/active_support/dependencies/autoload.rb2
-rw-r--r--activesupport/lib/active_support/dependencies/interlock.rb2
-rw-r--r--activesupport/lib/active_support/deprecation.rb18
-rw-r--r--activesupport/lib/active_support/deprecation/behaviors.rb33
-rw-r--r--activesupport/lib/active_support/deprecation/constant_accessor.rb2
-rw-r--r--activesupport/lib/active_support/deprecation/instance_delegator.rb4
-rw-r--r--activesupport/lib/active_support/deprecation/method_wrappers.rb4
-rw-r--r--activesupport/lib/active_support/deprecation/proxy_wrappers.rb4
-rw-r--r--activesupport/lib/active_support/deprecation/reporting.rb4
-rw-r--r--activesupport/lib/active_support/duration.rb61
-rw-r--r--activesupport/lib/active_support/duration/iso8601_parser.rb2
-rw-r--r--activesupport/lib/active_support/duration/iso8601_serializer.rb8
-rw-r--r--activesupport/lib/active_support/execution_wrapper.rb2
-rw-r--r--activesupport/lib/active_support/executor.rb2
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb2
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb17
-rw-r--r--activesupport/lib/active_support/i18n.rb10
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb12
-rw-r--r--activesupport/lib/active_support/inflections.rb2
-rw-r--r--activesupport/lib/active_support/inflector.rb10
-rw-r--r--activesupport/lib/active_support/inflector/inflections.rb6
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb19
-rw-r--r--activesupport/lib/active_support/inflector/transliterate.rb4
-rw-r--r--activesupport/lib/active_support/json.rb4
-rw-r--r--activesupport/lib/active_support/json/decoding.rb4
-rw-r--r--activesupport/lib/active_support/json/encoding.rb4
-rw-r--r--activesupport/lib/active_support/log_subscriber.rb15
-rw-r--r--activesupport/lib/active_support/log_subscriber/test_helper.rb6
-rw-r--r--activesupport/lib/active_support/logger.rb4
-rw-r--r--activesupport/lib/active_support/logger_silence.rb7
-rw-r--r--activesupport/lib/active_support/logger_thread_safe_level.rb2
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb28
-rw-r--r--activesupport/lib/active_support/message_verifier.rb4
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb10
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb2
-rw-r--r--activesupport/lib/active_support/notifications.rb8
-rw-r--r--activesupport/lib/active_support/number_helper.rb1
-rw-r--r--activesupport/lib/active_support/number_helper/number_converter.rb10
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_currency_converter.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_human_converter.rb6
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_phone_converter.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb32
-rw-r--r--activesupport/lib/active_support/number_helper/rounding_helper.rb64
-rw-r--r--activesupport/lib/active_support/option_merger.rb2
-rw-r--r--activesupport/lib/active_support/ordered_options.rb2
-rw-r--r--activesupport/lib/active_support/per_thread_registry.rb2
-rw-r--r--activesupport/lib/active_support/rails.rb14
-rw-r--r--activesupport/lib/active_support/railtie.rb28
-rw-r--r--activesupport/lib/active_support/reloader.rb9
-rw-r--r--activesupport/lib/active_support/rescuable.rb19
-rw-r--r--activesupport/lib/active_support/subscriber.rb4
-rw-r--r--activesupport/lib/active_support/tagged_logging.rb6
-rw-r--r--activesupport/lib/active_support/test_case.rb20
-rw-r--r--activesupport/lib/active_support/testing/assertions.rb2
-rw-r--r--activesupport/lib/active_support/testing/constant_lookup.rb4
-rw-r--r--activesupport/lib/active_support/testing/deprecation.rb4
-rw-r--r--activesupport/lib/active_support/testing/setup_and_teardown.rb4
-rw-r--r--activesupport/lib/active_support/testing/time_helpers.rb3
-rw-r--r--activesupport/lib/active_support/time.rb14
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb44
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb5
-rw-r--r--activesupport/lib/active_support/xml_mini.rb8
-rw-r--r--activesupport/lib/active_support/xml_mini/jdom.rb2
-rw-r--r--activesupport/lib/active_support/xml_mini/libxml.rb8
-rw-r--r--activesupport/lib/active_support/xml_mini/libxmlsax.rb11
-rw-r--r--activesupport/lib/active_support/xml_mini/nokogiri.rb8
-rw-r--r--activesupport/lib/active_support/xml_mini/nokogirisax.rb8
-rw-r--r--activesupport/lib/active_support/xml_mini/rexml.rb6
-rw-r--r--activesupport/test/cache/behaviors.rb7
-rw-r--r--activesupport/test/cache/behaviors/autoloading_cache_behavior.rb41
-rw-r--r--activesupport/test/cache/behaviors/cache_delete_matched_behavior.rb13
-rw-r--r--activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb21
-rw-r--r--activesupport/test/cache/behaviors/cache_store_behavior.rb329
-rw-r--r--activesupport/test/cache/behaviors/cache_store_version_behavior.rb86
-rw-r--r--activesupport/test/cache/behaviors/encoded_key_cache_behavior.rb34
-rw-r--r--activesupport/test/cache/behaviors/local_cache_behavior.rb126
-rw-r--r--activesupport/test/cache/cache_entry_test.rb28
-rw-r--r--activesupport/test/cache/cache_key_test.rb88
-rw-r--r--activesupport/test/cache/cache_store_logger_test.rb34
-rw-r--r--activesupport/test/cache/cache_store_namespace_test.rb38
-rw-r--r--activesupport/test/cache/cache_store_setting_test.rb66
-rw-r--r--activesupport/test/cache/cache_store_write_multi_test.rb60
-rw-r--r--activesupport/test/cache/local_cache_middleware_test.rb61
-rw-r--r--activesupport/test/cache/stores/file_store_test.rb128
-rw-r--r--activesupport/test/cache/stores/mem_cache_store_test.rb74
-rw-r--r--activesupport/test/cache/stores/memory_store_test.rb107
-rw-r--r--activesupport/test/cache/stores/null_store_test.rb57
-rw-r--r--activesupport/test/caching_test.rb1203
-rw-r--r--activesupport/test/core_ext/array/conversions_test.rb2
-rw-r--r--activesupport/test/core_ext/class/attribute_test.rb10
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb25
-rw-r--r--activesupport/test/core_ext/duration_test.rb36
-rw-r--r--activesupport/test/core_ext/enumerable_test.rb6
-rw-r--r--activesupport/test/core_ext/module/attribute_accessor_test.rb38
-rw-r--r--activesupport/test/core_ext/module_test.rb78
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb19
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb7
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb6
-rw-r--r--activesupport/test/current_attributes_test.rb96
-rw-r--r--activesupport/test/dependencies_test.rb20
-rw-r--r--activesupport/test/dependencies_test_helpers.rb2
-rw-r--r--activesupport/test/deprecation_test.rb35
-rw-r--r--activesupport/test/hash_with_indifferent_access_test.rb13
-rw-r--r--activesupport/test/inflector_test.rb7
-rw-r--r--activesupport/test/logger_test.rb4
-rw-r--r--activesupport/test/message_encryptor_test.rb26
-rw-r--r--activesupport/test/multibyte_chars_test.rb28
-rw-r--r--activesupport/test/multibyte_test_helpers.rb2
-rw-r--r--activesupport/test/number_helper_test.rb6
-rw-r--r--activesupport/test/rescuable_test.rb34
-rw-r--r--activesupport/test/test_case_test.rb3
-rw-r--r--activesupport/test/testing/file_fixtures_test.rb8
-rw-r--r--activesupport/test/time_travel_test.rb2
-rw-r--r--activesupport/test/xml_mini/jdom_engine_test.rb2
-rw-r--r--activesupport/test/xml_mini/xml_mini_engine_test.rb5
-rw-r--r--guides/Rakefile6
-rw-r--r--guides/assets/images/belongs_to.pngbin25803 -> 22147 bytes
-rw-r--r--guides/assets/images/getting_started/article_with_comments.pngbin22560 -> 13884 bytes
-rw-r--r--guides/assets/images/getting_started/challenge.pngbin21690 -> 20347 bytes
-rw-r--r--guides/assets/images/getting_started/confirm_dialog.pngbin18809 -> 17507 bytes
-rw-r--r--guides/assets/images/getting_started/forbidden_attributes_for_new_article.pngbin10783 -> 9851 bytes
-rw-r--r--guides/assets/images/getting_started/form_with_errors.pngbin12447 -> 11665 bytes
-rw-r--r--guides/assets/images/getting_started/index_action_with_edit_link.pngbin10209 -> 9703 bytes
-rw-r--r--guides/assets/images/getting_started/new_article.pngbin3579 -> 3193 bytes
-rw-r--r--guides/assets/images/getting_started/rails_welcome.pngbin1053549 -> 732190 bytes
-rw-r--r--guides/assets/images/getting_started/routing_error_no_controller.pngbin4186 -> 3869 bytes
-rw-r--r--guides/assets/images/getting_started/show_action_for_articles.pngbin2965 -> 2901 bytes
-rw-r--r--guides/assets/images/getting_started/template_is_missing_articles_new.pngbin587962 -> 472167 bytes
-rw-r--r--guides/assets/images/getting_started/unknown_action_create_for_articles.pngbin5327 -> 4808 bytes
-rw-r--r--guides/assets/images/getting_started/unknown_action_new_for_articles.pngbin5481 -> 4933 bytes
-rw-r--r--guides/assets/images/habtm.pngbin49332 -> 47284 bytes
-rw-r--r--guides/assets/images/has_many.pngbin28919 -> 24300 bytes
-rw-r--r--guides/assets/images/has_many_through.pngbin79428 -> 78099 bytes
-rw-r--r--guides/assets/images/has_one.pngbin29072 -> 27547 bytes
-rw-r--r--guides/assets/images/has_one_through.pngbin72434 -> 70130 bytes
-rw-r--r--guides/assets/images/header_backdrop.pngbin224 -> 206 bytes
-rw-r--r--guides/assets/images/i18n/demo_html_safe.pngbin10073 -> 9860 bytes
-rw-r--r--guides/assets/images/i18n/demo_localized_pirate.pngbin11485 -> 11214 bytes
-rw-r--r--guides/assets/images/i18n/demo_translated_en.pngbin9325 -> 9069 bytes
-rw-r--r--guides/assets/images/i18n/demo_translated_pirate.pngbin10202 -> 9974 bytes
-rw-r--r--guides/assets/images/i18n/demo_translation_missing.pngbin10260 -> 9984 bytes
-rw-r--r--guides/assets/images/i18n/demo_untranslated.pngbin9224 -> 8985 bytes
-rw-r--r--guides/assets/images/icons/callouts/14.pngbin246 -> 190 bytes
-rw-r--r--guides/assets/images/icons/example.pngbin2078 -> 2052 bytes
-rw-r--r--guides/assets/images/icons/home.pngbin1163 -> 1134 bytes
-rw-r--r--guides/assets/images/icons/important.pngbin2451 -> 2426 bytes
-rw-r--r--guides/assets/images/icons/next.pngbin1146 -> 1111 bytes
-rw-r--r--guides/assets/images/icons/note.pngbin2155 -> 2096 bytes
-rw-r--r--guides/assets/images/icons/prev.pngbin1126 -> 1093 bytes
-rw-r--r--guides/assets/images/icons/tip.pngbin2248 -> 2170 bytes
-rw-r--r--guides/assets/images/icons/up.pngbin1133 -> 1106 bytes
-rw-r--r--guides/assets/images/polymorphic.pngbin66415 -> 65417 bytes
-rw-r--r--guides/assets/images/rails4_features.pngbin67766 -> 65840 bytes
-rw-r--r--guides/assets/images/session_fixation.pngbin38451 -> 38296 bytes
-rw-r--r--guides/assets/images/tab_yellow.pngbin1441 -> 1395 bytes
-rw-r--r--guides/bug_report_templates/action_controller_gem.rb4
-rw-r--r--guides/bug_report_templates/action_controller_master.rb2
-rw-r--r--guides/bug_report_templates/active_job_gem.rb2
-rw-r--r--guides/bug_report_templates/active_record_gem.rb2
-rw-r--r--guides/bug_report_templates/active_record_migrations_gem.rb8
-rw-r--r--guides/bug_report_templates/active_record_migrations_master.rb6
-rw-r--r--guides/bug_report_templates/generic_gem.rb2
-rw-r--r--guides/rails_guides/generator.rb2
-rw-r--r--guides/rails_guides/helpers.rb2
-rw-r--r--guides/rails_guides/markdown.rb2
-rw-r--r--guides/rails_guides/markdown/renderer.rb20
-rw-r--r--guides/source/5_1_release_notes.md435
-rw-r--r--guides/source/action_controller_overview.md3
-rw-r--r--guides/source/action_mailer_basics.md3
-rw-r--r--guides/source/action_view_overview.md2
-rw-r--r--guides/source/active_job_basics.md6
-rw-r--r--guides/source/active_model_basics.md2
-rw-r--r--guides/source/active_record_callbacks.md10
-rw-r--r--guides/source/active_record_migrations.md10
-rw-r--r--guides/source/active_record_postgresql.md11
-rw-r--r--guides/source/active_record_querying.md23
-rw-r--r--guides/source/active_record_validations.md2
-rw-r--r--guides/source/active_support_core_extensions.md35
-rw-r--r--guides/source/api_app.md20
-rw-r--r--guides/source/api_documentation_guidelines.md6
-rw-r--r--guides/source/asset_pipeline.md22
-rw-r--r--guides/source/association_basics.md10
-rw-r--r--guides/source/autoloading_and_reloading_constants.md17
-rw-r--r--guides/source/command_line.md15
-rw-r--r--guides/source/configuring.md16
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md33
-rw-r--r--guides/source/development_dependencies_install.md2
-rw-r--r--guides/source/documents.yaml6
-rw-r--r--guides/source/engines.md112
-rw-r--r--guides/source/form_helpers.md4
-rw-r--r--guides/source/generators.md14
-rw-r--r--guides/source/getting_started.md40
-rw-r--r--guides/source/initialization.md2
-rw-r--r--guides/source/layouts_and_rendering.md8
-rw-r--r--guides/source/nested_model_forms.md230
-rw-r--r--guides/source/plugins.md6
-rw-r--r--guides/source/profiling.md16
-rw-r--r--guides/source/rails_application_templates.md2
-rw-r--r--guides/source/rails_on_rack.md28
-rw-r--r--guides/source/routing.md13
-rw-r--r--guides/source/security.md34
-rw-r--r--guides/source/testing.md41
-rw-r--r--guides/source/upgrading_ruby_on_rails.md26
-rw-r--r--guides/source/working_with_javascript_in_rails.md217
-rw-r--r--rails.gemspec4
-rw-r--r--railties/CHANGELOG.md28
-rw-r--r--railties/Rakefile10
-rwxr-xr-xrailties/bin/test2
-rwxr-xr-xrailties/exe/rails4
-rw-r--r--railties/lib/rails.rb8
-rw-r--r--railties/lib/rails/app_loader.rb4
-rw-r--r--railties/lib/rails/application.rb11
-rw-r--r--railties/lib/rails/application/bootstrap.rb3
-rw-r--r--railties/lib/rails/application/configuration.rb29
-rw-r--r--railties/lib/rails/application/default_middleware_stack.rb2
-rw-r--r--railties/lib/rails/application_controller.rb2
-rw-r--r--railties/lib/rails/cli.rb6
-rw-r--r--railties/lib/rails/code_statistics.rb5
-rw-r--r--railties/lib/rails/command.rb2
-rw-r--r--railties/lib/rails/command/actions.rb2
-rw-r--r--railties/lib/rails/command/base.rb2
-rw-r--r--railties/lib/rails/commands.rb2
-rw-r--r--railties/lib/rails/commands/application/application_command.rb4
-rw-r--r--railties/lib/rails/commands/console/console_command.rb18
-rw-r--r--railties/lib/rails/commands/dbconsole/dbconsole_command.rb4
-rw-r--r--railties/lib/rails/commands/destroy/destroy_command.rb2
-rw-r--r--railties/lib/rails/commands/generate/generate_command.rb2
-rw-r--r--railties/lib/rails/commands/help/USAGE17
-rw-r--r--railties/lib/rails/commands/plugin/plugin_command.rb4
-rw-r--r--railties/lib/rails/commands/secrets/secrets_command.rb25
-rw-r--r--railties/lib/rails/commands/server/server_command.rb18
-rw-r--r--railties/lib/rails/commands/test/test_command.rb6
-rw-r--r--railties/lib/rails/configuration.rb4
-rw-r--r--railties/lib/rails/engine.rb12
-rw-r--r--railties/lib/rails/engine/commands.rb2
-rw-r--r--railties/lib/rails/engine/configuration.rb2
-rw-r--r--railties/lib/rails/engine/updater.rb4
-rw-r--r--railties/lib/rails/generators.rb4
-rw-r--r--railties/lib/rails/generators/actions.rb1
-rw-r--r--railties/lib/rails/generators/app_base.rb12
-rw-r--r--railties/lib/rails/generators/base.rb2
-rw-r--r--railties/lib/rails/generators/css/assets/assets_generator.rb4
-rw-r--r--railties/lib/rails/generators/css/scaffold/scaffold_generator.rb2
-rw-r--r--railties/lib/rails/generators/erb.rb2
-rw-r--r--railties/lib/rails/generators/erb/controller/controller_generator.rb2
-rw-r--r--railties/lib/rails/generators/erb/mailer/mailer_generator.rb2
-rw-r--r--railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb4
-rw-r--r--railties/lib/rails/generators/js/assets/assets_generator.rb4
-rw-r--r--railties/lib/rails/generators/migration.rb2
-rw-r--r--railties/lib/rails/generators/model_helpers.rb2
-rw-r--r--railties/lib/rails/generators/named_base.rb6
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb37
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile10
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/bundle2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/setup.tt5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/update.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/yarn5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_1.rb.tt13
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt19
-rw-r--r--railties/lib/rails/generators/rails/app/templates/gitignore4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/404.html12
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/422.html12
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/500.html12
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/test_helper.rb2
-rw-r--r--railties/lib/rails/generators/rails/encrypted_secrets/encrypted_secrets_generator.rb34
-rw-r--r--railties/lib/rails/generators/rails/encrypted_secrets/templates/config/secrets.yml.enc3
-rw-r--r--railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/model/model_generator.rb2
-rw-r--r--railties/lib/rails/generators/rails/plugin/plugin_generator.rb9
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/Rakefile2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt8
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/bin/test.tt2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb4
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/railtie.rb5
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb8
-rw-r--r--railties/lib/rails/generators/rails/resource/resource_generator.rb4
-rw-r--r--railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb5
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/USAGE2
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb2
-rw-r--r--railties/lib/rails/generators/resource_helpers.rb4
-rw-r--r--railties/lib/rails/generators/test_case.rb12
-rw-r--r--railties/lib/rails/generators/test_unit.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/controller/controller_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/generator/generator_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/helper/helper_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/integration/integration_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/job/job_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/model/model_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/plugin/plugin_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb23
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb4
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb4
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb49
-rw-r--r--railties/lib/rails/generators/test_unit/system/system_generator.rb4
-rw-r--r--railties/lib/rails/generators/testing/behaviour.rb14
-rw-r--r--railties/lib/rails/info.rb5
-rw-r--r--railties/lib/rails/info_controller.rb2
-rw-r--r--railties/lib/rails/mailers_controller.rb10
-rw-r--r--railties/lib/rails/plugin/test.rb2
-rw-r--r--railties/lib/rails/railtie.rb13
-rw-r--r--railties/lib/rails/railtie/configuration.rb2
-rw-r--r--railties/lib/rails/secrets.rb44
-rw-r--r--railties/lib/rails/tasks/annotations.rake2
-rw-r--r--railties/lib/rails/tasks/dev.rake2
-rw-r--r--railties/lib/rails/tasks/engine.rake2
-rw-r--r--railties/lib/rails/tasks/framework.rake10
-rw-r--r--railties/lib/rails/tasks/statistics.rake2
-rw-r--r--railties/lib/rails/tasks/tmp.rake11
-rw-r--r--railties/lib/rails/templates/rails/mailers/email.html.erb6
-rw-r--r--railties/lib/rails/templates/rails/welcome/index.html.erb18
-rw-r--r--railties/lib/rails/test_help.rb11
-rw-r--r--railties/lib/rails/test_unit/minitest_plugin.rb8
-rw-r--r--railties/lib/rails/test_unit/railtie.rb4
-rw-r--r--railties/lib/rails/test_unit/reporter.rb3
-rw-r--r--railties/lib/rails/test_unit/testing.rake3
-rw-r--r--railties/lib/rails/welcome_controller.rb2
-rw-r--r--railties/railties.gemspec7
-rw-r--r--railties/test/abstract_unit.rb2
-rw-r--r--railties/test/application/configuration_test.rb145
-rw-r--r--railties/test/application/console_test.rb16
-rw-r--r--railties/test/application/current_attributes_integration_test.rb84
-rw-r--r--railties/test/application/initializers/i18n_test.rb2
-rw-r--r--railties/test/application/mailer_previews_test.rb51
-rw-r--r--railties/test/application/middleware/exceptions_test.rb14
-rw-r--r--railties/test/application/middleware/session_test.rb93
-rw-r--r--railties/test/application/per_request_digest_cache_test.rb4
-rw-r--r--railties/test/application/rake/migrations_test.rb69
-rw-r--r--railties/test/application/rake/tmp_test.rb43
-rw-r--r--railties/test/application/rake_test.rb7
-rw-r--r--railties/test/application/test_runner_test.rb4
-rw-r--r--railties/test/code_statistics_calculator_test.rb2
-rw-r--r--railties/test/code_statistics_test.rb2
-rw-r--r--railties/test/commands/secrets_test.rb9
-rw-r--r--railties/test/commands/server_test.rb18
-rw-r--r--railties/test/fixtures/lib/generators/usage_template/usage_template_generator.rb2
-rw-r--r--railties/test/generators/actions_test.rb7
-rw-r--r--railties/test/generators/api_app_generator_test.rb1
-rw-r--r--railties/test/generators/app_generator_test.rb44
-rw-r--r--railties/test/generators/channel_generator_test.rb4
-rw-r--r--railties/test/generators/create_migration_test.rb20
-rw-r--r--railties/test/generators/generators_test_helper.rb4
-rw-r--r--railties/test/generators/mailer_generator_test.rb12
-rw-r--r--railties/test/generators/migration_generator_test.rb8
-rw-r--r--railties/test/generators/model_generator_test.rb12
-rw-r--r--railties/test/generators/plugin_generator_test.rb60
-rw-r--r--railties/test/generators/scaffold_generator_test.rb34
-rw-r--r--railties/test/generators/shared_generator_tests.rb4
-rw-r--r--railties/test/generators/system_test_generator_test.rb5
-rw-r--r--railties/test/generators/test_runner_in_engine_test.rb2
-rw-r--r--railties/test/generators_test.rb2
-rw-r--r--railties/test/isolation/abstract_unit.rb8
-rw-r--r--railties/test/rails_info_test.rb2
-rw-r--r--railties/test/railties/engine_test.rb18
-rw-r--r--railties/test/secrets_test.rb32
-rw-r--r--railties/test/test_unit/reporter_test.rb10
-rw-r--r--tasks/release.rb2
-rw-r--r--tools/README.md2
-rw-r--r--tools/test.rb7
989 files changed, 11300 insertions, 7066 deletions
diff --git a/.rubocop.yml b/.rubocop.yml
index 0d1d0c36ce..8a0c55d5d4 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -18,27 +18,27 @@ Style/BracesAroundHashParameters:
Enabled: true
# Align `when` with `case`.
-Style/CaseIndentation:
+Layout/CaseIndentation:
Enabled: true
# Align comments with method definitions.
-Style/CommentIndentation:
+Layout/CommentIndentation:
Enabled: true
# No extra empty lines.
-Style/EmptyLines:
+Layout/EmptyLines:
Enabled: true
# In a regular class definition, no empty lines around the body.
-Style/EmptyLinesAroundClassBody:
+Layout/EmptyLinesAroundClassBody:
Enabled: true
# In a regular method definition, no empty lines around the body.
-Style/EmptyLinesAroundMethodBody:
+Layout/EmptyLinesAroundMethodBody:
Enabled: true
# In a regular module definition, no empty lines around the body.
-Style/EmptyLinesAroundModuleBody:
+Layout/EmptyLinesAroundModuleBody:
Enabled: true
# Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }.
@@ -47,30 +47,30 @@ Style/HashSyntax:
# Method definitions after `private` or `protected` isolated calls need one
# extra level of indentation.
-Style/IndentationConsistency:
+Layout/IndentationConsistency:
Enabled: true
EnforcedStyle: rails
# Two spaces, no tabs (for indentation).
-Style/IndentationWidth:
+Layout/IndentationWidth:
Enabled: true
-Style/SpaceAfterColon:
+Layout/SpaceAfterColon:
Enabled: true
-Style/SpaceAfterComma:
+Layout/SpaceAfterComma:
Enabled: true
-Style/SpaceAroundEqualsInParameterDefault:
+Layout/SpaceAroundEqualsInParameterDefault:
Enabled: true
-Style/SpaceAroundKeyword:
+Layout/SpaceAroundKeyword:
Enabled: true
-Style/SpaceAroundOperators:
+Layout/SpaceAroundOperators:
Enabled: true
-Style/SpaceBeforeFirstArg:
+Layout/SpaceBeforeFirstArg:
Enabled: true
# Defining a method with parameters needs parentheses.
@@ -78,18 +78,18 @@ Style/MethodDefParentheses:
Enabled: true
# Use `foo {}` not `foo{}`.
-Style/SpaceBeforeBlockBraces:
+Layout/SpaceBeforeBlockBraces:
Enabled: true
# Use `foo { bar }` not `foo {bar}`.
-Style/SpaceInsideBlockBraces:
+Layout/SpaceInsideBlockBraces:
Enabled: true
# Use `{ a: 1 }` not `{a:1}`.
-Style/SpaceInsideHashLiteralBraces:
+Layout/SpaceInsideHashLiteralBraces:
Enabled: true
-Style/SpaceInsideParens:
+Layout/SpaceInsideParens:
Enabled: true
# Check quotes usage according to lint rule below.
@@ -98,15 +98,15 @@ Style/StringLiterals:
EnforcedStyle: double_quotes
# Detect hard tabs, no hard tabs.
-Style/Tab:
+Layout/Tab:
Enabled: true
# Blank lines should not have any spaces.
-Style/TrailingBlankLines:
+Layout/TrailingBlankLines:
Enabled: true
# No trailing whitespace.
-Style/TrailingWhitespace:
+Layout/TrailingWhitespace:
Enabled: true
# Use quotes for string literals when they are enough.
diff --git a/.travis.yml b/.travis.yml
index af19cd41de..91ac7e8e5e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -11,13 +11,9 @@ cache:
services:
- memcached
- - redis
addons:
postgresql: "9.4"
- apt:
- packages:
- - postgresql-9.4
bundler_args: --without test --jobs 3 --retry 3
before_install:
@@ -37,6 +33,7 @@ before_script:
# Decodes to e.g. `export VARIABLE=VALUE`
- $(base64 --decode <<< "ZXhwb3J0IFNBVUNFX0FDQ0VTU19LRVk9YTAzNTM0M2YtZTkyMi00MGIzLWFhM2MtMDZiM2VhNjM1YzQ4")
- $(base64 --decode <<< "ZXhwb3J0IFNBVUNFX1VTRVJOQU1FPXJ1YnlvbnJhaWxz")
+ - redis-server --bind 127.0.0.1 --port 6379 --requirepass 'password' --daemonize yes
script: 'ci/travis.rb'
@@ -68,51 +65,46 @@ matrix:
env: "GEM=aj:integration"
services:
- memcached
- - redis
- rabbitmq
- rvm: 2.3.4
env: "GEM=aj:integration"
services:
- memcached
- - redis
- rabbitmq
- rvm: 2.4.1
env: "GEM=aj:integration"
services:
- memcached
- - redis
- rabbitmq
- rvm: ruby-head
env: "GEM=aj:integration"
services:
- memcached
- - redis
- rabbitmq
- rvm: 2.3.4
env:
- "GEM=ar:mysql2 MYSQL=mariadb"
addons:
mariadb: 10.0
- - rvm: 2.4.1
+ - rvm: 2.3.4
env:
- "GEM=ar:sqlite3_mem"
- - rvm: jruby-9.1.8.0
+ - rvm: 2.3.4
+ env:
+ - "GEM=ar:postgresql POSTGRES=9.2"
+ addons:
+ postgresql: "9.2"
+ - rvm: jruby-9.1.12.0
jdk: oraclejdk8
env:
- "GEM=ap"
- - rvm: jruby-9.1.8.0
+ - rvm: jruby-9.1.12.0
jdk: oraclejdk8
env:
- "GEM=am,amo,aj"
- # Test with old (< 9.4.2) postgresql
- - rvm: 2.4.1
- env:
- - "GEM=ar:postgresql"
- addons:
- postgresql: "9.4"
allow_failures:
- rvm: ruby-head
- - rvm: jruby-9.1.8.0
+ - rvm: jruby-9.1.12.0
- env: "GEM=ac:integration"
fast_finish: true
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b44486c75a..097e2f2f49 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -42,7 +42,6 @@ Changes that are cosmetic in nature and do not add anything substantial to the s
* Please read [Contributing to the Rails Documentation](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation).
-</br>
Ruby on Rails is a volunteer effort. We encourage you to pitch in and [join the team](http://contributors.rubyonrails.org)!
Thanks! :heart: :heart: :heart:
diff --git a/Gemfile b/Gemfile
index 5077c257d9..23b9b34b77 100644
--- a/Gemfile
+++ b/Gemfile
@@ -16,7 +16,7 @@ gem "rake", ">= 11.1"
# be loaded after loading the test library.
gem "mocha", "~> 0.14", require: false
-gem "capybara", "~> 2.13.0"
+gem "capybara", "~> 2.13"
gem "rack-cache", "~> 1.2"
gem "jquery-rails"
@@ -33,16 +33,13 @@ gem "bcrypt", "~> 3.1.11", require: false
# sprockets.
gem "uglifier", ">= 1.3.0", require: false
-# FIXME: Remove this fork after https://github.com/nex3/rb-inotify/pull/49 is fixed.
-gem "rb-inotify", github: "matthewd/rb-inotify", branch: "close-handling", require: false
-
# Explicitly avoid 1.x that doesn't support Ruby 2.4+
gem "json", ">= 2.0.0"
gem "rubocop", ">= 0.47", require: false
group :doc do
- gem "sdoc", "1.0.0.rc1"
+ gem "sdoc", "> 1.0.0.rc1", "< 2.0"
gem "redcarpet", "~> 3.2.3", platforms: :ruby
gem "w3c_validators"
gem "kindlerb", "~> 1.2.0"
@@ -90,7 +87,7 @@ group :cable do
end
# Add your own local bundler stuff.
-local_gemfile = File.dirname(__FILE__) + "/.Gemfile"
+local_gemfile = File.expand_path(".Gemfile", __dir__)
instance_eval File.read local_gemfile if File.exist? local_gemfile
group :test do
diff --git a/Gemfile.lock b/Gemfile.lock
index 4feb8e2968..8bb0956de8 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -7,14 +7,6 @@ GIT
pg (>= 0.17, < 0.20)
GIT
- remote: https://github.com/matthewd/rb-inotify.git
- revision: 90553518d1fb79aedc98a3036c59bd2b6731ac40
- branch: close-handling
- specs:
- rb-inotify (0.9.7)
- ffi (>= 0.5.0)
-
-GIT
remote: https://github.com/matthewd/websocket-client-simple.git
revision: e161305f1a466b9398d86df3b1731b03362da91b
branch: close-race
@@ -25,7 +17,7 @@ GIT
GIT
remote: https://github.com/rails/arel.git
- revision: 437aa3a4bb8ad4f3f4eba299dbb1112852f9c7ac
+ revision: 67a51c62f4e19390cd8eb408596ca48bb0806362
specs:
arel (8.0.0)
@@ -78,7 +70,7 @@ PATH
activemodel (= 5.2.0.alpha)
activerecord (= 5.2.0.alpha)
activesupport (= 5.2.0.alpha)
- bundler (>= 1.3.0, < 2.0)
+ bundler (>= 1.3.0)
railties (= 5.2.0.alpha)
sprockets-rails (>= 2.0.0)
railties (5.2.0.alpha)
@@ -91,7 +83,7 @@ PATH
GEM
remote: https://rubygems.org/
specs:
- addressable (2.5.0)
+ addressable (2.5.1)
public_suffix (~> 2.0, >= 2.0.2)
amq-protocol (2.1.0)
ast (2.3.0)
@@ -125,7 +117,7 @@ GEM
bunny (2.6.2)
amq-protocol (>= 2.0.1)
byebug (9.0.6)
- capybara (2.13.0)
+ capybara (2.14.1)
addressable
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
@@ -141,17 +133,17 @@ GEM
coffee-script-source
execjs
coffee-script-source (1.12.2)
- concurrent-ruby (1.0.4)
+ concurrent-ruby (1.0.5)
connection_pool (2.2.1)
cookiejar (0.3.3)
curses (1.0.2)
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 (4.1.3)
+ activesupport (>= 3.0, < 5.2)
+ delayed_job_active_record (4.1.2)
+ activerecord (>= 3.0, < 5.2)
delayed_job (>= 3.0, < 5)
em-hiredis (0.3.1)
eventmachine (~> 1.0)
@@ -187,8 +179,8 @@ GEM
ffi (1.9.17)
ffi (1.9.17-x64-mingw32)
ffi (1.9.17-x86-mingw32)
- globalid (0.3.7)
- activesupport (>= 4.1.0)
+ globalid (0.4.0)
+ activesupport (>= 4.2.0)
hiredis (0.6.1)
http_parser.rb (0.6.0)
i18n (0.8.1)
@@ -207,14 +199,14 @@ GEM
ruby_dep (~> 1.2)
loofah (2.0.3)
nokogiri (>= 1.5.9)
- mail (2.6.4)
+ mail (2.6.5)
mime-types (>= 1.16, < 4)
metaclass (0.0.4)
method_source (0.8.2)
mime-types (3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521)
- mini_portile2 (2.1.0)
+ mini_portile2 (2.2.0)
minitest (5.3.3)
mocha (0.14.0)
metaclass (~> 0.0.1)
@@ -222,16 +214,17 @@ GEM
multi_json (1.12.1)
multipart-post (2.0.0)
mustache (1.0.3)
- mysql2 (0.4.5)
- mysql2 (0.4.5-x64-mingw32)
- mysql2 (0.4.5-x86-mingw32)
+ mysql2 (0.4.6)
+ mysql2 (0.4.6-x64-mingw32)
+ mysql2 (0.4.6-x86-mingw32)
nio4r (2.0.0)
- nokogiri (1.7.0.1)
- mini_portile2 (~> 2.1.0)
- nokogiri (1.7.0.1-x64-mingw32)
- mini_portile2 (~> 2.1.0)
- nokogiri (1.7.0.1-x86-mingw32)
- mini_portile2 (~> 2.1.0)
+ nokogiri (1.8.0)
+ mini_portile2 (~> 2.2.0)
+ nokogiri (1.8.0-x64-mingw32)
+ mini_portile2 (~> 2.2.0)
+ nokogiri (1.8.0-x86-mingw32)
+ mini_portile2 (~> 2.2.0)
+ parallel (1.11.2)
parser (2.4.0.0)
ast (~> 2.2)
pg (0.19.0)
@@ -249,10 +242,10 @@ GEM
simple_uuid
que (0.12.0)
racc (1.4.14)
- rack (2.0.1)
+ rack (2.0.3)
rack-cache (1.6.1)
rack (>= 0.4)
- rack-protection (1.5.3)
+ rack-protection (2.0.0)
rack
rack-test (0.6.3)
rack (>= 1.0)
@@ -261,12 +254,15 @@ GEM
nokogiri (~> 1.6)
rails-html-sanitizer (1.0.3)
loofah (~> 2.0)
- rainbow (2.2.1)
+ rainbow (2.2.2)
+ rake
rake (12.0.0)
rb-fsevent (0.9.8)
- rdoc (5.0.0)
+ rb-inotify (0.9.9)
+ ffi (~> 1.0)
+ rdoc (5.1.0)
redcarpet (3.2.3)
- redis (3.3.2)
+ redis (3.3.3)
redis-namespace (1.5.2)
redis (~> 3.0, >= 3.0.4)
resque (1.27.0)
@@ -280,7 +276,8 @@ GEM
redis (~> 3.3)
resque (~> 1.26)
rufus-scheduler (~> 3.2)
- rubocop (0.47.1)
+ rubocop (0.49.1)
+ parallel (~> 1.10)
parser (>= 2.3.3.1, < 3.0)
powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0)
@@ -298,8 +295,8 @@ GEM
sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
- sdoc (1.0.0.rc1)
- rdoc (= 5.0.0)
+ sdoc (1.0.0.rc2)
+ rdoc (~> 5.0)
selenium-webdriver (3.0.5)
childprocess (~> 0.5)
rubyzip (~> 1.0)
@@ -307,11 +304,11 @@ GEM
sequel (4.42.1)
serverengine (1.5.11)
sigdump (~> 0.2.2)
- sidekiq (4.2.9)
+ sidekiq (5.0.0)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
rack-protection (>= 1.5.0)
- redis (~> 3.2, >= 3.2.1)
+ redis (~> 3.3, >= 3.3.3)
sigdump (0.2.4)
simple_uuid (0.4.0)
sinatra (1.0)
@@ -341,18 +338,18 @@ GEM
rack (>= 1, < 3)
thor (0.19.4)
thread (0.1.7)
- thread_safe (0.3.5)
+ thread_safe (0.3.6)
tilt (2.0.5)
turbolinks (5.0.1)
turbolinks-source (~> 5)
turbolinks-source (5.0.0)
- tzinfo (1.2.2)
+ tzinfo (1.2.3)
thread_safe (~> 0.1)
tzinfo-data (1.2016.10)
tzinfo (>= 1.0.0)
uglifier (3.0.4)
execjs (>= 0.3.0, < 3)
- unicode-display_width (1.1.3)
+ unicode-display_width (1.2.1)
useragent (0.16.8)
vegas (0.1.11)
rack (>= 1.0.0)
@@ -364,7 +361,7 @@ GEM
websocket-driver (0.6.4)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.2)
- xpath (2.0.0)
+ xpath (2.1.0)
nokogiri (~> 1.3)
PLATFORMS
@@ -383,7 +380,7 @@ DEPENDENCIES
blade
blade-sauce_labs_plugin
byebug
- capybara (~> 2.13.0)
+ capybara (~> 2.13)
coffee-rails
dalli (>= 2.2.1)
delayed_job
@@ -410,14 +407,13 @@ DEPENDENCIES
rack-cache (~> 1.2)
rails!
rake (>= 11.1)
- rb-inotify!
redcarpet (~> 3.2.3)
redis
resque
resque-scheduler
rubocop (>= 0.47)
sass-rails
- sdoc (= 1.0.0.rc1)
+ sdoc (> 1.0.0.rc1, < 2.0)
sequel
sidekiq
sneakers
@@ -433,4 +429,4 @@ DEPENDENCIES
websocket-client-simple!
BUNDLED WITH
- 1.14.6
+ 1.15.1
diff --git a/Rakefile b/Rakefile
index 202eb5e6fc..ae269fbce7 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,6 +1,6 @@
require "net/http"
-$:.unshift File.expand_path("..", __FILE__)
+$:.unshift __dir__
require "tasks/release"
require "railties/lib/rails/api/task"
diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md
index d5bd58cfdb..b1408496a0 100644
--- a/actioncable/CHANGELOG.md
+++ b/actioncable/CHANGELOG.md
@@ -1 +1,17 @@
+* ActionCable's `redis` adapter allows for other common redis-rb options (`host`, `port`, `db`, `password`) in cable.yml.
+
+ Previously, it accepts only a [redis:// url](https://www.iana.org/assignments/uri-schemes/prov/redis) as an option.
+ While we can add all of these options to the `url` itself, it is not explicitly documented. This alternative setup
+ is shown as the first example in the [Redis rubygem](https://github.com/redis/redis-rb#getting-started), which
+ makes this set of options as sensible as using just the `url`.
+
+ *Marc Rendl Ignacio*
+
+* ActionCable socket errors are now logged to the console
+
+ Previously any socket errors were ignored and this made it hard to diagnose socket issues (e.g. as discussed in #28362).
+
+ *Edward Poot*
+
+
Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/actioncable/CHANGELOG.md) for previous changes.
diff --git a/actioncable/README.md b/actioncable/README.md
index e044f54b45..6946dbefb0 100644
--- a/actioncable/README.md
+++ b/actioncable/README.md
@@ -409,7 +409,7 @@ application. The recommended basic setup is as follows:
```ruby
# cable/config.ru
-require ::File.expand_path('../../config/environment', __FILE__)
+require_relative '../config/environment'
Rails.application.eager_load!
run ActionCable.server
diff --git a/actioncable/Rakefile b/actioncable/Rakefile
index bda8c7b6c8..e21843bb44 100644
--- a/actioncable/Rakefile
+++ b/actioncable/Rakefile
@@ -3,15 +3,13 @@ require "pathname"
require "open3"
require "action_cable"
-dir = File.dirname(__FILE__)
-
task default: :test
task package: %w( assets:compile assets:verify )
Rake::TestTask.new do |t|
t.libs << "test"
- t.test_files = Dir.glob("#{dir}/test/**/*_test.rb")
+ t.test_files = Dir.glob("#{__dir__}/test/**/*_test.rb")
t.warning = true
t.verbose = true
t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
@@ -46,7 +44,7 @@ namespace :assets do
desc "Verify compiled Action Cable assets"
task :verify do
file = "lib/assets/compiled/action_cable.js"
- pathname = Pathname.new("#{dir}/#{file}")
+ pathname = Pathname.new("#{__dir__}/#{file}")
print "[verify] #{file} exists "
if pathname.exist?
@@ -64,8 +62,8 @@ namespace :assets do
fail
end
- print "[verify] #{dir} can be required as a module "
- stdout, stderr, status = Open3.capture3("node", "--print", "window = {}; require('#{dir}');")
+ print "[verify] #{__dir__} can be required as a module "
+ _, stderr, status = Open3.capture3("node", "--print", "window = {}; require('#{__dir__}');")
if status.success?
puts "[OK]"
else
diff --git a/actioncable/actioncable.gemspec b/actioncable/actioncable.gemspec
index 6d95f022fa..a72c0d2608 100644
--- a/actioncable/actioncable.gemspec
+++ b/actioncable/actioncable.gemspec
@@ -1,4 +1,4 @@
-version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
+version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
@@ -18,6 +18,11 @@ Gem::Specification.new do |s|
s.files = Dir["CHANGELOG.md", "MIT-LICENSE", "README.md", "lib/**/*"]
s.require_path = "lib"
+ s.metadata = {
+ "source_code_uri" => "https://github.com/rails/rails/tree/v#{version}/actioncable",
+ "changelog_uri" => "https://github.com/rails/rails/blob/v#{version}/actioncable/CHANGELOG.md"
+ }
+
s.add_dependency "actionpack", version
s.add_dependency "nio4r", "~> 2.0"
diff --git a/actioncable/bin/test b/actioncable/bin/test
index a7beb14b27..470ce93f10 100755
--- a/actioncable/bin/test
+++ b/actioncable/bin/test
@@ -1,4 +1,4 @@
#!/usr/bin/env ruby
COMPONENT_ROOT = File.expand_path("..", __dir__)
-require File.expand_path("../tools/test", COMPONENT_ROOT)
+require_relative "../../tools/test"
diff --git a/actioncable/lib/action_cable.rb b/actioncable/lib/action_cable.rb
index c2d3550acb..9edd82e7b5 100644
--- a/actioncable/lib/action_cable.rb
+++ b/actioncable/lib/action_cable.rb
@@ -23,7 +23,7 @@
require "active_support"
require "active_support/rails"
-require "action_cable/version"
+require_relative "action_cable/version"
module ActionCable
extend ActiveSupport::Autoload
diff --git a/actioncable/lib/action_cable/channel/periodic_timers.rb b/actioncable/lib/action_cable/channel/periodic_timers.rb
index c9daa0bcd3..90c68cfe84 100644
--- a/actioncable/lib/action_cable/channel/periodic_timers.rb
+++ b/actioncable/lib/action_cable/channel/periodic_timers.rb
@@ -4,8 +4,7 @@ module ActionCable
extend ActiveSupport::Concern
included do
- class_attribute :periodic_timers, instance_reader: false
- self.periodic_timers = []
+ class_attribute :periodic_timers, instance_reader: false, default: []
after_subscribe :start_periodic_timers
after_unsubscribe :stop_periodic_timers
diff --git a/actioncable/lib/action_cable/connection/base.rb b/actioncable/lib/action_cable/connection/base.rb
index 0a517a532d..ac5f405dea 100644
--- a/actioncable/lib/action_cable/connection/base.rb
+++ b/actioncable/lib/action_cable/connection/base.rb
@@ -126,7 +126,8 @@ module ActionCable
end
def on_error(message) # :nodoc:
- # ignore
+ # log errors to make diagnosing socket errors easier
+ logger.error "WebSocket error occurred: #{message}"
end
def on_close(reason, code) # :nodoc:
diff --git a/actioncable/lib/action_cable/connection/identification.rb b/actioncable/lib/action_cable/connection/identification.rb
index c91a1d1fd7..ffab359429 100644
--- a/actioncable/lib/action_cable/connection/identification.rb
+++ b/actioncable/lib/action_cable/connection/identification.rb
@@ -6,8 +6,7 @@ module ActionCable
extend ActiveSupport::Concern
included do
- class_attribute :identifiers
- self.identifiers = Set.new
+ class_attribute :identifiers, default: Set.new
end
class_methods do
diff --git a/actioncable/lib/action_cable/connection/web_socket.rb b/actioncable/lib/action_cable/connection/web_socket.rb
index 03eb6e2ea8..27ae499f29 100644
--- a/actioncable/lib/action_cable/connection/web_socket.rb
+++ b/actioncable/lib/action_cable/connection/web_socket.rb
@@ -3,7 +3,7 @@ require "websocket/driver"
module ActionCable
module Connection
# Wrap the real socket to minimize the externally-presented API
- class WebSocket
+ class WebSocket # :nodoc:
def initialize(env, event_target, event_loop, protocols: ActionCable::INTERNAL[:protocols])
@websocket = ::WebSocket::Driver.websocket?(env) ? ClientSocket.new(env, event_target, event_loop, protocols) : nil
end
diff --git a/actioncable/lib/action_cable/engine.rb b/actioncable/lib/action_cable/engine.rb
index 63a26636a0..d8c49ec6e1 100644
--- a/actioncable/lib/action_cable/engine.rb
+++ b/actioncable/lib/action_cable/engine.rb
@@ -1,6 +1,6 @@
require "rails"
require "action_cable"
-require "action_cable/helpers/action_cable_helper"
+require_relative "helpers/action_cable_helper"
require "active_support/core_ext/hash/indifferent_access"
module ActionCable
diff --git a/actioncable/lib/action_cable/remote_connections.rb b/actioncable/lib/action_cable/remote_connections.rb
index d2856bc6ae..e689fbf21b 100644
--- a/actioncable/lib/action_cable/remote_connections.rb
+++ b/actioncable/lib/action_cable/remote_connections.rb
@@ -45,7 +45,7 @@ module ActionCable
end
# Returns all the identifiers that were applied to this connection.
- def identifiers
+ redefine_method :identifiers do
server.connection_identifiers
end
diff --git a/actioncable/lib/action_cable/server/base.rb b/actioncable/lib/action_cable/server/base.rb
index 419eccd73c..3b3a17a532 100644
--- a/actioncable/lib/action_cable/server/base.rb
+++ b/actioncable/lib/action_cable/server/base.rb
@@ -10,7 +10,7 @@ module ActionCable
include ActionCable::Server::Broadcasting
include ActionCable::Server::Connections
- cattr_accessor(:config, instance_accessor: true) { ActionCable::Server::Configuration.new }
+ cattr_accessor :config, instance_accessor: true, default: ActionCable::Server::Configuration.new
def self.logger; config.logger; end
delegate :logger, to: :config
diff --git a/actioncable/lib/action_cable/subscription_adapter/async.rb b/actioncable/lib/action_cable/subscription_adapter/async.rb
index 46819dbfec..9169734471 100644
--- a/actioncable/lib/action_cable/subscription_adapter/async.rb
+++ b/actioncable/lib/action_cable/subscription_adapter/async.rb
@@ -1,4 +1,4 @@
-require "action_cable/subscription_adapter/inline"
+require_relative "inline"
module ActionCable
module SubscriptionAdapter
diff --git a/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb b/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb
index ed8f315791..ae71708240 100644
--- a/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb
+++ b/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb
@@ -17,11 +17,11 @@ module ActionCable
# Overwrite this factory method for EventMachine Redis connections if you want to use a different Redis connection library than EM::Hiredis.
# This is needed, for example, when using Makara proxies for distributed Redis.
- cattr_accessor(:em_redis_connector) { ->(config) { EM::Hiredis.connect(config[:url]) } }
+ cattr_accessor :em_redis_connector, default: ->(config) { EM::Hiredis.connect(config[:url]) }
# Overwrite this factory method for Redis connections if you want to use a different Redis connection library than Redis.
# This is needed, for example, when using Makara proxies for distributed Redis.
- cattr_accessor(:redis_connector) { ->(config) { ::Redis.new(url: config[:url]) } }
+ cattr_accessor :redis_connector, default: ->(config) { ::Redis.new(url: config[:url]) }
def initialize(*)
ActiveSupport::Deprecation.warn(<<-MSG.squish)
diff --git a/actioncable/lib/action_cable/subscription_adapter/redis.rb b/actioncable/lib/action_cable/subscription_adapter/redis.rb
index 41a6e55822..225609c236 100644
--- a/actioncable/lib/action_cable/subscription_adapter/redis.rb
+++ b/actioncable/lib/action_cable/subscription_adapter/redis.rb
@@ -10,7 +10,9 @@ module ActionCable
# 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]) } }
+ cattr_accessor :redis_connector, default: ->(config) do
+ ::Redis.new(config.slice(:url, :host, :port, :db, :password))
+ end
def initialize(*)
super
diff --git a/actioncable/lib/rails/generators/channel/channel_generator.rb b/actioncable/lib/rails/generators/channel/channel_generator.rb
index 984b78bc9c..80f512c94c 100644
--- a/actioncable/lib/rails/generators/channel/channel_generator.rb
+++ b/actioncable/lib/rails/generators/channel/channel_generator.rb
@@ -1,7 +1,7 @@
module Rails
module Generators
class ChannelGenerator < NamedBase
- source_root File.expand_path("../templates", __FILE__)
+ source_root File.expand_path("templates", __dir__)
argument :actions, type: :array, default: [], banner: "method method"
diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb
index 4f7c585ba3..30ac1e9c38 100644
--- a/actioncable/test/client_test.rb
+++ b/actioncable/test/client_test.rb
@@ -78,10 +78,20 @@ class ClientTest < ActionCable::TestCase
begin
thread.join
+
+ rescue IOError
+ # Work around https://bugs.ruby-lang.org/issues/13405
+ #
+ # Puma's sometimes raising while shutting down, when it closes
+ # its internal pipe. We can safely ignore that, but we do need
+ # to do the step skipped by the exception:
+ server.binder.close
+
rescue RuntimeError => ex
+ # Work around https://bugs.ruby-lang.org/issues/13239
raise unless ex.message =~ /can't modify frozen IOError/
- # Work around https://bugs.ruby-lang.org/issues/13239
+ # Handle this as if it were the IOError: do the same as above.
server.binder.close
end
end
diff --git a/actioncable/test/subscription_adapter/async_test.rb b/actioncable/test/subscription_adapter/async_test.rb
index 7bc2e55d40..8a447c7a4f 100644
--- a/actioncable/test/subscription_adapter/async_test.rb
+++ b/actioncable/test/subscription_adapter/async_test.rb
@@ -1,5 +1,5 @@
require "test_helper"
-require_relative "./common"
+require_relative "common"
class AsyncAdapterTest < ActionCable::TestCase
include CommonSubscriptionAdapterTest
diff --git a/actioncable/test/subscription_adapter/base_test.rb b/actioncable/test/subscription_adapter/base_test.rb
index 212ea49d2f..5793415b5f 100644
--- a/actioncable/test/subscription_adapter/base_test.rb
+++ b/actioncable/test/subscription_adapter/base_test.rb
@@ -39,35 +39,25 @@ class ActionCable::SubscriptionAdapter::BaseTest < ActionCable::TestCase
# TEST METHODS THAT ARE REQUIRED OF THE ADAPTER'S BACKEND STORAGE OBJECT
test "#broadcast is implemented" do
- broadcast = SuccessAdapter.new(@server).broadcast("channel", "payload")
-
- assert_respond_to(SuccessAdapter.new(@server), :broadcast)
-
assert_nothing_raised do
- broadcast
+ SuccessAdapter.new(@server).broadcast("channel", "payload")
end
end
test "#subscribe is implemented" do
callback = lambda { puts "callback" }
success_callback = lambda { puts "success" }
- subscribe = SuccessAdapter.new(@server).subscribe("channel", callback, success_callback)
-
- assert_respond_to(SuccessAdapter.new(@server), :subscribe)
assert_nothing_raised do
- subscribe
+ SuccessAdapter.new(@server).subscribe("channel", callback, success_callback)
end
end
test "#unsubscribe is implemented" do
callback = lambda { puts "callback" }
- unsubscribe = SuccessAdapter.new(@server).unsubscribe("channel", callback)
-
- assert_respond_to(SuccessAdapter.new(@server), :unsubscribe)
assert_nothing_raised do
- unsubscribe
+ SuccessAdapter.new(@server).unsubscribe("channel", callback)
end
end
end
diff --git a/actioncable/test/subscription_adapter/evented_redis_test.rb b/actioncable/test/subscription_adapter/evented_redis_test.rb
index 256458bc24..1c99031ab0 100644
--- a/actioncable/test/subscription_adapter/evented_redis_test.rb
+++ b/actioncable/test/subscription_adapter/evented_redis_test.rb
@@ -1,6 +1,6 @@
require "test_helper"
-require_relative "./common"
-require_relative "./channel_prefix"
+require_relative "common"
+require_relative "channel_prefix"
class EventedRedisAdapterTest < ActionCable::TestCase
include CommonSubscriptionAdapterTest
@@ -54,6 +54,6 @@ class EventedRedisAdapterTest < ActionCable::TestCase
end
def cable_config
- { adapter: "evented_redis", url: "redis://127.0.0.1:6379/12" }
+ { adapter: "evented_redis", url: "redis://:password@127.0.0.1:6379/12" }
end
end
diff --git a/actioncable/test/subscription_adapter/inline_test.rb b/actioncable/test/subscription_adapter/inline_test.rb
index 52bfa00aba..eafa3df2df 100644
--- a/actioncable/test/subscription_adapter/inline_test.rb
+++ b/actioncable/test/subscription_adapter/inline_test.rb
@@ -1,5 +1,5 @@
require "test_helper"
-require_relative "./common"
+require_relative "common"
class InlineAdapterTest < ActionCable::TestCase
include CommonSubscriptionAdapterTest
diff --git a/actioncable/test/subscription_adapter/postgresql_test.rb b/actioncable/test/subscription_adapter/postgresql_test.rb
index beb6efab28..ada4c2e2bd 100644
--- a/actioncable/test/subscription_adapter/postgresql_test.rb
+++ b/actioncable/test/subscription_adapter/postgresql_test.rb
@@ -1,5 +1,5 @@
require "test_helper"
-require_relative "./common"
+require_relative "common"
require "active_record"
diff --git a/actioncable/test/subscription_adapter/redis_test.rb b/actioncable/test/subscription_adapter/redis_test.rb
index 4df5e0cbcd..60596dd205 100644
--- a/actioncable/test/subscription_adapter/redis_test.rb
+++ b/actioncable/test/subscription_adapter/redis_test.rb
@@ -1,13 +1,13 @@
require "test_helper"
-require_relative "./common"
-require_relative "./channel_prefix"
+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" }
+ { adapter: "redis", driver: "ruby", url: "redis://:password@127.0.0.1:6379/12" }
end
end
@@ -16,3 +16,11 @@ class RedisAdapterTest::Hiredis < RedisAdapterTest
super.merge(driver: "hiredis")
end
end
+
+class RedisAdapterTest::AlternateConfiguration < RedisAdapterTest
+ def cable_config
+ alt_cable_config = super.dup
+ alt_cable_config.delete(:url)
+ alt_cable_config.merge(host: "127.0.0.1", port: 6379, db: 12, password: "password")
+ end
+end
diff --git a/actioncable/test/test_helper.rb b/actioncable/test/test_helper.rb
index a47032753b..5d246c2b76 100644
--- a/actioncable/test/test_helper.rb
+++ b/actioncable/test/test_helper.rb
@@ -11,7 +11,7 @@ rescue LoadError
end
# Require all the stubs and models
-Dir[File.dirname(__FILE__) + "/stubs/*.rb"].each { |file| require file }
+Dir[File.expand_path("stubs/*.rb", __dir__)].each { |file| require file }
class ActionCable::TestCase < ActiveSupport::TestCase
def wait_for_async
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index e488d867de..9993a11c9d 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1 +1,12 @@
+* Allow Action Mailer classes to configure their delivery job.
+
+ class MyMailer < ApplicationMailer
+ self.delivery_job = MyCustomDeliveryJob
+
+ ...
+ end
+
+ *Matthew Mongeau*
+
+
Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/actionmailer/CHANGELOG.md) for previous changes.
diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec
index e75dae6cf9..ae908ddda7 100644
--- a/actionmailer/actionmailer.gemspec
+++ b/actionmailer/actionmailer.gemspec
@@ -1,4 +1,4 @@
-version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
+version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
@@ -19,6 +19,11 @@ Gem::Specification.new do |s|
s.require_path = "lib"
s.requirements << "none"
+ s.metadata = {
+ "source_code_uri" => "https://github.com/rails/rails/tree/v#{version}/actionmailer",
+ "changelog_uri" => "https://github.com/rails/rails/blob/v#{version}/actionmailer/CHANGELOG.md"
+ }
+
s.add_dependency "actionpack", version
s.add_dependency "actionview", version
s.add_dependency "activejob", version
diff --git a/actionmailer/bin/test b/actionmailer/bin/test
index a7beb14b27..470ce93f10 100755
--- a/actionmailer/bin/test
+++ b/actionmailer/bin/test
@@ -1,4 +1,4 @@
#!/usr/bin/env ruby
COMPONENT_ROOT = File.expand_path("..", __dir__)
-require File.expand_path("../tools/test", COMPONENT_ROOT)
+require_relative "../../tools/test"
diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb
index 211190560a..8eba811ab6 100644
--- a/actionmailer/lib/action_mailer.rb
+++ b/actionmailer/lib/action_mailer.rb
@@ -22,9 +22,10 @@
#++
require "abstract_controller"
-require "action_mailer/version"
+require_relative "action_mailer/version"
# Common Active Support usage in Action Mailer
+require "active_support"
require "active_support/rails"
require "active_support/core_ext/class"
require "active_support/core_ext/module/attr_internal"
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 6849f5c0f9..2b4992791d 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -1,11 +1,11 @@
require "mail"
-require "action_mailer/collector"
+require_relative "collector"
require "active_support/core_ext/string/inflections"
require "active_support/core_ext/hash/except"
require "active_support/core_ext/module/anonymous"
-require "action_mailer/log_subscriber"
-require "action_mailer/rescuable"
+require_relative "log_subscriber"
+require_relative "rescuable"
module ActionMailer
# Action Mailer allows you to send email from your application using a mailer model and views.
@@ -459,8 +459,8 @@ module ActionMailer
helper ActionMailer::MailHelper
- class_attribute :default_params
- self.default_params = {
+ class_attribute :delivery_job, default: ::ActionMailer::DeliveryJob
+ class_attribute :default_params, default: {
mime_version: "1.0",
charset: "UTF-8",
content_type: "text/plain",
diff --git a/actionmailer/lib/action_mailer/delivery_methods.rb b/actionmailer/lib/action_mailer/delivery_methods.rb
index bcc4ef03cf..93ae10fd70 100644
--- a/actionmailer/lib/action_mailer/delivery_methods.rb
+++ b/actionmailer/lib/action_mailer/delivery_methods.rb
@@ -7,20 +7,13 @@ module ActionMailer
extend ActiveSupport::Concern
included do
- class_attribute :delivery_methods, :delivery_method
-
# Do not make this inheritable, because we always want it to propagate
- cattr_accessor :raise_delivery_errors
- self.raise_delivery_errors = true
-
- cattr_accessor :perform_deliveries
- self.perform_deliveries = true
-
- cattr_accessor :deliver_later_queue_name
- self.deliver_later_queue_name = :mailers
+ cattr_accessor :raise_delivery_errors, default: true
+ cattr_accessor :perform_deliveries, default: true
+ cattr_accessor :deliver_later_queue_name, default: :mailers
- self.delivery_methods = {}.freeze
- self.delivery_method = :smtp
+ class_attribute :delivery_methods, default: {}.freeze
+ class_attribute :delivery_method, default: :smtp
add_delivery_method :smtp, Mail::SMTP,
address: "localhost",
diff --git a/actionmailer/lib/action_mailer/message_delivery.rb b/actionmailer/lib/action_mailer/message_delivery.rb
index cf7c57e6bf..0b54e12431 100644
--- a/actionmailer/lib/action_mailer/message_delivery.rb
+++ b/actionmailer/lib/action_mailer/message_delivery.rb
@@ -51,6 +51,14 @@ module ActionMailer
# Notifier.welcome(User.first).deliver_later!(wait: 1.hour)
# Notifier.welcome(User.first).deliver_later!(wait_until: 10.hours.from_now)
#
+ # By default, the email will be enqueued using <tt>ActionMailer::DeliveryJob</tt>. Each
+ # <tt>ActionMailer::Base</tt> class can specify the job to use by setting the class variable
+ # +delivery_job+.
+ #
+ # class AccountRegistrationMailer < ApplicationMailer
+ # self.delivery_job = RegistrationDeliveryJob
+ # end
+ #
# Options:
#
# * <tt>:wait</tt> - Enqueue the email to be delivered with a delay
@@ -67,6 +75,14 @@ module ActionMailer
# Notifier.welcome(User.first).deliver_later(wait: 1.hour)
# Notifier.welcome(User.first).deliver_later(wait_until: 10.hours.from_now)
#
+ # By default, the email will be enqueued using <tt>ActionMailer::DeliveryJob</tt>. Each
+ # <tt>ActionMailer::Base</tt> class can specify the job to use by setting the class variable
+ # +delivery_job+.
+ #
+ # class AccountRegistrationMailer < ApplicationMailer
+ # self.delivery_job = RegistrationDeliveryJob
+ # end
+ #
# Options:
#
# * <tt>:wait</tt> - Enqueue the email to be delivered with a delay.
@@ -118,7 +134,8 @@ module ActionMailer
"method*, or 3. use a custom Active Job instead of #deliver_later."
else
args = @mailer_class.name, @action.to_s, delivery_method.to_s, *@args
- ::ActionMailer::DeliveryJob.set(options).perform_later(*args)
+ job = @mailer_class.delivery_job
+ job.set(options).perform_later(*args)
end
end
end
diff --git a/actionmailer/lib/action_mailer/preview.rb b/actionmailer/lib/action_mailer/preview.rb
index b0152aff03..4f72eca930 100644
--- a/actionmailer/lib/action_mailer/preview.rb
+++ b/actionmailer/lib/action_mailer/preview.rb
@@ -20,8 +20,7 @@ module ActionMailer
mattr_accessor :show_previews, instance_writer: false
# :nodoc:
- mattr_accessor :preview_interceptors, instance_writer: false
- self.preview_interceptors = [ActionMailer::InlinePreviewInterceptor]
+ mattr_accessor :preview_interceptors, instance_writer: false, default: [ActionMailer::InlinePreviewInterceptor]
end
module ClassMethods
@@ -52,6 +51,12 @@ module ActionMailer
class Preview
extend ActiveSupport::DescendantsTracker
+ attr_reader :params
+
+ def initialize(params = {})
+ @params = params
+ end
+
class << self
# Returns all mailer preview classes.
def all
@@ -62,8 +67,8 @@ module ActionMailer
# Returns the mail object for the given email name. The registered preview
# 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 = new
+ def call(email, params = {})
+ preview = new(params)
message = preview.public_send(email)
inform_preview_interceptors(message)
message
diff --git a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb
index 99fe4544f1..bc21b07109 100644
--- a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb
+++ b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb
@@ -1,7 +1,7 @@
module Rails
module Generators
class MailerGenerator < NamedBase
- source_root File.expand_path("../templates", __FILE__)
+ source_root File.expand_path("templates", __dir__)
argument :actions, type: :array, default: [], banner: "method method"
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index a646cbd581..dbfdb07e6e 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -9,7 +9,7 @@ end
module Rails
def self.root
- File.expand_path("../", File.dirname(__FILE__))
+ File.expand_path("..", __dir__)
end
end
@@ -28,7 +28,7 @@ ActiveSupport::Deprecation.debug = true
# Disable available locale checks to avoid warnings running the test suite.
I18n.enforce_available_locales = false
-FIXTURE_LOAD_PATH = File.expand_path("fixtures", File.dirname(__FILE__))
+FIXTURE_LOAD_PATH = File.expand_path("fixtures", __dir__)
ActionMailer::Base.view_paths = FIXTURE_LOAD_PATH
class ActiveSupport::TestCase
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index 61960d411d..06d4ea197c 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -120,7 +120,7 @@ class BaseTest < ActiveSupport::TestCase
email = BaseMailer.attachment_with_hash
assert_equal(1, email.attachments.length)
assert_equal("invoice.jpg", email.attachments[0].filename)
- expected = "\312\213\254\232)b"
+ expected = "\312\213\254\232)b".dup
expected.force_encoding(Encoding::BINARY)
assert_equal expected, email.attachments["invoice.jpg"].decoded
end
@@ -129,7 +129,7 @@ class BaseTest < ActiveSupport::TestCase
email = BaseMailer.attachment_with_hash_default_encoding
assert_equal(1, email.attachments.length)
assert_equal("invoice.jpg", email.attachments[0].filename)
- expected = "\312\213\254\232)b"
+ expected = "\312\213\254\232)b".dup
expected.force_encoding(Encoding::BINARY)
assert_equal expected, email.attachments["invoice.jpg"].decoded
end
@@ -968,3 +968,19 @@ class BasePreviewInterceptorsTest < ActiveSupport::TestCase
end
end
end
+
+class BasePreviewTest < ActiveSupport::TestCase
+ class BaseMailerPreview < ActionMailer::Preview
+ def welcome
+ BaseMailer.welcome(params)
+ end
+ end
+
+ test "has access to params" do
+ params = { name: "World" }
+
+ assert_called_with(BaseMailer, :welcome, [params]) do
+ BaseMailerPreview.call(:welcome, params)
+ end
+ end
+end
diff --git a/actionmailer/test/caching_test.rb b/actionmailer/test/caching_test.rb
index cff49c8894..e76466439e 100644
--- a/actionmailer/test/caching_test.rb
+++ b/actionmailer/test/caching_test.rb
@@ -5,7 +5,7 @@ require "mailers/caching_mailer"
CACHE_DIR = "test_cache"
# Don't change '/../temp/' cavalierly or you might hose something you don't want hosed
-FILE_STORE_PATH = File.join(File.dirname(__FILE__), "/../temp/", CACHE_DIR)
+FILE_STORE_PATH = File.join(__dir__, "/../temp/", CACHE_DIR)
class FragmentCachingMailer < ActionMailer::Base
abstract!
@@ -21,10 +21,6 @@ class BaseCachingTest < ActiveSupport::TestCase
@mailer.perform_caching = true
@mailer.cache_store = @store
end
-
- def test_fragment_cache_key
- assert_equal "views/what a key", @mailer.fragment_cache_key("what a key")
- end
end
class FragmentCachingTest < BaseCachingTest
@@ -126,7 +122,7 @@ class FunctionalFragmentCachingTest < BaseCachingTest
assert_match expected_body, email.body.encoded
assert_match expected_body,
- @store.read("views/caching/#{template_digest("caching_mailer/fragment_cache")}")
+ @store.read("views/caching_mailer/fragment_cache:#{template_digest("caching_mailer/fragment_cache")}/caching")
end
def test_fragment_caching_in_partials
@@ -135,7 +131,7 @@ class FunctionalFragmentCachingTest < BaseCachingTest
assert_match(expected_body, email.body.encoded)
assert_match(expected_body,
- @store.read("views/caching/#{template_digest("caching_mailer/_partial")}"))
+ @store.read("views/caching_mailer/_partial:#{template_digest("caching_mailer/_partial")}/caching"))
end
def test_skip_fragment_cache_digesting
@@ -185,7 +181,7 @@ class FunctionalFragmentCachingTest < BaseCachingTest
end
assert_equal "caching_mailer", payload[:mailer]
- assert_equal "views/caching/#{template_digest("caching_mailer/fragment_cache")}", payload[:key]
+ assert_equal [ :views, "caching_mailer/fragment_cache:#{template_digest("caching_mailer/fragment_cache")}", :caching ], payload[:key]
ensure
@mailer.enable_fragment_cache_logging = true
end
diff --git a/actionmailer/test/log_subscriber_test.rb b/actionmailer/test/log_subscriber_test.rb
index d864c3acca..799c6144d7 100644
--- a/actionmailer/test/log_subscriber_test.rb
+++ b/actionmailer/test/log_subscriber_test.rb
@@ -26,7 +26,7 @@ class AMLogSubscriberTest < ActionMailer::TestCase
wait
assert_equal(1, @logger.logged(:info).size)
- assert_match(/Sent mail to system@test.lindsaar.net/, @logger.logged(:info).first)
+ assert_match(/Sent mail to system@test\.lindsaar\.net/, @logger.logged(:info).first)
assert_equal(2, @logger.logged(:debug).size)
assert_match(/BaseMailer#welcome: processed outbound mail in [\d.]+ms/, @logger.logged(:debug).first)
@@ -36,7 +36,7 @@ class AMLogSubscriberTest < ActionMailer::TestCase
end
def test_receive_is_notified
- fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email")
+ fixture = File.read(File.expand_path("fixtures/raw_email", __dir__))
TestMailer.receive(fixture)
wait
assert_equal(1, @logger.logged(:info).size)
diff --git a/actionmailer/test/message_delivery_test.rb b/actionmailer/test/message_delivery_test.rb
index c0683be94d..51f10b0bf1 100644
--- a/actionmailer/test/message_delivery_test.rb
+++ b/actionmailer/test/message_delivery_test.rb
@@ -95,6 +95,19 @@ class MessageDeliveryTest < ActiveSupport::TestCase
end
end
+ test "should enqueue the job with the correct delivery job" do
+ old_delivery_job = DelayedMailer.delivery_job
+ DelayedMailer.delivery_job = DummyJob
+
+ assert_performed_with(job: DummyJob, args: ["DelayedMailer", "test_message", "deliver_now", 1, 2, 3]) do
+ @mail.deliver_later
+ end
+
+ DelayedMailer.delivery_job = old_delivery_job
+ end
+
+ class DummyJob < ActionMailer::DeliveryJob; end
+
test "can override the queue when enqueuing mail" do
assert_performed_with(job: ActionMailer::DeliveryJob, args: ["DelayedMailer", "test_message", "deliver_now", 1, 2, 3], queue: "another_queue") do
@mail.deliver_later(queue: :another_queue)
diff --git a/actionmailer/test/parameterized_test.rb b/actionmailer/test/parameterized_test.rb
index 914ed12312..e988fffcb9 100644
--- a/actionmailer/test/parameterized_test.rb
+++ b/actionmailer/test/parameterized_test.rb
@@ -14,7 +14,6 @@ class ParameterizedTest < ActiveSupport::TestCase
@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
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index c5b679207a..f8fd2403ef 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1 +1,51 @@
+* Fallback `ActionController::Parameters#to_s` to `Hash#to_s`.
+
+ *Kir Shatrov*
+
+* `driven_by` now registers poltergeist and capybara-webkit
+
+ If driver poltergeist or capybara-webkit is set for System Tests,
+ `driven_by` will register the driver and set additional options passed via
+ `:options` param.
+
+ Refer to drivers documentation to learn what options can be passed.
+
+ *Mario Chavez*
+
+* AEAD encrypted cookies and sessions with GCM
+
+ Encrypted cookies now use AES-GCM which couples authentication and
+ encryption in one faster step and produces shorter ciphertexts. Cookies
+ encrypted using AES in CBC HMAC mode will be seamlessly upgraded when
+ this new mode is enabled via the
+ `action_dispatch.use_authenticated_cookie_encryption` configuration value.
+
+ *Michael J Coyne*
+
+* Change the cache key format for fragments to make it easier to debug key churn. The new format is:
+
+ views/template/action.html.erb:7a1156131a6928cb0026877f8b749ac9/projects/123
+ ^template path ^template tree digest ^class ^id
+
+ *DHH*
+
+* Add support for recyclable cache keys with fragment caching. This uses the new versioned entries in the
+ `ActiveSupport::Cache` stores and relies on the fact that Active Record has split `#cache_key` and `#cache_version`
+ to support it.
+
+ *DHH*
+
+* Add `action_controller_api` and `action_controller_base` load hooks to be called in `ActiveSupport.on_load`
+
+ `ActionController::Base` and `ActionController::API` have differing implementations. This means that
+ the one umbrella hook `action_controller` is not able to address certain situations where a method
+ may not exist in a certain implementation.
+
+ This is fixed by adding two new hooks so you can target `ActionController::Base` vs `ActionController::API`
+
+ Fixes #27013.
+
+ *Julian Nadeau*
+
+
Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/actionpack/CHANGELOG.md) for previous changes.
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index 31dd1865f9..69408c8aab 100644
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -26,7 +26,7 @@ namespace :test do
end
task :lines do
- load File.expand_path("..", File.dirname(__FILE__)) + "/tools/line_statistics"
+ load File.expand_path("..", __dir__) + "/tools/line_statistics"
files = FileList["lib/**/*.rb"]
CodeTools::LineStatistics.new(files).print_loc
end
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 2c24a54305..294cc45593 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -1,4 +1,4 @@
-version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
+version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
@@ -19,6 +19,11 @@ Gem::Specification.new do |s|
s.require_path = "lib"
s.requirements << "none"
+ s.metadata = {
+ "source_code_uri" => "https://github.com/rails/rails/tree/v#{version}/actionpack",
+ "changelog_uri" => "https://github.com/rails/rails/blob/v#{version}/actionpack/CHANGELOG.md"
+ }
+
s.add_dependency "activesupport", version
s.add_dependency "rack", "~> 2.0"
diff --git a/actionpack/bin/test b/actionpack/bin/test
index a7beb14b27..470ce93f10 100755
--- a/actionpack/bin/test
+++ b/actionpack/bin/test
@@ -1,4 +1,4 @@
#!/usr/bin/env ruby
COMPONENT_ROOT = File.expand_path("..", __dir__)
-require File.expand_path("../tools/test", COMPONENT_ROOT)
+require_relative "../../tools/test"
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index e7cb6347a2..bca850c0c0 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -1,4 +1,4 @@
-require "abstract_controller/error"
+require_relative "error"
require "active_support/configurable"
require "active_support/descendants_tracker"
require "active_support/core_ext/module/anonymous"
@@ -14,8 +14,16 @@ module AbstractController
# expected to provide their own +render+ method, since rendering means
# different things depending on the context.
class Base
+ ##
+ # Returns the body of the HTTP response sent by the controller.
attr_internal :response_body
+
+ ##
+ # Returns the name of the action this controller is processing.
attr_internal :action_name
+
+ ##
+ # Returns the formats that can be processed by the controller.
attr_internal :formats
include ActiveSupport::Configurable
diff --git a/actionpack/lib/abstract_controller/caching.rb b/actionpack/lib/abstract_controller/caching.rb
index 26e3f08bc1..30e3d4426c 100644
--- a/actionpack/lib/abstract_controller/caching.rb
+++ b/actionpack/lib/abstract_controller/caching.rb
@@ -37,8 +37,7 @@ module AbstractController
config_accessor :enable_fragment_cache_logging
self.enable_fragment_cache_logging = false
- class_attribute :_view_cache_dependencies
- self._view_cache_dependencies = []
+ class_attribute :_view_cache_dependencies, default: []
helper_method :view_cache_dependencies if respond_to?(:helper_method)
end
diff --git a/actionpack/lib/abstract_controller/caching/fragments.rb b/actionpack/lib/abstract_controller/caching/fragments.rb
index c85b4adba1..14e4a82523 100644
--- a/actionpack/lib/abstract_controller/caching/fragments.rb
+++ b/actionpack/lib/abstract_controller/caching/fragments.rb
@@ -25,7 +25,10 @@ module AbstractController
self.fragment_cache_keys = []
- helper_method :fragment_cache_key if respond_to?(:helper_method)
+ if respond_to?(:helper_method)
+ helper_method :fragment_cache_key
+ helper_method :combined_fragment_cache_key
+ end
end
module ClassMethods
@@ -62,17 +65,36 @@ module AbstractController
# with the specified +key+ value. The key is expanded using
# ActiveSupport::Cache.expand_cache_key.
def fragment_cache_key(key)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Calling fragment_cache_key directly is deprecated and will be removed in Rails 6.0.
+ All fragment accessors now use the combined_fragment_cache_key method that retains the key as an array,
+ such that the caching stores can interrogate the parts for cache versions used in
+ recyclable cache keys.
+ MSG
+
head = self.class.fragment_cache_keys.map { |k| instance_exec(&k) }
tail = key.is_a?(Hash) ? url_for(key).split("://").last : key
ActiveSupport::Cache.expand_cache_key([*head, *tail], :views)
end
+ # Given a key (as described in +expire_fragment+), returns
+ # a key array suitable for use in reading, writing, or expiring a
+ # cached fragment. All keys begin with <tt>:views</tt>,
+ # followed by ENV["RAILS_CACHE_ID"] or ENV["RAILS_APP_VERSION"] if set,
+ # followed by any controller-wide key prefix values, ending
+ # with the specified +key+ value.
+ def combined_fragment_cache_key(key)
+ head = self.class.fragment_cache_keys.map { |k| instance_exec(&k) }
+ tail = key.is_a?(Hash) ? url_for(key).split("://").last : key
+ [ :views, (ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]), *head, *tail ].compact
+ end
+
# Writes +content+ to the location signified by
# +key+ (see +expire_fragment+ for acceptable formats).
def write_fragment(key, content, options = nil)
return content unless cache_configured?
- key = fragment_cache_key(key)
+ key = combined_fragment_cache_key(key)
instrument_fragment_cache :write_fragment, key do
content = content.to_str
cache_store.write(key, content, options)
@@ -85,7 +107,7 @@ module AbstractController
def read_fragment(key, options = nil)
return unless cache_configured?
- key = fragment_cache_key(key)
+ key = combined_fragment_cache_key(key)
instrument_fragment_cache :read_fragment, key do
result = cache_store.read(key, options)
result.respond_to?(:html_safe) ? result.html_safe : result
@@ -96,7 +118,7 @@ module AbstractController
# +key+ exists (see +expire_fragment+ for acceptable formats).
def fragment_exist?(key, options = nil)
return unless cache_configured?
- key = fragment_cache_key(key)
+ key = combined_fragment_cache_key(key)
instrument_fragment_cache :exist_fragment?, key do
cache_store.exist?(key, options)
@@ -123,7 +145,7 @@ module AbstractController
# method (or <tt>delete_matched</tt>, for Regexp keys).
def expire_fragment(key, options = nil)
return unless cache_configured?
- key = fragment_cache_key(key) unless key.is_a?(Regexp)
+ key = combined_fragment_cache_key(key) unless key.is_a?(Regexp)
instrument_fragment_cache :expire_fragment, key do
if key.is_a?(Regexp)
@@ -135,8 +157,7 @@ module AbstractController
end
def instrument_fragment_cache(name, key) # :nodoc:
- payload = instrument_payload(key)
- ActiveSupport::Notifications.instrument("#{name}.#{instrument_name}", payload) { yield }
+ ActiveSupport::Notifications.instrument("#{name}.#{instrument_name}", instrument_payload(key)) { yield }
end
end
end
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index ce4ecf17cc..e4400e8704 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -1,4 +1,24 @@
module AbstractController
+ # = Abstract Controller Callbacks
+ #
+ # Abstract Controller provides hooks during the life cycle of a controller action.
+ # Callbacks allow you to trigger logic during this cycle. Available callbacks are:
+ #
+ # * <tt>after_action</tt>
+ # * <tt>append_after_action</tt>
+ # * <tt>append_around_action</tt>
+ # * <tt>append_before_action</tt>
+ # * <tt>around_action</tt>
+ # * <tt>before_action</tt>
+ # * <tt>prepend_after_action</tt>
+ # * <tt>prepend_around_action</tt>
+ # * <tt>prepend_before_action</tt>
+ # * <tt>skip_after_action</tt>
+ # * <tt>skip_around_action</tt>
+ # * <tt>skip_before_action</tt>
+ #
+ # NOTE: Calling the same callback multiple times will overwrite previous callback definitions.
+ #
module Callbacks
extend ActiveSupport::Concern
@@ -9,7 +29,7 @@ module AbstractController
included do
define_callbacks :process_action,
- terminator: ->(controller, result_lambda) { result_lambda.call if result_lambda.is_a?(Proc); controller.performed? },
+ terminator: ->(controller, result_lambda) { result_lambda.call; controller.performed? },
skip_after_callbacks_if_terminated: true
end
diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb
index ef3be7af83..2e50637c39 100644
--- a/actionpack/lib/abstract_controller/helpers.rb
+++ b/actionpack/lib/abstract_controller/helpers.rb
@@ -5,11 +5,8 @@ module AbstractController
extend ActiveSupport::Concern
included do
- class_attribute :_helpers
- self._helpers = Module.new
-
- class_attribute :_helper_methods
- self._helper_methods = Array.new
+ class_attribute :_helpers, default: Module.new
+ class_attribute :_helper_methods, default: Array.new
end
class MissingHelperError < LoadError
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index 54af938a93..1c87739d42 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -1,4 +1,4 @@
-require "abstract_controller/error"
+require_relative "error"
require "action_view"
require "action_view/view_paths"
require "set"
diff --git a/actionpack/lib/abstract_controller/translation.rb b/actionpack/lib/abstract_controller/translation.rb
index 9e3858802a..e4ac95df50 100644
--- a/actionpack/lib/abstract_controller/translation.rb
+++ b/actionpack/lib/abstract_controller/translation.rb
@@ -13,7 +13,7 @@ module AbstractController
path = controller_path.tr("/", ".")
defaults = [:"#{path}#{key}"]
defaults << options[:default] if options[:default]
- options[:default] = defaults
+ options[:default] = defaults.flatten
key = "#{path}.#{action_name}#{key}"
end
I18n.translate(key, options)
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index 50f20aa789..4e2d67954f 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -1,8 +1,8 @@
require "active_support/rails"
require "abstract_controller"
require "action_dispatch"
-require "action_controller/metal/live"
-require "action_controller/metal/strong_parameters"
+require_relative "action_controller/metal/live"
+require_relative "action_controller/metal/strong_parameters"
module ActionController
extend ActiveSupport::Autoload
diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb
index 0d1af0d0bd..2bfa65021d 100644
--- a/actionpack/lib/action_controller/api.rb
+++ b/actionpack/lib/action_controller/api.rb
@@ -1,6 +1,6 @@
require "action_view"
require "action_controller"
-require "action_controller/log_subscriber"
+require_relative "log_subscriber"
module ActionController
# API Controller is a lightweight version of <tt>ActionController::Base</tt>,
@@ -141,6 +141,7 @@ module ActionController
include mod
end
+ ActiveSupport.run_load_hooks(:action_controller_api, self)
ActiveSupport.run_load_hooks(:action_controller, self)
end
end
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index b420e00c78..6e195fa359 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -1,6 +1,6 @@
require "action_view"
-require "action_controller/log_subscriber"
-require "action_controller/metal/params_wrapper"
+require_relative "log_subscriber"
+require_relative "metal/params_wrapper"
module ActionController
# Action Controllers are the core of a web request in \Rails. They are made up of one or more actions that are executed
@@ -267,6 +267,7 @@ module ActionController
end
end
+ ActiveSupport.run_load_hooks(:action_controller_base, self)
ActiveSupport.run_load_hooks(:action_controller, self)
end
end
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index d29a5fe68f..5d75393897 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -24,7 +24,7 @@ module ActionController
exception_class_name = payload[:exception].first
status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
end
- message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms"
+ message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms".dup
message << " (#{additions.join(" | ".freeze)})" unless additions.empty?
message << "\n\n" if defined?(Rails.env) && Rails.env.development?
@@ -60,9 +60,9 @@ module ActionController
class_eval <<-METHOD, __FILE__, __LINE__ + 1
def #{method}(event)
return unless logger.info? && ActionController::Base.enable_fragment_cache_logging
- key_or_path = event.payload[:key] || event.payload[:path]
+ key = ActiveSupport::Cache.expand_cache_key(event.payload[:key] || event.payload[:path])
human_name = #{method.to_s.humanize.inspect}
- info("\#{human_name} \#{key_or_path} (\#{event.duration.round(1)}ms)")
+ info("\#{human_name} \#{key} (\#{event.duration.round(1)}ms)")
end
METHOD
end
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 246644dcbd..96c708f45a 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -208,8 +208,7 @@ module ActionController
@_request.reset_session
end
- class_attribute :middleware_stack
- self.middleware_stack = ActionController::MiddlewareStack.new
+ class_attribute :middleware_stack, default: ActionController::MiddlewareStack.new
def self.inherited(base) # :nodoc:
base.middleware_stack = middleware_stack.dup
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index eb636fa3f6..0525252c7c 100644
--- a/actionpack/lib/action_controller/metal/conditional_get.rb
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -7,8 +7,7 @@ module ActionController
include Head
included do
- class_attribute :etaggers
- self.etaggers = []
+ class_attribute :etaggers, default: []
end
module ClassMethods
diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb
index 731e03e2fc..3dbdd4a1b6 100644
--- a/actionpack/lib/action_controller/metal/data_streaming.rb
+++ b/actionpack/lib/action_controller/metal/data_streaming.rb
@@ -1,4 +1,4 @@
-require "action_controller/metal/exceptions"
+require_relative "exceptions"
module ActionController #:nodoc:
# Methods for sending arbitrary data and for streaming files to the browser,
diff --git a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb
index 798564db96..69c3979a0e 100644
--- a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb
+++ b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb
@@ -22,8 +22,7 @@ module ActionController
include ActionController::ConditionalGet
included do
- class_attribute :etag_with_template_digest
- self.etag_with_template_digest = true
+ class_attribute :etag_with_template_digest, default: true
ActiveSupport.on_load :action_view, yield: true do
etag do |options|
diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb
index 347fbf0e74..24d1097ebe 100644
--- a/actionpack/lib/action_controller/metal/flash.rb
+++ b/actionpack/lib/action_controller/metal/flash.rb
@@ -3,8 +3,7 @@ module ActionController #:nodoc:
extend ActiveSupport::Concern
included do
- class_attribute :_flash_types, instance_accessor: false
- self._flash_types = []
+ class_attribute :_flash_types, instance_accessor: false, default: []
delegate :flash, to: :request
add_flash_types(:alert, :notice)
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index 476d081239..913a4b9a04 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -53,9 +53,8 @@ module ActionController
include AbstractController::Helpers
included do
- class_attribute :helpers_path, :include_all_helpers
- self.helpers_path ||= []
- self.include_all_helpers = true
+ class_attribute :helpers_path, default: []
+ class_attribute :include_all_helpers, default: true
end
module ClassMethods
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index a89fc1678b..818af549eb 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -159,8 +159,7 @@ module ActionController
end
included do
- class_attribute :_wrapper_options
- self._wrapper_options = Options.from_hash(format: [])
+ class_attribute :_wrapper_options, default: Options.from_hash(format: [])
end
module ClassMethods
@@ -233,12 +232,7 @@ module ActionController
# by the metal call stack.
def process_action(*args)
if _wrapper_enabled?
- if request.parameters[_wrapper_key].present?
- wrapped_hash = _extract_parameters(request.parameters)
- else
- wrapped_hash = _wrap_parameters request.request_parameters
- end
-
+ wrapped_hash = _wrap_parameters request.request_parameters
wrapped_keys = request.request_parameters.keys
wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys)
@@ -283,7 +277,7 @@ module ActionController
return false unless request.has_content_type?
ref = request.content_mime_type.ref
- _wrapper_formats.include?(ref) && _wrapper_key && !request.request_parameters[_wrapper_key]
+ _wrapper_formats.include?(ref) && _wrapper_key && !request.parameters.key?(_wrapper_key)
end
end
end
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index 733aca195d..23c21b0501 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -26,8 +26,7 @@ module ActionController
RENDERERS = Set.new
included do
- class_attribute :_renderers
- self._renderers = Set.new.freeze
+ class_attribute :_renderers, default: Set.new.freeze
end
# Used in <tt>ActionController::Base</tt>
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index d9a8b9c12d..4468cbb2fc 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -1,5 +1,5 @@
require "rack/session/abstract/id"
-require "action_controller/metal/exceptions"
+require_relative "exceptions"
require "active_support/security_utils"
module ActionController #:nodoc:
@@ -213,7 +213,11 @@ module ActionController #:nodoc:
if !verified_request?
if logger && log_warning_on_csrf_failure
- logger.warn "Can't verify CSRF token authenticity."
+ if valid_request_origin?
+ logger.warn "Can't verify CSRF token authenticity."
+ else
+ logger.warn "HTTP Origin header (#{request.origin}) didn't match request.base_url (#{request.base_url})"
+ end
end
handle_unverified_request
end
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index baf15570d5..a1b8b7cd6e 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -43,6 +43,18 @@ module ActionController
end
end
+ # Raised when a Parameters instance is not marked as permitted and
+ # an operation to transform it to hash is called.
+ #
+ # params = ActionController::Parameters.new(a: "123", b: "456")
+ # params.to_h
+ # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash
+ class UnfilteredParameters < ArgumentError
+ def initialize # :nodoc:
+ super("unable to convert unpermitted parameters to hash")
+ end
+ end
+
# == Action Controller \Parameters
#
# Allows you to choose which attributes should be whitelisted for mass updating
@@ -53,9 +65,9 @@ module ActionController
#
# params = ActionController::Parameters.new({
# person: {
- # name: 'Francesco',
+ # name: "Francesco",
# age: 22,
- # role: 'admin'
+ # role: "admin"
# }
# })
#
@@ -103,12 +115,11 @@ module ActionController
# You can fetch values of <tt>ActionController::Parameters</tt> using either
# <tt>:key</tt> or <tt>"key"</tt>.
#
- # params = ActionController::Parameters.new(key: 'value')
+ # params = ActionController::Parameters.new(key: "value")
# params[:key] # => "value"
# params["key"] # => "value"
class Parameters
- cattr_accessor :permit_all_parameters, instance_accessor: false
- self.permit_all_parameters = false
+ cattr_accessor :permit_all_parameters, instance_accessor: false, default: false
cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false
@@ -169,6 +180,14 @@ module ActionController
# Returns a new array of the keys of the parameters.
##
+ # :method: to_s
+ #
+ # :call-seq:
+ # to_s()
+ #
+ # Returns the content of the parameters as a string.
+
+ ##
# :method: value?
#
# :call-seq:
@@ -184,7 +203,7 @@ module ActionController
#
# Returns a new array of the values of the parameters.
delegate :keys, :key?, :has_key?, :values, :has_value?, :value?, :empty?, :include?,
- :as_json, to: :@parameters
+ :as_json, :to_s, to: :@parameters
# By default, never raise an UnpermittedParameters exception if these
# params are present. The default includes both 'controller' and 'action'
@@ -193,8 +212,7 @@ module ActionController
# config. For instance:
#
# config.always_permitted_parameters = %w( controller action format )
- cattr_accessor :always_permitted_parameters
- self.always_permitted_parameters = %w( controller action )
+ cattr_accessor :always_permitted_parameters, default: %w( controller action )
# Returns a new instance of <tt>ActionController::Parameters</tt>.
# Also, sets the +permitted+ attribute to the default value of
@@ -203,13 +221,13 @@ module ActionController
# class Person < ActiveRecord::Base
# end
#
- # params = ActionController::Parameters.new(name: 'Francesco')
+ # params = ActionController::Parameters.new(name: "Francesco")
# params.permitted? # => false
# Person.new(params) # => ActiveModel::ForbiddenAttributesError
#
# ActionController::Parameters.permit_all_parameters = true
#
- # params = ActionController::Parameters.new(name: 'Francesco')
+ # params = ActionController::Parameters.new(name: "Francesco")
# params.permitted? # => true
# Person.new(params) # => #<Person id: nil, name: "Francesco">
def initialize(parameters = {})
@@ -228,13 +246,14 @@ module ActionController
end
# Returns a safe <tt>ActiveSupport::HashWithIndifferentAccess</tt>
- # representation of this parameter with all unpermitted keys removed.
+ # representation of the parameters with all unpermitted keys removed.
#
# params = ActionController::Parameters.new({
- # name: 'Senjougahara Hitagi',
- # oddity: 'Heavy stone crab'
+ # name: "Senjougahara Hitagi",
+ # oddity: "Heavy stone crab"
# })
- # params.to_h # => {}
+ # params.to_h
+ # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash
#
# safe_params = params.permit(:name)
# safe_params.to_h # => {"name"=>"Senjougahara Hitagi"}
@@ -242,17 +261,66 @@ module ActionController
if permitted?
convert_parameters_to_hashes(@parameters, :to_h)
else
- slice(*self.class.always_permitted_parameters).permit!.to_h
+ raise UnfilteredParameters
end
end
+ # Returns a safe <tt>Hash</tt> representation of the parameters
+ # with all unpermitted keys removed.
+ #
+ # params = ActionController::Parameters.new({
+ # name: "Senjougahara Hitagi",
+ # oddity: "Heavy stone crab"
+ # })
+ # params.to_hash
+ # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash
+ #
+ # safe_params = params.permit(:name)
+ # safe_params.to_hash # => {"name"=>"Senjougahara Hitagi"}
+ def to_hash
+ to_h.to_hash
+ end
+
+ # Returns a string representation of the receiver suitable for use as a URL
+ # query string:
+ #
+ # params = ActionController::Parameters.new({
+ # name: "David",
+ # nationality: "Danish"
+ # })
+ # params.to_query
+ # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash
+ #
+ # safe_params = params.permit(:name, :nationality)
+ # safe_params.to_query
+ # # => "name=David&nationality=Danish"
+ #
+ # An optional namespace can be passed to enclose key names:
+ #
+ # params = ActionController::Parameters.new({
+ # name: "David",
+ # nationality: "Danish"
+ # })
+ # safe_params = params.permit(:name, :nationality)
+ # safe_params.to_query("user")
+ # # => "user%5Bname%5D=David&user%5Bnationality%5D=Danish"
+ #
+ # The string pairs "key=value" that conform the query string
+ # are sorted lexicographically in ascending order.
+ #
+ # This method is also aliased as +to_param+.
+ def to_query(*args)
+ to_h.to_query(*args)
+ end
+ alias_method :to_param, :to_query
+
# Returns an unsafe, unfiltered
- # <tt>ActiveSupport::HashWithIndifferentAccess</tt> representation of this
- # parameter.
+ # <tt>ActiveSupport::HashWithIndifferentAccess</tt> representation of the
+ # parameters.
#
# params = ActionController::Parameters.new({
- # name: 'Senjougahara Hitagi',
- # oddity: 'Heavy stone crab'
+ # name: "Senjougahara Hitagi",
+ # oddity: "Heavy stone crab"
# })
# params.to_unsafe_h
# # => {"name"=>"Senjougahara Hitagi", "oddity" => "Heavy stone crab"}
@@ -297,7 +365,7 @@ module ActionController
# class Person < ActiveRecord::Base
# end
#
- # params = ActionController::Parameters.new(name: 'Francesco')
+ # params = ActionController::Parameters.new(name: "Francesco")
# params.permitted? # => false
# Person.new(params) # => ActiveModel::ForbiddenAttributesError
# params.permit!
@@ -319,7 +387,7 @@ module ActionController
# When passed a single key, if it exists and its associated value is
# either present or the singleton +false+, returns said value:
#
- # ActionController::Parameters.new(person: { name: 'Francesco' }).require(:person)
+ # ActionController::Parameters.new(person: { name: "Francesco" }).require(:person)
# # => <ActionController::Parameters {"name"=>"Francesco"} permitted: false>
#
# Otherwise raises <tt>ActionController::ParameterMissing</tt>:
@@ -352,7 +420,7 @@ module ActionController
# Technically this method can be used to fetch terminal values:
#
# # CAREFUL
- # params = ActionController::Parameters.new(person: { name: 'Finn' })
+ # params = ActionController::Parameters.new(person: { name: "Finn" })
# name = params.require(:person).require(:name) # CAREFUL
#
# but take into account that at some point those ones have to be permitted:
@@ -382,7 +450,7 @@ module ActionController
# for the object to +true+. This is useful for limiting which attributes
# should be allowed for mass updating.
#
- # params = ActionController::Parameters.new(user: { name: 'Francesco', age: 22, role: 'admin' })
+ # params = ActionController::Parameters.new(user: { name: "Francesco", age: 22, role: "admin" })
# permitted = params.require(:user).permit(:name, :age)
# permitted.permitted? # => true
# permitted.has_key?(:name) # => true
@@ -402,7 +470,7 @@ module ActionController
# You may declare that the parameter should be an array of permitted scalars
# by mapping it to an empty array:
#
- # params = ActionController::Parameters.new(tags: ['rails', 'parameters'])
+ # params = ActionController::Parameters.new(tags: ["rails", "parameters"])
# params.permit(tags: [])
#
# Sometimes it is not possible or convenient to declare the valid keys of
@@ -418,11 +486,11 @@ module ActionController
#
# params = ActionController::Parameters.new({
# person: {
- # name: 'Francesco',
+ # name: "Francesco",
# age: 22,
# pets: [{
- # name: 'Purplish',
- # category: 'dogs'
+ # name: "Purplish",
+ # category: "dogs"
# }]
# }
# })
@@ -441,8 +509,8 @@ module ActionController
# params = ActionController::Parameters.new({
# person: {
# contact: {
- # email: 'none@test.com',
- # phone: '555-1234'
+ # email: "none@test.com",
+ # phone: "555-1234"
# }
# }
# })
@@ -475,7 +543,7 @@ module ActionController
# Returns a parameter for the given +key+. If not found,
# returns +nil+.
#
- # params = ActionController::Parameters.new(person: { name: 'Francesco' })
+ # params = ActionController::Parameters.new(person: { name: "Francesco" })
# params[:person] # => <ActionController::Parameters {"name"=>"Francesco"} permitted: false>
# params[:none] # => nil
def [](key)
@@ -494,11 +562,11 @@ module ActionController
# if more arguments are given, then that will be returned; if a block
# is given, then that will be run and its result returned.
#
- # params = ActionController::Parameters.new(person: { name: 'Francesco' })
+ # params = ActionController::Parameters.new(person: { name: "Francesco" })
# params.fetch(:person) # => <ActionController::Parameters {"name"=>"Francesco"} permitted: false>
# params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none
- # params.fetch(:none, 'Francesco') # => "Francesco"
- # params.fetch(:none) { 'Francesco' } # => "Francesco"
+ # params.fetch(:none, "Francesco") # => "Francesco"
+ # params.fetch(:none) { "Francesco" } # => "Francesco"
def fetch(key, *args)
convert_value_to_parameters(
@parameters.fetch(key) {
@@ -609,8 +677,8 @@ module ActionController
# to key. If the key is not found, returns the default value. If the
# optional code block is given and the key is not found, pass in the key
# and return the result of block.
- def delete(key)
- convert_value_to_parameters(@parameters.delete(key))
+ def delete(key, &block)
+ convert_value_to_parameters(@parameters.delete(key, &block))
end
# Returns a new instance of <tt>ActionController::Parameters</tt> with only
@@ -715,8 +783,6 @@ module ActionController
end
end
- undef_method :to_param
-
# Returns duplicate of object including all parameters.
def deep_dup
self.class.new(@parameters.deep_dup).tap do |duplicate|
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index fadfc8de60..31db7518f1 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -2,7 +2,7 @@ require "rails"
require "action_controller"
require "action_dispatch/railtie"
require "abstract_controller/railties/routes_helpers"
-require "action_controller/railties/helpers"
+require_relative "railties/helpers"
require "action_view/railtie"
module ActionController
@@ -22,13 +22,15 @@ module ActionController
initializer "action_controller.parameters_config" do |app|
options = app.config.action_controller
- ActionController::Parameters.permit_all_parameters = options.delete(:permit_all_parameters) { false }
- if app.config.action_controller[:always_permitted_parameters]
- ActionController::Parameters.always_permitted_parameters =
- app.config.action_controller.delete(:always_permitted_parameters)
- end
- ActionController::Parameters.action_on_unpermitted_parameters = options.delete(:action_on_unpermitted_parameters) do
- (Rails.env.test? || Rails.env.development?) ? :log : false
+ ActiveSupport.on_load(:action_controller) do
+ ActionController::Parameters.permit_all_parameters = options.delete(:permit_all_parameters) { false }
+ if app.config.action_controller[:always_permitted_parameters]
+ ActionController::Parameters.always_permitted_parameters =
+ app.config.action_controller.delete(:always_permitted_parameters)
+ end
+ ActionController::Parameters.action_on_unpermitted_parameters = options.delete(:action_on_unpermitted_parameters) do
+ (Rails.env.test? || Rails.env.development?) ? :log : false
+ end
end
end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index bc42d50205..9d8240e46d 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -4,7 +4,7 @@ require "active_support/core_ext/object/to_query"
require "active_support/core_ext/module/anonymous"
require "active_support/core_ext/hash/keys"
require "active_support/testing/constant_lookup"
-require "action_controller/template_assertions"
+require_relative "template_assertions"
require "rails-dom-testing"
module ActionController
diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb
index e584b84d92..eb6761d067 100644
--- a/actionpack/lib/action_dispatch/http/filter_parameters.rb
+++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb
@@ -1,4 +1,4 @@
-require "action_dispatch/http/parameter_filter"
+require_relative "parameter_filter"
module ActionDispatch
module Http
@@ -74,7 +74,7 @@ module ActionDispatch
PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})}
def filtered_query_string # :doc:
query_string.gsub(PAIR_RE) do |_|
- parameter_filter.filter([[$1, $2]]).first.join("=")
+ parameter_filter.filter($1 => $2).first.join("=")
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index 19f89edbc1..5994a01c78 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -6,8 +6,7 @@ module ActionDispatch
extend ActiveSupport::Concern
included do
- mattr_accessor :ignore_accept_header
- self.ignore_accept_header = false
+ mattr_accessor :ignore_accept_header, default: false
end
# The MIME type of the HTTP request, such as Mime[:xml].
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 0cf010f59e..5a0f661d99 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -326,15 +326,15 @@ module Mime
def ref; end
- def respond_to_missing?(method, include_private = false)
- method.to_s.ends_with? "?"
- end
-
private
+ def respond_to_missing?(method, _)
+ method.to_s.ends_with? "?"
+ end
+
def method_missing(method, *args)
false if method.to_s.ends_with? "?"
end
end
end
-require "action_dispatch/http/mime_types"
+require_relative "mime_types"
diff --git a/actionpack/lib/action_dispatch/http/parameter_filter.rb b/actionpack/lib/action_dispatch/http/parameter_filter.rb
index 889f55a52a..1d2b4b902b 100644
--- a/actionpack/lib/action_dispatch/http/parameter_filter.rb
+++ b/actionpack/lib/action_dispatch/http/parameter_filter.rb
@@ -54,7 +54,7 @@ module ActionDispatch
end
def call(original_params, parents = [])
- filtered_params = {}
+ filtered_params = original_params.class.new
original_params.each do |key, value|
parents.push(key) if deep_regexps
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index 79a2ef965b..7c585dbe68 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -85,7 +85,7 @@ module ActionDispatch
def set_binary_encoding(params)
action = params[:action]
- if controller_class.binary_params_for?(action)
+ if binary_params_for?(action)
ActionDispatch::Request::Utils.each_param_value(params) do |param|
param.force_encoding ::Encoding::ASCII_8BIT
end
@@ -93,6 +93,12 @@ module ActionDispatch
params
end
+ def binary_params_for?(action)
+ controller_class.binary_params_for?(action)
+ rescue NameError
+ false
+ end
+
def parse_formatted_parameters(parsers)
return yield if content_length.zero? || content_mime_type.nil?
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 6d42404a98..648348d9de 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -1,15 +1,15 @@
require "stringio"
require "active_support/inflector"
-require "action_dispatch/http/headers"
+require_relative "headers"
require "action_controller/metal/exceptions"
require "rack/request"
-require "action_dispatch/http/cache"
-require "action_dispatch/http/mime_negotiation"
-require "action_dispatch/http/parameters"
-require "action_dispatch/http/filter_parameters"
-require "action_dispatch/http/upload"
-require "action_dispatch/http/url"
+require_relative "cache"
+require_relative "mime_negotiation"
+require_relative "parameters"
+require_relative "filter_parameters"
+require_relative "upload"
+require_relative "url"
require "active_support/core_ext/array/conversions"
module ActionDispatch
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 2ee52eeb48..eab663e2e0 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -1,6 +1,6 @@
require "active_support/core_ext/module/attribute_accessors"
-require "action_dispatch/http/filter_redirect"
-require "action_dispatch/http/cache"
+require_relative "filter_redirect"
+require_relative "cache"
require "monitor"
module ActionDispatch # :nodoc:
@@ -81,8 +81,8 @@ module ActionDispatch # :nodoc:
LOCATION = "Location".freeze
NO_CONTENT_CODES = [100, 101, 102, 204, 205, 304]
- cattr_accessor(:default_charset) { "utf-8" }
- cattr_accessor(:default_headers)
+ cattr_accessor :default_charset, default: "utf-8"
+ cattr_accessor :default_headers
include Rack::Response::Helpers
# Aliasing these off because AD::Http::Cache::Response defines them.
@@ -103,7 +103,7 @@ module ActionDispatch # :nodoc:
def body
@str_body ||= begin
- buf = ""
+ buf = "".dup
each { |chunk| buf << chunk }
buf
end
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index 61ba052e45..225272d66e 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -27,14 +27,18 @@ module ActionDispatch
@tempfile = hash[:tempfile]
raise(ArgumentError, ":tempfile is required") unless @tempfile
- @original_filename = hash[:filename]
- if @original_filename
+ if hash[:filename]
+ @original_filename = hash[:filename].dup
+
begin
@original_filename.encode!(Encoding::UTF_8)
rescue EncodingError
@original_filename.force_encoding(Encoding::UTF_8)
end
+ else
+ @original_filename = nil
end
+
@content_type = hash[:type]
@headers = hash[:head]
end
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index b6be48a48b..f902fe36e0 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -7,8 +7,7 @@ module ActionDispatch
HOST_REGEXP = /(^[^:]+:\/\/)?(\[[^\]]+\]|[^:]+)(?::(\d+$))?/
PROTOCOL_REGEXP = /^([^:]+)(:)?(\/\/)?$/
- mattr_accessor :tld_length
- self.tld_length = 1
+ mattr_accessor :tld_length, default: 1
class << self
# Returns the domain part of a host given the domain level.
diff --git a/actionpack/lib/action_dispatch/journey.rb b/actionpack/lib/action_dispatch/journey.rb
index d1cfc51f3e..222cbf4584 100644
--- a/actionpack/lib/action_dispatch/journey.rb
+++ b/actionpack/lib/action_dispatch/journey.rb
@@ -1,5 +1,5 @@
-require "action_dispatch/journey/router"
-require "action_dispatch/journey/gtg/builder"
-require "action_dispatch/journey/gtg/simulator"
-require "action_dispatch/journey/nfa/builder"
-require "action_dispatch/journey/nfa/simulator"
+require_relative "journey/router"
+require_relative "journey/gtg/builder"
+require_relative "journey/gtg/simulator"
+require_relative "journey/nfa/builder"
+require_relative "journey/nfa/simulator"
diff --git a/actionpack/lib/action_dispatch/journey/gtg/builder.rb b/actionpack/lib/action_dispatch/journey/gtg/builder.rb
index 0f8bed89bf..b1132ef17c 100644
--- a/actionpack/lib/action_dispatch/journey/gtg/builder.rb
+++ b/actionpack/lib/action_dispatch/journey/gtg/builder.rb
@@ -1,4 +1,4 @@
-require "action_dispatch/journey/gtg/transition_table"
+require_relative "transition_table"
module ActionDispatch
module Journey # :nodoc:
diff --git a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb
index d692f6415c..62f052ced6 100644
--- a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb
+++ b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb
@@ -18,14 +18,6 @@ module ActionDispatch
@tt = transition_table
end
- def simulate(string)
- ms = memos(string) { return }
- MatchData.new(ms)
- end
-
- alias :=~ :simulate
- alias :match :simulate
-
def memos(string)
input = StringScanner.new(string)
state = [0]
diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
index e1ac2c873e..278e4f0e11 100644
--- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
+++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
@@ -1,4 +1,4 @@
-require "action_dispatch/journey/nfa/dot"
+require_relative "../nfa/dot"
module ActionDispatch
module Journey # :nodoc:
@@ -82,7 +82,7 @@ module ActionDispatch
end
def visualizer(paths, title = "FSM")
- viz_dir = File.join File.dirname(__FILE__), "..", "visualizer"
+ viz_dir = File.join __dir__, "..", "visualizer"
fsm_js = File.read File.join(viz_dir, "fsm.js")
fsm_css = File.read File.join(viz_dir, "fsm.css")
erb = File.read File.join(viz_dir, "index.html.erb")
diff --git a/actionpack/lib/action_dispatch/journey/nfa/builder.rb b/actionpack/lib/action_dispatch/journey/nfa/builder.rb
index 532f765094..5b628d8cef 100644
--- a/actionpack/lib/action_dispatch/journey/nfa/builder.rb
+++ b/actionpack/lib/action_dispatch/journey/nfa/builder.rb
@@ -1,5 +1,5 @@
-require "action_dispatch/journey/nfa/transition_table"
-require "action_dispatch/journey/gtg/transition_table"
+require_relative "transition_table"
+require_relative "../gtg/transition_table"
module ActionDispatch
module Journey # :nodoc:
diff --git a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb
index 543a670da0..d18243545b 100644
--- a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb
+++ b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb
@@ -1,4 +1,4 @@
-require "action_dispatch/journey/nfa/dot"
+require_relative "dot"
module ActionDispatch
module Journey # :nodoc:
diff --git a/actionpack/lib/action_dispatch/journey/nodes/node.rb b/actionpack/lib/action_dispatch/journey/nodes/node.rb
index 0d874a84c9..97acad6995 100644
--- a/actionpack/lib/action_dispatch/journey/nodes/node.rb
+++ b/actionpack/lib/action_dispatch/journey/nodes/node.rb
@@ -1,4 +1,4 @@
-require "action_dispatch/journey/visitors"
+require_relative "../visitors"
module ActionDispatch
module Journey # :nodoc:
diff --git a/actionpack/lib/action_dispatch/journey/parser.rb b/actionpack/lib/action_dispatch/journey/parser.rb
index e002755bcf..6ddfe96098 100644
--- a/actionpack/lib/action_dispatch/journey/parser.rb
+++ b/actionpack/lib/action_dispatch/journey/parser.rb
@@ -8,7 +8,7 @@ require 'racc/parser.rb'
# :stopdoc:
-require "action_dispatch/journey/parser_extras"
+require_relative "parser_extras"
module ActionDispatch
module Journey
class Parser < Racc::Parser
diff --git a/actionpack/lib/action_dispatch/journey/parser.y b/actionpack/lib/action_dispatch/journey/parser.y
index f9b1a7a958..850c84ea1a 100644
--- a/actionpack/lib/action_dispatch/journey/parser.y
+++ b/actionpack/lib/action_dispatch/journey/parser.y
@@ -47,4 +47,4 @@ end
---- header
# :stopdoc:
-require "action_dispatch/journey/parser_extras"
+require_relative "parser_extras"
diff --git a/actionpack/lib/action_dispatch/journey/parser_extras.rb b/actionpack/lib/action_dispatch/journey/parser_extras.rb
index 4c7e82d93c..d26f0e121f 100644
--- a/actionpack/lib/action_dispatch/journey/parser_extras.rb
+++ b/actionpack/lib/action_dispatch/journey/parser_extras.rb
@@ -1,5 +1,5 @@
-require "action_dispatch/journey/scanner"
-require "action_dispatch/journey/nodes/node"
+require_relative "scanner"
+require_relative "nodes/node"
module ActionDispatch
# :stopdoc:
diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb
index 7bc15aa6b3..0acbac1d9d 100644
--- a/actionpack/lib/action_dispatch/journey/route.rb
+++ b/actionpack/lib/action_dispatch/journey/route.rb
@@ -10,11 +10,11 @@ module ActionDispatch
module VerbMatchers
VERBS = %w{ DELETE GET HEAD OPTIONS LINK PATCH POST PUT TRACE UNLINK }
VERBS.each do |v|
- class_eval <<-eoc
- class #{v}
- def self.verb; name.split("::").last; end
- def self.call(req); req.#{v.downcase}?; end
- end
+ class_eval <<-eoc, __FILE__, __LINE__ + 1
+ class #{v}
+ def self.verb; name.split("::").last; end
+ def self.call(req); req.#{v.downcase}?; end
+ end
eoc
end
diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb
index d55e1399e4..85f4aade55 100644
--- a/actionpack/lib/action_dispatch/journey/router.rb
+++ b/actionpack/lib/action_dispatch/journey/router.rb
@@ -1,14 +1,14 @@
-require "action_dispatch/journey/router/utils"
-require "action_dispatch/journey/routes"
-require "action_dispatch/journey/formatter"
+require_relative "router/utils"
+require_relative "routes"
+require_relative "formatter"
before = $-w
$-w = false
-require "action_dispatch/journey/parser"
+require_relative "parser"
$-w = before
-require "action_dispatch/journey/route"
-require "action_dispatch/journey/path/pattern"
+require_relative "route"
+require_relative "path/pattern"
module ActionDispatch
module Journey # :nodoc:
diff --git a/actionpack/lib/action_dispatch/journey/router/utils.rb b/actionpack/lib/action_dispatch/journey/router/utils.rb
index ffcd875b4d..1ac86d10d6 100644
--- a/actionpack/lib/action_dispatch/journey/router/utils.rb
+++ b/actionpack/lib/action_dispatch/journey/router/utils.rb
@@ -13,11 +13,13 @@ module ActionDispatch
# normalize_path("") # => "/"
# normalize_path("/%ab") # => "/%AB"
def self.normalize_path(path)
- path = "/#{path}"
+ encoding = path.encoding
+ path = "/#{path}".dup
path.squeeze!("/".freeze)
path.sub!(%r{/+\Z}, "".freeze)
path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase }
- path = "/" if path == "".freeze
+ path = "/".dup if path == "".freeze
+ path.force_encoding(encoding)
path
end
@@ -27,7 +29,7 @@ module ActionDispatch
ENCODE = "%%%02X".freeze
US_ASCII = Encoding::US_ASCII
UTF_8 = Encoding::UTF_8
- EMPTY = "".force_encoding(US_ASCII).freeze
+ EMPTY = "".dup.force_encoding(US_ASCII).freeze
DEC2HEX = (0..255).to_a.map { |i| ENCODE % i }.map { |s| s.force_encoding(US_ASCII) }
ALPHA = "a-zA-Z".freeze
@@ -59,11 +61,11 @@ module ActionDispatch
end
private
- def escape(component, pattern) # :doc:
+ def escape(component, pattern)
component.gsub(pattern) { |unsafe| percent_encode(unsafe) }.force_encoding(US_ASCII)
end
- def percent_encode(unsafe) # :doc:
+ def percent_encode(unsafe)
safe = EMPTY.dup
unsafe.each_byte { |b| safe << DEC2HEX[b] }
safe
@@ -84,6 +86,10 @@ module ActionDispatch
ENCODER.escape_fragment(fragment.to_s)
end
+ # Replaces any escaped sequences with their unescaped representations.
+ #
+ # uri = "/topics?title=Ruby%20on%20Rails"
+ # unescape_uri(uri) #=> "/topics?title=Ruby on Rails"
def self.unescape_uri(uri)
ENCODER.unescape_uri(uri)
end
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index e565c03a8a..533925ebe1 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -43,6 +43,10 @@ module ActionDispatch
get_header Cookies::ENCRYPTED_SIGNED_COOKIE_SALT
end
+ def authenticated_encrypted_cookie_salt
+ get_header Cookies::AUTHENTICATED_ENCRYPTED_COOKIE_SALT
+ end
+
def secret_token
get_header Cookies::SECRET_TOKEN
end
@@ -149,6 +153,7 @@ module ActionDispatch
SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt".freeze
ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt".freeze
ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze
+ AUTHENTICATED_ENCRYPTED_COOKIE_SALT = "action_dispatch.authenticated_encrypted_cookie_salt".freeze
SECRET_TOKEN = "action_dispatch.secret_token".freeze
SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze
COOKIES_SERIALIZER = "action_dispatch.cookies_serializer".freeze
@@ -207,6 +212,9 @@ module ActionDispatch
# If +secrets.secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
# legacy cookies signed with the old key generator will be transparently upgraded.
#
+ # If +config.action_dispatch.encrypted_cookie_salt+ and +config.action_dispatch.encrypted_signed_cookie_salt+
+ # are both set, legacy cookies encrypted with HMAC AES-256-CBC will be transparently upgraded.
+ #
# This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+.
#
# Example:
@@ -219,6 +227,8 @@ module ActionDispatch
@encrypted ||=
if upgrade_legacy_signed_cookies?
UpgradeLegacyEncryptedCookieJar.new(self)
+ elsif upgrade_legacy_hmac_aes_cbc_cookies?
+ UpgradeLegacyHmacAesCbcCookieJar.new(self)
else
EncryptedCookieJar.new(self)
end
@@ -240,6 +250,13 @@ module ActionDispatch
def upgrade_legacy_signed_cookies?
request.secret_token.present? && request.secret_key_base.present?
end
+
+ def upgrade_legacy_hmac_aes_cbc_cookies?
+ request.secret_key_base.present? &&
+ request.authenticated_encrypted_cookie_salt.present? &&
+ request.encrypted_signed_cookie_salt.present? &&
+ request.encrypted_cookie_salt.present?
+ end
end
# Passing the ActiveSupport::MessageEncryptor::NullSerializer downstream
@@ -415,8 +432,7 @@ module ActionDispatch
end
end
- mattr_accessor :always_write_cookie
- self.always_write_cookie = false
+ mattr_accessor :always_write_cookie, default: false
private
@@ -576,9 +592,11 @@ module ActionDispatch
"Read the upgrade documentation to learn more about this new config option."
end
- secret = key_generator.generate_key(request.encrypted_cookie_salt || "")[0, ActiveSupport::MessageEncryptor.key_len]
- sign_secret = key_generator.generate_key(request.encrypted_signed_cookie_salt || "")
- @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
+ cipher = "aes-256-gcm"
+ key_len = ActiveSupport::MessageEncryptor.key_len(cipher)
+ secret = key_generator.generate_key(request.authenticated_encrypted_cookie_salt || "")[0, key_len]
+
+ @encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
end
private
@@ -603,6 +621,32 @@ module ActionDispatch
include VerifyAndUpgradeLegacySignedMessage
end
+ # UpgradeLegacyHmacAesCbcCookieJar is used by ActionDispatch::Session::CookieStore
+ # to upgrade cookies encrypted with AES-256-CBC with HMAC to AES-256-GCM
+ class UpgradeLegacyHmacAesCbcCookieJar < EncryptedCookieJar
+ def initialize(parent_jar)
+ super
+
+ secret = key_generator.generate_key(request.encrypted_cookie_salt || "")[0, ActiveSupport::MessageEncryptor.key_len]
+ sign_secret = key_generator.generate_key(request.encrypted_signed_cookie_salt || "")
+
+ @legacy_encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, cipher: "aes-256-cbc", digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
+ end
+
+ def decrypt_and_verify_legacy_encrypted_message(name, signed_message)
+ deserialize(name, @legacy_encryptor.decrypt_and_verify(signed_message)).tap do |value|
+ self[name] = { value: value }
+ end
+ rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
+ nil
+ end
+
+ private
+ def parse(name, signed_message)
+ super || decrypt_and_verify_legacy_encrypted_message(name, signed_message)
+ end
+ end
+
def initialize(app)
@app = app
end
diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
index 1c720c5a8e..d42b35a4cf 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -1,6 +1,6 @@
-require "action_dispatch/http/request"
-require "action_dispatch/middleware/exception_wrapper"
-require "action_dispatch/routing/inspector"
+require_relative "../http/request"
+require_relative "exception_wrapper"
+require_relative "../routing/inspector"
require "action_view"
require "action_view/base"
@@ -10,7 +10,7 @@ module ActionDispatch
# This middleware is responsible for logging exceptions and
# showing a debugging page in case the request is local.
class DebugExceptions
- RESCUES_TEMPLATE_PATH = File.expand_path("../templates", __FILE__)
+ RESCUES_TEMPLATE_PATH = File.expand_path("templates", __dir__)
class DebugView < ActionView::Base
def debug_params(params)
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index 397f0a8b92..08b4541d24 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -3,9 +3,7 @@ require "rack/utils"
module ActionDispatch
class ExceptionWrapper
- cattr_accessor :rescue_responses
- @@rescue_responses = Hash.new(:internal_server_error)
- @@rescue_responses.merge!(
+ cattr_accessor :rescue_responses, default: Hash.new(:internal_server_error).merge!(
"ActionController::RoutingError" => :not_found,
"AbstractController::ActionNotFound" => :not_found,
"ActionController::MethodNotAllowed" => :method_not_allowed,
@@ -21,9 +19,7 @@ module ActionDispatch
"Rack::QueryParser::InvalidParameterError" => :bad_request
)
- cattr_accessor :rescue_templates
- @@rescue_templates = Hash.new("diagnostics")
- @@rescue_templates.merge!(
+ cattr_accessor :rescue_templates, default: Hash.new("diagnostics").merge!(
"ActionView::MissingTemplate" => "missing_template",
"ActionController::RoutingError" => "routing_error",
"AbstractController::ActionNotFound" => "unknown_action",
diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
index 21ccf5a097..31979fa576 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -1,8 +1,8 @@
require "rack/utils"
require "rack/request"
require "rack/session/abstract/id"
-require "action_dispatch/middleware/cookies"
-require "action_dispatch/request/session"
+require_relative "../cookies"
+require_relative "../../request/session"
module ActionDispatch
module Session
diff --git a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
index 71274bc13a..4babeb6354 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
@@ -1,4 +1,4 @@
-require "action_dispatch/middleware/session/abstract_store"
+require_relative "abstract_store"
module ActionDispatch
module Session
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index 57d325a9d8..496f221617 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -1,5 +1,5 @@
require "active_support/core_ext/hash/keys"
-require "action_dispatch/middleware/session/abstract_store"
+require_relative "abstract_store"
require "rack/session/cookie"
module ActionDispatch
diff --git a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
index ee2b1f26ad..bc2b1c9b12 100644
--- a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
@@ -1,4 +1,4 @@
-require "action_dispatch/middleware/session/abstract_store"
+require_relative "abstract_store"
begin
require "rack/session/dalli"
rescue LoadError => e
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index 5a99714ec2..db84ff48e9 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -1,5 +1,5 @@
-require "action_dispatch/http/request"
-require "action_dispatch/middleware/exception_wrapper"
+require_relative "../http/request"
+require_relative "exception_wrapper"
module ActionDispatch
# This middleware rescues any exception returned by the application
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index 5d10129d21..fb99f13a1c 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -6,11 +6,11 @@ module ActionDispatch
# When initialized, it can accept optional HTTP headers, which will be set
# when a response containing a file's contents is delivered.
#
- # This middleware will render the file specified in `env["PATH_INFO"]`
+ # This middleware will render the file specified in <tt>env["PATH_INFO"]</tt>
# where the base path is in the +root+ directory. For example, if the +root+
- # is set to `public/`, then a request with `env["PATH_INFO"]` of
- # `assets/application.js` will return a response with the contents of a file
- # located at `public/assets/application.js` if the file exists. If the file
+ # is set to +public/+, then a request with <tt>env["PATH_INFO"]</tt> of
+ # +assets/application.js+ will return a response with the contents of a file
+ # located at +public/assets/application.js+ if the file exists. If the file
# does not exist, a 404 "File not Found" response will be returned.
class FileHandler
def initialize(root, index: "index", headers: {})
@@ -23,8 +23,8 @@ module ActionDispatch
# correct read permissions, the return value is a URI-escaped string
# representing the filename. Otherwise, false is returned.
#
- # Used by the `Static` class to check the existence of a valid file
- # in the server's `public/` directory (see Static#call).
+ # Used by the +Static+ class to check the existence of a valid file
+ # in the server's +public/+ directory (see Static#call).
def match?(path)
path = ::Rack::Utils.unescape_path path
return false unless ::Rack::Utils.valid_path? path
@@ -99,7 +99,7 @@ module ActionDispatch
# This middleware will attempt to return the contents of a file's body from
# disk in the response. If a file is not found on disk, the request will be
# delegated to the application stack. This middleware is commonly initialized
- # to serve assets from a server's `public/` directory.
+ # to serve assets from a server's +public/+ directory.
#
# This middleware verifies the path to ensure that only files
# living in the root directory can be rendered. A request cannot
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index 16a18a7f25..7662e164b8 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -16,6 +16,7 @@ module ActionDispatch
config.action_dispatch.signed_cookie_salt = "signed cookie"
config.action_dispatch.encrypted_cookie_salt = "encrypted cookie"
config.action_dispatch.encrypted_signed_cookie_salt = "signed encrypted cookie"
+ config.action_dispatch.use_authenticated_cookie_encryption = false
config.action_dispatch.perform_deep_munge = true
config.action_dispatch.default_headers = {
@@ -36,6 +37,8 @@ module ActionDispatch
ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses)
ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates)
+ config.action_dispatch.authenticated_encrypted_cookie_salt = "authenticated encrypted cookie" if config.action_dispatch.use_authenticated_cookie_encryption
+
config.action_dispatch.always_write_cookie = Rails.env.development? if config.action_dispatch.always_write_cookie.nil?
ActionDispatch::Cookies::CookieJar.always_write_cookie = config.action_dispatch.always_write_cookie
diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb
index 74ba6466cf..3547a8604f 100644
--- a/actionpack/lib/action_dispatch/request/session.rb
+++ b/actionpack/lib/action_dispatch/request/session.rb
@@ -101,11 +101,13 @@ module ActionDispatch
# Returns keys of the session as Array.
def keys
+ load_for_read!
@delegate.keys
end
# Returns values of the session as Array.
def values
+ load_for_read!
@delegate.values
end
diff --git a/actionpack/lib/action_dispatch/request/utils.rb b/actionpack/lib/action_dispatch/request/utils.rb
index 3615e4b1d8..4f79c4c21e 100644
--- a/actionpack/lib/action_dispatch/request/utils.rb
+++ b/actionpack/lib/action_dispatch/request/utils.rb
@@ -1,8 +1,7 @@
module ActionDispatch
class Request
class Utils # :nodoc:
- mattr_accessor :perform_deep_munge
- self.perform_deep_munge = true
+ mattr_accessor :perform_deep_munge, default: true
def self.each_param_value(params, &block)
case params
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 60d4789a63..87dd1eba38 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -254,14 +254,5 @@ module ActionDispatch
SEPARATORS = %w( / . ? ) #:nodoc:
HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] #:nodoc:
-
- #:stopdoc:
- INSECURE_URL_PARAMETERS_MESSAGE = <<-MSG.squish
- Attempting to generate a URL from non-sanitized request parameters!
-
- An attacker can inject malicious data into the generated URL, such as
- changing the host. Whitelist and sanitize passed parameters to be secure.
- MSG
- #:startdoc:
end
end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 8ad17504ae..d1c5b5a7ff 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -2,8 +2,8 @@ require "active_support/core_ext/hash/slice"
require "active_support/core_ext/enumerable"
require "active_support/core_ext/array/extract_options"
require "active_support/core_ext/regexp"
-require "action_dispatch/routing/redirection"
-require "action_dispatch/routing/endpoint"
+require_relative "redirection"
+require_relative "endpoint"
module ActionDispatch
module Routing
@@ -54,6 +54,7 @@ module ActionDispatch
class Mapping #:nodoc:
ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
+ OPTIONAL_FORMAT_REGEX = %r{(?:\(\.:format\)+|\.:format|/)\Z}
attr_reader :requirements, :defaults
attr_reader :to, :default_controller, :default_action
@@ -93,7 +94,7 @@ module ActionDispatch
end
def self.optional_format?(path, format)
- format != false && !path.include?(":format") && !path.end_with?("/")
+ format != false && path !~ OPTIONAL_FORMAT_REGEX
end
def initialize(set, ast, defaults, controller, default_action, modyoule, to, formatted, scope_constraints, blocks, via, options_constraints, anchor, options)
@@ -1836,7 +1837,7 @@ module ActionDispatch
path_types.fetch(String, []).each do |_path|
route_options = options.dup
if _path && option_path
- raise ArgumentError, "Ambigous route definition. Both :path and the route path where specified as strings."
+ raise ArgumentError, "Ambiguous route definition. Both :path and the route path where specified as strings."
end
to = get_to_from_path(_path, to, route_options[:action])
decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints)
@@ -2037,8 +2038,8 @@ module ActionDispatch
# { 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
+ # 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
@@ -2057,17 +2058,17 @@ module ActionDispatch
# [ :products, options.merge(params.permit(:page, :size).to_h.symbolize_keys) ]
# end
#
- # In this instance the `params` object comes from the context in which the the
+ # In this instance the +params+ object comes from the context in which the the
# block is executed, e.g. generating a URL inside a controller action or a view.
# If the block is executed where there isn't a params object such as this:
#
# Rails.application.routes.url_helpers.browse_path
#
- # then it will raise a `NameError`. Because of this you need to be aware of the
+ # then it will raise a +NameError+. Because of this you need to be aware of the
# context in which you will use your custom URL helper when defining it.
#
- # 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.
+ # 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"
@@ -2077,8 +2078,8 @@ module ActionDispatch
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:
+ # behavior of +polymorphic_url+ and consequently the behavior of
+ # +link_to+ and +form_for+ when passed a model instance, e.g:
#
# resource :basket
#
@@ -2086,8 +2087,8 @@ module ActionDispatch
# [: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".
+ # 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:
@@ -2104,7 +2105,7 @@ module ActionDispatch
# link_to "Profile", @current_user
# link_to "Profile", [:admin, @current_user]
#
- # The first `link_to` will generate "/profile" but the second will generate
+ # 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
@@ -2115,11 +2116,11 @@ module ActionDispatch
# 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
+ # 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.
+ # 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"
diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb
index 3bcb341758..6396b5031d 100644
--- a/actionpack/lib/action_dispatch/routing/redirection.rb
+++ b/actionpack/lib/action_dispatch/routing/redirection.rb
@@ -1,9 +1,9 @@
-require "action_dispatch/http/request"
+require_relative "../http/request"
require "active_support/core_ext/uri"
require "active_support/core_ext/array/extract_options"
require "rack/utils"
require "action_controller/metal/exceptions"
-require "action_dispatch/routing/endpoint"
+require_relative "endpoint"
module ActionDispatch
module Routing
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 129e90037e..ebe809f64e 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -1,11 +1,11 @@
-require "action_dispatch/journey"
+require_relative "../journey"
require "active_support/core_ext/object/to_query"
require "active_support/core_ext/hash/slice"
require "active_support/core_ext/module/remove_method"
require "active_support/core_ext/array/extract_options"
require "action_controller/metal/exceptions"
-require "action_dispatch/http/request"
-require "action_dispatch/routing/endpoint"
+require_relative "../http/request"
+require_relative "endpoint"
module ActionDispatch
module Routing
@@ -73,7 +73,6 @@ 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
@@ -96,23 +95,9 @@ module ActionDispatch
@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)
@@ -158,21 +143,29 @@ module ActionDispatch
routes.length
end
+ # Given a +name+, defines name_path and name_url helpers.
+ # Used by 'direct', 'resolve', and 'polymorphic' route helpers.
def add_url_helper(name, defaults, &block)
- @custom_helpers << name
helper = CustomUrlHelper.new(name, defaults, &block)
+ path_name = :"#{name}_path"
+ url_name = :"#{name}_url"
@path_helpers_module.module_eval do
- define_method(:"#{name}_path") do |*args|
+ define_method(path_name) do |*args|
helper.call(self, args, true)
end
end
@url_helpers_module.module_eval do
- define_method(:"#{name}_url") do |*args|
+ define_method(url_name) do |*args|
helper.call(self, args, false)
end
end
+
+ @path_helpers << path_name
+ @url_helpers << url_name
+
+ self
end
class UrlHelper
@@ -279,6 +272,8 @@ module ActionDispatch
if args.size < path_params_size
path_params -= controller_options.keys
path_params -= result.keys
+ else
+ path_params = path_params.dup
end
inner_options.each_key do |key|
path_params.delete(key)
@@ -318,11 +313,7 @@ module ActionDispatch
when Hash
args.pop
when ActionController::Parameters
- if last.permitted?
- args.pop.to_h
- else
- raise ArgumentError, ActionDispatch::Routing::INSECURE_URL_PARAMETERS_MESSAGE
- end
+ args.pop.to_h
end
helper.call self, args, options
end
diff --git a/actionpack/lib/action_dispatch/routing/routes_proxy.rb b/actionpack/lib/action_dispatch/routing/routes_proxy.rb
index ee847eaeed..c1423f770f 100644
--- a/actionpack/lib/action_dispatch/routing/routes_proxy.rb
+++ b/actionpack/lib/action_dispatch/routing/routes_proxy.rb
@@ -19,7 +19,8 @@ module ActionDispatch
end
end
- def respond_to_missing?(method, include_private = false)
+ private
+ def respond_to_missing?(method, _)
super || @helpers.respond_to?(method)
end
@@ -32,7 +33,7 @@ module ActionDispatch
@helpers.#{method}(*args)
end
RUBY
- send(method, *args)
+ public_send(method, *args)
else
super
end
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index 008216cc80..a9bdefa775 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -171,17 +171,10 @@ module ActionDispatch
case options
when nil
_routes.url_for(url_options.symbolize_keys)
- when Hash
+ when Hash, ActionController::Parameters
route_name = options.delete :use_route
- _routes.url_for(options.symbolize_keys.reverse_merge!(url_options),
- route_name)
- when ActionController::Parameters
- unless options.permitted?
- raise ArgumentError.new(ActionDispatch::Routing::INSECURE_URL_PARAMETERS_MESSAGE)
- end
- route_name = options.delete :use_route
- _routes.url_for(options.to_h.symbolize_keys.
- reverse_merge!(url_options), route_name)
+ merged_url_options = options.to_h.symbolize_keys.reverse_merge!(url_options)
+ _routes.url_for(merged_url_options, route_name)
when String
options
when Symbol
diff --git a/actionpack/lib/action_dispatch/system_test_case.rb b/actionpack/lib/action_dispatch/system_test_case.rb
index 98fdb36c91..23ce8d5a92 100644
--- a/actionpack/lib/action_dispatch/system_test_case.rb
+++ b/actionpack/lib/action_dispatch/system_test_case.rb
@@ -1,10 +1,11 @@
require "capybara/dsl"
require "capybara/minitest"
require "action_controller"
-require "action_dispatch/system_testing/driver"
-require "action_dispatch/system_testing/server"
-require "action_dispatch/system_testing/test_helpers/screenshot_helper"
-require "action_dispatch/system_testing/test_helpers/setup_and_teardown"
+require_relative "system_testing/driver"
+require_relative "system_testing/server"
+require_relative "system_testing/test_helpers/screenshot_helper"
+require_relative "system_testing/test_helpers/setup_and_teardown"
+require_relative "system_testing/test_helpers/undef_methods"
module ActionDispatch
# = System Testing
@@ -66,14 +67,18 @@ module ActionDispatch
#
# 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.
+ # +application_system_test_case.rb+ file. In this case, you would leave out
+ # the +:using+ option because the driver is headless, but you can still use
+ # +:screen_size+ to change the size of the browser screen, also you can use
+ # +:options+ to pass options supported by the driver. Please refer to your
+ # driver documentation to learn about supported options.
#
# require "test_helper"
# require "capybara/poltergeist"
#
# class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
- # driven_by :poltergeist
+ # driven_by :poltergeist, screen_size: [1400, 1400], options:
+ # { js_errors: true }
# end
#
# Because <tt>ActionDispatch::SystemTestCase</tt> is a shim between Capybara
@@ -84,6 +89,7 @@ module ActionDispatch
include Capybara::Minitest::Assertions
include SystemTesting::TestHelpers::SetupAndTeardown
include SystemTesting::TestHelpers::ScreenshotHelper
+ include SystemTesting::TestHelpers::UndefMethods
def initialize(*) # :nodoc:
super
diff --git a/actionpack/lib/action_dispatch/system_testing/driver.rb b/actionpack/lib/action_dispatch/system_testing/driver.rb
index 5cf17883f7..1a027f2e23 100644
--- a/actionpack/lib/action_dispatch/system_testing/driver.rb
+++ b/actionpack/lib/action_dispatch/system_testing/driver.rb
@@ -9,23 +9,42 @@ module ActionDispatch
end
def use
- register if selenium?
+ register unless rack_test?
+
setup
end
private
- def selenium?
- @name == :selenium
+ def rack_test?
+ @name == :rack_test
end
def register
Capybara.register_driver @name do |app|
- Capybara::Selenium::Driver.new(app, { browser: @browser }.merge(@options)).tap do |driver|
- driver.browser.manage.window.size = Selenium::WebDriver::Dimension.new(*@screen_size)
+ case @name
+ when :selenium then register_selenium(app)
+ when :poltergeist then register_poltergeist(app)
+ when :webkit then register_webkit(app)
end
end
end
+ def register_selenium(app)
+ Capybara::Selenium::Driver.new(app, { browser: @browser }.merge(@options)).tap do |driver|
+ driver.browser.manage.window.size = Selenium::WebDriver::Dimension.new(*@screen_size)
+ end
+ end
+
+ def register_poltergeist(app)
+ Capybara::Poltergeist::Driver.new(app, @options.merge(window_size: @screen_size))
+ end
+
+ def register_webkit(app)
+ Capybara::Webkit::Driver.new(app, Capybara::Webkit::Configuration.to_hash.merge(@options)).tap do |driver|
+ driver.resize_window(*@screen_size)
+ end
+ end
+
def setup
Capybara.current_driver = @name
end
diff --git a/actionpack/lib/action_dispatch/system_testing/server.rb b/actionpack/lib/action_dispatch/system_testing/server.rb
index 4a214ef713..89ca6944d9 100644
--- a/actionpack/lib/action_dispatch/system_testing/server.rb
+++ b/actionpack/lib/action_dispatch/system_testing/server.rb
@@ -3,6 +3,12 @@ require "rack/handler/puma"
module ActionDispatch
module SystemTesting
class Server # :nodoc:
+ class << self
+ attr_accessor :silence_puma
+ end
+
+ self.silence_puma = false
+
def run
register
setup
@@ -11,7 +17,12 @@ module ActionDispatch
private
def register
Capybara.register_server :rails_puma do |app, port, host|
- Rack::Handler::Puma.run(app, Port: port, Threads: "0:1")
+ Rack::Handler::Puma.run(
+ app,
+ Port: port,
+ Threads: "0:1",
+ Silent: self.class.silence_puma
+ )
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
index 187ba2cc5f..f03f0d4299 100644
--- 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
@@ -2,7 +2,12 @@ module ActionDispatch
module SystemTesting
module TestHelpers
module SetupAndTeardown # :nodoc:
- DEFAULT_HOST = "127.0.0.1"
+ DEFAULT_HOST = "http://127.0.0.1"
+
+ def host!(host)
+ super
+ Capybara.app_host = host
+ end
def before_setup
host! DEFAULT_HOST
diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb
new file mode 100644
index 0000000000..2d3f4662d7
--- /dev/null
+++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb
@@ -0,0 +1,24 @@
+module ActionDispatch
+ module SystemTesting
+ module TestHelpers
+ module UndefMethods # :nodoc:
+ extend ActiveSupport::Concern
+ included do
+ METHODS = %i(get post put patch delete).freeze
+
+ METHODS.each do |verb|
+ undef_method verb
+ end
+
+ def method_missing(method, *args, &block)
+ if METHODS.include?(method)
+ raise NoMethodError
+ else
+ super
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index 1baf979ac9..749f2eab57 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -79,7 +79,7 @@ module ActionDispatch
def generate_response_message(expected, actual = @response.response_code)
"Expected response to be a <#{code_with_name(expected)}>,"\
" but was a <#{code_with_name(actual)}>"
- .concat(location_if_redirected).concat(response_body_if_short)
+ .dup.concat(location_if_redirected).concat(response_body_if_short)
end
def response_body_if_short
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 2e2db98ad6..beba4e3c36 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -5,7 +5,7 @@ require "active_support/core_ext/object/try"
require "rack/test"
require "minitest"
-require "action_dispatch/testing/request_encoder"
+require_relative "request_encoder"
module ActionDispatch
module Integration #:nodoc:
@@ -338,8 +338,7 @@ module ActionDispatch
@integration_session = nil
end
- %w(get post patch put head delete cookies assigns
- xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
+ %w(get post patch put head delete cookies assigns follow_redirect!).each do |method|
define_method(method) do |*args|
# reset the html_document variable, except for cookies/assigns calls
unless method == "cookies" || method == "assigns"
@@ -385,14 +384,15 @@ module ActionDispatch
integration_session.default_url_options = options
end
- def respond_to_missing?(method, include_private = false)
- integration_session.respond_to?(method, include_private) || super
+ private
+ def respond_to_missing?(method, _)
+ integration_session.respond_to?(method) || super
end
# Delegate unhandled messages to the current session instance.
- def method_missing(sym, *args, &block)
- if integration_session.respond_to?(sym)
- integration_session.__send__(sym, *args, &block).tap do
+ def method_missing(method, *args, &block)
+ if integration_session.respond_to?(method)
+ integration_session.public_send(method, *args, &block).tap do
copy_session_variables!
end
else
diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb
index 0282eb15c3..8061ac2e3b 100644
--- a/actionpack/lib/action_dispatch/testing/test_process.rb
+++ b/actionpack/lib/action_dispatch/testing/test_process.rb
@@ -1,5 +1,5 @@
-require "action_dispatch/middleware/cookies"
-require "action_dispatch/middleware/flash"
+require_relative "../middleware/cookies"
+require_relative "../middleware/flash"
module ActionDispatch
module TestProcess
diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb
index 5c89f9c75e..2a1a9ffce9 100644
--- a/actionpack/lib/action_dispatch/testing/test_response.rb
+++ b/actionpack/lib/action_dispatch/testing/test_response.rb
@@ -1,4 +1,4 @@
-require "action_dispatch/testing/request_encoder"
+require_relative "request_encoder"
module ActionDispatch
# Integration test methods such as ActionDispatch::Integration::Session#get
diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb
index eec622e085..6a74baff09 100644
--- a/actionpack/lib/action_pack.rb
+++ b/actionpack/lib/action_pack.rb
@@ -21,4 +21,4 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
-require "action_pack/version"
+require_relative "action_pack/version"
diff --git a/actionpack/test/abstract/translation_test.rb b/actionpack/test/abstract/translation_test.rb
index 0c4071df8d..4893144905 100644
--- a/actionpack/test/abstract/translation_test.rb
+++ b/actionpack/test/abstract/translation_test.rb
@@ -62,6 +62,7 @@ module AbstractController
def test_default_translation
@controller.stub :action_name, :index do
assert_equal "bar", @controller.t("one.two")
+ assert_equal "baz", @controller.t(".twoz", default: ["baz", :twoz])
end
end
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 4185ce1a1f..bd118b46be 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -1,6 +1,6 @@
-$:.unshift(File.dirname(__FILE__) + "/lib")
-$:.unshift(File.dirname(__FILE__) + "/fixtures/helpers")
-$:.unshift(File.dirname(__FILE__) + "/fixtures/alternate_helpers")
+$:.unshift File.expand_path("lib", __dir__)
+$:.unshift File.expand_path("fixtures/helpers", __dir__)
+$:.unshift File.expand_path("fixtures/alternate_helpers", __dir__)
require "active_support/core_ext/kernel/reporting"
@@ -56,7 +56,7 @@ ActiveSupport::Deprecation.debug = true
# Disable available locale checks to avoid warnings running the test suite.
I18n.enforce_available_locales = false
-FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), "fixtures")
+FIXTURE_LOAD_PATH = File.join(__dir__, "fixtures")
SharedTestRoutes = ActionDispatch::Routing::RouteSet.new
@@ -156,7 +156,7 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
end
def with_autoload_path(path)
- path = File.join(File.dirname(__FILE__), "fixtures", path)
+ path = File.join(__dir__, "fixtures", path)
if ActiveSupport::Dependencies.autoload_paths.include?(path)
yield
else
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index 9ab152fc5c..73aab5848b 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -83,7 +83,7 @@ class ActionPackAssertionsController < ActionController::Base
end
def render_file_absolute_path
- render file: File.expand_path("../../../README.rdoc", __FILE__)
+ render file: File.expand_path("../../README.rdoc", __dir__)
end
def render_file_relative_path
diff --git a/actionpack/test/controller/api/data_streaming_test.rb b/actionpack/test/controller/api/data_streaming_test.rb
index f15b78d102..e6419b9adf 100644
--- a/actionpack/test/controller/api/data_streaming_test.rb
+++ b/actionpack/test/controller/api/data_streaming_test.rb
@@ -1,7 +1,7 @@
require "abstract_unit"
module TestApiFileUtils
- def file_path() File.expand_path(__FILE__) end
+ def file_path() __FILE__ end
def file_data() @data ||= File.open(file_path, "rb") { |f| f.read } end
end
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index fa8d9dc09a..c86dcafee5 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -4,7 +4,7 @@ require "lib/controller/fake_models"
CACHE_DIR = "test_cache"
# Don't change '/../temp/' cavalierly or you might hose something you don't want hosed
-FILE_STORE_PATH = File.join(File.dirname(__FILE__), "/../temp/", CACHE_DIR)
+FILE_STORE_PATH = File.join(__dir__, "../temp/", CACHE_DIR)
class FragmentCachingMetalTestController < ActionController::Metal
abstract!
@@ -26,10 +26,6 @@ class FragmentCachingMetalTest < ActionController::TestCase
@controller.request = @request
@controller.response = @response
end
-
- def test_fragment_cache_key
- assert_equal "views/what a key", @controller.fragment_cache_key("what a key")
- end
end
class CachingController < ActionController::Base
@@ -43,6 +39,8 @@ class FragmentCachingTestController < CachingController
end
class FragmentCachingTest < ActionController::TestCase
+ ModelWithKeyAndVersion = Struct.new(:cache_key, :cache_version)
+
def setup
super
@store = ActiveSupport::Cache::MemoryStore.new
@@ -53,12 +51,25 @@ class FragmentCachingTest < ActionController::TestCase
@controller.params = @params
@controller.request = @request
@controller.response = @response
+
+ @m1v1 = ModelWithKeyAndVersion.new("model/1", "1")
+ @m1v2 = ModelWithKeyAndVersion.new("model/1", "2")
+ @m2v1 = ModelWithKeyAndVersion.new("model/2", "1")
+ @m2v2 = ModelWithKeyAndVersion.new("model/2", "2")
end
def test_fragment_cache_key
- assert_equal "views/what a key", @controller.fragment_cache_key("what a key")
- assert_equal "views/test.host/fragment_caching_test/some_action",
- @controller.fragment_cache_key(controller: "fragment_caching_test", action: "some_action")
+ assert_deprecated do
+ assert_equal "views/what a key", @controller.fragment_cache_key("what a key")
+ assert_equal "views/test.host/fragment_caching_test/some_action",
+ @controller.fragment_cache_key(controller: "fragment_caching_test", action: "some_action")
+ end
+ end
+
+ def test_combined_fragment_cache_key
+ assert_equal [ :views, "what a key" ], @controller.combined_fragment_cache_key("what a key")
+ assert_equal [ :views, "test.host/fragment_caching_test/some_action" ],
+ @controller.combined_fragment_cache_key(controller: "fragment_caching_test", action: "some_action")
end
def test_read_fragment_with_caching_enabled
@@ -72,6 +83,12 @@ class FragmentCachingTest < ActionController::TestCase
assert_nil @controller.read_fragment("name")
end
+ def test_read_fragment_with_versioned_model
+ @controller.write_fragment([ "stuff", @m1v1 ], "hello")
+ assert_equal "hello", @controller.read_fragment([ "stuff", @m1v1 ])
+ assert_nil @controller.read_fragment([ "stuff", @m1v2 ])
+ end
+
def test_fragment_exist_with_caching_enabled
@store.write("views/name", "value")
assert @controller.fragment_exist?("name")
@@ -198,7 +215,7 @@ CACHED
assert_equal expected_body, @response.body
assert_equal "This bit's fragment cached",
- @store.read("views/test.host/functional_caching/fragment_cached/#{template_digest("functional_caching/fragment_cached")}")
+ @store.read("views/functional_caching/fragment_cached:#{template_digest("functional_caching/fragment_cached")}/fragment")
end
def test_fragment_caching_in_partials
@@ -207,7 +224,7 @@ CACHED
assert_match(/Old fragment caching in a partial/, @response.body)
assert_match("Old fragment caching in a partial",
- @store.read("views/test.host/functional_caching/html_fragment_cached_with_partial/#{template_digest("functional_caching/_partial")}"))
+ @store.read("views/functional_caching/_partial:#{template_digest("functional_caching/_partial")}/test.host/functional_caching/html_fragment_cached_with_partial"))
end
def test_skipping_fragment_cache_digesting
@@ -237,7 +254,7 @@ CACHED
assert_match(/Some inline content/, @response.body)
assert_match(/Some cached content/, @response.body)
assert_match("Some cached content",
- @store.read("views/test.host/functional_caching/inline_fragment_cached/#{template_digest("functional_caching/inline_fragment_cached")}"))
+ @store.read("views/functional_caching/inline_fragment_cached:#{template_digest("functional_caching/inline_fragment_cached")}/test.host/functional_caching/inline_fragment_cached"))
end
def test_fragment_cache_instrumentation
@@ -264,7 +281,7 @@ CACHED
assert_equal expected_body, @response.body
assert_equal "<p>ERB</p>",
- @store.read("views/test.host/functional_caching/formatted_fragment_cached/#{template_digest("functional_caching/formatted_fragment_cached")}")
+ @store.read("views/functional_caching/formatted_fragment_cached:#{template_digest("functional_caching/formatted_fragment_cached")}/fragment")
end
def test_xml_formatted_fragment_caching
@@ -275,7 +292,7 @@ CACHED
assert_equal expected_body, @response.body
assert_equal " <p>Builder</p>\n",
- @store.read("views/test.host/functional_caching/formatted_fragment_cached/#{template_digest("functional_caching/formatted_fragment_cached")}")
+ @store.read("views/functional_caching/formatted_fragment_cached:#{template_digest("functional_caching/formatted_fragment_cached")}/fragment")
end
def test_fragment_caching_with_variant
@@ -286,7 +303,7 @@ CACHED
assert_equal expected_body, @response.body
assert_equal "<p>PHONE</p>",
- @store.read("views/test.host/functional_caching/formatted_fragment_cached_with_variant/#{template_digest("functional_caching/formatted_fragment_cached_with_variant")}")
+ @store.read("views/functional_caching/formatted_fragment_cached_with_variant:#{template_digest("functional_caching/formatted_fragment_cached_with_variant")}/fragment")
end
private
@@ -412,7 +429,7 @@ class CollectionCacheTest < ActionController::TestCase
def test_collection_fetches_cached_views
get :index
assert_equal 1, @controller.partial_rendered_times
- assert_customer_cached "david/1", "david, 1"
+ assert_match "david, 1", ActionView::PartialRenderer.collection_cache.read("views/customers/_customer:7c228ab609f0baf0b1f2367469210937/david/1")
get :index
assert_equal 1, @controller.partial_rendered_times
@@ -444,14 +461,8 @@ class CollectionCacheTest < ActionController::TestCase
def test_caching_with_callable_cache_key
get :index_with_callable_cache_key
- assert_customer_cached "cached_david", "david, 1"
+ assert_match "david, 1", ActionView::PartialRenderer.collection_cache.read("views/customers/_customer:7c228ab609f0baf0b1f2367469210937/cached_david")
end
-
- private
- def assert_customer_cached(key, content)
- assert_match content,
- ActionView::PartialRenderer.collection_cache.read("views/#{key}/7c228ab609f0baf0b1f2367469210937")
- end
end
class FragmentCacheKeyTestController < CachingController
@@ -470,11 +481,21 @@ class FragmentCacheKeyTest < ActionController::TestCase
@controller.cache_store = @store
end
- def test_fragment_cache_key
+ def test_combined_fragment_cache_key
@controller.account_id = "123"
- assert_equal "views/v1/123/what a key", @controller.fragment_cache_key("what a key")
+ assert_equal [ :views, "v1", "123", "what a key" ], @controller.combined_fragment_cache_key("what a key")
@controller.account_id = nil
- assert_equal "views/v1//what a key", @controller.fragment_cache_key("what a key")
+ assert_equal [ :views, "v1", "what a key" ], @controller.combined_fragment_cache_key("what a key")
+ end
+
+ def test_combined_fragment_cache_key_with_envs
+ ENV["RAILS_APP_VERSION"] = "55"
+ assert_equal [ :views, "55", "v1", "what a key" ], @controller.combined_fragment_cache_key("what a key")
+
+ ENV["RAILS_CACHE_ID"] = "66"
+ assert_equal [ :views, "66", "v1", "what a key" ], @controller.combined_fragment_cache_key("what a key")
+ ensure
+ ENV["RAILS_CACHE_ID"] = ENV["RAILS_APP_VERSION"] = nil
end
end
diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb
index 4c6a772062..03dbd63614 100644
--- a/actionpack/test/controller/helper_test.rb
+++ b/actionpack/test/controller/helper_test.rb
@@ -1,6 +1,6 @@
require "abstract_unit"
-ActionController::Base.helpers_path = File.expand_path("../../fixtures/helpers", __FILE__)
+ActionController::Base.helpers_path = File.expand_path("../fixtures/helpers", __dir__)
module Fun
class GamesController < ActionController::Base
@@ -48,7 +48,7 @@ end
class HelpersPathsController < ActionController::Base
paths = ["helpers2_pack", "helpers1_pack"].map do |path|
- File.join(File.expand_path("../../fixtures", __FILE__), path)
+ File.join(File.expand_path("../fixtures", __dir__), path)
end
$:.unshift(*paths)
@@ -61,7 +61,7 @@ class HelpersPathsController < ActionController::Base
end
class HelpersTypoController < ActionController::Base
- path = File.expand_path("../../fixtures/helpers_typo", __FILE__)
+ path = File.expand_path("../fixtures/helpers_typo", __dir__)
$:.unshift(path)
self.helpers_path = path
end
@@ -178,7 +178,7 @@ class HelperTest < ActiveSupport::TestCase
end
def test_all_helpers_with_alternate_helper_dir
- @controller_class.helpers_path = File.expand_path("../../fixtures/alternate_helpers", __FILE__)
+ @controller_class.helpers_path = File.expand_path("../fixtures/alternate_helpers", __dir__)
# Reload helpers
@controller_class._helpers = Module.new
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index 57f58fd835..cb282d4330 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -335,6 +335,18 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
end
end
+ def test_redirect_reset_html_document
+ with_test_route_set do
+ get "/redirect"
+ previous_html_document = html_document
+
+ follow_redirect!
+
+ assert_response :ok
+ refute_same previous_html_document, html_document
+ end
+ end
+
def test_xml_http_request_get
with_test_route_set do
get "/get", xhr: true
@@ -1091,7 +1103,7 @@ class IntegrationFileUploadTest < ActionDispatch::IntegrationTest
end
def self.fixture_path
- File.dirname(__FILE__) + "/../fixtures/multipart"
+ File.expand_path("../fixtures/multipart", __dir__)
end
routes.draw do
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
index 581081dd07..bfb47b90d5 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -152,7 +152,7 @@ module ActionController
end
def write_sleep_autoload
- path = File.join(File.dirname(__FILE__), "../fixtures")
+ path = File.expand_path("../fixtures", __dir__)
ActiveSupport::Dependencies.autoload_paths << path
response.headers["Content-Type"] = "text/event-stream"
diff --git a/actionpack/test/controller/mime/accept_format_test.rb b/actionpack/test/controller/mime/accept_format_test.rb
index a22fa39051..d1c4dbfef7 100644
--- a/actionpack/test/controller/mime/accept_format_test.rb
+++ b/actionpack/test/controller/mime/accept_format_test.rb
@@ -29,7 +29,7 @@ class StarStarMimeControllerTest < ActionController::TestCase
end
class AbstractPostController < ActionController::Base
- self.view_paths = File.dirname(__FILE__) + "/../../fixtures/post_test/"
+ self.view_paths = File.expand_path("../../fixtures/post_test", __dir__)
end
# For testing layouts which are set automatically
diff --git a/actionpack/test/controller/new_base/render_file_test.rb b/actionpack/test/controller/new_base/render_file_test.rb
index 6d651e0104..4491dd96ed 100644
--- a/actionpack/test/controller/new_base/render_file_test.rb
+++ b/actionpack/test/controller/new_base/render_file_test.rb
@@ -2,15 +2,15 @@ require "abstract_unit"
module RenderFile
class BasicController < ActionController::Base
- self.view_paths = File.dirname(__FILE__)
+ self.view_paths = __dir__
def index
- render file: File.join(File.dirname(__FILE__), *%w[.. .. fixtures test hello_world])
+ render file: File.expand_path("../../fixtures/test/hello_world", __dir__)
end
def with_instance_variables
@secret = "in the sauce"
- render file: File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_ivar")
+ render file: File.expand_path("../../fixtures/test/render_file_with_ivar", __dir__)
end
def relative_path
@@ -25,11 +25,11 @@ module RenderFile
def pathname
@secret = "in the sauce"
- render file: Pathname.new(File.dirname(__FILE__)).join(*%w[.. .. fixtures test dot.directory render_file_with_ivar])
+ render file: Pathname.new(__dir__).join(*%w[.. .. fixtures test dot.directory render_file_with_ivar])
end
def with_locals
- path = File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_locals")
+ path = File.expand_path("../../fixtures/test/render_file_with_locals", __dir__)
render file: path, locals: { secret: "in the sauce" }
end
end
diff --git a/actionpack/test/controller/new_base/render_implicit_action_test.rb b/actionpack/test/controller/new_base/render_implicit_action_test.rb
index 796283466a..c5fc8e15e1 100644
--- a/actionpack/test/controller/new_base/render_implicit_action_test.rb
+++ b/actionpack/test/controller/new_base/render_implicit_action_test.rb
@@ -6,7 +6,7 @@ module RenderImplicitAction
"render_implicit_action/simple/hello_world.html.erb" => "Hello world!",
"render_implicit_action/simple/hyphen-ated.html.erb" => "Hello hyphen-ated!",
"render_implicit_action/simple/not_implemented.html.erb" => "Not Implemented"
- ), ActionView::FileSystemResolver.new(File.expand_path("../../../controller", __FILE__))]
+ ), ActionView::FileSystemResolver.new(File.expand_path("../../controller", __dir__))]
def hello_world() end
end
diff --git a/actionpack/test/controller/parameters/accessors_test.rb b/actionpack/test/controller/parameters/accessors_test.rb
index 7725c25e22..87407a4272 100644
--- a/actionpack/test/controller/parameters/accessors_test.rb
+++ b/actionpack/test/controller/parameters/accessors_test.rb
@@ -35,6 +35,11 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
assert @params.as_json.key? "person"
end
+ test "to_s returns the string representation of the parameters hash" do
+ assert_equal '{"person"=>{"age"=>"32", "name"=>{"first"=>"David", "last"=>"Heinemeier Hansson"}, ' \
+ '"addresses"=>[{"city"=>"Chicago", "state"=>"Illinois"}]}}', @params.to_s
+ end
+
test "each carries permitted status" do
@params.permit!
@params.each { |key, value| assert(value.permitted?) if key == "person" }
diff --git a/actionpack/test/controller/parameters/mutators_test.rb b/actionpack/test/controller/parameters/mutators_test.rb
index e61bbdbe13..3fe7340782 100644
--- a/actionpack/test/controller/parameters/mutators_test.rb
+++ b/actionpack/test/controller/parameters/mutators_test.rb
@@ -25,6 +25,27 @@ class ParametersMutatorsTest < ActiveSupport::TestCase
assert_not @params.delete(:person).permitted?
end
+ test "delete returns the value when the key is present" do
+ assert_equal "32", @params[:person].delete(:age)
+ end
+
+ test "delete removes the entry when the key present" do
+ @params[:person].delete(:age)
+ assert_not @params[:person].key?(:age)
+ end
+
+ test "delete returns nil when the key is not present" do
+ assert_nil @params[:person].delete(:first_name)
+ end
+
+ test "delete returns the value of the given block when the key is not present" do
+ assert_equal "David", @params[:person].delete(:first_name) { "David" }
+ end
+
+ test "delete yields the key to the given block when the key is not present" do
+ assert_equal "first_name: David", @params[:person].delete(:first_name) { |k| "#{k}: David" }
+ end
+
test "delete_if retains permitted status" do
@params.permit!
assert @params.delete_if { |k| k == "person" }.permitted?
diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb
index 3e067314d6..ae2b45c9f0 100644
--- a/actionpack/test/controller/parameters/parameters_permit_test.rb
+++ b/actionpack/test/controller/parameters/parameters_permit_test.rb
@@ -377,17 +377,17 @@ class ParametersPermitTest < ActiveSupport::TestCase
assert_equal "32", @params[:person].permit([ :age ])[:age]
end
- test "to_h returns empty hash on unpermitted params" do
- assert @params.to_h.is_a? ActiveSupport::HashWithIndifferentAccess
- assert_not @params.to_h.is_a? ActionController::Parameters
- assert @params.to_h.empty?
+ test "to_h raises UnfilteredParameters on unfiltered params" do
+ assert_raises(ActionController::UnfilteredParameters) do
+ @params.to_h
+ end
end
test "to_h returns converted hash on permitted params" do
@params.permit!
- assert @params.to_h.is_a? ActiveSupport::HashWithIndifferentAccess
- assert_not @params.to_h.is_a? ActionController::Parameters
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, @params.to_h
+ assert_not_kind_of ActionController::Parameters, @params.to_h
end
test "to_h returns converted hash when .permit_all_parameters is set" do
@@ -395,39 +395,71 @@ class ParametersPermitTest < ActiveSupport::TestCase
ActionController::Parameters.permit_all_parameters = true
params = ActionController::Parameters.new(crab: "Senjougahara Hitagi")
- assert params.to_h.is_a? ActiveSupport::HashWithIndifferentAccess
- assert_not @params.to_h.is_a? ActionController::Parameters
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, params.to_h
+ assert_not_kind_of ActionController::Parameters, params.to_h
assert_equal({ "crab" => "Senjougahara Hitagi" }, params.to_h)
ensure
ActionController::Parameters.permit_all_parameters = false
end
end
- test "to_h returns always permitted parameter on unpermitted params" do
- params = ActionController::Parameters.new(
- controller: "users",
- action: "create",
- user: {
- name: "Sengoku Nadeko"
- }
- )
+ test "to_hash raises UnfilteredParameters on unfiltered params" do
+ assert_raises(ActionController::UnfilteredParameters) do
+ @params.to_hash
+ end
+ end
+
+ test "to_hash returns converted hash on permitted params" do
+ @params.permit!
+
+ assert_instance_of Hash, @params.to_hash
+ assert_not_kind_of ActionController::Parameters, @params.to_hash
+ end
+
+ test "parameters can be implicit converted to Hash" do
+ params = ActionController::Parameters.new
+ params.permit!
+
+ assert_equal({ a: 1 }, { a: 1 }.merge!(params))
+ end
- assert_equal({ "controller" => "users", "action" => "create" }, params.to_h)
+ test "to_hash returns converted hash when .permit_all_parameters is set" do
+ begin
+ ActionController::Parameters.permit_all_parameters = true
+ params = ActionController::Parameters.new(crab: "Senjougahara Hitagi")
+
+ assert_instance_of Hash, params.to_hash
+ assert_not_kind_of ActionController::Parameters, params.to_hash
+ assert_equal({ "crab" => "Senjougahara Hitagi" }, params.to_hash)
+ assert_equal({ "crab" => "Senjougahara Hitagi" }, params)
+ ensure
+ ActionController::Parameters.permit_all_parameters = false
+ end
end
test "to_unsafe_h returns unfiltered params" do
- assert @params.to_unsafe_h.is_a? ActiveSupport::HashWithIndifferentAccess
- assert_not @params.to_unsafe_h.is_a? ActionController::Parameters
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, @params.to_unsafe_h
+ assert_not_kind_of ActionController::Parameters, @params.to_unsafe_h
end
test "to_unsafe_h returns unfiltered params even after accessing few keys" do
params = ActionController::Parameters.new("f" => { "language_facet" => ["Tibetan"] })
expected = { "f" => { "language_facet" => ["Tibetan"] } }
- assert params["f"].is_a? ActionController::Parameters
+ assert_instance_of ActionController::Parameters, params["f"]
assert_equal expected, params.to_unsafe_h
end
+ test "to_unsafe_h does not mutate the parameters" do
+ params = ActionController::Parameters.new("f" => { "language_facet" => ["Tibetan"] })
+ params[:f]
+
+ params.to_unsafe_h
+
+ assert_not_predicate params, :permitted?
+ assert_not_predicate params[:f], :permitted?
+ end
+
test "to_h only deep dups Ruby collections" do
company = Class.new do
attr_reader :dupped
diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb
index 2a41d57b26..c0f01e6df8 100644
--- a/actionpack/test/controller/params_wrapper_test.rb
+++ b/actionpack/test/controller/params_wrapper_test.rb
@@ -170,6 +170,14 @@ class ParamsWrapperTest < ActionController::TestCase
end
end
+ def test_no_double_wrap_if_key_exists_and_value_is_nil
+ with_default_wrapper_options do
+ @request.env["CONTENT_TYPE"] = "application/json"
+ post :parse, params: { "user" => nil }
+ assert_parameters("user" => nil)
+ end
+ end
+
def test_nested_params
with_default_wrapper_options do
@request.env["CONTENT_TYPE"] = "application/json"
@@ -218,6 +226,14 @@ class ParamsWrapperTest < ActionController::TestCase
end
end
+ def test_preserves_query_string_params_in_filtered_params
+ with_default_wrapper_options do
+ @request.env["CONTENT_TYPE"] = "application/json"
+ get :parse, params: { "user" => { "username" => "nixon" } }
+ assert_equal({ "controller" => "params_wrapper_test/users", "action" => "parse", "user" => { "username" => "nixon" } }, @request.filtered_parameters)
+ end
+ end
+
def test_empty_parameter_set
with_default_wrapper_options do
@request.env["CONTENT_TYPE"] = "application/json"
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index f06a1f4d23..5b16af78c4 100644
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -285,10 +285,10 @@ class RedirectTest < ActionController::TestCase
end
def test_redirect_to_params
- error = assert_raise(ArgumentError) do
+ error = assert_raise(ActionController::UnfilteredParameters) do
get :redirect_to_params
end
- assert_equal ActionDispatch::Routing::INSECURE_URL_PARAMETERS_MESSAGE, error.message
+ assert_equal "unable to convert unpermitted parameters to hash", error.message
end
def test_redirect_to_with_block
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 3a0a0a8bde..17d834d55f 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -257,7 +257,7 @@ end
module TemplateModificationHelper
private
def modify_template(name)
- path = File.expand_path("../../fixtures/#{name}.erb", __FILE__)
+ path = File.expand_path("../fixtures/#{name}.erb", __dir__)
original = File.read(path)
File.write(path, "#{original} Modified!")
ActionView::LookupContext::DetailsKey.clear
@@ -287,9 +287,9 @@ class ExpiresInRenderTest < ActionController::TestCase
def test_dynamic_render_with_file
# This is extremely bad, but should be possible to do.
- assert File.exist?(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb"))
+ assert File.exist?(File.expand_path("../../test/abstract_unit.rb", __dir__))
response = get :dynamic_render_with_file, params: { id: '../\\../test/abstract_unit.rb' }
- assert_equal File.read(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb")),
+ assert_equal File.read(File.expand_path("../../test/abstract_unit.rb", __dir__)),
response.body
end
@@ -306,16 +306,16 @@ class ExpiresInRenderTest < ActionController::TestCase
end
def test_dynamic_render
- assert File.exist?(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb"))
+ assert File.exist?(File.expand_path("../../test/abstract_unit.rb", __dir__))
assert_raises ActionView::MissingTemplate do
get :dynamic_render, params: { id: '../\\../test/abstract_unit.rb' }
end
end
def test_permitted_dynamic_render_file_hash
- assert File.exist?(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb"))
+ assert File.exist?(File.expand_path("../../test/abstract_unit.rb", __dir__))
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")),
+ assert_equal File.read(File.expand_path("../../test/abstract_unit.rb", __dir__)),
response.body
end
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index d645ddfdbe..521d93f02e 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -35,6 +35,22 @@ module RequestForgeryProtectionActions
render inline: "<%= form_for(:some_resource, :remote => true, :authenticity_token => 'external_token') {} %>"
end
+ def form_with_remote
+ render inline: "<%= form_with(scope: :some_resource) {} %>"
+ end
+
+ def form_with_remote_with_token
+ render inline: "<%= form_with(scope: :some_resource, authenticity_token: true) {} %>"
+ end
+
+ def form_with_local_with_token
+ render inline: "<%= form_with(scope: :some_resource, local: true, authenticity_token: true) {} %>"
+ end
+
+ def form_with_remote_with_external_token
+ render inline: "<%= form_with(scope: :some_resource, authenticity_token: 'external_token') {} %>"
+ end
+
def same_origin_js
render js: "foo();"
end
@@ -235,6 +251,80 @@ module RequestForgeryProtectionTests
end
end
+ def test_should_render_form_with_with_token_tag_if_remote
+ assert_not_blocked do
+ get :form_with_remote
+ end
+ assert_match(/authenticity_token/, response.body)
+ end
+
+ def test_should_render_form_with_without_token_tag_if_remote_and_embedding_token_is_off
+ original = ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms
+ begin
+ ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = false
+ assert_not_blocked do
+ get :form_with_remote
+ end
+ assert_no_match(/authenticity_token/, response.body)
+ ensure
+ ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = original
+ end
+ end
+
+ def test_should_render_form_with_with_token_tag_if_remote_and_external_authenticity_token_requested_and_embedding_is_on
+ original = ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms
+ begin
+ ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = true
+ assert_not_blocked do
+ get :form_with_remote_with_external_token
+ end
+ assert_select "form>input[name=?][value=?]", "custom_authenticity_token", "external_token"
+ ensure
+ ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = original
+ end
+ end
+
+ def test_should_render_form_with_with_token_tag_if_remote_and_external_authenticity_token_requested
+ assert_not_blocked do
+ get :form_with_remote_with_external_token
+ end
+ assert_select "form>input[name=?][value=?]", "custom_authenticity_token", "external_token"
+ end
+
+ def test_should_render_form_with_with_token_tag_if_remote_and_authenticity_token_requested
+ @controller.stub :form_authenticity_token, @token do
+ assert_not_blocked do
+ get :form_with_remote_with_token
+ end
+ assert_select "form>input[name=?][value=?]", "custom_authenticity_token", @token
+ end
+ end
+
+ def test_should_render_form_with_with_token_tag_with_authenticity_token_requested
+ @controller.stub :form_authenticity_token, @token do
+ assert_not_blocked do
+ get :form_with_local_with_token
+ end
+ assert_select "form>input[name=?][value=?]", "custom_authenticity_token", @token
+ end
+ end
+
+ def test_should_render_form_with_with_token_tag_if_remote_and_embedding_token_is_on
+ original = ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms
+ begin
+ ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = true
+
+ @controller.stub :form_authenticity_token, @token do
+ assert_not_blocked do
+ get :form_with_remote
+ end
+ end
+ assert_select "form>input[name=?][value=?]", "custom_authenticity_token", @token
+ ensure
+ ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = original
+ end
+ end
+
def test_should_allow_get
assert_not_blocked { get :index }
end
@@ -347,6 +437,10 @@ module RequestForgeryProtectionTests
end
def test_should_block_post_with_origin_checking_and_wrong_origin
+ old_logger = ActionController::Base.logger
+ logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
+ ActionController::Base.logger = logger
+
forgery_protection_origin_check do
session[:_csrf_token] = @token
@controller.stub :form_authenticity_token, @token do
@@ -356,6 +450,13 @@ module RequestForgeryProtectionTests
end
end
end
+
+ assert_match(
+ "HTTP Origin header (http://bad.host) didn't match request.base_url (http://test.host)",
+ logger.logged(:warn).last
+ )
+ ensure
+ ActionController::Base.logger = old_logger
end
def test_should_warn_on_missing_csrf_token
diff --git a/actionpack/test/controller/required_params_test.rb b/actionpack/test/controller/required_params_test.rb
index dd07c2486b..46bb374b3f 100644
--- a/actionpack/test/controller/required_params_test.rb
+++ b/actionpack/test/controller/required_params_test.rb
@@ -72,9 +72,27 @@ class ParametersRequireTest < ActiveSupport::TestCase
assert params.value?("cinco")
end
- test "to_query is not supported" do
- assert_raises(NoMethodError) do
- ActionController::Parameters.new(foo: "bar").to_param
+ test "to_param works like in a Hash" do
+ params = ActionController::Parameters.new(nested: { key: "value" }).permit!
+ assert_equal({ nested: { key: "value" } }.to_param, params.to_param)
+
+ params = { root: ActionController::Parameters.new(nested: { key: "value" }).permit! }
+ assert_equal({ root: { nested: { key: "value" } } }.to_param, params.to_param)
+
+ assert_raise(ActionController::UnfilteredParameters) do
+ ActionController::Parameters.new(nested: { key: "value" }).to_param
+ end
+ end
+
+ test "to_query works like in a Hash" do
+ params = ActionController::Parameters.new(nested: { key: "value" }).permit!
+ assert_equal({ nested: { key: "value" } }.to_query, params.to_query)
+
+ params = { root: ActionController::Parameters.new(nested: { key: "value" }).permit! }
+ assert_equal({ root: { nested: { key: "value" } } }.to_query, params.to_query)
+
+ assert_raise(ActionController::UnfilteredParameters) do
+ ActionController::Parameters.new(nested: { key: "value" }).to_query
end
end
end
diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb
index 9e6b975fe2..e265c6c49c 100644
--- a/actionpack/test/controller/send_file_test.rb
+++ b/actionpack/test/controller/send_file_test.rb
@@ -2,7 +2,7 @@ require "abstract_unit"
module TestFileUtils
def file_name() File.basename(__FILE__) end
- def file_path() File.expand_path(__FILE__) end
+ def file_path() __FILE__ end
def file_data() @data ||= File.open(file_path, "rb") { |f| f.read } end
end
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index 3a4307b64b..677e2ddded 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -122,7 +122,7 @@ XML
end
def test_send_file
- send_file(File.expand_path(__FILE__))
+ send_file(__FILE__)
end
def redirect_to_same_controller
@@ -780,7 +780,7 @@ XML
end
end
- FILES_DIR = File.dirname(__FILE__) + "/../fixtures/multipart"
+ FILES_DIR = File.expand_path("../fixtures/multipart", __dir__)
READ_BINARY = "rb:binary"
READ_PLAIN = "r:binary"
@@ -855,7 +855,7 @@ XML
end
def test_fixture_file_upload_ignores_fixture_path_given_full_path
- TestCaseTest.stub :fixture_path, File.dirname(__FILE__) do
+ TestCaseTest.stub :fixture_path, __dir__ do
uploaded_file = fixture_file_upload("#{FILES_DIR}/ruby_on_rails.jpg", "image/jpg")
assert_equal File.open("#{FILES_DIR}/ruby_on_rails.jpg", READ_PLAIN).read, uploaded_file.read
end
diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb
index 862dcf01c3..2afe67ed91 100644
--- a/actionpack/test/controller/url_for_test.rb
+++ b/actionpack/test/controller/url_for_test.rb
@@ -386,7 +386,7 @@ module AbstractController
def test_url_action_controller_parameters
add_host!
- assert_raise(ArgumentError) do
+ assert_raise(ActionController::UnfilteredParameters) do
W.new.url_for(ActionController::Parameters.new(controller: "c", action: "a", protocol: "javascript", f: "%0Aeval(name)"))
end
end
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 664faa31bb..e5646de82e 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -288,8 +288,7 @@ class CookiesTest < ActionController::TestCase
@request.env["action_dispatch.key_generator"] = ActiveSupport::KeyGenerator.new(SALT, iterations: 2)
@request.env["action_dispatch.signed_cookie_salt"] =
- @request.env["action_dispatch.encrypted_cookie_salt"] =
- @request.env["action_dispatch.encrypted_signed_cookie_salt"] = SALT
+ @request.env["action_dispatch.authenticated_encrypted_cookie_salt"] = SALT
@request.host = "www.nextangle.com"
end
@@ -531,9 +530,7 @@ class CookiesTest < ActionController::TestCase
get :set_encrypted_cookie
cookies = @controller.send :cookies
assert_not_equal "bar", cookies[:foo]
- assert_raise TypeError do
- cookies.signed[:foo]
- end
+ assert_nil cookies.signed[:foo]
assert_equal "bar", cookies.encrypted[:foo]
end
@@ -542,9 +539,7 @@ class CookiesTest < ActionController::TestCase
get :set_encrypted_cookie
cookies = @controller.send :cookies
assert_not_equal "bar", cookies[:foo]
- assert_raises TypeError do
- cookies.signed[:foo]
- end
+ assert_nil cookies.signed[:foo]
assert_equal "bar", cookies.encrypted[:foo]
end
@@ -553,9 +548,7 @@ class CookiesTest < ActionController::TestCase
get :set_encrypted_cookie
cookies = @controller.send :cookies
assert_not_equal "bar", cookies[:foo]
- assert_raises ::JSON::ParserError do
- cookies.signed[:foo]
- end
+ assert_nil cookies.signed[:foo]
assert_equal "bar", cookies.encrypted[:foo]
end
@@ -564,9 +557,7 @@ class CookiesTest < ActionController::TestCase
get :set_wrapped_encrypted_cookie
cookies = @controller.send :cookies
assert_not_equal "wrapped: bar", cookies[:foo]
- assert_raises ::JSON::ParserError do
- cookies.signed[:foo]
- end
+ assert_nil cookies.signed[:foo]
assert_equal "wrapped: bar", cookies.encrypted[:foo]
end
@@ -577,38 +568,16 @@ class CookiesTest < ActionController::TestCase
assert_equal "bar was dumped and loaded", cookies.encrypted[:foo]
end
- def test_encrypted_cookie_using_custom_digest
- @request.env["action_dispatch.cookies_digest"] = "SHA256"
- get :set_encrypted_cookie
- cookies = @controller.send :cookies
- assert_not_equal "bar", cookies[:foo]
- assert_equal "bar", cookies.encrypted[:foo]
-
- sign_secret = @request.env["action_dispatch.key_generator"].generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"])
-
- sha1_verifier = ActiveSupport::MessageVerifier.new(sign_secret, serializer: ActiveSupport::MessageEncryptor::NullSerializer, digest: "SHA1")
- sha256_verifier = ActiveSupport::MessageVerifier.new(sign_secret, serializer: ActiveSupport::MessageEncryptor::NullSerializer, digest: "SHA256")
-
- assert_raises(ActiveSupport::MessageVerifier::InvalidSignature) do
- sha1_verifier.verify(cookies[:foo])
- end
-
- assert_nothing_raised do
- sha256_verifier.verify(cookies[:foo])
- end
- end
-
def test_encrypted_cookie_using_hybrid_serializer_can_migrate_marshal_dumped_value_to_json
@request.env["action_dispatch.cookies_serializer"] = :hybrid
- key_generator = @request.env["action_dispatch.key_generator"]
- encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"]
- encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"]
- secret = key_generator.generate_key(encrypted_cookie_salt)
- sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt)
+ cipher = "aes-256-gcm"
+ salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
+ secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)]
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: Marshal)
- marshal_value = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: Marshal).encrypt_and_sign("bar")
- @request.headers["Cookie"] = "foo=#{marshal_value}"
+ marshal_value = encryptor.encrypt_and_sign("bar")
+ @request.headers["Cookie"] = "foo=#{::Rack::Utils.escape marshal_value}"
get :get_encrypted_cookie
@@ -616,40 +585,28 @@ class CookiesTest < ActionController::TestCase
assert_not_equal "bar", cookies[:foo]
assert_equal "bar", cookies.encrypted[:foo]
- encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON)
- assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
+ json_encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: JSON)
+ assert_not_nil @response.cookies["foo"]
+ assert_equal "bar", json_encryptor.decrypt_and_verify(@response.cookies["foo"])
end
def test_encrypted_cookie_using_hybrid_serializer_can_read_from_json_dumped_value
@request.env["action_dispatch.cookies_serializer"] = :hybrid
- key_generator = @request.env["action_dispatch.key_generator"]
- encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"]
- encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"]
- secret = key_generator.generate_key(encrypted_cookie_salt)
- sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt)
- json_value = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON).encrypt_and_sign("bar")
- @request.headers["Cookie"] = "foo=#{json_value}"
-
- get :get_encrypted_cookie
+ cipher = "aes-256-gcm"
+ salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
+ secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)]
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: JSON)
- cookies = @controller.send :cookies
- assert_not_equal "bar", cookies[:foo]
- assert_equal "bar", cookies.encrypted[:foo]
-
- assert_nil @response.cookies["foo"]
- end
-
- def test_compat_encrypted_cookie_using_64_byte_key
- # Cookie generated with 64 bytes secret
- message = ["566d4e75536d686e633246564e6b493062557079626c566d51574d30515430394c53315665564a694e4563786555744f57537454576b396a5a31566a626e52525054303d2d2d34663234333330623130623261306163363562316266323335396164666364613564643134623131"].pack("H*")
- @request.headers["Cookie"] = "foo=#{message}"
+ json_value = encryptor.encrypt_and_sign("bar")
+ @request.headers["Cookie"] = "foo=#{::Rack::Utils.escape json_value}"
get :get_encrypted_cookie
cookies = @controller.send :cookies
assert_not_equal "bar", cookies[:foo]
assert_equal "bar", cookies.encrypted[:foo]
+
assert_nil @response.cookies["foo"]
end
@@ -813,10 +770,10 @@ class CookiesTest < ActionController::TestCase
assert_equal "bar", @controller.send(:cookies).encrypted[:foo]
- key_generator = @request.env["action_dispatch.key_generator"]
- secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"])
- sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"])
- encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret)
+ cipher = "aes-256-gcm"
+ salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
+ secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)]
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: Marshal)
assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
end
@@ -842,8 +799,6 @@ class CookiesTest < ActionController::TestCase
@request.env["action_dispatch.cookies_serializer"] = :json
@request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
@request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
- @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee"
- @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9"
legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate("bar")
@@ -852,10 +807,10 @@ class CookiesTest < ActionController::TestCase
assert_equal "bar", @controller.send(:cookies).encrypted[:foo]
- key_generator = @request.env["action_dispatch.key_generator"]
- secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"])
- sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"])
- encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON)
+ cipher = "aes-256-gcm"
+ salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
+ secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)]
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: JSON)
assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
end
@@ -881,8 +836,6 @@ class CookiesTest < ActionController::TestCase
@request.env["action_dispatch.cookies_serializer"] = :hybrid
@request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
@request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
- @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee"
- @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9"
legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate("bar")
@@ -891,10 +844,10 @@ class CookiesTest < ActionController::TestCase
assert_equal "bar", @controller.send(:cookies).encrypted[:foo]
- key_generator = @request.env["action_dispatch.key_generator"]
- secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"])
- sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"])
- encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON)
+ cipher = "aes-256-gcm"
+ salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
+ secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)]
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: JSON)
assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
end
@@ -920,8 +873,6 @@ class CookiesTest < ActionController::TestCase
@request.env["action_dispatch.cookies_serializer"] = :hybrid
@request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
@request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
- @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee"
- @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9"
legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate("bar")
@@ -930,10 +881,10 @@ class CookiesTest < ActionController::TestCase
assert_equal "bar", @controller.send(:cookies).encrypted[:foo]
- key_generator = @request.env["action_dispatch.key_generator"]
- secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"])
- sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"])
- encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON)
+ cipher = "aes-256-gcm"
+ salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
+ secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)]
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: JSON)
assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
end
@@ -959,6 +910,89 @@ class CookiesTest < ActionController::TestCase
assert_nil @response.cookies["foo"]
end
+ def test_legacy_hmac_aes_cbc_encrypted_marshal_cookie_is_upgraded_to_authenticated_encrypted_cookie
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ @request.env["action_dispatch.encrypted_cookie_salt"] =
+ @request.env["action_dispatch.encrypted_signed_cookie_salt"] = SALT
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"]
+ encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"]
+ secret = key_generator.generate_key(encrypted_cookie_salt)
+ sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt)
+ marshal_value = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: Marshal).encrypt_and_sign("bar")
+
+ @request.headers["Cookie"] = "foo=#{marshal_value}"
+
+ get :get_encrypted_cookie
+
+ cookies = @controller.send :cookies
+ assert_not_equal "bar", cookies[:foo]
+ assert_equal "bar", cookies.encrypted[:foo]
+
+ aead_cipher = "aes-256-gcm"
+ aead_salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
+ aead_secret = key_generator.generate_key(aead_salt)[0, ActiveSupport::MessageEncryptor.key_len(aead_cipher)]
+ aead_encryptor = ActiveSupport::MessageEncryptor.new(aead_secret, cipher: aead_cipher, serializer: Marshal)
+
+ assert_equal "bar", aead_encryptor.decrypt_and_verify(@response.cookies["foo"])
+ end
+
+ def test_legacy_hmac_aes_cbc_encrypted_json_cookie_is_upgraded_to_authenticated_encrypted_cookie
+ @request.env["action_dispatch.cookies_serializer"] = :json
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ @request.env["action_dispatch.encrypted_cookie_salt"] =
+ @request.env["action_dispatch.encrypted_signed_cookie_salt"] = SALT
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"]
+ encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"]
+ secret = key_generator.generate_key(encrypted_cookie_salt)
+ sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt)
+ marshal_value = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON).encrypt_and_sign("bar")
+
+ @request.headers["Cookie"] = "foo=#{marshal_value}"
+
+ get :get_encrypted_cookie
+
+ cookies = @controller.send :cookies
+ assert_not_equal "bar", cookies[:foo]
+ assert_equal "bar", cookies.encrypted[:foo]
+
+ aead_cipher = "aes-256-gcm"
+ aead_salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
+ aead_secret = key_generator.generate_key(aead_salt)[0, ActiveSupport::MessageEncryptor.key_len(aead_cipher)]
+ aead_encryptor = ActiveSupport::MessageEncryptor.new(aead_secret, cipher: aead_cipher, serializer: JSON)
+
+ assert_equal "bar", aead_encryptor.decrypt_and_verify(@response.cookies["foo"])
+ end
+
+ def test_legacy_hmac_aes_cbc_encrypted_cookie_using_64_byte_key_is_upgraded_to_authenticated_encrypted_cookie
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ @request.env["action_dispatch.encrypted_cookie_salt"] =
+ @request.env["action_dispatch.encrypted_signed_cookie_salt"] = SALT
+
+ # Cookie generated with 64 bytes secret
+ message = ["566d4e75536d686e633246564e6b493062557079626c566d51574d30515430394c53315665564a694e4563786555744f57537454576b396a5a31566a626e52525054303d2d2d34663234333330623130623261306163363562316266323335396164666364613564643134623131"].pack("H*")
+ @request.headers["Cookie"] = "foo=#{message}"
+
+ get :get_encrypted_cookie
+
+ cookies = @controller.send :cookies
+ assert_not_equal "bar", cookies[:foo]
+ assert_equal "bar", cookies.encrypted[:foo]
+ cipher = "aes-256-gcm"
+
+ salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
+ secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)]
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: Marshal)
+
+ assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
+ end
+
def test_cookie_with_all_domain_option
get :set_cookie_with_domain
assert_response :success
diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
index 01c5ff1429..e7e8c82974 100644
--- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
@@ -21,7 +21,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
end
end
- FIXTURE_PATH = File.dirname(__FILE__) + "/../../fixtures/multipart"
+ FIXTURE_PATH = File.expand_path("../../fixtures/multipart", __dir__)
def teardown
TestController.last_request_parameters = nil
diff --git a/actionpack/test/dispatch/request/session_test.rb b/actionpack/test/dispatch/request/session_test.rb
index 311b80ea0a..228135c547 100644
--- a/actionpack/test/dispatch/request/session_test.rb
+++ b/actionpack/test/dispatch/request/session_test.rb
@@ -54,6 +54,11 @@ module ActionDispatch
assert_equal %w[rails adequate], s.keys
end
+ def test_keys_with_deferred_loading
+ s = Session.create(store_with_data, req, {})
+ assert_equal %w[sample_key], s.keys
+ end
+
def test_values
s = Session.create(store, req, {})
s["rails"] = "ftw"
@@ -61,6 +66,11 @@ module ActionDispatch
assert_equal %w[ftw awesome], s.values
end
+ def test_values_with_deferred_loading
+ s = Session.create(store_with_data, req, {})
+ assert_equal %w[sample_value], s.values
+ end
+
def test_clear
s = Session.create(store, req, {})
s["rails"] = "ftw"
@@ -113,6 +123,14 @@ module ActionDispatch
def delete_session(env, id, options); 123; end
}.new
end
+
+ def store_with_data
+ Class.new {
+ def load_session(env); [1, { "sample_key" => "sample_value" }]; end
+ def session_exists?(env); true; end
+ def delete_session(env, id, options); 123; end
+ }.new
+ end
end
class SessionIntegrationTest < ActionDispatch::IntegrationTest
diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
index 1169bf0cdb..6721a388c1 100644
--- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
@@ -107,7 +107,7 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest
query = [
"customers[boston][first][name]=David",
"something_else=blah",
- "logo=#{File.expand_path(__FILE__)}"
+ "logo=#{__FILE__}"
].join("&")
expected = {
"customers" => {
@@ -118,7 +118,7 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest
}
},
"something_else" => "blah",
- "logo" => File.expand_path(__FILE__),
+ "logo" => __FILE__,
}
assert_parses expected, query
end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 2f9228a62d..899b27b962 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -110,8 +110,8 @@ class RequestIP < BaseRequestTest
request.remote_ip
}
assert_match(/IP spoofing attack/, e.message)
- assert_match(/HTTP_X_FORWARDED_FOR="1.1.1.1"/, e.message)
- assert_match(/HTTP_CLIENT_IP="2.2.2.2"/, e.message)
+ assert_match(/HTTP_X_FORWARDED_FOR="1\.1\.1\.1"/, e.message)
+ assert_match(/HTTP_CLIENT_IP="2\.2\.2\.2"/, e.message)
end
test "remote ip with spoof detection disabled" do
@@ -1098,6 +1098,19 @@ class RequestParameterFilter < BaseRequestTest
end
end
+ test "parameter filter should maintain hash with indifferent access" do
+ test_hashes = [
+ [{ "foo" => "bar" }.with_indifferent_access, ["blah"]],
+ [{ "foo" => "bar" }.with_indifferent_access, []]
+ ]
+
+ test_hashes.each do |before_filter, filter_words|
+ parameter_filter = ActionDispatch::Http::ParameterFilter.new(filter_words)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess,
+ parameter_filter.filter(before_filter)
+ end
+ end
+
test "filtered_parameters returns params filtered" do
request = stub_request(
"action_dispatch.request.parameters" => {
diff --git a/actionpack/test/dispatch/routing/custom_url_helpers_test.rb b/actionpack/test/dispatch/routing/custom_url_helpers_test.rb
index cb5ca5888b..cbbed66056 100644
--- a/actionpack/test/dispatch/routing/custom_url_helpers_test.rb
+++ b/actionpack/test/dispatch/routing/custom_url_helpers_test.rb
@@ -165,8 +165,8 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest
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_raises(ActionController::UnfilteredParameters) { params_path(@unsafe_params) }
+ assert_raises(ActionController::UnfilteredParameters) { Routes.url_helpers.params_path(@unsafe_params) }
assert_equal "/basket", symbol_path
assert_equal "/basket", Routes.url_helpers.symbol_path
@@ -208,8 +208,8 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest
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_raises(ActionController::UnfilteredParameters) { params_url(@unsafe_params) }
+ assert_raises(ActionController::UnfilteredParameters) { 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
@@ -322,4 +322,10 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest
end
end
end
+
+ def test_defining_direct_url_registers_helper_method
+ assert_equal "http://www.example.com/basket", Routes.url_helpers.symbol_url
+ assert_equal true, Routes.named_routes.route_defined?(:symbol_url), "'symbol_url' named helper not found"
+ assert_equal true, Routes.named_routes.route_defined?(:symbol_path), "'symbol_path' named helper not found"
+ end
end
diff --git a/actionpack/test/dispatch/routing/route_set_test.rb b/actionpack/test/dispatch/routing/route_set_test.rb
index ace35dda53..d6ecbda092 100644
--- a/actionpack/test/dispatch/routing/route_set_test.rb
+++ b/actionpack/test/dispatch/routing/route_set_test.rb
@@ -138,6 +138,15 @@ module ActionDispatch
assert_equal "/a/users/1", url_helpers.user_path(1, foo: "a")
end
+ test "implicit path components consistently return the same result" do
+ draw do
+ resources :users, to: SimpleApp.new("foo#index")
+ end
+ assert_equal "/users/1.json", url_helpers.user_path(1, :json)
+ assert_equal "/users/1.json", url_helpers.user_path(1, format: :json)
+ assert_equal "/users/1.json", url_helpers.user_path(1, :json)
+ end
+
private
def draw(&block)
@set.draw(&block)
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 64818e6ca1..32cd78e492 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -3633,7 +3633,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
params = ActionController::Parameters.new(id: "1")
- assert_raises ArgumentError do
+ assert_raises ActionController::UnfilteredParameters do
root_path(params)
end
end
@@ -3706,6 +3706,24 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal "/bar", bar_root_path
end
+ def test_nested_routes_under_format_resource
+ draw do
+ resources :formats do
+ resources :items
+ end
+ end
+
+ get "/formats/1/items.json"
+ assert_equal 200, @response.status
+ assert_equal "items#index", @response.body
+ assert_equal "/formats/1/items.json", format_items_path(1, :json)
+
+ get "/formats/1/items/2.json"
+ assert_equal 200, @response.status
+ assert_equal "items#show", @response.body
+ assert_equal "/formats/1/items/2.json", format_item_path(1, 2, :json)
+ end
+
private
def draw(&block)
@@ -4401,7 +4419,7 @@ class TestInvalidUrls < ActionDispatch::IntegrationTest
end
end
- test "invalid UTF-8 encoding returns a 400 Bad Request" do
+ test "invalid UTF-8 encoding is treated as ASCII 8BIT encode" do
with_routing do |set|
set.draw do
get "/bar/:id", to: redirect("/foo/show/%{id}")
@@ -4417,19 +4435,19 @@ class TestInvalidUrls < ActionDispatch::IntegrationTest
end
get "/%E2%EF%BF%BD%A6"
- assert_response :bad_request
+ assert_response :not_found
get "/foo/%E2%EF%BF%BD%A6"
- assert_response :bad_request
+ assert_response :not_found
get "/foo/show/%E2%EF%BF%BD%A6"
- assert_response :bad_request
+ assert_response :ok
get "/bar/%E2%EF%BF%BD%A6"
- assert_response :bad_request
+ assert_response :redirect
get "/foobar/%E2%EF%BF%BD%A6"
- assert_response :bad_request
+ assert_response :ok
end
end
end
diff --git a/actionpack/test/dispatch/system_testing/driver_test.rb b/actionpack/test/dispatch/system_testing/driver_test.rb
index 814e1d707b..4a1b971da5 100644
--- a/actionpack/test/dispatch/system_testing/driver_test.rb
+++ b/actionpack/test/dispatch/system_testing/driver_test.rb
@@ -15,7 +15,21 @@ class DriverTest < ActiveSupport::TestCase
assert_equal ({ url: "http://example.com/wd/hub" }), driver.instance_variable_get(:@options)
end
- test "selenium? returns false if driver is poltergeist" do
- assert_not ActionDispatch::SystemTesting::Driver.new(:poltergeist).send(:selenium?)
+ test "initializing the driver with a poltergeist" do
+ driver = ActionDispatch::SystemTesting::Driver.new(:poltergeist, screen_size: [1400, 1400], options: { js_errors: false })
+ assert_equal :poltergeist, driver.instance_variable_get(:@name)
+ assert_equal [1400, 1400], driver.instance_variable_get(:@screen_size)
+ assert_equal ({ js_errors: false }), driver.instance_variable_get(:@options)
+ end
+
+ test "initializing the driver with a webkit" do
+ driver = ActionDispatch::SystemTesting::Driver.new(:webkit, screen_size: [1400, 1400], options: { skip_image_loading: true })
+ assert_equal :webkit, driver.instance_variable_get(:@name)
+ assert_equal [1400, 1400], driver.instance_variable_get(:@screen_size)
+ assert_equal ({ skip_image_loading: true }), driver.instance_variable_get(:@options)
+ end
+
+ test "rack_test? returns false if driver is poltergeist" do
+ assert_not ActionDispatch::SystemTesting::Driver.new(:poltergeist).send(:rack_test?)
end
end
diff --git a/actionpack/test/dispatch/system_testing/system_test_case_test.rb b/actionpack/test/dispatch/system_testing/system_test_case_test.rb
index 33d98f924f..53f1a1bb37 100644
--- a/actionpack/test/dispatch/system_testing/system_test_case_test.rb
+++ b/actionpack/test/dispatch/system_testing/system_test_case_test.rb
@@ -19,3 +19,47 @@ class SetDriverToSeleniumTest < DrivenBySeleniumWithChrome
assert_equal :selenium, Capybara.current_driver
end
end
+
+class SetHostTest < DrivenByRackTest
+ test "sets default host" do
+ assert_equal "http://127.0.0.1", Capybara.app_host
+ end
+
+ test "overrides host" do
+ host! "http://example.com"
+
+ assert_equal "http://example.com", Capybara.app_host
+ end
+end
+
+class UndefMethodsTest < DrivenBySeleniumWithChrome
+ test "get" do
+ assert_raise NoMethodError do
+ get "http://example.com"
+ end
+ end
+
+ test "post" do
+ assert_raise NoMethodError do
+ post "http://example.com"
+ end
+ end
+
+ test "put" do
+ assert_raise NoMethodError do
+ put "http://example.com"
+ end
+ end
+
+ test "patch" do
+ assert_raise NoMethodError do
+ patch "http://example.com"
+ end
+ end
+
+ test "delete" do
+ assert_raise NoMethodError do
+ delete "http://example.com"
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/uploaded_file_test.rb b/actionpack/test/dispatch/uploaded_file_test.rb
index 51680216e4..0074d2a314 100644
--- a/actionpack/test/dispatch/uploaded_file_test.rb
+++ b/actionpack/test/dispatch/uploaded_file_test.rb
@@ -13,6 +13,12 @@ module ActionDispatch
assert_equal "foo", uf.original_filename
end
+ def test_filename_is_different_object
+ file_str = "foo"
+ uf = Http::UploadedFile.new(filename: file_str, tempfile: Object.new)
+ assert_not_equal file_str.object_id , uf.original_filename.object_id
+ end
+
def test_filename_should_be_in_utf_8
uf = Http::UploadedFile.new(filename: "foo", tempfile: Object.new)
assert_equal "UTF-8", uf.original_filename.encoding.to_s
diff --git a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.html.erb b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.html.erb
index 9b88fa1f5a..dfcd423978 100644
--- a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.html.erb
+++ b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.html.erb
@@ -1,3 +1,3 @@
<body>
-<%= cache do %><p>ERB</p><% end %>
+<%= cache("fragment") do %><p>ERB</p><% end %>
</body>
diff --git a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder
index efdcc28e0f..6599579740 100644
--- a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder
+++ b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder
@@ -1,5 +1,5 @@
xml.body do
- cache do
+ cache("fragment") do
xml.p "Builder"
end
end
diff --git a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb
index e523b74ae3..abf7017ce6 100644
--- a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb
+++ b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb
@@ -1,3 +1,3 @@
<body>
-<%= cache do %><p>PHONE</p><% end %>
+<%= cache("fragment") do %><p>PHONE</p><% end %>
</body>
diff --git a/actionpack/test/fixtures/functional_caching/fragment_cached.html.erb b/actionpack/test/fixtures/functional_caching/fragment_cached.html.erb
index fa5e6bd318..1148d83ad7 100644
--- a/actionpack/test/fixtures/functional_caching/fragment_cached.html.erb
+++ b/actionpack/test/fixtures/functional_caching/fragment_cached.html.erb
@@ -1,3 +1,3 @@
Hello
-<%= cache do %>This bit's fragment cached<% end %>
+<%= cache "fragment" do %>This bit's fragment cached<% end %>
<%= 'Ciao' %>
diff --git a/actionpack/test/fixtures/layouts/builder.builder b/actionpack/test/fixtures/layouts/builder.builder
index 7c7d4b2dd1..c55488edd0 100644
--- a/actionpack/test/fixtures/layouts/builder.builder
+++ b/actionpack/test/fixtures/layouts/builder.builder
@@ -1,3 +1,3 @@
xml.wrapper do
xml << yield
-end \ No newline at end of file
+end
diff --git a/actionpack/test/fixtures/old_content_type/render_default_for_builder.builder b/actionpack/test/fixtures/old_content_type/render_default_for_builder.builder
index 598d62e2fc..15c8a7f5cf 100644
--- a/actionpack/test/fixtures/old_content_type/render_default_for_builder.builder
+++ b/actionpack/test/fixtures/old_content_type/render_default_for_builder.builder
@@ -1 +1 @@
-xml.p "Hello world!" \ No newline at end of file
+xml.p "Hello world!"
diff --git a/actionpack/test/fixtures/respond_to/using_defaults.xml.builder b/actionpack/test/fixtures/respond_to/using_defaults.xml.builder
index 598d62e2fc..15c8a7f5cf 100644
--- a/actionpack/test/fixtures/respond_to/using_defaults.xml.builder
+++ b/actionpack/test/fixtures/respond_to/using_defaults.xml.builder
@@ -1 +1 @@
-xml.p "Hello world!" \ No newline at end of file
+xml.p "Hello world!"
diff --git a/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.xml.builder b/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.xml.builder
index 598d62e2fc..15c8a7f5cf 100644
--- a/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.xml.builder
+++ b/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.xml.builder
@@ -1 +1 @@
-xml.p "Hello world!" \ No newline at end of file
+xml.p "Hello world!"
diff --git a/actionpack/test/fixtures/test/formatted_xml_erb.builder b/actionpack/test/fixtures/test/formatted_xml_erb.builder
index 14fd3549fb..f98aaa34a5 100644
--- a/actionpack/test/fixtures/test/formatted_xml_erb.builder
+++ b/actionpack/test/fixtures/test/formatted_xml_erb.builder
@@ -1 +1 @@
-xml.test 'failed' \ No newline at end of file
+xml.test "failed"
diff --git a/actionpack/test/fixtures/test/hello_xml_world.builder b/actionpack/test/fixtures/test/hello_xml_world.builder
index e7081b89fe..d16bb6b5cb 100644
--- a/actionpack/test/fixtures/test/hello_xml_world.builder
+++ b/actionpack/test/fixtures/test/hello_xml_world.builder
@@ -8,4 +8,4 @@ xml.html do
xml.p "monks"
xml.p "wiseguys"
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/journey/gtg/transition_table_test.rb b/actionpack/test/journey/gtg/transition_table_test.rb
index c7315c0338..889640fdd7 100644
--- a/actionpack/test/journey/gtg/transition_table_test.rb
+++ b/actionpack/test/journey/gtg/transition_table_test.rb
@@ -35,25 +35,25 @@ module ActionDispatch
def test_simulate_gt
sim = simulator_for ["/foo", "/bar"]
- assert_match sim, "/foo"
+ assert_match_route sim, "/foo"
end
def test_simulate_gt_regexp
sim = simulator_for [":foo"]
- assert_match sim, "foo"
+ assert_match_route sim, "foo"
end
def test_simulate_gt_regexp_mix
sim = simulator_for ["/get", "/:method/foo"]
- assert_match sim, "/get"
- assert_match sim, "/get/foo"
+ assert_match_route sim, "/get"
+ assert_match_route sim, "/get/foo"
end
def test_simulate_optional
sim = simulator_for ["/foo(/bar)"]
- assert_match sim, "/foo"
- assert_match sim, "/foo/bar"
- assert_no_match sim, "/foo/"
+ assert_match_route sim, "/foo"
+ assert_match_route sim, "/foo/bar"
+ assert_no_match_route sim, "/foo/"
end
def test_match_data
@@ -65,11 +65,11 @@ module ActionDispatch
sim = GTG::Simulator.new tt
- match = sim.match "/get"
- assert_equal [paths.first], match.memos
+ memos = sim.memos "/get"
+ assert_equal [paths.first], memos
- match = sim.match "/get/foo"
- assert_equal [paths.last], match.memos
+ memos = sim.memos "/get/foo"
+ assert_equal [paths.last], memos
end
def test_match_data_ambiguous
@@ -86,8 +86,8 @@ module ActionDispatch
builder = GTG::Builder.new ast
sim = GTG::Simulator.new builder.transition_table
- match = sim.match "/articles/new"
- assert_equal [paths[1], paths[3]], match.memos
+ memos = sim.memos "/articles/new"
+ assert_equal [paths[1], paths[3]], memos
end
private
@@ -109,6 +109,14 @@ module ActionDispatch
def simulator_for(paths)
GTG::Simulator.new tt(paths)
end
+
+ def assert_match_route(simulator, path)
+ assert simulator.memos(path), "Simulator should match #{path}."
+ end
+
+ def assert_no_match_route(simulator, path)
+ assert_not simulator.memos(path) { nil }, "Simulator should not match #{path}."
+ end
end
end
end
diff --git a/actionpack/test/journey/router/utils_test.rb b/actionpack/test/journey/router/utils_test.rb
index b77bf6628a..74277a4325 100644
--- a/actionpack/test/journey/router/utils_test.rb
+++ b/actionpack/test/journey/router/utils_test.rb
@@ -31,6 +31,11 @@ module ActionDispatch
def test_normalize_path_uppercase
assert_equal "/foo%AAbar%AAbaz", Utils.normalize_path("/foo%aabar%aabaz")
end
+
+ def test_normalize_path_maintains_string_encoding
+ path = "/foo%AAbar%AAbaz".b
+ assert_equal Encoding::ASCII_8BIT, Utils.normalize_path(path).encoding
+ end
end
end
end
diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb
index b768553e7a..ff37d85ed8 100644
--- a/actionpack/test/lib/controller/fake_models.rb
+++ b/actionpack/test/lib/controller/fake_models.rb
@@ -26,6 +26,10 @@ Customer = Struct.new(:name, :id) do
def persisted?
id.present?
end
+
+ def cache_key
+ "#{name}/#{id}"
+ end
end
Post = Struct.new(:title, :author_name, :body, :secret, :persisted, :written_on, :cost) do
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index c514e757c8..5bc93fcb5b 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1 +1,29 @@
+* Add `srcset` option to `image_tag` helper.
+
+ *Roberto Miranda*
+
+* Fix issues with scopes and engine on `current_page?` method.
+
+ Fixes #29401.
+
+ *Nikita Savrov*
+
+* Generate field ids in `collection_check_boxes` and `collection_radio_buttons`.
+
+ This makes sure that the labels are linked up with the fields.
+
+ Fixes #29014.
+
+ *Yuji Yaginuma*
+
+* Add `:json` type to `auto_discovery_link_tag` to support [JSON Feeds](https://jsonfeed.org/version/1)
+
+ *Mike Gunderloy*
+
+* Update `distance_of_time_in_words` helper to display better error messages
+ for bad input.
+
+ *Jay Hayes*
+
+
Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/actionview/CHANGELOG.md) for previous changes.
diff --git a/actionview/Rakefile b/actionview/Rakefile
index de588ad25c..0fc38e8db4 100644
--- a/actionview/Rakefile
+++ b/actionview/Rakefile
@@ -2,8 +2,6 @@ require "rake/testtask"
require "fileutils"
require "open3"
-dir = File.dirname(__FILE__)
-
desc "Default Task"
task default: :test
@@ -95,7 +93,7 @@ namespace :assets do
desc "Verify compiled Action View assets"
task :verify do
file = "lib/assets/compiled/rails-ujs.js"
- pathname = Pathname.new("#{dir}/#{file}")
+ pathname = Pathname.new("#{__dir__}/#{file}")
print "[verify] #{file} exists "
if pathname.exist?
@@ -113,13 +111,13 @@ namespace :assets do
fail
end
- print "[verify] #{dir} can be required as a module "
+ print "[verify] #{__dir__} can be required as a module "
js = <<-JS
window = { Event: class {} }
class Element {}
- require('#{dir}')
+ require('#{__dir__}')
JS
- stdout, stderr, status = Open3.capture3("node", "--print", js)
+ _, stderr, status = Open3.capture3("node", "--print", js)
if status.success?
puts "[OK]"
else
@@ -130,7 +128,7 @@ namespace :assets do
end
task :lines do
- load File.expand_path("..", File.dirname(__FILE__)) + "/tools/line_statistics"
+ load File.join(File.expand_path("..", __dir__), "/tools/line_statistics")
files = FileList["lib/**/*.rb"]
CodeTools::LineStatistics.new(files).print_loc
end
diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec
index cfaa5007a1..48e79c53ca 100644
--- a/actionview/actionview.gemspec
+++ b/actionview/actionview.gemspec
@@ -1,4 +1,4 @@
-version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
+version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
@@ -19,6 +19,11 @@ Gem::Specification.new do |s|
s.require_path = "lib"
s.requirements << "none"
+ s.metadata = {
+ "source_code_uri" => "https://github.com/rails/rails/tree/v#{version}/actionview",
+ "changelog_uri" => "https://github.com/rails/rails/blob/v#{version}/actionview/CHANGELOG.md"
+ }
+
s.add_dependency "activesupport", version
s.add_dependency "builder", "~> 3.1"
diff --git a/actionview/app/assets/javascripts/README.md b/actionview/app/assets/javascripts/README.md
index 92f3e8a3b3..f321b9f720 100644
--- a/actionview/app/assets/javascripts/README.md
+++ b/actionview/app/assets/javascripts/README.md
@@ -30,16 +30,27 @@ Run `yarn add rails-ujs` to install the rails-ujs package.
Usage
------------
-Require `rails-ujs` into your application.js manifest.
+Require `rails-ujs` in your application.js manifest.
```javascript
//= require rails-ujs
```
+Usage with yarn
+------------
+
+When using with the Webpacker gem or your preferred JavaScript bundler, just
+add the following to your main JS file and compile.
+
+```javascript
+import Rails from 'rails-ujs';
+Rails.start()
+```
+
How to run tests
------------
-Run `bundle exec rake ujs:server` first, and then run the web tests by visiting [[http://localhost:4567]] in your browser.
+Run `bundle exec rake ujs:server` first, and then run the web tests by visiting http://localhost:4567 in your browser.
## License
rails-ujs is released under the [MIT License](MIT-LICENSE).
diff --git a/actionview/app/assets/javascripts/rails-ujs/start.coffee b/actionview/app/assets/javascripts/rails-ujs/start.coffee
index 5746a22287..55595ac96f 100644
--- a/actionview/app/assets/javascripts/rails-ujs/start.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/start.coffee
@@ -9,7 +9,7 @@
} = Rails
# For backward compatibility
-if jQuery? and not jQuery.rails
+if jQuery? and jQuery.ajax? and not jQuery.rails
jQuery.rails = Rails
jQuery.ajaxPrefilter (options, originalOptions, xhr) ->
CSRFProtection(xhr) unless options.crossDomain
diff --git a/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee
index 9af515beda..a653d3af3d 100644
--- a/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee
@@ -14,7 +14,7 @@ AcceptHeaders =
Rails.ajax = (options) ->
options = prepareOptions(options)
xhr = createXHR options, ->
- response = processResponse(xhr.response, xhr.getResponseHeader('Content-Type'))
+ response = processResponse(xhr.response ? xhr.responseText, xhr.getResponseHeader('Content-Type'))
if xhr.status // 100 == 2
options.success?(response, xhr.statusText, xhr)
else
@@ -29,6 +29,7 @@ Rails.ajax = (options) ->
fire(document, 'ajaxStop') # to be compatible with jQuery.ajax
prepareOptions = (options) ->
+ options.url = options.url or location.href
options.type = options.type.toUpperCase()
# append data to url if it's a GET request
if options.type is 'GET' and options.data
@@ -63,10 +64,10 @@ processResponse = (response, type) ->
if typeof response is 'string' and typeof type is 'string'
if type.match(/\bjson\b/)
try response = JSON.parse(response)
- else if type.match(/\bjavascript\b/)
+ else if type.match(/\b(?:java|ecma)script\b/)
script = document.createElement('script')
- script.innerHTML = response
- document.body.appendChild(script)
+ script.text = response
+ document.head.appendChild(script).parentNode.removeChild(script)
else if type.match(/\b(xml|html|svg)\b/)
parser = new DOMParser()
type = type.replace(/;.+/, '') # remove something like ';charset=utf-8'
diff --git a/actionview/bin/test b/actionview/bin/test
index a7beb14b27..470ce93f10 100755
--- a/actionview/bin/test
+++ b/actionview/bin/test
@@ -1,4 +1,4 @@
#!/usr/bin/env ruby
COMPONENT_ROOT = File.expand_path("..", __dir__)
-require File.expand_path("../tools/test", COMPONENT_ROOT)
+require_relative "../../tools/test"
diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb
index ca586f1d52..53a83d48f6 100644
--- a/actionview/lib/action_view.rb
+++ b/actionview/lib/action_view.rb
@@ -23,7 +23,7 @@
require "active_support"
require "active_support/rails"
-require "action_view/version"
+require_relative "action_view/version"
module ActionView
extend ActiveSupport::Autoload
@@ -92,5 +92,5 @@ end
require "active_support/core_ext/string/output_safety"
ActiveSupport.on_load(:i18n) do
- I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en.yml"
+ I18n.load_path << File.expand_path("action_view/locale/en.yml", __dir__)
end
diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb
index 5387174467..969d300bc1 100644
--- a/actionview/lib/action_view/base.rb
+++ b/actionview/lib/action_view/base.rb
@@ -1,11 +1,11 @@
require "active_support/core_ext/module/attr_internal"
require "active_support/core_ext/module/attribute_accessors"
require "active_support/ordered_options"
-require "action_view/log_subscriber"
-require "action_view/helpers"
-require "action_view/context"
-require "action_view/template"
-require "action_view/lookup_context"
+require_relative "log_subscriber"
+require_relative "helpers"
+require_relative "context"
+require_relative "template"
+require_relative "lookup_context"
module ActionView #:nodoc:
# = Action View Base
@@ -140,30 +140,25 @@ module ActionView #:nodoc:
include Helpers, ::ERB::Util, Context
# Specify the proc used to decorate input tags that refer to attributes with errors.
- cattr_accessor :field_error_proc
- @@field_error_proc = Proc.new { |html_tag, instance| "<div class=\"field_with_errors\">#{html_tag}</div>".html_safe }
+ cattr_accessor :field_error_proc, default: Proc.new { |html_tag, instance| "<div class=\"field_with_errors\">#{html_tag}</div>".html_safe }
# How to complete the streaming when an exception occurs.
# This is our best guess: first try to close the attribute, then the tag.
- cattr_accessor :streaming_completion_on_exception
- @@streaming_completion_on_exception = %("><script>window.location = "/500.html"</script></html>)
+ cattr_accessor :streaming_completion_on_exception, default: %("><script>window.location = "/500.html"</script></html>)
# Specify whether rendering within namespaced controllers should prefix
# the partial paths for ActiveModel objects with the namespace.
# (e.g., an Admin::PostsController would render @post using /admin/posts/_post.erb)
- cattr_accessor :prefix_partial_path_with_controller_namespace
- @@prefix_partial_path_with_controller_namespace = true
+ cattr_accessor :prefix_partial_path_with_controller_namespace, default: true
# Specify default_formats that can be rendered.
cattr_accessor :default_formats
# Specify whether an error should be raised for missing translations
- cattr_accessor :raise_on_missing_translations
- @@raise_on_missing_translations = false
+ cattr_accessor :raise_on_missing_translations, default: false
# Specify whether submit_tag should automatically disable on click
- cattr_accessor :automatically_disable_submit_tag
- @@automatically_disable_submit_tag = true
+ cattr_accessor :automatically_disable_submit_tag, default: true
class_attribute :_routes
class_attribute :logger
@@ -207,6 +202,7 @@ module ActionView #:nodoc:
@view_renderer = ActionView::Renderer.new(lookup_context)
end
+ @cache_hit = {}
assign(assigns)
assign_controller(controller)
_prepare_context
diff --git a/actionview/lib/action_view/dependency_tracker.rb b/actionview/lib/action_view/dependency_tracker.rb
index 451eeec9d6..ee438f9311 100644
--- a/actionview/lib/action_view/dependency_tracker.rb
+++ b/actionview/lib/action_view/dependency_tracker.rb
@@ -1,5 +1,5 @@
require "concurrent/map"
-require "action_view/path_set"
+require_relative "path_set"
module ActionView
class DependencyTracker # :nodoc:
diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb
index ba189e23fe..00ff36c879 100644
--- a/actionview/lib/action_view/digestor.rb
+++ b/actionview/lib/action_view/digestor.rb
@@ -1,5 +1,5 @@
require "concurrent/map"
-require "action_view/dependency_tracker"
+require_relative "dependency_tracker"
require "monitor"
module ActionView
@@ -62,7 +62,7 @@ module ActionView
node
end
else
- unless name.include?('#') # Dynamic template partial names can never be tracked
+ unless name.include?("#") # Dynamic template partial names can never be tracked
logger.error " Couldn't find template for digesting: #{name}"
end
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index 750f96f29e..cc8690e7bc 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -1,7 +1,7 @@
require "active_support/core_ext/array/extract_options"
require "active_support/core_ext/hash/keys"
-require "action_view/helpers/asset_url_helper"
-require "action_view/helpers/tag_helper"
+require_relative "asset_url_helper"
+require_relative "tag_helper"
module ActionView
# = Action View Asset Tag Helpers
@@ -122,9 +122,9 @@ module ActionView
end
# Returns a link tag that browsers and feed readers can use to auto-detect
- # an RSS or Atom feed. The +type+ can either be <tt>:rss</tt> (default) or
- # <tt>:atom</tt>. Control the link options in url_for format using the
- # +url_options+. You can modify the LINK tag itself in +tag_options+.
+ # an RSS, Atom, or JSON feed. The +type+ can be <tt>:rss</tt> (default),
+ # <tt>:atom</tt>, or <tt>:json</tt>. Control the link options in url_for format
+ # using the +url_options+. You can modify the LINK tag itself in +tag_options+.
#
# ==== Options
#
@@ -138,6 +138,8 @@ module ActionView
# # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/action" />
# auto_discovery_link_tag(:atom)
# # => <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.currenthost.com/controller/action" />
+ # auto_discovery_link_tag(:json)
+ # # => <link rel="alternate" type="application/json" title="JSON" href="http://www.currenthost.com/controller/action" />
# auto_discovery_link_tag(:rss, {action: "feed"})
# # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/feed" />
# auto_discovery_link_tag(:rss, {action: "feed"}, {title: "My RSS"})
@@ -147,8 +149,8 @@ module ActionView
# auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {title: "Example RSS"})
# # => <link rel="alternate" type="application/rss+xml" title="Example RSS" href="http://www.example.com/feed.rss" />
def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {})
- if !(type == :rss || type == :atom) && tag_options[:type].blank?
- raise ArgumentError.new("You should pass :type tag_option key explicitly, because you have passed #{type} type other than :rss or :atom.")
+ if !(type == :rss || type == :atom || type == :json) && tag_options[:type].blank?
+ raise ArgumentError.new("You should pass :type tag_option key explicitly, because you have passed #{type} type other than :rss, :atom, or :json.")
end
tag(
@@ -201,13 +203,15 @@ module ActionView
# ==== Options
#
# You can add HTML attributes using the +options+. The +options+ supports
- # two additional keys for convenience and conformance:
+ # additional keys for convenience and conformance:
#
# * <tt>:alt</tt> - If no alt text is given, the file name part of the
# +source+ is used (capitalized and without the extension)
# * <tt>:size</tt> - Supplied as "{Width}x{Height}" or "{Number}", so "30x45" becomes
# width="30" and height="45", and "50" becomes width="50" and height="50".
# <tt>:size</tt> will be ignored if the value is not in the correct format.
+ # * <tt>:srcset</tt> - If supplied as a hash or array of <tt>[source, descriptor]</tt>
+ # pairs, each image path will be expanded before the list is formatted as a string.
#
# ==== Examples
#
@@ -225,16 +229,28 @@ module ActionView
# # => <img alt="Icon" class="menu_icon" src="/icons/icon.gif" />
# image_tag("/icons/icon.gif", data: { title: 'Rails Application' })
# # => <img data-title="Rails Application" src="/icons/icon.gif" />
+ # image_tag("icon.png", srcset: { "icon_2x.png" => "2x", "icon_4x.png" => "4x" })
+ # # => <img src="/assets/icon.png" srcset="/assets/icon_2x.png 2x, /assets/icon_4x.png 4x">
+ # image_tag("pic.jpg", srcset: [["pic_1024.jpg", "1024w"], ["pic_1980.jpg", "1980w"]], sizes: "100vw")
+ # # => <img src="/assets/pic.jpg" srcset="/assets/pic_1024.jpg 1024w, /assets/pic_1980.jpg 1980w" sizes="100vw">
def image_tag(source, options = {})
options = options.symbolize_keys
check_for_image_tag_errors(options)
+ skip_pipeline = options.delete(:skip_pipeline)
- src = options[:src] = path_to_image(source, skip_pipeline: options.delete(:skip_pipeline))
+ src = options[:src] = path_to_image(source, skip_pipeline: skip_pipeline)
unless src.start_with?("cid:") || src.start_with?("data:") || src.blank?
options[:alt] = options.fetch(:alt) { image_alt(src) }
end
+ if options[:srcset] && !options[:srcset].is_a?(String)
+ options[:srcset] = options[:srcset].map do |src_path, size|
+ src_path = path_to_image(src_path, skip_pipeline: skip_pipeline)
+ "#{src_path} #{size}"
+ end.join(", ")
+ end
+
options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size]
tag("img", options)
end
diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb
index 15ab7e304f..b7c7324f31 100644
--- a/actionview/lib/action_view/helpers/cache_helper.rb
+++ b/actionview/lib/action_view/helpers/cache_helper.rb
@@ -8,10 +8,9 @@ module ActionView
# fragments, and so on. This method takes a block that contains
# the content you wish to cache.
#
- # The best way to use this is by doing key-based cache expiration
- # on top of a cache store like Memcached that'll automatically
- # kick out old entries. For more on key-based expiration, see:
- # http://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works
+ # The best way to use this is by doing recyclable key-based cache expiration
+ # on top of a cache store like Memcached or Redis that'll automatically
+ # kick out old entries.
#
# When using this method, you list the cache dependency as the name of the cache, like so:
#
@@ -23,10 +22,14 @@ module ActionView
# This approach will assume that when a new topic is added, you'll touch
# the project. The cache key generated from this call will be something like:
#
- # views/projects/123-20120806214154/7a1156131a6928cb0026877f8b749ac9
- # ^class ^id ^updated_at ^template tree digest
+ # views/template/action.html.erb:7a1156131a6928cb0026877f8b749ac9/projects/123
+ # ^template path ^template tree digest ^class ^id
#
- # The cache is thus automatically bumped whenever the project updated_at is touched.
+ # This cache key is stable, but it's combined with a cache version derived from the project
+ # record. When the project updated_at is touched, the #cache_version changes, even
+ # if the key stays stable. This means that unlike a traditional key-based cache expiration
+ # approach, you won't be generating cache trash, unused keys, simply because the dependent
+ # record is updated.
#
# If your template cache depends on multiple sources (try to avoid this to keep things simple),
# you can name all these dependencies as part of an array:
@@ -211,16 +214,19 @@ module ActionView
end
end
- attr_reader :cache_hit # :nodoc:
-
private
def fragment_name_with_digest(name, virtual_path)
virtual_path ||= @virtual_path
+
if virtual_path
name = controller.url_for(name).split("://").last if name.is_a?(Hash)
- digest = Digestor.digest name: virtual_path, finder: lookup_context, dependencies: view_cache_dependencies
- [ name, digest ]
+
+ if digest = Digestor.digest(name: virtual_path, finder: lookup_context, dependencies: view_cache_dependencies).presence
+ [ "#{virtual_path}:#{digest}", name ]
+ else
+ [ virtual_path, name ]
+ end
else
name
end
@@ -228,10 +234,10 @@ module ActionView
def fragment_for(name = {}, options = nil, &block)
if content = read_fragment_for(name, options)
- @cache_hit = true
+ @view_renderer.cache_hits[@virtual_path] = :hit if defined?(@view_renderer)
content
else
- @cache_hit = false
+ @view_renderer.cache_hits[@virtual_path] = :miss if defined?(@view_renderer)
write_fragment_for(name, options, &block)
end
end
diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb
index 09dc6ef6bd..3fbed44f7e 100644
--- a/actionview/lib/action_view/helpers/date_helper.rb
+++ b/actionview/lib/action_view/helpers/date_helper.rb
@@ -1,5 +1,5 @@
require "date"
-require "action_view/helpers/tag_helper"
+require_relative "tag_helper"
require "active_support/core_ext/array/extract_options"
require "active_support/core_ext/date/conversions"
require "active_support/core_ext/hash/slice"
@@ -95,8 +95,8 @@ module ActionView
scope: :'datetime.distance_in_words'
}.merge!(options)
- from_time = from_time.to_time if from_time.respond_to?(:to_time)
- to_time = to_time.to_time if to_time.respond_to?(:to_time)
+ from_time = normalize_distance_of_time_argument_to_time(from_time)
+ to_time = normalize_distance_of_time_argument_to_time(to_time)
from_time, to_time = to_time, from_time if from_time > to_time
distance_in_minutes = ((to_time - from_time) / 60.0).round
distance_in_seconds = (to_time - from_time).round
@@ -130,22 +130,18 @@ module ActionView
# 60 days up to 365 days
when 86400...525600 then locale.t :x_months, count: (distance_in_minutes.to_f / 43200.0).round
else
- if from_time.acts_like?(:time) && to_time.acts_like?(:time)
- fyear = from_time.year
- fyear += 1 if from_time.month >= 3
- tyear = to_time.year
- tyear -= 1 if to_time.month < 3
- leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count { |x| Date.leap?(x) }
- minute_offset_for_leap_year = leap_years * 1440
- # Discount the leap year days when calculating year distance.
- # e.g. if there are 20 leap year days between 2 dates having the same day
- # and month then the based on 365 days calculation
- # the distance in years will come out to over 80 years when in written
- # English it would read better as about 80 years.
- minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year
- else
- minutes_with_offset = distance_in_minutes
- end
+ from_year = from_time.year
+ from_year += 1 if from_time.month >= 3
+ to_year = to_time.year
+ to_year -= 1 if to_time.month < 3
+ leap_years = (from_year > to_year) ? 0 : (from_year..to_year).count { |x| Date.leap?(x) }
+ minute_offset_for_leap_year = leap_years * 1440
+ # Discount the leap year days when calculating year distance.
+ # e.g. if there are 20 leap year days between 2 dates having the same day
+ # and month then the based on 365 days calculation
+ # the distance in years will come out to over 80 years when in written
+ # English it would read better as about 80 years.
+ minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year
remainder = (minutes_with_offset % MINUTES_IN_YEAR)
distance_in_years = (minutes_with_offset.div MINUTES_IN_YEAR)
if remainder < MINUTES_IN_QUARTER_YEAR
@@ -687,6 +683,18 @@ module ActionView
content_tag("time".freeze, content, options.reverse_merge(datetime: datetime), &block)
end
+
+ private
+
+ def normalize_distance_of_time_argument_to_time(value)
+ if value.is_a?(Numeric)
+ Time.at(value)
+ elsif value.respond_to?(:to_time)
+ value.to_time
+ else
+ raise ArgumentError, "#{value.inspect} can't be converted to a Time value"
+ end
+ end
end
class DateTimeSelector #:nodoc:
@@ -999,7 +1007,7 @@ module ActionView
select_options[:disabled] = "disabled" if @options[:disabled]
select_options[:class] = css_class_attribute(type, select_options[:class], @options[:with_css_classes]) if @options[:with_css_classes]
- select_html = "\n"
+ select_html = "\n".dup
select_html << content_tag("option".freeze, "", value: "") + "\n" if @options[:include_blank]
select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt]
select_html << select_options_as_html
@@ -1081,7 +1089,7 @@ module ActionView
# Given an ordering of datetime components, create the selection HTML
# and join them with their appropriate separators.
def build_selects_from_types(order)
- select = ""
+ select = "".dup
first_visible = order.find { |type| !@options[:"discard_#{type}"] }
order.reverse_each do |type|
separator = separator(type) unless type == first_visible # don't add before first visible field
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 26a625e4fe..6b36c2272a 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -1,10 +1,10 @@
require "cgi"
-require "action_view/helpers/date_helper"
-require "action_view/helpers/tag_helper"
-require "action_view/helpers/form_tag_helper"
-require "action_view/helpers/active_model_helper"
-require "action_view/model_naming"
-require "action_view/record_identifier"
+require_relative "date_helper"
+require_relative "tag_helper"
+require_relative "form_tag_helper"
+require_relative "active_model_helper"
+require_relative "../model_naming"
+require_relative "../record_identifier"
require "active_support/core_ext/module/attribute_accessors"
require "active_support/core_ext/hash/slice"
require "active_support/core_ext/string/output_safety"
@@ -201,9 +201,9 @@ module ActionView
# <%= f.submit %>
# <% end %>
#
- # This also works for the methods in FormOptionHelper and DateHelper that
+ # This also works for the methods in FormOptionsHelper and DateHelper that
# are designed to work with an object as base, like
- # FormOptionHelper#collection_select and DateHelper#datetime_select.
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
#
# === #form_for with a model object
#
@@ -416,13 +416,13 @@ module ActionView
#
# To set an authenticity token you need to pass an <tt>:authenticity_token</tt> parameter
#
- # <%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f|
+ # <%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f| %>
# ...
# <% end %>
#
# If you don't want to an authenticity token field be rendered at all just pass <tt>false</tt>:
#
- # <%= form_for @invoice, url: external_url, authenticity_token: false do |f|
+ # <%= form_for @invoice, url: external_url, authenticity_token: false do |f| %>
# ...
# <% end %>
def form_for(record, options = {}, &block)
@@ -474,6 +474,8 @@ module ActionView
end
private :apply_form_for_options!
+ mattr_accessor :form_with_generates_remote_forms, default: true
+
# Creates a form tag based on mixing URLs, scopes, or models.
#
# # Using just a URL:
@@ -632,9 +634,9 @@ module ActionView
# <%= form.submit %>
# <% end %>
#
- # Same goes for the methods in FormOptionHelper and DateHelper designed
+ # Same goes for the methods in FormOptionsHelper and DateHelper designed
# to work with an object as a base, like
- # FormOptionHelper#collection_select and DateHelper#datetime_select.
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
#
# === Setting the method
#
@@ -791,9 +793,9 @@ module ActionView
# _class_ of the model object, e.g. if <tt>@person.permission</tt>, is
# of class +Permission+, the field will still be named <tt>permission[admin]</tt>.
#
- # Note: This also works for the methods in FormOptionHelper and
+ # Note: This also works for the methods in FormOptionsHelper and
# DateHelper that are designed to work with an object as base, like
- # FormOptionHelper#collection_select and DateHelper#datetime_select.
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
#
# === Nested Attributes Examples
#
@@ -1033,9 +1035,9 @@ module ActionView
# <%= check_box_tag "comment[all_caps]", "1", @comment.commenter.hulk_mode? %>
# <% end %>
#
- # Same goes for the methods in FormOptionHelper and DateHelper designed
+ # Same goes for the methods in FormOptionsHelper and DateHelper designed
# to work with an object as a base, like
- # FormOptionHelper#collection_select and DateHelper#datetime_select.
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
def fields(scope = nil, model: nil, **options, &block)
options[:allow_method_names_outside_object] = true
options[:skip_default_ids] = true
@@ -1503,7 +1505,7 @@ module ActionView
end
private
- def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: false,
+ def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: !form_with_generates_remote_forms,
skip_enforcing_utf8: false, **options)
html_options = options.slice(:id, :class, :multipart, :method, :data).merge(html)
html_options[:method] ||= :patch if model.respond_to?(:persisted?) && model.persisted?
@@ -1517,12 +1519,14 @@ module ActionView
html_options[:"accept-charset"] = "UTF-8"
html_options[:"data-remote"] = true unless local
- if !local && !embed_authenticity_token_in_remote_forms &&
- html_options[:authenticity_token].blank?
- # The authenticity token is taken from the meta tag in this case
- html_options[:authenticity_token] = false
- elsif html_options[:authenticity_token] == true
- # Include the default authenticity_token, which is only generated when its set to nil,
+ html_options[:authenticity_token] = options.delete(:authenticity_token)
+
+ if !local && html_options[:authenticity_token].blank?
+ html_options[:authenticity_token] = embed_authenticity_token_in_remote_forms
+ end
+
+ if html_options[:authenticity_token] == true
+ # Include the default authenticity_token, which is only generated when it's set to nil,
# but we needed the true value to override the default of no authenticity_token on data-remote.
html_options[:authenticity_token] = nil
end
@@ -1602,14 +1606,15 @@ module ActionView
include ModelNaming
# The methods which wrap a form helper call.
- class_attribute :field_helpers
- self.field_helpers = [:fields_for, :fields, :label, :text_field, :password_field,
- :hidden_field, :file_field, :text_area, :check_box,
- :radio_button, :color_field, :search_field,
- :telephone_field, :phone_field, :date_field,
- :time_field, :datetime_field, :datetime_local_field,
- :month_field, :week_field, :url_field, :email_field,
- :number_field, :range_field]
+ class_attribute :field_helpers, default: [
+ :fields_for, :fields, :label, :text_field, :password_field,
+ :hidden_field, :file_field, :text_area, :check_box,
+ :radio_button, :color_field, :search_field,
+ :telephone_field, :phone_field, :date_field,
+ :time_field, :datetime_field, :datetime_local_field,
+ :month_field, :week_field, :url_field, :email_field,
+ :number_field, :range_field
+ ]
attr_accessor :object_name, :object, :options
@@ -1724,9 +1729,9 @@ module ActionView
# _class_ of the model object, e.g. if <tt>@person.permission</tt>, is
# of class +Permission+, the field will still be named <tt>permission[admin]</tt>.
#
- # Note: This also works for the methods in FormOptionHelper and
+ # Note: This also works for the methods in FormOptionsHelper and
# DateHelper that are designed to work with an object as base, like
- # FormOptionHelper#collection_select and DateHelper#datetime_select.
+ # FormOptionsHelper#collection_select and DateHelper#datetime_select.
#
# === Nested Attributes Examples
#
@@ -2313,8 +2318,6 @@ module ActionView
end
ActiveSupport.on_load(:action_view) do
- cattr_accessor(:default_form_builder, instance_writer: false, instance_reader: false) do
- ::ActionView::Helpers::FormBuilder
- end
+ cattr_accessor :default_form_builder, instance_writer: false, instance_reader: false, default: ::ActionView::Helpers::FormBuilder
end
end
diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb
index 07d4310a4e..0de3800a51 100644
--- a/actionview/lib/action_view/helpers/form_options_helper.rb
+++ b/actionview/lib/action_view/helpers/form_options_helper.rb
@@ -1,6 +1,6 @@
require "cgi"
require "erb"
-require "action_view/helpers/form_helper"
+require_relative "form_helper"
require "active_support/core_ext/string/output_safety"
require "active_support/core_ext/array/extract_options"
require "active_support/core_ext/array/wrap"
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index ffc64e7118..c8c6632781 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -1,5 +1,5 @@
require "cgi"
-require "action_view/helpers/tag_helper"
+require_relative "tag_helper"
require "active_support/core_ext/string/output_safety"
require "active_support/core_ext/module/attribute_accessors"
@@ -18,7 +18,7 @@ module ActionView
include TextHelper
mattr_accessor :embed_authenticity_token_in_remote_forms
- self.embed_authenticity_token_in_remote_forms = false
+ self.embed_authenticity_token_in_remote_forms = nil
# Starts a form tag that points the action to a url configured with <tt>url_for_options</tt> just like
# ActionController::Base#url_for. The method for the form defaults to POST.
diff --git a/actionview/lib/action_view/helpers/javascript_helper.rb b/actionview/lib/action_view/helpers/javascript_helper.rb
index 22e1e74ad6..8806492572 100644
--- a/actionview/lib/action_view/helpers/javascript_helper.rb
+++ b/actionview/lib/action_view/helpers/javascript_helper.rb
@@ -1,4 +1,4 @@
-require "action_view/helpers/tag_helper"
+require_relative "tag_helper"
module ActionView
module Helpers
@@ -13,8 +13,8 @@ module ActionView
"'" => "\\'"
}
- JS_ESCAPE_MAP["\342\200\250".force_encoding(Encoding::UTF_8).encode!] = "&#x2028;"
- JS_ESCAPE_MAP["\342\200\251".force_encoding(Encoding::UTF_8).encode!] = "&#x2029;"
+ JS_ESCAPE_MAP["\342\200\250".dup.force_encoding(Encoding::UTF_8).encode!] = "&#x2028;"
+ JS_ESCAPE_MAP["\342\200\251".dup.force_encoding(Encoding::UTF_8).encode!] = "&#x2029;"
# Escapes carriage returns and single and double quotes for JavaScript segments.
#
diff --git a/actionview/lib/action_view/helpers/tags/check_box.rb b/actionview/lib/action_view/helpers/tags/check_box.rb
index 02f87fc89f..b9b988325d 100644
--- a/actionview/lib/action_view/helpers/tags/check_box.rb
+++ b/actionview/lib/action_view/helpers/tags/check_box.rb
@@ -1,4 +1,4 @@
-require "action_view/helpers/tags/checkable"
+require_relative "checkable"
module ActionView
module Helpers
diff --git a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
index 7252d4f2d9..ef37c1c342 100644
--- a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
@@ -1,4 +1,4 @@
-require "action_view/helpers/tags/collection_helpers"
+require_relative "collection_helpers"
module ActionView
module Helpers
@@ -10,6 +10,7 @@ module ActionView
def check_box(extra_html_options = {})
html_options = extra_html_options.merge(@input_html_options)
html_options[:multiple] = true
+ html_options[:skip_default_ids] = false
@template_object.check_box(@object_name, @method_name, html_options, @value, nil)
end
end
diff --git a/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb b/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb
index a5f72af9ff..c7d28905d0 100644
--- a/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb
@@ -1,4 +1,4 @@
-require "action_view/helpers/tags/collection_helpers"
+require_relative "collection_helpers"
module ActionView
module Helpers
@@ -9,6 +9,7 @@ module ActionView
class RadioButtonBuilder < Builder # :nodoc:
def radio_button(extra_html_options = {})
html_options = extra_html_options.merge(@input_html_options)
+ html_options[:skip_default_ids] = false
@template_object.radio_button(@object_name, @method_name, @value, html_options)
end
end
diff --git a/actionview/lib/action_view/helpers/tags/radio_button.rb b/actionview/lib/action_view/helpers/tags/radio_button.rb
index 43dbd32083..782263ac5b 100644
--- a/actionview/lib/action_view/helpers/tags/radio_button.rb
+++ b/actionview/lib/action_view/helpers/tags/radio_button.rb
@@ -1,4 +1,4 @@
-require "action_view/helpers/tags/checkable"
+require_relative "checkable"
module ActionView
module Helpers
diff --git a/actionview/lib/action_view/helpers/tags/select.rb b/actionview/lib/action_view/helpers/tags/select.rb
index 667c7e945a..380f7a8c4e 100644
--- a/actionview/lib/action_view/helpers/tags/select.rb
+++ b/actionview/lib/action_view/helpers/tags/select.rb
@@ -6,7 +6,7 @@ module ActionView
@choices = block_given? ? template_object.capture { yield || "" } : choices
@choices = @choices.to_a if @choices.is_a?(Range)
- @html_options = html_options
+ @html_options = html_options.except(:skip_default_ids, :allow_method_names_outside_object)
super(object_name, method_name, template_object, options)
end
@@ -33,7 +33,7 @@ module ActionView
# [nil, []]
# { nil => [] }
def grouped_choices?
- !@choices.empty? && @choices.first.respond_to?(:last) && Array === @choices.first.last
+ !@choices.blank? && @choices.first.respond_to?(:last) && Array === @choices.first.last
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/text_area.rb b/actionview/lib/action_view/helpers/tags/text_area.rb
index 31e3a9e9b1..1058fdf55f 100644
--- a/actionview/lib/action_view/helpers/tags/text_area.rb
+++ b/actionview/lib/action_view/helpers/tags/text_area.rb
@@ -1,4 +1,4 @@
-require "action_view/helpers/tags/placeholderable"
+require_relative "placeholderable"
module ActionView
module Helpers
diff --git a/actionview/lib/action_view/helpers/tags/text_field.rb b/actionview/lib/action_view/helpers/tags/text_field.rb
index 613cade7b3..1d55105587 100644
--- a/actionview/lib/action_view/helpers/tags/text_field.rb
+++ b/actionview/lib/action_view/helpers/tags/text_field.rb
@@ -1,4 +1,4 @@
-require "action_view/helpers/tags/placeholderable"
+require_relative "placeholderable"
module ActionView
module Helpers
diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb
index 47ed41a129..b10cfadaed 100644
--- a/actionview/lib/action_view/helpers/translation_helper.rb
+++ b/actionview/lib/action_view/helpers/translation_helper.rb
@@ -1,4 +1,4 @@
-require "action_view/helpers/tag_helper"
+require_relative "tag_helper"
require "active_support/core_ext/string/access"
require "i18n/exceptions"
@@ -11,8 +11,7 @@ module ActionView
include TagHelper
included do
- mattr_accessor :debug_missing_translation
- self.debug_missing_translation = true
+ mattr_accessor :debug_missing_translation, default: true
end
# Delegates to <tt>I18n#translate</tt> but also performs three additional
@@ -96,7 +95,7 @@ module ActionView
raise e if raise_error
keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
- title = "translation missing: #{keys.join('.')}"
+ title = "translation missing: #{keys.join('.')}".dup
interpolations = options.except(:default, :scope)
if interpolations.any?
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index 304db38060..644e1e4391 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -1,4 +1,4 @@
-require "action_view/helpers/javascript_helper"
+require_relative "javascript_helper"
require "active_support/core_ext/array/access"
require "active_support/core_ext/hash/keys"
require "active_support/core_ext/string/output_safety"
@@ -542,7 +542,7 @@ module ActionView
return false unless request.get? || request.head?
- check_parameters ||= !options.is_a?(String) && options.try(:delete, :check_parameters)
+ check_parameters ||= options.is_a?(Hash) && options.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
@@ -552,7 +552,10 @@ module ActionView
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 != "/"
+ if url_string.start_with?("/") && url_string != "/"
+ url_string.chomp!("/")
+ request_uri.chomp!("/")
+ end
if %r{^\w+://}.match?(url_string)
url_string == "#{request.protocol}#{request.host_with_port}#{request_uri}"
@@ -621,11 +624,6 @@ module ActionView
# # => [{name: 'country[name]', value: 'Denmark'}]
def to_form_params(attribute, namespace = nil)
attribute = if attribute.respond_to?(:permitted?)
- unless attribute.permitted?
- raise ArgumentError, "Attempting to generate a button from non-sanitized request parameters!" \
- " Whitelist and sanitize passed parameters to be secure."
- end
-
attribute.to_h
else
attribute
diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb
index 81feb90486..b62fde30e8 100644
--- a/actionview/lib/action_view/layouts.rb
+++ b/actionview/lib/action_view/layouts.rb
@@ -1,4 +1,4 @@
-require "action_view/rendering"
+require_relative "rendering"
require "active_support/core_ext/module/remove_method"
module ActionView
@@ -204,9 +204,9 @@ module ActionView
include ActionView::Rendering
included do
- class_attribute :_layout, :_layout_conditions, instance_accessor: false
- self._layout = nil
- self._layout_conditions = {}
+ class_attribute :_layout, instance_accessor: false
+ class_attribute :_layout_conditions, instance_accessor: false, default: {}
+
_write_layout_method
end
diff --git a/actionview/lib/action_view/log_subscriber.rb b/actionview/lib/action_view/log_subscriber.rb
index d03e1a51b8..613be2b877 100644
--- a/actionview/lib/action_view/log_subscriber.rb
+++ b/actionview/lib/action_view/log_subscriber.rb
@@ -14,7 +14,7 @@ module ActionView
def render_template(event)
info do
- message = " Rendered #{from_rails_root(event.payload[:identifier])}"
+ message = " Rendered #{from_rails_root(event.payload[:identifier])}".dup
message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
message << " (#{event.duration.round(1)}ms)"
end
@@ -22,10 +22,10 @@ module ActionView
def render_partial(event)
info do
- message = " Rendered #{from_rails_root(event.payload[:identifier])}"
+ message = " Rendered #{from_rails_root(event.payload[:identifier])}".dup
message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
message << " (#{event.duration.round(1)}ms)"
- message << " #{cache_message(event.payload)}" if event.payload.key?(:cache_hit)
+ message << " #{cache_message(event.payload)}" unless event.payload[:cache_hit].nil?
message
end
end
@@ -73,9 +73,10 @@ module ActionView
end
def cache_message(payload) # :doc:
- if payload[:cache_hit]
+ case payload[:cache_hit]
+ when :hit
"[cache hit]"
- else
+ when :miss
"[cache miss]"
end
end
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index f385a7cd04..ce5493c01b 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -1,7 +1,7 @@
require "concurrent/map"
require "active_support/core_ext/module/remove_method"
require "active_support/core_ext/module/attribute_accessors"
-require "action_view/template/resolver"
+require_relative "template/resolver"
module ActionView
# = Action View Lookup Context
@@ -14,11 +14,9 @@ module ActionView
class LookupContext #:nodoc:
attr_accessor :prefixes, :rendered_format
- mattr_accessor :fallbacks
- @@fallbacks = FallbackFileSystemResolver.instances
+ mattr_accessor :fallbacks, default: FallbackFileSystemResolver.instances
- mattr_accessor :registered_details
- self.registered_details = []
+ mattr_accessor :registered_details, default: []
def self.register_detail(name, &block)
registered_details << name
diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb
index d344d98f4b..61678933e9 100644
--- a/actionview/lib/action_view/railtie.rb
+++ b/actionview/lib/action_view/railtie.rb
@@ -5,7 +5,7 @@ module ActionView
# = Action View Railtie
class Railtie < Rails::Engine # :nodoc:
config.action_view = ActiveSupport::OrderedOptions.new
- config.action_view.embed_authenticity_token_in_remote_forms = false
+ config.action_view.embed_authenticity_token_in_remote_forms = nil
config.action_view.debug_missing_translation = true
config.eager_load_namespaces << ActionView
@@ -17,6 +17,15 @@ module ActionView
end
end
+ initializer "action_view.form_with_generates_remote_forms" do |app|
+ ActiveSupport.on_load(:action_view) do
+ form_with_generates_remote_forms = app.config.action_view.delete(:form_with_generates_remote_forms)
+ unless form_with_generates_remote_forms.nil?
+ ActionView::Helpers::FormHelper.form_with_generates_remote_forms = form_with_generates_remote_forms
+ end
+ end
+ end
+
initializer "action_view.logger" do
ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger }
end
diff --git a/actionview/lib/action_view/record_identifier.rb b/actionview/lib/action_view/record_identifier.rb
index 48bea315a9..6805513347 100644
--- a/actionview/lib/action_view/record_identifier.rb
+++ b/actionview/lib/action_view/record_identifier.rb
@@ -1,5 +1,5 @@
require "active_support/core_ext/module"
-require "action_view/model_naming"
+require_relative "model_naming"
module ActionView
# RecordIdentifier encapsulates methods used by various ActionView helpers
diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb
index 647b15ea94..77f5084686 100644
--- a/actionview/lib/action_view/renderer/partial_renderer.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer.rb
@@ -1,5 +1,5 @@
require "concurrent/map"
-require "action_view/renderer/partial_renderer/collection_caching"
+require_relative "partial_renderer/collection_caching"
module ActionView
class PartialIteration
@@ -344,7 +344,7 @@ module ActionView
end
content = layout.render(view, locals) { content } if layout
- payload[:cache_hit] = view.cache_hit
+ payload[:cache_hit] = view.view_renderer.cache_hits[@template.virtual_path]
content
end
end
diff --git a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
index 1fbe209200..32663fb80d 100644
--- a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
@@ -5,7 +5,7 @@ module ActionView
included do
# Fallback cache store if Action View is used without Rails.
# Otherwise overridden in Railtie to use Rails.cache.
- mattr_accessor(:collection_cache) { ActiveSupport::Cache::MemoryStore.new }
+ mattr_accessor :collection_cache, default: ActiveSupport::Cache::MemoryStore.new
end
private
@@ -38,7 +38,7 @@ module ActionView
end
def expanded_cache_key(key)
- key = @view.fragment_cache_key(@view.cache_fragment_name(key, virtual_path: @template.virtual_path))
+ key = @view.combined_fragment_cache_key(@view.cache_fragment_name(key, virtual_path: @template.virtual_path))
key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0.
end
diff --git a/actionview/lib/action_view/renderer/renderer.rb b/actionview/lib/action_view/renderer/renderer.rb
index 2a3b89aebf..bcdeb85d30 100644
--- a/actionview/lib/action_view/renderer/renderer.rb
+++ b/actionview/lib/action_view/renderer/renderer.rb
@@ -46,5 +46,9 @@ module ActionView
def render_partial(context, options, &block) #:nodoc:
PartialRenderer.new(@lookup_context).render(context, options, block)
end
+
+ def cache_hits # :nodoc:
+ @cache_hits ||= {}
+ end
end
end
diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb
index cf18562c45..9bee76a1f7 100644
--- a/actionview/lib/action_view/rendering.rb
+++ b/actionview/lib/action_view/rendering.rb
@@ -1,4 +1,4 @@
-require "action_view/view_paths"
+require_relative "view_paths"
module ActionView
# This is a class to fix I18n global state. Whenever you provide I18n.locale during a request,
diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb
index b0e2f1e54e..3f2546664e 100644
--- a/actionview/lib/action_view/template.rb
+++ b/actionview/lib/action_view/template.rb
@@ -282,7 +282,7 @@ module ActionView
# Make sure that the resulting String to be eval'd is in the
# encoding of the code
- source = <<-end_src
+ source = <<-end_src.dup
def #{method_name}(local_assigns, output_buffer)
_old_virtual_path, @virtual_path = @virtual_path, #{@virtual_path.inspect};_old_output_buffer = @output_buffer;#{locals_code};#{code}
ensure
@@ -329,12 +329,12 @@ module ActionView
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}];" }
+ locals.each_with_object("".dup) { |key, code| code << "#{key} = #{key} = local_assigns[:#{key}];" }
end
def method_name
@method_name ||= begin
- m = "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}"
+ m = "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}".dup
m.tr!("-".freeze, "_".freeze)
m
end
diff --git a/actionview/lib/action_view/template/handlers/builder.rb b/actionview/lib/action_view/template/handlers/builder.rb
index e99b921cb7..67ad78133d 100644
--- a/actionview/lib/action_view/template/handlers/builder.rb
+++ b/actionview/lib/action_view/template/handlers/builder.rb
@@ -1,9 +1,7 @@
module ActionView
module Template::Handlers
class Builder
- # Default format used by Builder.
- class_attribute :default_format
- self.default_format = :xml
+ class_attribute :default_format, default: :xml
def call(template)
require_engine
@@ -14,7 +12,6 @@ module ActionView
end
private
-
def require_engine # :doc:
@required ||= begin
require "builder"
diff --git a/actionview/lib/action_view/template/handlers/erb.rb b/actionview/lib/action_view/template/handlers/erb.rb
index 58c7fd1a88..48c2e22a89 100644
--- a/actionview/lib/action_view/template/handlers/erb.rb
+++ b/actionview/lib/action_view/template/handlers/erb.rb
@@ -9,16 +9,13 @@ module ActionView
# Specify trim mode for the ERB compiler. Defaults to '-'.
# See ERB documentation for suitable values.
- class_attribute :erb_trim_mode
- self.erb_trim_mode = "-"
+ class_attribute :erb_trim_mode, default: "-"
# Default implementation used.
- class_attribute :erb_implementation
- self.erb_implementation = Erubi
+ class_attribute :erb_implementation, default: Erubi
# Do not escape templates of these mime types.
- class_attribute :escape_whitelist
- self.escape_whitelist = ["text/plain"]
+ class_attribute :escape_whitelist, default: ["text/plain"]
ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*")
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index d3905b5f23..0ccf398d9a 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -1,7 +1,7 @@
require "pathname"
require "active_support/core_ext/class"
require "active_support/core_ext/module/attribute_accessors"
-require "action_view/template"
+require_relative "../template"
require "thread"
require "concurrent/map"
@@ -14,7 +14,7 @@ module ActionView
alias_method :partial?, :partial
def self.build(name, prefix, partial)
- virtual = ""
+ virtual = "".dup
virtual << "#{prefix}/" unless prefix.empty?
virtual << (partial ? "_#{name}" : name)
new name, prefix, partial, virtual
@@ -125,8 +125,7 @@ module ActionView
end
end
- cattr_accessor :caching
- self.caching = true
+ cattr_accessor :caching, default: true
class << self
alias :caching? :caching
diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb
index ae4fec4337..424a86ba3e 100644
--- a/actionview/lib/action_view/test_case.rb
+++ b/actionview/lib/action_view/test_case.rb
@@ -71,7 +71,7 @@ module ActionView
def helper_method(*methods)
# Almost a duplicate from ActionController::Helpers
methods.flatten.each do |method|
- _helpers.module_eval <<-end_eval
+ _helpers.module_eval <<-end_eval, __FILE__, __LINE__ + 1
def #{method}(*args, &block) # def current_user(*args, &block)
_test_case.send(%(#{method}), *args, &block) # _test_case.send(%(current_user), *args, &block)
end # end
@@ -104,7 +104,7 @@ module ActionView
# empty string ensures buffer has UTF-8 encoding as
# new without arguments returns ASCII-8BIT encoded buffer like String#new
@output_buffer = ActiveSupport::SafeBuffer.new ""
- @rendered = ""
+ @rendered = "".dup
make_test_case_available_to_view!
say_no_to_protect_against_forgery!
diff --git a/actionview/lib/action_view/testing/resolvers.rb b/actionview/lib/action_view/testing/resolvers.rb
index 3188526b63..5e853311e6 100644
--- a/actionview/lib/action_view/testing/resolvers.rb
+++ b/actionview/lib/action_view/testing/resolvers.rb
@@ -1,4 +1,4 @@
-require "action_view/template/resolver"
+require_relative "../template/resolver"
module ActionView #:nodoc:
# Use FixtureResolver in your tests to simulate the presence of files on the
@@ -20,7 +20,7 @@ module ActionView #:nodoc:
private
def query(path, exts, _, _)
- query = ""
+ query = "".dup
EXTENSIONS.each_key do |ext|
query << "(" << exts[ext].map { |e| e && Regexp.escape(".#{e}") }.join("|") << "|)"
end
diff --git a/actionview/lib/action_view/view_paths.rb b/actionview/lib/action_view/view_paths.rb
index f0fe6831fa..938f0fc17f 100644
--- a/actionview/lib/action_view/view_paths.rb
+++ b/actionview/lib/action_view/view_paths.rb
@@ -3,9 +3,7 @@ module ActionView
extend ActiveSupport::Concern
included do
- class_attribute :_view_paths
- self._view_paths = ActionView::PathSet.new
- _view_paths.freeze
+ class_attribute :_view_paths, default: ActionView::PathSet.new.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 dde66a7ba0..a7d706c5e1 100644
--- a/actionview/test/abstract_unit.rb
+++ b/actionview/test/abstract_unit.rb
@@ -1,8 +1,8 @@
-$:.unshift(File.dirname(__FILE__) + "/lib")
-$:.unshift(File.dirname(__FILE__) + "/fixtures/helpers")
-$:.unshift(File.dirname(__FILE__) + "/fixtures/alternate_helpers")
+$:.unshift File.expand_path("lib", __dir__)
+$:.unshift File.expand_path("fixtures/helpers", __dir__)
+$:.unshift File.expand_path("fixtures/alternate_helpers", __dir__)
-ENV["TMPDIR"] = File.join(File.dirname(__FILE__), "tmp")
+ENV["TMPDIR"] = File.expand_path("tmp", __dir__)
require "active_support/core_ext/kernel/reporting"
@@ -47,7 +47,7 @@ I18n.backend.store_translations "da", {}
I18n.backend.store_translations "pt-BR", {}
ORIGINAL_LOCALES = I18n.available_locales.map(&:to_s).sort
-FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), "fixtures")
+FIXTURE_LOAD_PATH = File.expand_path("fixtures", __dir__)
module RenderERBUtils
def view
@@ -133,7 +133,7 @@ class BasicController
def config
@config ||= ActiveSupport::InheritableOptions.new(ActionController::Base.config).tap do |config|
# VIEW TODO: View tests should not require a controller
- public_dir = File.expand_path("../fixtures/public", __FILE__)
+ public_dir = File.expand_path("fixtures/public", __dir__)
config.assets_dir = public_dir
config.javascripts_dir = "#{public_dir}/javascripts"
config.stylesheets_dir = "#{public_dir}/stylesheets"
@@ -196,7 +196,7 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
end
def with_autoload_path(path)
- path = File.join(File.dirname(__FILE__), "fixtures", path)
+ path = File.join(File.expand_path("fixtures", __dir__), path)
if ActiveSupport::Dependencies.autoload_paths.include?(path)
yield
else
diff --git a/actionview/test/actionpack/abstract/abstract_controller_test.rb b/actionview/test/actionpack/abstract/abstract_controller_test.rb
index a2cd3deb58..8f65a61493 100644
--- a/actionview/test/actionpack/abstract/abstract_controller_test.rb
+++ b/actionview/test/actionpack/abstract/abstract_controller_test.rb
@@ -42,7 +42,7 @@ module AbstractController
super
end
- append_view_path File.expand_path(File.join(File.dirname(__FILE__), "views"))
+ append_view_path File.expand_path("views", __dir__)
end
class Me2 < RenderingController
@@ -152,7 +152,7 @@ module AbstractController
class OverridingLocalPrefixes < AbstractController::Base
include AbstractController::Rendering
include ActionView::Rendering
- append_view_path File.expand_path(File.join(File.dirname(__FILE__), "views"))
+ append_view_path File.expand_path("views", __dir__)
def index
render
diff --git a/actionview/test/actionpack/abstract/helper_test.rb b/actionview/test/actionpack/abstract/helper_test.rb
index 83237518d7..13922e4485 100644
--- a/actionview/test/actionpack/abstract/helper_test.rb
+++ b/actionview/test/actionpack/abstract/helper_test.rb
@@ -1,6 +1,6 @@
require "abstract_unit"
-ActionController::Base.helpers_path = File.expand_path("../../../fixtures/helpers", __FILE__)
+ActionController::Base.helpers_path = File.expand_path("../../fixtures/helpers", __dir__)
module AbstractController
module Testing
@@ -51,7 +51,7 @@ module AbstractController
class AbstractInvalidHelpers < AbstractHelpers
include ActionController::Helpers
- path = File.expand_path("../../../fixtures/helpers_missing", __FILE__)
+ path = File.expand_path("../../fixtures/helpers_missing", __dir__)
$:.unshift(path)
self.helpers_path = path
end
diff --git a/actionview/test/actionpack/controller/capture_test.rb b/actionview/test/actionpack/controller/capture_test.rb
index f0ae609845..cc3a23c60c 100644
--- a/actionview/test/actionpack/controller/capture_test.rb
+++ b/actionview/test/actionpack/controller/capture_test.rb
@@ -2,7 +2,7 @@ require "abstract_unit"
require "active_support/logger"
class CaptureController < ActionController::Base
- self.view_paths = [ File.dirname(__FILE__) + "/../../fixtures/actionpack" ]
+ self.view_paths = [ File.expand_path("../../fixtures/actionpack", __dir__) ]
def self.controller_name; "test"; end
def self.controller_path; "test"; end
diff --git a/actionview/test/actionpack/controller/layout_test.rb b/actionview/test/actionpack/controller/layout_test.rb
index b79835ff34..b3e0329f57 100644
--- a/actionview/test/actionpack/controller/layout_test.rb
+++ b/actionview/test/actionpack/controller/layout_test.rb
@@ -5,7 +5,7 @@ require "active_support/core_ext/array/extract_options"
# method has access to the view_paths array when looking for a layout to automatically assign.
old_load_paths = ActionController::Base.view_paths
-ActionController::Base.view_paths = [ File.dirname(__FILE__) + "/../../fixtures/actionpack/layout_tests/" ]
+ActionController::Base.view_paths = [ File.expand_path("../../fixtures/actionpack/layout_tests", __dir__) ]
class LayoutTest < ActionController::Base
def self.controller_path; "views" end
@@ -96,7 +96,7 @@ class StreamingLayoutController < LayoutTest
end
class AbsolutePathLayoutController < LayoutTest
- layout File.expand_path(File.expand_path(__FILE__) + "/../../../fixtures/actionpack/layout_tests/layouts/layout_test")
+ layout File.expand_path("../../fixtures/actionpack/layout_tests/layouts/layout_test", __dir__)
end
class HasOwnLayoutController < LayoutTest
@@ -117,7 +117,7 @@ end
class PrependsViewPathController < LayoutTest
def hello
- prepend_view_path File.dirname(__FILE__) + "/../../fixtures/actionpack/layout_tests/alt/"
+ prepend_view_path File.expand_path("../../fixtures/actionpack/layout_tests/alt", __dir__)
render layout: "alt"
end
end
diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb
index 51ec8899b1..6528169312 100644
--- a/actionview/test/actionpack/controller/render_test.rb
+++ b/actionview/test/actionpack/controller/render_test.rb
@@ -56,7 +56,7 @@ class TestController < ApplicationController
end
def hello_world_file
- render file: File.expand_path("../../../fixtures/actionpack/hello", __FILE__), formats: [:html]
+ render file: File.expand_path("../../fixtures/actionpack/hello", __dir__), formats: [:html]
end
# :ported:
@@ -125,7 +125,7 @@ class TestController < ApplicationController
# :ported:
def render_file_with_instance_variables
@secret = "in the sauce"
- path = File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_ivar")
+ path = File.expand_path("../../fixtures/test/render_file_with_ivar", __dir__)
render file: path
end
@@ -142,21 +142,21 @@ class TestController < ApplicationController
def render_file_using_pathname
@secret = "in the sauce"
- render file: Pathname.new(File.dirname(__FILE__)).join("..", "..", "fixtures", "test", "dot.directory", "render_file_with_ivar")
+ render file: Pathname.new(__dir__).join("..", "..", "fixtures", "test", "dot.directory", "render_file_with_ivar")
end
def render_file_from_template
@secret = "in the sauce"
- @path = File.expand_path(File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_ivar"))
+ @path = File.expand_path("../../fixtures/test/render_file_with_ivar", __dir__)
end
def render_file_with_locals
- path = File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_locals")
+ path = File.expand_path("../../fixtures/test/render_file_with_locals", __dir__)
render file: path, locals: { secret: "in the sauce" }
end
def render_file_as_string_with_locals
- path = File.expand_path(File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_locals"))
+ path = File.expand_path("../../fixtures/test/render_file_with_locals", __dir__)
render file: path, locals: { secret: "in the sauce" }
end
diff --git a/actionview/test/active_record_unit.rb b/actionview/test/active_record_unit.rb
index 7f94b7ebb4..901c0e2b3e 100644
--- a/actionview/test/active_record_unit.rb
+++ b/actionview/test/active_record_unit.rb
@@ -13,7 +13,7 @@ end
# Try to grab AR
unless defined?(ActiveRecord) && defined?(FixtureSet)
begin
- PATH_TO_AR = "#{File.dirname(__FILE__)}/../../activerecord/lib"
+ PATH_TO_AR = File.expand_path("../../activerecord/lib", __dir__)
raise LoadError, "#{PATH_TO_AR} doesn't exist" unless File.directory?(PATH_TO_AR)
$LOAD_PATH.unshift PATH_TO_AR
require "active_record"
@@ -58,13 +58,13 @@ class ActiveRecordTestConnector
# Load actionpack sqlite3 tables
def load_schema
- File.read(File.dirname(__FILE__) + "/fixtures/db_definitions/sqlite.sql").split(";").each do |sql|
+ File.read(File.expand_path("fixtures/db_definitions/sqlite.sql", __dir__)).split(";").each do |sql|
ActiveRecord::Base.connection.execute(sql) unless sql.blank?
end
end
def require_fixture_models
- Dir.glob(File.dirname(__FILE__) + "/fixtures/*.rb").each { |f| require f }
+ Dir.glob(File.expand_path("fixtures/*.rb", __dir__)).each { |f| require f }
end
end
end
diff --git a/actionview/test/activerecord/controller_runtime_test.rb b/actionview/test/activerecord/controller_runtime_test.rb
index 590559f592..1cec5072c0 100644
--- a/actionview/test/activerecord/controller_runtime_test.rb
+++ b/actionview/test/activerecord/controller_runtime_test.rb
@@ -67,7 +67,7 @@ class ControllerRuntimeLogSubscriberTest < ActionController::TestCase
wait
assert_equal 2, @logger.logged(:info).size
- assert_match(/\(Views: [\d.]+ms \| ActiveRecord: 0.0ms\)/, @logger.logged(:info)[1])
+ assert_match(/\(Views: [\d.]+ms \| ActiveRecord: 0\.0ms\)/, @logger.logged(:info)[1])
end
def test_log_with_active_record_when_post
diff --git a/actionview/test/activerecord/form_helper_activerecord_test.rb b/actionview/test/activerecord/form_helper_activerecord_test.rb
index 3b314588c7..9949c3fde0 100644
--- a/actionview/test/activerecord/form_helper_activerecord_test.rb
+++ b/actionview/test/activerecord/form_helper_activerecord_test.rb
@@ -55,7 +55,7 @@ class FormHelperActiveRecordTest < ActionView::TestCase
private
def hidden_fields(method = nil)
- txt = %{<input name="utf8" type="hidden" value="&#x2713;" />}
+ txt = %{<input name="utf8" type="hidden" value="&#x2713;" />}.dup
if method && !%w(get post).include?(method.to_s)
txt << %{<input name="_method" type="hidden" value="#{method}" />}
@@ -65,7 +65,7 @@ class FormHelperActiveRecordTest < ActionView::TestCase
end
def form_text(action = "/", id = nil, html_class = nil, remote = nil, multipart = nil, method = nil)
- txt = %{<form accept-charset="UTF-8" action="#{action}"}
+ txt = %{<form accept-charset="UTF-8" action="#{action}"}.dup
txt << %{ enctype="multipart/form-data"} if multipart
txt << %{ data-remote="true"} if remote
txt << %{ class="#{html_class}"} if html_class
diff --git a/actionview/test/activerecord/relation_cache_test.rb b/actionview/test/activerecord/relation_cache_test.rb
index 43f7242ee9..d12c426586 100644
--- a/actionview/test/activerecord/relation_cache_test.rb
+++ b/actionview/test/activerecord/relation_cache_test.rb
@@ -4,13 +4,17 @@ class RelationCacheTest < ActionView::TestCase
tests ActionView::Helpers::CacheHelper
def setup
- @virtual_path = "path"
+ view_paths = ActionController::Base.view_paths
+ lookup_context = ActionView::LookupContext.new(view_paths, {}, ["test"])
+ @view_renderer = ActionView::Renderer.new(lookup_context)
+ @virtual_path = "path"
+
controller.cache_store = ActiveSupport::Cache::MemoryStore.new
end
def test_cache_relation_other
cache(Project.all) { concat("Hello World") }
- assert_equal "Hello World", controller.cache_store.read("views/projects-#{Project.count}/")
+ assert_equal "Hello World", controller.cache_store.read("views/path/projects-#{Project.count}")
end
def view_cache_dependencies; end
diff --git a/actionview/test/fixtures/actionpack/layouts/builder.builder b/actionview/test/fixtures/actionpack/layouts/builder.builder
index 7c7d4b2dd1..c55488edd0 100644
--- a/actionview/test/fixtures/actionpack/layouts/builder.builder
+++ b/actionview/test/fixtures/actionpack/layouts/builder.builder
@@ -1,3 +1,3 @@
xml.wrapper do
xml << yield
-end \ No newline at end of file
+end
diff --git a/actionview/test/fixtures/actionpack/test/_hello.builder b/actionview/test/fixtures/actionpack/test/_hello.builder
index ef52f632d1..fc72df16d0 100644
--- a/actionview/test/fixtures/actionpack/test/_hello.builder
+++ b/actionview/test/fixtures/actionpack/test/_hello.builder
@@ -1 +1 @@
-xm.hello \ No newline at end of file
+xm.hello
diff --git a/actionview/test/fixtures/actionpack/test/formatted_xml_erb.builder b/actionview/test/fixtures/actionpack/test/formatted_xml_erb.builder
index 14fd3549fb..f98aaa34a5 100644
--- a/actionview/test/fixtures/actionpack/test/formatted_xml_erb.builder
+++ b/actionview/test/fixtures/actionpack/test/formatted_xml_erb.builder
@@ -1 +1 @@
-xml.test 'failed' \ No newline at end of file
+xml.test "failed"
diff --git a/actionview/test/fixtures/actionpack/test/hello.builder b/actionview/test/fixtures/actionpack/test/hello.builder
index a471553941..b8ab17ad5b 100644
--- a/actionview/test/fixtures/actionpack/test/hello.builder
+++ b/actionview/test/fixtures/actionpack/test/hello.builder
@@ -1,4 +1,4 @@
xml.html do
xml.p "Hello #{@name}"
- xml << render(:file => "test/greeting")
-end \ No newline at end of file
+ xml << render(file: "test/greeting")
+end
diff --git a/actionview/test/fixtures/actionpack/test/hello_world_container.builder b/actionview/test/fixtures/actionpack/test/hello_world_container.builder
index e48d75c405..24032ab5e0 100644
--- a/actionview/test/fixtures/actionpack/test/hello_world_container.builder
+++ b/actionview/test/fixtures/actionpack/test/hello_world_container.builder
@@ -1,3 +1,3 @@
xml.test do
- render :partial => 'hello', :locals => { :xm => xml }
-end \ No newline at end of file
+ render partial: "hello", locals: { xm: xml }
+end
diff --git a/actionview/test/fixtures/actionpack/test/hello_xml_world.builder b/actionview/test/fixtures/actionpack/test/hello_xml_world.builder
index e7081b89fe..d16bb6b5cb 100644
--- a/actionview/test/fixtures/actionpack/test/hello_xml_world.builder
+++ b/actionview/test/fixtures/actionpack/test/hello_xml_world.builder
@@ -8,4 +8,4 @@ xml.html do
xml.p "monks"
xml.p "wiseguys"
end
-end \ No newline at end of file
+end
diff --git a/actionview/test/fixtures/actionpack/test/non_erb_block_content_for.builder b/actionview/test/fixtures/actionpack/test/non_erb_block_content_for.builder
index d539a425a4..cd65da751b 100644
--- a/actionview/test/fixtures/actionpack/test/non_erb_block_content_for.builder
+++ b/actionview/test/fixtures/actionpack/test/non_erb_block_content_for.builder
@@ -1,4 +1,4 @@
content_for :title do
- 'Putting stuff in the title!'
+ "Putting stuff in the title!"
end
xml << "Great stuff!"
diff --git a/actionview/test/fixtures/comments/empty.html+grid.erb b/actionview/test/fixtures/comments/empty.html+grid.erb
new file mode 100644
index 0000000000..dc3fa32a81
--- /dev/null
+++ b/actionview/test/fixtures/comments/empty.html+grid.erb
@@ -0,0 +1 @@
+<h1>No Comment</h1>
diff --git a/actionview/test/fixtures/comments/empty.html.builder b/actionview/test/fixtures/comments/empty.html.builder
index 2b0c7207a3..12d6fdd9a5 100644
--- a/actionview/test/fixtures/comments/empty.html.builder
+++ b/actionview/test/fixtures/comments/empty.html.builder
@@ -1 +1 @@
-xml.h1 'No Comment' \ No newline at end of file
+xml.h1 "No Comment"
diff --git a/actionview/test/fixtures/ruby_template.ruby b/actionview/test/fixtures/ruby_template.ruby
index 5097bce47c..93334610a8 100644
--- a/actionview/test/fixtures/ruby_template.ruby
+++ b/actionview/test/fixtures/ruby_template.ruby
@@ -1,2 +1,2 @@
-body = ""
+body = "".dup
body << ["Hello", "from", "Ruby", "code"].join(" ")
diff --git a/actionview/test/fixtures/test/_cached_nested_cached_customer.erb b/actionview/test/fixtures/test/_cached_nested_cached_customer.erb
new file mode 100644
index 0000000000..01bf025cd3
--- /dev/null
+++ b/actionview/test/fixtures/test/_cached_nested_cached_customer.erb
@@ -0,0 +1,3 @@
+<% cache cached_customer do %>
+ <%= render partial: "test/cached_customer", locals: { cached_customer: cached_customer } %>
+<% end %>
diff --git a/actionview/test/fixtures/test/_nested_cached_customer.erb b/actionview/test/fixtures/test/_nested_cached_customer.erb
new file mode 100644
index 0000000000..f43adc94c9
--- /dev/null
+++ b/actionview/test/fixtures/test/_nested_cached_customer.erb
@@ -0,0 +1 @@
+<%= render partial: "test/cached_customer", locals: { cached_customer: cached_customer } %>
diff --git a/actionview/test/fixtures/test/_partial_with_variants.html+grid.erb b/actionview/test/fixtures/test/_partial_with_variants.html+grid.erb
new file mode 100644
index 0000000000..225363c8c3
--- /dev/null
+++ b/actionview/test/fixtures/test/_partial_with_variants.html+grid.erb
@@ -0,0 +1 @@
+<h1>Partial with variants</h1>
diff --git a/actionview/test/fixtures/test/hello.builder b/actionview/test/fixtures/test/hello.builder
index a471553941..b8ab17ad5b 100644
--- a/actionview/test/fixtures/test/hello.builder
+++ b/actionview/test/fixtures/test/hello.builder
@@ -1,4 +1,4 @@
xml.html do
xml.p "Hello #{@name}"
- xml << render(:file => "test/greeting")
-end \ No newline at end of file
+ xml << render(file: "test/greeting")
+end
diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb
index 07a6452cc1..d1190b1bd7 100644
--- a/actionview/test/template/asset_tag_helper_test.rb
+++ b/actionview/test/template/asset_tag_helper_test.rb
@@ -53,6 +53,7 @@ class AssetTagHelperTest < ActionView::TestCase
%(auto_discovery_link_tag) => %(<link href="http://www.example.com" rel="alternate" title="RSS" type="application/rss+xml" />),
%(auto_discovery_link_tag(:rss)) => %(<link href="http://www.example.com" rel="alternate" title="RSS" type="application/rss+xml" />),
%(auto_discovery_link_tag(:atom)) => %(<link href="http://www.example.com" rel="alternate" title="ATOM" type="application/atom+xml" />),
+ %(auto_discovery_link_tag(:json)) => %(<link href="http://www.example.com" rel="alternate" title="JSON" type="application/json" />),
%(auto_discovery_link_tag(:rss, :action => "feed")) => %(<link href="http://www.example.com" rel="alternate" title="RSS" type="application/rss+xml" />),
%(auto_discovery_link_tag(:rss, "http://localhost/feed")) => %(<link href="http://localhost/feed" rel="alternate" title="RSS" type="application/rss+xml" />),
%(auto_discovery_link_tag(:rss, "//localhost/feed")) => %(<link href="//localhost/feed" rel="alternate" title="RSS" type="application/rss+xml" />),
@@ -196,7 +197,10 @@ class AssetTagHelperTest < ActionView::TestCase
%(image_tag("mouse.png", :alt => nil)) => %(<img src="/images/mouse.png" />),
%(image_tag("data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==", :alt => nil)) => %(<img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" />),
%(image_tag("")) => %(<img src="" />),
- %(image_tag("gold.png", data: { title: 'Rails Application' })) => %(<img data-title="Rails Application" src="/images/gold.png" alt="Gold" />)
+ %(image_tag("gold.png", data: { title: 'Rails Application' })) => %(<img data-title="Rails Application" src="/images/gold.png" alt="Gold" />),
+ %(image_tag("rss.gif", srcset: "/assets/pic_640.jpg 640w, /assets/pic_1024.jpg 1024w")) => %(<img srcset="/assets/pic_640.jpg 640w, /assets/pic_1024.jpg 1024w" src="/images/rss.gif" alt="Rss" />),
+ %(image_tag("rss.gif", srcset: { "pic_640.jpg" => "640w", "pic_1024.jpg" => "1024w" })) => %(<img srcset="/images/pic_640.jpg 640w, /images/pic_1024.jpg 1024w" src="/images/rss.gif" alt="Rss" />),
+ %(image_tag("rss.gif", srcset: [["pic_640.jpg", "640w"], ["pic_1024.jpg", "1024w"]])) => %(<img srcset="/images/pic_640.jpg 640w, /images/pic_1024.jpg 1024w" src="/images/rss.gif" alt="Rss" />)
}
FaviconLinkToTag = {
@@ -709,13 +713,13 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase
def test_should_wildcard_asset_host
@controller.config.asset_host = "http://a%d.example.com"
- assert_match(%r(http://a[0123].example.com), compute_asset_host("foo"))
+ assert_match(%r(http://a[0123]\.example\.com), compute_asset_host("foo"))
end
def test_should_wildcard_asset_host_between_zero_and_four
@controller.config.asset_host = "http://a%d.example.com"
- assert_match(%r(http://a[0123].example.com/collaboration/hieraki/images/xml.png), image_path("xml.png"))
- assert_match(%r(http://a[0123].example.com/collaboration/hieraki/images/xml.png), image_url("xml.png"))
+ assert_match(%r(http://a[0123]\.example\.com/collaboration/hieraki/images/xml\.png), image_path("xml.png"))
+ assert_match(%r(http://a[0123]\.example\.com/collaboration/hieraki/images/xml\.png), image_url("xml.png"))
end
def test_asset_host_without_protocol_should_be_protocol_relative
diff --git a/actionview/test/template/atom_feed_helper_test.rb b/actionview/test/template/atom_feed_helper_test.rb
index 1245a1a966..7fa5e042fb 100644
--- a/actionview/test/template/atom_feed_helper_test.rb
+++ b/actionview/test/template/atom_feed_helper_test.rb
@@ -194,7 +194,7 @@ class ScrollsController < ActionController::Base
FEEDS["provide_builder"] = <<-'EOT'
# we pass in the new_xml to the helper so it doesn't
# call anything on the original builder
- new_xml = Builder::XmlMarkup.new(:target=>'')
+ new_xml = Builder::XmlMarkup.new(:target=>''.dup)
atom_feed(:xml => new_xml) do |feed|
feed.title("My great blog!")
feed.updated(@scrolls.first.created_at)
@@ -301,8 +301,8 @@ class AtomFeedTest < ActionController::TestCase
with_restful_routing(:scrolls) do
get :index, params: { id: "feed_with_atomPub_namespace" }
assert_match %r{xml:lang="en-US"}, @response.body
- assert_match %r{xmlns="http://www.w3.org/2005/Atom"}, @response.body
- assert_match %r{xmlns:app="http://www.w3.org/2007/app"}, @response.body
+ assert_match %r{xmlns="http://www\.w3\.org/2005/Atom"}, @response.body
+ assert_match %r{xmlns:app="http://www\.w3\.org/2007/app"}, @response.body
end
end
@@ -319,7 +319,7 @@ class AtomFeedTest < ActionController::TestCase
with_restful_routing(:scrolls) do
get :index, params: { id: "feed_with_xml_processing_instructions" }
assert_match %r{<\?xml-stylesheet [^\?]*type="text/css"}, @response.body
- assert_match %r{<\?xml-stylesheet [^\?]*href="t.css"}, @response.body
+ assert_match %r{<\?xml-stylesheet [^\?]*href="t\.css"}, @response.body
end
end
@@ -334,7 +334,7 @@ class AtomFeedTest < ActionController::TestCase
def test_feed_xhtml
with_restful_routing(:scrolls) do
get :index, params: { id: "feed_with_xhtml_content" }
- assert_match %r{xmlns="http://www.w3.org/1999/xhtml"}, @response.body
+ assert_match %r{xmlns="http://www\.w3\.org/1999/xhtml"}, @response.body
assert_select "summary", text: /Something Boring/
assert_select "summary", text: /after 2/
end
diff --git a/actionview/test/template/date_helper_test.rb b/actionview/test/template/date_helper_test.rb
index d257147e1f..b9b8194e69 100644
--- a/actionview/test/template/date_helper_test.rb
+++ b/actionview/test/template/date_helper_test.rb
@@ -128,11 +128,29 @@ class DateHelperTest < ActionView::TestCase
assert_distance_of_time_in_words(from)
end
- def test_distance_in_words_with_mathn_required
- # test we avoid Integer#/ (redefined by mathn)
- silence_warnings { require "mathn" }
+ def test_distance_in_words_with_nil_input
+ assert_raises(ArgumentError) { distance_of_time_in_words(nil) }
+ assert_raises(ArgumentError) { distance_of_time_in_words(0, nil) }
+ end
+
+ def test_distance_in_words_with_mixed_argument_types
+ assert_equal "1 minute", distance_of_time_in_words(0, Time.at(60))
+ assert_equal "10 minutes", distance_of_time_in_words(Time.at(600), 0)
+ end
+
+ def test_distance_in_words_doesnt_use_the_quotient_operator
+ rubinius_skip "Date is written in Ruby and relies on Fixnum#/"
+ jruby_skip "Date is written in Ruby and relies on Fixnum#/"
+
+ klass = RUBY_VERSION > "2.4" ? Integer : Fixnum
+
+ # Make sure that we avoid {Integer,Fixnum}#/ (redefined by mathn)
+ klass.send :private, :/
+
from = Time.utc(2004, 6, 6, 21, 45, 0)
assert_distance_of_time_in_words(from)
+ ensure
+ klass.send :public, :/
end
def test_time_ago_in_words_passes_include_seconds
@@ -196,7 +214,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_day
- expected = %(<select id="date_day" name="date[day]">\n)
+ expected = %(<select id="date_day" name="date[day]">\n).dup
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
expected << "</select>\n"
@@ -205,7 +223,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_day_with_blank
- expected = %(<select id="date_day" name="date[day]">\n)
+ expected = %(<select id="date_day" name="date[day]">\n).dup
expected << %(<option value=""></option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
expected << "</select>\n"
@@ -214,7 +232,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_day_nil_with_blank
- expected = %(<select id="date_day" name="date[day]">\n)
+ expected = %(<select id="date_day" name="date[day]">\n).dup
expected << %(<option value=""></option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
expected << "</select>\n"
@@ -222,7 +240,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_day_with_two_digit_numbers
- expected = %(<select id="date_day" name="date[day]">\n)
+ expected = %(<select id="date_day" name="date[day]">\n).dup
expected << %(<option value="1">01</option>\n<option selected="selected" value="2">02</option>\n<option value="3">03</option>\n<option value="4">04</option>\n<option value="5">05</option>\n<option value="6">06</option>\n<option value="7">07</option>\n<option value="8">08</option>\n<option value="9">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
expected << "</select>\n"
@@ -231,7 +249,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_day_with_html_options
- expected = %(<select id="date_day" name="date[day]" class="selector">\n)
+ expected = %(<select id="date_day" name="date[day]" class="selector">\n).dup
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
expected << "</select>\n"
@@ -240,7 +258,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_day_with_default_prompt
- expected = %(<select id="date_day" name="date[day]">\n)
+ expected = %(<select id="date_day" name="date[day]">\n).dup
expected << %(<option value="">Day</option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
expected << "</select>\n"
@@ -248,7 +266,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_day_with_custom_prompt
- expected = %(<select id="date_day" name="date[day]">\n)
+ expected = %(<select id="date_day" name="date[day]">\n).dup
expected << %(<option value="">Choose day</option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
expected << "</select>\n"
@@ -256,7 +274,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_day_with_generic_with_css_classes
- expected = %(<select id="date_day" name="date[day]" class="day">\n)
+ expected = %(<select id="date_day" name="date[day]" class="day">\n).dup
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
expected << "</select>\n"
@@ -264,7 +282,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_day_with_custom_with_css_classes
- expected = %(<select id="date_day" name="date[day]" class="my-day">\n)
+ expected = %(<select id="date_day" name="date[day]" class="my-day">\n).dup
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
expected << "</select>\n"
@@ -272,7 +290,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_month
- expected = %(<select id="date_month" name="date[month]">\n)
+ expected = %(<select id="date_month" name="date[month]">\n).dup
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
expected << "</select>\n"
@@ -281,7 +299,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_month_with_two_digit_numbers
- expected = %(<select id="date_month" name="date[month]">\n)
+ expected = %(<select id="date_month" name="date[month]">\n).dup
expected << %(<option value="1">01</option>\n<option value="2">02</option>\n<option value="3">03</option>\n<option value="4">04</option>\n<option value="5">05</option>\n<option value="6">06</option>\n<option value="7">07</option>\n<option value="8" selected="selected">08</option>\n<option value="9">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n)
expected << "</select>\n"
@@ -290,7 +308,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_month_with_disabled
- expected = %(<select id="date_month" name="date[month]" disabled="disabled">\n)
+ expected = %(<select id="date_month" name="date[month]" disabled="disabled">\n).dup
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
expected << "</select>\n"
@@ -299,7 +317,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_month_with_field_name_override
- expected = %(<select id="date_mois" name="date[mois]">\n)
+ expected = %(<select id="date_mois" name="date[mois]">\n).dup
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
expected << "</select>\n"
@@ -308,7 +326,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_month_with_blank
- expected = %(<select id="date_month" name="date[month]">\n)
+ expected = %(<select id="date_month" name="date[month]">\n).dup
expected << %(<option value=""></option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
expected << "</select>\n"
@@ -317,7 +335,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_month_nil_with_blank
- expected = %(<select id="date_month" name="date[month]">\n)
+ expected = %(<select id="date_month" name="date[month]">\n).dup
expected << %(<option value=""></option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
expected << "</select>\n"
@@ -325,7 +343,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_month_with_numbers
- expected = %(<select id="date_month" name="date[month]">\n)
+ expected = %(<select id="date_month" name="date[month]">\n).dup
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8" selected="selected">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n)
expected << "</select>\n"
@@ -334,7 +352,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_month_with_numbers_and_names
- expected = %(<select id="date_month" name="date[month]">\n)
+ expected = %(<select id="date_month" name="date[month]">\n).dup
expected << %(<option value="1">1 - January</option>\n<option value="2">2 - February</option>\n<option value="3">3 - March</option>\n<option value="4">4 - April</option>\n<option value="5">5 - May</option>\n<option value="6">6 - June</option>\n<option value="7">7 - July</option>\n<option value="8" selected="selected">8 - August</option>\n<option value="9">9 - September</option>\n<option value="10">10 - October</option>\n<option value="11">11 - November</option>\n<option value="12">12 - December</option>\n)
expected << "</select>\n"
@@ -343,7 +361,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_month_with_format_string
- expected = %(<select id="date_month" name="date[month]">\n)
+ expected = %(<select id="date_month" name="date[month]">\n).dup
expected << %(<option value="1">January (01)</option>\n<option value="2">February (02)</option>\n<option value="3">March (03)</option>\n<option value="4">April (04)</option>\n<option value="5">May (05)</option>\n<option value="6">June (06)</option>\n<option value="7">July (07)</option>\n<option value="8" selected="selected">August (08)</option>\n<option value="9">September (09)</option>\n<option value="10">October (10)</option>\n<option value="11">November (11)</option>\n<option value="12">December (12)</option>\n)
expected << "</select>\n"
@@ -353,7 +371,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_month_with_numbers_and_names_with_abbv
- expected = %(<select id="date_month" name="date[month]">\n)
+ expected = %(<select id="date_month" name="date[month]">\n).dup
expected << %(<option value="1">1 - Jan</option>\n<option value="2">2 - Feb</option>\n<option value="3">3 - Mar</option>\n<option value="4">4 - Apr</option>\n<option value="5">5 - May</option>\n<option value="6">6 - Jun</option>\n<option value="7">7 - Jul</option>\n<option value="8" selected="selected">8 - Aug</option>\n<option value="9">9 - Sep</option>\n<option value="10">10 - Oct</option>\n<option value="11">11 - Nov</option>\n<option value="12">12 - Dec</option>\n)
expected << "</select>\n"
@@ -362,7 +380,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_month_with_abbv
- expected = %(<select id="date_month" name="date[month]">\n)
+ expected = %(<select id="date_month" name="date[month]">\n).dup
expected << %(<option value="1">Jan</option>\n<option value="2">Feb</option>\n<option value="3">Mar</option>\n<option value="4">Apr</option>\n<option value="5">May</option>\n<option value="6">Jun</option>\n<option value="7">Jul</option>\n<option value="8" selected="selected">Aug</option>\n<option value="9">Sep</option>\n<option value="10">Oct</option>\n<option value="11">Nov</option>\n<option value="12">Dec</option>\n)
expected << "</select>\n"
@@ -373,7 +391,7 @@ class DateHelperTest < ActionView::TestCase
def test_select_month_with_custom_names
month_names = %w(nil Januar Februar Marts April Maj Juni Juli August September Oktober November December)
- expected = %(<select id="date_month" name="date[month]">\n)
+ expected = %(<select id="date_month" name="date[month]">\n).dup
1.upto(12) { |month| expected << %(<option value="#{month}"#{' selected="selected"' if month == 8}>#{month_names[month]}</option>\n) }
expected << "</select>\n"
@@ -384,7 +402,7 @@ class DateHelperTest < ActionView::TestCase
def test_select_month_with_zero_indexed_custom_names
month_names = %w(Januar Februar Marts April Maj Juni Juli August September Oktober November December)
- expected = %(<select id="date_month" name="date[month]">\n)
+ expected = %(<select id="date_month" name="date[month]">\n).dup
1.upto(12) { |month| expected << %(<option value="#{month}"#{' selected="selected"' if month == 8}>#{month_names[month - 1]}</option>\n) }
expected << "</select>\n"
@@ -401,7 +419,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_month_with_html_options
- expected = %(<select id="date_month" name="date[month]" class="selector" accesskey="M">\n)
+ expected = %(<select id="date_month" name="date[month]" class="selector" accesskey="M">\n).dup
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
expected << "</select>\n"
@@ -409,7 +427,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_month_with_default_prompt
- expected = %(<select id="date_month" name="date[month]">\n)
+ expected = %(<select id="date_month" name="date[month]">\n).dup
expected << %(<option value="">Month</option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
expected << "</select>\n"
@@ -417,7 +435,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_month_with_custom_prompt
- expected = %(<select id="date_month" name="date[month]">\n)
+ expected = %(<select id="date_month" name="date[month]">\n).dup
expected << %(<option value="">Choose month</option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
expected << "</select>\n"
@@ -425,7 +443,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_month_with_generic_with_css_classes
- expected = %(<select id="date_month" name="date[month]" class="month">\n)
+ expected = %(<select id="date_month" name="date[month]" class="month">\n).dup
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
expected << "</select>\n"
@@ -433,7 +451,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_month_with_custom_with_css_classes
- expected = %(<select id="date_month" name="date[month]" class="my-month">\n)
+ expected = %(<select id="date_month" name="date[month]" class="my-month">\n).dup
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
expected << "</select>\n"
@@ -441,7 +459,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_year
- expected = %(<select id="date_year" name="date[year]">\n)
+ expected = %(<select id="date_year" name="date[year]">\n).dup
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -450,7 +468,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_year_with_disabled
- expected = %(<select id="date_year" name="date[year]" disabled="disabled">\n)
+ expected = %(<select id="date_year" name="date[year]" disabled="disabled">\n).dup
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -459,7 +477,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_year_with_field_name_override
- expected = %(<select id="date_annee" name="date[annee]">\n)
+ expected = %(<select id="date_annee" name="date[annee]">\n).dup
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -468,7 +486,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_year_with_type_discarding
- expected = %(<select id="date_year" name="date_year">\n)
+ expected = %(<select id="date_year" name="date_year">\n).dup
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -479,7 +497,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_year_descending
- expected = %(<select id="date_year" name="date[year]">\n)
+ expected = %(<select id="date_year" name="date[year]">\n).dup
expected << %(<option value="2005" selected="selected">2005</option>\n<option value="2004">2004</option>\n<option value="2003">2003</option>\n)
expected << "</select>\n"
@@ -496,7 +514,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_year_with_html_options
- expected = %(<select id="date_year" name="date[year]" class="selector" accesskey="M">\n)
+ expected = %(<select id="date_year" name="date[year]" class="selector" accesskey="M">\n).dup
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -504,7 +522,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_year_with_default_prompt
- expected = %(<select id="date_year" name="date[year]">\n)
+ expected = %(<select id="date_year" name="date[year]">\n).dup
expected << %(<option value="">Year</option>\n<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -512,7 +530,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_year_with_custom_prompt
- expected = %(<select id="date_year" name="date[year]">\n)
+ expected = %(<select id="date_year" name="date[year]">\n).dup
expected << %(<option value="">Choose year</option>\n<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -520,7 +538,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_year_with_generic_with_css_classes
- expected = %(<select id="date_year" name="date[year]" class="year">\n)
+ expected = %(<select id="date_year" name="date[year]" class="year">\n).dup
expected << %(<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -528,7 +546,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_year_with_custom_with_css_classes
- expected = %(<select id="date_year" name="date[year]" class="my-year">\n)
+ expected = %(<select id="date_year" name="date[year]" class="my-year">\n).dup
expected << %(<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -536,14 +554,14 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_year_with_position
- expected = %(<select id="date_year_1i" name="date[year(1i)]">\n)
+ expected = %(<select id="date_year_1i" name="date[year(1i)]">\n).dup
expected << %(<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
assert_dom_equal expected, select_year(Date.current, include_position: true, start_year: 2003, end_year: 2005)
end
def test_select_hour
- expected = %(<select id="date_hour" name="date[hour]">\n)
+ expected = %(<select id="date_hour" name="date[hour]">\n).dup
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
expected << "</select>\n"
@@ -551,7 +569,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_hour_with_ampm
- expected = %(<select id="date_hour" name="date[hour]">\n)
+ expected = %(<select id="date_hour" name="date[hour]">\n).dup
expected << %(<option value="00">12 AM</option>\n<option value="01">01 AM</option>\n<option value="02">02 AM</option>\n<option value="03">03 AM</option>\n<option value="04">04 AM</option>\n<option value="05">05 AM</option>\n<option value="06">06 AM</option>\n<option value="07">07 AM</option>\n<option value="08" selected="selected">08 AM</option>\n<option value="09">09 AM</option>\n<option value="10">10 AM</option>\n<option value="11">11 AM</option>\n<option value="12">12 PM</option>\n<option value="13">01 PM</option>\n<option value="14">02 PM</option>\n<option value="15">03 PM</option>\n<option value="16">04 PM</option>\n<option value="17">05 PM</option>\n<option value="18">06 PM</option>\n<option value="19">07 PM</option>\n<option value="20">08 PM</option>\n<option value="21">09 PM</option>\n<option value="22">10 PM</option>\n<option value="23">11 PM</option>\n)
expected << "</select>\n"
@@ -559,7 +577,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_hour_with_disabled
- expected = %(<select id="date_hour" name="date[hour]" disabled="disabled">\n)
+ expected = %(<select id="date_hour" name="date[hour]" disabled="disabled">\n).dup
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
expected << "</select>\n"
@@ -567,7 +585,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_hour_with_field_name_override
- expected = %(<select id="date_heure" name="date[heure]">\n)
+ expected = %(<select id="date_heure" name="date[heure]">\n).dup
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
expected << "</select>\n"
@@ -575,7 +593,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_hour_with_blank
- expected = %(<select id="date_hour" name="date[hour]">\n)
+ expected = %(<select id="date_hour" name="date[hour]">\n).dup
expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
expected << "</select>\n"
@@ -583,7 +601,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_hour_nil_with_blank
- expected = %(<select id="date_hour" name="date[hour]">\n)
+ expected = %(<select id="date_hour" name="date[hour]">\n).dup
expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
expected << "</select>\n"
@@ -591,7 +609,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_hour_with_html_options
- expected = %(<select id="date_hour" name="date[hour]" class="selector" accesskey="M">\n)
+ expected = %(<select id="date_hour" name="date[hour]" class="selector" accesskey="M">\n).dup
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
expected << "</select>\n"
@@ -599,7 +617,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_hour_with_default_prompt
- expected = %(<select id="date_hour" name="date[hour]">\n)
+ expected = %(<select id="date_hour" name="date[hour]">\n).dup
expected << %(<option value="">Hour</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
expected << "</select>\n"
@@ -607,7 +625,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_hour_with_custom_prompt
- expected = %(<select id="date_hour" name="date[hour]">\n)
+ expected = %(<select id="date_hour" name="date[hour]">\n).dup
expected << %(<option value="">Choose hour</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
expected << "</select>\n"
@@ -615,7 +633,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_hour_with_generic_with_css_classes
- expected = %(<select id="date_hour" name="date[hour]" class="hour">\n)
+ expected = %(<select id="date_hour" name="date[hour]" class="hour">\n).dup
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
expected << "</select>\n"
@@ -623,7 +641,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_hour_with_custom_with_css_classes
- expected = %(<select id="date_hour" name="date[hour]" class="my-hour">\n)
+ expected = %(<select id="date_hour" name="date[hour]" class="my-hour">\n).dup
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
expected << "</select>\n"
@@ -631,7 +649,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_minute
- expected = %(<select id="date_minute" name="date[minute]">\n)
+ expected = %(<select id="date_minute" name="date[minute]">\n).dup
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
expected << "</select>\n"
@@ -639,7 +657,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_minute_with_disabled
- expected = %(<select id="date_minute" name="date[minute]" disabled="disabled">\n)
+ expected = %(<select id="date_minute" name="date[minute]" disabled="disabled">\n).dup
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
expected << "</select>\n"
@@ -647,7 +665,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_minute_with_field_name_override
- expected = %(<select id="date_minuto" name="date[minuto]">\n)
+ expected = %(<select id="date_minuto" name="date[minuto]">\n).dup
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
expected << "</select>\n"
@@ -655,7 +673,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_minute_with_blank
- expected = %(<select id="date_minute" name="date[minute]">\n)
+ expected = %(<select id="date_minute" name="date[minute]">\n).dup
expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
expected << "</select>\n"
@@ -663,7 +681,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_minute_with_blank_and_step
- expected = %(<select id="date_minute" name="date[minute]">\n)
+ expected = %(<select id="date_minute" name="date[minute]">\n).dup
expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="15">15</option>\n<option value="30">30</option>\n<option value="45">45</option>\n)
expected << "</select>\n"
@@ -671,7 +689,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_minute_nil_with_blank
- expected = %(<select id="date_minute" name="date[minute]">\n)
+ expected = %(<select id="date_minute" name="date[minute]">\n).dup
expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
expected << "</select>\n"
@@ -679,7 +697,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_minute_nil_with_blank_and_step
- expected = %(<select id="date_minute" name="date[minute]">\n)
+ expected = %(<select id="date_minute" name="date[minute]">\n).dup
expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="15">15</option>\n<option value="30">30</option>\n<option value="45">45</option>\n)
expected << "</select>\n"
@@ -695,7 +713,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_minute_with_html_options
- expected = %(<select id="date_minute" name="date[minute]" class="selector" accesskey="M">\n)
+ expected = %(<select id="date_minute" name="date[minute]" class="selector" accesskey="M">\n).dup
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
expected << "</select>\n"
@@ -703,7 +721,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_minute_with_default_prompt
- expected = %(<select id="date_minute" name="date[minute]">\n)
+ expected = %(<select id="date_minute" name="date[minute]">\n).dup
expected << %(<option value="">Minute</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
expected << "</select>\n"
@@ -711,7 +729,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_minute_with_custom_prompt
- expected = %(<select id="date_minute" name="date[minute]">\n)
+ expected = %(<select id="date_minute" name="date[minute]">\n).dup
expected << %(<option value="">Choose minute</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
expected << "</select>\n"
@@ -719,7 +737,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_minute_with_generic_with_css_classes
- expected = %(<select id="date_minute" name="date[minute]" class="minute">\n)
+ expected = %(<select id="date_minute" name="date[minute]" class="minute">\n).dup
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
expected << "</select>\n"
@@ -727,7 +745,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_minute_with_custom_with_css_classes
- expected = %(<select id="date_minute" name="date[minute]" class="my-minute">\n)
+ expected = %(<select id="date_minute" name="date[minute]" class="my-minute">\n).dup
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
expected << "</select>\n"
@@ -735,7 +753,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_second
- expected = %(<select id="date_second" name="date[second]">\n)
+ expected = %(<select id="date_second" name="date[second]">\n).dup
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
expected << "</select>\n"
@@ -743,7 +761,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_second_with_disabled
- expected = %(<select id="date_second" name="date[second]" disabled="disabled">\n)
+ expected = %(<select id="date_second" name="date[second]" disabled="disabled">\n).dup
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
expected << "</select>\n"
@@ -751,7 +769,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_second_with_field_name_override
- expected = %(<select id="date_segundo" name="date[segundo]">\n)
+ expected = %(<select id="date_segundo" name="date[segundo]">\n).dup
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
expected << "</select>\n"
@@ -759,7 +777,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_second_with_blank
- expected = %(<select id="date_second" name="date[second]">\n)
+ expected = %(<select id="date_second" name="date[second]">\n).dup
expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
expected << "</select>\n"
@@ -767,7 +785,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_second_nil_with_blank
- expected = %(<select id="date_second" name="date[second]">\n)
+ expected = %(<select id="date_second" name="date[second]">\n).dup
expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
expected << "</select>\n"
@@ -775,7 +793,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_second_with_html_options
- expected = %(<select id="date_second" name="date[second]" class="selector" accesskey="M">\n)
+ expected = %(<select id="date_second" name="date[second]" class="selector" accesskey="M">\n).dup
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
expected << "</select>\n"
@@ -783,7 +801,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_second_with_default_prompt
- expected = %(<select id="date_second" name="date[second]">\n)
+ expected = %(<select id="date_second" name="date[second]">\n).dup
expected << %(<option value="">Seconds</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
expected << "</select>\n"
@@ -791,7 +809,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_second_with_custom_prompt
- expected = %(<select id="date_second" name="date[second]">\n)
+ expected = %(<select id="date_second" name="date[second]">\n).dup
expected << %(<option value="">Choose seconds</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
expected << "</select>\n"
@@ -799,7 +817,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_second_with_generic_with_css_classes
- expected = %(<select id="date_second" name="date[second]" class="second">\n)
+ expected = %(<select id="date_second" name="date[second]" class="second">\n).dup
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
expected << "</select>\n"
@@ -807,7 +825,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_second_with_custom_with_css_classes
- expected = %(<select id="date_second" name="date[second]" class="my-second">\n)
+ expected = %(<select id="date_second" name="date[second]" class="my-second">\n).dup
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
expected << "</select>\n"
@@ -815,7 +833,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_date
- expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]">\n).dup
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -840,7 +858,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_date_with_order
- expected = %(<select id="date_first_month" name="date[first][month]">\n)
+ expected = %(<select id="date_first_month" name="date[first][month]">\n).dup
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
expected << "</select>\n"
@@ -857,7 +875,7 @@ class DateHelperTest < ActionView::TestCase
def test_select_date_with_incomplete_order
# Since the order is incomplete nothing will be shown
- expected = %(<input id="date_first_year" name="date[first][year]" type="hidden" value="2003" />\n)
+ expected = %(<input id="date_first_year" name="date[first][year]" type="hidden" value="2003" />\n).dup
expected << %(<input id="date_first_month" name="date[first][month]" type="hidden" value="8" />\n)
expected << %(<input id="date_first_day" name="date[first][day]" type="hidden" value="1" />\n)
@@ -865,7 +883,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_date_with_disabled
- expected = %(<select id="date_first_year" name="date[first][year]" disabled="disabled">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]" disabled="disabled">\n).dup
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -881,7 +899,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_date_with_no_start_year
- expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]">\n).dup
(Date.today.year - 5).upto(Date.today.year + 1) do |y|
if y == Date.today.year
expected << %(<option value="#{y}" selected="selected">#{y}</option>\n)
@@ -905,7 +923,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_date_with_no_end_year
- expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]">\n).dup
2003.upto(2008) do |y|
if y == 2003
expected << %(<option value="#{y}" selected="selected">#{y}</option>\n)
@@ -929,7 +947,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_date_with_no_start_or_end_year
- expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]">\n).dup
(Date.today.year - 5).upto(Date.today.year + 5) do |y|
if y == Date.today.year
expected << %(<option value="#{y}" selected="selected">#{y}</option>\n)
@@ -953,7 +971,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_date_with_zero_value
- expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]">\n).dup
expected << %(<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -969,7 +987,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_date_with_zero_value_and_no_start_year
- expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]">\n).dup
(Date.today.year - 5).upto(Date.today.year + 1) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
expected << "</select>\n"
@@ -985,7 +1003,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_date_with_zero_value_and_no_end_year
- expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]">\n).dup
last_year = Time.now.year + 5
2003.upto(last_year) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
expected << "</select>\n"
@@ -1002,7 +1020,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_date_with_zero_value_and_no_start_and_end_year
- expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]">\n).dup
(Date.today.year - 5).upto(Date.today.year + 5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
expected << "</select>\n"
@@ -1018,7 +1036,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_date_with_nil_value_and_no_start_and_end_year
- expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]">\n).dup
(Date.today.year - 5).upto(Date.today.year + 5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
expected << "</select>\n"
@@ -1034,7 +1052,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_date_with_html_options
- expected = %(<select id="date_first_year" name="date[first][year]" class="selector">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]" class="selector">\n).dup
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -1050,7 +1068,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_date_with_separator
- expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]">\n).dup
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -1070,7 +1088,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_date_with_separator_and_discard_day
- expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]">\n).dup
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -1086,7 +1104,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_date_with_separator_discard_month_and_day
- expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]">\n).dup
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -1097,7 +1115,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_date_with_hidden
- expected = %(<input id="date_first_year" name="date[first][year]" type="hidden" value="2003"/>\n)
+ expected = %(<input id="date_first_year" name="date[first][year]" type="hidden" value="2003"/>\n).dup
expected << %(<input id="date_first_month" name="date[first][month]" type="hidden" value="8" />\n)
expected << %(<input id="date_first_day" name="date[first][day]" type="hidden" value="16" />\n)
@@ -1106,7 +1124,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_date_with_css_classes_option
- expected = %(<select id="date_first_year" name="date[first][year]" class="year">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]" class="year">\n).dup
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -1122,7 +1140,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_date_with_custom_with_css_classes
- expected = %(<select id="date_year" name="date[year]" class="my-year">\n)
+ expected = %(<select id="date_year" name="date[year]" class="my-year">\n).dup
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -1138,7 +1156,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_date_with_css_classes_option_and_html_class_option
- expected = %(<select id="date_first_year" name="date[first][year]" class="datetime optional year">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]" class="datetime optional year">\n).dup
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -1154,7 +1172,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_date_with_custom_with_css_classes_and_html_class_option
- expected = %(<select id="date_year" name="date[year]" class="date optional my-year">\n)
+ expected = %(<select id="date_year" name="date[year]" class="date optional my-year">\n).dup
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -1170,7 +1188,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_date_with_partial_with_css_classes_and_html_class_option
- expected = %(<select id="date_year" name="date[year]" class="date optional">\n)
+ expected = %(<select id="date_year" name="date[year]" class="date optional">\n).dup
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -1186,7 +1204,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_date_with_html_class_option
- expected = %(<select id="date_year" name="date[year]" class="date optional custom-grid">\n)
+ expected = %(<select id="date_year" name="date[year]" class="date optional custom-grid">\n).dup
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -1202,7 +1220,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_datetime
- expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]">\n).dup
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -1230,7 +1248,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_datetime_with_ampm
- expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]">\n).dup
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -1258,7 +1276,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_datetime_with_separators
- expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]">\n).dup
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -1286,7 +1304,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_datetime_with_nil_value_and_no_start_and_end_year
- expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]">\n).dup
(Date.today.year - 5).upto(Date.today.year + 5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
expected << "</select>\n"
@@ -1314,7 +1332,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_datetime_with_html_options
- expected = %(<select id="date_first_year" name="date[first][year]" class="selector">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]" class="selector">\n).dup
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -1342,7 +1360,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_datetime_with_all_separators
- expected = %(<select id="date_first_year" name="date[first][year]" class="selector">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]" class="selector">\n).dup
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -1378,7 +1396,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_datetime_with_default_prompt
- expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]">\n).dup
expected << %(<option value="">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"
@@ -1407,7 +1425,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_datetime_with_custom_prompt
- expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]">\n).dup
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"
@@ -1436,7 +1454,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_datetime_with_generic_with_css_classes
- expected = %(<select id="date_year" name="date[year]" class="year">\n)
+ expected = %(<select id="date_year" name="date[year]" class="year">\n).dup
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -1464,7 +1482,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_datetime_with_custom_with_css_classes
- expected = %(<select id="date_year" name="date[year]" class="my-year">\n)
+ expected = %(<select id="date_year" name="date[year]" class="my-year">\n).dup
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
@@ -1492,7 +1510,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_datetime_with_custom_hours
- expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]">\n).dup
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"
@@ -1521,7 +1539,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_datetime_with_hidden
- expected = %(<input id="date_first_year" name="date[first][year]" type="hidden" value="2003" />\n)
+ expected = %(<input id="date_first_year" name="date[first][year]" type="hidden" value="2003" />\n).dup
expected << %(<input id="date_first_month" name="date[first][month]" type="hidden" value="8" />\n)
expected << %(<input id="date_first_day" name="date[first][day]" type="hidden" value="16" />\n)
expected << %(<input id="date_first_hour" name="date[first][hour]" type="hidden" value="8" />\n)
@@ -1533,7 +1551,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_time
- expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n)
+ expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n).dup
expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" />\n)
expected << %(<input name="date[day]" id="date_day" value="16" type="hidden" />\n)
@@ -1552,7 +1570,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_time_with_ampm
- expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n)
+ expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n).dup
expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" />\n)
expected << %(<input name="date[day]" id="date_day" value="16" type="hidden" />\n)
@@ -1570,7 +1588,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_time_with_separator
- expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n)
+ expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n).dup
expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" />\n)
expected << %(<input name="date[day]" id="date_day" value="16" type="hidden" />\n)
expected << %(<select id="date_hour" name="date[hour]">\n)
@@ -1588,7 +1606,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_time_with_seconds
- expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n)
+ expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n).dup
expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" />\n)
expected << %(<input name="date[day]" id="date_day" value="16" type="hidden" />\n)
@@ -1612,7 +1630,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_time_with_seconds_and_separator
- expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n)
+ expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n).dup
expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" />\n)
expected << %(<input name="date[day]" id="date_day" value="16" type="hidden" />\n)
@@ -1636,7 +1654,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_time_with_html_options
- expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n)
+ expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n).dup
expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" />\n)
expected << %(<input name="date[day]" id="date_day" value="16" type="hidden" />\n)
@@ -1659,7 +1677,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_time_with_default_prompt
- expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n)
+ expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n).dup
expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" />\n)
expected << %(<input name="date[day]" id="date_day" value="16" type="hidden" />\n)
@@ -1683,7 +1701,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_time_with_custom_prompt
- expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n)
+ expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n).dup
expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" />\n)
expected << %(<input name="date[day]" id="date_day" value="16" type="hidden" />\n)
@@ -1708,7 +1726,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_time_with_generic_with_css_classes
- expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n)
+ expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n).dup
expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" />\n)
expected << %(<input name="date[day]" id="date_day" value="16" type="hidden" />\n)
@@ -1732,7 +1750,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_time_with_custom_with_css_classes
- expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n)
+ expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n).dup
expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" />\n)
expected << %(<input name="date[day]" id="date_day" value="16" type="hidden" />\n)
@@ -1756,7 +1774,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_time_with_hidden
- expected = %(<input id="date_first_year" name="date[first][year]" type="hidden" value="2003" />\n)
+ expected = %(<input id="date_first_year" name="date[first][year]" type="hidden" value="2003" />\n).dup
expected << %(<input id="date_first_month" name="date[first][month]" type="hidden" value="8" />\n)
expected << %(<input id="date_first_day" name="date[first][day]" type="hidden" value="16" />\n)
expected << %(<input id="date_first_hour" name="date[first][hour]" type="hidden" value="8" />\n)
@@ -1770,7 +1788,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
- expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}.dup
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
@@ -1790,7 +1808,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
- expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}.dup
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
@@ -1810,7 +1828,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
- expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}.dup
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
@@ -1848,7 +1866,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
- expected = "<input type=\"hidden\" id=\"post_written_on_3i\" name=\"post[written_on(3i)]\" value=\"1\" />\n"
+ expected = "<input type=\"hidden\" id=\"post_written_on_3i\" name=\"post[written_on(3i)]\" value=\"1\" />\n".dup
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n}
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
@@ -1865,7 +1883,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Date.new(2004, 2, 29)
- expected = "<input type=\"hidden\" id=\"post_written_on_2i\" name=\"post[written_on(2i)]\" value=\"2\" />\n"
+ expected = "<input type=\"hidden\" id=\"post_written_on_2i\" name=\"post[written_on(2i)]\" value=\"2\" />\n".dup
expected << "<input type=\"hidden\" id=\"post_written_on_3i\" name=\"post[written_on(3i)]\" value=\"1\" />\n"
expected << %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
@@ -1879,7 +1897,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
- expected = "<input type=\"hidden\" id=\"post_written_on_3i\" name=\"post[written_on(3i)]\" value=\"1\" />\n"
+ expected = "<input type=\"hidden\" id=\"post_written_on_3i\" name=\"post[written_on(3i)]\" value=\"1\" />\n".dup
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n}
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
@@ -1898,7 +1916,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
- expected = "<input type=\"hidden\" id=\"post_written_on_3i\" disabled=\"disabled\" name=\"post[written_on(3i)]\" value=\"1\" />\n"
+ expected = "<input type=\"hidden\" id=\"post_written_on_3i\" disabled=\"disabled\" name=\"post[written_on(3i)]\" value=\"1\" />\n".dup
expected << %{<select id="post_written_on_2i" disabled="disabled" name="post[written_on(2i)]">\n}
expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
@@ -1919,7 +1937,7 @@ class DateHelperTest < ActionView::TestCase
concat f.date_select(:written_on)
end
- expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n</select>\n}
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n</select>\n}.dup
expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option selected="selected" value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n</select>\n}
expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option selected="selected" value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n</select>\n}
@@ -1935,7 +1953,7 @@ class DateHelperTest < ActionView::TestCase
concat f.date_select(:written_on)
end
- expected = %{<select id="post_#{id}_written_on_1i" name="post[#{id}][written_on(1i)]">\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n</select>\n}
+ expected = %{<select id="post_#{id}_written_on_1i" name="post[#{id}][written_on(1i)]">\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n</select>\n}.dup
expected << %{<select id="post_#{id}_written_on_2i" name="post[#{id}][written_on(2i)]">\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option selected="selected" value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n</select>\n}
expected << %{<select id="post_#{id}_written_on_3i" name="post[#{id}][written_on(3i)]">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option selected="selected" value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n</select>\n}
@@ -1951,7 +1969,7 @@ class DateHelperTest < ActionView::TestCase
concat f.date_select(:written_on)
end
- expected = %{<select id="post_#{id}_written_on_1i" name="post[#{id}][written_on(1i)]">\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n</select>\n}
+ expected = %{<select id="post_#{id}_written_on_1i" name="post[#{id}][written_on(1i)]">\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n</select>\n}.dup
expected << %{<select id="post_#{id}_written_on_2i" name="post[#{id}][written_on(2i)]">\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option selected="selected" value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n</select>\n}
expected << %{<select id="post_#{id}_written_on_3i" name="post[#{id}][written_on(3i)]">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option selected="selected" value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n</select>\n}
@@ -1963,7 +1981,7 @@ class DateHelperTest < ActionView::TestCase
@post.written_on = Date.new(2004, 6, 15)
id = 456
- expected = %{<select id="post_456_written_on_1i" name="post[#{id}][written_on(1i)]">\n}
+ expected = %{<select id="post_456_written_on_1i" name="post[#{id}][written_on(1i)]">\n}.dup
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
@@ -1983,7 +2001,7 @@ class DateHelperTest < ActionView::TestCase
@post.written_on = Date.new(2004, 6, 15)
id = 123
- expected = %{<select id="post_123_written_on_1i" name="post[#{id}][written_on(1i)]">\n}
+ expected = %{<select id="post_123_written_on_1i" name="post[#{id}][written_on(1i)]">\n}.dup
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
@@ -2002,7 +2020,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
- expected = %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n}
+ expected = %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n}.dup
1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 15}>#{i}</option>\n) }
expected << "</select>\n"
@@ -2022,7 +2040,7 @@ class DateHelperTest < ActionView::TestCase
start_year = Time.now.year - 5
end_year = Time.now.year + 5
- expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}.dup
start_year.upto(end_year) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == Time.now.year}>#{i}</option>\n) }
expected << "</select>\n"
@@ -2042,7 +2060,7 @@ class DateHelperTest < ActionView::TestCase
start_year = Time.now.year - 5
end_year = Time.now.year + 5
- expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}.dup
expected << "<option value=\"\"></option>\n"
start_year.upto(end_year) { |i| expected << %(<option value="#{i}">#{i}</option>\n) }
expected << "</select>\n"
@@ -2086,7 +2104,7 @@ class DateHelperTest < ActionView::TestCase
start_year = Time.now.year - 5
end_year = Time.now.year + 5
- expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}.dup
expected << "<option value=\"\"></option>\n"
start_year.upto(end_year) { |i| expected << %(<option value="#{i}">#{i}</option>\n) }
expected << "</select>\n"
@@ -2118,7 +2136,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
- expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}.dup
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
@@ -2137,7 +2155,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
- expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]" class="selector">\n}
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]" class="selector">\n}.dup
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
@@ -2161,7 +2179,7 @@ class DateHelperTest < ActionView::TestCase
concat f.date_select(:written_on, {}, class: "selector")
end
- expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]" class="selector">\n}
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]" class="selector">\n}.dup
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
@@ -2181,7 +2199,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
- expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}.dup
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
@@ -2205,7 +2223,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
- expected = %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n}
+ expected = %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n}.dup
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
expected << "</select>\n"
@@ -2228,7 +2246,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
- expected = %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n}
+ expected = %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n}.dup
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
expected << "</select>\n"
@@ -2246,7 +2264,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
- expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}.dup
expected << %{<option value="">Year</option>\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
@@ -2266,7 +2284,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
- expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}.dup
expected << %{<option value="">Choose year</option>\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
@@ -2286,7 +2304,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
- expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]" class="year">\n}
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]" class="year">\n}.dup
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
@@ -2306,7 +2324,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
- expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]" class="my-year">\n}
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]" class="my-year">\n}.dup
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
@@ -2326,7 +2344,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
- expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}
+ expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}.dup
expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" />\n}
expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n}
@@ -2345,7 +2363,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
- expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}
+ expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}.dup
expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" />\n}
expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n}
@@ -2364,7 +2382,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
- expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="1" />\n}
+ expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="1" />\n}.dup
expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="1" />\n}
expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="1" />\n}
@@ -2383,7 +2401,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
- expected = %(<select id="post_written_on_4i" name="post[written_on(4i)]">\n)
+ expected = %(<select id="post_written_on_4i" name="post[written_on(4i)]">\n).dup
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) }
expected << "</select>\n"
expected << " : "
@@ -2398,7 +2416,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
- expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}
+ expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}.dup
expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" />\n}
expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n}
@@ -2421,7 +2439,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
- expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}
+ expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}.dup
expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" />\n}
expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n}
@@ -2444,7 +2462,7 @@ class DateHelperTest < ActionView::TestCase
concat f.time_select(:written_on, {}, class: "selector")
end
- expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}
+ expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}.dup
expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" />\n}
expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n}
@@ -2463,7 +2481,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
- expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}
+ expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}.dup
expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" />\n}
expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n}
@@ -2490,7 +2508,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
- expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}
+ expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}.dup
expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" />\n}
expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n}
@@ -2511,7 +2529,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
- expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}
+ expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}.dup
expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" />\n}
expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n}
@@ -2532,7 +2550,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
- expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}
+ expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}.dup
expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" />\n}
expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n}
@@ -2553,7 +2571,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
- expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}
+ expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}.dup
expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" />\n}
expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n}
@@ -2574,7 +2592,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
- expected = %{<input type="hidden" id="post_written_on_1i" disabled="disabled" name="post[written_on(1i)]" value="2004" />\n}
+ expected = %{<input type="hidden" id="post_written_on_1i" disabled="disabled" name="post[written_on(1i)]" value="2004" />\n}.dup
expected << %{<input type="hidden" id="post_written_on_2i" disabled="disabled" name="post[written_on(2i)]" value="6" />\n}
expected << %{<input type="hidden" id="post_written_on_3i" disabled="disabled" name="post[written_on(3i)]" value="15" />\n}
@@ -2593,7 +2611,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.updated_at = Time.local(2004, 6, 15, 16, 35)
- expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}
+ expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}.dup
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
@@ -2622,7 +2640,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.updated_at = Time.local(2004, 6, 15, 16, 35)
- expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}
+ expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}.dup
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
@@ -2688,7 +2706,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
- expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}
+ expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}.dup
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
@@ -2723,7 +2741,7 @@ class DateHelperTest < ActionView::TestCase
concat f.datetime_select(:updated_at, {}, class: "selector")
end
- expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]" class="selector">\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n</select>\n}
+ expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]" class="selector">\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n</select>\n}.dup
expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]" class="selector">\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option selected="selected" value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n</select>\n}
expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]" class="selector">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option selected="selected" value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n</select>\n}
expected << %{ &mdash; <select id="post_updated_at_4i" name="post[updated_at(4i)]" class="selector">\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option selected="selected" value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n</select>\n}
@@ -2736,7 +2754,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.updated_at = Time.local(2004, 6, 15, 15, 16, 35)
- expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}
+ expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}.dup
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
@@ -2789,7 +2807,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.updated_at = nil
- expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}
+ expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}.dup
expected << %{<option value="">Year</option>\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
@@ -2818,7 +2836,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.updated_at = nil
- expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}
+ expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}.dup
expected << %{<option value="">Choose year</option>\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
@@ -2847,7 +2865,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
- expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]" class="year">\n}
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]" class="year">\n}.dup
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
@@ -2876,7 +2894,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
- expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]" class="my-year">\n}
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]" class="my-year">\n}.dup
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
@@ -2902,7 +2920,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_date_select_with_zero_value_and_no_start_year
- expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]">\n).dup
(Date.today.year - 5).upto(Date.today.year + 1) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
expected << "</select>\n"
@@ -2918,7 +2936,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_date_select_with_zero_value_and_no_end_year
- expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]">\n).dup
last_year = Time.now.year + 5
2003.upto(last_year) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
expected << "</select>\n"
@@ -2935,7 +2953,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_date_select_with_zero_value_and_no_start_and_end_year
- expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]">\n).dup
(Date.today.year - 5).upto(Date.today.year + 5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
expected << "</select>\n"
@@ -2951,7 +2969,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_date_select_with_nil_value_and_no_start_and_end_year
- expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]">\n).dup
(Date.today.year - 5).upto(Date.today.year + 5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
expected << "</select>\n"
@@ -2967,7 +2985,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_datetime_select_with_nil_value_and_no_start_and_end_year
- expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected = %(<select id="date_first_year" name="date[first][year]">\n).dup
(Date.today.year - 5).upto(Date.today.year + 5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
expected << "</select>\n"
@@ -2999,7 +3017,7 @@ class DateHelperTest < ActionView::TestCase
@post.updated_at = Time.local(2004, 6, 15, 16, 35)
id = 456
- expected = %{<select id="post_456_updated_at_1i" name="post[#{id}][updated_at(1i)]">\n}
+ expected = %{<select id="post_456_updated_at_1i" name="post[#{id}][updated_at(1i)]">\n}.dup
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
@@ -3033,7 +3051,7 @@ class DateHelperTest < ActionView::TestCase
concat f.datetime_select(:updated_at)
end
- expected = %{<select id="post_456_updated_at_1i" name="post[#{id}][updated_at(1i)]">\n}
+ expected = %{<select id="post_456_updated_at_1i" name="post[#{id}][updated_at(1i)]">\n}.dup
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
@@ -3063,7 +3081,7 @@ class DateHelperTest < ActionView::TestCase
@post.updated_at = Time.local(2004, 6, 15, 16, 35)
id = @post.id
- expected = %{<select id="post_123_updated_at_1i" name="post[#{id}][updated_at(1i)]">\n}
+ expected = %{<select id="post_123_updated_at_1i" name="post[#{id}][updated_at(1i)]">\n}.dup
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
@@ -3092,7 +3110,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.updated_at = Time.local(2004, 6, 15, 15, 16, 35)
- expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}
+ expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}.dup
1999.upto(2009) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2004}>#{i}</option>\n) }
expected << "</select>\n"
expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n}
@@ -3123,7 +3141,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.updated_at = Time.local(2004, 6, 15, 15, 16, 35)
- expected = %{<input type="hidden" id="post_updated_at_1i" name="post[updated_at(1i)]" value="2004" />\n}
+ expected = %{<input type="hidden" id="post_updated_at_1i" name="post[updated_at(1i)]" value="2004" />\n}.dup
expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n}
1.upto(12) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 6}>#{Date::MONTHNAMES[i]}</option>\n) }
expected << "</select>\n"
@@ -3148,7 +3166,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.updated_at = Time.local(2004, 6, 15, 15, 16, 35)
- expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}
+ expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}.dup
1999.upto(2009) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2004}>#{i}</option>\n) }
expected << "</select>\n"
expected << %{<input type="hidden" id="post_updated_at_2i" name="post[updated_at(2i)]" value="6" />\n}
@@ -3171,7 +3189,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.updated_at = Time.local(2004, 6, 15, 15, 16, 35)
- expected = %{<input type="hidden" id="post_updated_at_1i" name="post[updated_at(1i)]" value="2004" />\n}
+ expected = %{<input type="hidden" id="post_updated_at_1i" name="post[updated_at(1i)]" value="2004" />\n}.dup
expected << %{<input type="hidden" id="post_updated_at_2i" name="post[updated_at(2i)]" value="6" />\n}
expected << %{<input type="hidden" id="post_updated_at_3i" name="post[updated_at(3i)]" value="1" />\n}
@@ -3190,7 +3208,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.updated_at = Time.local(2004, 6, 15, 15, 16, 35)
- expected = %{<input type="hidden" id="post_updated_at_1i" disabled="disabled" name="post[updated_at(1i)]" value="2004" />\n}
+ expected = %{<input type="hidden" id="post_updated_at_1i" disabled="disabled" name="post[updated_at(1i)]" value="2004" />\n}.dup
expected << %{<input type="hidden" id="post_updated_at_2i" disabled="disabled" name="post[updated_at(2i)]" value="6" />\n}
expected << %{<input type="hidden" id="post_updated_at_3i" disabled="disabled" name="post[updated_at(3i)]" value="1" />\n}
@@ -3209,7 +3227,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.updated_at = Time.local(2004, 6, 15, 15, 16, 35)
- expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}
+ expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}.dup
1999.upto(2009) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2004}>#{i}</option>\n) }
expected << "</select>\n"
expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n}
@@ -3226,7 +3244,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.updated_at = Time.local(2004, 6, 15, 15, 16, 35)
- expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}
+ expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}.dup
1999.upto(2009) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2004}>#{i}</option>\n) }
expected << "</select>\n"
expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n}
@@ -3250,7 +3268,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.updated_at = Time.local(2004, 6, 15, 15, 16, 35)
- expected = %{<select id="post_updated_at_1i" disabled="disabled" name="post[updated_at(1i)]">\n}
+ expected = %{<select id="post_updated_at_1i" disabled="disabled" name="post[updated_at(1i)]">\n}.dup
1999.upto(2009) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2004}>#{i}</option>\n) }
expected << "</select>\n"
expected << %{<select id="post_updated_at_2i" disabled="disabled" name="post[updated_at(2i)]">\n}
@@ -3274,7 +3292,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.updated_at = Time.local(2004, 6, 15, 15, 16, 35)
- expected = %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n}
+ expected = %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n}.dup
1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 15}>#{i}</option>\n) }
expected << "</select>\n"
expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n}
@@ -3301,7 +3319,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.updated_at = Time.local(2004, 6, 15, 15, 16, 35)
- expected = %{<input type="hidden" id="post_updated_at_1i" name="post[updated_at(1i)]" value="2004" />\n}
+ expected = %{<input type="hidden" id="post_updated_at_1i" name="post[updated_at(1i)]" value="2004" />\n}.dup
expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n}
1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 15}>#{i}</option>\n) }
expected << "</select>\n"
@@ -3326,7 +3344,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.updated_at = nil
- expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}
+ expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}.dup
2001.upto(2011) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2006}>#{i}</option>\n) }
expected << "</select>\n"
expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n}
@@ -3353,7 +3371,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.updated_at = nil
- expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}
+ expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}.dup
expected << %(<option value=""></option>\n)
(Time.now.year - 5).upto(Time.now.year + 5) { |i| expected << %(<option value="#{i}">#{i}</option>\n) }
expected << "</select>\n"
@@ -3373,7 +3391,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.updated_at = nil
- expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}
+ expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n}.dup
(Time.now.year - 5).upto(Time.now.year + 5) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == Time.now.year}>#{i}</option>\n) }
expected << "</select>\n"
expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n}
@@ -3400,7 +3418,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.updated_at = Time.local(2004, 6, 15, 16, 35)
- expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]" class="selector">\n}
+ expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]" class="selector">\n}.dup
expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
expected << "</select>\n"
diff --git a/actionview/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb
index e225c3de09..de04f3f25d 100644
--- a/actionview/test/template/digestor_test.rb
+++ b/actionview/test/template/digestor_test.rb
@@ -14,7 +14,7 @@ class FixtureTemplate
end
class FixtureFinder < ActionView::LookupContext
- FIXTURES_DIR = "#{File.dirname(__FILE__)}/../fixtures/digestor"
+ FIXTURES_DIR = File.expand_path("../fixtures/digestor", __dir__)
def initialize(details = {})
super(ActionView::PathSet.new(["digestor", "digestor/api"]), details, [])
diff --git a/actionview/test/template/form_helper/form_with_test.rb b/actionview/test/template/form_helper/form_with_test.rb
index a4a2966ff9..c1a8181204 100644
--- a/actionview/test/template/form_helper/form_with_test.rb
+++ b/actionview/test/template/form_helper/form_with_test.rb
@@ -16,7 +16,7 @@ class FormWithActsLikeFormTagTest < FormWithTest
method = options[:method]
skip_enforcing_utf8 = options.fetch(:skip_enforcing_utf8, false)
- "".tap do |txt|
+ "".dup.tap do |txt|
unless skip_enforcing_utf8
txt << %{<input name="utf8" type="hidden" value="&#x2713;" />}
end
@@ -32,7 +32,7 @@ class FormWithActsLikeFormTagTest < FormWithTest
method = method.to_s == "get" ? "get" : "post"
- txt = %{<form accept-charset="UTF-8" action="#{action}"}
+ txt = %{<form accept-charset="UTF-8" action="#{action}"}.dup
txt << %{ enctype="multipart/form-data"} if enctype
txt << %{ data-remote="true"} unless local
txt << %{ class="#{html_class}"} if html_class
@@ -302,6 +302,7 @@ class FormWithActsLikeFormForTest < FormWithTest
concat f.text_field(:title)
concat f.text_area(:body)
concat f.check_box(:secret)
+ concat f.select(:category, %w( animal economy sports ))
concat f.submit("Create post")
concat f.button("Create post")
concat f.button {
@@ -315,6 +316,7 @@ class FormWithActsLikeFormForTest < FormWithTest
"<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" \
"<input name='post[secret]' type='hidden' value='0' />" \
"<input name='post[secret]' checked='checked' type='checkbox' value='1' />" \
+ "<select name='post[category]'><option value='animal'>animal</option>\n<option value='economy'>economy</option>\n<option value='sports'>sports</option></select>" \
"<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />" \
"<button name='button' type='submit'>Create post</button>" \
"<button name='button' type='submit'><span>Create post</span></button>"
@@ -401,9 +403,9 @@ class FormWithActsLikeFormForTest < FormWithTest
expected = whole_form("/posts") do
"<input type='hidden' name='post[active]' value='' />" \
- "<input name='post[active]' type='radio' value='true' />" \
+ "<input name='post[active]' type='radio' value='true' id='post_active_true' />" \
"<label for='post_active_true'>true</label>" \
- "<input checked='checked' name='post[active]' type='radio' value='false' />" \
+ "<input checked='checked' name='post[active]' type='radio' value='false' id='post_active_false' />" \
"<label for='post_active_false'>false</label>"
end
@@ -424,10 +426,10 @@ class FormWithActsLikeFormForTest < FormWithTest
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' />" \
+ "<input name='post[active]' type='radio' value='true' id='post_active_true' />" \
"true</label>" \
"<label for='post_active_false'>" \
- "<input checked='checked' name='post[active]' type='radio' value='false' />" \
+ "<input checked='checked' name='post[active]' type='radio' value='false' id='post_active_false' />" \
"false</label>"
end
@@ -450,10 +452,10 @@ class FormWithActsLikeFormForTest < FormWithTest
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' />" \
+ "<input name='post[active]' type='radio' value='true' id='post_active_true' />" \
"true</label>" \
"<label for='post_active_false'>" \
- "<input checked='checked' name='post[active]' type='radio' value='false' />" \
+ "<input checked='checked' name='post[active]' type='radio' value='false' id='post_active_false' />" \
"false</label>" \
"<input name='post[id]' type='hidden' value='1' />"
end
@@ -471,9 +473,9 @@ class FormWithActsLikeFormForTest < FormWithTest
expected = whole_form("/posts") do
"<input type='hidden' name='post[1][active]' value='' />" \
- "<input name='post[1][active]' type='radio' value='true' />" \
+ "<input name='post[1][active]' type='radio' value='true' id='post_1_active_true' />" \
"<label for='post_1_active_true'>true</label>" \
- "<input checked='checked' name='post[1][active]' type='radio' value='false' />" \
+ "<input checked='checked' name='post[1][active]' type='radio' value='false' id='post_1_active_false' />" \
"<label for='post_1_active_false'>false</label>"
end
@@ -490,11 +492,11 @@ class FormWithActsLikeFormForTest < FormWithTest
expected = whole_form("/posts") do
"<input name='post[tag_ids][]' type='hidden' value='' />" \
- "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='1' />" \
+ "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='1' id='post_tag_ids_1' />" \
"<label for='post_tag_ids_1'>Tag 1</label>" \
- "<input name='post[tag_ids][]' type='checkbox' value='2' />" \
+ "<input name='post[tag_ids][]' type='checkbox' value='2' id='post_tag_ids_2' />" \
"<label for='post_tag_ids_2'>Tag 2</label>" \
- "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='3' />" \
+ "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='3' id='post_tag_ids_3' />" \
"<label for='post_tag_ids_3'>Tag 3</label>"
end
@@ -515,13 +517,13 @@ class FormWithActsLikeFormForTest < FormWithTest
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' />" \
+ "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='1' id='post_tag_ids_1' />" \
"Tag 1</label>" \
"<label for='post_tag_ids_2'>" \
- "<input name='post[tag_ids][]' type='checkbox' value='2' />" \
+ "<input name='post[tag_ids][]' type='checkbox' value='2' id='post_tag_ids_2' />" \
"Tag 2</label>" \
"<label for='post_tag_ids_3'>" \
- "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='3' />" \
+ "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='3' id='post_tag_ids_3' />" \
"Tag 3</label>"
end
@@ -545,13 +547,13 @@ class FormWithActsLikeFormForTest < FormWithTest
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' />" \
+ "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='1' id='post_tag_ids_1' />" \
"Tag 1</label>" \
"<label for='post_tag_ids_2'>" \
- "<input name='post[tag_ids][]' type='checkbox' value='2' />" \
+ "<input name='post[tag_ids][]' type='checkbox' value='2' id='post_tag_ids_2' />" \
"Tag 2</label>" \
"<label for='post_tag_ids_3'>" \
- "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='3' />" \
+ "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='3' id='post_tag_ids_3' />" \
"Tag 3</label>" \
"<input name='post[id]' type='hidden' value='1' />"
end
@@ -570,7 +572,7 @@ class FormWithActsLikeFormForTest < FormWithTest
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 checked='checked' name='post[1][tag_ids][]' type='checkbox' value='1' id='post_1_tag_ids_1' />" \
"<label for='post_1_tag_ids_1'>Tag 1</label>"
end
@@ -729,6 +731,28 @@ class FormWithActsLikeFormForTest < FormWithTest
assert_dom_equal expected, output_buffer
end
+ def test_form_is_not_remote_by_default_if_form_with_generates_remote_forms_is_false
+ old_value = ActionView::Helpers::FormHelper.form_with_generates_remote_forms
+ ActionView::Helpers::FormHelper.form_with_generates_remote_forms = false
+
+ form_with(model: @post, url: "/", id: "create-post", method: :patch) do |f|
+ concat f.text_field(:title)
+ concat f.text_area(:body)
+ concat f.check_box(:secret)
+ end
+
+ expected = whole_form("/", "create-post", method: "patch", local: true) do
+ "<input name='post[title]' type='text' value='Hello World' />" \
+ "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" \
+ "<input name='post[secret]' type='hidden' value='0' />" \
+ "<input name='post[secret]' checked='checked' type='checkbox' value='1' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ ensure
+ ActionView::Helpers::FormHelper.form_with_generates_remote_forms = old_value
+ end
+
def test_form_with_skip_enforcing_utf8_true
form_with(scope: :post, skip_enforcing_utf8: true) do |f|
concat f.text_field(:title)
@@ -854,24 +878,6 @@ class FormWithActsLikeFormForTest < FormWithTest
assert_dom_equal expected, output_buffer
end
- def test_form_with_with_namespace
- skip "Do namespaces still make sense?"
- form_for(@post, namespace: "namespace") do |f|
- concat f.text_field(:title)
- concat f.text_area(:body)
- concat f.check_box(:secret)
- 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[secret]' checked='checked' type='checkbox' value='1' />"
- end
-
- assert_dom_equal expected, output_buffer
- end
-
def test_submit_with_object_as_new_record_and_locale_strings
with_locale :submit do
@post.persisted = false
@@ -1636,7 +1642,7 @@ class FormWithActsLikeFormForTest < FormWithTest
expected = 0
@post.comments.each do |comment|
f.fields(:comments, model: comment) { |cf|
- assert_equal cf.index, expected
+ assert_equal expected, cf.index
expected += 1
}
end
@@ -1650,7 +1656,7 @@ class FormWithActsLikeFormForTest < FormWithTest
expected = 0
@post.comments.each do |comment|
f.fields(:comments, model: comment) { |cf|
- assert_equal cf.index, expected
+ assert_equal expected, cf.index
expected += 1
}
end
@@ -1663,7 +1669,7 @@ class FormWithActsLikeFormForTest < FormWithTest
form_with(model: @post) do |f|
expected = 0
f.fields(:comments, model: @post.comments) { |cf|
- assert_equal cf.index, expected
+ assert_equal expected, cf.index
expected += 1
}
end
@@ -1674,7 +1680,7 @@ class FormWithActsLikeFormForTest < FormWithTest
form_with(model: @post) do |f|
f.fields(:comments, model: Comment.new(321), child_index: "abc") { |cf|
- assert_equal cf.index, "abc"
+ assert_equal "abc", cf.index
}
end
end
@@ -2188,9 +2194,9 @@ class FormWithActsLikeFormForTest < FormWithTest
method = options[:method]
if options.fetch(:skip_enforcing_utf8, false)
- txt = ""
+ txt = "".dup
else
- txt = %{<input name="utf8" type="hidden" value="&#x2713;" />}
+ txt = %{<input name="utf8" type="hidden" value="&#x2713;" />}.dup
end
if method && !%w(get post).include?(method.to_s)
@@ -2201,7 +2207,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
def form_text(action = "/", id = nil, html_class = nil, local = nil, multipart = nil, method = nil)
- txt = %{<form accept-charset="UTF-8" action="#{action}"}
+ txt = %{<form accept-charset="UTF-8" action="#{action}"}.dup
txt << %{ enctype="multipart/form-data"} if multipart
txt << %{ data-remote="true"} unless local
txt << %{ class="#{html_class}"} if html_class
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index b3a180b28a..b24f9ed3f0 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -2893,7 +2893,7 @@ class FormHelperTest < ActionView::TestCase
expected = 0
@post.comments.each do |comment|
f.fields_for(:comments, comment) { |cf|
- assert_equal cf.index, expected
+ assert_equal expected, cf.index
expected += 1
}
end
@@ -2907,7 +2907,7 @@ class FormHelperTest < ActionView::TestCase
expected = 0
@post.comments.each do |comment|
f.fields_for(:comments, comment) { |cf|
- assert_equal cf.index, expected
+ assert_equal expected, cf.index
expected += 1
}
end
@@ -2920,7 +2920,7 @@ class FormHelperTest < ActionView::TestCase
form_for(@post) do |f|
expected = 0
f.fields_for(:comments, @post.comments) { |cf|
- assert_equal cf.index, expected
+ assert_equal expected, cf.index
expected += 1
}
end
@@ -2931,7 +2931,7 @@ class FormHelperTest < ActionView::TestCase
form_for(@post) do |f|
f.fields_for(:comments, Comment.new(321), child_index: "abc") { |cf|
- assert_equal cf.index, "abc"
+ assert_equal "abc", cf.index
}
end
end
@@ -3446,9 +3446,9 @@ class FormHelperTest < ActionView::TestCase
method = options[:method]
if options.fetch(:enforce_utf8, true)
- txt = %{<input name="utf8" type="hidden" value="&#x2713;" />}
+ txt = %{<input name="utf8" type="hidden" value="&#x2713;" />}.dup
else
- txt = ""
+ txt = "".dup
end
if method && !%w(get post).include?(method.to_s)
@@ -3459,7 +3459,7 @@ class FormHelperTest < ActionView::TestCase
end
def form_text(action = "/", id = nil, html_class = nil, remote = nil, multipart = nil, method = nil)
- txt = %{<form accept-charset="UTF-8" action="#{action}"}
+ txt = %{<form accept-charset="UTF-8" action="#{action}"}.dup
txt << %{ enctype="multipart/form-data"} if multipart
txt << %{ data-remote="true"} if remote
txt << %{ class="#{html_class}"} if html_class
diff --git a/actionview/test/template/form_options_helper_test.rb b/actionview/test/template/form_options_helper_test.rb
index 258dcdb806..3247f20ba7 100644
--- a/actionview/test/template/form_options_helper_test.rb
+++ b/actionview/test/template/form_options_helper_test.rb
@@ -6,6 +6,15 @@ class Map < Hash
end
end
+class CustomEnumerable
+ include Enumerable
+
+ def each
+ yield "one"
+ yield "two"
+ end
+end
+
class FormOptionsHelperTest < ActionView::TestCase
tests ActionView::Helpers::FormOptionsHelper
@@ -904,6 +913,14 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_select_with_enumerable
+ @post = Post.new
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"one\">one</option>\n<option value=\"two\">two</option></select>",
+ select("post", "category", CustomEnumerable.new)
+ )
+ end
+
def test_collection_select
@post = Post.new
@post.author_name = "Babe"
diff --git a/actionview/test/template/form_tag_helper_test.rb b/actionview/test/template/form_tag_helper_test.rb
index 084c540139..7ac15db9fe 100644
--- a/actionview/test/template/form_tag_helper_test.rb
+++ b/actionview/test/template/form_tag_helper_test.rb
@@ -14,7 +14,7 @@ class FormTagHelperTest < ActionView::TestCase
method = options[:method]
enforce_utf8 = options.fetch(:enforce_utf8, true)
- "".tap do |txt|
+ "".dup.tap do |txt|
if enforce_utf8
txt << %{<input name="utf8" type="hidden" value="&#x2713;" />}
end
@@ -30,7 +30,7 @@ class FormTagHelperTest < ActionView::TestCase
method = method.to_s == "get" ? "get" : "post"
- txt = %{<form accept-charset="UTF-8" action="#{action}"}
+ txt = %{<form accept-charset="UTF-8" action="#{action}"}.dup
txt << %{ enctype="multipart/form-data"} if enctype
txt << %{ data-remote="true"} if remote
txt << %{ class="#{html_class}"} if html_class
@@ -345,6 +345,12 @@ class FormTagHelperTest < ActionView::TestCase
assert_dom_equal expected, actual
end
+ def test_text_field_tag_with_ac_parameters
+ actual = text_field_tag "title", ActionController::Parameters.new(key: "value")
+ expected = %(<input id="title" name="title" type="text" value="{&quot;key&quot;=&gt;&quot;value&quot;}" />)
+ assert_dom_equal expected, actual
+ end
+
def test_text_field_tag_size_string
actual = text_field_tag "title", "Hello!", "size" => "75"
expected = %(<input id="title" name="title" size="75" type="text" value="Hello!" />)
diff --git a/actionview/test/template/javascript_helper_test.rb b/actionview/test/template/javascript_helper_test.rb
index c7670b056b..67747a7a83 100644
--- a/actionview/test/template/javascript_helper_test.rb
+++ b/actionview/test/template/javascript_helper_test.rb
@@ -20,8 +20,8 @@ class JavaScriptHelperTest < ActionView::TestCase
assert_equal %(This \\"thing\\" is really\\n netos\\'), escape_javascript(%(This "thing" is really\n netos'))
assert_equal %(backslash\\\\test), escape_javascript(%(backslash\\test))
assert_equal %(dont <\\/close> tags), escape_javascript(%(dont </close> tags))
- assert_equal %(unicode &#x2028; newline), escape_javascript(%(unicode \342\200\250 newline).force_encoding(Encoding::UTF_8).encode!)
- assert_equal %(unicode &#x2029; newline), escape_javascript(%(unicode \342\200\251 newline).force_encoding(Encoding::UTF_8).encode!)
+ assert_equal %(unicode &#x2028; newline), escape_javascript(%(unicode \342\200\250 newline).dup.force_encoding(Encoding::UTF_8).encode!)
+ assert_equal %(unicode &#x2029; newline), escape_javascript(%(unicode \342\200\251 newline).dup.force_encoding(Encoding::UTF_8).encode!)
assert_equal %(dont <\\/close> tags), j(%(dont </close> tags))
end
diff --git a/actionview/test/template/log_subscriber_test.rb b/actionview/test/template/log_subscriber_test.rb
index 7f358add7e..4c9f84f277 100644
--- a/actionview/test/template/log_subscriber_test.rb
+++ b/actionview/test/template/log_subscriber_test.rb
@@ -8,11 +8,14 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
def setup
super
- view_paths = ActionController::Base.view_paths
+
+ view_paths = ActionController::Base.view_paths
lookup_context = ActionView::LookupContext.new(view_paths, {}, ["test"])
- renderer = ActionView::Renderer.new(lookup_context)
- @view = ActionView::Base.new(renderer, {})
+ renderer = ActionView::Renderer.new(lookup_context)
+ @view = ActionView::Base.new(renderer, {})
+
ActionView::LogSubscriber.attach_to :action_view
+
unless Rails.respond_to?(:root)
@defined_root = true
def Rails.root; :defined_root; end # Minitest `stub` expects the method to be defined.
@@ -21,7 +24,9 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
def teardown
super
+
ActiveSupport::LogSubscriber.log_subscribers.clear
+
# We need to undef `root`, RenderTestCases don't want this to be defined
Rails.instance_eval { undef :root } if @defined_root
end
@@ -39,7 +44,7 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
def set_view_cache_dependencies
def @view.view_cache_dependencies; []; end
- def @view.fragment_cache_key(*); "ahoy `controller` dependency"; end
+ def @view.combined_fragment_cache_key(*); "ahoy `controller` dependency"; end
end
def test_render_file_template
@@ -103,9 +108,9 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
set_view_cache_dependencies
set_cache_controller
- @view.render(partial: "test/cached_customer", locals: { cached_customer: Customer.new("david") })
# Second render should hit cache.
@view.render(partial: "test/cached_customer", locals: { cached_customer: Customer.new("david") })
+ @view.render(partial: "test/cached_customer", locals: { cached_customer: Customer.new("david") })
wait
assert_equal 2, @logger.logged(:info).size
@@ -113,6 +118,46 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
end
end
+ def test_render_uncached_outer_partial_with_inner_cached_partial_wont_mix_cache_hits_or_misses
+ Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do
+ set_view_cache_dependencies
+ set_cache_controller
+
+ @view.render(partial: "test/nested_cached_customer", locals: { cached_customer: Customer.new("Stan") })
+ wait
+ *, cached_inner, uncached_outer = @logger.logged(:info)
+ assert_match(/Rendered test\/_cached_customer\.erb (.*) \[cache miss\]/, cached_inner)
+ assert_match(/Rendered test\/_nested_cached_customer\.erb \(.*?ms\)$/, uncached_outer)
+
+ # Second render hits the cache for the _cached_customer partial. Outer template's log shouldn't be affected.
+ @view.render(partial: "test/nested_cached_customer", locals: { cached_customer: Customer.new("Stan") })
+ wait
+ *, cached_inner, uncached_outer = @logger.logged(:info)
+ assert_match(/Rendered test\/_cached_customer\.erb (.*) \[cache hit\]/, cached_inner)
+ assert_match(/Rendered test\/_nested_cached_customer\.erb \(.*?ms\)$/, uncached_outer)
+ end
+ end
+
+ def test_render_cached_outer_partial_with_cached_inner_partial
+ Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do
+ set_view_cache_dependencies
+ set_cache_controller
+
+ @view.render(partial: "test/cached_nested_cached_customer", locals: { cached_customer: Customer.new("Stan") })
+ wait
+ *, cached_inner, cached_outer = @logger.logged(:info)
+ assert_match(/Rendered test\/_cached_customer\.erb (.*) \[cache miss\]/, cached_inner)
+ assert_match(/Rendered test\/_cached_nested_cached_customer\.erb (.*) \[cache miss\]/, cached_outer)
+
+ # One render: inner partial skipped, because the outer has been cached.
+ assert_difference -> { @logger.logged(:info).size }, +1 do
+ @view.render(partial: "test/cached_nested_cached_customer", locals: { cached_customer: Customer.new("Stan") })
+ wait
+ end
+ assert_match(/Rendered test\/_cached_nested_cached_customer\.erb (.*) \[cache hit\]/, @logger.logged(:info).last)
+ end
+ end
+
def test_render_partial_with_cache_hitted_and_missed
Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do
set_view_cache_dependencies
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index 412948719c..1fd8b4fe1a 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -10,8 +10,8 @@ module RenderTestCases
@view = Class.new(ActionView::Base) do
def view_cache_dependencies; end
- def fragment_cache_key(key)
- ActiveSupport::Cache.expand_cache_key(key, :views)
+ def combined_fragment_cache_key(key)
+ [ :views, key ]
end
end.new(paths, @assigns)
@@ -27,7 +27,7 @@ module RenderTestCases
def test_render_without_options
e = assert_raises(ArgumentError) { @view.render() }
- assert_match(/You invoked render but did not give any of (.+) option./, e.message)
+ assert_match(/You invoked render but did not give any of (.+) option\./, e.message)
end
def test_render_file
@@ -83,6 +83,10 @@ module RenderTestCases
assert_equal "<h1>Kein Kommentar</h1>", @view.render(template: "comments/empty", locale: [:de])
end
+ def test_render_template_with_variants
+ assert_equal "<h1>No Comment</h1>\n", @view.render(template: "comments/empty", variants: :grid)
+ end
+
def test_render_file_with_handlers
assert_equal "<h1>No Comment</h1>\n", @view.render(file: "comments/empty", handlers: [:builder])
assert_equal "<h1>No Comment</h1>\n", @view.render(file: "comments/empty", handlers: :builder)
@@ -134,7 +138,7 @@ module RenderTestCases
end
def test_render_file_with_full_path
- template_path = File.join(File.dirname(__FILE__), "../fixtures/test/hello_world")
+ template_path = File.expand_path("../fixtures/test/hello_world", __dir__)
assert_equal "Hello world!", @view.render(file: template_path)
end
@@ -156,7 +160,7 @@ module RenderTestCases
end
def test_render_outside_path
- assert File.exist?(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb"))
+ assert File.exist?(File.expand_path("../../test/abstract_unit.rb", __dir__))
assert_raises ActionView::MissingTemplate do
@view.render(template: "../\\../test/abstract_unit.rb")
end
@@ -170,6 +174,10 @@ module RenderTestCases
assert_equal "partial html", @view.render(partial: "test/partial")
end
+ def test_render_partial_with_variants
+ assert_equal "<h1>Partial with variants</h1>\n", @view.render(partial: "test/partial_with_variants", variants: :grid)
+ end
+
def test_render_partial_with_selected_format
assert_equal "partial html", @view.render(partial: "test/partial", formats: :html)
assert_equal "partial js", @view.render(partial: "test/partial", formats: [:js])
@@ -253,7 +261,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_match %r{Trace of template inclusion: .*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
@@ -431,7 +439,7 @@ module RenderTestCases
end
CustomHandler = lambda do |template|
- "@output_buffer = ''\n" \
+ "@output_buffer = ''.dup\n" \
"@output_buffer << 'source: #{template.source.inspect}'\n"
end
@@ -575,7 +583,7 @@ module RenderTestCases
def test_render_with_passing_couple_extensions_to_one_register_template_handler_function_call
ActionView::Template.register_template_handler :foo1, :foo2, CustomHandler
- assert_equal @view.render(inline: "Hello, World!", type: :foo1), @view.render(inline: "Hello, World!", type: :foo2)
+ assert_equal @view.render(inline: "Hello, World!".dup, type: :foo1), @view.render(inline: "Hello, World!".dup, type: :foo2)
ensure
ActionView::Template.unregister_template_handler :foo1, :foo2
end
@@ -710,6 +718,6 @@ class CachedCollectionViewRenderTest < ActiveSupport::TestCase
private
def cache_key(*names, virtual_path)
digest = ActionView::Digestor.digest name: virtual_path, finder: @view.lookup_context, dependencies: []
- @view.fragment_cache_key([ *names, digest ])
+ @view.combined_fragment_cache_key([ "#{virtual_path}:#{digest}", *names ])
end
end
diff --git a/actionview/test/template/resolver_patterns_test.rb b/actionview/test/template/resolver_patterns_test.rb
index 43e3f21076..8e21f4b828 100644
--- a/actionview/test/template/resolver_patterns_test.rb
+++ b/actionview/test/template/resolver_patterns_test.rb
@@ -2,7 +2,7 @@ require "abstract_unit"
class ResolverPatternsTest < ActiveSupport::TestCase
def setup
- path = File.expand_path("../../fixtures/", __FILE__)
+ path = File.expand_path("../fixtures", __dir__)
pattern = ":prefix/{:formats/,}:action{.:formats,}{+:variants,}{.:handlers,}"
@resolver = ActionView::FileSystemResolver.new(path, pattern)
end
diff --git a/actionview/test/template/streaming_render_test.rb b/actionview/test/template/streaming_render_test.rb
index 6ce66a1275..41b9063417 100644
--- a/actionview/test/template/streaming_render_test.rb
+++ b/actionview/test/template/streaming_render_test.rb
@@ -17,7 +17,7 @@ class FiberedTest < ActiveSupport::TestCase
def buffered_render(options)
body = render_body(options)
- string = ""
+ string = "".dup
body.each do |piece|
string << piece
end
diff --git a/actionview/test/template/template_test.rb b/actionview/test/template/template_test.rb
index 9d31a98703..dd131f0246 100644
--- a/actionview/test/template/template_test.rb
+++ b/actionview/test/template/template_test.rb
@@ -53,7 +53,7 @@ class TestERBTemplate < ActiveSupport::TestCase
end
def new_template(body = "<%= hello %>", details = { format: :html })
- ActionView::Template.new(body, "hello template", details.fetch(:handler) { ERBHandler }, { virtual_path: "hello" }.merge!(details))
+ ActionView::Template.new(body.dup, "hello template", details.fetch(:handler) { ERBHandler }, { virtual_path: "hello" }.merge!(details))
end
def render(locals = {})
diff --git a/actionview/test/template/text_helper_test.rb b/actionview/test/template/text_helper_test.rb
index 251c98230f..21ee4cc8e5 100644
--- a/actionview/test/template/text_helper_test.rb
+++ b/actionview/test/template/text_helper_test.rb
@@ -11,7 +11,7 @@ class TextHelperTest < ActionView::TestCase
end
def test_concat
- self.output_buffer = "foo"
+ self.output_buffer = "foo".dup
assert_equal "foobar", concat("bar")
assert_equal "foobar", output_buffer
end
@@ -104,8 +104,8 @@ class TextHelperTest < ActionView::TestCase
end
def test_truncate_multibyte
- assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding(Encoding::UTF_8),
- truncate("\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding(Encoding::UTF_8), length: 10)
+ assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".dup.force_encoding(Encoding::UTF_8),
+ truncate("\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".dup.force_encoding(Encoding::UTF_8), length: 10)
end
def test_truncate_does_not_modify_the_options_hash
@@ -325,7 +325,7 @@ class TextHelperTest < ActionView::TestCase
end
def test_excerpt_with_utf8
- assert_equal("...\357\254\203ciency could not be...".force_encoding(Encoding::UTF_8), excerpt("That's why e\357\254\203ciency could not be helped".force_encoding(Encoding::UTF_8), "could", radius: 8))
+ assert_equal("...\357\254\203ciency could not be...".dup.force_encoding(Encoding::UTF_8), excerpt("That's why e\357\254\203ciency could not be helped".dup.force_encoding(Encoding::UTF_8), "could", radius: 8))
end
def test_excerpt_does_not_modify_the_options_hash
diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb
index 09454b32cc..a087c5c0cd 100644
--- a/actionview/test/template/url_helper_test.rb
+++ b/actionview/test/template/url_helper_test.rb
@@ -7,8 +7,7 @@ class UrlHelperTest < ActiveSupport::TestCase
# In those cases, we'll set up a simple mock
attr_accessor :controller, :request
- cattr_accessor :request_forgery
- self.request_forgery = false
+ cattr_accessor :request_forgery, default: false
routes = ActionDispatch::Routing::RouteSet.new
routes.draw do
@@ -16,6 +15,10 @@ class UrlHelperTest < ActiveSupport::TestCase
get "/other" => "foo#other"
get "/article/:id" => "foo#article", :as => :article
get "/category/:category" => "foo#category"
+
+ scope :engine do
+ get "/" => "foo#bar"
+ end
end
include ActionView::Helpers::UrlHelper
@@ -231,7 +234,11 @@ class UrlHelperTest < ActiveSupport::TestCase
end
def to_h
- { foo: :bar, baz: "quux" }
+ if permitted?
+ { foo: :bar, baz: "quux" }
+ else
+ raise ArgumentError
+ end
end
end
@@ -505,6 +512,12 @@ class UrlHelperTest < ActiveSupport::TestCase
assert !current_page?("http://www.example.com/", check_parameters: true)
end
+ def test_current_page_considering_params_when_options_does_not_respond_to_to_hash
+ @request = request_for_url("/?order=desc&page=1")
+
+ assert !current_page?(:back, check_parameters: false)
+ end
+
def test_current_page_with_params_that_match
@request = request_for_url("/?order=desc&page=1")
@@ -512,10 +525,10 @@ class UrlHelperTest < ActiveSupport::TestCase
assert current_page?("http://www.example.com/?order=desc&page=1")
end
- def test_current_page_with_not_get_verb
- @request = request_for_url("/events", method: :post)
+ def test_current_page_with_scope_that_match
+ @request = request_for_url("/engine/")
- assert !current_page?("/events")
+ assert current_page?("/engine")
end
def test_current_page_with_escaped_params
@@ -526,7 +539,7 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_current_page_with_escaped_params_with_different_encoding
@request = request_for_url("/")
- @request.stub(:path, "/category/administra%c3%a7%c3%a3o".force_encoding(Encoding::ASCII_8BIT)) do
+ @request.stub(:path, "/category/administra%c3%a7%c3%a3o".dup.force_encoding(Encoding::ASCII_8BIT)) do
assert current_page?(controller: "foo", action: "category", category: "administração")
assert current_page?("http://www.example.com/category/administra%c3%a7%c3%a3o")
end
@@ -544,6 +557,12 @@ class UrlHelperTest < ActiveSupport::TestCase
assert current_page?("/posts/")
end
+ def test_current_page_with_not_get_verb
+ @request = request_for_url("/events", method: :post)
+
+ assert !current_page?("/events")
+ end
+
def test_link_unless_current
@request = request_for_url("/")
@@ -605,8 +624,8 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_mail_to_with_special_characters
assert_dom_equal(
- %{<a href="mailto:%23%21%24%25%26%27%2A%2B-%2F%3D%3F%5E_%60%7B%7D%7C%7E@example.org">#!$%&amp;&#39;*+-/=?^_`{}|~@example.org</a>},
- mail_to("#!$%&'*+-/=?^_`{}|~@example.org")
+ %{<a href="mailto:%23%21%24%25%26%27%2A%2B-%2F%3D%3F%5E_%60%7B%7D%7C@example.org">#!$%&amp;&#39;*+-/=?^_`{}|@example.org</a>},
+ mail_to("#!$%&'*+-/=?^_`{}|@example.org")
)
end
@@ -672,13 +691,6 @@ class UrlHelperTest < ActiveSupport::TestCase
def request_forgery_protection_token
"form_token"
end
-
- private
- def sort_query_string_params(uri)
- path, qs = uri.split("?")
- qs = qs.split("&amp;").sort.join("&amp;") if qs
- qs ? "#{path}?#{qs}" : path
- end
end
class UrlHelperControllerTest < ActionController::TestCase
@@ -876,6 +888,11 @@ class WorkshopsController < ActionController::Base
@workshop = Workshop.new(params[:id])
render inline: "<%= url_for(@workshop) %>\n<%= link_to('Workshop', @workshop) %>"
end
+
+ def edit
+ @workshop = Workshop.new(params[:id])
+ render inline: "<%= current_page?(@workshop) %>"
+ end
end
class SessionsController < ActionController::Base
@@ -940,4 +957,11 @@ class PolymorphicControllerTest < ActionController::TestCase
get :edit, params: { workshop_id: 1, id: 1, format: "json" }
assert_equal %{/workshops/1/sessions/1.json\n<a href="/workshops/1/sessions/1.json">Session</a>}, @response.body
end
+
+ def test_current_page_when_options_does_not_respond_to_to_hash
+ @controller = WorkshopsController.new
+
+ get :edit, params: { id: 1 }
+ assert_equal "false", @response.body
+ end
end
diff --git a/actionview/test/ujs/config.ru b/actionview/test/ujs/config.ru
index 48b7a4b53a..213a41127a 100644
--- a/actionview/test/ujs/config.ru
+++ b/actionview/test/ujs/config.ru
@@ -1,4 +1,4 @@
-$LOAD_PATH.unshift File.expand_path("..", __FILE__)
+$LOAD_PATH.unshift __dir__
require "server"
run UJS::Server
diff --git a/actionview/test/ujs/public/test/call-remote.js b/actionview/test/ujs/public/test/call-remote.js
index dbeb8ad832..5932195363 100644
--- a/actionview/test/ujs/public/test/call-remote.js
+++ b/actionview/test/ujs/public/test/call-remote.js
@@ -100,6 +100,34 @@ asyncTest('JS code should be executed', 1, function() {
submit()
})
+asyncTest('ecmascript code should be executed', 1, function() {
+ buildForm({ method: 'post', 'data-type': 'script' })
+
+ $('form').append('<input type="text" name="content_type" value="application/ecmascript">')
+ $('form').append('<input type="text" name="content" value="ok(true, \'remote code should be run\')">')
+
+ submit()
+})
+
+asyncTest('execution of JS code does not modify current DOM', 1, function() {
+ var docLength, newDocLength
+ function getDocLength() {
+ return document.documentElement.outerHTML.length
+ }
+
+ buildForm({ method: 'post', 'data-type': 'script' })
+
+ $('form').append('<input type="text" name="content_type" value="text/javascript">')
+ $('form').append('<input type="text" name="content" value="\'remote code should be run\'">')
+
+ docLength = getDocLength()
+
+ submit(function() {
+ newDocLength = getDocLength()
+ ok(docLength === newDocLength, 'executed JS should not present in the document')
+ })
+})
+
asyncTest('XML document should be parsed', 1, function() {
buildForm({ method: 'post', 'data-type': 'html' })
diff --git a/actionview/test/ujs/public/test/data-remote.js b/actionview/test/ujs/public/test/data-remote.js
index b756add24e..161a92ac11 100644
--- a/actionview/test/ujs/public/test/data-remote.js
+++ b/actionview/test/ujs/public/test/data-remote.js
@@ -1,3 +1,17 @@
+(function() {
+
+function buildSelect(attrs) {
+ attrs = $.extend({
+ 'name': 'user_data', 'data-remote': 'true', 'data-url': '/echo', 'data-params': 'data1=value1'
+ }, attrs)
+
+ $('#qunit-fixture').append(
+ $('<select />', attrs)
+ .append($('<option />', {value: 'optionValue1', text: 'option1'}))
+ .append($('<option />', {value: 'optionValue2', text: 'option2'}))
+ )
+}
+
module('data-remote', {
setup: function() {
$('#qunit-fixture')
@@ -135,17 +149,7 @@ asyncTest('clicking on a button with data-remote attribute', 5, function() {
})
asyncTest('changing a select option with data-remote attribute', 5, function() {
- $('form')
- .append(
- $('<select />', {
- 'name': 'user_data',
- 'data-remote': 'true',
- 'data-params': 'data1=value1',
- 'data-url': '/echo'
- })
- .append($('<option />', {value: 'optionValue1', text: 'option1'}))
- .append($('<option />', {value: 'optionValue2', text: 'option2'}))
- )
+ buildSelect()
$('select[data-remote]')
.bindNative('ajax:success', function(e, data, status, xhr) {
@@ -350,17 +354,7 @@ asyncTest('submitting a form with falsy "data-remote" attribute', 0, function()
})
asyncTest('changing a select option with falsy "data-remote" attribute', 0, function() {
- $('form')
- .append(
- $('<select />', {
- 'name': 'user_data',
- 'data-remote': 'false',
- 'data-params': 'data1=value1',
- 'data-url': '/echo'
- })
- .append($('<option />', {value: 'optionValue1', text: 'option1'}))
- .append($('<option />', {value: 'optionValue2', text: 'option2'}))
- )
+ buildSelect({'data-remote': 'false'})
$('select[data-remote=false]:first')
.bindNative('ajax:beforeSend', function() {
@@ -413,3 +407,32 @@ asyncTest('form buttons should only be serialized when clicked', 4, function() {
})
.find('[name=submit2]').triggerNative('click')
})
+
+asyncTest('changing a select option without "data-url" attribute still fires ajax request to current location', 1, function() {
+ var currentLocation, ajaxLocation
+
+ buildSelect({'data-url': ''});
+
+ $('select[data-remote]')
+ .bindNative('ajax:beforeSend', function(e, xhr, settings) {
+ // Get current location (the same way jQuery does)
+ try {
+ currentLocation = location.href
+ } catch(err) {
+ currentLocation = document.createElement( 'a' )
+ currentLocation.href = ''
+ currentLocation = currentLocation.href
+ }
+
+ ajaxLocation = settings.url.replace(settings.data, '').replace(/&$/, '').replace(/\?$/, '')
+ equal(ajaxLocation, currentLocation, 'URL should be current page by default')
+
+ return false
+ })
+ .val('optionValue2')
+ .triggerNative('change')
+
+ setTimeout(function() { start() }, 20)
+})
+
+})()
diff --git a/activejob/.gitignore b/activejob/.gitignore
deleted file mode 100644
index b3aaf55871..0000000000
--- a/activejob/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-test/dummy
diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md
index 6b4f93df8b..77dfdefc05 100644
--- a/activejob/CHANGELOG.md
+++ b/activejob/CHANGELOG.md
@@ -1 +1,8 @@
+* Change logging instrumentation to log errors when a job raises an exception.
+
+ Fixes #26848.
+
+ *Steven Bull*
+
+
Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/activejob/CHANGELOG.md) for previous changes.
diff --git a/activejob/Rakefile b/activejob/Rakefile
index 41ff76135e..dd03ab0b8f 100644
--- a/activejob/Rakefile
+++ b/activejob/Rakefile
@@ -1,8 +1,7 @@
require "rake/testtask"
#TODO: add qu back to the list after it support Rails 5.1
-#TODO: add delayed_job back to the list after it support Rails 5.1
-ACTIVEJOB_ADAPTERS = %w(async inline que queue_classic resque sidekiq sneakers sucker_punch backburner test)
+ACTIVEJOB_ADAPTERS = %w(async inline delayed_job que queue_classic resque sidekiq sneakers sucker_punch backburner test)
ACTIVEJOB_ADAPTERS.delete("queue_classic") if defined?(JRUBY_VERSION)
task default: :test
@@ -44,9 +43,8 @@ namespace :test do
namespace :isolated do
task adapter => "test:env:#{adapter}" do
- dir = File.dirname(__FILE__)
- Dir.glob("#{dir}/test/cases/**/*_test.rb").all? do |file|
- sh(Gem.ruby, "-w", "-I#{dir}/lib", "-I#{dir}/test", file)
+ Dir.glob("#{__dir__}/test/cases/**/*_test.rb").all? do |file|
+ sh(Gem.ruby, "-w", "-I#{__dir__}/lib", "-I#{__dir__}/test", file)
end || raise("Failures")
end
end
diff --git a/activejob/activejob.gemspec b/activejob/activejob.gemspec
index 2547e91262..bdf23223d1 100644
--- a/activejob/activejob.gemspec
+++ b/activejob/activejob.gemspec
@@ -1,4 +1,4 @@
-version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
+version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
@@ -18,6 +18,11 @@ Gem::Specification.new do |s|
s.files = Dir["CHANGELOG.md", "MIT-LICENSE", "README.md", "lib/**/*"]
s.require_path = "lib"
+ s.metadata = {
+ "source_code_uri" => "https://github.com/rails/rails/tree/v#{version}/activejob",
+ "changelog_uri" => "https://github.com/rails/rails/blob/v#{version}/activejob/CHANGELOG.md"
+ }
+
s.add_dependency "activesupport", version
s.add_dependency "globalid", ">= 0.3.6"
end
diff --git a/activejob/bin/test b/activejob/bin/test
index a7beb14b27..470ce93f10 100755
--- a/activejob/bin/test
+++ b/activejob/bin/test
@@ -1,4 +1,4 @@
#!/usr/bin/env ruby
COMPONENT_ROOT = File.expand_path("..", __dir__)
-require File.expand_path("../tools/test", COMPONENT_ROOT)
+require_relative "../../tools/test"
diff --git a/activejob/lib/active_job.rb b/activejob/lib/active_job.rb
index 8b7aef65a2..79cad9457d 100644
--- a/activejob/lib/active_job.rb
+++ b/activejob/lib/active_job.rb
@@ -23,7 +23,7 @@
require "active_support"
require "active_support/rails"
-require "active_job/version"
+require_relative "active_job/version"
require "global_id"
module ActiveJob
diff --git a/activejob/lib/active_job/base.rb b/activejob/lib/active_job/base.rb
index 18e8641e50..af76d019c4 100644
--- a/activejob/lib/active_job/base.rb
+++ b/activejob/lib/active_job/base.rb
@@ -1,13 +1,13 @@
-require "active_job/core"
-require "active_job/queue_adapter"
-require "active_job/queue_name"
-require "active_job/queue_priority"
-require "active_job/enqueuing"
-require "active_job/execution"
-require "active_job/callbacks"
-require "active_job/exceptions"
-require "active_job/logging"
-require "active_job/translation"
+require_relative "core"
+require_relative "queue_adapter"
+require_relative "queue_name"
+require_relative "queue_priority"
+require_relative "enqueuing"
+require_relative "execution"
+require_relative "callbacks"
+require_relative "exceptions"
+require_relative "logging"
+require_relative "translation"
module ActiveJob #:nodoc:
# = Active Job
diff --git a/activejob/lib/active_job/callbacks.rb b/activejob/lib/active_job/callbacks.rb
index d5b17de8b5..9aebc880a5 100644
--- a/activejob/lib/active_job/callbacks.rb
+++ b/activejob/lib/active_job/callbacks.rb
@@ -4,7 +4,7 @@ module ActiveJob
# = Active Job Callbacks
#
# Active Job provides hooks during the life cycle of a job. Callbacks allow you
- # to trigger logic during the life cycle of a job. Available callbacks are:
+ # to trigger logic during this cycle. Available callbacks are:
#
# * <tt>before_enqueue</tt>
# * <tt>around_enqueue</tt>
@@ -13,6 +13,8 @@ module ActiveJob
# * <tt>around_perform</tt>
# * <tt>after_perform</tt>
#
+ # NOTE: Calling the same callback multiple times will overwrite previous callback definitions.
+ #
module Callbacks
extend ActiveSupport::Concern
include ActiveSupport::Callbacks
diff --git a/activejob/lib/active_job/core.rb b/activejob/lib/active_job/core.rb
index 548ec89ee2..e3e63f227e 100644
--- a/activejob/lib/active_job/core.rb
+++ b/activejob/lib/active_job/core.rb
@@ -80,6 +80,7 @@ module ActiveJob
{
"job_class" => self.class.name,
"job_id" => job_id,
+ "provider_job_id" => provider_job_id,
"queue_name" => queue_name,
"priority" => priority,
"arguments" => serialize_arguments(arguments),
diff --git a/activejob/lib/active_job/enqueuing.rb b/activejob/lib/active_job/enqueuing.rb
index c73117e7f3..994645c62a 100644
--- a/activejob/lib/active_job/enqueuing.rb
+++ b/activejob/lib/active_job/enqueuing.rb
@@ -1,4 +1,4 @@
-require "active_job/arguments"
+require_relative "arguments"
module ActiveJob
# Provides behavior for enqueuing jobs.
diff --git a/activejob/lib/active_job/execution.rb b/activejob/lib/active_job/execution.rb
index 94d30c8eaf..69486676d1 100644
--- a/activejob/lib/active_job/execution.rb
+++ b/activejob/lib/active_job/execution.rb
@@ -1,5 +1,5 @@
require "active_support/rescuable"
-require "active_job/arguments"
+require_relative "arguments"
module ActiveJob
module Execution
diff --git a/activejob/lib/active_job/logging.rb b/activejob/lib/active_job/logging.rb
index d7e2cd03e3..ddc4915fd3 100644
--- a/activejob/lib/active_job/logging.rb
+++ b/activejob/lib/active_job/logging.rb
@@ -8,7 +8,7 @@ module ActiveJob
extend ActiveSupport::Concern
included do
- cattr_accessor(:logger) { ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT)) }
+ cattr_accessor :logger, default: ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
around_enqueue do |_, block, _|
tag_logger do
@@ -74,9 +74,16 @@ module ActiveJob
end
def perform(event)
- info do
- job = event.payload[:job]
- "Performed #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms"
+ job = event.payload[:job]
+ ex = event.payload[:exception_object]
+ if ex
+ error do
+ "Error performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms: #{ex.class} (#{ex.message}):\n" + Array(ex.backtrace).join("\n")
+ end
+ else
+ info do
+ "Performed #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms"
+ end
end
end
diff --git a/activejob/lib/active_job/queue_adapter.rb b/activejob/lib/active_job/queue_adapter.rb
index 9dae80ffc2..b22d8b8347 100644
--- a/activejob/lib/active_job/queue_adapter.rb
+++ b/activejob/lib/active_job/queue_adapter.rb
@@ -7,6 +7,7 @@ module ActiveJob
extend ActiveSupport::Concern
included do
+ class_attribute :_queue_adapter_name, instance_accessor: false, instance_predicate: false
class_attribute :_queue_adapter, instance_accessor: false, instance_predicate: false
self.queue_adapter = :async
end
@@ -19,11 +20,15 @@ module ActiveJob
_queue_adapter
end
+ def queue_adapter_name
+ _queue_adapter_name
+ end
+
# Specify the backend queue provider. The default queue adapter
# is the +:async+ queue. See QueueAdapters for more
# information.
def queue_adapter=(name_or_adapter_or_class)
- self._queue_adapter = interpret_adapter(name_or_adapter_or_class)
+ interpret_adapter(name_or_adapter_or_class)
end
private
@@ -31,16 +36,24 @@ module ActiveJob
def interpret_adapter(name_or_adapter_or_class)
case name_or_adapter_or_class
when Symbol, String
- ActiveJob::QueueAdapters.lookup(name_or_adapter_or_class).new
+ assign_adapter(name_or_adapter_or_class.to_s,
+ ActiveJob::QueueAdapters.lookup(name_or_adapter_or_class).new)
else
if queue_adapter?(name_or_adapter_or_class)
- name_or_adapter_or_class
+ adapter_name = "#{name_or_adapter_or_class.class.name.demodulize.remove('Adapter').underscore}"
+ assign_adapter(adapter_name,
+ name_or_adapter_or_class)
else
raise ArgumentError
end
end
end
+ def assign_adapter(adapter_name, queue_adapter)
+ self._queue_adapter_name = adapter_name
+ self._queue_adapter = queue_adapter
+ end
+
QUEUE_ADAPTER_METHODS = [:enqueue, :enqueue_at].freeze
def queue_adapter?(object)
diff --git a/activejob/lib/active_job/queue_name.rb b/activejob/lib/active_job/queue_name.rb
index 352cf62424..6f521a1c1a 100644
--- a/activejob/lib/active_job/queue_name.rb
+++ b/activejob/lib/active_job/queue_name.rb
@@ -4,8 +4,8 @@ module ActiveJob
# Includes the ability to override the default queue name and prefix.
module ClassMethods
- mattr_accessor(:queue_name_prefix)
- mattr_accessor(:default_queue_name) { "default" }
+ mattr_accessor :queue_name_prefix
+ mattr_accessor :default_queue_name, default: "default"
# Specifies the name of the queue to process the job on.
#
@@ -32,11 +32,8 @@ module ActiveJob
end
included do
- class_attribute :queue_name, instance_accessor: false
- class_attribute :queue_name_delimiter, instance_accessor: false
-
- self.queue_name = default_queue_name
- self.queue_name_delimiter = "_" # set default delimiter to '_'
+ class_attribute :queue_name, instance_accessor: false, default: default_queue_name
+ class_attribute :queue_name_delimiter, instance_accessor: false, default: "_"
end
# Returns the name of the queue the job will be run on.
diff --git a/activejob/lib/active_job/queue_priority.rb b/activejob/lib/active_job/queue_priority.rb
index b02202fcc8..399d7a135a 100644
--- a/activejob/lib/active_job/queue_priority.rb
+++ b/activejob/lib/active_job/queue_priority.rb
@@ -4,7 +4,7 @@ module ActiveJob
# Includes the ability to override the default queue priority.
module ClassMethods
- mattr_accessor(:default_priority)
+ mattr_accessor :default_priority
# Specifies the priority of the queue to create the job with.
#
@@ -27,9 +27,7 @@ module ActiveJob
end
included do
- class_attribute :priority, instance_accessor: false
-
- self.priority = default_priority
+ class_attribute :priority, instance_accessor: false, default: default_priority
end
# Returns the priority that the job will be created with
diff --git a/activejob/lib/rails/generators/job/job_generator.rb b/activejob/lib/rails/generators/job/job_generator.rb
index 50476a2e50..474f181f65 100644
--- a/activejob/lib/rails/generators/job/job_generator.rb
+++ b/activejob/lib/rails/generators/job/job_generator.rb
@@ -12,7 +12,7 @@ module Rails # :nodoc:
hook_for :test_framework
def self.default_generator_root
- File.dirname(__FILE__)
+ __dir__
end
def create_job_file
diff --git a/activejob/test/adapters/delayed_job.rb b/activejob/test/adapters/delayed_job.rb
index 5f0ee2418c..98e41c0c36 100644
--- a/activejob/test/adapters/delayed_job.rb
+++ b/activejob/test/adapters/delayed_job.rb
@@ -1,6 +1,6 @@
ActiveJob::Base.queue_adapter = :delayed_job
-$LOAD_PATH << File.dirname(__FILE__) + "/../support/delayed_job"
+$LOAD_PATH << File.expand_path("../support/delayed_job", __dir__)
Delayed::Worker.delay_jobs = false
Delayed::Worker.backend = :test
diff --git a/activejob/test/cases/job_serialization_test.rb b/activejob/test/cases/job_serialization_test.rb
index 3f2e300dfa..c737557ece 100644
--- a/activejob/test/cases/job_serialization_test.rb
+++ b/activejob/test/cases/job_serialization_test.rb
@@ -44,4 +44,12 @@ class JobSerializationTest < ActiveSupport::TestCase
job.deserialize({})
assert_equal "en", job.locale
end
+
+ test "serialize stores provider_job_id" do
+ job = HelloJob.new
+ assert_nil job.serialize["provider_job_id"]
+
+ job.provider_job_id = "some value set by adapter"
+ assert_equal job.provider_job_id, job.serialize["provider_job_id"]
+ end
end
diff --git a/activejob/test/cases/logging_test.rb b/activejob/test/cases/logging_test.rb
index b37736f859..d5ca0f385c 100644
--- a/activejob/test/cases/logging_test.rb
+++ b/activejob/test/cases/logging_test.rb
@@ -5,6 +5,7 @@ require "jobs/hello_job"
require "jobs/logging_job"
require "jobs/overridden_logging_job"
require "jobs/nested_job"
+require "jobs/rescue_job"
require "models/person"
class LoggingTest < ActiveSupport::TestCase
@@ -124,4 +125,11 @@ class LoggingTest < ActiveSupport::TestCase
set_logger ::Logger.new(nil)
OverriddenLoggingJob.perform_later "Dummy"
end
+
+ def test_job_error_logging
+ RescueJob.perform_later "other"
+ rescue RescueJob::OtherError
+ assert_match(/Performing RescueJob \(Job ID: .*?\) from .*? with arguments:.*other/, @logger.messages)
+ assert_match(/Error performing RescueJob \(Job ID: .*?\) from .*? in .*ms: RescueJob::OtherError \(Bad hair\):\n.*\brescue_job\.rb:\d+:in `perform'/, @logger.messages)
+ end
end
diff --git a/activejob/test/cases/queue_adapter_test.rb b/activejob/test/cases/queue_adapter_test.rb
index 9611b0909b..8368107bdf 100644
--- a/activejob/test/cases/queue_adapter_test.rb
+++ b/activejob/test/cases/queue_adapter_test.rb
@@ -25,14 +25,19 @@ class QueueAdapterTest < ActiveJob::TestCase
base_queue_adapter = ActiveJob::Base.queue_adapter
child_job_one = Class.new(ActiveJob::Base)
+ assert_equal child_job_one.queue_adapter_name, ActiveJob::Base.queue_adapter_name
+
child_job_one.queue_adapter = :stub_one
assert_not_equal ActiveJob::Base.queue_adapter, child_job_one.queue_adapter
+ assert_equal "stub_one", child_job_one.queue_adapter_name
assert_kind_of ActiveJob::QueueAdapters::StubOneAdapter, child_job_one.queue_adapter
child_job_two = Class.new(ActiveJob::Base)
child_job_two.queue_adapter = :stub_two
+ assert_equal "stub_two", child_job_two.queue_adapter_name
+
assert_kind_of ActiveJob::QueueAdapters::StubTwoAdapter, child_job_two.queue_adapter
assert_kind_of ActiveJob::QueueAdapters::StubOneAdapter, child_job_one.queue_adapter, "child_job_one's queue adapter should remain unchanged"
assert_equal base_queue_adapter, ActiveJob::Base.queue_adapter, "ActiveJob::Base's queue adapter should remain unchanged"
diff --git a/activejob/test/jobs/rescue_job.rb b/activejob/test/jobs/rescue_job.rb
index ef8f777437..62add4271a 100644
--- a/activejob/test/jobs/rescue_job.rb
+++ b/activejob/test/jobs/rescue_job.rb
@@ -19,7 +19,7 @@ class RescueJob < ActiveJob::Base
when "david"
raise ArgumentError, "Hair too good"
when "other"
- raise OtherError
+ raise OtherError, "Bad hair"
else
JobBuffer.add("performed beautifully")
end
diff --git a/activejob/test/support/delayed_job/delayed/backend/test.rb b/activejob/test/support/delayed_job/delayed/backend/test.rb
index 98d731ff1e..68288c062b 100644
--- a/activejob/test/support/delayed_job/delayed/backend/test.rb
+++ b/activejob/test/support/delayed_job/delayed/backend/test.rb
@@ -19,8 +19,7 @@ module Delayed
include Delayed::Backend::Base
- cattr_accessor :id
- self.id = 0
+ cattr_accessor :id, default: 0
def initialize(hash = {})
self.attempts = 0
diff --git a/activejob/test/support/integration/dummy_app_template.rb b/activejob/test/support/integration/dummy_app_template.rb
index 29a5691f30..14fe3c9adc 100644
--- a/activejob/test/support/integration/dummy_app_template.rb
+++ b/activejob/test/support/integration/dummy_app_template.rb
@@ -5,7 +5,7 @@ end
rails_command("db:migrate")
initializer "activejob.rb", <<-CODE
-require "#{File.expand_path("../jobs_manager.rb", __FILE__)}"
+require "#{File.expand_path("jobs_manager.rb", __dir__)}"
JobsManager.current_manager.setup
CODE
diff --git a/activejob/test/support/integration/helper.rb b/activejob/test/support/integration/helper.rb
index 626b932cce..545b62752e 100644
--- a/activejob/test/support/integration/helper.rb
+++ b/activejob/test/support/integration/helper.rb
@@ -7,7 +7,7 @@ require "rails/generators/rails/app/app_generator"
require "tmpdir"
dummy_app_path = Dir.mktmpdir + "/dummy"
-dummy_app_template = File.expand_path("../dummy_app_template.rb", __FILE__)
+dummy_app_template = File.expand_path("dummy_app_template.rb", __dir__)
args = Rails::Generators::ARGVScrubber.new(["new", dummy_app_path, "--skip-gemfile", "--skip-bundle",
"--skip-git", "--skip-spring", "-d", "sqlite3", "--skip-javascript", "--force", "--quiet",
"--template", dummy_app_template]).prepare!
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index cdba0cee12..2916e5eabb 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,30 +1,35 @@
+* Fix regression in numericality validator when comparing Decimal and Float input
+ values with more scale than the schema.
+
+ *Bradley Priest*
+
* Fix methods `#keys`, `#values` in `ActiveModel::Errors`.
Change `#keys` to only return the keys that don't have empty messages.
Change `#values` to only return the not empty values.
- Example:
-
- # Before
- person = Person.new
- person.errors.keys # => []
- person.errors.values # => []
- person.errors.messages # => {}
- person.errors[:name] # => []
- person.errors.messages # => {:name => []}
- person.errors.keys # => [:name]
- person.errors.values # => [[]]
-
- # After
- person = Person.new
- person.errors.keys # => []
- person.errors.values # => []
- person.errors.messages # => {}
- person.errors[:name] # => []
- person.errors.messages # => {:name => []}
- person.errors.keys # => []
- person.errors.values # => []
+ Example:
+
+ # Before
+ person = Person.new
+ person.errors.keys # => []
+ person.errors.values # => []
+ person.errors.messages # => {}
+ person.errors[:name] # => []
+ person.errors.messages # => {:name => []}
+ person.errors.keys # => [:name]
+ person.errors.values # => [[]]
+
+ # After
+ person = Person.new
+ person.errors.keys # => []
+ person.errors.values # => []
+ person.errors.messages # => {}
+ person.errors[:name] # => []
+ person.errors.messages # => {:name => []}
+ person.errors.keys # => []
+ person.errors.values # => []
*bogdanvlviv*
diff --git a/activemodel/Rakefile b/activemodel/Rakefile
index c7f97a4258..d60f6d9997 100644
--- a/activemodel/Rakefile
+++ b/activemodel/Rakefile
@@ -1,14 +1,12 @@
require "rake/testtask"
-dir = File.dirname(__FILE__)
-
task default: :test
task :package
Rake::TestTask.new do |t|
t.libs << "test"
- t.test_files = Dir.glob("#{dir}/test/cases/**/*_test.rb")
+ t.test_files = Dir.glob("#{__dir__}/test/cases/**/*_test.rb")
t.warning = true
t.verbose = true
t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
@@ -16,8 +14,8 @@ end
namespace :test do
task :isolated do
- Dir.glob("#{dir}/test/**/*_test.rb").all? do |file|
- sh(Gem.ruby, "-w", "-I#{dir}/lib", "-I#{dir}/test", file)
+ Dir.glob("#{__dir__}/test/**/*_test.rb").all? do |file|
+ sh(Gem.ruby, "-w", "-I#{__dir__}/lib", "-I#{__dir__}/test", file)
end || raise("Failures")
end
end
diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec
index fd715f6ba9..18a35678f1 100644
--- a/activemodel/activemodel.gemspec
+++ b/activemodel/activemodel.gemspec
@@ -1,4 +1,4 @@
-version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
+version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
@@ -18,5 +18,10 @@ Gem::Specification.new do |s|
s.files = Dir["CHANGELOG.md", "MIT-LICENSE", "README.rdoc", "lib/**/*"]
s.require_path = "lib"
+ s.metadata = {
+ "source_code_uri" => "https://github.com/rails/rails/tree/v#{version}/activemodel",
+ "changelog_uri" => "https://github.com/rails/rails/blob/v#{version}/activemodel/CHANGELOG.md"
+ }
+
s.add_dependency "activesupport", version
end
diff --git a/activemodel/bin/test b/activemodel/bin/test
index a7beb14b27..470ce93f10 100755
--- a/activemodel/bin/test
+++ b/activemodel/bin/test
@@ -1,4 +1,4 @@
#!/usr/bin/env ruby
COMPONENT_ROOT = File.expand_path("..", __dir__)
-require File.expand_path("../tools/test", COMPONENT_ROOT)
+require_relative "../../tools/test"
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index 2389c858d5..ba1d2fbd44 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -23,7 +23,7 @@
require "active_support"
require "active_support/rails"
-require "active_model/version"
+require_relative "active_model/version"
module ActiveModel
extend ActiveSupport::Autoload
@@ -68,5 +68,5 @@ module ActiveModel
end
ActiveSupport.on_load(:i18n) do
- I18n.load_path << File.dirname(__FILE__) + "/active_model/locale/en.yml"
+ I18n.load_path << File.expand_path("active_model/locale/en.yml", __dir__)
end
diff --git a/activemodel/lib/active_model/attribute_assignment.rb b/activemodel/lib/active_model/attribute_assignment.rb
index ee130df989..aa931119ff 100644
--- a/activemodel/lib/active_model/attribute_assignment.rb
+++ b/activemodel/lib/active_model/attribute_assignment.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/core_ext/hash/keys"
module ActiveModel
@@ -19,10 +21,10 @@ module ActiveModel
# cat = Cat.new
# cat.assign_attributes(name: "Gorby", status: "yawning")
# cat.name # => 'Gorby'
- # cat.status => 'yawning'
+ # cat.status # => 'yawning'
# cat.assign_attributes(status: "sleeping")
# cat.name # => 'Gorby'
- # cat.status => 'sleeping'
+ # cat.status # => 'sleeping'
def assign_attributes(new_attributes)
if !new_attributes.respond_to?(:stringify_keys)
raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
@@ -42,8 +44,9 @@ module ActiveModel
end
def _assign_attribute(k, v)
- if respond_to?("#{k}=")
- public_send("#{k}=", v)
+ setter = :"#{k}="
+ if respond_to?(setter)
+ public_send(setter, v)
else
raise UnknownAttributeError.new(self, k)
end
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 166c6ac21f..b3b39bf7ae 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -68,9 +68,8 @@ module ActiveModel
CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
included do
- class_attribute :attribute_aliases, :attribute_method_matchers, instance_writer: false
- self.attribute_aliases = {}
- self.attribute_method_matchers = [ClassMethods::AttributeMethodMatcher.new]
+ class_attribute :attribute_aliases, instance_writer: false, default: {}
+ class_attribute :attribute_method_matchers, instance_writer: false, default: [ ClassMethods::AttributeMethodMatcher.new ]
end
module ClassMethods
@@ -472,5 +471,9 @@ module ActiveModel
def missing_attribute(attr_name, stack)
raise ActiveModel::MissingAttributeError, "missing attribute: #{attr_name}", stack
end
+
+ def _read_attribute(attr)
+ __send__(attr)
+ end
end
end
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index eac2761433..835e6f7716 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -56,6 +56,9 @@ module ActiveModel
#
# Would only create the +after_create+ and +before_create+ callback methods in
# your class.
+ #
+ # NOTE: Calling the same callback multiple times will overwrite previous callback definitions.
+ #
module Callbacks
def self.extended(base) #:nodoc:
base.class_eval do
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 6e0af99ad7..dc81f74779 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -179,13 +179,13 @@ module ActiveModel
# Handles <tt>*_changed?</tt> for +method_missing+.
def attribute_changed?(attr, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN) # :nodoc:
!!changes_include?(attr) &&
- (to == OPTION_NOT_GIVEN || to == __send__(attr)) &&
+ (to == OPTION_NOT_GIVEN || to == _read_attribute(attr)) &&
(from == OPTION_NOT_GIVEN || from == changed_attributes[attr])
end
# Handles <tt>*_was</tt> for +method_missing+.
def attribute_was(attr) # :nodoc:
- attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
+ attribute_changed?(attr) ? changed_attributes[attr] : _read_attribute(attr)
end
# Handles <tt>*_previously_changed?</tt> for +method_missing+.
@@ -226,7 +226,7 @@ module ActiveModel
# Handles <tt>*_change</tt> for +method_missing+.
def attribute_change(attr)
- [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
+ [changed_attributes[attr], _read_attribute(attr)] if attribute_changed?(attr)
end
# Handles <tt>*_previous_change</tt> for +method_missing+.
@@ -239,7 +239,7 @@ module ActiveModel
return if attribute_changed?(attr)
begin
- value = __send__(attr)
+ value = _read_attribute(attr)
value = value.duplicable? ? value.clone : value
rescue TypeError, NoMethodError
end
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 5d3472802b..942b4fa9bb 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -172,8 +172,8 @@ module ActiveModel
# person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
# person.errors.values # => [["cannot be nil", "must be specified"]]
def values
- messages.reject do |key, value|
- value.empty?
+ messages.select do |key, value|
+ !value.empty?
end.values
end
@@ -182,8 +182,8 @@ module ActiveModel
# person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
# person.errors.keys # => [:name]
def keys
- messages.reject do |key, value|
- value.empty?
+ messages.select do |key, value|
+ !value.empty?
end.keys
end
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index 9853cf38fe..9ac56526a2 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -47,7 +47,7 @@ module ActiveModel
# :method: <=>
#
# :call-seq:
- # ==(other)
+ # <=>(other)
#
# Equivalent to <tt>String#<=></tt>.
#
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index a9d92eb92a..205b84ddb4 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -10,8 +10,7 @@ module ActiveModel
included do
extend ActiveModel::Naming
- class_attribute :include_root_in_json, instance_writer: false
- self.include_root_in_json = false
+ class_attribute :include_root_in_json, instance_writer: false, default: false
end
# Returns a hash representing the model. Some configuration can be
diff --git a/activemodel/lib/active_model/type.rb b/activemodel/lib/active_model/type.rb
index 095801d8f0..1741a67a4d 100644
--- a/activemodel/lib/active_model/type.rb
+++ b/activemodel/lib/active_model/type.rb
@@ -1,19 +1,19 @@
-require "active_model/type/helpers"
-require "active_model/type/value"
+require_relative "type/helpers"
+require_relative "type/value"
-require "active_model/type/big_integer"
-require "active_model/type/binary"
-require "active_model/type/boolean"
-require "active_model/type/date"
-require "active_model/type/date_time"
-require "active_model/type/decimal"
-require "active_model/type/float"
-require "active_model/type/immutable_string"
-require "active_model/type/integer"
-require "active_model/type/string"
-require "active_model/type/time"
+require_relative "type/big_integer"
+require_relative "type/binary"
+require_relative "type/boolean"
+require_relative "type/date"
+require_relative "type/date_time"
+require_relative "type/decimal"
+require_relative "type/float"
+require_relative "type/immutable_string"
+require_relative "type/integer"
+require_relative "type/string"
+require_relative "type/time"
-require "active_model/type/registry"
+require_relative "type/registry"
module ActiveModel
module Type
diff --git a/activemodel/lib/active_model/type/big_integer.rb b/activemodel/lib/active_model/type/big_integer.rb
index 3b629682fe..f461d7041e 100644
--- a/activemodel/lib/active_model/type/big_integer.rb
+++ b/activemodel/lib/active_model/type/big_integer.rb
@@ -1,4 +1,4 @@
-require "active_model/type/integer"
+require_relative "integer"
module ActiveModel
module Type
diff --git a/activemodel/lib/active_model/type/helpers.rb b/activemodel/lib/active_model/type/helpers.rb
index 82cd9ebe98..1fe06ab3d5 100644
--- a/activemodel/lib/active_model/type/helpers.rb
+++ b/activemodel/lib/active_model/type/helpers.rb
@@ -1,4 +1,4 @@
-require "active_model/type/helpers/accepts_multiparameter_time"
-require "active_model/type/helpers/numeric"
-require "active_model/type/helpers/mutable"
-require "active_model/type/helpers/time_value"
+require_relative "helpers/accepts_multiparameter_time"
+require_relative "helpers/numeric"
+require_relative "helpers/mutable"
+require_relative "helpers/time_value"
diff --git a/activemodel/lib/active_model/type/string.rb b/activemodel/lib/active_model/type/string.rb
index c7e0208a5a..2fc027d3c4 100644
--- a/activemodel/lib/active_model/type/string.rb
+++ b/activemodel/lib/active_model/type/string.rb
@@ -1,4 +1,4 @@
-require "active_model/type/immutable_string"
+require_relative "immutable_string"
module ActiveModel
module Type
@@ -12,7 +12,12 @@ module ActiveModel
private
def cast_value(value)
- ::String.new(super)
+ case value
+ when ::String then ::String.new(value)
+ when true then "t".freeze
+ when false then "f".freeze
+ else value.to_s
+ end
end
end
end
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index d460068830..ae1d69f685 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -49,8 +49,7 @@ module ActiveModel
private :validation_context=
define_callbacks :validate, scope: :name
- class_attribute :_validators, instance_writer: false
- self._validators = Hash.new { |h, k| h[k] = [] }
+ class_attribute :_validators, instance_writer: false, default: Hash.new { |h, k| h[k] = [] }
end
module ClassMethods
@@ -147,6 +146,9 @@ module ActiveModel
# 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.
+ #
+ # NOTE: Calling +validate+ multiple times on the same method will overwrite previous definitions.
+ #
def validate(*args, &block)
options = args.extract_options!
@@ -432,4 +434,4 @@ module ActiveModel
end
end
-Dir[File.dirname(__FILE__) + "/validations/*.rb"].each { |file| require file }
+Dir[File.expand_path("validations/*.rb", __dir__)].each { |file| require file }
diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb
index b7156ba802..d587079bd3 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -1,4 +1,4 @@
-require "active_model/validations/clusivity"
+require_relative "clusivity"
module ActiveModel
module Validations
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index b4b8d9f33c..98b3f6a8e5 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -1,4 +1,3 @@
-
module ActiveModel
module Validations
class FormatValidator < EachValidator # :nodoc:
diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb
index c6c5bae649..74dac3b7df 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -1,4 +1,4 @@
-require "active_model/validations/clusivity"
+require_relative "clusivity"
module ActiveModel
module Validations
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index 995b331245..fb053a4c4e 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -36,7 +36,9 @@ module ActiveModel
return
end
- unless raw_value.is_a?(Numeric)
+ if raw_value.is_a?(Numeric)
+ value = raw_value
+ else
value = parse_raw_value_as_a_number(raw_value)
end
@@ -104,7 +106,7 @@ module ActiveModel
module HelperMethods
# Validates whether the value of the specified attribute is numeric by
# trying to convert it to a float with Kernel.Float (if <tt>only_integer</tt>
- # is +false+) or applying it to the regular expression <tt>/\A[\+\-]?\d+\Z/</tt>
+ # is +false+) or applying it to the regular expression <tt>/\A[\+\-]?\d+\z/</tt>
# (if <tt>only_integer</tt> is set to +true+).
#
# class Person < ActiveRecord::Base
diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb
index 6e8a434bbe..ce4106a5e1 100644
--- a/activemodel/lib/active_model/validations/presence.rb
+++ b/activemodel/lib/active_model/validations/presence.rb
@@ -1,4 +1,3 @@
-
module ActiveModel
module Validations
class PresenceValidator < EachValidator # :nodoc:
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index 0ce5935f3a..a8b958e974 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -18,7 +18,6 @@ module ActiveModel
# validates :first_name, length: { maximum: 30 }
# validates :age, numericality: true
# validates :username, presence: true
- # validates :username, uniqueness: true
#
# The power of the +validates+ method comes when using custom validators
# and default validators in one call for a given attribute.
@@ -34,7 +33,7 @@ module ActiveModel
# include ActiveModel::Validations
# attr_accessor :name, :email
#
- # validates :name, presence: true, uniqueness: true, length: { maximum: 100 }
+ # validates :name, presence: true, length: { maximum: 100 }
# validates :email, presence: true, email: true
# end
#
@@ -94,7 +93,7 @@ module ActiveModel
# Example:
#
# validates :password, presence: true, confirmation: true, if: :password_required?
- # validates :token, uniqueness: true, strict: TokenGenerationException
+ # validates :token, length: 24, strict: TokenLengthException
#
#
# Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+, +:strict+
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index 98234e9b6b..6c11981e1d 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -82,7 +82,7 @@ module ActiveModel
# end
#
# It can be useful to access the class that is using that validator when there are prerequisites such
- # as an +attr_accessor+ being present. This class is accessible via +options[:class]+ in the constructor.
+ # as an +attr_accessor+ being present. This class is accessible via <tt>options[:class]</tt> in the constructor.
# To setup your validator override the constructor.
#
# class MyValidator < ActiveModel::Validator
@@ -97,7 +97,7 @@ module ActiveModel
# Returns the kind of the validator.
#
# PresenceValidator.kind # => :presence
- # UniquenessValidator.kind # => :uniqueness
+ # AcceptanceValidator.kind # => :acceptance
def self.kind
@kind ||= name.split("::").last.underscore.chomp("_validator").to_sym unless anonymous?
end
@@ -109,8 +109,8 @@ module ActiveModel
# Returns the kind for this validator.
#
- # PresenceValidator.new.kind # => :presence
- # UniquenessValidator.new.kind # => :uniqueness
+ # PresenceValidator.new(attributes: [:username]).kind # => :presence
+ # AcceptanceValidator.new(attributes: [:terms]).kind # => :acceptance
def kind
self.class.kind
end
diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb
index fdd18d7601..0242b96083 100644
--- a/activemodel/test/cases/dirty_test.rb
+++ b/activemodel/test/cases/dirty_test.rb
@@ -96,7 +96,7 @@ class DirtyTest < ActiveModel::TestCase
end
test "attribute mutation" do
- @model.instance_variable_set("@name", "Yam")
+ @model.instance_variable_set("@name", "Yam".dup)
assert !@model.name_changed?
@model.name.replace("Hadad")
assert !@model.name_changed?
diff --git a/activemodel/test/cases/type/string_test.rb b/activemodel/test/cases/type/string_test.rb
index 222083817e..5bf9fc3527 100644
--- a/activemodel/test/cases/type/string_test.rb
+++ b/activemodel/test/cases/type/string_test.rb
@@ -12,16 +12,25 @@ module ActiveModel
end
test "cast strings are mutable" do
- s = "foo"
type = Type::String.new
+
+ s = "foo".dup
assert_equal false, type.cast(s).frozen?
+ assert_equal false, s.frozen?
+
+ f = "foo".freeze
+ assert_equal false, type.cast(f).frozen?
+ assert_equal true, f.frozen?
end
test "values are duped coming out" do
- s = "foo"
type = Type::String.new
+
+ s = "foo"
assert_not_same s, type.cast(s)
+ assert_equal s, type.cast(s)
assert_not_same s, type.deserialize(s)
+ assert_equal s, type.deserialize(s)
end
end
end
diff --git a/activemodel/test/cases/validations/format_validation_test.rb b/activemodel/test/cases/validations/format_validation_test.rb
index d7e6bf3707..f5b1ad721c 100644
--- a/activemodel/test/cases/validations/format_validation_test.rb
+++ b/activemodel/test/cases/validations/format_validation_test.rb
@@ -107,7 +107,7 @@ class PresenceValidationTest < ActiveModel::TestCase
end
def test_validates_format_of_with_lambda
- Topic.validates_format_of :content, with: lambda { |topic| topic.title == "digit" ? /\A\d+\Z/ : /\A\S+\Z/ }
+ Topic.validates_format_of :content, with: lambda { |topic| topic.title == "digit" ? /\A\d+\z/ : /\A\S+\z/ }
t = Topic.new
t.title = "digit"
@@ -119,7 +119,7 @@ class PresenceValidationTest < ActiveModel::TestCase
end
def test_validates_format_of_without_lambda
- Topic.validates_format_of :content, without: lambda { |topic| topic.title == "characters" ? /\A\d+\Z/ : /\A\S+\Z/ }
+ Topic.validates_format_of :content, without: lambda { |topic| topic.title == "characters" ? /\A\d+\z/ : /\A\S+\z/ }
t = Topic.new
t.title = "characters"
@@ -131,7 +131,7 @@ class PresenceValidationTest < ActiveModel::TestCase
end
def test_validates_format_of_for_ruby_class
- Person.validates_format_of :karma, with: /\A\d+\Z/
+ Person.validates_format_of :karma, with: /\A\d+\z/
p = Person.new
p.karma = "Pixies"
diff --git a/activemodel/test/validators/email_validator.rb b/activemodel/test/validators/email_validator.rb
index 9b74d83b37..f733c45cf0 100644
--- a/activemodel/test/validators/email_validator.rb
+++ b/activemodel/test/validators/email_validator.rb
@@ -1,4 +1,3 @@
-
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors[attribute] << (options[:message] || "is not an email") unless
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 30d580b9e3..8d900d9669 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1 +1,142 @@
+* Fix eager loading/preloading association with scope including joins.
+
+ Fixes #28324.
+
+ *Ryuta Kamizono*
+
+* Fix transactions to apply state to child transactions
+
+ Previously if you had a nested transaction and the outer transaction was rolledback the record from the
+ inner transaction would still be marked as persisted.
+
+ This change fixes that by applying the state of the parent transaction to the child transaction when the
+ parent transaction is rolledback. This will correctly mark records from the inner transaction as not persisted.
+
+ *Eileen M. Uchitelle*, *Aaron Patterson*
+
+* Deprecate `set_state` method in `TransactionState`
+
+ Deprecated the `set_state` method in favor of setting the state via specific methods. If you need to mark the
+ state of the transaction you can now use `rollback!`, `commit!` or `nullify!` instead of
+ `set_state(:rolledback)`, `set_state(:committed)`, or `set_state(nil)`.
+
+ *Eileen M. Uchitelle*, *Aaron Patterson*
+
+* Deprecate delegating to `arel` in `Relation`.
+
+ *Ryuta Kamizono*
+
+* Fix eager loading to respect `store_full_sti_class` setting.
+
+ *Ryuta Kamizono*
+
+* Query cache was unavailable when entering the `ActiveRecord::Base.cache` block
+ without being connected.
+
+ *Tsukasa Oishi*
+
+* Previously, when building records using a `has_many :through` association,
+ if the child records were deleted before the parent was saved, they would
+ still be persisted. Now, if child records are deleted before the parent is saved
+ on a `has_many :through` association, the child records will not be persisted.
+
+ *Tobias Kraze*
+
+* Merging two relations representing nested joins no longer transforms the joins of
+ the merged relation into LEFT OUTER JOIN. Example to clarify:
+
+ ```
+ Author.joins(:posts).merge(Post.joins(:comments))
+ # Before the change:
+ #=> SELECT ... FROM authors INNER JOIN posts ON ... LEFT OUTER JOIN comments ON...
+
+ # After the change:
+ #=> SELECT ... FROM authors INNER JOIN posts ON ... INNER JOIN comments ON...
+ ```
+
+ TODO: Add to the Rails 5.2 upgrade guide
+
+ *Maxime Handfield Lapointe*
+
+* `ActiveRecord::Persistence#touch` does not work well when optimistic locking enabled and
+ `locking_column`, without default value, is null in the database.
+
+ *bogdanvlviv*
+
+* Fix destroying existing object does not work well when optimistic locking enabled and
+ `locking_column` is null in the database.
+
+ *bogdanvlviv*
+
+* Use bulk INSERT to insert fixtures for better performance.
+
+ *Kir Shatrov*
+
+* Prevent making bind param if casted value is nil.
+
+ *Ryuta Kamizono*
+
+* Deprecate passing arguments and block at the same time to `count` and `sum` in `ActiveRecord::Calculations`.
+
+ *Ryuta Kamizono*
+
+* Loading model schema from database is now thread-safe.
+
+ Fixes #28589.
+
+ *Vikrant Chaudhary*, *David Abdemoulaie*
+
+* Add `ActiveRecord::Base#cache_version` to support recyclable cache keys via the new versioned entries
+ in `ActiveSupport::Cache`. This also means that `ActiveRecord::Base#cache_key` will now return a stable key
+ that does not include a timestamp any more.
+
+ NOTE: This feature is turned off by default, and `#cache_key` will still return cache keys with timestamps
+ until you set `ActiveRecord::Base.cache_versioning = true`. That's the setting for all new apps on Rails 5.2+
+
+ *DHH*
+
+* Respect `SchemaDumper.ignore_tables` in rake tasks for databases structure dump
+
+ *Rusty Geldmacher*, *Guillermo Iguaran*
+
+* Add type caster to `RuntimeReflection#alias_name`
+
+ Fixes #28959.
+
+ *Jon Moss*
+
+* Deprecate `supports_statement_cache?`.
+
+ *Ryuta Kamizono*
+
+* Quote database name in `db:create` grant statement (when database user does not have access to create the database).
+
+ *Rune Philosof*
+
+* Raise error `UnknownMigrationVersionError` on the movement of migrations
+ when the current migration does not exist.
+
+ *bogdanvlviv*
+
+* Fix `bin/rails db:forward` first migration.
+
+ *bogdanvlviv*
+
+* Support Descending Indexes for MySQL.
+
+ MySQL 8.0.1 and higher supports descending indexes: `DESC` in an index definition is no longer ignored.
+ See https://dev.mysql.com/doc/refman/8.0/en/descending-indexes.html.
+
+ *Ryuta Kamizono*
+
+* Fix inconsistency with changed attributes when overriding AR attribute reader.
+
+ *bogdanvlviv*
+
+* When calling the dynamic fixture accessor method with no arguments it now returns all fixtures of this type.
+ Previously this method always returned an empty array.
+
+ *Kevin McPhillips*
+
+
Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/activerecord/CHANGELOG.md) for previous changes.
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 7be3d851f1..fe5f9d1071 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -1,7 +1,7 @@
require "rake/testtask"
-require File.expand_path(File.dirname(__FILE__)) + "/test/config"
-require File.expand_path(File.dirname(__FILE__)) + "/test/support/config"
+require_relative "test/config"
+require_relative "test/support/config"
def run_without_aborting(*tasks)
errors = []
@@ -134,7 +134,7 @@ task drop_postgresql_databases: "db:postgresql:drop"
task rebuild_postgresql_databases: "db:postgresql:rebuild"
task :lines do
- load File.expand_path("..", File.dirname(__FILE__)) + "/tools/line_statistics"
+ load File.expand_path("../tools/line_statistics", __dir__)
files = FileList["lib/active_record/**/*.rb"]
CodeTools::LineStatistics.new(files).print_loc
end
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index 0b37e9076c..a626a1f21b 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -1,4 +1,4 @@
-version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
+version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
@@ -21,6 +21,11 @@ Gem::Specification.new do |s|
s.extra_rdoc_files = %w(README.rdoc)
s.rdoc_options.concat ["--main", "README.rdoc"]
+ s.metadata = {
+ "source_code_uri" => "https://github.com/rails/rails/tree/v#{version}/activerecord",
+ "changelog_uri" => "https://github.com/rails/rails/blob/v#{version}/activerecord/CHANGELOG.md"
+ }
+
s.add_dependency "activesupport", version
s.add_dependency "activemodel", version
diff --git a/activerecord/bin/test b/activerecord/bin/test
index 3a9547e5c1..ab69f4f603 100755
--- a/activerecord/bin/test
+++ b/activerecord/bin/test
@@ -1,7 +1,7 @@
#!/usr/bin/env ruby
COMPONENT_ROOT = File.expand_path("..", __dir__)
-require File.expand_path("../tools/test", COMPONENT_ROOT)
+require_relative "../../tools/test"
module Minitest
def self.plugin_active_record_options(opts, options)
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 96b8545dfc..25d5e87317 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -26,8 +26,8 @@ require "active_support/rails"
require "active_model"
require "arel"
-require "active_record/version"
-require "active_record/attribute_set"
+require_relative "active_record/version"
+require_relative "active_record/attribute_set"
module ActiveRecord
extend ActiveSupport::Autoload
@@ -177,5 +177,5 @@ ActiveSupport.on_load(:active_record) do
end
ActiveSupport.on_load(:i18n) do
- I18n.load_path << File.dirname(__FILE__) + "/active_record/locale/en.yml"
+ I18n.load_path << File.expand_path("active_record/locale/en.yml", __dir__)
end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 6efa448d49..e782196ce6 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1,7 +1,7 @@
require "active_support/core_ext/enumerable"
require "active_support/core_ext/string/conversions"
require "active_support/core_ext/module/remove_method"
-require "active_record/errors"
+require_relative "errors"
module ActiveRecord
class AssociationNotFoundError < ConfigurationError #:nodoc:
@@ -342,7 +342,7 @@ module ActiveRecord
# | | belongs_to |
# generated methods | belongs_to | :polymorphic | has_one
# ----------------------------------+------------+--------------+---------
- # other(force_reload=false) | X | X | X
+ # other | X | X | X
# other=(other) | X | X | X
# build_other(attributes={}) | X | | X
# create_other(attributes={}) | X | | X
@@ -352,7 +352,7 @@ module ActiveRecord
# | | | has_many
# generated methods | habtm | has_many | :through
# ----------------------------------+-------+----------+----------
- # others(force_reload=false) | X | X | X
+ # others | X | X | X
# others=(other,other,...) | X | X | X
# other_ids | X | X | X
# other_ids=(id,id,...) | X | X | X
@@ -1187,7 +1187,7 @@ module ActiveRecord
# +collection+ is a placeholder for the symbol passed as the +name+ argument, so
# <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.
#
- # [collection(force_reload = false)]
+ # [collection]
# Returns an array of all the associated objects.
# An empty array is returned if none are found.
# [collection<<(object, ...)]
@@ -1276,7 +1276,7 @@ module ActiveRecord
# Scope examples:
# has_many :comments, -> { where(author_id: 1) }
# has_many :employees, -> { joins(:address) }
- # has_many :posts, ->(post) { where("max_post_length > ?", post.length) }
+ # has_many :posts, ->(blog) { where("max_post_length > ?", blog.max_post_length) }
#
# === Extensions
#
@@ -1407,7 +1407,7 @@ module ActiveRecord
# +association+ is a placeholder for the symbol passed as the +name+ argument, so
# <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.
#
- # [association(force_reload = false)]
+ # [association]
# Returns the associated object. +nil+ is returned if none is found.
# [association=(associate)]
# Assigns the associate object, extracts the primary key, sets it as the foreign key,
@@ -1443,7 +1443,7 @@ module ActiveRecord
# Scope examples:
# has_one :author, -> { where(comment_id: 1) }
# has_one :employer, -> { joins(:company) }
- # has_one :dob, ->(dob) { where("Date.new(2000, 01, 01) > ?", dob) }
+ # has_one :latest_post, ->(blog) { where("created_at > ?", blog.enabled_at) }
#
# === Options
#
@@ -1539,7 +1539,7 @@ module ActiveRecord
# +association+ is a placeholder for the symbol passed as the +name+ argument, so
# <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.
#
- # [association(force_reload = false)]
+ # [association]
# Returns the associated object. +nil+ is returned if none is found.
# [association=(associate)]
# Assigns the associate object, extracts the primary key, and sets it as the foreign key.
@@ -1573,7 +1573,7 @@ module ActiveRecord
# Scope examples:
# belongs_to :firm, -> { where(id: 2) }
# belongs_to :user, -> { joins(:friends) }
- # belongs_to :level, ->(level) { where("game_level > ?", level.current) }
+ # belongs_to :level, ->(game) { where("game_level > ?", game.current_level) }
#
# === Options
#
@@ -1649,7 +1649,7 @@ module ActiveRecord
# you don't want to have association presence validated, use <tt>optional: true</tt>.
# [:default]
# Provide a callable (i.e. proc or lambda) to specify that the association should
- # be initialized with a particular record before validation.
+ # be initialized with a particular record before validation.
#
# Option examples:
# belongs_to :firm, foreign_key: "client_of"
@@ -1701,7 +1701,7 @@ module ActiveRecord
# +collection+ is a placeholder for the symbol passed as the +name+ argument, so
# <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>.
#
- # [collection(force_reload = false)]
+ # [collection]
# Returns an array of all the associated objects.
# An empty array is returned if none are found.
# [collection<<(object, ...)]
@@ -1769,9 +1769,8 @@ module ActiveRecord
#
# Scope examples:
# has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) }
- # has_and_belongs_to_many :categories, ->(category) {
- # where("default_category = ?", category.name)
- # }
+ # has_and_belongs_to_many :categories, ->(post) {
+ # where("default_category = ?", post.default_category)
#
# === Extensions
#
diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb
index 3963008a76..104de4f69d 100644
--- a/activerecord/lib/active_record/associations/alias_tracker.rb
+++ b/activerecord/lib/active_record/associations/alias_tracker.rb
@@ -4,23 +4,21 @@ module ActiveRecord
module Associations
# Keeps track of table aliases for ActiveRecord::Associations::JoinDependency
class AliasTracker # :nodoc:
- attr_reader :aliases
-
- def self.create(connection, initial_table, type_caster)
+ def self.create(connection, initial_table)
aliases = Hash.new(0)
aliases[initial_table] = 1
- new connection, aliases, type_caster
+ new(connection, aliases)
end
- def self.create_with_joins(connection, initial_table, joins, type_caster)
+ def self.create_with_joins(connection, initial_table, joins)
if joins.empty?
- create(connection, initial_table, type_caster)
+ create(connection, initial_table)
else
aliases = Hash.new { |h, k|
h[k] = initial_count_for(connection, k, joins)
}
aliases[initial_table] = 1
- new connection, aliases, type_caster
+ new(connection, aliases)
end
end
@@ -53,17 +51,16 @@ module ActiveRecord
end
# table_joins is an array of arel joins which might conflict with the aliases we assign here
- def initialize(connection, aliases, type_caster)
+ def initialize(connection, aliases)
@aliases = aliases
@connection = connection
- @type_caster = type_caster
end
- def aliased_table_for(table_name, aliased_name)
+ def aliased_table_for(table_name, aliased_name, type_caster)
if aliases[table_name].zero?
# If it's zero, we can have our table_name
aliases[table_name] = 1
- Arel::Table.new(table_name, type_caster: @type_caster)
+ Arel::Table.new(table_name, type_caster: type_caster)
else
# Otherwise, we need to use an alias
aliased_name = @connection.table_alias_for(aliased_name)
@@ -76,10 +73,15 @@ module ActiveRecord
else
aliased_name
end
- Arel::Table.new(table_name, type_caster: @type_caster).alias(table_alias)
+ Arel::Table.new(table_name, type_caster: type_caster).alias(table_alias)
end
end
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
+ # Workaround for Ruby 2.2 "private attribute?" warning.
+ protected
+ attr_reader :aliases
+
private
def truncate(name)
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 1cb2b2d7c6..1138ae3462 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -30,14 +30,6 @@ module ActiveRecord
reset_scope
end
- # Returns the name of the table of the associated class:
- #
- # post.comments.aliased_table_name # => "comments"
- #
- def aliased_table_name
- klass.table_name
- end
-
# Resets the \loaded flag to +false+ and sets the \target to +nil+.
def reset
@loaded = false
@@ -94,7 +86,7 @@ module ActiveRecord
# actually gets built.
def association_scope
if klass
- @association_scope ||= AssociationScope.scope(self, klass.connection)
+ @association_scope ||= AssociationScope.scope(self)
end
end
@@ -133,6 +125,16 @@ module ActiveRecord
AssociationRelation.create(klass, klass.arel_table, klass.predicate_builder, self).merge!(klass.all)
end
+ def extensions
+ extensions = klass.default_extensions | reflection.extensions
+
+ if scope = reflection.scope
+ extensions |= klass.unscoped.instance_exec(owner, &scope).extensions
+ end
+
+ extensions
+ end
+
# Loads the \target if needed and returns it.
#
# This method is abstract in the sense that it relies on +find_target+,
@@ -152,14 +154,6 @@ module ActiveRecord
reset
end
- def interpolate(sql, record = nil)
- if sql.respond_to?(:to_proc)
- owner.instance_exec(record, &sql)
- else
- sql
- end
- end
-
# We can't dump @reflection since it contains the scope proc
def marshal_dump
ivars = (instance_variables - [:@reflection]).map { |name| [name, instance_variable_get(name)] }
@@ -274,7 +268,7 @@ module ActiveRecord
end
# Returns true if statement cache should be skipped on the association reader.
- def skip_statement_cache?
+ def skip_statement_cache?(scope)
reflection.has_scope? ||
scope.eager_loading? ||
klass.scope_attributes? ||
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 120d75416c..6ef225b725 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -1,8 +1,8 @@
module ActiveRecord
module Associations
class AssociationScope #:nodoc:
- def self.scope(association, connection)
- INSTANCE.scope(association, connection)
+ def self.scope(association)
+ INSTANCE.scope(association)
end
def self.create(&block)
@@ -16,15 +16,15 @@ module ActiveRecord
INSTANCE = create
- def scope(association, connection)
+ def scope(association)
klass = association.klass
reflection = association.reflection
scope = klass.unscoped
owner = association.owner
- alias_tracker = AliasTracker.create connection, association.klass.table_name, klass.type_caster
+ alias_tracker = AliasTracker.create(klass.connection, klass.table_name)
chain_head, chain_tail = get_chain(reflection, association, alias_tracker)
- scope.extending! Array(reflection.options[:extend])
+ scope.extending! reflection.extensions
add_constraints(scope, owner, reflection, chain_head, chain_tail)
end
@@ -112,7 +112,11 @@ module ActiveRecord
runtime_reflection = Reflection::RuntimeReflection.new(reflection, association)
previous_reflection = runtime_reflection
reflection.chain.drop(1).each do |refl|
- alias_name = tracker.aliased_table_for(refl.table_name, refl.alias_candidate(name))
+ alias_name = tracker.aliased_table_for(
+ refl.table_name,
+ refl.alias_candidate(name),
+ refl.klass.type_caster
+ )
proxy = ReflectionProxy.new(refl, alias_name)
previous_reflection.next = proxy
previous_reflection = proxy
@@ -138,7 +142,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, table, scope_chain_item, owner)
+ item = eval_scope(reflection, table, scope_chain_item, owner)
if scope_chain_item == refl.scope
scope.merge! item.except(:where, :includes)
@@ -159,9 +163,8 @@ module ActiveRecord
scope
end
- 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)
+ def eval_scope(reflection, table, scope, owner)
+ reflection.build_scope(table).instance_exec(owner, &scope)
end
end
end
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index a2432e389a..0e61dbfb00 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -22,7 +22,7 @@ module ActiveRecord
end
def default(&block)
- writer(instance_exec(&block)) if reader.nil?
+ writer(owner.instance_exec(&block)) if reader.nil?
end
def reset
diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb
index edeb6491bd..c58b7d8160 100644
--- a/activerecord/lib/active_record/associations/builder/collection_association.rb
+++ b/activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -1,6 +1,4 @@
-# This class is inherited by the has_many and has_many_and_belongs_to_many association classes
-
-require "active_record/associations"
+require_relative "../../associations"
module ActiveRecord::Associations::Builder # :nodoc:
class CollectionAssociation < Association #:nodoc:
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 77282e6463..a49fb155ee 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -30,7 +30,8 @@ module ActiveRecord
reload
end
- CollectionProxy.create(klass, self)
+ @proxy ||= CollectionProxy.create(klass, self)
+ @proxy.reset_scope
end
# Implements the writer method, e.g. foo.items= for Foo.has_many :items
@@ -43,10 +44,7 @@ module ActiveRecord
if loaded?
target.pluck(reflection.association_primary_key)
else
- @association_ids ||= (
- column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
- scope.pluck(column)
- )
+ @association_ids ||= scope.pluck(reflection.association_primary_key)
end
end
@@ -68,6 +66,7 @@ module ActiveRecord
def reset
super
@target = []
+ @association_ids = nil
end
def find(*args)
@@ -280,35 +279,6 @@ module ActiveRecord
replace_on_target(record, index, skip_callbacks, &block)
end
- def replace_on_target(record, index, skip_callbacks)
- callback(:before_add, record) unless skip_callbacks
-
- begin
- if index
- record_was = target[index]
- target[index] = record
- else
- target << record
- end
-
- set_inverse_instance(record)
-
- yield(record) if block_given?
- rescue
- if index
- target[index] = record_was
- else
- target.delete(record)
- end
-
- raise
- end
-
- callback(:after_add, record) unless skip_callbacks
-
- record
- end
-
def scope
scope = super
scope.none! if null_scope?
@@ -328,13 +298,14 @@ module ActiveRecord
private
def find_target
- return scope.to_a if skip_statement_cache?
+ scope = self.scope
+ return scope.to_a if skip_statement_cache?(scope)
conn = klass.connection
sc = reflection.association_scope_cache(conn, owner) do
StatementCache.create(conn) { |params|
as = AssociationScope.create { params.bind }
- target_scope.merge as.scope(self, conn)
+ target_scope.merge!(as.scope(self))
}
end
@@ -385,15 +356,22 @@ module ActiveRecord
transaction do
add_to_target(build_record(attributes)) do |record|
yield(record) if block_given?
- insert_record(record, true, raise)
+ insert_record(record, true, raise) {
+ @_was_loaded = loaded?
+ @association_ids = nil
+ }
end
end
end
end
# Do the relevant stuff to insert the given record into the association collection.
- def insert_record(record, validate = true, raise = false)
- raise NotImplementedError
+ def insert_record(record, validate = true, raise = false, &block)
+ if raise
+ record.save!(validate: validate, &block)
+ else
+ record.save(validate: validate, &block)
+ end
end
def create_scope
@@ -448,19 +426,46 @@ module ActiveRecord
end
end
- def concat_records(records, should_raise = false)
+ def concat_records(records, raise = false)
result = true
records.each do |record|
raise_on_type_mismatch!(record)
- add_to_target(record) do |rec|
- result &&= insert_record(rec, true, should_raise) unless owner.new_record?
+ add_to_target(record) do
+ unless owner.new_record?
+ result &&= insert_record(record, true, raise) {
+ @_was_loaded = loaded?
+ @association_ids = nil
+ }
+ end
end
end
result && records
end
+ def replace_on_target(record, index, skip_callbacks)
+ callback(:before_add, record) unless skip_callbacks
+
+ set_inverse_instance(record)
+
+ @_was_loaded = true
+
+ yield(record) if block_given?
+
+ if index
+ target[index] = record
+ elsif @_was_loaded || !loaded?
+ target << record
+ end
+
+ callback(:after_add, record) unless skip_callbacks
+
+ record
+ ensure
+ @_was_loaded = nil
+ end
+
def callback(method, record)
callbacks_for(method).each do |callback|
callback.call(method, owner, record)
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 5d6676f0df..d77fcaf668 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -31,6 +31,9 @@ module ActiveRecord
def initialize(klass, association) #:nodoc:
@association = association
super klass, klass.arel_table, klass.predicate_builder
+
+ extensions = association.extensions
+ extend(*extensions) if extensions.any?
end
def target
@@ -1084,9 +1087,8 @@ 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
+ reset_scope
end
# Unloads the association. Returns +self+.
@@ -1106,9 +1108,14 @@ 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
+ reset_scope
+ end
+
+ def reset_scope # :nodoc:
+ @offsets = {}
+ @scope = nil
self
end
@@ -1144,19 +1151,6 @@ 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.extending_values.any?
- extend(*scope.extending_values)
- 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 b413eb3f9c..10ca0e47ff 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -31,12 +31,7 @@ module ActiveRecord
def insert_record(record, validate = true, raise = false)
set_owner_attributes(record)
-
- if raise
- record.save!(validate: validate)
- else
- record.save(validate: validate)
- end
+ super
end
def empty?
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index c4a7fe4432..2fd20b4368 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -39,11 +39,7 @@ module ActiveRecord
ensure_not_nested
if record.new_record? || record.has_changes_to_save?
- if raise
- record.save!(validate: validate)
- else
- return unless record.save(validate: validate)
- end
+ return unless super
end
save_through_record(record)
@@ -113,6 +109,11 @@ module ActiveRecord
record
end
+ def remove_records(existing_records, records, method)
+ super
+ delete_through_records(records)
+ end
+
def target_reflection_has_associated_record?
!(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?)
end
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index 8995b1e352..04cdcb6a7f 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -93,7 +93,7 @@ module ActiveRecord
# joins # => []
#
def initialize(base, associations, joins, eager_loading: true)
- @alias_tracker = AliasTracker.create_with_joins(base.connection, base.table_name, joins, base.type_caster)
+ @alias_tracker = AliasTracker.create_with_joins(base.connection, base.table_name, joins)
@eager_loading = eager_loading
tree = self.class.make_tree associations
@join_root = JoinBase.new base, build(tree, base)
@@ -104,22 +104,17 @@ module ActiveRecord
join_root.drop(1).map!(&:reflection)
end
- def join_constraints(outer_joins, join_type)
+ def join_constraints(joins_to_add, join_type)
joins = join_root.children.flat_map { |child|
-
- if join_type == Arel::Nodes::OuterJoin
- make_left_outer_joins join_root, child
- else
- make_inner_joins join_root, child
- end
+ make_join_constraints(join_root, child, join_type)
}
- joins.concat outer_joins.flat_map { |oj|
+ joins.concat joins_to_add.flat_map { |oj|
if join_root.match? oj.join_root
walk join_root, oj.join_root
else
oj.join_root.children.flat_map { |child|
- make_outer_joins oj.join_root, child
+ make_join_constraints(oj.join_root, child, join_type)
}
end
}
@@ -175,34 +170,23 @@ module ActiveRecord
end
def make_outer_joins(parent, child)
- tables = table_aliases_for(parent, child)
- join_type = Arel::Nodes::OuterJoin
- info = make_constraints parent, child, tables, join_type
-
- [info] + child.children.flat_map { |c| make_outer_joins(child, c) }
- end
-
- def make_left_outer_joins(parent, child)
- tables = child.tables
join_type = Arel::Nodes::OuterJoin
- info = make_constraints parent, child, tables, join_type
-
- [info] + child.children.flat_map { |c| make_left_outer_joins(child, c) }
+ make_join_constraints(parent, child, join_type, true)
end
- def make_inner_joins(parent, child)
- tables = child.tables
- join_type = Arel::Nodes::InnerJoin
- info = make_constraints parent, child, tables, join_type
+ def make_join_constraints(parent, child, join_type, aliasing = false)
+ tables = aliasing ? table_aliases_for(parent, child) : child.tables
+ info = make_constraints(parent, child, tables, join_type)
- [info] + child.children.flat_map { |c| make_inner_joins(child, c) }
+ [info] + child.children.flat_map { |c| make_join_constraints(child, c, join_type, aliasing) }
end
def table_aliases_for(parent, node)
node.reflection.chain.map { |reflection|
alias_tracker.aliased_table_for(
reflection.table_name,
- table_alias_for(reflection, parent, reflection != node.reflection)
+ table_alias_for(reflection, parent, reflection != node.reflection),
+ reflection.klass.type_caster
)
}
end
@@ -214,8 +198,7 @@ module ActiveRecord
def table_alias_for(reflection, parent, join)
name = "#{reflection.plural_name}_#{parent.table_name}"
- name << "_join" if join
- name
+ join ? "#{name}_join" : name
end
def walk(left, right)
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 97cfec0302..b14ddfeeeb 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -1,4 +1,4 @@
-require "active_record/associations/join_dependency/join_part"
+require_relative "join_part"
module ActiveRecord
module Associations
@@ -34,37 +34,19 @@ module ActiveRecord
table = tables.shift
klass = reflection.klass
- join_keys = reflection.join_keys
- key = join_keys.key
- foreign_key = join_keys.foreign_key
+ constraint = reflection.build_join_constraint(table, foreign_table)
- constraint = build_constraint(klass, table, key, foreign_table, foreign_key)
-
- predicate_builder = PredicateBuilder.new(TableMetadata.new(klass, table))
- scope_chain_items = reflection.join_scopes(table, predicate_builder)
- klass_scope = reflection.klass_join_scope(table, predicate_builder)
-
- scope_chain_items.concat [klass_scope].compact
-
- rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right|
- left.merge right
- end
-
- if rel && !rel.arel.constraints.empty?
- binds += rel.bound_attributes
- constraint = constraint.and rel.arel.constraints
- end
+ joins << table.create_join(table, table.create_on(constraint), join_type)
- if reflection.type
- value = foreign_klass.base_class.name
- column = klass.columns_hash[reflection.type.to_s]
+ join_scope = reflection.join_scope(table, foreign_klass)
- binds << Relation::QueryAttribute.new(column.name, value, klass.type_for_attribute(column.name))
- constraint = constraint.and klass.arel_attribute(reflection.type, table).eq(Arel::Nodes::BindParam.new)
+ if join_scope.arel.constraints.any?
+ binds.concat join_scope.bound_attributes
+ joins.concat join_scope.arel.join_sources
+ right = joins.last.right
+ right.expr = right.expr.and(join_scope.arel.constraints)
end
- joins << table.create_join(table, table.create_on(constraint), join_type)
-
# The current table in this iteration becomes the foreign table in the next
foreign_table, foreign_klass = table, klass
end
@@ -72,34 +54,6 @@ module ActiveRecord
JoinInformation.new joins, binds
end
- # Builds equality condition.
- #
- # Example:
- #
- # class Physician < ActiveRecord::Base
- # has_many :appointments
- # end
- #
- # If I execute `Physician.joins(:appointments).to_a` then
- # klass # => Physician
- # table # => #<Arel::Table @name="appointments" ...>
- # key # => physician_id
- # foreign_table # => #<Arel::Table @name="physicians" ...>
- # foreign_key # => id
- #
- def build_constraint(klass, table, key, foreign_table, foreign_key)
- constraint = table[key].eq(foreign_table[foreign_key])
-
- if klass.finder_needs_type_condition?
- constraint = table.create_and([
- constraint,
- klass.send(:type_condition, table)
- ])
- end
-
- constraint
- end
-
def table
tables.first
end
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_base.rb b/activerecord/lib/active_record/associations/join_dependency/join_base.rb
index fca20514d1..6e0963425d 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_base.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_base.rb
@@ -1,4 +1,4 @@
-require "active_record/associations/join_dependency/join_part"
+require_relative "join_part"
module ActiveRecord
module Associations
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_part.rb b/activerecord/lib/active_record/associations/join_dependency/join_part.rb
index 61cec5403a..80c9fde5d1 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_part.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_part.rb
@@ -22,10 +22,6 @@ module ActiveRecord
@children = children
end
- def name
- reflection.name
- end
-
def match?(other)
self.class == other.class
end
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index 9f77f38b35..a18994cec4 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -54,8 +54,6 @@ module ActiveRecord
autoload :BelongsTo, "active_record/associations/preloader/belongs_to"
end
- NULL_RELATION = Struct.new(:values, :where_clause, :joins_values).new({}, Relation::WhereClause.empty, [])
-
# Eager loads the named associations for the given Active Record record(s).
#
# In this description, 'association name' shall refer to the name passed
@@ -93,7 +91,6 @@ module ActiveRecord
def preload(records, associations, preload_scope = nil)
records = Array.wrap(records).compact.uniq
associations = Array.wrap(associations)
- preload_scope = preload_scope || NULL_RELATION
if records.empty?
[]
@@ -147,7 +144,7 @@ module ActiveRecord
def preloaders_for_one(association, records, scope)
grouped_records(association, records).flat_map do |reflection, klasses|
klasses.map do |rhs_klass, rs|
- loader = preloader_for(reflection, rs, rhs_klass).new(rhs_klass, rs, reflection, scope)
+ loader = preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope)
loader.run self
loader
end
@@ -159,6 +156,7 @@ module ActiveRecord
records.each do |record|
next unless record
assoc = record.association(association)
+ next unless assoc.klass
klasses = h[assoc.reflection] ||= {}
(klasses[assoc.klass] ||= []) << record
end
@@ -180,20 +178,11 @@ module ActiveRecord
end
end
- class NullPreloader # :nodoc:
- def self.new(klass, owners, reflection, preload_scope); self; end
- def self.run(preloader); end
- def self.preloaded_records; []; end
- def self.owners; []; end
- end
-
# Returns a class containing the logic needed to load preload the data
# and attach it to a relation. For example +Preloader::Association+ or
# +Preloader::HasManyThrough+. The class returned implements a `run` method
# that accepts a preloader.
- def preloader_for(reflection, owners, rhs_klass)
- return NullPreloader unless rhs_klass
-
+ def preloader_for(reflection, owners)
if owners.first.association(reflection.name).loaded?
return AlreadyLoaded
end
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index 4072d19380..85343040db 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -11,7 +11,6 @@ module ActiveRecord
@reflection = reflection
@preload_scope = preload_scope
@model = owners.first && owners.first.class
- @scope = nil
@preloaded_records = []
end
@@ -23,39 +22,20 @@ module ActiveRecord
raise NotImplementedError
end
- def scope
- @scope ||= build_scope
- end
-
- def records_for(ids)
- scope.where(association_key_name => ids)
- end
-
- def table
- klass.arel_table
- end
-
# The name of the key on the associated records
def association_key_name
raise NotImplementedError
end
- # This is overridden by HABTM as the condition should be on the foreign_key column in
- # the join table
- def association_key
- klass.arel_attribute(association_key_name, table)
- end
-
# The name of the key on the model which declares the association
def owner_key_name
raise NotImplementedError
end
- def options
- reflection.options
- end
-
private
+ def options
+ reflection.options
+ end
def associated_records_by_owner(preloader)
records = load_records do |record|
@@ -115,54 +95,35 @@ module ActiveRecord
# Make several smaller queries if necessary or make one query if the adapter supports it
slices = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
@preloaded_records = slices.flat_map do |slice|
- records_for(slice).load(&block)
+ records_for(slice, &block)
end
@preloaded_records.group_by do |record|
convert_key(record[association_key_name])
end
end
+ def records_for(ids, &block)
+ scope.where(association_key_name => ids).load(&block)
+ end
+
+ def scope
+ @scope ||= build_scope
+ end
+
def reflection_scope
@reflection_scope ||= reflection.scope_for(klass)
end
def build_scope
- scope = klass.unscoped
-
- values = reflection_scope.values
- preload_values = preload_scope.values
-
- scope.where_clause = reflection_scope.where_clause + preload_scope.where_clause
- scope.references_values = Array(values[:references]) + Array(preload_values[:references])
-
- if preload_values[:select] || values[:select]
- scope._select!(preload_values[:select] || values[:select])
- end
- scope.includes! preload_values[:includes] || values[:includes]
- if preload_scope.joins_values.any?
- scope.joins!(preload_scope.joins_values)
- else
- scope.joins!(reflection_scope.joins_values)
- end
-
- if order_values = preload_values[:order] || values[:order]
- scope.order!(order_values)
- end
-
- if preload_values[:reordering] || values[:reordering]
- scope.reordering_value = true
- end
-
- if preload_values[:readonly] || values[:readonly]
- scope.readonly!
- end
+ scope = klass.default_scoped
- if options[:as]
- scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
+ if reflection.type
+ scope.where!(reflection.type => model.base_class.sti_name)
end
- scope.unscope_values = Array(values[:unscope]) + Array(preload_values[:unscope])
- klass.default_scoped.merge(scope)
+ scope.merge!(reflection_scope)
+ scope.merge!(preload_scope) if preload_scope
+ scope
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/belongs_to.rb b/activerecord/lib/active_record/associations/preloader/belongs_to.rb
index 38e231826c..c20145770f 100644
--- a/activerecord/lib/active_record/associations/preloader/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/preloader/belongs_to.rb
@@ -3,7 +3,7 @@ module ActiveRecord
class Preloader
class BelongsTo < SingularAssociation #:nodoc:
def association_key_name
- reflection.options[:primary_key] || klass && klass.primary_key
+ options[:primary_key] || klass && klass.primary_key
end
def owner_key_name
diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb
index 9d44a02021..0999746cd5 100644
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
@@ -11,20 +11,20 @@ module ActiveRecord
end
def associated_records_by_owner(preloader)
+ through_scope = through_scope()
+
preloader.preload(owners,
through_reflection.name,
through_scope)
through_records = owners.map do |owner|
- association = owner.association through_reflection.name
-
- center = target_records_from_association(association)
+ center = owner.association(through_reflection.name).target
[owner, Array(center)]
end
- reset_association owners, through_reflection.name
+ reset_association(owners, through_reflection.name, through_scope)
- middle_records = through_records.flat_map { |(_, rec)| rec }
+ middle_records = through_records.flat_map(&:last)
preloaders = preloader.preload(middle_records,
source_reflection.name,
@@ -43,9 +43,7 @@ module ActiveRecord
records_by_owner[lhs] = pl_to_middle.flat_map do |pl, middles|
rhs_records = middles.flat_map { |r|
- association = r.association source_reflection.name
-
- target_records_from_association(association)
+ r.association(source_reflection.name).target
}.compact
# Respect the order on `reflection_scope` if it exists, else use the natural order.
@@ -67,9 +65,9 @@ module ActiveRecord
id_map
end
- def reset_association(owners, association_name)
+ def reset_association(owners, association_name, through_scope)
should_reset = (through_scope != through_reflection.klass.unscoped) ||
- (reflection.options[:source_type] && through_reflection.collection?)
+ (options[:source_type] && through_reflection.collection?)
# Don't cache the association - we would only be caching a subset
if should_reset
@@ -81,27 +79,30 @@ module ActiveRecord
def through_scope
scope = through_reflection.klass.unscoped
+ values = reflection_scope.values
if options[:source_type]
scope.where! reflection.foreign_type => options[:source_type]
else
unless reflection_scope.where_clause.empty?
- scope.includes_values = Array(reflection_scope.values[:includes] || options[:source])
+ scope.includes_values = Array(values[:includes] || options[:source])
scope.where_clause = reflection_scope.where_clause
+ if joins = values[:joins]
+ scope.joins!(source_reflection.name => joins)
+ end
+ if left_outer_joins = values[:left_outer_joins]
+ scope.left_outer_joins!(source_reflection.name => left_outer_joins)
+ end
end
- scope.references! reflection_scope.values[:references]
- if scope.eager_loading? && order_values = reflection_scope.values[:order]
+ scope.references! values[:references]
+ if scope.eager_loading? && order_values = values[:order]
scope = scope.order(order_values)
end
end
scope
end
-
- def target_records_from_association(association)
- association.loaded? ? association.target : association.reader
- end
end
end
end
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index 91580a28d0..f8bbe4c2ed 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -36,13 +36,14 @@ module ActiveRecord
end
def find_target
- return scope.take if skip_statement_cache?
+ scope = self.scope
+ return scope.take if skip_statement_cache?(scope)
conn = klass.connection
sc = reflection.association_scope_cache(conn, owner) do
StatementCache.create(conn) { |params|
as = AssociationScope.create { params.bind }
- target_scope.merge(as.scope(self, conn)).limit(1)
+ target_scope.merge!(as.scope(self)).limit(1)
}
end
@@ -63,6 +64,10 @@ module ActiveRecord
end
def _create_record(attributes, raise_error = false)
+ unless owner.persisted?
+ raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
+ end
+
record = build_record(attributes)
yield(record) if block_given?
saved = record.save
diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb
index 38281158d8..78662433eb 100644
--- a/activerecord/lib/active_record/attribute.rb
+++ b/activerecord/lib/active_record/attribute.rb
@@ -122,7 +122,7 @@ module ActiveRecord
def encode_with(coder)
coder["name"] = name
- coder["value_before_type_cast"] = value_before_type_cast if value_before_type_cast
+ coder["value_before_type_cast"] = value_before_type_cast unless value_before_type_cast.nil?
coder["type"] = type if type
coder["original_attribute"] = original_attribute if original_attribute
coder["value"] = value if defined?(@value)
diff --git a/activerecord/lib/active_record/attribute/user_provided_default.rb b/activerecord/lib/active_record/attribute/user_provided_default.rb
index 57f8bbed76..c4e731fb28 100644
--- a/activerecord/lib/active_record/attribute/user_provided_default.rb
+++ b/activerecord/lib/active_record/attribute/user_provided_default.rb
@@ -1,4 +1,4 @@
-require "active_record/attribute"
+require_relative "../attribute"
module ActiveRecord
class Attribute # :nodoc:
diff --git a/activerecord/lib/active_record/attribute_decorators.rb b/activerecord/lib/active_record/attribute_decorators.rb
index c39e9ce4c5..5bc8527745 100644
--- a/activerecord/lib/active_record/attribute_decorators.rb
+++ b/activerecord/lib/active_record/attribute_decorators.rb
@@ -3,8 +3,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- class_attribute :attribute_type_decorations, instance_accessor: false # :internal:
- self.attribute_type_decorations = TypeDecorator.new
+ class_attribute :attribute_type_decorations, instance_accessor: false, default: TypeDecorator.new # :internal:
end
module ClassMethods # :nodoc:
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index ebe06566cc..83c61fad19 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -62,7 +62,6 @@ module ActiveRecord
super(attribute_names)
@attribute_methods_generated = true
end
- true
end
def undefine_attribute_methods # :nodoc:
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index bd5003d63a..948249a6fd 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
require "active_support/core_ext/module/attribute_accessors"
-require "active_record/attribute_mutation_tracker"
+require_relative "../attribute_mutation_tracker"
module ActiveRecord
module AttributeMethods
@@ -14,8 +14,7 @@ module ActiveRecord
raise "You cannot include Dirty after Timestamp"
end
- class_attribute :partial_writes, instance_writer: false
- self.partial_writes = true
+ class_attribute :partial_writes, instance_writer: false, default: true
after_create { changes_internally_applied }
after_update { changes_internally_applied }
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index 2f32caa257..b9b2acff37 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -8,17 +8,14 @@ module ActiveRecord
# Returns this record's primary key value wrapped in an array if one is
# available.
def to_key
- sync_with_transaction_state
key = id
[key] if key
end
# Returns the primary key value.
def id
- if pk = self.class.primary_key
- sync_with_transaction_state
- _read_attribute(pk)
- end
+ sync_with_transaction_state
+ _read_attribute(self.class.primary_key) if self.class.primary_key
end
# Sets the primary key value.
@@ -57,16 +54,12 @@ module ActiveRecord
end
module ClassMethods
- def define_method_attribute(attr_name)
- super
+ ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was id_in_database).to_set
- if attr_name == primary_key && attr_name != "id"
- generated_attribute_methods.send(:alias_method, :id, primary_key)
- end
+ def instance_method_already_implemented?(method_name)
+ super || primary_key && ID_ATTRIBUTE_METHODS.include?(method_name)
end
- ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was id_in_database).to_set
-
def dangerous_attribute_method?(method_name)
super && !ID_ATTRIBUTE_METHODS.include?(method_name)
end
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 321d039ed4..1f1efe8812 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -54,14 +54,10 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- mattr_accessor :time_zone_aware_attributes, instance_writer: false
- self.time_zone_aware_attributes = false
+ mattr_accessor :time_zone_aware_attributes, instance_writer: false, default: false
- class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false
- self.skip_time_zone_conversion_for_attributes = []
-
- class_attribute :time_zone_aware_types, instance_writer: false
- self.time_zone_aware_types = [:datetime, :time]
+ class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false, default: []
+ class_attribute :time_zone_aware_types, instance_writer: false, default: [ :datetime, :time ]
end
module ClassMethods
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index fe0e01db28..75c5a1a600 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -35,11 +35,15 @@ module ActiveRecord
attr_name.to_s
end
- write_attribute_with_type_cast(name, value, true)
+ name = self.class.primary_key if name == "id".freeze && self.class.primary_key
+ @attributes.write_from_user(name, value)
+ value
end
def raw_write_attribute(attr_name, value) # :nodoc:
- write_attribute_with_type_cast(attr_name, value, false)
+ name = attr_name.to_s
+ @attributes.write_cast_value(name, value)
+ value
end
private
@@ -47,19 +51,6 @@ module ActiveRecord
def attribute=(attribute_name, value)
write_attribute(attribute_name, value)
end
-
- def write_attribute_with_type_cast(attr_name, value, should_type_cast)
- attr_name = attr_name.to_s
- attr_name = self.class.primary_key if attr_name == "id" && self.class.primary_key
-
- if should_type_cast
- @attributes.write_from_user(attr_name, value)
- else
- @attributes.write_cast_value(attr_name, value)
- end
-
- value
- end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_mutation_tracker.rb b/activerecord/lib/active_record/attribute_mutation_tracker.rb
index 4de993e169..a01a58f8a5 100644
--- a/activerecord/lib/active_record/attribute_mutation_tracker.rb
+++ b/activerecord/lib/active_record/attribute_mutation_tracker.rb
@@ -26,6 +26,7 @@ module ActiveRecord
end
def change_to_attribute(attr_name)
+ attr_name = attr_name.to_s
if changed?(attr_name)
[attributes[attr_name].original_value, attributes.fetch_value(attr_name)]
end
@@ -44,7 +45,7 @@ module ActiveRecord
end
def changed_in_place?(attr_name)
- attributes[attr_name].changed_in_place?
+ attributes[attr_name.to_s].changed_in_place?
end
def forget_change(attr_name)
@@ -54,7 +55,7 @@ module ActiveRecord
end
def original_value(attr_name)
- attributes[attr_name].original_value
+ attributes[attr_name.to_s].original_value
end
def force_change(attr_name)
diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb
index 66b278219a..6399e3de70 100644
--- a/activerecord/lib/active_record/attribute_set.rb
+++ b/activerecord/lib/active_record/attribute_set.rb
@@ -1,5 +1,5 @@
-require "active_record/attribute_set/builder"
-require "active_record/attribute_set/yaml_encoder"
+require_relative "attribute_set/builder"
+require_relative "attribute_set/yaml_encoder"
module ActiveRecord
class AttributeSet # :nodoc:
@@ -64,7 +64,7 @@ module ActiveRecord
end
def deep_dup
- dup.tap do |copy|
+ self.class.allocate.tap do |copy|
copy.instance_variable_set(:@attributes, attributes.deep_dup)
end
end
diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb
index 2f624d32af..abe22b9ae4 100644
--- a/activerecord/lib/active_record/attribute_set/builder.rb
+++ b/activerecord/lib/active_record/attribute_set/builder.rb
@@ -1,4 +1,4 @@
-require "active_record/attribute"
+require_relative "../attribute"
module ActiveRecord
class AttributeSet # :nodoc:
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
index 75f5ba3a96..dde22bcdaa 100644
--- a/activerecord/lib/active_record/attributes.rb
+++ b/activerecord/lib/active_record/attributes.rb
@@ -1,4 +1,4 @@
-require "active_record/attribute/user_provided_default"
+require_relative "attribute/user_provided_default"
module ActiveRecord
# See ActiveRecord::Attributes::ClassMethods for documentation
@@ -6,8 +6,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false # :internal:
- self.attributes_to_define_after_schema_loads = {}
+ class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false, default: {} # :internal:
end
module ClassMethods
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 6bccbc06cd..70f0e2af8e 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -140,8 +140,7 @@ module ActiveRecord
included do
Associations::Builder::Association.extensions << AssociationBuilderExtension
- mattr_accessor :index_nested_attribute_errors, instance_writer: false
- self.index_nested_attribute_errors = false
+ mattr_accessor :index_nested_attribute_errors, instance_writer: false, default: false
end
module ClassMethods # :nodoc:
@@ -181,6 +180,7 @@ module ActiveRecord
if reflection.collection?
before_save :before_save_collection_association
+ after_save :after_save_collection_association
define_non_cyclic_method(save_method) { save_collection_association(reflection) }
# Doesn't use after_save as that would save associations added in after_create/after_update twice
@@ -215,13 +215,7 @@ module ActiveRecord
method = :validate_single_association
end
- define_non_cyclic_method(validation_method) do
- send(method, reflection)
- # TODO: remove the following line as soon as the return value of
- # callbacks is ignored, that is, returning `false` does not
- # display a deprecation warning or halts the callback chain.
- true
- end
+ define_non_cyclic_method(validation_method) { send(method, reflection) }
validate validation_method
after_validation :_ensure_no_duplicate_errors
end
@@ -368,7 +362,10 @@ module ActiveRecord
# association whether or not the parent was a new record before saving.
def before_save_collection_association
@new_record_before_save = new_record?
- true
+ end
+
+ def after_save_collection_association
+ @new_record_before_save = false
end
# Saves any new associated records, or all loaded autosave associations if
@@ -384,7 +381,7 @@ module ActiveRecord
autosave = reflection.options[:autosave]
# reconstruct the scope now that we know the owner's id
- association.reset_scope if association.respond_to?(:reset_scope)
+ association.reset_scope
if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
if autosave
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index ac1aa2df45..f0e455478a 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -13,14 +13,14 @@ require "active_support/core_ext/kernel/singleton_class"
require "active_support/core_ext/module/introspection"
require "active_support/core_ext/object/duplicable"
require "active_support/core_ext/class/subclasses"
-require "active_record/attribute_decorators"
-require "active_record/define_callbacks"
-require "active_record/errors"
-require "active_record/log_subscriber"
-require "active_record/explain_subscriber"
-require "active_record/relation/delegation"
-require "active_record/attributes"
-require "active_record/type_caster"
+require_relative "attribute_decorators"
+require_relative "define_callbacks"
+require_relative "errors"
+require_relative "log_subscriber"
+require_relative "explain_subscriber"
+require_relative "relation/delegation"
+require_relative "attributes"
+require_relative "type_caster"
module ActiveRecord #:nodoc:
# = Active Record
diff --git a/activerecord/lib/active_record/collection_cache_key.rb b/activerecord/lib/active_record/collection_cache_key.rb
index 43784b70e3..8b937b6703 100644
--- a/activerecord/lib/active_record/collection_cache_key.rb
+++ b/activerecord/lib/active_record/collection_cache_key.rb
@@ -7,17 +7,27 @@ module ActiveRecord
if collection.loaded?
size = collection.size
if size > 0
- timestamp = collection.max_by(&timestamp_column).public_send(timestamp_column)
+ timestamp = collection.max_by(&timestamp_column)._read_attribute(timestamp_column)
end
else
column_type = type_for_attribute(timestamp_column.to_s)
column = "#{connection.quote_table_name(collection.table_name)}.#{connection.quote_column_name(timestamp_column)}"
+ select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp"
- query = collection
- .unscope(:select)
- .select("COUNT(*) AS #{connection.quote_column_name("size")}", "MAX(#{column}) AS timestamp")
- .unscope(:order)
- result = connection.select_one(query)
+ if collection.limit_value || collection.offset_value
+ query = collection.spawn
+ query.select_values = [column]
+ subquery_alias = "subquery_for_cache_key"
+ subquery_column = "#{subquery_alias}.#{timestamp_column}"
+ subquery = query.arel.as(subquery_alias)
+ arel = Arel::SelectManager.new(subquery).project(select_values % subquery_column)
+ else
+ query = collection.unscope(:order)
+ query.select_values = [select_values % column]
+ arel = query.arel
+ end
+
+ result = connection.select_one(arel, nil, query.bound_attributes)
if result.blank?
size = 0
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 53dbbd8c21..627b753f01 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -506,14 +506,16 @@ module ActiveRecord
# +conn+: an AbstractAdapter object, which was obtained by earlier by
# calling #checkout on this pool.
def checkin(conn)
- synchronize do
- remove_connection_from_thread_cache conn
+ conn.lock.synchronize do
+ synchronize do
+ remove_connection_from_thread_cache conn
- conn._run_checkin_callbacks do
- conn.expire
- end
+ conn._run_checkin_callbacks do
+ conn.expire
+ end
- @available.add conn
+ @available.add conn
+ end
end
end
@@ -677,7 +679,7 @@ module ActiveRecord
# this block can't be easily moved into attempt_to_checkout_all_existing_connections's
# rescue block, because doing so would put it outside of synchronize section, without
# being in a critical section thread_report might become inaccurate
- msg = "could not obtain ownership of all database connections in #{checkout_timeout} seconds"
+ msg = "could not obtain ownership of all database connections in #{checkout_timeout} seconds".dup
thread_report = []
@connections.each do |conn|
@@ -857,9 +859,9 @@ module ActiveRecord
# All Active Record models use this handler to determine the connection pool that they
# should use.
#
- # The ConnectionHandler class is not coupled with the Active models, as it has no knowlodge
+ # The ConnectionHandler class is not coupled with the Active models, as it has no knowledge
# about the model. The model needs to pass a specification name to the handler,
- # in order to lookup the correct connection pool.
+ # in order to look up the correct connection pool.
class ConnectionHandler
def initialize
# These caches are keyed by spec.name (ConnectionSpecification#name).
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 769f488469..879626b72a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -51,9 +51,7 @@ module ActiveRecord
# Returns a single value from a record
def select_value(arel, name = nil, binds = [])
- if result = select_rows(arel, name, binds).first
- result.first
- end
+ single_value_from_rows(select_rows(arel, name, binds))
end
# Returns an array of the values of the first column in a select:
@@ -68,6 +66,18 @@ module ActiveRecord
select_all(arel, name, binds).rows
end
+ def query_value(sql, name = nil) # :nodoc:
+ single_value_from_rows(query(sql, name))
+ end
+
+ def query_values(sql, name = nil) # :nodoc:
+ query(sql, name).map(&:first)
+ end
+
+ def query(sql, name = nil) # :nodoc:
+ exec_query(sql, name).rows
+ end
+
# Executes the SQL statement in the context of this connection and returns
# the raw result from the connection adapter.
# Note: depending on your database connector, the result returned by this
@@ -137,9 +147,10 @@ module ActiveRecord
# Returns +true+ when the connection adapter supports prepared statement
# caching, otherwise returns +false+
- def supports_statement_cache?
- false
+ def supports_statement_cache? # :nodoc:
+ true
end
+ deprecate :supports_statement_cache?
# Runs the given block in a database transaction, and returns the result
# of the block.
@@ -295,6 +306,9 @@ module ActiveRecord
# Inserts the given fixture into the table. Overridden in adapters that require
# something beyond a simple insert (eg. Oracle).
+ # Most of adapters should implement `insert_fixtures` that leverages bulk SQL insert.
+ # We keep this method to provide fallback
+ # for databases like sqlite that do not support bulk inserts.
def insert_fixture(fixture, table_name)
fixture = fixture.stringify_keys
@@ -307,16 +321,52 @@ module ActiveRecord
raise Fixture::FixtureError, %(table "#{table_name}" has no column named #{name.inspect}.)
end
end
- key_list = fixture.keys.map { |name| quote_column_name(name) }
- value_list = binds.map(&:value_for_database).map do |value|
- begin
- quote(value)
- rescue TypeError
- quote(YAML.dump(value))
+
+ table = Arel::Table.new(table_name)
+
+ values = binds.map do |bind|
+ value = with_yaml_fallback(bind.value_for_database)
+ [table[bind.name], value]
+ end
+
+ manager = Arel::InsertManager.new
+ manager.into(table)
+ manager.insert(values)
+ execute manager.to_sql, "Fixture Insert"
+ end
+
+ # Inserts a set of fixtures into the table. Overridden in adapters that require
+ # something beyond a simple insert (eg. Oracle).
+ def insert_fixtures(fixtures, table_name)
+ return if fixtures.empty?
+
+ columns = schema_cache.columns_hash(table_name)
+
+ values = fixtures.map do |fixture|
+ fixture = fixture.stringify_keys
+
+ unknown_columns = fixture.keys - columns.keys
+ if unknown_columns.any?
+ raise Fixture::FixtureError, %(table "#{table_name}" has no columns named #{unknown_columns.map(&:inspect).join(', ')}.)
+ end
+
+ columns.map do |name, column|
+ if fixture.key?(name)
+ type = lookup_cast_type_from_column(column)
+ bind = Relation::QueryAttribute.new(name, fixture[name], type)
+ with_yaml_fallback(bind.value_for_database)
+ else
+ Arel.sql("DEFAULT")
+ end
end
end
- execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", "Fixture Insert"
+ table = Arel::Table.new(table_name)
+ manager = Arel::InsertManager.new
+ manager.into(table)
+ columns.each_key { |column| manager.columns << table[column] }
+ manager.values = manager.create_values_list(values)
+ execute manager.to_sql, "Fixtures Insert"
end
def empty_insert_statement_value
@@ -370,7 +420,11 @@ module ActiveRecord
end
def last_inserted_id(result)
- row = result.rows.first
+ single_value_from_rows(result.rows)
+ end
+
+ def single_value_from_rows(rows)
+ row = rows.first
row && row.first
end
@@ -380,6 +434,17 @@ module ActiveRecord
end
[relation, binds]
end
+
+ # Fixture value is quoted by Arel, however scalar values
+ # are not quotable. In this case we want to convert
+ # the column value to YAML.
+ def with_yaml_fallback(value)
+ if value.is_a?(Hash) || value.is_a?(Array)
+ YAML.dump(value)
+ else
+ value
+ end
+ end
end
end
end
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 e53ba4e666..c352ddfc11 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -108,6 +108,7 @@ module ActiveRecord
"sql.active_record",
sql: sql,
binds: binds,
+ type_casted_binds: -> { type_casted_binds(binds) },
name: name,
connection_id: object_id,
cached: true,
@@ -123,6 +124,7 @@ module ActiveRecord
# If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such
# queries should not be cached.
def locked?(arel)
+ arel = arel.arel if arel.is_a?(Relation)
arel.respond_to?(:locked) && arel.locked
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index f0c0fbab6c..61233dcc51 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -10,8 +10,15 @@ module ActiveRecord
value = id_value_for_database(value) if value.is_a?(Base)
if value.respond_to?(:quoted_id)
+ at = value.method(:quoted_id).source_location
+ at &&= " at %s:%d" % at
+
+ owner = value.method(:quoted_id).owner.to_s
+ klass = value.class.to_s
+ klass += "(#{owner})" unless owner == klass
+
ActiveSupport::Deprecation.warn \
- "Using #quoted_id is deprecated and will be removed in Rails 5.2."
+ "Defining #quoted_id is deprecated and will be ignored in Rails 5.2. (defined on #{klass}#{at})"
return value.quoted_id
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
index a4fecc4a8e..8865e7c703 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -22,7 +22,7 @@ module ActiveRecord
private
def visit_AlterTable(o)
- sql = "ALTER TABLE #{quote_table_name(o.name)} "
+ sql = "ALTER TABLE #{quote_table_name(o.name)} ".dup
sql << o.adds.map { |col| accept col }.join(" ")
sql << o.foreign_key_adds.map { |fk| visit_AddForeignKey fk }.join(" ")
sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(" ")
@@ -30,17 +30,17 @@ module ActiveRecord
def visit_ColumnDefinition(o)
o.sql_type = type_to_sql(o.type, o.options)
- column_sql = "#{quote_column_name(o.name)} #{o.sql_type}"
+ column_sql = "#{quote_column_name(o.name)} #{o.sql_type}".dup
add_column_options!(column_sql, column_options(o)) unless o.type == :primary_key
column_sql
end
def visit_AddColumnDefinition(o)
- "ADD #{accept(o.column)}"
+ "ADD #{accept(o.column)}".dup
end
def visit_TableDefinition(o)
- create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(o.name)} "
+ create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(o.name)} ".dup
statements = o.columns.map { |c| accept c }
statements << accept(o.primary_keys) if o.primary_keys
@@ -55,7 +55,7 @@ module ActiveRecord
create_sql << "(#{statements.join(', ')})" if statements.present?
add_table_options!(create_sql, table_options(o))
- create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
+ create_sql << " AS #{to_sql(o.as)}" if o.as
create_sql
end
@@ -114,6 +114,11 @@ module ActiveRecord
sql
end
+ def to_sql(sql)
+ sql = sql.to_sql if sql.respond_to?(:to_sql)
+ sql
+ end
+
def foreign_key_in_create(from_table, to_table, options)
options = foreign_key_options(from_table, to_table, options)
accept ForeignKeyDefinition.new(from_table, to_table, 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 4682afc188..a30fbe0e05 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -2,8 +2,33 @@ module ActiveRecord
module ConnectionAdapters #:nodoc:
# 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
- IndexDefinition = Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment) #:nodoc:
+ # adapters. e.g. ActiveRecord::ConnectionAdapters::MySQL::SchemaStatements#indexes
+ class IndexDefinition # :nodoc:
+ attr_reader :table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment
+
+ def initialize(
+ table, name,
+ unique = false,
+ columns = [],
+ lengths: {},
+ orders: {},
+ where: nil,
+ type: nil,
+ using: nil,
+ comment: nil
+ )
+ @table = table
+ @name = name
+ @unique = unique
+ @columns = columns
+ @lengths = lengths
+ @orders = orders
+ @where = where
+ @type = type
+ @using = using
+ @comment = comment
+ end
+ end
# Abstract representation of a column definition. Instances of this type
# are typically created by methods in TableDefinition, and added to the
@@ -121,7 +146,7 @@ module ActiveRecord
end
def polymorphic_options
- as_options(polymorphic)
+ as_options(polymorphic).merge(null: options[:null])
end
def index_options
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 13629dee7f..475463c4fd 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -1,4 +1,4 @@
-require "active_record/migration/join_table"
+require_relative "../../migration/join_table"
require "active_support/core_ext/string/access"
require "digest"
@@ -31,7 +31,7 @@ module ActiveRecord
# Returns the relation names useable to back Active Record models.
# For most adapters this means all #tables and #views.
def data_sources
- select_values(data_source_sql, "SCHEMA")
+ query_values(data_source_sql, "SCHEMA")
rescue NotImplementedError
tables | views
end
@@ -41,14 +41,14 @@ module ActiveRecord
# data_source_exists?(:ebooks)
#
def data_source_exists?(name)
- select_values(data_source_sql(name), "SCHEMA").any? if name.present?
+ query_values(data_source_sql(name), "SCHEMA").any? if name.present?
rescue NotImplementedError
data_sources.include?(name.to_s)
end
# Returns an array of table names defined in the database.
def tables
- select_values(data_source_sql(type: "BASE TABLE"), "SCHEMA")
+ query_values(data_source_sql(type: "BASE TABLE"), "SCHEMA")
end
# Checks to see if the table +table_name+ exists on the database.
@@ -56,14 +56,14 @@ module ActiveRecord
# table_exists?(:developers)
#
def table_exists?(table_name)
- select_values(data_source_sql(table_name, type: "BASE TABLE"), "SCHEMA").any? if table_name.present?
+ query_values(data_source_sql(table_name, type: "BASE TABLE"), "SCHEMA").any? if table_name.present?
rescue NotImplementedError
tables.include?(table_name.to_s)
end
# Returns an array of view names defined in the database.
def views
- select_values(data_source_sql(type: "VIEW"), "SCHEMA")
+ query_values(data_source_sql(type: "VIEW"), "SCHEMA")
end
# Checks to see if the view +view_name+ exists on the database.
@@ -71,7 +71,7 @@ module ActiveRecord
# view_exists?(:ebooks)
#
def view_exists?(view_name)
- select_values(data_source_sql(view_name, type: "VIEW"), "SCHEMA").any? if view_name.present?
+ query_values(data_source_sql(view_name, type: "VIEW"), "SCHEMA").any? if view_name.present?
rescue NotImplementedError
views.include?(view_name.to_s)
end
@@ -188,6 +188,8 @@ module ActiveRecord
# The name of the primary key, if one is to be added automatically.
# Defaults to +id+. If <tt>:id</tt> is false, then this option is ignored.
#
+ # If an array is passed, a composite primary key will be created.
+ #
# Note that Active Record models will automatically detect their
# primary key. This can be avoided by using
# {self.primary_key=}[rdoc-ref:AttributeMethods::PrimaryKey::ClassMethods#primary_key=] on the model
@@ -241,6 +243,23 @@ module ActiveRecord
# label varchar
# )
#
+ # ====== Create a composite primary key
+ #
+ # create_table(:orders, primary_key: [:product_id, :client_id]) do |t|
+ # t.belongs_to :product
+ # t.belongs_to :client
+ # end
+ #
+ # generates:
+ #
+ # CREATE TABLE order (
+ # product_id integer NOT NULL,
+ # client_id integer NOT NULL
+ # );
+ #
+ # ALTER TABLE ONLY "orders"
+ # ADD CONSTRAINT orders_pkey PRIMARY KEY (product_id, client_id);
+ #
# ====== Do not add a primary key column
#
# create_table(:categories_suppliers, id: false) do |t|
@@ -493,8 +512,7 @@ module ActiveRecord
# * <tt>:default</tt> -
# The column's default value. Use +nil+ for +NULL+.
# * <tt>:null</tt> -
- # Allows or disallows +NULL+ values in the column. This option could
- # have been named <tt>:null_allowed</tt>.
+ # Allows or disallows +NULL+ values in the column.
# * <tt>:precision</tt> -
# Specifies the precision for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
# * <tt>:scale</tt> -
@@ -992,7 +1010,7 @@ module ActiveRecord
def dump_schema_information #:nodoc:
versions = ActiveRecord::SchemaMigration.all_versions
- insert_versions_sql(versions)
+ insert_versions_sql(versions) if versions.any?
end
def initialize_schema_migrations_table # :nodoc:
@@ -1280,9 +1298,10 @@ module ActiveRecord
end
def foreign_key_name(table_name, options)
- identifier = "#{table_name}_#{options.fetch(:column)}_fk"
- hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
options.fetch(:name) do
+ identifier = "#{table_name}_#{options.fetch(:column)}_fk"
+ hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
+
"fk_rails_#{hashed_identifier}"
end
end
@@ -1329,7 +1348,7 @@ module ActiveRecord
sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
if versions.is_a?(Array)
- sql = "INSERT INTO #{sm_table} (version) VALUES\n"
+ sql = "INSERT INTO #{sm_table} (version) VALUES\n".dup
sql << versions.map { |v| "(#{quote(v)})" }.join(",\n")
sql << ";\n\n"
sql
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index 6bb072dd73..f63d09039f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -1,10 +1,13 @@
module ActiveRecord
module ConnectionAdapters
class TransactionState
- VALID_STATES = Set.new([:committed, :rolledback, nil])
-
def initialize(state = nil)
@state = state
+ @children = []
+ end
+
+ def add_child(state)
+ @children << state
end
def finalized?
@@ -19,15 +22,43 @@ module ActiveRecord
@state == :rolledback
end
+ def fully_completed?
+ completed?
+ end
+
def completed?
committed? || rolledback?
end
def set_state(state)
- unless VALID_STATES.include?(state)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ The set_state method is deprecated and will be removed in
+ Rails 6.0. Please use rollback! or commit! to set transaction
+ state directly.
+ MSG
+ case state
+ when :rolledback
+ rollback!
+ when :committed
+ commit!
+ when nil
+ nullify!
+ else
raise ArgumentError, "Invalid transaction state: #{state}"
end
- @state = state
+ end
+
+ def rollback!
+ @children.each { |c| c.rollback! }
+ @state = :rolledback
+ end
+
+ def commit!
+ @state = :committed
+ end
+
+ def nullify!
+ @state = nil
end
end
@@ -57,7 +88,7 @@ module ActiveRecord
end
def rollback
- @state.set_state(:rolledback)
+ @state.rollback!
end
def rollback_records
@@ -72,7 +103,7 @@ module ActiveRecord
end
def commit
- @state.set_state(:committed)
+ @state.commit!
end
def before_commit_records
@@ -100,8 +131,11 @@ module ActiveRecord
end
class SavepointTransaction < Transaction
- def initialize(connection, savepoint_name, options, *args)
+ def initialize(connection, savepoint_name, parent_transaction, options, *args)
super(connection, options, *args)
+
+ parent_transaction.state.add_child(@state)
+
if options[:isolation]
raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
end
@@ -149,57 +183,67 @@ module ActiveRecord
end
def begin_transaction(options = {})
- run_commit_callbacks = !current_transaction.joinable?
- transaction =
- if @stack.empty?
- RealTransaction.new(@connection, options, run_commit_callbacks: run_commit_callbacks)
- else
- SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options,
- run_commit_callbacks: run_commit_callbacks)
- end
+ @connection.lock.synchronize do
+ run_commit_callbacks = !current_transaction.joinable?
+ transaction =
+ if @stack.empty?
+ RealTransaction.new(@connection, options, run_commit_callbacks: run_commit_callbacks)
+ else
+ SavepointTransaction.new(@connection, "active_record_#{@stack.size}", @stack.last, options,
+ run_commit_callbacks: run_commit_callbacks)
+ end
- @stack.push(transaction)
- transaction
+ @stack.push(transaction)
+ transaction
+ end
end
def commit_transaction
- transaction = @stack.last
+ @connection.lock.synchronize do
+ transaction = @stack.last
- begin
- transaction.before_commit_records
- ensure
- @stack.pop
- end
+ begin
+ transaction.before_commit_records
+ ensure
+ @stack.pop
+ end
- transaction.commit
- transaction.commit_records
+ transaction.commit
+ transaction.commit_records
+ end
end
def rollback_transaction(transaction = nil)
- transaction ||= @stack.pop
- transaction.rollback
- transaction.rollback_records
+ @connection.lock.synchronize do
+ transaction ||= @stack.pop
+ transaction.rollback
+ transaction.rollback_records
+ end
end
def within_new_transaction(options = {})
- transaction = begin_transaction options
- yield
- rescue Exception => error
- if transaction
- rollback_transaction
- after_failure_actions(transaction, error)
- end
- raise
- ensure
- unless error
- if Thread.current.status == "aborting"
- rollback_transaction if transaction
- else
- begin
- commit_transaction
- rescue Exception
- rollback_transaction(transaction) unless transaction.state.completed?
- raise
+ @connection.lock.synchronize do
+ begin
+ transaction = begin_transaction options
+ yield
+ rescue Exception => error
+ if transaction
+ rollback_transaction
+ after_failure_actions(transaction, error)
+ end
+ raise
+ ensure
+ unless error
+ if Thread.current.status == "aborting"
+ rollback_transaction if transaction
+ else
+ begin
+ commit_transaction
+ rescue Exception
+ rollback_transaction(transaction) unless transaction.state.completed?
+ raise
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 96083e6519..cfe1892d78 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -1,9 +1,9 @@
-require "active_record/type"
-require "active_record/connection_adapters/determine_if_preparable_visitor"
-require "active_record/connection_adapters/schema_cache"
-require "active_record/connection_adapters/sql_type_metadata"
-require "active_record/connection_adapters/abstract/schema_dumper"
-require "active_record/connection_adapters/abstract/schema_creation"
+require_relative "../type"
+require_relative "determine_if_preparable_visitor"
+require_relative "schema_cache"
+require_relative "sql_type_metadata"
+require_relative "abstract/schema_dumper"
+require_relative "abstract/schema_creation"
require "arel/collectors/bind"
require "arel/collectors/sql_string"
@@ -74,7 +74,7 @@ module ActiveRecord
SIMPLE_INT = /\A\d+\z/
attr_accessor :visitor, :pool
- attr_reader :schema_cache, :owner, :logger, :prepared_statements
+ attr_reader :schema_cache, :owner, :logger, :prepared_statements, :lock
alias :in_use? :owner
def self.type_cast_config_to_integer(config)
@@ -147,7 +147,7 @@ module ActiveRecord
# this method must only be called while holding connection pool's mutex
def lease
if in_use?
- msg = "Cannot lease connection, "
+ msg = "Cannot lease connection, ".dup
if @owner == Thread.current
msg << "it is already leased by the current thread."
else
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 f118e086bb..c15b4a1a05 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -1,13 +1,13 @@
-require "active_record/connection_adapters/abstract_adapter"
-require "active_record/connection_adapters/statement_pool"
-require "active_record/connection_adapters/mysql/column"
-require "active_record/connection_adapters/mysql/explain_pretty_printer"
-require "active_record/connection_adapters/mysql/quoting"
-require "active_record/connection_adapters/mysql/schema_creation"
-require "active_record/connection_adapters/mysql/schema_definitions"
-require "active_record/connection_adapters/mysql/schema_dumper"
-require "active_record/connection_adapters/mysql/schema_statements"
-require "active_record/connection_adapters/mysql/type_metadata"
+require_relative "abstract_adapter"
+require_relative "statement_pool"
+require_relative "mysql/column"
+require_relative "mysql/explain_pretty_printer"
+require_relative "mysql/quoting"
+require_relative "mysql/schema_creation"
+require_relative "mysql/schema_definitions"
+require_relative "mysql/schema_dumper"
+require_relative "mysql/schema_statements"
+require_relative "mysql/type_metadata"
require "active_support/core_ext/string/strip"
@@ -29,8 +29,7 @@ module ActiveRecord
# to your application.rb file:
#
# ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false
- class_attribute :emulate_booleans
- self.emulate_booleans = true
+ class_attribute :emulate_booleans, default: true
NATIVE_DATABASE_TYPES = {
primary_key: "bigint auto_increment PRIMARY KEY",
@@ -48,9 +47,6 @@ module ActiveRecord
json: { name: "json" },
}
- INDEX_TYPES = [:fulltext, :spatial]
- INDEX_USINGS = [:btree, :hash]
-
class StatementPool < ConnectionAdapters::StatementPool
private def dealloc(stmt)
stmt[:stmt].close
@@ -67,14 +63,6 @@ module ActiveRecord
end
end
- CHARSETS_OF_4BYTES_MAXLEN = ["utf8mb4", "utf16", "utf16le", "utf32"]
-
- def internal_string_options_for_primary_key # :nodoc:
- super.tap { |options|
- options[:collation] = collation.sub(/\A[^_]+/, "utf8") if CHARSETS_OF_4BYTES_MAXLEN.include?(charset)
- }
- end
-
def version #:nodoc:
@version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
end
@@ -87,16 +75,8 @@ module ActiveRecord
true
end
- # Returns true, since this connection adapter supports prepared statement
- # caching.
- def supports_statement_cache?
- true
- end
-
- # Technically MySQL allows to create indexes with the sort order syntax
- # but at the moment (5.5) it doesn't yet implement them
def supports_index_sort_order?
- true
+ !mariadb? && version >= "8.0.1"
end
def supports_transaction_isolation?
@@ -140,11 +120,11 @@ module ActiveRecord
end
def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
- select_value("SELECT GET_LOCK(#{quote(lock_name)}, #{timeout})") == 1
+ query_value("SELECT GET_LOCK(#{quote(lock_name)}, #{timeout})") == 1
end
def release_advisory_lock(lock_name) # :nodoc:
- select_value("SELECT RELEASE_LOCK(#{quote(lock_name)})") == 1
+ query_value("SELECT RELEASE_LOCK(#{quote(lock_name)})") == 1
end
def native_database_types
@@ -152,7 +132,7 @@ module ActiveRecord
end
def index_algorithms
- { default: "ALGORITHM = DEFAULT", copy: "ALGORITHM = COPY", inplace: "ALGORITHM = INPLACE" }
+ { default: "ALGORITHM = DEFAULT".dup, copy: "ALGORITHM = COPY".dup, inplace: "ALGORITHM = INPLACE".dup }
end
# HELPER METHODS ===========================================
@@ -172,7 +152,7 @@ module ActiveRecord
# REFERENTIAL INTEGRITY ====================================
def disable_referential_integrity #:nodoc:
- old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
+ old = query_value("SELECT @@FOREIGN_KEY_CHECKS")
begin
update("SET FOREIGN_KEY_CHECKS = 0")
@@ -287,7 +267,7 @@ module ActiveRecord
end
def current_database
- select_value "SELECT DATABASE() as db"
+ query_value("SELECT database()", "SCHEMA")
end
# Returns the database character set.
@@ -304,40 +284,10 @@ module ActiveRecord
execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
end
- # Returns an array of indexes for the given table.
- def indexes(table_name, name = nil) #:nodoc:
- if name
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing name to #indexes is deprecated without replacement.
- MSG
- end
-
- indexes = []
- current_index = nil
- execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
- each_hash(result) do |row|
- if current_index != row[:Key_name]
- next if row[:Key_name] == "PRIMARY" # skip the primary key
- current_index = row[:Key_name]
-
- mysql_index_type = row[:Index_type].downcase.to_sym
- index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
- index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
- indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], {}, nil, nil, index_type, index_using, row[:Index_comment].presence)
- end
-
- indexes.last.columns << row[:Column_name]
- indexes.last.lengths.merge!(row[:Column_name] => row[:Sub_part].to_i) if row[:Sub_part]
- end
- end
-
- indexes
- end
-
def table_comment(table_name) # :nodoc:
scope = quoted_scope(table_name)
- select_value(<<-SQL.strip_heredoc, "SCHEMA")
+ query_value(<<-SQL.strip_heredoc, "SCHEMA")
SELECT table_comment
FROM information_schema.tables
WHERE table_schema = #{scope[:schema]}
@@ -429,7 +379,7 @@ module ActiveRecord
def add_index(table_name, column_name, options = {}) #:nodoc:
index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
- sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
+ sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}".dup
execute add_sql_comment!(sql, comment)
end
@@ -443,7 +393,7 @@ module ActiveRecord
scope = quoted_scope(table_name)
- fk_info = select_all(<<-SQL.strip_heredoc, "SCHEMA")
+ fk_info = exec_query(<<-SQL.strip_heredoc, "SCHEMA")
SELECT fk.referenced_table_name AS 'to_table',
fk.referenced_column_name AS 'primary_key',
fk.column_name AS 'column',
@@ -514,13 +464,13 @@ module ActiveRecord
super
end
- sql << " unsigned" if unsigned && type != :primary_key
+ sql = "#{sql} unsigned" if unsigned && type != :primary_key
sql
end
# SHOW VARIABLES LIKE 'name'
def show_variable(name)
- select_value("SELECT @@#{name}", "SCHEMA")
+ query_value("SELECT @@#{name}", "SCHEMA")
rescue ActiveRecord::StatementInvalid
nil
end
@@ -530,7 +480,7 @@ module ActiveRecord
scope = quoted_scope(table_name)
- select_values(<<-SQL.strip_heredoc, "SCHEMA")
+ query_values(<<-SQL.strip_heredoc, "SCHEMA")
SELECT column_name
FROM information_schema.key_column_usage
WHERE constraint_name = 'PRIMARY'
@@ -576,8 +526,25 @@ module ActiveRecord
index.using == :btree || super
end
+ def insert_fixtures(*)
+ without_sql_mode("NO_AUTO_VALUE_ON_ZERO") { super }
+ end
+
private
+ def without_sql_mode(mode)
+ result = execute("SELECT @@SESSION.sql_mode")
+ current_mode = result.first[0]
+ return yield unless current_mode.include?(mode)
+
+ sql_mode = "REPLACE(@@sql_mode, '#{mode}', '')"
+ execute("SET @@SESSION.sql_mode = #{sql_mode}")
+ yield
+ ensure
+ sql_mode = "CONCAT(@@sql_mode, ',#{mode}')"
+ execute("SET @@SESSION.sql_mode = #{sql_mode}")
+ end
+
def initialize_type_map(m)
super
@@ -593,7 +560,7 @@ module ActiveRecord
m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
m.register_type %r(^float)i, Type::Float.new(limit: 24)
m.register_type %r(^double)i, Type::Float.new(limit: 53)
- m.register_type %r(^json)i, MysqlJson.new
+ m.register_type %r(^json)i, Type::Json.new
register_integer_type m, %r(^bigint)i, limit: 8
register_integer_type m, %r(^int)i, limit: 4
@@ -727,7 +694,7 @@ module ActiveRecord
auto_increment: column.auto_increment?
}
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", "SCHEMA")["Type"]
+ current_type = exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
td = create_table_definition(table_name)
cd = td.new_column_definition(new_column_name, current_type, options)
schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
@@ -763,16 +730,14 @@ module ActiveRecord
# MySQL is too stupid to create a temporary table for use subquery, so we have
# to give it some prompting in the form of a subsubquery. Ugh!
def subquery_for(key, select)
- subsubselect = select.clone
- subsubselect.projections = [key]
+ subselect = select.clone
+ subselect.projections = [key]
# Materialize subquery by adding distinct
# to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
- subsubselect.distinct unless select.limit || select.offset || select.orders.any?
+ subselect.distinct unless select.limit || select.offset || select.orders.any?
- subselect = Arel::SelectManager.new(select.engine)
- subselect.project Arel.sql(key.name)
- subselect.from subsubselect.as("__active_record_temp")
+ Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key.name))
end
def supports_rename_index?
@@ -813,7 +778,7 @@ module ActiveRecord
# http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430
# (trailing comma because variable_assignments will always have content)
if @config[:encoding]
- encoding = "NAMES #{@config[:encoding]}"
+ encoding = "NAMES #{@config[:encoding]}".dup
encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
encoding << ", "
end
@@ -839,7 +804,7 @@ module ActiveRecord
end
def create_table_info(table_name) # :nodoc:
- select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
+ exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
end
def arel_visitor
@@ -889,14 +854,6 @@ module ActiveRecord
end
end
- class MysqlJson < Type::Internal::AbstractJson # :nodoc:
- def changed_in_place?(raw_old_value, new_value)
- # Normalization is required because MySQL JSON data format includes
- # the space between the elements.
- super(serialize(deserialize(raw_old_value)), new_value)
- end
- end
-
class MysqlString < Type::String # :nodoc:
def serialize(value)
case value
@@ -917,7 +874,6 @@ module ActiveRecord
end
end
- ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2)
ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
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 8c67a7a80b..bda482a00f 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
@@ -13,13 +13,8 @@ module ActiveRecord
result
end
- # Returns an array of arrays containing the field values.
- # Order is the same as that returned by +columns+.
- 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
+ def query(sql, name = nil) # :nodoc:
+ execute(sql, name).to_a
end
# Executes the SQL statement in the context of this connection.
@@ -58,16 +53,6 @@ module ActiveRecord
@connection.last_id
end
- 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
- exec_stmt_and_free(sql, name, binds, cache_stmt: true) { |_, result| yield result }
- end
- end
-
def exec_stmt_and_free(sql, name, binds, cache_stmt: false)
# make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
# made since we established the connection
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
index 083cd6340f..eea4984680 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
@@ -16,7 +16,7 @@ module ActiveRecord
end
def visit_ChangeColumnDefinition(o)
- change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}"
+ change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}".dup
add_column_position!(change_column_sql, column_options(o.column))
end
@@ -63,7 +63,7 @@ module ActiveRecord
def index_in_create(table_name, column_name, options)
index_name, index_type, index_columns, _, _, index_using, comment = @conn.add_index_options(table_name, column_name, options)
- add_sql_comment!("#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})", comment)
+ add_sql_comment!("#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})".dup, comment)
end
end
end
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 3e0afd9761..eff96e329f 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
@@ -47,7 +47,7 @@ module ActiveRecord
def schema_collation(column)
if column.collation && table_name = column.table_name
@table_collation_cache ||= {}
- @table_collation_cache[table_name] ||= select_one("SHOW TABLE STATUS LIKE '#{table_name}'")["Collation"]
+ @table_collation_cache[table_name] ||= exec_query("SHOW TABLE STATUS LIKE #{quote(table_name)}", "SCHEMA").first["Collation"]
column.collation.inspect if column.collation != @table_collation_cache[table_name]
end
end
@@ -55,15 +55,16 @@ module ActiveRecord
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
+ if %r/#{quote_column_name(column.name)} #{Regexp.quote(column.sql_type)}(?: COLLATE \w+)? AS \((?<expression>.+?)\) #{column.extra}/ =~ create_table_info
$~[:expression].inspect
end
else
+ scope = quoted_scope(column.table_name)
sql = "SELECT generation_expression FROM information_schema.columns" \
- " WHERE table_schema = #{quote(@config[:database])}" \
- " AND table_name = #{quote(column.table_name)}" \
+ " WHERE table_schema = #{scope[:schema]}" \
+ " AND table_name = #{scope[:name]}" \
" AND column_name = #{quote(column.name)}"
- select_value(sql, "SCHEMA").inspect
+ query_value(sql, "SCHEMA").inspect
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
index 9e2d0fb5e7..a01fbba201 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
@@ -2,7 +2,67 @@ module ActiveRecord
module ConnectionAdapters
module MySQL
module SchemaStatements # :nodoc:
+ # Returns an array of indexes for the given table.
+ def indexes(table_name, name = nil)
+ if name
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing name to #indexes is deprecated without replacement.
+ MSG
+ end
+
+ indexes = []
+ current_index = nil
+ execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
+ each_hash(result) do |row|
+ if current_index != row[:Key_name]
+ next if row[:Key_name] == "PRIMARY" # skip the primary key
+ current_index = row[:Key_name]
+
+ mysql_index_type = row[:Index_type].downcase.to_sym
+ case mysql_index_type
+ when :fulltext, :spatial
+ index_type = mysql_index_type
+ when :btree, :hash
+ index_using = mysql_index_type
+ end
+
+ indexes << IndexDefinition.new(
+ row[:Table],
+ row[:Key_name],
+ row[:Non_unique].to_i == 0,
+ type: index_type,
+ using: index_using,
+ comment: row[:Index_comment].presence
+ )
+ end
+
+ indexes.last.columns << row[:Column_name]
+ indexes.last.lengths.merge!(row[:Column_name] => row[:Sub_part].to_i) if row[:Sub_part]
+ indexes.last.orders.merge!(row[:Column_name] => :desc) if row[:Collation] == "D"
+ end
+ end
+
+ indexes
+ end
+
+ def remove_column(table_name, column_name, type = nil, options = {})
+ if foreign_key_exists?(table_name, column: column_name)
+ remove_foreign_key(table_name, column: column_name)
+ end
+ super
+ end
+
+ def internal_string_options_for_primary_key
+ super.tap do |options|
+ if CHARSETS_OF_4BYTES_MAXLEN.include?(charset) && (mariadb? || version < "8.0.0")
+ options[:collation] = collation.sub(/\A[^_]+/, "utf8")
+ end
+ end
+ end
+
private
+ CHARSETS_OF_4BYTES_MAXLEN = ["utf8mb4", "utf16", "utf16le", "utf32"]
+
def schema_creation
MySQL::SchemaCreation.new(self)
end
@@ -42,7 +102,7 @@ module ActiveRecord
def data_source_sql(name = nil, type: nil)
scope = quoted_scope(name, type: type)
- sql = "SELECT table_name FROM information_schema.tables"
+ sql = "SELECT table_name FROM information_schema.tables".dup
sql << " WHERE table_schema = #{scope[:schema]}"
sql << " AND table_name = #{scope[:name]}" if scope[:name]
sql << " AND table_type = #{scope[:type]}" if scope[:type]
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 45e400b75b..c5c0a071e7 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -1,5 +1,5 @@
-require "active_record/connection_adapters/abstract_mysql_adapter"
-require "active_record/connection_adapters/mysql/database_statements"
+require_relative "abstract_mysql_adapter"
+require_relative "mysql/database_statements"
gem "mysql2", ">= 0.3.18", "< 0.5"
require "mysql2"
@@ -10,8 +10,6 @@ module ActiveRecord
# Establishes a connection to the database that's used by all Active Record objects.
def mysql2_connection(config)
config = config.symbolize_keys
-
- config[:username] = "root" if config[:username].nil?
config[:flags] ||= 0
if config[:flags].kind_of? Array
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 705e6063dc..ebf1715ed0 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -7,30 +7,6 @@ module ActiveRecord
PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", binds))
end
- 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 = []) # :nodoc:
- select_result(arel, name, binds) do |result|
- if result.nfields > 0
- result.column_values(0)
- else
- []
- end
- end
- end
-
- # Executes a SELECT query and returns an array of rows. Each row is an
- # array of field values.
- def select_rows(arel, name = nil, binds = []) # :nodoc:
- select_result(arel, name, binds) do |result|
- result.values
- end
- end
-
# The internal PostgreSQL identifier of the money data type.
MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
# The internal PostgreSQL identifier of the BYTEA data type.
@@ -171,18 +147,14 @@ module ActiveRecord
end
private
+ # Returns the current ID of a table's sequence.
+ def last_insert_id_result(sequence_name)
+ exec_query("SELECT currval(#{quote(sequence_name)})", "SQL")
+ end
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 4098250f3e..6666622c08 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -1,26 +1,25 @@
-require "active_record/connection_adapters/postgresql/oid/array"
-require "active_record/connection_adapters/postgresql/oid/bit"
-require "active_record/connection_adapters/postgresql/oid/bit_varying"
-require "active_record/connection_adapters/postgresql/oid/bytea"
-require "active_record/connection_adapters/postgresql/oid/cidr"
-require "active_record/connection_adapters/postgresql/oid/date_time"
-require "active_record/connection_adapters/postgresql/oid/decimal"
-require "active_record/connection_adapters/postgresql/oid/enum"
-require "active_record/connection_adapters/postgresql/oid/hstore"
-require "active_record/connection_adapters/postgresql/oid/inet"
-require "active_record/connection_adapters/postgresql/oid/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"
-require "active_record/connection_adapters/postgresql/oid/specialized_string"
-require "active_record/connection_adapters/postgresql/oid/uuid"
-require "active_record/connection_adapters/postgresql/oid/vector"
-require "active_record/connection_adapters/postgresql/oid/xml"
+require_relative "oid/array"
+require_relative "oid/bit"
+require_relative "oid/bit_varying"
+require_relative "oid/bytea"
+require_relative "oid/cidr"
+require_relative "oid/date_time"
+require_relative "oid/decimal"
+require_relative "oid/enum"
+require_relative "oid/hstore"
+require_relative "oid/inet"
+require_relative "oid/jsonb"
+require_relative "oid/money"
+require_relative "oid/oid"
+require_relative "oid/point"
+require_relative "oid/legacy_point"
+require_relative "oid/range"
+require_relative "oid/specialized_string"
+require_relative "oid/uuid"
+require_relative "oid/vector"
+require_relative "oid/xml"
-require "active_record/connection_adapters/postgresql/oid/type_map_initializer"
+require_relative "oid/type_map_initializer"
module ActiveRecord
module ConnectionAdapters
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb
deleted file mode 100644
index dbc879ffd4..0000000000
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-module ActiveRecord
- module ConnectionAdapters
- module PostgreSQL
- module OID # :nodoc:
- class Json < Type::Internal::AbstractJson
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb
index 87391b5dc7..a1fec289d4 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb
@@ -2,20 +2,10 @@ module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
- class Jsonb < Json # :nodoc:
+ class Jsonb < Type::Json # :nodoc:
def type
:jsonb
end
-
- def changed_in_place?(raw_old_value, new_value)
- # Postgres does not preserve insignificant whitespaces when
- # round-tripping jsonb columns. This causes some false positives for
- # the comparison here. Therefore, we need to parse and re-dump the
- # raw value here to ensure the insignificant whitespaces are
- # consistent with our encoder's output.
- raw_old_value = serialize(deserialize(raw_old_value))
- super(raw_old_value, new_value)
- end
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 da8d0c6992..ee4230c6f2 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -62,7 +62,7 @@ module ActiveRecord
def quote_default_expression(value, column) # :nodoc:
if value.is_a?(Proc)
value.call
- elsif column.type == :uuid && value.include?("()")
+ elsif column.type == :uuid && /\(\)/.match?(value)
value # Does not quote function default values for UUID columns
elsif column.respond_to?(:array?)
value = type_cast_from_column(column, value)
@@ -78,7 +78,7 @@ module ActiveRecord
private
def lookup_cast_type(sql_type)
- super(select_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
+ super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
end
def _quote(value)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb
index 730e7c7137..44a7338bf5 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb
@@ -6,50 +6,8 @@ module ActiveRecord
true
end
- def disable_referential_integrity(&block) # :nodoc:
+ def disable_referential_integrity # :nodoc:
if supports_disable_referential_integrity?
- if supports_alter_constraint?
- disable_referential_integrity_with_alter_constraint(&block)
- else
- disable_referential_integrity_with_disable_trigger(&block)
- end
- else
- yield
- end
- end
-
- private
-
- def disable_referential_integrity_with_alter_constraint
- tables_constraints = execute(<<-SQL).values
- SELECT table_name, constraint_name
- FROM information_schema.table_constraints
- WHERE constraint_type = 'FOREIGN KEY'
- AND is_deferrable = 'NO'
- SQL
-
- execute(
- tables_constraints.collect { |table, constraint|
- "ALTER TABLE #{quote_table_name(table)} ALTER CONSTRAINT #{constraint} DEFERRABLE"
- }.join(";")
- )
-
- begin
- transaction do
- execute("SET CONSTRAINTS ALL DEFERRED")
-
- yield
- end
- ensure
- execute(
- tables_constraints.collect { |table, constraint|
- "ALTER TABLE #{quote_table_name(table)} ALTER CONSTRAINT #{constraint} NOT DEFERRABLE"
- }.join(";")
- )
- end
- end
-
- def disable_referential_integrity_with_disable_trigger
original_exception = nil
begin
@@ -81,7 +39,10 @@ Rails needs superuser privileges to disable referential integrity.
end
rescue ActiveRecord::ActiveRecordError
end
+ else
+ yield
end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 1d439acb07..a710ea6cc9 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -60,7 +60,7 @@ module ActiveRecord
# Returns true if schema exists.
def schema_exists?(name)
- select_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = #{quote(name)}", "SCHEMA").to_i > 0
+ query_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = #{quote(name)}", "SCHEMA").to_i > 0
end
# Verifies existence of an index with a given name.
@@ -73,7 +73,7 @@ module ActiveRecord
table = quoted_scope(table_name)
index = quoted_scope(index_name)
- select_value(<<-SQL, "SCHEMA").to_i > 0
+ query_value(<<-SQL, "SCHEMA").to_i > 0
SELECT COUNT(*)
FROM pg_class t
INNER JOIN pg_index d ON t.oid = d.indrelid
@@ -140,8 +140,17 @@ module ActiveRecord
]
end
- IndexDefinition.new(table_name, index_name, unique, columns, [], orders, where, nil, using.to_sym, comment.presence)
- end.compact
+ IndexDefinition.new(
+ table_name,
+ index_name,
+ unique,
+ columns,
+ orders: orders,
+ where: where,
+ using: using.to_sym,
+ comment: comment.presence
+ )
+ end
end
def table_options(table_name) # :nodoc:
@@ -154,7 +163,7 @@ module ActiveRecord
def table_comment(table_name) # :nodoc:
scope = quoted_scope(table_name, type: "BASE TABLE")
if scope[:name]
- select_value(<<-SQL.strip_heredoc, "SCHEMA")
+ query_value(<<-SQL.strip_heredoc, "SCHEMA")
SELECT pg_catalog.obj_description(c.oid, 'pg_class')
FROM pg_catalog.pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
@@ -167,32 +176,32 @@ module ActiveRecord
# Returns the current database name.
def current_database
- select_value("SELECT current_database()", "SCHEMA")
+ query_value("SELECT current_database()", "SCHEMA")
end
# Returns the current schema name.
def current_schema
- select_value("SELECT current_schema", "SCHEMA")
+ query_value("SELECT current_schema", "SCHEMA")
end
# Returns the current database encoding format.
def encoding
- select_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname LIKE '#{current_database}'", "SCHEMA")
+ query_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = current_database()", "SCHEMA")
end
# Returns the current database collation.
def collation
- select_value("SELECT datcollate FROM pg_database WHERE datname LIKE '#{current_database}'", "SCHEMA")
+ query_value("SELECT datcollate FROM pg_database WHERE datname = current_database()", "SCHEMA")
end
# Returns the current database ctype.
def ctype
- select_value("SELECT datctype FROM pg_database WHERE datname LIKE '#{current_database}'", "SCHEMA")
+ query_value("SELECT datctype FROM pg_database WHERE datname = current_database()", "SCHEMA")
end
# Returns an array of schema names.
def schema_names
- select_values(<<-SQL, "SCHEMA")
+ query_values(<<-SQL, "SCHEMA")
SELECT nspname
FROM pg_namespace
WHERE nspname !~ '^pg_.*'
@@ -225,12 +234,12 @@ module ActiveRecord
# Returns the active schema search path.
def schema_search_path
- @schema_search_path ||= select_value("SHOW search_path", "SCHEMA")
+ @schema_search_path ||= query_value("SHOW search_path", "SCHEMA")
end
# Returns the current client message level.
def client_min_messages
- select_value("SHOW client_min_messages", "SCHEMA")
+ query_value("SHOW client_min_messages", "SCHEMA")
end
# Set the client message level.
@@ -248,7 +257,7 @@ module ActiveRecord
end
def serial_sequence(table, column)
- select_value("SELECT pg_get_serial_sequence('#{table}', '#{column}')", "SCHEMA")
+ query_value("SELECT pg_get_serial_sequence(#{quote(table)}, #{quote(column)})", "SCHEMA")
end
# Sets the sequence of a table's primary key to the specified value.
@@ -259,7 +268,7 @@ module ActiveRecord
if sequence
quoted_sequence = quote_table_name(sequence)
- select_value("SELECT setval('#{quoted_sequence}', #{value})", "SCHEMA")
+ query_value("SELECT setval(#{quote(quoted_sequence)}, #{value})", "SCHEMA")
else
@logger.warn "#{table} has primary key #{pk} with no default sequence." if @logger
end
@@ -281,10 +290,16 @@ module ActiveRecord
if pk && sequence
quoted_sequence = quote_table_name(sequence)
+ max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA")
+ if max_pk.nil?
+ if postgresql_version >= 100000
+ minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA")
+ else
+ minvalue = query_value("SELECT min_value FROM #{quoted_sequence}", "SCHEMA")
+ end
+ end
- select_value(<<-end_sql, "SCHEMA")
- SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
- end_sql
+ query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})", "SCHEMA")
end
end
@@ -308,7 +323,7 @@ module ActiveRecord
AND seq.relnamespace = nsp.oid
AND cons.contype = 'p'
AND dep.classid = 'pg_class'::regclass
- AND dep.refobjid = '#{quote_table_name(table)}'::regclass
+ AND dep.refobjid = #{quote(quote_table_name(table))}::regclass
end_sql
if result.nil? || result.empty?
@@ -326,7 +341,7 @@ module ActiveRecord
JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
- WHERE t.oid = '#{quote_table_name(table)}'::regclass
+ WHERE t.oid = #{quote(quote_table_name(table))}::regclass
AND cons.contype = 'p'
AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
end_sql
@@ -343,14 +358,18 @@ module ActiveRecord
end
def primary_keys(table_name) # :nodoc:
- select_values(<<-SQL.strip_heredoc, "SCHEMA")
- SELECT a.attname FROM pg_index i
- CROSS JOIN generate_subscripts(i.indkey, 1) k
- JOIN pg_attribute a
- ON a.attrelid = i.indrelid
- AND a.attnum = i.indkey[k]
- WHERE i.indrelid = #{quote(quote_table_name(table_name))}::regclass
- AND i.indisprimary
+ query_values(<<-SQL.strip_heredoc, "SCHEMA")
+ SELECT a.attname
+ FROM (
+ SELECT indrelid, indkey, generate_subscripts(indkey, 1) idx
+ FROM pg_index
+ WHERE indrelid = #{quote(quote_table_name(table_name))}::regclass
+ AND indisprimary
+ ) i
+ JOIN pg_attribute a
+ ON a.attrelid = i.indrelid
+ AND a.attnum = i.indkey[i.idx]
+ ORDER BY i.idx
SQL
end
@@ -364,14 +383,15 @@ module ActiveRecord
clear_cache!
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
pk, seq = pk_and_sequence_for(new_name)
- if seq && seq.identifier == "#{table_name}_#{pk}_seq"
- new_seq = "#{new_name}_#{pk}_seq"
+ if pk
idx = "#{table_name}_pkey"
new_idx = "#{new_name}_pkey"
- execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}"
execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
+ if seq && seq.identifier == "#{table_name}_#{pk}_seq"
+ new_seq = "#{new_name}_#{pk}_seq"
+ execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}"
+ end
end
-
rename_table_indexes(table_name, new_name)
end
@@ -386,7 +406,7 @@ module ActiveRecord
quoted_table_name = quote_table_name(table_name)
quoted_column_name = quote_column_name(column_name)
sql_type = type_to_sql(type, options)
- sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}"
+ sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}".dup
if options[:collation]
sql << " COLLATE \"#{options[:collation]}\""
end
@@ -489,7 +509,7 @@ module ActiveRecord
def foreign_keys(table_name)
scope = quoted_scope(table_name)
- fk_info = select_all(<<-SQL.strip_heredoc, "SCHEMA")
+ fk_info = exec_query(<<-SQL.strip_heredoc, "SCHEMA")
SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete
FROM pg_constraint c
JOIN pg_class t1 ON c.conrelid = t1.oid
@@ -546,7 +566,7 @@ module ActiveRecord
super
end
- sql << "[]" if array && type != :primary_key
+ sql = "#{sql}[]" if array && type != :primary_key
sql
end
@@ -615,7 +635,7 @@ module ActiveRecord
scope = quoted_scope(name, type: type)
scope[:type] ||= "'r','v','m'" # (r)elation/table, (v)iew, (m)aterialized view
- sql = "SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace"
+ sql = "SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace".dup
sql << " WHERE n.nspname = #{scope[:schema]}"
sql << " AND c.relname = #{scope[:name]}" if scope[:name]
sql << " AND c.relkind IN (#{scope[:type]})"
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 0ad114165e..8baef19030 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -2,20 +2,20 @@
gem "pg", "~> 0.18"
require "pg"
-require "active_record/connection_adapters/abstract_adapter"
-require "active_record/connection_adapters/statement_pool"
-require "active_record/connection_adapters/postgresql/column"
-require "active_record/connection_adapters/postgresql/database_statements"
-require "active_record/connection_adapters/postgresql/explain_pretty_printer"
-require "active_record/connection_adapters/postgresql/oid"
-require "active_record/connection_adapters/postgresql/quoting"
-require "active_record/connection_adapters/postgresql/referential_integrity"
-require "active_record/connection_adapters/postgresql/schema_creation"
-require "active_record/connection_adapters/postgresql/schema_definitions"
-require "active_record/connection_adapters/postgresql/schema_dumper"
-require "active_record/connection_adapters/postgresql/schema_statements"
-require "active_record/connection_adapters/postgresql/type_metadata"
-require "active_record/connection_adapters/postgresql/utils"
+require_relative "abstract_adapter"
+require_relative "statement_pool"
+require_relative "postgresql/column"
+require_relative "postgresql/database_statements"
+require_relative "postgresql/explain_pretty_printer"
+require_relative "postgresql/oid"
+require_relative "postgresql/quoting"
+require_relative "postgresql/referential_integrity"
+require_relative "postgresql/schema_creation"
+require_relative "postgresql/schema_definitions"
+require_relative "postgresql/schema_dumper"
+require_relative "postgresql/schema_statements"
+require_relative "postgresql/type_metadata"
+require_relative "postgresql/utils"
module ActiveRecord
module ConnectionHandling # :nodoc:
@@ -121,12 +121,6 @@ module ActiveRecord
include PostgreSQL::DatabaseStatements
include PostgreSQL::ColumnDumper
- # Returns true, since this connection adapter supports prepared statement
- # caching.
- def supports_statement_cache?
- true
- end
-
def supports_index_sort_order?
true
end
@@ -239,7 +233,9 @@ module ActiveRecord
# Is this connection alive and ready for queries?
def active?
- @connection.query "SELECT 1"
+ @lock.synchronize do
+ @connection.query "SELECT 1"
+ end
true
rescue PG::Error
false
@@ -247,26 +243,32 @@ module ActiveRecord
# Close then reopen the connection.
def reconnect!
- super
- @connection.reset
- configure_connection
+ @lock.synchronize do
+ super
+ @connection.reset
+ configure_connection
+ end
end
def reset!
- clear_cache!
- reset_transaction
- unless @connection.transaction_status == ::PG::PQTRANS_IDLE
- @connection.query "ROLLBACK"
+ @lock.synchronize do
+ clear_cache!
+ reset_transaction
+ unless @connection.transaction_status == ::PG::PQTRANS_IDLE
+ @connection.query "ROLLBACK"
+ end
+ @connection.query "DISCARD ALL"
+ configure_connection
end
- @connection.query "DISCARD ALL"
- configure_connection
end
# Disconnects from the database if already connected. Otherwise, this
# method does nothing.
def disconnect!
- super
- @connection.close rescue nil
+ @lock.synchronize do
+ super
+ @connection.close rescue nil
+ end
end
def native_database_types #:nodoc:
@@ -306,24 +308,18 @@ module ActiveRecord
postgresql_version >= 90400
end
- def supports_alter_constraint?
- # PostgreSQL 9.4 introduces ALTER TABLE ... ALTER CONSTRAINT but it has a bug and fixed in 9.4.2
- # https://www.postgresql.org/docs/9.4/static/release-9-4-2.html
- postgresql_version >= 90402
- end
-
def get_advisory_lock(lock_id) # :nodoc:
unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
raise(ArgumentError, "Postgres requires advisory lock ids to be a signed 64 bit integer")
end
- select_value("SELECT pg_try_advisory_lock(#{lock_id});")
+ query_value("SELECT pg_try_advisory_lock(#{lock_id})")
end
def release_advisory_lock(lock_id) # :nodoc:
unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
raise(ArgumentError, "Postgres requires advisory lock ids to be a signed 64 bit integer")
end
- select_value("SELECT pg_advisory_unlock(#{lock_id})")
+ query_value("SELECT pg_advisory_unlock(#{lock_id})")
end
def enable_extension(name)
@@ -340,15 +336,14 @@ module ActiveRecord
def extension_enabled?(name)
if supports_extensions?
- res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled",
- "SCHEMA"
+ res = exec_query("SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled", "SCHEMA")
res.cast_values.first
end
end
def extensions
if supports_extensions?
- exec_query("SELECT extname from pg_extension", "SCHEMA").cast_values
+ exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values
else
super
end
@@ -356,14 +351,14 @@ module ActiveRecord
# Returns the configured supported identifier length supported by PostgreSQL
def table_alias_length
- @max_identifier_length ||= select_value("SHOW max_identifier_length", "SCHEMA").to_i
+ @max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
end
alias index_name_length table_alias_length
# Set the authorized user for this session
def session_auth=(user)
clear_cache!
- exec_query "SET SESSION AUTHORIZATION #{user}"
+ execute("SET SESSION AUTHORIZATION #{user}")
end
def use_insert_returning?
@@ -462,7 +457,7 @@ module ActiveRecord
m.register_type "bytea", OID::Bytea.new
m.register_type "point", OID::Point.new
m.register_type "hstore", OID::Hstore.new
- m.register_type "json", OID::Json.new
+ m.register_type "json", Type::Json.new
m.register_type "jsonb", OID::Jsonb.new
m.register_type "cidr", OID::Cidr.new
m.register_type "inet", OID::Inet.new
@@ -553,7 +548,7 @@ module ActiveRecord
end
def has_default_function?(default_value, default)
- !default_value && (%r{\w+\(.*\)|\(.*\)::\w+} === default)
+ !default_value && %r{\w+\(.*\)|\(.*\)::\w+|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default)
end
def load_additional_types(type_map, oids = nil)
@@ -727,11 +722,6 @@ module ActiveRecord
end
end
- # Returns the current ID of a table's sequence.
- def last_insert_id_result(sequence_name)
- exec_query("SELECT currval('#{sequence_name}')", "SQL")
- end
-
# Returns the list of a table's column names, data types, and default values.
#
# The underlying query is roughly:
@@ -847,7 +837,6 @@ module ActiveRecord
ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql)
ActiveRecord::Type.register(:hstore, OID::Hstore, adapter: :postgresql)
ActiveRecord::Type.register(:inet, OID::Inet, adapter: :postgresql)
- ActiveRecord::Type.register(:json, OID::Json, adapter: :postgresql)
ActiveRecord::Type.register(:jsonb, OID::Jsonb, adapter: :postgresql)
ActiveRecord::Type.register(:money, OID::Money, adapter: :postgresql)
ActiveRecord::Type.register(:point, OID::Point, adapter: :postgresql)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
index 8066a05c5e..31e83f9260 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
@@ -2,6 +2,41 @@ module ActiveRecord
module ConnectionAdapters
module SQLite3
module SchemaStatements # :nodoc:
+ # Returns an array of indexes for the given table.
+ def indexes(table_name, name = nil)
+ if name
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing name to #indexes is deprecated without replacement.
+ MSG
+ end
+
+ exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row|
+ index_sql = query_value(<<-SQL, "SCHEMA")
+ SELECT sql
+ FROM sqlite_master
+ WHERE name = #{quote(row['name'])} AND type = 'index'
+ UNION ALL
+ SELECT sql
+ FROM sqlite_temp_master
+ WHERE name = #{quote(row['name'])} AND type = 'index'
+ SQL
+
+ /\sWHERE\s+(?<where>.+)$/i =~ index_sql
+
+ columns = exec_query("PRAGMA index_info(#{quote(row['name'])})", "SCHEMA").map do |col|
+ col["name"]
+ end
+
+ IndexDefinition.new(
+ table_name,
+ row["name"],
+ row["unique"] != 0,
+ columns,
+ where: where
+ )
+ end
+ end
+
private
def schema_creation
SQLite3::SchemaCreation.new(self)
@@ -32,7 +67,7 @@ module ActiveRecord
scope = quoted_scope(name, type: type)
scope[:type] ||= "'table','view'"
- sql = "SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence'"
+ sql = "SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence'".dup
sql << " AND name = #{scope[:name]}" if scope[:name]
sql << " AND type IN (#{scope[:type]})"
sql
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index c54b88f7d1..04129841e4 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -1,11 +1,11 @@
-require "active_record/connection_adapters/abstract_adapter"
-require "active_record/connection_adapters/statement_pool"
-require "active_record/connection_adapters/sqlite3/explain_pretty_printer"
-require "active_record/connection_adapters/sqlite3/quoting"
-require "active_record/connection_adapters/sqlite3/schema_creation"
-require "active_record/connection_adapters/sqlite3/schema_definitions"
-require "active_record/connection_adapters/sqlite3/schema_dumper"
-require "active_record/connection_adapters/sqlite3/schema_statements"
+require_relative "abstract_adapter"
+require_relative "statement_pool"
+require_relative "sqlite3/explain_pretty_printer"
+require_relative "sqlite3/quoting"
+require_relative "sqlite3/schema_creation"
+require_relative "sqlite3/schema_definitions"
+require_relative "sqlite3/schema_dumper"
+require_relative "sqlite3/schema_statements"
gem "sqlite3", "~> 1.3.6"
require "sqlite3"
@@ -105,12 +105,6 @@ module ActiveRecord
sqlite_version >= "3.8.0"
end
- # Returns true, since this connection adapter supports prepared statement
- # caching.
- def supports_statement_cache?
- true
- end
-
def requires_reloading?
true
end
@@ -175,7 +169,7 @@ module ActiveRecord
# REFERENTIAL INTEGRITY ====================================
def disable_referential_integrity # :nodoc:
- old = select_value("PRAGMA foreign_keys")
+ old = query_value("PRAGMA foreign_keys")
begin
execute("PRAGMA foreign_keys = OFF")
@@ -259,37 +253,6 @@ module ActiveRecord
# SCHEMA STATEMENTS ========================================
- # 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
- FROM sqlite_master
- WHERE name=#{quote(row['name'])} AND type='index'
- UNION ALL
- SELECT sql
- FROM sqlite_temp_master
- WHERE name=#{quote(row['name'])} AND type='index'
- SQL
- index_sql = exec_query(sql).first["sql"]
- match = /\sWHERE\s+(.+)$/i.match(index_sql)
- where = match[1] if match
- IndexDefinition.new(
- table_name,
- row["name"],
- row["unique"] != 0,
- exec_query("PRAGMA index_info('#{row['name']}')", "SCHEMA").map { |col|
- col["name"]
- }, nil, nil, where)
- end
- end
-
def primary_keys(table_name) # :nodoc:
pks = table_structure(table_name).select { |f| f["pk"] > 0 }
pks.sort_by { |f| f["pk"] }.map { |f| f["name"] }
@@ -374,7 +337,7 @@ module ActiveRecord
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 = exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
fk_info.map do |row|
options = {
column: row["from"],
@@ -386,6 +349,12 @@ module ActiveRecord
end
end
+ def insert_fixtures(rows, table_name)
+ rows.each do |row|
+ insert_fixture(row, table_name)
+ end
+ end
+
private
def table_structure(table_name)
@@ -474,7 +443,7 @@ module ActiveRecord
end
def sqlite_version
- @sqlite_version ||= SQLite3Adapter::Version.new(select_value("SELECT sqlite_version(*)"))
+ @sqlite_version ||= SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
end
def translate_exception(exception, message)
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index 2ede92feff..b8fbb489b6 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -1,6 +1,6 @@
module ActiveRecord
module ConnectionHandling
- RAILS_ENV = -> { (Rails.env if defined?(Rails.env)) || ENV["RAILS_ENV"] || ENV["RACK_ENV"] }
+ RAILS_ENV = -> { (Rails.env if defined?(Rails.env)) || ENV["RAILS_ENV"].presence || ENV["RACK_ENV"].presence }
DEFAULT_ENV = -> { RAILS_ENV.call || "default_env" }
# Establishes the connection to the database. Accepts a hash as input where
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 8f78330d4a..198c712abc 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -56,8 +56,7 @@ module ActiveRecord
# :singleton-method:
# Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
# dates and times from the database. This is set to :utc by default.
- mattr_accessor :default_timezone, instance_writer: false
- self.default_timezone = :utc
+ mattr_accessor :default_timezone, instance_writer: false, default: :utc
##
# :singleton-method:
@@ -67,16 +66,14 @@ module ActiveRecord
# ActiveRecord::Schema file which can be loaded into any database that
# supports migrations. Use :ruby if you want to have different database
# adapters for, e.g., your development and test environments.
- mattr_accessor :schema_format, instance_writer: false
- self.schema_format = :ruby
+ mattr_accessor :schema_format, instance_writer: false, default: :ruby
##
# :singleton-method:
# Specifies if an error should be raised if the query has an order being
# ignored when doing batch queries. Useful in applications where the
# scope being ignored is error-worthy, rather than a warning.
- mattr_accessor :error_on_ignored_order, instance_writer: false
- self.error_on_ignored_order = false
+ mattr_accessor :error_on_ignored_order, instance_writer: false, default: false
def self.error_on_ignored_order_or_limit
ActiveSupport::Deprecation.warn(<<-MSG.squish)
@@ -101,8 +98,7 @@ module ActiveRecord
##
# :singleton-method:
# Specify whether or not to use timestamps for migration versions
- mattr_accessor :timestamped_migrations, instance_writer: false
- self.timestamped_migrations = true
+ mattr_accessor :timestamped_migrations, instance_writer: false, default: true
##
# :singleton-method:
@@ -110,8 +106,7 @@ module ActiveRecord
# db:migrate rake task. This is true by default, which is useful for the
# development environment. This should ideally be false in the production
# environment where dumping schema is rarely needed.
- mattr_accessor :dump_schema_after_migration, instance_writer: false
- self.dump_schema_after_migration = true
+ mattr_accessor :dump_schema_after_migration, instance_writer: false, default: true
##
# :singleton-method:
@@ -120,8 +115,7 @@ module ActiveRecord
# schema_search_path are dumped. Use :all to dump all schemas regardless
# of schema_search_path, or a string of comma separated schemas for a
# custom list.
- mattr_accessor :dump_schemas, instance_writer: false
- self.dump_schemas = :schema_search_path
+ mattr_accessor :dump_schemas, instance_writer: false, default: :schema_search_path
##
# :singleton-method:
@@ -130,7 +124,6 @@ module ActiveRecord
# be used to identify queries which load thousands of records and
# potentially cause memory bloat.
mattr_accessor :warn_on_records_fetched_greater_than, instance_writer: false
- self.warn_on_records_fetched_greater_than = nil
mattr_accessor :maintain_test_schema, instance_accessor: false
diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb
index 08d42f3dd4..3a9625092e 100644
--- a/activerecord/lib/active_record/dynamic_matchers.rb
+++ b/activerecord/lib/active_record/dynamic_matchers.rb
@@ -1,16 +1,14 @@
-
module ActiveRecord
module DynamicMatchers #:nodoc:
- def respond_to_missing?(name, include_private = false)
- if self == Base
- super
- else
- match = Method.match(self, name)
- match && match.valid? || super
- end
- end
-
private
+ def respond_to_missing?(name, _)
+ if self == Base
+ super
+ else
+ match = Method.match(self, name)
+ match && match.valid? || super
+ end
+ end
def method_missing(name, *arguments, &block)
match = Method.match(self, name)
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 0ab03b2ab3..12ef58a941 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -95,8 +95,7 @@ module ActiveRecord
module Enum
def self.extended(base) # :nodoc:
- base.class_attribute(:defined_enums, instance_writer: false)
- base.defined_enums = {}
+ base.class_attribute(:defined_enums, instance_writer: false, default: {})
end
def inherited(base) # :nodoc:
@@ -154,11 +153,12 @@ module ActiveRecord
definitions.each do |name, values|
# statuses = { }
enum_values = ActiveSupport::HashWithIndifferentAccess.new
- name = name.to_sym
+ name = name.to_s
# def self.statuses() statuses end
- detect_enum_conflict!(name, name.to_s.pluralize, true)
- klass.singleton_class.send(:define_method, name.to_s.pluralize) { enum_values }
+ detect_enum_conflict!(name, name.pluralize, true)
+ singleton_class.send(:define_method, name.pluralize) { enum_values }
+ defined_enums[name] = enum_values
detect_enum_conflict!(name, name)
detect_enum_conflict!(name, "#{name}=")
@@ -170,7 +170,7 @@ module ActiveRecord
_enum_methods_module.module_eval do
pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
- pairs.each do |value, i|
+ pairs.each do |label, value|
if enum_prefix == true
prefix = "#{name}_"
elsif enum_prefix
@@ -182,23 +182,23 @@ module ActiveRecord
suffix = "_#{enum_suffix}"
end
- value_method_name = "#{prefix}#{value}#{suffix}"
- enum_values[value] = i
+ value_method_name = "#{prefix}#{label}#{suffix}"
+ enum_values[label] = value
+ label = label.to_s
- # def active?() status == 0 end
+ # def active?() status == "active" end
klass.send(:detect_enum_conflict!, name, "#{value_method_name}?")
- define_method("#{value_method_name}?") { self[attr] == value.to_s }
+ define_method("#{value_method_name}?") { self[attr] == label }
- # def active!() update! status: :active end
+ # def active!() update!(status: 0) end
klass.send(:detect_enum_conflict!, name, "#{value_method_name}!")
define_method("#{value_method_name}!") { update!(attr => value) }
- # scope :active, -> { where status: 0 }
+ # scope :active, -> { where(status: 0) }
klass.send(:detect_enum_conflict!, name, value_method_name, true)
klass.scope value_method_name, -> { where(attr => value) }
end
end
- defined_enums[name.to_s] = enum_values
end
end
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 18fac5af1b..60d4fb70e0 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -105,7 +105,7 @@ module ActiveRecord
class WrappedDatabaseException < StatementInvalid
end
- # Raised when a record cannot be inserted because it would violate a uniqueness constraint.
+ # Raised when a record cannot be inserted or updated because it would violate a uniqueness constraint.
class RecordNotUnique < WrappedDatabaseException
end
diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb
index 8f7ae2c33c..eff5990f3a 100644
--- a/activerecord/lib/active_record/explain.rb
+++ b/activerecord/lib/active_record/explain.rb
@@ -1,4 +1,4 @@
-require "active_record/explain_registry"
+require_relative "explain_registry"
module ActiveRecord
module Explain
@@ -16,7 +16,7 @@ module ActiveRecord
# Returns a formatted string ready to be logged.
def exec_explain(queries) # :nodoc:
str = queries.map do |sql, binds|
- msg = "EXPLAIN for: #{sql}"
+ msg = "EXPLAIN for: #{sql}".dup
unless binds.empty?
msg << " "
msg << binds.map { |attr| render_bind(attr) }.inspect
diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb
index abd8cfc8f2..928720d011 100644
--- a/activerecord/lib/active_record/explain_subscriber.rb
+++ b/activerecord/lib/active_record/explain_subscriber.rb
@@ -1,5 +1,5 @@
require "active_support/notifications"
-require "active_record/explain_registry"
+require_relative "explain_registry"
module ActiveRecord
class ExplainSubscriber # :nodoc:
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index e79167d568..c9e97d9d2b 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -4,8 +4,8 @@ require "zlib"
require "set"
require "active_support/dependencies"
require "active_support/core_ext/digest/uuid"
-require "active_record/fixture_set/file"
-require "active_record/errors"
+require_relative "fixture_set/file"
+require_relative "errors"
module ActiveRecord
class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
@@ -70,13 +70,32 @@ module ActiveRecord
# test. To ensure consistent data, the environment deletes the fixtures before running the load.
#
# In addition to being available in the database, the fixture's data may also be accessed by
- # using a special dynamic method, which has the same name as the model, and accepts the
- # name of the fixture to instantiate:
+ # using a special dynamic method, which has the same name as the model.
#
- # test "find" do
+ # Passing in a fixture name to this dynamic method returns the fixture matching this name:
+ #
+ # test "find one" do
# assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
# end
#
+ # Passing in multiple fixture names returns all fixtures matching these names:
+ #
+ # test "find all by name" do
+ # assert_equal 2, web_sites(:rubyonrails, :google).length
+ # end
+ #
+ # Passing in no arguments returns all fixtures:
+ #
+ # test "find all" do
+ # assert_equal 2, web_sites.length
+ # end
+ #
+ # Passing in any fixture name that does not exist will raise <tt>StandardError</tt>:
+ #
+ # test "find by name that does not exist" do
+ # assert_raise(StandardError) { web_sites(:reddit) }
+ # end
+ #
# Alternatively, you may enable auto-instantiation of the fixture data. For instance, take the
# following tests:
#
@@ -473,8 +492,7 @@ module ActiveRecord
end
end
- cattr_accessor :all_loaded_fixtures
- self.all_loaded_fixtures = {}
+ cattr_accessor :all_loaded_fixtures, default: {}
class ClassCache
def initialize(class_names, config)
@@ -549,9 +567,7 @@ module ActiveRecord
end
table_rows.each do |fixture_set_name, rows|
- rows.each do |row|
- conn.insert_fixture(row, fixture_set_name)
- end
+ conn.insert_fixtures(rows, fixture_set_name)
end
# Cap primary key sequences to max(pk).
@@ -859,20 +875,12 @@ module ActiveRecord
included do
class_attribute :fixture_path, instance_writer: false
- class_attribute :fixture_table_names
- class_attribute :fixture_class_names
- class_attribute :use_transactional_tests
- class_attribute :use_instantiated_fixtures # true, false, or :no_instances
- class_attribute :pre_loaded_fixtures
- class_attribute :config
-
- self.fixture_table_names = []
- self.use_instantiated_fixtures = false
- self.pre_loaded_fixtures = false
- self.config = ActiveRecord::Base
-
- self.fixture_class_names = {}
- self.use_transactional_tests = true
+ class_attribute :fixture_table_names, default: []
+ class_attribute :fixture_class_names, default: {}
+ class_attribute :use_transactional_tests, default: true
+ class_attribute :use_instantiated_fixtures, default: false # true, false, or :no_instances
+ class_attribute :pre_loaded_fixtures, default: false
+ class_attribute :config, default: ActiveRecord::Base
end
module ClassMethods
@@ -909,6 +917,8 @@ module ActiveRecord
define_method(accessor_name) do |*fixture_names|
force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
+ return_single_record = fixture_names.size == 1
+ fixture_names = @loaded_fixtures[fs_name].fixtures.keys if fixture_names.empty?
@fixture_cache[fs_name] ||= {}
@@ -923,7 +933,7 @@ module ActiveRecord
end
end
- instances.size == 1 ? instances.first : instances
+ return_single_record ? instances.first : instances
end
private accessor_name
end
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index fbdaeaae51..5776807507 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -38,8 +38,7 @@ module ActiveRecord
included do
# Determines whether to store the full constant name including namespace when using STI.
# This is true, by default.
- class_attribute :store_full_sti_class, instance_writer: false
- self.store_full_sti_class = true
+ class_attribute :store_full_sti_class, instance_writer: false, default: true
end
module ClassMethods
@@ -217,7 +216,7 @@ module ActiveRecord
def subclass_from_attributes(attrs)
attrs = attrs.to_h if attrs.respond_to?(:permitted?)
if attrs.is_a?(Hash)
- subclass_name = attrs.with_indifferent_access[inheritance_column]
+ subclass_name = attrs[inheritance_column] || attrs[inheritance_column.to_sym]
if subclass_name.present?
find_sti_class(subclass_name)
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
index 8e71b60b29..cf954852bc 100644
--- a/activerecord/lib/active_record/integration.rb
+++ b/activerecord/lib/active_record/integration.rb
@@ -7,12 +7,19 @@ module ActiveRecord
included do
##
# :singleton-method:
- # Indicates the format used to generate the timestamp in the cache key.
- # Accepts any of the symbols in <tt>Time::DATE_FORMATS</tt>.
+ # Indicates the format used to generate the timestamp in the cache key, if
+ # versioning is off. Accepts any of the symbols in <tt>Time::DATE_FORMATS</tt>.
#
# This is +:usec+, by default.
- class_attribute :cache_timestamp_format, instance_writer: false
- self.cache_timestamp_format = :usec
+ class_attribute :cache_timestamp_format, instance_writer: false, default: :usec
+
+ ##
+ # :singleton-method:
+ # Indicates whether to use a stable #cache_key method that is accompanied
+ # by a changing version in the #cache_version method.
+ #
+ # This is +false+, by default until Rails 6.0.
+ class_attribute :cache_versioning, instance_writer: false, default: false
end
# Returns a +String+, which Action Pack uses for constructing a URL to this
@@ -42,35 +49,65 @@ module ActiveRecord
id && id.to_s # Be sure to stringify the id for routes
end
- # Returns a cache key that can be used to identify this record.
+ # Returns a stable cache key that can be used to identify this record.
#
# Product.new.cache_key # => "products/new"
- # Product.find(5).cache_key # => "products/5" (updated_at not available)
- # Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
+ # Product.find(5).cache_key # => "products/5"
#
- # You can also pass a list of named timestamps, and the newest in the list will be
- # used to generate the key:
+ # If ActiveRecord::Base.cache_versioning is turned off, as it was in Rails 5.1 and earlier,
+ # the cache key will also include a version.
#
- # Person.find(5).cache_key(:updated_at, :last_reviewed_at)
+ # Product.cache_versioning = false
+ # Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
def cache_key(*timestamp_names)
if new_record?
"#{model_name.cache_key}/new"
else
- timestamp = if timestamp_names.any?
- max_updated_column_timestamp(timestamp_names)
+ if cache_version && timestamp_names.none?
+ "#{model_name.cache_key}/#{id}"
else
- max_updated_column_timestamp
- end
+ timestamp = if timestamp_names.any?
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Specifying a timestamp name for #cache_key has been deprecated in favor of
+ the explicit #cache_version method that can be overwritten.
+ MSG
- if timestamp
- timestamp = timestamp.utc.to_s(cache_timestamp_format)
- "#{model_name.cache_key}/#{id}-#{timestamp}"
- else
- "#{model_name.cache_key}/#{id}"
+ max_updated_column_timestamp(timestamp_names)
+ else
+ max_updated_column_timestamp
+ end
+
+ if timestamp
+ timestamp = timestamp.utc.to_s(cache_timestamp_format)
+ "#{model_name.cache_key}/#{id}-#{timestamp}"
+ else
+ "#{model_name.cache_key}/#{id}"
+ end
end
end
end
+ # Returns a cache version that can be used together with the cache key to form
+ # a recyclable caching scheme. By default, the #updated_at column is used for the
+ # cache_version, but this method can be overwritten to return something else.
+ #
+ # Note, this method will return nil if ActiveRecord::Base.cache_versioning is set to
+ # +false+ (which it is by default until Rails 6.0).
+ def cache_version
+ if cache_versioning && timestamp = try(:updated_at)
+ timestamp.utc.to_s(:usec)
+ end
+ end
+
+ # Returns a cache key along with the version.
+ def cache_key_with_version
+ if version = cache_version
+ "#{cache_key}-#{version}"
+ else
+ cache_key
+ end
+ end
+
module ClassMethods
# Defines your model's +to_param+ method to generate "pretty" URLs
# using +method_name+, which can be any attribute or method that
diff --git a/activerecord/lib/active_record/internal_metadata.rb b/activerecord/lib/active_record/internal_metadata.rb
index 25ee9d6bfe..89e5d153b8 100644
--- a/activerecord/lib/active_record/internal_metadata.rb
+++ b/activerecord/lib/active_record/internal_metadata.rb
@@ -1,5 +1,5 @@
-require "active_record/scoping/default"
-require "active_record/scoping/named"
+require_relative "scoping/default"
+require_relative "scoping/named"
module ActiveRecord
# This class is used to create a table that keeps track of values and keys such
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 78ce9f8291..522da6a571 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -51,8 +51,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- class_attribute :lock_optimistically, instance_writer: false
- self.lock_optimistically = true
+ class_attribute :lock_optimistically, instance_writer: false, default: true
end
def locking_enabled? #:nodoc:
@@ -63,8 +62,8 @@ module ActiveRecord
def increment_lock
lock_col = self.class.locking_column
- previous_lock_value = send(lock_col).to_i
- send(lock_col + "=", previous_lock_value + 1)
+ previous_lock_value = send(lock_col)
+ send("#{lock_col}=", previous_lock_value + 1)
end
def _create_record(attribute_names = self.attribute_names, *)
@@ -108,7 +107,8 @@ module ActiveRecord
# If something went wrong, revert the locking_column value.
rescue Exception
- send(lock_col + "=", previous_lock_value.to_i)
+ send("#{lock_col}=", previous_lock_value.to_i)
+
raise
end
end
@@ -128,7 +128,7 @@ module ActiveRecord
if locking_enabled?
locking_column = self.class.locking_column
- relation = relation.where(locking_column => _read_attribute(locking_column))
+ relation = relation.where(locking_column => read_attribute_before_type_cast(locking_column))
end
relation
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index 2297c77835..e39ca5f6dc 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -29,7 +29,7 @@ module ActiveRecord
binds = nil
unless (payload[:binds] || []).empty?
- casted_params = type_casted_binds(payload[:binds], payload[:type_casted_binds])
+ casted_params = type_casted_binds(payload[:type_casted_binds])
binds = " " + payload[:binds].zip(casted_params).map { |attr, value|
render_bind(attr, value)
}.inspect
@@ -42,9 +42,8 @@ module ActiveRecord
end
private
-
- def type_casted_binds(binds, casted_binds)
- casted_binds || ActiveRecord::Base.connection.type_casted_binds(binds)
+ def type_casted_binds(casted_binds)
+ casted_binds.respond_to?(:call) ? casted_binds.call : casted_binds
end
def render_bind(attr, value)
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 4e1df1432c..42220b9a5e 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -157,7 +157,7 @@ module ActiveRecord
class ProtectedEnvironmentError < ActiveRecordError #:nodoc:
def initialize(env = "production")
- msg = "You are attempting to run a destructive action against your '#{env}' database.\n"
+ msg = "You are attempting to run a destructive action against your '#{env}' database.\n".dup
msg << "If you are sure you want to continue, run the same command with the environment variable:\n"
msg << "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
super(msg)
@@ -166,7 +166,7 @@ module ActiveRecord
class EnvironmentMismatchError < ActiveRecordError
def initialize(current: nil, stored: nil)
- msg = "You are attempting to modify a database that was last run in `#{ stored }` environment.\n"
+ msg = "You are attempting to modify a database that was last run in `#{ stored }` environment.\n".dup
msg << "You are running in `#{ current }` environment. "
msg << "If you are sure you want to continue, first set the environment using:\n\n"
msg << " bin/rails db:environment:set"
@@ -863,15 +863,17 @@ module ActiveRecord
source_migrations.each do |migration|
source = File.binread(migration.filename)
inserted_comment = "# This migration comes from #{scope} (originally #{migration.version})\n"
- if /\A#.*\b(?:en)?coding:\s*\S+/ =~ source
+ magic_comments = "".dup
+ loop do
# If we have a magic comment in the original migration,
# insert our comment after the first newline(end of the magic comment line)
# so the magic keep working.
# Note that magic comments must be at the first line(except sh-bang).
- source[/\n/] = "\n#{inserted_comment}"
- else
- source = "#{inserted_comment}#{source}"
+ source.sub!(/\A(?:#.*\b(?:en)?coding:\s*\S+|#\s*frozen_string_literal:\s*(?:true|false)).*\n/) do |magic_comment|
+ magic_comments << magic_comment; ""
+ end || break
end
+ source = "#{magic_comments}#{inserted_comment}#{source}"
if duplicate = destination_migrations.detect { |m| m.name == migration.name }
if options[:on_skip] && duplicate.scope != scope.to_s
@@ -1104,13 +1106,21 @@ module ActiveRecord
def move(direction, migrations_paths, steps)
migrator = new(direction, migrations(migrations_paths))
- start_index = migrator.migrations.index(migrator.current_migration)
- if start_index
- finish = migrator.migrations[start_index + steps]
- version = finish ? finish.version : 0
- send(direction, migrations_paths, version)
+ if current_version != 0 && !migrator.current_migration
+ raise UnknownMigrationVersionError.new(current_version)
end
+
+ start_index =
+ if current_version == 0
+ 0
+ else
+ migrator.migrations.index(migrator.current_migration)
+ end
+
+ finish = migrator.migrations[start_index + steps]
+ version = finish ? finish.version : 0
+ send(direction, migrations_paths, version)
end
end
@@ -1231,7 +1241,7 @@ module ActiveRecord
record_version_state_after_migrating(migration.version)
end
rescue => e
- msg = "An error has occurred, "
+ msg = "An error has occurred, ".dup
msg << "this and " if use_transaction?(migration)
msg << "all later migrations canceled:\n\n#{e}"
raise StandardError, msg, e.backtrace
diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb
index 03103bba98..f9cf59b283 100644
--- a/activerecord/lib/active_record/migration/command_recorder.rb
+++ b/activerecord/lib/active_record/migration/command_recorder.rb
@@ -92,10 +92,6 @@ module ActiveRecord
send(method, args, &block)
end
- def respond_to_missing?(*args) # :nodoc:
- super || delegate.respond_to?(*args)
- end
-
ReversibleAndIrreversibleMethods.each do |method|
class_eval <<-EOV, __FILE__, __LINE__ + 1
def #{method}(*args, &block) # def create_table(*args, &block)
@@ -225,10 +221,14 @@ module ActiveRecord
[:add_foreign_key, reversed_args]
end
+ def respond_to_missing?(method, _)
+ super || delegate.respond_to?(method)
+ end
+
# Forwards any missing method call to the \target.
def method_missing(method, *args, &block)
- if @delegate.respond_to?(method)
- @delegate.send(method, *args, &block)
+ if delegate.respond_to?(method)
+ delegate.public_send(method, *args, &block)
else
super
end
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 54216caaaf..14e0f5bff7 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -1,3 +1,5 @@
+require "monitor"
+
module ActiveRecord
module ModelSchema
extend ActiveSupport::Concern
@@ -128,30 +130,19 @@ module ActiveRecord
included do
mattr_accessor :primary_key_prefix_type, instance_writer: false
- class_attribute :table_name_prefix, instance_writer: false
- self.table_name_prefix = ""
-
- class_attribute :table_name_suffix, instance_writer: false
- self.table_name_suffix = ""
-
- class_attribute :schema_migrations_table_name, instance_accessor: false
- self.schema_migrations_table_name = "schema_migrations"
-
- class_attribute :internal_metadata_table_name, instance_accessor: false
- self.internal_metadata_table_name = "ar_internal_metadata"
-
- class_attribute :protected_environments, instance_accessor: false
- self.protected_environments = ["production"]
-
- class_attribute :pluralize_table_names, instance_writer: false
- self.pluralize_table_names = true
-
- class_attribute :ignored_columns, instance_accessor: false
- self.ignored_columns = [].freeze
+ class_attribute :table_name_prefix, instance_writer: false, default: ""
+ class_attribute :table_name_suffix, instance_writer: false, default: ""
+ class_attribute :schema_migrations_table_name, instance_accessor: false, default: "schema_migrations"
+ class_attribute :internal_metadata_table_name, instance_accessor: false, default: "ar_internal_metadata"
+ class_attribute :protected_environments, instance_accessor: false, default: [ "production" ]
+ class_attribute :pluralize_table_names, instance_writer: false, default: true
+ class_attribute :ignored_columns, instance_accessor: false, default: [].freeze
self.inheritance_column = "type"
delegate :type_for_attribute, to: :class
+
+ initialize_load_schema_monitor
end
# Derives the join table name for +first_table+ and +second_table+. The
@@ -377,7 +368,7 @@ module ActiveRecord
# default values when instantiating the Active Record object for this table.
def column_defaults
load_schema
- _default_attributes.to_hash
+ @column_defaults ||= _default_attributes.to_hash
end
def _default_attributes # :nodoc:
@@ -435,15 +426,31 @@ module ActiveRecord
initialize_find_by_cache
end
+ protected
+
+ def initialize_load_schema_monitor
+ @load_schema_monitor = Monitor.new
+ end
+
private
+ def inherited(child_class)
+ super
+ child_class.initialize_load_schema_monitor
+ end
+
def schema_loaded?
- defined?(@columns_hash) && @columns_hash
+ defined?(@schema_loaded) && @schema_loaded
end
def load_schema
- unless schema_loaded?
+ return if schema_loaded?
+ @load_schema_monitor.synchronize do
+ return if defined?(@columns_hash) && @columns_hash
+
load_schema!
+
+ @schema_loaded = true
end
end
@@ -466,10 +473,12 @@ module ActiveRecord
@attribute_types = nil
@content_columns = nil
@default_attributes = nil
+ @column_defaults = nil
@inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column
@attributes_builder = nil
@columns = nil
@columns_hash = nil
+ @schema_loaded = false
@attribute_names = nil
@yaml_encoder = nil
direct_descendants.each do |descendant|
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 01ecd79b8f..917bc76993 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -10,8 +10,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- class_attribute :nested_attributes_options, instance_writer: false
- self.nested_attributes_options = {}
+ class_attribute :nested_attributes_options, instance_writer: false, default: {}
end
# = Active Record Nested Attributes
@@ -458,7 +457,7 @@ module ActiveRecord
end
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
- raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
+ raise ArgumentError, "Hash or Array expected for attribute `#{association_name}`, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
end
check_record_limit!(options[:limit], attributes_collection)
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 7ceb7d1a55..b2dba5516e 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -100,6 +100,10 @@ module ActiveRecord
!(@new_record || @destroyed)
end
+ ##
+ # :call-seq:
+ # save(*args)
+ #
# Saves the model.
#
# If the model is new, a record gets created in the database, otherwise
@@ -121,12 +125,16 @@ module ActiveRecord
#
# Attributes marked as readonly are silently ignored if the record is
# being updated.
- def save(*args)
- create_or_update(*args)
+ def save(*args, &block)
+ create_or_update(*args, &block)
rescue ActiveRecord::RecordInvalid
false
end
+ ##
+ # :call-seq:
+ # save!(*args)
+ #
# Saves the model.
#
# If the model is new, a record gets created in the database, otherwise
@@ -150,8 +158,8 @@ module ActiveRecord
# 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))
+ def save!(*args, &block)
+ create_or_update(*args, &block) || raise(RecordNotSaved.new("Failed to save the record", self))
end
# Deletes the record in the database and freezes this instance to
@@ -518,7 +526,7 @@ module ActiveRecord
if locking_enabled?
locking_column = self.class.locking_column
- scope = scope.where(locking_column => _read_attribute(locking_column))
+ scope = scope.where(locking_column => read_attribute_before_type_cast(locking_column))
changes[locking_column] = increment_lock
end
@@ -550,9 +558,9 @@ module ActiveRecord
self.class.unscoped.where(self.class.primary_key => id)
end
- def create_or_update(*args)
+ def create_or_update(*args, &block)
_raise_readonly_record_error if readonly?
- result = new_record? ? _create_record : _update_record(*args)
+ result = new_record? ? _create_record(&block) : _update_record(*args, &block)
result != false
end
@@ -567,6 +575,9 @@ module ActiveRecord
rows_affected = self.class.unscoped._update_record attributes_values, id, id_in_database
@_trigger_update_callback = rows_affected > 0
end
+
+ yield(self) if block_given?
+
rows_affected
end
@@ -579,6 +590,9 @@ module ActiveRecord
self.id ||= new_id if self.class.primary_key
@new_record = false
+
+ yield(self) if block_given?
+
id
end
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index ec246e97bc..e4c2e1f86f 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -5,20 +5,20 @@ module ActiveRecord
# Enable the query cache within the block if Active Record is configured.
# If it's not, it will execute the given block.
def cache(&block)
- if connected?
- connection.cache(&block)
- else
+ if configurations.empty?
yield
+ else
+ connection.cache(&block)
end
end
# Disable the query cache within the block if Active Record is configured.
# If it's not, it will execute the given block.
def uncached(&block)
- if connected?
- connection.uncached(&block)
- else
+ if configurations.empty?
yield
+ else
+ connection.uncached(&block)
end
end
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index c4a22398f0..b16e178358 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -8,7 +8,7 @@ module ActiveRecord
delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, to: :all
delegate :find_each, :find_in_batches, :in_batches, to: :all
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :left_joins, :left_outer_joins, :or,
- :where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly,
+ :where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly, :extending,
:having, :create_with, :distinct, :references, :none, :unscope, :merge, to: :all
delegate :count, :average, :minimum, :maximum, :sum, :calculate, to: :all
delegate :pluck, :ids, to: :all
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 73518ca144..9cca103a18 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -48,8 +48,8 @@ module ActiveRecord
# to avoid cross references when loading a constant for the
# first time. Also, make it output to STDERR.
console do |app|
- require "active_record/railties/console_sandbox" if app.sandbox?
- require "active_record/base"
+ require_relative "railties/console_sandbox" if app.sandbox?
+ require_relative "base"
unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDERR, STDOUT)
console = ActiveSupport::Logger.new(STDERR)
Rails.logger.extend ActiveSupport::Logger.broadcast console
@@ -57,7 +57,7 @@ module ActiveRecord
end
runner do
- require "active_record/base"
+ require_relative "base"
end
initializer "active_record.initialize_timezone" do
@@ -101,7 +101,7 @@ module ActiveRecord
initializer "active_record.warn_on_records_fetched_greater_than" do
if config.active_record.warn_on_records_fetched_greater_than
ActiveSupport.on_load(:active_record) do
- require "active_record/relation/record_fetch_warning"
+ require_relative "relation/record_fetch_warning"
end
end
end
@@ -139,7 +139,7 @@ end_warning
# Expose database runtime to controller for logging.
initializer "active_record.log_runtime" do
- require "active_record/railties/controller_runtime"
+ require_relative "railties/controller_runtime"
ActiveSupport.on_load(:action_controller) do
include ActiveRecord::Railties::ControllerRuntime
end
diff --git a/activerecord/lib/active_record/railties/controller_runtime.rb b/activerecord/lib/active_record/railties/controller_runtime.rb
index 8658188623..4030cdc158 100644
--- a/activerecord/lib/active_record/railties/controller_runtime.rb
+++ b/activerecord/lib/active_record/railties/controller_runtime.rb
@@ -1,5 +1,5 @@
require "active_support/core_ext/module/attr_internal"
-require "active_record/log_subscriber"
+require_relative "../log_subscriber"
module ActiveRecord
module Railties # :nodoc:
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index f3e2df8786..abc7323341 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -93,7 +93,7 @@ db_namespace = namespace :db do
# desc 'Runs the "up" for a given migration VERSION.'
task up: [:environment, :load_config] do
- raise "VERSION is required" if ENV["VERSION"] && ENV["VERSION"].empty?
+ raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty?
version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
ActiveRecord::Migrator.run(:up, ActiveRecord::Tasks::DatabaseTasks.migrations_paths, version)
@@ -102,7 +102,7 @@ db_namespace = namespace :db do
# desc 'Runs the "down" for a given migration VERSION.'
task down: [:environment, :load_config] do
- raise "VERSION is required - To go down one migration, use db:rollback" if ENV["VERSION"] && ENV["VERSION"].empty?
+ raise "VERSION is required - To go down one migration, use db:rollback" if !ENV["VERSION"] || ENV["VERSION"].empty?
version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
ActiveRecord::Migrator.run(:down, ActiveRecord::Tasks::DatabaseTasks.migrations_paths, version)
db_namespace["_dump"].invoke
@@ -187,7 +187,7 @@ db_namespace = namespace :db do
namespace :fixtures do
desc "Loads fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
task load: [:environment, :load_config] do
- require "active_record/fixtures"
+ require_relative "../fixtures"
base_dir = ActiveRecord::Tasks::DatabaseTasks.fixtures_path
@@ -209,7 +209,7 @@ db_namespace = namespace :db do
# desc "Search for a fixture given a LABEL or ID. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
task identify: [:environment, :load_config] do
- require "active_record/fixtures"
+ require_relative "../fixtures"
label, id = ENV["LABEL"], ENV["ID"]
raise "LABEL or ID required" if label.blank? && id.blank?
@@ -235,7 +235,7 @@ db_namespace = namespace :db do
namespace :schema do
desc "Creates a db/schema.rb file that is portable against any DB supported by Active Record"
task dump: [:environment, :load_config] do
- require "active_record/schema_dumper"
+ require_relative "../schema_dumper"
filename = ENV["SCHEMA"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema.rb")
File.open(filename, "w:utf-8") do |file|
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
diff --git a/activerecord/lib/active_record/readonly_attributes.rb b/activerecord/lib/active_record/readonly_attributes.rb
index 6274996ab8..af6473d250 100644
--- a/activerecord/lib/active_record/readonly_attributes.rb
+++ b/activerecord/lib/active_record/readonly_attributes.rb
@@ -3,8 +3,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- class_attribute :_attr_readonly, instance_accessor: false
- self._attr_readonly = []
+ class_attribute :_attr_readonly, instance_accessor: false, default: []
end
module ClassMethods
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 24ca8b0be4..a453ca55c7 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -8,10 +8,8 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- class_attribute :_reflections, instance_writer: false
- class_attribute :aggregate_reflections, instance_writer: false
- self._reflections = {}
- self.aggregate_reflections = {}
+ class_attribute :_reflections, instance_writer: false, default: {}
+ class_attribute :aggregate_reflections, instance_writer: false, default: {}
end
def self.create(macro, name, scope, options, ar)
@@ -173,7 +171,7 @@ module ActiveRecord
JoinKeys = Struct.new(:key, :foreign_key) # :nodoc:
def join_keys
- get_join_keys klass
+ @join_keys ||= get_join_keys(klass)
end
# Returns a list of scopes that should be applied for this Reflection
@@ -187,10 +185,34 @@ module ActiveRecord
end
deprecate :scope_chain
+ def build_join_constraint(table, foreign_table)
+ key = join_keys.key
+ foreign_key = join_keys.foreign_key
+
+ constraint = table[key].eq(foreign_table[foreign_key])
+
+ if klass.finder_needs_type_condition?
+ table.create_and([constraint, klass.send(:type_condition, table)])
+ else
+ constraint
+ end
+ end
+
+ def join_scope(table, foreign_klass)
+ predicate_builder = predicate_builder(table)
+ scope_chain_items = join_scopes(table, predicate_builder)
+ klass_scope = klass_join_scope(table, predicate_builder)
+
+ if type
+ klass_scope.where!(type => foreign_klass.base_class.sti_name)
+ end
+
+ scope_chain_items.inject(klass_scope, &:merge!)
+ end
+
def join_scopes(table, predicate_builder) # :nodoc:
if scope
- [ActiveRecord::Relation.create(klass, table, predicate_builder)
- .instance_exec(&scope)]
+ [build_scope(table, predicate_builder).instance_exec(&scope)]
else
[]
end
@@ -199,20 +221,15 @@ module ActiveRecord
def klass_join_scope(table, predicate_builder) # :nodoc:
if klass.current_scope
klass.current_scope.clone.tap { |scope|
- scope.joins_values = []
+ scope.joins_values = scope.left_outer_joins_values = [].freeze
}
else
- relation = ActiveRecord::Relation.create(
- klass,
- table,
- predicate_builder,
- )
- klass.send(:build_default_scope, relation)
+ klass.default_scoped(build_scope(table, predicate_builder))
end
end
def constraints
- chain.map(&:scopes).flatten
+ chain.flat_map(&:scopes)
end
def counter_cache_column
@@ -289,7 +306,19 @@ module ActiveRecord
JoinKeys.new(join_pk(association_klass), join_fk)
end
+ def build_scope(table, predicate_builder = predicate_builder(table))
+ Relation.create(klass, table, predicate_builder)
+ end
+
+ protected
+ def actual_source_reflection # FIXME: this is a horrible name
+ self
+ end
+
private
+ def predicate_builder(table)
+ PredicateBuilder.new(TableMetadata.new(klass, table))
+ end
def join_pk(_)
foreign_key
@@ -567,7 +596,7 @@ module ActiveRecord
end
VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to]
- INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :polymorphic, :foreign_key]
+ INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :foreign_key]
def add_as_source(seed)
seed
@@ -581,11 +610,9 @@ module ActiveRecord
seed + [self]
end
- protected
-
- def actual_source_reflection # FIXME: this is a horrible name
- self
- end
+ def extensions
+ Array(options[:extend])
+ end
private
@@ -640,9 +667,8 @@ module ActiveRecord
# us from being able to guess the inverse automatically. First, the
# <tt>inverse_of</tt> option cannot be set to false. Second, we must
# have <tt>has_many</tt>, <tt>has_one</tt>, <tt>belongs_to</tt> associations.
- # Third, we must not have options such as <tt>:polymorphic</tt> or
- # <tt>:foreign_key</tt> which prevent us from correctly guessing the
- # inverse association.
+ # Third, we must not have options such as <tt>:foreign_key</tt>
+ # which prevent us from correctly guessing the inverse association.
#
# Anything with a scope can additionally ruin our attempt at finding an
# inverse, so we exclude reflections with scopes.
@@ -745,10 +771,6 @@ module ActiveRecord
end
class HasAndBelongsToManyReflection < AssociationReflection # :nodoc:
- def initialize(name, scope, options, active_record)
- super
- end
-
def macro; :has_and_belongs_to_many; end
def collection?
@@ -759,7 +781,6 @@ module ActiveRecord
# 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
delegate :foreign_key, :foreign_type, :association_foreign_key,
:active_record_primary_key, :type, :get_join_keys, to: :source_reflection
@@ -985,19 +1006,23 @@ module ActiveRecord
collect_join_reflections(seed + [self])
end
- def collect_join_reflections(seed)
- a = source_reflection.add_as_source seed
- if options[:source_type]
- through_reflection.add_as_polymorphic_through self, a
- else
- through_reflection.add_as_through a
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
+ # Workaround for Ruby 2.2 "private attribute?" warning.
+ protected
+ attr_reader :delegate_reflection
+
+ def actual_source_reflection # FIXME: this is a horrible name
+ source_reflection.actual_source_reflection
end
- end
private
-
- def actual_source_reflection # FIXME: this is a horrible name
- source_reflection.send(:actual_source_reflection)
+ def collect_join_reflections(seed)
+ a = source_reflection.add_as_source seed
+ if options[:source_type]
+ through_reflection.add_as_polymorphic_through self, a
+ else
+ through_reflection.add_as_through a
+ end
end
def primary_key(klass)
@@ -1105,7 +1130,7 @@ module ActiveRecord
end
def alias_name
- Arel::Table.new(table_name)
+ Arel::Table.new(table_name, type_caster: klass.type_caster)
end
def all_includes; yield; end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 5775eda5a5..52f5d5f3e3 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -2,7 +2,7 @@ module ActiveRecord
# = Active Record \Relation
class Relation
MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
- :order, :joins, :left_joins, :left_outer_joins, :references,
+ :order, :joins, :left_outer_joins, :references,
:extending, :unscope]
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering,
@@ -18,6 +18,7 @@ module ActiveRecord
attr_reader :table, :klass, :loaded, :predicate_builder
alias :model :klass
alias :loaded? :loaded
+ alias :locked? :lock_value
def initialize(klass, table, predicate_builder, values = {})
@klass = klass
@@ -269,8 +270,7 @@ module ActiveRecord
# Returns true if there are no records.
def empty?
return @records.empty? if loaded?
-
- limit_value == 0 || !exists?
+ !exists?
end
# Returns true if there are no records.
@@ -333,7 +333,7 @@ module ActiveRecord
# Please check unscoped if you want to remove all previous scopes (including
# the default_scope) during the execution of a block.
def scoping
- previous, klass.current_scope = klass.current_scope, self
+ previous, klass.current_scope = klass.current_scope(true), self
yield
ensure
klass.current_scope = previous
@@ -404,9 +404,9 @@ module ActiveRecord
#
# Note: Updating a large number of records will run an
# UPDATE query for each record, which may cause a performance
- # issue. So if it is not needed to run callbacks for each update, it is
- # preferred to use #update_all for updating all records using
- # a single query.
+ # issue. When running callbacks is not needed for each record update,
+ # it is preferred to use #update_all for updating all records
+ # in a single query.
def update(id = :all, attributes)
if id.is_a?(Array)
id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 76031515fd..ee1f25ec84 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -1,4 +1,4 @@
-require "active_record/relation/batches/batch_enumerator"
+require_relative "batches/batch_enumerator"
module ActiveRecord
module Batches
@@ -30,14 +30,14 @@ module ActiveRecord
# end
#
# ==== Options
- # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
+ # * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
- # an order is present in the relation.
+ # an order is present in the relation.
#
# Limits are honored, and if present there is no requirement for the batch
- # size, it can be less than, equal, or greater than the limit.
+ # size: it can be less than, equal to, or greater than the limit.
#
# The options +start+ and +finish+ are especially useful if you want
# multiple workers dealing with the same processing queue. You can make
@@ -89,14 +89,14 @@ module ActiveRecord
# To be yielded each record one by one, use #find_each instead.
#
# ==== Options
- # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
+ # * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
- # an order is present in the relation.
+ # an order is present in the relation.
#
# Limits are honored, and if present there is no requirement for the batch
- # size, it can be less than, equal, or greater than the limit.
+ # size: it can be less than, equal to, or greater than the limit.
#
# The options +start+ and +finish+ are especially useful if you want
# multiple workers dealing with the same processing queue. You can make
@@ -140,9 +140,9 @@ module ActiveRecord
# If you do not provide a block to #in_batches, it will return a
# BatchEnumerator which is enumerable.
#
- # Person.in_batches.with_index do |relation, batch_index|
+ # Person.in_batches.each_with_index do |relation, batch_index|
# puts "Processing relation ##{batch_index}"
- # relation.each { |relation| relation.delete_all }
+ # relation.delete_all
# end
#
# Examples of calling methods on the returned BatchEnumerator object:
@@ -152,12 +152,12 @@ module ActiveRecord
# Person.in_batches.each_record(&:party_all_night!)
#
# ==== Options
- # * <tt>:of</tt> - Specifies the size of the batch. Default to 1000.
- # * <tt>:load</tt> - Specifies if the relation should be loaded. Default to false.
+ # * <tt>:of</tt> - Specifies the size of the batch. Defaults to 1000.
+ # * <tt>:load</tt> - Specifies if the relation should be loaded. Defaults to false.
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
- # an order is present in the relation.
+ # an order is present in the relation.
#
# Limits are honored, and if present there is no requirement for the batch
# size, it can be less than, equal, or greater than the limit.
@@ -186,7 +186,7 @@ module ActiveRecord
#
# NOTE: It's not possible to set the order. That is automatically set to
# ascending on the primary key ("id ASC") to make the batch ordering
- # consistent. Therefore the primary key must be orderable, e.g an integer
+ # consistent. Therefore the primary key must be orderable, e.g. an integer
# or a string.
#
# NOTE: By its nature, batch processing is subject to race conditions if
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 9cabd1af13..8a54f8f2c3 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -37,7 +37,16 @@ module ActiveRecord
# Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
# between databases. In invalid cases, an error from the database is thrown.
def count(column_name = nil)
- return super() if block_given?
+ if block_given?
+ unless column_name.nil?
+ ActiveSupport::Deprecation.warn \
+ "When `count' is called with a block, it ignores other arguments. " \
+ "This behavior is now deprecated and will result in an ArgumentError in Rails 6.0."
+ end
+
+ return super()
+ end
+
calculate(:count, column_name)
end
@@ -73,7 +82,16 @@ module ActiveRecord
#
# Person.sum(:age) # => 4562
def sum(column_name = nil)
- return super() if block_given?
+ if block_given?
+ unless column_name.nil?
+ ActiveSupport::Deprecation.warn \
+ "When `sum' is called with a block, it ignores other arguments. " \
+ "This behavior is now deprecated and will result in an ArgumentError in Rails 6.0."
+ end
+
+ return super()
+ end
+
calculate(:sum, column_name)
end
@@ -293,7 +311,7 @@ module ActiveRecord
relation.group_values = group_fields
relation.select_values = select_values
- calculated_data = @klass.connection.select_all(relation, nil, relation.bound_attributes)
+ calculated_data = @klass.connection.select_all(relation.arel, nil, relation.bound_attributes)
if association
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
@@ -368,9 +386,8 @@ module ActiveRecord
relation.select_values = [aliased_column]
subquery = relation.arel.as(subquery_alias)
- sm = Arel::SelectManager.new relation.engine
select_value = operation_over_aggregate_column(column_alias, "count", distinct)
- sm.project(select_value).from(subquery)
+ Arel::SelectManager.new(subquery).project(select_value)
end
end
end
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 50378f9d99..48c4dcdef4 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -89,6 +89,8 @@ module ActiveRecord
self.class.delegate_to_scoped_klass(method)
scoping { @klass.public_send(method, *args, &block) }
elsif arel.respond_to?(method)
+ ActiveSupport::Deprecation.warn \
+ "Delegating #{method} to arel is deprecated and will be removed in Rails 6.0."
self.class.delegate method, to: :arel
arel.public_send(method, *args, &block)
else
@@ -109,21 +111,9 @@ module ActiveRecord
end
end
- def respond_to_missing?(method, include_private = false)
- super || @klass.respond_to?(method, include_private) ||
- arel.respond_to?(method, include_private)
- end
-
private
-
- def method_missing(method, *args, &block)
- if @klass.respond_to?(method)
- scoping { @klass.public_send(method, *args, &block) }
- elsif arel.respond_to?(method)
- arel.public_send(method, *args, &block)
- else
- super
- end
+ def respond_to_missing?(method, _)
+ super || @klass.respond_to?(method) || arel.respond_to?(method)
end
end
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 5d24f5f5ca..eee0f36f63 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -147,8 +147,7 @@ module ActiveRecord
def last(limit = nil)
return find_last(limit) if loaded? || limit_value
- result = limit(limit)
- result.order!(arel_attribute(primary_key)) if order_values.empty? && primary_key
+ result = ordered_relation.limit(limit)
result = result.reverse_order!
limit ? result.reverse : result.first
@@ -307,23 +306,16 @@ module ActiveRecord
MSG
end
- return false if !conditions
+ return false if !conditions || limit_value == 0
- relation = apply_join_dependency(self, construct_join_dependency(eager_loading: false))
- return false if ActiveRecord::NullRelation === relation
+ relation = self unless eager_loading?
+ relation ||= apply_join_dependency(self, construct_join_dependency(eager_loading: false))
- relation = relation.except(:select, :distinct).select(ONE_AS_ONE).limit(1)
+ return false if ActiveRecord::NullRelation === relation
- case conditions
- when Array, Hash
- relation = relation.where(conditions)
- else
- unless conditions == :none
- relation = relation.where(primary_key => conditions)
- end
- end
+ relation = construct_relation_for_exists(relation, conditions)
- connection.select_value(relation, "#{name} Exists", relation.bound_attributes) ? true : false
+ connection.select_value(relation.arel, "#{name} Exists", relation.bound_attributes) ? true : false
rescue ::RangeError
false
end
@@ -342,14 +334,14 @@ module ActiveRecord
name = @klass.name
if ids.nil?
- error = "Couldn't find #{name}"
+ error = "Couldn't find #{name}".dup
error << " with#{conditions}" if conditions
raise RecordNotFound.new(error, name)
elsif Array(ids).size == 1
error = "Couldn't find #{name} with '#{key}'=#{ids}#{conditions}"
raise RecordNotFound.new(error, name, key, ids)
else
- error = "Couldn't find all #{name.pluralize} with '#{key}': "
+ error = "Couldn't find all #{name.pluralize} with '#{key}': ".dup
error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})"
raise RecordNotFound.new(error, name, primary_key, ids)
@@ -384,13 +376,25 @@ module ActiveRecord
if ActiveRecord::NullRelation === relation
[]
else
- arel = relation.arel
- rows = connection.select_all(arel, "SQL", relation.bound_attributes)
+ rows = connection.select_all(relation.arel, "SQL", relation.bound_attributes)
join_dependency.instantiate(rows, aliases)
end
end
end
+ def construct_relation_for_exists(relation, conditions)
+ relation = relation.except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
+
+ case conditions
+ when Array, Hash
+ relation.where!(conditions)
+ else
+ relation.where!(primary_key => conditions) unless conditions == :none
+ end
+
+ relation
+ end
+
def construct_join_dependency(joins = [], eager_loading: true)
including = eager_load_values + includes_values
ActiveRecord::Associations::JoinDependency.new(@klass, including, joins, eager_loading: eager_loading)
@@ -401,8 +405,7 @@ module ActiveRecord
end
def apply_join_dependency(relation, join_dependency)
- relation = relation.except(:includes, :eager_load, :preload)
- relation = relation.joins join_dependency
+ relation = relation.except(:includes, :eager_load, :preload).joins!(join_dependency)
if using_limitable_reflections?(join_dependency.reflections)
relation
@@ -420,9 +423,8 @@ module ActiveRecord
"#{quoted_table_name}.#{quoted_primary_key}", relation.order_values)
relation = relation.except(:select).select(values).distinct!
- arel = relation.arel
- id_rows = @klass.connection.select_all(arel, "SQL", relation.bound_attributes)
+ id_rows = @klass.connection.select_all(relation.arel, "SQL", relation.bound_attributes)
id_rows.map { |row| row[primary_key] }
end
@@ -530,11 +532,7 @@ module ActiveRecord
if loaded?
records[index, limit] || []
else
- relation = if order_values.empty? && primary_key
- order(arel_attribute(primary_key).asc)
- else
- self
- end
+ relation = ordered_relation
if limit_value.nil? || index < limit_value
relation = relation.offset(offset_index + index) unless index.zero?
@@ -549,11 +547,7 @@ module ActiveRecord
if loaded?
records[-index]
else
- relation = if order_values.empty? && primary_key
- order(arel_attribute(primary_key).asc)
- else
- self
- end
+ relation = ordered_relation
relation.to_a[-index]
# TODO: can be made more performant on large result sets by
@@ -567,5 +561,13 @@ module ActiveRecord
def find_last(limit)
limit ? records.last(limit) : records.last
end
+
+ def ordered_relation
+ if order_values.empty? && primary_key
+ order(arel_attribute(primary_key).asc)
+ else
+ self
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index fca914aedd..eb80c9a00d 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -1,13 +1,5 @@
module ActiveRecord
class PredicateBuilder # :nodoc:
- require "active_record/relation/predicate_builder/array_handler"
- require "active_record/relation/predicate_builder/association_query_handler"
- require "active_record/relation/predicate_builder/base_handler"
- require "active_record/relation/predicate_builder/basic_object_handler"
- require "active_record/relation/predicate_builder/polymorphic_array_handler"
- require "active_record/relation/predicate_builder/range_handler"
- require "active_record/relation/predicate_builder/relation_handler"
-
delegate :resolve_column_aliases, to: :table
def initialize(table)
@@ -20,8 +12,6 @@ module ActiveRecord
register_handler(RangeHandler::RangeWithBinds, RangeHandler.new)
register_handler(Relation, RelationHandler.new)
register_handler(Array, ArrayHandler.new(self))
- register_handler(AssociationQueryValue, AssociationQueryHandler.new(self))
- register_handler(PolymorphicArrayValue, PolymorphicArrayHandler.new(self))
end
def build_from_hash(attributes)
@@ -87,7 +77,6 @@ module ActiveRecord
binds = []
attributes.each do |column_name, value|
- binds.concat(value.bound_attributes) if value.is_a?(Relation)
case
when value.is_a?(Hash) && !table.has_column?(column_name)
attrs, bvs = associated_predicate_builder(column_name).create_binds_for_hash(value)
@@ -99,24 +88,45 @@ module ActiveRecord
#
# For polymorphic relationships, find the foreign key and type:
# PriceEstimate.where(estimate_of: treasure)
- result[column_name] = AssociationQueryHandler.value_for(table, column_name, value)
+ associated_table = table.associated_table(column_name)
+ if associated_table.polymorphic_association?
+ case value.is_a?(Array) ? value.first : value
+ when Base, Relation
+ value = [value] unless value.is_a?(Array)
+ klass = PolymorphicArrayValue
+ end
+ end
+
+ klass ||= AssociationQueryValue
+ result[column_name] = klass.new(associated_table, value).queries.map do |query|
+ attrs, bvs = create_binds_for_hash(query)
+ binds.concat(bvs)
+ attrs
+ end
when value.is_a?(Range) && !table.type(column_name).respond_to?(:subtype)
first = value.begin
last = value.end
unless first.respond_to?(:infinite?) && first.infinite?
- binds << build_bind_param(column_name, first)
+ binds << build_bind_attribute(column_name, first)
first = Arel::Nodes::BindParam.new
end
unless last.respond_to?(:infinite?) && last.infinite?
- binds << build_bind_param(column_name, last)
+ binds << build_bind_attribute(column_name, last)
last = Arel::Nodes::BindParam.new
end
result[column_name] = RangeHandler::RangeWithBinds.new(first, last, value.exclude_end?)
+ when value.is_a?(Relation)
+ binds.concat(value.bound_attributes)
else
if can_be_bound?(column_name, value)
- result[column_name] = Arel::Nodes::BindParam.new
- binds << build_bind_param(column_name, value)
+ bind_attribute = build_bind_attribute(column_name, value)
+ if value.is_a?(StatementCache::Substitute) || !bind_attribute.value_for_database.nil?
+ result[column_name] = Arel::Nodes::BindParam.new
+ binds << bind_attribute
+ else
+ result[column_name] = nil
+ end
end
end
end
@@ -159,8 +169,17 @@ module ActiveRecord
end
end
- def build_bind_param(column_name, value)
+ def build_bind_attribute(column_name, value)
Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name))
end
end
end
+
+require_relative "predicate_builder/array_handler"
+require_relative "predicate_builder/base_handler"
+require_relative "predicate_builder/basic_object_handler"
+require_relative "predicate_builder/range_handler"
+require_relative "predicate_builder/relation_handler"
+
+require_relative "predicate_builder/association_query_value"
+require_relative "predicate_builder/polymorphic_array_value"
diff --git a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
index 88b6c37d43..1068e700e2 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
@@ -6,11 +6,11 @@ module ActiveRecord
end
def call(attribute, value)
+ return attribute.in([]) if value.empty?
+ return queries_predicates(value) if value.all? { |v| v.is_a?(Hash) }
+
values = value.map { |x| x.is_a?(Base) ? x.id : x }
nils, values = values.partition(&:nil?)
-
- return attribute.in([]) if values.empty? && nils.empty?
-
ranges, values = values.partition { |v| v.is_a?(Range) }
values_predicate =
@@ -26,7 +26,7 @@ module ActiveRecord
array_predicates = ranges.map { |range| predicate_builder.build(attribute, range) }
array_predicates.unshift(values_predicate)
- array_predicates.inject { |composite, predicate| composite.or(predicate) }
+ array_predicates.inject(&:or)
end
# TODO Change this to private once we've dropped Ruby 2.2 support.
@@ -40,6 +40,17 @@ module ActiveRecord
other
end
end
+
+ private
+ def queries_predicates(queries)
+ if queries.size > 1
+ queries.map do |query|
+ Arel::Nodes::And.new(predicate_builder.build_from_hash(query))
+ end.inject(&:or)
+ else
+ predicate_builder.build_from_hash(queries.first)
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
deleted file mode 100644
index 29860ec677..0000000000
--- a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-module ActiveRecord
- class PredicateBuilder
- class AssociationQueryHandler # :nodoc:
- def self.value_for(table, column, value)
- associated_table = table.associated_table(column)
- klass = if associated_table.polymorphic_association? && ::Array === value && value.first.is_a?(Base)
- PolymorphicArrayValue
- else
- AssociationQueryValue
- end
-
- klass.new(associated_table, value)
- end
-
- def initialize(predicate_builder)
- @predicate_builder = predicate_builder
- end
-
- def call(attribute, value)
- queries = {}
-
- table = value.associated_table
- if value.base_class
- queries[table.association_foreign_type.to_s] = value.base_class.name
- end
-
- queries[table.association_foreign_key.to_s] = value.ids
- predicate_builder.build_from_hash(queries)
- 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 :predicate_builder
- end
-
- class AssociationQueryValue # :nodoc:
- attr_reader :associated_table, :value
-
- def initialize(associated_table, value)
- @associated_table = associated_table
- @value = value
- end
-
- def ids
- case value
- when Relation
- value.select(primary_key)
- when Array
- value.map { |v| convert_to_id(v) }
- else
- convert_to_id(value)
- end
- end
-
- def base_class
- if associated_table.polymorphic_association?
- @base_class ||= polymorphic_base_class_from_value
- end
- end
-
- private
-
- def primary_key
- associated_table.association_primary_key(base_class)
- end
-
- def polymorphic_base_class_from_value
- case value
- when Relation
- value.klass.base_class
- when Base
- value.class.base_class
- end
- end
-
- def convert_to_id(value)
- case value
- when Base
- value._read_attribute(primary_key)
- else
- value
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb b/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb
new file mode 100644
index 0000000000..3e19646ae5
--- /dev/null
+++ b/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb
@@ -0,0 +1,44 @@
+module ActiveRecord
+ class PredicateBuilder
+ class AssociationQueryValue # :nodoc:
+ def initialize(associated_table, value)
+ @associated_table = associated_table
+ @value = value
+ end
+
+ def queries
+ [associated_table.association_foreign_key.to_s => ids]
+ 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 :associated_table, :value
+
+ private
+ def ids
+ case value
+ when Relation
+ value.select_values.empty? ? value.select(primary_key) : value
+ when Array
+ value.map { |v| convert_to_id(v) }
+ else
+ convert_to_id(value)
+ end
+ end
+
+ def primary_key
+ associated_table.association_primary_key
+ end
+
+ def convert_to_id(value)
+ case value
+ when Base
+ value._read_attribute(primary_key)
+ else
+ value
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb
deleted file mode 100644
index 335124c952..0000000000
--- a/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-module ActiveRecord
- class PredicateBuilder
- class PolymorphicArrayHandler # :nodoc:
- def initialize(predicate_builder)
- @predicate_builder = predicate_builder
- end
-
- def call(attribute, value)
- table = value.associated_table
- queries = value.type_to_ids_mapping.map do |type, ids|
- { table.association_foreign_type.to_s => type, table.association_foreign_key.to_s => ids }
- end
-
- predicates = queries.map { |query| predicate_builder.build_from_hash(query) }
-
- if predicates.size > 1
- type_and_ids_predicates = predicates.map { |type_predicate, id_predicate| Arel::Nodes::Grouping.new(type_predicate.and(id_predicate)) }
- type_and_ids_predicates.inject(&:or)
- else
- predicates.first
- end
- end
-
- # TODO Change this to private once we've dropped Ruby 2.2 support.
- # Workaround for Ruby 2.2 "private attribute?" warning.
- protected
-
- attr_reader :predicate_builder
- end
-
- class PolymorphicArrayValue # :nodoc:
- attr_reader :associated_table, :values
-
- def initialize(associated_table, values)
- @associated_table = associated_table
- @values = values
- end
-
- def type_to_ids_mapping
- default_hash = Hash.new { |hsh, key| hsh[key] = [] }
- values.each_with_object(default_hash) { |value, hash| hash[base_class(value).name] << convert_to_id(value) }
- end
-
- private
-
- def primary_key(value)
- associated_table.association_primary_key(base_class(value))
- end
-
- def base_class(value)
- value.class.base_class
- end
-
- def convert_to_id(value)
- value._read_attribute(primary_key(value))
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb
new file mode 100644
index 0000000000..7029ae5f47
--- /dev/null
+++ b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb
@@ -0,0 +1,52 @@
+module ActiveRecord
+ class PredicateBuilder
+ class PolymorphicArrayValue # :nodoc:
+ def initialize(associated_table, values)
+ @associated_table = associated_table
+ @values = values
+ end
+
+ def queries
+ type_to_ids_mapping.map do |type, ids|
+ {
+ associated_table.association_foreign_type.to_s => type,
+ associated_table.association_foreign_key.to_s => ids.size > 1 ? ids : ids.first
+ }
+ end
+ end
+
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
+ # Workaround for Ruby 2.2 "private attribute?" warning.
+ protected
+ attr_reader :associated_table, :values
+
+ private
+ def type_to_ids_mapping
+ default_hash = Hash.new { |hsh, key| hsh[key] = [] }
+ values.each_with_object(default_hash) { |value, hash| hash[base_class(value).name] << convert_to_id(value) }
+ end
+
+ def primary_key(value)
+ associated_table.association_primary_key(base_class(value))
+ end
+
+ def base_class(value)
+ case value
+ when Base
+ value.class.base_class
+ when Relation
+ value.klass.base_class
+ end
+ end
+
+ def convert_to_id(value)
+ case value
+ when Base
+ value._read_attribute(primary_key(value))
+ when Relation
+ value.select(primary_key(value))
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/query_attribute.rb b/activerecord/lib/active_record/relation/query_attribute.rb
index a68e508fcc..0e1f64775d 100644
--- a/activerecord/lib/active_record/relation/query_attribute.rb
+++ b/activerecord/lib/active_record/relation/query_attribute.rb
@@ -1,4 +1,4 @@
-require "active_record/attribute"
+require_relative "../attribute"
module ActiveRecord
class Relation
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 1178dec706..9da8f96337 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -1,7 +1,7 @@
-require "active_record/relation/from_clause"
-require "active_record/relation/query_attribute"
-require "active_record/relation/where_clause"
-require "active_record/relation/where_clause_factory"
+require_relative "from_clause"
+require_relative "query_attribute"
+require_relative "where_clause"
+require_relative "where_clause_factory"
require "active_model/forbidden_attributes_protection"
module ActiveRecord
@@ -248,7 +248,7 @@ module ActiveRecord
return super()
end
- raise ArgumentError, "Call this with at least one field" if fields.empty?
+ raise ArgumentError, "Call `select' with at least one field" if fields.empty?
spawn._select!(*fields)
end
@@ -1100,14 +1100,16 @@ module ActiveRecord
end
VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
- "asc", "desc", "ASC", "DESC"] # :nodoc:
+ "asc", "desc", "ASC", "DESC"].to_set # :nodoc:
def validate_order_args(args)
args.each do |arg|
next unless arg.is_a?(Hash)
arg.each do |_key, value|
- raise ArgumentError, "Direction \"#{value}\" is invalid. Valid " \
- "directions are: #{VALID_DIRECTIONS.inspect}" unless VALID_DIRECTIONS.include?(value)
+ unless VALID_DIRECTIONS.include?(value)
+ raise ArgumentError,
+ "Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
+ end
end
end
end
@@ -1120,7 +1122,7 @@ module ActiveRecord
validate_order_args(order_args)
references = order_args.grep(String)
- references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
+ references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
references!(references) if references.any?
# if a symbol is given we prepend the quoted table name
@@ -1165,7 +1167,7 @@ module ActiveRecord
end
end
- STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having]
+ STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having, :unscope]
def structurally_incompatible_values_for_or(other)
STRUCTURAL_OR_METHODS.reject do |method|
get_value(method) == other.get_value(method)
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index ada89b5ec3..ddf7f825c1 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -1,6 +1,6 @@
require "active_support/core_ext/hash/except"
require "active_support/core_ext/hash/slice"
-require "active_record/relation/merger"
+require_relative "merger"
module ActiveRecord
module SpawnMethods
diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb
index 04bee73e8f..b862dd56a5 100644
--- a/activerecord/lib/active_record/relation/where_clause_factory.rb
+++ b/activerecord/lib/active_record/relation/where_clause_factory.rb
@@ -57,7 +57,7 @@ module ActiveRecord
else
column = klass.column_for_attribute(attribute)
- binds << predicate_builder.send(:build_bind_param, attribute, value)
+ binds << predicate_builder.send(:build_bind_attribute, attribute, value)
value = Arel::Nodes::BindParam.new
predicate = if options[:case_sensitive]
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 2bbfd01698..66a2846f3a 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -11,10 +11,9 @@ module ActiveRecord
##
# :singleton-method:
# A list of tables which should not be dumped to the schema.
- # Acceptable values are strings as well as regexp.
- # This setting is only used if ActiveRecord::Base.schema_format == :ruby
- cattr_accessor :ignore_tables
- @@ignore_tables = []
+ # Acceptable values are strings as well as regexp if ActiveRecord::Base.schema_format == :ruby.
+ # Only strings are accepted if ActiveRecord::Base.schema_format == :sql.
+ cattr_accessor :ignore_tables, default: []
class << self
def dump(connection = ActiveRecord::Base.connection, stream = STDOUT, config = ActiveRecord::Base)
@@ -47,9 +46,18 @@ module ActiveRecord
@options = options
end
- def header(stream)
- define_params = @version ? "version: #{@version}" : ""
+ # turns 20170404131909 into "2017_04_04_131909"
+ def formatted_version
+ stringified = @version.to_s
+ return stringified unless stringified.length == 14
+ stringified.insert(4, "_").insert(7, "_").insert(10, "_")
+ end
+ def define_params
+ @version ? "version: #{formatted_version}" : ""
+ end
+
+ def header(stream)
stream.puts <<HEADER
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb
index f59737afb0..6dbabd69a1 100644
--- a/activerecord/lib/active_record/schema_migration.rb
+++ b/activerecord/lib/active_record/schema_migration.rb
@@ -1,5 +1,5 @@
-require "active_record/scoping/default"
-require "active_record/scoping/named"
+require_relative "scoping/default"
+require_relative "scoping/named"
module ActiveRecord
# This class is used to create a table that keeps track of which migrations
diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb
index 7c00e7e4ed..94e0ef6724 100644
--- a/activerecord/lib/active_record/scoping.rb
+++ b/activerecord/lib/active_record/scoping.rb
@@ -10,8 +10,8 @@ module ActiveRecord
end
module ClassMethods
- def current_scope #:nodoc:
- ScopeRegistry.value_for(:current_scope, self)
+ def current_scope(skip_inherited_scope = false) # :nodoc:
+ ScopeRegistry.value_for(:current_scope, self, skip_inherited_scope)
end
def current_scope=(scope) #:nodoc:
@@ -75,8 +75,9 @@ module ActiveRecord
end
# Obtains the value for a given +scope_type+ and +model+.
- def value_for(scope_type, model)
+ def value_for(scope_type, model, skip_inherited_scope = false)
raise_invalid_scope_type!(scope_type)
+ return @registry[scope_type][model.name] if skip_inherited_scope
klass = model
base = model.base_class
while klass <= base
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index 2daa48859a..70b2693b28 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -5,11 +5,8 @@ module ActiveRecord
included do
# Stores the default scope for the class.
- class_attribute :default_scopes, instance_writer: false, instance_predicate: false
- class_attribute :default_scope_override, instance_writer: false, instance_predicate: false
-
- self.default_scopes = []
- self.default_scope_override = nil
+ class_attribute :default_scopes, instance_writer: false, instance_predicate: false, default: []
+ class_attribute :default_scope_override, instance_writer: false, instance_predicate: false, default: nil
end
module ClassMethods
@@ -110,7 +107,11 @@ module ActiveRecord
if default_scope_override
# The user has defined their own default scope method, so call that
- evaluate_default_scope { default_scope }
+ evaluate_default_scope do
+ if scope = default_scope
+ (base_rel ||= relation).merge(scope)
+ end
+ end
elsif default_scopes.any?
base_rel ||= relation
evaluate_default_scope do
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index 27cdf8cb7e..b4026fabb2 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -29,13 +29,15 @@ module ActiveRecord
end
end
- def default_scoped # :nodoc:
- scope = build_default_scope
+ def default_scoped(scope = relation) # :nodoc:
+ build_default_scope(scope) || scope
+ end
- if scope
- relation.spawn.merge!(scope)
+ def default_extensions # :nodoc:
+ if scope = current_scope || build_default_scope
+ scope.extensions
else
- relation
+ []
end
end
@@ -156,17 +158,17 @@ module ActiveRecord
if body.respond_to?(:to_proc)
singleton_class.send(:define_method, name) do |*args|
- scope = all.scoping { instance_exec(*args, &body) }
+ scope = all
+ scope = scope.instance_exec(*args, &body) || scope
scope = scope.extending(extension) if extension
-
- scope || all
+ scope
end
else
singleton_class.send(:define_method, name) do |*args|
- scope = all.scoping { body.call(*args) }
+ scope = all
+ scope = scope.scoping { body.call(*args) || scope }
scope = scope.extending(extension) if extension
-
- scope || all
+ scope
end
end
end
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 46fa8a70a3..ba686fc562 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -71,9 +71,9 @@ module ActiveRecord
@tasks[pattern] = task
end
- register_task(/mysql/, ActiveRecord::Tasks::MySQLDatabaseTasks)
- register_task(/postgresql/, ActiveRecord::Tasks::PostgreSQLDatabaseTasks)
- register_task(/sqlite/, ActiveRecord::Tasks::SQLiteDatabaseTasks)
+ register_task(/mysql/, "ActiveRecord::Tasks::MySQLDatabaseTasks")
+ register_task(/postgresql/, "ActiveRecord::Tasks::PostgreSQLDatabaseTasks")
+ register_task(/sqlite/, "ActiveRecord::Tasks::SQLiteDatabaseTasks")
def db_dir
@db_dir ||= Rails.application.config.paths["db"].first
@@ -164,7 +164,7 @@ module ActiveRecord
def migrate
raise "Empty VERSION provided" if ENV["VERSION"] && ENV["VERSION"].empty?
- verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
+ verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] != "false" : true
version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
scope = ENV["SCOPE"]
verbose_was, Migration.verbose = Migration.verbose, verbose
@@ -288,11 +288,11 @@ module ActiveRecord
private
def class_for_adapter(adapter)
- key = @tasks.keys.detect { |pattern| adapter[pattern] }
- unless key
+ _key, task = @tasks.each_pair.detect { |pattern, _task| adapter[pattern] }
+ unless task
raise DatabaseNotSupported, "Rake tasks not supported by '#{adapter}' adapter"
end
- @tasks[key]
+ task.is_a?(String) ? task.constantize : task
end
def each_current_configuration(environment)
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index 920830b9cf..ff6745f7b5 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -59,8 +59,14 @@ module ActiveRecord
args.concat(["--no-data"])
args.concat(["--routines"])
args.concat(["--skip-comments"])
- args.concat(Array(extra_flags)) if extra_flags
+
+ ignore_tables = ActiveRecord::SchemaDumper.ignore_tables
+ if ignore_tables.any?
+ args += ignore_tables.map { |table| "--ignore-table=#{configuration['database']}.#{table}" }
+ end
+
args.concat(["#{configuration['database']}"])
+ args.unshift(*extra_flags) if extra_flags
run_cmd("mysqldump", args, "dumping")
end
@@ -69,7 +75,7 @@ module ActiveRecord
args = prepare_command_options
args.concat(["--execute", %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}])
args.concat(["--database", "#{configuration['database']}"])
- args.concat(Array(extra_flags)) if extra_flags
+ args.unshift(*extra_flags) if extra_flags
run_cmd("mysql", args, "loading")
end
@@ -93,7 +99,7 @@ module ActiveRecord
def error_class
if configuration["adapter"].include?("jdbc")
- require "active_record/railties/jdbcmysql_error"
+ require_relative "../railties/jdbcmysql_error"
ArJdbcMySQL::Error
elsif defined?(Mysql2)
Mysql2::Error
@@ -104,7 +110,7 @@ module ActiveRecord
def grant_statement
<<-SQL
-GRANT ALL PRIVILEGES ON #{configuration['database']}.*
+GRANT ALL PRIVILEGES ON `#{configuration['database']}`.*
TO '#{configuration['username']}'@'localhost'
IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION;
SQL
@@ -145,7 +151,7 @@ IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION;
end
def run_cmd_error(cmd, args, action)
- msg = "failed to execute: `#{cmd}`\n"
+ msg = "failed to execute: `#{cmd}`\n".dup
msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
msg
end
diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
index f1af90c1e8..7f1a768d8b 100644
--- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
@@ -66,6 +66,12 @@ module ActiveRecord
"--schema=#{part.strip}"
end
end
+
+ ignore_tables = ActiveRecord::SchemaDumper.ignore_tables
+ if ignore_tables.any?
+ args += ignore_tables.flat_map { |table| ["-T", table] }
+ end
+
args << configuration["database"]
run_cmd("pg_dump", args, "dumping")
remove_sql_header_comments(filename)
diff --git a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
index 1f756c2979..01562b21e9 100644
--- a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
@@ -36,9 +36,18 @@ module ActiveRecord
end
def structure_dump(filename, extra_flags)
- dbfile = configuration["database"]
- flags = extra_flags.join(" ") if extra_flags
- `sqlite3 #{flags} #{dbfile} .schema > #{filename}`
+ args = []
+ args.concat(Array(extra_flags)) if extra_flags
+ args << configuration["database"]
+
+ ignore_tables = ActiveRecord::SchemaDumper.ignore_tables
+ if ignore_tables.any?
+ condition = ignore_tables.map { |table| connection.quote(table) }.join(", ")
+ args << "SELECT sql FROM sqlite_master WHERE tbl_name NOT IN (#{condition}) ORDER BY tbl_name, type DESC, name"
+ else
+ args << ".schema"
+ end
+ run_cmd("sqlite3", args, filename)
end
def structure_load(filename, extra_flags)
@@ -56,6 +65,17 @@ module ActiveRecord
def root
@root
end
+
+ def run_cmd(cmd, args, out)
+ fail run_cmd_error(cmd, args) unless Kernel.system(cmd, *args, out: out)
+ end
+
+ def run_cmd_error(cmd, args)
+ msg = "failed to execute:\n"
+ msg << "#{cmd} #{args.join(' ')}\n\n"
+ msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
+ msg
+ end
end
end
end
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index 09d8d1cdd4..dc4540eea6 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -43,8 +43,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- class_attribute :record_timestamps
- self.record_timestamps = true
+ class_attribute :record_timestamps, default: true
end
def initialize_dup(other) # :nodoc:
@@ -127,7 +126,7 @@ module ActiveRecord
self.class.send(:current_time_from_proper_timezone)
end
- def max_updated_column_timestamp(timestamp_names = self.class.send(:timestamp_attributes_for_update))
+ def max_updated_column_timestamp(timestamp_names = timestamp_attributes_for_update_in_model)
timestamp_names
.map { |attr| self[attr] }
.compact
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 45795fa287..463bb1f314 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -490,7 +490,7 @@ module ActiveRecord
def update_attributes_from_transaction_state(transaction_state)
if transaction_state && transaction_state.finalized?
restore_transaction_record_state if transaction_state.rolledback?
- clear_transaction_record_state
+ clear_transaction_record_state if transaction_state.fully_completed?
end
end
end
diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb
index 4f632660a8..6f4e35b159 100644
--- a/activerecord/lib/active_record/type.rb
+++ b/activerecord/lib/active_record/type.rb
@@ -1,20 +1,20 @@
require "active_model/type"
-require "active_record/type/internal/abstract_json"
-require "active_record/type/internal/timezone"
+require_relative "type/internal/timezone"
-require "active_record/type/date"
-require "active_record/type/date_time"
-require "active_record/type/decimal_without_scale"
-require "active_record/type/time"
-require "active_record/type/text"
-require "active_record/type/unsigned_integer"
+require_relative "type/date"
+require_relative "type/date_time"
+require_relative "type/decimal_without_scale"
+require_relative "type/json"
+require_relative "type/time"
+require_relative "type/text"
+require_relative "type/unsigned_integer"
-require "active_record/type/serialized"
-require "active_record/type/adapter_specific_registry"
+require_relative "type/serialized"
+require_relative "type/adapter_specific_registry"
-require "active_record/type/type_map"
-require "active_record/type/hash_lookup_type_map"
+require_relative "type/type_map"
+require_relative "type/hash_lookup_type_map"
module ActiveRecord
module Type
@@ -69,6 +69,7 @@ module ActiveRecord
register(:decimal, Type::Decimal, override: false)
register(:float, Type::Float, override: false)
register(:integer, Type::Integer, override: false)
+ register(:json, Type::Json, override: false)
register(:string, Type::String, override: false)
register(:text, Type::Text, override: false)
register(:time, Type::Time, override: false)
diff --git a/activerecord/lib/active_record/type/internal/abstract_json.rb b/activerecord/lib/active_record/type/internal/abstract_json.rb
deleted file mode 100644
index e19c5a14da..0000000000
--- a/activerecord/lib/active_record/type/internal/abstract_json.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-module ActiveRecord
- module Type
- module Internal # :nodoc:
- class AbstractJson < ActiveModel::Type::Value # :nodoc:
- include ActiveModel::Type::Helpers::Mutable
-
- def type
- :json
- end
-
- def deserialize(value)
- if value.is_a?(::String)
- ::ActiveSupport::JSON.decode(value) rescue nil
- else
- value
- end
- end
-
- def serialize(value)
- if value.nil?
- nil
- else
- ::ActiveSupport::JSON.encode(value)
- end
- end
-
- def accessor
- ActiveRecord::Store::StringKeyedHashAccessor
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/type/json.rb b/activerecord/lib/active_record/type/json.rb
new file mode 100644
index 0000000000..c4732fe388
--- /dev/null
+++ b/activerecord/lib/active_record/type/json.rb
@@ -0,0 +1,28 @@
+module ActiveRecord
+ module Type
+ class Json < ActiveModel::Type::Value
+ include ActiveModel::Type::Helpers::Mutable
+
+ def type
+ :json
+ end
+
+ def deserialize(value)
+ return value unless value.is_a?(::String)
+ ActiveSupport::JSON.decode(value) rescue nil
+ end
+
+ def serialize(value)
+ ActiveSupport::JSON.encode(value) unless value.nil?
+ end
+
+ def changed_in_place?(raw_old_value, new_value)
+ deserialize(raw_old_value) != new_value
+ end
+
+ def accessor
+ ActiveRecord::Store::StringKeyedHashAccessor
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type_caster.rb b/activerecord/lib/active_record/type_caster.rb
index f1686e4913..8b12a30c6a 100644
--- a/activerecord/lib/active_record/type_caster.rb
+++ b/activerecord/lib/active_record/type_caster.rb
@@ -1,5 +1,5 @@
-require "active_record/type_caster/map"
-require "active_record/type_caster/connection"
+require_relative "type_caster/map"
+require_relative "type_caster/connection"
module ActiveRecord
module TypeCaster # :nodoc:
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 9633f226f0..046b69bee2 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -84,8 +84,8 @@ module ActiveRecord
end
end
-require "active_record/validations/associated"
-require "active_record/validations/uniqueness"
-require "active_record/validations/presence"
-require "active_record/validations/absence"
-require "active_record/validations/length"
+require_relative "validations/associated"
+require_relative "validations/uniqueness"
+require_relative "validations/presence"
+require_relative "validations/absence"
+require_relative "validations/length"
diff --git a/activerecord/lib/rails/generators/active_record.rb b/activerecord/lib/rails/generators/active_record.rb
index 68fca44e3b..a79b8eafea 100644
--- a/activerecord/lib/rails/generators/active_record.rb
+++ b/activerecord/lib/rails/generators/active_record.rb
@@ -10,7 +10,7 @@ module ActiveRecord
# Set the current directory as base for the inherited generators.
def self.base_root
- File.dirname(__FILE__)
+ __dir__
end
end
end
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 601d575c0e..a1fb6427f9 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -220,7 +220,7 @@ module ActiveRecord
def test_select_all_with_legacy_binds
post = Post.create!(title: "foo", body: "bar")
expected = @connection.select_all("SELECT * FROM posts WHERE id = #{post.id}")
- result = @connection.select_all("SELECT * FROM posts WHERE id = #{Arel::Nodes::BindParam.new.to_sql}", nil, [[nil, post.id]])
+ result = @connection.select_all("SELECT * FROM posts WHERE id = #{bind_param.to_sql}", nil, [[nil, post.id]])
assert_equal expected.to_hash, result.to_hash
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/boolean_test.rb b/activerecord/test/cases/adapters/mysql2/boolean_test.rb
index 2fa39282fb..58698d59db 100644
--- a/activerecord/test/cases/adapters/mysql2/boolean_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/boolean_test.rb
@@ -38,7 +38,7 @@ class Mysql2BooleanTest < ActiveRecord::Mysql2TestCase
assert_equal :string, string_column.type
end
- test "test type casting with emulated booleans" do
+ test "type casting with emulated booleans" do
emulate_booleans true
boolean = BooleanType.create!(archived: true, published: true)
@@ -55,7 +55,7 @@ class Mysql2BooleanTest < ActiveRecord::Mysql2TestCase
assert_equal 0, @connection.type_cast(false)
end
- test "test type casting without emulated booleans" do
+ test "type casting without emulated booleans" do
emulate_booleans false
boolean = BooleanType.create!(archived: true, published: true)
diff --git a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
index 8826ad7fd1..e4a6ed5482 100644
--- a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
@@ -48,7 +48,7 @@ class Mysql2CharsetCollationTest < ActiveRecord::Mysql2TestCase
test "schema dump includes collation" do
output = dump_table_schema("charset_collations")
- assert_match %r{t.string\s+"string_ascii_bin",\s+collation: "ascii_bin"$}, output
- assert_match %r{t.text\s+"text_ucs2_unicode_ci",\s+collation: "ucs2_unicode_ci"$}, output
+ assert_match %r{t\.string\s+"string_ascii_bin",\s+collation: "ascii_bin"$}, output
+ assert_match %r{t\.text\s+"text_ucs2_unicode_ci",\s+collation: "ucs2_unicode_ci"$}, output
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/json_test.rb b/activerecord/test/cases/adapters/mysql2/json_test.rb
index 6954006003..26c69edc7b 100644
--- a/activerecord/test/cases/adapters/mysql2/json_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/json_test.rb
@@ -1,195 +1,22 @@
require "cases/helper"
-require "support/schema_dumping_helper"
+require "cases/json_shared_test_cases"
if ActiveRecord::Base.connection.supports_json?
class Mysql2JSONTest < ActiveRecord::Mysql2TestCase
- include SchemaDumpingHelper
+ include JSONSharedTestCases
self.use_transactional_tests = false
- class JsonDataType < ActiveRecord::Base
- self.table_name = "json_data_type"
-
- store_accessor :settings, :resolution
- end
-
def setup
- @connection = ActiveRecord::Base.connection
- begin
- @connection.create_table("json_data_type") do |t|
- t.json "payload"
- t.json "settings"
- end
+ super
+ @connection.create_table("json_data_type") do |t|
+ t.json "payload"
+ t.json "settings"
end
end
- def teardown
- @connection.drop_table :json_data_type, if_exists: true
- JsonDataType.reset_column_information
- end
-
- def test_column
- column = JsonDataType.columns_hash["payload"]
- assert_equal :json, column.type
- assert_equal "json", column.sql_type
-
- type = JsonDataType.type_for_attribute("payload")
- assert_not type.binary?
- end
-
- def test_change_table_supports_json
- @connection.change_table("json_data_type") do |t|
- t.json "users"
+ private
+ def column_type
+ :json
end
- JsonDataType.reset_column_information
- column = JsonDataType.columns_hash["users"]
- assert_equal :json, column.type
- end
-
- def test_schema_dumping
- output = dump_table_schema("json_data_type")
- assert_match(/t\.json\s+"settings"/, output)
- end
-
- def test_cast_value_on_write
- x = JsonDataType.new payload: { "string" => "foo", :symbol => :bar }
- assert_equal({ "string" => "foo", :symbol => :bar }, x.payload_before_type_cast)
- assert_equal({ "string" => "foo", "symbol" => "bar" }, x.payload)
- x.save
- assert_equal({ "string" => "foo", "symbol" => "bar" }, x.reload.payload)
- end
-
- def test_type_cast_json
- type = JsonDataType.type_for_attribute("payload")
-
- data = "{\"a_key\":\"a_value\"}"
- hash = type.deserialize(data)
- assert_equal({ "a_key" => "a_value" }, hash)
- assert_equal({ "a_key" => "a_value" }, type.deserialize(data))
-
- assert_equal({}, type.deserialize("{}"))
- assert_equal({ "key" => nil }, type.deserialize('{"key": null}'))
- assert_equal({ "c" => "}", '"a"' => 'b "a b' }, type.deserialize(%q({"c":"}", "\"a\"":"b \"a b"})))
- end
-
- def test_rewrite
- @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')"
- x = JsonDataType.first
- x.payload = { '"a\'' => "b" }
- assert x.save!
- end
-
- def test_select
- @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')"
- x = JsonDataType.first
- assert_equal({ "k" => "v" }, x.payload)
- end
-
- def test_select_multikey
- @connection.execute %q|insert into json_data_type (payload) VALUES ('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}')|
- x = JsonDataType.first
- assert_equal({ "k1" => "v1", "k2" => "v2", "k3" => [1, 2, 3] }, x.payload)
- end
-
- def test_null_json
- @connection.execute "insert into json_data_type (payload) VALUES(null)"
- x = JsonDataType.first
- assert_nil(x.payload)
- end
-
- def test_select_array_json_value
- @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|
- x = JsonDataType.first
- assert_equal(["v0", { "k1" => "v1" }], x.payload)
- end
-
- def test_select_nil_json_after_create
- json = JsonDataType.create(payload: nil)
- x = JsonDataType.where(payload: nil).first
- assert_equal(json, x)
- end
-
- def test_select_nil_json_after_update
- json = JsonDataType.create(payload: "foo")
- x = JsonDataType.where(payload: nil).first
- assert_nil(x)
-
- json.update_attributes payload: nil
- x = JsonDataType.where(payload: nil).first
- assert_equal(json.reload, x)
- end
-
- def test_rewrite_array_json_value
- @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|
- x = JsonDataType.first
- x.payload = ["v1", { "k2" => "v2" }, "v3"]
- assert x.save!
- end
-
- def test_with_store_accessors
- x = JsonDataType.new(resolution: "320×480")
- assert_equal "320×480", x.resolution
-
- x.save!
- x = JsonDataType.first
- assert_equal "320×480", x.resolution
-
- x.resolution = "640×1136"
- x.save!
-
- x = JsonDataType.first
- assert_equal "640×1136", x.resolution
- end
-
- def test_duplication_with_store_accessors
- x = JsonDataType.new(resolution: "320×480")
- assert_equal "320×480", x.resolution
-
- y = x.dup
- assert_equal "320×480", y.resolution
- end
-
- def test_yaml_round_trip_with_store_accessors
- x = JsonDataType.new(resolution: "320×480")
- assert_equal "320×480", x.resolution
-
- y = YAML.load(YAML.dump(x))
- assert_equal "320×480", y.resolution
- end
-
- def test_changes_in_place
- json = JsonDataType.new
- assert_not json.changed?
-
- json.payload = { "one" => "two" }
- assert json.changed?
- assert json.payload_changed?
-
- json.save!
- assert_not json.changed?
-
- json.payload["three"] = "four"
- assert json.payload_changed?
-
- json.save!
- json.reload
-
- assert_equal({ "one" => "two", "three" => "four" }, json.payload)
- assert_not json.changed?
- end
-
- def test_assigning_string_literal
- json = JsonDataType.create(payload: "foo")
- assert_equal "foo", json.payload
- end
-
- def test_assigning_number
- json = JsonDataType.create(payload: 1.234)
- assert_equal 1.234, json.payload
- end
-
- def test_assigning_boolean
- json = JsonDataType.create(payload: true)
- assert_equal true, json.payload
- end
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
deleted file mode 100644
index 2c778b1150..0000000000
--- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
+++ /dev/null
@@ -1,151 +0,0 @@
-require "cases/helper"
-
-# a suite of tests to ensure the ConnectionAdapters#MysqlAdapter can handle tables with
-# reserved word names (ie: group, order, values, etc...)
-class Mysql2ReservedWordTest < ActiveRecord::Mysql2TestCase
- class Group < ActiveRecord::Base
- Group.table_name = "group"
- belongs_to :select
- has_one :values
- end
-
- class Select < ActiveRecord::Base
- Select.table_name = "select"
- has_many :groups
- end
-
- class Values < ActiveRecord::Base
- Values.table_name = "values"
- end
-
- class Distinct < ActiveRecord::Base
- Distinct.table_name = "distinct"
- has_and_belongs_to_many :selects
- has_many :values, through: :groups
- end
-
- def setup
- @connection = ActiveRecord::Base.connection
-
- # we call execute directly here (and do similar below) because ActiveRecord::Base#create_table()
- # will fail with these table names if these test cases fail
-
- create_tables_directly "group" => "id int auto_increment primary key, `order` varchar(255), select_id int",
- "select" => "id int auto_increment primary key",
- "values" => "id int auto_increment primary key, group_id int",
- "distinct" => "id int auto_increment primary key",
- "distinct_select" => "distinct_id int, select_id int"
- end
-
- teardown do
- drop_tables_directly ["group", "select", "values", "distinct", "distinct_select", "order"]
- end
-
- # create tables with reserved-word names and columns
- def test_create_tables
- assert_nothing_raised {
- @connection.create_table :order do |t|
- t.column :group, :string
- end
- }
- end
-
- # rename tables with reserved-word names
- def test_rename_tables
- assert_nothing_raised { @connection.rename_table(:group, :order) }
- end
-
- # alter column with a reserved-word name in a table with a reserved-word name
- def test_change_columns
- assert_nothing_raised { @connection.change_column_default(:group, :order, "whatever") }
- #the quoting here will reveal any double quoting issues in change_column's interaction with the column method in the adapter
- assert_nothing_raised { @connection.change_column("group", "order", :Int, default: 0) }
- assert_nothing_raised { @connection.rename_column(:group, :order, :values) }
- end
-
- # introspect table with reserved word name
- def test_introspect
- assert_nothing_raised { @connection.columns(:group) }
- assert_nothing_raised { @connection.indexes(:group) }
- end
-
- #fixtures
- self.use_instantiated_fixtures = true
- self.use_transactional_tests = false
-
- #activerecord model class with reserved-word table name
- def test_activerecord_model
- create_test_fixtures :select, :distinct, :group, :values, :distinct_select
- x = nil
- assert_nothing_raised { x = Group.new }
- x.order = "x"
- assert_nothing_raised { x.save }
- x.order = "y"
- assert_nothing_raised { x.save }
- assert_nothing_raised { Group.find_by_order("y") }
- assert_nothing_raised { Group.find(1) }
- end
-
- # has_one association with reserved-word table name
- def test_has_one_associations
- create_test_fixtures :select, :distinct, :group, :values, :distinct_select
- v = nil
- assert_nothing_raised { v = Group.find(1).values }
- assert_equal 2, v.id
- end
-
- # belongs_to association with reserved-word table name
- def test_belongs_to_associations
- create_test_fixtures :select, :distinct, :group, :values, :distinct_select
- gs = nil
- assert_nothing_raised { gs = Select.find(2).groups }
- assert_equal gs.length, 2
- assert(gs.collect(&:id).sort == [2, 3])
- end
-
- # has_and_belongs_to_many with reserved-word table name
- def test_has_and_belongs_to_many
- create_test_fixtures :select, :distinct, :group, :values, :distinct_select
- s = nil
- assert_nothing_raised { s = Distinct.find(1).selects }
- assert_equal s.length, 2
- assert(s.collect(&:id).sort == [1, 2])
- end
-
- # activerecord model introspection with reserved-word table and column names
- def test_activerecord_introspection
- assert_nothing_raised { Group.table_exists? }
- assert_nothing_raised { Group.columns }
- end
-
- # Calculations
- def test_calculations_work_with_reserved_words
- assert_nothing_raised { Group.count }
- end
-
- def test_associations_work_with_reserved_words
- assert_nothing_raised { Select.all.merge!(includes: [:groups]).to_a }
- end
-
- #the following functions were added to DRY test cases
-
- private
- # custom fixture loader, uses FixtureSet#create_fixtures and appends base_path to the current file's path
- def create_test_fixtures(*fixture_names)
- ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names)
- end
-
- # custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name
- def drop_tables_directly(table_names, connection = @connection)
- table_names.each do |name|
- connection.drop_table name, if_exists: true
- end
- end
-
- # custom create table, uses execute on connection to create a table, note: escapes table_name, does NOT escape columns
- def create_tables_directly(tables, connection = @connection)
- tables.each do |table_name, column_properties|
- connection.execute("CREATE TABLE `#{table_name}` ( #{column_properties} )")
- end
- 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 605baa9905..251a50e41e 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
@@ -18,7 +18,7 @@ class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase
ActiveRecord::SchemaMigration.create_table
- assert connection.column_exists?(table_name, :version, :string, collation: "utf8_general_ci")
+ assert connection.column_exists?(table_name, :version, :string)
end
end
@@ -29,7 +29,7 @@ class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase
ActiveRecord::InternalMetadata.create_table
- assert connection.column_exists?(table_name, :key, :string, collation: "utf8_general_ci")
+ assert connection.column_exists?(table_name, :key, :string)
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
index a0823be143..71dcfaa241 100644
--- a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
@@ -58,9 +58,9 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase
test "schema dump includes unsigned option" do
schema = dump_table_schema "unsigned_types"
- assert_match %r{t.integer\s+"unsigned_integer",\s+unsigned: true$}, schema
- assert_match %r{t.bigint\s+"unsigned_bigint",\s+unsigned: true$}, schema
- assert_match %r{t.float\s+"unsigned_float",\s+limit: 24,\s+unsigned: true$}, schema
- assert_match %r{t.decimal\s+"unsigned_decimal",\s+precision: 10,\s+scale: 2,\s+unsigned: true$}, schema
+ assert_match %r{t\.integer\s+"unsigned_integer",\s+unsigned: true$}, schema
+ assert_match %r{t\.bigint\s+"unsigned_bigint",\s+unsigned: true$}, schema
+ assert_match %r{t\.float\s+"unsigned_float",\s+limit: 24,\s+unsigned: true$}, schema
+ assert_match %r{t\.decimal\s+"unsigned_decimal",\s+precision: 10,\s+scale: 2,\s+unsigned: true$}, schema
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb
index c78c6178ff..121c62dadf 100644
--- a/activerecord/test/cases/adapters/postgresql/array_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/array_test.rb
@@ -191,6 +191,12 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
assert_equal(PgArray.last.tags, tag_values)
end
+ def test_insert_fixtures
+ tag_values = ["val1", "val2", "val3_with_'_multiple_quote_'_chars"]
+ @connection.insert_fixtures([{ "tags" => tag_values }], "pg_arrays")
+ assert_equal(PgArray.last.tags, tag_values)
+ end
+
def test_attribute_for_inspect_for_array_field
record = PgArray.new { |a| a.ratings = (1..10).to_a }
assert_equal("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]", record.attribute_for_inspect(:ratings))
diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
index 99175e8091..65baed34e9 100644
--- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
@@ -47,7 +47,7 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase
end
def test_type_cast_binary_value
- data = "\u001F\x8B".force_encoding("BINARY")
+ data = "\u001F\x8B".dup.force_encoding("BINARY")
assert_equal(data, @type.deserialize(data))
end
@@ -96,7 +96,7 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase
end
def test_write_binary
- data = File.read(File.join(File.dirname(__FILE__), "..", "..", "..", "assets", "example.log"))
+ data = File.read(File.join(__dir__, "..", "..", "..", "assets", "example.log"))
assert(data.size > 1)
record = ByteaDataType.create(payload: data)
assert_not record.new_record?
diff --git a/activerecord/test/cases/adapters/postgresql/collation_test.rb b/activerecord/test/cases/adapters/postgresql/collation_test.rb
index b39e298a5d..a603221d8f 100644
--- a/activerecord/test/cases/adapters/postgresql/collation_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/collation_test.rb
@@ -47,7 +47,7 @@ class PostgresqlCollationTest < ActiveRecord::PostgreSQLTestCase
test "schema dump includes collation" do
output = dump_table_schema("postgresql_collations")
- assert_match %r{t.string\s+"string_c",\s+collation: "C"$}, output
- assert_match %r{t.text\s+"text_posix",\s+collation: "POSIX"$}, output
+ assert_match %r{t\.string\s+"string_c",\s+collation: "C"$}, output
+ assert_match %r{t\.text\s+"text_posix",\s+collation: "POSIX"$}, output
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index c52d9e37cc..32afe331fa 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -31,15 +31,21 @@ module ActiveRecord
end
def test_encoding
- assert_not_nil @connection.encoding
+ assert_queries(1) do
+ assert_not_nil @connection.encoding
+ end
end
def test_collation
- assert_not_nil @connection.collation
+ assert_queries(1) do
+ assert_not_nil @connection.collation
+ end
end
def test_ctype
- assert_not_nil @connection.ctype
+ assert_queries(1) do
+ assert_not_nil @connection.ctype
+ end
end
def test_default_client_min_messages
@@ -127,8 +133,8 @@ module ActiveRecord
if ActiveRecord::Base.connection.prepared_statements
def test_statement_key_is_logged
- bind = Relation::QueryAttribute.new(nil, 1, Type::Value.new)
- @connection.exec_query("SELECT $1::integer", "SQL", [bind], prepare: true)
+ binds = [bind_attribute(nil, 1)]
+ @connection.exec_query("SELECT $1::integer", "SQL", binds, prepare: true)
name = @subscriber.payloads.last[:statement_name]
assert name
res = @connection.exec_query("EXPLAIN (FORMAT JSON) EXECUTE #{name}(1)")
diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb
index 7493bce4fb..d79fbccf47 100644
--- a/activerecord/test/cases/adapters/postgresql/explain_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb
@@ -7,14 +7,14 @@ class PostgreSQLExplainTest < ActiveRecord::PostgreSQLTestCase
def test_explain_for_one_query
explain = Developer.where(id: 1).explain
- assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\$1 \[\["id", 1\]\]|1)), explain
+ assert_match %r(EXPLAIN for: SELECT "developers"\.\* FROM "developers" WHERE "developers"\."id" = (?:\$1 \[\["id", 1\]\]|1)), explain
assert_match %(QUERY PLAN), explain
end
def test_explain_with_eager_loading
explain = Developer.where(id: 1).includes(:audit_logs).explain
assert_match %(QUERY PLAN), explain
- assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\$1 \[\["id", 1\]\]|1)), explain
+ assert_match %r(EXPLAIN for: SELECT "developers"\.\* FROM "developers" WHERE "developers"\."id" = (?:\$1 \[\["id", 1\]\]|1)), explain
assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
index c1f3a4ae2c..3b6840a1c9 100644
--- a/activerecord/test/cases/adapters/postgresql/geometric_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
@@ -93,8 +93,6 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase
end
def test_empty_string_assignment
- assert_nothing_raised { PostgresqlPoint.new(x: "") }
-
p = PostgresqlPoint.new(x: "")
assert_nil p.x
end
diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb
index d4e627001c..6aa60630c2 100644
--- a/activerecord/test/cases/adapters/postgresql/json_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/json_test.rb
@@ -1,223 +1,36 @@
require "cases/helper"
-require "support/schema_dumping_helper"
+require "cases/json_shared_test_cases"
module PostgresqlJSONSharedTestCases
- include SchemaDumpingHelper
-
- class JsonDataType < ActiveRecord::Base
- self.table_name = "json_data_type"
-
- store_accessor :settings, :resolution
- end
+ include JSONSharedTestCases
def setup
- @connection = ActiveRecord::Base.connection
- begin
- @connection.create_table("json_data_type") do |t|
- t.public_send column_type, "payload", default: {} # t.json 'payload', default: {}
- t.public_send column_type, "settings" # t.json 'settings'
- t.public_send column_type, "objects", array: true # t.json 'objects', array: true
- end
- rescue ActiveRecord::StatementInvalid
- skip "do not test on PostgreSQL without #{column_type} type."
+ super
+ @connection.create_table("json_data_type") do |t|
+ t.public_send column_type, "payload", default: {} # t.json 'payload', default: {}
+ t.public_send column_type, "settings" # t.json 'settings'
+ t.public_send column_type, "objects", array: true # t.json 'objects', array: true
end
- end
-
- def teardown
- @connection.drop_table :json_data_type, if_exists: true
- JsonDataType.reset_column_information
- end
-
- def test_column
- column = JsonDataType.columns_hash["payload"]
- assert_equal column_type, column.type
- assert_equal column_type.to_s, column.sql_type
- assert_not column.array?
-
- type = JsonDataType.type_for_attribute("payload")
- assert_not type.binary?
+ rescue ActiveRecord::StatementInvalid
+ skip "do not test on PostgreSQL without #{column_type} type."
end
def test_default
@connection.add_column "json_data_type", "permissions", column_type, default: { "users": "read", "posts": ["read", "write"] }
- JsonDataType.reset_column_information
+ klass.reset_column_information
assert_equal({ "users" => "read", "posts" => ["read", "write"] }, JsonDataType.column_defaults["permissions"])
assert_equal({ "users" => "read", "posts" => ["read", "write"] }, JsonDataType.new.permissions)
- ensure
- JsonDataType.reset_column_information
- end
-
- def test_change_table_supports_json
- @connection.transaction do
- @connection.change_table("json_data_type") do |t|
- t.public_send column_type, "users", default: "{}" # t.json 'users', default: '{}'
- end
- JsonDataType.reset_column_information
- column = JsonDataType.columns_hash["users"]
- assert_equal column_type, column.type
-
- raise ActiveRecord::Rollback # reset the schema change
- end
- ensure
- JsonDataType.reset_column_information
- end
-
- def test_schema_dumping
- output = dump_table_schema("json_data_type")
- assert_match(/t\.#{column_type.to_s}\s+"payload",\s+default: {}/, output)
- end
-
- def test_cast_value_on_write
- x = JsonDataType.new payload: { "string" => "foo", :symbol => :bar }
- assert_equal({ "string" => "foo", :symbol => :bar }, x.payload_before_type_cast)
- assert_equal({ "string" => "foo", "symbol" => "bar" }, x.payload)
- x.save
- assert_equal({ "string" => "foo", "symbol" => "bar" }, x.reload.payload)
end
def test_deserialize_with_array
- x = JsonDataType.new(objects: ["foo" => "bar"])
+ x = klass.new(objects: ["foo" => "bar"])
assert_equal ["foo" => "bar"], x.objects
x.save!
assert_equal ["foo" => "bar"], x.objects
x.reload
assert_equal ["foo" => "bar"], x.objects
end
-
- def test_type_cast_json
- type = JsonDataType.type_for_attribute("payload")
-
- data = "{\"a_key\":\"a_value\"}"
- hash = type.deserialize(data)
- assert_equal({ "a_key" => "a_value" }, hash)
- assert_equal({ "a_key" => "a_value" }, type.deserialize(data))
-
- assert_equal({}, type.deserialize("{}"))
- assert_equal({ "key" => nil }, type.deserialize('{"key": null}'))
- assert_equal({ "c" => "}", '"a"' => 'b "a b' }, type.deserialize(%q({"c":"}", "\"a\"":"b \"a b"})))
- end
-
- def test_rewrite
- @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')"
- x = JsonDataType.first
- x.payload = { '"a\'' => "b" }
- assert x.save!
- end
-
- def test_select
- @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')"
- x = JsonDataType.first
- assert_equal({ "k" => "v" }, x.payload)
- end
-
- def test_select_multikey
- @connection.execute %q|insert into json_data_type (payload) VALUES ('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}')|
- x = JsonDataType.first
- assert_equal({ "k1" => "v1", "k2" => "v2", "k3" => [1, 2, 3] }, x.payload)
- end
-
- def test_null_json
- @connection.execute "insert into json_data_type (payload) VALUES(null)"
- x = JsonDataType.first
- assert_nil(x.payload)
- end
-
- def test_select_nil_json_after_create
- json = JsonDataType.create(payload: nil)
- x = JsonDataType.where(payload: nil).first
- assert_equal(json, x)
- end
-
- def test_select_nil_json_after_update
- json = JsonDataType.create(payload: "foo")
- x = JsonDataType.where(payload: nil).first
- assert_nil(x)
-
- json.update_attributes payload: nil
- x = JsonDataType.where(payload: nil).first
- assert_equal(json.reload, x)
- end
-
- def test_select_array_json_value
- @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|
- x = JsonDataType.first
- assert_equal(["v0", { "k1" => "v1" }], x.payload)
- end
-
- def test_rewrite_array_json_value
- @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|
- x = JsonDataType.first
- x.payload = ["v1", { "k2" => "v2" }, "v3"]
- assert x.save!
- end
-
- def test_with_store_accessors
- x = JsonDataType.new(resolution: "320×480")
- assert_equal "320×480", x.resolution
-
- x.save!
- x = JsonDataType.first
- assert_equal "320×480", x.resolution
-
- x.resolution = "640×1136"
- x.save!
-
- x = JsonDataType.first
- assert_equal "640×1136", x.resolution
- end
-
- def test_duplication_with_store_accessors
- x = JsonDataType.new(resolution: "320×480")
- assert_equal "320×480", x.resolution
-
- y = x.dup
- assert_equal "320×480", y.resolution
- end
-
- def test_yaml_round_trip_with_store_accessors
- x = JsonDataType.new(resolution: "320×480")
- assert_equal "320×480", x.resolution
-
- y = YAML.load(YAML.dump(x))
- assert_equal "320×480", y.resolution
- end
-
- def test_changes_in_place
- json = JsonDataType.new
- assert_not json.changed?
-
- json.payload = { "one" => "two" }
- assert json.changed?
- assert json.payload_changed?
-
- json.save!
- assert_not json.changed?
-
- json.payload["three"] = "four"
- assert json.payload_changed?
-
- json.save!
- json.reload
-
- assert_equal({ "one" => "two", "three" => "four" }, json.payload)
- assert_not json.changed?
- end
-
- def test_assigning_string_literal
- json = JsonDataType.create(payload: "foo")
- assert_equal "foo", json.payload
- end
-
- def test_assigning_number
- json = JsonDataType.create(payload: 1.234)
- assert_equal 1.234, json.payload
- end
-
- def test_assigning_boolean
- json = JsonDataType.create(payload: true)
- assert_equal true, json.payload
- end
end
class PostgresqlJSONTest < ActiveRecord::PostgreSQLTestCase
diff --git a/activerecord/test/cases/adapters/postgresql/money_test.rb b/activerecord/test/cases/adapters/postgresql/money_test.rb
index 1b5d8362af..ea060345a7 100644
--- a/activerecord/test/cases/adapters/postgresql/money_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/money_test.rb
@@ -47,10 +47,10 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase
def test_money_type_cast
type = PostgresqlMoney.type_for_attribute("wealth")
- assert_equal(12345678.12, type.cast("$12,345,678.12"))
- assert_equal(12345678.12, type.cast("$12.345.678,12"))
- assert_equal(-1.15, type.cast("-$1.15"))
- assert_equal(-2.25, type.cast("($2.25)"))
+ assert_equal(12345678.12, type.cast("$12,345,678.12".dup))
+ assert_equal(12345678.12, type.cast("$12.345.678,12".dup))
+ assert_equal(-1.15, type.cast("-$1.15".dup))
+ assert_equal(-2.25, type.cast("($2.25)".dup))
end
def test_schema_dumping
@@ -60,7 +60,7 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase
end
def test_create_and_update_money
- money = PostgresqlMoney.create(wealth: "987.65")
+ money = PostgresqlMoney.create(wealth: "987.65".dup)
assert_equal 987.65, money.wealth
new_value = BigDecimal.new("123.45")
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 003e6e62e7..76e0ad60fe 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -202,8 +202,8 @@ module ActiveRecord
string = @connection.quote("foo")
@connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
- bind = Relation::QueryAttribute.new("id", 1, Type::Value.new)
- result = @connection.exec_query("SELECT id, data FROM ex WHERE id = $1", nil, [bind])
+ binds = [bind_attribute("id", 1)]
+ result = @connection.exec_query("SELECT id, data FROM ex WHERE id = $1", nil, binds)
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
@@ -217,8 +217,8 @@ module ActiveRecord
string = @connection.quote("foo")
@connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
- bind = Relation::QueryAttribute.new("id", "1-fuu", Type::Integer.new)
- result = @connection.exec_query("SELECT id, data FROM ex WHERE id = $1", nil, [bind])
+ binds = [bind_attribute("id", "1-fuu", Type::Integer.new)]
+ result = @connection.exec_query("SELECT id, data FROM ex WHERE id = $1", nil, binds)
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
@@ -324,13 +324,13 @@ module ActiveRecord
reset_connection
end
- def test_only_reload_type_map_once_for_every_unknown_type
+ def test_only_reload_type_map_once_for_every_unrecognized_type
silence_warnings do
assert_queries 2, ignore_none: true do
- @connection.select_all "SELECT NULL::anyelement"
+ @connection.select_all "select 'pg_catalog.pg_class'::regclass"
end
assert_queries 1, ignore_none: true do
- @connection.select_all "SELECT NULL::anyelement"
+ @connection.select_all "select 'pg_catalog.pg_class'::regclass"
end
assert_queries 2, ignore_none: true do
@connection.select_all "SELECT NULL::anyarray"
@@ -340,13 +340,13 @@ module ActiveRecord
reset_connection
end
- def test_only_warn_on_first_encounter_of_unknown_oid
+ def test_only_warn_on_first_encounter_of_unrecognized_oid
warning = capture(:stderr) {
- @connection.select_all "SELECT NULL::anyelement"
- @connection.select_all "SELECT NULL::anyelement"
- @connection.select_all "SELECT NULL::anyelement"
+ @connection.select_all "select 'pg_catalog.pg_class'::regclass"
+ @connection.select_all "select 'pg_catalog.pg_class'::regclass"
+ @connection.select_all "select 'pg_catalog.pg_class'::regclass"
}
- assert_match(/\Aunknown OID \d+: failed to recognize type of 'anyelement'. It will be treated as String.\n\z/, warning)
+ assert_match(/\Aunknown OID \d+: failed to recognize type of 'regclass'\. It will be treated as String\.\n\z/, warning)
ensure
reset_connection
end
diff --git a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb
index c5c540cebc..0ff04bfa27 100644
--- a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb
@@ -1,150 +1,111 @@
require "cases/helper"
require "support/connection_helper"
-if ActiveRecord::Base.connection.respond_to?(:supports_alter_constraint?) &&
- ActiveRecord::Base.connection.supports_alter_constraint?
- class PostgreSQLReferentialIntegrityWithAlterConstraintTest < ActiveRecord::PostgreSQLTestCase
- self.use_transactional_tests = false
+class PostgreSQLReferentialIntegrityTest < ActiveRecord::PostgreSQLTestCase
+ self.use_transactional_tests = false
- include ConnectionHelper
+ include ConnectionHelper
- IS_REFERENTIAL_INTEGRITY_SQL = lambda do |sql|
- sql.match(/SET CONSTRAINTS ALL DEFERRED/)
- end
+ IS_REFERENTIAL_INTEGRITY_SQL = lambda do |sql|
+ sql.match(/DISABLE TRIGGER ALL/) || sql.match(/ENABLE TRIGGER ALL/)
+ end
- module ProgrammerMistake
- def execute(sql)
- if IS_REFERENTIAL_INTEGRITY_SQL.call(sql)
- raise ArgumentError, "something is not right."
- else
- super
- end
+ module MissingSuperuserPrivileges
+ def execute(sql)
+ if IS_REFERENTIAL_INTEGRITY_SQL.call(sql)
+ super "BROKEN;" rescue nil # put transaction in broken state
+ raise ActiveRecord::StatementInvalid, "PG::InsufficientPrivilege"
+ else
+ super
end
end
+ end
- def setup
- @connection = ActiveRecord::Base.connection
- end
-
- def teardown
- reset_connection
- end
-
- def test_errors_bubble_up
- @connection.extend ProgrammerMistake
-
- assert_raises ArgumentError do
- @connection.disable_referential_integrity {}
+ module ProgrammerMistake
+ def execute(sql)
+ if IS_REFERENTIAL_INTEGRITY_SQL.call(sql)
+ raise ArgumentError, "something is not right."
+ else
+ super
end
end
end
-else
- class PostgreSQLReferentialIntegrityWithDisableTriggerTest < ActiveRecord::PostgreSQLTestCase
- self.use_transactional_tests = false
- include ConnectionHelper
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
- IS_REFERENTIAL_INTEGRITY_SQL = lambda do |sql|
- sql.match(/DISABLE TRIGGER ALL/) || sql.match(/ENABLE TRIGGER ALL/)
+ def teardown
+ reset_connection
+ if ActiveRecord::Base.connection.is_a?(MissingSuperuserPrivileges)
+ raise "MissingSuperuserPrivileges patch was not removed"
end
+ end
- module MissingSuperuserPrivileges
- def execute(sql)
- if IS_REFERENTIAL_INTEGRITY_SQL.call(sql)
- super "BROKEN;" rescue nil # put transaction in broken state
- raise ActiveRecord::StatementInvalid, "PG::InsufficientPrivilege"
- else
- super
- end
- end
- end
+ def test_should_reraise_invalid_foreign_key_exception_and_show_warning
+ @connection.extend MissingSuperuserPrivileges
- module ProgrammerMistake
- def execute(sql)
- if IS_REFERENTIAL_INTEGRITY_SQL.call(sql)
- raise ArgumentError, "something is not right."
- else
- super
+ warning = capture(:stderr) do
+ e = assert_raises(ActiveRecord::InvalidForeignKey) do
+ @connection.disable_referential_integrity do
+ raise ActiveRecord::InvalidForeignKey, "Should be re-raised"
end
end
+ assert_equal "Should be re-raised", e.message
end
+ assert_match (/WARNING: Rails was not able to disable referential integrity/), warning
+ assert_match (/cause: PG::InsufficientPrivilege/), warning
+ end
- def setup
- @connection = ActiveRecord::Base.connection
- end
-
- def teardown
- reset_connection
- if ActiveRecord::Base.connection.is_a?(MissingSuperuserPrivileges)
- raise "MissingSuperuserPrivileges patch was not removed"
- end
- end
-
- def test_should_reraise_invalid_foreign_key_exception_and_show_warning
- @connection.extend MissingSuperuserPrivileges
+ def test_does_not_print_warning_if_no_invalid_foreign_key_exception_was_raised
+ @connection.extend MissingSuperuserPrivileges
- warning = capture(:stderr) do
- e = assert_raises(ActiveRecord::InvalidForeignKey) do
- @connection.disable_referential_integrity do
- raise ActiveRecord::InvalidForeignKey, "Should be re-raised"
- end
+ warning = capture(:stderr) do
+ e = assert_raises(ActiveRecord::StatementInvalid) do
+ @connection.disable_referential_integrity do
+ raise ActiveRecord::StatementInvalid, "Should be re-raised"
end
- assert_equal "Should be re-raised", e.message
end
- assert_match (/WARNING: Rails was not able to disable referential integrity/), warning
- assert_match (/cause: PG::InsufficientPrivilege/), warning
+ assert_equal "Should be re-raised", e.message
end
+ assert warning.blank?, "expected no warnings but got:\n#{warning}"
+ end
- def test_does_not_print_warning_if_no_invalid_foreign_key_exception_was_raised
- @connection.extend MissingSuperuserPrivileges
+ def test_does_not_break_transactions
+ @connection.extend MissingSuperuserPrivileges
- warning = capture(:stderr) do
- e = assert_raises(ActiveRecord::StatementInvalid) do
- @connection.disable_referential_integrity do
- raise ActiveRecord::StatementInvalid, "Should be re-raised"
- end
- end
- assert_equal "Should be re-raised", e.message
+ @connection.transaction do
+ @connection.disable_referential_integrity do
+ assert_transaction_is_not_broken
end
- assert warning.blank?, "expected no warnings but got:\n#{warning}"
+ assert_transaction_is_not_broken
end
+ end
- def test_does_not_break_transactions
- @connection.extend MissingSuperuserPrivileges
+ def test_does_not_break_nested_transactions
+ @connection.extend MissingSuperuserPrivileges
- @connection.transaction do
+ @connection.transaction do
+ @connection.transaction(requires_new: true) do
@connection.disable_referential_integrity do
assert_transaction_is_not_broken
end
- assert_transaction_is_not_broken
end
+ assert_transaction_is_not_broken
end
+ end
- def test_does_not_break_nested_transactions
- @connection.extend MissingSuperuserPrivileges
+ def test_only_catch_active_record_errors_others_bubble_up
+ @connection.extend ProgrammerMistake
- @connection.transaction do
- @connection.transaction(requires_new: true) do
- @connection.disable_referential_integrity do
- assert_transaction_is_not_broken
- end
- end
- assert_transaction_is_not_broken
- end
+ assert_raises ArgumentError do
+ @connection.disable_referential_integrity {}
end
+ end
- def test_only_catch_active_record_errors_others_bubble_up
- @connection.extend ProgrammerMistake
+ private
- assert_raises ArgumentError do
- @connection.disable_referential_integrity {}
- end
+ def assert_transaction_is_not_broken
+ assert_equal 1, @connection.select_value("SELECT 1")
end
-
- private
-
- def assert_transaction_is_not_broken
- assert_equal 1, @connection.select_value("SELECT 1")
- end
- end
end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
index f6a07da85f..f86a76e08a 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
@@ -68,24 +68,13 @@ class SchemaAuthorizationTest < ActiveRecord::PostgreSQLTestCase
USERS.each do |u|
@connection.clear_cache!
set_session_auth u
- assert_equal u, @connection.select_value("SELECT name FROM #{TABLE_NAME} WHERE id = $1", "SQL", [bind_param(1)])
+ assert_equal u, @connection.select_value("SELECT name FROM #{TABLE_NAME} WHERE id = $1", "SQL", [bind_attribute("id", 1)])
set_session_auth
end
end
end
end
- def test_schema_uniqueness
- assert_nothing_raised do
- set_session_auth
- USERS.each do |u|
- set_session_auth u
- assert_equal u, @connection.select_value("SELECT name FROM #{TABLE_NAME} WHERE id = 1")
- set_session_auth
- end
- end
- end
-
def test_sequence_schema_caching
assert_nothing_raised do
USERS.each do |u|
@@ -112,8 +101,4 @@ class SchemaAuthorizationTest < ActiveRecord::PostgreSQLTestCase
def set_session_auth(auth = nil)
@connection.session_auth = auth || "default"
end
-
- def bind_param(value)
- ActiveRecord::Relation::QueryAttribute.new(nil, value, ActiveRecord::Type::Value.new)
- end
end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index 75e30e4fe9..f6b957476b 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -169,17 +169,17 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
def test_raise_wrapped_exception_on_bad_prepare
assert_raises(ActiveRecord::StatementInvalid) do
- @connection.exec_query "select * from developers where id = ?", "sql", [bind_param(1)]
+ @connection.exec_query "select * from developers where id = ?", "sql", [bind_attribute("id", 1)]
end
end
if ActiveRecord::Base.connection.prepared_statements
def test_schema_change_with_prepared_stmt
altered = false
- @connection.exec_query "select * from developers where id = $1", "sql", [bind_param(1)]
+ @connection.exec_query "select * from developers where id = $1", "sql", [bind_attribute("id", 1)]
@connection.exec_query "alter table developers add column zomg int", "sql", []
altered = true
- @connection.exec_query "select * from developers where id = $1", "sql", [bind_param(1)]
+ @connection.exec_query "select * from developers where id = $1", "sql", [bind_attribute("id", 1)]
ensure
# We are not using DROP COLUMN IF EXISTS because that syntax is only
# supported by pg 9.X
@@ -467,10 +467,6 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
assert_equal this_index_column, this_index.columns[0]
assert_equal this_index_name, this_index.name
end
-
- def bind_param(value)
- ActiveRecord::Relation::QueryAttribute.new(nil, value, ActiveRecord::Type::Value.new)
- end
end
class SchemaForeignKeyTest < ActiveRecord::PostgreSQLTestCase
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index 6aa6a79705..8eddd81c38 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -13,6 +13,10 @@ module PostgresqlUUIDHelper
def uuid_function
connection.supports_pgcrypto_uuid? ? "gen_random_uuid()" : "uuid_generate_v4()"
end
+
+ def uuid_default
+ connection.supports_pgcrypto_uuid? ? {} : { default: uuid_function }
+ end
end
class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase
@@ -36,7 +40,8 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase
drop_table "uuid_data_type"
end
- if ActiveRecord::Base.connection.supports_pgcrypto_uuid?
+ if ActiveRecord::Base.connection.respond_to?(:supports_pgcrypto_uuid?) &&
+ ActiveRecord::Base.connection.supports_pgcrypto_uuid?
def test_uuid_column_default
connection.add_column :uuid_data_type, :thingy, :uuid, null: false, default: "gen_random_uuid()"
UUIDType.reset_column_information
@@ -59,6 +64,16 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase
UUIDType.reset_column_information
end
+ def test_add_column_with_null_true_and_default_nil
+ connection.add_column :uuid_data_type, :thingy, :uuid, null: true, default: nil
+
+ UUIDType.reset_column_information
+ column = UUIDType.columns_hash["thingy"]
+
+ assert column.null
+ assert_nil column.default
+ end
+
def test_data_type_of_uuid_types
column = UUIDType.columns_hash["guid"]
assert_equal :uuid, column.type
@@ -178,7 +193,7 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase
t.uuid "other_uuid_2", default: "my_uuid_generator()"
end
- connection.create_table("pg_uuids_3", id: :uuid) do |t|
+ connection.create_table("pg_uuids_3", id: :uuid, **uuid_default) do |t|
t.string "name"
end
end
@@ -320,10 +335,10 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::PostgreSQLTestCase
setup do
connection.transaction do
- connection.create_table("pg_uuid_posts", id: :uuid) do |t|
+ connection.create_table("pg_uuid_posts", id: :uuid, **uuid_default) do |t|
t.string "title"
end
- connection.create_table("pg_uuid_comments", id: :uuid) do |t|
+ connection.create_table("pg_uuid_comments", id: :uuid, **uuid_default) do |t|
t.references :uuid_post, type: :uuid
t.string "content"
end
diff --git a/activerecord/test/cases/adapters/sqlite3/collation_test.rb b/activerecord/test/cases/adapters/sqlite3/collation_test.rb
index 28e8f12c18..dd88ed3656 100644
--- a/activerecord/test/cases/adapters/sqlite3/collation_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/collation_test.rb
@@ -47,7 +47,7 @@ class SQLite3CollationTest < ActiveRecord::SQLite3TestCase
test "schema dump includes collation" do
output = dump_table_schema("collation_table_sqlite3")
- assert_match %r{t.string\s+"string_nocase",\s+collation: "NOCASE"$}, output
- assert_match %r{t.text\s+"text_rtrim",\s+collation: "RTRIM"$}, output
+ assert_match %r{t\.string\s+"string_nocase",\s+collation: "NOCASE"$}, output
+ assert_match %r{t\.text\s+"text_rtrim",\s+collation: "RTRIM"$}, output
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/explain_test.rb b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
index 128acb79cf..29d97ae78c 100644
--- a/activerecord/test/cases/adapters/sqlite3/explain_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
@@ -7,13 +7,13 @@ class SQLite3ExplainTest < ActiveRecord::SQLite3TestCase
def test_explain_for_one_query
explain = Developer.where(id: 1).explain
- assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\? \[\["id", 1\]\]|1)), explain
+ assert_match %r(EXPLAIN for: SELECT "developers"\.\* FROM "developers" WHERE "developers"\."id" = (?:\? \[\["id", 1\]\]|1)), explain
assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
end
def test_explain_with_eager_loading
explain = Developer.where(id: 1).includes(:audit_logs).explain
- assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\? \[\["id", 1\]\]|1)), explain
+ assert_match %r(EXPLAIN for: SELECT "developers"\.\* FROM "developers" WHERE "developers"\."id" = (?:\? \[\["id", 1\]\]|1)), explain
assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain
assert_match(/(SCAN )?TABLE audit_logs/, explain)
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 2179d1294c..cf0c37f70c 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -66,11 +66,11 @@ module ActiveRecord
def test_exec_insert
with_example_table do
- vals = [Relation::QueryAttribute.new("number", 10, Type::Value.new)]
- @conn.exec_insert("insert into ex (number) VALUES (?)", "SQL", vals)
+ binds = [bind_attribute("number", 10)]
+ @conn.exec_insert("insert into ex (number) VALUES (?)", "SQL", binds)
result = @conn.exec_query(
- "select number from ex where number = ?", "SQL", vals)
+ "select number from ex where number = ?", "SQL", binds)
assert_equal 1, result.rows.length
assert_equal 10, result.rows.first.first
@@ -134,7 +134,7 @@ module ActiveRecord
with_example_table "id int, data string" do
@conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
result = @conn.exec_query(
- "SELECT id, data FROM ex WHERE id = ?", nil, [Relation::QueryAttribute.new(nil, 1, Type::Value.new)])
+ "SELECT id, data FROM ex WHERE id = ?", nil, [bind_attribute("id", 1)])
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
@@ -148,7 +148,7 @@ module ActiveRecord
@conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
result = @conn.exec_query(
- "SELECT id, data FROM ex WHERE id = ?", nil, [Relation::QueryAttribute.new("id", "1-fuu", Type::Integer.new)])
+ "SELECT id, data FROM ex WHERE id = ?", nil, [bind_attribute("id", "1-fuu", Type::Integer.new)])
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
@@ -165,7 +165,7 @@ module ActiveRecord
data binary
)
eosql
- str = "\x80".force_encoding("ASCII-8BIT")
+ str = "\x80".dup.force_encoding("ASCII-8BIT")
binary = DualEncoding.new name: "いただきます!", data: str
binary.save!
assert_equal str, binary.data
@@ -174,7 +174,7 @@ module ActiveRecord
end
def test_type_cast_should_not_mutate_encoding
- name = "hello".force_encoding(Encoding::ASCII_8BIT)
+ name = "hello".dup.force_encoding(Encoding::ASCII_8BIT)
Owner.create(name: name)
assert_equal Encoding::ASCII_8BIT, name.encoding
ensure
diff --git a/activerecord/test/cases/associations/association_scope_test.rb b/activerecord/test/cases/associations/association_scope_test.rb
index c322333f6d..c54542ff7b 100644
--- a/activerecord/test/cases/associations/association_scope_test.rb
+++ b/activerecord/test/cases/associations/association_scope_test.rb
@@ -6,8 +6,7 @@ module ActiveRecord
module Associations
class AssociationScopeTest < ActiveRecord::TestCase
test "does not duplicate conditions" do
- scope = AssociationScope.scope(Author.new.association(:welcome_posts),
- Author.connection)
+ scope = AssociationScope.scope(Author.new.association(:welcome_posts))
binds = scope.where_clause.binds.map(&:value)
assert_equal binds.uniq, binds
end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 5b08ba1358..a727cc6e60 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -136,6 +136,24 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal david, ship.developer
end
+ def test_default_with_lambda
+ model = Class.new(ActiveRecord::Base) do
+ self.table_name = "ships"
+ def self.name; "Temp"; end
+ belongs_to :developer, default: -> { default_developer }
+
+ def default_developer
+ Developer.first
+ end
+ end
+
+ ship = model.create!
+ assert_equal developers(:david), ship.developer
+
+ ship = model.create!(developer: developers(:jamis))
+ assert_equal developers(:jamis), ship.developer
+ end
+
def test_default_scope_on_relations_is_not_cached
counter = 0
@@ -362,7 +380,6 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
sponsor.sponsorable = Member.new name: "Bert"
assert_equal Member, sponsor.association(:sponsorable).send(:klass)
- assert_equal "members", sponsor.association(:sponsorable).aliased_table_name
end
def test_with_polymorphic_and_condition
diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb
index 7721bd5cd9..f9d1e44595 100644
--- a/activerecord/test/cases/associations/callbacks_test.rb
+++ b/activerecord/test/cases/associations/callbacks_test.rb
@@ -128,7 +128,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
assert ar.developers_log.empty?
alice = Developer.new(name: "alice")
ar.developers_with_callbacks << alice
- assert_equal"after_adding#{alice.id}", ar.developers_log.last
+ assert_equal "after_adding#{alice.id}", ar.developers_log.last
bob = ar.developers_with_callbacks.create(name: "bob")
assert_equal "after_adding#{bob.id}", ar.developers_log.last
diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
index 3638c87968..7b0445025c 100644
--- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
+++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -34,18 +34,12 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_with_hmt_does_not_table_name_collide_when_joining_associations
- assert_nothing_raised do
- Author.joins(:posts).eager_load(:comments).where(posts: { tags_count: 1 }).to_a
- end
authors = Author.joins(:posts).eager_load(:comments).where(posts: { tags_count: 1 }).to_a
assert_equal 1, assert_no_queries { authors.size }
assert_equal 10, assert_no_queries { authors[0].comments.size }
end
def test_eager_association_loading_grafts_stashed_associations_to_correct_parent
- assert_nothing_raised do
- Person.eager_load(primary_contact: :primary_contact).where("primary_contacts_people_2.first_name = ?", "Susan").order("people.id").to_a
- end
assert_equal people(:michael), Person.eager_load(primary_contact: :primary_contact).where("primary_contacts_people_2.first_name = ?", "Susan").order("people.id").first
end
diff --git a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb
index 4f0fe3236e..61f39b4136 100644
--- a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb
+++ b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb
@@ -11,25 +11,32 @@ end
class EagerLoadIncludeFullStiClassNamesTest < ActiveRecord::TestCase
def setup
- generate_test_objects
- end
-
- def generate_test_objects
post = Namespaced::Post.create(title: "Great stuff", body: "This is not", author_id: 1)
- Tagging.create(taggable: post)
+ @tagging = Tagging.create(taggable: post)
+ @old = ActiveRecord::Base.store_full_sti_class
end
- def test_class_names
- old = ActiveRecord::Base.store_full_sti_class
+ def teardown
+ ActiveRecord::Base.store_full_sti_class = @old
+ end
+ def test_class_names_with_includes
ActiveRecord::Base.store_full_sti_class = false
post = Namespaced::Post.includes(:tagging).find_by_title("Great stuff")
assert_nil post.tagging
ActiveRecord::Base.store_full_sti_class = true
post = Namespaced::Post.includes(:tagging).find_by_title("Great stuff")
- assert_instance_of Tagging, post.tagging
- ensure
- ActiveRecord::Base.store_full_sti_class = old
+ assert_equal @tagging, post.tagging
+ end
+
+ def test_class_names_with_eager_load
+ ActiveRecord::Base.store_full_sti_class = false
+ post = Namespaced::Post.eager_load(:tagging).find_by_title("Great stuff")
+ assert_nil post.tagging
+
+ ActiveRecord::Base.store_full_sti_class = true
+ post = Namespaced::Post.eager_load(:tagging).find_by_title("Great stuff")
+ assert_equal @tagging, post.tagging
end
end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 11f4aae5b3..c0bab19e82 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -68,6 +68,11 @@ class EagerAssociationTest < ActiveRecord::TestCase
"expected to find only david's posts"
end
+ def test_loading_with_scope_including_joins
+ assert_equal clubs(:boring_club), Member.preload(:general_club).find(1).general_club
+ assert_equal clubs(:boring_club), Member.eager_load(:general_club).find(1).general_club
+ end
+
def test_with_ordering
list = Post.all.merge!(includes: :comments, order: "posts.id DESC").to_a
[:other_by_mary, :other_by_bob, :misc_by_mary, :misc_by_bob, :eager_other,
@@ -271,9 +276,6 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_loading_from_an_association_that_has_a_hash_of_conditions
- assert_nothing_raised do
- Author.all.merge!(includes: :hello_posts_with_hash_conditions).to_a
- end
assert !Author.all.merge!(includes: :hello_posts_with_hash_conditions).find(authors(:david).id).hello_posts.empty?
end
@@ -1094,12 +1096,6 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal authors(:david), assert_no_queries { posts[0].author }
posts = assert_queries(2) do
- Post.all.merge!(select: "distinct posts.*", includes: :author, joins: [:comments], where: "comments.body like 'Thank you%'", order: "posts.id").to_a
- end
- assert_equal [posts(:welcome)], posts
- assert_equal authors(:david), assert_no_queries { posts[0].author }
-
- posts = assert_queries(2) do
Post.all.merge!(includes: :author, joins: { taggings: :tag }, where: "tags.name = 'General'", order: "posts.id").to_a
end
assert_equal posts(:welcome, :thinking), posts
@@ -1363,6 +1359,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_nothing_raised do
authors(:david).essays.includes(:writer).any?
authors(:david).essays.includes(:writer).exists?
+ authors(:david).essays.includes(:owner).where("name IS NOT NULL").exists?
end
end
diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb
index 87d842f21d..f707a170f5 100644
--- a/activerecord/test/cases/associations/extension_test.rb
+++ b/activerecord/test/cases/associations/extension_test.rb
@@ -78,6 +78,12 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase
assert_equal post.association(:comments), post.comments.where("1=1").the_association
end
+ def test_association_with_default_scope
+ assert_raises OopsError do
+ posts(:welcome).comments.destroy_all
+ end
+ end
+
private
def extend!(model)
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 d6b595d7e7..f73005b3cb 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
@@ -111,6 +111,21 @@ class ProjectUnscopingDavidDefaultScope < ActiveRecord::Base
association_foreign_key: "developer_id"
end
+class Kitchen < ActiveRecord::Base
+ has_one :sink
+end
+
+class Sink < ActiveRecord::Base
+ has_and_belongs_to_many :sources, join_table: :edges
+ belongs_to :kitchen
+ accepts_nested_attributes_for :kitchen
+end
+
+class Source < ActiveRecord::Base
+ self.table_name = "men"
+ has_and_belongs_to_many :sinks, join_table: :edges
+end
+
class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects,
:parrots, :pirates, :parrots_pirates, :treasures, :price_estimates, :tags, :taggings, :computers
@@ -352,19 +367,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated
end
- def test_create_by_new_record
- devel = Developer.new(name: "Marcel", salary: 75000)
- devel.projects.build(name: "Make bed")
- proj2 = devel.projects.build(name: "Lie in it")
- assert_equal devel.projects.last, proj2
- assert !proj2.persisted?
- devel.save
- assert devel.persisted?
- assert proj2.persisted?
- assert_equal devel.projects.last, proj2
- assert_equal Developer.find_by_name("Marcel").projects.last, proj2 # prove join table is updated
- end
-
def test_creation_respects_hash_condition
# in Oracle '' is saved as null therefore need to save ' ' in not null column
post = categories(:general).post_with_conditions.build(body: " ")
@@ -939,7 +941,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_not_nil Developer._reflections["shared_computers"]
# Checking the fixture for named association is important here, because it's the only way
# we've been able to reproduce this bug
- assert_not_nil File.read(File.expand_path("../../../fixtures/developers.yml", __FILE__)).index("shared_computers")
+ assert_not_nil File.read(File.expand_path("../../fixtures/developers.yml", __dir__)).index("shared_computers")
assert_equal developers(:david).shared_computers.first, computers(:laptop)
end
@@ -1021,4 +1023,9 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
ActiveRecord::Base.partial_writes = original_partial_writes
end
end
+
+ def test_has_and_belongs_to_many_with_belongs_to
+ sink = Sink.create! kitchen: Kitchen.new, sources: [Source.new]
+ assert_equal 1, sink.sources.count
+ end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index e2f044c139..a7e16af88d 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -241,6 +241,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal "defaulty", bulb.name
end
+ def test_build_from_association_sets_inverse_instance
+ car = Car.new(name: "honda")
+
+ bulb = car.bulbs.build
+ assert_equal car, bulb.car
+ end
+
def test_do_not_call_callbacks_for_delete_all
car = Car.create(name: "honda")
car.funky_bulbs.create!
@@ -741,6 +748,41 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal client2, firm.clients.merge!(where: ["#{QUOTED_TYPE} = :type", { type: "Client" }], order: "id").first
end
+ def test_find_first_after_reset_scope
+ firm = Firm.all.merge!(order: "id").first
+ collection = firm.clients
+
+ original_object = collection.first
+ assert_same original_object, collection.first, "Expected second call to #first to cache the same object"
+
+ # It should return a different object, since the association has been reloaded
+ assert_not_same original_object, firm.clients.first, "Expected #first to return a new object"
+ end
+
+ def test_find_first_after_reset
+ firm = Firm.all.merge!(order: "id").first
+ collection = firm.clients
+
+ original_object = collection.first
+ assert_same original_object, collection.first, "Expected second call to #first to cache the same object"
+ collection.reset
+
+ # It should return a different object, since the association has been reloaded
+ assert_not_same original_object, collection.first, "Expected #first after #reset to return a new object"
+ end
+
+ def test_find_first_after_reload
+ firm = Firm.all.merge!(order: "id").first
+ collection = firm.clients
+
+ original_object = collection.first
+ assert_same original_object, collection.first, "Expected second call to #first to cache the same object"
+ collection.reload
+
+ # It should return a different object, since the association has been reloaded
+ assert_not_same original_object, collection.first, "Expected #first after #reload to return a new object"
+ end
+
def test_find_all_with_include_and_conditions
assert_nothing_raised do
Developer.all.merge!(joins: :audit_logs, where: { "audit_logs.message" => nil, :name => "Smith" }).to_a
@@ -1355,7 +1397,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
Client.create(client_of: firm.id, name: "SmallTime Inc.")
# only one of two clients is included in the association due to the :conditions key
assert_equal 2, Client.where(client_of: firm.id).size
- assert_equal 1, firm.dependent_sanitized_conditional_clients_of_firm.size
+ assert_equal 1, firm.dependent_hash_conditional_clients_of_firm.size
firm.destroy
# only the correctly associated client should have been deleted
assert_equal 1, Client.where(client_of: firm.id).size
@@ -2037,12 +2079,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal client_association.new.attributes, client_association.send(:new).attributes
end
- def test_respond_to_private_class_methods
- client_association = companies(:first_firm).clients
- assert !client_association.respond_to?(:private_method)
- assert client_association.respond_to?(:private_method, true)
- end
-
def test_creating_using_primary_key
firm = Firm.all.merge!(order: "id").first
client = firm.clients_using_primary_key.create!(name: "test")
@@ -2146,6 +2182,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal "Post", tagging.taggable_type
end
+ def test_build_from_polymorphic_association_sets_inverse_instance
+ post = Post.new
+ tagging = post.taggings.build
+
+ assert_equal post, tagging.taggable
+ end
+
def test_dont_call_save_callbacks_twice_on_has_many
firm = companies(:first_firm)
contract = firm.contracts.create!
@@ -2257,7 +2300,15 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
test "association with extend option with multiple extensions" do
post = posts(:welcome)
assert_equal "lifo", post.comments_with_extend_2.author
- assert_equal "hello", post.comments_with_extend_2.greeting
+ assert_equal "hullo", post.comments_with_extend_2.greeting
+ end
+
+ test "extend option affects per association" do
+ post = posts(:welcome)
+ assert_equal "lifo", post.comments_with_extend.author
+ assert_equal "lifo", post.comments_with_extend_2.author
+ assert_equal "hello", post.comments_with_extend.greeting
+ assert_equal "hullo", post.comments_with_extend_2.greeting
end
test "delete record with complex joins" do
@@ -2317,8 +2368,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
car = Car.create!
bulb = Bulb.create! name: "other", car: car
- assert_equal bulb, Car.find(car.id).all_bulbs.first
- assert_equal bulb, Car.includes(:all_bulbs).find(car.id).all_bulbs.first
+ assert_equal [bulb], Car.find(car.id).all_bulbs
+ assert_equal [bulb], Car.includes(:all_bulbs).find(car.id).all_bulbs
+ assert_equal [bulb], Car.eager_load(:all_bulbs).find(car.id).all_bulbs
end
test "raises RecordNotDestroyed when replaced child can't be destroyed" do
@@ -2447,15 +2499,29 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [first_bulb, second_bulb], car.bulbs
end
- test "double insertion of new object to association when same association used in the after create callback of a new object" do
+ test "prevent double insertion of new object when the parent association loaded in the after save callback" do
reset_callbacks(:save, Bulb) do
Bulb.after_save { |record| record.car.bulbs.load }
+
car = Car.create!
car.bulbs << Bulb.new
+
assert_equal 1, car.bulbs.size
end
end
+ test "prevent double firing the before save callback of new object when the parent association saved in the callback" do
+ reset_callbacks(:save, Bulb) do
+ count = 0
+ Bulb.before_save { |record| record.car.save && count += 1 }
+
+ car = Car.create!
+ car.bulbs.create!
+
+ assert_equal 1, count
+ end
+ end
+
class AuthorWithErrorDestroyingAssociation < ActiveRecord::Base
self.table_name = "authors"
has_many :posts_with_error_destroying,
@@ -2492,11 +2558,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [bulb.id], car.bulb_ids
assert_no_queries { car.bulb_ids }
+
+ bulb2 = car.bulbs.create!
+
+ assert_equal [bulb.id, bulb2.id], car.bulb_ids
+ assert_no_queries { car.bulb_ids }
end
def test_loading_association_in_validate_callback_doesnt_affect_persistence
reset_callbacks(:validation, Bulb) do
- Bulb.after_validation { |m| m.car.bulbs.load }
+ Bulb.after_validation { |record| record.car.bulbs.load }
car = Car.create!(name: "Car")
bulb = car.bulbs.create!
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 ea52fb5a67..1c2138a3d0 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -64,10 +64,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
club1.members.sort_by(&:id)
end
- def make_model(name)
- Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } }
- end
-
def test_ordered_has_many_through
person_prime = Class.new(ActiveRecord::Base) do
def self.name; "Person"; end
@@ -152,20 +148,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert after_destroy_called, "after destroy should be called"
end
- def make_no_pk_hm_t
- lesson = make_model "Lesson"
- student = make_model "Student"
-
- lesson_student = make_model "LessonStudent"
- lesson_student.table_name = "lessons_students"
-
- lesson_student.belongs_to :lesson, anonymous_class: lesson
- lesson_student.belongs_to :student, anonymous_class: student
- lesson.has_many :lesson_students, anonymous_class: lesson_student
- lesson.has_many :students, through: :lesson_students, anonymous_class: student
- [lesson, lesson_student, student]
- end
-
def test_pk_is_not_required_for_join
post = Post.includes(:scategories).first
post2 = Post.includes(:categories).first
@@ -337,6 +319,17 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_includes post.single_people, person
end
+ def test_build_then_remove_then_save
+ post = posts(:thinking)
+ post.people.build(first_name: "Bob")
+ ted = post.people.build(first_name: "Ted")
+ post.people.delete(ted)
+ post.save!
+ post.reload
+
+ assert_equal ["Bob"], post.people.collect(&:first_name)
+ end
+
def test_both_parent_ids_set_when_saving_new
post = Post.new(title: "Hello", body: "world")
person = Person.new(first_name: "Sean")
@@ -1252,4 +1245,23 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
)
end
end
+
+ private
+ def make_model(name)
+ Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } }
+ end
+
+ def make_no_pk_hm_t
+ lesson = make_model "Lesson"
+ student = make_model "Student"
+
+ lesson_student = make_model "LessonStudent"
+ lesson_student.table_name = "lessons_students"
+
+ lesson_student.belongs_to :lesson, anonymous_class: lesson
+ lesson_student.belongs_to :student, anonymous_class: student
+ lesson.has_many :lesson_students, anonymous_class: lesson_student
+ lesson.has_many :students, through: :lesson_students, anonymous_class: student
+ [lesson, lesson_student, student]
+ 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 7c11d2e7fc..bf3b8dcd63 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -307,6 +307,15 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_create_when_parent_is_new_raises
+ firm = Firm.new
+ error = assert_raise(ActiveRecord::RecordNotSaved) do
+ firm.create_account
+ end
+
+ assert_equal "You cannot call create unless the parent is saved", error.message
+ end
+
def test_reload_association
odegy = companies(:odegy)
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index 287b3e9ebc..9fbaa43274 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -24,11 +24,9 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase
monkey_reflection = MixedCaseMonkey.reflect_on_association(:man)
man_reflection = Man.reflect_on_association(:mixed_case_monkey)
- assert_respond_to monkey_reflection, :has_inverse?
assert monkey_reflection.has_inverse?, "The monkey reflection should have an inverse"
assert_equal man_reflection, monkey_reflection.inverse_of, "The monkey reflection's inverse should be the man reflection"
- assert_respond_to man_reflection, :has_inverse?
assert man_reflection.has_inverse?, "The man reflection should have an inverse"
assert_equal monkey_reflection, man_reflection.inverse_of, "The man reflection's inverse should be the monkey reflection"
end
@@ -37,7 +35,6 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase
account_reflection = Admin::Account.reflect_on_association(:users)
user_reflection = Admin::User.reflect_on_association(:account)
- assert_respond_to account_reflection, :has_inverse?
assert account_reflection.has_inverse?, "The Admin::Account reflection should have an inverse"
assert_equal user_reflection, account_reflection.inverse_of, "The Admin::Account reflection's inverse should be the Admin::User reflection"
end
@@ -46,11 +43,9 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase
car_reflection = Car.reflect_on_association(:bulb)
bulb_reflection = Bulb.reflect_on_association(:car)
- assert_respond_to car_reflection, :has_inverse?
assert car_reflection.has_inverse?, "The Car reflection should have an inverse"
assert_equal bulb_reflection, car_reflection.inverse_of, "The Car reflection's inverse should be the Bulb reflection"
- assert_respond_to bulb_reflection, :has_inverse?
assert bulb_reflection.has_inverse?, "The Bulb reflection should have an inverse"
assert_equal car_reflection, bulb_reflection.inverse_of, "The Bulb reflection's inverse should be the Car reflection"
end
@@ -59,7 +54,6 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase
comment_reflection = Comment.reflect_on_association(:ratings)
rating_reflection = Rating.reflect_on_association(:comment)
- assert_respond_to comment_reflection, :has_inverse?
assert comment_reflection.has_inverse?, "The Comment reflection should have an inverse"
assert_equal rating_reflection, comment_reflection.inverse_of, "The Comment reflection's inverse should be the Rating reflection"
end
@@ -107,24 +101,17 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase
def test_polymorphic_and_has_many_through_relationships_should_not_have_inverses
sponsor_reflection = Sponsor.reflect_on_association(:sponsorable)
- assert_respond_to sponsor_reflection, :has_inverse?
assert !sponsor_reflection.has_inverse?, "A polymorphic association should not find an inverse automatically"
club_reflection = Club.reflect_on_association(:members)
- assert_respond_to club_reflection, :has_inverse?
assert !club_reflection.has_inverse?, "A has_many_through association should not find an inverse automatically"
end
- def test_polymorphic_relationships_should_still_not_have_inverses_when_non_polymorphic_relationship_has_the_same_name
+ def test_polymorphic_has_one_should_find_inverse_automatically
man_reflection = Man.reflect_on_association(:polymorphic_face_without_inverse)
- face_reflection = Face.reflect_on_association(:man)
- assert_respond_to face_reflection, :has_inverse?
- assert face_reflection.has_inverse?, "For this test, the non-polymorphic association must have an inverse"
-
- assert_respond_to man_reflection, :has_inverse?
- assert !man_reflection.has_inverse?, "The target of a polymorphic association should not find an inverse automatically"
+ assert man_reflection.has_inverse?
end
end
@@ -145,41 +132,24 @@ class InverseAssociationTests < ActiveRecord::TestCase
def test_should_be_able_to_ask_a_reflection_if_it_has_an_inverse
has_one_with_inverse_ref = Man.reflect_on_association(:face)
- assert_respond_to has_one_with_inverse_ref, :has_inverse?
assert has_one_with_inverse_ref.has_inverse?
has_many_with_inverse_ref = Man.reflect_on_association(:interests)
- assert_respond_to has_many_with_inverse_ref, :has_inverse?
assert has_many_with_inverse_ref.has_inverse?
belongs_to_with_inverse_ref = Face.reflect_on_association(:man)
- assert_respond_to belongs_to_with_inverse_ref, :has_inverse?
assert belongs_to_with_inverse_ref.has_inverse?
has_one_without_inverse_ref = Club.reflect_on_association(:sponsor)
- assert_respond_to has_one_without_inverse_ref, :has_inverse?
assert !has_one_without_inverse_ref.has_inverse?
has_many_without_inverse_ref = Club.reflect_on_association(:memberships)
- assert_respond_to has_many_without_inverse_ref, :has_inverse?
assert !has_many_without_inverse_ref.has_inverse?
belongs_to_without_inverse_ref = Sponsor.reflect_on_association(:sponsor_club)
- assert_respond_to belongs_to_without_inverse_ref, :has_inverse?
assert !belongs_to_without_inverse_ref.has_inverse?
end
- def test_should_be_able_to_ask_a_reflection_what_it_is_the_inverse_of
- has_one_ref = Man.reflect_on_association(:face)
- assert_respond_to has_one_ref, :inverse_of
-
- has_many_ref = Man.reflect_on_association(:interests)
- assert_respond_to has_many_ref, :inverse_of
-
- belongs_to_ref = Face.reflect_on_association(:man)
- assert_respond_to belongs_to_ref, :inverse_of
- end
-
def test_inverse_of_method_should_supply_the_actual_reflection_instance_it_is_the_inverse_of
has_one_ref = Man.reflect_on_association(:face)
assert_equal Face.reflect_on_association(:man), has_one_ref.inverse_of
@@ -651,20 +621,6 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same after changes to replaced-parent-owned instance"
end
- def test_child_instance_should_be_shared_with_replaced_via_method_parent
- face = faces(:confused)
- new_man = Man.new
-
- assert_not_nil face.polymorphic_man
- face.polymorphic_man = new_man
-
- assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same before changes to parent instance"
- face.description = "Bongo"
- assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same after changes to parent instance"
- new_man.polymorphic_face.description = "Mungo"
- assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same after changes to replaced-parent-owned instance"
- end
-
def test_inversed_instance_should_not_be_reloaded_after_stale_state_changed
new_man = Man.new
face = Face.new
diff --git a/activerecord/test/cases/associations/left_outer_join_association_test.rb b/activerecord/test/cases/associations/left_outer_join_association_test.rb
index 2aca3523c4..6d3757f467 100644
--- a/activerecord/test/cases/associations/left_outer_join_association_test.rb
+++ b/activerecord/test/cases/associations/left_outer_join_association_test.rb
@@ -7,7 +7,7 @@ require "models/categorization"
require "models/person"
class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
- fixtures :authors, :essays, :posts, :comments, :categorizations, :people, :author_addresses
+ fixtures :authors, :author_addresses, :essays, :posts, :comments, :categorizations, :people
def test_construct_finder_sql_applies_aliases_tables_on_association_conditions
result = Author.left_outer_joins(:thinking_posts, :welcome_posts).to_a
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 4ab690bfc6..2eb31326a5 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -220,6 +220,18 @@ class AssociationProxyTest < ActiveRecord::TestCase
assert_equal david.projects, david.projects.scope
end
+ test "proxy object is cached" do
+ david = developers(:david)
+ assert_same david.projects, david.projects
+ end
+
+ test "proxy object can be stubbed" do
+ david = developers(:david)
+ david.projects.define_singleton_method(:extra_method) { 42 }
+
+ assert_equal 42, david.projects.extra_method
+ end
+
test "inverses get set of subsets of the association" do
man = Man.create
man.interests.create
diff --git a/activerecord/test/cases/attribute_test.rb b/activerecord/test/cases/attribute_test.rb
index 7cf6b498c9..e856d551c0 100644
--- a/activerecord/test/cases/attribute_test.rb
+++ b/activerecord/test/cases/attribute_test.rb
@@ -76,7 +76,7 @@ module ActiveRecord
end
test "duping dups the value" do
- @type.expect(:deserialize, "type cast", ["a value"])
+ @type.expect(:deserialize, "type cast".dup, ["a value"])
attribute = Attribute.from_database(nil, "a value", @type)
value_from_orig = attribute.value
@@ -244,7 +244,7 @@ module ActiveRecord
end
test "with_type preserves mutations" do
- attribute = Attribute.from_database(:foo, "", Type::Value.new)
+ attribute = Attribute.from_database(:foo, "".dup, Type::Value.new)
attribute.value << "1"
assert_equal 1, attribute.with_type(Type::Integer.new).value
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index ee1be09358..dc32e995a4 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -703,6 +703,17 @@ class BasicsTest < ActiveRecord::TestCase
assert_nil topic.bonus_time
end
+ def test_attributes
+ category = Category.new(name: "Ruby")
+
+ expected_attributes = category.attribute_names.map do |attribute_name|
+ [attribute_name, category.public_send(attribute_name)]
+ end.to_h
+
+ assert_instance_of Hash, category.attributes
+ assert_equal expected_attributes, category.attributes
+ end
+
def test_boolean
b_nil = Boolean.create("value" => nil)
nil_id = b_nil.id
@@ -896,80 +907,6 @@ class BasicsTest < ActiveRecord::TestCase
end
end
- class NumericData < ActiveRecord::Base
- self.table_name = "numeric_data"
-
- attribute :my_house_population, :integer
- attribute :atoms_in_universe, :integer
- end
-
- def test_big_decimal_conditions
- m = NumericData.new(
- bank_balance: 1586.43,
- big_bank_balance: BigDecimal("1000234000567.95"),
- world_population: 6000000000,
- my_house_population: 3
- )
- assert m.save
- assert_equal 0, NumericData.where("bank_balance > ?", 2000.0).count
- end
-
- def test_numeric_fields
- m = NumericData.new(
- bank_balance: 1586.43,
- big_bank_balance: BigDecimal("1000234000567.95"),
- world_population: 6000000000,
- my_house_population: 3
- )
- assert m.save
-
- m1 = NumericData.find(m.id)
- assert_not_nil m1
-
- # As with migration_test.rb, we should make world_population >= 2**62
- # to cover 64-bit platforms and test it is a Bignum, but the main thing
- # is that it's an Integer.
- assert_kind_of Integer, m1.world_population
- assert_equal 6000000000, m1.world_population
-
- assert_kind_of Integer, m1.my_house_population
- assert_equal 3, m1.my_house_population
-
- assert_kind_of BigDecimal, m1.bank_balance
- assert_equal BigDecimal("1586.43"), m1.bank_balance
-
- assert_kind_of BigDecimal, m1.big_bank_balance
- assert_equal BigDecimal("1000234000567.95"), m1.big_bank_balance
- end
-
- def test_numeric_fields_with_scale
- m = NumericData.new(
- bank_balance: 1586.43122334,
- big_bank_balance: BigDecimal("234000567.952344"),
- world_population: 6000000000,
- my_house_population: 3
- )
- assert m.save
-
- m1 = NumericData.find(m.id)
- assert_not_nil m1
-
- # As with migration_test.rb, we should make world_population >= 2**62
- # to cover 64-bit platforms and test it is a Bignum, but the main thing
- # is that it's an Integer.
- assert_kind_of Integer, m1.world_population
- assert_equal 6000000000, m1.world_population
-
- assert_kind_of Integer, m1.my_house_population
- assert_equal 3, m1.my_house_population
-
- assert_kind_of BigDecimal, m1.bank_balance
- assert_equal BigDecimal("1586.43"), m1.bank_balance
-
- assert_kind_of BigDecimal, m1.big_bank_balance
- assert_equal BigDecimal("234000567.95"), m1.big_bank_balance
- end
-
def test_auto_id
auto = AutoId.new
auto.save
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index f7e21faf0f..1a66b82b2e 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -35,12 +35,10 @@ class EachTest < ActiveRecord::TestCase
end
end
- if Enumerator.method_defined? :size
- def test_each_should_return_a_sized_enumerator
- assert_equal 11, Post.find_each(batch_size: 1).size
- assert_equal 5, Post.find_each(batch_size: 2, start: 7).size
- assert_equal 11, Post.find_each(batch_size: 10_000).size
- end
+ def test_each_should_return_a_sized_enumerator
+ assert_equal 11, Post.find_each(batch_size: 1).size
+ assert_equal 5, Post.find_each(batch_size: 2, start: 7).size
+ assert_equal 11, Post.find_each(batch_size: 10_000).size
end
def test_each_enumerator_should_execute_one_query_per_batch
@@ -145,7 +143,7 @@ class EachTest < ActiveRecord::TestCase
def test_find_in_batches_should_quote_batch_order
c = Post.connection
- assert_sql(/ORDER BY #{c.quote_table_name('posts')}.#{c.quote_column_name('id')}/) do
+ assert_sql(/ORDER BY #{c.quote_table_name('posts')}\.#{c.quote_column_name('id')}/) do
Post.find_in_batches(batch_size: 1) do |batch|
assert_kind_of Array, batch
assert_kind_of Post, batch.first
@@ -154,7 +152,7 @@ class EachTest < ActiveRecord::TestCase
end
def test_find_in_batches_should_not_use_records_after_yielding_them_in_case_original_array_is_modified
- not_a_post = "not a post"
+ not_a_post = "not a post".dup
def not_a_post.id; end
not_a_post.stub(:id, -> { raise StandardError.new("not_a_post had #id called on it") }) do
assert_nothing_raised do
@@ -410,7 +408,7 @@ class EachTest < ActiveRecord::TestCase
def test_in_batches_should_quote_batch_order
c = Post.connection
- assert_sql(/ORDER BY #{c.quote_table_name('posts')}.#{c.quote_column_name('id')}/) do
+ assert_sql(/ORDER BY #{c.quote_table_name('posts')}\.#{c.quote_column_name('id')}/) do
Post.in_batches(of: 1) do |relation|
assert_kind_of ActiveRecord::Relation, relation
assert_kind_of Post, relation.first
@@ -419,7 +417,7 @@ class EachTest < ActiveRecord::TestCase
end
def test_in_batches_should_not_use_records_after_yielding_them_in_case_original_array_is_modified
- not_a_post = "not a post"
+ not_a_post = "not a post".dup
def not_a_post.id
raise StandardError.new("not_a_post had #id called on it")
end
@@ -515,14 +513,12 @@ class EachTest < ActiveRecord::TestCase
assert_equal 2, person.reload.author_id # incremented only once
end
- if Enumerator.method_defined? :size
- def test_find_in_batches_should_return_a_sized_enumerator
- assert_equal 11, Post.find_in_batches(batch_size: 1).size
- assert_equal 6, Post.find_in_batches(batch_size: 2).size
- assert_equal 4, Post.find_in_batches(batch_size: 2, start: 4).size
- assert_equal 4, Post.find_in_batches(batch_size: 3).size
- assert_equal 1, Post.find_in_batches(batch_size: 10_000).size
- end
+ def test_find_in_batches_should_return_a_sized_enumerator
+ assert_equal 11, Post.find_in_batches(batch_size: 1).size
+ assert_equal 6, Post.find_in_batches(batch_size: 2).size
+ assert_equal 4, Post.find_in_batches(batch_size: 2, start: 4).size
+ assert_equal 4, Post.find_in_batches(batch_size: 3).size
+ assert_equal 1, Post.find_in_batches(batch_size: 10_000).size
end
[true, false].each do |load|
diff --git a/activerecord/test/cases/binary_test.rb b/activerecord/test/cases/binary_test.rb
index 1fc30e24d2..f6ac7990d1 100644
--- a/activerecord/test/cases/binary_test.rb
+++ b/activerecord/test/cases/binary_test.rb
@@ -10,7 +10,7 @@ unless current_adapter?(:DB2Adapter)
FIXTURES = %w(flowers.jpg example.log test.txt)
def test_mixed_encoding
- str = "\x80"
+ str = "\x80".dup
str.force_encoding("ASCII-8BIT")
binary = Binary.new name: "いただきます!", data: str
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 6032aa9250..5af44c27eb 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -3,8 +3,7 @@ require "models/topic"
require "models/author"
require "models/post"
-if ActiveRecord::Base.connection.supports_statement_cache? &&
- ActiveRecord::Base.connection.prepared_statements
+if ActiveRecord::Base.connection.prepared_statements
module ActiveRecord
class BindParameterTest < ActiveRecord::TestCase
fixtures :topics, :authors, :author_addresses, :posts
@@ -40,9 +39,8 @@ if ActiveRecord::Base.connection.supports_statement_cache? &&
end
def test_binds_are_logged
- sub = Arel::Nodes::BindParam.new
- binds = [Relation::QueryAttribute.new("id", 1, Type::Value.new)]
- sql = "select * from topics where id = #{sub.to_sql}"
+ binds = [bind_attribute("id", 1)]
+ sql = "select * from topics where id = #{bind_param.to_sql}"
@connection.exec_query(sql, "SQL", binds)
@@ -57,7 +55,7 @@ if ActiveRecord::Base.connection.supports_statement_cache? &&
end
def test_logs_binds_after_type_cast
- binds = [Relation::QueryAttribute.new("id", "10", Type::Integer.new)]
+ binds = [bind_attribute("id", "10", Type::Integer.new)]
assert_logs_binds(binds)
end
@@ -66,6 +64,10 @@ if ActiveRecord::Base.connection.supports_statement_cache? &&
assert_logs_binds(binds)
end
+ def test_deprecate_supports_statement_cache
+ assert_deprecated { ActiveRecord::Base.connection.supports_statement_cache? }
+ end
+
private
def assert_logs_binds(binds)
payload = {
diff --git a/activerecord/test/cases/cache_key_test.rb b/activerecord/test/cases/cache_key_test.rb
index bb2829b3c1..7b8264e6e8 100644
--- a/activerecord/test/cases/cache_key_test.rb
+++ b/activerecord/test/cases/cache_key_test.rb
@@ -4,22 +4,48 @@ module ActiveRecord
class CacheKeyTest < ActiveRecord::TestCase
self.use_transactional_tests = false
- class CacheMe < ActiveRecord::Base; end
+ class CacheMe < ActiveRecord::Base
+ self.cache_versioning = false
+ end
+
+ class CacheMeWithVersion < ActiveRecord::Base
+ self.cache_versioning = true
+ end
setup do
@connection = ActiveRecord::Base.connection
- @connection.create_table(:cache_mes) { |t| t.timestamps }
+ @connection.create_table(:cache_mes, force: true) { |t| t.timestamps }
+ @connection.create_table(:cache_me_with_versions, force: true) { |t| t.timestamps }
end
teardown do
@connection.drop_table :cache_mes, if_exists: true
+ @connection.drop_table :cache_me_with_versions, if_exists: true
end
- test "test_cache_key_format_is_not_too_precise" do
+ test "cache_key format is not too precise" do
record = CacheMe.create
key = record.cache_key
assert_equal key, record.reload.cache_key
end
+
+ test "cache_key has no version when versioning is on" do
+ record = CacheMeWithVersion.create
+ assert_equal "active_record/cache_key_test/cache_me_with_versions/#{record.id}", record.cache_key
+ end
+
+ test "cache_version is only there when versioning is on" do
+ assert CacheMeWithVersion.create.cache_version.present?
+ assert_not CacheMe.create.cache_version.present?
+ end
+
+ test "cache_key_with_version always has both key and version" do
+ r1 = CacheMeWithVersion.create
+ assert_equal "active_record/cache_key_test/cache_me_with_versions/#{r1.id}-#{r1.updated_at.to_s(:usec)}", r1.cache_key_with_version
+
+ r2 = CacheMe.create
+ assert_equal "active_record/cache_key_test/cache_mes/#{r2.id}-#{r2.updated_at.to_s(:usec)}", r2.cache_key_with_version
+ end
end
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 3214d778d4..80baaac30a 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -8,6 +8,7 @@ require "models/organization"
require "models/possession"
require "models/topic"
require "models/reply"
+require "models/numeric_data"
require "models/minivan"
require "models/speedometer"
require "models/ship_part"
@@ -17,14 +18,6 @@ require "models/post"
require "models/comment"
require "models/rating"
-class NumericData < ActiveRecord::Base
- self.table_name = "numeric_data"
-
- attribute :world_population, :integer
- attribute :my_house_population, :integer
- attribute :atoms_in_universe, :integer
-end
-
class CalculationsTest < ActiveRecord::TestCase
fixtures :companies, :accounts, :topics, :speedometers, :minivans, :books
@@ -587,8 +580,11 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_pluck_without_column_names
- assert_equal [[1, "Firm", 1, nil, "37signals", nil, 1, nil, ""]],
- Company.order(:id).limit(1).pluck
+ if current_adapter?(:OracleAdapter)
+ assert_equal [[1, "Firm", 1, nil, "37signals", nil, 1, nil, nil]], Company.order(:id).limit(1).pluck
+ else
+ assert_equal [[1, "Firm", 1, nil, "37signals", nil, 1, nil, ""]], Company.order(:id).limit(1).pluck
+ end
end
def test_pluck_type_cast
@@ -809,4 +805,16 @@ class CalculationsTest < ActiveRecord::TestCase
def test_group_by_attribute_with_custom_type
assert_equal({ "proposed" => 2, "published" => 2 }, Book.group(:status).count)
end
+
+ def test_deprecate_count_with_block_and_column_name
+ assert_deprecated do
+ assert_equal 6, Account.count(:firm_id) { true }
+ end
+ end
+
+ def test_deprecate_sum_with_block_and_column_name
+ assert_deprecated do
+ assert_equal 6, Account.sum(:firm_id) { 1 }
+ end
+ end
end
diff --git a/activerecord/test/cases/coders/yaml_column_test.rb b/activerecord/test/cases/coders/yaml_column_test.rb
index 59ef389326..a26a72712d 100644
--- a/activerecord/test/cases/coders/yaml_column_test.rb
+++ b/activerecord/test/cases/coders/yaml_column_test.rb
@@ -1,4 +1,3 @@
-
require "cases/helper"
module ActiveRecord
diff --git a/activerecord/test/cases/collection_cache_key_test.rb b/activerecord/test/cases/collection_cache_key_test.rb
index 381a78a8e2..f344c77691 100644
--- a/activerecord/test/cases/collection_cache_key_test.rb
+++ b/activerecord/test/cases/collection_cache_key_test.rb
@@ -11,16 +11,42 @@ module ActiveRecord
fixtures :developers, :projects, :developers_projects, :topics, :comments, :posts
test "collection_cache_key on model" do
- assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\Z/, Developer.collection_cache_key)
+ assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, Developer.collection_cache_key)
end
test "cache_key for relation" do
- developers = Developer.where(name: "David")
- last_developer_timestamp = developers.order(updated_at: :desc).first.updated_at
+ developers = Developer.where(salary: 100000).order(updated_at: :desc)
+ last_developer_timestamp = developers.first.updated_at
+
+ assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, developers.cache_key)
+
+ /\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/ =~ developers.cache_key
+
+ assert_equal Digest::MD5.hexdigest(developers.to_sql), $1
+ assert_equal developers.count.to_s, $2
+ assert_equal last_developer_timestamp.to_s(ActiveRecord::Base.cache_timestamp_format), $3
+ end
+
+ test "cache_key for relation with limit" do
+ developers = Developer.where(salary: 100000).order(updated_at: :desc).limit(5)
+ last_developer_timestamp = developers.first.updated_at
+
+ assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, developers.cache_key)
+
+ /\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/ =~ developers.cache_key
+
+ assert_equal Digest::MD5.hexdigest(developers.to_sql), $1
+ assert_equal developers.count.to_s, $2
+ assert_equal last_developer_timestamp.to_s(ActiveRecord::Base.cache_timestamp_format), $3
+ end
+
+ test "cache_key for loaded relation" do
+ developers = Developer.where(salary: 100000).order(updated_at: :desc).limit(5).load
+ last_developer_timestamp = developers.first.updated_at
- assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\Z/, developers.cache_key)
+ assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, developers.cache_key)
- /\Adevelopers\/query-(\h+)-(\d+)-(\d+)\Z/ =~ developers.cache_key
+ /\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/ =~ developers.cache_key
assert_equal Digest::MD5.hexdigest(developers.to_sql), $1
assert_equal developers.count.to_s, $2
@@ -48,7 +74,7 @@ module ActiveRecord
test "cache_key for empty relation" do
developers = Developer.where(name: "Non Existent Developer")
- assert_match(/\Adevelopers\/query-(\h+)-0\Z/, developers.cache_key)
+ assert_match(/\Adevelopers\/query-(\h+)-0\z/, developers.cache_key)
end
test "cache_key with custom timestamp column" do
@@ -64,7 +90,7 @@ module ActiveRecord
test "collection proxy provides a cache_key" do
developers = projects(:active_record).developers
- assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\Z/, developers.cache_key)
+ assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, developers.cache_key)
end
test "cache_key for loaded collection with zero size" do
@@ -72,18 +98,18 @@ module ActiveRecord
posts = Post.includes(:comments)
empty_loaded_collection = posts.first.comments
- assert_match(/\Acomments\/query-(\h+)-0\Z/, empty_loaded_collection.cache_key)
+ assert_match(/\Acomments\/query-(\h+)-0\z/, empty_loaded_collection.cache_key)
end
test "cache_key for queries with offset which return 0 rows" do
developers = Developer.offset(20)
- assert_match(/\Adevelopers\/query-(\h+)-0\Z/, developers.cache_key)
+ assert_match(/\Adevelopers\/query-(\h+)-0\z/, developers.cache_key)
end
test "cache_key with a relation having selected columns" do
developers = Developer.select(:salary)
- assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\Z/, developers.cache_key)
+ assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, developers.cache_key)
end
end
end
diff --git a/activerecord/test/cases/comment_test.rb b/activerecord/test/cases/comment_test.rb
index 63f67a9a16..c23be52a6c 100644
--- a/activerecord/test/cases/comment_test.rb
+++ b/activerecord/test/cases/comment_test.rb
@@ -72,9 +72,11 @@ if ActiveRecord::Base.connection.supports_comments?
end
def test_add_index_with_comment_later
- @connection.add_index :commenteds, :obvious, name: "idx_obvious", comment: "We need to see obvious comments"
- index = @connection.indexes("commenteds").find { |idef| idef.name == "idx_obvious" }
- assert_equal "We need to see obvious comments", index.comment
+ unless current_adapter?(:OracleAdapter)
+ @connection.add_index :commenteds, :obvious, name: "idx_obvious", comment: "We need to see obvious comments"
+ index = @connection.indexes("commenteds").find { |idef| idef.name == "idx_obvious" }
+ assert_equal "We need to see obvious comments", index.comment
+ end
end
def test_add_comment_to_column
@@ -112,8 +114,10 @@ if ActiveRecord::Base.connection.supports_comments?
assert_match %r[t\.string\s+"obvious"\n], output
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
+ unless current_adapter?(:OracleAdapter)
+ 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
+ end
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 681399c8bb..2a71f08d90 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -9,6 +9,17 @@ module ActiveRecord
@pool = @handler.establish_connection(ActiveRecord::Base.configurations["arunit"])
end
+ def test_default_env_fall_back_to_default_env_when_rails_env_or_rack_env_is_empty_string
+ original_rails_env = ENV["RAILS_ENV"]
+ original_rack_env = ENV["RACK_ENV"]
+ ENV["RAILS_ENV"] = ENV["RACK_ENV"] = ""
+
+ assert_equal "default_env", ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
+ ensure
+ ENV["RAILS_ENV"] = original_rails_env
+ ENV["RACK_ENV"] = original_rack_env
+ end
+
def test_establish_connection_uses_spec_name
config = { "readonly" => { "adapter" => "sqlite3" } }
resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(config)
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index 7e88c9cf7a..00a0187b57 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -499,21 +499,8 @@ module ActiveRecord
if failed
second_thread_done.set
- puts
- puts ">>> test_disconnect_and_clear_reloadable_connections_are_able_to_preempt_other_waiting_threads / #{group_action_method}"
- p [first_thread, second_thread]
- p pool.stat
- p pool.connections.map(&:owner)
-
first_thread.join(2)
second_thread.join(2)
-
- puts "---"
- p [first_thread, second_thread]
- p pool.stat
- p pool.connections.map(&:owner)
- puts "<<<"
- puts
end
first_thread.join(10) || raise("first_thread got stuck")
diff --git a/activerecord/test/cases/core_test.rb b/activerecord/test/cases/core_test.rb
index 3735572898..936f26ce04 100644
--- a/activerecord/test/cases/core_test.rb
+++ b/activerecord/test/cases/core_test.rb
@@ -35,7 +35,7 @@ class CoreTest < ActiveRecord::TestCase
def test_pretty_print_new
topic = Topic.new
- actual = ""
+ actual = "".dup
PP.pp(topic, StringIO.new(actual))
expected = <<-PRETTY.strip_heredoc
#<Topic:0xXXXXXX
@@ -64,7 +64,7 @@ class CoreTest < ActiveRecord::TestCase
def test_pretty_print_persisted
topic = topics(:first)
- actual = ""
+ actual = "".dup
PP.pp(topic, StringIO.new(actual))
expected = <<-PRETTY.strip_heredoc
#<Topic:0x\\w+
@@ -92,7 +92,7 @@ class CoreTest < ActiveRecord::TestCase
def test_pretty_print_uninitialized
topic = Topic.allocate
- actual = ""
+ actual = "".dup
PP.pp(topic, StringIO.new(actual))
expected = "#<Topic:XXXXXX not initialized>\n"
assert actual.start_with?(expected.split("XXXXXX").first)
@@ -105,7 +105,7 @@ class CoreTest < ActiveRecord::TestCase
"inspecting topic"
end
end
- actual = ""
+ actual = "".dup
PP.pp(subtopic.new, StringIO.new(actual))
assert_equal "inspecting topic\n", actual
end
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index a6297673c9..996d298689 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -87,9 +87,14 @@ if current_adapter?(:PostgreSQLAdapter)
test "schema dump includes default expression" do
output = dump_table_schema("defaults")
- assert_match %r/t\.date\s+"modified_date",\s+default: -> { "\('now'::text\)::date" }/, output
+ if ActiveRecord::Base.connection.postgresql_version >= 100000
+ assert_match %r/t\.date\s+"modified_date",\s+default: -> { "CURRENT_DATE" }/, output
+ assert_match %r/t\.datetime\s+"modified_time",\s+default: -> { "CURRENT_TIMESTAMP" }/, output
+ else
+ assert_match %r/t\.date\s+"modified_date",\s+default: -> { "\('now'::text\)::date" }/, output
+ assert_match %r/t\.datetime\s+"modified_time",\s+default: -> { "now\(\)" }/, output
+ end
assert_match %r/t\.date\s+"modified_date_function",\s+default: -> { "now\(\)" }/, output
- assert_match %r/t\.datetime\s+"modified_time",\s+default: -> { "now\(\)" }/, output
assert_match %r/t\.datetime\s+"modified_time_function",\s+default: -> { "now\(\)" }/, output
end
end
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index f9eccfbda1..f72e0d2ead 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -4,10 +4,7 @@ require "models/pirate" # For timestamps
require "models/parrot"
require "models/person" # For optimistic locking
require "models/aircraft"
-
-class NumericData < ActiveRecord::Base
- self.table_name = "numeric_data"
-end
+require "models/numeric_data"
class DirtyTest < ActiveRecord::TestCase
include InTimeZone
@@ -671,6 +668,47 @@ class DirtyTest < ActiveRecord::TestCase
assert binary.changed?
end
+ test "changes is correct for subclass" do
+ foo = Class.new(Pirate) do
+ def catchphrase
+ super.upcase
+ end
+ end
+
+ pirate = foo.create!(catchphrase: "arrrr")
+
+ new_catchphrase = "arrrr matey!"
+
+ pirate.catchphrase = new_catchphrase
+ assert pirate.catchphrase_changed?
+
+ expected_changes = {
+ "catchphrase" => ["arrrr", new_catchphrase]
+ }
+
+ assert_equal new_catchphrase.upcase, pirate.catchphrase
+ assert_equal expected_changes, pirate.changes
+ end
+
+ test "changes is correct if override attribute reader" do
+ pirate = Pirate.create!(catchphrase: "arrrr")
+ def pirate.catchphrase
+ super.upcase
+ end
+
+ new_catchphrase = "arrrr matey!"
+
+ pirate.catchphrase = new_catchphrase
+ assert pirate.catchphrase_changed?
+
+ expected_changes = {
+ "catchphrase" => ["arrrr", new_catchphrase]
+ }
+
+ assert_equal new_catchphrase.upcase, pirate.catchphrase
+ assert_equal expected_changes, pirate.changes
+ end
+
test "attribute_changed? doesn't compute in-place changes for unrelated attributes" do
test_type_class = Class.new(ActiveRecord::Type::Value) do
define_method(:changed_in_place?) do |*|
diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb
index b7641fcf32..4ef9a125e6 100644
--- a/activerecord/test/cases/enum_test.rb
+++ b/activerecord/test/cases/enum_test.rb
@@ -1,8 +1,9 @@
require "cases/helper"
+require "models/author"
require "models/book"
class EnumTest < ActiveRecord::TestCase
- fixtures :books
+ fixtures :books, :authors
setup do
@book = books(:awdr)
@@ -37,6 +38,8 @@ class EnumTest < ActiveRecord::TestCase
assert_equal @book, Book.author_visibility_visible.first
assert_equal @book, Book.illustrator_visibility_visible.first
assert_equal @book, Book.medium_to_read.first
+ assert_equal books(:ddd), Book.forgotten.first
+ assert_equal books(:rfr), authors(:david).unpublished_books.first
end
test "find via where with values" do
@@ -57,6 +60,7 @@ class EnumTest < ActiveRecord::TestCase
assert_not_equal @book, Book.where(status: [:written]).first
assert_not_equal @book, Book.where.not(status: :published).first
assert_equal @book, Book.where.not(status: :written).first
+ assert_equal books(:ddd), Book.where(read_status: :forgotten).first
end
test "find via where with strings" do
@@ -66,6 +70,7 @@ class EnumTest < ActiveRecord::TestCase
assert_not_equal @book, Book.where(status: ["written"]).first
assert_not_equal @book, Book.where.not(status: "published").first
assert_equal @book, Book.where.not(status: "written").first
+ assert_equal books(:ddd), Book.where(read_status: "forgotten").first
end
test "build from scope" do
diff --git a/activerecord/test/cases/errors_test.rb b/activerecord/test/cases/errors_test.rb
index 73feb831d0..e90669e0c7 100644
--- a/activerecord/test/cases/errors_test.rb
+++ b/activerecord/test/cases/errors_test.rb
@@ -1,4 +1,4 @@
-require_relative "../cases/helper"
+require "cases/helper"
class ErrorsTest < ActiveRecord::TestCase
def test_can_be_instantiated_with_no_args
diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb
index 86fe90ae51..4f6bd9327c 100644
--- a/activerecord/test/cases/explain_test.rb
+++ b/activerecord/test/cases/explain_test.rb
@@ -47,7 +47,7 @@ if ActiveRecord::Base.connection.supports_explain?
def test_exec_explain_with_binds
sqls = %w(foo bar)
- binds = [[bind_param("wadus", 1)], [bind_param("chaflan", 2)]]
+ binds = [[bind_attribute("wadus", 1)], [bind_attribute("chaflan", 2)]]
queries = sqls.zip(binds)
stub_explain_for_query_plans(["query plan foo\n", "query plan bar\n"]) do
@@ -79,9 +79,5 @@ if ActiveRecord::Base.connection.supports_explain?
yield
end
end
-
- def bind_param(name, value)
- ActiveRecord::Relation::QueryAttribute.new(name, value, ActiveRecord::Type::Value.new)
- end
end
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 89d8a8bdca..420f552ef6 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -202,11 +202,29 @@ class FinderTest < ActiveRecord::TestCase
assert_equal true, Topic.first.replies.exists?
end
- # ensures +exists?+ runs valid SQL by excluding order value
- def test_exists_with_order
+ # Ensure +exists?+ runs without an error by excluding distinct value.
+ # See https://github.com/rails/rails/pull/26981.
+ def test_exists_with_order_and_distinct
assert_equal true, Topic.order(:id).distinct.exists?
end
+ # Ensure +exists?+ runs without an error by excluding order value.
+ def test_exists_with_order
+ assert_equal true, Topic.order("invalid sql here").exists?
+ end
+
+ def test_exists_with_joins
+ assert_equal true, Topic.joins(:replies).where(replies_topics: { approved: true }).order("replies_topics.created_at DESC").exists?
+ end
+
+ def test_exists_with_left_joins
+ assert_equal true, Topic.left_joins(:replies).where(replies_topics: { approved: true }).order("replies_topics.created_at DESC").exists?
+ end
+
+ def test_exists_with_eager_load
+ assert_equal true, Topic.eager_load(:replies).where(replies_topics: { approved: true }).order("replies_topics.created_at DESC").exists?
+ end
+
def test_exists_with_includes_limit_and_empty_result
assert_equal false, Topic.includes(:replies).limit(0).exists?
assert_equal false, Topic.includes(:replies).limit(1).where("0 = 1").exists?
@@ -236,9 +254,9 @@ class FinderTest < ActiveRecord::TestCase
def test_exists_with_aggregate_having_three_mappings_with_one_difference
existing_address = customers(:david).address
- assert_equal false, Customer.exists?(address: Address.new(existing_address.street, existing_address.city, existing_address.country + "1"))
- assert_equal false, Customer.exists?(address: Address.new(existing_address.street, existing_address.city + "1", existing_address.country))
- assert_equal false, Customer.exists?(address: Address.new(existing_address.street + "1", existing_address.city, existing_address.country))
+ assert_equal false, Customer.exists?(address: Address.new(existing_address.street, existing_address.city, existing_address.country + "1"))
+ assert_equal false, Customer.exists?(address: Address.new(existing_address.street, existing_address.city + "1", existing_address.country))
+ assert_equal false, Customer.exists?(address: Address.new(existing_address.street + "1", existing_address.city, existing_address.country))
end
def test_exists_does_not_instantiate_records
@@ -725,7 +743,6 @@ class FinderTest < ActiveRecord::TestCase
assert Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: false).find(1)
assert_raise(ActiveRecord::RecordNotFound) { Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: true).find(1) }
assert_raise(ActiveRecord::RecordNotFound) { Topic.where(author_name: "David", title: "HHC", replies_count: 1, approved: false).find(1) }
- assert_raise(ActiveRecord::RecordNotFound) { Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: true).find(1) }
end
def test_condition_interpolation
@@ -1006,16 +1023,6 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" }
end
- def test_find_all_with_join
- developers_on_project_one = Developer.
- joins("LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id").
- where("project_id=1").to_a
- assert_equal 3, developers_on_project_one.length
- developer_names = developers_on_project_one.map(&:name)
- assert_includes developer_names, "David"
- assert_includes developer_names, "Jamis"
- end
-
def test_joins_dont_clobber_id
first = Firm.
joins("INNER JOIN companies clients ON clients.firm_id = companies.id").
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 3720b0cc1a..b499e60922 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -54,6 +54,31 @@ class FixturesTest < ActiveRecord::TestCase
end
end
+ class InsertQuerySubscriber
+ attr_reader :events
+
+ def initialize
+ @events = []
+ end
+
+ def call(_, _, _, _, values)
+ @events << values[:sql] if values[:sql] =~ /INSERT/
+ end
+ end
+
+ if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
+ def test_bulk_insert
+ begin
+ subscriber = InsertQuerySubscriber.new
+ subscription = ActiveSupport::Notifications.subscribe("sql.active_record", subscriber)
+ create_fixtures("bulbs")
+ assert_equal 1, subscriber.events.size, "It takes one INSERT query to insert two fixtures"
+ ensure
+ ActiveSupport::Notifications.unsubscribe(subscription)
+ end
+ end
+ end
+
def test_broken_yaml_exception
badyaml = Tempfile.new ["foo", ".yml"]
badyaml.write "a: : "
@@ -95,6 +120,24 @@ class FixturesTest < ActiveRecord::TestCase
assert_nil(topics["second"]["author_email_address"])
end
+ def test_no_args_returns_all
+ all_topics = topics
+ assert_equal 5, all_topics.length
+ assert_equal "The First Topic", all_topics.first["title"]
+ assert_equal 5, all_topics.last.id
+ end
+
+ def test_no_args_record_returns_all_without_array
+ all_binaries = binaries
+ assert_kind_of(Array, all_binaries)
+ assert_equal 1, binaries.length
+ end
+
+ def test_nil_raises
+ assert_raise(StandardError) { topics(nil) }
+ assert_raise(StandardError) { topics([nil]) }
+ end
+
def test_inserts
create_fixtures("topics")
first_row = ActiveRecord::Base.connection.select_one("SELECT * FROM topics WHERE author_name = 'David'")
@@ -230,7 +273,12 @@ class FixturesTest < ActiveRecord::TestCase
e = assert_raise(ActiveRecord::Fixture::FixtureError) do
ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/naked/yml", "parrots")
end
- assert_equal(%(table "parrots" has no column named "arrr".), e.message)
+
+ if current_adapter?(:SQLite3Adapter)
+ assert_equal(%(table "parrots" has no column named "arrr".), e.message)
+ else
+ assert_equal(%(table "parrots" has no columns named "arrr", "foobar".), e.message)
+ end
end
def test_yaml_file_with_symbol_columns
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index e570e9ac1d..fb5a7bcc31 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -1,6 +1,7 @@
require "cases/helper"
require "models/author"
require "models/company"
+require "models/membership"
require "models/person"
require "models/post"
require "models/project"
@@ -29,7 +30,7 @@ end
class InheritanceTest < ActiveRecord::TestCase
include InheritanceTestHelper
- fixtures :companies, :projects, :subscribers, :accounts, :vegetables
+ fixtures :companies, :projects, :subscribers, :accounts, :vegetables, :memberships
def test_class_with_store_full_sti_class_returns_full_name
with_store_full_sti_class do
@@ -316,7 +317,7 @@ class InheritanceTest < ActiveRecord::TestCase
end
def test_new_with_autoload_paths
- path = File.expand_path("../../models/autoloadable", __FILE__)
+ path = File.expand_path("../models/autoloadable", __dir__)
ActiveSupport::Dependencies.autoload_paths << path
firm = Company.new(type: "ExtraFirm")
@@ -417,7 +418,7 @@ class InheritanceTest < ActiveRecord::TestCase
def test_eager_load_belongs_to_primary_key_quoting
con = Account.connection
- assert_sql(/#{con.quote_table_name('companies')}.#{con.quote_column_name('id')} = 1/) do
+ assert_sql(/#{con.quote_table_name('companies')}\.#{con.quote_column_name('id')} = 1/) do
Account.all.merge!(includes: :firm).find(1)
end
end
@@ -435,6 +436,10 @@ class InheritanceTest < ActiveRecord::TestCase
assert_nothing_raised { Company.of_first_firm }
assert_nothing_raised { Client.of_first_firm }
end
+
+ def test_inheritance_with_default_scope
+ assert_equal 1, SelectedMembership.count(:all)
+ end
end
class InheritanceComputeTypeTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/integration_test.rb b/activerecord/test/cases/integration_test.rb
index d7aa091623..9104976126 100644
--- a/activerecord/test/cases/integration_test.rb
+++ b/activerecord/test/cases/integration_test.rb
@@ -1,4 +1,3 @@
-
require "cases/helper"
require "models/company"
require "models/developer"
@@ -169,13 +168,65 @@ class IntegrationTest < ActiveRecord::TestCase
end
def test_named_timestamps_for_cache_key
- owner = owners(:blackbeard)
- assert_equal "owners/#{owner.id}-#{owner.happy_at.utc.to_s(:usec)}", owner.cache_key(:updated_at, :happy_at)
+ assert_deprecated do
+ owner = owners(:blackbeard)
+ assert_equal "owners/#{owner.id}-#{owner.happy_at.utc.to_s(:usec)}", owner.cache_key(:updated_at, :happy_at)
+ end
end
def test_cache_key_when_named_timestamp_is_nil
- owner = owners(:blackbeard)
- owner.happy_at = nil
- assert_equal "owners/#{owner.id}", owner.cache_key(:happy_at)
+ assert_deprecated do
+ owner = owners(:blackbeard)
+ owner.happy_at = nil
+ assert_equal "owners/#{owner.id}", owner.cache_key(:happy_at)
+ end
+ end
+
+ def test_cache_key_is_stable_with_versioning_on
+ Developer.cache_versioning = true
+
+ developer = Developer.first
+ first_key = developer.cache_key
+
+ developer.touch
+ second_key = developer.cache_key
+
+ assert_equal first_key, second_key
+ ensure
+ Developer.cache_versioning = false
+ end
+
+ def test_cache_version_changes_with_versioning_on
+ Developer.cache_versioning = true
+
+ developer = Developer.first
+ first_version = developer.cache_version
+
+ travel 10.seconds do
+ developer.touch
+ end
+
+ second_version = developer.cache_version
+
+ assert_not_equal first_version, second_version
+ ensure
+ Developer.cache_versioning = false
+ end
+
+ def test_cache_key_retains_version_when_custom_timestamp_is_used
+ Developer.cache_versioning = true
+
+ developer = Developer.first
+ first_key = developer.cache_key_with_version
+
+ travel 10.seconds do
+ developer.touch
+ end
+
+ second_key = developer.cache_key_with_version
+
+ assert_not_equal first_key, second_key
+ ensure
+ Developer.cache_versioning = false
end
end
diff --git a/activerecord/test/cases/json_attribute_test.rb b/activerecord/test/cases/json_attribute_test.rb
new file mode 100644
index 0000000000..e5848b45f8
--- /dev/null
+++ b/activerecord/test/cases/json_attribute_test.rb
@@ -0,0 +1,33 @@
+require "cases/helper"
+require "cases/json_shared_test_cases"
+
+class JsonAttributeTest < ActiveRecord::TestCase
+ include JSONSharedTestCases
+ self.use_transactional_tests = false
+
+ class JsonDataTypeOnText < ActiveRecord::Base
+ self.table_name = "json_data_type"
+
+ attribute :payload, :json
+ attribute :settings, :json
+
+ store_accessor :settings, :resolution
+ end
+
+ def setup
+ super
+ @connection.create_table("json_data_type") do |t|
+ t.text "payload"
+ t.text "settings"
+ end
+ end
+
+ private
+ def column_type
+ :text
+ end
+
+ def klass
+ JsonDataTypeOnText
+ end
+end
diff --git a/activerecord/test/cases/json_shared_test_cases.rb b/activerecord/test/cases/json_shared_test_cases.rb
new file mode 100644
index 0000000000..9a1c1c3f3f
--- /dev/null
+++ b/activerecord/test/cases/json_shared_test_cases.rb
@@ -0,0 +1,221 @@
+require "support/schema_dumping_helper"
+
+module JSONSharedTestCases
+ include SchemaDumpingHelper
+
+ class JsonDataType < ActiveRecord::Base
+ self.table_name = "json_data_type"
+
+ store_accessor :settings, :resolution
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def teardown
+ @connection.drop_table :json_data_type, if_exists: true
+ klass.reset_column_information
+ end
+
+ def test_column
+ column = klass.columns_hash["payload"]
+ assert_equal column_type, column.type
+ assert_equal column_type.to_s, column.sql_type
+
+ type = klass.type_for_attribute("payload")
+ assert_not type.binary?
+ end
+
+ def test_change_table_supports_json
+ @connection.change_table("json_data_type") do |t|
+ t.public_send column_type, "users"
+ end
+ klass.reset_column_information
+ column = klass.columns_hash["users"]
+ assert_equal column_type, column.type
+ assert_equal column_type.to_s, column.sql_type
+ end
+
+ def test_schema_dumping
+ output = dump_table_schema("json_data_type")
+ assert_match(/t\.#{column_type}\s+"settings"/, output)
+ end
+
+ def test_cast_value_on_write
+ x = klass.new(payload: { "string" => "foo", :symbol => :bar })
+ assert_equal({ "string" => "foo", :symbol => :bar }, x.payload_before_type_cast)
+ assert_equal({ "string" => "foo", "symbol" => "bar" }, x.payload)
+ x.save!
+ assert_equal({ "string" => "foo", "symbol" => "bar" }, x.reload.payload)
+ end
+
+ def test_type_cast_json
+ type = klass.type_for_attribute("payload")
+
+ data = '{"a_key":"a_value"}'
+ hash = type.deserialize(data)
+ assert_equal({ "a_key" => "a_value" }, hash)
+ assert_equal({ "a_key" => "a_value" }, type.deserialize(data))
+
+ assert_equal({}, type.deserialize("{}"))
+ assert_equal({ "key" => nil }, type.deserialize('{"key": null}'))
+ assert_equal({ "c" => "}", '"a"' => 'b "a b' }, type.deserialize(%q({"c":"}", "\"a\"":"b \"a b"})))
+ end
+
+ def test_rewrite
+ @connection.execute(%q|insert into json_data_type (payload) VALUES ('{"k":"v"}')|)
+ x = klass.first
+ x.payload = { '"a\'' => "b" }
+ assert x.save!
+ end
+
+ def test_select
+ @connection.execute(%q|insert into json_data_type (payload) VALUES ('{"k":"v"}')|)
+ x = klass.first
+ assert_equal({ "k" => "v" }, x.payload)
+ end
+
+ def test_select_multikey
+ @connection.execute(%q|insert into json_data_type (payload) VALUES ('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}')|)
+ x = klass.first
+ assert_equal({ "k1" => "v1", "k2" => "v2", "k3" => [1, 2, 3] }, x.payload)
+ end
+
+ def test_null_json
+ @connection.execute("insert into json_data_type (payload) VALUES(null)")
+ x = klass.first
+ assert_nil(x.payload)
+ end
+
+ def test_select_nil_json_after_create
+ json = klass.create!(payload: nil)
+ x = klass.where(payload: nil).first
+ assert_equal(json, x)
+ end
+
+ def test_select_nil_json_after_update
+ json = klass.create!(payload: "foo")
+ x = klass.where(payload: nil).first
+ assert_nil(x)
+
+ json.update_attributes(payload: nil)
+ x = klass.where(payload: nil).first
+ assert_equal(json.reload, x)
+ end
+
+ def test_select_array_json_value
+ @connection.execute(%q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|)
+ x = klass.first
+ assert_equal(["v0", { "k1" => "v1" }], x.payload)
+ end
+
+ def test_rewrite_array_json_value
+ @connection.execute(%q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|)
+ x = klass.first
+ x.payload = ["v1", { "k2" => "v2" }, "v3"]
+ assert x.save!
+ end
+
+ def test_with_store_accessors
+ x = klass.new(resolution: "320×480")
+ assert_equal "320×480", x.resolution
+
+ x.save!
+ x = klass.first
+ assert_equal "320×480", x.resolution
+
+ x.resolution = "640×1136"
+ x.save!
+
+ x = klass.first
+ assert_equal "640×1136", x.resolution
+ end
+
+ def test_duplication_with_store_accessors
+ x = klass.new(resolution: "320×480")
+ assert_equal "320×480", x.resolution
+
+ y = x.dup
+ assert_equal "320×480", y.resolution
+ end
+
+ def test_yaml_round_trip_with_store_accessors
+ x = klass.new(resolution: "320×480")
+ assert_equal "320×480", x.resolution
+
+ y = YAML.load(YAML.dump(x))
+ assert_equal "320×480", y.resolution
+ end
+
+ def test_changes_in_place
+ json = klass.new
+ assert_not json.changed?
+
+ json.payload = { "one" => "two" }
+ assert json.changed?
+ assert json.payload_changed?
+
+ json.save!
+ assert_not json.changed?
+
+ json.payload["three"] = "four"
+ assert json.payload_changed?
+
+ json.save!
+ json.reload
+
+ assert_equal({ "one" => "two", "three" => "four" }, json.payload)
+ assert_not json.changed?
+ end
+
+ def test_changes_in_place_ignores_key_order
+ json = klass.new
+ assert_not json.changed?
+
+ json.payload = { "three" => "four", "one" => "two" }
+ json.save!
+ json.reload
+
+ json.payload = { "three" => "four", "one" => "two" }
+ assert_not json.changed?
+
+ json.payload = [{ "three" => "four", "one" => "two" }, { "seven" => "eight", "five" => "six" }]
+ json.save!
+ json.reload
+
+ json.payload = [{ "three" => "four", "one" => "two" }, { "seven" => "eight", "five" => "six" }]
+ assert_not json.changed?
+ end
+
+ def test_changes_in_place_with_ruby_object
+ time = Time.now.utc
+ json = klass.create!(payload: time)
+
+ json.reload
+ assert_not json.changed?
+
+ json.payload = time
+ assert_not json.changed?
+ end
+
+ def test_assigning_string_literal
+ json = klass.create!(payload: "foo")
+ assert_equal "foo", json.payload
+ end
+
+ def test_assigning_number
+ json = klass.create!(payload: 1.234)
+ assert_equal 1.234, json.payload
+ end
+
+ def test_assigning_boolean
+ json = klass.create!(payload: true)
+ assert_equal true, json.payload
+ end
+
+ private
+ def klass
+ JsonDataType
+ end
+end
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 3a3b8e51f9..2fc52393f2 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -167,6 +167,12 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal 0, p1.lock_version
end
+ def test_lock_new_when_explicitly_passing_value
+ p1 = Person.new(first_name: "Douglas Adams", lock_version: 42)
+ p1.save!
+ assert_equal 42, p1.lock_version
+ end
+
def test_touch_existing_lock
p1 = Person.find(1)
assert_equal 0, p1.lock_version
@@ -186,6 +192,19 @@ class OptimisticLockingTest < ActiveRecord::TestCase
end
end
+ def test_explicit_update_lock_column_raise_error
+ person = Person.find(1)
+
+ assert_raises(ActiveRecord::StaleObjectError) do
+ person.first_name = "Douglas Adams"
+ person.lock_version = 42
+
+ assert person.lock_version_changed?
+
+ person.save
+ end
+ end
+
def test_lock_column_name_existing
t1 = LegacyThing.find(1)
t2 = LegacyThing.find(1)
@@ -225,10 +244,33 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal 0, t1.lock_version_before_type_cast
end
+ def test_touch_existing_lock_without_default_should_work_with_null_in_the_database
+ ActiveRecord::Base.connection.execute("INSERT INTO lock_without_defaults(title) VALUES('title1')")
+ t1 = LockWithoutDefault.last
+
+ assert_equal 0, t1.lock_version
+ assert_nil t1.lock_version_before_type_cast
+
+ t1.touch
+
+ assert_equal 1, t1.lock_version
+ end
+
+ def test_touch_stale_object_with_lock_without_default
+ t1 = LockWithoutDefault.create!(title: "title1")
+ stale_object = LockWithoutDefault.find(t1.id)
+
+ t1.update!(title: "title2")
+
+ assert_raises(ActiveRecord::StaleObjectError) do
+ stale_object.touch
+ end
+ end
+
def test_lock_without_default_should_work_with_null_in_the_database
ActiveRecord::Base.connection.execute("INSERT INTO lock_without_defaults(title) VALUES('title1')")
t1 = LockWithoutDefault.last
- t2 = LockWithoutDefault.last
+ t2 = LockWithoutDefault.find(t1.id)
assert_equal 0, t1.lock_version
assert_nil t1.lock_version_before_type_cast
@@ -285,7 +327,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase
ActiveRecord::Base.connection.execute("INSERT INTO lock_without_defaults_cust(title) VALUES('title1')")
t1 = LockWithCustomColumnWithoutDefault.last
- t2 = LockWithCustomColumnWithoutDefault.last
+ t2 = LockWithCustomColumnWithoutDefault.find(t1.id)
assert_equal 0, t1.custom_lock_version
assert_nil t1.custom_lock_version_before_type_cast
@@ -434,6 +476,31 @@ class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase
PersonalLegacyThing.reset_column_information
end
+ def test_destroy_existing_object_with_locking_column_value_null_in_the_database
+ ActiveRecord::Base.connection.execute("INSERT INTO lock_without_defaults(title) VALUES('title1')")
+ t1 = LockWithoutDefault.last
+
+ assert_equal 0, t1.lock_version
+ assert_nil t1.lock_version_before_type_cast
+
+ t1.destroy
+
+ assert t1.destroyed?
+ end
+
+ def test_destroy_stale_object
+ t1 = LockWithoutDefault.create!(title: "title1")
+ stale_object = LockWithoutDefault.find(t1.id)
+
+ t1.update!(title: "title2")
+
+ assert_raises(ActiveRecord::StaleObjectError) do
+ stale_object.destroy!
+ end
+
+ refute stale_object.destroyed?
+ end
+
private
def add_counter_column_to(model, col = "test_count")
diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index 90ad970e16..b80257962c 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -21,6 +21,7 @@ class LogSubscriberTest < ActiveRecord::TestCase
TRANSACTION: REGEXP_CYAN,
OTHER: REGEXP_MAGENTA
}
+ Event = Struct.new(:duration, :payload)
class TestDebugLogSubscriber < ActiveRecord::LogSubscriber
attr_reader :debugs
@@ -55,25 +56,22 @@ class LogSubscriberTest < ActiveRecord::TestCase
end
def test_schema_statements_are_ignored
- event = Struct.new(:duration, :payload)
-
logger = TestDebugLogSubscriber.new
assert_equal 0, logger.debugs.length
- logger.sql(event.new(0, sql: "hi mom!"))
+ logger.sql(Event.new(0.9, sql: "hi mom!"))
assert_equal 1, logger.debugs.length
- logger.sql(event.new(0, sql: "hi mom!", name: "foo"))
+ logger.sql(Event.new(0.9, sql: "hi mom!", name: "foo"))
assert_equal 2, logger.debugs.length
- logger.sql(event.new(0, sql: "hi mom!", name: "SCHEMA"))
+ logger.sql(Event.new(0.9, sql: "hi mom!", name: "SCHEMA"))
assert_equal 2, logger.debugs.length
end
def test_sql_statements_are_not_squeezed
- event = Struct.new(:duration, :payload)
logger = TestDebugLogSubscriber.new
- logger.sql(event.new(0, sql: "ruby rails"))
+ logger.sql(Event.new(0.9, sql: "ruby rails"))
assert_match(/ruby rails/, logger.debugs.first)
end
@@ -86,56 +84,51 @@ class LogSubscriberTest < ActiveRecord::TestCase
end
def test_basic_query_logging_coloration
- event = Struct.new(:duration, :payload)
logger = TestDebugLogSubscriber.new
logger.colorize_logging = true
SQL_COLORINGS.each do |verb, color_regex|
- logger.sql(event.new(0, sql: verb.to_s))
+ logger.sql(Event.new(0.9, sql: verb.to_s))
assert_match(/#{REGEXP_BOLD}#{color_regex}#{verb}#{REGEXP_CLEAR}/i, logger.debugs.last)
end
end
def test_basic_payload_name_logging_coloration_generic_sql
- event = Struct.new(:duration, :payload)
logger = TestDebugLogSubscriber.new
logger.colorize_logging = true
SQL_COLORINGS.each do |verb, _|
- logger.sql(event.new(0, sql: verb.to_s))
- assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
+ logger.sql(Event.new(0.9, sql: verb.to_s))
+ assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0\.9ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
- logger.sql(event.new(0, sql: verb.to_s, name: "SQL"))
- assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA}SQL \(0.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
+ logger.sql(Event.new(0.9, sql: verb.to_s, name: "SQL"))
+ assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA}SQL \(0\.9ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
end
end
def test_basic_payload_name_logging_coloration_named_sql
- event = Struct.new(:duration, :payload)
logger = TestDebugLogSubscriber.new
logger.colorize_logging = true
SQL_COLORINGS.each do |verb, _|
- logger.sql(event.new(0, sql: verb.to_s, name: "Model Load"))
- assert_match(/#{REGEXP_BOLD}#{REGEXP_CYAN}Model Load \(0.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
+ logger.sql(Event.new(0.9, sql: verb.to_s, name: "Model Load"))
+ assert_match(/#{REGEXP_BOLD}#{REGEXP_CYAN}Model Load \(0\.9ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
- logger.sql(event.new(0, sql: verb.to_s, name: "Model Exists"))
- assert_match(/#{REGEXP_BOLD}#{REGEXP_CYAN}Model Exists \(0.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
+ logger.sql(Event.new(0.9, sql: verb.to_s, name: "Model Exists"))
+ assert_match(/#{REGEXP_BOLD}#{REGEXP_CYAN}Model Exists \(0\.9ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
- logger.sql(event.new(0, sql: verb.to_s, name: "ANY SPECIFIC NAME"))
- assert_match(/#{REGEXP_BOLD}#{REGEXP_CYAN}ANY SPECIFIC NAME \(0.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
+ logger.sql(Event.new(0.9, sql: verb.to_s, name: "ANY SPECIFIC NAME"))
+ assert_match(/#{REGEXP_BOLD}#{REGEXP_CYAN}ANY SPECIFIC NAME \(0\.9ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
end
end
def test_query_logging_coloration_with_nested_select
- event = Struct.new(:duration, :payload)
logger = TestDebugLogSubscriber.new
logger.colorize_logging = true
SQL_COLORINGS.slice(:SELECT, :INSERT, :UPDATE, :DELETE).each do |verb, color_regex|
- logger.sql(event.new(0, sql: "#{verb} WHERE ID IN SELECT"))
- assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0.0ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{color_regex}#{verb} WHERE ID IN SELECT#{REGEXP_CLEAR}/i, logger.debugs.last)
+ logger.sql(Event.new(0.9, sql: "#{verb} WHERE ID IN SELECT"))
+ assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0\.9ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{color_regex}#{verb} WHERE ID IN SELECT#{REGEXP_CLEAR}/i, logger.debugs.last)
end
end
def test_query_logging_coloration_with_multi_line_nested_select
- event = Struct.new(:duration, :payload)
logger = TestDebugLogSubscriber.new
logger.colorize_logging = true
SQL_COLORINGS.slice(:SELECT, :INSERT, :UPDATE, :DELETE).each do |verb, color_regex|
@@ -145,13 +138,12 @@ class LogSubscriberTest < ActiveRecord::TestCase
SELECT ID FROM THINGS
)
EOS
- logger.sql(event.new(0, sql: sql))
- assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0.0ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{color_regex}.*#{verb}.*#{REGEXP_CLEAR}/mi, logger.debugs.last)
+ logger.sql(Event.new(0.9, sql: sql))
+ assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0\.9ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{color_regex}.*#{verb}.*#{REGEXP_CLEAR}/mi, logger.debugs.last)
end
end
def test_query_logging_coloration_with_lock
- event = Struct.new(:duration, :payload)
logger = TestDebugLogSubscriber.new
logger.colorize_logging = true
sql = <<-EOS
@@ -159,14 +151,14 @@ class LogSubscriberTest < ActiveRecord::TestCase
(SELECT * FROM mytable FOR UPDATE) ss
WHERE col1 = 5;
EOS
- logger.sql(event.new(0, sql: sql))
- assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0.0ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{SQL_COLORINGS[:LOCK]}.*FOR UPDATE.*#{REGEXP_CLEAR}/mi, logger.debugs.last)
+ logger.sql(Event.new(0.9, sql: sql))
+ assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0\.9ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{SQL_COLORINGS[:LOCK]}.*FOR UPDATE.*#{REGEXP_CLEAR}/mi, logger.debugs.last)
sql = <<-EOS
LOCK TABLE films IN SHARE MODE;
EOS
- logger.sql(event.new(0, sql: sql))
- assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0.0ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{SQL_COLORINGS[:LOCK]}.*LOCK TABLE.*#{REGEXP_CLEAR}/mi, logger.debugs.last)
+ logger.sql(Event.new(0.9, sql: sql))
+ assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0\.9ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{SQL_COLORINGS[:LOCK]}.*LOCK TABLE.*#{REGEXP_CLEAR}/mi, logger.debugs.last)
end
def test_exists_query_logging
diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb
index 802a969cb7..007926f1b9 100644
--- a/activerecord/test/cases/migration/command_recorder_test.rb
+++ b/activerecord/test/cases/migration/command_recorder_test.rb
@@ -211,11 +211,6 @@ module ActiveRecord
assert_equal [:remove_index, [:table, { name: "new_index" }]], remove
end
- def test_invert_add_index_with_no_options
- remove = @recorder.inverse_of :add_index, [:table, [:one, :two]]
- assert_equal [:remove_index, [:table, { column: [:one, :two] }]], remove
- end
-
def test_invert_remove_index
add = @recorder.inverse_of :remove_index, [:table, :one]
assert_equal [:add_index, [:table, :one]], add
diff --git a/activerecord/test/cases/migration/compatibility_test.rb b/activerecord/test/cases/migration/compatibility_test.rb
index 7a80bfb899..596a21dcbc 100644
--- a/activerecord/test/cases/migration/compatibility_test.rb
+++ b/activerecord/test/cases/migration/compatibility_test.rb
@@ -90,6 +90,21 @@ module ActiveRecord
connection.drop_table :more_testings rescue nil
end
+ def test_timestamps_have_null_constraints_if_not_present_in_migration_of_change_table
+ migration = Class.new(ActiveRecord::Migration[4.2]) {
+ def migrate(x)
+ change_table :testings do |t|
+ t.timestamps
+ end
+ end
+ }.new
+
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+
+ assert connection.columns(:testings).find { |c| c.name == "created_at" }.null
+ assert connection.columns(:testings).find { |c| c.name == "updated_at" }.null
+ end
+
def test_timestamps_have_null_constraints_if_not_present_in_migration_for_adding_timestamps_to_existing_table
migration = Class.new(ActiveRecord::Migration[4.2]) {
def migrate(x)
diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb
index f1ddac1ee2..718b9a0613 100644
--- a/activerecord/test/cases/migration/references_foreign_key_test.rb
+++ b/activerecord/test/cases/migration/references_foreign_key_test.rb
@@ -139,6 +139,16 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
end
end
+ test "removing column removes foreign key" do
+ @connection.create_table :testings do |t|
+ t.references :testing_parent, index: true, foreign_key: true
+ end
+
+ assert_difference "@connection.foreign_keys('testings').size", -1 do
+ @connection.remove_column :testings, :testing_parent_id
+ end
+ end
+
test "foreign key methods respect pluralize_table_names" do
begin
original_pluralize_table_names = ActiveRecord::Base.pluralize_table_names
diff --git a/activerecord/test/cases/migration/references_statements_test.rb b/activerecord/test/cases/migration/references_statements_test.rb
index 06c44c8c52..e9eb9968cb 100644
--- a/activerecord/test/cases/migration/references_statements_test.rb
+++ b/activerecord/test/cases/migration/references_statements_test.rb
@@ -50,6 +50,14 @@ module ActiveRecord
assert column_exists?(table_name, :taggable_type, :string, default: "Photo")
end
+ def test_creates_reference_type_column_with_not_null
+ connection.create_table table_name, force: true do |t|
+ t.references :taggable, null: false, polymorphic: true
+ end
+ assert column_exists?(table_name, :taggable_id, :integer, null: false)
+ assert column_exists?(table_name, :taggable_type, :string, null: false)
+ end
+
def test_does_not_share_options_with_reference_type_column
add_reference table_name, :taggable, type: :integer, limit: 2, polymorphic: true
assert column_exists?(table_name, :taggable_id, :integer, limit: 2)
diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb
index 19588d28a2..5da3ad33a3 100644
--- a/activerecord/test/cases/migration/rename_table_test.rb
+++ b/activerecord/test/cases/migration/rename_table_test.rb
@@ -79,10 +79,33 @@ module ActiveRecord
assert_equal ConnectionAdapters::PostgreSQL::Name.new("public", "octopi_#{pk}_seq"), seq
end
+ def test_renaming_table_renames_primary_key
+ connection.create_table :cats, id: :uuid, default: "uuid_generate_v4()"
+ rename_table :cats, :felines
+
+ assert connection.table_exists? :felines
+ refute connection.table_exists? :cats
+
+ primary_key_name = connection.select_values(<<-SQL.strip_heredoc, "SCHEMA")[0]
+ SELECT c.relname
+ FROM pg_class c
+ JOIN pg_index i
+ ON c.oid = i.indexrelid
+ WHERE i.indisprimary
+ AND i.indrelid = 'felines'::regclass
+ SQL
+
+ assert_equal "felines_pkey", primary_key_name
+ ensure
+ connection.drop_table :cats, if_exists: true
+ connection.drop_table :felines, if_exists: true
+ end
+
def test_renaming_table_doesnt_attempt_to_rename_non_existent_sequences
- connection.create_table :cats, id: :uuid
+ connection.create_table :cats, id: :uuid, default: "uuid_generate_v4()"
assert_nothing_raised { rename_table :cats, :felines }
assert connection.table_exists? :felines
+ refute connection.table_exists? :cats
ensure
connection.drop_table :cats, if_exists: true
connection.drop_table :felines, if_exists: true
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index da7875187a..eff6e09eb7 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -402,33 +402,6 @@ class MigrationTest < ActiveRecord::TestCase
ActiveRecord::Migrator.up(migrations_path)
end
- def test_migration_sets_internal_metadata_even_when_fully_migrated
- current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
- migrations_path = MIGRATIONS_ROOT + "/valid"
- old_path = ActiveRecord::Migrator.migrations_paths
- ActiveRecord::Migrator.migrations_paths = migrations_path
-
- ActiveRecord::Migrator.up(migrations_path)
- assert_equal current_env, ActiveRecord::InternalMetadata[:environment]
-
- original_rails_env = ENV["RAILS_ENV"]
- original_rack_env = ENV["RACK_ENV"]
- ENV["RAILS_ENV"] = ENV["RACK_ENV"] = "foofoo"
- new_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
-
- refute_equal current_env, new_env
-
- sleep 1 # mysql by default does not store fractional seconds in the database
-
- ActiveRecord::Migrator.up(migrations_path)
- assert_equal new_env, ActiveRecord::InternalMetadata[:environment]
- ensure
- 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
ActiveRecord::InternalMetadata.delete_all
ActiveRecord::InternalMetadata[:foo] = "bar"
@@ -529,11 +502,10 @@ class MigrationTest < ActiveRecord::TestCase
unless mysql_enforcing_gtid_consistency?
def test_create_table_with_query
- Person.connection.create_table(:person, force: true)
-
- Person.connection.create_table :table_from_query_testings, as: "SELECT id FROM person"
+ Person.connection.create_table :table_from_query_testings, as: "SELECT id FROM people WHERE id = 1"
columns = Person.connection.columns(:table_from_query_testings)
+ assert_equal [1], Person.connection.select_values("SELECT * FROM table_from_query_testings")
assert_equal 1, columns.length
assert_equal "id", columns.first.name
ensure
@@ -541,11 +513,10 @@ class MigrationTest < ActiveRecord::TestCase
end
def test_create_table_with_query_from_relation
- Person.connection.create_table(:person, force: true)
-
- Person.connection.create_table :table_from_query_testings, as: Person.select(:id)
+ Person.connection.create_table :table_from_query_testings, as: Person.select(:id).where(id: 1)
columns = Person.connection.columns(:table_from_query_testings)
+ assert_equal [1], Person.connection.select_values("SELECT * FROM table_from_query_testings")
assert_equal 1, columns.length
assert_equal "id", columns.first.name
ensure
@@ -1044,8 +1015,8 @@ class CopyMigrationsTest < ActiveRecord::TestCase
assert File.exist?(@migrations_path + "/4_currencies_have_symbols.bukkits.rb")
assert_equal [@migrations_path + "/4_currencies_have_symbols.bukkits.rb"], copied.map(&:filename)
- expected = "# coding: ISO-8859-15\n# This migration comes from bukkits (originally 1)"
- assert_equal expected, IO.readlines(@migrations_path + "/4_currencies_have_symbols.bukkits.rb")[0..1].join.chomp
+ expected = "# frozen_string_literal: true\n# coding: ISO-8859-15\n# This migration comes from bukkits (originally 1)"
+ assert_equal expected, IO.readlines(@migrations_path + "/4_currencies_have_symbols.bukkits.rb")[0..2].join.chomp
files_count = Dir[@migrations_path + "/*.rb"].length
copied = ActiveRecord::Migration.copy(@migrations_path, bukkits: MIGRATIONS_ROOT + "/magic")
diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb
index aadbc375af..2e4b454a86 100644
--- a/activerecord/test/cases/migrator_test.rb
+++ b/activerecord/test/cases/migrator_test.rb
@@ -299,6 +299,7 @@ class MigratorTest < ActiveRecord::TestCase
def test_migrator_verbosity
_, migrations = sensors(3)
+ ActiveRecord::Migration.verbose = true
ActiveRecord::Migrator.new(:up, migrations, 1).migrate
assert_not_equal 0, ActiveRecord::Migration.message_count
@@ -311,7 +312,6 @@ class MigratorTest < ActiveRecord::TestCase
def test_migrator_verbosity_off
_, migrations = sensors(3)
- ActiveRecord::Migration.message_count = 0
ActiveRecord::Migration.verbose = false
ActiveRecord::Migrator.new(:up, migrations, 1).migrate
assert_equal 0, ActiveRecord::Migration.message_count
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index b87419d203..154faa56aa 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -117,7 +117,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
def test_reject_if_with_a_proc_which_returns_true_always_for_has_one
Pirate.accepts_nested_attributes_for :ship, reject_if: proc { |attributes| true }
- pirate = Pirate.new(catchphrase: "Stop wastin' me time")
+ pirate = Pirate.create(catchphrase: "Stop wastin' me time")
ship = pirate.create_ship(name: "s1")
pirate.update(ship_attributes: { name: "s2", id: ship.id })
assert_equal "s1", ship.reload.name
@@ -752,7 +752,7 @@ module NestedAttributesOnACollectionAssociationTests
exception = assert_raise ArgumentError do
@pirate.send(association_setter, "foo")
end
- assert_equal 'Hash or Array expected, got String ("foo")', exception.message
+ assert_equal %{Hash or Array expected for attribute `#{@association_name}`, got String ("foo")}, exception.message
end
def test_should_work_with_update_as_well
diff --git a/activerecord/test/cases/numeric_data_test.rb b/activerecord/test/cases/numeric_data_test.rb
new file mode 100644
index 0000000000..76b97033af
--- /dev/null
+++ b/activerecord/test/cases/numeric_data_test.rb
@@ -0,0 +1,71 @@
+require "cases/helper"
+require "models/numeric_data"
+
+class NumericDataTest < ActiveRecord::TestCase
+ def test_big_decimal_conditions
+ m = NumericData.new(
+ bank_balance: 1586.43,
+ big_bank_balance: BigDecimal("1000234000567.95"),
+ world_population: 6000000000,
+ my_house_population: 3
+ )
+ assert m.save
+ assert_equal 0, NumericData.where("bank_balance > ?", 2000.0).count
+ end
+
+ def test_numeric_fields
+ m = NumericData.new(
+ bank_balance: 1586.43,
+ big_bank_balance: BigDecimal("1000234000567.95"),
+ world_population: 6000000000,
+ my_house_population: 3
+ )
+ assert m.save
+
+ m1 = NumericData.find(m.id)
+ assert_not_nil m1
+
+ # As with migration_test.rb, we should make world_population >= 2**62
+ # to cover 64-bit platforms and test it is a Bignum, but the main thing
+ # is that it's an Integer.
+ assert_kind_of Integer, m1.world_population
+ assert_equal 6000000000, m1.world_population
+
+ assert_kind_of Integer, m1.my_house_population
+ assert_equal 3, m1.my_house_population
+
+ assert_kind_of BigDecimal, m1.bank_balance
+ assert_equal BigDecimal("1586.43"), m1.bank_balance
+
+ assert_kind_of BigDecimal, m1.big_bank_balance
+ assert_equal BigDecimal("1000234000567.95"), m1.big_bank_balance
+ end
+
+ def test_numeric_fields_with_scale
+ m = NumericData.new(
+ bank_balance: 1586.43122334,
+ big_bank_balance: BigDecimal("234000567.952344"),
+ world_population: 6000000000,
+ my_house_population: 3
+ )
+ assert m.save
+
+ m1 = NumericData.find(m.id)
+ assert_not_nil m1
+
+ # As with migration_test.rb, we should make world_population >= 2**62
+ # to cover 64-bit platforms and test it is a Bignum, but the main thing
+ # is that it's an Integer.
+ assert_kind_of Integer, m1.world_population
+ assert_equal 6000000000, m1.world_population
+
+ assert_kind_of Integer, m1.my_house_population
+ assert_equal 3, m1.my_house_population
+
+ assert_kind_of BigDecimal, m1.bank_balance
+ assert_equal BigDecimal("1586.43"), m1.bank_balance
+
+ assert_kind_of BigDecimal, m1.big_bank_balance
+ assert_equal BigDecimal("234000567.95"), m1.big_bank_balance
+ end
+end
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 5b7e2fd008..5895c51714 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -49,7 +49,7 @@ class PersistenceTest < ActiveRecord::TestCase
assert !test_update_with_order_succeeds.call("id ASC") # test that this wasn't a fluke and using an incorrect order results in an exception
else
# test that we're failing because the current Arel's engine doesn't support UPDATE ORDER BY queries is using subselects instead
- assert_sql(/\AUPDATE .+ \(SELECT .* ORDER BY id DESC\)\Z/i) do
+ assert_sql(/\AUPDATE .+ \(SELECT .* ORDER BY id DESC\)\z/i) do
test_update_with_order_succeeds.call("id DESC")
end
end
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 5ded619716..56229b70bc 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -46,7 +46,7 @@ class PrimaryKeysTest < ActiveRecord::TestCase
topic = Topic.new
topic.title = "New Topic"
assert_nil topic.id
- assert_nothing_raised { topic.save! }
+ topic.save!
id = topic.id
topicReloaded = Topic.find(id)
@@ -56,23 +56,36 @@ class PrimaryKeysTest < ActiveRecord::TestCase
def test_customized_primary_key_auto_assigns_on_save
Keyboard.delete_all
keyboard = Keyboard.new(name: "HHKB")
- assert_nothing_raised { keyboard.save! }
+ keyboard.save!
assert_equal keyboard.id, Keyboard.find_by_name("HHKB").id
end
def test_customized_primary_key_can_be_get_before_saving
keyboard = Keyboard.new
assert_nil keyboard.id
- assert_nothing_raised { assert_nil keyboard.key_number }
+ assert_nil keyboard.key_number
end
def test_customized_string_primary_key_settable_before_save
subscriber = Subscriber.new
- assert_nothing_raised { subscriber.id = "webster123" }
+ subscriber.id = "webster123"
assert_equal "webster123", subscriber.id
assert_equal "webster123", subscriber.nick
end
+ def test_update_with_non_primary_key_id_column
+ subscriber = Subscriber.first
+ subscriber.update(update_count: 1)
+ subscriber.reload
+ assert_equal 1, subscriber.update_count
+ end
+
+ def test_update_columns_with_non_primary_key_id_column
+ subscriber = Subscriber.first
+ subscriber.update_columns(id: 1)
+ assert_not_equal 1, subscriber.nick
+ end
+
def test_string_key
subscriber = Subscriber.find(subscribers(:first).nick)
assert_equal(subscribers(:first).name, subscriber.name)
@@ -83,7 +96,7 @@ class PrimaryKeysTest < ActiveRecord::TestCase
subscriber.id = "jdoe"
assert_equal("jdoe", subscriber.id)
subscriber.name = "John Doe"
- assert_nothing_raised { subscriber.save! }
+ subscriber.save!
assert_equal("jdoe", subscriber.id)
subscriberReloaded = Subscriber.find("jdoe")
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 494663eb04..b018a7b6c0 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -42,7 +42,8 @@ class QueryCacheTest < ActiveRecord::TestCase
mw = middleware { |env|
Task.find 1
Task.find 1
- assert_equal 1, ActiveRecord::Base.connection.query_cache.length
+ query_cache = ActiveRecord::Base.connection.query_cache
+ assert_equal 1, query_cache.length, query_cache.keys
raise "lol borked"
}
assert_raises(RuntimeError) { mw.call({}) }
@@ -149,7 +150,8 @@ class QueryCacheTest < ActiveRecord::TestCase
mw = middleware { |env|
Task.find 1
Task.find 1
- assert_equal 1, ActiveRecord::Base.connection.query_cache.length
+ query_cache = ActiveRecord::Base.connection.query_cache
+ assert_equal 1, query_cache.length, query_cache.keys
[200, {}, nil]
}
mw.call({})
@@ -204,6 +206,52 @@ class QueryCacheTest < ActiveRecord::TestCase
end
end
+ def test_exists_queries_with_cache
+ Post.cache do
+ assert_queries(1) { Post.exists?; Post.exists? }
+ end
+ end
+
+ def test_select_all_with_cache
+ Post.cache do
+ assert_queries(1) do
+ 2.times { Post.connection.select_all(Post.all) }
+ end
+ end
+ end
+
+ def test_select_one_with_cache
+ Post.cache do
+ assert_queries(1) do
+ 2.times { Post.connection.select_one(Post.all) }
+ end
+ end
+ end
+
+ def test_select_value_with_cache
+ Post.cache do
+ assert_queries(1) do
+ 2.times { Post.connection.select_value(Post.all) }
+ end
+ end
+ end
+
+ def test_select_values_with_cache
+ Post.cache do
+ assert_queries(1) do
+ 2.times { Post.connection.select_values(Post.all) }
+ end
+ end
+ end
+
+ def test_select_rows_with_cache
+ Post.cache do
+ assert_queries(1) do
+ 2.times { Post.connection.select_rows(Post.all) }
+ end
+ end
+ end
+
def test_query_cache_dups_results_correctly
Task.cache do
now = Time.now.utc
@@ -274,18 +322,8 @@ class QueryCacheTest < ActiveRecord::TestCase
end
end
- def test_cache_is_available_when_connection_is_connected
- conf = ActiveRecord::Base.configurations
-
- ActiveRecord::Base.configurations = {}
- Task.cache do
- assert_queries(1) { Task.find(1); Task.find(1) }
- end
- ensure
- ActiveRecord::Base.configurations = conf
- end
-
- def test_cache_is_not_available_when_using_a_not_connected_connection
+ def test_cache_is_available_when_using_a_not_connected_connection
+ skip "In-Memory DB can't test for using a not connected connection" if in_memory_db?
with_temporary_connection_pool do
spec_name = Task.connection_specification_name
conf = ActiveRecord::Base.configurations["arunit"].merge("name" => "test2")
@@ -295,15 +333,7 @@ class QueryCacheTest < ActiveRecord::TestCase
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) }
+ assert_queries(1) { Task.find(1); Task.find(1) }
ensure
ActiveRecord::Base.connection_handler.remove_connection(Task.connection_specification_name)
Task.connection_specification_name = spec_name
diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb
index f260d043e4..0819776fbf 100644
--- a/activerecord/test/cases/quoting_test.rb
+++ b/activerecord/test/cases/quoting_test.rb
@@ -81,8 +81,21 @@ module ActiveRecord
end
end
+ class QuotedOne
+ def quoted_id
+ 1
+ end
+ end
+ class SubQuotedOne < QuotedOne
+ end
def test_quote_with_quoted_id
- assert_deprecated { assert_equal 1, @quoter.quote(Struct.new(:quoted_id).new(1)) }
+ assert_deprecated(/defined on \S+::QuotedOne at .*quoting_test\.rb:[0-9]/) do
+ assert_equal 1, @quoter.quote(QuotedOne.new)
+ end
+
+ assert_deprecated(/defined on \S+::SubQuotedOne\(\S+::QuotedOne\) at .*quoting_test\.rb:[0-9]/) do
+ assert_equal 1, @quoter.quote(SubQuotedOne.new)
+ end
end
def test_quote_nil
diff --git a/activerecord/test/cases/relation/delegation_test.rb b/activerecord/test/cases/relation/delegation_test.rb
index 8cb7b82015..3b15f051b2 100644
--- a/activerecord/test/cases/relation/delegation_test.rb
+++ b/activerecord/test/cases/relation/delegation_test.rb
@@ -3,29 +3,7 @@ require "models/post"
require "models/comment"
module ActiveRecord
- class DelegationTest < ActiveRecord::TestCase
- fixtures :posts
-
- def call_method(target, method)
- method_arity = target.to_a.method(method).arity
-
- if method_arity.zero?
- target.public_send(method)
- elsif method_arity < 0
- if method == :shuffle!
- target.public_send(method)
- else
- target.public_send(method, 1)
- end
- elsif method_arity == 1
- target.public_send(method, 1)
- else
- raise NotImplementedError
- end
- end
- end
-
- module DelegationWhitelistBlacklistTests
+ module DelegationWhitelistTests
ARRAY_DELEGATES = [
:+, :-, :|, :&, :[], :shuffle,
:all?, :collect, :compact, :detect, :each, :each_cons, :each_with_index,
@@ -43,16 +21,33 @@ module ActiveRecord
end
end
- class DelegationAssociationTest < DelegationTest
- include DelegationWhitelistBlacklistTests
+ module DeprecatedArelDelegationTests
+ AREL_METHODS = [
+ :with, :orders, :froms, :project, :projections, :taken, :constraints, :exists, :locked, :where_sql,
+ :ast, :source, :join_sources, :to_dot, :bind_values, :create_insert, :create_true, :create_false
+ ]
+
+ def test_deprecate_arel_delegation
+ AREL_METHODS.each do |method|
+ assert_deprecated { target.public_send(method) }
+ end
+ end
+ end
+
+ class DelegationAssociationTest < ActiveRecord::TestCase
+ include DelegationWhitelistTests
+ include DeprecatedArelDelegationTests
+
+ fixtures :posts
def target
Post.first.comments
end
end
- class DelegationRelationTest < DelegationTest
- include DelegationWhitelistBlacklistTests
+ class DelegationRelationTest < ActiveRecord::TestCase
+ include DelegationWhitelistTests
+ include DeprecatedArelDelegationTests
fixtures :comments
diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb
index 9e95149ede..3901824aac 100644
--- a/activerecord/test/cases/relation/merging_test.rb
+++ b/activerecord/test/cases/relation/merging_test.rb
@@ -21,7 +21,7 @@ class RelationMergingTest < ActiveRecord::TestCase
def test_relation_to_sql
post = Post.first
sql = post.comments.to_sql
- assert_match(/.?post_id.? = #{post.id}\Z/i, sql)
+ assert_match(/.?post_id.? = #{post.id}\z/i, sql)
end
def test_relation_merging_with_arel_equalities_keeps_last_equality
@@ -56,7 +56,7 @@ class RelationMergingTest < ActiveRecord::TestCase
def test_relation_merging_with_locks
devs = Developer.lock.where("salary >= 80000").order("id DESC").merge(Developer.limit(2))
- assert devs.locked.present?
+ assert devs.locked?
end
def test_relation_merging_with_preload
@@ -143,7 +143,7 @@ class MergingDifferentRelationsTest < ActiveRecord::TestCase
assert_equal ["Mary", "Mary", "Mary", "David"], posts_by_author_name
end
- test "relation merging (using a proc argument)" do
+ test "relation merging (using a proc argument)" do
dev = Developer.where(name: "Jamis").first
comment_1 = dev.comments.create!(body: "I'm Jamis", post: Post.first)
diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb
index 4f92f71a09..dea787c07f 100644
--- a/activerecord/test/cases/relation/mutation_test.rb
+++ b/activerecord/test/cases/relation/mutation_test.rb
@@ -36,7 +36,7 @@ module ActiveRecord
@relation ||= Relation.new FakeKlass.new("posts"), Post.arel_table, Post.predicate_builder
end
- (Relation::MULTI_VALUE_METHODS - [:references, :extending, :order, :unscope, :select, :left_joins]).each do |method|
+ (Relation::MULTI_VALUE_METHODS - [:references, :extending, :order, :unscope, :select]).each do |method|
test "##{method}!" do
assert relation.public_send("#{method}!", :foo).equal?(relation)
assert_equal [:foo], relation.public_send("#{method}_values")
@@ -143,7 +143,7 @@ module ActiveRecord
assert_equal({ foo: "bar" }, relation.create_with_value)
end
- test "test_merge!" do
+ test "merge!" do
assert relation.merge!(select: :foo).equal?(relation)
assert_equal [:foo], relation.select_values
end
diff --git a/activerecord/test/cases/relation/or_test.rb b/activerecord/test/cases/relation/or_test.rb
index abb7ca72dd..61b6601580 100644
--- a/activerecord/test/cases/relation/or_test.rb
+++ b/activerecord/test/cases/relation/or_test.rb
@@ -59,6 +59,31 @@ module ActiveRecord
assert_equal "Relation passed to #or must be structurally compatible. Incompatible values: [:order]", error.message
end
+ def test_or_with_unscope_where
+ expected = Post.where("id = 1 or id = 2")
+ partial = Post.where("id = 1 and id != 2")
+ assert_equal expected, partial.or(partial.unscope(:where).where("id = 2")).to_a
+ end
+
+ def test_or_with_unscope_where_column
+ expected = Post.where("id = 1 or id = 2")
+ partial = Post.where(id: 1).where.not(id: 2)
+ assert_equal expected, partial.or(partial.unscope(where: :id).where("id = 2")).to_a
+ end
+
+ def test_or_with_unscope_order
+ expected = Post.where("id = 1 or id = 2")
+ assert_equal expected, Post.order("body asc").where("id = 1").unscope(:order).or(Post.where("id = 2")).to_a
+ end
+
+ def test_or_with_incompatible_unscope
+ error = assert_raises ArgumentError do
+ Post.order("body asc").where("id = 1").or(Post.order("body asc").where("id = 2").unscope(:order)).to_a
+ end
+
+ assert_equal "Relation passed to #or must be structurally compatible. Incompatible values: [:order]", error.message
+ end
+
def test_or_when_grouping
groups = Post.where("id < 10").group("body").select("body, COUNT(*) AS c")
expected = groups.having("COUNT(*) > 1 OR body like 'Such%'").to_a.map { |o| [o.body, o.c] }
diff --git a/activerecord/test/cases/relation/where_chain_test.rb b/activerecord/test/cases/relation/where_chain_test.rb
index a96d1ae5b5..86e150ed79 100644
--- a/activerecord/test/cases/relation/where_chain_test.rb
+++ b/activerecord/test/cases/relation/where_chain_test.rb
@@ -25,7 +25,7 @@ module ActiveRecord
end
def test_association_not_eq
- expected = Arel::Nodes::Grouping.new(Comment.arel_table[@name].not_eq(Arel::Nodes::BindParam.new))
+ expected = Arel::Nodes::Grouping.new(Comment.arel_table[@name].not_eq(bind_param))
relation = Post.joins(:comments).where.not(comments: { title: "hello" })
assert_equal(expected.to_sql, relation.where_clause.ast.to_sql)
end
diff --git a/activerecord/test/cases/relation/where_clause_test.rb b/activerecord/test/cases/relation/where_clause_test.rb
index d8e4c304f0..f8eb0dee91 100644
--- a/activerecord/test/cases/relation/where_clause_test.rb
+++ b/activerecord/test/cases/relation/where_clause_test.rb
@@ -47,15 +47,15 @@ class ActiveRecord::Relation
test "merge removes bind parameters matching overlapping equality clauses" do
a = WhereClause.new(
[table["id"].eq(bind_param), table["name"].eq(bind_param)],
- [attribute("id", 1), attribute("name", "Sean")],
+ [bind_attribute("id", 1), bind_attribute("name", "Sean")],
)
b = WhereClause.new(
[table["name"].eq(bind_param)],
- [attribute("name", "Jim")]
+ [bind_attribute("name", "Jim")]
)
expected = WhereClause.new(
[table["id"].eq(bind_param), table["name"].eq(bind_param)],
- [attribute("id", 1), attribute("name", "Jim")],
+ [bind_attribute("id", 1), bind_attribute("name", "Jim")],
)
assert_equal expected, a.merge(b)
@@ -103,10 +103,10 @@ class ActiveRecord::Relation
table["name"].eq(bind_param),
table["age"].gteq(bind_param),
], [
- attribute("name", "Sean"),
- attribute("age", 30),
+ bind_attribute("name", "Sean"),
+ bind_attribute("age", 30),
])
- expected = WhereClause.new([table["age"].gteq(bind_param)], [attribute("age", 30)])
+ expected = WhereClause.new([table["age"].gteq(bind_param)], [bind_attribute("age", 30)])
assert_equal expected, where_clause.except("id", "name")
end
@@ -146,8 +146,8 @@ class ActiveRecord::Relation
end
test "or joins the two clauses using OR" do
- where_clause = WhereClause.new([table["id"].eq(bind_param)], [attribute("id", 1)])
- other_clause = WhereClause.new([table["name"].eq(bind_param)], [attribute("name", "Sean")])
+ where_clause = WhereClause.new([table["id"].eq(bind_param)], [bind_attribute("id", 1)])
+ other_clause = WhereClause.new([table["name"].eq(bind_param)], [bind_attribute("name", "Sean")])
expected_ast =
Arel::Nodes::Grouping.new(
Arel::Nodes::Or.new(table["id"].eq(bind_param), table["name"].eq(bind_param))
@@ -159,7 +159,7 @@ class ActiveRecord::Relation
end
test "or returns an empty where clause when either side is empty" do
- where_clause = WhereClause.new([table["id"].eq(bind_param)], [attribute("id", 1)])
+ where_clause = WhereClause.new([table["id"].eq(bind_param)], [bind_attribute("id", 1)])
assert_equal WhereClause.empty, where_clause.or(WhereClause.empty)
assert_equal WhereClause.empty, WhereClause.empty.or(where_clause)
@@ -170,13 +170,5 @@ class ActiveRecord::Relation
def table
Arel::Table.new("table")
end
-
- def bind_param
- Arel::Nodes::BindParam.new
- end
-
- def attribute(name, value)
- ActiveRecord::Attribute.with_cast_value(name, value, ActiveRecord::Type::Value.new)
- end
end
end
diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb
index c7b2ac90fb..42dae4d569 100644
--- a/activerecord/test/cases/relation/where_test.rb
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -15,7 +15,7 @@ require "models/vertex"
module ActiveRecord
class WhereTest < ActiveRecord::TestCase
- fixtures :posts, :edges, :authors, :author_addresses, :binaries, :essays, :cars, :treasures, :price_estimates
+ fixtures :posts, :edges, :authors, :author_addresses, :binaries, :essays, :cars, :treasures, :price_estimates, :topics
def test_where_copies_bind_params
author = authors(:david)
@@ -48,6 +48,10 @@ module ActiveRecord
assert_equal [chef], chefs.to_a
end
+ def test_where_with_casted_value_is_nil
+ assert_equal 4, Topic.where(last_read: "").count
+ end
+
def test_rewhere_on_root
assert_equal posts(:welcome), Post.rewhere(title: "Welcome to the weblog").first
end
@@ -289,6 +293,11 @@ module ActiveRecord
assert_equal essays(:david_modest_proposal), essay
end
+ def test_where_on_association_with_select_relation
+ essay = Essay.where(author: Author.where(name: "David").select(:name)).take
+ assert_equal essays(:david_modest_proposal), essay
+ end
+
def test_where_with_strong_parameters
protected_params = Class.new do
attr_reader :permitted
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 5fb32270b7..a403824f1a 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -6,7 +6,7 @@ require "models/rating"
module ActiveRecord
class RelationTest < ActiveRecord::TestCase
- fixtures :posts, :comments, :authors, :author_addresses
+ fixtures :posts, :comments, :authors, :author_addresses, :ratings
FakeKlass = Struct.new(:table_name, :name) do
extend ActiveRecord::Delegation::DelegateCache
@@ -224,7 +224,26 @@ module ActiveRecord
def test_relation_merging_with_merged_joins_as_symbols
special_comments_with_ratings = SpecialComment.joins(:ratings)
posts_with_special_comments_with_ratings = Post.group("posts.id").joins(:special_comments).merge(special_comments_with_ratings)
- assert_equal({ 2 => 1, 4 => 3, 5 => 1 }, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count)
+ assert_equal({ 4 => 2 }, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count)
+ end
+
+ def test_relation_merging_with_merged_symbol_joins_keeps_inner_joins
+ queries = capture_sql { Author.joins(:posts).merge(Post.joins(:comments)).to_a }
+
+ nb_inner_join = queries.sum { |sql| sql.scan(/INNER\s+JOIN/i).size }
+ assert_equal 2, nb_inner_join, "Wrong amount of INNER JOIN in query"
+ assert queries.none? { |sql| /LEFT\s+(OUTER)?\s+JOIN/i.match?(sql) }, "Shouldn't have any LEFT JOIN in query"
+ end
+
+ def test_relation_merging_with_merged_symbol_joins_has_correct_size_and_count
+ # Has one entry per comment
+ merged_authors_with_commented_posts_relation = Author.joins(:posts).merge(Post.joins(:comments))
+
+ post_ids_with_author = Post.joins(:author).pluck(:id)
+ manual_comments_on_post_that_have_author = Comment.where(post_id: post_ids_with_author).pluck(:id)
+
+ assert_equal manual_comments_on_post_that_have_author.size, merged_authors_with_commented_posts_relation.count
+ assert_equal manual_comments_on_post_that_have_author.size, merged_authors_with_commented_posts_relation.to_a.size
end
def test_relation_merging_with_joins_as_join_dependency_pick_proper_parent
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 856469c710..5767dec315 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -594,7 +594,7 @@ class RelationTest < ActiveRecord::TestCase
end
end
- def test_respond_to_delegates_to_relation
+ def test_respond_to_delegates_to_arel
relation = Topic.all
fake_arel = Struct.new(:responds) {
def respond_to?(method, access = false)
@@ -607,10 +607,6 @@ class RelationTest < ActiveRecord::TestCase
relation.respond_to?(:matching_attributes)
assert_equal [:matching_attributes, false], fake_arel.responds.first
-
- fake_arel.responds = []
- relation.respond_to?(:matching_attributes, true)
- assert_equal [:matching_attributes, true], fake_arel.responds.first
end
def test_respond_to_dynamic_finders
@@ -699,16 +695,6 @@ class RelationTest < ActiveRecord::TestCase
end
end
- def test_default_scope_with_conditions_string
- assert_equal Developer.where(name: "David").map(&:id).sort, DeveloperCalledDavid.all.map(&:id).sort
- assert_nil DeveloperCalledDavid.create!.name
- end
-
- def test_default_scope_with_conditions_hash
- assert_equal Developer.where(name: "Jamis").map(&:id).sort, DeveloperCalledJamis.all.map(&:id).sort
- assert_equal "Jamis", DeveloperCalledJamis.create!.name
- end
-
def test_default_scoping_finder_methods
developers = DeveloperCalledDavid.order("id").map(&:id).sort
assert_equal Developer.where(name: "David").map(&:id).sort, developers
@@ -1731,6 +1717,9 @@ class RelationTest < ActiveRecord::TestCase
scope = Post.order("comments.body")
assert_equal ["comments"], scope.references_values
+ scope = Post.order("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}")
+ assert_equal ["comments"], scope.references_values
+
scope = Post.order("comments.body", "yaks.body")
assert_equal ["comments", "yaks"], scope.references_values
@@ -1749,6 +1738,9 @@ class RelationTest < ActiveRecord::TestCase
scope = Post.reorder("comments.body")
assert_equal %w(comments), scope.references_values
+ scope = Post.reorder("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}")
+ assert_equal ["comments"], scope.references_values
+
scope = Post.reorder("comments.body", "yaks.body")
assert_equal %w(comments yaks), scope.references_values
@@ -1902,7 +1894,7 @@ class RelationTest < ActiveRecord::TestCase
end
test "relations don't load all records in #inspect" do
- assert_sql(/LIMIT/) do
+ assert_sql(/LIMIT|ROWNUM <=|FETCH FIRST/) do
Post.all.inspect
end
end
@@ -1985,24 +1977,28 @@ class RelationTest < ActiveRecord::TestCase
end
def test_unscope_removes_binds
- left = Post.where(id: Arel::Nodes::BindParam.new)
- column = Post.columns_hash["id"]
- left.bind_values += [[column, 20]]
+ left = Post.where(id: 20)
+
+ binds = [bind_attribute("id", 20, Post.type_for_attribute("id"))]
+ assert_equal binds, left.bound_attributes
relation = left.unscope(where: :id)
- assert_equal [], relation.bind_values
+ assert_equal [], relation.bound_attributes
end
- def test_merging_removes_rhs_bind_parameters
+ def test_merging_removes_rhs_binds
left = Post.where(id: 20)
right = Post.where(id: [1, 2, 3, 4])
+ binds = [bind_attribute("id", 20, Post.type_for_attribute("id"))]
+ assert_equal binds, left.bound_attributes
+
merged = left.merge(right)
- assert_equal [], merged.bind_values
+ assert_equal [], merged.bound_attributes
end
- def test_merging_keeps_lhs_bind_parameters
- binds = [ActiveRecord::Relation::QueryAttribute.new("id", 20, Post.type_for_attribute("id"))]
+ def test_merging_keeps_lhs_binds
+ binds = [bind_attribute("id", 20, Post.type_for_attribute("id"))]
right = Post.where(id: 20)
left = Post.where(id: 10)
@@ -2011,13 +2007,10 @@ class RelationTest < ActiveRecord::TestCase
assert_equal binds, merged.bound_attributes
end
- def test_merging_reorders_bind_params
- post = Post.first
- right = Post.where(id: post.id)
- left = Post.where(title: post.title)
-
- merged = left.merge(right)
- assert_equal post, merged.first
+ def test_locked_should_not_build_arel
+ posts = Post.locked
+ assert posts.locked?
+ assert_nothing_raised { posts.lock!(false) }
end
def test_relation_join_method
diff --git a/activerecord/test/cases/reload_models_test.rb b/activerecord/test/cases/reload_models_test.rb
index 5dc9d6d8b7..3f4c0c03e3 100644
--- a/activerecord/test/cases/reload_models_test.rb
+++ b/activerecord/test/cases/reload_models_test.rb
@@ -13,7 +13,7 @@ class ReloadModelsTest < ActiveRecord::TestCase
# development environment. Note that meanwhile the class Pet is not
# reloaded, simulating a class that is present in a plugin.
Object.class_eval { remove_const :Owner }
- Kernel.load(File.expand_path(File.join(File.dirname(__FILE__), "../models/owner.rb")))
+ Kernel.load(File.expand_path("../models/owner.rb", __dir__))
pet = Pet.find_by_name("parrot")
pet.owner = Owner.find_by_name("ashley")
diff --git a/activerecord/test/cases/reserved_word_test.rb b/activerecord/test/cases/reserved_word_test.rb
new file mode 100644
index 0000000000..f3019a5326
--- /dev/null
+++ b/activerecord/test/cases/reserved_word_test.rb
@@ -0,0 +1,132 @@
+require "cases/helper"
+
+class ReservedWordTest < ActiveRecord::TestCase
+ self.use_instantiated_fixtures = true
+ self.use_transactional_tests = false
+
+ class Group < ActiveRecord::Base
+ Group.table_name = "group"
+ belongs_to :select
+ has_one :values
+ end
+
+ class Select < ActiveRecord::Base
+ Select.table_name = "select"
+ has_many :groups
+ end
+
+ class Values < ActiveRecord::Base
+ Values.table_name = "values"
+ end
+
+ class Distinct < ActiveRecord::Base
+ Distinct.table_name = "distinct"
+ has_and_belongs_to_many :selects
+ has_many :values, through: :groups
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table :select, force: true
+ @connection.create_table :distinct, force: true
+ @connection.create_table :distinct_select, id: false, force: true do |t|
+ t.belongs_to :distinct
+ t.belongs_to :select
+ end
+ @connection.create_table :group, force: true do |t|
+ t.string :order
+ t.belongs_to :select
+ end
+ @connection.create_table :values, force: true do |t|
+ t.belongs_to :group
+ end
+ end
+
+ def teardown
+ @connection.drop_table :select, if_exists: true
+ @connection.drop_table :distinct, if_exists: true
+ @connection.drop_table :distinct_select, if_exists: true
+ @connection.drop_table :group, if_exists: true
+ @connection.drop_table :values, if_exists: true
+ @connection.drop_table :order, if_exists: true
+ end
+
+ def test_create_tables
+ assert_not @connection.table_exists?(:order)
+
+ @connection.create_table :order do |t|
+ t.string :group
+ end
+
+ assert @connection.table_exists?(:order)
+ end
+
+ def test_rename_tables
+ assert_nothing_raised { @connection.rename_table(:group, :order) }
+ end
+
+ def test_change_columns
+ assert_nothing_raised { @connection.change_column_default(:group, :order, "whatever") }
+ assert_nothing_raised { @connection.change_column("group", "order", :text, default: nil) }
+ assert_nothing_raised { @connection.rename_column(:group, :order, :values) }
+ end
+
+ def test_introspect
+ assert_equal ["id", "order", "select_id"], @connection.columns(:group).map(&:name).sort
+ assert_equal ["index_group_on_select_id"], @connection.indexes(:group).map(&:name).sort
+ end
+
+ def test_activerecord_model
+ x = Group.new
+ x.order = "x"
+ x.save!
+ x.order = "y"
+ x.save!
+ assert_equal x, Group.find_by_order("y")
+ assert_equal x, Group.find(x.id)
+ end
+
+ def test_has_one_associations
+ create_test_fixtures :group, :values
+ v = Group.find(1).values
+ assert_equal 2, v.id
+ end
+
+ def test_belongs_to_associations
+ create_test_fixtures :select, :group
+ gs = Select.find(2).groups
+ assert_equal 2, gs.length
+ assert_equal [2, 3], gs.collect(&:id).sort
+ end
+
+ def test_has_and_belongs_to_many
+ create_test_fixtures :select, :distinct, :distinct_select
+ s = Distinct.find(1).selects
+ assert_equal 2, s.length
+ assert_equal [1, 2], s.collect(&:id).sort
+ end
+
+ def test_activerecord_introspection
+ assert Group.table_exists?
+ assert_equal ["id", "order", "select_id"], Group.columns.map(&:name).sort
+ end
+
+ def test_calculations_work_with_reserved_words
+ create_test_fixtures :group
+ assert_equal 3, Group.count
+ end
+
+ def test_associations_work_with_reserved_words
+ create_test_fixtures :select, :group
+ selects = Select.all.merge!(includes: [:groups]).to_a
+ assert_no_queries do
+ selects.each { |select| select.groups }
+ end
+ end
+
+ private
+ # custom fixture loader, uses FixtureSet#create_fixtures and appends base_path to the current file's path
+ def create_test_fixtures(*fixture_names)
+ ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names)
+ end
+end
diff --git a/activerecord/test/cases/result_test.rb b/activerecord/test/cases/result_test.rb
index 949086fda0..1a0b7c6ca7 100644
--- a/activerecord/test/cases/result_test.rb
+++ b/activerecord/test/cases/result_test.rb
@@ -45,10 +45,8 @@ module ActiveRecord
end
end
- if Enumerator.method_defined? :size
- test "each without block returns a sized enumerator" do
- assert_equal 3, result.each.size
- end
+ test "each without block returns a sized enumerator" do
+ assert_equal 3, result.each.size
end
test "cast_values returns rows after type casting" do
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index fccba4738f..4c81e825fa 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -17,6 +17,12 @@ class SchemaDumperTest < ActiveRecord::TestCase
dump_all_table_schema []
end
+ def test_dump_schema_information_with_empty_versions
+ ActiveRecord::SchemaMigration.delete_all
+ schema_info = ActiveRecord::Base.connection.dump_schema_information
+ assert_no_match(/INSERT INTO/, schema_info)
+ end
+
def test_dump_schema_information_outputs_lexically_ordered_versions
versions = %w{ 20100101010101 20100201010101 20100301010101 }
versions.reverse_each do |v|
@@ -116,32 +122,22 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_schema_dump_includes_limit_constraint_for_integer_columns
output = dump_all_table_schema([/^(?!integer_limits)/])
- assert_match %r{c_int_without_limit}, output
+ assert_match %r{"c_int_without_limit"(?!.*limit)}, output
if current_adapter?(:PostgreSQLAdapter)
- assert_no_match %r{c_int_without_limit.*limit:}, output
-
assert_match %r{c_int_1.*limit: 2}, output
assert_match %r{c_int_2.*limit: 2}, output
# int 3 is 4 bytes in postgresql
- assert_match %r{c_int_3.*}, output
- assert_no_match %r{c_int_3.*limit:}, output
-
- assert_match %r{c_int_4.*}, output
- assert_no_match %r{c_int_4.*limit:}, output
+ assert_match %r{"c_int_3"(?!.*limit)}, output
+ assert_match %r{"c_int_4"(?!.*limit)}, output
elsif current_adapter?(:Mysql2Adapter)
- assert_match %r{c_int_without_limit"$}, output
-
assert_match %r{c_int_1.*limit: 1}, output
assert_match %r{c_int_2.*limit: 2}, output
assert_match %r{c_int_3.*limit: 3}, output
- assert_match %r{c_int_4.*}, output
- assert_no_match %r{c_int_4.*:limit}, output
+ assert_match %r{"c_int_4"(?!.*limit)}, output
elsif current_adapter?(:SQLite3Adapter)
- assert_no_match %r{c_int_without_limit.*limit:}, output
-
assert_match %r{c_int_1.*limit: 1}, output
assert_match %r{c_int_2.*limit: 2}, output
assert_match %r{c_int_3.*limit: 3}, output
@@ -182,7 +178,11 @@ class SchemaDumperTest < ActiveRecord::TestCase
if current_adapter?(:PostgreSQLAdapter)
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 }', index_definition
+ if ActiveRecord::Base.connection.supports_index_sort_order?
+ assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", length: { type: 10 }, order: { rating: :desc }', index_definition
+ else
+ assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", length: { type: 10 }', index_definition
+ end
else
assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index"', index_definition
end
@@ -326,7 +326,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_schema_dump_keeps_id_false_when_id_is_false_and_unique_not_null_column_added
output = standard_dump
- assert_match %r{create_table "subscribers", id: false}, output
+ assert_match %r{create_table "string_key_objects", id: false}, output
end
if ActiveRecord::Base.connection.supports_foreign_keys?
diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb
index 0c2cffe0d3..483ea7128d 100644
--- a/activerecord/test/cases/scoping/named_scoping_test.rb
+++ b/activerecord/test/cases/scoping/named_scoping_test.rb
@@ -551,6 +551,12 @@ class NamedScopingTest < ActiveRecord::TestCase
assert_equal 1, SpecialComment.where(body: "go crazy").created.count
end
+ def test_model_class_should_respond_to_extending
+ assert_raises OopsError do
+ Comment.unscoped.oops_comments.destroy_all
+ end
+ end
+
def test_model_class_should_respond_to_none
assert !Topic.none?
Topic.delete_all
diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb
index 3fbff7664b..10553bf057 100644
--- a/activerecord/test/cases/scoping/relation_scoping_test.rb
+++ b/activerecord/test/cases/scoping/relation_scoping_test.rb
@@ -229,12 +229,28 @@ class RelationScopingTest < ActiveRecord::TestCase
end
end
- def test_circular_joins_with_current_scope_does_not_crash
+ def test_scoping_is_correctly_restored
+ Comment.unscoped do
+ SpecialComment.unscoped.created
+ end
+
+ assert_nil Comment.current_scope
+ assert_nil SpecialComment.current_scope
+ end
+
+ def test_circular_joins_with_scoping_does_not_crash
posts = Post.joins(comments: :post).scoping do
- Post.current_scope.first(10)
+ Post.first(10)
end
assert_equal posts, Post.joins(comments: :post).first(10)
end
+
+ def test_circular_left_joins_with_scoping_does_not_crash
+ posts = Post.left_joins(comments: :post).scoping do
+ Post.first(10)
+ end
+ assert_equal posts, Post.left_joins(comments: :post).first(10)
+ end
end
class NestedRelationScopingTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index 673392b4c4..e1bdaab5cf 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -349,4 +349,32 @@ class SerializedAttributeTest < ActiveRecord::TestCase
topic.foo
refute topic.changed?
end
+
+ def test_serialized_attribute_works_under_concurrent_initial_access
+ model = Topic.dup
+
+ topic = model.last
+ topic.update group: "1"
+
+ model.serialize :group, JSON
+ model.reset_column_information
+
+ # This isn't strictly necessary for the test, but a little bit of
+ # knowledge of internals allows us to make failures far more likely.
+ model.define_singleton_method(:define_attribute) do |*args|
+ Thread.pass
+ super(*args)
+ end
+
+ threads = 4.times.map do
+ Thread.new do
+ topic.reload.group
+ end
+ end
+
+ # All the threads should retrieve the value knowing it is JSON, and
+ # thus decode it. If this fails, some threads will instead see the
+ # raw string ("1"), or raise an exception.
+ assert_equal [1] * threads.size, threads.map(&:value)
+ end
end
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index baa41a3a47..c47c97e9d9 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -343,8 +343,23 @@ module ActiveRecord
ENV["VERBOSE"] = "false"
ENV["VERSION"] = "4"
-
ActiveRecord::Migrator.expects(:migrate).with("custom/path", 4)
+ ActiveRecord::Migration.expects(:verbose=).with(false)
+ ActiveRecord::Migration.expects(:verbose=).with(ActiveRecord::Migration.verbose)
+ ActiveRecord::Tasks::DatabaseTasks.migrate
+
+ ENV.delete("VERBOSE")
+ ENV.delete("VERSION")
+ ActiveRecord::Migrator.expects(:migrate).with("custom/path", nil)
+ ActiveRecord::Migration.expects(:verbose=).with(true)
+ ActiveRecord::Migration.expects(:verbose=).with(ActiveRecord::Migration.verbose)
+ ActiveRecord::Tasks::DatabaseTasks.migrate
+
+ ENV["VERBOSE"] = "yes"
+ ENV["VERSION"] = "unknown"
+ ActiveRecord::Migrator.expects(:migrate).with("custom/path", 0)
+ ActiveRecord::Migration.expects(:verbose=).with(true)
+ ActiveRecord::Migration.expects(:verbose=).with(ActiveRecord::Migration.verbose)
ActiveRecord::Tasks::DatabaseTasks.migrate
ensure
ENV["VERBOSE"], ENV["VERSION"] = verbose, version
@@ -353,7 +368,8 @@ module ActiveRecord
def test_migrate_raise_error_on_empty_version
version = ENV["VERSION"]
ENV["VERSION"] = ""
- assert_raise(RuntimeError, "Empty VERSION provided") { ActiveRecord::Tasks::DatabaseTasks.migrate }
+ e = assert_raise(RuntimeError) { ActiveRecord::Tasks::DatabaseTasks.migrate }
+ assert_equal "Empty VERSION provided", e.message
ensure
ENV["VERSION"] = version
end
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index f30e0958c3..9c6fb14376 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -167,7 +167,7 @@ if current_adapter?(:Mysql2Adapter)
def assert_permissions_granted_for(db_user)
db_name = @configuration["database"]
db_password = @configuration["password"]
- @connection.expects(:execute).with("GRANT ALL PRIVILEGES ON #{db_name}.* TO '#{db_user}'@'localhost' IDENTIFIED BY '#{db_password}' WITH GRANT OPTION;")
+ @connection.expects(:execute).with("GRANT ALL PRIVILEGES ON `#{db_name}`.* TO '#{db_user}'@'localhost' IDENTIFIED BY '#{db_password}' WITH GRANT OPTION;")
end
end
@@ -296,7 +296,7 @@ if current_adapter?(:Mysql2Adapter)
def test_structure_dump_with_extra_flags
filename = "awesome-file.sql"
- expected_command = ["mysqldump", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "--noop", "test-db"]
+ expected_command = ["mysqldump", "--noop", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "test-db"]
assert_called_with(Kernel, :system, expected_command, returns: true) do
with_structure_dump_flags(["--noop"]) do
@@ -305,6 +305,15 @@ if current_adapter?(:Mysql2Adapter)
end
end
+ def test_structure_dump_with_ignore_tables
+ filename = "awesome-file.sql"
+ ActiveRecord::SchemaDumper.expects(:ignore_tables).returns(["foo", "bar"])
+
+ Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "--ignore-table=test-db.foo", "--ignore-table=test-db.bar", "test-db").returns(true)
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
+ end
+
def test_warn_when_external_structure_dump_command_execution_fails
filename = "awesome-file.sql"
Kernel.expects(:system)
@@ -355,7 +364,7 @@ if current_adapter?(:Mysql2Adapter)
def test_structure_load
filename = "awesome-file.sql"
- expected_command = ["mysql", "--execute", %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}, "--database", "test-db", "--noop"]
+ expected_command = ["mysql", "--noop", "--execute", %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}, "--database", "test-db"]
assert_called_with(Kernel, :system, expected_command, returns: true) do
with_structure_load_flags(["--noop"]) do
diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb
index 512388af6b..a2e968aedf 100644
--- a/activerecord/test/cases/tasks/postgresql_rake_test.rb
+++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb
@@ -259,6 +259,14 @@ if current_adapter?(:PostgreSQLAdapter)
end
end
+ def test_structure_dump_with_ignore_tables
+ ActiveRecord::SchemaDumper.expects(:ignore_tables).returns(["foo", "bar"])
+
+ Kernel.expects(:system).with("pg_dump", "-s", "-x", "-O", "-f", @filename, "-T", "foo", "-T", "bar", "my-app-db").returns(true)
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
+ end
+
def test_structure_dump_with_schema_search_path
@configuration["schema_search_path"] = "foo,bar"
diff --git a/activerecord/test/cases/tasks/sqlite_rake_test.rb b/activerecord/test/cases/tasks/sqlite_rake_test.rb
index 0d917f3f6c..ccb3834fee 100644
--- a/activerecord/test/cases/tasks/sqlite_rake_test.rb
+++ b/activerecord/test/cases/tasks/sqlite_rake_test.rb
@@ -180,6 +180,9 @@ if current_adapter?(:SQLite3Adapter)
"adapter" => "sqlite3",
"database" => @database
}
+
+ `sqlite3 #{@database} 'CREATE TABLE bar(id INTEGER)'`
+ `sqlite3 #{@database} 'CREATE TABLE foo(id INTEGER)'`
end
def test_structure_dump
@@ -189,6 +192,23 @@ if current_adapter?(:SQLite3Adapter)
ActiveRecord::Tasks::DatabaseTasks.structure_dump @configuration, filename, "/rails/root"
assert File.exist?(dbfile)
assert File.exist?(filename)
+ assert_match(/CREATE TABLE foo/, File.read(filename))
+ assert_match(/CREATE TABLE bar/, File.read(filename))
+ ensure
+ FileUtils.rm_f(filename)
+ FileUtils.rm_f(dbfile)
+ end
+
+ def test_structure_dump_with_ignore_tables
+ dbfile = @database
+ filename = "awesome-file.sql"
+ ActiveRecord::SchemaDumper.expects(:ignore_tables).returns(["foo"])
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename, "/rails/root")
+ assert File.exist?(dbfile)
+ assert File.exist?(filename)
+ assert_match(/bar/, File.read(filename))
+ assert_no_match(/foo/, File.read(filename))
ensure
FileUtils.rm_f(filename)
FileUtils.rm_f(dbfile)
diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb
index 31b11c19f7..9f594fef85 100644
--- a/activerecord/test/cases/test_case.rb
+++ b/activerecord/test/cases/test_case.rb
@@ -75,6 +75,14 @@ module ActiveRecord
model.reset_column_information
model.column_names.include?(column_name.to_s)
end
+
+ def bind_param
+ Arel::Nodes::BindParam.new
+ end
+
+ def bind_attribute(name, value, type = ActiveRecord::Type.default_value)
+ ActiveRecord::Relation::QueryAttribute.new(name, value, type)
+ end
end
class PostgreSQLTestCase < TestCase
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 5c6d78b574..76a997ba8b 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -304,6 +304,76 @@ class TransactionTest < ActiveRecord::TestCase
assert !Topic.find(2).approved?, "Second should have been unapproved"
end
+ def test_nested_transaction_with_new_transaction_applies_parent_state_on_rollback
+ topic_one = Topic.new(title: "A new topic")
+ topic_two = Topic.new(title: "Another new topic")
+
+ Topic.transaction do
+ topic_one.save
+
+ Topic.transaction(requires_new: true) do
+ topic_two.save
+
+ assert_predicate topic_one, :persisted?
+ assert_predicate topic_two, :persisted?
+ end
+
+ raise ActiveRecord::Rollback
+ end
+
+ refute_predicate topic_one, :persisted?
+ refute_predicate topic_two, :persisted?
+ end
+
+ def test_nested_transaction_without_new_transaction_applies_parent_state_on_rollback
+ topic_one = Topic.new(title: "A new topic")
+ topic_two = Topic.new(title: "Another new topic")
+
+ Topic.transaction do
+ topic_one.save
+
+ Topic.transaction do
+ topic_two.save
+
+ assert_predicate topic_one, :persisted?
+ assert_predicate topic_two, :persisted?
+ end
+
+ raise ActiveRecord::Rollback
+ end
+
+ refute_predicate topic_one, :persisted?
+ refute_predicate topic_two, :persisted?
+ end
+
+ def test_double_nested_transaction_applies_parent_state_on_rollback
+ topic_one = Topic.new(title: "A new topic")
+ topic_two = Topic.new(title: "Another new topic")
+ topic_three = Topic.new(title: "Another new topic of course")
+
+ Topic.transaction do
+ topic_one.save
+
+ Topic.transaction do
+ topic_two.save
+
+ Topic.transaction do
+ topic_three.save
+ end
+ end
+
+ assert_predicate topic_one, :persisted?
+ assert_predicate topic_two, :persisted?
+ assert_predicate topic_three, :persisted?
+
+ raise ActiveRecord::Rollback
+ end
+
+ refute_predicate topic_one, :persisted?
+ refute_predicate topic_two, :persisted?
+ refute_predicate topic_three, :persisted?
+ end
+
def test_manually_rolling_back_a_transaction
Topic.transaction do
@first.approved = true
@@ -595,6 +665,52 @@ class TransactionTest < ActiveRecord::TestCase
assert_not topic.frozen?
end
+ def test_restore_id_after_rollback
+ topic = Topic.new
+
+ Topic.transaction do
+ topic.save!
+ raise ActiveRecord::Rollback
+ end
+
+ assert_nil topic.id
+ end
+
+ def test_restore_custom_primary_key_after_rollback
+ movie = Movie.new(name: "foo")
+
+ Movie.transaction do
+ movie.save!
+ raise ActiveRecord::Rollback
+ end
+
+ assert_nil movie.id
+ end
+
+ def test_assign_id_after_rollback
+ topic = Topic.create!
+
+ Topic.transaction do
+ topic.save!
+ raise ActiveRecord::Rollback
+ end
+
+ topic.id = nil
+ assert_nil topic.id
+ end
+
+ def test_assign_custom_primary_key_after_rollback
+ movie = Movie.create!(name: "foo")
+
+ Movie.transaction do
+ movie.save!
+ raise ActiveRecord::Rollback
+ end
+
+ movie.id = nil
+ assert_nil movie.id
+ end
+
def test_rollback_of_frozen_records
topic = Topic.create.freeze
Topic.transaction do
@@ -679,6 +795,44 @@ class TransactionTest < ActiveRecord::TestCase
assert transaction.state.committed?
end
+ def test_set_state_method_is_deprecated
+ connection = Topic.connection
+ transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction
+
+ transaction.commit
+
+ assert_deprecated do
+ transaction.state.set_state(:rolledback)
+ end
+ end
+
+ def test_mark_transaction_state_as_committed
+ connection = Topic.connection
+ transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction
+
+ transaction.rollback
+
+ assert_equal :committed, transaction.state.commit!
+ end
+
+ def test_mark_transaction_state_as_rolledback
+ connection = Topic.connection
+ transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction
+
+ transaction.commit
+
+ assert_equal :rolledback, transaction.state.rollback!
+ end
+
+ def test_mark_transaction_state_as_nil
+ connection = Topic.connection
+ transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction
+
+ transaction.commit
+
+ assert_nil transaction.state.nullify!
+ end
+
def test_transaction_rollback_with_primarykeyless_tables
connection = ActiveRecord::Base.connection
connection.create_table(:transaction_without_primary_keys, force: true, id: false) do |t|
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index 5d9aa99497..a305aa295a 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -167,6 +167,20 @@ class ValidationsTest < ActiveRecord::TestCase
assert topic.valid?
end
+ def test_numericality_validation_checks_against_raw_value
+ klass = Class.new(Topic) do
+ def self.model_name
+ ActiveModel::Name.new(self, nil, "Topic")
+ end
+ attribute :wibble, :decimal, scale: 2, precision: 9
+ validates_numericality_of :wibble, greater_than_or_equal_to: BigDecimal.new("97.18")
+ end
+
+ assert_not klass.new(wibble: "97.179").valid?
+ assert_not klass.new(wibble: 97.179).valid?
+ assert_not klass.new(wibble: BigDecimal.new("97.179")).valid?
+ end
+
def test_acceptance_validator_doesnt_require_db_connection
klass = Class.new(ActiveRecord::Base) do
self.table_name = "posts"
diff --git a/activerecord/test/cases/view_test.rb b/activerecord/test/cases/view_test.rb
index 07288568e8..1d21a2454f 100644
--- a/activerecord/test/cases/view_test.rb
+++ b/activerecord/test/cases/view_test.rb
@@ -154,7 +154,9 @@ if ActiveRecord::Base.connection.supports_views?
end
# sqlite dose not support CREATE, INSERT, and DELETE for VIEW
- if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :SQLServerAdapter)
+ if current_adapter?(:Mysql2Adapter, :SQLServerAdapter) ||
+ current_adapter?(:PostgreSQLAdapter) && ActiveRecord::Base.connection.postgresql_version >= 90300
+
class UpdateableViewTest < ActiveRecord::TestCase
self.use_transactional_tests = false
fixtures :books
diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb
index ab0e67cd9d..dd8d2c1178 100644
--- a/activerecord/test/cases/yaml_serialization_test.rb
+++ b/activerecord/test/cases/yaml_serialization_test.rb
@@ -119,12 +119,20 @@ class YamlSerializationTest < ActiveRecord::TestCase
assert_equal author.changes, dumped.changes
end
+ def test_yaml_encoding_keeps_false_values
+ topic = Topic.first
+ topic.approved = false
+ dumped = YAML.load(YAML.dump(topic))
+
+ assert_equal false, dumped.approved
+ end
+
private
def yaml_fixture(file_name)
path = File.expand_path(
- "../../support/yaml_compatibility_fixtures/#{file_name}.yml",
- __FILE__
+ "../support/yaml_compatibility_fixtures/#{file_name}.yml",
+ __dir__
)
File.read(path)
end
diff --git a/activerecord/test/config.rb b/activerecord/test/config.rb
index 6e2e8b2145..a65e6ff776 100644
--- a/activerecord/test/config.rb
+++ b/activerecord/test/config.rb
@@ -1,4 +1,4 @@
-TEST_ROOT = File.expand_path(File.dirname(__FILE__))
+TEST_ROOT = __dir__
ASSETS_ROOT = TEST_ROOT + "/assets"
FIXTURES_ROOT = TEST_ROOT + "/fixtures"
MIGRATIONS_ROOT = TEST_ROOT + "/migrations"
diff --git a/activerecord/test/fixtures/books.yml b/activerecord/test/fixtures/books.yml
index b3625ee72e..699623a6f9 100644
--- a/activerecord/test/fixtures/books.yml
+++ b/activerecord/test/fixtures/books.yml
@@ -25,6 +25,7 @@ ddd:
name: "Domain-Driven Design"
format: "hardcover"
status: 2
+ read_status: "forgotten"
tlg:
author_id: 1
diff --git a/activerecord/test/fixtures/naked/yml/parrots.yml b/activerecord/test/fixtures/naked/yml/parrots.yml
index 3e10331105..76f66e01ae 100644
--- a/activerecord/test/fixtures/naked/yml/parrots.yml
+++ b/activerecord/test/fixtures/naked/yml/parrots.yml
@@ -1,2 +1,3 @@
george:
arrr: "Curious George"
+ foobar: Foobar
diff --git a/activerecord/test/migrations/magic/1_currencies_have_symbols.rb b/activerecord/test/migrations/magic/1_currencies_have_symbols.rb
index d4b0e6cd95..2ba2875751 100644
--- a/activerecord/test/migrations/magic/1_currencies_have_symbols.rb
+++ b/activerecord/test/migrations/magic/1_currencies_have_symbols.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
# coding: ISO-8859-15
class CurrenciesHaveSymbols < ActiveRecord::Migration::Current
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index fab613afd1..2d9cba77e0 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -106,6 +106,7 @@ class Author < ActiveRecord::Base
has_many :tags_with_primary_key, through: :posts
has_many :books
+ has_many :unpublished_books, -> { where(status: [:proposed, :written]) }, class_name: "Book"
has_many :subscriptions, through: :books
has_many :subscribers, -> { order("subscribers.nick") }, through: :subscriptions
has_many :distinct_subscribers, -> { select("DISTINCT subscribers.*").order("subscribers.nick") }, through: :subscriptions, source: :subscriber
diff --git a/activerecord/test/models/book.rb b/activerecord/test/models/book.rb
index 17bf3fbcb4..6466e1b341 100644
--- a/activerecord/test/models/book.rb
+++ b/activerecord/test/models/book.rb
@@ -1,5 +1,5 @@
class Book < ActiveRecord::Base
- has_many :authors
+ belongs_to :author
has_many :citations, foreign_key: "book1_id"
has_many :references, -> { distinct }, through: :citations, source: :reference_of
@@ -8,7 +8,7 @@ class Book < ActiveRecord::Base
has_many :subscribers, through: :subscriptions
enum status: [:proposed, :written, :published]
- enum read_status: { unread: 0, reading: 2, read: 3 }
+ enum read_status: { unread: 0, reading: 2, read: 3, forgotten: nil }
enum nullable_status: [:single, :married]
enum language: [:english, :spanish, :french], _prefix: :in
enum author_visibility: [:visible, :invisible], _prefix: true
diff --git a/activerecord/test/models/category.rb b/activerecord/test/models/category.rb
index e8654dca01..4b2840c653 100644
--- a/activerecord/test/models/category.rb
+++ b/activerecord/test/models/category.rb
@@ -29,6 +29,15 @@ class Category < ActiveRecord::Base
has_many :authors_with_select, -> { select "authors.*, categorizations.post_id" }, through: :categorizations, source: :author
scope :general, -> { where(name: "General") }
+
+ # Should be delegated `ast` and `locked` to `arel`.
+ def self.ast
+ raise
+ end
+
+ def self.locked
+ raise
+ end
end
class SpecialCategory < Category
diff --git a/activerecord/test/models/club.rb b/activerecord/test/models/club.rb
index 49d7b24a3b..3d441b1d48 100644
--- a/activerecord/test/models/club.rb
+++ b/activerecord/test/models/club.rb
@@ -8,6 +8,8 @@ class Club < ActiveRecord::Base
has_many :favourites, -> { where(memberships: { favourite: true }) }, through: :memberships, source: :member
+ scope :general, -> { left_joins(:category).where(categories: { name: "General" }) }
+
private
def private_method
diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb
index a4b81d56e0..eecf923046 100644
--- a/activerecord/test/models/comment.rb
+++ b/activerecord/test/models/comment.rb
@@ -9,7 +9,6 @@ class Comment < ActiveRecord::Base
belongs_to :post, counter_cache: true
belongs_to :author, polymorphic: true
belongs_to :resource, polymorphic: true
- belongs_to :developer
has_many :ratings
@@ -19,6 +18,23 @@ class Comment < ActiveRecord::Base
has_many :children, class_name: "Comment", foreign_key: :parent_id
belongs_to :parent, class_name: "Comment", counter_cache: :children_count
+ class ::OopsError < RuntimeError; end
+
+ module OopsExtension
+ def destroy_all(*)
+ raise OopsError
+ end
+ end
+
+ default_scope { extending OopsExtension }
+
+ scope :oops_comments, -> { extending OopsExtension }
+
+ # Should not be called if extending modules that having the method exists on an association.
+ def self.greeting
+ raise
+ end
+
def self.what_are_you
"a comment..."
end
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index 20e37710e7..c6a5bf1c92 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -170,19 +170,12 @@ class Client < Company
def overwrite_to_raise
end
-
- class << self
- private
-
- def private_method
- "darkness"
- end
- end
end
class ExclusivelyDependentFirm < Company
has_one :account, foreign_key: "firm_id", dependent: :delete
has_many :dependent_sanitized_conditional_clients_of_firm, -> { order("id").where("name = 'BigShot Inc.'") }, foreign_key: "client_of", class_name: "Client", dependent: :delete_all
+ has_many :dependent_hash_conditional_clients_of_firm, -> { order("id").where(name: "BigShot Inc.") }, foreign_key: "client_of", class_name: "Client", dependent: :delete_all
has_many :dependent_conditional_clients_of_firm, -> { order("id").where("name = ?", "BigShot Inc.") }, foreign_key: "client_of", class_name: "Client", dependent: :delete_all
end
diff --git a/activerecord/test/models/essay.rb b/activerecord/test/models/essay.rb
index 13267fbc21..1f9772870e 100644
--- a/activerecord/test/models/essay.rb
+++ b/activerecord/test/models/essay.rb
@@ -1,4 +1,5 @@
class Essay < ActiveRecord::Base
+ belongs_to :author
belongs_to :writer, primary_key: :name, polymorphic: true
belongs_to :category, primary_key: :name
has_one :owner, primary_key: :name
diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb
index 36f2461b84..b9597c6b9a 100644
--- a/activerecord/test/models/member.rb
+++ b/activerecord/test/models/member.rb
@@ -22,6 +22,7 @@ class Member < ActiveRecord::Base
has_many :organization_member_details_2, through: :organization, source: :member_details
has_one :club_category, through: :club, source: :category
+ has_one :general_club, -> { general }, through: :current_membership, source: :club
has_many :current_memberships, -> { where favourite: true }
has_many :clubs, through: :current_memberships
diff --git a/activerecord/test/models/membership.rb b/activerecord/test/models/membership.rb
index 2c3ad230a7..0f8be0ad85 100644
--- a/activerecord/test/models/membership.rb
+++ b/activerecord/test/models/membership.rb
@@ -1,4 +1,5 @@
class Membership < ActiveRecord::Base
+ enum type: %i(Membership CurrentMembership SuperMembership SelectedMembership TenantMembership)
belongs_to :member
belongs_to :club
end
diff --git a/activerecord/test/models/numeric_data.rb b/activerecord/test/models/numeric_data.rb
new file mode 100644
index 0000000000..c6e025a9ce
--- /dev/null
+++ b/activerecord/test/models/numeric_data.rb
@@ -0,0 +1,8 @@
+class NumericData < ActiveRecord::Base
+ self.table_name = "numeric_data"
+ # Decimal columns with 0 scale being automatically treated as integers
+ # is deprecated, and will be removed in a future version of Rails.
+ attribute :world_population, :big_integer
+ attribute :my_house_population, :big_integer
+ attribute :atoms_in_universe, :big_integer
+end
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index a2028b3eb9..ed64e0ee52 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -13,7 +13,7 @@ class Post < ActiveRecord::Base
module NamedExtension2
def greeting
- "hello"
+ "hullo"
end
end
@@ -22,6 +22,7 @@ class Post < ActiveRecord::Base
scope :ranked_by_comments, -> { order("comments_count DESC") }
scope :limit_by, lambda { |l| limit(l) }
+ scope :locked, -> { lock }
belongs_to :author
belongs_to :readonly_author, -> { readonly }, class_name: "Author", foreign_key: :author_id
diff --git a/activerecord/test/models/subject.rb b/activerecord/test/models/subject.rb
deleted file mode 100644
index 504f68a296..0000000000
--- a/activerecord/test/models/subject.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# used for OracleSynonymTest, see test/synonym_test_oracle.rb
-#
-class Subject < ActiveRecord::Base
- # added initialization of author_email_address in the same way as in Topic class
- # as otherwise synonym test was failing
- after_initialize :set_email_address
-
- private
- def set_email_address
- unless persisted?
- self.author_email_address = "test@test.com"
- end
- end
-end
diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb
index 0420e2d15c..d9381ac9cf 100644
--- a/activerecord/test/models/topic.rb
+++ b/activerecord/test/models/topic.rb
@@ -14,7 +14,7 @@ class Topic < ActiveRecord::Base
scope :replied, -> { where "replies_count > 0" }
scope "approved_as_string", -> { where(approved: true) }
- scope :anonymous_extension, -> { all } do
+ scope :anonymous_extension, -> {} do
def one
1
end
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index 860c63b27c..e56e8fa36a 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -3,11 +3,13 @@ ActiveRecord::Schema.define do
enable_extension!("uuid-ossp", ActiveRecord::Base.connection)
enable_extension!("pgcrypto", ActiveRecord::Base.connection) if ActiveRecord::Base.connection.supports_pgcrypto_uuid?
- create_table :uuid_parents, id: :uuid, force: true do |t|
+ uuid_default = connection.supports_pgcrypto_uuid? ? {} : { default: "uuid_generate_v4()" }
+
+ create_table :uuid_parents, id: :uuid, force: true, **uuid_default do |t|
t.string :name
end
- create_table :uuid_children, id: :uuid, force: true do |t|
+ create_table :uuid_children, id: :uuid, force: true, **uuid_default do |t|
t.string :name
t.uuid :uuid_parent_id
end
@@ -102,7 +104,7 @@ _SQL
end
create_table :uuid_items, force: true, id: false do |t|
- t.uuid :uuid, primary_key: true
+ t.uuid :uuid, primary_key: true, **uuid_default
t.string :title
end
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 08bef08abc..f534e9c00e 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -107,7 +107,7 @@ ActiveRecord::Schema.define do
t.boolean :has_fun, null: false, default: false
end
- create_table :bulbs, force: true do |t|
+ create_table :bulbs, primary_key: "ID", force: true do |t|
t.integer :car_id
t.string :name
t.boolean :frickinawesome, default: false
@@ -413,6 +413,9 @@ ActiveRecord::Schema.define do
t.string :name
end
+ create_table :kitchens, force: true do |t|
+ end
+
create_table :legacy_things, force: true do |t|
t.integer :tps_report_number
t.integer :version, null: false, default: 0
@@ -450,11 +453,13 @@ ActiveRecord::Schema.define do
create_table :lock_without_defaults, force: true do |t|
t.column :title, :string
t.column :lock_version, :integer
+ t.timestamps null: true
end
create_table :lock_without_defaults_cust, force: true do |t|
t.column :title, :string
t.column :custom_lock_version, :integer
+ t.timestamps null: true
end
create_table :magazines, force: true do |t|
@@ -486,7 +491,7 @@ ActiveRecord::Schema.define do
t.datetime :joined_on
t.integer :club_id, :member_id
t.boolean :favourite, default: false
- t.string :type
+ t.integer :type
end
create_table :member_types, force: true do |t|
@@ -783,6 +788,10 @@ ActiveRecord::Schema.define do
t.belongs_to :ship
end
+ create_table :sinks, force: true do |t|
+ t.references :kitchen
+ end
+
create_table :shop_accounts, force: true do |t|
t.references :customer
t.references :customer_carrier
@@ -800,16 +809,19 @@ ActiveRecord::Schema.define do
t.string :sponsorable_type
end
- create_table :string_key_objects, id: false, primary_key: :id, force: true do |t|
- t.string :id
- t.string :name
- t.integer :lock_version, null: false, default: 0
+ create_table :string_key_objects, id: false, force: true do |t|
+ t.string :id, null: false
+ t.string :name
+ t.integer :lock_version, null: false, default: 0
+ t.index :id, unique: true
end
- create_table :subscribers, force: true, id: false do |t|
+ create_table :subscribers, id: false, force: true do |t|
t.string :nick, null: false
t.string :name
- t.column :books_count, :integer, null: false, default: 0
+ t.integer :id
+ t.integer :books_count, null: false, default: 0
+ t.integer :update_count, null: false, default: 0
t.index :nick, unique: true
end
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 6f99155199..ab5237488a 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1 +1,106 @@
+* Default `ActiveSupport::MessageEncryptor` to use AES 256 GCM encryption.
+
+ On for new Rails 5.2 apps. Upgrading apps can find the config as a new
+ framework default.
+
+ *Assain Jaleel*
+
+* Cache: `write_multi`
+
+ Rails.cache.write_multi foo: 'bar', baz: 'qux'
+
+ Plus faster fetch_multi with stores that implement `write_multi_entries`.
+ Keys that aren't found may be written to the cache store in one shot
+ instead of separate writes.
+
+ The default implementation simply calls `write_entry` for each entry.
+ Stores may override if they're capable of one-shot bulk writes, like
+ Redis `MSET`.
+
+ *Jeremy Daer*
+
+* Add default option to module and class attribute accessors.
+
+ mattr_accessor :settings, default: {}
+
+ Works for `mattr_reader`, `mattr_writer`, `cattr_accessor`, `cattr_reader`,
+ and `cattr_writer` as well.
+
+ *Genadi Samokovarov*
+
+* Add `Date#prev_occurring` and `Date#next_occurring` to return specified next/previous occurring day of week.
+
+ *Shota Iguchi*
+
+* Add default option to `class_attribute`.
+
+ Before:
+
+ class_attribute :settings
+ self.settings = {}
+
+ Now:
+
+ class_attribute :settings, default: {}
+
+ *DHH*
+
+* `#singularize` and `#pluralize` now respect uncountables for the specified locale.
+
+ *Eilis Hamilton*
+
+* Add `ActiveSupport::CurrentAttributes` to provide a thread-isolated attributes singleton.
+ Primary use case is keeping all the per-request attributes easily available to the whole system.
+
+ *DHH*
+
+* Fix implicit coercion calculations with scalars and durations
+
+ Previously calculations where the scalar is first would be converted to a duration
+ of seconds but this causes issues with dates being converted to times, e.g:
+
+ Time.zone = "Beijing" # => Asia/Shanghai
+ date = Date.civil(2017, 5, 20) # => Mon, 20 May 2017
+ 2 * 1.day # => 172800 seconds
+ date + 2 * 1.day # => Mon, 22 May 2017 00:00:00 CST +08:00
+
+ Now the `ActiveSupport::Duration::Scalar` calculation methods will try to maintain
+ the part structure of the duration where possible, e.g:
+
+ Time.zone = "Beijing" # => Asia/Shanghai
+ date = Date.civil(2017, 5, 20) # => Mon, 20 May 2017
+ 2 * 1.day # => 2 days
+ date + 2 * 1.day # => Mon, 22 May 2017
+
+ Fixes #29160, #28970.
+
+ *Andrew White*
+
+* Add support for versioned cache entries. This enables the cache stores to recycle cache keys, greatly saving
+ on storage in cases with frequent churn. Works together with the separation of `#cache_key` and `#cache_version`
+ in Active Record and its use in Action Pack's fragment caching.
+
+ *DHH*
+
+* Pass gem name and deprecation horizon to deprecation notifications.
+
+ *Willem van Bergen*
+
+* Add support for `:offset` and `:zone` to `ActiveSupport::TimeWithZone#change`
+
+ *Andrew White*
+
+* Add support for `:offset` to `Time#change`
+
+ Fixes #28723.
+
+ *Andrew White*
+
+* Add `fetch_values` for `HashWithIndifferentAccess`
+
+ The method was originally added to `Hash` in Ruby 2.3.0.
+
+ *Josh Pencheon*
+
+
Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/activesupport/CHANGELOG.md) for previous changes.
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index 08370cba85..16e912694c 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -1,4 +1,4 @@
-version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
+version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
@@ -20,6 +20,11 @@ Gem::Specification.new do |s|
s.rdoc_options.concat ["--encoding", "UTF-8"]
+ s.metadata = {
+ "source_code_uri" => "https://github.com/rails/rails/tree/v#{version}/activesupport",
+ "changelog_uri" => "https://github.com/rails/rails/blob/v#{version}/activesupport/CHANGELOG.md"
+ }
+
s.add_dependency "i18n", "~> 0.7"
s.add_dependency "tzinfo", "~> 1.1"
s.add_dependency "minitest", "~> 5.1"
diff --git a/activesupport/bin/generate_tables b/activesupport/bin/generate_tables
index aa36a01b5b..6f62593f14 100755
--- a/activesupport/bin/generate_tables
+++ b/activesupport/bin/generate_tables
@@ -1,7 +1,7 @@
#!/usr/bin/env ruby
begin
- $:.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
+ $:.unshift(File.expand_path("../lib", __dir__))
require "active_support"
rescue IOError
end
diff --git a/activesupport/bin/test b/activesupport/bin/test
index a7beb14b27..470ce93f10 100755
--- a/activesupport/bin/test
+++ b/activesupport/bin/test
@@ -1,4 +1,4 @@
#!/usr/bin/env ruby
COMPONENT_ROOT = File.expand_path("..", __dir__)
-require File.expand_path("../tools/test", COMPONENT_ROOT)
+require_relative "../../tools/test"
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 03e3ce821a..7302841075 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -22,16 +22,17 @@
#++
require "securerandom"
-require "active_support/dependencies/autoload"
-require "active_support/version"
-require "active_support/logger"
-require "active_support/lazy_load_hooks"
-require "active_support/core_ext/date_and_time/compatibility"
+require_relative "active_support/dependencies/autoload"
+require_relative "active_support/version"
+require_relative "active_support/logger"
+require_relative "active_support/lazy_load_hooks"
+require_relative "active_support/core_ext/date_and_time/compatibility"
module ActiveSupport
extend ActiveSupport::Autoload
autoload :Concern
+ autoload :CurrentAttributes
autoload :Dependencies
autoload :DescendantsTracker
autoload :ExecutionWrapper
diff --git a/activesupport/lib/active_support/all.rb b/activesupport/lib/active_support/all.rb
index 72a23075af..413314167d 100644
--- a/activesupport/lib/active_support/all.rb
+++ b/activesupport/lib/active_support/all.rb
@@ -1,3 +1,3 @@
require "active_support"
-require "active_support/time"
-require "active_support/core_ext"
+require_relative "time"
+require_relative "core_ext"
diff --git a/activesupport/lib/active_support/benchmarkable.rb b/activesupport/lib/active_support/benchmarkable.rb
index 70493c8da7..74e39c74e1 100644
--- a/activesupport/lib/active_support/benchmarkable.rb
+++ b/activesupport/lib/active_support/benchmarkable.rb
@@ -1,5 +1,5 @@
-require "active_support/core_ext/benchmark"
-require "active_support/core_ext/hash/keys"
+require_relative "core_ext/benchmark"
+require_relative "core_ext/hash/keys"
module ActiveSupport
module Benchmarkable
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 4d8c2046e8..f706f8ecf7 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -1,11 +1,11 @@
require "zlib"
-require "active_support/core_ext/array/extract_options"
-require "active_support/core_ext/array/wrap"
-require "active_support/core_ext/module/attribute_accessors"
-require "active_support/core_ext/numeric/bytes"
-require "active_support/core_ext/numeric/time"
-require "active_support/core_ext/object/to_param"
-require "active_support/core_ext/string/inflections"
+require_relative "core_ext/array/extract_options"
+require_relative "core_ext/array/wrap"
+require_relative "core_ext/module/attribute_accessors"
+require_relative "core_ext/numeric/bytes"
+require_relative "core_ext/numeric/time"
+require_relative "core_ext/object/to_param"
+require_relative "core_ext/string/inflections"
module ActiveSupport
# See ActiveSupport::Cache::Store for documentation.
@@ -75,7 +75,7 @@ module ActiveSupport
#
# The +key+ argument can also respond to +cache_key+ or +to_param+.
def expand_cache_key(key, namespace = nil)
- expanded_cache_key = namespace ? "#{namespace}/" : ""
+ expanded_cache_key = (namespace ? "#{namespace}/" : "").dup
if prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
expanded_cache_key << "#{prefix}/"
@@ -88,17 +88,18 @@ module ActiveSupport
private
def retrieve_cache_key(key)
case
- when key.respond_to?(:cache_key) then key.cache_key
- when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param
- when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a)
- else key.to_param
+ when key.respond_to?(:cache_key_with_version) then key.cache_key_with_version
+ when key.respond_to?(:cache_key) then key.cache_key
+ when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param
+ when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a)
+ else key.to_param
end.to_s
end
# Obtains the specified cache store class, given the name of the +store+.
# Raises an error when the store class cannot be found.
def retrieve_store_class(store)
- require "active_support/cache/#{store}"
+ require_relative "cache/#{store}"
rescue LoadError => e
raise "Could not find cache store adapter for #{store} (#{e})"
else
@@ -219,6 +220,10 @@ module ActiveSupport
# cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes)
# cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry
#
+ # Setting <tt>:version</tt> verifies the cache stored under <tt>name</tt>
+ # is of the same version. nil is returned on mismatches despite contents.
+ # This feature is used to support recyclable cache keys.
+ #
# Setting <tt>:race_condition_ttl</tt> is very useful in situations where
# a cache entry is used very frequently and is under heavy load. If a
# cache expires and due to heavy load several different processes will try
@@ -287,6 +292,7 @@ module ActiveSupport
instrument(:read, name, options) do |payload|
cached_entry = read_entry(key, options) unless options[:force]
entry = handle_expired_entry(cached_entry, key, options)
+ entry = nil if entry && entry.mismatched?(normalize_version(name, options))
payload[:super_operation] = :fetch if payload
payload[:hit] = !!entry if payload
end
@@ -303,21 +309,30 @@ module ActiveSupport
end
end
- # Fetches data from the cache, using the given key. If there is data in
+ # Reads data from the cache, using the given key. If there is data in
# the cache with the given key, then that data is returned. Otherwise,
# +nil+ is returned.
#
+ # Note, if data was written with the <tt>:expires_in<tt> or <tt>:version</tt> options,
+ # both of these conditions are applied before the data is returned.
+ #
# Options are passed to the underlying cache implementation.
def read(name, options = nil)
options = merged_options(options)
- key = normalize_key(name, options)
+ key = normalize_key(name, options)
+ version = normalize_version(name, options)
+
instrument(:read, name, options) do |payload|
entry = read_entry(key, options)
+
if entry
if entry.expired?
delete_entry(key, options)
payload[:hit] = false if payload
nil
+ elsif entry.mismatched?(version)
+ payload[:hit] = false if payload
+ nil
else
payload[:hit] = true if payload
entry.value
@@ -341,11 +356,15 @@ module ActiveSupport
results = {}
names.each do |name|
- key = normalize_key(name, options)
- entry = read_entry(key, options)
+ key = normalize_key(name, options)
+ version = normalize_version(name, options)
+ entry = read_entry(key, options)
+
if entry
if entry.expired?
delete_entry(key, options)
+ elsif entry.mismatched?(version)
+ # Skip mismatched versions
else
results[name] = entry.value
end
@@ -354,6 +373,19 @@ module ActiveSupport
results
end
+ # Cache Storage API to write multiple values at once.
+ def write_multi(hash, options = nil)
+ options = merged_options(options)
+
+ instrument :write_multi, hash, options do |payload|
+ entries = hash.each_with_object({}) do |(name, value), memo|
+ memo[normalize_key(name, options)] = Entry.new(value, options.merge(version: normalize_version(name, options)))
+ end
+
+ write_multi_entries entries, options
+ end
+ end
+
# Fetches data from the cache, using the given keys. If there is data in
# the cache with the given keys, then that data is returned. Otherwise,
# the supplied block is called for each key for which there was no data,
@@ -378,14 +410,15 @@ module ActiveSupport
options = names.extract_options!
options = merged_options(options)
- results = read_multi(*names, options)
- names.each_with_object({}) do |name, memo|
- memo[name] = results.fetch(name) do
- value = yield name
- write(name, value, options)
- value
+ read_multi(*names, options).tap do |results|
+ writes = {}
+
+ (names - results.keys).each do |name|
+ results[name] = writes[name] = yield(name)
end
+
+ write_multi writes, options
end
end
@@ -396,7 +429,7 @@ module ActiveSupport
options = merged_options(options)
instrument(:write, name, options) do
- entry = Entry.new(value, options)
+ entry = Entry.new(value, options.merge(version: normalize_version(name, options)))
write_entry(normalize_key(name, options), entry, options)
end
end
@@ -420,7 +453,7 @@ module ActiveSupport
instrument(:exist?, name) do
entry = read_entry(normalize_key(name, options), options)
- (entry && !entry.expired?) || false
+ (entry && !entry.expired? && !entry.mismatched?(normalize_version(name, options))) || false
end
end
@@ -466,7 +499,7 @@ module ActiveSupport
# The options hash is passed to the underlying cache implementation.
#
# All implementations may not support this method.
- def clear
+ def clear(options = nil)
raise NotImplementedError.new("#{self.class.name} does not support clear")
end
@@ -502,6 +535,14 @@ module ActiveSupport
raise NotImplementedError.new
end
+ # Writes multiple entries to the cache implementation. Subclasses MAY
+ # implement this method.
+ def write_multi_entries(hash, options)
+ hash.each do |key, entry|
+ write_entry key, entry, options
+ end
+ end
+
# Deletes an entry from the cache implementation. Subclasses must
# implement this method.
def delete_entry(key, options)
@@ -517,6 +558,16 @@ module ActiveSupport
end
end
+ # Prefixes a key with the namespace. Namespace and key will be delimited
+ # with a colon.
+ def normalize_key(key, options)
+ key = expanded_key(key)
+ namespace = options[:namespace] if options
+ prefix = namespace.is_a?(Proc) ? namespace.call : namespace
+ key = "#{prefix}:#{key}" if prefix
+ key
+ end
+
# Expands key to be a consistent string value. Invokes +cache_key+ if
# object responds to +cache_key+. Otherwise, +to_param+ method will be
# called. If the key is a Hash, then keys will be sorted alphabetically.
@@ -537,14 +588,16 @@ module ActiveSupport
key.to_param
end
- # Prefixes a key with the namespace. Namespace and key will be delimited
- # with a colon.
- def normalize_key(key, options)
- key = expanded_key(key)
- namespace = options[:namespace] if options
- prefix = namespace.is_a?(Proc) ? namespace.call : namespace
- key = "#{prefix}:#{key}" if prefix
- key
+ def normalize_version(key, options = nil)
+ (options && options[:version].try(:to_param)) || expanded_version(key)
+ end
+
+ def expanded_version(key)
+ case
+ when key.respond_to?(:cache_version) then key.cache_version.to_param
+ when key.is_a?(Array) then key.map { |element| expanded_version(element) }.compact.to_param
+ when key.respond_to?(:to_a) then expanded_version(key.to_a)
+ end
end
def instrument(operation, key, options = nil)
@@ -591,13 +644,16 @@ module ActiveSupport
end
end
- # This class is used to represent cache entries. Cache entries have a value and an optional
- # expiration time. The expiration time is used to support the :race_condition_ttl option
- # on the cache.
+ # This class is used to represent cache entries. Cache entries have a value, an optional
+ # expiration time, and an optional version. The expiration time is used to support the :race_condition_ttl option
+ # on the cache. The version is used to support the :version option on the cache for rejecting
+ # mismatches.
#
# Since cache entries in most instances will be serialized, the internals of this class are highly optimized
# using short instance variable names that are lazily defined.
class Entry # :nodoc:
+ attr_reader :version
+
DEFAULT_COMPRESS_LIMIT = 16.kilobytes
# Creates a new cache entry for the specified value. Options supported are
@@ -610,6 +666,7 @@ module ActiveSupport
@value = value
end
+ @version = options[:version]
@created_at = Time.now.to_f
@expires_in = options[:expires_in]
@expires_in = @expires_in.to_f if @expires_in
@@ -619,6 +676,10 @@ module ActiveSupport
compressed? ? uncompress(@value) : @value
end
+ def mismatched?(version)
+ @version && version && @version != version
+ end
+
# Checks if the entry is expired. The +expires_in+ parameter can override
# the value set when the entry was created.
def expired?
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index d5c8585816..bb65da2723 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -1,6 +1,6 @@
-require "active_support/core_ext/marshal"
-require "active_support/core_ext/file/atomic"
-require "active_support/core_ext/string/conversions"
+require_relative "../core_ext/marshal"
+require_relative "../core_ext/file/atomic"
+require_relative "../core_ext/string/conversions"
require "uri/common"
module ActiveSupport
@@ -27,7 +27,7 @@ module ActiveSupport
# Deletes all items from the cache. In this case it deletes all the entries in the specified
# file store directory except for .keep or .gitkeep. Be careful which directory is specified in your
# config file when using +FileStore+ because everything in that directory will be deleted.
- def clear
+ def clear(options = nil)
root_dirs = exclude_from(cache_path, EXCLUDED_DIRS + GITKEEP_FILES)
FileUtils.rm_r(root_dirs.collect { |f| File.join(cache_path, f) })
rescue Errno::ENOENT
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index e09cee3335..7efe6fe3bf 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -6,8 +6,8 @@ rescue LoadError => e
end
require "digest/md5"
-require "active_support/core_ext/marshal"
-require "active_support/core_ext/array/extract_options"
+require_relative "../core_ext/marshal"
+require_relative "../core_ext/array/extract_options"
module ActiveSupport
module Cache
@@ -97,12 +97,18 @@ module ActiveSupport
options = merged_options(options)
keys_to_names = Hash[names.map { |name| [normalize_key(name, options), name] }]
+
raw_values = @data.get_multi(keys_to_names.keys)
values = {}
+
raw_values.each do |key, value|
entry = deserialize_entry(value)
- values[keys_to_names[key]] = entry.value unless entry.expired?
+
+ unless entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options))
+ values[keys_to_names[key]] = entry.value
+ end
end
+
values
end
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb
index 672eb2bb80..2e7e5af0e3 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb
@@ -1,6 +1,6 @@
-require "active_support/core_ext/object/duplicable"
-require "active_support/core_ext/string/inflections"
-require "active_support/per_thread_registry"
+require_relative "../../core_ext/object/duplicable"
+require_relative "../../core_ext/string/inflections"
+require_relative "../../per_thread_registry"
module ActiveSupport
module Cache
@@ -44,7 +44,7 @@ module ActiveSupport
yield
end
- def clear
+ def clear(options = nil)
@data.clear
end
@@ -79,15 +79,15 @@ module ActiveSupport
local_cache_key)
end
- def clear # :nodoc:
+ def clear(options = nil) # :nodoc:
return super unless cache = local_cache
- cache.clear
+ cache.clear(options)
super
end
def cleanup(options = nil) # :nodoc:
return super unless cache = local_cache
- cache.clear(options)
+ cache.clear
super
end
@@ -115,7 +115,12 @@ module ActiveSupport
end
def write_entry(key, entry, options)
- local_cache.write_entry(key, entry, options) if local_cache
+ if options[:unless_exist]
+ local_cache.delete_entry(key, options) if local_cache
+ else
+ local_cache.write_entry(key, entry, options) if local_cache
+ end
+
super
end
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index ea71569ca8..34d62ec3a9 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -1,11 +1,11 @@
-require "active_support/concern"
-require "active_support/descendants_tracker"
-require "active_support/core_ext/array/extract_options"
-require "active_support/core_ext/class/attribute"
-require "active_support/core_ext/kernel/reporting"
-require "active_support/core_ext/kernel/singleton_class"
-require "active_support/core_ext/string/filters"
-require "active_support/deprecation"
+require_relative "concern"
+require_relative "descendants_tracker"
+require_relative "core_ext/array/extract_options"
+require_relative "core_ext/class/attribute"
+require_relative "core_ext/kernel/reporting"
+require_relative "core_ext/kernel/singleton_class"
+require_relative "core_ext/string/filters"
+require_relative "deprecation"
require "thread"
module ActiveSupport
@@ -62,8 +62,7 @@ module ActiveSupport
included do
extend ActiveSupport::DescendantsTracker
- class_attribute :__callbacks, instance_writer: false
- self.__callbacks ||= {}
+ class_attribute :__callbacks, instance_writer: false, default: {}
end
CALLBACK_FILTER_TYPES = [:before, :after, :around]
@@ -512,7 +511,6 @@ module ActiveSupport
end
end
- # An Array with a compile method.
class CallbackChain #:nodoc:#
include Enumerable
@@ -598,7 +596,7 @@ module ActiveSupport
Proc.new do |target, result_lambda|
terminate = true
catch(:abort) do
- result_lambda.call if result_lambda.is_a?(Proc)
+ result_lambda.call
terminate = false
end
terminate
@@ -664,8 +662,10 @@ module ActiveSupport
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.
+ Passing string to be evaluated in :if and :unless conditional
+ options is deprecated and will be removed in Rails 5.2 without
+ replacement. Pass a symbol for an instance method, or a lambda,
+ proc or block, instead.
MSG
end
diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb
index f72a893bcc..ebe4055f91 100644
--- a/activesupport/lib/active_support/configurable.rb
+++ b/activesupport/lib/active_support/configurable.rb
@@ -1,7 +1,7 @@
-require "active_support/concern"
-require "active_support/ordered_options"
-require "active_support/core_ext/array/extract_options"
-require "active_support/core_ext/regexp"
+require_relative "concern"
+require_relative "ordered_options"
+require_relative "core_ext/array/extract_options"
+require_relative "core_ext/regexp"
module ActiveSupport
# Configurable provides a <tt>config</tt> method to store and retrieve
diff --git a/activesupport/lib/active_support/core_ext.rb b/activesupport/lib/active_support/core_ext.rb
index f397f658f3..42e0acf66a 100644
--- a/activesupport/lib/active_support/core_ext.rb
+++ b/activesupport/lib/active_support/core_ext.rb
@@ -1,3 +1,3 @@
-(Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"]).each do |path|
+Dir.glob(File.expand_path("core_ext/*.rb", __dir__)).each do |path|
require path
end
diff --git a/activesupport/lib/active_support/core_ext/array.rb b/activesupport/lib/active_support/core_ext/array.rb
index e908386a1c..3536c7f2bf 100644
--- a/activesupport/lib/active_support/core_ext/array.rb
+++ b/activesupport/lib/active_support/core_ext/array.rb
@@ -1,7 +1,7 @@
-require "active_support/core_ext/array/wrap"
-require "active_support/core_ext/array/access"
-require "active_support/core_ext/array/conversions"
-require "active_support/core_ext/array/extract_options"
-require "active_support/core_ext/array/grouping"
-require "active_support/core_ext/array/prepend_and_append"
-require "active_support/core_ext/array/inquiry"
+require_relative "array/wrap"
+require_relative "array/access"
+require_relative "array/conversions"
+require_relative "array/extract_options"
+require_relative "array/grouping"
+require_relative "array/prepend_and_append"
+require_relative "array/inquiry"
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index cac15e1100..57ad32483a 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -1,8 +1,8 @@
-require "active_support/xml_mini"
-require "active_support/core_ext/hash/keys"
-require "active_support/core_ext/string/inflections"
-require "active_support/core_ext/object/to_param"
-require "active_support/core_ext/object/to_query"
+require_relative "../../xml_mini"
+require_relative "../hash/keys"
+require_relative "../string/inflections"
+require_relative "../object/to_param"
+require_relative "../object/to_query"
class Array
# Converts the array to a comma-separated sentence where the last element is
@@ -179,7 +179,7 @@ class Array
# </messages>
#
def to_xml(options = {})
- require "active_support/builder" unless defined?(Builder)
+ require_relative "../../builder" unless defined?(Builder)
options = options.dup
options[:indent] ||= 2
diff --git a/activesupport/lib/active_support/core_ext/array/inquiry.rb b/activesupport/lib/active_support/core_ext/array/inquiry.rb
index 66fa5fcd0c..c46b01baf0 100644
--- a/activesupport/lib/active_support/core_ext/array/inquiry.rb
+++ b/activesupport/lib/active_support/core_ext/array/inquiry.rb
@@ -1,4 +1,4 @@
-require "active_support/array_inquirer"
+require_relative "../../array_inquirer"
class Array
# Wraps the array in an +ArrayInquirer+ object, which gives a friendlier way
diff --git a/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb b/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb
index 16a6789f8d..88a34128c9 100644
--- a/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb
+++ b/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb
@@ -1,7 +1,7 @@
class Array
# The human way of thinking about adding stuff to the end of a list is with append.
- alias_method :append, :<<
+ alias_method :append, :push unless [].respond_to?(:append)
# The human way of thinking about adding stuff to the beginning of a list is with prepend.
- alias_method :prepend, :unshift
+ alias_method :prepend, :unshift unless [].respond_to?(:prepend)
end
diff --git a/activesupport/lib/active_support/core_ext/big_decimal.rb b/activesupport/lib/active_support/core_ext/big_decimal.rb
index 7b4f87f10e..7e396cac3c 100644
--- a/activesupport/lib/active_support/core_ext/big_decimal.rb
+++ b/activesupport/lib/active_support/core_ext/big_decimal.rb
@@ -1 +1 @@
-require "active_support/core_ext/big_decimal/conversions"
+require_relative "big_decimal/conversions"
diff --git a/activesupport/lib/active_support/core_ext/class.rb b/activesupport/lib/active_support/core_ext/class.rb
index 6a19e862d0..66dd1c939a 100644
--- a/activesupport/lib/active_support/core_ext/class.rb
+++ b/activesupport/lib/active_support/core_ext/class.rb
@@ -1,2 +1,2 @@
-require "active_support/core_ext/class/attribute"
-require "active_support/core_ext/class/subclasses"
+require_relative "class/attribute"
+require_relative "class/subclasses"
diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb
index ba422f9071..26b826b39b 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute.rb
@@ -1,6 +1,6 @@
-require "active_support/core_ext/kernel/singleton_class"
-require "active_support/core_ext/module/remove_method"
-require "active_support/core_ext/array/extract_options"
+require_relative "../kernel/singleton_class"
+require_relative "../module/remove_method"
+require_relative "../array/extract_options"
class Class
# Declare a class-level attribute whose value is inheritable by subclasses.
@@ -68,11 +68,16 @@ class Class
# object.setting = false # => NoMethodError
#
# To opt out of both instance methods, pass <tt>instance_accessor: false</tt>.
+ #
+ # To set a default value for the attribute, pass <tt>default:</tt>, like so:
+ #
+ # class_attribute :settings, default: {}
def class_attribute(*attrs)
options = attrs.extract_options!
- instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true)
- instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true)
+ instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true)
+ instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true)
instance_predicate = options.fetch(:instance_predicate, true)
+ default_value = options.fetch(:default, nil)
attrs.each do |name|
remove_possible_singleton_method(name)
@@ -123,6 +128,10 @@ class Class
remove_possible_method "#{name}="
attr_writer name
end
+
+ unless default_value.nil?
+ self.send("#{name}=", default_value)
+ end
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
index 0f767925ed..eebf2e294a 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
@@ -1,4 +1,4 @@
# cattr_* became mattr_* aliases in 7dfbd91b0780fbd6a1dd9bfbc176e10894871d2d,
# but we keep this around for libraries that directly require it knowing they
# want cattr_*. No need to deprecate.
-require "active_support/core_ext/module/attribute_accessors"
+require_relative "../module/attribute_accessors"
diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb
index 62397d9508..545ad7fb3e 100644
--- a/activesupport/lib/active_support/core_ext/class/subclasses.rb
+++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb
@@ -1,5 +1,5 @@
-require "active_support/core_ext/module/anonymous"
-require "active_support/core_ext/module/reachable"
+require_relative "../module/anonymous"
+require_relative "../module/reachable"
class Class
begin
diff --git a/activesupport/lib/active_support/core_ext/date.rb b/activesupport/lib/active_support/core_ext/date.rb
index 4f66804da2..aba72c732f 100644
--- a/activesupport/lib/active_support/core_ext/date.rb
+++ b/activesupport/lib/active_support/core_ext/date.rb
@@ -1,5 +1,5 @@
-require "active_support/core_ext/date/acts_like"
-require "active_support/core_ext/date/blank"
-require "active_support/core_ext/date/calculations"
-require "active_support/core_ext/date/conversions"
-require "active_support/core_ext/date/zones"
+require_relative "date/acts_like"
+require_relative "date/blank"
+require_relative "date/calculations"
+require_relative "date/conversions"
+require_relative "date/zones"
diff --git a/activesupport/lib/active_support/core_ext/date/acts_like.rb b/activesupport/lib/active_support/core_ext/date/acts_like.rb
index 46fe99ad47..dfea3e3f09 100644
--- a/activesupport/lib/active_support/core_ext/date/acts_like.rb
+++ b/activesupport/lib/active_support/core_ext/date/acts_like.rb
@@ -1,4 +1,4 @@
-require "active_support/core_ext/object/acts_like"
+require_relative "../object/acts_like"
class Date
# Duck-types as a Date-like class. See Object#acts_like?.
diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb
index d6f60cac04..24d3721d48 100644
--- a/activesupport/lib/active_support/core_ext/date/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date/calculations.rb
@@ -1,9 +1,9 @@
require "date"
-require "active_support/duration"
-require "active_support/core_ext/object/acts_like"
-require "active_support/core_ext/date/zones"
-require "active_support/core_ext/time/zones"
-require "active_support/core_ext/date_and_time/calculations"
+require_relative "../../duration"
+require_relative "../object/acts_like"
+require_relative "zones"
+require_relative "../time/zones"
+require_relative "../date_and_time/calculations"
class Date
include DateAndTime::Calculations
diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb
index d553406dff..7971d39fb7 100644
--- a/activesupport/lib/active_support/core_ext/date/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date/conversions.rb
@@ -1,7 +1,7 @@
require "date"
-require "active_support/inflector/methods"
-require "active_support/core_ext/date/zones"
-require "active_support/core_ext/module/remove_method"
+require_relative "../../inflector/methods"
+require_relative "zones"
+require_relative "../module/remove_method"
class Date
DATE_FORMATS = {
@@ -79,6 +79,9 @@ class Date
# date.to_time(:local) # => 2007-11-10 00:00:00 0800
#
# date.to_time(:utc) # => 2007-11-10 00:00:00 UTC
+ #
+ # NOTE: The :local timezone is Ruby's *process* timezone, i.e. ENV['TZ'].
+ # If the *application's* timezone is needed, then use +in_time_zone+ instead.
def to_time(form = :local)
raise ArgumentError, "Expected :local or :utc, got #{form.inspect}." unless [:local, :utc].include?(form)
::Time.send(form, year, month, day)
diff --git a/activesupport/lib/active_support/core_ext/date/zones.rb b/activesupport/lib/active_support/core_ext/date/zones.rb
index da23fe4892..d94c7bcfc2 100644
--- a/activesupport/lib/active_support/core_ext/date/zones.rb
+++ b/activesupport/lib/active_support/core_ext/date/zones.rb
@@ -1,5 +1,5 @@
require "date"
-require "active_support/core_ext/date_and_time/zones"
+require_relative "../date_and_time/zones"
class Date
include DateAndTime::Zones
diff --git a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
index f2ba7fdda5..cf01a4542f 100644
--- a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
@@ -1,4 +1,4 @@
-require "active_support/core_ext/object/try"
+require_relative "../object/try"
module DateAndTime
module Calculations
@@ -320,6 +320,22 @@ module DateAndTime
beginning_of_year..end_of_year
end
+ # Returns specific next occurring day of week
+ def next_occurring(day_of_week)
+ current_day_number = wday != 0 ? wday - 1 : 6
+ from_now = DAYS_INTO_WEEK.fetch(day_of_week) - current_day_number
+ from_now += 7 unless from_now > 0
+ since(from_now.days)
+ end
+
+ # Returns specific previous occurring day of week
+ def prev_occurring(day_of_week)
+ current_day_number = wday != 0 ? wday - 1 : 6
+ ago = current_day_number - DAYS_INTO_WEEK.fetch(day_of_week)
+ ago += 7 unless ago > 0
+ ago(ago.days)
+ end
+
private
def first_hour(date_or_time)
date_or_time.acts_like?(:time) ? date_or_time.beginning_of_day : date_or_time
diff --git a/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb b/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb
index ab80392460..bb9908df61 100644
--- a/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb
+++ b/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb
@@ -1,4 +1,4 @@
-require "active_support/core_ext/module/attribute_accessors"
+require_relative "../module/attribute_accessors"
module DateAndTime
module Compatibility
@@ -9,6 +9,6 @@ module DateAndTime
# of the receiver. For backwards compatibility we're overriding
# this behavior, but new apps will have an initializer that sets
# this to true, because the new behavior is preferred.
- mattr_accessor(:preserve_timezone, instance_writer: false) { false }
+ mattr_accessor :preserve_timezone, instance_writer: false, default: false
end
end
diff --git a/activesupport/lib/active_support/core_ext/date_time.rb b/activesupport/lib/active_support/core_ext/date_time.rb
index 6fd498f864..ce35026a71 100644
--- a/activesupport/lib/active_support/core_ext/date_time.rb
+++ b/activesupport/lib/active_support/core_ext/date_time.rb
@@ -1,5 +1,5 @@
-require "active_support/core_ext/date_time/acts_like"
-require "active_support/core_ext/date_time/blank"
-require "active_support/core_ext/date_time/calculations"
-require "active_support/core_ext/date_time/compatibility"
-require "active_support/core_ext/date_time/conversions"
+require_relative "date_time/acts_like"
+require_relative "date_time/blank"
+require_relative "date_time/calculations"
+require_relative "date_time/compatibility"
+require_relative "date_time/conversions"
diff --git a/activesupport/lib/active_support/core_ext/date_time/acts_like.rb b/activesupport/lib/active_support/core_ext/date_time/acts_like.rb
index 6f50f55a53..f0356ee6a0 100644
--- a/activesupport/lib/active_support/core_ext/date_time/acts_like.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/acts_like.rb
@@ -1,5 +1,5 @@
require "date"
-require "active_support/core_ext/object/acts_like"
+require_relative "../object/acts_like"
class DateTime
# Duck-types as a Date-like class. See Object#acts_like?.
diff --git a/activesupport/lib/active_support/core_ext/date_time/compatibility.rb b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
index eb8b8b2c65..fe255c087f 100644
--- a/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
@@ -1,4 +1,5 @@
-require "active_support/core_ext/date_and_time/compatibility"
+require_relative "../date_and_time/compatibility"
+require_relative "../module/remove_method"
class DateTime
include DateAndTime::Compatibility
diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
index d9b3743858..36bfd28f31 100644
--- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
@@ -1,8 +1,8 @@
require "date"
-require "active_support/inflector/methods"
-require "active_support/core_ext/time/conversions"
-require "active_support/core_ext/date_time/calculations"
-require "active_support/values/time_zone"
+require_relative "../../inflector/methods"
+require_relative "../time/conversions"
+require_relative "calculations"
+require_relative "../../values/time_zone"
class DateTime
# Convert to a formatted string. See Time::DATE_FORMATS for predefined formats.
diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb
index 90d7d2947f..3a4ae6cb8b 100644
--- a/activesupport/lib/active_support/core_ext/enumerable.rb
+++ b/activesupport/lib/active_support/core_ext/enumerable.rb
@@ -1,28 +1,50 @@
module Enumerable
- # Calculates a sum from the elements.
+ # Enumerable#sum was added in Ruby 2.4, but it only works with Numeric elements
+ # when we omit an identity.
#
- # payments.sum { |p| p.price * p.tax_rate }
- # payments.sum(&:price)
- #
- # The latter is a shortcut for:
- #
- # payments.inject(0) { |sum, p| sum + p.price }
- #
- # It can also calculate the sum without the use of a block.
- #
- # [5, 15, 10].sum # => 30
- # ['foo', 'bar'].sum # => "foobar"
- # [[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:
- #
- # [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0)
- def sum(identity = nil, &block)
- if block_given?
- map(&block).sum(identity)
- else
- sum = identity ? inject(identity, :+) : inject(:+)
- sum || identity || 0
+ # We tried shimming it to attempt the fast native method, rescue TypeError,
+ # and fall back to the compatible implementation, but that's much slower than
+ # just calling the compat method in the first place.
+ if Enumerable.instance_methods(false).include?(:sum) && !((?a..?b).sum rescue false)
+ # We can't use Refinements here because Refinements with Module which will be prepended
+ # doesn't work well https://bugs.ruby-lang.org/issues/13446
+ alias :_original_sum_with_required_identity :sum
+ private :_original_sum_with_required_identity
+ # Calculates a sum from the elements.
+ #
+ # payments.sum { |p| p.price * p.tax_rate }
+ # payments.sum(&:price)
+ #
+ # The latter is a shortcut for:
+ #
+ # payments.inject(0) { |sum, p| sum + p.price }
+ #
+ # It can also calculate the sum without the use of a block.
+ #
+ # [5, 15, 10].sum # => 30
+ # ['foo', 'bar'].sum # => "foobar"
+ # [[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:
+ #
+ # [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0)
+ def sum(identity = nil, &block)
+ if identity
+ _original_sum_with_required_identity(identity, &block)
+ elsif block_given?
+ map(&block).sum(identity)
+ else
+ inject(:+) || 0
+ end
+ end
+ else
+ def sum(identity = nil, &block)
+ if block_given?
+ map(&block).sum(identity)
+ else
+ sum = identity ? inject(identity, :+) : inject(:+)
+ sum || identity || 0
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/file.rb b/activesupport/lib/active_support/core_ext/file.rb
index 6d99bad2af..ea3267f5da 100644
--- a/activesupport/lib/active_support/core_ext/file.rb
+++ b/activesupport/lib/active_support/core_ext/file.rb
@@ -1 +1 @@
-require "active_support/core_ext/file/atomic"
+require_relative "file/atomic"
diff --git a/activesupport/lib/active_support/core_ext/hash.rb b/activesupport/lib/active_support/core_ext/hash.rb
index c819307e8a..6122ac57ba 100644
--- a/activesupport/lib/active_support/core_ext/hash.rb
+++ b/activesupport/lib/active_support/core_ext/hash.rb
@@ -1,9 +1,9 @@
-require "active_support/core_ext/hash/compact"
-require "active_support/core_ext/hash/conversions"
-require "active_support/core_ext/hash/deep_merge"
-require "active_support/core_ext/hash/except"
-require "active_support/core_ext/hash/indifferent_access"
-require "active_support/core_ext/hash/keys"
-require "active_support/core_ext/hash/reverse_merge"
-require "active_support/core_ext/hash/slice"
-require "active_support/core_ext/hash/transform_values"
+require_relative "hash/compact"
+require_relative "hash/conversions"
+require_relative "hash/deep_merge"
+require_relative "hash/except"
+require_relative "hash/indifferent_access"
+require_relative "hash/keys"
+require_relative "hash/reverse_merge"
+require_relative "hash/slice"
+require_relative "hash/transform_values"
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 2a58a7f1ca..713711e16a 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -1,11 +1,11 @@
-require "active_support/xml_mini"
-require "active_support/time"
-require "active_support/core_ext/object/blank"
-require "active_support/core_ext/object/to_param"
-require "active_support/core_ext/object/to_query"
-require "active_support/core_ext/array/wrap"
-require "active_support/core_ext/hash/reverse_merge"
-require "active_support/core_ext/string/inflections"
+require_relative "../../xml_mini"
+require_relative "../../time"
+require_relative "../object/blank"
+require_relative "../object/to_param"
+require_relative "../object/to_query"
+require_relative "../array/wrap"
+require_relative "reverse_merge"
+require_relative "../string/inflections"
class Hash
# Returns a string containing an XML representation of its receiver:
@@ -71,7 +71,7 @@ class Hash
# configure your own builder with the <tt>:builder</tt> option. The method also accepts
# options like <tt>:dasherize</tt> and friends, they are forwarded to the builder.
def to_xml(options = {})
- require "active_support/builder" unless defined?(Builder)
+ require_relative "../../builder" unless defined?(Builder)
options = options.dup
options[:indent] ||= 2
diff --git a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
index 3e1ccecb6c..4b7fe34cc9 100644
--- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
+++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
@@ -1,4 +1,4 @@
-require "active_support/hash_with_indifferent_access"
+require_relative "../../hash_with_indifferent_access"
class Hash
# Returns an <tt>ActiveSupport::HashWithIndifferentAccess</tt> out of its receiver:
diff --git a/activesupport/lib/active_support/core_ext/integer.rb b/activesupport/lib/active_support/core_ext/integer.rb
index 8f0c55f9d3..e7c1aae980 100644
--- a/activesupport/lib/active_support/core_ext/integer.rb
+++ b/activesupport/lib/active_support/core_ext/integer.rb
@@ -1,3 +1,3 @@
-require "active_support/core_ext/integer/multiple"
-require "active_support/core_ext/integer/inflections"
-require "active_support/core_ext/integer/time"
+require_relative "integer/multiple"
+require_relative "integer/inflections"
+require_relative "integer/time"
diff --git a/activesupport/lib/active_support/core_ext/integer/inflections.rb b/activesupport/lib/active_support/core_ext/integer/inflections.rb
index bc21b65533..0b2ada8b22 100644
--- a/activesupport/lib/active_support/core_ext/integer/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/integer/inflections.rb
@@ -1,4 +1,4 @@
-require "active_support/inflector"
+require_relative "../../inflector"
class Integer
# Ordinalize turns a number into an ordinal string used to denote the
diff --git a/activesupport/lib/active_support/core_ext/integer/time.rb b/activesupport/lib/active_support/core_ext/integer/time.rb
index 74baae3639..d5625dfd1c 100644
--- a/activesupport/lib/active_support/core_ext/integer/time.rb
+++ b/activesupport/lib/active_support/core_ext/integer/time.rb
@@ -1,5 +1,5 @@
-require "active_support/duration"
-require "active_support/core_ext/numeric/time"
+require_relative "../../duration"
+require_relative "../numeric/time"
class Integer
# Enables the use of time calculations and declarations, like <tt>45.minutes +
diff --git a/activesupport/lib/active_support/core_ext/kernel.rb b/activesupport/lib/active_support/core_ext/kernel.rb
index 3d41ff7876..26142b107b 100644
--- a/activesupport/lib/active_support/core_ext/kernel.rb
+++ b/activesupport/lib/active_support/core_ext/kernel.rb
@@ -1,4 +1,4 @@
-require "active_support/core_ext/kernel/agnostics"
-require "active_support/core_ext/kernel/concern"
-require "active_support/core_ext/kernel/reporting"
-require "active_support/core_ext/kernel/singleton_class"
+require_relative "kernel/agnostics"
+require_relative "kernel/concern"
+require_relative "kernel/reporting"
+require_relative "kernel/singleton_class"
diff --git a/activesupport/lib/active_support/core_ext/kernel/concern.rb b/activesupport/lib/active_support/core_ext/kernel/concern.rb
index 307a7f7a63..535f0d21df 100644
--- a/activesupport/lib/active_support/core_ext/kernel/concern.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/concern.rb
@@ -1,4 +1,4 @@
-require "active_support/core_ext/module/concerning"
+require_relative "../module/concerning"
module Kernel
module_function
diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb
index 2930255557..3ee394edd9 100644
--- a/activesupport/lib/active_support/core_ext/module.rb
+++ b/activesupport/lib/active_support/core_ext/module.rb
@@ -1,11 +1,11 @@
-require "active_support/core_ext/module/aliasing"
-require "active_support/core_ext/module/introspection"
-require "active_support/core_ext/module/anonymous"
-require "active_support/core_ext/module/reachable"
-require "active_support/core_ext/module/attribute_accessors"
-require "active_support/core_ext/module/attribute_accessors_per_thread"
-require "active_support/core_ext/module/attr_internal"
-require "active_support/core_ext/module/concerning"
-require "active_support/core_ext/module/delegation"
-require "active_support/core_ext/module/deprecation"
-require "active_support/core_ext/module/remove_method"
+require_relative "module/aliasing"
+require_relative "module/introspection"
+require_relative "module/anonymous"
+require_relative "module/reachable"
+require_relative "module/attribute_accessors"
+require_relative "module/attribute_accessors_per_thread"
+require_relative "module/attr_internal"
+require_relative "module/concerning"
+require_relative "module/delegation"
+require_relative "module/deprecation"
+require_relative "module/remove_method"
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
index 2c24081eb9..e978bd41d6 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -1,5 +1,5 @@
-require "active_support/core_ext/array/extract_options"
-require "active_support/core_ext/regexp"
+require_relative "../array/extract_options"
+require_relative "../regexp"
# Extends the module object with class/module and instance accessors for
# class/module attributes, just like the native attr* accessors for instance
@@ -38,13 +38,10 @@ class Module
#
# Person.new.hair_colors # => NoMethodError
#
- #
- # Also, you can pass a block to set up the attribute with a default value.
+ # You can set a default value for the attribute.
#
# module HairColors
- # mattr_reader :hair_colors do
- # [:brown, :black, :blonde, :red]
- # end
+ # mattr_reader :hair_colors, default: [:brown, :black, :blonde, :red]
# end
#
# class Person
@@ -52,8 +49,7 @@ class Module
# end
#
# Person.new.hair_colors # => [:brown, :black, :blonde, :red]
- def mattr_reader(*syms)
- options = syms.extract_options!
+ def mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil)
syms.each do |sym|
raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym)
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
@@ -64,14 +60,16 @@ class Module
end
EOS
- unless options[:instance_reader] == false || options[:instance_accessor] == false
+ if instance_reader && instance_accessor
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def #{sym}
@@#{sym}
end
EOS
end
- class_variable_set("@@#{sym}", yield) if block_given?
+
+ sym_default_value = (block_given? && default.nil?) ? yield : default
+ class_variable_set("@@#{sym}", sym_default_value) unless sym_default_value.nil?
end
end
alias :cattr_reader :mattr_reader
@@ -107,12 +105,10 @@ class Module
#
# Person.new.hair_colors = [:blonde, :red] # => NoMethodError
#
- # Also, you can pass a block to set up the attribute with a default value.
+ # You can set a default value for the attribute.
#
# module HairColors
- # mattr_writer :hair_colors do
- # [:brown, :black, :blonde, :red]
- # end
+ # mattr_writer :hair_colors, default: [:brown, :black, :blonde, :red]
# end
#
# class Person
@@ -120,8 +116,7 @@ class Module
# end
#
# Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
- def mattr_writer(*syms)
- options = syms.extract_options!
+ def mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil)
syms.each do |sym|
raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym)
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
@@ -132,14 +127,16 @@ class Module
end
EOS
- unless options[:instance_writer] == false || options[:instance_accessor] == false
+ if instance_writer && instance_accessor
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def #{sym}=(obj)
@@#{sym} = obj
end
EOS
end
- send("#{sym}=", yield) if block_given?
+
+ sym_default_value = (block_given? && default.nil?) ? yield : default
+ send("#{sym}=", sym_default_value) unless sym_default_value.nil?
end
end
alias :cattr_writer :mattr_writer
@@ -197,12 +194,10 @@ class Module
# Person.new.hair_colors = [:brown] # => NoMethodError
# Person.new.hair_colors # => NoMethodError
#
- # Also you can pass a block to set up the attribute with a default value.
+ # You can set a default value for the attribute.
#
# module HairColors
- # mattr_accessor :hair_colors do
- # [:brown, :black, :blonde, :red]
- # end
+ # mattr_accessor :hair_colors, default: [:brown, :black, :blonde, :red]
# end
#
# class Person
@@ -210,9 +205,9 @@ class Module
# end
#
# Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
- def mattr_accessor(*syms, &blk)
- mattr_reader(*syms, &blk)
- mattr_writer(*syms)
+ def mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil, &blk)
+ mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default, &blk)
+ mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor, default: default)
end
alias :cattr_accessor :mattr_accessor
end
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
index 1e82b4acc2..9ef834fa6f 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
@@ -1,5 +1,5 @@
-require "active_support/core_ext/array/extract_options"
-require "active_support/core_ext/regexp"
+require_relative "../array/extract_options"
+require_relative "../regexp"
# Extends the module object with class/module and instance accessors for
# class/module attributes, just like the native attr* accessors for instance
diff --git a/activesupport/lib/active_support/core_ext/module/concerning.rb b/activesupport/lib/active_support/core_ext/module/concerning.rb
index 97b0a382ce..eb821aba60 100644
--- a/activesupport/lib/active_support/core_ext/module/concerning.rb
+++ b/activesupport/lib/active_support/core_ext/module/concerning.rb
@@ -1,4 +1,4 @@
-require "active_support/concern"
+require_relative "../../concern"
class Module
# = Bite-sized separation of concerns
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index cdf27f49ad..13bb8070ae 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -1,5 +1,5 @@
require "set"
-require "active_support/core_ext/regexp"
+require_relative "../regexp"
class Module
# Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+
@@ -174,7 +174,7 @@ class Module
to = to.to_s
to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to)
- methods.each do |method|
+ methods.map do |method|
# Attribute writer methods only accept one argument. Makes sure []=
# methods still accept two arguments.
definition = /[^\]]=$/.match?(method) ? "arg" : "*args, &block"
@@ -219,55 +219,53 @@ class Module
# When building decorators, a common pattern may emerge:
#
# class Partition
- # def initialize(first_event)
- # @events = [ first_event ]
+ # def initialize(event)
+ # @event = event
# end
#
- # def people
- # if @events.first.detail.people.any?
- # @events.collect { |e| Array(e.detail.people) }.flatten.uniq
- # else
- # @events.collect(&:creator).uniq
- # end
+ # def person
+ # @event.detail.person || @event.creator
# end
#
# private
# def respond_to_missing?(name, include_private = false)
- # @events.respond_to?(name, include_private)
+ # @event.respond_to?(name, include_private)
# end
#
# def method_missing(method, *args, &block)
- # @events.send(method, *args, &block)
+ # @event.send(method, *args, &block)
# end
# end
#
- # With `Module#delegate_missing_to`, the above is condensed to:
+ # With <tt>Module#delegate_missing_to</tt>, the above is condensed to:
#
# class Partition
- # delegate_missing_to :@events
+ # delegate_missing_to :@event
#
- # def initialize(first_event)
- # @events = [ first_event ]
+ # def initialize(event)
+ # @event = event
# end
#
- # def people
- # if @events.first.detail.people.any?
- # @events.collect { |e| Array(e.detail.people) }.flatten.uniq
- # else
- # @events.collect(&:creator).uniq
- # end
+ # def person
+ # @event.detail.person || @event.creator
# end
# end
#
- # The target can be anything callable within the object. E.g. instance
- # variables, methods, constants and the likes.
+ # The target can be anything callable within the object, e.g. instance
+ # variables, methods, constants, etc.
+ #
+ # The delegated method must be public on the target, otherwise it will
+ # raise +NoMethodError+.
def delegate_missing_to(target)
target = target.to_s
target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target)
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def respond_to_missing?(name, include_private = false)
- #{target}.respond_to?(name, include_private)
+ # It may look like an oversight, but we deliberately do not pass
+ # +include_private+, because they do not get delegated.
+
+ #{target}.respond_to?(name) || super
end
def method_missing(method, *args, &block)
diff --git a/activesupport/lib/active_support/core_ext/module/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb
index ca20a6d4c5..4a7930792c 100644
--- a/activesupport/lib/active_support/core_ext/module/introspection.rb
+++ b/activesupport/lib/active_support/core_ext/module/introspection.rb
@@ -1,4 +1,4 @@
-require "active_support/inflector"
+require_relative "../../inflector"
class Module
# Returns the name of the module containing this one.
diff --git a/activesupport/lib/active_support/core_ext/module/reachable.rb b/activesupport/lib/active_support/core_ext/module/reachable.rb
index b89a38f26c..0f78999634 100644
--- a/activesupport/lib/active_support/core_ext/module/reachable.rb
+++ b/activesupport/lib/active_support/core_ext/module/reachable.rb
@@ -1,5 +1,5 @@
-require "active_support/core_ext/module/anonymous"
-require "active_support/core_ext/string/inflections"
+require_relative "anonymous"
+require_relative "../string/inflections"
class Module
def reachable? #:nodoc:
diff --git a/activesupport/lib/active_support/core_ext/numeric.rb b/activesupport/lib/active_support/core_ext/numeric.rb
index 6062f9e3a8..e10d7708bd 100644
--- a/activesupport/lib/active_support/core_ext/numeric.rb
+++ b/activesupport/lib/active_support/core_ext/numeric.rb
@@ -1,4 +1,4 @@
-require "active_support/core_ext/numeric/bytes"
-require "active_support/core_ext/numeric/time"
-require "active_support/core_ext/numeric/inquiry"
-require "active_support/core_ext/numeric/conversions"
+require_relative "numeric/bytes"
+require_relative "numeric/time"
+require_relative "numeric/inquiry"
+require_relative "numeric/conversions"
diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
index 4f6621693e..397e32ee8e 100644
--- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
@@ -1,6 +1,6 @@
-require "active_support/core_ext/big_decimal/conversions"
-require "active_support/number_helper"
-require "active_support/core_ext/module/deprecation"
+require_relative "../big_decimal/conversions"
+require_relative "../../number_helper"
+require_relative "../module/deprecation"
module ActiveSupport::NumericWithFormat
# Provides options for converting numbers into formatted strings.
diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb
index 2e6c70d418..cf002552fb 100644
--- a/activesupport/lib/active_support/core_ext/numeric/time.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/time.rb
@@ -1,8 +1,8 @@
-require "active_support/duration"
-require "active_support/core_ext/time/calculations"
-require "active_support/core_ext/time/acts_like"
-require "active_support/core_ext/date/calculations"
-require "active_support/core_ext/date/acts_like"
+require_relative "../../duration"
+require_relative "../time/calculations"
+require_relative "../time/acts_like"
+require_relative "../date/calculations"
+require_relative "../date/acts_like"
class Numeric
# Enables the use of time calculations and declarations, like 45.minutes + 2.hours + 4.years.
diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb
index 58bbf78601..993cc6a059 100644
--- a/activesupport/lib/active_support/core_ext/object.rb
+++ b/activesupport/lib/active_support/core_ext/object.rb
@@ -1,14 +1,14 @@
-require "active_support/core_ext/object/acts_like"
-require "active_support/core_ext/object/blank"
-require "active_support/core_ext/object/duplicable"
-require "active_support/core_ext/object/deep_dup"
-require "active_support/core_ext/object/try"
-require "active_support/core_ext/object/inclusion"
+require_relative "object/acts_like"
+require_relative "object/blank"
+require_relative "object/duplicable"
+require_relative "object/deep_dup"
+require_relative "object/try"
+require_relative "object/inclusion"
-require "active_support/core_ext/object/conversions"
-require "active_support/core_ext/object/instance_variables"
+require_relative "object/conversions"
+require_relative "object/instance_variables"
-require "active_support/core_ext/object/json"
-require "active_support/core_ext/object/to_param"
-require "active_support/core_ext/object/to_query"
-require "active_support/core_ext/object/with_options"
+require_relative "object/json"
+require_relative "object/to_param"
+require_relative "object/to_query"
+require_relative "object/with_options"
diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb
index bdb50ee291..c37c2649d6 100644
--- a/activesupport/lib/active_support/core_ext/object/blank.rb
+++ b/activesupport/lib/active_support/core_ext/object/blank.rb
@@ -1,4 +1,4 @@
-require "active_support/core_ext/regexp"
+require_relative "../regexp"
class Object
# An object is blank if it's false, empty, or a whitespace string.
diff --git a/activesupport/lib/active_support/core_ext/object/conversions.rb b/activesupport/lib/active_support/core_ext/object/conversions.rb
index 918ebcdc9f..9d3bdbc528 100644
--- a/activesupport/lib/active_support/core_ext/object/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/object/conversions.rb
@@ -1,4 +1,4 @@
-require "active_support/core_ext/object/to_param"
-require "active_support/core_ext/object/to_query"
-require "active_support/core_ext/array/conversions"
-require "active_support/core_ext/hash/conversions"
+require_relative "to_param"
+require_relative "to_query"
+require_relative "../array/conversions"
+require_relative "../hash/conversions"
diff --git a/activesupport/lib/active_support/core_ext/object/deep_dup.rb b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
index 5ac649e552..d966a29408 100644
--- a/activesupport/lib/active_support/core_ext/object/deep_dup.rb
+++ b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
@@ -1,4 +1,4 @@
-require "active_support/core_ext/object/duplicable"
+require_relative "duplicable"
class Object
# Returns a deep copy of object if it's duplicable. If it's
diff --git a/activesupport/lib/active_support/core_ext/object/json.rb b/activesupport/lib/active_support/core_ext/object/json.rb
index 1c4d181443..d54373d5f2 100644
--- a/activesupport/lib/active_support/core_ext/object/json.rb
+++ b/activesupport/lib/active_support/core_ext/object/json.rb
@@ -3,14 +3,14 @@ require "json"
require "bigdecimal"
require "uri/generic"
require "pathname"
-require "active_support/core_ext/big_decimal/conversions" # for #to_s
-require "active_support/core_ext/hash/except"
-require "active_support/core_ext/hash/slice"
-require "active_support/core_ext/object/instance_variables"
+require_relative "../big_decimal/conversions" # for #to_s
+require_relative "../hash/except"
+require_relative "../hash/slice"
+require_relative "instance_variables"
require "time"
-require "active_support/core_ext/time/conversions"
-require "active_support/core_ext/date_time/conversions"
-require "active_support/core_ext/date/conversions"
+require_relative "../time/conversions"
+require_relative "../date_time/conversions"
+require_relative "../date/conversions"
# The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting
# their default behavior. That said, we need to define the basic to_json method in all of them,
diff --git a/activesupport/lib/active_support/core_ext/object/to_param.rb b/activesupport/lib/active_support/core_ext/object/to_param.rb
index 5eeaf03163..d39727ce00 100644
--- a/activesupport/lib/active_support/core_ext/object/to_param.rb
+++ b/activesupport/lib/active_support/core_ext/object/to_param.rb
@@ -1 +1 @@
-require "active_support/core_ext/object/to_query"
+require_relative "to_query"
diff --git a/activesupport/lib/active_support/core_ext/object/with_options.rb b/activesupport/lib/active_support/core_ext/object/with_options.rb
index 3a44e08630..68a8da25ff 100644
--- a/activesupport/lib/active_support/core_ext/object/with_options.rb
+++ b/activesupport/lib/active_support/core_ext/object/with_options.rb
@@ -1,4 +1,4 @@
-require "active_support/option_merger"
+require_relative "../../option_merger"
class Object
# An elegant way to factor duplication out of options passed to a series of
diff --git a/activesupport/lib/active_support/core_ext/range.rb b/activesupport/lib/active_support/core_ext/range.rb
index 3190e3ff76..3e17780b76 100644
--- a/activesupport/lib/active_support/core_ext/range.rb
+++ b/activesupport/lib/active_support/core_ext/range.rb
@@ -1,4 +1,4 @@
-require "active_support/core_ext/range/conversions"
-require "active_support/core_ext/range/include_range"
-require "active_support/core_ext/range/overlaps"
-require "active_support/core_ext/range/each"
+require_relative "range/conversions"
+require_relative "range/include_range"
+require_relative "range/overlaps"
+require_relative "range/each"
diff --git a/activesupport/lib/active_support/core_ext/string.rb b/activesupport/lib/active_support/core_ext/string.rb
index 4cb3200875..b16b35144e 100644
--- a/activesupport/lib/active_support/core_ext/string.rb
+++ b/activesupport/lib/active_support/core_ext/string.rb
@@ -1,13 +1,13 @@
-require "active_support/core_ext/string/conversions"
-require "active_support/core_ext/string/filters"
-require "active_support/core_ext/string/multibyte"
-require "active_support/core_ext/string/starts_ends_with"
-require "active_support/core_ext/string/inflections"
-require "active_support/core_ext/string/access"
-require "active_support/core_ext/string/behavior"
-require "active_support/core_ext/string/output_safety"
-require "active_support/core_ext/string/exclude"
-require "active_support/core_ext/string/strip"
-require "active_support/core_ext/string/inquiry"
-require "active_support/core_ext/string/indent"
-require "active_support/core_ext/string/zones"
+require_relative "string/conversions"
+require_relative "string/filters"
+require_relative "string/multibyte"
+require_relative "string/starts_ends_with"
+require_relative "string/inflections"
+require_relative "string/access"
+require_relative "string/behavior"
+require_relative "string/output_safety"
+require_relative "string/exclude"
+require_relative "string/strip"
+require_relative "string/inquiry"
+require_relative "string/indent"
+require_relative "string/zones"
diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb
index 221b4969cc..383d93e2cd 100644
--- a/activesupport/lib/active_support/core_ext/string/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/string/conversions.rb
@@ -1,5 +1,5 @@
require "date"
-require "active_support/core_ext/time/calculations"
+require_relative "../time/calculations"
class String
# Converts a string to a Time value.
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index b27cfe53f4..70c732ca44 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -1,5 +1,5 @@
-require "active_support/inflector/methods"
-require "active_support/inflector/transliterate"
+require_relative "../../inflector/methods"
+require_relative "../../inflector/transliterate"
# String inflections define new methods on the String class to transform names for different purposes.
# For instance, you can figure out the name of a table from the name of a class.
diff --git a/activesupport/lib/active_support/core_ext/string/inquiry.rb b/activesupport/lib/active_support/core_ext/string/inquiry.rb
index c95d83beae..dfa78b2ff8 100644
--- a/activesupport/lib/active_support/core_ext/string/inquiry.rb
+++ b/activesupport/lib/active_support/core_ext/string/inquiry.rb
@@ -1,4 +1,4 @@
-require "active_support/string_inquirer"
+require_relative "../../string_inquirer"
class String
# Wraps the current string in the <tt>ActiveSupport::StringInquirer</tt> class,
diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb
index 1c73182259..56a1cede1a 100644
--- a/activesupport/lib/active_support/core_ext/string/multibyte.rb
+++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb
@@ -1,4 +1,4 @@
-require "active_support/multibyte"
+require_relative "../../multibyte"
class String
# == Multibyte proxy
diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb
index 94ce3f6a61..607870237b 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -1,6 +1,6 @@
require "erb"
-require "active_support/core_ext/kernel/singleton_class"
-require "active_support/multibyte/unicode"
+require_relative "../kernel/singleton_class"
+require_relative "../../multibyte/unicode"
class ERB
module Util
diff --git a/activesupport/lib/active_support/core_ext/string/zones.rb b/activesupport/lib/active_support/core_ext/string/zones.rb
index de5a28e4f7..d8d22a6799 100644
--- a/activesupport/lib/active_support/core_ext/string/zones.rb
+++ b/activesupport/lib/active_support/core_ext/string/zones.rb
@@ -1,5 +1,5 @@
-require "active_support/core_ext/string/conversions"
-require "active_support/core_ext/time/zones"
+require_relative "conversions"
+require_relative "../time/zones"
class String
# Converts String to a TimeWithZone in the current zone if Time.zone or Time.zone_default
diff --git a/activesupport/lib/active_support/core_ext/time.rb b/activesupport/lib/active_support/core_ext/time.rb
index b1ae4a45d9..19d4d91dac 100644
--- a/activesupport/lib/active_support/core_ext/time.rb
+++ b/activesupport/lib/active_support/core_ext/time.rb
@@ -1,5 +1,5 @@
-require "active_support/core_ext/time/acts_like"
-require "active_support/core_ext/time/calculations"
-require "active_support/core_ext/time/compatibility"
-require "active_support/core_ext/time/conversions"
-require "active_support/core_ext/time/zones"
+require_relative "time/acts_like"
+require_relative "time/calculations"
+require_relative "time/compatibility"
+require_relative "time/conversions"
+require_relative "time/zones"
diff --git a/activesupport/lib/active_support/core_ext/time/acts_like.rb b/activesupport/lib/active_support/core_ext/time/acts_like.rb
index cf4b2539c5..08756a2dae 100644
--- a/activesupport/lib/active_support/core_ext/time/acts_like.rb
+++ b/activesupport/lib/active_support/core_ext/time/acts_like.rb
@@ -1,4 +1,4 @@
-require "active_support/core_ext/object/acts_like"
+require_relative "../object/acts_like"
class Time
# Duck-types as a Time-like class. See Object#acts_like?.
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 7b7aeef25a..9ba6bf8fa1 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -1,9 +1,9 @@
-require "active_support/duration"
-require "active_support/core_ext/time/conversions"
-require "active_support/time_with_zone"
-require "active_support/core_ext/time/zones"
-require "active_support/core_ext/date_and_time/calculations"
-require "active_support/core_ext/date/calculations"
+require_relative "../../duration"
+require_relative "conversions"
+require_relative "../../time_with_zone"
+require_relative "zones"
+require_relative "../date_and_time/calculations"
+require_relative "../date/calculations"
class Time
include DateAndTime::Calculations
@@ -107,21 +107,22 @@ class Time
# to the +options+ parameter. The time options (<tt>:hour</tt>, <tt>:min</tt>,
# <tt>:sec</tt>, <tt>:usec</tt>, <tt>:nsec</tt>) reset cascadingly, so if only
# the hour is passed, then minute, sec, usec and nsec is set to 0. If the hour
- # and minute is passed, then sec, usec and nsec is set to 0. The +options+
- # parameter takes a hash with any of these keys: <tt>:year</tt>, <tt>:month</tt>,
- # <tt>:day</tt>, <tt>:hour</tt>, <tt>:min</tt>, <tt>:sec</tt>, <tt>:usec</tt>
- # <tt>:nsec</tt>. Pass either <tt>:usec</tt> or <tt>:nsec</tt>, not both.
+ # and minute is passed, then sec, usec and nsec is set to 0. The +options+ parameter
+ # takes a hash with any of these keys: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>,
+ # <tt>:hour</tt>, <tt>:min</tt>, <tt>:sec</tt>, <tt>:usec</tt>, <tt>:nsec</tt>,
+ # <tt>:offset</tt>. Pass either <tt>:usec</tt> or <tt>:nsec</tt>, not both.
#
# Time.new(2012, 8, 29, 22, 35, 0).change(day: 1) # => Time.new(2012, 8, 1, 22, 35, 0)
# Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => Time.new(1981, 8, 1, 22, 35, 0)
# Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, hour: 0) # => Time.new(1981, 8, 29, 0, 0, 0)
def change(options)
- new_year = options.fetch(:year, year)
- new_month = options.fetch(:month, month)
- new_day = options.fetch(:day, day)
- new_hour = options.fetch(:hour, hour)
- new_min = options.fetch(:min, options[:hour] ? 0 : min)
- new_sec = options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec)
+ new_year = options.fetch(:year, year)
+ new_month = options.fetch(:month, month)
+ new_day = options.fetch(:day, day)
+ new_hour = options.fetch(:hour, hour)
+ new_min = options.fetch(:min, options[:hour] ? 0 : min)
+ new_sec = options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec)
+ new_offset = options.fetch(:offset, nil)
if new_nsec = options[:nsec]
raise ArgumentError, "Can't change both :nsec and :usec at the same time: #{options.inspect}" if options[:usec]
@@ -130,13 +131,18 @@ class Time
new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000))
end
- if utc?
- ::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec)
+ raise ArgumentError, "argument out of range" if new_usec >= 1000000
+
+ new_sec += Rational(new_usec, 1000000)
+
+ if new_offset
+ ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, new_offset)
+ elsif utc?
+ ::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec)
elsif zone
- ::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec)
+ ::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec)
else
- raise ArgumentError, "argument out of range" if new_usec >= 1000000
- ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec + (new_usec.to_r / 1000000), utc_offset)
+ ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, utc_offset)
end
end
diff --git a/activesupport/lib/active_support/core_ext/time/compatibility.rb b/activesupport/lib/active_support/core_ext/time/compatibility.rb
index 45e86b77ce..a554e71b74 100644
--- a/activesupport/lib/active_support/core_ext/time/compatibility.rb
+++ b/activesupport/lib/active_support/core_ext/time/compatibility.rb
@@ -1,5 +1,5 @@
-require "active_support/core_ext/date_and_time/compatibility"
-require "active_support/core_ext/module/remove_method"
+require_relative "../date_and_time/compatibility"
+require_relative "../module/remove_method"
class Time
include DateAndTime::Compatibility
diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb
index 595bda6b4f..0dbce99ccb 100644
--- a/activesupport/lib/active_support/core_ext/time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/time/conversions.rb
@@ -1,5 +1,5 @@
-require "active_support/inflector/methods"
-require "active_support/values/time_zone"
+require_relative "../../inflector/methods"
+require_relative "../../values/time_zone"
class Time
DATE_FORMATS = {
diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb
index 87b5ad903a..ba8b5b73f8 100644
--- a/activesupport/lib/active_support/core_ext/time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/time/zones.rb
@@ -1,6 +1,6 @@
-require "active_support/time_with_zone"
-require "active_support/core_ext/time/acts_like"
-require "active_support/core_ext/date_and_time/zones"
+require_relative "../../time_with_zone"
+require_relative "acts_like"
+require_relative "../date_and_time/zones"
class Time
include DateAndTime::Zones
diff --git a/activesupport/lib/active_support/current_attributes.rb b/activesupport/lib/active_support/current_attributes.rb
new file mode 100644
index 0000000000..872b0663c7
--- /dev/null
+++ b/activesupport/lib/active_support/current_attributes.rb
@@ -0,0 +1,193 @@
+module ActiveSupport
+ # Abstract super class that provides a thread-isolated attributes singleton, which resets automatically
+ # before and after each request. This allows you to keep all the per-request attributes easily
+ # available to the whole system.
+ #
+ # The following full app-like example demonstrates how to use a Current class to
+ # facilitate easy access to the global, per-request attributes without passing them deeply
+ # around everywhere:
+ #
+ # # app/models/current.rb
+ # class Current < ActiveSupport::CurrentAttributes
+ # attribute :account, :user
+ # attribute :request_id, :user_agent, :ip_address
+ #
+ # resets { Time.zone = nil }
+ #
+ # def user=(user)
+ # super
+ # self.account = user.account
+ # Time.zone = user.time_zone
+ # end
+ # end
+ #
+ # # app/controllers/concerns/authentication.rb
+ # module Authentication
+ # extend ActiveSupport::Concern
+ #
+ # included do
+ # before_action :authenticate
+ # end
+ #
+ # private
+ # def authenticate
+ # if authenticated_user = User.find_by(id: cookies.signed[:user_id])
+ # Current.user = authenticated_user
+ # else
+ # redirect_to new_session_url
+ # end
+ # end
+ # end
+ #
+ # # app/controllers/concerns/set_current_request_details.rb
+ # module SetCurrentRequestDetails
+ # extend ActiveSupport::Concern
+ #
+ # included do
+ # before_action do
+ # Current.request_id = request.uuid
+ # Current.user_agent = request.user_agent
+ # Current.ip_address = request.ip
+ # end
+ # end
+ # end
+ #
+ # class ApplicationController < ActionController::Base
+ # include Authentication
+ # include SetCurrentRequestDetails
+ # end
+ #
+ # class MessagesController < ApplicationController
+ # def create
+ # Current.account.messages.create(message_params)
+ # end
+ # end
+ #
+ # class Message < ApplicationRecord
+ # belongs_to :creator, default: -> { Current.user }
+ # after_create { |message| Event.create(record: message) }
+ # end
+ #
+ # class Event < ApplicationRecord
+ # before_create do
+ # self.request_id = Current.request_id
+ # self.user_agent = Current.user_agent
+ # self.ip_address = Current.ip_address
+ # end
+ # end
+ #
+ # A word of caution: It's easy to overdo a global singleton like Current and tangle your model as a result.
+ # Current should only be used for a few, top-level globals, like account, user, and request details.
+ # The attributes stuck in Current should be used by more or less all actions on all requests. If you start
+ # sticking controller-specific attributes in there, you're going to create a mess.
+ class CurrentAttributes
+ include ActiveSupport::Callbacks
+ define_callbacks :reset
+
+ class << self
+ # Returns singleton instance for this class in this thread. If none exists, one is created.
+ def instance
+ current_instances[name] ||= new
+ end
+
+ # Declares one or more attributes that will be given both class and instance accessor methods.
+ def attribute(*names)
+ generated_attribute_methods.module_eval do
+ names.each do |name|
+ define_method(name) do
+ attributes[name.to_sym]
+ end
+
+ define_method("#{name}=") do |attribute|
+ attributes[name.to_sym] = attribute
+ end
+ end
+ end
+
+ names.each do |name|
+ define_singleton_method(name) do
+ instance.public_send(name)
+ end
+
+ define_singleton_method("#{name}=") do |attribute|
+ instance.public_send("#{name}=", attribute)
+ end
+ end
+ end
+
+ # Calls this block after #reset is called on the instance. Used for resetting external collaborators, like Time.zone.
+ def resets(&block)
+ set_callback :reset, :after, &block
+ end
+
+ delegate :set, :reset, to: :instance
+
+ def reset_all # :nodoc:
+ current_instances.each_value(&:reset)
+ end
+
+ def clear_all # :nodoc:
+ reset_all
+ current_instances.clear
+ end
+
+ private
+ def generated_attribute_methods
+ @generated_attribute_methods ||= Module.new.tap { |mod| include mod }
+ end
+
+ def current_instances
+ Thread.current[:current_attributes_instances] ||= {}
+ end
+
+ def method_missing(name, *args, &block)
+ # Caches the method definition as a singleton method of the receiver.
+ #
+ # By letting #delegate handle it, we avoid an enclosure that'll capture args.
+ singleton_class.delegate name, to: :instance
+
+ send(name, *args, &block)
+ end
+ end
+
+ attr_accessor :attributes
+
+ def initialize
+ @attributes = {}
+ end
+
+ # Expose one or more attributes within a block. Old values are returned after the block concludes.
+ # Example demonstrating the common use of needing to set Current attributes outside the request-cycle:
+ #
+ # class Chat::PublicationJob < ApplicationJob
+ # def perform(attributes, room_number, creator)
+ # Current.set(person: creator) do
+ # Chat::Publisher.publish(attributes: attributes, room_number: room_number)
+ # end
+ # end
+ # end
+ def set(set_attributes)
+ old_attributes = compute_attributes(set_attributes.keys)
+ assign_attributes(set_attributes)
+ yield
+ ensure
+ assign_attributes(old_attributes)
+ end
+
+ # Reset all attributes. Should be called before and after actions, when used as a per-request singleton.
+ def reset
+ run_callbacks :reset do
+ self.attributes = {}
+ end
+ end
+
+ private
+ def assign_attributes(new_attributes)
+ new_attributes.each { |key, value| public_send("#{key}=", value) }
+ end
+
+ def compute_attributes(keys)
+ keys.collect { |key| [ key, public_send(key) ] }.to_h
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index e125b657f2..05b9b5b211 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -2,24 +2,23 @@ require "set"
require "thread"
require "concurrent/map"
require "pathname"
-require "active_support/core_ext/module/aliasing"
-require "active_support/core_ext/module/attribute_accessors"
-require "active_support/core_ext/module/introspection"
-require "active_support/core_ext/module/anonymous"
-require "active_support/core_ext/object/blank"
-require "active_support/core_ext/kernel/reporting"
-require "active_support/core_ext/load_error"
-require "active_support/core_ext/name_error"
-require "active_support/core_ext/string/starts_ends_with"
-require "active_support/dependencies/interlock"
-require "active_support/inflector"
+require_relative "core_ext/module/aliasing"
+require_relative "core_ext/module/attribute_accessors"
+require_relative "core_ext/module/introspection"
+require_relative "core_ext/module/anonymous"
+require_relative "core_ext/object/blank"
+require_relative "core_ext/kernel/reporting"
+require_relative "core_ext/load_error"
+require_relative "core_ext/name_error"
+require_relative "core_ext/string/starts_ends_with"
+require_relative "dependencies/interlock"
+require_relative "inflector"
module ActiveSupport #:nodoc:
module Dependencies #:nodoc:
extend self
- mattr_accessor :interlock
- self.interlock = Interlock.new
+ mattr_accessor :interlock, default: Interlock.new
# :doc:
@@ -46,46 +45,37 @@ module ActiveSupport #:nodoc:
# :nodoc:
# Should we turn on Ruby warnings on the first load of dependent files?
- mattr_accessor :warnings_on_first_load
- self.warnings_on_first_load = false
+ mattr_accessor :warnings_on_first_load, default: false
# All files ever loaded.
- mattr_accessor :history
- self.history = Set.new
+ mattr_accessor :history, default: Set.new
# All files currently loaded.
- mattr_accessor :loaded
- self.loaded = Set.new
+ mattr_accessor :loaded, default: Set.new
# Stack of files being loaded.
- mattr_accessor :loading
- self.loading = []
+ mattr_accessor :loading, default: []
# Should we load files or require them?
- mattr_accessor :mechanism
- self.mechanism = ENV["NO_RELOAD"] ? :require : :load
+ mattr_accessor :mechanism, default: ENV["NO_RELOAD"] ? :require : :load
# The set of directories from which we may automatically load files. Files
# under these directories will be reloaded on each request in development mode,
# unless the directory also appears in autoload_once_paths.
- mattr_accessor :autoload_paths
- self.autoload_paths = []
+ mattr_accessor :autoload_paths, default: []
# The set of directories from which automatically loaded constants are loaded
# only once. All directories in this set must also be present in +autoload_paths+.
- mattr_accessor :autoload_once_paths
- self.autoload_once_paths = []
+ mattr_accessor :autoload_once_paths, default: []
# An array of qualified constant names that have been loaded. Adding a name
# to this array will cause it to be unloaded the next time Dependencies are
# cleared.
- mattr_accessor :autoloaded_constants
- self.autoloaded_constants = []
+ mattr_accessor :autoloaded_constants, default: []
# An array of constant names that need to be unloaded on every request. Used
# to allow arbitrary constants to be marked for unloading.
- mattr_accessor :explicitly_unloadable_constants
- self.explicitly_unloadable_constants = []
+ mattr_accessor :explicitly_unloadable_constants, default: []
# The WatchStack keeps a stack of the modules being watched as files are
# loaded. If a file in the process of being loaded (parent.rb) triggers the
@@ -175,8 +165,7 @@ module ActiveSupport #:nodoc:
end
# An internal stack used to record which constants are loaded by any block.
- mattr_accessor :constant_watch_stack
- self.constant_watch_stack = WatchStack.new
+ mattr_accessor :constant_watch_stack, default: WatchStack.new
# Module includes this module.
module ModuleConstMissing #:nodoc:
diff --git a/activesupport/lib/active_support/dependencies/autoload.rb b/activesupport/lib/active_support/dependencies/autoload.rb
index 13036d521d..9e4d3b783b 100644
--- a/activesupport/lib/active_support/dependencies/autoload.rb
+++ b/activesupport/lib/active_support/dependencies/autoload.rb
@@ -1,4 +1,4 @@
-require "active_support/inflector/methods"
+require_relative "../inflector/methods"
module ActiveSupport
# Autoload and eager load conveniences for your library.
diff --git a/activesupport/lib/active_support/dependencies/interlock.rb b/activesupport/lib/active_support/dependencies/interlock.rb
index e4e18439c5..b88cf08dd2 100644
--- a/activesupport/lib/active_support/dependencies/interlock.rb
+++ b/activesupport/lib/active_support/dependencies/interlock.rb
@@ -1,4 +1,4 @@
-require "active_support/concurrency/share_lock"
+require_relative "../concurrency/share_lock"
module ActiveSupport #:nodoc:
module Dependencies #:nodoc:
diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index 72c74e966a..eaaf198ccb 100644
--- a/activesupport/lib/active_support/deprecation.rb
+++ b/activesupport/lib/active_support/deprecation.rb
@@ -12,13 +12,13 @@ module ActiveSupport
# a circular require warning for active_support/deprecation.rb.
#
# So, we define the constant first, and load dependencies later.
- require "active_support/deprecation/instance_delegator"
- require "active_support/deprecation/behaviors"
- require "active_support/deprecation/reporting"
- require "active_support/deprecation/constant_accessor"
- require "active_support/deprecation/method_wrappers"
- require "active_support/deprecation/proxy_wrappers"
- require "active_support/core_ext/module/deprecation"
+ require_relative "deprecation/instance_delegator"
+ require_relative "deprecation/behaviors"
+ require_relative "deprecation/reporting"
+ require_relative "deprecation/constant_accessor"
+ require_relative "deprecation/method_wrappers"
+ require_relative "deprecation/proxy_wrappers"
+ require_relative "core_ext/module/deprecation"
include Singleton
include InstanceDelegator
@@ -30,10 +30,10 @@ module ActiveSupport
attr_accessor :deprecation_horizon
# It accepts two parameters on initialization. The first is a version of library
- # and the second is a library name
+ # and the second is a library name.
#
# ActiveSupport::Deprecation.new('2.0', 'MyLibrary')
- def initialize(deprecation_horizon = "5.3", gem_name = "Rails")
+ def initialize(deprecation_horizon = "6.0", gem_name = "Rails")
self.gem_name = gem_name
self.deprecation_horizon = deprecation_horizon
# By default, warnings are not silenced and debugging is off.
diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb
index 1d1354c23e..409f4cf6a6 100644
--- a/activesupport/lib/active_support/deprecation/behaviors.rb
+++ b/activesupport/lib/active_support/deprecation/behaviors.rb
@@ -1,4 +1,4 @@
-require "active_support/notifications"
+require_relative "../notifications"
module ActiveSupport
# Raised when <tt>ActiveSupport::Deprecation::Behavior#behavior</tt> is set with <tt>:raise</tt>.
@@ -9,35 +9,39 @@ module ActiveSupport
class Deprecation
# Default warning behaviors per Rails.env.
DEFAULT_BEHAVIORS = {
- raise: ->(message, callstack) {
+ raise: ->(message, callstack, deprecation_horizon, gem_name) {
e = DeprecationException.new(message)
e.set_backtrace(callstack.map(&:to_s))
raise e
},
- stderr: ->(message, callstack) {
+ stderr: ->(message, callstack, deprecation_horizon, gem_name) {
$stderr.puts(message)
$stderr.puts callstack.join("\n ") if debug
},
- log: ->(message, callstack) {
+ log: ->(message, callstack, deprecation_horizon, gem_name) {
logger =
if defined?(Rails.logger) && Rails.logger
Rails.logger
else
- require "active_support/logger"
+ require_relative "../logger"
ActiveSupport::Logger.new($stderr)
end
logger.warn message
logger.debug callstack.join("\n ") if debug
},
- notify: ->(message, callstack) {
- ActiveSupport::Notifications.instrument("deprecation.rails",
- message: message, callstack: callstack)
+ notify: ->(message, callstack, deprecation_horizon, gem_name) {
+ notification_name = "deprecation.#{gem_name.underscore.tr('/', '_')}"
+ ActiveSupport::Notifications.instrument(notification_name,
+ message: message,
+ callstack: callstack,
+ gem_name: gem_name,
+ deprecation_horizon: deprecation_horizon)
},
- silence: ->(message, callstack) {},
+ silence: ->(message, callstack, deprecation_horizon, gem_name) {},
}
# Behavior module allows to determine how to display deprecation messages.
@@ -83,8 +87,17 @@ module ActiveSupport
# # custom stuff
# }
def behavior=(behavior)
- @behavior = Array(behavior).map { |b| DEFAULT_BEHAVIORS[b] || b }
+ @behavior = Array(behavior).map { |b| DEFAULT_BEHAVIORS[b] || arity_coerce(b) }
end
+
+ private
+ def arity_coerce(behavior)
+ if behavior.arity == 4 || behavior.arity == -1
+ behavior
+ else
+ -> message, callstack, _, _ { behavior.call(message, callstack) }
+ end
+ end
end
end
end
diff --git a/activesupport/lib/active_support/deprecation/constant_accessor.rb b/activesupport/lib/active_support/deprecation/constant_accessor.rb
index 2b19de365f..895b23e8ef 100644
--- a/activesupport/lib/active_support/deprecation/constant_accessor.rb
+++ b/activesupport/lib/active_support/deprecation/constant_accessor.rb
@@ -1,4 +1,4 @@
-require "active_support/inflector/methods"
+require_relative "../inflector/methods"
module ActiveSupport
class Deprecation
diff --git a/activesupport/lib/active_support/deprecation/instance_delegator.rb b/activesupport/lib/active_support/deprecation/instance_delegator.rb
index 6d390f3b37..23eac0e5d3 100644
--- a/activesupport/lib/active_support/deprecation/instance_delegator.rb
+++ b/activesupport/lib/active_support/deprecation/instance_delegator.rb
@@ -1,5 +1,5 @@
-require "active_support/core_ext/kernel/singleton_class"
-require "active_support/core_ext/module/delegation"
+require_relative "../core_ext/kernel/singleton_class"
+require_relative "../core_ext/module/delegation"
module ActiveSupport
class Deprecation
diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb
index 930d71e8d2..ed41e9aaa4 100644
--- a/activesupport/lib/active_support/deprecation/method_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb
@@ -1,5 +1,5 @@
-require "active_support/core_ext/module/aliasing"
-require "active_support/core_ext/array/extract_options"
+require_relative "../core_ext/module/aliasing"
+require_relative "../core_ext/array/extract_options"
module ActiveSupport
class Deprecation
diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
index ce39e9a232..64aaef3e43 100644
--- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
@@ -1,5 +1,5 @@
-require "active_support/inflector/methods"
-require "active_support/core_ext/regexp"
+require_relative "../inflector/methods"
+require_relative "../core_ext/regexp"
module ActiveSupport
class Deprecation
diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb
index 58c5c50e30..140bdccbb3 100644
--- a/activesupport/lib/active_support/deprecation/reporting.rb
+++ b/activesupport/lib/active_support/deprecation/reporting.rb
@@ -18,7 +18,7 @@ module ActiveSupport
callstack ||= caller_locations(2)
deprecation_message(callstack, message).tap do |m|
- behavior.each { |b| b.call(m, callstack) }
+ behavior.each { |b| b.call(m, callstack, deprecation_horizon, gem_name) }
end
end
@@ -102,7 +102,7 @@ module ActiveSupport
end
end
- RAILS_GEM_ROOT = File.expand_path("../../../../..", __FILE__) + "/"
+ RAILS_GEM_ROOT = File.expand_path("../../../..", __dir__)
def ignored_callstack(path)
path.start_with?(RAILS_GEM_ROOT) || path.start_with?(RbConfig::CONFIG["rubylibdir"])
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 99080e34a1..0ad591e683 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -1,8 +1,8 @@
-require "active_support/core_ext/array/conversions"
-require "active_support/core_ext/module/delegation"
-require "active_support/core_ext/object/acts_like"
-require "active_support/core_ext/string/filters"
-require "active_support/deprecation"
+require_relative "core_ext/array/conversions"
+require_relative "core_ext/module/delegation"
+require_relative "core_ext/object/acts_like"
+require_relative "core_ext/string/filters"
+require_relative "deprecation"
module ActiveSupport
# Provides accurate date and time measurements using Date#advance and
@@ -37,27 +37,56 @@ module ActiveSupport
end
def +(other)
- calculate(:+, other)
+ if Duration === other
+ seconds = value + other.parts[:seconds]
+ new_parts = other.parts.merge(seconds: seconds)
+ new_value = value + other.value
+
+ Duration.new(new_value, new_parts)
+ else
+ calculate(:+, other)
+ end
end
def -(other)
- calculate(:-, other)
+ if Duration === other
+ seconds = value - other.parts[:seconds]
+ new_parts = other.parts.map { |part, other_value| [part, -other_value] }.to_h
+ new_parts = new_parts.merge(seconds: seconds)
+ new_value = value - other.value
+
+ Duration.new(new_value, new_parts)
+ else
+ calculate(:-, other)
+ end
end
def *(other)
- calculate(:*, other)
+ if Duration === other
+ new_parts = other.parts.map { |part, other_value| [part, value * other_value] }.to_h
+ new_value = value * other.value
+
+ Duration.new(new_value, new_parts)
+ else
+ calculate(:*, other)
+ end
end
def /(other)
- calculate(:/, other)
+ if Duration === other
+ new_parts = other.parts.map { |part, other_value| [part, value / other_value] }.to_h
+ new_value = new_parts.inject(0) { |total, (part, value)| total + value * Duration::PARTS_IN_SECONDS[part] }
+
+ Duration.new(new_value, new_parts)
+ else
+ calculate(:/, other)
+ end
end
private
def calculate(op, other)
if Scalar === other
Scalar.new(value.public_send(op, other.value))
- elsif Duration === other
- Duration.seconds(value).public_send(op, other)
elsif Numeric === other
Scalar.new(value.public_send(op, other))
else
@@ -305,10 +334,6 @@ module ActiveSupport
to_i
end
- def respond_to_missing?(method, include_private = false) #:nodoc:
- @value.respond_to?(method, include_private)
- 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)
@@ -335,8 +360,12 @@ module ActiveSupport
end
end
+ def respond_to_missing?(method, _)
+ value.respond_to?(method)
+ end
+
def method_missing(method, *args, &block)
- value.send(method, *args, &block)
+ value.public_send(method, *args, &block)
end
def raise_type_error(other)
diff --git a/activesupport/lib/active_support/duration/iso8601_parser.rb b/activesupport/lib/active_support/duration/iso8601_parser.rb
index e96cb8e883..78a00b5e4f 100644
--- a/activesupport/lib/active_support/duration/iso8601_parser.rb
+++ b/activesupport/lib/active_support/duration/iso8601_parser.rb
@@ -1,5 +1,5 @@
require "strscan"
-require "active_support/core_ext/regexp"
+require_relative "../core_ext/regexp"
module ActiveSupport
class Duration
diff --git a/activesupport/lib/active_support/duration/iso8601_serializer.rb b/activesupport/lib/active_support/duration/iso8601_serializer.rb
index e5d458b3ab..83c3a46692 100644
--- a/activesupport/lib/active_support/duration/iso8601_serializer.rb
+++ b/activesupport/lib/active_support/duration/iso8601_serializer.rb
@@ -1,5 +1,5 @@
-require "active_support/core_ext/object/blank"
-require "active_support/core_ext/hash/transform_values"
+require_relative "../core_ext/object/blank"
+require_relative "../core_ext/hash/transform_values"
module ActiveSupport
class Duration
@@ -15,12 +15,12 @@ module ActiveSupport
parts, sign = normalize
return "PT0S".freeze if parts.empty?
- output = "P"
+ output = "P".dup
output << "#{parts[:years]}Y" if parts.key?(:years)
output << "#{parts[:months]}M" if parts.key?(:months)
output << "#{parts[:weeks]}W" if parts.key?(:weeks)
output << "#{parts[:days]}D" if parts.key?(:days)
- time = ""
+ time = "".dup
time << "#{parts[:hours]}H" if parts.key?(:hours)
time << "#{parts[:minutes]}M" if parts.key?(:minutes)
if parts.key?(:seconds)
diff --git a/activesupport/lib/active_support/execution_wrapper.rb b/activesupport/lib/active_support/execution_wrapper.rb
index ca88e7876b..3513e9023c 100644
--- a/activesupport/lib/active_support/execution_wrapper.rb
+++ b/activesupport/lib/active_support/execution_wrapper.rb
@@ -1,4 +1,4 @@
-require "active_support/callbacks"
+require_relative "callbacks"
module ActiveSupport
class ExecutionWrapper
diff --git a/activesupport/lib/active_support/executor.rb b/activesupport/lib/active_support/executor.rb
index a6400cae0a..4a618d9fa2 100644
--- a/activesupport/lib/active_support/executor.rb
+++ b/activesupport/lib/active_support/executor.rb
@@ -1,4 +1,4 @@
-require "active_support/execution_wrapper"
+require_relative "execution_wrapper"
module ActiveSupport
class Executor < ExecutionWrapper
diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb
index 2b5e3c1350..b9d8740569 100644
--- a/activesupport/lib/active_support/file_update_checker.rb
+++ b/activesupport/lib/active_support/file_update_checker.rb
@@ -1,4 +1,4 @@
-require "active_support/core_ext/time/calculations"
+require_relative "core_ext/time/calculations"
module ActiveSupport
# FileUpdateChecker specifies the API used by Rails to watch files
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 1e1d6d3aac..6c98960292 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -1,5 +1,5 @@
-require "active_support/core_ext/hash/keys"
-require "active_support/core_ext/hash/reverse_merge"
+require_relative "core_ext/hash/keys"
+require_relative "core_ext/hash/reverse_merge"
module ActiveSupport
# Implements a hash where keys <tt>:foo</tt> and <tt>"foo"</tt> are considered
@@ -195,6 +195,19 @@ module ActiveSupport
indices.collect { |key| self[convert_key(key)] }
end
+ # Returns an array of the values at the specified indices, but also
+ # raises an exception when one of the keys can't be found.
+ #
+ # hash = ActiveSupport::HashWithIndifferentAccess.new
+ # hash[:a] = 'x'
+ # hash[:b] = 'y'
+ # hash.fetch_values('a', 'b') # => ["x", "y"]
+ # hash.fetch_values('a', 'c') { |key| 'z' } # => ["x", "z"]
+ # hash.fetch_values('a', 'c') # => KeyError: key not found: "c"
+ def fetch_values(*indices, &block)
+ indices.collect { |key| fetch(key, &block) }
+ end if Hash.method_defined?(:fetch_values)
+
# Returns a shallow copy of the hash.
#
# hash = ActiveSupport::HashWithIndifferentAccess.new({ a: { b: 'b' } })
diff --git a/activesupport/lib/active_support/i18n.rb b/activesupport/lib/active_support/i18n.rb
index f0408f429c..3f47da3b38 100644
--- a/activesupport/lib/active_support/i18n.rb
+++ b/activesupport/lib/active_support/i18n.rb
@@ -1,13 +1,13 @@
-require "active_support/core_ext/hash/deep_merge"
-require "active_support/core_ext/hash/except"
-require "active_support/core_ext/hash/slice"
+require_relative "core_ext/hash/deep_merge"
+require_relative "core_ext/hash/except"
+require_relative "core_ext/hash/slice"
begin
require "i18n"
rescue LoadError => e
$stderr.puts "The i18n gem is not available. Please add it to your Gemfile and run bundle install"
raise e
end
-require "active_support/lazy_load_hooks"
+require_relative "lazy_load_hooks"
ActiveSupport.run_load_hooks(:i18n)
-I18n.load_path << "#{File.dirname(__FILE__)}/locale/en.yml"
+I18n.load_path << File.expand_path("locale/en.yml", __dir__)
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index b749913ee9..6809a650cf 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -1,6 +1,6 @@
require "active_support"
-require "active_support/file_update_checker"
-require "active_support/core_ext/array/wrap"
+require_relative "file_update_checker"
+require_relative "core_ext/array/wrap"
# :enddoc:
@@ -42,7 +42,7 @@ module I18n
case setting
when :railties_load_path
reloadable_paths = value
- app.config.i18n.load_path.unshift(*value.map(&:existent).flatten)
+ app.config.i18n.load_path.unshift(*value.flat_map(&:existent))
when :load_path
I18n.load_path += value
else
@@ -58,7 +58,7 @@ module I18n
directories = watched_dirs_with_extensions(reloadable_paths)
reloader = app.config.file_watcher.new(I18n.load_path.dup, directories) do
I18n.load_path.keep_if { |p| File.exist?(p) }
- I18n.load_path |= reloadable_paths.map(&:existent).flatten
+ I18n.load_path |= reloadable_paths.flat_map(&:existent)
I18n.reload!
end
@@ -66,10 +66,6 @@ module I18n
app.reloaders << reloader
app.reloader.to_run do
reloader.execute_if_updated { require_unload_lock! }
- # TODO: remove the following line as soon as the return value of
- # callbacks is ignored, that is, returning `false` does not
- # display a deprecation warning or halts the callback chain.
- true
end
reloader.execute
diff --git a/activesupport/lib/active_support/inflections.rb b/activesupport/lib/active_support/inflections.rb
index afa7d1f325..1b94ce9b1b 100644
--- a/activesupport/lib/active_support/inflections.rb
+++ b/activesupport/lib/active_support/inflections.rb
@@ -1,4 +1,4 @@
-require "active_support/inflector/inflections"
+require_relative "inflector/inflections"
#--
# Defines the standard inflection rules. These are the starting point for
diff --git a/activesupport/lib/active_support/inflector.rb b/activesupport/lib/active_support/inflector.rb
index 48631b16a8..a4bd8aaeb4 100644
--- a/activesupport/lib/active_support/inflector.rb
+++ b/activesupport/lib/active_support/inflector.rb
@@ -1,7 +1,7 @@
# in case active_support/inflector is required without the rest of active_support
-require "active_support/inflector/inflections"
-require "active_support/inflector/transliterate"
-require "active_support/inflector/methods"
+require_relative "inflector/inflections"
+require_relative "inflector/transliterate"
+require_relative "inflector/methods"
-require "active_support/inflections"
-require "active_support/core_ext/string/inflections"
+require_relative "inflections"
+require_relative "core_ext/string/inflections"
diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb
index c47a2e34e1..2cb7828968 100644
--- a/activesupport/lib/active_support/inflector/inflections.rb
+++ b/activesupport/lib/active_support/inflector/inflections.rb
@@ -1,7 +1,7 @@
require "concurrent/map"
-require "active_support/core_ext/array/prepend_and_append"
-require "active_support/core_ext/regexp"
-require "active_support/i18n"
+require_relative "../core_ext/array/prepend_and_append"
+require_relative "../core_ext/regexp"
+require_relative "../i18n"
module ActiveSupport
module Inflector
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 1b089a7538..ba85d3bb33 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -1,5 +1,5 @@
-require "active_support/inflections"
-require "active_support/core_ext/regexp"
+require_relative "../inflections"
+require_relative "../core_ext/regexp"
module ActiveSupport
# The Inflector transforms words from singular to plural, class names to table
@@ -28,7 +28,7 @@ module ActiveSupport
# pluralize('CamelOctopus') # => "CamelOctopi"
# pluralize('ley', :es) # => "leyes"
def pluralize(word, locale = :en)
- apply_inflections(word, inflections(locale).plurals)
+ apply_inflections(word, inflections(locale).plurals, locale)
end
# The reverse of #pluralize, returns the singular form of a word in a
@@ -45,7 +45,7 @@ module ActiveSupport
# singularize('CamelOctopi') # => "CamelOctopus"
# singularize('leyes', :es) # => "ley"
def singularize(word, locale = :en)
- apply_inflections(word, inflections(locale).singulars)
+ apply_inflections(word, inflections(locale).singulars, locale)
end
# Converts strings to UpperCamelCase.
@@ -387,12 +387,15 @@ module ActiveSupport
# Applies inflection rules for +singularize+ and +pluralize+.
#
- # apply_inflections('post', inflections.plurals) # => "posts"
- # apply_inflections('posts', inflections.singulars) # => "post"
- def apply_inflections(word, rules)
+ # If passed an optional +locale+ parameter, the uncountables will be
+ # found for that locale.
+ #
+ # apply_inflections('post', inflections.plurals, :en) # => "posts"
+ # apply_inflections('posts', inflections.singulars, :en) # => "post"
+ def apply_inflections(word, rules, locale = :en)
result = word.to_s.dup
- if word.empty? || inflections.uncountables.uncountable?(result)
+ if word.empty? || inflections(locale).uncountables.uncountable?(result)
result
else
rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb
index de6dd2720b..0775ee51bc 100644
--- a/activesupport/lib/active_support/inflector/transliterate.rb
+++ b/activesupport/lib/active_support/inflector/transliterate.rb
@@ -1,5 +1,5 @@
-require "active_support/core_ext/string/multibyte"
-require "active_support/i18n"
+require_relative "../core_ext/string/multibyte"
+require_relative "../i18n"
module ActiveSupport
module Inflector
diff --git a/activesupport/lib/active_support/json.rb b/activesupport/lib/active_support/json.rb
index da938d1555..e0a6ab3768 100644
--- a/activesupport/lib/active_support/json.rb
+++ b/activesupport/lib/active_support/json.rb
@@ -1,2 +1,2 @@
-require "active_support/json/decoding"
-require "active_support/json/encoding"
+require_relative "json/decoding"
+require_relative "json/encoding"
diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb
index f487fa0c65..c60ebb2795 100644
--- a/activesupport/lib/active_support/json/decoding.rb
+++ b/activesupport/lib/active_support/json/decoding.rb
@@ -1,5 +1,5 @@
-require "active_support/core_ext/module/attribute_accessors"
-require "active_support/core_ext/module/delegation"
+require_relative "../core_ext/module/attribute_accessors"
+require_relative "../core_ext/module/delegation"
require "json"
module ActiveSupport
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index defaf3f395..e7679e4630 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -1,5 +1,5 @@
-require "active_support/core_ext/object/json"
-require "active_support/core_ext/module/delegation"
+require_relative "../core_ext/object/json"
+require_relative "../core_ext/module/delegation"
module ActiveSupport
class << self
diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb
index e2c4f33565..2b494e6d6c 100644
--- a/activesupport/lib/active_support/log_subscriber.rb
+++ b/activesupport/lib/active_support/log_subscriber.rb
@@ -1,6 +1,6 @@
-require "active_support/core_ext/module/attribute_accessors"
-require "active_support/core_ext/class/attribute"
-require "active_support/subscriber"
+require_relative "core_ext/module/attribute_accessors"
+require_relative "core_ext/class/attribute"
+require_relative "subscriber"
module ActiveSupport
# ActiveSupport::LogSubscriber is an object set to consume
@@ -49,8 +49,7 @@ module ActiveSupport
CYAN = "\e[36m"
WHITE = "\e[37m"
- mattr_accessor :colorize_logging
- self.colorize_logging = true
+ mattr_accessor :colorize_logging, default: true
class << self
def logger
@@ -81,8 +80,10 @@ module ActiveSupport
def finish(name, id, payload)
super if logger
- rescue Exception => e
- logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
+ rescue => e
+ if logger
+ logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
+ end
end
private
diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb
index 953ee77c2a..469e6c6bb4 100644
--- a/activesupport/lib/active_support/log_subscriber/test_helper.rb
+++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb
@@ -1,6 +1,6 @@
-require "active_support/log_subscriber"
-require "active_support/logger"
-require "active_support/notifications"
+require_relative "../log_subscriber"
+require_relative "../logger"
+require_relative "../notifications"
module ActiveSupport
class LogSubscriber
diff --git a/activesupport/lib/active_support/logger.rb b/activesupport/lib/active_support/logger.rb
index ea09d7d2df..677ec8d5bc 100644
--- a/activesupport/lib/active_support/logger.rb
+++ b/activesupport/lib/active_support/logger.rb
@@ -1,5 +1,5 @@
-require "active_support/logger_silence"
-require "active_support/logger_thread_safe_level"
+require_relative "logger_silence"
+require_relative "logger_thread_safe_level"
require "logger"
module ActiveSupport
diff --git a/activesupport/lib/active_support/logger_silence.rb b/activesupport/lib/active_support/logger_silence.rb
index 632994cf50..7f85f54542 100644
--- a/activesupport/lib/active_support/logger_silence.rb
+++ b/activesupport/lib/active_support/logger_silence.rb
@@ -1,13 +1,12 @@
-require "active_support/concern"
-require "active_support/core_ext/module/attribute_accessors"
+require_relative "concern"
+require_relative "core_ext/module/attribute_accessors"
require "concurrent"
module LoggerSilence
extend ActiveSupport::Concern
included do
- cattr_accessor :silencer
- self.silencer = true
+ cattr_accessor :silencer, default: true
end
# Silences the logger for the duration of the block.
diff --git a/activesupport/lib/active_support/logger_thread_safe_level.rb b/activesupport/lib/active_support/logger_thread_safe_level.rb
index 7fb175dea6..405bb239cf 100644
--- a/activesupport/lib/active_support/logger_thread_safe_level.rb
+++ b/activesupport/lib/active_support/logger_thread_safe_level.rb
@@ -1,4 +1,4 @@
-require "active_support/concern"
+require_relative "concern"
module ActiveSupport
module LoggerThreadSafeLevel # :nodoc:
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index 24053b4fe5..3aaa1ae02c 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -1,7 +1,7 @@
require "openssl"
require "base64"
-require "active_support/core_ext/array/extract_options"
-require "active_support/message_verifier"
+require_relative "core_ext/array/extract_options"
+require_relative "message_verifier"
module ActiveSupport
# MessageEncryptor is a simple way to encrypt values which get stored
@@ -19,7 +19,17 @@ module ActiveSupport
# 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"
+ class << self
+ attr_accessor :use_authenticated_message_encryption #:nodoc:
+
+ def default_cipher #:nodoc:
+ if use_authenticated_message_encryption
+ "aes-256-gcm"
+ else
+ "aes-256-cbc"
+ end
+ end
+ end
module NullSerializer #:nodoc:
def self.load(value)
@@ -45,7 +55,7 @@ module ActiveSupport
OpenSSLCipherError = OpenSSL::Cipher::CipherError
# Initialize a new MessageEncryptor. +secret+ must be at least as long as
- # the cipher key size. For the default 'aes-256-cbc' cipher, this is 256
+ # the cipher key size. For the default 'aes-256-gcm' cipher, this is 256
# bits. If you are using a user-entered secret, you can generate a suitable
# key by using <tt>ActiveSupport::KeyGenerator</tt> or a similar key
# derivation function.
@@ -57,7 +67,7 @@ module ActiveSupport
#
# Options:
# * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by
- # <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-cbc'.
+ # <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-gcm'.
# * <tt>:digest</tt> - String of digest to use for signing. Default is
# +SHA1+. Ignored when using an AEAD cipher like 'aes-256-gcm'.
# * <tt>:serializer</tt> - Object serializer to use. Default is +Marshal+.
@@ -66,7 +76,7 @@ module ActiveSupport
sign_secret = signature_key_or_options.first
@secret = secret
@sign_secret = sign_secret
- @cipher = options[:cipher] || DEFAULT_CIPHER
+ @cipher = options[:cipher] || self.class.default_cipher
@digest = options[:digest] || "SHA1" unless aead_mode?
@verifier = resolve_verifier
@serializer = options[:serializer] || Marshal
@@ -85,7 +95,7 @@ module ActiveSupport
end
# Given a cipher, returns the key length of the cipher to help generate the key of desired size
- def self.key_len(cipher = DEFAULT_CIPHER)
+ def self.key_len(cipher = default_cipher)
OpenSSL::Cipher.new(cipher).key_len
end
@@ -104,7 +114,7 @@ module ActiveSupport
encrypted_data << cipher.final
blob = "#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}"
- blob << "--#{::Base64.strict_encode64 cipher.auth_tag}" if aead_mode?
+ blob = "#{blob}--#{::Base64.strict_encode64 cipher.auth_tag}" if aead_mode?
blob
end
@@ -115,7 +125,7 @@ module ActiveSupport
# Currently the OpenSSL bindings do not raise an error if auth_tag is
# truncated, which would allow an attacker to easily forge it. See
# https://github.com/ruby/openssl/issues/63
- raise InvalidMessage if aead_mode? && auth_tag.bytes.length != 16
+ raise InvalidMessage if aead_mode? && (auth_tag.nil? || auth_tag.bytes.length != 16)
cipher.decrypt
cipher.key = @secret
diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb
index 8419e858c6..e71dcd3a5b 100644
--- a/activesupport/lib/active_support/message_verifier.rb
+++ b/activesupport/lib/active_support/message_verifier.rb
@@ -1,6 +1,6 @@
require "base64"
-require "active_support/core_ext/object/blank"
-require "active_support/security_utils"
+require_relative "core_ext/object/blank"
+require_relative "security_utils"
module ActiveSupport
# +MessageVerifier+ makes it easy to generate and verify messages which are
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index 8c58466556..e4c544d1fa 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -1,8 +1,8 @@
-require "active_support/json"
-require "active_support/core_ext/string/access"
-require "active_support/core_ext/string/behavior"
-require "active_support/core_ext/module/delegation"
-require "active_support/core_ext/regexp"
+require_relative "../json"
+require_relative "../core_ext/string/access"
+require_relative "../core_ext/string/behavior"
+require_relative "../core_ext/module/delegation"
+require_relative "../core_ext/regexp"
module ActiveSupport #:nodoc:
module Multibyte #:nodoc:
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 0912912aba..8223e45e5a 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -357,7 +357,7 @@ module ActiveSupport
# Returns the directory in which the data files are stored.
def self.dirname
- File.dirname(__FILE__) + "/../values/"
+ File.expand_path("../values", __dir__)
end
# Returns the filename for the data file for this version.
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index 2df819e554..10e40e04f9 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -1,6 +1,6 @@
-require "active_support/notifications/instrumenter"
-require "active_support/notifications/fanout"
-require "active_support/per_thread_registry"
+require_relative "notifications/instrumenter"
+require_relative "notifications/fanout"
+require_relative "per_thread_registry"
module ActiveSupport
# = Notifications
@@ -64,6 +64,8 @@ module ActiveSupport
# If an exception happens during that particular instrumentation the payload will
# have a key <tt>:exception</tt> with an array of two elements as value: a string with
# the name of the exception class, and the exception message.
+ # The <tt>:exception_object</tt> key of the payload will have the exception
+ # itself as the value.
#
# As the previous example depicts, the class <tt>ActiveSupport::Notifications::Event</tt>
# is able to take the arguments as they come and provide an object-oriented
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index 880340ca86..9cb2821cb6 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -4,6 +4,7 @@ module ActiveSupport
eager_autoload do
autoload :NumberConverter
+ autoload :RoundingHelper
autoload :NumberToRoundedConverter
autoload :NumberToDelimitedConverter
autoload :NumberToHumanConverter
diff --git a/activesupport/lib/active_support/number_helper/number_converter.rb b/activesupport/lib/active_support/number_helper/number_converter.rb
index ce363287cf..f3fed6e88b 100644
--- a/activesupport/lib/active_support/number_helper/number_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_converter.rb
@@ -1,8 +1,8 @@
-require "active_support/core_ext/big_decimal/conversions"
-require "active_support/core_ext/object/blank"
-require "active_support/core_ext/hash/keys"
-require "active_support/i18n"
-require "active_support/core_ext/class/attribute"
+require_relative "../core_ext/big_decimal/conversions"
+require_relative "../core_ext/object/blank"
+require_relative "../core_ext/hash/keys"
+require_relative "../i18n"
+require_relative "../core_ext/class/attribute"
module ActiveSupport
module NumberHelper
diff --git a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
index 0f9dce722f..7bd2d4869a 100644
--- a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
@@ -1,4 +1,4 @@
-require "active_support/core_ext/numeric/inquiry"
+require_relative "../core_ext/numeric/inquiry"
module ActiveSupport
module NumberHelper
diff --git a/activesupport/lib/active_support/number_helper/number_to_human_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb
index 56185ddf4b..040343b5dd 100644
--- a/activesupport/lib/active_support/number_helper/number_to_human_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb
@@ -9,6 +9,7 @@ module ActiveSupport
self.validate_float = true
def convert # :nodoc:
+ @number = RoundingHelper.new(options).round(number)
@number = Float(number)
# for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
@@ -20,10 +21,7 @@ module ActiveSupport
exponent = calculate_exponent(units)
@number = number / (10**exponent)
- until (rounded_number = NumberToRoundedConverter.convert(number, options)) != NumberToRoundedConverter.convert(1000, options)
- @number = number / 1000.0
- exponent += 3
- end
+ rounded_number = NumberToRoundedConverter.convert(number, options)
unit = determine_unit(units, exponent)
format.gsub("%n".freeze, rounded_number).gsub("%u".freeze, unit).strip
end
diff --git a/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb b/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb
index 1de9f50f34..3546ee5c2a 100644
--- a/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb
@@ -2,7 +2,7 @@ module ActiveSupport
module NumberHelper
class NumberToPhoneConverter < NumberConverter #:nodoc:
def convert
- str = country_code(opts[:country_code])
+ str = country_code(opts[:country_code]).dup
str << convert_to_phone_number(number.to_s.strip)
str << phone_ext(opts[:extension])
end
diff --git a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
index 1f013990ea..c32d85a45f 100644
--- a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
@@ -5,26 +5,14 @@ module ActiveSupport
self.validate_float = true
def convert
- precision = options.delete :precision
+ helper = RoundingHelper.new(options)
+ rounded_number = helper.round(number)
- if precision
- case number
- when Float, String
- @number = BigDecimal(number.to_s)
- when Rational
- @number = BigDecimal(number, digit_count(number.to_i) + precision)
- else
- @number = number.to_d
- end
-
- if options.delete(:significant) && precision > 0
- digits, rounded_number = digits_and_rounded_number(precision)
+ if precision = options[:precision]
+ if options[:significant] && precision > 0
+ digits = helper.digit_count(rounded_number)
precision -= digits
precision = 0 if precision < 0 # don't let it be negative
- else
- rounded_number = number.round(precision)
- rounded_number = rounded_number.to_i if precision == 0 && rounded_number.finite?
- rounded_number = rounded_number.abs if rounded_number.zero? # prevent showing negative zeros
end
formatted_string =
@@ -38,7 +26,7 @@ module ActiveSupport
"%00.#{precision}f" % rounded_number
end
else
- formatted_string = number
+ formatted_string = rounded_number
end
delimited_number = NumberToDelimitedConverter.convert(formatted_string, options)
@@ -79,14 +67,6 @@ module ActiveSupport
number
end
end
-
- def absolute_number(number)
- number.respond_to?(:abs) ? number.abs : number.to_d.abs
- end
-
- def zero?
- number.respond_to?(:zero?) ? number.zero? : number.to_d.zero?
- end
end
end
end
diff --git a/activesupport/lib/active_support/number_helper/rounding_helper.rb b/activesupport/lib/active_support/number_helper/rounding_helper.rb
new file mode 100644
index 0000000000..63b48444a6
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper/rounding_helper.rb
@@ -0,0 +1,64 @@
+module ActiveSupport
+ module NumberHelper
+ class RoundingHelper # :nodoc:
+ attr_reader :options
+
+ def initialize(options)
+ @options = options
+ end
+
+ def round(number)
+ return number unless precision
+ number = convert_to_decimal(number)
+ if significant && precision > 0
+ round_significant(number)
+ else
+ round_without_significant(number)
+ end
+ end
+
+ def digit_count(number)
+ return 1 if number.zero?
+ (Math.log10(absolute_number(number)) + 1).floor
+ end
+
+ private
+ def round_without_significant(number)
+ number = number.round(precision)
+ number = number.to_i if precision == 0 && number.finite?
+ number = number.abs if number.zero? # prevent showing negative zeros
+ number
+ end
+
+ def round_significant(number)
+ return 0 if number.zero?
+ digits = digit_count(number)
+ multiplier = 10**(digits - precision)
+ (number / BigDecimal.new(multiplier.to_f.to_s)).round * multiplier
+ end
+
+ def convert_to_decimal(number)
+ case number
+ when Float, String
+ BigDecimal(number.to_s)
+ when Rational
+ BigDecimal(number, digit_count(number.to_i) + precision)
+ else
+ number.to_d
+ end
+ end
+
+ def precision
+ options[:precision]
+ end
+
+ def significant
+ options[:significant]
+ end
+
+ def absolute_number(number)
+ number.respond_to?(:abs) ? number.abs : number.to_d.abs
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/option_merger.rb b/activesupport/lib/active_support/option_merger.rb
index 0f2caa98f2..9edb0e6cd8 100644
--- a/activesupport/lib/active_support/option_merger.rb
+++ b/activesupport/lib/active_support/option_merger.rb
@@ -1,4 +1,4 @@
-require "active_support/core_ext/hash/deep_merge"
+require_relative "core_ext/hash/deep_merge"
module ActiveSupport
class OptionMerger #:nodoc:
diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb
index 04d6dfaf9c..c0f39a392b 100644
--- a/activesupport/lib/active_support/ordered_options.rb
+++ b/activesupport/lib/active_support/ordered_options.rb
@@ -1,4 +1,4 @@
-require "active_support/core_ext/object/blank"
+require_relative "core_ext/object/blank"
module ActiveSupport
# Usually key value pairs are handled something like this:
diff --git a/activesupport/lib/active_support/per_thread_registry.rb b/activesupport/lib/active_support/per_thread_registry.rb
index 02431704d3..1e0d8345a0 100644
--- a/activesupport/lib/active_support/per_thread_registry.rb
+++ b/activesupport/lib/active_support/per_thread_registry.rb
@@ -1,4 +1,4 @@
-require "active_support/core_ext/module/delegation"
+require_relative "core_ext/module/delegation"
module ActiveSupport
# NOTE: This approach has been deprecated for end-user code in favor of {thread_mattr_accessor}[rdoc-ref:Module#thread_mattr_accessor] and friends.
diff --git a/activesupport/lib/active_support/rails.rb b/activesupport/lib/active_support/rails.rb
index f6b018f0d3..656af6dee9 100644
--- a/activesupport/lib/active_support/rails.rb
+++ b/activesupport/lib/active_support/rails.rb
@@ -9,25 +9,25 @@
# Rails and can change anytime.
# Defines Object#blank? and Object#present?.
-require "active_support/core_ext/object/blank"
+require_relative "core_ext/object/blank"
# Rails own autoload, eager_load, etc.
-require "active_support/dependencies/autoload"
+require_relative "dependencies/autoload"
# Support for ClassMethods and the included macro.
-require "active_support/concern"
+require_relative "concern"
# Defines Class#class_attribute.
-require "active_support/core_ext/class/attribute"
+require_relative "core_ext/class/attribute"
# Defines Module#delegate.
-require "active_support/core_ext/module/delegation"
+require_relative "core_ext/module/delegation"
# Defines ActiveSupport::Deprecation.
-require "active_support/deprecation"
+require_relative "deprecation"
# Defines Regexp#match?.
#
# This should be removed when Rails needs Ruby 2.4 or later, and the require
# added where other Regexp extensions are being used (easy to grep).
-require "active_support/core_ext/regexp"
+require_relative "core_ext/regexp"
diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb
index b875875afe..3377748263 100644
--- a/activesupport/lib/active_support/railtie.rb
+++ b/activesupport/lib/active_support/railtie.rb
@@ -1,5 +1,5 @@
require "active_support"
-require "active_support/i18n_railtie"
+require_relative "i18n_railtie"
module ActiveSupport
class Railtie < Rails::Railtie # :nodoc:
@@ -7,6 +7,19 @@ module ActiveSupport
config.eager_load_namespaces << ActiveSupport
+ initializer "active_support.set_authenticated_message_encryption" do |app|
+ if app.config.active_support.respond_to?(:use_authenticated_message_encryption)
+ ActiveSupport::MessageEncryptor.use_authenticated_message_encryption =
+ app.config.active_support.use_authenticated_message_encryption
+ end
+ end
+
+ initializer "active_support.reset_all_current_attributes_instances" do |app|
+ app.reloader.before_class_unload { ActiveSupport::CurrentAttributes.clear_all }
+ app.executor.to_run { ActiveSupport::CurrentAttributes.reset_all }
+ app.executor.to_complete { ActiveSupport::CurrentAttributes.reset_all }
+ end
+
initializer "active_support.deprecation_behavior" do |app|
if deprecation = app.config.active_support.deprecation
ActiveSupport::Deprecation.behavior = deprecation
@@ -21,21 +34,14 @@ module ActiveSupport
rescue TZInfo::DataSourceNotFound => e
raise e.exception "tzinfo-data is not present. Please add gem 'tzinfo-data' to your Gemfile and run bundle install"
end
- require "active_support/core_ext/time/zones"
- zone_default = Time.find_zone!(app.config.time_zone)
-
- unless zone_default
- raise "Value assigned to config.time_zone not recognized. " \
- 'Run "rake time:zones:all" for a time zone names list.'
- end
-
- Time.zone_default = zone_default
+ require_relative "core_ext/time/zones"
+ Time.zone_default = Time.find_zone!(app.config.time_zone)
end
# Sets the default week start
# If assigned value is not a valid day symbol (e.g. :sunday, :monday, ...), an exception will be raised.
initializer "active_support.initialize_beginning_of_week" do |app|
- require "active_support/core_ext/date/calculations"
+ require_relative "core_ext/date/calculations"
beginning_of_week_default = Date.find_beginning_of_week!(app.config.beginning_of_week)
Date.beginning_of_week_default = beginning_of_week_default
diff --git a/activesupport/lib/active_support/reloader.rb b/activesupport/lib/active_support/reloader.rb
index 121c621751..a87c82e68a 100644
--- a/activesupport/lib/active_support/reloader.rb
+++ b/activesupport/lib/active_support/reloader.rb
@@ -1,4 +1,4 @@
-require "active_support/execution_wrapper"
+require_relative "execution_wrapper"
module ActiveSupport
#--
@@ -69,11 +69,8 @@ module ActiveSupport
end
end
- class_attribute :executor
- class_attribute :check
-
- self.executor = Executor
- self.check = lambda { false }
+ class_attribute :executor, default: Executor
+ class_attribute :check, default: lambda { false }
def self.check! # :nodoc:
@should_reload ||= check.call
diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb
index ee6592fb5a..06f6a71de4 100644
--- a/activesupport/lib/active_support/rescuable.rb
+++ b/activesupport/lib/active_support/rescuable.rb
@@ -1,6 +1,6 @@
-require "active_support/concern"
-require "active_support/core_ext/class/attribute"
-require "active_support/core_ext/string/inflections"
+require_relative "concern"
+require_relative "core_ext/class/attribute"
+require_relative "core_ext/string/inflections"
module ActiveSupport
# Rescuable module adds support for easier exception handling.
@@ -8,8 +8,7 @@ module ActiveSupport
extend Concern
included do
- class_attribute :rescue_handlers
- self.rescue_handlers = []
+ class_attribute :rescue_handlers, default: []
end
module ClassMethods
@@ -84,12 +83,18 @@ module ActiveSupport
# end
#
# Returns the exception if it was handled and +nil+ if it was not.
- def rescue_with_handler(exception, object: self)
+ def rescue_with_handler(exception, object: self, visited_exceptions: [])
+ visited_exceptions << exception
+
if handler = handler_for_rescue(exception, object: object)
handler.call exception
exception
elsif exception
- rescue_with_handler(exception.cause, object: object)
+ if visited_exceptions.include?(exception.cause)
+ nil
+ else
+ rescue_with_handler(exception.cause, object: object, visited_exceptions: visited_exceptions)
+ end
end
end
diff --git a/activesupport/lib/active_support/subscriber.rb b/activesupport/lib/active_support/subscriber.rb
index 2924139755..3a54482c75 100644
--- a/activesupport/lib/active_support/subscriber.rb
+++ b/activesupport/lib/active_support/subscriber.rb
@@ -1,5 +1,5 @@
-require "active_support/per_thread_registry"
-require "active_support/notifications"
+require_relative "per_thread_registry"
+require_relative "notifications"
module ActiveSupport
# ActiveSupport::Subscriber is an object set to consume
diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb
index ad134c49b6..90d096c397 100644
--- a/activesupport/lib/active_support/tagged_logging.rb
+++ b/activesupport/lib/active_support/tagged_logging.rb
@@ -1,7 +1,7 @@
-require "active_support/core_ext/module/delegation"
-require "active_support/core_ext/object/blank"
+require_relative "core_ext/module/delegation"
+require_relative "core_ext/object/blank"
require "logger"
-require "active_support/logger"
+require_relative "logger"
module ActiveSupport
# Wraps any standard Logger object to provide tagging capabilities.
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index 3de4ccc1da..f2940a176f 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -1,15 +1,15 @@
gem "minitest" # make sure we get the gem, not stdlib
require "minitest"
-require "active_support/testing/tagged_logging"
-require "active_support/testing/setup_and_teardown"
-require "active_support/testing/assertions"
-require "active_support/testing/deprecation"
-require "active_support/testing/declarative"
-require "active_support/testing/isolation"
-require "active_support/testing/constant_lookup"
-require "active_support/testing/time_helpers"
-require "active_support/testing/file_fixtures"
-require "active_support/core_ext/kernel/reporting"
+require_relative "testing/tagged_logging"
+require_relative "testing/setup_and_teardown"
+require_relative "testing/assertions"
+require_relative "testing/deprecation"
+require_relative "testing/declarative"
+require_relative "testing/isolation"
+require_relative "testing/constant_lookup"
+require_relative "testing/time_helpers"
+require_relative "testing/file_fixtures"
+require_relative "core_ext/kernel/reporting"
module ActiveSupport
class TestCase < ::Minitest::Test
diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb
index 28cf2953bf..28e1df8870 100644
--- a/activesupport/lib/active_support/testing/assertions.rb
+++ b/activesupport/lib/active_support/testing/assertions.rb
@@ -167,7 +167,7 @@ module ActiveSupport
retval
end
- # Assertion that the result of evaluating an expression is changed before
+ # Assertion that the result of evaluating an expression is not changed before
# and after invoking the passed in block.
#
# assert_no_changes 'Status.all_good?' do
diff --git a/activesupport/lib/active_support/testing/constant_lookup.rb b/activesupport/lib/active_support/testing/constant_lookup.rb
index 647395d2b3..c0b58aba96 100644
--- a/activesupport/lib/active_support/testing/constant_lookup.rb
+++ b/activesupport/lib/active_support/testing/constant_lookup.rb
@@ -1,5 +1,5 @@
-require "active_support/concern"
-require "active_support/inflector"
+require_relative "../concern"
+require_relative "../inflector"
module ActiveSupport
module Testing
diff --git a/activesupport/lib/active_support/testing/deprecation.rb b/activesupport/lib/active_support/testing/deprecation.rb
index 58911570e8..6ee49530f0 100644
--- a/activesupport/lib/active_support/testing/deprecation.rb
+++ b/activesupport/lib/active_support/testing/deprecation.rb
@@ -1,5 +1,5 @@
-require "active_support/deprecation"
-require "active_support/core_ext/regexp"
+require_relative "../deprecation"
+require_relative "../core_ext/regexp"
module ActiveSupport
module Testing
diff --git a/activesupport/lib/active_support/testing/setup_and_teardown.rb b/activesupport/lib/active_support/testing/setup_and_teardown.rb
index 358c79c321..c39f3bac2b 100644
--- a/activesupport/lib/active_support/testing/setup_and_teardown.rb
+++ b/activesupport/lib/active_support/testing/setup_and_teardown.rb
@@ -1,5 +1,5 @@
-require "active_support/concern"
-require "active_support/callbacks"
+require_relative "../concern"
+require_relative "../callbacks"
module ActiveSupport
module Testing
diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb
index 07c9be0604..4db7065ddf 100644
--- a/activesupport/lib/active_support/testing/time_helpers.rb
+++ b/activesupport/lib/active_support/testing/time_helpers.rb
@@ -1,4 +1,5 @@
-require "active_support/core_ext/string/strip" # for strip_heredoc
+require_relative "../core_ext/string/strip" # for strip_heredoc
+require_relative "../core_ext/time/calculations"
require "concurrent/map"
module ActiveSupport
diff --git a/activesupport/lib/active_support/time.rb b/activesupport/lib/active_support/time.rb
index 7658228ca6..bc5f0177e2 100644
--- a/activesupport/lib/active_support/time.rb
+++ b/activesupport/lib/active_support/time.rb
@@ -7,12 +7,12 @@ end
require "date"
require "time"
-require "active_support/core_ext/time"
-require "active_support/core_ext/date"
-require "active_support/core_ext/date_time"
+require_relative "core_ext/time"
+require_relative "core_ext/date"
+require_relative "core_ext/date_time"
-require "active_support/core_ext/integer/time"
-require "active_support/core_ext/numeric/time"
+require_relative "core_ext/integer/time"
+require_relative "core_ext/numeric/time"
-require "active_support/core_ext/string/conversions"
-require "active_support/core_ext/string/zones"
+require_relative "core_ext/string/conversions"
+require_relative "core_ext/string/zones"
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index b0dd6b7e8c..67d4a8683b 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -1,7 +1,7 @@
-require "active_support/duration"
-require "active_support/values/time_zone"
-require "active_support/core_ext/object/acts_like"
-require "active_support/core_ext/date_and_time/compatibility"
+require_relative "duration"
+require_relative "values/time_zone"
+require_relative "core_ext/object/acts_like"
+require_relative "core_ext/date_and_time/compatibility"
module ActiveSupport
# A Time-like class that can represent a time in any time zone. Necessary
@@ -330,6 +330,42 @@ module ActiveSupport
since(-other)
end
+ # Returns a new +ActiveSupport::TimeWithZone+ where one or more of the elements have
+ # been changed according to the +options+ parameter. The time options (<tt>:hour</tt>,
+ # <tt>:min</tt>, <tt>:sec</tt>, <tt>:usec</tt>, <tt>:nsec</tt>) reset cascadingly,
+ # so if only the hour is passed, then minute, sec, usec and nsec is set to 0. If the
+ # hour and minute is passed, then sec, usec and nsec is set to 0. The +options+
+ # parameter takes a hash with any of these keys: <tt>:year</tt>, <tt>:month</tt>,
+ # <tt>:day</tt>, <tt>:hour</tt>, <tt>:min</tt>, <tt>:sec</tt>, <tt>:usec</tt>,
+ # <tt>:nsec</tt>, <tt>:offset</tt>, <tt>:zone</tt>. Pass either <tt>:usec</tt>
+ # or <tt>:nsec</tt>, not both. Similarly, pass either <tt>:zone</tt> or
+ # <tt>:offset</tt>, not both.
+ #
+ # t = Time.zone.now # => Fri, 14 Apr 2017 11:45:15 EST -05:00
+ # t.change(year: 2020) # => Tue, 14 Apr 2020 11:45:15 EST -05:00
+ # t.change(hour: 12) # => Fri, 14 Apr 2017 12:00:00 EST -05:00
+ # t.change(min: 30) # => Fri, 14 Apr 2017 11:30:00 EST -05:00
+ # t.change(offset: "-10:00") # => Fri, 14 Apr 2017 11:45:15 HST -10:00
+ # t.change(zone: "Hawaii") # => Fri, 14 Apr 2017 11:45:15 HST -10:00
+ def change(options)
+ if options[:zone] && options[:offset]
+ raise ArgumentError, "Can't change both :offset and :zone at the same time: #{options.inspect}"
+ end
+
+ new_time = time.change(options)
+
+ if options[:zone]
+ new_zone = ::Time.find_zone(options[:zone])
+ elsif options[:offset]
+ new_zone = ::Time.find_zone(new_time.utc_offset)
+ end
+
+ new_zone ||= time_zone
+ periods = new_zone.periods_for_local(new_time)
+
+ self.class.new(nil, new_zone, new_time, periods.include?(period) ? period : nil)
+ end
+
# Uses Date to provide precise Time calculations for years, months, and days
# according to the proleptic Gregorian calendar. The result is returned as a
# new TimeWithZone object.
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index dd69383f5f..890a2af51c 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -1,12 +1,12 @@
require "tzinfo"
require "concurrent/map"
-require "active_support/core_ext/object/blank"
+require_relative "../core_ext/object/blank"
module ActiveSupport
# The TimeZone class serves as a wrapper around TZInfo::Timezone instances.
# It allows us to do the following:
#
- # * Limit the set of zones provided by TZInfo to a meaningful subset of 146
+ # * Limit the set of zones provided by TZInfo to a meaningful subset of 134
# zones.
# * Retrieve and display zones with a friendlier name
# (e.g., "Eastern Time (US & Canada)" instead of "America/New_York").
@@ -59,6 +59,7 @@ module ActiveSupport
"Buenos Aires" => "America/Argentina/Buenos_Aires",
"Montevideo" => "America/Montevideo",
"Georgetown" => "America/Guyana",
+ "Puerto Rico" => "America/Puerto_Rico",
"Greenland" => "America/Godthab",
"Mid-Atlantic" => "Atlantic/South_Georgia",
"Azores" => "Atlantic/Azores",
diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb
index 782fb41288..14725ffd64 100644
--- a/activesupport/lib/active_support/xml_mini.rb
+++ b/activesupport/lib/active_support/xml_mini.rb
@@ -1,9 +1,9 @@
require "time"
require "base64"
require "bigdecimal"
-require "active_support/core_ext/module/delegation"
-require "active_support/core_ext/string/inflections"
-require "active_support/core_ext/date_time/calculations"
+require_relative "core_ext/module/delegation"
+require_relative "core_ext/string/inflections"
+require_relative "core_ext/date_time/calculations"
module ActiveSupport
# = XmlMini
@@ -197,7 +197,7 @@ module ActiveSupport
if name.is_a?(Module)
name
else
- require "active_support/xml_mini/#{name.downcase}"
+ require_relative "xml_mini/#{name.downcase}"
ActiveSupport.const_get("XmlMini_#{name}")
end
end
diff --git a/activesupport/lib/active_support/xml_mini/jdom.rb b/activesupport/lib/active_support/xml_mini/jdom.rb
index a7939b3185..331d9960e7 100644
--- a/activesupport/lib/active_support/xml_mini/jdom.rb
+++ b/activesupport/lib/active_support/xml_mini/jdom.rb
@@ -3,7 +3,7 @@ raise "JRuby is required to use the JDOM backend for XmlMini" unless RUBY_PLATFO
require "jruby"
include Java
-require "active_support/core_ext/object/blank"
+require_relative "../core_ext/object/blank"
java_import javax.xml.parsers.DocumentBuilder unless defined? DocumentBuilder
java_import javax.xml.parsers.DocumentBuilderFactory unless defined? DocumentBuilderFactory
diff --git a/activesupport/lib/active_support/xml_mini/libxml.rb b/activesupport/lib/active_support/xml_mini/libxml.rb
index cde2967132..d7097dcc64 100644
--- a/activesupport/lib/active_support/xml_mini/libxml.rb
+++ b/activesupport/lib/active_support/xml_mini/libxml.rb
@@ -1,5 +1,5 @@
require "libxml"
-require "active_support/core_ext/object/blank"
+require_relative "../core_ext/object/blank"
require "stringio"
module ActiveSupport
@@ -14,11 +14,9 @@ module ActiveSupport
data = StringIO.new(data || "")
end
- char = data.getc
- if char.nil?
+ if data.eof?
{}
else
- data.ungetc(char)
LibXML::XML::Parser.io(data).parse.to_hash
end
end
@@ -55,7 +53,7 @@ module LibXML #:nodoc:
if c.element?
c.to_hash(node_hash)
elsif c.text? || c.cdata?
- node_hash[CONTENT_ROOT] ||= ""
+ node_hash[CONTENT_ROOT] ||= "".dup
node_hash[CONTENT_ROOT] << c.content
end
end
diff --git a/activesupport/lib/active_support/xml_mini/libxmlsax.rb b/activesupport/lib/active_support/xml_mini/libxmlsax.rb
index 8a43b05b17..0cfa2b2cd3 100644
--- a/activesupport/lib/active_support/xml_mini/libxmlsax.rb
+++ b/activesupport/lib/active_support/xml_mini/libxmlsax.rb
@@ -1,5 +1,5 @@
require "libxml"
-require "active_support/core_ext/object/blank"
+require_relative "../core_ext/object/blank"
require "stringio"
module ActiveSupport
@@ -21,7 +21,7 @@ module ActiveSupport
end
def on_start_document
- @hash = { CONTENT_KEY => "" }
+ @hash = { CONTENT_KEY => "".dup }
@hash_stack = [@hash]
end
@@ -31,7 +31,7 @@ module ActiveSupport
end
def on_start_element(name, attrs = {})
- new_hash = { CONTENT_KEY => "" }.merge!(attrs)
+ new_hash = { CONTENT_KEY => "".dup }.merge!(attrs)
new_hash[HASH_SIZE_KEY] = new_hash.size + 1
case current_hash[name]
@@ -65,12 +65,9 @@ module ActiveSupport
data = StringIO.new(data || "")
end
- char = data.getc
- if char.nil?
+ if data.eof?
{}
else
- data.ungetc(char)
-
LibXML::XML::Error.set_handler(&LibXML::XML::Error::QUIET_HANDLER)
parser = LibXML::XML::SaxParser.io(data)
document = document_class.new
diff --git a/activesupport/lib/active_support/xml_mini/nokogiri.rb b/activesupport/lib/active_support/xml_mini/nokogiri.rb
index 4c2be3f3ec..57d8cec0c6 100644
--- a/activesupport/lib/active_support/xml_mini/nokogiri.rb
+++ b/activesupport/lib/active_support/xml_mini/nokogiri.rb
@@ -4,7 +4,7 @@ rescue LoadError => e
$stderr.puts "You don't have nokogiri installed in your application. Please add it to your Gemfile and run bundle install"
raise e
end
-require "active_support/core_ext/object/blank"
+require_relative "../core_ext/object/blank"
require "stringio"
module ActiveSupport
@@ -19,11 +19,9 @@ module ActiveSupport
data = StringIO.new(data || "")
end
- char = data.getc
- if char.nil?
+ if data.eof?
{}
else
- data.ungetc(char)
doc = Nokogiri::XML(data)
raise doc.errors.first if doc.errors.length > 0
doc.to_hash
@@ -59,7 +57,7 @@ module ActiveSupport
if c.element?
c.to_hash(node_hash)
elsif c.text? || c.cdata?
- node_hash[CONTENT_ROOT] ||= ""
+ node_hash[CONTENT_ROOT] ||= "".dup
node_hash[CONTENT_ROOT] << c.content
end
end
diff --git a/activesupport/lib/active_support/xml_mini/nokogirisax.rb b/activesupport/lib/active_support/xml_mini/nokogirisax.rb
index 7388bea5d8..8fc1dfa727 100644
--- a/activesupport/lib/active_support/xml_mini/nokogirisax.rb
+++ b/activesupport/lib/active_support/xml_mini/nokogirisax.rb
@@ -4,7 +4,7 @@ rescue LoadError => e
$stderr.puts "You don't have nokogiri installed in your application. Please add it to your Gemfile and run bundle install"
raise e
end
-require "active_support/core_ext/object/blank"
+require_relative "../core_ext/object/blank"
require "stringio"
module ActiveSupport
@@ -37,7 +37,7 @@ module ActiveSupport
end
def start_element(name, attrs = [])
- new_hash = { CONTENT_KEY => "" }.merge!(Hash[attrs])
+ new_hash = { CONTENT_KEY => "".dup }.merge!(Hash[attrs])
new_hash[HASH_SIZE_KEY] = new_hash.size + 1
case current_hash[name]
@@ -71,11 +71,9 @@ module ActiveSupport
data = StringIO.new(data || "")
end
- char = data.getc
- if char.nil?
+ if data.eof?
{}
else
- data.ungetc(char)
document = document_class.new
parser = Nokogiri::XML::SAX::Parser.new(document)
parser.parse(data)
diff --git a/activesupport/lib/active_support/xml_mini/rexml.rb b/activesupport/lib/active_support/xml_mini/rexml.rb
index 03fa910fa5..04d12c6109 100644
--- a/activesupport/lib/active_support/xml_mini/rexml.rb
+++ b/activesupport/lib/active_support/xml_mini/rexml.rb
@@ -1,5 +1,5 @@
-require "active_support/core_ext/kernel/reporting"
-require "active_support/core_ext/object/blank"
+require_relative "../core_ext/kernel/reporting"
+require_relative "../core_ext/object/blank"
require "stringio"
module ActiveSupport
@@ -74,7 +74,7 @@ module ActiveSupport
hash
else
# must use value to prevent double-escaping
- texts = ""
+ texts = "".dup
element.texts.each { |t| texts << t.value }
merge!(hash, CONTENT_KEY, texts)
end
diff --git a/activesupport/test/cache/behaviors.rb b/activesupport/test/cache/behaviors.rb
new file mode 100644
index 0000000000..efd045ac5e
--- /dev/null
+++ b/activesupport/test/cache/behaviors.rb
@@ -0,0 +1,7 @@
+require_relative "behaviors/autoloading_cache_behavior"
+require_relative "behaviors/cache_delete_matched_behavior"
+require_relative "behaviors/cache_increment_decrement_behavior"
+require_relative "behaviors/cache_store_behavior"
+require_relative "behaviors/cache_store_version_behavior"
+require_relative "behaviors/encoded_key_cache_behavior"
+require_relative "behaviors/local_cache_behavior"
diff --git a/activesupport/test/cache/behaviors/autoloading_cache_behavior.rb b/activesupport/test/cache/behaviors/autoloading_cache_behavior.rb
new file mode 100644
index 0000000000..5f8af331f6
--- /dev/null
+++ b/activesupport/test/cache/behaviors/autoloading_cache_behavior.rb
@@ -0,0 +1,41 @@
+require "dependencies_test_helpers"
+
+module AutoloadingCacheBehavior
+ include DependenciesTestHelpers
+
+ def test_simple_autoloading
+ with_autoloading_fixtures do
+ @cache.write("foo", EM.new)
+ end
+
+ remove_constants(:EM)
+ ActiveSupport::Dependencies.clear
+
+ with_autoloading_fixtures do
+ assert_kind_of EM, @cache.read("foo")
+ end
+
+ remove_constants(:EM)
+ ActiveSupport::Dependencies.clear
+ end
+
+ def test_two_classes_autoloading
+ with_autoloading_fixtures do
+ @cache.write("foo", [EM.new, ClassFolder.new])
+ end
+
+ remove_constants(:EM, :ClassFolder)
+ ActiveSupport::Dependencies.clear
+
+ with_autoloading_fixtures do
+ loaded = @cache.read("foo")
+ assert_kind_of Array, loaded
+ assert_equal 2, loaded.size
+ assert_kind_of EM, loaded[0]
+ assert_kind_of ClassFolder, loaded[1]
+ end
+
+ remove_constants(:EM, :ClassFolder)
+ ActiveSupport::Dependencies.clear
+ end
+end
diff --git a/activesupport/test/cache/behaviors/cache_delete_matched_behavior.rb b/activesupport/test/cache/behaviors/cache_delete_matched_behavior.rb
new file mode 100644
index 0000000000..b872eb0279
--- /dev/null
+++ b/activesupport/test/cache/behaviors/cache_delete_matched_behavior.rb
@@ -0,0 +1,13 @@
+module CacheDeleteMatchedBehavior
+ def test_delete_matched
+ @cache.write("foo", "bar")
+ @cache.write("fu", "baz")
+ @cache.write("foo/bar", "baz")
+ @cache.write("fu/baz", "bar")
+ @cache.delete_matched(/oo/)
+ assert !@cache.exist?("foo")
+ assert @cache.exist?("fu")
+ assert !@cache.exist?("foo/bar")
+ assert @cache.exist?("fu/baz")
+ end
+end
diff --git a/activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb b/activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb
new file mode 100644
index 0000000000..0d32339565
--- /dev/null
+++ b/activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb
@@ -0,0 +1,21 @@
+module CacheIncrementDecrementBehavior
+ def test_increment
+ @cache.write("foo", 1, raw: true)
+ assert_equal 1, @cache.read("foo").to_i
+ assert_equal 2, @cache.increment("foo")
+ assert_equal 2, @cache.read("foo").to_i
+ assert_equal 3, @cache.increment("foo")
+ assert_equal 3, @cache.read("foo").to_i
+ assert_nil @cache.increment("bar")
+ end
+
+ def test_decrement
+ @cache.write("foo", 3, raw: true)
+ assert_equal 3, @cache.read("foo").to_i
+ assert_equal 2, @cache.decrement("foo")
+ assert_equal 2, @cache.read("foo").to_i
+ assert_equal 1, @cache.decrement("foo")
+ assert_equal 1, @cache.read("foo").to_i
+ assert_nil @cache.decrement("bar")
+ end
+end
diff --git a/activesupport/test/cache/behaviors/cache_store_behavior.rb b/activesupport/test/cache/behaviors/cache_store_behavior.rb
new file mode 100644
index 0000000000..4631e6aee8
--- /dev/null
+++ b/activesupport/test/cache/behaviors/cache_store_behavior.rb
@@ -0,0 +1,329 @@
+# Tests the base functionality that should be identical across all cache stores.
+module CacheStoreBehavior
+ def test_should_read_and_write_strings
+ assert @cache.write("foo", "bar")
+ assert_equal "bar", @cache.read("foo")
+ end
+
+ def test_should_overwrite
+ @cache.write("foo", "bar")
+ @cache.write("foo", "baz")
+ assert_equal "baz", @cache.read("foo")
+ end
+
+ def test_fetch_without_cache_miss
+ @cache.write("foo", "bar")
+ assert_not_called(@cache, :write) do
+ assert_equal "bar", @cache.fetch("foo") { "baz" }
+ end
+ end
+
+ def test_fetch_with_cache_miss
+ assert_called_with(@cache, :write, ["foo", "baz", @cache.options]) do
+ assert_equal "baz", @cache.fetch("foo") { "baz" }
+ end
+ end
+
+ def test_fetch_with_cache_miss_passes_key_to_block
+ cache_miss = false
+ assert_equal 3, @cache.fetch("foo") { |key| cache_miss = true; key.length }
+ assert cache_miss
+
+ cache_miss = false
+ assert_equal 3, @cache.fetch("foo") { |key| cache_miss = true; key.length }
+ assert !cache_miss
+ end
+
+ def test_fetch_with_forced_cache_miss
+ @cache.write("foo", "bar")
+ assert_not_called(@cache, :read) do
+ assert_called_with(@cache, :write, ["foo", "bar", @cache.options.merge(force: true)]) do
+ @cache.fetch("foo", force: true) { "bar" }
+ end
+ end
+ end
+
+ def test_fetch_with_cached_nil
+ @cache.write("foo", nil)
+ assert_not_called(@cache, :write) do
+ assert_nil @cache.fetch("foo") { "baz" }
+ end
+ end
+
+ def test_fetch_with_forced_cache_miss_with_block
+ @cache.write("foo", "bar")
+ assert_equal "foo_bar", @cache.fetch("foo", force: true) { "foo_bar" }
+ end
+
+ def test_fetch_with_forced_cache_miss_without_block
+ @cache.write("foo", "bar")
+ assert_raises(ArgumentError) do
+ @cache.fetch("foo", force: true)
+ end
+
+ assert_equal "bar", @cache.read("foo")
+ end
+
+ def test_should_read_and_write_hash
+ assert @cache.write("foo", a: "b")
+ assert_equal({ a: "b" }, @cache.read("foo"))
+ end
+
+ def test_should_read_and_write_integer
+ assert @cache.write("foo", 1)
+ assert_equal 1, @cache.read("foo")
+ end
+
+ def test_should_read_and_write_nil
+ assert @cache.write("foo", nil)
+ assert_nil @cache.read("foo")
+ end
+
+ def test_should_read_and_write_false
+ assert @cache.write("foo", false)
+ assert_equal false, @cache.read("foo")
+ end
+
+ def test_read_multi
+ @cache.write("foo", "bar")
+ @cache.write("fu", "baz")
+ @cache.write("fud", "biz")
+ assert_equal({ "foo" => "bar", "fu" => "baz" }, @cache.read_multi("foo", "fu"))
+ end
+
+ def test_read_multi_with_expires
+ time = Time.now
+ @cache.write("foo", "bar", expires_in: 10)
+ @cache.write("fu", "baz")
+ @cache.write("fud", "biz")
+ Time.stub(:now, time + 11) do
+ assert_equal({ "fu" => "baz" }, @cache.read_multi("foo", "fu"))
+ end
+ end
+
+ def test_fetch_multi
+ @cache.write("foo", "bar")
+ @cache.write("fud", "biz")
+
+ values = @cache.fetch_multi("foo", "fu", "fud") { |value| value * 2 }
+
+ assert_equal({ "foo" => "bar", "fu" => "fufu", "fud" => "biz" }, values)
+ assert_equal("fufu", @cache.read("fu"))
+ end
+
+ def test_multi_with_objects
+ cache_struct = Struct.new(:cache_key, :title)
+ foo = cache_struct.new("foo", "FOO!")
+ bar = cache_struct.new("bar")
+
+ @cache.write("bar", "BAM!")
+
+ values = @cache.fetch_multi(foo, bar) { |object| object.title }
+
+ assert_equal({ foo => "FOO!", bar => "BAM!" }, values)
+ end
+
+ def test_fetch_multi_without_block
+ assert_raises(ArgumentError) do
+ @cache.fetch_multi("foo")
+ end
+ end
+
+ def test_read_and_write_compressed_small_data
+ @cache.write("foo", "bar", compress: true)
+ assert_equal "bar", @cache.read("foo")
+ end
+
+ def test_read_and_write_compressed_large_data
+ @cache.write("foo", "bar", compress: true, compress_threshold: 2)
+ assert_equal "bar", @cache.read("foo")
+ end
+
+ def test_read_and_write_compressed_nil
+ @cache.write("foo", nil, compress: true)
+ assert_nil @cache.read("foo")
+ end
+
+ def test_cache_key
+ obj = Object.new
+ def obj.cache_key
+ :foo
+ end
+ @cache.write(obj, "bar")
+ assert_equal "bar", @cache.read("foo")
+ end
+
+ def test_param_as_cache_key
+ obj = Object.new
+ def obj.to_param
+ "foo"
+ end
+ @cache.write(obj, "bar")
+ assert_equal "bar", @cache.read("foo")
+ end
+
+ def test_array_as_cache_key
+ @cache.write([:fu, "foo"], "bar")
+ assert_equal "bar", @cache.read("fu/foo")
+ end
+
+ def test_hash_as_cache_key
+ @cache.write({ foo: 1, fu: 2 }, "bar")
+ assert_equal "bar", @cache.read("foo=1/fu=2")
+ end
+
+ def test_keys_are_case_sensitive
+ @cache.write("foo", "bar")
+ assert_nil @cache.read("FOO")
+ end
+
+ def test_exist
+ @cache.write("foo", "bar")
+ assert_equal true, @cache.exist?("foo")
+ assert_equal false, @cache.exist?("bar")
+ end
+
+ def test_nil_exist
+ @cache.write("foo", nil)
+ assert @cache.exist?("foo")
+ end
+
+ def test_delete
+ @cache.write("foo", "bar")
+ assert @cache.exist?("foo")
+ assert @cache.delete("foo")
+ assert !@cache.exist?("foo")
+ end
+
+ def test_original_store_objects_should_not_be_immutable
+ bar = "bar".dup
+ @cache.write("foo", bar)
+ assert_nothing_raised { bar.gsub!(/.*/, "baz") }
+ end
+
+ def test_expires_in
+ time = Time.local(2008, 4, 24)
+
+ Time.stub(:now, time) do
+ @cache.write("foo", "bar")
+ assert_equal "bar", @cache.read("foo")
+ end
+
+ Time.stub(:now, time + 30) do
+ assert_equal "bar", @cache.read("foo")
+ end
+
+ Time.stub(:now, time + 61) do
+ assert_nil @cache.read("foo")
+ end
+ end
+
+ def test_race_condition_protection_skipped_if_not_defined
+ @cache.write("foo", "bar")
+ time = @cache.send(:read_entry, @cache.send(:normalize_key, "foo", {}), {}).expires_at
+
+ Time.stub(:now, Time.at(time)) do
+ result = @cache.fetch("foo") do
+ assert_nil @cache.read("foo")
+ "baz"
+ end
+ assert_equal "baz", result
+ end
+ end
+
+ def test_race_condition_protection_is_limited
+ time = Time.now
+ @cache.write("foo", "bar", expires_in: 60)
+ Time.stub(:now, time + 71) do
+ result = @cache.fetch("foo", race_condition_ttl: 10) do
+ assert_nil @cache.read("foo")
+ "baz"
+ end
+ assert_equal "baz", result
+ end
+ end
+
+ def test_race_condition_protection_is_safe
+ time = Time.now
+ @cache.write("foo", "bar", expires_in: 60)
+ Time.stub(:now, time + 61) do
+ begin
+ @cache.fetch("foo", race_condition_ttl: 10) do
+ assert_equal "bar", @cache.read("foo")
+ raise ArgumentError.new
+ end
+ rescue ArgumentError
+ end
+ assert_equal "bar", @cache.read("foo")
+ end
+ Time.stub(:now, time + 91) do
+ assert_nil @cache.read("foo")
+ end
+ end
+
+ def test_race_condition_protection
+ time = Time.now
+ @cache.write("foo", "bar", expires_in: 60)
+ Time.stub(:now, time + 61) do
+ result = @cache.fetch("foo", race_condition_ttl: 10) do
+ assert_equal "bar", @cache.read("foo")
+ "baz"
+ end
+ assert_equal "baz", result
+ end
+ end
+
+ def test_crazy_key_characters
+ crazy_key = "#/:*(<+=> )&$%@?;'\"\'`~-"
+ assert @cache.write(crazy_key, "1", raw: true)
+ assert_equal "1", @cache.read(crazy_key)
+ assert_equal "1", @cache.fetch(crazy_key)
+ assert @cache.delete(crazy_key)
+ assert_equal "2", @cache.fetch(crazy_key, raw: true) { "2" }
+ assert_equal 3, @cache.increment(crazy_key)
+ assert_equal 2, @cache.decrement(crazy_key)
+ end
+
+ def test_really_long_keys
+ key = "".dup
+ 900.times { key << "x" }
+ assert @cache.write(key, "bar")
+ assert_equal "bar", @cache.read(key)
+ assert_equal "bar", @cache.fetch(key)
+ assert_nil @cache.read("#{key}x")
+ assert_equal({ key => "bar" }, @cache.read_multi(key))
+ assert @cache.delete(key)
+ end
+
+ def test_cache_hit_instrumentation
+ key = "test_key"
+ @events = []
+ ActiveSupport::Notifications.subscribe "cache_read.active_support" do |*args|
+ @events << ActiveSupport::Notifications::Event.new(*args)
+ end
+ assert @cache.write(key, "1", raw: true)
+ assert @cache.fetch(key) {}
+ assert_equal 1, @events.length
+ assert_equal "cache_read.active_support", @events[0].name
+ assert_equal :fetch, @events[0].payload[:super_operation]
+ assert @events[0].payload[:hit]
+ ensure
+ ActiveSupport::Notifications.unsubscribe "cache_read.active_support"
+ end
+
+ def test_cache_miss_instrumentation
+ @events = []
+ ActiveSupport::Notifications.subscribe(/^cache_(.*)\.active_support$/) do |*args|
+ @events << ActiveSupport::Notifications::Event.new(*args)
+ end
+ assert_not @cache.fetch("bad_key") {}
+ assert_equal 3, @events.length
+ assert_equal "cache_read.active_support", @events[0].name
+ assert_equal "cache_generate.active_support", @events[1].name
+ assert_equal "cache_write.active_support", @events[2].name
+ assert_equal :fetch, @events[0].payload[:super_operation]
+ assert_not @events[0].payload[:hit]
+ ensure
+ ActiveSupport::Notifications.unsubscribe "cache_read.active_support"
+ end
+end
diff --git a/activesupport/test/cache/behaviors/cache_store_version_behavior.rb b/activesupport/test/cache/behaviors/cache_store_version_behavior.rb
new file mode 100644
index 0000000000..a0170c896f
--- /dev/null
+++ b/activesupport/test/cache/behaviors/cache_store_version_behavior.rb
@@ -0,0 +1,86 @@
+module CacheStoreVersionBehavior
+ ModelWithKeyAndVersion = Struct.new(:cache_key, :cache_version)
+
+ def test_fetch_with_right_version_should_hit
+ @cache.fetch("foo", version: 1) { "bar" }
+ assert_equal "bar", @cache.read("foo", version: 1)
+ end
+
+ def test_fetch_with_wrong_version_should_miss
+ @cache.fetch("foo", version: 1) { "bar" }
+ assert_nil @cache.read("foo", version: 2)
+ end
+
+ def test_read_with_right_version_should_hit
+ @cache.write("foo", "bar", version: 1)
+ assert_equal "bar", @cache.read("foo", version: 1)
+ end
+
+ def test_read_with_wrong_version_should_miss
+ @cache.write("foo", "bar", version: 1)
+ assert_nil @cache.read("foo", version: 2)
+ end
+
+ def test_exist_with_right_version_should_be_true
+ @cache.write("foo", "bar", version: 1)
+ assert @cache.exist?("foo", version: 1)
+ end
+
+ def test_exist_with_wrong_version_should_be_false
+ @cache.write("foo", "bar", version: 1)
+ assert !@cache.exist?("foo", version: 2)
+ end
+
+ def test_reading_and_writing_with_model_supporting_cache_version
+ m1v1 = ModelWithKeyAndVersion.new("model/1", 1)
+ m1v2 = ModelWithKeyAndVersion.new("model/1", 2)
+
+ @cache.write(m1v1, "bar")
+ assert_equal "bar", @cache.read(m1v1)
+ assert_nil @cache.read(m1v2)
+ end
+
+ def test_reading_and_writing_with_model_supporting_cache_version_using_nested_key
+ m1v1 = ModelWithKeyAndVersion.new("model/1", 1)
+ m1v2 = ModelWithKeyAndVersion.new("model/1", 2)
+
+ @cache.write([ "something", m1v1 ], "bar")
+ assert_equal "bar", @cache.read([ "something", m1v1 ])
+ assert_nil @cache.read([ "something", m1v2 ])
+ end
+
+ def test_fetching_with_model_supporting_cache_version
+ m1v1 = ModelWithKeyAndVersion.new("model/1", 1)
+ m1v2 = ModelWithKeyAndVersion.new("model/1", 2)
+
+ @cache.fetch(m1v1) { "bar" }
+ assert_equal "bar", @cache.fetch(m1v1) { "bu" }
+ assert_equal "bu", @cache.fetch(m1v2) { "bu" }
+ end
+
+ def test_exist_with_model_supporting_cache_version
+ m1v1 = ModelWithKeyAndVersion.new("model/1", 1)
+ m1v2 = ModelWithKeyAndVersion.new("model/1", 2)
+
+ @cache.write(m1v1, "bar")
+ assert @cache.exist?(m1v1)
+ assert_not @cache.fetch(m1v2)
+ end
+
+ def test_fetch_multi_with_model_supporting_cache_version
+ m1v1 = ModelWithKeyAndVersion.new("model/1", 1)
+ m2v1 = ModelWithKeyAndVersion.new("model/2", 1)
+ m2v2 = ModelWithKeyAndVersion.new("model/2", 2)
+
+ first_fetch_values = @cache.fetch_multi(m1v1, m2v1) { |m| m.cache_key }
+ second_fetch_values = @cache.fetch_multi(m1v1, m2v2) { |m| m.cache_key + " 2nd" }
+
+ assert_equal({ m1v1 => "model/1", m2v1 => "model/2" }, first_fetch_values)
+ assert_equal({ m1v1 => "model/1", m2v2 => "model/2 2nd" }, second_fetch_values)
+ end
+
+ def test_version_is_normalized
+ @cache.write("foo", "bar", version: 1)
+ assert_equal "bar", @cache.read("foo", version: "1")
+ end
+end
diff --git a/activesupport/test/cache/behaviors/encoded_key_cache_behavior.rb b/activesupport/test/cache/behaviors/encoded_key_cache_behavior.rb
new file mode 100644
index 0000000000..6adef2916d
--- /dev/null
+++ b/activesupport/test/cache/behaviors/encoded_key_cache_behavior.rb
@@ -0,0 +1,34 @@
+# https://rails.lighthouseapp.com/projects/8994/tickets/6225-memcachestore-cant-deal-with-umlauts-and-special-characters
+# The error is caused by character encodings that can't be compared with ASCII-8BIT regular expressions and by special
+# characters like the umlaut in UTF-8.
+module EncodedKeyCacheBehavior
+ Encoding.list.each do |encoding|
+ define_method "test_#{encoding.name.underscore}_encoded_values" do
+ key = "foo".dup.force_encoding(encoding)
+ assert @cache.write(key, "1", raw: true)
+ assert_equal "1", @cache.read(key)
+ assert_equal "1", @cache.fetch(key)
+ assert @cache.delete(key)
+ assert_equal "2", @cache.fetch(key, raw: true) { "2" }
+ assert_equal 3, @cache.increment(key)
+ assert_equal 2, @cache.decrement(key)
+ end
+ end
+
+ def test_common_utf8_values
+ key = "\xC3\xBCmlaut".dup.force_encoding(Encoding::UTF_8)
+ assert @cache.write(key, "1", raw: true)
+ assert_equal "1", @cache.read(key)
+ assert_equal "1", @cache.fetch(key)
+ assert @cache.delete(key)
+ assert_equal "2", @cache.fetch(key, raw: true) { "2" }
+ assert_equal 3, @cache.increment(key)
+ assert_equal 2, @cache.decrement(key)
+ end
+
+ def test_retains_encoding
+ key = "\xC3\xBCmlaut".dup.force_encoding(Encoding::UTF_8)
+ assert @cache.write(key, "1", raw: true)
+ assert_equal Encoding::UTF_8, key.encoding
+ end
+end
diff --git a/activesupport/test/cache/behaviors/local_cache_behavior.rb b/activesupport/test/cache/behaviors/local_cache_behavior.rb
new file mode 100644
index 0000000000..8530296374
--- /dev/null
+++ b/activesupport/test/cache/behaviors/local_cache_behavior.rb
@@ -0,0 +1,126 @@
+module LocalCacheBehavior
+ def test_local_writes_are_persistent_on_the_remote_cache
+ retval = @cache.with_local_cache do
+ @cache.write("foo", "bar")
+ end
+ assert retval
+ assert_equal "bar", @cache.read("foo")
+ end
+
+ def test_clear_also_clears_local_cache
+ @cache.with_local_cache do
+ @cache.write("foo", "bar")
+ @cache.clear
+ assert_nil @cache.read("foo")
+ end
+
+ assert_nil @cache.read("foo")
+ end
+
+ def test_cleanup_clears_local_cache_but_not_remote_cache
+ skip unless @cache.class.instance_methods(false).include?(:cleanup)
+
+ @cache.with_local_cache do
+ @cache.write("foo", "bar")
+ assert_equal "bar", @cache.read("foo")
+
+ @cache.send(:bypass_local_cache) { @cache.write("foo", "baz") }
+ assert_equal "bar", @cache.read("foo")
+
+ @cache.cleanup
+ assert_equal "baz", @cache.read("foo")
+ end
+ end
+
+ def test_local_cache_of_write
+ @cache.with_local_cache do
+ @cache.write("foo", "bar")
+ @peek.delete("foo")
+ assert_equal "bar", @cache.read("foo")
+ end
+ end
+
+ def test_local_cache_of_read
+ @cache.write("foo", "bar")
+ @cache.with_local_cache do
+ assert_equal "bar", @cache.read("foo")
+ end
+ end
+
+ def test_local_cache_of_read_nil
+ @cache.with_local_cache do
+ assert_nil @cache.read("foo")
+ @cache.send(:bypass_local_cache) { @cache.write "foo", "bar" }
+ assert_nil @cache.read("foo")
+ end
+ end
+
+ def test_local_cache_fetch
+ @cache.with_local_cache do
+ @cache.send(:local_cache).write "foo", "bar"
+ assert_equal "bar", @cache.send(:local_cache).fetch("foo")
+ end
+ end
+
+ def test_local_cache_of_write_nil
+ @cache.with_local_cache do
+ assert @cache.write("foo", nil)
+ assert_nil @cache.read("foo")
+ @peek.write("foo", "bar")
+ assert_nil @cache.read("foo")
+ end
+ end
+
+ def test_local_cache_of_write_with_unless_exist
+ @cache.with_local_cache do
+ @cache.write("foo", "bar")
+ @cache.write("foo", "baz", unless_exist: true)
+ assert_equal @peek.read("foo"), @cache.read("foo")
+ end
+ end
+
+ def test_local_cache_of_delete
+ @cache.with_local_cache do
+ @cache.write("foo", "bar")
+ @cache.delete("foo")
+ assert_nil @cache.read("foo")
+ end
+ end
+
+ def test_local_cache_of_exist
+ @cache.with_local_cache do
+ @cache.write("foo", "bar")
+ @peek.delete("foo")
+ assert @cache.exist?("foo")
+ end
+ end
+
+ def test_local_cache_of_increment
+ @cache.with_local_cache do
+ @cache.write("foo", 1, raw: true)
+ @peek.write("foo", 2, raw: true)
+ @cache.increment("foo")
+ assert_equal 3, @cache.read("foo")
+ end
+ end
+
+ def test_local_cache_of_decrement
+ @cache.with_local_cache do
+ @cache.write("foo", 1, raw: true)
+ @peek.write("foo", 3, raw: true)
+ @cache.decrement("foo")
+ assert_equal 2, @cache.read("foo")
+ end
+ end
+
+ def test_middleware
+ app = lambda { |env|
+ result = @cache.write("foo", "bar")
+ assert_equal "bar", @cache.read("foo") # make sure 'foo' was written
+ assert result
+ [200, {}, []]
+ }
+ app = @cache.middleware.new(app)
+ app.call({})
+ end
+end
diff --git a/activesupport/test/cache/cache_entry_test.rb b/activesupport/test/cache/cache_entry_test.rb
new file mode 100644
index 0000000000..e446e39b10
--- /dev/null
+++ b/activesupport/test/cache/cache_entry_test.rb
@@ -0,0 +1,28 @@
+require "abstract_unit"
+require "active_support/cache"
+
+class CacheEntryTest < ActiveSupport::TestCase
+ def test_expired
+ entry = ActiveSupport::Cache::Entry.new("value")
+ assert !entry.expired?, "entry not expired"
+ entry = ActiveSupport::Cache::Entry.new("value", expires_in: 60)
+ assert !entry.expired?, "entry not expired"
+ Time.stub(:now, Time.now + 61) do
+ assert entry.expired?, "entry is expired"
+ end
+ end
+
+ def test_compress_values
+ value = "value" * 100
+ entry = ActiveSupport::Cache::Entry.new(value, compress: true, compress_threshold: 1)
+ assert_equal value, entry.value
+ assert(value.bytesize > entry.size, "value is compressed")
+ end
+
+ def test_non_compress_values
+ value = "value" * 100
+ entry = ActiveSupport::Cache::Entry.new(value)
+ assert_equal value, entry.value
+ assert_equal value.bytesize, entry.size
+ end
+end
diff --git a/activesupport/test/cache/cache_key_test.rb b/activesupport/test/cache/cache_key_test.rb
new file mode 100644
index 0000000000..ea39d7a299
--- /dev/null
+++ b/activesupport/test/cache/cache_key_test.rb
@@ -0,0 +1,88 @@
+require "abstract_unit"
+require "active_support/cache"
+
+class CacheKeyTest < ActiveSupport::TestCase
+ def test_entry_legacy_optional_ivars
+ legacy = Class.new(ActiveSupport::Cache::Entry) do
+ def initialize(value, options = {})
+ @value = value
+ @expires_in = nil
+ @created_at = nil
+ super
+ end
+ end
+
+ entry = legacy.new "foo"
+ assert_equal "foo", entry.value
+ end
+
+ def test_expand_cache_key
+ assert_equal "1/2/true", ActiveSupport::Cache.expand_cache_key([1, "2", true])
+ assert_equal "name/1/2/true", ActiveSupport::Cache.expand_cache_key([1, "2", true], :name)
+ end
+
+ def test_expand_cache_key_with_rails_cache_id
+ with_env("RAILS_CACHE_ID" => "c99") do
+ assert_equal "c99/foo", ActiveSupport::Cache.expand_cache_key(:foo)
+ assert_equal "c99/foo", ActiveSupport::Cache.expand_cache_key([:foo])
+ assert_equal "c99/foo/bar", ActiveSupport::Cache.expand_cache_key([:foo, :bar])
+ assert_equal "nm/c99/foo", ActiveSupport::Cache.expand_cache_key(:foo, :nm)
+ assert_equal "nm/c99/foo", ActiveSupport::Cache.expand_cache_key([:foo], :nm)
+ assert_equal "nm/c99/foo/bar", ActiveSupport::Cache.expand_cache_key([:foo, :bar], :nm)
+ end
+ end
+
+ def test_expand_cache_key_with_rails_app_version
+ with_env("RAILS_APP_VERSION" => "rails3") do
+ assert_equal "rails3/foo", ActiveSupport::Cache.expand_cache_key(:foo)
+ end
+ end
+
+ def test_expand_cache_key_rails_cache_id_should_win_over_rails_app_version
+ with_env("RAILS_CACHE_ID" => "c99", "RAILS_APP_VERSION" => "rails3") do
+ assert_equal "c99/foo", ActiveSupport::Cache.expand_cache_key(:foo)
+ end
+ end
+
+ def test_expand_cache_key_respond_to_cache_key
+ key = "foo".dup
+ def key.cache_key
+ :foo_key
+ end
+ assert_equal "foo_key", ActiveSupport::Cache.expand_cache_key(key)
+ end
+
+ def test_expand_cache_key_array_with_something_that_responds_to_cache_key
+ key = "foo".dup
+ def key.cache_key
+ :foo_key
+ end
+ assert_equal "foo_key", ActiveSupport::Cache.expand_cache_key([key])
+ end
+
+ def test_expand_cache_key_of_nil
+ assert_equal "", ActiveSupport::Cache.expand_cache_key(nil)
+ end
+
+ def test_expand_cache_key_of_false
+ assert_equal "false", ActiveSupport::Cache.expand_cache_key(false)
+ end
+
+ def test_expand_cache_key_of_true
+ assert_equal "true", ActiveSupport::Cache.expand_cache_key(true)
+ end
+
+ def test_expand_cache_key_of_array_like_object
+ assert_equal "foo/bar/baz", ActiveSupport::Cache.expand_cache_key(%w{foo bar baz}.to_enum)
+ end
+
+ private
+
+ def with_env(kv)
+ old_values = {}
+ kv.each { |key, value| old_values[key], ENV[key] = ENV[key], value }
+ yield
+ ensure
+ old_values.each { |key, value| ENV[key] = value }
+ end
+end
diff --git a/activesupport/test/cache/cache_store_logger_test.rb b/activesupport/test/cache/cache_store_logger_test.rb
new file mode 100644
index 0000000000..621cfebb10
--- /dev/null
+++ b/activesupport/test/cache/cache_store_logger_test.rb
@@ -0,0 +1,34 @@
+require "abstract_unit"
+require "active_support/cache"
+
+class CacheStoreLoggerTest < ActiveSupport::TestCase
+ def setup
+ @cache = ActiveSupport::Cache.lookup_store(:memory_store)
+
+ @buffer = StringIO.new
+ @cache.logger = ActiveSupport::Logger.new(@buffer)
+ end
+
+ def test_logging
+ @cache.fetch("foo") { "bar" }
+ assert @buffer.string.present?
+ end
+
+ def test_log_with_string_namespace
+ @cache.fetch("foo", namespace: "string_namespace") { "bar" }
+ assert_match %r{string_namespace:foo}, @buffer.string
+ end
+
+ def test_log_with_proc_namespace
+ proc = Proc.new do
+ "proc_namespace"
+ end
+ @cache.fetch("foo", namespace: proc) { "bar" }
+ assert_match %r{proc_namespace:foo}, @buffer.string
+ end
+
+ def test_mute_logging
+ @cache.mute { @cache.fetch("foo") { "bar" } }
+ assert @buffer.string.blank?
+ end
+end
diff --git a/activesupport/test/cache/cache_store_namespace_test.rb b/activesupport/test/cache/cache_store_namespace_test.rb
new file mode 100644
index 0000000000..e395c88271
--- /dev/null
+++ b/activesupport/test/cache/cache_store_namespace_test.rb
@@ -0,0 +1,38 @@
+require "abstract_unit"
+require "active_support/cache"
+
+class CacheStoreNamespaceTest < ActiveSupport::TestCase
+ def test_static_namespace
+ cache = ActiveSupport::Cache.lookup_store(:memory_store, namespace: "tester")
+ cache.write("foo", "bar")
+ assert_equal "bar", cache.read("foo")
+ assert_equal "bar", cache.instance_variable_get(:@data)["tester:foo"].value
+ end
+
+ def test_proc_namespace
+ test_val = "tester"
+ proc = lambda { test_val }
+ cache = ActiveSupport::Cache.lookup_store(:memory_store, namespace: proc)
+ cache.write("foo", "bar")
+ assert_equal "bar", cache.read("foo")
+ assert_equal "bar", cache.instance_variable_get(:@data)["tester:foo"].value
+ end
+
+ def test_delete_matched_key_start
+ cache = ActiveSupport::Cache.lookup_store(:memory_store, namespace: "tester")
+ cache.write("foo", "bar")
+ cache.write("fu", "baz")
+ cache.delete_matched(/^fo/)
+ assert !cache.exist?("foo")
+ assert cache.exist?("fu")
+ end
+
+ def test_delete_matched_key
+ cache = ActiveSupport::Cache.lookup_store(:memory_store, namespace: "foo")
+ cache.write("foo", "bar")
+ cache.write("fu", "baz")
+ cache.delete_matched(/OO/i)
+ assert !cache.exist?("foo")
+ assert cache.exist?("fu")
+ end
+end
diff --git a/activesupport/test/cache/cache_store_setting_test.rb b/activesupport/test/cache/cache_store_setting_test.rb
new file mode 100644
index 0000000000..cb9b006abe
--- /dev/null
+++ b/activesupport/test/cache/cache_store_setting_test.rb
@@ -0,0 +1,66 @@
+require "abstract_unit"
+require "active_support/cache"
+require "dalli"
+
+class CacheStoreSettingTest < ActiveSupport::TestCase
+ def test_memory_store_gets_created_if_no_arguments_passed_to_lookup_store_method
+ store = ActiveSupport::Cache.lookup_store
+ assert_kind_of(ActiveSupport::Cache::MemoryStore, store)
+ end
+
+ def test_memory_store
+ store = ActiveSupport::Cache.lookup_store :memory_store
+ assert_kind_of(ActiveSupport::Cache::MemoryStore, store)
+ end
+
+ def test_file_fragment_cache_store
+ store = ActiveSupport::Cache.lookup_store :file_store, "/path/to/cache/directory"
+ assert_kind_of(ActiveSupport::Cache::FileStore, store)
+ assert_equal "/path/to/cache/directory", store.cache_path
+ end
+
+ def test_mem_cache_fragment_cache_store
+ assert_called_with(Dalli::Client, :new, [%w[localhost], {}]) do
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost"
+ assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ end
+ end
+
+ def test_mem_cache_fragment_cache_store_with_given_mem_cache
+ mem_cache = Dalli::Client.new
+ assert_not_called(Dalli::Client, :new) do
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store, mem_cache
+ assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ end
+ end
+
+ def test_mem_cache_fragment_cache_store_with_not_dalli_client
+ assert_not_called(Dalli::Client, :new) do
+ memcache = Object.new
+ assert_raises(ArgumentError) do
+ ActiveSupport::Cache.lookup_store :mem_cache_store, memcache
+ end
+ end
+ end
+
+ def test_mem_cache_fragment_cache_store_with_multiple_servers
+ assert_called_with(Dalli::Client, :new, [%w[localhost 192.168.1.1], {}]) do
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", "192.168.1.1"
+ assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ end
+ end
+
+ def test_mem_cache_fragment_cache_store_with_options
+ assert_called_with(Dalli::Client, :new, [%w[localhost 192.168.1.1], { timeout: 10 }]) do
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", "192.168.1.1", namespace: "foo", timeout: 10
+ assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ assert_equal "foo", store.options[:namespace]
+ end
+ end
+
+ def test_object_assigned_fragment_cache_store
+ store = ActiveSupport::Cache.lookup_store ActiveSupport::Cache::FileStore.new("/path/to/cache/directory")
+ assert_kind_of(ActiveSupport::Cache::FileStore, store)
+ assert_equal "/path/to/cache/directory", store.cache_path
+ end
+end
diff --git a/activesupport/test/cache/cache_store_write_multi_test.rb b/activesupport/test/cache/cache_store_write_multi_test.rb
new file mode 100644
index 0000000000..16e3f3b842
--- /dev/null
+++ b/activesupport/test/cache/cache_store_write_multi_test.rb
@@ -0,0 +1,60 @@
+require "abstract_unit"
+require "active_support/cache"
+
+class CacheStoreWriteMultiEntriesStoreProviderInterfaceTest < ActiveSupport::TestCase
+ setup do
+ @cache = ActiveSupport::Cache.lookup_store(:null_store)
+ end
+
+ test "fetch_multi uses write_multi_entries store provider interface" do
+ assert_called_with(@cache, :write_multi_entries) do
+ @cache.fetch_multi "a", "b", "c" do |key|
+ key * 2
+ end
+ end
+ end
+end
+
+class CacheStoreWriteMultiInstrumentationTest < ActiveSupport::TestCase
+ setup do
+ @cache = ActiveSupport::Cache.lookup_store(:null_store)
+ end
+
+ test "instrumentation" do
+ writes = { "a" => "aa", "b" => "bb" }
+
+ events = with_instrumentation "write_multi" do
+ @cache.write_multi(writes)
+ end
+
+ assert_equal %w[ cache_write_multi.active_support ], events.map(&:name)
+ assert_nil events[0].payload[:super_operation]
+ assert_equal({ "a" => "aa", "b" => "bb" }, events[0].payload[:key])
+ end
+
+ test "instrumentation with fetch_multi as super operation" do
+ skip "fetch_multi isn't instrumented yet"
+
+ events = with_instrumentation "write_multi" do
+ @cache.fetch_multi("a", "b") { |key| key * 2 }
+ end
+
+ assert_equal %w[ cache_write_multi.active_support ], events.map(&:name)
+ assert_nil events[0].payload[:super_operation]
+ assert !events[0].payload[:hit]
+ end
+
+ private
+ def with_instrumentation(method)
+ event_name = "cache_#{method}.active_support"
+
+ [].tap do |events|
+ ActiveSupport::Notifications.subscribe event_name do |*args|
+ events << ActiveSupport::Notifications::Event.new(*args)
+ end
+ yield
+ end
+ ensure
+ ActiveSupport::Notifications.unsubscribe event_name
+ end
+end
diff --git a/activesupport/test/cache/local_cache_middleware_test.rb b/activesupport/test/cache/local_cache_middleware_test.rb
new file mode 100644
index 0000000000..352502fb43
--- /dev/null
+++ b/activesupport/test/cache/local_cache_middleware_test.rb
@@ -0,0 +1,61 @@
+require "abstract_unit"
+require "active_support/cache"
+
+module ActiveSupport
+ module Cache
+ module Strategy
+ module LocalCache
+ class MiddlewareTest < ActiveSupport::TestCase
+ def test_local_cache_cleared_on_close
+ key = "super awesome key"
+ assert_nil LocalCacheRegistry.cache_for key
+ middleware = Middleware.new("<3", key).new(->(env) {
+ assert LocalCacheRegistry.cache_for(key), "should have a cache"
+ [200, {}, []]
+ })
+ _, _, body = middleware.call({})
+ assert LocalCacheRegistry.cache_for(key), "should still have a cache"
+ body.each {}
+ assert LocalCacheRegistry.cache_for(key), "should still have a cache"
+ body.close
+ assert_nil LocalCacheRegistry.cache_for(key)
+ end
+
+ def test_local_cache_cleared_and_response_should_be_present_on_invalid_parameters_error
+ key = "super awesome key"
+ assert_nil LocalCacheRegistry.cache_for key
+ middleware = Middleware.new("<3", key).new(->(env) {
+ assert LocalCacheRegistry.cache_for(key), "should have a cache"
+ raise Rack::Utils::InvalidParameterError
+ })
+ response = middleware.call({})
+ assert response, "response should exist"
+ assert_nil LocalCacheRegistry.cache_for(key)
+ end
+
+ def test_local_cache_cleared_on_exception
+ key = "super awesome key"
+ assert_nil LocalCacheRegistry.cache_for key
+ middleware = Middleware.new("<3", key).new(->(env) {
+ assert LocalCacheRegistry.cache_for(key), "should have a cache"
+ raise
+ })
+ assert_raises(RuntimeError) { middleware.call({}) }
+ assert_nil LocalCacheRegistry.cache_for(key)
+ end
+
+ def test_local_cache_cleared_on_throw
+ key = "super awesome key"
+ assert_nil LocalCacheRegistry.cache_for key
+ middleware = Middleware.new("<3", key).new(->(env) {
+ assert LocalCacheRegistry.cache_for(key), "should have a cache"
+ throw :warden
+ })
+ assert_throws(:warden) { middleware.call({}) }
+ assert_nil LocalCacheRegistry.cache_for(key)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activesupport/test/cache/stores/file_store_test.rb b/activesupport/test/cache/stores/file_store_test.rb
new file mode 100644
index 0000000000..48b304fe6e
--- /dev/null
+++ b/activesupport/test/cache/stores/file_store_test.rb
@@ -0,0 +1,128 @@
+require "abstract_unit"
+require "active_support/cache"
+require_relative "../behaviors"
+require "pathname"
+
+class FileStoreTest < ActiveSupport::TestCase
+ def setup
+ Dir.mkdir(cache_dir) unless File.exist?(cache_dir)
+ @cache = ActiveSupport::Cache.lookup_store(:file_store, cache_dir, expires_in: 60)
+ @peek = ActiveSupport::Cache.lookup_store(:file_store, cache_dir, expires_in: 60)
+ @cache_with_pathname = ActiveSupport::Cache.lookup_store(:file_store, Pathname.new(cache_dir), expires_in: 60)
+
+ @buffer = StringIO.new
+ @cache.logger = ActiveSupport::Logger.new(@buffer)
+ end
+
+ def teardown
+ FileUtils.rm_r(cache_dir)
+ rescue Errno::ENOENT
+ end
+
+ def cache_dir
+ File.join(Dir.pwd, "tmp_cache")
+ end
+
+ include CacheStoreBehavior
+ include CacheStoreVersionBehavior
+ include LocalCacheBehavior
+ include CacheDeleteMatchedBehavior
+ include CacheIncrementDecrementBehavior
+ include AutoloadingCacheBehavior
+
+ def test_clear
+ gitkeep = File.join(cache_dir, ".gitkeep")
+ keep = File.join(cache_dir, ".keep")
+ FileUtils.touch([gitkeep, keep])
+ @cache.clear
+ assert File.exist?(gitkeep)
+ assert File.exist?(keep)
+ end
+
+ def test_clear_without_cache_dir
+ FileUtils.rm_r(cache_dir)
+ @cache.clear
+ end
+
+ def test_long_uri_encoded_keys
+ @cache.write("%" * 870, 1)
+ assert_equal 1, @cache.read("%" * 870)
+ end
+
+ def test_key_transformation
+ key = @cache.send(:normalize_key, "views/index?id=1", {})
+ assert_equal "views/index?id=1", @cache.send(:file_path_key, key)
+ end
+
+ def test_key_transformation_with_pathname
+ FileUtils.touch(File.join(cache_dir, "foo"))
+ key = @cache_with_pathname.send(:normalize_key, "views/index?id=1", {})
+ assert_equal "views/index?id=1", @cache_with_pathname.send(:file_path_key, key)
+ end
+
+ # Test that generated cache keys are short enough to have Tempfile stuff added to them and
+ # remain valid
+ def test_filename_max_size
+ key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}"
+ path = @cache.send(:normalize_key, key, {})
+ Dir::Tmpname.create(path) do |tmpname, n, opts|
+ assert File.basename(tmpname + ".lock").length <= 255, "Temp filename too long: #{File.basename(tmpname + '.lock').length}"
+ end
+ end
+
+ # Because file systems have a maximum filename size, filenames > max size should be split in to directories
+ # If filename is 'AAAAB', where max size is 4, the returned path should be AAAA/B
+ def test_key_transformation_max_filename_size
+ key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}B"
+ path = @cache.send(:normalize_key, key, {})
+ assert path.split("/").all? { |dir_name| dir_name.size <= ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE }
+ assert_equal "B", File.basename(path)
+ end
+
+ # If nothing has been stored in the cache, there is a chance the cache directory does not yet exist
+ # Ensure delete_matched gracefully handles this case
+ def test_delete_matched_when_cache_directory_does_not_exist
+ assert_nothing_raised do
+ ActiveSupport::Cache::FileStore.new("/test/cache/directory").delete_matched(/does_not_exist/)
+ end
+ end
+
+ def test_delete_does_not_delete_empty_parent_dir
+ sub_cache_dir = File.join(cache_dir, "subdir/")
+ sub_cache_store = ActiveSupport::Cache::FileStore.new(sub_cache_dir)
+ assert_nothing_raised do
+ assert sub_cache_store.write("foo", "bar")
+ assert sub_cache_store.delete("foo")
+ end
+ assert File.exist?(cache_dir), "Parent of top level cache dir was deleted!"
+ assert File.exist?(sub_cache_dir), "Top level cache dir was deleted!"
+ assert Dir.entries(sub_cache_dir).reject { |f| ActiveSupport::Cache::FileStore::EXCLUDED_DIRS.include?(f) }.empty?
+ end
+
+ def test_log_exception_when_cache_read_fails
+ File.stub(:exist?, -> { raise StandardError.new("failed") }) do
+ @cache.send(:read_entry, "winston", {})
+ assert @buffer.string.present?
+ end
+ end
+
+ def test_cleanup_removes_all_expired_entries
+ time = Time.now
+ @cache.write("foo", "bar", expires_in: 10)
+ @cache.write("baz", "qux")
+ @cache.write("quux", "corge", expires_in: 20)
+ Time.stub(:now, time + 15) do
+ @cache.cleanup
+ assert_not @cache.exist?("foo")
+ assert @cache.exist?("baz")
+ assert @cache.exist?("quux")
+ end
+ end
+
+ def test_write_with_unless_exist
+ assert_equal true, @cache.write(1, "aaaaaaaaaa")
+ assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true)
+ @cache.write(1, nil)
+ assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true)
+ end
+end
diff --git a/activesupport/test/cache/stores/mem_cache_store_test.rb b/activesupport/test/cache/stores/mem_cache_store_test.rb
new file mode 100644
index 0000000000..2dd5264818
--- /dev/null
+++ b/activesupport/test/cache/stores/mem_cache_store_test.rb
@@ -0,0 +1,74 @@
+require "abstract_unit"
+require "active_support/cache"
+require_relative "../behaviors"
+require "dalli"
+
+class MemCacheStoreTest < ActiveSupport::TestCase
+ begin
+ ss = Dalli::Client.new("localhost:11211").stats
+ raise Dalli::DalliError unless ss["localhost:11211"]
+
+ MEMCACHE_UP = true
+ rescue Dalli::DalliError
+ $stderr.puts "Skipping memcached tests. Start memcached and try again."
+ MEMCACHE_UP = false
+ end
+
+ def setup
+ skip "memcache server is not up" unless MEMCACHE_UP
+
+ @cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, expires_in: 60)
+ @peek = ActiveSupport::Cache.lookup_store(:mem_cache_store)
+ @data = @cache.instance_variable_get(:@data)
+ @cache.clear
+ @cache.silence!
+ @cache.logger = ActiveSupport::Logger.new("/dev/null")
+ end
+
+ include CacheStoreBehavior
+ include CacheStoreVersionBehavior
+ include LocalCacheBehavior
+ include CacheIncrementDecrementBehavior
+ include EncodedKeyCacheBehavior
+ include AutoloadingCacheBehavior
+
+ def test_raw_values
+ cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true)
+ cache.clear
+ cache.write("foo", 2)
+ assert_equal "2", cache.read("foo")
+ end
+
+ def test_raw_values_with_marshal
+ cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true)
+ cache.clear
+ cache.write("foo", Marshal.dump([]))
+ assert_equal [], cache.read("foo")
+ end
+
+ def test_local_cache_raw_values
+ cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true)
+ cache.clear
+ cache.with_local_cache do
+ cache.write("foo", 2)
+ assert_equal "2", cache.read("foo")
+ end
+ end
+
+ def test_local_cache_raw_values_with_marshal
+ cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true)
+ cache.clear
+ cache.with_local_cache do
+ cache.write("foo", Marshal.dump([]))
+ assert_equal [], cache.read("foo")
+ end
+ end
+
+ def test_read_should_return_a_different_object_id_each_time_it_is_called
+ @cache.write("foo", "bar")
+ value = @cache.read("foo")
+ assert_not_equal value.object_id, @cache.read("foo").object_id
+ value << "bingo"
+ assert_not_equal value, @cache.read("foo")
+ end
+end
diff --git a/activesupport/test/cache/stores/memory_store_test.rb b/activesupport/test/cache/stores/memory_store_test.rb
new file mode 100644
index 0000000000..3dd1646d56
--- /dev/null
+++ b/activesupport/test/cache/stores/memory_store_test.rb
@@ -0,0 +1,107 @@
+require "abstract_unit"
+require "active_support/cache"
+require_relative "../behaviors"
+
+class MemoryStoreTest < ActiveSupport::TestCase
+ def setup
+ @record_size = ActiveSupport::Cache.lookup_store(:memory_store).send(:cached_size, 1, ActiveSupport::Cache::Entry.new("aaaaaaaaaa"))
+ @cache = ActiveSupport::Cache.lookup_store(:memory_store, expires_in: 60, size: @record_size * 10 + 1)
+ end
+
+ include CacheStoreBehavior
+ include CacheStoreVersionBehavior
+ include CacheDeleteMatchedBehavior
+ include CacheIncrementDecrementBehavior
+
+ def test_prune_size
+ @cache.write(1, "aaaaaaaaaa") && sleep(0.001)
+ @cache.write(2, "bbbbbbbbbb") && sleep(0.001)
+ @cache.write(3, "cccccccccc") && sleep(0.001)
+ @cache.write(4, "dddddddddd") && sleep(0.001)
+ @cache.write(5, "eeeeeeeeee") && sleep(0.001)
+ @cache.read(2) && sleep(0.001)
+ @cache.read(4)
+ @cache.prune(@record_size * 3)
+ assert @cache.exist?(5)
+ assert @cache.exist?(4)
+ assert !@cache.exist?(3), "no entry"
+ assert @cache.exist?(2)
+ assert !@cache.exist?(1), "no entry"
+ end
+
+ def test_prune_size_on_write
+ @cache.write(1, "aaaaaaaaaa") && sleep(0.001)
+ @cache.write(2, "bbbbbbbbbb") && sleep(0.001)
+ @cache.write(3, "cccccccccc") && sleep(0.001)
+ @cache.write(4, "dddddddddd") && sleep(0.001)
+ @cache.write(5, "eeeeeeeeee") && sleep(0.001)
+ @cache.write(6, "ffffffffff") && sleep(0.001)
+ @cache.write(7, "gggggggggg") && sleep(0.001)
+ @cache.write(8, "hhhhhhhhhh") && sleep(0.001)
+ @cache.write(9, "iiiiiiiiii") && sleep(0.001)
+ @cache.write(10, "kkkkkkkkkk") && sleep(0.001)
+ @cache.read(2) && sleep(0.001)
+ @cache.read(4) && sleep(0.001)
+ @cache.write(11, "llllllllll")
+ assert @cache.exist?(11)
+ assert @cache.exist?(10)
+ assert @cache.exist?(9)
+ assert @cache.exist?(8)
+ assert @cache.exist?(7)
+ assert !@cache.exist?(6), "no entry"
+ assert !@cache.exist?(5), "no entry"
+ assert @cache.exist?(4)
+ assert !@cache.exist?(3), "no entry"
+ assert @cache.exist?(2)
+ assert !@cache.exist?(1), "no entry"
+ end
+
+ def test_prune_size_on_write_based_on_key_length
+ @cache.write(1, "aaaaaaaaaa") && sleep(0.001)
+ @cache.write(2, "bbbbbbbbbb") && sleep(0.001)
+ @cache.write(3, "cccccccccc") && sleep(0.001)
+ @cache.write(4, "dddddddddd") && sleep(0.001)
+ @cache.write(5, "eeeeeeeeee") && sleep(0.001)
+ @cache.write(6, "ffffffffff") && sleep(0.001)
+ @cache.write(7, "gggggggggg") && sleep(0.001)
+ @cache.write(8, "hhhhhhhhhh") && sleep(0.001)
+ @cache.write(9, "iiiiiiiiii") && sleep(0.001)
+ long_key = "*" * 2 * @record_size
+ @cache.write(long_key, "llllllllll")
+ assert @cache.exist?(long_key)
+ assert @cache.exist?(9)
+ assert @cache.exist?(8)
+ assert @cache.exist?(7)
+ assert @cache.exist?(6)
+ assert !@cache.exist?(5), "no entry"
+ assert !@cache.exist?(4), "no entry"
+ assert !@cache.exist?(3), "no entry"
+ assert !@cache.exist?(2), "no entry"
+ assert !@cache.exist?(1), "no entry"
+ end
+
+ def test_pruning_is_capped_at_a_max_time
+ def @cache.delete_entry(*args)
+ sleep(0.01)
+ super
+ end
+ @cache.write(1, "aaaaaaaaaa") && sleep(0.001)
+ @cache.write(2, "bbbbbbbbbb") && sleep(0.001)
+ @cache.write(3, "cccccccccc") && sleep(0.001)
+ @cache.write(4, "dddddddddd") && sleep(0.001)
+ @cache.write(5, "eeeeeeeeee") && sleep(0.001)
+ @cache.prune(30, 0.001)
+ assert @cache.exist?(5)
+ assert @cache.exist?(4)
+ assert @cache.exist?(3)
+ assert @cache.exist?(2)
+ assert !@cache.exist?(1)
+ end
+
+ def test_write_with_unless_exist
+ assert_equal true, @cache.write(1, "aaaaaaaaaa")
+ assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true)
+ @cache.write(1, nil)
+ assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true)
+ end
+end
diff --git a/activesupport/test/cache/stores/null_store_test.rb b/activesupport/test/cache/stores/null_store_test.rb
new file mode 100644
index 0000000000..23c4e64ee4
--- /dev/null
+++ b/activesupport/test/cache/stores/null_store_test.rb
@@ -0,0 +1,57 @@
+require "abstract_unit"
+require "active_support/cache"
+require_relative "../behaviors"
+
+class NullStoreTest < ActiveSupport::TestCase
+ def setup
+ @cache = ActiveSupport::Cache.lookup_store(:null_store)
+ end
+
+ def test_clear
+ @cache.clear
+ end
+
+ def test_cleanup
+ @cache.cleanup
+ end
+
+ def test_write
+ assert_equal true, @cache.write("name", "value")
+ end
+
+ def test_read
+ @cache.write("name", "value")
+ assert_nil @cache.read("name")
+ end
+
+ def test_delete
+ @cache.write("name", "value")
+ assert_equal false, @cache.delete("name")
+ end
+
+ def test_increment
+ @cache.write("name", 1, raw: true)
+ assert_nil @cache.increment("name")
+ end
+
+ def test_decrement
+ @cache.write("name", 1, raw: true)
+ assert_nil @cache.increment("name")
+ end
+
+ def test_delete_matched
+ @cache.write("name", "value")
+ @cache.delete_matched(/name/)
+ end
+
+ def test_local_store_strategy
+ @cache.with_local_cache do
+ @cache.write("name", "value")
+ assert_equal "value", @cache.read("name")
+ @cache.delete("name")
+ assert_nil @cache.read("name")
+ @cache.write("name", "value")
+ end
+ assert_nil @cache.read("name")
+ end
+end
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
deleted file mode 100644
index c67ffe69b8..0000000000
--- a/activesupport/test/caching_test.rb
+++ /dev/null
@@ -1,1203 +0,0 @@
-require "logger"
-require "abstract_unit"
-require "active_support/cache"
-require "dependencies_test_helpers"
-
-require "pathname"
-
-module ActiveSupport
- module Cache
- module Strategy
- module LocalCache
- class MiddlewareTest < ActiveSupport::TestCase
- def test_local_cache_cleared_on_close
- key = "super awesome key"
- assert_nil LocalCacheRegistry.cache_for key
- middleware = Middleware.new("<3", key).new(->(env) {
- assert LocalCacheRegistry.cache_for(key), "should have a cache"
- [200, {}, []]
- })
- _, _, body = middleware.call({})
- assert LocalCacheRegistry.cache_for(key), "should still have a cache"
- body.each {}
- assert LocalCacheRegistry.cache_for(key), "should still have a cache"
- body.close
- assert_nil LocalCacheRegistry.cache_for(key)
- end
-
- def test_local_cache_cleared_and_response_should_be_present_on_invalid_parameters_error
- key = "super awesome key"
- assert_nil LocalCacheRegistry.cache_for key
- middleware = Middleware.new("<3", key).new(->(env) {
- assert LocalCacheRegistry.cache_for(key), "should have a cache"
- raise Rack::Utils::InvalidParameterError
- })
- response = middleware.call({})
- assert response, "response should exist"
- assert_nil LocalCacheRegistry.cache_for(key)
- end
-
- def test_local_cache_cleared_on_exception
- key = "super awesome key"
- assert_nil LocalCacheRegistry.cache_for key
- middleware = Middleware.new("<3", key).new(->(env) {
- assert LocalCacheRegistry.cache_for(key), "should have a cache"
- raise
- })
- assert_raises(RuntimeError) { middleware.call({}) }
- assert_nil LocalCacheRegistry.cache_for(key)
- end
-
- def test_local_cache_cleared_on_throw
- key = "super awesome key"
- assert_nil LocalCacheRegistry.cache_for key
- middleware = Middleware.new("<3", key).new(->(env) {
- assert LocalCacheRegistry.cache_for(key), "should have a cache"
- throw :warden
- })
- assert_throws(:warden) { middleware.call({}) }
- assert_nil LocalCacheRegistry.cache_for(key)
- end
- end
- end
- end
- end
-end
-
-class CacheKeyTest < ActiveSupport::TestCase
- def test_entry_legacy_optional_ivars
- legacy = Class.new(ActiveSupport::Cache::Entry) do
- def initialize(value, options = {})
- @value = value
- @expires_in = nil
- @created_at = nil
- super
- end
- end
-
- entry = legacy.new "foo"
- assert_equal "foo", entry.value
- end
-
- def test_expand_cache_key
- assert_equal "1/2/true", ActiveSupport::Cache.expand_cache_key([1, "2", true])
- assert_equal "name/1/2/true", ActiveSupport::Cache.expand_cache_key([1, "2", true], :name)
- end
-
- def test_expand_cache_key_with_rails_cache_id
- with_env("RAILS_CACHE_ID" => "c99") do
- assert_equal "c99/foo", ActiveSupport::Cache.expand_cache_key(:foo)
- assert_equal "c99/foo", ActiveSupport::Cache.expand_cache_key([:foo])
- assert_equal "c99/foo/bar", ActiveSupport::Cache.expand_cache_key([:foo, :bar])
- assert_equal "nm/c99/foo", ActiveSupport::Cache.expand_cache_key(:foo, :nm)
- assert_equal "nm/c99/foo", ActiveSupport::Cache.expand_cache_key([:foo], :nm)
- assert_equal "nm/c99/foo/bar", ActiveSupport::Cache.expand_cache_key([:foo, :bar], :nm)
- end
- end
-
- def test_expand_cache_key_with_rails_app_version
- with_env("RAILS_APP_VERSION" => "rails3") do
- assert_equal "rails3/foo", ActiveSupport::Cache.expand_cache_key(:foo)
- end
- end
-
- def test_expand_cache_key_rails_cache_id_should_win_over_rails_app_version
- with_env("RAILS_CACHE_ID" => "c99", "RAILS_APP_VERSION" => "rails3") do
- assert_equal "c99/foo", ActiveSupport::Cache.expand_cache_key(:foo)
- end
- end
-
- def test_expand_cache_key_respond_to_cache_key
- key = "foo"
- def key.cache_key
- :foo_key
- end
- assert_equal "foo_key", ActiveSupport::Cache.expand_cache_key(key)
- end
-
- def test_expand_cache_key_array_with_something_that_responds_to_cache_key
- key = "foo"
- def key.cache_key
- :foo_key
- end
- assert_equal "foo_key", ActiveSupport::Cache.expand_cache_key([key])
- end
-
- def test_expand_cache_key_of_nil
- assert_equal "", ActiveSupport::Cache.expand_cache_key(nil)
- end
-
- def test_expand_cache_key_of_false
- assert_equal "false", ActiveSupport::Cache.expand_cache_key(false)
- end
-
- def test_expand_cache_key_of_true
- assert_equal "true", ActiveSupport::Cache.expand_cache_key(true)
- end
-
- def test_expand_cache_key_of_array_like_object
- assert_equal "foo/bar/baz", ActiveSupport::Cache.expand_cache_key(%w{foo bar baz}.to_enum)
- end
-
- private
-
- def with_env(kv)
- old_values = {}
- kv.each { |key, value| old_values[key], ENV[key] = ENV[key], value }
- yield
- ensure
- old_values.each { |key, value| ENV[key] = value }
- end
-end
-
-class CacheStoreSettingTest < ActiveSupport::TestCase
- def test_memory_store_gets_created_if_no_arguments_passed_to_lookup_store_method
- store = ActiveSupport::Cache.lookup_store
- assert_kind_of(ActiveSupport::Cache::MemoryStore, store)
- end
-
- def test_memory_store
- store = ActiveSupport::Cache.lookup_store :memory_store
- assert_kind_of(ActiveSupport::Cache::MemoryStore, store)
- end
-
- def test_file_fragment_cache_store
- store = ActiveSupport::Cache.lookup_store :file_store, "/path/to/cache/directory"
- assert_kind_of(ActiveSupport::Cache::FileStore, store)
- assert_equal "/path/to/cache/directory", store.cache_path
- end
-
- def test_mem_cache_fragment_cache_store
- assert_called_with(Dalli::Client, :new, [%w[localhost], {}]) do
- store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost"
- assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
- end
- end
-
- def test_mem_cache_fragment_cache_store_with_given_mem_cache
- mem_cache = Dalli::Client.new
- assert_not_called(Dalli::Client, :new) do
- store = ActiveSupport::Cache.lookup_store :mem_cache_store, mem_cache
- assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
- end
- end
-
- def test_mem_cache_fragment_cache_store_with_not_dalli_client
- assert_not_called(Dalli::Client, :new) do
- memcache = Object.new
- assert_raises(ArgumentError) do
- ActiveSupport::Cache.lookup_store :mem_cache_store, memcache
- end
- end
- end
-
- def test_mem_cache_fragment_cache_store_with_multiple_servers
- assert_called_with(Dalli::Client, :new, [%w[localhost 192.168.1.1], {}]) do
- store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", "192.168.1.1"
- assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
- end
- end
-
- def test_mem_cache_fragment_cache_store_with_options
- assert_called_with(Dalli::Client, :new, [%w[localhost 192.168.1.1], { timeout: 10 }]) do
- store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", "192.168.1.1", namespace: "foo", timeout: 10
- assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
- assert_equal "foo", store.options[:namespace]
- end
- end
-
- def test_object_assigned_fragment_cache_store
- store = ActiveSupport::Cache.lookup_store ActiveSupport::Cache::FileStore.new("/path/to/cache/directory")
- assert_kind_of(ActiveSupport::Cache::FileStore, store)
- assert_equal "/path/to/cache/directory", store.cache_path
- end
-end
-
-class CacheStoreNamespaceTest < ActiveSupport::TestCase
- def test_static_namespace
- cache = ActiveSupport::Cache.lookup_store(:memory_store, namespace: "tester")
- cache.write("foo", "bar")
- assert_equal "bar", cache.read("foo")
- assert_equal "bar", cache.instance_variable_get(:@data)["tester:foo"].value
- end
-
- def test_proc_namespace
- test_val = "tester"
- proc = lambda { test_val }
- cache = ActiveSupport::Cache.lookup_store(:memory_store, namespace: proc)
- cache.write("foo", "bar")
- assert_equal "bar", cache.read("foo")
- assert_equal "bar", cache.instance_variable_get(:@data)["tester:foo"].value
- end
-
- def test_delete_matched_key_start
- cache = ActiveSupport::Cache.lookup_store(:memory_store, namespace: "tester")
- cache.write("foo", "bar")
- cache.write("fu", "baz")
- cache.delete_matched(/^fo/)
- assert !cache.exist?("foo")
- assert cache.exist?("fu")
- end
-
- def test_delete_matched_key
- cache = ActiveSupport::Cache.lookup_store(:memory_store, namespace: "foo")
- cache.write("foo", "bar")
- cache.write("fu", "baz")
- cache.delete_matched(/OO/i)
- assert !cache.exist?("foo")
- assert cache.exist?("fu")
- end
-end
-
-# Tests the base functionality that should be identical across all cache stores.
-module CacheStoreBehavior
- def test_should_read_and_write_strings
- assert @cache.write("foo", "bar")
- assert_equal "bar", @cache.read("foo")
- end
-
- def test_should_overwrite
- @cache.write("foo", "bar")
- @cache.write("foo", "baz")
- assert_equal "baz", @cache.read("foo")
- end
-
- def test_fetch_without_cache_miss
- @cache.write("foo", "bar")
- assert_not_called(@cache, :write) do
- assert_equal "bar", @cache.fetch("foo") { "baz" }
- end
- end
-
- def test_fetch_with_cache_miss
- assert_called_with(@cache, :write, ["foo", "baz", @cache.options]) do
- assert_equal "baz", @cache.fetch("foo") { "baz" }
- end
- end
-
- def test_fetch_with_cache_miss_passes_key_to_block
- cache_miss = false
- assert_equal 3, @cache.fetch("foo") { |key| cache_miss = true; key.length }
- assert cache_miss
-
- cache_miss = false
- assert_equal 3, @cache.fetch("foo") { |key| cache_miss = true; key.length }
- assert !cache_miss
- end
-
- def test_fetch_with_forced_cache_miss
- @cache.write("foo", "bar")
- assert_not_called(@cache, :read) do
- assert_called_with(@cache, :write, ["foo", "bar", @cache.options.merge(force: true)]) do
- @cache.fetch("foo", force: true) { "bar" }
- end
- end
- end
-
- def test_fetch_with_cached_nil
- @cache.write("foo", nil)
- assert_not_called(@cache, :write) do
- assert_nil @cache.fetch("foo") { "baz" }
- end
- end
-
- def test_fetch_with_forced_cache_miss_with_block
- @cache.write("foo", "bar")
- assert_equal "foo_bar", @cache.fetch("foo", force: true) { "foo_bar" }
- end
-
- def test_fetch_with_forced_cache_miss_without_block
- @cache.write("foo", "bar")
- assert_raises(ArgumentError) do
- @cache.fetch("foo", force: true)
- end
-
- assert_equal "bar", @cache.read("foo")
- end
-
- def test_should_read_and_write_hash
- assert @cache.write("foo", a: "b")
- assert_equal({ a: "b" }, @cache.read("foo"))
- end
-
- def test_should_read_and_write_integer
- assert @cache.write("foo", 1)
- assert_equal 1, @cache.read("foo")
- end
-
- def test_should_read_and_write_nil
- assert @cache.write("foo", nil)
- assert_nil @cache.read("foo")
- end
-
- def test_should_read_and_write_false
- assert @cache.write("foo", false)
- assert_equal false, @cache.read("foo")
- end
-
- def test_read_multi
- @cache.write("foo", "bar")
- @cache.write("fu", "baz")
- @cache.write("fud", "biz")
- assert_equal({ "foo" => "bar", "fu" => "baz" }, @cache.read_multi("foo", "fu"))
- end
-
- def test_read_multi_with_expires
- time = Time.now
- @cache.write("foo", "bar", expires_in: 10)
- @cache.write("fu", "baz")
- @cache.write("fud", "biz")
- Time.stub(:now, time + 11) do
- assert_equal({ "fu" => "baz" }, @cache.read_multi("foo", "fu"))
- end
- end
-
- def test_fetch_multi
- @cache.write("foo", "bar")
- @cache.write("fud", "biz")
-
- values = @cache.fetch_multi("foo", "fu", "fud") { |value| value * 2 }
-
- assert_equal({ "foo" => "bar", "fu" => "fufu", "fud" => "biz" }, values)
- assert_equal("fufu", @cache.read("fu"))
- end
-
- def test_multi_with_objects
- cache_struct = Struct.new(:cache_key, :title)
- foo = cache_struct.new("foo", "FOO!")
- bar = cache_struct.new("bar")
-
- @cache.write("bar", "BAM!")
-
- values = @cache.fetch_multi(foo, bar) { |object| object.title }
-
- assert_equal({ foo => "FOO!", bar => "BAM!" }, values)
- end
-
- def test_fetch_multi_without_block
- assert_raises(ArgumentError) do
- @cache.fetch_multi("foo")
- end
- end
-
- def test_read_and_write_compressed_small_data
- @cache.write("foo", "bar", compress: true)
- assert_equal "bar", @cache.read("foo")
- end
-
- def test_read_and_write_compressed_large_data
- @cache.write("foo", "bar", compress: true, compress_threshold: 2)
- assert_equal "bar", @cache.read("foo")
- end
-
- def test_read_and_write_compressed_nil
- @cache.write("foo", nil, compress: true)
- assert_nil @cache.read("foo")
- end
-
- def test_cache_key
- obj = Object.new
- def obj.cache_key
- :foo
- end
- @cache.write(obj, "bar")
- assert_equal "bar", @cache.read("foo")
- end
-
- def test_param_as_cache_key
- obj = Object.new
- def obj.to_param
- "foo"
- end
- @cache.write(obj, "bar")
- assert_equal "bar", @cache.read("foo")
- end
-
- def test_array_as_cache_key
- @cache.write([:fu, "foo"], "bar")
- assert_equal "bar", @cache.read("fu/foo")
- end
-
- def test_hash_as_cache_key
- @cache.write({ foo: 1, fu: 2 }, "bar")
- assert_equal "bar", @cache.read("foo=1/fu=2")
- end
-
- def test_keys_are_case_sensitive
- @cache.write("foo", "bar")
- assert_nil @cache.read("FOO")
- end
-
- def test_exist
- @cache.write("foo", "bar")
- assert_equal true, @cache.exist?("foo")
- assert_equal false, @cache.exist?("bar")
- end
-
- def test_nil_exist
- @cache.write("foo", nil)
- assert @cache.exist?("foo")
- end
-
- def test_delete
- @cache.write("foo", "bar")
- assert @cache.exist?("foo")
- assert @cache.delete("foo")
- assert !@cache.exist?("foo")
- end
-
- def test_original_store_objects_should_not_be_immutable
- bar = "bar"
- @cache.write("foo", bar)
- assert_nothing_raised { bar.gsub!(/.*/, "baz") }
- end
-
- def test_expires_in
- time = Time.local(2008, 4, 24)
-
- Time.stub(:now, time) do
- @cache.write("foo", "bar")
- assert_equal "bar", @cache.read("foo")
- end
-
- Time.stub(:now, time + 30) do
- assert_equal "bar", @cache.read("foo")
- end
-
- Time.stub(:now, time + 61) do
- assert_nil @cache.read("foo")
- end
- end
-
- def test_race_condition_protection_skipped_if_not_defined
- @cache.write("foo", "bar")
- time = @cache.send(:read_entry, @cache.send(:normalize_key, "foo", {}), {}).expires_at
-
- Time.stub(:now, Time.at(time)) do
- result = @cache.fetch("foo") do
- assert_nil @cache.read("foo")
- "baz"
- end
- assert_equal "baz", result
- end
- end
-
- def test_race_condition_protection_is_limited
- time = Time.now
- @cache.write("foo", "bar", expires_in: 60)
- Time.stub(:now, time + 71) do
- result = @cache.fetch("foo", race_condition_ttl: 10) do
- assert_nil @cache.read("foo")
- "baz"
- end
- assert_equal "baz", result
- end
- end
-
- def test_race_condition_protection_is_safe
- time = Time.now
- @cache.write("foo", "bar", expires_in: 60)
- Time.stub(:now, time + 61) do
- begin
- @cache.fetch("foo", race_condition_ttl: 10) do
- assert_equal "bar", @cache.read("foo")
- raise ArgumentError.new
- end
- rescue ArgumentError
- end
- assert_equal "bar", @cache.read("foo")
- end
- Time.stub(:now, time + 91) do
- assert_nil @cache.read("foo")
- end
- end
-
- def test_race_condition_protection
- time = Time.now
- @cache.write("foo", "bar", expires_in: 60)
- Time.stub(:now, time + 61) do
- result = @cache.fetch("foo", race_condition_ttl: 10) do
- assert_equal "bar", @cache.read("foo")
- "baz"
- end
- assert_equal "baz", result
- end
- end
-
- def test_crazy_key_characters
- crazy_key = "#/:*(<+=> )&$%@?;'\"\'`~-"
- assert @cache.write(crazy_key, "1", raw: true)
- assert_equal "1", @cache.read(crazy_key)
- assert_equal "1", @cache.fetch(crazy_key)
- assert @cache.delete(crazy_key)
- assert_equal "2", @cache.fetch(crazy_key, raw: true) { "2" }
- assert_equal 3, @cache.increment(crazy_key)
- assert_equal 2, @cache.decrement(crazy_key)
- end
-
- def test_really_long_keys
- key = ""
- 900.times { key << "x" }
- assert @cache.write(key, "bar")
- assert_equal "bar", @cache.read(key)
- assert_equal "bar", @cache.fetch(key)
- assert_nil @cache.read("#{key}x")
- assert_equal({ key => "bar" }, @cache.read_multi(key))
- assert @cache.delete(key)
- end
-
- def test_cache_hit_instrumentation
- key = "test_key"
- @events = []
- ActiveSupport::Notifications.subscribe "cache_read.active_support" do |*args|
- @events << ActiveSupport::Notifications::Event.new(*args)
- end
- assert @cache.write(key, "1", raw: true)
- assert @cache.fetch(key) {}
- assert_equal 1, @events.length
- assert_equal "cache_read.active_support", @events[0].name
- assert_equal :fetch, @events[0].payload[:super_operation]
- assert @events[0].payload[:hit]
- ensure
- ActiveSupport::Notifications.unsubscribe "cache_read.active_support"
- end
-
- def test_cache_miss_instrumentation
- @events = []
- ActiveSupport::Notifications.subscribe(/^cache_(.*)\.active_support$/) do |*args|
- @events << ActiveSupport::Notifications::Event.new(*args)
- end
- assert_not @cache.fetch("bad_key") {}
- assert_equal 3, @events.length
- assert_equal "cache_read.active_support", @events[0].name
- assert_equal "cache_generate.active_support", @events[1].name
- assert_equal "cache_write.active_support", @events[2].name
- assert_equal :fetch, @events[0].payload[:super_operation]
- assert_not @events[0].payload[:hit]
- ensure
- ActiveSupport::Notifications.unsubscribe "cache_read.active_support"
- end
-end
-
-# https://rails.lighthouseapp.com/projects/8994/tickets/6225-memcachestore-cant-deal-with-umlauts-and-special-characters
-# The error is caused by character encodings that can't be compared with ASCII-8BIT regular expressions and by special
-# characters like the umlaut in UTF-8.
-module EncodedKeyCacheBehavior
- Encoding.list.each do |encoding|
- define_method "test_#{encoding.name.underscore}_encoded_values" do
- key = "foo".force_encoding(encoding)
- assert @cache.write(key, "1", raw: true)
- assert_equal "1", @cache.read(key)
- assert_equal "1", @cache.fetch(key)
- assert @cache.delete(key)
- assert_equal "2", @cache.fetch(key, raw: true) { "2" }
- assert_equal 3, @cache.increment(key)
- assert_equal 2, @cache.decrement(key)
- end
- end
-
- def test_common_utf8_values
- key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8)
- assert @cache.write(key, "1", raw: true)
- assert_equal "1", @cache.read(key)
- assert_equal "1", @cache.fetch(key)
- assert @cache.delete(key)
- assert_equal "2", @cache.fetch(key, raw: true) { "2" }
- assert_equal 3, @cache.increment(key)
- assert_equal 2, @cache.decrement(key)
- end
-
- def test_retains_encoding
- key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8)
- assert @cache.write(key, "1", raw: true)
- assert_equal Encoding::UTF_8, key.encoding
- end
-end
-
-module CacheDeleteMatchedBehavior
- def test_delete_matched
- @cache.write("foo", "bar")
- @cache.write("fu", "baz")
- @cache.write("foo/bar", "baz")
- @cache.write("fu/baz", "bar")
- @cache.delete_matched(/oo/)
- assert !@cache.exist?("foo")
- assert @cache.exist?("fu")
- assert !@cache.exist?("foo/bar")
- assert @cache.exist?("fu/baz")
- end
-end
-
-module CacheIncrementDecrementBehavior
- def test_increment
- @cache.write("foo", 1, raw: true)
- assert_equal 1, @cache.read("foo").to_i
- assert_equal 2, @cache.increment("foo")
- assert_equal 2, @cache.read("foo").to_i
- assert_equal 3, @cache.increment("foo")
- assert_equal 3, @cache.read("foo").to_i
- assert_nil @cache.increment("bar")
- end
-
- def test_decrement
- @cache.write("foo", 3, raw: true)
- assert_equal 3, @cache.read("foo").to_i
- assert_equal 2, @cache.decrement("foo")
- assert_equal 2, @cache.read("foo").to_i
- assert_equal 1, @cache.decrement("foo")
- assert_equal 1, @cache.read("foo").to_i
- assert_nil @cache.decrement("bar")
- end
-end
-
-module LocalCacheBehavior
- def test_local_writes_are_persistent_on_the_remote_cache
- retval = @cache.with_local_cache do
- @cache.write("foo", "bar")
- end
- assert retval
- assert_equal "bar", @cache.read("foo")
- end
-
- def test_clear_also_clears_local_cache
- @cache.with_local_cache do
- @cache.write("foo", "bar")
- @cache.clear
- assert_nil @cache.read("foo")
- end
-
- assert_nil @cache.read("foo")
- end
-
- def test_local_cache_of_write
- @cache.with_local_cache do
- @cache.write("foo", "bar")
- @peek.delete("foo")
- assert_equal "bar", @cache.read("foo")
- end
- end
-
- def test_local_cache_of_read
- @cache.write("foo", "bar")
- @cache.with_local_cache do
- assert_equal "bar", @cache.read("foo")
- end
- end
-
- def test_local_cache_of_read_nil
- @cache.with_local_cache do
- assert_nil @cache.read("foo")
- @cache.send(:bypass_local_cache) { @cache.write "foo", "bar" }
- assert_nil @cache.read("foo")
- end
- end
-
- def test_local_cache_fetch
- @cache.with_local_cache do
- @cache.send(:local_cache).write "foo", "bar"
- assert_equal "bar", @cache.send(:local_cache).fetch("foo")
- end
- end
-
- def test_local_cache_of_write_nil
- @cache.with_local_cache do
- assert @cache.write("foo", nil)
- assert_nil @cache.read("foo")
- @peek.write("foo", "bar")
- assert_nil @cache.read("foo")
- end
- end
-
- def test_local_cache_of_delete
- @cache.with_local_cache do
- @cache.write("foo", "bar")
- @cache.delete("foo")
- assert_nil @cache.read("foo")
- end
- end
-
- def test_local_cache_of_exist
- @cache.with_local_cache do
- @cache.write("foo", "bar")
- @peek.delete("foo")
- assert @cache.exist?("foo")
- end
- end
-
- def test_local_cache_of_increment
- @cache.with_local_cache do
- @cache.write("foo", 1, raw: true)
- @peek.write("foo", 2, raw: true)
- @cache.increment("foo")
- assert_equal 3, @cache.read("foo")
- end
- end
-
- def test_local_cache_of_decrement
- @cache.with_local_cache do
- @cache.write("foo", 1, raw: true)
- @peek.write("foo", 3, raw: true)
- @cache.decrement("foo")
- assert_equal 2, @cache.read("foo")
- end
- end
-
- def test_middleware
- app = lambda { |env|
- result = @cache.write("foo", "bar")
- assert_equal "bar", @cache.read("foo") # make sure 'foo' was written
- assert result
- [200, {}, []]
- }
- app = @cache.middleware.new(app)
- app.call({})
- end
-end
-
-module AutoloadingCacheBehavior
- include DependenciesTestHelpers
- def test_simple_autoloading
- with_autoloading_fixtures do
- @cache.write("foo", EM.new)
- end
-
- remove_constants(:EM)
- ActiveSupport::Dependencies.clear
-
- with_autoloading_fixtures do
- assert_kind_of EM, @cache.read("foo")
- end
-
- remove_constants(:EM)
- ActiveSupport::Dependencies.clear
- end
-
- def test_two_classes_autoloading
- with_autoloading_fixtures do
- @cache.write("foo", [EM.new, ClassFolder.new])
- end
-
- remove_constants(:EM, :ClassFolder)
- ActiveSupport::Dependencies.clear
-
- with_autoloading_fixtures do
- loaded = @cache.read("foo")
- assert_kind_of Array, loaded
- assert_equal 2, loaded.size
- assert_kind_of EM, loaded[0]
- assert_kind_of ClassFolder, loaded[1]
- end
-
- remove_constants(:EM, :ClassFolder)
- ActiveSupport::Dependencies.clear
- end
-end
-
-class FileStoreTest < ActiveSupport::TestCase
- def setup
- Dir.mkdir(cache_dir) unless File.exist?(cache_dir)
- @cache = ActiveSupport::Cache.lookup_store(:file_store, cache_dir, expires_in: 60)
- @peek = ActiveSupport::Cache.lookup_store(:file_store, cache_dir, expires_in: 60)
- @cache_with_pathname = ActiveSupport::Cache.lookup_store(:file_store, Pathname.new(cache_dir), expires_in: 60)
-
- @buffer = StringIO.new
- @cache.logger = ActiveSupport::Logger.new(@buffer)
- end
-
- def teardown
- FileUtils.rm_r(cache_dir)
- rescue Errno::ENOENT
- end
-
- def cache_dir
- File.join(Dir.pwd, "tmp_cache")
- end
-
- include CacheStoreBehavior
- include LocalCacheBehavior
- include CacheDeleteMatchedBehavior
- include CacheIncrementDecrementBehavior
- include AutoloadingCacheBehavior
-
- def test_clear
- gitkeep = File.join(cache_dir, ".gitkeep")
- keep = File.join(cache_dir, ".keep")
- FileUtils.touch([gitkeep, keep])
- @cache.clear
- assert File.exist?(gitkeep)
- assert File.exist?(keep)
- end
-
- def test_clear_without_cache_dir
- FileUtils.rm_r(cache_dir)
- @cache.clear
- end
-
- def test_long_uri_encoded_keys
- @cache.write("%" * 870, 1)
- assert_equal 1, @cache.read("%" * 870)
- end
-
- def test_key_transformation
- key = @cache.send(:normalize_key, "views/index?id=1", {})
- assert_equal "views/index?id=1", @cache.send(:file_path_key, key)
- end
-
- def test_key_transformation_with_pathname
- FileUtils.touch(File.join(cache_dir, "foo"))
- key = @cache_with_pathname.send(:normalize_key, "views/index?id=1", {})
- assert_equal "views/index?id=1", @cache_with_pathname.send(:file_path_key, key)
- end
-
- # Test that generated cache keys are short enough to have Tempfile stuff added to them and
- # remain valid
- def test_filename_max_size
- key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}"
- path = @cache.send(:normalize_key, key, {})
- Dir::Tmpname.create(path) do |tmpname, n, opts|
- assert File.basename(tmpname + ".lock").length <= 255, "Temp filename too long: #{File.basename(tmpname + '.lock').length}"
- end
- end
-
- # Because file systems have a maximum filename size, filenames > max size should be split in to directories
- # If filename is 'AAAAB', where max size is 4, the returned path should be AAAA/B
- def test_key_transformation_max_filename_size
- key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}B"
- path = @cache.send(:normalize_key, key, {})
- assert path.split("/").all? { |dir_name| dir_name.size <= ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE }
- assert_equal "B", File.basename(path)
- end
-
- # If nothing has been stored in the cache, there is a chance the cache directory does not yet exist
- # Ensure delete_matched gracefully handles this case
- def test_delete_matched_when_cache_directory_does_not_exist
- assert_nothing_raised do
- ActiveSupport::Cache::FileStore.new("/test/cache/directory").delete_matched(/does_not_exist/)
- end
- end
-
- def test_delete_does_not_delete_empty_parent_dir
- sub_cache_dir = File.join(cache_dir, "subdir/")
- sub_cache_store = ActiveSupport::Cache::FileStore.new(sub_cache_dir)
- assert_nothing_raised do
- assert sub_cache_store.write("foo", "bar")
- assert sub_cache_store.delete("foo")
- end
- assert File.exist?(cache_dir), "Parent of top level cache dir was deleted!"
- assert File.exist?(sub_cache_dir), "Top level cache dir was deleted!"
- assert Dir.entries(sub_cache_dir).reject { |f| ActiveSupport::Cache::FileStore::EXCLUDED_DIRS.include?(f) }.empty?
- end
-
- def test_log_exception_when_cache_read_fails
- File.stub(:exist?, -> { raise StandardError.new("failed") }) do
- @cache.send(:read_entry, "winston", {})
- assert @buffer.string.present?
- end
- end
-
- def test_cleanup_removes_all_expired_entries
- time = Time.now
- @cache.write("foo", "bar", expires_in: 10)
- @cache.write("baz", "qux")
- @cache.write("quux", "corge", expires_in: 20)
- Time.stub(:now, time + 15) do
- @cache.cleanup
- assert_not @cache.exist?("foo")
- assert @cache.exist?("baz")
- assert @cache.exist?("quux")
- end
- end
-
- def test_write_with_unless_exist
- assert_equal true, @cache.write(1, "aaaaaaaaaa")
- assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true)
- @cache.write(1, nil)
- assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true)
- end
-end
-
-class MemoryStoreTest < ActiveSupport::TestCase
- def setup
- @record_size = ActiveSupport::Cache.lookup_store(:memory_store).send(:cached_size, 1, ActiveSupport::Cache::Entry.new("aaaaaaaaaa"))
- @cache = ActiveSupport::Cache.lookup_store(:memory_store, expires_in: 60, size: @record_size * 10 + 1)
- end
-
- include CacheStoreBehavior
- include CacheDeleteMatchedBehavior
- include CacheIncrementDecrementBehavior
-
- def test_prune_size
- @cache.write(1, "aaaaaaaaaa") && sleep(0.001)
- @cache.write(2, "bbbbbbbbbb") && sleep(0.001)
- @cache.write(3, "cccccccccc") && sleep(0.001)
- @cache.write(4, "dddddddddd") && sleep(0.001)
- @cache.write(5, "eeeeeeeeee") && sleep(0.001)
- @cache.read(2) && sleep(0.001)
- @cache.read(4)
- @cache.prune(@record_size * 3)
- assert @cache.exist?(5)
- assert @cache.exist?(4)
- assert !@cache.exist?(3), "no entry"
- assert @cache.exist?(2)
- assert !@cache.exist?(1), "no entry"
- end
-
- def test_prune_size_on_write
- @cache.write(1, "aaaaaaaaaa") && sleep(0.001)
- @cache.write(2, "bbbbbbbbbb") && sleep(0.001)
- @cache.write(3, "cccccccccc") && sleep(0.001)
- @cache.write(4, "dddddddddd") && sleep(0.001)
- @cache.write(5, "eeeeeeeeee") && sleep(0.001)
- @cache.write(6, "ffffffffff") && sleep(0.001)
- @cache.write(7, "gggggggggg") && sleep(0.001)
- @cache.write(8, "hhhhhhhhhh") && sleep(0.001)
- @cache.write(9, "iiiiiiiiii") && sleep(0.001)
- @cache.write(10, "kkkkkkkkkk") && sleep(0.001)
- @cache.read(2) && sleep(0.001)
- @cache.read(4) && sleep(0.001)
- @cache.write(11, "llllllllll")
- assert @cache.exist?(11)
- assert @cache.exist?(10)
- assert @cache.exist?(9)
- assert @cache.exist?(8)
- assert @cache.exist?(7)
- assert !@cache.exist?(6), "no entry"
- assert !@cache.exist?(5), "no entry"
- assert @cache.exist?(4)
- assert !@cache.exist?(3), "no entry"
- assert @cache.exist?(2)
- assert !@cache.exist?(1), "no entry"
- end
-
- def test_prune_size_on_write_based_on_key_length
- @cache.write(1, "aaaaaaaaaa") && sleep(0.001)
- @cache.write(2, "bbbbbbbbbb") && sleep(0.001)
- @cache.write(3, "cccccccccc") && sleep(0.001)
- @cache.write(4, "dddddddddd") && sleep(0.001)
- @cache.write(5, "eeeeeeeeee") && sleep(0.001)
- @cache.write(6, "ffffffffff") && sleep(0.001)
- @cache.write(7, "gggggggggg") && sleep(0.001)
- @cache.write(8, "hhhhhhhhhh") && sleep(0.001)
- @cache.write(9, "iiiiiiiiii") && sleep(0.001)
- long_key = "*" * 2 * @record_size
- @cache.write(long_key, "llllllllll")
- assert @cache.exist?(long_key)
- assert @cache.exist?(9)
- assert @cache.exist?(8)
- assert @cache.exist?(7)
- assert @cache.exist?(6)
- assert !@cache.exist?(5), "no entry"
- assert !@cache.exist?(4), "no entry"
- assert !@cache.exist?(3), "no entry"
- assert !@cache.exist?(2), "no entry"
- assert !@cache.exist?(1), "no entry"
- end
-
- def test_pruning_is_capped_at_a_max_time
- def @cache.delete_entry(*args)
- sleep(0.01)
- super
- end
- @cache.write(1, "aaaaaaaaaa") && sleep(0.001)
- @cache.write(2, "bbbbbbbbbb") && sleep(0.001)
- @cache.write(3, "cccccccccc") && sleep(0.001)
- @cache.write(4, "dddddddddd") && sleep(0.001)
- @cache.write(5, "eeeeeeeeee") && sleep(0.001)
- @cache.prune(30, 0.001)
- assert @cache.exist?(5)
- assert @cache.exist?(4)
- assert @cache.exist?(3)
- assert @cache.exist?(2)
- assert !@cache.exist?(1)
- end
-
- def test_write_with_unless_exist
- assert_equal true, @cache.write(1, "aaaaaaaaaa")
- assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true)
- @cache.write(1, nil)
- assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true)
- end
-end
-
-class MemCacheStoreTest < ActiveSupport::TestCase
- require "dalli"
-
- begin
- ss = Dalli::Client.new("localhost:11211").stats
- raise Dalli::DalliError unless ss["localhost:11211"]
-
- MEMCACHE_UP = true
- rescue Dalli::DalliError
- $stderr.puts "Skipping memcached tests. Start memcached and try again."
- MEMCACHE_UP = false
- end
-
- def setup
- skip "memcache server is not up" unless MEMCACHE_UP
-
- @cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, expires_in: 60)
- @peek = ActiveSupport::Cache.lookup_store(:mem_cache_store)
- @data = @cache.instance_variable_get(:@data)
- @cache.clear
- @cache.silence!
- @cache.logger = ActiveSupport::Logger.new("/dev/null")
- end
-
- include CacheStoreBehavior
- include LocalCacheBehavior
- include CacheIncrementDecrementBehavior
- include EncodedKeyCacheBehavior
- include AutoloadingCacheBehavior
-
- def test_raw_values
- cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true)
- cache.clear
- cache.write("foo", 2)
- assert_equal "2", cache.read("foo")
- end
-
- def test_raw_values_with_marshal
- cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true)
- cache.clear
- cache.write("foo", Marshal.dump([]))
- assert_equal [], cache.read("foo")
- end
-
- def test_local_cache_raw_values
- cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true)
- cache.clear
- cache.with_local_cache do
- cache.write("foo", 2)
- assert_equal "2", cache.read("foo")
- end
- end
-
- def test_local_cache_raw_values_with_marshal
- cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true)
- cache.clear
- cache.with_local_cache do
- cache.write("foo", Marshal.dump([]))
- assert_equal [], cache.read("foo")
- end
- end
-
- def test_read_should_return_a_different_object_id_each_time_it_is_called
- @cache.write("foo", "bar")
- value = @cache.read("foo")
- assert_not_equal value.object_id, @cache.read("foo").object_id
- value << "bingo"
- assert_not_equal value, @cache.read("foo")
- end
-end
-
-class NullStoreTest < ActiveSupport::TestCase
- def setup
- @cache = ActiveSupport::Cache.lookup_store(:null_store)
- end
-
- def test_clear
- @cache.clear
- end
-
- def test_cleanup
- @cache.cleanup
- end
-
- def test_write
- assert_equal true, @cache.write("name", "value")
- end
-
- def test_read
- @cache.write("name", "value")
- assert_nil @cache.read("name")
- end
-
- def test_delete
- @cache.write("name", "value")
- assert_equal false, @cache.delete("name")
- end
-
- def test_increment
- @cache.write("name", 1, raw: true)
- assert_nil @cache.increment("name")
- end
-
- def test_decrement
- @cache.write("name", 1, raw: true)
- assert_nil @cache.increment("name")
- end
-
- def test_delete_matched
- @cache.write("name", "value")
- @cache.delete_matched(/name/)
- end
-
- def test_local_store_strategy
- @cache.with_local_cache do
- @cache.write("name", "value")
- assert_equal "value", @cache.read("name")
- @cache.delete("name")
- assert_nil @cache.read("name")
- @cache.write("name", "value")
- end
- assert_nil @cache.read("name")
- end
-end
-
-class CacheStoreLoggerTest < ActiveSupport::TestCase
- def setup
- @cache = ActiveSupport::Cache.lookup_store(:memory_store)
-
- @buffer = StringIO.new
- @cache.logger = ActiveSupport::Logger.new(@buffer)
- end
-
- def test_logging
- @cache.fetch("foo") { "bar" }
- assert @buffer.string.present?
- end
-
- def test_log_with_string_namespace
- @cache.fetch("foo", namespace: "string_namespace") { "bar" }
- assert_match %r{string_namespace:foo}, @buffer.string
- end
-
- def test_log_with_proc_namespace
- proc = Proc.new do
- "proc_namespace"
- end
- @cache.fetch("foo", namespace: proc) { "bar" }
- assert_match %r{proc_namespace:foo}, @buffer.string
- end
-
- def test_mute_logging
- @cache.mute { @cache.fetch("foo") { "bar" } }
- assert @buffer.string.blank?
- end
-end
-
-class CacheEntryTest < ActiveSupport::TestCase
- def test_expired
- entry = ActiveSupport::Cache::Entry.new("value")
- assert !entry.expired?, "entry not expired"
- entry = ActiveSupport::Cache::Entry.new("value", expires_in: 60)
- assert !entry.expired?, "entry not expired"
- Time.stub(:now, Time.now + 61) do
- assert entry.expired?, "entry is expired"
- end
- end
-
- def test_compress_values
- value = "value" * 100
- entry = ActiveSupport::Cache::Entry.new(value, compress: true, compress_threshold: 1)
- assert_equal value, entry.value
- assert(value.bytesize > entry.size, "value is compressed")
- end
-
- def test_non_compress_values
- value = "value" * 100
- entry = ActiveSupport::Cache::Entry.new(value)
- assert_equal value, entry.value
- assert_equal value.bytesize, entry.size
- end
-end
diff --git a/activesupport/test/core_ext/array/conversions_test.rb b/activesupport/test/core_ext/array/conversions_test.rb
index 29e661a99b..bbe81ae70c 100644
--- a/activesupport/test/core_ext/array/conversions_test.rb
+++ b/activesupport/test/core_ext/array/conversions_test.rb
@@ -58,7 +58,7 @@ class ToSentenceTest < ActiveSupport::TestCase
["one", "two"].to_sentence(passing: "invalid option")
end
- assert_equal exception.message, "Unknown key: :passing. Valid keys are: :words_connector, :two_words_connector, :last_word_connector, :locale"
+ assert_equal "Unknown key: :passing. Valid keys are: :words_connector, :two_words_connector, :last_word_connector, :locale", exception.message
end
def test_always_returns_string
diff --git a/activesupport/test/core_ext/class/attribute_test.rb b/activesupport/test/core_ext/class/attribute_test.rb
index 5a9ec78cc1..f16043c612 100644
--- a/activesupport/test/core_ext/class/attribute_test.rb
+++ b/activesupport/test/core_ext/class/attribute_test.rb
@@ -3,7 +3,11 @@ require "active_support/core_ext/class/attribute"
class ClassAttributeTest < ActiveSupport::TestCase
def setup
- @klass = Class.new { class_attribute :setting }
+ @klass = Class.new do
+ class_attribute :setting
+ class_attribute :timeout, default: 5
+ end
+
@sub = Class.new(@klass)
end
@@ -12,6 +16,10 @@ class ClassAttributeTest < ActiveSupport::TestCase
assert_nil @sub.setting
end
+ test "custom default" do
+ assert_equal 5, @klass.timeout
+ end
+
test "inheritable" do
@klass.setting = 1
assert_equal 1, @sub.setting
diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index 36f0ee22b8..276fa2bfd3 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -28,6 +28,28 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
end
end
+ def test_next_occur
+ datetime = DateTime.new(2016, 9, 24, 0, 0) # saturday
+ assert_equal datetime.next_occurring(:monday), datetime.since(2.days)
+ assert_equal datetime.next_occurring(:tuesday), datetime.since(3.days)
+ assert_equal datetime.next_occurring(:wednesday), datetime.since(4.days)
+ assert_equal datetime.next_occurring(:thursday), datetime.since(5.days)
+ assert_equal datetime.next_occurring(:friday), datetime.since(6.days)
+ assert_equal datetime.next_occurring(:saturday), datetime.since(1.week)
+ assert_equal datetime.next_occurring(:sunday), datetime.since(1.day)
+ end
+
+ def test_prev_occur
+ datetime = DateTime.new(2016, 9, 24, 0, 0) # saturday
+ assert_equal datetime.prev_occurring(:monday), datetime.ago(5.days)
+ assert_equal datetime.prev_occurring(:tuesday), datetime.ago(4.days)
+ assert_equal datetime.prev_occurring(:wednesday), datetime.ago(3.days)
+ assert_equal datetime.prev_occurring(:thursday), datetime.ago(2.days)
+ assert_equal datetime.prev_occurring(:friday), datetime.ago(1.day)
+ assert_equal datetime.prev_occurring(:saturday), datetime.ago(1.week)
+ assert_equal datetime.prev_occurring(:sunday), datetime.ago(6.days)
+ end
+
def test_readable_inspect
datetime = DateTime.new(2005, 2, 21, 14, 30, 0)
assert_equal "Mon, 21 Feb 2005 14:30:00 +0000", datetime.readable_inspect
@@ -166,6 +188,9 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal DateTime.civil(2005, 2, 22, 16, 45), DateTime.civil(2005, 2, 22, 15, 15, 10).change(hour: 16, min: 45)
assert_equal DateTime.civil(2005, 2, 22, 15, 45), DateTime.civil(2005, 2, 22, 15, 15, 10).change(min: 45)
+ # datetime with non-zero offset
+ assert_equal DateTime.civil(2005, 2, 22, 15, 15, 10, Rational(-5, 24)), DateTime.civil(2005, 2, 22, 15, 15, 10, 0).change(offset: Rational(-5, 24))
+
# datetime with fractions of a second
assert_equal DateTime.civil(2005, 2, 1, 15, 15, 10.7), DateTime.civil(2005, 2, 22, 15, 15, 10.7).change(day: 1)
assert_equal DateTime.civil(2005, 1, 2, 11, 22, Rational(33000008, 1000000)), DateTime.civil(2005, 1, 2, 11, 22, 33).change(usec: 8)
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index 1648a9b270..cd1b505c34 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -315,7 +315,7 @@ class DurationTest < ActiveSupport::TestCase
assert_equal(1, scalar <=> 5)
assert_equal(0, scalar <=> 10)
assert_equal(-1, scalar <=> 15)
- assert_equal(nil, scalar <=> "foo")
+ assert_nil(scalar <=> "foo")
end
def test_scalar_plus
@@ -337,6 +337,13 @@ class DurationTest < ActiveSupport::TestCase
assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
end
+ def test_scalar_plus_parts
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal({ days: 1, seconds: 10 }, (scalar + 1.day).parts)
+ assert_equal({ days: -1, seconds: 10 }, (scalar + -1.day).parts)
+ end
+
def test_scalar_minus
scalar = ActiveSupport::Duration::Scalar.new(10)
@@ -349,6 +356,9 @@ class DurationTest < ActiveSupport::TestCase
assert_equal 5, scalar - 5.seconds
assert_instance_of ActiveSupport::Duration, scalar - 5.seconds
+ assert_equal({ days: -1, seconds: 10 }, (scalar - 1.day).parts)
+ assert_equal({ days: 1, seconds: 10 }, (scalar - -1.day).parts)
+
exception = assert_raises(TypeError) do
scalar - "foo"
end
@@ -356,6 +366,13 @@ class DurationTest < ActiveSupport::TestCase
assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
end
+ def test_scalar_minus_parts
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal({ days: -1, seconds: 10 }, (scalar - 1.day).parts)
+ assert_equal({ days: 1, seconds: 10 }, (scalar - -1.day).parts)
+ end
+
def test_scalar_multiply
scalar = ActiveSupport::Duration::Scalar.new(5)
@@ -375,6 +392,14 @@ class DurationTest < ActiveSupport::TestCase
assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
end
+ def test_scalar_multiply_parts
+ scalar = ActiveSupport::Duration::Scalar.new(1)
+ assert_equal({ days: 2 }, (scalar * 2.days).parts)
+ assert_equal(172800, (scalar * 2.days).value)
+ assert_equal({ days: -2 }, (scalar * -2.days).parts)
+ assert_equal(-172800, (scalar * -2.days).value)
+ end
+
def test_scalar_divide
scalar = ActiveSupport::Duration::Scalar.new(10)
@@ -394,6 +419,15 @@ class DurationTest < ActiveSupport::TestCase
assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
end
+ def test_scalar_divide_parts
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal({ days: 2 }, (scalar / 5.days).parts)
+ assert_equal(172800, (scalar / 5.days).value)
+ assert_equal({ days: -2 }, (scalar / -5.days).parts)
+ assert_equal(-172800, (scalar / -5.days).value)
+ end
+
def test_twelve_months_equals_one_year
assert_equal 12.months, 1.year
end
diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb
index 4f1ab993b8..0b345ecf0f 100644
--- a/activesupport/test/core_ext/enumerable_test.rb
+++ b/activesupport/test/core_ext/enumerable_test.rb
@@ -171,10 +171,8 @@ class EnumerableTests < ActiveSupport::TestCase
assert_equal({ 5 => Payment.new(5), 15 => Payment.new(15), 10 => Payment.new(10) },
payments.index_by(&:price))
assert_equal Enumerator, payments.index_by.class
- if Enumerator.method_defined? :size
- assert_nil payments.index_by.size
- assert_equal 42, (1..42).index_by.size
- end
+ assert_nil payments.index_by.size
+ assert_equal 42, (1..42).index_by.size
assert_equal({ 5 => Payment.new(5), 15 => Payment.new(15), 10 => Payment.new(10) },
payments.index_by.each(&:price))
end
diff --git a/activesupport/test/core_ext/module/attribute_accessor_test.rb b/activesupport/test/core_ext/module/attribute_accessor_test.rb
index 464a000d59..9b185e9381 100644
--- a/activesupport/test/core_ext/module/attribute_accessor_test.rb
+++ b/activesupport/test/core_ext/module/attribute_accessor_test.rb
@@ -12,7 +12,14 @@ class ModuleAttributeAccessorTest < ActiveSupport::TestCase
cattr_accessor(:defa) { "default_accessor_value" }
cattr_reader(:defr) { "default_reader_value" }
cattr_writer(:defw) { "default_writer_value" }
+ cattr_accessor(:deff) { false }
cattr_accessor(:quux) { :quux }
+
+ cattr_accessor :def_accessor, default: "default_accessor_value"
+ cattr_reader :def_reader, default: "default_reader_value"
+ cattr_writer :def_writer, default: "default_writer_value"
+ cattr_accessor :def_false, default: false
+ cattr_accessor(:def_priority, default: false) { :no_priority }
end
@class = Class.new
@class.instance_eval { include m }
@@ -24,6 +31,21 @@ class ModuleAttributeAccessorTest < ActiveSupport::TestCase
assert_nil @object.foo
end
+ def test_mattr_default_keyword_arguments
+ assert_equal "default_accessor_value", @module.def_accessor
+ assert_equal "default_reader_value", @module.def_reader
+ assert_equal "default_writer_value", @module.class_variable_get(:@@def_writer)
+ end
+
+ def test_mattr_can_default_to_false
+ assert_equal false, @module.def_false
+ assert_equal false, @module.deff
+ end
+
+ def test_mattr_default_priority
+ assert_equal false, @module.def_priority
+ end
+
def test_should_set_mattr_value
@module.foo = :test
assert_equal :test, @object.foo
@@ -91,9 +113,23 @@ class ModuleAttributeAccessorTest < ActiveSupport::TestCase
assert_equal "default_writer_value", @module.class_variable_get("@@defw")
end
- def test_should_not_invoke_default_value_block_multiple_times
+ def test_method_invocation_should_not_invoke_the_default_block
count = 0
+
@module.cattr_accessor(:defcount) { count += 1 }
+
assert_equal 1, count
+ assert_no_difference "count" do
+ @module.defcount
+ end
+ end
+
+ def test_declaring_multiple_attributes_at_once_invokes_the_block_multiple_times
+ count = 0
+
+ @module.cattr_accessor(:defn1, :defn2) { count += 1 }
+
+ assert_equal 1, @module.defn1
+ assert_equal 2, @module.defn2
end
end
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index a17438bf4d..68bd5233bf 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -70,7 +70,23 @@ Product = Struct.new(:name) do
end
end
+module ExtraMissing
+ def method_missing(sym, *args)
+ if sym == :extra_missing
+ 42
+ else
+ super
+ end
+ end
+
+ def respond_to_missing?(sym, priv = false)
+ sym == :extra_missing || super
+ end
+end
+
DecoratedTester = Struct.new(:client) do
+ include ExtraMissing
+
delegate_missing_to :client
end
@@ -332,15 +348,15 @@ class ModuleTest < ActiveSupport::TestCase
assert has_block.hello?
end
- def test_delegate_to_missing_with_method
+ def test_delegate_missing_to_with_method
assert_equal "David", DecoratedTester.new(@david).name
end
- def test_delegate_to_missing_with_reserved_methods
+ def test_delegate_missing_to_with_reserved_methods
assert_equal "David", DecoratedReserved.new(@david).name
end
- def test_delegate_to_missing_does_not_delegate_to_private_methods
+ def test_delegate_missing_to_does_not_delegate_to_private_methods
e = assert_raises(NoMethodError) do
DecoratedReserved.new(@david).private_name
end
@@ -348,7 +364,7 @@ class ModuleTest < ActiveSupport::TestCase
assert_match(/undefined method `private_name' for/, e.message)
end
- def test_delegate_to_missing_does_not_delegate_to_fake_methods
+ def test_delegate_missing_to_does_not_delegate_to_fake_methods
e = assert_raises(NoMethodError) do
DecoratedReserved.new(@david).my_fake_method
end
@@ -356,8 +372,62 @@ class ModuleTest < ActiveSupport::TestCase
assert_match(/undefined method `my_fake_method' for/, e.message)
end
+ def test_delegate_missing_to_affects_respond_to
+ assert DecoratedTester.new(@david).respond_to?(:name)
+ assert_not DecoratedTester.new(@david).respond_to?(:private_name)
+ assert_not DecoratedTester.new(@david).respond_to?(:my_fake_method)
+
+ assert DecoratedTester.new(@david).respond_to?(:name, true)
+ assert_not DecoratedTester.new(@david).respond_to?(:private_name, true)
+ assert_not DecoratedTester.new(@david).respond_to?(:my_fake_method, true)
+ end
+
+ def test_delegate_missing_to_respects_superclass_missing
+ assert_equal 42, DecoratedTester.new(@david).extra_missing
+
+ assert_respond_to DecoratedTester.new(@david), :extra_missing
+ end
+
def test_delegate_with_case
event = Event.new(Tester.new)
assert_equal 1, event.foo
end
+
+ def test_private_delegate
+ location = Class.new do
+ def initialize(place)
+ @place = place
+ end
+
+ private(*delegate(:street, :city, to: :@place))
+ end
+
+ place = location.new(Somewhere.new("Such street", "Sad city"))
+
+ assert_not place.respond_to?(:street)
+ assert_not place.respond_to?(:city)
+
+ assert place.respond_to?(:street, true) # Asking for private method
+ assert place.respond_to?(:city, true)
+ end
+
+ def test_private_delegate_prefixed
+ location = Class.new do
+ def initialize(place)
+ @place = place
+ end
+
+ private(*delegate(:street, :city, to: :@place, prefix: :the))
+ end
+
+ place = location.new(Somewhere.new("Such street", "Sad city"))
+
+ assert_not place.respond_to?(:street)
+ assert_not place.respond_to?(:city)
+
+ assert_not place.respond_to?(:the_street)
+ assert place.respond_to?(:the_street, true)
+ assert_not place.respond_to?(:the_city)
+ assert place.respond_to?(:the_city, true)
+ end
end
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index a98951e889..daa8c3f2ef 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -233,7 +233,7 @@ class StringInflectionsTest < ActiveSupport::TestCase
def test_string_squish
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}
+ with tabs(\t\t), newlines(\n\n), unicode nextlines(\u0085\u0085) and many spaces( ). \u00a0\u2007}.dup
expected = "A string surrounded by various unicode spaces, " \
"with tabs( ), newlines( ), unicode nextlines( ) and many spaces( )."
@@ -303,8 +303,8 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
def test_truncate_multibyte
- assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding(Encoding::UTF_8),
- "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding(Encoding::UTF_8).truncate(10)
+ assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".dup.force_encoding(Encoding::UTF_8),
+ "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".dup.force_encoding(Encoding::UTF_8).truncate(10)
end
def test_truncate_should_not_be_html_safe
@@ -325,7 +325,7 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
def test_remove!
- original = "This is a very good day to die"
+ original = "This is a very good day to die".dup
assert_equal "This is a good day to die", original.remove!(" very")
assert_equal "This is a good day to die", original
assert_equal "This is a good day", original.remove!(" to ", /die/)
@@ -658,7 +658,7 @@ end
class OutputSafetyTest < ActiveSupport::TestCase
def setup
- @string = "hello"
+ @string = "hello".dup
@object = Class.new(Object) do
def to_s
"other"
@@ -734,7 +734,7 @@ class OutputSafetyTest < ActiveSupport::TestCase
end
test "Concatting safe onto unsafe yields unsafe" do
- @other_string = "other"
+ @other_string = "other".dup
string = @string.html_safe
@other_string.concat(string)
@@ -757,7 +757,7 @@ class OutputSafetyTest < ActiveSupport::TestCase
end
test "Concatting safe onto unsafe with << yields unsafe" do
- @other_string = "other"
+ @other_string = "other".dup
string = @string.html_safe
@other_string << string
@@ -813,7 +813,7 @@ class OutputSafetyTest < ActiveSupport::TestCase
test "Concatting an integer to safe always yields safe" do
string = @string.html_safe
string = string.concat(13)
- assert_equal "hello".concat(13), string
+ assert_equal "hello".dup.concat(13), string
assert string.html_safe?
end
@@ -868,7 +868,8 @@ end
class StringIndentTest < ActiveSupport::TestCase
test "does not indent strings that only contain newlines (edge cases)" do
- ["", "\n", "\n" * 7].each do |str|
+ ["", "\n", "\n" * 7].each do |string|
+ str = string.dup
assert_nil str.indent!(8)
assert_equal str, str.indent(8)
assert_equal str, str.indent(1, "\t")
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index bd644c8457..625a5bffb8 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -433,6 +433,13 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_raise(ArgumentError) { Time.new(2005, 2, 22, 15, 15, 45, "-08:00").change(nsec: 1000000000) }
end
+ def test_change_offset
+ assert_equal Time.new(2006, 2, 22, 15, 15, 10, "-08:00"), Time.new(2006, 2, 22, 15, 15, 10, "+01:00").change(offset: "-08:00")
+ assert_equal Time.new(2006, 2, 22, 15, 15, 10, -28800), Time.new(2006, 2, 22, 15, 15, 10, 3600).change(offset: -28800)
+ assert_raise(ArgumentError) { Time.new(2005, 2, 22, 15, 15, 45, "+01:00").change(usec: 1000000, offset: "-08:00") }
+ assert_raise(ArgumentError) { Time.new(2005, 2, 22, 15, 15, 45, "+01:00").change(nsec: 1000000000, offset: -28800) }
+ end
+
def test_advance
assert_equal Time.local(2006, 2, 28, 15, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(years: 1)
assert_equal Time.local(2005, 6, 28, 15, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(months: 4)
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index c3afe68378..70ae793cda 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -625,6 +625,12 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal "Fri, 31 Dec 1999 06:00:00 EST -05:00", @twz.change(hour: 6).inspect
assert_equal "Fri, 31 Dec 1999 19:15:00 EST -05:00", @twz.change(min: 15).inspect
assert_equal "Fri, 31 Dec 1999 19:00:30 EST -05:00", @twz.change(sec: 30).inspect
+ assert_equal "Fri, 31 Dec 1999 19:00:00 HST -10:00", @twz.change(offset: "-10:00").inspect
+ assert_equal "Fri, 31 Dec 1999 19:00:00 HST -10:00", @twz.change(offset: -36000).inspect
+ assert_equal "Fri, 31 Dec 1999 19:00:00 HST -10:00", @twz.change(zone: "Hawaii").inspect
+ assert_equal "Fri, 31 Dec 1999 19:00:00 HST -10:00", @twz.change(zone: -10).inspect
+ assert_equal "Fri, 31 Dec 1999 19:00:00 HST -10:00", @twz.change(zone: -36000).inspect
+ assert_equal "Fri, 31 Dec 1999 19:00:00 HST -10:00", @twz.change(zone: "Pacific/Honolulu").inspect
end
def test_change_at_dst_boundary
diff --git a/activesupport/test/current_attributes_test.rb b/activesupport/test/current_attributes_test.rb
new file mode 100644
index 0000000000..67ef6ef619
--- /dev/null
+++ b/activesupport/test/current_attributes_test.rb
@@ -0,0 +1,96 @@
+require "abstract_unit"
+
+class CurrentAttributesTest < ActiveSupport::TestCase
+ Person = Struct.new(:name, :time_zone)
+
+ class Current < ActiveSupport::CurrentAttributes
+ attribute :world, :account, :person, :request
+ delegate :time_zone, to: :person
+
+ resets { Time.zone = "UTC" }
+
+ def account=(account)
+ super
+ self.person = "#{account}'s person"
+ end
+
+ def person=(person)
+ super
+ Time.zone = person.try(:time_zone)
+ end
+
+ def request
+ "#{super} something"
+ end
+
+ def intro
+ "#{person.name}, in #{time_zone}"
+ end
+ end
+
+ setup { Current.reset }
+
+ test "read and write attribute" do
+ Current.world = "world/1"
+ assert_equal "world/1", Current.world
+ end
+
+ test "read overwritten attribute method" do
+ Current.request = "request/1"
+ assert_equal "request/1 something", Current.request
+ end
+
+ test "set attribute via overwritten method" do
+ Current.account = "account/1"
+ assert_equal "account/1", Current.account
+ assert_equal "account/1's person", Current.person
+ end
+
+ test "set auxiliary class via overwritten method" do
+ Current.person = Person.new("David", "Central Time (US & Canada)")
+ assert_equal "Central Time (US & Canada)", Time.zone.name
+ end
+
+ test "resets auxiliary class via callback" do
+ Current.person = Person.new("David", "Central Time (US & Canada)")
+ assert_equal "Central Time (US & Canada)", Time.zone.name
+
+ Current.reset
+ assert_equal "UTC", Time.zone.name
+ end
+
+ test "set attribute only via scope" do
+ Current.world = "world/1"
+
+ Current.set(world: "world/2") do
+ assert_equal "world/2", Current.world
+ end
+
+ assert_equal "world/1", Current.world
+ end
+
+ test "set multiple attributes" do
+ Current.world = "world/1"
+ Current.account = "account/1"
+
+ Current.set(world: "world/2", account: "account/2") do
+ assert_equal "world/2", Current.world
+ assert_equal "account/2", Current.account
+ end
+
+ assert_equal "world/1", Current.world
+ assert_equal "account/1", Current.account
+ end
+
+ test "delegation" do
+ Current.person = Person.new("David", "Central Time (US & Canada)")
+ assert_equal "Central Time (US & Canada)", Current.time_zone
+ assert_equal "Central Time (US & Canada)", Current.instance.time_zone
+ end
+
+ test "all methods forward to the instance" do
+ Current.person = Person.new("David", "Central Time (US & Canada)")
+ assert_equal "David, in Central Time (US & Canada)", Current.intro
+ assert_equal "David, in Central Time (US & Canada)", Current.instance.intro
+ end
+end
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index e38d4e83e5..1ea36418ff 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -104,7 +104,7 @@ class DependenciesTest < ActiveSupport::TestCase
with_loading "dependencies" do
old_warnings, ActiveSupport::Dependencies.warnings_on_first_load = ActiveSupport::Dependencies.warnings_on_first_load, true
filename = "check_warnings"
- expanded = File.expand_path("#{File.dirname(__FILE__)}/dependencies/#{filename}")
+ expanded = File.expand_path("dependencies/#{filename}", __dir__)
$check_warnings_load_count = 0
assert_not ActiveSupport::Dependencies.loaded.include?(expanded)
@@ -293,7 +293,7 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_doesnt_break_normal_require
- path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
+ path = File.expand_path("autoloading_fixtures/load_path", __dir__)
original_path = $:.dup
$:.push(path)
with_autoloading_fixtures do
@@ -312,7 +312,7 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_doesnt_break_normal_require_nested
- path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
+ path = File.expand_path("autoloading_fixtures/load_path", __dir__)
original_path = $:.dup
$:.push(path)
@@ -332,7 +332,7 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_require_returns_true_when_file_not_yet_required
- path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
+ path = File.expand_path("autoloading_fixtures/load_path", __dir__)
original_path = $:.dup
$:.push(path)
@@ -345,7 +345,7 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_require_returns_true_when_file_not_yet_required_even_when_no_new_constants_added
- path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
+ path = File.expand_path("autoloading_fixtures/load_path", __dir__)
original_path = $:.dup
$:.push(path)
@@ -359,7 +359,7 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_require_returns_false_when_file_already_required
- path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
+ path = File.expand_path("autoloading_fixtures/load_path", __dir__)
original_path = $:.dup
$:.push(path)
@@ -379,7 +379,7 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_load_returns_true_when_file_found
- path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
+ path = File.expand_path("autoloading_fixtures/load_path", __dir__)
original_path = $:.dup
$:.push(path)
@@ -438,7 +438,7 @@ class DependenciesTest < ActiveSupport::TestCase
def test_loadable_constants_for_path_should_handle_relative_paths
fake_root = "dependencies"
- relative_root = File.dirname(__FILE__) + "/dependencies"
+ relative_root = File.expand_path("dependencies", __dir__)
["", "/"].each do |suffix|
with_loading fake_root + suffix do
assert_equal ["A::B"], ActiveSupport::Dependencies.loadable_constants_for_path(relative_root + "/a/b")
@@ -463,7 +463,7 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_loadable_constants_with_load_path_without_trailing_slash
- path = File.dirname(__FILE__) + "/autoloading_fixtures/class_folder/inline_class.rb"
+ path = File.expand_path("autoloading_fixtures/class_folder/inline_class.rb", __dir__)
with_loading "autoloading_fixtures/class/" do
assert_equal [], ActiveSupport::Dependencies.loadable_constants_for_path(path)
end
@@ -991,7 +991,7 @@ class DependenciesTest < ActiveSupport::TestCase
def test_remove_constant_does_not_trigger_loading_autoloads
constant = "ShouldNotBeAutoloaded"
Object.class_eval do
- autoload constant, File.expand_path("../autoloading_fixtures/should_not_be_required", __FILE__)
+ autoload constant, File.expand_path("autoloading_fixtures/should_not_be_required", __dir__)
end
assert_nil ActiveSupport::Dependencies.remove_constant(constant), "Kernel#autoload has been triggered by remove_constant"
diff --git a/activesupport/test/dependencies_test_helpers.rb b/activesupport/test/dependencies_test_helpers.rb
index 9bc63ed89e..451195a143 100644
--- a/activesupport/test/dependencies_test_helpers.rb
+++ b/activesupport/test/dependencies_test_helpers.rb
@@ -1,7 +1,7 @@
module DependenciesTestHelpers
def with_loading(*from)
old_mechanism, ActiveSupport::Dependencies.mechanism = ActiveSupport::Dependencies.mechanism, :load
- this_dir = File.dirname(__FILE__)
+ this_dir = __dir__
parent_dir = File.dirname(this_dir)
path_copy = $LOAD_PATH.dup
$LOAD_PATH.unshift(parent_dir) unless $LOAD_PATH.include?(parent_dir)
diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb
index 36d1ef0849..257cb50fb2 100644
--- a/activesupport/test/deprecation_test.rb
+++ b/activesupport/test/deprecation_test.rb
@@ -100,16 +100,18 @@ class DeprecationTest < ActiveSupport::TestCase
end
def test_several_behaviors
- @a, @b = nil, nil
+ @a, @b, @c = nil, nil, nil
ActiveSupport::Deprecation.behavior = [
- Proc.new { |msg, callstack| @a = msg },
- Proc.new { |msg, callstack| @b = msg }
+ lambda { |msg, callstack, horizon, gem| @a = msg },
+ lambda { |msg, callstack| @b = msg },
+ lambda { |*args| @c = args },
]
@dtc.partially
assert_match(/foo=nil/, @a)
assert_match(/foo=nil/, @b)
+ assert_equal 4, @c.size
end
def test_raise_behaviour
@@ -119,7 +121,7 @@ class DeprecationTest < ActiveSupport::TestCase
callstack = caller_locations
e = assert_raise ActiveSupport::DeprecationException do
- ActiveSupport::Deprecation.behavior.first.call(message, callstack)
+ ActiveSupport::Deprecation.behavior.first.call(message, callstack, "horizon", "gem")
end
assert_equal message, e.message
assert_equal callstack.map(&:to_s), e.backtrace.map(&:to_s)
@@ -130,7 +132,7 @@ class DeprecationTest < ActiveSupport::TestCase
behavior = ActiveSupport::Deprecation.behavior.first
content = capture(:stderr) {
- assert_nil behavior.call("Some error!", ["call stack!"])
+ assert_nil behavior.call("Some error!", ["call stack!"], "horizon", "gem")
}
assert_match(/Some error!/, content)
assert_match(/call stack!/, content)
@@ -152,11 +154,32 @@ class DeprecationTest < ActiveSupport::TestCase
behavior = ActiveSupport::Deprecation.behavior.first
stderr_output = capture(:stderr) {
- assert_nil behavior.call("Some error!", ["call stack!"])
+ assert_nil behavior.call("Some error!", ["call stack!"], "horizon", "gem")
}
assert stderr_output.empty?
end
+ def test_default_notify_behavior
+ ActiveSupport::Deprecation.behavior = :notify
+ behavior = ActiveSupport::Deprecation.behavior.first
+
+ begin
+ events = []
+ ActiveSupport::Notifications.subscribe("deprecation.my_gem_custom") { |_, **args|
+ events << args
+ }
+
+ assert_nil behavior.call("Some error!", ["call stack!"], "horizon", "MyGem::Custom")
+ assert_equal 1, events.size
+ assert_equal "Some error!", events.first[:message]
+ assert_equal ["call stack!"], events.first[:callstack]
+ assert_equal "horizon", events.first[:deprecation_horizon]
+ assert_equal "MyGem::Custom", events.first[:gem_name]
+ ensure
+ ActiveSupport::Notifications.unsubscribe("deprecation.my_gem_custom")
+ end
+ end
+
def test_deprecated_instance_variable_proxy
assert_not_deprecated { @dtc.request.size }
diff --git a/activesupport/test/hash_with_indifferent_access_test.rb b/activesupport/test/hash_with_indifferent_access_test.rb
index ee862320e8..d68add46cd 100644
--- a/activesupport/test/hash_with_indifferent_access_test.rb
+++ b/activesupport/test/hash_with_indifferent_access_test.rb
@@ -166,6 +166,18 @@ class HashWithIndifferentAccessTest < ActiveSupport::TestCase
assert_equal [1, 2], @mixed.values_at(:a, :b)
end
+ def test_indifferent_fetch_values
+ skip unless Hash.method_defined?(:fetch_values)
+
+ @mixed = @mixed.with_indifferent_access
+
+ assert_equal [1, 2], @mixed.fetch_values("a", "b")
+ assert_equal [1, 2], @mixed.fetch_values(:a, :b)
+ assert_equal [1, 2], @mixed.fetch_values(:a, "b")
+ assert_equal [1, "c"], @mixed.fetch_values(:a, :c) { |key| key }
+ assert_raise(KeyError) { @mixed.fetch_values(:a, :c) }
+ end
+
def test_indifferent_reading
hash = HashWithIndifferentAccess.new
hash["a"] = 1
@@ -675,7 +687,6 @@ class HashWithIndifferentAccessTest < ActiveSupport::TestCase
assert_includes yaml_output, "hash"
end
end
- HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess
def test_should_use_default_proc_for_unknown_key
hash_wia = HashWithIndifferentAccess.new { 1 + 2 }
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 14bc10513b..ef956eda90 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -420,6 +420,8 @@ class InflectorTest < ActiveSupport::TestCase
inflect.singular(/es$/, "")
inflect.irregular("el", "los")
+
+ inflect.uncountable("agua")
end
assert_equal("hijos", "hijo".pluralize(:es))
@@ -432,12 +434,17 @@ class InflectorTest < ActiveSupport::TestCase
assert_equal("los", "el".pluralize(:es))
assert_equal("els", "el".pluralize)
+ assert_equal("agua", "agua".pluralize(:es))
+ assert_equal("aguas", "agua".pluralize)
+
ActiveSupport::Inflector.inflections(:es) { |inflect| inflect.clear }
assert ActiveSupport::Inflector.inflections(:es).plurals.empty?
assert ActiveSupport::Inflector.inflections(:es).singulars.empty?
+ assert ActiveSupport::Inflector.inflections(:es).uncountables.empty?
assert !ActiveSupport::Inflector.inflections.plurals.empty?
assert !ActiveSupport::Inflector.inflections.singulars.empty?
+ assert !ActiveSupport::Inflector.inflections.uncountables.empty?
end
def test_clear_all
diff --git a/activesupport/test/logger_test.rb b/activesupport/test/logger_test.rb
index 3f04783401..1bc443ae65 100644
--- a/activesupport/test/logger_test.rb
+++ b/activesupport/test/logger_test.rb
@@ -37,7 +37,7 @@ class LoggerTest < ActiveSupport::TestCase
logger = Logger.new f
logger.level = Logger::DEBUG
- str = "\x80"
+ str = "\x80".dup
str.force_encoding("ASCII-8BIT")
logger.add Logger::DEBUG, str
@@ -55,7 +55,7 @@ class LoggerTest < ActiveSupport::TestCase
logger = Logger.new f
logger.level = Logger::DEBUG
- str = "\x80"
+ str = "\x80".dup
str.force_encoding("ASCII-8BIT")
logger.add Logger::DEBUG, str
diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb
index 56a436f751..4c3515b5e1 100644
--- a/activesupport/test/message_encryptor_test.rb
+++ b/activesupport/test/message_encryptor_test.rb
@@ -86,20 +86,32 @@ class MessageEncryptorTest < ActiveSupport::TestCase
assert_equal @data, encryptor.decrypt_and_verify(message)
end
+ def test_aead_mode_with_hmac_cbc_cipher_text
+ encryptor = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm")
+
+ assert_aead_not_decrypted(encryptor, "eHdGeExnZEwvMSt3U3dKaFl1WFo0TjVvYzA0eGpjbm5WSkt5MXlsNzhpZ0ZnbWhBWFlQZTRwaXE1bVJCS2oxMDZhYVp2dVN3V0lNZUlWQ3c2eVhQbnhnVjFmeVVubmhRKzF3WnZyWHVNMDg9LS1HSisyakJVSFlPb05ISzRMaXRzcFdBPT0=--831a1d54a3cda8a0658dc668a03dedcbce13b5ca")
+ end
+
def test_messing_with_aead_values_causes_failures
encryptor = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm")
text, iv, auth_tag = encryptor.encrypt_and_sign(@data).split("--")
- assert_not_decrypted([iv, text, auth_tag] * "--")
- assert_not_decrypted([munge(text), iv, auth_tag] * "--")
- assert_not_decrypted([text, munge(iv), auth_tag] * "--")
- assert_not_decrypted([text, iv, munge(auth_tag)] * "--")
- assert_not_decrypted([munge(text), munge(iv), munge(auth_tag)] * "--")
- assert_not_decrypted([text, iv] * "--")
- assert_not_decrypted([text, iv, auth_tag[0..-2]] * "--")
+ assert_aead_not_decrypted(encryptor, [iv, text, auth_tag] * "--")
+ assert_aead_not_decrypted(encryptor, [munge(text), iv, auth_tag] * "--")
+ assert_aead_not_decrypted(encryptor, [text, munge(iv), auth_tag] * "--")
+ assert_aead_not_decrypted(encryptor, [text, iv, munge(auth_tag)] * "--")
+ assert_aead_not_decrypted(encryptor, [munge(text), munge(iv), munge(auth_tag)] * "--")
+ assert_aead_not_decrypted(encryptor, [text, iv] * "--")
+ assert_aead_not_decrypted(encryptor, [text, iv, auth_tag[0..-2]] * "--")
end
private
+ def assert_aead_not_decrypted(encryptor, value)
+ assert_raise(ActiveSupport::MessageEncryptor::InvalidMessage) do
+ encryptor.decrypt_and_verify(value)
+ end
+ end
+
def assert_not_decrypted(value)
assert_raise(ActiveSupport::MessageEncryptor::InvalidMessage) do
@encryptor.decrypt_and_verify(@verifier.generate(value))
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index d80d340986..16a7d488c7 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -51,7 +51,7 @@ class MultibyteCharsTest < ActiveSupport::TestCase
end
def test_forwarded_method_with_non_string_result_should_be_returned_verbatim
- str = ""
+ str = "".dup
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 }
@@ -59,14 +59,14 @@ class MultibyteCharsTest < ActiveSupport::TestCase
end
def test_should_concatenate
- mb_a = "a".mb_chars
- mb_b = "b".mb_chars
+ mb_a = "a".dup.mb_chars
+ mb_b = "b".dup.mb_chars
assert_equal "ab", mb_a + "b"
assert_equal "ab", "a" + mb_b
assert_equal "ab", mb_a + mb_b
assert_equal "ab", mb_a << "b"
- assert_equal "ab", "a" << mb_b
+ assert_equal "ab", "a".dup << mb_b
assert_equal "abb", mb_a << mb_b
end
@@ -78,7 +78,7 @@ class MultibyteCharsTest < ActiveSupport::TestCase
def test_concatenation_should_return_a_proxy_class_instance
assert_equal ActiveSupport::Multibyte.proxy_class, ("a".mb_chars + "b").class
- assert_equal ActiveSupport::Multibyte.proxy_class, ("a".mb_chars << "b").class
+ assert_equal ActiveSupport::Multibyte.proxy_class, ("a".dup.mb_chars << "b").class
end
def test_ascii_strings_are_treated_at_utf8_strings
@@ -88,8 +88,8 @@ class MultibyteCharsTest < ActiveSupport::TestCase
def test_concatenate_should_return_proxy_instance
assert(("a".mb_chars + "b").kind_of?(@proxy_class))
assert(("a".mb_chars + "b".mb_chars).kind_of?(@proxy_class))
- assert(("a".mb_chars << "b").kind_of?(@proxy_class))
- assert(("a".mb_chars << "b".mb_chars).kind_of?(@proxy_class))
+ assert(("a".dup.mb_chars << "b").kind_of?(@proxy_class))
+ assert(("a".dup.mb_chars << "b".mb_chars).kind_of?(@proxy_class))
end
def test_should_return_string_as_json
@@ -115,12 +115,12 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
%w{capitalize downcase lstrip reverse rstrip swapcase upcase}.each do |method|
class_eval(<<-EOTESTS, __FILE__, __LINE__ + 1)
def test_#{method}_bang_should_return_self_when_modifying_wrapped_string
- chars = ' él piDió Un bUen café '
+ chars = ' él piDió Un bUen café '.dup
assert_equal chars.object_id, chars.send("#{method}!").object_id
end
def test_#{method}_bang_should_change_wrapped_string
- original = ' él piDió Un bUen café '
+ original = ' él piDió Un bUen café '.dup
proxy = chars(original.dup)
proxy.send("#{method}!")
assert_not_equal original, proxy.to_s
@@ -133,7 +133,7 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
end
def test_tidy_bytes_bang_should_change_wrapped_string
- original = " Un bUen café \x92"
+ original = " Un bUen café \x92".dup
proxy = chars(original.dup)
proxy.tidy_bytes!
assert_not_equal original, proxy.to_s
@@ -150,7 +150,7 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
end
def test_string_methods_are_chainable
- assert chars("").insert(0, "").kind_of?(ActiveSupport::Multibyte.proxy_class)
+ assert chars("".dup).insert(0, "").kind_of?(ActiveSupport::Multibyte.proxy_class)
assert chars("").rjust(1).kind_of?(ActiveSupport::Multibyte.proxy_class)
assert chars("").ljust(1).kind_of?(ActiveSupport::Multibyte.proxy_class)
assert chars("").center(1).kind_of?(ActiveSupport::Multibyte.proxy_class)
@@ -195,7 +195,7 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
end
def test_should_use_character_offsets_for_insert_offsets
- assert_equal "", "".mb_chars.insert(0, "")
+ assert_equal "", "".dup.mb_chars.insert(0, "")
assert_equal "こわにちわ", @chars.insert(1, "わ")
assert_equal "こわわわにちわ", @chars.insert(2, "わわ")
assert_equal "わこわわわにちわ", @chars.insert(0, "わ")
@@ -418,13 +418,13 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
end
def test_slice_bang_removes_the_slice_from_the_receiver
- chars = "úüù".mb_chars
+ chars = "úüù".dup.mb_chars
chars.slice!(0, 2)
assert_equal "ù", chars
end
def test_slice_bang_returns_nil_and_does_not_modify_receiver_if_out_of_bounds
- string = "úüù"
+ string = "úüù".dup
chars = string.mb_chars
assert_nil chars.slice!(4, 5)
assert_equal "úüù", chars
diff --git a/activesupport/test/multibyte_test_helpers.rb b/activesupport/test/multibyte_test_helpers.rb
index a70516bb08..e1e0ae86d9 100644
--- a/activesupport/test/multibyte_test_helpers.rb
+++ b/activesupport/test/multibyte_test_helpers.rb
@@ -23,7 +23,7 @@ module MultibyteTestHelpers
UNICODE_STRING = "こにちわ".freeze
ASCII_STRING = "ohayo".freeze
- BYTE_STRING = "\270\236\010\210\245".force_encoding("ASCII-8BIT").freeze
+ BYTE_STRING = "\270\236\010\210\245".dup.force_encoding("ASCII-8BIT").freeze
def chars(str)
ActiveSupport::Multibyte::Chars.new(str)
diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb
index dc0c34d4e2..4caf1428ea 100644
--- a/activesupport/test/number_helper_test.rb
+++ b/activesupport/test/number_helper_test.rb
@@ -321,12 +321,18 @@ module ActiveSupport
gangster = { hundred: "hundred bucks", million: "thousand quids" }
assert_equal "1 hundred bucks", number_helper.number_to_human(100, units: gangster)
assert_equal "25 hundred bucks", number_helper.number_to_human(2500, units: gangster)
+ assert_equal "1000 hundred bucks", number_helper.number_to_human(100_000, units: gangster)
+ assert_equal "1 thousand quids", number_helper.number_to_human(999_999, units: gangster)
+ assert_equal "1 thousand quids", number_helper.number_to_human(1_000_000, units: gangster)
assert_equal "25 thousand quids", number_helper.number_to_human(25000000, units: gangster)
assert_equal "12300 thousand quids", number_helper.number_to_human(12345000000, units: gangster)
#Spaces are stripped from the resulting string
assert_equal "4", number_helper.number_to_human(4, units: { unit: "", ten: "tens " })
assert_equal "4.5 tens", number_helper.number_to_human(45, units: { unit: "", ten: " tens " })
+
+ #Uses only the provided units and does not try to use larger ones
+ assert_equal "1000 kilometers", number_helper.number_to_human(1_000_000, units: { unit: "meter", thousand: "kilometers" })
end
end
diff --git a/activesupport/test/rescuable_test.rb b/activesupport/test/rescuable_test.rb
index f7eb047d44..3bdd1651e7 100644
--- a/activesupport/test/rescuable_test.rb
+++ b/activesupport/test/rescuable_test.rb
@@ -43,7 +43,9 @@ class Stargate
def dispatch(method)
send(method)
rescue Exception => e
- rescue_with_handler(e)
+ unless rescue_with_handler(e)
+ @result = "unhandled"
+ end
end
def attack
@@ -58,6 +60,26 @@ class Stargate
raise MadRonon.new("dex")
end
+ def crash
+ raise "unhandled RuntimeError"
+ end
+
+ def looped_crash
+ ex1 = StandardError.new("error 1")
+ ex2 = StandardError.new("error 2")
+ begin
+ begin
+ raise ex1
+ rescue
+ # sets the cause on ex2 to be ex1
+ raise ex2
+ end
+ rescue
+ # sets the cause on ex1 to be ex2
+ raise ex1
+ end
+ end
+
def fall_back_to_cause
# This exception is the cause and has a handler.
ronanize
@@ -139,4 +161,14 @@ class RescuableTest < ActiveSupport::TestCase
@stargate.dispatch :fall_back_to_cause
assert_equal "dex", @stargate.result
end
+
+ def test_unhandled_exceptions
+ @stargate.dispatch(:crash)
+ assert_equal "unhandled", @stargate.result
+ end
+
+ def test_rescue_handles_loops_in_exception_cause_chain
+ @stargate.dispatch :looped_crash
+ assert_equal "unhandled", @stargate.result
+ end
end
diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb
index af7fc44d66..40dfbe2542 100644
--- a/activesupport/test/test_case_test.rb
+++ b/activesupport/test/test_case_test.rb
@@ -237,9 +237,6 @@ class AssertDifferenceTest < ActiveSupport::TestCase
end
end
-class AlsoDoingNothingTest < ActiveSupport::TestCase
-end
-
# Setup and teardown callbacks.
class SetupAndTeardownTest < ActiveSupport::TestCase
setup :reset_callback_record, :foo
diff --git a/activesupport/test/testing/file_fixtures_test.rb b/activesupport/test/testing/file_fixtures_test.rb
index e44fe58ce9..9f28252c31 100644
--- a/activesupport/test/testing/file_fixtures_test.rb
+++ b/activesupport/test/testing/file_fixtures_test.rb
@@ -3,12 +3,12 @@ require "abstract_unit"
require "pathname"
class FileFixturesTest < ActiveSupport::TestCase
- self.file_fixture_path = File.expand_path("../../file_fixtures", __FILE__)
+ self.file_fixture_path = File.expand_path("../file_fixtures", __dir__)
test "#file_fixture returns Pathname to file fixture" do
path = file_fixture("sample.txt")
assert_kind_of Pathname, path
- assert_match %r{.*/test/file_fixtures/sample.txt$}, path.to_s
+ assert_match %r{.*/test/file_fixtures/sample\.txt$}, path.to_s
end
test "raises an exception when the fixture file does not exist" do
@@ -20,11 +20,11 @@ class FileFixturesTest < ActiveSupport::TestCase
end
class FileFixturesPathnameDirectoryTest < ActiveSupport::TestCase
- self.file_fixture_path = Pathname.new(File.expand_path("../../file_fixtures", __FILE__))
+ self.file_fixture_path = Pathname.new(File.expand_path("../file_fixtures", __dir__))
test "#file_fixture_path returns Pathname to file fixture" do
path = file_fixture("sample.txt")
assert_kind_of Pathname, path
- assert_match %r{.*/test/file_fixtures/sample.txt$}, path.to_s
+ assert_match %r{.*/test/file_fixtures/sample\.txt$}, path.to_s
end
end
diff --git a/activesupport/test/time_travel_test.rb b/activesupport/test/time_travel_test.rb
index e0d3fb0cf5..9d354f14f4 100644
--- a/activesupport/test/time_travel_test.rb
+++ b/activesupport/test/time_travel_test.rb
@@ -99,7 +99,7 @@ class TimeTravelTest < ActiveSupport::TestCase
#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)
+ 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
diff --git a/activesupport/test/xml_mini/jdom_engine_test.rb b/activesupport/test/xml_mini/jdom_engine_test.rb
index e783cea67c..fc35ac113b 100644
--- a/activesupport/test/xml_mini/jdom_engine_test.rb
+++ b/activesupport/test/xml_mini/jdom_engine_test.rb
@@ -2,7 +2,7 @@ require_relative "xml_mini_engine_test"
XMLMiniEngineTest.run_with_platform("java") do
class JDOMEngineTest < XMLMiniEngineTest
- FILES_DIR = File.dirname(__FILE__) + "/../fixtures/xml"
+ FILES_DIR = File.expand_path("../fixtures/xml", __dir__)
def test_not_allowed_to_expand_entities_to_files
attack_xml = <<-EOT
diff --git a/activesupport/test/xml_mini/xml_mini_engine_test.rb b/activesupport/test/xml_mini/xml_mini_engine_test.rb
index 5be9084c9d..244e0b0d3a 100644
--- a/activesupport/test/xml_mini/xml_mini_engine_test.rb
+++ b/activesupport/test/xml_mini/xml_mini_engine_test.rb
@@ -75,6 +75,11 @@ class XMLMiniEngineTest < ActiveSupport::TestCase
assert_equal({}, ActiveSupport::XmlMini.parse(""))
end
+ def test_parse_from_frozen_string
+ xml_string = "<root/>".freeze
+ assert_equal({ "root" => {} }, ActiveSupport::XmlMini.parse(xml_string))
+ end
+
def test_array_type_makes_an_array
assert_equal_rexml(<<-eoxml)
<blog>
diff --git a/guides/Rakefile b/guides/Rakefile
index 0a591558e1..3a6f10040f 100644
--- a/guides/Rakefile
+++ b/guides/Rakefile
@@ -17,13 +17,13 @@ namespace :guides do
namespace :generate do
desc "Generate HTML guides"
- task :html => :encoding do
+ task html: :encoding do
ENV["WARNINGS"] = "1" # authors can't disable this
ruby "rails_guides.rb"
end
desc "Generate .mobi file. The kindlegen executable must be in your PATH. You can get it for free from http://www.amazon.com/gp/feature.html?docId=1000765211"
- task :kindle => :encoding do
+ task kindle: :encoding do
require "kindlerb"
unless Kindlerb.kindlegen_available?
abort "Please run `setupkindlerb` to install kindlegen"
@@ -38,7 +38,7 @@ namespace :guides do
# Validate guides -------------------------------------------------------------------------
desc 'Validate guides, use ONLY=foo to process just "foo.html"'
- task :validate => :encoding do
+ task validate: :encoding do
ruby "w3c_validator.rb"
end
diff --git a/guides/assets/images/belongs_to.png b/guides/assets/images/belongs_to.png
index 077d237e4e..1a9926e578 100644
--- a/guides/assets/images/belongs_to.png
+++ b/guides/assets/images/belongs_to.png
Binary files differ
diff --git a/guides/assets/images/getting_started/article_with_comments.png b/guides/assets/images/getting_started/article_with_comments.png
index c489e4c00e..3f16f3b280 100644
--- a/guides/assets/images/getting_started/article_with_comments.png
+++ b/guides/assets/images/getting_started/article_with_comments.png
Binary files differ
diff --git a/guides/assets/images/getting_started/challenge.png b/guides/assets/images/getting_started/challenge.png
index 5b88a842b2..d05ef31bbe 100644
--- a/guides/assets/images/getting_started/challenge.png
+++ b/guides/assets/images/getting_started/challenge.png
Binary files differ
diff --git a/guides/assets/images/getting_started/confirm_dialog.png b/guides/assets/images/getting_started/confirm_dialog.png
index 9755f581a6..ce65734e6c 100644
--- a/guides/assets/images/getting_started/confirm_dialog.png
+++ b/guides/assets/images/getting_started/confirm_dialog.png
Binary files differ
diff --git a/guides/assets/images/getting_started/forbidden_attributes_for_new_article.png b/guides/assets/images/getting_started/forbidden_attributes_for_new_article.png
index 9f32c68472..50b178808e 100644
--- a/guides/assets/images/getting_started/forbidden_attributes_for_new_article.png
+++ b/guides/assets/images/getting_started/forbidden_attributes_for_new_article.png
Binary files differ
diff --git a/guides/assets/images/getting_started/form_with_errors.png b/guides/assets/images/getting_started/form_with_errors.png
index 98bff37d4a..6eefd2885a 100644
--- a/guides/assets/images/getting_started/form_with_errors.png
+++ b/guides/assets/images/getting_started/form_with_errors.png
Binary files differ
diff --git a/guides/assets/images/getting_started/index_action_with_edit_link.png b/guides/assets/images/getting_started/index_action_with_edit_link.png
index 0566a3ffde..a2a087a598 100644
--- a/guides/assets/images/getting_started/index_action_with_edit_link.png
+++ b/guides/assets/images/getting_started/index_action_with_edit_link.png
Binary files differ
diff --git a/guides/assets/images/getting_started/new_article.png b/guides/assets/images/getting_started/new_article.png
index bd3ae4fa67..6edcc161b6 100644
--- a/guides/assets/images/getting_started/new_article.png
+++ b/guides/assets/images/getting_started/new_article.png
Binary files differ
diff --git a/guides/assets/images/getting_started/rails_welcome.png b/guides/assets/images/getting_started/rails_welcome.png
index baccb11322..44f89ec8de 100644
--- a/guides/assets/images/getting_started/rails_welcome.png
+++ b/guides/assets/images/getting_started/rails_welcome.png
Binary files differ
diff --git a/guides/assets/images/getting_started/routing_error_no_controller.png b/guides/assets/images/getting_started/routing_error_no_controller.png
index ed62862291..52150f0426 100644
--- a/guides/assets/images/getting_started/routing_error_no_controller.png
+++ b/guides/assets/images/getting_started/routing_error_no_controller.png
Binary files differ
diff --git a/guides/assets/images/getting_started/show_action_for_articles.png b/guides/assets/images/getting_started/show_action_for_articles.png
index 4dad704f89..68837131f7 100644
--- a/guides/assets/images/getting_started/show_action_for_articles.png
+++ b/guides/assets/images/getting_started/show_action_for_articles.png
Binary files differ
diff --git a/guides/assets/images/getting_started/template_is_missing_articles_new.png b/guides/assets/images/getting_started/template_is_missing_articles_new.png
index f4f054f3c6..a1603f5d28 100644
--- a/guides/assets/images/getting_started/template_is_missing_articles_new.png
+++ b/guides/assets/images/getting_started/template_is_missing_articles_new.png
Binary files differ
diff --git a/guides/assets/images/getting_started/unknown_action_create_for_articles.png b/guides/assets/images/getting_started/unknown_action_create_for_articles.png
index fd20cd53dc..ec4758e085 100644
--- a/guides/assets/images/getting_started/unknown_action_create_for_articles.png
+++ b/guides/assets/images/getting_started/unknown_action_create_for_articles.png
Binary files differ
diff --git a/guides/assets/images/getting_started/unknown_action_new_for_articles.png b/guides/assets/images/getting_started/unknown_action_new_for_articles.png
index e948a51e4a..f7e7464d61 100644
--- a/guides/assets/images/getting_started/unknown_action_new_for_articles.png
+++ b/guides/assets/images/getting_started/unknown_action_new_for_articles.png
Binary files differ
diff --git a/guides/assets/images/habtm.png b/guides/assets/images/habtm.png
index b062bc73fe..41013b743d 100644
--- a/guides/assets/images/habtm.png
+++ b/guides/assets/images/habtm.png
Binary files differ
diff --git a/guides/assets/images/has_many.png b/guides/assets/images/has_many.png
index 79da2613d7..0d67bea38b 100644
--- a/guides/assets/images/has_many.png
+++ b/guides/assets/images/has_many.png
Binary files differ
diff --git a/guides/assets/images/has_many_through.png b/guides/assets/images/has_many_through.png
index 858c898dc1..b4da60e1fb 100644
--- a/guides/assets/images/has_many_through.png
+++ b/guides/assets/images/has_many_through.png
Binary files differ
diff --git a/guides/assets/images/has_one.png b/guides/assets/images/has_one.png
index 93faa05b07..c70763856a 100644
--- a/guides/assets/images/has_one.png
+++ b/guides/assets/images/has_one.png
Binary files differ
diff --git a/guides/assets/images/has_one_through.png b/guides/assets/images/has_one_through.png
index 07dac1a27d..888a02b775 100644
--- a/guides/assets/images/has_one_through.png
+++ b/guides/assets/images/has_one_through.png
Binary files differ
diff --git a/guides/assets/images/header_backdrop.png b/guides/assets/images/header_backdrop.png
index 72b030478f..81f4d91774 100644
--- a/guides/assets/images/header_backdrop.png
+++ b/guides/assets/images/header_backdrop.png
Binary files differ
diff --git a/guides/assets/images/i18n/demo_html_safe.png b/guides/assets/images/i18n/demo_html_safe.png
index 9afa8ebec1..be75d4830e 100644
--- a/guides/assets/images/i18n/demo_html_safe.png
+++ b/guides/assets/images/i18n/demo_html_safe.png
Binary files differ
diff --git a/guides/assets/images/i18n/demo_localized_pirate.png b/guides/assets/images/i18n/demo_localized_pirate.png
index bf8d0b558c..528cc27900 100644
--- a/guides/assets/images/i18n/demo_localized_pirate.png
+++ b/guides/assets/images/i18n/demo_localized_pirate.png
Binary files differ
diff --git a/guides/assets/images/i18n/demo_translated_en.png b/guides/assets/images/i18n/demo_translated_en.png
index e887bfa306..bbb5e93c3a 100644
--- a/guides/assets/images/i18n/demo_translated_en.png
+++ b/guides/assets/images/i18n/demo_translated_en.png
Binary files differ
diff --git a/guides/assets/images/i18n/demo_translated_pirate.png b/guides/assets/images/i18n/demo_translated_pirate.png
index aa5618a865..305fa93a14 100644
--- a/guides/assets/images/i18n/demo_translated_pirate.png
+++ b/guides/assets/images/i18n/demo_translated_pirate.png
Binary files differ
diff --git a/guides/assets/images/i18n/demo_translation_missing.png b/guides/assets/images/i18n/demo_translation_missing.png
index 867aa7c42d..e9833ba307 100644
--- a/guides/assets/images/i18n/demo_translation_missing.png
+++ b/guides/assets/images/i18n/demo_translation_missing.png
Binary files differ
diff --git a/guides/assets/images/i18n/demo_untranslated.png b/guides/assets/images/i18n/demo_untranslated.png
index 2ea6404822..2653abc491 100644
--- a/guides/assets/images/i18n/demo_untranslated.png
+++ b/guides/assets/images/i18n/demo_untranslated.png
Binary files differ
diff --git a/guides/assets/images/icons/callouts/14.png b/guides/assets/images/icons/callouts/14.png
index 4274e6580a..dbde9ca749 100644
--- a/guides/assets/images/icons/callouts/14.png
+++ b/guides/assets/images/icons/callouts/14.png
Binary files differ
diff --git a/guides/assets/images/icons/example.png b/guides/assets/images/icons/example.png
index de23c0aa87..a0e855befa 100644
--- a/guides/assets/images/icons/example.png
+++ b/guides/assets/images/icons/example.png
Binary files differ
diff --git a/guides/assets/images/icons/home.png b/guides/assets/images/icons/home.png
index 24149d6e78..e70e164522 100644
--- a/guides/assets/images/icons/home.png
+++ b/guides/assets/images/icons/home.png
Binary files differ
diff --git a/guides/assets/images/icons/important.png b/guides/assets/images/icons/important.png
index dafcf0f59e..bab53bf3aa 100644
--- a/guides/assets/images/icons/important.png
+++ b/guides/assets/images/icons/important.png
Binary files differ
diff --git a/guides/assets/images/icons/next.png b/guides/assets/images/icons/next.png
index 355b329f5a..a158832725 100644
--- a/guides/assets/images/icons/next.png
+++ b/guides/assets/images/icons/next.png
Binary files differ
diff --git a/guides/assets/images/icons/note.png b/guides/assets/images/icons/note.png
index 08d35a6f5c..62eec7845f 100644
--- a/guides/assets/images/icons/note.png
+++ b/guides/assets/images/icons/note.png
Binary files differ
diff --git a/guides/assets/images/icons/prev.png b/guides/assets/images/icons/prev.png
index ea564c865e..8a96960422 100644
--- a/guides/assets/images/icons/prev.png
+++ b/guides/assets/images/icons/prev.png
Binary files differ
diff --git a/guides/assets/images/icons/tip.png b/guides/assets/images/icons/tip.png
index d834e6d1bb..a5316d318f 100644
--- a/guides/assets/images/icons/tip.png
+++ b/guides/assets/images/icons/tip.png
Binary files differ
diff --git a/guides/assets/images/icons/up.png b/guides/assets/images/icons/up.png
index 379f0045af..6cac818170 100644
--- a/guides/assets/images/icons/up.png
+++ b/guides/assets/images/icons/up.png
Binary files differ
diff --git a/guides/assets/images/polymorphic.png b/guides/assets/images/polymorphic.png
index a3cbc4502a..e0a7f6d64a 100644
--- a/guides/assets/images/polymorphic.png
+++ b/guides/assets/images/polymorphic.png
Binary files differ
diff --git a/guides/assets/images/rails4_features.png b/guides/assets/images/rails4_features.png
index b3bd5ef69e..ac73f05cf7 100644
--- a/guides/assets/images/rails4_features.png
+++ b/guides/assets/images/rails4_features.png
Binary files differ
diff --git a/guides/assets/images/session_fixation.png b/guides/assets/images/session_fixation.png
index ac3ab01614..e009484f09 100644
--- a/guides/assets/images/session_fixation.png
+++ b/guides/assets/images/session_fixation.png
Binary files differ
diff --git a/guides/assets/images/tab_yellow.png b/guides/assets/images/tab_yellow.png
index 3ab1c56c4d..053c807d28 100644
--- a/guides/assets/images/tab_yellow.png
+++ b/guides/assets/images/tab_yellow.png
Binary files differ
diff --git a/guides/bug_report_templates/action_controller_gem.rb b/guides/bug_report_templates/action_controller_gem.rb
index 46fabca3e8..8b7aa893fd 100644
--- a/guides/bug_report_templates/action_controller_gem.rb
+++ b/guides/bug_report_templates/action_controller_gem.rb
@@ -8,14 +8,14 @@ end
gemfile(true) do
source "https://rubygems.org"
# Activate the gem you are reporting the issue against.
- gem "rails", "5.1.0.rc1"
+ gem "rails", "5.1.0"
end
require "rack/test"
require "action_controller/railtie"
class TestApp < Rails::Application
- config.root = File.dirname(__FILE__)
+ config.root = __dir__
config.session_store :cookie_store, key: "cookie_store_key"
secrets.secret_token = "secret_token"
secrets.secret_key_base = "secret_key_base"
diff --git a/guides/bug_report_templates/action_controller_master.rb b/guides/bug_report_templates/action_controller_master.rb
index 7644f6fe4a..3dd66c95ec 100644
--- a/guides/bug_report_templates/action_controller_master.rb
+++ b/guides/bug_report_templates/action_controller_master.rb
@@ -14,7 +14,7 @@ end
require "action_controller/railtie"
class TestApp < Rails::Application
- config.root = File.dirname(__FILE__)
+ config.root = __dir__
secrets.secret_token = "secret_token"
secrets.secret_key_base = "secret_key_base"
diff --git a/guides/bug_report_templates/active_job_gem.rb b/guides/bug_report_templates/active_job_gem.rb
index 71fe356ea0..252b270a0c 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.1.0.rc1"
+ gem "activejob", "5.1.0"
end
require "minitest/autorun"
diff --git a/guides/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb
index a685c257ea..61d4e8d395 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.1.0.rc1"
+ gem "activerecord", "5.1.0"
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 b4e822dfe0..00ba3c1cd6 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.1.0.rc1"
+ gem "activerecord", "5.1.0"
gem "sqlite3"
end
@@ -48,16 +48,14 @@ end
class BugTest < Minitest::Test
def test_migration_up
- migrator = ActiveRecord::Migrator.new(:up, [ChangeAmountToAddScale])
- migrator.run
+ ChangeAmountToAddScale.migrate(:up)
Payment.reset_column_information
assert_equal "decimal(10,2)", Payment.columns.last.sql_type
end
def test_migration_down
- migrator = ActiveRecord::Migrator.new(:down, [ChangeAmountToAddScale])
- migrator.run
+ ChangeAmountToAddScale.migrate(:down)
Payment.reset_column_information
assert_equal "decimal(10,0)", Payment.columns.last.sql_type
diff --git a/guides/bug_report_templates/active_record_migrations_master.rb b/guides/bug_report_templates/active_record_migrations_master.rb
index 84a4b71909..52c9028b0f 100644
--- a/guides/bug_report_templates/active_record_migrations_master.rb
+++ b/guides/bug_report_templates/active_record_migrations_master.rb
@@ -48,16 +48,14 @@ end
class BugTest < Minitest::Test
def test_migration_up
- migrator = ActiveRecord::Migrator.new(:up, [ChangeAmountToAddScale])
- migrator.run
+ ChangeAmountToAddScale.migrate(:up)
Payment.reset_column_information
assert_equal "decimal(10,2)", Payment.columns.last.sql_type
end
def test_migration_down
- migrator = ActiveRecord::Migrator.new(:down, [ChangeAmountToAddScale])
- migrator.run
+ ChangeAmountToAddScale.migrate(:down)
Payment.reset_column_information
assert_equal "decimal(10,0)", Payment.columns.last.sql_type
diff --git a/guides/bug_report_templates/generic_gem.rb b/guides/bug_report_templates/generic_gem.rb
index e1b705bea4..4dcd04ea27 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.1.0.rc1"
+ gem "activesupport", "5.1.0"
end
require "active_support/core_ext/object/blank"
diff --git a/guides/rails_guides/generator.rb b/guides/rails_guides/generator.rb
index 28164a3cb4..35f014747c 100644
--- a/guides/rails_guides/generator.rb
+++ b/guides/rails_guides/generator.rb
@@ -73,7 +73,7 @@ module RailsGuides
@output_dir = "#{@guides_dir}/output"
@output_dir += "/kindle" if @kindle
- @source_dir += "/#{@language}" if @language
+ @output_dir += "/#{@language}" if @language
end
def create_output_dir_if_needed
diff --git a/guides/rails_guides/helpers.rb b/guides/rails_guides/helpers.rb
index 6f4b0b492c..520aa7f7cc 100644
--- a/guides/rails_guides/helpers.rb
+++ b/guides/rails_guides/helpers.rb
@@ -15,7 +15,7 @@ module RailsGuides
end
def documents_by_section
- @documents_by_section ||= YAML.load_file(File.expand_path("../../source/#{@lang ? @lang + '/' : ''}documents.yaml", __FILE__))
+ @documents_by_section ||= YAML.load_file(File.expand_path("../source/#{@language ? @language + '/' : ''}documents.yaml", __dir__))
end
def documents_flat
diff --git a/guides/rails_guides/markdown.rb b/guides/rails_guides/markdown.rb
index 16aaa7d1eb..02d58601c4 100644
--- a/guides/rails_guides/markdown.rb
+++ b/guides/rails_guides/markdown.rb
@@ -106,7 +106,7 @@ module RailsGuides
end
end
- doc.css('h3, h4, h5, h6').each do |node|
+ doc.css("h3, h4, h5, h6").each do |node|
node.inner_html = "<a class='anchorlink' href='##{node[:id]}'>#{node.inner_html}</a>"
end
end.to_html
diff --git a/guides/rails_guides/markdown/renderer.rb b/guides/rails_guides/markdown/renderer.rb
index 20cbd568c9..7ac3d417a4 100644
--- a/guides/rails_guides/markdown/renderer.rb
+++ b/guides/rails_guides/markdown/renderer.rb
@@ -93,16 +93,16 @@ HTML
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
-
+ root = file_path[%r{(\w+)/}, 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
diff --git a/guides/source/5_1_release_notes.md b/guides/source/5_1_release_notes.md
index 5d4885d55c..fa92b9e5f8 100644
--- a/guides/source/5_1_release_notes.md
+++ b/guides/source/5_1_release_notes.md
@@ -24,7 +24,14 @@ repository on GitHub.
Upgrading to Rails 5.1
----------------------
-ToDo
+If you're upgrading an existing application, it's a great idea to have good test
+coverage before going in. You should also first upgrade to Rails 5.0 in case you
+haven't and make sure your application still runs as expected before attempting
+an update to Rails 5.1. A list of things to watch out for when upgrading is
+available in the
+[Upgrading Ruby on Rails](upgrading_ruby_on_rails.html#upgrading-from-rails-5-0-to-rails-5-1)
+guide.
+
Major Features
--------------
@@ -33,7 +40,7 @@ Major Features
[Pull Request](https://github.com/rails/rails/pull/26836)
-Rails 5.1 will allow managing JavaScript dependencies
+Rails 5.1 allows managing JavaScript dependencies
from NPM via Yarn. This will make it easy to use libraries like React, VueJS
or any other library from NPM world. The Yarn support is integrated with
the asset pipeline so that all dependencies will work seamlessly with the
@@ -63,14 +70,14 @@ offerings. It is no longer required, as the UJS has been rewritten to use plain,
vanilla JavaScript. This code now ships inside of Action View as
`rails-ujs`.
-You can still use the jQuery version if needed, but it is no longer required by default.
+You can still use jQuery if needed, but it is no longer required by default.
### System tests
[Pull Request](https://github.com/rails/rails/pull/26703)
Rails 5.1 has baked-in support for writing Capybara tests, in the form of
-System tests. You need no longer worry about configuring Capybara and
+System tests. You no longer need to worry about configuring Capybara and
database cleaning strategies for such tests. Rails 5.1 provides a wrapper
for running tests in Chrome with additional features such as failure
screenshots.
@@ -79,8 +86,8 @@ screenshots.
[Pull Request](https://github.com/rails/rails/pull/28038)
-Rails will now allow management of application secrets in a secure way,
-building on top of the [sekrets](https://github.com/ahoward/sekrets) gem.
+Rails now allows management of application secrets in a secure way,
+inspired by the [sekrets](https://github.com/ahoward/sekrets) gem.
Run `bin/rails secrets:setup` to setup a new encrypted secrets file. This will
also generate a master key, which must be stored outside of the repository. The
@@ -94,38 +101,29 @@ Secrets will be decrypted in production, using a key stored either in the
[Pull Request](https://github.com/rails/rails/pull/27825)
-Allows specifying common params used for all methods in a mailer class
-to share instance variables, headers and other common setup.
+Allows specifying common parameters used for all methods in a mailer class in
+order to share instance variables, headers and other common setup.
``` ruby
class InvitationsMailer < ApplicationMailer
-
before_action { @inviter, @invitee = params[:inviter], params[:invitee] }
before_action { @account = params[:inviter].account }
def account_invitation
mail subject: "#{@inviter.name} invited you to their Basecamp (#{@account.name})"
end
-
- def project_invitation
- @project = params[:project]
- @summarizer = ProjectInvitationSummarizer.new(@project.bucket)
-
- mail subject: "#{@inviter.name.familiar} added you to a project in Basecamp (#{@account.name})"
- end
end
-InvitationsMailer.with(inviter: person_a, invitee: person_b).account_invitation.deliver_later
+InvitationsMailer.with(inviter: person_a, invitee: person_b)
+ .account_invitation.deliver_later
```
### Direct & resolved routes
[Pull Request](https://github.com/rails/rails/pull/23138)
-Rails 5.1 has added two new methods, `resolve` and `direct`, to the routing
-DSL.
-
-The `resolve` method allows customizing polymorphic mapping of models.
+Rails 5.1 adds two new methods, `resolve` and `direct`, to the routing
+DSL. The `resolve` method allows customizing polymorphic mapping of models.
``` ruby
resource :basket
@@ -174,90 +172,467 @@ Before Rails 5.1, there were two interfaces for handling HTML forms:
Rails 5.1 combines both of these interfaces with `form_with`, and
can generate form tags based on URLs, scopes or models.
-``` erb
-# Using just a URL:
+Using just a URL:
+``` erb
<%= form_with url: posts_path do |form| %>
<%= form.text_field :title %>
<% end %>
-# =>
+<%# Will generate %>
+
<form action="/posts" method="post" data-remote="true">
<input type="text" name="title">
</form>
+```
-# Adding a scope prefixes the input field names:
+Adding a scope prefixes the input field names:
+``` erb
<%= form_with scope: :post, url: posts_path do |form| %>
<%= form.text_field :title %>
<% end %>
-# =>
+
+<%# Will generate %>
+
<form action="/posts" method="post" data-remote="true">
<input type="text" name="post[title]">
</form>
+```
-# Using a model infers both the URL and scope:
+Using a model infers both the URL and scope:
+``` erb
<%= form_with model: Post.new do |form| %>
<%= form.text_field :title %>
<% end %>
-# =>
+
+<%# Will generate %>
+
<form action="/posts" method="post" data-remote="true">
<input type="text" name="post[title]">
</form>
+```
-# An existing model makes an update form and fills out field values:
+An existing model makes an update form and fills out field values:
+``` erb
<%= form_with model: Post.first do |form| %>
<%= form.text_field :title %>
<% end %>
-# =>
+
+<%# Will generate %>
+
<form action="/posts/1" method="post" data-remote="true">
<input type="hidden" name="_method" value="patch">
<input type="text" name="post[title]" value="<the title of the post>">
</form>
```
+Incompatibilities
+-----------------
+
+The following changes may require immediate action upon upgrade.
+
+### Transactional tests with multiple connections
+
+Transactional tests now wrap all Active Record connections in database
+transactions.
+
+When a test spawns additional threads, and those threads obtain database
+connections, those connections are now handled specially:
+
+The threads will share a single connection, which is inside the managed
+transaction. This ensures all threads see the database in the same
+state, ignoring the outermost transaction. Previously, such additional
+connections were unable to see the fixture rows, for example.
+
+When a thread enters a nested transaction, it will temporarily obtain
+exclusive use of the connection, to maintain isolation.
+
+If your tests currently rely on obtaining a separate,
+outside-of-transaction, connection in a spawned thread, you'll need to
+switch to more explicit connection management.
+
+If your tests spawn threads and those threads interact while also using
+explicit database transactions, this change may introduce a deadlock.
+
+The easy way to opt out of this new behavior is to disable transactional
+tests on any test cases it affects.
+
Railties
--------
Please refer to the [Changelog][railties] for detailed changes.
+### Removals
+
+* Remove deprecated `config.static_cache_control`.
+ ([commit](https://github.com/rails/rails/commit/c861decd44198f8d7d774ee6a74194d1ac1a5a13))
+
+* Remove deprecated `config.serve_static_files`.
+ ([commit](https://github.com/rails/rails/commit/0129ca2eeb6d5b2ea8c6e6be38eeb770fe45f1fa))
+
+* Remove deprecated file `rails/rack/debugger`.
+ ([commit](https://github.com/rails/rails/commit/7563bf7b46e6f04e160d664e284a33052f9804b8))
+
+* Remove deprecated tasks: `rails:update`, `rails:template`, `rails:template:copy`,
+ `rails:update:configs` and `rails:update:bin`.
+ ([commit](https://github.com/rails/rails/commit/f7782812f7e727178e4a743aa2874c078b722eef))
+
+* Remove deprecated `CONTROLLER` environment variable for `routes` task.
+ ([commit](https://github.com/rails/rails/commit/f9ed83321ac1d1902578a0aacdfe55d3db754219))
+
+* Remove -j (--javascript) option from `rails new` command.
+ ([Pull Request](https://github.com/rails/rails/pull/28546))
+
+### Notable changes
+
+* Added a shared section to `config/secrets.yml` that will be loaded for all
+ environments.
+ ([commit](https://github.com/rails/rails/commit/e530534265d2c32b5c5f772e81cb9002dcf5e9cf))
+
+* The config file `config/secrets.yml` is now loaded in with all keys as symbols.
+ ([Pull Request](https://github.com/rails/rails/pull/26929))
+
+* Removed jquery-rails from default stack. rails-ujs, which is shipped
+ with Action View, is included as default UJS adapter.
+ ([Pull Request](https://github.com/rails/rails/pull/27113))
+
+* Add Yarn support in new apps with a yarn binstub and package.json.
+ ([Pull Request](https://github.com/rails/rails/pull/26836))
+
+* Add Webpack support in new apps via the `--webpack` option, which will delegate
+ to the rails/webpacker gem.
+ ([Pull Request](https://github.com/rails/rails/pull/27288))
+
+* Initialize Git repo when generating new app, if option `--skip-git` is not
+ provided.
+ ([Pull Request](https://github.com/rails/rails/pull/27632))
+
+* Add encrypted secrets in `config/secrets.yml.enc`.
+ ([Pull Request](https://github.com/rails/rails/pull/28038))
+
+* Display railtie class name in `rails initializers`.
+ ([Pull Request](https://github.com/rails/rails/pull/25257))
+
+Action Cable
+-----------
+
+Please refer to the [Changelog][action-cable] for detailed changes.
+
+### Notable changes
+
+* Added support for `channel_prefix` to Redis and evented Redis adapters
+ in `cable.yml` to avoid name collisions when using the same Redis server
+ with multiple applications.
+ ([Pull Request](https://github.com/rails/rails/pull/27425))
+
+* Add `ActiveSupport::Notifications` hook for broadcasting data.
+ ([Pull Request](https://github.com/rails/rails/pull/24988))
+
Action Pack
-----------
Please refer to the [Changelog][action-pack] for detailed changes.
+### Removals
+
+* Removed support for non-keyword arguments in `#process`, `#get`, `#post`,
+ `#patch`, `#put`, `#delete`, and `#head` for the `ActionDispatch::IntegrationTest`
+ and `ActionController::TestCase` classes.
+ ([Commit](https://github.com/rails/rails/commit/98b8309569a326910a723f521911e54994b112fb),
+ [Commit](https://github.com/rails/rails/commit/de9542acd56f60d281465a59eac11e15ca8b3323))
+
+* Removed deprecated `ActionDispatch::Callbacks.to_prepare` and
+ `ActionDispatch::Callbacks.to_cleanup`.
+ ([Commit](https://github.com/rails/rails/commit/3f2b7d60a52ffb2ad2d4fcf889c06b631db1946b))
+
+* Removed deprecated methods related to controller filters.
+ ([Commit](https://github.com/rails/rails/commit/d7be30e8babf5e37a891522869e7b0191b79b757))
+
+### Deprecations
+
+* Deprecated `config.action_controller.raise_on_unfiltered_parameters`.
+ It doesn't have any effect in Rails 5.1.
+ ([Commit](https://github.com/rails/rails/commit/c6640fb62b10db26004a998d2ece98baede509e5))
+
+### Notable changes
+
+* Added the `direct` and `resolve` methods to the routing DSL.
+ ([Pull Request](https://github.com/rails/rails/pull/23138))
+
+* Added a new `ActionDispatch::SystemTestCase` class to write system tests in
+ your applications.
+ ([Pull Request](https://github.com/rails/rails/pull/26703))
+
Action View
-------------
Please refer to the [Changelog][action-view] for detailed changes.
+### Removals
+
+* Removed deprecated `#original_exception` in `ActionView::Template::Error`.
+ ([commit](https://github.com/rails/rails/commit/b9ba263e5aaa151808df058f5babfed016a1879f))
+
+* Remove the option `encode_special_chars` misnomer from `strip_tags`.
+ ([Pull Request](https://github.com/rails/rails/pull/28061))
+
+### Deprecations
+
+* Deprecated Erubis ERB handler in favor of Erubi.
+ ([Pull Request](https://github.com/rails/rails/pull/27757))
+
+### Notable changes
+
+* Raw template handler (the default template handler in Rails 5) now outputs
+ HTML-safe strings.
+ ([commit](https://github.com/rails/rails/commit/1de0df86695f8fa2eeae6b8b46f9b53decfa6ec8))
+
+* Change `datetime_field` and `datetime_field_tag` to generate `datetime-local`
+ fields.
+ ([Pull Request](https://github.com/rails/rails/pull/28061))
+
+* New Builder-style syntax for HTML tags (`tag.div`, `tag.br`, etc.)
+ ([Pull Request](https://github.com/rails/rails/pull/25543))
+
+* Add `form_with` to unify `form_tag` and `form_for` usage.
+ ([Pull Request](https://github.com/rails/rails/pull/26976))
+
+* Add `check_parameters` option to `current_page?`.
+ ([Pull Request](https://github.com/rails/rails/pull/27549))
+
Action Mailer
-------------
Please refer to the [Changelog][action-mailer] for detailed changes.
+### Notable changes
+
+* Allowed setting custom content type when attachments are included
+ and body is set inline.
+ ([Pull Request](https://github.com/rails/rails/pull/27227))
+
+* Allowed passing lambdas as values to the `default` method.
+ ([Commit](https://github.com/rails/rails/commit/1cec84ad2ddd843484ed40b1eb7492063ce71baf))
+
+* Added support for parameterized invocation of mailers to share before filters and defaults
+ between different mailer actions.
+ ([Commit](https://github.com/rails/rails/commit/1cec84ad2ddd843484ed40b1eb7492063ce71baf))
+
+* Passed the incoming arguments to the mailer action to `process.action_mailer` event under
+ an `args` key.
+ ([Pull Request](https://github.com/rails/rails/pull/27900))
+
Active Record
-------------
Please refer to the [Changelog][active-record] for detailed changes.
+### Removals
+
+* Removed support for passing arguments and block at the same time to
+ `ActiveRecord::QueryMethods#select`.
+ ([Commit](https://github.com/rails/rails/commit/4fc3366d9d99a0eb19e45ad2bf38534efbf8c8ce))
+
+* Removed deprecated `activerecord.errors.messages.restrict_dependent_destroy.one` and
+ `activerecord.errors.messages.restrict_dependent_destroy.many` i18n scopes.
+ ([Commit](https://github.com/rails/rails/commit/00e3973a311))
+
+* Removed deprecated force reload argument in singular and collection association readers.
+ ([Commit](https://github.com/rails/rails/commit/09cac8c67af))
+
+* Removed deprecated support for passing a column to `#quote`.
+ ([Commit](https://github.com/rails/rails/commit/e646bad5b7c))
+
+* Removed deprecated `name` arguments from `#tables`.
+ ([Commit](https://github.com/rails/rails/commit/d5be101dd02214468a27b6839ffe338cfe8ef5f3))
+
+* Removed deprecated behavior of `#tables` and `#table_exists?` to return tables and views
+ to return only tables and not views.
+ ([Commit](https://github.com/rails/rails/commit/5973a984c369a63720c2ac18b71012b8347479a8))
+
+* Removed deprecated `original_exception` argument in `ActiveRecord::StatementInvalid#initialize`
+ and `ActiveRecord::StatementInvalid#original_exception`.
+ ([Commit](https://github.com/rails/rails/commit/bc6c5df4699d3f6b4a61dd12328f9e0f1bd6cf46))
+
+* Removed deprecated support of passing a class as a value in a query.
+ ([Commit](https://github.com/rails/rails/commit/b4664864c972463c7437ad983832d2582186e886))
+
+* Removed deprecated support to query using commas on LIMIT.
+ ([Commit](https://github.com/rails/rails/commit/fc3e67964753fb5166ccbd2030d7382e1976f393))
+
+* Removed deprecated `conditions` parameter from `#destroy_all`.
+ ([Commit](https://github.com/rails/rails/commit/d31a6d1384cd740c8518d0bf695b550d2a3a4e9b))
+
+* Removed deprecated `conditions` parameter from `#delete_all`.
+ ([Commit](https://github.com/rails/rails/pull/27503/commits/e7381d289e4f8751dcec9553dcb4d32153bd922b))
+
+* Removed deprecated method `#load_schema_for` in favor of `#load_schema`.
+ ([Commit](https://github.com/rails/rails/commit/419e06b56c3b0229f0c72d3e4cdf59d34d8e5545))
+
+* Removed deprecated `#raise_in_transactional_callbacks` configuration.
+ ([Commit](https://github.com/rails/rails/commit/8029f779b8a1dd9848fee0b7967c2e0849bf6e07))
+
+* Removed deprecated `#use_transactional_fixtures` configuration.
+ ([Commit](https://github.com/rails/rails/commit/3955218dc163f61c932ee80af525e7cd440514b3))
+
+### Deprecations
+
+* Deprecated `error_on_ignored_order_or_limit` flag in favor of
+ `error_on_ignored_order`.
+ ([Commit](https://github.com/rails/rails/commit/451437c6f57e66cc7586ec966e530493927098c7))
+
+* Deprecated `sanitize_conditions` in favor of `sanitize_sql`.
+ ([Pull Request](https://github.com/rails/rails/pull/25999))
+
+* Deprecated `supports_migrations?` on connection adapters.
+ ([Pull Request](https://github.com/rails/rails/pull/28172))
+
+* Deprecated `Migrator.schema_migrations_table_name`, use `SchemaMigration.table_name` instead.
+ ([Pull Request](https://github.com/rails/rails/pull/28351))
+
+* Deprecated using `#quoted_id` in quoting and type casting.
+ ([Pull Request](https://github.com/rails/rails/pull/27962))
+
+* Deprecated passing `default` argument to `#index_name_exists?`.
+ ([Pull Request](https://github.com/rails/rails/pull/26930))
+
+### Notable changes
+
+* Change Default Primary Keys to BIGINT.
+ ([Pull Request](https://github.com/rails/rails/pull/26266))
+
+* Virtual/generated column support for MySQL 5.7.5+ and MariaDB 5.2.0+.
+ ([Commit](https://github.com/rails/rails/commit/65bf1c60053e727835e06392d27a2fb49665484c))
+
+* Added support for limits in batch processing.
+ ([Commit](https://github.com/rails/rails/commit/451437c6f57e66cc7586ec966e530493927098c7))
+
+* Transactional tests now wrap all Active Record connections in database
+ transactions.
+ ([Pull Request](https://github.com/rails/rails/pull/28726))
+
+* Skipped comments in the output of `mysqldump` command by default.
+ ([Pull Request](https://github.com/rails/rails/pull/23301))
+
+* Fixed `ActiveRecord::Relation#count` to use Ruby's `Enumerable#count` for counting
+ records when a block is passed as argument instead of silently ignoring the
+ passed block.
+ ([Pull Request](https://github.com/rails/rails/pull/24203))
+
+* Pass `"-v ON_ERROR_STOP=1"` flag with `psql` command to not suppress SQL errors.
+ ([Pull Request](https://github.com/rails/rails/pull/24773))
+
+* Add `ActiveRecord::Base.connection_pool.stat`.
+ ([Pull Request](https://github.com/rails/rails/pull/26988))
+
+* Inheriting directly from `ActiveRecord::Migration` raises an error.
+ Specify the Rails version for which the migration was written for.
+ ([Commit](https://github.com/rails/rails/commit/249f71a22ab21c03915da5606a063d321f04d4d3))
+
+* An error is raised when `through` association has ambiguous reflection name.
+ ([Commit](https://github.com/rails/rails/commit/0944182ad7ed70d99b078b22426cbf844edd3f61))
+
Active Model
------------
Please refer to the [Changelog][active-model] for detailed changes.
+### Removals
+
+* Removed deprecated methods in `ActiveModel::Errors`.
+ ([commit](https://github.com/rails/rails/commit/9de6457ab0767ebab7f2c8bc583420fda072e2bd))
+
+* Removed deprecated `:tokenizer` option in the length validator.
+ ([commit](https://github.com/rails/rails/commit/6a78e0ecd6122a6b1be9a95e6c4e21e10e429513))
+
+* Remove deprecated behavior that halts callbacks when the return value is false.
+ ([commit](https://github.com/rails/rails/commit/3a25cdca3e0d29ee2040931d0cb6c275d612dffe))
+
+### Notable changes
+
+* The original string assigned to a model attribute is no longer incorrectly
+ frozen.
+ ([Pull Request](https://github.com/rails/rails/pull/28729))
+
Active Job
-----------
Please refer to the [Changelog][active-job] for detailed changes.
+### Removals
+
+* Removed deprecated support to passing the adapter class to `.queue_adapter`.
+ ([commit](https://github.com/rails/rails/commit/d1fc0a5eb286600abf8505516897b96c2f1ef3f6))
+
+* Removed deprecated `#original_exception` in `ActiveJob::DeserializationError`.
+ ([commit](https://github.com/rails/rails/commit/d861a1fcf8401a173876489d8cee1ede1cecde3b))
+
+### Notable changes
+
+* Added declarative exception handling via `ActiveJob::Base.retry_on` and `ActiveJob::Base.discard_on`.
+ ([Pull Request](https://github.com/rails/rails/pull/25991))
+
+* Yield the job instance so you have access to things like `job.arguments` on
+ the custom logic after retries fail.
+ ([commit](https://github.com/rails/rails/commit/a1e4c197cb12fef66530a2edfaeda75566088d1f))
+
Active Support
--------------
Please refer to the [Changelog][active-support] for detailed changes.
+### Removals
+
+* Removed the `ActiveSupport::Concurrency::Latch` class.
+ ([Commit](https://github.com/rails/rails/commit/0d7bd2031b4054fbdeab0a00dd58b1b08fb7fea6))
+
+* Removed `halt_callback_chains_on_return_false`.
+ ([Commit](https://github.com/rails/rails/commit/4e63ce53fc25c3bc15c5ebf54bab54fa847ee02a))
+
+* Removed deprecated behavior that halts callbacks when the return is false.
+ ([Commit](https://github.com/rails/rails/commit/3a25cdca3e0d29ee2040931d0cb6c275d612dffe))
+
+### Deprecations
+
+* The top level `HashWithIndifferentAccess` class has been softly deprecated
+ in favor of the `ActiveSupport::HashWithIndifferentAccess` one.
+ ([Pull Request](https://github.com/rails/rails/pull/28157))
+
+* Deprecated passing string to `:if` and `:unless` conditional options on `set_callback` and `skip_callback`.
+ ([Commit](https://github.com/rails/rails/commit/0952552)
+
+### Notable changes
+
+* Fixed duration parsing and traveling to make it consistent across DST changes.
+ ([Commit](https://github.com/rails/rails/commit/8931916f4a1c1d8e70c06063ba63928c5c7eab1e),
+ [Pull Request](https://github.com/rails/rails/pull/26597))
+
+* Updated Unicode to version 9.0.0.
+ ([Pull Request](https://github.com/rails/rails/pull/27822))
+
+* Add Duration#before and #after as aliases for #ago and #since.
+ ([Pull Request](https://github.com/rails/rails/pull/27721))
+
+* Added `Module#delegate_missing_to` to delegate method calls not
+ defined for the current object to a proxy object.
+ ([Pull Request](https://github.com/rails/rails/pull/23930))
+
+* Added `Date#all_day` which returns a range representing the whole day
+ of the current date & time.
+ ([Pull Request](https://github.com/rails/rails/pull/24930))
+
+* Introduced the `assert_changes` and `assert_no_changes` methods for tests.
+ ([Pull Request](https://github.com/rails/rails/pull/25393))
+
+* The `travel` and `travel_to` methods now raise on nested calls.
+ ([Pull Request](https://github.com/rails/rails/pull/24890))
+
+* Update `DateTime#change` to support usec and nsec.
+ ([Pull Request](https://github.com/rails/rails/pull/28242))
+
Credits
-------
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index 5d987264f5..22537f960c 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -715,6 +715,9 @@ end
Now, the `LoginsController`'s `new` and `create` actions will work as before without requiring the user to be logged in. The `:only` option is used to skip this filter only for these actions, and there is also an `:except` option which works the other way. These options can be used when adding filters too, so you can add a filter which only runs for selected actions in the first place.
+NOTE: Calling the same filter multiple times with different options will not work,
+since the last filter definition will overwrite the previous ones.
+
### After Filters and Around Filters
In addition to "before" filters, you can also run filters after an action has been executed, or both before and after.
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index 65146ee7da..7751ac00df 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -781,7 +781,8 @@ config.action_mailer.smtp_settings = {
enable_starttls_auto: true }
```
Note: As of July 15, 2014, Google increased [its security measures](https://support.google.com/accounts/answer/6010255) and now blocks attempts from apps it deems less secure.
-You can change your gmail settings [here](https://www.google.com/settings/security/lesssecureapps) to allow the attempts or
+You can change your Gmail settings [here](https://www.google.com/settings/security/lesssecureapps) to allow the attempts. If your Gmail account has 2-factor authentication enabled,
+then you will need to set an [app password](https://myaccount.google.com/apppasswords) and use that instead of your regular password. Alternatively, you can
use another ESP to send email by replacing 'smtp.gmail.com' above with the address of your provider.
Mailer Testing
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index c835adeab6..10412128cc 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -419,7 +419,7 @@ image_tag("rails.png") # => <img src="http://assets.example.com/images/rails.png
#### auto_discovery_link_tag
-Returns a link tag that browsers and feed readers can use to auto-detect an RSS or Atom feed.
+Returns a link tag that browsers and feed readers can use to auto-detect an RSS, Atom, or JSON feed.
```ruby
auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", { title: "RSS Feed" }) # =>
diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md
index b58ca61848..443be77934 100644
--- a/guides/source/active_job_basics.md
+++ b/guides/source/active_job_basics.md
@@ -310,6 +310,12 @@ UserMailer.welcome(@user).deliver_now
UserMailer.welcome(@user).deliver_later
```
+NOTE: Using the asynchronous queue from a Rake task (for example, to
+send an email using `.deliver_later`) will generally not work because Rake will
+likely end, causing the in-process thread pool to be deleted, before any/all
+of the `.deliver_later` emails are processed. To avoid this problem, use
+`.deliver_now` or run a persistent queue in development.
+
Internationalization
--------------------
diff --git a/guides/source/active_model_basics.md b/guides/source/active_model_basics.md
index e26805d22c..b8f076a27b 100644
--- a/guides/source/active_model_basics.md
+++ b/guides/source/active_model_basics.md
@@ -469,7 +469,7 @@ In order to make this work, the model must have an accessor named `password_dige
The `has_secure_password` will add the following validations on the `password` accessor:
1. Password should be present.
-2. Password should be equal to its confirmation (provided +password_confirmation+ is passed along).
+2. Password should be equal to its confirmation (provided `password_confirmation` is passed along).
3. The maximum length of a password is 72 (required by `bcrypt` on which ActiveModel::SecurePassword depends)
#### Examples
diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md
index 77bd3c97e8..b1705855d0 100644
--- a/guides/source/active_record_callbacks.md
+++ b/guides/source/active_record_callbacks.md
@@ -117,6 +117,10 @@ Here is a list with all the available Active Record callbacks, listed in the sam
WARNING. `after_save` runs both on create and update, but always _after_ the more specific callbacks `after_create` and `after_update`, no matter the order in which the macro calls were executed.
+NOTE: `before_destroy` callbacks should be placed before `dependent: :destroy`
+associations (or use the `prepend: true` option), to ensure they execute before
+the records are deleted by `dependent: :destroy`.
+
### `after_initialize` and `after_find`
The `after_initialize` callback will be called whenever an Active Record object is instantiated, either by directly using `new` or when a record is loaded from the database. It can be useful to avoid the need to directly override your Active Record `initialize` method.
@@ -254,7 +258,11 @@ Halting Execution
As you start registering new callbacks for your models, they will be queued for execution. This queue will include all your model's validations, the registered callbacks, and the database operation to be executed.
-The whole callback chain is wrapped in a transaction. If any _before_ callback method returns exactly `false` or raises an exception, the execution chain gets halted and a ROLLBACK is issued; _after_ callbacks can only accomplish that by raising an exception.
+The whole callback chain is wrapped in a transaction. If any callback raises an exception, the execution chain gets halted and a ROLLBACK is issued. To intentionally stop a chain use:
+
+```ruby
+throw :abort
+```
WARNING. Any exception that is not `ActiveRecord::Rollback` or `ActiveRecord::RecordInvalid` will be re-raised by Rails after the callback chain is halted. Raising an exception other than `ActiveRecord::Rollback` or `ActiveRecord::RecordInvalid` may break code that does not expect methods like `save` and `update_attributes` (which normally try to return `true` or `false`) to raise an exception.
diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md
index 6e7e29ed60..7fdb5901f3 100644
--- a/guides/source/active_record_migrations.md
+++ b/guides/source/active_record_migrations.md
@@ -972,11 +972,11 @@ on. Because this is database-independent, it could be loaded into any database
that Active Record supports. This could be very useful if you were to
distribute an application that is able to run against multiple databases.
-There is however a trade-off: `db/schema.rb` cannot express database specific
-items such as triggers, stored procedures or check constraints. While in a
-migration you can execute custom SQL statements, the schema dumper cannot
-reconstitute those statements from the database. If you are using features like
-this, then you should set the schema format to `:sql`.
+NOTE: `db/schema.rb` cannot express database specific items such as triggers,
+sequences, stored procedures or check constraints, etc. Please note that while
+custom SQL statements can be run in migrations, these statements cannot be reconstituted
+by the schema dumper. If you are using features like this, then you
+should set the schema format to `:sql`.
Instead of using Active Record's schema dumper, the database's structure will
be dumped using a tool specific to the database (via the `db:structure:dump`
diff --git a/guides/source/active_record_postgresql.md b/guides/source/active_record_postgresql.md
index 6d07291b07..8543fcd20f 100644
--- a/guides/source/active_record_postgresql.md
+++ b/guides/source/active_record_postgresql.md
@@ -84,7 +84,7 @@ Book.where("array_length(ratings, 1) >= 3")
### Hstore
* [type definition](http://www.postgresql.org/docs/current/static/hstore.html)
-* [functions and operators](http://www.postgresql.org/docs/current/static/hstore.html#AEN167712)
+* [functions and operators](http://www.postgresql.org/docs/current/static/hstore.html#AEN179902)
NOTE: You need to enable the `hstore` extension to use hstore.
@@ -114,16 +114,21 @@ Profile.where("settings->'color' = ?", "yellow")
# => #<ActiveRecord::Relation [#<Profile id: 1, settings: {"color"=>"yellow", "resolution"=>"1280x1024"}>]>
```
-### JSON
+### JSON and JSONB
* [type definition](http://www.postgresql.org/docs/current/static/datatype-json.html)
* [functions and operators](http://www.postgresql.org/docs/current/static/functions-json.html)
```ruby
# db/migrate/20131220144913_create_events.rb
+# ... for json datatype:
create_table :events do |t|
t.json 'payload'
end
+# ... or for jsonb datatype:
+create_table :events do |t|
+ t.jsonb 'payload'
+end
# app/models/event.rb
class Event < ApplicationRecord
@@ -285,7 +290,7 @@ SELECT n.nspname AS enum_schema,
### UUID
* [type definition](http://www.postgresql.org/docs/current/static/datatype-uuid.html)
-* [pgcrypto generator function](http://www.postgresql.org/docs/current/static/pgcrypto.html#AEN159361)
+* [pgcrypto generator function](http://www.postgresql.org/docs/current/static/pgcrypto.html#AEN182570)
* [uuid-ossp generator functions](http://www.postgresql.org/docs/current/static/uuid-ossp.html)
NOTE: You need to enable the `pgcrypto` (only PostgreSQL >= 9.4) or `uuid-ossp`
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 26d01d4ede..215142223d 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -118,7 +118,7 @@ You can also use this method to query for multiple objects. Call the `find` meth
```ruby
# Find the clients with primary keys 1 and 10.
-client = Client.find([1, 10]) # Or even Client.find(1, 10)
+clients = Client.find([1, 10]) # Or even Client.find(1, 10)
# => [#<Client id: 1, first_name: "Lifo">, #<Client id: 10, first_name: "Ryan">]
```
@@ -150,7 +150,7 @@ The `take` method returns `nil` if no record is found and no exception will be r
You can pass in a numerical argument to the `take` method to return up to that number of results. For example
```ruby
-client = Client.take(2)
+clients = Client.take(2)
# => [
# #<Client id: 1, first_name: "Lifo">,
# #<Client id: 220, first_name: "Sara">
@@ -189,7 +189,7 @@ If your [default scope](active_record_querying.html#applying-a-default-scope) co
You can pass in a numerical argument to the `first` method to return up to that number of results. For example
```ruby
-client = Client.first(3)
+clients = Client.first(3)
# => [
# #<Client id: 1, first_name: "Lifo">,
# #<Client id: 2, first_name: "Fifo">,
@@ -240,7 +240,7 @@ If your [default scope](active_record_querying.html#applying-a-default-scope) co
You can pass in a numerical argument to the `last` method to return up to that number of results. For example
```ruby
-client = Client.last(3)
+clients = Client.last(3)
# => [
# #<Client id: 219, first_name: "James">,
# #<Client id: 220, first_name: "Sara">,
@@ -513,8 +513,6 @@ Article.where(author: author)
Author.joins(:articles).where(articles: { author: author })
```
-NOTE: The values cannot be symbols. For example, you cannot do `Client.where(status: :active)`.
-
#### Range Conditions
```ruby
@@ -557,6 +555,19 @@ In other words, this query can be generated by calling `where` with no argument,
SELECT * FROM clients WHERE (clients.locked != 1)
```
+### OR Conditions
+
+`OR` conditions between two relations can be built by calling `or` on the first
+relation, and passing the second one as an argument.
+
+```ruby
+Client.where(locked: true).or(Client.where(orders_count: [1,3,5]))
+```
+
+```sql
+SELECT * FROM clients WHERE (clients.locked = 1 OR clients.orders_count IN (1,3,5))
+```
+
Ordering
--------
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index 5313361dfd..6eb5de78be 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -953,7 +953,7 @@ should happen, an `Array` can be used. Moreover, you can apply both `:if` and
```ruby
class Computer < ApplicationRecord
validates :mouse, presence: true,
- if: ["market.retail?", :desktop?],
+ if: [Proc.new { |c| c.market.retail? }, :desktop?],
unless: Proc.new { |c| c.trackpad.present? }
end
```
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index 67bed4c8da..23f53ac084 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -755,6 +755,8 @@ NOTE: Defined in `active_support/core_ext/module/anonymous.rb`.
### Method Delegation
+#### `delegate`
+
The macro `delegate` offers an easy way to forward methods.
Let's imagine that users in some application have login information in the `User` model but name and other data in a separate `Profile` model:
@@ -837,13 +839,32 @@ In the previous example the macro generates `avatar_size` rather than `size`.
NOTE: Defined in `active_support/core_ext/module/delegation.rb`
+#### `delegate_missing_to`
+
+Imagine you would like to delegate everything missing from the `User` object,
+to the `Profile` one. The `delegate_missing_to` macro lets you implement this
+in a breeze:
+
+```ruby
+class User < ApplicationRecord
+ has_one :profile
+
+ delegate_missing_to :profile
+end
+```
+
+The target can be anything callable within the object, e.g. instance variables,
+methods, constants, etc. Only the public methods of the target are delegated.
+
+NOTE: Defined in `active_support/core_ext/module/delegation.rb`.
+
### Redefining Methods
There are cases where you need to define a method with `define_method`, but don't know whether a method with that name already exists. If it does, a warning is issued if they are enabled. No big deal, but not clean either.
The method `redefine_method` prevents such a potential warning, removing the existing method before if needed.
-NOTE: Defined in `active_support/core_ext/module/remove_method.rb`
+NOTE: Defined in `active_support/core_ext/module/remove_method.rb`.
Extensions to `Class`
---------------------
@@ -931,7 +952,7 @@ When `:instance_reader` is `false`, the instance predicate returns a `NoMethodEr
If you do not want the instance predicate, pass `instance_predicate: false` and it will not be defined.
-NOTE: Defined in `active_support/core_ext/class/attribute.rb`
+NOTE: Defined in `active_support/core_ext/class/attribute.rb`.
#### `cattr_reader`, `cattr_writer`, and `cattr_accessor`
@@ -940,8 +961,7 @@ The macros `cattr_reader`, `cattr_writer`, and `cattr_accessor` are analogous to
```ruby
class MysqlAdapter < AbstractAdapter
# Generates class methods to access @@emulate_booleans.
- cattr_accessor :emulate_booleans
- self.emulate_booleans = true
+ cattr_accessor :emulate_booleans, default: true
end
```
@@ -950,8 +970,7 @@ Instance methods are created as well for convenience, they are just proxies to t
```ruby
module ActionView
class Base
- cattr_accessor :field_error_proc
- @@field_error_proc = Proc.new{ ... }
+ cattr_accessor :field_error_proc, default: Proc.new { ... }
end
end
```
@@ -963,7 +982,7 @@ Also, you can pass a block to `cattr_*` to set up the attribute with a default v
```ruby
class MysqlAdapter < AbstractAdapter
# Generates class methods to access @@emulate_booleans with default value of true.
- cattr_accessor(:emulate_booleans) { true }
+ cattr_accessor :emulate_booleans, default: true
end
```
@@ -1829,7 +1848,7 @@ as well as adding or subtracting their results from a Time object. For example:
(4.months + 5.years).from_now
```
-NOTE: Defined in `active_support/core_ext/numeric/time.rb`
+NOTE: Defined in `active_support/core_ext/numeric/time.rb`.
### Formatting
diff --git a/guides/source/api_app.md b/guides/source/api_app.md
index f373d313cc..64200ec242 100644
--- a/guides/source/api_app.md
+++ b/guides/source/api_app.md
@@ -206,16 +206,17 @@ An API application comes with the following middleware by default:
- `ActiveSupport::Cache::Strategy::LocalCache::Middleware`
- `Rack::Runtime`
- `ActionDispatch::RequestId`
+- `ActionDispatch::RemoteIp`
- `Rails::Rack::Logger`
- `ActionDispatch::ShowExceptions`
- `ActionDispatch::DebugExceptions`
-- `ActionDispatch::RemoteIp`
- `ActionDispatch::Reloader`
- `ActionDispatch::Callbacks`
- `ActiveRecord::Migration::CheckPending`
- `Rack::Head`
- `Rack::ConditionalGet`
- `Rack::ETag`
+- `MyApi::Application::Routes`
See the [internal middleware](rails_on_rack.html#internal-middleware-stack)
section of the Rack guide for further information on them.
@@ -360,7 +361,7 @@ middleware set, you can remove it with:
config.middleware.delete ::Rack::Sendfile
```
-Keep in mind that removing these middleware will remove support for certain
+Keep in mind that removing these middlewares will remove support for certain
features in Action Controller.
Choosing Controller Modules
@@ -385,8 +386,9 @@ controller modules by default:
hooks defined by Action Controller (see [the instrumentation
guide](active_support_instrumentation.html#action-controller) for
more information regarding this).
-- `ActionController::ParamsWrapper`: Wraps the parameters hash into a nested hash,
+- `ActionController::ParamsWrapper`: Wraps the parameters hash into a nested hash,
so that you don't have to specify root elements sending POST requests for instance.
+- `ActionController::Head`: Support for returning a response with no content, only headers
Other plugins may add additional modules. You can get a list of all modules
included into `ActionController::API` in the rails console:
@@ -394,12 +396,12 @@ included into `ActionController::API` in the rails console:
```bash
$ bin/rails c
>> ActionController::API.ancestors - ActionController::Metal.ancestors
-=> [ActionController::API,
- ActiveRecord::Railties::ControllerRuntime,
- ActionDispatch::Routing::RouteSet::MountedHelpers,
- ActionController::ParamsWrapper,
- ... ,
- AbstractController::Rendering,
+=> [ActionController::API,
+ ActiveRecord::Railties::ControllerRuntime,
+ ActionDispatch::Routing::RouteSet::MountedHelpers,
+ ActionController::ParamsWrapper,
+ ... ,
+ AbstractController::Rendering,
ActionView::ViewPaths]
```
diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md
index 34b9c0d2ca..c3c7367304 100644
--- a/guides/source/api_documentation_guidelines.md
+++ b/guides/source/api_documentation_guidelines.md
@@ -281,7 +281,7 @@ Methods created with `(module|class)_eval(STRING)` have a comment by their side
```ruby
for severity in Severity.constants
- class_eval <<-EOT, __FILE__, __LINE__
+ class_eval <<-EOT, __FILE__, __LINE__ + 1
def #{severity.downcase}(message = nil, progname = nil, &block) # def debug(message = nil, progname = nil, &block)
add(#{severity}, message, progname, &block) # add(DEBUG, message, progname, &block)
end # end
@@ -333,10 +333,6 @@ As a contributor, it's important to think about whether this API is meant for en
A class or module is marked with `:nodoc:` to indicate that all methods are internal API and should never be used directly.
-If you come across an existing `:nodoc:` you should tread lightly. Consider asking someone from the core team or author of the code before removing it. This should almost always happen through a pull request instead of the docrails project.
-
-A `:nodoc:` should never be added simply because a method or class is missing documentation. There may be an instance where an internal public method wasn't given a `:nodoc:` by mistake, for example when switching a method from private to public visibility. When this happens it should be discussed over a PR on a case-by-case basis and never committed directly to docrails.
-
To summarize, the Rails team uses `:nodoc:` to mark publicly visible methods and classes for internal use; changes to the visibility of API should be considered carefully and discussed over a pull request first.
Regarding the Rails Stack
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 61b7112247..a02eebf263 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -447,15 +447,15 @@ For example, a new Rails application includes a default
```js
// ...
-//= require jquery
-//= require jquery_ujs
+//= require rails-ujs
+//= require turbolinks
//= require_tree .
```
In JavaScript files, Sprockets directives begin with `//=`. In the above case,
the file is using the `require` and the `require_tree` directives. The `require`
directive is used to tell Sprockets the files you wish to require. Here, you are
-requiring the files `jquery.js` and `jquery_ujs.js` that are available somewhere
+requiring the files `rails-ujs.js` and `turbolinks.js` that are available somewhere
in the search path for Sprockets. You need not supply the extensions explicitly.
Sprockets assumes you are requiring a `.js` file when done from within a `.js`
file.
@@ -572,20 +572,6 @@ would generate this HTML:
The `body` param is required by Sprockets.
-### Runtime Error Checking
-
-By default the asset pipeline will check for potential errors in development mode during
-runtime. To disable this behavior you can set:
-
-```ruby
-config.assets.raise_runtime_errors = false
-```
-
-When this option is true, the asset pipeline will check if all the assets loaded
-in your application are included in the `config.assets.precompile` list.
-If `config.assets.digest` is also true, the asset pipeline will require that
-all requests for assets include digests.
-
### Raise an Error When an Asset is Not Found
If you are using sprockets-rails >= 3.2.0 you can configure what happens
@@ -868,7 +854,7 @@ pre-existing JavaScript runtimes, you may want to add one to your Gemfile:
```ruby
group :production do
- gem 'therubyracer'
+ gem 'mini_racer'
end
```
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index 5794bfa666..bead931529 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -599,7 +599,7 @@ class CreateBooks < ActiveRecord::Migration[5.0]
t.string :book_number
t.integer :author_id
end
-
+
add_index :books, :author_id
add_foreign_key :books, :authors
end
@@ -960,7 +960,7 @@ class Author < ApplicationRecord
end
```
-NOTE: You only need to specify the :counter_cache option on the `belongs_to`
+NOTE: You only need to specify the `:counter_cache` option on the `belongs_to`
side of the association.
Counter cache columns are added to the containing model's list of read-only attributes through `attr_readonly`.
@@ -1417,7 +1417,7 @@ If either of these saves fails due to validation errors, then the assignment sta
If the parent object (the one declaring the `has_one` association) is unsaved (that is, `new_record?` returns `true`) then the child objects are not saved. They will automatically when the parent object is saved.
-If you want to assign an object to a `has_one` association without saving the object, use the `association.build` method.
+If you want to assign an object to a `has_one` association without saving the object, use the `build_association` method.
### `has_many` Association Reference
@@ -1559,7 +1559,7 @@ The `collection.size` method returns the number of objects in the collection.
The `collection.find` method finds objects within the collection. It uses the same syntax and options as `ActiveRecord::Base.find`.
```ruby
-@available_books = @author.books.find(1)
+@available_book = @author.books.find(1)
```
##### `collection.where(...)`
@@ -1831,7 +1831,7 @@ The `limit` method lets you restrict the total number of objects that will be fe
class Author < ApplicationRecord
has_many :recent_books,
-> { order('published_at desc').limit(100) },
- class_name: "Book",
+ class_name: "Book"
end
```
diff --git a/guides/source/autoloading_and_reloading_constants.md b/guides/source/autoloading_and_reloading_constants.md
index 61657023e7..05743ee4ce 100644
--- a/guides/source/autoloading_and_reloading_constants.md
+++ b/guides/source/autoloading_and_reloading_constants.md
@@ -983,20 +983,19 @@ WHERE "polygons"."type" IN ("Rectangle")
That is not a bug, the query includes all *known* descendants of `Rectangle`.
A way to ensure this works correctly regardless of the order of execution is to
-load the leaves of the tree by hand at the bottom of the file that defines the
-root class:
+manually load the direct subclasses at the bottom of the file that defines each
+intermediate class:
```ruby
-# app/models/polygon.rb
-class Polygon < ApplicationRecord
+# app/models/rectangle.rb
+class Rectangle < Polygon
end
-require_dependency ‘square’
+require_dependency 'square'
```
-Only the leaves that are **at least grandchildren** need to be loaded this
-way. Direct subclasses do not need to be preloaded. If the hierarchy is
-deeper, intermediate classes will be autoloaded recursively from the bottom
-because their constant will appear in the class definitions as superclass.
+This needs to happen for every intermediate (non-root and non-leaf) class. The
+root class does not scope the query by type, and therefore does not necessarily
+have to know all its descendants.
### Autoloading and `require`
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index 3360496c08..9fddbf76b6 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -262,12 +262,12 @@ $ bin/rails db:migrate
== CreateHighScores: migrated (0.0019s) ======================================
```
-INFO: Let's talk about unit tests. Unit tests are code that tests and makes assertions
-about code. In unit testing, we take a little part of code, say a method of a model,
-and test its inputs and outputs. Unit tests are your friend. The sooner you make
-peace with the fact that your quality of life will drastically increase when you unit
-test your code, the better. Seriously. Please visit
-[the testing guide](http://guides.rubyonrails.org/testing.html) for an in-depth
+INFO: Let's talk about unit tests. Unit tests are code that tests and makes assertions
+about code. In unit testing, we take a little part of code, say a method of a model,
+and test its inputs and outputs. Unit tests are your friend. The sooner you make
+peace with the fact that your quality of life will drastically increase when you unit
+test your code, the better. Seriously. Please visit
+[the testing guide](http://guides.rubyonrails.org/testing.html) for an in-depth
look at unit testing.
Let's see the interface Rails created for us.
@@ -533,7 +533,8 @@ The `tmp:` namespaced tasks will help you clear and create the `Rails.root/tmp`
* `rails tmp:cache:clear` clears `tmp/cache`.
* `rails tmp:sockets:clear` clears `tmp/sockets`.
-* `rails tmp:clear` clears all cache and sockets files.
+* `rails tmp:screenshots:clear` clears `tmp/screenshots`.
+* `rails tmp:clear` clears all cache, sockets and screenshot files.
* `rails tmp:create` creates tmp directories for cache, sockets and pids.
### Miscellaneous
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index ae70b06996..21b3ca0efa 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -157,8 +157,6 @@ defaults to `:debug` for all environments. The available log levels are: `:debug
* `config.assets.enabled` a flag that controls whether the asset
pipeline is enabled. It is set to `true` by default.
-* `config.assets.raise_runtime_errors` Set this flag to `true` to enable additional runtime error checking. Recommended in `config/environments/development.rb` to minimize unexpected behavior when deploying to `production`.
-
* `config.assets.css_compressor` defines the CSS compressor to use. It is set by default by `sass-rails`. The unique alternative value at the moment is `:yui`, which uses the `yui-compressor` gem.
* `config.assets.js_compressor` defines the JavaScript compressor to use. Possible values are `:closure`, `:uglifier` and `:yui` which require the use of the `closure-compiler`, `uglifier` or `yui-compressor` gems respectively.
@@ -456,10 +454,14 @@ to `'http authentication'`.
Defaults to `'signed cookie'`.
* `config.action_dispatch.encrypted_cookie_salt` sets the encrypted cookies salt
-value. Defaults to `'encrypted cookie'`.
+ value. Defaults to `'encrypted cookie'`.
* `config.action_dispatch.encrypted_signed_cookie_salt` sets the signed
-encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
+ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
+
+* `config.action_dispatch.authenticated_encrypted_cookie_salt` sets the
+ authenticated encrypted cookie salt. Defaults to `'authenticated encrypted
+ cookie'`.
* `config.action_dispatch.perform_deep_munge` configures whether `deep_munge`
method should be performed on the parameters. See [Security Guide](security.html#unsafe-query-generation)
@@ -493,8 +495,6 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
* `ActionDispatch::Callbacks.before` takes a block of code to run before the request.
-* `ActionDispatch::Callbacks.to_prepare` takes a block to run after `ActionDispatch::Callbacks.before`, but before the request. Runs for every request in `development` mode, but only once for `production` or environments with `cache_classes` set to `true`.
-
* `ActionDispatch::Callbacks.after` takes a block of code to run after the request.
### Configuring Action View
@@ -543,6 +543,8 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
* `config.action_view.debug_missing_translation` determines whether to wrap the missing translations key in a `<span>` tag or not. This defaults to `true`.
+* `config.action_view.form_with_generates_remote_forms` determines whether `form_with` generates remote forms or not. This defaults to `true`.
+
### Configuring Action Mailer
There are a number of settings available on `config.action_mailer`:
@@ -1186,7 +1188,7 @@ Below is a comprehensive list of all the initializers found in Rails in the orde
* `finisher_hook`: Provides a hook for after the initialization of process of the application is complete, as well as running all the `config.after_initialize` blocks for the application, railties and engines.
-* `set_routes_reloader_hook`: Configures Action Dispatch to reload the routes file using `ActionDispatch::Callbacks.to_prepare`.
+* `set_routes_reloader_hook`: Configures Action Dispatch to reload the routes file using `ActiveSupport::Callbacks.to_run`.
* `disable_dependency_loading`: Disables the automatic dependency loading if the `config.eager_load` is set to `true`.
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index 3b19b0dff1..2f2962a3e6 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -132,35 +132,24 @@ learn about Ruby on Rails, and the API, which serves as a reference.
You can help improve the Rails guides by making them more coherent, consistent or readable, adding missing information, correcting factual errors, fixing typos, or bringing them up to date with the latest edge Rails.
-You can either open a pull request to [Rails](https://github.com/rails/rails) or
-ask the [Rails core team](http://rubyonrails.org/community/#core) for commit access on
-docrails if you contribute regularly.
-Please do not open pull requests in docrails, if you'd like to get feedback on your
-change, ask for it in [Rails](https://github.com/rails/rails) instead.
-
-Docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation.
-
-If you are unsure of the documentation changes, you can create an issue in the [Rails](https://github.com/rails/rails/issues) issues tracker on GitHub.
+To do so, open a pull request to [Rails](https://github.com/rails/rails) on GitHub.
When working with documentation, please take into account the [API Documentation Guidelines](api_documentation_guidelines.html) and the [Ruby on Rails Guides Guidelines](ruby_on_rails_guides_guidelines.html).
-NOTE: As explained earlier, ordinary code patches should have proper documentation coverage. Docrails is only used for isolated documentation improvements.
-
NOTE: To help our CI servers you should add [ci skip] to your documentation commit message to skip build on that commit. Please remember to use it for commits containing only documentation changes.
-WARNING: Docrails has a very strict policy: no code can be touched whatsoever, no matter how trivial or small the change. Only RDoc and guides can be edited via docrails. Also, CHANGELOGs should never be edited in docrails.
-
Translating Rails Guides
------------------------
-We are happy to have people volunteer to translate the Rails guides into their own language.
-If you want to translate the Rails guides in your own language, follows these steps:
+We are happy to have people volunteer to translate the Rails guides. Just follow these steps:
-* Fork the project (rails/rails).
+* Fork https://github.com/rails/rails.
* Add a source folder for your own language, for example: *guides/source/it-IT* for Italian.
* Copy the contents of *guides/source* into your own language directory and translate them.
* Do NOT translate the HTML files, as they are automatically generated.
+Note that translations are not submitted to the Rails repository. As detailed above, your work happens in a fork. This is so because in practice documentation maintenance via patches is only sustainable in English.
+
To generate the guides in HTML format cd into the *guides* directory then run (eg. for it-IT):
```bash
@@ -418,16 +407,6 @@ examples or multiple paragraphs. Otherwise, it's best to make a new paragraph.
Some changes require the dependencies to be upgraded. In these cases make sure you run `bundle update` to get the right version of the dependency and commit the `Gemfile.lock` file within your changes.
-### Sanity Check
-
-You should not be the only person who looks at the code before you submit it.
-If you know someone else who uses Rails, try asking them if they'll check out
-your work. If you don't know anyone else using Rails, try hopping into the IRC
-room or posting about your idea to the rails-core mailing list. Doing this in
-private before you push a patch out publicly is the "smoke test" for a patch:
-if you can't convince one other developer of the beauty of your code, you’re
-unlikely to convince the core team either.
-
### Commit Your Changes
When you're happy with the code on your computer, you need to commit the changes to Git:
@@ -685,4 +664,4 @@ And then... think about your next contribution!
Rails Contributors
------------------
-All contributions, either via master or docrails, get credit in [Rails Contributors](http://contributors.rubyonrails.org).
+All contributions get credit in [Rails Contributors](http://contributors.rubyonrails.org).
diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md
index 7ec038eb4d..c57efd6362 100644
--- a/guides/source/development_dependencies_install.md
+++ b/guides/source/development_dependencies_install.md
@@ -62,7 +62,7 @@ $ sudo apt-get install sqlite3 libsqlite3-dev
If you are on Fedora or CentOS, you're done with
```bash
-$ sudo yum install sqlite3 sqlite3-devel
+$ sudo yum install libsqlite3x libsqlite3x-devel
```
If you are on Arch Linux, you will need to run:
diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml
index 5fccdcccec..59205ee465 100644
--- a/guides/source/documents.yaml
+++ b/guides/source/documents.yaml
@@ -130,11 +130,6 @@
url: active_support_instrumentation.html
description: This guide explains how to use the instrumentation API inside of Active Support to measure events inside of Rails and other Ruby code.
-
- name: Profiling Rails Applications
- work_in_progress: true
- url: profiling.html
- description: This guide explains how to profile your Rails applications to improve performance.
- -
name: Using Rails for API-only Applications
url: api_app.html
description: This guide explains how to effectively use Rails to develop a JSON API application.
@@ -197,7 +192,6 @@
name: Ruby on Rails 5.1 Release Notes
url: 5_1_release_notes.html
description: Release notes for Rails 5.1.
- work_in_progress: true
-
name: Ruby on Rails 5.0 Release Notes
url: 5_0_release_notes.html
diff --git a/guides/source/engines.md b/guides/source/engines.md
index 180a786237..2276f348a1 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -14,6 +14,7 @@ After reading this guide, you will know:
* How to build features for the engine.
* How to hook the engine into an application.
* How to override engine functionality in the application.
+* Avoid loading Rails frameworks with Load and Configuration Hooks
--------------------------------------------------------------------------------
@@ -1410,3 +1411,114 @@ module MyEngine
end
end
```
+
+Active Support On Load Hooks
+----------------------------
+
+Active Support is the Ruby on Rails component responsible for providing Ruby language extensions, utilities, and other transversal utilities.
+
+Rails code can often be referenced on load of an application. Rails is responsible for the load order of these frameworks, so when you load frameworks, such as `ActiveRecord::Base`, prematurely you are violating an implicit contract your application has with Rails. Moreover, by loading code such as `ActiveRecord::Base` on boot of your application you are loading entire frameworks which may slow down your boot time and could cause conflicts with load order and boot of your application.
+
+On Load hooks are the API that allow you to hook into this initialization process without violating the load contract with Rails. This will also mitigate boot performance degradation and avoid conflicts.
+
+## What are `on_load` hooks?
+
+Since Ruby is a dynamic language, some code will cause different Rails frameworks to load. Take this snippet for instance:
+
+```ruby
+ActiveRecord::Base.include(MyActiveRecordHelper)
+```
+
+This snippet means that when this file is loaded, it will encounter `ActiveRecord::Base`. This encounter causes Ruby to look for the definition of that constant and will require it. This causes the entire Active Record framework to be loaded on boot.
+
+`ActiveSupport.on_load` is a mechanism that can be used to defer the loading of code until it is actually needed. The snippet above can be changed to:
+
+```ruby
+ActiveSupport.on_load(:active_record) { include MyActiveRecordHelper }
+```
+
+This new snippet will only include `MyActiveRecordHelper` when `ActiveRecord::Base` is loaded.
+
+## How does it work?
+
+In the Rails framework these hooks are called when a specific library is loaded. For example, when `ActionController::Base` is loaded, the `:action_controller_base` hook is called. This means that all `ActiveSupport.on_load` calls with `:action_controller_base` hooks will be called in the context of `ActionController::Base` (that means `self` will be an `ActionController::Base`).
+
+## Modifying code to use `on_load` hooks
+
+Modifying code is generally straightforward. If you have a line of code that refers to a Rails framework such as `ActiveRecord::Base` you can wrap that code in an `on_load` hook.
+
+### Example 1
+
+```ruby
+ActiveRecord::Base.include(MyActiveRecordHelper)
+```
+
+becomes
+
+```ruby
+ActiveSupport.on_load(:active_record) { include MyActiveRecordHelper } # self refers to ActiveRecord::Base here, so we can simply #include
+```
+
+### Example 2
+
+```ruby
+ActionController::Base.prepend(MyActionControllerHelper)
+```
+
+becomes
+
+```ruby
+ActiveSupport.on_load(:action_controller_base) { prepend MyActionControllerHelper } # self refers to ActionController::Base here, so we can simply #prepend
+```
+
+### Example 3
+
+```ruby
+ActiveRecord::Base.include_root_in_json = true
+```
+
+becomes
+
+```ruby
+ActiveSupport.on_load(:active_record) { self.include_root_in_json = true } # self refers to ActiveRecord::Base here
+```
+
+## Available Hooks
+
+These are the hooks you can use in your own code.
+
+To hook into the initialization process of one of the following classes use the available hook.
+
+| Class | Available Hooks |
+| --------------------------------- | ------------------------------------ |
+| `ActionCable` | `action_cable` |
+| `ActionController::API` | `action_controller_api` |
+| `ActionController::API` | `action_controller` |
+| `ActionController::Base` | `action_controller_base` |
+| `ActionController::Base` | `action_controller` |
+| `ActionController::TestCase` | `action_controller_test_case` |
+| `ActionDispatch::IntegrationTest` | `action_dispatch_integration_test` |
+| `ActionMailer::Base` | `action_mailer` |
+| `ActionMailer::TestCase` | `action_mailer_test_case` |
+| `ActionView::Base` | `action_view` |
+| `ActionView::TestCase` | `action_view_test_case` |
+| `ActiveJob::Base` | `active_job` |
+| `ActiveJob::TestCase` | `active_job_test_case` |
+| `ActiveRecord::Base` | `active_record` |
+| `ActiveSupport::TestCase` | `active_support_test_case` |
+| `i18n` | `i18n` |
+
+## Configuration hooks
+
+These are the available configuration hooks. They do not hook into any particular framework, instead they run in context of the entire application.
+
+| Hook | Use Case |
+| ---------------------- | ------------------------------------------------------------------------------------- |
+| `before_configuration` | First configurable block to run. Called before any initializers are run. |
+| `before_initialize` | Second configurable block to run. Called before frameworks initialize. |
+| `before_eager_load` | Third configurable block to run. Does not run if `config.cache_classes` set to false. |
+| `after_initialize` | Last configurable block to run. Called after frameworks initialize. |
+
+### Example
+
+`config.before_configuration { puts 'I am called before any initializers' }`
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index 0508b0fb38..f46f1648b3 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -164,7 +164,7 @@ make it easier for users to click the inputs.
Other form controls worth mentioning are textareas, password fields,
hidden fields, search fields, telephone fields, date fields, time fields,
-color fields, datetime fields, datetime-local fields, month fields, week fields,
+color fields, datetime-local fields, month fields, week fields,
URL fields, email fields, number fields and range fields:
```erb
@@ -877,7 +877,7 @@ Active Record provides model level support via the `accepts_nested_attributes_fo
```ruby
class Person < ApplicationRecord
- has_many :addresses
+ has_many :addresses, inverse_of: :person
accepts_nested_attributes_for :addresses
end
diff --git a/guides/source/generators.md b/guides/source/generators.md
index d0b6cef3fd..be1be75e7a 100644
--- a/guides/source/generators.md
+++ b/guides/source/generators.md
@@ -96,7 +96,7 @@ This is the generator just created:
```ruby
class InitializerGenerator < Rails::Generators::NamedBase
- source_root File.expand_path("../templates", __FILE__)
+ source_root File.expand_path("templates", __dir__)
end
```
@@ -122,7 +122,7 @@ And now let's change the generator to copy this template when invoked:
```ruby
class InitializerGenerator < Rails::Generators::NamedBase
- source_root File.expand_path("../templates", __FILE__)
+ source_root File.expand_path("templates", __dir__)
def copy_initializer_file
copy_file "initializer.rb", "config/initializers/#{file_name}.rb"
@@ -426,7 +426,7 @@ Fallbacks allow your generators to have a single responsibility, increasing code
Application Templates
---------------------
-Now that you've seen how generators can be used _inside_ an application, did you know they can also be used to _generate_ applications too? This kind of generator is referred as a "template". This is a brief overview of the Templates API. For detailed documentation see the [Rails Application Templates guide](rails_application_templates.html).
+Now that you've seen how generators can be used _inside_ an application, did you know they can also be used to _generate_ applications too? This kind of generator is referred to as a "template". This is a brief overview of the Templates API. For detailed documentation see the [Rails Application Templates guide](rails_application_templates.html).
```ruby
gem "rspec-rails", group: "test"
@@ -689,14 +689,6 @@ Available options are:
* `:env` - Specifies the environment in which to run this rake task.
* `:sudo` - Whether or not to run this task using `sudo`. Defaults to `false`.
-### `capify!`
-
-Runs the `capify` command from Capistrano at the root of the application which generates Capistrano configuration.
-
-```ruby
-capify!
-```
-
### `route`
Adds text to the `config/routes.rb` file:
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index 068114898d..2ed1883ede 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -20,16 +20,7 @@ Guide Assumptions
This guide is designed for beginners who want to get started with a Rails
application from scratch. It does not assume that you have any prior experience
-with Rails. However, to get the most out of it, you need to have some
-prerequisites installed:
-
-* The [Ruby](https://www.ruby-lang.org/en/downloads) language version 2.2.2 or newer.
-* Right version of [Development Kit](http://rubyinstaller.org/downloads/), if you
- are using Windows.
-* The [RubyGems](https://rubygems.org) packaging system, which is installed with
- Ruby by default. To learn more about RubyGems, please read the
- [RubyGems Guides](http://guides.rubygems.org).
-* A working installation of the [SQLite3 Database](https://www.sqlite.org).
+with Rails.
Rails is a web application framework running on the Ruby programming language.
If you have no prior experience with Ruby, you will find a very steep learning
@@ -46,7 +37,7 @@ development with Rails.
What is Rails?
--------------
-Rails is a web application development framework written in the Ruby language.
+Rails is a web application development framework written in the Ruby programming language.
It is designed to make programming web applications easier by making assumptions
about what every developer needs to get started. It allows you to write less
code while accomplishing more than many other languages and frameworks.
@@ -86,6 +77,9 @@ your prompt will look something like `c:\source_code>`
### Installing Rails
+Before you install Rails, you should check to make sure that your system has the
+proper prerequisites installed. These include Ruby and SQLite3.
+
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
@@ -96,12 +90,19 @@ $ ruby -v
ruby 2.3.1p112
```
+Rails requires Ruby version 2.2.2 or later. If the version number returned is
+less than that number, you'll need to install a fresh copy of Ruby.
+
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 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/).
+If you are working on Windows, you should also install the
+[Ruby Installer Development Kit](http://rubyinstaller.org/downloads/).
+
+You will also need an installation of the SQLite3 database.
Many popular UNIX-like OSes ship with an acceptable version of SQLite3.
On Windows, if you installed Rails through Rails Installer, you
already have SQLite installed. Others can find installation instructions
@@ -127,7 +128,7 @@ run the following:
$ rails --version
```
-If it says something like "Rails 5.1.0", you are ready to continue.
+If it says something like "Rails 5.1.1", you are ready to continue.
### Creating the Blog Application
@@ -182,7 +183,7 @@ of the files and folders that Rails created by default:
|test/|Unit tests, fixtures, and other test apparatus. These are covered in [Testing Rails Applications](testing.html).|
|tmp/|Temporary files (like cache and pid files).|
|vendor/|A place for all third-party code. In a typical Rails application this includes vendored gems.|
-|.gitignore|This file tells git which files (or patterns) it should ignore. See [Github - Ignoring files](https://help.github.com/articles/ignoring-files) for more info about ignoring files.
+|.gitignore|This file tells git which files (or patterns) it should ignore. See [GitHub - Ignoring files](https://help.github.com/articles/ignoring-files) for more info about ignoring files.
Hello, Rails!
-------------
@@ -207,7 +208,7 @@ 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 macOS and Windows come with a JavaScript runtime installed.
-Rails adds the `therubyracer` gem to the generated `Gemfile` in a
+Rails adds the `mini_racer` 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
default to the `Gemfile` in apps generated under JRuby. You can investigate
@@ -909,6 +910,7 @@ And then finally, add the view for this action, located at
<tr>
<th>Title</th>
<th>Text</th>
+ <th></th>
</tr>
<% @articles.each do |article| %>
@@ -1195,7 +1197,7 @@ it look as follows:
This time we point the form to the `update` action, which is not defined yet
but will be very soon.
-Passing the article object to the method, will automagically create url for submitting the edited article form.
+Passing the article object to the method, will automagically create url for submitting the edited article form.
This option tells Rails that we want this form to be submitted
via the `PATCH` HTTP method which is the HTTP method you're expected to use to
**update** resources according to the REST protocol.
@@ -1488,14 +1490,14 @@ second argument, and then the options as another argument. The `method: :delete`
and `data: { confirm: 'Are you sure?' }` options are used as HTML5 attributes so
that when the link is clicked, Rails will first show a confirm dialog to the
user, and then submit the link with method `delete`. This is done via the
-JavaScript file `jquery_ujs` which is automatically included in your
+JavaScript file `rails-ujs` which is automatically included in your
application's layout (`app/views/layouts/application.html.erb`) when you
generated the application. Without this file, the confirmation dialog box won't
appear.
![Confirm Dialog](images/getting_started/confirm_dialog.png)
-TIP: Learn more about jQuery Unobtrusive Adapter (jQuery UJS) on
+TIP: Learn more about Unobtrusive JavaScript on
[Working With JavaScript in Rails](working_with_javascript_in_rails.html) guide.
Congratulations, you can now create, show, list, update and destroy
@@ -1544,8 +1546,8 @@ You'll learn a little about associations in the next section of this guide.
The (`:references`) keyword used in the bash command is a special data type for models.
It creates a new column on your database table with the provided model name appended with an `_id`
-that can hold integer values. You can get a better understanding after analyzing the
-`db/schema.rb` file below.
+that can hold integer values. To get a better understanding, analyze the
+`db/schema.rb` file after running the migration.
In addition to the model, Rails has also made a migration to create the
corresponding database table:
diff --git a/guides/source/initialization.md b/guides/source/initialization.md
index 3ea156c6fe..86aea2c24d 100644
--- a/guides/source/initialization.md
+++ b/guides/source/initialization.md
@@ -155,7 +155,7 @@ defined here to find the matching command.
### `rails/command.rb`
When one types a Rails command, `invoke` tries to lookup a command for the given
-namespace and executing the command if found.
+namespace and executes the command if found.
If Rails doesn't recognize the command, it hands the reins over to Rake
to run a task of the same name.
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index 48bb3147f3..76b325d0bf 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -221,7 +221,7 @@ service requests that are expecting something other than proper HTML.
NOTE: By default, if you use the `:plain` option, the text is rendered without
using the current layout. If you want Rails to put the text into the current
-layout, you need to add the `layout: true` option and use the `.txt.erb`
+layout, you need to add the `layout: true` option and use the `.text.erb`
extension for the layout file.
#### Rendering HTML
@@ -379,6 +379,7 @@ Rails understands both numeric status codes and the corresponding symbols shown
| | 415 | :unsupported_media_type |
| | 416 | :range_not_satisfiable |
| | 417 | :expectation_failed |
+| | 421 | :misdirected_request |
| | 422 | :unprocessable_entity |
| | 423 | :locked |
| | 424 | :failed_dependency |
@@ -386,6 +387,7 @@ Rails understands both numeric status codes and the corresponding symbols shown
| | 428 | :precondition_required |
| | 429 | :too_many_requests |
| | 431 | :request_header_fields_too_large |
+| | 451 | :unavailable_for_legal_reasons |
| **Server Error** | 500 | :internal_server_error |
| | 501 | :not_implemented |
| | 502 | :bad_gateway |
@@ -768,7 +770,7 @@ WARNING: The asset tag helpers do _not_ verify the existence of the assets at th
#### Linking to Feeds with the `auto_discovery_link_tag`
-The `auto_discovery_link_tag` helper builds HTML that most browsers and feed readers can use to detect the presence of RSS or Atom feeds. It takes the type of the link (`:rss` or `:atom`), a hash of options that are passed through to url_for, and a hash of options for the tag:
+The `auto_discovery_link_tag` helper builds HTML that most browsers and feed readers can use to detect the presence of RSS, Atom, or JSON feeds. It takes the type of the link (`:rss`, `:atom`, or `:json`), a hash of options that are passed through to url_for, and a hash of options for the tag:
```erb
<%= auto_discovery_link_tag(:rss, {action: "feed"},
@@ -1171,7 +1173,7 @@ To pass a local variable to a partial in only specific cases use the `local_assi
This way it is possible to use the partial without the need to declare all local variables.
-Every partial also has a local variable with the same name as the partial (minus the underscore). You can pass an object in to this local variable via the `:object` option:
+Every partial also has a local variable with the same name as the partial (minus the leading underscore). You can pass an object in to this local variable via the `:object` option:
```erb
<%= render partial: "customer", object: @new_customer %>
diff --git a/guides/source/nested_model_forms.md b/guides/source/nested_model_forms.md
deleted file mode 100644
index 71efa4b0d0..0000000000
--- a/guides/source/nested_model_forms.md
+++ /dev/null
@@ -1,230 +0,0 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
-
-Rails Nested Model Forms
-========================
-
-Creating a form for a model _and_ its associations can become quite tedious. Therefore Rails provides helpers to assist in dealing with the complexities of generating these forms _and_ the required CRUD operations to create, update, and destroy associations.
-
-After reading this guide, you will know:
-
-* do stuff.
-
---------------------------------------------------------------------------------
-
-NOTE: This guide assumes the user knows how to use the [Rails form helpers](form_helpers.html) in general. Also, it's **not** an API reference. For a complete reference please visit [the Rails API documentation](http://api.rubyonrails.org/).
-
-
-Model setup
------------
-
-To be able to use the nested model functionality in your forms, the model will need to support some basic operations.
-
-First of all, it needs to define a writer method for the attribute that corresponds to the association you are building a nested model form for. The `fields_for` form helper will look for this method to decide whether or not a nested model form should be built.
-
-If the associated object is an array, a form builder will be yielded for each object, else only a single form builder will be yielded.
-
-Consider a Person model with an associated Address. When asked to yield a nested FormBuilder for the `:address` attribute, the `fields_for` form helper will look for a method on the Person instance named `address_attributes=`.
-
-### ActiveRecord::Base model
-
-For an ActiveRecord::Base model and association this writer method is commonly defined with the `accepts_nested_attributes_for` class method:
-
-#### has_one
-
-```ruby
-class Person < ApplicationRecord
- has_one :address
- accepts_nested_attributes_for :address
-end
-```
-
-#### belongs_to
-
-```ruby
-class Person < ApplicationRecord
- belongs_to :firm
- accepts_nested_attributes_for :firm
-end
-```
-
-#### has_many / has_and_belongs_to_many
-
-```ruby
-class Person < ApplicationRecord
- has_many :projects
- accepts_nested_attributes_for :projects
-end
-```
-
-NOTE: For greater detail on associations see [Active Record Associations](association_basics.html).
-For a complete reference on associations please visit the API documentation for [ActiveRecord::Associations::ClassMethods](http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html).
-
-### Custom model
-
-As you might have inflected from this explanation, you _don't_ necessarily need an ActiveRecord::Base model to use this functionality. The following examples are sufficient to enable the nested model form behavior:
-
-#### Single associated object
-
-```ruby
-class Person
- def address
- Address.new
- end
-
- def address_attributes=(attributes)
- # ...
- end
-end
-```
-
-#### Association collection
-
-```ruby
-class Person
- def projects
- [Project.new, Project.new]
- end
-
- def projects_attributes=(attributes)
- # ...
- end
-end
-```
-
-NOTE: See (TODO) in the advanced section for more information on how to deal with the CRUD operations in your custom model.
-
-Views
------
-
-### Controller code
-
-A nested model form will _only_ be built if the associated object(s) exist. This means that for a new model instance you would probably want to build the associated object(s) first.
-
-Consider the following typical RESTful controller which will prepare a new Person instance and its `address` and `projects` associations before rendering the `new` template:
-
-```ruby
-class PeopleController < ApplicationController
- def new
- @person = Person.new
- @person.build_address
- 2.times { @person.projects.build }
- end
-
- def create
- @person = Person.new(params[:person])
- if @person.save
- # ...
- end
- end
-end
-```
-
-NOTE: Obviously the instantiation of the associated object(s) can become tedious and not DRY, so you might want to move that into the model itself. ActiveRecord::Base provides an `after_initialize` callback which is a good way to refactor this.
-
-### Form code
-
-Now that you have a model instance, with the appropriate methods and associated object(s), you can start building the nested model form.
-
-#### Standard form
-
-Start out with a regular RESTful form:
-
-```erb
-<%= form_for @person do |f| %>
- <%= f.text_field :name %>
-<% end %>
-```
-
-This will generate the following html:
-
-```html
-<form action="/people" class="new_person" id="new_person" method="post">
- <input id="person_name" name="person[name]" type="text" />
-</form>
-```
-
-#### Nested form for a single associated object
-
-Now add a nested form for the `address` association:
-
-```erb
-<%= form_for @person do |f| %>
- <%= f.text_field :name %>
-
- <%= f.fields_for :address do |af| %>
- <%= af.text_field :street %>
- <% end %>
-<% end %>
-```
-
-This generates:
-
-```html
-<form action="/people" class="new_person" id="new_person" method="post">
- <input id="person_name" name="person[name]" type="text" />
-
- <input id="person_address_attributes_street" name="person[address_attributes][street]" type="text" />
-</form>
-```
-
-Notice that `fields_for` recognized the `address` as an association for which a nested model form should be built by the way it has namespaced the `name` attribute.
-
-When this form is posted the Rails parameter parser will construct a hash like the following:
-
-```ruby
-{
- "person" => {
- "name" => "Eloy Duran",
- "address_attributes" => {
- "street" => "Nieuwe Prinsengracht"
- }
- }
-}
-```
-
-That's it. The controller will simply pass this hash on to the model from the `create` action. The model will then handle building the `address` association for you and automatically save it when the parent (`person`) is saved.
-
-#### Nested form for a collection of associated objects
-
-The form code for an association collection is pretty similar to that of a single associated object:
-
-```erb
-<%= form_for @person do |f| %>
- <%= f.text_field :name %>
-
- <%= f.fields_for :projects do |pf| %>
- <%= pf.text_field :name %>
- <% end %>
-<% end %>
-```
-
-Which generates:
-
-```html
-<form action="/people" class="new_person" id="new_person" method="post">
- <input id="person_name" name="person[name]" type="text" />
-
- <input id="person_projects_attributes_0_name" name="person[projects_attributes][0][name]" type="text" />
- <input id="person_projects_attributes_1_name" name="person[projects_attributes][1][name]" type="text" />
-</form>
-```
-
-As you can see it has generated 2 `project name` inputs, one for each new `project` that was built in the controller's `new` action. Only this time the `name` attribute of the input contains a digit as an extra namespace. This will be parsed by the Rails parameter parser as:
-
-```ruby
-{
- "person" => {
- "name" => "Eloy Duran",
- "projects_attributes" => {
- "0" => { "name" => "Project 1" },
- "1" => { "name" => "Project 2" }
- }
- }
-}
-```
-
-You can basically see the `projects_attributes` hash as an array of attribute hashes, one for each model instance.
-
-NOTE: The reason that `fields_for` constructed a hash instead of an array is that it won't work for any form nested deeper than one level deep.
-
-TIP: You _can_ however pass an array to the writer method generated by `accepts_nested_attributes_for` if you're using plain Ruby or some other API access. See (TODO) for more info and example.
diff --git a/guides/source/plugins.md b/guides/source/plugins.md
index 760ff431c0..8c2d56ceb8 100644
--- a/guides/source/plugins.md
+++ b/guides/source/plugins.md
@@ -340,8 +340,7 @@ module Yaffle
module ClassMethods
def acts_as_yaffle(options = {})
- cattr_accessor :yaffle_text_field
- self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s
+ cattr_accessor :yaffle_text_field, default: (options[:yaffle_text_field] || :last_squawk).to_s
end
end
end
@@ -411,8 +410,7 @@ module Yaffle
module ClassMethods
def acts_as_yaffle(options = {})
- cattr_accessor :yaffle_text_field
- self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s
+ cattr_accessor :yaffle_text_field, default: (options[:yaffle_text_field] || :last_squawk).to_s
include Yaffle::ActsAsYaffle::LocalInstanceMethods
end
diff --git a/guides/source/profiling.md b/guides/source/profiling.md
deleted file mode 100644
index ce093f78ba..0000000000
--- a/guides/source/profiling.md
+++ /dev/null
@@ -1,16 +0,0 @@
-*DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
-
-A Guide to Profiling Rails Applications
-=======================================
-
-This guide covers built-in mechanisms in Rails for profiling your application.
-
-After reading this guide, you will know:
-
-* Rails profiling terminology.
-* How to write benchmark tests for your application.
-* Other benchmarking approaches and plugins.
-
---------------------------------------------------------------------------------
-
-
diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md
index 3e99ee7021..e087834a2f 100644
--- a/guides/source/rails_application_templates.md
+++ b/guides/source/rails_application_templates.md
@@ -277,6 +277,6 @@ relative paths to your template's location.
```ruby
def source_paths
- [File.expand_path(File.dirname(__FILE__))]
+ [__dir__]
end
```
diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md
index 340933c7ee..cef8450ee4 100644
--- a/guides/source/rails_on_rack.md
+++ b/guides/source/rails_on_rack.md
@@ -20,9 +20,9 @@ Introduction to Rack
Rack provides a minimal, modular and adaptable interface for developing web applications in Ruby. By wrapping HTTP requests and responses in the simplest way possible, it unifies and distills the API for web servers, web frameworks, and software in between (the so-called middleware) into a single method call.
-* [Rack API Documentation](http://rack.github.io/)
-
-Explaining Rack is not really in the scope of this guide. In case you are not familiar with Rack's basics, you should check out the [Resources](#resources) section below.
+Explaining how Rack works is not really in the scope of this guide. In case you
+are not familiar with Rack's basics, you should check out the [Resources](#resources)
+section below.
Rails on Rack
-------------
@@ -74,7 +74,7 @@ And start the server:
$ rackup config.ru
```
-To find out more about different `rackup` options:
+To find out more about different `rackup` options, you can run:
```bash
$ rackup --help
@@ -89,7 +89,8 @@ Action Dispatcher Middleware Stack
Many of Action Dispatcher's internal components are implemented as Rack middlewares. `Rails::Application` uses `ActionDispatch::MiddlewareStack` to combine various internal and external middlewares to form a complete Rails Rack application.
-NOTE: `ActionDispatch::MiddlewareStack` is Rails equivalent of `Rack::Builder`, but built for better flexibility and more features to meet Rails' requirements.
+NOTE: `ActionDispatch::MiddlewareStack` is Rails' equivalent of `Rack::Builder`,
+but is built for better flexibility and more features to meet Rails' requirements.
### Inspecting Middleware Stack
@@ -109,11 +110,12 @@ use ActiveSupport::Cache::Strategy::LocalCache::Middleware
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
+use ActionDispatch::RemoteIp
+use Sprockets::Rails::QuietAssets
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use WebConsole::Middleware
use ActionDispatch::DebugExceptions
-use ActionDispatch::RemoteIp
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
@@ -123,7 +125,7 @@ use ActionDispatch::Flash
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
-run Rails.application.routes
+run MyApp.application.routes
```
The default middlewares shown here (and some others) are each summarized in the [Internal Middlewares](#internal-middleware-stack) section, below.
@@ -237,6 +239,14 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol
* Makes a unique `X-Request-Id` header available to the response and enables the `ActionDispatch::Request#request_id` method.
+**`ActionDispatch::RemoteIp`**
+
+* Checks for IP spoofing attacks.
+
+**`Sprockets::Rails::QuietAssets`**
+
+* Suppresses logger output for asset requests.
+
**`Rails::Rack::Logger`**
* Notifies the logs that the request has began. After request is complete, flushes all the logs.
@@ -249,10 +259,6 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol
* Responsible for logging exceptions and showing a debugging page in case the request is local.
-**`ActionDispatch::RemoteIp`**
-
-* Checks for IP spoofing attacks.
-
**`ActionDispatch::Reloader`**
* Provides prepare and cleanup callbacks, intended to assist with code reloading during development.
diff --git a/guides/source/routing.md b/guides/source/routing.md
index 53735ce82e..f7dbbc510e 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -142,16 +142,17 @@ Sometimes, you have a resource that clients always look up without referencing a
get 'profile', to: 'users#show'
```
-Passing a `String` to `get` will expect a `controller#action` format, while passing a `Symbol` will map directly to an action but you must also specify the `controller:` to use:
+Passing a `String` to `to:` will expect a `controller#action` format. When using a `Symbol`, the `to:` option should be replaced with `action:`. When using a `String` without a `#`, the `to:` option should be replaced with `controller:`:
```ruby
-get 'profile', to: :show, controller: 'users'
+get 'profile', action: :show, controller: 'users'
```
This resourceful route:
```ruby
resource :geocoder
+resolve('Geocoder') { [:geocoder] }
```
creates six different routes in your application, all mapping to the `Geocoders` controller:
@@ -175,14 +176,6 @@ A singular resourceful route generates these helpers:
As with plural resources, the same helpers ending in `_url` will also include the host, port and path prefix.
-WARNING: A [long-standing bug](https://github.com/rails/rails/issues/1769) prevents `form_for` from working automatically with singular resources. As a workaround, specify the URL for the form directly, like so:
-
-```ruby
-form_for @geocoder, url: geocoder_path do |f|
-
-# snippet for brevity
-```
-
### Controller Namespaces and Routing
You may wish to organize groups of controllers under a namespace. Most commonly, you might group a number of administrative controllers under an `Admin::` namespace. You would place these controllers under the `app/controllers/admin` directory, and you can group them together in your router:
diff --git a/guides/source/security.md b/guides/source/security.md
index c305350243..297680b176 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -95,16 +95,23 @@ Rails 2 introduced a new default session storage, CookieStore. CookieStore saves
* The client can see everything you store in a session, because it is stored in clear-text (actually Base64-encoded, so not encrypted). So, of course, _you don't want to store any secrets here_. To prevent session hash tampering, a digest is calculated from the session with a server-side secret (`secrets.secret_token`) and inserted into the end of the cookie.
-However, since Rails 4, the default store is EncryptedCookieStore. With
-EncryptedCookieStore the session is encrypted before being stored in a cookie.
-This prevents the user from accessing and tampering the content of the cookie.
-Thus the session becomes a more secure place to store data. The encryption is
-done using a server-side secret key `secrets.secret_key_base` stored in
-`config/secrets.yml`.
+In Rails 4, encrypted cookies through AES in CBC mode with HMAC using SHA1 for
+verification was introduced. This prevents the user from accessing and tampering
+the content of the cookie. Thus the session becomes a more secure place to store
+data. The encryption is performed using a server-side `secrets.secret_key_base`.
+Two salts are used when deriving keys for encryption and verification. These
+salts are set via the `config.action_dispatch.encrypted_cookie_salt` and
+`config.action_dispatch.encrypted_signed_cookie_salt` configuration values.
-That means the security of this storage depends on this secret (and on the digest algorithm, which defaults to SHA1, for compatibility). So _don't use a trivial secret, i.e. a word from a dictionary, or one which is shorter than 30 characters, use `rails secret` instead_.
+Rails 5.2 uses AES-GCM for the encryption which couples authentication
+and encryption in one faster step and produces shorter ciphertexts.
-`secrets.secret_key_base` is used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `secrets.secret_key_base` initialized to a random key present in `config/secrets.yml`, e.g.:
+Encrypted cookies are automatically upgraded if the
+`config.action_dispatch.use_authenticated_cookie_encryption` is enabled.
+
+_Do not use a trivial secret, i.e. a word from a dictionary, or one which is shorter than 30 characters! Instead use `rails secret` to generate secret keys!_
+
+Applications get `secrets.secret_key_base` initialized to a random key present in `config/secrets.yml`, e.g.:
development:
secret_key_base: a75d...
@@ -356,7 +363,7 @@ send_file('/var/www/uploads/' + params[:filename])
Simply pass a file name like "../../../etc/passwd" to download the server's login information. A simple solution against this, is to _check that the requested file is in the expected directory_:
```ruby
-basename = File.expand_path(File.join(File.dirname(__FILE__), '../../files'))
+basename = File.expand_path('../../files', __dir__)
filename = File.expand_path(File.join(basename, @file.public_filename))
raise if basename !=
File.expand_path(File.join(File.dirname(filename), '../../../'))
@@ -796,7 +803,7 @@ In December 2006, 34,000 actual user names and passwords were stolen in a [MySpa
INFO: _CSS Injection is actually JavaScript injection, because some browsers (IE, some versions of Safari and others) allow JavaScript in CSS. Think twice about allowing custom CSS in your web application._
-CSS Injection is explained best by the well-known [MySpace Samy worm](http://namb.la/popular/tech.html). This worm automatically sent a friend request to Samy (the attacker) simply by visiting his profile. Within several hours he had over 1 million friend requests, which created so much traffic that MySpace went offline. The following is a technical explanation of that worm.
+CSS Injection is explained best by the well-known [MySpace Samy worm](https://samy.pl/popular/tech.html). This worm automatically sent a friend request to Samy (the attacker) simply by visiting his profile. Within several hours he had over 1 million friend requests, which created so much traffic that MySpace went offline. The following is a technical explanation of that worm.
MySpace blocked many tags, but allowed CSS. So the worm's author put JavaScript into CSS like this:
@@ -1053,6 +1060,7 @@ Additional Resources
The security landscape shifts and it is important to keep up to date, because missing a new vulnerability can be catastrophic. You can find additional resources about (Rails) security here:
-* Subscribe to the Rails security [mailing list](http://groups.google.com/group/rubyonrails-security)
-* [Keep up to date on the other application layers](http://secunia.com/) (they have a weekly newsletter, too)
-* A [good security blog](https://www.owasp.org) including the [Cross-Site scripting Cheat Sheet](https://www.owasp.org/index.php/DOM_based_XSS_Prevention_Cheat_Sheet)
+* Subscribe to the Rails security [mailing list.](http://groups.google.com/group/rubyonrails-security)
+* [Brakeman - Rails Security Scanner](http://brakemanscanner.org/) - To perform static security analysis for Rails applications.
+* [Keep up to date on the other application layers.](http://secunia.com/) (they have a weekly newsletter, too)
+* A [good security blog](https://www.owasp.org) including the [Cross-Site scripting Cheat Sheet.](https://www.owasp.org/index.php/DOM_based_XSS_Prevention_Cheat_Sheet)
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 7741834153..7abf3af187 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -350,7 +350,9 @@ Rails adds some custom assertions of its own to the `minitest` framework:
| --------------------------------------------------------------------------------- | ------- |
| [`assert_difference(expressions, difference = 1, message = nil) {...}`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_difference) | Test numeric difference between the return value of an expression as a result of what is evaluated in the yielded block.|
| [`assert_no_difference(expressions, message = nil, &block)`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_no_difference) | Asserts that the numeric result of evaluating an expression is not changed before and after invoking the passed in block.|
-| [`assert_nothing_raised { block }`](http://api.rubyonrails.org/classes/ActiveSupport/TestCase.html#method-i-assert_nothing_raised) | Ensures that the given block doesn't raise any exceptions.|
+| [`assert_changes(expressions, message = nil, from:, to:, &block)`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_changes) | Test that the result of evaluating an expression is changed after invoking the passed in block.|
+| [`assert_no_changes(expressions, message = nil, &block)`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_no_changes) | Test the result of evaluating an expression is not changed after invoking the passed in block.|
+| [`assert_nothing_raised { block }`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_nothing_raised) | Ensures that the given block doesn't raise any exceptions.|
| [`assert_recognizes(expected_options, path, extras={}, message=nil)`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html#method-i-assert_recognizes) | Asserts that the routing of the given path was handled correctly and that the parsed options (given in the expected_options hash) match path. Basically, it asserts that Rails recognizes the route given by expected_options.|
| [`assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html#method-i-assert_generates) | Asserts that the provided options can be used to generate the provided path. This is the inverse of assert_recognizes. The extras parameter is used to tell the request the names and values of additional request parameters that would be in a query string. The message parameter allows you to specify a custom error message for assertion failures.|
| [`assert_response(type, message = nil)`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/ResponseAssertions.html#method-i-assert_response) | Asserts that the response comes with a specific status code. You can specify `:success` to indicate 200-299, `:redirect` to indicate 300-399, `:missing` to indicate 404, or `:error` to match the 500-599 range. You can also pass an explicit status number or its symbolic equivalent. For more information, see [full list of status codes](http://rubydoc.info/github/rack/rack/master/Rack/Utils#HTTP_STATUS_CODES-constant) and how their [mapping](http://rubydoc.info/github/rack/rack/master/Rack/Utils#SYMBOL_TO_STATUS_CODE-constant) works.|
@@ -600,19 +602,16 @@ Model tests don't have their own superclass like `ActionMailer::TestCase` instea
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.
+System tests allow you to test user interactions with your application, running tests
+in either a real or a headless browser. System tests uses Capybara under the hood.
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
+$ bin/rails generate system_test users
invoke test_unit
- create test/system/users_creates_test.rb
+ create test/system/users_test.rb
```
Here's what a freshly-generated system test looks like:
@@ -620,11 +619,11 @@ Here's what a freshly-generated system test looks like:
```ruby
require "application_system_test_case"
-class UsersCreatesTest < ApplicationSystemTestCase
+class UsersTest < ApplicationSystemTestCase
# test "visiting the index" do
- # visit users_creates_url
+ # visit users_url
#
- # assert_selector "h1", text: "UsersCreate"
+ # assert_selector "h1", text: "Users"
# end
end
```
@@ -658,8 +657,9 @@ 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), and `:screen_size` to change
-the size of the screen for screenshots.
+be used by Selenium), `:screen_size` to change the size of the screen for
+screenshots, and `:options` which can be used to set options supported by the
+driver.
```ruby
require "test_helper"
@@ -669,8 +669,9 @@ class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
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.
+If your Capybara configuration requires more setup than provided by Rails, this
+additional configuration could be added into the `application_system_test_case.rb`
+file.
Please see [Capybara's documentation](https://github.com/teamcapybara/capybara#setup)
for additional settings.
@@ -693,9 +694,9 @@ take a screenshot of the browser.
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.
+If you used the scaffold generator, a system test skeleton was automatically
+created for you. If you didn't use the scaffold generator, start by creating a
+system test skeleton.
```bash
$ bin/rails generate system_test articles
@@ -1406,7 +1407,7 @@ In order to test that your mailer is working as expected, you can use unit tests
For the purposes of unit testing a mailer, fixtures are used to provide an example of how the output _should_ look. Because these are example emails, and not Active Record data like the other fixtures, they are kept in their own subdirectory apart from the other fixtures. The name of the directory within `test/fixtures` directly corresponds to the name of the mailer. So, for a mailer named `UserMailer`, the fixtures should reside in `test/fixtures/user_mailer` directory.
-When you generated your mailer, the generator creates stub fixtures for each of the mailers actions. If you didn't use the generator, you'll have to create those files yourself.
+If you generated your mailer, the generator does not create stub fixtures for the mailers actions. You'll have to create those files yourself as described above.
#### The Basic Test Case
@@ -1482,7 +1483,7 @@ class UserControllerTest < ActionDispatch::IntegrationTest
assert_equal "You have been invited by me@example.com", invite_email.subject
assert_equal 'friend@example.com', invite_email.to[0]
- assert_match(/Hi friend@example.com/, invite_email.body.to_s)
+ assert_match(/Hi friend@example\.com/, invite_email.body.to_s)
end
end
```
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 3afc0e5309..88a7d0a464 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -73,16 +73,32 @@ For more information on changes made to Rails 5.1 please see the [release notes]
### Top-level `HashWithIndifferentAccess` is soft-deprecated
If your application uses the the top-level `HashWithIndifferentAccess` class, you
-should slowly move your code to use the `ActiveSupport::HashWithIndifferentAccess`
-one.
+should slowly move your code to instead use `ActiveSupport::HashWithIndifferentAccess`.
It is only soft-deprecated, which means that your code will not break at the
-moment and no deprecation warning will be displayed but this constant will be
+moment and no deprecation warning will be displayed, but this constant will be
removed in the future.
Also, if you have pretty old YAML documents containing dumps of such objects,
you may need to load and dump them again to make sure that they reference
-the right constant and that loading them won't break in the future.
+the right constant, and that loading them won't break in the future.
+
+### `application.secrets` now loaded with all keys as symbols
+
+If your application stores nested configuration in `config/secrets.yml`, all keys
+are now loaded as symbols, so access using strings should be changed.
+
+From:
+
+```ruby
+Rails.application.secrets[:smtp_settings]["address"]
+```
+
+To:
+
+```ruby
+Rails.application.secrets[:smtp_settings][:address]
+```
Upgrading from Rails 4.2 to Rails 5.0
-------------------------------------
@@ -222,7 +238,7 @@ Run `bin/rails` to see the list of commands available.
### `ActionController::Parameters` No Longer Inherits from `HashWithIndifferentAccess`
Calling `params` in your application will now return an object instead of a hash. If your
-parameters are already permitted, then you will not need to make any changes. If you are using `slice`
+parameters are already permitted, then you will not need to make any changes. If you are using `map`
and other methods that depend on being able to read the hash regardless of `permitted?` you will
need to upgrade your application to first permit and then convert to a hash.
diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md
index cbaf9100f7..ed27752a06 100644
--- a/guides/source/working_with_javascript_in_rails.md
+++ b/guides/source/working_with_javascript_in_rails.md
@@ -141,6 +141,8 @@ follow this pattern.
Built-in Helpers
----------------------
+### Remote elements
+
Rails provides a bunch of view helper methods written in Ruby to assist you
in generating HTML. Sometimes, you want to add a little Ajax to those elements,
and Rails has got your back in those cases.
@@ -153,14 +155,18 @@ Unless you have disabled the Asset Pipeline,
provides the JavaScript half, and the regular Ruby view helpers add appropriate
tags to your DOM.
-### form_for
+You can read below about the different events that are fired dealing with
+remote elements inside your application.
+
+#### form_with
-[`form_for`](http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for)
-is a helper that assists with writing forms. `form_for` takes a `:remote`
-option. It works like this:
+[`form_with`](http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_with)
+is a helper that assists with writing forms. By default, `form_with` assumes that
+your form will be using Ajax. You can opt out of this behavior by
+passing the `:local` option `form_with`.
```erb
-<%= form_for(@article, remote: true) do |f| %>
+<%= form_with(model: @article) do |f| %>
...
<% end %>
```
@@ -168,7 +174,7 @@ option. It works like this:
This will generate the following HTML:
```html
-<form accept-charset="UTF-8" action="/articles" class="new_article" data-remote="true" id="new_article" method="post">
+<form action="/articles" method="post" data-remote="true">
...
</form>
```
@@ -189,32 +195,9 @@ $(document).ready ->
```
Obviously, you'll want to be a bit more sophisticated than that, but it's a
-start. You can see more about the events [in the jquery-ujs wiki](https://github.com/rails/jquery-ujs/wiki/ajax).
-
-### form_tag
-
-[`form_tag`](http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-form_tag)
-is very similar to `form_for`. It has a `:remote` option that you can use like
-this:
-
-```erb
-<%= form_tag('/articles', remote: true) do %>
- ...
-<% end %>
-```
-
-This will generate the following HTML:
+start.
-```html
-<form accept-charset="UTF-8" action="/articles" data-remote="true" method="post">
- ...
-</form>
-```
-
-Everything else is the same as `form_for`. See its documentation for full
-details.
-
-### link_to
+#### link_to
[`link_to`](http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to)
is a helper that assists with generating links. It has a `:remote` option you
@@ -230,7 +213,7 @@ which generates
<a href="/articles/1" data-remote="true">an article</a>
```
-You can bind to the same Ajax events as `form_for`. Here's an example. Let's
+You can bind to the same Ajax events as `form_with`. Here's an example. Let's
assume that we have a list of articles that can be deleted with just one
click. We would generate some HTML like this:
@@ -246,7 +229,7 @@ $ ->
alert "The article was deleted."
```
-### button_to
+#### button_to
[`button_to`](http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-button_to) is a helper that helps you create buttons. It has a `:remote` option that you can call like this:
@@ -262,7 +245,165 @@ this generates
</form>
```
-Since it's just a `<form>`, all of the information on `form_for` also applies.
+Since it's just a `<form>`, all of the information on `form_with` also applies.
+
+### Customize remote elements
+
+It is possible to customize the behavior of elements with a `data-remote`
+attribute without writing a line of JavaScript. You can specify extra `data-`
+attributes to accomplish this.
+
+#### `data-method`
+
+Activating hyperlinks always results in an HTTP GET request. However, if your
+application is [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer),
+some links are in fact actions that change data on the server, and must be
+performed with non-GET requests. This attribute allows marking up such links
+with an explicit method such as "post", "put" or "delete".
+
+The way it works is that, when the link is activated, it constructs a hidden form
+in the document with the "action" attribute corresponding to "href" value of the
+link, and the method corresponding to `data-method` value, and submits that form.
+
+NOTE: Because submitting forms with HTTP methods other than GET and POST isn't
+widely supported across browsers, all other HTTP methods are actually sent over
+POST with the intended method indicated in the `_method` parameter. Rails
+automatically detects and compensates for this.
+
+#### `data-url` and `data-params`
+
+Certain elements of your page aren't actually referring to any URL, but you may want
+them to trigger Ajax calls. Specifying the `data-url` attribute along with
+the `data-remote` one will trigger an Ajax call to the given URL. You can also
+specify extra parameters through the `data-params` attribute.
+
+This can be useful to trigger an action on check-boxes for instance:
+
+```html
+<input type="checkbox" data-remote="true"
+ data-url="/update" data-params="id=10" data-method="put">
+```
+
+#### `data-type`
+
+It is also possible to define the Ajax `dataType` explicitly while performing
+requests for `data-remote` elements, by way of the `data-type` attribute.
+
+### Confirmations
+
+You can ask for an extra confirmation of the user by adding a `data-confirm`
+attribute on links and forms. The user will be presented a JavaScript `confirm()`
+dialog containing the attribute's text. If the user chooses to cancel, the action
+doesn't take place.
+
+Adding this attribute on links will trigger the dialog on click, and adding it
+on forms will trigger it on submit. For example:
+
+```erb
+<%= link_to "Dangerous zone", dangerous_zone_path,
+ data: { confirm: 'Are you sure?' } %>
+```
+
+This generates:
+
+```html
+<a href="..." data-confirm="Are you sure?">Dangerous zone</a>
+```
+
+The attribute is also allowed on form submit buttons. This allows you to customize
+the warning message depending on the button which was activated. In this case,
+you should **not** have `data-confirm` on the form itself.
+
+The default confirmation uses a JavaScript confirm dialog, but you can customize
+this by listening to the `confirm` event, which is fired just before the confirmation
+window appears to the user. To cancel this default confirmation, have the confirm
+handler to return `false`.
+
+### Automatic disabling
+
+It is also possible to automatically disable an input while the form is submitting
+by using the `data-disable-with` attribute. This is to prevent accidental
+double-clicks from the user, which could result in duplicate HTTP requests that
+the backend may not detect as such. The value of the attribute is the text that will
+become the new value of the button in its disabled state.
+
+This also works for links with `data-method` attribute.
+
+For example:
+
+```erb
+<%= form_with(model: @article.new) do |f| %>
+ <%= f.submit data: { "disable-with": "Saving..." } %>
+<%= end %>
+```
+
+This generates a form with:
+
+```html
+<input data-disable-with="Saving..." type="submit">
+```
+
+Dealing with Ajax events
+------------------------
+
+Here are the different events that are fired when you deal with elements
+that have a `data-remote` attribute:
+
+NOTE: All handlers bound to these events are always passed the event object as the
+first argument. The table below describes the extra parameters passed after the
+event argument. For example, if the extra parameters are listed as `xhr, settings`,
+then to access them, you would define your handler with `function(event, xhr, settings)`.
+
+| Event name | Extra parameters | Fired |
+|---------------------|------------------|-------------------------------------------------------------|
+| `ajax:before` | | Before the whole ajax business, aborts if stopped. |
+| `ajax:beforeSend` | xhr, options | Before the request is sent, aborts if stopped. |
+| `ajax:send` | xhr | When the request is sent. |
+| `ajax:success` | xhr, status, err | After completion, if the response was a success. |
+| `ajax:error` | xhr, status, err | After completion, if the response was an error. |
+| `ajax:complete` | xhr, status | After the request has been completed, no matter the outcome.|
+| `ajax:aborted:file` | elements | If there are non-blank file inputs, aborts if stopped. |
+
+### Stoppable events
+
+If you stop `ajax:before` or `ajax:beforeSend` by returning false from the
+handler method, the Ajax request will never take place. The `ajax:before` event
+is also useful for manipulating form data before serialization. The
+`ajax:beforeSend` event is also useful for adding custom request headers.
+
+If you stop the `ajax:aborted:file` event, the default behavior of allowing the
+browser to submit the form via normal means (i.e. non-AJAX submission) will be
+canceled and the form will not be submitted at all. This is useful for
+implementing your own AJAX file upload workaround.
+
+### Rails-ujs event handlers
+
+Rails 5.1 introduced rails-ujs and dropped jQuery as a dependency.
+As a result the Unobtrusive JavaScript (UJS) driver has been rewritten to operate without jQuery.
+These introductions cause small changes to `custom events` fired during the request:
+
+NOTE: Signature of calls to UJS’s event handlers has changed.
+Unlike the version with jQuery, all custom events return only one parameter: `event`.
+In this parameter, there is an additional attribute `detail` which contains an array of extra parameters.
+
+| Event name | Extra parameters (event.detail) | Fired |
+|---------------------|---------------------------------|-------------------------------------------------------------|
+| `ajax:before` | | Before the whole ajax business. |
+| `ajax:beforeSend` | [xhr, options] | Before the request is sent. |
+| `ajax:send` | [xhr] | When the request is sent. |
+| `ajax:stopped` | | When the request is stopped. |
+| `ajax:success` | [response, status, xhr] | After completion, if the response was a success. |
+| `ajax:error` | [response, status, xhr] | After completion, if the response was an error. |
+| `ajax:complete` | [xhr, status] | After the request has been completed, no matter the outcome.|
+
+Example usage:
+
+```html
+document.body.addEventListener('ajax:success', function(event) {
+ var detail = event.detail;
+ var data = detail[0], status = detail[1], xhr = detail[2];
+})
+```
Server-Side Concerns
--------------------
@@ -297,7 +438,7 @@ The index view (`app/views/users/index.html.erb`) contains:
<br>
-<%= form_for(@user, remote: true) do |f| %>
+<%= form_with(model: @user) do |f| %>
<%= f.label :name %><br>
<%= f.text_field :name %>
<%= f.submit %>
@@ -338,7 +479,7 @@ this:
end
```
-Notice the format.js in the `respond_to` block; that allows the controller to
+Notice the `format.js` in the `respond_to` block: that allows the controller to
respond to your Ajax request. You then have a corresponding
`app/views/users/create.js.erb` view file that generates the actual JavaScript
code that will be sent and executed on the client side.
@@ -355,7 +496,7 @@ which uses Ajax to speed up page rendering in most applications.
### How Turbolinks Works
-Turbolinks attaches a click handler to all `<a>` on the page. If your browser
+Turbolinks attaches a click handler to all `<a>` tags on the page. If your browser
supports
[PushState](https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Manipulating_the_browser_history#The_pushState%28%29_method),
Turbolinks will make an Ajax request for the page, parse the response, and
@@ -385,7 +526,7 @@ $(document).ready ->
```
However, because Turbolinks overrides the normal page loading process, the
-event that this relies on will not be fired. If you have code that looks like
+event that this relies upon will not be fired. If you have code that looks like
this, you must change your code to do this instead:
```coffeescript
diff --git a/rails.gemspec b/rails.gemspec
index 2d5be58c17..1dbd86d2fb 100644
--- a/rails.gemspec
+++ b/rails.gemspec
@@ -1,4 +1,4 @@
-version = File.read(File.expand_path("../RAILS_VERSION", __FILE__)).strip
+version = File.read(File.expand_path("RAILS_VERSION", __dir__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
@@ -28,6 +28,6 @@ Gem::Specification.new do |s|
s.add_dependency "actioncable", version
s.add_dependency "railties", version
- s.add_dependency "bundler", ">= 1.3.0", "< 2.0"
+ s.add_dependency "bundler", ">= 1.3.0"
s.add_dependency "sprockets-rails", ">= 2.0.0"
end
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 6032d2e1a1..f8137d95a8 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1 +1,29 @@
+* Clear screenshot files in `tmp:clear` task.
+
+ *Yuji Yaginuma*
+
+* Add `railtie.rb` to the plugin generator
+
+ *Tsukuru Tanimichi*
+
+* Deprecate `capify!` method in generators and templates.
+
+ *Yuji Yaginuma*
+
+* Allow irb options to be passed from `rails console` command.
+
+ Fixes #28988.
+
+ *Yuji Yaginuma*
+
+* Added a shared section to `config/database.yml` that will be loaded for all environments.
+
+ *Pierre Schambacher*
+
+* Namespace error pages' CSS selectors to stop the styles from bleeding into other pages
+ when using Turbolinks.
+
+ *Jan Krutisch*
+
+
Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/railties/CHANGELOG.md) for previous changes.
diff --git a/railties/Rakefile b/railties/Rakefile
index 680ed03f75..d6284b7dc5 100644
--- a/railties/Rakefile
+++ b/railties/Rakefile
@@ -16,10 +16,10 @@ namespace :test do
dash_i = [
"test",
"lib",
- "#{File.dirname(__FILE__)}/../activesupport/lib",
- "#{File.dirname(__FILE__)}/../actionpack/lib",
- "#{File.dirname(__FILE__)}/../actionview/lib",
- "#{File.dirname(__FILE__)}/../activemodel/lib"
+ "#{__dir__}/../activesupport/lib",
+ "#{__dir__}/../actionpack/lib",
+ "#{__dir__}/../actionview/lib",
+ "#{__dir__}/../activemodel/lib"
]
ruby "-w", "-I#{dash_i.join ':'}", file
end
@@ -27,7 +27,7 @@ namespace :test do
end
Rake::TestTask.new("test:regular") do |t|
- t.libs << "test" << "#{File.dirname(__FILE__)}/../activesupport/lib"
+ t.libs << "test" << "#{__dir__}/../activesupport/lib"
t.pattern = "test/**/*_test.rb"
t.warning = false
t.verbose = true
diff --git a/railties/bin/test b/railties/bin/test
index a7beb14b27..470ce93f10 100755
--- a/railties/bin/test
+++ b/railties/bin/test
@@ -1,4 +1,4 @@
#!/usr/bin/env ruby
COMPONENT_ROOT = File.expand_path("..", __dir__)
-require File.expand_path("../tools/test", COMPONENT_ROOT)
+require_relative "../../tools/test"
diff --git a/railties/exe/rails b/railties/exe/rails
index 7e791c1f99..a5635c2297 100755
--- a/railties/exe/rails
+++ b/railties/exe/rails
@@ -1,9 +1,9 @@
#!/usr/bin/env ruby
-git_path = File.expand_path("../../../.git", __FILE__)
+git_path = File.expand_path("../../.git", __dir__)
if File.exist?(git_path)
- railties_path = File.expand_path("../../lib", __FILE__)
+ railties_path = File.expand_path("../lib", __dir__)
$:.unshift(railties_path)
end
require "rails/cli"
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index 6d8bf28943..a12ca42c30 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -1,4 +1,4 @@
-require "rails/ruby_version_check"
+require_relative "rails/ruby_version_check"
require "pathname"
@@ -9,8 +9,8 @@ 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"
+require_relative "rails/application"
+require_relative "rails/version"
require "active_support/railtie"
require "action_dispatch/railtie"
@@ -48,7 +48,7 @@ module Rails
def backtrace_cleaner
@backtrace_cleaner ||= begin
# Relies on Active Support, so we have to lazy load to postpone definition until Active Support has been loaded
- require "rails/backtrace_cleaner"
+ require_relative "rails/backtrace_cleaner"
Rails::BacktraceCleaner.new
end
end
diff --git a/railties/lib/rails/app_loader.rb b/railties/lib/rails/app_loader.rb
index 525d5f0161..3dc4fe5414 100644
--- a/railties/lib/rails/app_loader.rb
+++ b/railties/lib/rails/app_loader.rb
@@ -1,5 +1,5 @@
require "pathname"
-require "rails/version"
+require_relative "version"
module Rails
module AppLoader # :nodoc:
@@ -43,7 +43,7 @@ EOS
$stderr.puts(BUNDLER_WARNING)
Object.const_set(:APP_PATH, File.expand_path("config/application", Dir.pwd))
require File.expand_path("../boot", APP_PATH)
- require "rails/commands"
+ require_relative "commands"
break
end
end
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 89f7b5991f..a71f36840e 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -3,8 +3,8 @@ require "active_support/core_ext/hash/keys"
require "active_support/core_ext/object/blank"
require "active_support/key_generator"
require "active_support/message_verifier"
-require "rails/engine"
-require "rails/secrets"
+require_relative "engine"
+require_relative "secrets"
module Rails
# An Engine with the responsibility of coordinating the whole boot process.
@@ -260,6 +260,7 @@ module Rails
"action_dispatch.signed_cookie_salt" => config.action_dispatch.signed_cookie_salt,
"action_dispatch.encrypted_cookie_salt" => config.action_dispatch.encrypted_cookie_salt,
"action_dispatch.encrypted_signed_cookie_salt" => config.action_dispatch.encrypted_signed_cookie_salt,
+ "action_dispatch.authenticated_encrypted_cookie_salt" => config.action_dispatch.authenticated_encrypted_cookie_salt,
"action_dispatch.cookies_serializer" => config.action_dispatch.cookies_serializer,
"action_dispatch.cookies_digest" => config.action_dispatch.cookies_digest
)
@@ -386,7 +387,9 @@ module Rails
def secrets
@secrets ||= begin
secrets = ActiveSupport::OrderedOptions.new
- secrets.merge! Rails::Secrets.parse(config.paths["config/secrets"].existent, env: Rails.env)
+ files = config.paths["config/secrets"].existent
+ files = files.reject { |path| path.end_with?(".enc") } unless config.read_encrypted_secrets
+ secrets.merge! Rails::Secrets.parse(files, env: Rails.env)
# Fallback to config.secret_key_base if secrets.secret_key_base isn't set
secrets.secret_key_base ||= config.secret_key_base
@@ -436,7 +439,7 @@ module Rails
def run_tasks_blocks(app) #:nodoc:
railties.each { |r| r.run_tasks_blocks(app) }
super
- require "rails/tasks"
+ require_relative "tasks"
task :environment do
ActiveSupport.on_load(:before_initialize) { config.eager_load = false }
diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb
index 4223c38146..44b49556d7 100644
--- a/railties/lib/rails/application/bootstrap.rb
+++ b/railties/lib/rails/application/bootstrap.rb
@@ -2,7 +2,7 @@ require "fileutils"
require "active_support/notifications"
require "active_support/dependencies"
require "active_support/descendants_tracker"
-require "rails/secrets"
+require_relative "../secrets"
module Rails
class Application
@@ -81,7 +81,6 @@ INFO
initializer :set_secrets_root, group: :all do
Rails::Secrets.root = root
- Rails::Secrets.read_encrypted_secrets = config.read_encrypted_secrets
end
end
end
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 7c49fabba5..7e1359c42b 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -1,7 +1,7 @@
require "active_support/core_ext/kernel/reporting"
require "active_support/file_update_checker"
-require "rails/engine/configuration"
-require "rails/source_annotation_extractor"
+require_relative "../engine/configuration"
+require_relative "../source_annotation_extractor"
module Rails
class Application
@@ -77,9 +77,25 @@ module Rails
assets.unknown_asset_fallback = false
end
+ if respond_to?(:action_view)
+ action_view.form_with_generates_remote_forms = true
+ end
+
when "5.2"
load_defaults "5.1"
+ if respond_to?(:active_record)
+ active_record.cache_versioning = true
+ end
+
+ if respond_to?(:action_dispatch)
+ action_dispatch.use_authenticated_cookie_encryption = true
+ end
+
+ if respond_to?(:active_support)
+ active_support.use_authenticated_message_encryption = true
+ end
+
else
raise "Unknown version #{target_version.to_s.inspect}"
end
@@ -133,7 +149,14 @@ module Rails
config = if yaml && yaml.exist?
require "yaml"
require "erb"
- YAML.load(ERB.new(yaml.read).result) || {}
+ loaded_yaml = YAML.load(ERB.new(yaml.read).result) || {}
+ shared = loaded_yaml.delete("shared")
+ if shared
+ loaded_yaml.each do |_k, values|
+ values.reverse_merge!(shared)
+ end
+ end
+ Hash.new(shared).merge(loaded_yaml)
elsif ENV["DATABASE_URL"]
# Value from ENV['DATABASE_URL'] is set to default database connection
# by Active Record.
diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb
index 8fe48feefb..63300ffef3 100644
--- a/railties/lib/rails/application/default_middleware_stack.rb
+++ b/railties/lib/rails/application/default_middleware_stack.rb
@@ -10,7 +10,7 @@ module Rails
end
def build_stack
- ActionDispatch::MiddlewareStack.new.tap do |middleware|
+ ActionDispatch::MiddlewareStack.new do |middleware|
if config.force_ssl
middleware.use ::ActionDispatch::SSL, config.ssl_options
end
diff --git a/railties/lib/rails/application_controller.rb b/railties/lib/rails/application_controller.rb
index a98e51fd28..f7d112900a 100644
--- a/railties/lib/rails/application_controller.rb
+++ b/railties/lib/rails/application_controller.rb
@@ -1,5 +1,5 @@
class Rails::ApplicationController < ActionController::Base # :nodoc:
- self.view_paths = File.expand_path("../templates", __FILE__)
+ self.view_paths = File.expand_path("templates", __dir__)
layout "application"
private
diff --git a/railties/lib/rails/cli.rb b/railties/lib/rails/cli.rb
index 973b746068..602e56c961 100644
--- a/railties/lib/rails/cli.rb
+++ b/railties/lib/rails/cli.rb
@@ -1,13 +1,13 @@
-require "rails/app_loader"
+require_relative "app_loader"
# If we are inside a Rails application this method performs an exec and thus
# the rest of this script is not run.
Rails::AppLoader.exec_app
-require "rails/ruby_version_check"
+require_relative "ruby_version_check"
Signal.trap("INT") { puts; exit(1) }
-require "rails/command"
+require_relative "command"
if ARGV.first == "plugin"
ARGV.shift
diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb
index 9c4bd16aad..5e0d807a21 100644
--- a/railties/lib/rails/code_statistics.rb
+++ b/railties/lib/rails/code_statistics.rb
@@ -1,4 +1,4 @@
-require "rails/code_statistics_calculator"
+require_relative "code_statistics_calculator"
require "active_support/core_ext/enumerable"
class CodeStatistics #:nodoc:
@@ -7,7 +7,8 @@ class CodeStatistics #:nodoc:
"Model tests",
"Mailer tests",
"Job tests",
- "Integration tests"]
+ "Integration tests",
+ "System tests"]
HEADERS = { lines: " Lines", code_lines: " LOC", classes: "Classes", methods: "Methods" }
diff --git a/railties/lib/rails/command.rb b/railties/lib/rails/command.rb
index 0d4e6dc5a1..ee020b58f9 100644
--- a/railties/lib/rails/command.rb
+++ b/railties/lib/rails/command.rb
@@ -23,7 +23,7 @@ module Rails
end
def environment # :nodoc:
- ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
+ ENV["RAILS_ENV"].presence || ENV["RACK_ENV"].presence || "development"
end
# Receives a namespace, arguments and the behavior to invoke the command.
diff --git a/railties/lib/rails/command/actions.rb b/railties/lib/rails/command/actions.rb
index 8fda1c87c6..a00e58997c 100644
--- a/railties/lib/rails/command/actions.rb
+++ b/railties/lib/rails/command/actions.rb
@@ -5,7 +5,7 @@ module Rails
# This allows us to run `rails server` from other directories, but still get
# the main config.ru and properly set the tmp directory.
def set_application_directory!
- Dir.chdir(File.expand_path("../../", APP_PATH)) unless File.exist?(File.expand_path("config.ru"))
+ Dir.chdir(File.expand_path("../..", APP_PATH)) unless File.exist?(File.expand_path("config.ru"))
end
def require_application_and_environment!
diff --git a/railties/lib/rails/command/base.rb b/railties/lib/rails/command/base.rb
index 4f074df473..cd0720be4e 100644
--- a/railties/lib/rails/command/base.rb
+++ b/railties/lib/rails/command/base.rb
@@ -4,7 +4,7 @@ require "erb"
require "active_support/core_ext/string/filters"
require "active_support/core_ext/string/inflections"
-require "rails/command/actions"
+require_relative "actions"
module Rails
module Command
diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb
index fff0119c65..a33e2778d4 100644
--- a/railties/lib/rails/commands.rb
+++ b/railties/lib/rails/commands.rb
@@ -1,4 +1,4 @@
-require "rails/command"
+require_relative "command"
aliases = {
"g" => "generate",
diff --git a/railties/lib/rails/commands/application/application_command.rb b/railties/lib/rails/commands/application/application_command.rb
index 7675d3b3d1..e92daf9be6 100644
--- a/railties/lib/rails/commands/application/application_command.rb
+++ b/railties/lib/rails/commands/application/application_command.rb
@@ -1,5 +1,5 @@
-require "rails/generators"
-require "rails/generators/rails/app/app_generator"
+require_relative "../../generators"
+require_relative "../../generators/rails/app/app_generator"
module Rails
module Generators
diff --git a/railties/lib/rails/commands/console/console_command.rb b/railties/lib/rails/commands/console/console_command.rb
index 62e3aa19df..1da1e331f1 100644
--- a/railties/lib/rails/commands/console/console_command.rb
+++ b/railties/lib/rails/commands/console/console_command.rb
@@ -1,7 +1,7 @@
require "irb"
require "irb/completion"
-require "rails/command/environment_argument"
+require_relative "../../command/environment_argument"
module Rails
class Console
@@ -73,14 +73,26 @@ module Rails
class_option :environment, aliases: "-e", type: :string,
desc: "Specifies the environment to run this console under (test/development/production)."
+ def initialize(args = [], local_options = {}, config = {})
+ console_options = []
+
+ # For the same behavior as OptionParser, leave only options after "--" in ARGV.
+ termination = local_options.find_index("--")
+ if termination
+ console_options = local_options[termination + 1..-1]
+ local_options = local_options[0...termination]
+ end
+
+ ARGV.replace(console_options)
+ super(args, local_options, config)
+ end
+
def perform
extract_environment_option_from_argument
# RAILS_ENV needs to be set before config/application is required.
ENV["RAILS_ENV"] = options[:environment]
- ARGV.clear # Clear ARGV so IRB doesn't freak.
-
require_application_and_environment!
Rails::Console.start(Rails.application, options)
end
diff --git a/railties/lib/rails/commands/dbconsole/dbconsole_command.rb b/railties/lib/rails/commands/dbconsole/dbconsole_command.rb
index 588fb06b15..5db588b66a 100644
--- a/railties/lib/rails/commands/dbconsole/dbconsole_command.rb
+++ b/railties/lib/rails/commands/dbconsole/dbconsole_command.rb
@@ -1,4 +1,4 @@
-require "rails/command/environment_argument"
+require_relative "../../command/environment_argument"
module Rails
class DBConsole
@@ -140,7 +140,7 @@ module Rails
class_option :mode, enum: %w( html list line column ), type: :string,
desc: "Automatically put the sqlite3 database in the specified mode (html, list, line, column)."
- class_option :header, type: :string
+ class_option :header, type: :boolean
class_option :environment, aliases: "-e", type: :string,
desc: "Specifies the environment to run this console under (test/development/production)."
diff --git a/railties/lib/rails/commands/destroy/destroy_command.rb b/railties/lib/rails/commands/destroy/destroy_command.rb
index 281732a936..8a6fe67f77 100644
--- a/railties/lib/rails/commands/destroy/destroy_command.rb
+++ b/railties/lib/rails/commands/destroy/destroy_command.rb
@@ -1,4 +1,4 @@
-require "rails/generators"
+require_relative "../../generators"
module Rails
module Command
diff --git a/railties/lib/rails/commands/generate/generate_command.rb b/railties/lib/rails/commands/generate/generate_command.rb
index 9dd7ad1012..c2089dbcb7 100644
--- a/railties/lib/rails/commands/generate/generate_command.rb
+++ b/railties/lib/rails/commands/generate/generate_command.rb
@@ -1,4 +1,4 @@
-require "rails/generators"
+require_relative "../../generators"
module Rails
module Command
diff --git a/railties/lib/rails/commands/help/USAGE b/railties/lib/rails/commands/help/USAGE
index c5f8ab72bb..8eb98319d2 100644
--- a/railties/lib/rails/commands/help/USAGE
+++ b/railties/lib/rails/commands/help/USAGE
@@ -1,13 +1,14 @@
The most common rails commands are:
- generate Generate new code (short-cut alias: "g")
- console Start the Rails console (short-cut alias: "c")
- server Start the Rails server (short-cut alias: "s")
- test Run tests (short-cut alias: "t")
- dbconsole Start a console for the database specified in config/database.yml
- (short-cut alias: "db")
+ generate Generate new code (short-cut alias: "g")
+ console Start the Rails console (short-cut alias: "c")
+ server Start the Rails server (short-cut alias: "s")
+ test Run tests except system tests (short-cut alias: "t")
+ test:system Run system tests
+ 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"
+ 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.
diff --git a/railties/lib/rails/commands/plugin/plugin_command.rb b/railties/lib/rails/commands/plugin/plugin_command.rb
index b40ab006af..e915a24e5b 100644
--- a/railties/lib/rails/commands/plugin/plugin_command.rb
+++ b/railties/lib/rails/commands/plugin/plugin_command.rb
@@ -34,8 +34,8 @@ module Rails
private
def run_plugin_generator(plugin_args)
- require "rails/generators"
- require "rails/generators/rails/plugin/plugin_generator"
+ require_relative "../../generators"
+ require_relative "../../generators/rails/plugin/plugin_generator"
Rails::Generators::PluginGenerator.start plugin_args
end
end
diff --git a/railties/lib/rails/commands/secrets/secrets_command.rb b/railties/lib/rails/commands/secrets/secrets_command.rb
index 03a640bd65..9e530f5e23 100644
--- a/railties/lib/rails/commands/secrets/secrets_command.rb
+++ b/railties/lib/rails/commands/secrets/secrets_command.rb
@@ -1,5 +1,5 @@
require "active_support"
-require "rails/secrets"
+require_relative "../../secrets"
module Rails
module Command
@@ -13,10 +13,7 @@ module Rails
end
def setup
- require "rails/generators"
- require "rails/generators/rails/encrypted_secrets/encrypted_secrets_generator"
-
- Rails::Generators::EncryptedSecretsGenerator.start
+ generator.start
end
def edit
@@ -34,8 +31,7 @@ module Rails
require_application_and_environment!
Rails::Secrets.read_for_editing do |tmp_path|
- say "Waiting for secrets file to be saved. Abort with Ctrl-C."
- system("\$EDITOR #{tmp_path}")
+ system("#{ENV["EDITOR"]} #{tmp_path}")
end
say "New secrets encrypted and saved."
@@ -43,7 +39,22 @@ module Rails
say "Aborted changing encrypted secrets: nothing saved."
rescue Rails::Secrets::MissingKeyError => error
say error.message
+ rescue Errno::ENOENT => error
+ raise unless error.message =~ /secrets\.yml\.enc/
+
+ Rails::Secrets.read_template_for_editing do |tmp_path|
+ system("#{ENV["EDITOR"]} #{tmp_path}")
+ generator.skip_secrets_file { setup }
+ end
end
+
+ private
+ def generator
+ require_relative "../../generators"
+ require_relative "../../generators/rails/encrypted_secrets/encrypted_secrets_generator"
+
+ Rails::Generators::EncryptedSecretsGenerator
+ end
end
end
end
diff --git a/railties/lib/rails/commands/server/server_command.rb b/railties/lib/rails/commands/server/server_command.rb
index 278fe63c51..b607a63176 100644
--- a/railties/lib/rails/commands/server/server_command.rb
+++ b/railties/lib/rails/commands/server/server_command.rb
@@ -2,7 +2,7 @@ require "fileutils"
require "optparse"
require "action_dispatch"
require "rails"
-require "rails/dev_caching"
+require_relative "../../dev_caching"
module Rails
class Server < ::Rack::Server
@@ -95,10 +95,11 @@ module Rails
module Command
class ServerCommand < Base # :nodoc:
+ DEFAULT_PORT = 3000
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
+ desc: "Runs Rails on the specified port - defaults to 3000.", banner: :port
class_option :binding, aliases: "-b", type: :string,
desc: "Binds Rails to the specified IP - defaults to 'localhost' in development and '0.0.0.0' in other environments'.",
banner: :IP
@@ -154,9 +155,16 @@ module Rails
def user_supplied_options
@user_supplied_options ||= begin
# Convert incoming options array to a hash of flags
- # ["-p", "3001", "-c", "foo"] # => {"-p" => true, "-c" => true}
+ # ["-p3001", "-C", "--binding", "127.0.0.1"] # => {"-p"=>true, "-C"=>true, "--binding"=>true}
user_flag = {}
- @original_options.each_with_index { |command, i| user_flag[command] = true if i.even? }
+ @original_options.each do |command|
+ if command.to_s.start_with?("--")
+ option = command.split("=")[0]
+ user_flag[option] = true
+ elsif command =~ /\A(-.)/
+ user_flag[Regexp.last_match[0]] = true
+ end
+ end
# Collect all options that the user has explicitly defined so we can
# differentiate them from defaults
@@ -184,7 +192,7 @@ module Rails
end
def port
- ENV.fetch("PORT", options[:port]).to_i
+ options[:port] || ENV.fetch("PORT", DEFAULT_PORT).to_i
end
def host
diff --git a/railties/lib/rails/commands/test/test_command.rb b/railties/lib/rails/commands/test/test_command.rb
index 65e16900ba..ca0b6c00fe 100644
--- a/railties/lib/rails/commands/test/test_command.rb
+++ b/railties/lib/rails/commands/test/test_command.rb
@@ -1,5 +1,5 @@
-require "rails/command"
-require "rails/test_unit/minitest_plugin"
+require_relative "../../command"
+require_relative "../../test_unit/minitest_plugin"
module Rails
module Command
@@ -11,7 +11,7 @@ module Rails
end
def perform(*)
- $LOAD_PATH << Rails::Command.root.join("test")
+ $LOAD_PATH << Rails::Command.root.join("test").to_s
Minitest.run_via = :rails
diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb
index fc7d4909f6..89ed313ff0 100644
--- a/railties/lib/rails/configuration.rb
+++ b/railties/lib/rails/configuration.rb
@@ -1,7 +1,7 @@
require "active_support/ordered_options"
require "active_support/core_ext/object"
-require "rails/paths"
-require "rails/rack"
+require_relative "paths"
+require_relative "rack"
module Rails
module Configuration
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index dc0b158bd4..a80593d4b9 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -1,5 +1,5 @@
-require "rails/railtie"
-require "rails/engine/railties"
+require_relative "railtie"
+require_relative "engine/railties"
require "active_support/core_ext/module/delegation"
require "pathname"
require "thread"
@@ -40,7 +40,7 @@ module Rails
#
# class MyEngine < Rails::Engine
# # Add a load path for this specific Engine
- # config.autoload_paths << File.expand_path("../lib/some/path", __FILE__)
+ # config.autoload_paths << File.expand_path("lib/some/path", __dir__)
#
# initializer "my_engine.add_middleware" do |app|
# app.middleware.use MyEngine::Middleware
@@ -437,8 +437,8 @@ module Rails
# Load console and invoke the registered hooks.
# Check <tt>Rails::Railtie.console</tt> for more info.
def load_console(app = self)
- require "rails/console/app"
- require "rails/console/helpers"
+ require_relative "console/app"
+ require_relative "console/helpers"
run_console_blocks(app)
self
end
@@ -461,7 +461,7 @@ module Rails
# Load Rails generators and invoke the registered hooks.
# Check <tt>Rails::Railtie.generators</tt> for more info.
def load_generators(app = self)
- require "rails/generators"
+ require_relative "generators"
run_generators_blocks(app)
Rails::Generators.configure!(app.config.generators)
self
diff --git a/railties/lib/rails/engine/commands.rb b/railties/lib/rails/engine/commands.rb
index b9ef63243a..5267b23d6c 100644
--- a/railties/lib/rails/engine/commands.rb
+++ b/railties/lib/rails/engine/commands.rb
@@ -4,4 +4,4 @@ unless defined?(APP_PATH)
end
end
-require "rails/commands"
+require_relative "../commands"
diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb
index 0c40173c38..9c7e2942bc 100644
--- a/railties/lib/rails/engine/configuration.rb
+++ b/railties/lib/rails/engine/configuration.rb
@@ -1,4 +1,4 @@
-require "rails/railtie/configuration"
+require_relative "../railtie/configuration"
module Rails
class Engine
diff --git a/railties/lib/rails/engine/updater.rb b/railties/lib/rails/engine/updater.rb
index 2ecf994a5c..af2bff1e59 100644
--- a/railties/lib/rails/engine/updater.rb
+++ b/railties/lib/rails/engine/updater.rb
@@ -1,5 +1,5 @@
-require "rails/generators"
-require "rails/generators/rails/plugin/plugin_generator"
+require_relative "../generators"
+require_relative "../generators/rails/plugin/plugin_generator"
module Rails
class Engine
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 8ec805370b..b9a0fe07c8 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -1,8 +1,8 @@
-activesupport_path = File.expand_path("../../../../activesupport/lib", __FILE__)
+activesupport_path = File.expand_path("../../../activesupport/lib", __dir__)
$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
require "thor/group"
-require "rails/command"
+require_relative "command"
require "active_support"
require "active_support/core_ext/object/blank"
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index 0bd0615b7e..5cf0985050 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -227,6 +227,7 @@ module Rails
#
# capify!
def capify!
+ ActiveSupport::Deprecation.warn("`capify!` is deprecated and will be removed in the next version of Rails.")
log :capify, ""
in_root { run("#{extify(:capify)} .", verbose: false) }
end
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 9109be5e04..760cb2f81e 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -1,10 +1,10 @@
require "fileutils"
require "digest/md5"
require "active_support/core_ext/string/strip"
-require "rails/version" unless defined?(Rails::VERSION)
+require_relative "../version" unless defined?(Rails::VERSION)
require "open-uri"
require "uri"
-require "rails/generators"
+require_relative "../generators"
require "active_support/core_ext/array/extract_options"
module Rails
@@ -13,7 +13,6 @@ module Rails
DATABASES = %w( mysql postgresql sqlite3 oracle frontbase ibm_db sqlserver )
JDBC_DATABASES = %w( jdbcmysql jdbcsqlite3 jdbcpostgresql jdbc )
DATABASES.concat(JDBC_DATABASES)
- WEBPACKS = %w( react vue angular )
attr_accessor :rails_template
add_shebang_option!
@@ -31,9 +30,6 @@ module Rails
class_option :database, type: :string, aliases: "-d", default: "sqlite3",
desc: "Preconfigure for selected database (options: #{DATABASES.join('/')})"
- class_option :webpack, type: :string, default: nil,
- desc: "Preconfigure for app-like JavaScript with Webpack (options: #{WEBPACKS.join('/')})"
-
class_option :skip_yarn, type: :boolean, default: false,
desc: "Don't use Yarn for managing JavaScript dependencies"
@@ -304,7 +300,7 @@ module Rails
return [] if options[:skip_sprockets]
gems = []
- gems << GemfileEntry.github("sass-rails", "rails/sass-rails", nil,
+ gems << GemfileEntry.version("sass-rails", "~> 5.0",
"Use SCSS for stylesheets")
if !options[:skip_javascript]
@@ -353,7 +349,7 @@ module Rails
if defined?(JRUBY_VERSION)
GemfileEntry.version "therubyrhino", nil, comment
else
- GemfileEntry.new "therubyracer", nil, comment, { platforms: :ruby }, true
+ GemfileEntry.new "mini_racer", nil, comment, { platforms: :ruby }, true
end
end
diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb
index a650c52626..e7f51dba99 100644
--- a/railties/lib/rails/generators/base.rb
+++ b/railties/lib/rails/generators/base.rb
@@ -215,7 +215,7 @@ module Rails
# Returns the base root for a common set of generators. This is used to dynamically
# guess the default source root.
def self.base_root
- File.dirname(__FILE__)
+ __dir__
end
# Cache source root and add lib/generators/base/generator/templates to
diff --git a/railties/lib/rails/generators/css/assets/assets_generator.rb b/railties/lib/rails/generators/css/assets/assets_generator.rb
index 20baf31a34..40f3bb3c70 100644
--- a/railties/lib/rails/generators/css/assets/assets_generator.rb
+++ b/railties/lib/rails/generators/css/assets/assets_generator.rb
@@ -1,9 +1,9 @@
-require "rails/generators/named_base"
+require_relative "../../named_base"
module Css # :nodoc:
module Generators # :nodoc:
class AssetsGenerator < Rails::Generators::NamedBase # :nodoc:
- source_root File.expand_path("../templates", __FILE__)
+ source_root File.expand_path("templates", __dir__)
def copy_stylesheet
copy_file "stylesheet.css", File.join("app/assets/stylesheets", class_path, "#{file_name}.css")
diff --git a/railties/lib/rails/generators/css/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/css/scaffold/scaffold_generator.rb
index cf534030f9..b8c72828a1 100644
--- a/railties/lib/rails/generators/css/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/css/scaffold/scaffold_generator.rb
@@ -1,4 +1,4 @@
-require "rails/generators/named_base"
+require_relative "../../named_base"
module Css # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/erb.rb b/railties/lib/rails/generators/erb.rb
index 97d9ab29d4..5314c42e46 100644
--- a/railties/lib/rails/generators/erb.rb
+++ b/railties/lib/rails/generators/erb.rb
@@ -1,4 +1,4 @@
-require "rails/generators/named_base"
+require_relative "named_base"
module Erb # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/erb/controller/controller_generator.rb b/railties/lib/rails/generators/erb/controller/controller_generator.rb
index 36ecfea09b..fb8222f654 100644
--- a/railties/lib/rails/generators/erb/controller/controller_generator.rb
+++ b/railties/lib/rails/generators/erb/controller/controller_generator.rb
@@ -1,4 +1,4 @@
-require "rails/generators/erb"
+require_relative "../../erb"
module Erb # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
index 3f1d9932f6..53dd3289d4 100644
--- a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
+++ b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
@@ -1,4 +1,4 @@
-require "rails/generators/erb"
+require_relative "../../erb"
module Erb # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb
index 0d77ef21da..0929425137 100644
--- a/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb
@@ -1,5 +1,5 @@
-require "rails/generators/erb"
-require "rails/generators/resource_helpers"
+require_relative "../../erb"
+require_relative "../../resource_helpers"
module Erb # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/js/assets/assets_generator.rb b/railties/lib/rails/generators/js/assets/assets_generator.rb
index 64d706ec91..a4f84cc1ad 100644
--- a/railties/lib/rails/generators/js/assets/assets_generator.rb
+++ b/railties/lib/rails/generators/js/assets/assets_generator.rb
@@ -1,9 +1,9 @@
-require "rails/generators/named_base"
+require_relative "../../named_base"
module Js # :nodoc:
module Generators # :nodoc:
class AssetsGenerator < Rails::Generators::NamedBase # :nodoc:
- source_root File.expand_path("../templates", __FILE__)
+ source_root File.expand_path("templates", __dir__)
def copy_javascript
copy_file "javascript.js", File.join("app/assets/javascripts", class_path, "#{file_name}.js")
diff --git a/railties/lib/rails/generators/migration.rb b/railties/lib/rails/generators/migration.rb
index 82481169c3..57c36f5668 100644
--- a/railties/lib/rails/generators/migration.rb
+++ b/railties/lib/rails/generators/migration.rb
@@ -1,5 +1,5 @@
require "active_support/concern"
-require "rails/generators/actions/create_migration"
+require_relative "actions/create_migration"
module Rails
module Generators
diff --git a/railties/lib/rails/generators/model_helpers.rb b/railties/lib/rails/generators/model_helpers.rb
index 6f87a18660..5bf480c5c0 100644
--- a/railties/lib/rails/generators/model_helpers.rb
+++ b/railties/lib/rails/generators/model_helpers.rb
@@ -1,4 +1,4 @@
-require "rails/generators/active_model"
+require_relative "active_model"
module Rails
module Generators
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index 46001f306a..d63a5b0c30 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -1,6 +1,6 @@
require "active_support/core_ext/module/introspection"
-require "rails/generators/base"
-require "rails/generators/generated_attribute"
+require_relative "base"
+require_relative "generated_attribute"
module Rails
module Generators
@@ -150,7 +150,7 @@ module Rails
end
def field_id(attribute_name)
- [singular_table_name, attribute_name].join('_')
+ [singular_table_name, attribute_name].join("_")
end
def singular_table_name # :doc:
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 324843a5f5..7614e6304b 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -1,4 +1,4 @@
-require "rails/generators/app_base"
+require_relative "../../app_base"
module Rails
module ActionMethods # :nodoc:
@@ -67,6 +67,10 @@ module Rails
end
end
+ def package_json
+ template "package.json"
+ end
+
def app
directory "app"
@@ -117,7 +121,6 @@ module Rails
action_cable_config_exist = File.exist?("config/cable.yml")
rack_cors_config_exist = File.exist?("config/initializers/cors.rb")
assets_config_exist = File.exist?("config/initializers/assets.rb")
- new_framework_defaults_5_1_exist = File.exist?("config/initializers/new_framework_defaults_5_1.rb")
config
@@ -141,12 +144,6 @@ module Rails
unless assets_config_exist
remove_file "config/initializers/assets.rb"
end
-
- # Sprockets owns the only new default for 5.1:
- # In API-only Applications, we don't want the file.
- unless new_framework_defaults_5_1_exist
- remove_file "config/initializers/new_framework_defaults_5_1.rb"
- end
end
end
@@ -198,20 +195,18 @@ module Rails
def vendor
empty_directory_with_keep_file "vendor"
-
- unless options[:skip_yarn]
- template "package.json"
- end
end
end
module Generators
# We need to store the RAILS_DEV_PATH in a constant, otherwise the path
# can change in Ruby 1.8.7 when we FileUtils.cd.
- RAILS_DEV_PATH = File.expand_path("../../../../../..", File.dirname(__FILE__))
+ RAILS_DEV_PATH = File.expand_path("../../../../../..", __dir__)
RESERVED_NAMES = %w[application destroy plugin runner test]
class AppGenerator < AppBase # :nodoc:
+ WEBPACKS = %w( react vue angular elm )
+
add_shared_options_for "application"
# Add bin/rails options
@@ -224,6 +219,9 @@ module Rails
class_option :skip_bundle, type: :boolean, aliases: "-B", default: false,
desc: "Don't run bundle install"
+ class_option :webpack, type: :string, default: nil,
+ desc: "Preconfigure for app-like JavaScript with Webpack (options: #{WEBPACKS.join('/')})"
+
def initialize(*args)
super
@@ -248,6 +246,7 @@ module Rails
build(:gitignore) unless options[:skip_git]
build(:gemfile) unless options[:skip_gemfile]
build(:version_control)
+ build(:package_json) unless options[:skip_yarn]
end
def create_app_files
@@ -317,10 +316,6 @@ module Rails
def create_vendor_files
build(:vendor)
-
- if options[:skip_yarn]
- remove_file "package.json"
- end
end
def delete_app_assets_if_api_option
@@ -403,10 +398,8 @@ module Rails
end
def delete_new_framework_defaults
- # Sprockets owns the only new default for 5.1: if it's disabled,
- # we don't want the file.
- unless options[:update] && !options[:skip_sprockets]
- remove_file "config/initializers/new_framework_defaults_5_1.rb"
+ unless options[:update]
+ remove_file "config/initializers/new_framework_defaults_5_2.rb"
end
end
@@ -522,7 +515,7 @@ module Rails
def handle_version_request!(argument)
if ["--version", "-v"].include?(argument)
- require "rails/version"
+ require_relative "../../../version"
puts "Rails #{Rails::VERSION::STRING}"
exit(0)
end
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index 06f0dd6d6d..64e2062aea 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -1,9 +1,5 @@
source 'https://rubygems.org'
-
-git_source(:github) do |repo_name|
- repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
- "https://github.com/#{repo_name}.git"
-end
+git_source(:github) { |repo| "https://github.com/#{repo}.git" }
<% gemfile_entries.each do |gem| -%>
<% if gem.comment -%>
@@ -34,14 +30,14 @@ group :development, :test do
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
<%- if depends_on_system_test? -%>
# Adds support for Capybara system testing and selenium driver
- gem 'capybara', '~> 2.13.0'
+ gem 'capybara', '~> 2.13'
gem 'selenium-webdriver'
<%- end -%>
end
group :development do
<%- unless options.api? -%>
- # Access an IRB console on exception pages or by using <%%= console %> anywhere in the code.
+ # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
<%- if options.dev? || options.edge? -%>
gem 'web-console', github: 'rails/web-console'
<%- else -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
index 865300bef9..d05ea0f511 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
@@ -2,7 +2,7 @@
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
- * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
* vendor/assets/stylesheets directory can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/bundle b/railties/lib/rails/generators/rails/app/templates/bin/bundle
index 1123dcf501..a84f0afe47 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/bundle
+++ b/railties/lib/rails/generators/rails/app/templates/bin/bundle
@@ -1,2 +1,2 @@
-ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
load Gem.bin_path('bundler', 'bundle')
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/setup.tt b/railties/lib/rails/generators/rails/app/templates/bin/setup.tt
index c6607dbb2b..ee9d077c30 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/setup.tt
+++ b/railties/lib/rails/generators/rails/app/templates/bin/setup.tt
@@ -1,9 +1,8 @@
-require 'pathname'
require 'fileutils'
include FileUtils
# path to your application root.
-APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
+APP_ROOT = File.expand_path('..', __dir__)
def system!(*args)
system(*args) || abort("\n== Command #{args} failed ==")
@@ -19,9 +18,9 @@ chdir APP_ROOT do
<% unless options[:skip_yarn] %>
# Install JavaScript dependencies if using Yarn
# system('bin/yarn')
-
<% end %>
<% unless options.skip_active_record -%>
+
# puts "\n== Copying sample files =="
# unless File.exist?('config/database.yml')
# cp 'config/database.yml.sample', 'config/database.yml'
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/update.tt b/railties/lib/rails/generators/rails/app/templates/bin/update.tt
index d23af018c7..5b6e50883e 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/update.tt
+++ b/railties/lib/rails/generators/rails/app/templates/bin/update.tt
@@ -1,9 +1,8 @@
-require 'pathname'
require 'fileutils'
include FileUtils
# path to your application root.
-APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
+APP_ROOT = File.expand_path('..', __dir__)
def system!(*args)
system(*args) || abort("\n== Command #{args} failed ==")
@@ -17,6 +16,7 @@ chdir APP_ROOT do
system! 'gem install bundler --conservative'
system('bundle check') || system!('bundle install')
<% unless options.skip_active_record -%>
+
puts "\n== Updating database =="
system! 'bin/rails db:migrate'
<% end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/yarn b/railties/lib/rails/generators/rails/app/templates/bin/yarn
index 4ae896a8d3..44f75c22a4 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/yarn
+++ b/railties/lib/rails/generators/rails/app/templates/bin/yarn
@@ -3,7 +3,8 @@ Dir.chdir(VENDOR_PATH) do
begin
exec "yarnpkg #{ARGV.join(" ")}"
rescue Errno::ENOENT
- puts "Yarn executable was not detected in the system."
- puts "Download Yarn at https://yarnpkg.com/en/docs/install"
+ $stderr.puts "Yarn executable was not detected in the system."
+ $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
+ exit 1
end
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb
index d5d214052f..0b1d22228e 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -34,6 +34,10 @@ module <%= app_const_base %>
# Middleware like session, flash, cookies can be added back manually.
# Skip views, helpers and assets when generating a new resource.
config.api_only = true
+<%- elsif !depends_on_system_test? -%>
+
+ # Don't generate system test files.
+ config.generators.system_tests = nil
<%- end -%>
end
end
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 a21555e573..049de65f22 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml
@@ -1,4 +1,4 @@
-# SQL Server (2012 or higher recommended)
+# SQL Server (2012 or higher required)
#
# Install the adapters and driver
# gem install tiny_tds
@@ -12,7 +12,7 @@ default: &default
adapter: sqlserver
encoding: utf8
username: sa
- password: <%= ENV['SA_PASSWORD'] %>
+ password: <%%= ENV['SA_PASSWORD'] %>
host: localhost
development:
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 9c4a77fd1d..d44331a888 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
@@ -36,8 +36,8 @@ Rails.application.configure do
config.assets.compile = false
# `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
- <%- end -%>
+ <%- end -%>
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.action_controller.asset_host = 'http://assets.example.com'
@@ -50,8 +50,8 @@ Rails.application.configure do
# config.action_cable.mount_path = nil
# config.action_cable.url = 'wss://example.com/cable'
# config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
- <%- end -%>
+ <%- end -%>
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
@@ -68,14 +68,15 @@ Rails.application.configure do
# Use a real queuing backend for Active Job (and separate queues per environment)
# config.active_job.queue_adapter = :resque
# config.active_job.queue_name_prefix = "<%= app_name %>_#{Rails.env}"
+
<%- unless options.skip_action_mailer? -%>
config.action_mailer.perform_caching = false
# Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
# config.action_mailer.raise_delivery_errors = false
- <%- end -%>
+ <%- end -%>
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation cannot be found).
config.i18n.fallbacks = true
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_1.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_1.rb.tt
deleted file mode 100644
index 5f5545c4c7..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_1.rb.tt
+++ /dev/null
@@ -1,13 +0,0 @@
-# Be sure to restart your server when you modify this file.
-#
-# This file contains migration options to ease your Rails 5.1 upgrade.
-#
-# Once upgraded flip defaults one by one to migrate to the new default.
-#
-# Read the Guide for Upgrading Ruby on Rails for more info on each option.
-<%- unless options[:skip_sprockets] -%>
-
-# Unknown asset fallback will return the path passed in when the given
-# asset is not present in the asset pipeline.
-# Rails.application.config.assets.unknown_asset_fallback = false
-<%- end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt
new file mode 100644
index 0000000000..3809936f9f
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt
@@ -0,0 +1,19 @@
+# Be sure to restart your server when you modify this file.
+#
+# This file contains migration options to ease your Rails 5.2 upgrade.
+#
+# Once upgraded flip defaults one by one to migrate to the new default.
+#
+# Read the Guide for Upgrading Ruby on Rails for more info on each option.
+
+# Make Active Record use stable #cache_key alongside new #cache_version method.
+# This is needed for recyclable cache keys.
+# Rails.application.config.active_record.cache_versioning = true
+
+# Use AES 256 GCM authenticated encryption for encrypted cookies.
+# Existing cookies will be converted on read then written with the new scheme.
+# Rails.application.config.action_dispatch.use_authenticated_cookie_encryption = true
+
+# Use AES-256-GCM authenticated encryption as default cipher for encrypting messages
+# instead of AES-256-CBC, when use_authenticated_message_encryption is set to true.
+# Rails.application.config.active_support.use_authenticated_message_encryption = true
diff --git a/railties/lib/rails/generators/rails/app/templates/gitignore b/railties/lib/rails/generators/rails/app/templates/gitignore
index 7221c26729..1e6b9afcd2 100644
--- a/railties/lib/rails/generators/rails/app/templates/gitignore
+++ b/railties/lib/rails/generators/rails/app/templates/gitignore
@@ -26,4 +26,8 @@
/yarn-error.log
<% end -%>
+
+<% unless options[:api] -%>
+/public/assets
+<% end -%>
.byebug_history
diff --git a/railties/lib/rails/generators/rails/app/templates/public/404.html b/railties/lib/rails/generators/rails/app/templates/public/404.html
index b612547fc2..2be3af26fc 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/404.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/404.html
@@ -4,7 +4,7 @@
<title>The page you were looking for doesn't exist (404)</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
- body {
+ .rails-default-error-page {
background-color: #EFEFEF;
color: #2E2F30;
text-align: center;
@@ -12,13 +12,13 @@
margin: 0;
}
- div.dialog {
+ .rails-default-error-page div.dialog {
width: 95%;
max-width: 33em;
margin: 4em auto 0;
}
- div.dialog > div {
+ .rails-default-error-page div.dialog > div {
border: 1px solid #CCC;
border-right-color: #999;
border-left-color: #999;
@@ -31,13 +31,13 @@
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
- h1 {
+ .rails-default-error-page h1 {
font-size: 100%;
color: #730E15;
line-height: 1.5em;
}
- div.dialog > p {
+ .rails-default-error-page div.dialog > p {
margin: 0 0 1em;
padding: 1em;
background-color: #F7F7F7;
@@ -54,7 +54,7 @@
</style>
</head>
-<body>
+<body class="rails-default-error-page">
<!-- This file lives in public/404.html -->
<div class="dialog">
<div>
diff --git a/railties/lib/rails/generators/rails/app/templates/public/422.html b/railties/lib/rails/generators/rails/app/templates/public/422.html
index a21f82b3bd..c08eac0d1d 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/422.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/422.html
@@ -4,7 +4,7 @@
<title>The change you wanted was rejected (422)</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
- body {
+ .rails-default-error-page {
background-color: #EFEFEF;
color: #2E2F30;
text-align: center;
@@ -12,13 +12,13 @@
margin: 0;
}
- div.dialog {
+ .rails-default-error-page div.dialog {
width: 95%;
max-width: 33em;
margin: 4em auto 0;
}
- div.dialog > div {
+ .rails-default-error-page div.dialog > div {
border: 1px solid #CCC;
border-right-color: #999;
border-left-color: #999;
@@ -31,13 +31,13 @@
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
- h1 {
+ .rails-default-error-page h1 {
font-size: 100%;
color: #730E15;
line-height: 1.5em;
}
- div.dialog > p {
+ .rails-default-error-page div.dialog > p {
margin: 0 0 1em;
padding: 1em;
background-color: #F7F7F7;
@@ -54,7 +54,7 @@
</style>
</head>
-<body>
+<body class="rails-default-error-page">
<!-- This file lives in public/422.html -->
<div class="dialog">
<div>
diff --git a/railties/lib/rails/generators/rails/app/templates/public/500.html b/railties/lib/rails/generators/rails/app/templates/public/500.html
index 061abc587d..78a030af22 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/500.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/500.html
@@ -4,7 +4,7 @@
<title>We're sorry, but something went wrong (500)</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
- body {
+ .rails-default-error-page {
background-color: #EFEFEF;
color: #2E2F30;
text-align: center;
@@ -12,13 +12,13 @@
margin: 0;
}
- div.dialog {
+ .rails-default-error-page div.dialog {
width: 95%;
max-width: 33em;
margin: 4em auto 0;
}
- div.dialog > div {
+ .rails-default-error-page div.dialog > div {
border: 1px solid #CCC;
border-right-color: #999;
border-left-color: #999;
@@ -31,13 +31,13 @@
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
- h1 {
+ .rails-default-error-page h1 {
font-size: 100%;
color: #730E15;
line-height: 1.5em;
}
- div.dialog > p {
+ .rails-default-error-page div.dialog > p {
margin: 0 0 1em;
padding: 1em;
background-color: #F7F7F7;
@@ -54,7 +54,7 @@
</style>
</head>
-<body>
+<body class="rails-default-error-page">
<!-- This file lives in public/500.html -->
<div class="dialog">
<div>
diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
index 2f92168eef..6ad1f11781 100644
--- a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
+++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../config/environment', __FILE__)
+require_relative '../config/environment'
require 'rails/test_help'
class ActiveSupport::TestCase
diff --git a/railties/lib/rails/generators/rails/encrypted_secrets/encrypted_secrets_generator.rb b/railties/lib/rails/generators/rails/encrypted_secrets/encrypted_secrets_generator.rb
index 8b29213610..8ba0fc4c5b 100644
--- a/railties/lib/rails/generators/rails/encrypted_secrets/encrypted_secrets_generator.rb
+++ b/railties/lib/rails/generators/rails/encrypted_secrets/encrypted_secrets_generator.rb
@@ -1,5 +1,5 @@
-require "rails/generators/base"
-require "rails/secrets"
+require_relative "../../base"
+require_relative "../../../secrets"
module Rails
module Generators
@@ -36,25 +36,29 @@ module Rails
end
def add_encrypted_secrets_file
- unless File.exist?("config/secrets.yml.enc")
+ unless (defined?(@@skip_secrets_file) && @@skip_secrets_file) || File.exist?("config/secrets.yml.enc")
say "Adding config/secrets.yml.enc to store secrets that needs to be encrypted."
say ""
+ say "For now the file contains this but it's been encrypted with the generated key:"
+ say ""
+ say Secrets.template, :on_green
+ say ""
- template "config/secrets.yml.enc" do |prefill|
- say ""
- say "For now the file contains this but it's been encrypted with the generated key:"
- say ""
- say prefill, :on_green
- say ""
-
- Secrets.encrypt(prefill)
- end
+ Secrets.write(Secrets.template)
say "You can edit encrypted secrets with `bin/rails secrets:edit`."
-
- say "Add this to your config/environments/production.rb:"
- say "config.read_encrypted_secrets = true"
+ say ""
end
+
+ say "Add this to your config/environments/production.rb:"
+ say "config.read_encrypted_secrets = true"
+ end
+
+ def self.skip_secrets_file
+ @@skip_secrets_file = true
+ yield
+ ensure
+ @@skip_secrets_file = false
end
private
diff --git a/railties/lib/rails/generators/rails/encrypted_secrets/templates/config/secrets.yml.enc b/railties/lib/rails/generators/rails/encrypted_secrets/templates/config/secrets.yml.enc
deleted file mode 100644
index 70426a66a5..0000000000
--- a/railties/lib/rails/generators/rails/encrypted_secrets/templates/config/secrets.yml.enc
+++ /dev/null
@@ -1,3 +0,0 @@
-# See `secrets.yml` for tips on generating suitable keys.
-# production:
-# external_api_key: 1466aac22e6a869134be3d09b9e89232fc2c2289…
diff --git a/railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt b/railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt
index d0575772bc..178d5c3f9f 100644
--- a/railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt
+++ b/railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt
@@ -1,3 +1,3 @@
class <%= class_name %>Generator < Rails::Generators::NamedBase
- source_root File.expand_path('../templates', __FILE__)
+ source_root File.expand_path('templates', __dir__)
end
diff --git a/railties/lib/rails/generators/rails/model/model_generator.rb b/railties/lib/rails/generators/rails/model/model_generator.rb
index c32a8a079a..8c8aac54bf 100644
--- a/railties/lib/rails/generators/rails/model/model_generator.rb
+++ b/railties/lib/rails/generators/rails/model/model_generator.rb
@@ -1,4 +1,4 @@
-require "rails/generators/model_helpers"
+require_relative "../../model_helpers"
module Rails
module Generators
diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
index 118e44d9d0..a1209e4624 100644
--- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
@@ -1,5 +1,5 @@
require "active_support/core_ext/hash/slice"
-require "rails/generators/rails/app/app_generator"
+require_relative "../app/app_generator"
require "date"
module Rails
@@ -60,7 +60,12 @@ module Rails
template "lib/%namespaced_name%.rb"
template "lib/tasks/%namespaced_name%_tasks.rake"
template "lib/%namespaced_name%/version.rb"
- template "lib/%namespaced_name%/engine.rb" if engine?
+
+ if engine?
+ template "lib/%namespaced_name%/engine.rb"
+ else
+ template "lib/%namespaced_name%/railtie.rb"
+ end
end
def config
diff --git a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec
index d84d1aabdb..9a8c4bf098 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec
+++ b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec
@@ -1,4 +1,4 @@
-$:.push File.expand_path("../lib", __FILE__)
+$:.push File.expand_path("lib", __dir__)
# Maintain your gem's version:
require "<%= namespaced_name %>/version"
diff --git a/railties/lib/rails/generators/rails/plugin/templates/Rakefile b/railties/lib/rails/generators/rails/plugin/templates/Rakefile
index 383d2fb2d1..3581dd401a 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/plugin/templates/Rakefile
@@ -15,7 +15,7 @@ RDoc::Task.new(:rdoc) do |rdoc|
end
<% if engine? && !options[:skip_active_record] && with_dummy_app? -%>
-APP_RAKEFILE = File.expand_path("../<%= dummy_path -%>/Rakefile", __FILE__)
+APP_RAKEFILE = File.expand_path("<%= dummy_path -%>/Rakefile", __dir__)
load 'rails/tasks/engine.rake'
<% end %>
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 c03d9953d4..ffa277e334 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt
+++ b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt
@@ -1,12 +1,12 @@
# This command will automatically be run when you run "rails" with Rails gems
# installed from the root of your application.
-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__)
+ENGINE_ROOT = File.expand_path('..', __dir__)
+ENGINE_PATH = File.expand_path('../lib/<%= namespaced_name -%>/engine', __dir__)
+APP_PATH = File.expand_path('../<%= dummy_path -%>/config/application', __dir__)
# Set up gems listed in the Gemfile.
-ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
require 'rails/all'
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 8385e6a8a2..8e7d321626 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt
+++ b/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt
@@ -1,4 +1,4 @@
-$: << File.expand_path(File.expand_path("../../test", __FILE__))
+$: << File.expand_path("../test", __dir__)
require "bundler/setup"
require "rails/plugin/test"
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb
index 40b1c4cee7..3285055eb7 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb
@@ -1,5 +1,7 @@
<% if engine? -%>
require "<%= namespaced_name %>/engine"
-
+<% else -%>
+require "<%= namespaced_name %>/railtie"
<% end -%>
+
<%= wrap_in_modules "# Your code goes here..." %>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/railtie.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/railtie.rb
new file mode 100644
index 0000000000..7bdf4ee5fb
--- /dev/null
+++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/railtie.rb
@@ -0,0 +1,5 @@
+<%= wrap_in_modules <<-rb.strip_heredoc
+ class Railtie < ::Rails::Railtie
+ end
+rb
+%>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb
index e84e403018..c281fc42ca 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb
@@ -1,8 +1,8 @@
-require File.expand_path("../../<%= options[:dummy_path] -%>/config/environment.rb", __FILE__)
+require_relative "<%= File.join('..', options[:dummy_path], 'config/environment') -%>"
<% unless options[:skip_active_record] -%>
-ActiveRecord::Migrator.migrations_paths = [File.expand_path("../../<%= options[:dummy_path] -%>/db/migrate", __FILE__)]
+ActiveRecord::Migrator.migrations_paths = [File.expand_path("../<%= options[:dummy_path] -%>/db/migrate", __dir__)]
<% if options[:mountable] -%>
-ActiveRecord::Migrator.migrations_paths << File.expand_path('../../db/migrate', __FILE__)
+ActiveRecord::Migrator.migrations_paths << File.expand_path('../db/migrate', __dir__)
<% end -%>
<% end -%>
require "rails/test_help"
@@ -17,7 +17,7 @@ Rails::TestUnitReporter.executable = 'bin/test'
# Load fixtures from the engine
if ActiveSupport::TestCase.respond_to?(:fixture_path=)
- ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__)
+ ActiveSupport::TestCase.fixture_path = File.expand_path("fixtures", __dir__)
ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path
ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + "/files"
ActiveSupport::TestCase.fixtures :all
diff --git a/railties/lib/rails/generators/rails/resource/resource_generator.rb b/railties/lib/rails/generators/rails/resource/resource_generator.rb
index 5ac5164af0..fa700c598a 100644
--- a/railties/lib/rails/generators/rails/resource/resource_generator.rb
+++ b/railties/lib/rails/generators/rails/resource/resource_generator.rb
@@ -1,5 +1,5 @@
-require "rails/generators/resource_helpers"
-require "rails/generators/rails/model/model_generator"
+require_relative "../../resource_helpers"
+require_relative "../model/model_generator"
module Rails
module Generators
diff --git a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
index 12d6bc85b2..3f92af4295 100644
--- a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
@@ -1,4 +1,4 @@
-require "rails/generators/rails/resource/resource_generator"
+require_relative "../resource/resource_generator"
module Rails
module Generators
@@ -16,13 +16,10 @@ 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/USAGE b/railties/lib/rails/generators/rails/scaffold_controller/USAGE
index 8ba4c5ccbc..28f229510b 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/USAGE
+++ b/railties/lib/rails/generators/rails/scaffold_controller/USAGE
@@ -12,7 +12,7 @@ Description:
Example:
`rails generate scaffold_controller CreditCard`
- Credit card controller with URLs like /credit_card/debit.
+ Credit card controller with URLs like /credit_cards.
Controller: app/controllers/credit_cards_controller.rb
Test: test/controllers/credit_cards_controller_test.rb
Views: app/views/credit_cards/index.html.erb [...]
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 cf97c22160..291a208498 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
@@ -1,4 +1,4 @@
-require "rails/generators/resource_helpers"
+require_relative "../../resource_helpers"
module Rails
module Generators
diff --git a/railties/lib/rails/generators/resource_helpers.rb b/railties/lib/rails/generators/resource_helpers.rb
index e7cb722473..170da6cb0d 100644
--- a/railties/lib/rails/generators/resource_helpers.rb
+++ b/railties/lib/rails/generators/resource_helpers.rb
@@ -1,5 +1,5 @@
-require "rails/generators/active_model"
-require "rails/generators/model_helpers"
+require_relative "active_model"
+require_relative "model_helpers"
module Rails
module Generators
diff --git a/railties/lib/rails/generators/test_case.rb b/railties/lib/rails/generators/test_case.rb
index 3eec929aeb..6cebd217a7 100644
--- a/railties/lib/rails/generators/test_case.rb
+++ b/railties/lib/rails/generators/test_case.rb
@@ -1,7 +1,7 @@
-require "rails/generators"
-require "rails/generators/testing/behaviour"
-require "rails/generators/testing/setup_and_teardown"
-require "rails/generators/testing/assertions"
+require_relative "../generators"
+require_relative "testing/behaviour"
+require_relative "testing/setup_and_teardown"
+require_relative "testing/assertions"
require "fileutils"
module Rails
@@ -14,7 +14,7 @@ module Rails
#
# class AppGeneratorTest < Rails::Generators::TestCase
# tests AppGenerator
- # destination File.expand_path("../tmp", File.dirname(__FILE__))
+ # destination File.expand_path("../tmp", __dir__)
# end
#
# If you want to ensure your destination root is clean before running each test,
@@ -22,7 +22,7 @@ module Rails
#
# class AppGeneratorTest < Rails::Generators::TestCase
# tests AppGenerator
- # destination File.expand_path("../tmp", File.dirname(__FILE__))
+ # destination File.expand_path("../tmp", __dir__)
# setup :prepare_destination
# end
class TestCase < ActiveSupport::TestCase
diff --git a/railties/lib/rails/generators/test_unit.rb b/railties/lib/rails/generators/test_unit.rb
index 722efcf492..986e673611 100644
--- a/railties/lib/rails/generators/test_unit.rb
+++ b/railties/lib/rails/generators/test_unit.rb
@@ -1,4 +1,4 @@
-require "rails/generators/named_base"
+require_relative "named_base"
module TestUnit # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/test_unit/controller/controller_generator.rb b/railties/lib/rails/generators/test_unit/controller/controller_generator.rb
index ac528d94f1..72a650e7d9 100644
--- a/railties/lib/rails/generators/test_unit/controller/controller_generator.rb
+++ b/railties/lib/rails/generators/test_unit/controller/controller_generator.rb
@@ -1,4 +1,4 @@
-require "rails/generators/test_unit"
+require_relative "../../test_unit"
module TestUnit # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/test_unit/generator/generator_generator.rb b/railties/lib/rails/generators/test_unit/generator/generator_generator.rb
index 6b6e094453..abc3615cfd 100644
--- a/railties/lib/rails/generators/test_unit/generator/generator_generator.rb
+++ b/railties/lib/rails/generators/test_unit/generator/generator_generator.rb
@@ -1,4 +1,4 @@
-require "rails/generators/test_unit"
+require_relative "../../test_unit"
module TestUnit # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/test_unit/helper/helper_generator.rb b/railties/lib/rails/generators/test_unit/helper/helper_generator.rb
index 6674a15fa3..2020e7785e 100644
--- a/railties/lib/rails/generators/test_unit/helper/helper_generator.rb
+++ b/railties/lib/rails/generators/test_unit/helper/helper_generator.rb
@@ -1,4 +1,4 @@
-require "rails/generators/test_unit"
+require_relative "../../test_unit"
module TestUnit # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/test_unit/integration/integration_generator.rb b/railties/lib/rails/generators/test_unit/integration/integration_generator.rb
index 9d065c1297..a31a795212 100644
--- a/railties/lib/rails/generators/test_unit/integration/integration_generator.rb
+++ b/railties/lib/rails/generators/test_unit/integration/integration_generator.rb
@@ -1,4 +1,4 @@
-require "rails/generators/test_unit"
+require_relative "../../test_unit"
module TestUnit # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/test_unit/job/job_generator.rb b/railties/lib/rails/generators/test_unit/job/job_generator.rb
index 6975252b99..f3ea7cd170 100644
--- a/railties/lib/rails/generators/test_unit/job/job_generator.rb
+++ b/railties/lib/rails/generators/test_unit/job/job_generator.rb
@@ -1,4 +1,4 @@
-require "rails/generators/test_unit"
+require_relative "../../test_unit"
module TestUnit # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb b/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb
index 67bff8e4f9..274755b057 100644
--- a/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb
+++ b/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb
@@ -1,4 +1,4 @@
-require "rails/generators/test_unit"
+require_relative "../../test_unit"
module TestUnit # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/test_unit/model/model_generator.rb b/railties/lib/rails/generators/test_unit/model/model_generator.rb
index 99495d5247..fef640f947 100644
--- a/railties/lib/rails/generators/test_unit/model/model_generator.rb
+++ b/railties/lib/rails/generators/test_unit/model/model_generator.rb
@@ -1,4 +1,4 @@
-require "rails/generators/test_unit"
+require_relative "../../test_unit"
module TestUnit # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/test_unit/plugin/plugin_generator.rb b/railties/lib/rails/generators/test_unit/plugin/plugin_generator.rb
index f1c9b6da5b..f324000235 100644
--- a/railties/lib/rails/generators/test_unit/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/test_unit/plugin/plugin_generator.rb
@@ -1,4 +1,4 @@
-require "rails/generators/test_unit"
+require_relative "../../test_unit"
module TestUnit # :nodoc:
module Generators # :nodoc:
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 292db35121..fbe5be3cdc 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
@@ -1,5 +1,5 @@
-require "rails/generators/test_unit"
-require "rails/generators/resource_helpers"
+require_relative "../../test_unit"
+require_relative "../../resource_helpers"
module TestUnit # :nodoc:
module Generators # :nodoc:
@@ -11,12 +11,19 @@ module TestUnit # :nodoc:
class_option :api, type: :boolean,
desc: "Generates API functional tests"
+ class_option :system_tests, type: :string,
+ desc: "Skip system test files"
+
argument :attributes, type: :array, default: [], banner: "field:type field:type"
def create_test_files
template_file = options.api? ? "api_functional_test.rb" : "functional_test.rb"
template template_file,
File.join("test/controllers", controller_class_path, "#{controller_file_name}_controller_test.rb")
+
+ unless options.api? || options[:system_tests].nil?
+ template "system_test.rb", File.join("test/system", class_path, "#{file_name.pluralize}_test.rb")
+ end
end
def fixture_name
@@ -30,16 +37,20 @@ module TestUnit # :nodoc:
private
+ def attributes_string
+ attributes_hash.map { |k, v| "#{k}: #{v}" }.join(", ")
+ end
+
def attributes_hash
- return if attributes_names.empty?
+ return {} if attributes_names.empty?
attributes_names.map do |name|
if %w(password password_confirmation).include?(name) && attributes.any?(&:password_digest?)
- "#{name}: 'secret'"
+ ["#{name}", "'secret'"]
else
- "#{name}: @#{singular_table_name}.#{name}"
+ ["#{name}", "@#{singular_table_name}.#{name}"]
end
- end.sort.join(", ")
+ end.sort.to_h
end
end
end
diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb
index c469c188e6..f21861d8e6 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb
@@ -17,7 +17,7 @@ class <%= controller_class_name %>ControllerTest < ActionDispatch::IntegrationTe
test "should create <%= singular_table_name %>" do
assert_difference('<%= class_name %>.count') do
- post <%= index_helper %>_url, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> }, as: :json
+ post <%= index_helper %>_url, params: { <%= "#{singular_table_name}: { #{attributes_string} }" %> }, as: :json
end
assert_response 201
@@ -29,7 +29,7 @@ class <%= controller_class_name %>ControllerTest < ActionDispatch::IntegrationTe
end
test "should update <%= singular_table_name %>" do
- patch <%= show_helper %>, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> }, as: :json
+ patch <%= show_helper %>, params: { <%= "#{singular_table_name}: { #{attributes_string} }" %> }, as: :json
assert_response 200
end
diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
index c33375b7b4..195d60be20 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
@@ -22,7 +22,7 @@ class <%= controller_class_name %>ControllerTest < ActionDispatch::IntegrationTe
test "should create <%= singular_table_name %>" do
assert_difference('<%= class_name %>.count') do
- post <%= index_helper %>_url, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> }
+ post <%= index_helper %>_url, params: { <%= "#{singular_table_name}: { #{attributes_string} }" %> }
end
assert_redirected_to <%= singular_table_name %>_url(<%= class_name %>.last)
@@ -39,7 +39,7 @@ class <%= controller_class_name %>ControllerTest < ActionDispatch::IntegrationTe
end
test "should update <%= singular_table_name %>" do
- patch <%= show_helper %>, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> }
+ patch <%= show_helper %>, params: { <%= "#{singular_table_name}: { #{attributes_string} }" %> }
assert_redirected_to <%= singular_table_name %>_url(<%= "@#{singular_table_name}" %>)
end
diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb
new file mode 100644
index 0000000000..f83f5a5c62
--- /dev/null
+++ b/railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb
@@ -0,0 +1,49 @@
+require "application_system_test_case"
+
+<% module_namespacing do -%>
+class <%= class_name.pluralize %>Test < ApplicationSystemTestCase
+ setup do
+ @<%= singular_table_name %> = <%= fixture_name %>(:one)
+ end
+
+ test "visiting the index" do
+ visit <%= plural_table_name %>_url
+ assert_selector "h1", text: "<%= class_name.pluralize.titleize %>"
+ end
+
+ test "creating a <%= human_name %>" do
+ visit <%= plural_table_name %>_url
+ click_on "New <%= class_name.titleize %>"
+
+ <%- attributes_hash.each do |attr, value| -%>
+ fill_in "<%= attr.humanize.titleize %>", with: <%= value %>
+ <%- end -%>
+ click_on "Create <%= human_name %>"
+
+ assert_text "<%= human_name %> was successfully created"
+ click_on "Back"
+ end
+
+ test "updating a <%= human_name %>" do
+ visit <%= plural_table_name %>_url
+ click_on "Edit", match: :first
+
+ <%- attributes_hash.each do |attr, value| -%>
+ fill_in "<%= attr.humanize.titleize %>", with: <%= value %>
+ <%- end -%>
+ click_on "Update <%= human_name %>"
+
+ assert_text "<%= human_name %> was successfully updated"
+ click_on "Back"
+ end
+
+ test "destroying a <%= human_name %>" do
+ visit <%= plural_table_name %>_url
+ page.accept_confirm do
+ click_on "Destroy", match: :first
+ end
+
+ assert_text "<%= human_name %> was successfully destroyed"
+ end
+end
+<% 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
index aec415a4e5..95cc86cc8a 100644
--- a/railties/lib/rails/generators/test_unit/system/system_generator.rb
+++ b/railties/lib/rails/generators/test_unit/system/system_generator.rb
@@ -1,4 +1,4 @@
-require "rails/generators/test_unit"
+require_relative "../../test_unit"
module TestUnit # :nodoc:
module Generators # :nodoc:
@@ -10,7 +10,7 @@ module TestUnit # :nodoc:
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")
+ template "system_test.rb", File.join("test/system", class_path, "#{file_name.pluralize}_test.rb")
end
end
end
diff --git a/railties/lib/rails/generators/testing/behaviour.rb b/railties/lib/rails/generators/testing/behaviour.rb
index 64d641d096..ede181a1bc 100644
--- a/railties/lib/rails/generators/testing/behaviour.rb
+++ b/railties/lib/rails/generators/testing/behaviour.rb
@@ -4,7 +4,7 @@ require "active_support/core_ext/hash/reverse_merge"
require "active_support/core_ext/kernel/reporting"
require "active_support/testing/stream"
require "active_support/concern"
-require "rails/generators"
+require_relative "../../generators"
module Rails
module Generators
@@ -14,12 +14,12 @@ module Rails
include ActiveSupport::Testing::Stream
included do
- class_attribute :destination_root, :current_path, :generator_class, :default_arguments
-
# Generators frequently change the current path using +FileUtils.cd+.
# So we need to store the path at file load and revert back to it after each test.
- self.current_path = File.expand_path(Dir.pwd)
- self.default_arguments = []
+ class_attribute :current_path, default: File.expand_path(Dir.pwd)
+ class_attribute :default_arguments, default: []
+ class_attribute :destination_root
+ class_attribute :generator_class
end
module ClassMethods
@@ -40,7 +40,7 @@ module Rails
# Sets the destination of generator files:
#
- # destination File.expand_path("../tmp", File.dirname(__FILE__))
+ # destination File.expand_path("../tmp", __dir__)
def destination(path)
self.destination_root = path
end
@@ -51,7 +51,7 @@ module Rails
#
# class AppGeneratorTest < Rails::Generators::TestCase
# tests AppGenerator
- # destination File.expand_path("../tmp", File.dirname(__FILE__))
+ # destination File.expand_path("../tmp", __dir__)
# setup :prepare_destination
#
# test "database.yml is not created when skipping Active Record" do
diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb
index fc064dac32..db08d578c0 100644
--- a/railties/lib/rails/info.rb
+++ b/railties/lib/rails/info.rb
@@ -5,8 +5,9 @@ module Rails
# Rails::InfoController responses. These include the active Rails version,
# Ruby version, Rack version, and so on.
module Info
- mattr_accessor :properties
- class << (@@properties = [])
+ mattr_accessor :properties, default: []
+
+ class << @@properties
def names
map(&:first)
end
diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb
index 8b553aea79..9fe6ef5a93 100644
--- a/railties/lib/rails/info_controller.rb
+++ b/railties/lib/rails/info_controller.rb
@@ -1,4 +1,4 @@
-require "rails/application_controller"
+require_relative "application_controller"
require "action_dispatch/routing/inspector"
class Rails::InfoController < Rails::ApplicationController # :nodoc:
diff --git a/railties/lib/rails/mailers_controller.rb b/railties/lib/rails/mailers_controller.rb
index 000ce40fbc..5b9af97617 100644
--- a/railties/lib/rails/mailers_controller.rb
+++ b/railties/lib/rails/mailers_controller.rb
@@ -1,4 +1,4 @@
-require "rails/application_controller"
+require_relative "application_controller"
class Rails::MailersController < Rails::ApplicationController # :nodoc:
prepend_view_path ActionDispatch::DebugExceptions::RESCUES_TEMPLATE_PATH
@@ -6,6 +6,8 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc:
before_action :require_local!, unless: :show_previews?
before_action :find_preview, only: :preview
+ helper_method :part_query
+
def index
@previews = ActionMailer::Preview.all
@page_title = "Mailer Previews"
@@ -19,7 +21,7 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc:
@email_action = File.basename(params[:path])
if @preview.email_exists?(@email_action)
- @email = @preview.call(@email_action)
+ @email = @preview.call(@email_action, params)
if params[:part]
part_type = Mime::Type.lookup(params[:part])
@@ -76,4 +78,8 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc:
@email
end
end
+
+ def part_query(mime_type)
+ request.query_parameters.merge(part: mime_type).to_query
+ end
end
diff --git a/railties/lib/rails/plugin/test.rb b/railties/lib/rails/plugin/test.rb
index ff043b488e..bd43cba688 100644
--- a/railties/lib/rails/plugin/test.rb
+++ b/railties/lib/rails/plugin/test.rb
@@ -1,4 +1,4 @@
-require "rails/test_unit/minitest_plugin"
+require_relative "../test_unit/minitest_plugin"
Rails::TestUnitReporter.executable = "bin/test"
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index 474118c0a9..a2a0192cfc 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -1,4 +1,4 @@
-require "rails/initializable"
+require_relative "initializable"
require "active_support/inflector"
require "active_support/core_ext/module/introspection"
require "active_support/core_ext/module/delegation"
@@ -103,6 +103,9 @@ module Rails
# end
# end
#
+ # Since filenames on the load path are shared across gems, be sure that files you load
+ # through a railtie have unique names.
+ #
# == Application and Engine
#
# An engine is nothing more than a railtie with some initializers already set. And since
@@ -162,10 +165,6 @@ module Rails
@instance ||= new
end
- def respond_to_missing?(*args)
- instance.respond_to?(*args) || super
- end
-
# Allows you to configure the railtie. This is the same method seen in
# Railtie::Configurable, but this module is no longer required for all
# subclasses of Railtie so we provide the class method here.
@@ -178,6 +177,10 @@ module Rails
ActiveSupport::Inflector.underscore(string).tr("/", "_")
end
+ def respond_to_missing?(name, _)
+ instance.respond_to?(name) || super
+ end
+
# If the class method does not have a method, then send the method call
# to the Railtie instance.
def method_missing(name, *args, &block)
diff --git a/railties/lib/rails/railtie/configuration.rb b/railties/lib/rails/railtie/configuration.rb
index aecc81c491..275d2039f6 100644
--- a/railties/lib/rails/railtie/configuration.rb
+++ b/railties/lib/rails/railtie/configuration.rb
@@ -1,4 +1,4 @@
-require "rails/configuration"
+require_relative "../configuration"
module Rails
class Railtie
diff --git a/railties/lib/rails/secrets.rb b/railties/lib/rails/secrets.rb
index 2a95712cd9..c7a8676d7b 100644
--- a/railties/lib/rails/secrets.rb
+++ b/railties/lib/rails/secrets.rb
@@ -1,5 +1,6 @@
require "yaml"
require "active_support/message_encryptor"
+require "active_support/core_ext/string/strip"
module Rails
# Greatly inspired by Ara T. Howard's magnificent sekrets gem. 😘
@@ -14,12 +15,10 @@ module Rails
end
@cipher = "aes-128-gcm"
- @read_encrypted_secrets = false
@root = File # Wonky, but ensures `join` uses the current directory.
class << self
- attr_writer :root
- attr_accessor :read_encrypted_secrets
+ attr_writer :root
def parse(paths, env:)
paths.each_with_object(Hash.new) do |path, all_secrets|
@@ -39,6 +38,15 @@ module Rails
ENV["RAILS_MASTER_KEY"] || read_key_file || handle_missing_key
end
+ def template
+ <<-end_of_template.strip_heredoc
+ # See `secrets.yml` for tips on generating suitable keys.
+ # production:
+ # external_api_key: 1466aac22e6a869134be3d09b9e89232fc2c2289
+
+ end_of_template
+ end
+
def encrypt(data)
encryptor.encrypt_and_sign(data)
end
@@ -56,15 +64,12 @@ module Rails
FileUtils.mv("#{path}.tmp", path)
end
- def read_for_editing
- tmp_path = File.join(Dir.tmpdir, File.basename(path))
- IO.binwrite(tmp_path, read)
-
- yield tmp_path
+ def read_for_editing(&block)
+ writing(read, &block)
+ end
- write(IO.binread(tmp_path))
- ensure
- FileUtils.rm(tmp_path) if File.exist?(tmp_path)
+ def read_template_for_editing(&block)
+ writing(template, &block)
end
private
@@ -88,16 +93,23 @@ module Rails
def preprocess(path)
if path.end_with?(".enc")
- if @read_encrypted_secrets
- decrypt(IO.binread(path))
- else
- ""
- end
+ decrypt(IO.binread(path))
else
IO.read(path)
end
end
+ def writing(contents)
+ tmp_path = File.join(Dir.tmpdir, File.basename(path))
+ File.write(tmp_path, contents)
+
+ yield tmp_path
+
+ write(File.read(tmp_path))
+ ensure
+ FileUtils.rm(tmp_path) if File.exist?(tmp_path)
+ end
+
def encryptor
@encryptor ||= ActiveSupport::MessageEncryptor.new([ key ].pack("H*"), cipher: @cipher)
end
diff --git a/railties/lib/rails/tasks/annotations.rake b/railties/lib/rails/tasks/annotations.rake
index 9a69eb9934..ae427249ef 100644
--- a/railties/lib/rails/tasks/annotations.rake
+++ b/railties/lib/rails/tasks/annotations.rake
@@ -1,4 +1,4 @@
-require "rails/source_annotation_extractor"
+require_relative "../source_annotation_extractor"
desc "Enumerate all annotations (use notes:optimize, :fixme, :todo for focus)"
task :notes do
diff --git a/railties/lib/rails/tasks/dev.rake b/railties/lib/rails/tasks/dev.rake
index 334e968123..b2447cbcfc 100644
--- a/railties/lib/rails/tasks/dev.rake
+++ b/railties/lib/rails/tasks/dev.rake
@@ -1,4 +1,4 @@
-require "rails/dev_caching"
+require_relative "../dev_caching"
namespace :dev do
desc "Toggle development mode caching on/off"
diff --git a/railties/lib/rails/tasks/engine.rake b/railties/lib/rails/tasks/engine.rake
index 177b138090..a8d67322da 100644
--- a/railties/lib/rails/tasks/engine.rake
+++ b/railties/lib/rails/tasks/engine.rake
@@ -6,7 +6,7 @@ task "load_app" do
task update: [ "update:bin" ]
namespace :update do
- require "rails/engine/updater"
+ require_relative "../engine/updater"
# desc "Adds new executables to the engine bin/ directory"
task :bin do
Rails::Engine::Updater.run(:create_bin_files)
diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake
index 32a6b109bc..f7e8f4f340 100644
--- a/railties/lib/rails/tasks/framework.rake
+++ b/railties/lib/rails/tasks/framework.rake
@@ -7,8 +7,8 @@ namespace :app do
template = ENV["LOCATION"]
raise "No LOCATION value given. Please set LOCATION either as path to a file or a URL" if template.blank?
template = File.expand_path(template) if template !~ %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://}
- require "rails/generators"
- require "rails/generators/rails/app/app_generator"
+ require_relative "../generators"
+ require_relative "../generators/rails/app/app_generator"
generator = Rails::Generators::AppGenerator.new [Rails.root], {}, destination_root: Rails.root
generator.apply template, verbose: false
end
@@ -16,7 +16,7 @@ namespace :app do
namespace :templates do
# desc "Copy all the templates from rails to the application directory for customization. Already existing local copies will be overwritten"
task :copy do
- generators_lib = File.expand_path("../../generators", __FILE__)
+ generators_lib = File.expand_path("../generators", __dir__)
project_templates = "#{Rails.root}/lib/templates"
default_templates = { "erb" => %w{controller mailer scaffold},
@@ -43,8 +43,8 @@ namespace :app do
def self.app_generator
@app_generator ||= begin
- require "rails/generators"
- require "rails/generators/rails/app/app_generator"
+ require_relative "../generators"
+ require_relative "../generators/rails/app/app_generator"
gen = Rails::Generators::AppGenerator.new ["rails"],
{ api: !!Rails.application.config.api_only, update: true },
destination_root: Rails.root
diff --git a/railties/lib/rails/tasks/statistics.rake b/railties/lib/rails/tasks/statistics.rake
index cb569be58b..0670050d34 100644
--- a/railties/lib/rails/tasks/statistics.rake
+++ b/railties/lib/rails/tasks/statistics.rake
@@ -24,6 +24,6 @@ end.select { |name, dir| File.directory?(dir) }
desc "Report code statistics (KLOCs, etc) from the application or engine"
task :stats do
- require "rails/code_statistics"
+ require_relative "../code_statistics"
CodeStatistics.new(*STATS_DIRECTORIES).to_s
end
diff --git a/railties/lib/rails/tasks/tmp.rake b/railties/lib/rails/tasks/tmp.rake
index d42a890cb6..3d8ced1bf3 100644
--- a/railties/lib/rails/tasks/tmp.rake
+++ b/railties/lib/rails/tasks/tmp.rake
@@ -1,6 +1,6 @@
namespace :tmp do
- desc "Clear cache and socket files from tmp/ (narrow w/ tmp:cache:clear, tmp:sockets:clear)"
- task clear: ["tmp:cache:clear", "tmp:sockets:clear"]
+ desc "Clear cache, socket and screenshot files from tmp/ (narrow w/ tmp:cache:clear, tmp:sockets:clear, tmp:screenshots:clear)"
+ task clear: ["tmp:cache:clear", "tmp:sockets:clear", "tmp:screenshots:clear"]
tmp_dirs = [ "tmp/cache",
"tmp/sockets",
@@ -32,4 +32,11 @@ namespace :tmp do
rm Dir["tmp/pids/[^.]*"], verbose: false
end
end
+
+ namespace :screenshots do
+ # desc "Clears all files in tmp/screenshots"
+ task :clear do
+ rm Dir["tmp/screenshots/[^.]*"], verbose: false
+ end
+ end
end
diff --git a/railties/lib/rails/templates/rails/mailers/email.html.erb b/railties/lib/rails/templates/rails/mailers/email.html.erb
index c63781ed0c..89c1129f90 100644
--- a/railties/lib/rails/templates/rails/mailers/email.html.erb
+++ b/railties/lib/rails/templates/rails/mailers/email.html.erb
@@ -98,8 +98,8 @@
<% if @email.multipart? %>
<dd>
<select onchange="formatChanged(this);">
- <option <%= request.format == Mime[:html] ? 'selected' : '' %> value="?part=text%2Fhtml">View as HTML email</option>
- <option <%= request.format == Mime[:text] ? 'selected' : '' %> value="?part=text%2Fplain">View as plain-text email</option>
+ <option <%= request.format == Mime[:html] ? 'selected' : '' %> value="?<%= part_query('text/html') %>">View as HTML email</option>
+ <option <%= request.format == Mime[:text] ? 'selected' : '' %> value="?<%= part_query('text/plain') %>">View as plain-text email</option>
</select>
</dd>
<% end %>
@@ -107,7 +107,7 @@
</header>
<% if @part && @part.mime_type %>
- <iframe seamless name="messageBody" src="?part=<%= Rack::Utils.escape(@part.mime_type) %>"></iframe>
+ <iframe seamless name="messageBody" src="?<%= part_query(@part.mime_type) %>"></iframe>
<% else %>
<p>
You are trying to preview an email that does not have any content.
diff --git a/railties/lib/rails/templates/rails/welcome/index.html.erb b/railties/lib/rails/templates/rails/welcome/index.html.erb
index 5cdb7e6a20..5a82bf913c 100644
--- a/railties/lib/rails/templates/rails/welcome/index.html.erb
+++ b/railties/lib/rails/templates/rails/welcome/index.html.erb
@@ -26,18 +26,28 @@
p { font-family: monospace; }
.container {
- width: 960px;
+ max-width: 960px;
margin: 0 auto 40px;
overflow: hidden;
}
-
section {
margin: 0 auto 2rem;
padding: 1rem 0 0;
- width: 700px;
text-align: center;
}
+
+ @media only screen and (max-width: 500px) {
+ h1 { font-size: 2rem; }
+
+ .version { font-size: 1.1rem; }
+ }
+
+ .welcome {
+ width: 600px;
+ max-width: 100%;
+ height: auto;
+ }
</style>
</head>
@@ -52,7 +62,7 @@
<h1>Yay! You&rsquo;re on Rails!</h1>
- <img alt="Welcome" width="600" height="350" src="data:image/jpg;base64,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" />
+ <img alt="Welcome" class="welcome" src="data:image/jpg;base64,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" />
<p class="version">
<strong>Rails version:</strong> <%= Rails.version %><br />
diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb
index 0f9bf98737..05048eb2e1 100644
--- a/railties/lib/rails/test_help.rb
+++ b/railties/lib/rails/test_help.rb
@@ -2,17 +2,22 @@
# so fixtures aren't loaded into that environment
abort("Abort testing: Your Rails environment is running in production mode!") if Rails.env.production?
-require "rails/test_unit/minitest_plugin"
+require_relative "test_unit/minitest_plugin"
require "active_support/test_case"
require "action_controller"
require "action_controller/test_case"
require "action_dispatch/testing/integration"
-require "rails/generators/test_case"
+require_relative "generators/test_case"
require "active_support/testing/autorun"
if defined?(ActiveRecord::Base)
- ActiveRecord::Migration.maintain_test_schema!
+ begin
+ ActiveRecord::Migration.maintain_test_schema!
+ rescue ActiveRecord::PendingMigrationError => e
+ puts e.to_s.strip
+ exit 1
+ end
module ActiveSupport
class TestCase
diff --git a/railties/lib/rails/test_unit/minitest_plugin.rb b/railties/lib/rails/test_unit/minitest_plugin.rb
index 8decdb0f4f..3571274bcd 100644
--- a/railties/lib/rails/test_unit/minitest_plugin.rb
+++ b/railties/lib/rails/test_unit/minitest_plugin.rb
@@ -1,12 +1,12 @@
require "active_support/core_ext/module/attribute_accessors"
-require "rails/test_unit/reporter"
-require "rails/test_unit/test_requirer"
+require_relative "reporter"
+require_relative "test_requirer"
require "shellwords"
module Minitest
class SuppressedSummaryReporter < SummaryReporter
# Disable extra failure output after a run if output is inline.
- def aggregated_results
+ def aggregated_results(*)
super unless options[:output_inline]
end
end
@@ -134,7 +134,7 @@ module Minitest
end
end
- mattr_reader(:run_via) { RunVia.new }
+ mattr_reader :run_via, default: 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 9cc3f73a9c..f1965f3b12 100644
--- a/railties/lib/rails/test_unit/railtie.rb
+++ b/railties/lib/rails/test_unit/railtie.rb
@@ -1,7 +1,7 @@
-require "rails/test_unit/line_filtering"
+require_relative "line_filtering"
if defined?(Rake.application) && Rake.application.top_level_tasks.grep(/^(default$|test(:|$))/).any?
- ENV["RAILS_ENV"] ||= "test"
+ ENV["RAILS_ENV"] ||= Rake.application.options.show_tasks ? "development" : "test"
end
module Rails
diff --git a/railties/lib/rails/test_unit/reporter.rb b/railties/lib/rails/test_unit/reporter.rb
index fe11664d5e..1cc27f7b6c 100644
--- a/railties/lib/rails/test_unit/reporter.rb
+++ b/railties/lib/rails/test_unit/reporter.rb
@@ -3,8 +3,7 @@ require "minitest"
module Rails
class TestUnitReporter < Minitest::StatisticsReporter
- class_attribute :executable
- self.executable = "bin/rails test"
+ class_attribute :executable, default: "bin/rails test"
def record(result)
super
diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake
index ef19bd7626..486446b463 100644
--- a/railties/lib/rails/test_unit/testing.rake
+++ b/railties/lib/rails/test_unit/testing.rake
@@ -1,6 +1,6 @@
gem "minitest"
require "minitest"
-require "rails/test_unit/minitest_plugin"
+require_relative "minitest_plugin"
task default: :test
@@ -48,6 +48,7 @@ namespace :test do
Minitest.rake_run(["test/controllers", "test/mailers", "test/functional"])
end
+ desc "Run system tests only"
task system: "test:prepare" do
$: << "test"
Minitest.rake_run(["test/system"])
diff --git a/railties/lib/rails/welcome_controller.rb b/railties/lib/rails/welcome_controller.rb
index b757dc72ef..3b417f65ba 100644
--- a/railties/lib/rails/welcome_controller.rb
+++ b/railties/lib/rails/welcome_controller.rb
@@ -1,4 +1,4 @@
-require "rails/application_controller"
+require_relative "application_controller"
class Rails::WelcomeController < Rails::ApplicationController # :nodoc:
layout false
diff --git a/railties/railties.gemspec b/railties/railties.gemspec
index 76de2b4639..e5587b531a 100644
--- a/railties/railties.gemspec
+++ b/railties/railties.gemspec
@@ -1,4 +1,4 @@
-version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
+version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
@@ -23,6 +23,11 @@ Gem::Specification.new do |s|
s.rdoc_options << "--exclude" << "."
+ s.metadata = {
+ "source_code_uri" => "https://github.com/rails/rails/tree/v#{version}/railties",
+ "changelog_uri" => "https://github.com/rails/rails/blob/v#{version}/railties/CHANGELOG.md"
+ }
+
s.add_dependency "activesupport", version
s.add_dependency "actionpack", version
diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb
index e4b2d0457d..2d4c7a0f0b 100644
--- a/railties/test/abstract_unit.rb
+++ b/railties/test/abstract_unit.rb
@@ -12,7 +12,7 @@ require "rails/all"
module TestApp
class Application < Rails::Application
- config.root = File.dirname(__FILE__)
+ config.root = __dir__
secrets.secret_key_base = "b3c631c314c0bbca50c1b2843150fe33"
end
end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 14433fbba0..983ea5c3e6 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -176,13 +176,11 @@ module ApplicationTests
test "Rails.application responds to all instance methods" do
app "development"
- assert_respond_to Rails.application, :routes_reloader
assert_equal Rails.application.routes_reloader, AppTemplate::Application.routes_reloader
end
test "Rails::Application responds to paths" do
app "development"
- assert_respond_to AppTemplate::Application, :paths
assert_equal ["#{app_path}/app/views"], AppTemplate::Application.paths["app/views"].expanded
end
@@ -693,6 +691,66 @@ module ApplicationTests
assert_match(/label/, last_response.body)
end
+ test "form_with can be configured with form_with_generates_remote_forms" do
+ app_file "config/initializers/form_builder.rb", <<-RUBY
+ Rails.configuration.action_view.form_with_generates_remote_forms = false
+ RUBY
+
+ app_file "app/models/post.rb", <<-RUBY
+ class Post
+ include ActiveModel::Model
+ attr_accessor :name
+ end
+ RUBY
+
+ app_file "app/controllers/posts_controller.rb", <<-RUBY
+ class PostsController < ApplicationController
+ def index
+ render inline: "<%= begin; form_with(model: Post.new) {|f| f.text_field(:name)}; rescue => e; e.to_s; end %>"
+ end
+ end
+ RUBY
+
+ add_to_config <<-RUBY
+ routes.prepend do
+ resources :posts
+ end
+ RUBY
+
+ app "development"
+
+ get "/posts"
+ assert_no_match(/data-remote/, last_response.body)
+ end
+
+ test "form_with generates remote forms by default" do
+ app_file "app/models/post.rb", <<-RUBY
+ class Post
+ include ActiveModel::Model
+ attr_accessor :name
+ end
+ RUBY
+
+ app_file "app/controllers/posts_controller.rb", <<-RUBY
+ class PostsController < ApplicationController
+ def index
+ render inline: "<%= begin; form_with(model: Post.new) {|f| f.text_field(:name)}; rescue => e; e.to_s; end %>"
+ end
+ end
+ RUBY
+
+ add_to_config <<-RUBY
+ routes.prepend do
+ resources :posts
+ end
+ RUBY
+
+ app "development"
+
+ get "/posts"
+ assert_match(/data-remote/, last_response.body)
+ end
+
test "default method for update can be changed" do
app_file "app/models/post.rb", <<-RUBY
class Post
@@ -1072,6 +1130,8 @@ module ApplicationTests
app "development"
+ ActionController::Base.object_id # force lazy load hooks to run
+
assert_equal :raise, ActionController::Parameters.action_on_unpermitted_parameters
post "/posts", post: { "title" => "zomg" }
@@ -1080,6 +1140,9 @@ module ApplicationTests
test "config.action_controller.always_permitted_parameters are: controller, action by default" do
app "development"
+
+ ActionController::Base.object_id # force lazy load hooks to run
+
assert_equal %w(controller action), ActionController::Parameters.always_permitted_parameters
end
@@ -1090,6 +1153,8 @@ module ApplicationTests
app "development"
+ ActionController::Base.object_id # force lazy load hooks to run
+
assert_equal %w( controller action format ), ActionController::Parameters.always_permitted_parameters
end
@@ -1112,6 +1177,8 @@ module ApplicationTests
app "development"
+ ActionController::Base.object_id # force lazy load hooks to run
+
assert_equal :raise, ActionController::Parameters.action_on_unpermitted_parameters
post "/posts", post: { "title" => "zomg" }, format: "json"
@@ -1121,21 +1188,60 @@ module ApplicationTests
test "config.action_controller.action_on_unpermitted_parameters is :log by default on development" do
app "development"
+ ActionController::Base.object_id # force lazy load hooks to run
+
assert_equal :log, ActionController::Parameters.action_on_unpermitted_parameters
end
test "config.action_controller.action_on_unpermitted_parameters is :log by default on test" do
app "test"
+ ActionController::Base.object_id # force lazy load hooks to run
+
assert_equal :log, ActionController::Parameters.action_on_unpermitted_parameters
end
test "config.action_controller.action_on_unpermitted_parameters is false by default on production" do
app "production"
+ ActionController::Base.object_id # force lazy load hooks to run
+
assert_equal false, ActionController::Parameters.action_on_unpermitted_parameters
end
+ test "config.action_controller.permit_all_parameters can be configured in an initializer" do
+ app_file "config/initializers/permit_all_parameters.rb", <<-RUBY
+ Rails.application.config.action_controller.permit_all_parameters = true
+ RUBY
+
+ app "development"
+
+ ActionController::Base.object_id # force lazy load hooks to run
+ assert_equal true, ActionController::Parameters.permit_all_parameters
+ end
+
+ test "config.action_controller.always_permitted_parameters can be configured in an initializer" do
+ app_file "config/initializers/always_permitted_parameters.rb", <<-RUBY
+ Rails.application.config.action_controller.always_permitted_parameters = []
+ RUBY
+
+ app "development"
+
+ ActionController::Base.object_id # force lazy load hooks to run
+ assert_equal [], ActionController::Parameters.always_permitted_parameters
+ end
+
+ test "config.action_controller.action_on_unpermitted_parameters can be configured in an initializer" do
+ app_file "config/initializers/action_on_unpermitted_parameters.rb", <<-RUBY
+ Rails.application.config.action_controller.action_on_unpermitted_parameters = :raise
+ RUBY
+
+ app "development"
+
+ ActionController::Base.object_id # force lazy load hooks to run
+ assert_equal :raise, ActionController::Parameters.action_on_unpermitted_parameters
+ end
+
test "config.action_dispatch.ignore_accept_header" do
make_basic_app do |application|
application.config.action_dispatch.ignore_accept_header = true
@@ -1160,7 +1266,6 @@ module ApplicationTests
test "Rails.application#env_config exists and include some existing parameters" do
make_basic_app
- assert_respond_to app, :env_config
assert_equal app.env_config["action_dispatch.parameter_filter"], app.config.filter_parameters
assert_equal app.env_config["action_dispatch.show_exceptions"], app.config.action_dispatch.show_exceptions
assert_equal app.env_config["action_dispatch.logger"], Rails.logger
@@ -1347,6 +1452,40 @@ module ApplicationTests
assert_match "config/database", err.message
end
+ test "loads database.yml using shared keys" do
+ app_file "config/database.yml", <<-YAML
+ shared:
+ username: bobby
+ adapter: sqlite3
+
+ development:
+ database: 'dev_db'
+ YAML
+
+ app "development"
+
+ ar_config = Rails.application.config.database_configuration
+ assert_equal "sqlite3", ar_config["development"]["adapter"]
+ assert_equal "bobby", ar_config["development"]["username"]
+ assert_equal "dev_db", ar_config["development"]["database"]
+ end
+
+ test "loads database.yml using shared keys for undefined environments" do
+ app_file "config/database.yml", <<-YAML
+ shared:
+ username: bobby
+ adapter: sqlite3
+ database: 'dev_db'
+ YAML
+
+ app "development"
+
+ ar_config = Rails.application.config.database_configuration
+ assert_equal "sqlite3", ar_config["development"]["adapter"]
+ assert_equal "bobby", ar_config["development"]["username"]
+ assert_equal "dev_db", ar_config["development"]["database"]
+ end
+
test "config.action_mailer.show_previews defaults to true in development" do
app "development"
diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb
index 72f340df34..057d473870 100644
--- a/railties/test/application/console_test.rb
+++ b/railties/test/application/console_test.rb
@@ -136,9 +136,9 @@ class FullStackConsoleTest < ActiveSupport::TestCase
assert_output "> "
end
- def spawn_console
+ def spawn_console(options)
Process.spawn(
- "#{app_path}/bin/rails console --sandbox",
+ "#{app_path}/bin/rails console #{options}",
in: @slave, out: @slave, err: @slave
)
@@ -146,18 +146,26 @@ class FullStackConsoleTest < ActiveSupport::TestCase
end
def test_sandbox
- spawn_console
+ spawn_console("--sandbox")
write_prompt "Post.count", "=> 0"
write_prompt "Post.create"
write_prompt "Post.count", "=> 1"
@master.puts "quit"
- spawn_console
+ spawn_console("--sandbox")
write_prompt "Post.count", "=> 0"
write_prompt "Post.transaction { Post.create; raise }"
write_prompt "Post.count", "=> 0"
@master.puts "quit"
end
+
+ def test_environment_option_and_irb_option
+ spawn_console("test -- --verbose")
+
+ write_prompt "a = 1", "a = 1"
+ write_prompt "puts Rails.env", "puts Rails.env\r\ntest"
+ @master.puts "quit"
+ end
end
diff --git a/railties/test/application/current_attributes_integration_test.rb b/railties/test/application/current_attributes_integration_test.rb
new file mode 100644
index 0000000000..5653ec0be1
--- /dev/null
+++ b/railties/test/application/current_attributes_integration_test.rb
@@ -0,0 +1,84 @@
+require "isolation/abstract_unit"
+require "rack/test"
+
+class CurrentAttributesIntegrationTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+ include Rack::Test::Methods
+
+ setup do
+ build_app
+
+ app_file "app/models/current.rb", <<-RUBY
+ class Current < ActiveSupport::CurrentAttributes
+ attribute :customer
+
+ resets { Time.zone = "UTC" }
+
+ def customer=(customer)
+ super
+ Time.zone = customer.try(:time_zone)
+ end
+ end
+ RUBY
+
+ app_file "app/models/customer.rb", <<-RUBY
+ class Customer < Struct.new(:name)
+ def time_zone
+ "Copenhagen"
+ end
+ end
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ get "/customers/:action", controller: :customers
+ end
+ RUBY
+
+ app_file "app/controllers/customers_controller.rb", <<-RUBY
+ class CustomersController < ApplicationController
+ def set_current_customer
+ Current.customer = Customer.new("david")
+ render :index
+ end
+
+ def set_no_customer
+ render :index
+ end
+ end
+ RUBY
+
+ app_file "app/views/customers/index.html.erb", <<-RUBY
+ <%= Current.customer.try(:name) || 'noone' %>,<%= Time.zone.name %>
+ RUBY
+
+ require "#{app_path}/config/environment"
+ end
+
+ teardown :teardown_app
+
+ test "current customer is assigned and cleared" do
+ get "/customers/set_current_customer"
+ assert_equal 200, last_response.status
+ assert_match(/david,Copenhagen/, last_response.body)
+
+ get "/customers/set_no_customer"
+ assert_equal 200, last_response.status
+ assert_match(/noone,UTC/, last_response.body)
+ end
+
+ test "resets after execution" do
+ assert_nil Current.customer
+ assert_equal "UTC", Time.zone.name
+
+ Rails.application.executor.wrap do
+ Current.customer = Customer.new("david")
+
+ assert_equal "david", Current.customer.name
+ assert_equal "Copenhagen", Time.zone.name
+ end
+
+ assert_nil Current.customer
+ assert_equal "UTC", Time.zone.name
+ end
+end
diff --git a/railties/test/application/initializers/i18n_test.rb b/railties/test/application/initializers/i18n_test.rb
index 206e42703b..cee198bd01 100644
--- a/railties/test/application/initializers/i18n_test.rb
+++ b/railties/test/application/initializers/i18n_test.rb
@@ -245,7 +245,7 @@ fr:
end
test "[shortcut] config.i18n.fallbacks = [{ :ca => :'es-ES' }] initializes fallbacks with a mapping ca => es-ES" do
- I18n::Railtie.config.i18n.fallbacks.map = { ca: :'es-ES' }
+ I18n::Railtie.config.i18n.fallbacks = [{ ca: :'es-ES' }]
load_app
assert_fallbacks ca: [:ca, :"es-ES", :es, :en]
end
diff --git a/railties/test/application/mailer_previews_test.rb b/railties/test/application/mailer_previews_test.rb
index c3a360e5d4..f5c013dab6 100644
--- a/railties/test/application/mailer_previews_test.rb
+++ b/railties/test/application/mailer_previews_test.rb
@@ -485,6 +485,57 @@ module ApplicationTests
assert_match '<li><a href="/my_app/rails/mailers/notifier/foo">foo</a></li>', last_response.body
end
+ test "mailer preview receives query params" do
+ mailer "notifier", <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo(name)
+ @name = name
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ html_template "notifier/foo", <<-RUBY
+ <p>Hello, <%= @name %>!</p>
+ RUBY
+
+ text_template "notifier/foo", <<-RUBY
+ Hello, <%= @name %>!
+ RUBY
+
+ mailer_preview "notifier", <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo(params[:name] || "World")
+ end
+ end
+ RUBY
+
+ app("development")
+
+ get "/rails/mailers/notifier/foo.txt"
+ assert_equal 200, last_response.status
+ assert_match '<iframe seamless name="messageBody" src="?part=text%2Fplain">', last_response.body
+ assert_match '<option selected value="?part=text%2Fplain">', last_response.body
+ assert_match '<option value="?part=text%2Fhtml">', last_response.body
+
+ get "/rails/mailers/notifier/foo?part=text%2Fplain"
+ assert_equal 200, last_response.status
+ assert_match %r[Hello, World!], last_response.body
+
+ get "/rails/mailers/notifier/foo.html?name=Ruby"
+ assert_equal 200, last_response.status
+ assert_match '<iframe seamless name="messageBody" src="?name=Ruby&amp;part=text%2Fhtml">', last_response.body
+ assert_match '<option selected value="?name=Ruby&amp;part=text%2Fhtml">', last_response.body
+ assert_match '<option value="?name=Ruby&amp;part=text%2Fplain">', last_response.body
+
+ get "/rails/mailers/notifier/foo?name=Ruby&part=text%2Fhtml"
+ assert_equal 200, last_response.status
+ assert_match %r[<p>Hello, Ruby!</p>], last_response.body
+ end
+
test "plain text mailer preview with attachment" do
image_file "pixel.png", "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEWzIioca/JlAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJgggo="
diff --git a/railties/test/application/middleware/exceptions_test.rb b/railties/test/application/middleware/exceptions_test.rb
index cbb990f13b..fe07ad3cbe 100644
--- a/railties/test/application/middleware/exceptions_test.rb
+++ b/railties/test/application/middleware/exceptions_test.rb
@@ -100,6 +100,20 @@ module ApplicationTests
end
end
+ test "routing to an nonexistent controller when action_dispatch.show_exceptions and consider_all_requests_local are set shows diagnostics" do
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ resources :articles
+ end
+ RUBY
+
+ app.config.action_dispatch.show_exceptions = true
+ app.config.consider_all_requests_local = true
+
+ get "/articles"
+ assert_match "<title>Action Controller: Exception caught</title>", last_response.body
+ end
+
test "displays diagnostics message when exception raised in template that contains UTF-8" do
controller :foo, <<-RUBY
class FooController < ActionController::Base
diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb
index 959a629ede..a14ea589ed 100644
--- a/railties/test/application/middleware/session_test.rb
+++ b/railties/test/application/middleware/session_test.rb
@@ -162,6 +162,11 @@ module ApplicationTests
end
RUBY
+ add_to_config <<-RUBY
+ # Enable AEAD cookies
+ config.action_dispatch.use_authenticated_cookie_encryption = true
+ RUBY
+
require "#{app_path}/config/environment"
get "/foo/write_session"
@@ -171,9 +176,9 @@ module ApplicationTests
get "/foo/read_encrypted_cookie"
assert_equal "1", last_response.body
- secret = app.key_generator.generate_key("encrypted cookie")
- sign_secret = app.key_generator.generate_key("signed encrypted cookie")
- encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret)
+ cipher = "aes-256-gcm"
+ secret = app.key_generator.generate_key("authenticated encrypted cookie")
+ encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher)
get "/foo/read_raw_cookie"
assert_equal 1, encryptor.decrypt_and_verify(last_response.body)["foo"]
@@ -209,6 +214,9 @@ module ApplicationTests
add_to_config <<-RUBY
secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
+
+ # Enable AEAD cookies
+ config.action_dispatch.use_authenticated_cookie_encryption = true
RUBY
require "#{app_path}/config/environment"
@@ -220,9 +228,9 @@ module ApplicationTests
get "/foo/read_encrypted_cookie"
assert_equal "1", last_response.body
- secret = app.key_generator.generate_key("encrypted cookie")
- sign_secret = app.key_generator.generate_key("signed encrypted cookie")
- encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret)
+ cipher = "aes-256-gcm"
+ secret = app.key_generator.generate_key("authenticated encrypted cookie")
+ encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher)
get "/foo/read_raw_cookie"
assert_equal 1, encryptor.decrypt_and_verify(last_response.body)["foo"]
@@ -264,6 +272,73 @@ module ApplicationTests
add_to_config <<-RUBY
secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
+
+ # Enable AEAD cookies
+ config.action_dispatch.use_authenticated_cookie_encryption = true
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ get "/foo/write_raw_session"
+ get "/foo/read_session"
+ assert_equal "1", last_response.body
+
+ get "/foo/write_session"
+ get "/foo/read_session"
+ assert_equal "2", last_response.body
+
+ get "/foo/read_encrypted_cookie"
+ assert_equal "2", last_response.body
+
+ cipher = "aes-256-gcm"
+ secret = app.key_generator.generate_key("authenticated encrypted cookie")
+ encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher)
+
+ get "/foo/read_raw_cookie"
+ assert_equal 2, encryptor.decrypt_and_verify(last_response.body)["foo"]
+ end
+
+ test "session upgrading from AES-CBC-HMAC encryption to AES-GCM encryption" do
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ get ':controller(/:action)'
+ end
+ RUBY
+
+ controller :foo, <<-RUBY
+ class FooController < ActionController::Base
+ def write_raw_session
+ # AES-256-CBC with SHA1 HMAC
+ # {"session_id"=>"1965d95720fffc123941bdfb7d2e6870", "foo"=>1}
+ cookies[:_myapp_session] = "TlgrdS85aUpDd1R2cDlPWlR6K0FJeGExckwySjZ2Z0pkR3d2QnRObGxZT25aalJWYWVvbFVLcHF4d0VQVDdSaFF2QjFPbG9MVjJzeWp3YjcyRUlKUUU2ZlR4bXlSNG9ZUkJPRUtld0E3dVU9LS0xNDZXbGpRZ3NjdW43N2haUEZJSUNRPT0=--3639b5ce54c09495cfeaae928cd5634e0c4b2e96"
+ head :ok
+ end
+
+ def write_session
+ session[:foo] = session[:foo] + 1
+ head :ok
+ end
+
+ def read_session
+ render plain: session[:foo]
+ end
+
+ def read_encrypted_cookie
+ render plain: cookies.encrypted[:_myapp_session]['foo']
+ end
+
+ def read_raw_cookie
+ render plain: cookies[:_myapp_session]
+ end
+ end
+ RUBY
+
+ add_to_config <<-RUBY
+ # Use a static key
+ secrets.secret_key_base = "known key base"
+
+ # Enable AEAD cookies
+ config.action_dispatch.use_authenticated_cookie_encryption = true
RUBY
require "#{app_path}/config/environment"
@@ -279,9 +354,9 @@ module ApplicationTests
get "/foo/read_encrypted_cookie"
assert_equal "2", last_response.body
- secret = app.key_generator.generate_key("encrypted cookie")
- sign_secret = app.key_generator.generate_key("signed encrypted cookie")
- encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret)
+ cipher = "aes-256-gcm"
+ secret = app.key_generator.generate_key("authenticated encrypted cookie")
+ encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher)
get "/foo/read_raw_cookie"
assert_equal 2, encryptor.decrypt_and_verify(last_response.body)["foo"]
diff --git a/railties/test/application/per_request_digest_cache_test.rb b/railties/test/application/per_request_digest_cache_test.rb
index 6c003e9bcc..6e6996a6ba 100644
--- a/railties/test/application/per_request_digest_cache_test.rb
+++ b/railties/test/application/per_request_digest_cache_test.rb
@@ -18,6 +18,10 @@ class PerRequestDigestCacheTest < ActiveSupport::TestCase
class Customer < Struct.new(:name, :id)
extend ActiveModel::Naming
include ActiveModel::Conversion
+
+ def cache_key
+ [ name, id ].join("/")
+ end
end
RUBY
diff --git a/railties/test/application/rake/migrations_test.rb b/railties/test/application/rake/migrations_test.rb
index 00f6620188..51dfe2ef98 100644
--- a/railties/test/application/rake/migrations_test.rb
+++ b/railties/test/application/rake/migrations_test.rb
@@ -48,8 +48,14 @@ module ApplicationTests
output = `bin/rails db:migrate:up VERSION= 2>&1`
assert_match(/VERSION is required/, output)
+ output = `bin/rails db:migrate:up 2>&1`
+ assert_match(/VERSION is required/, output)
+
output = `bin/rails db:migrate:down VERSION= 2>&1`
assert_match(/VERSION is required - To go down one migration, use db:rollback/, output)
+
+ output = `bin/rails db:migrate:down 2>&1`
+ assert_match(/VERSION is required - To go down one migration, use db:rollback/, output)
end
end
@@ -77,7 +83,7 @@ module ApplicationTests
assert_equal "Schema migrations table does not exist yet.\n", output
end
- test "test migration status" do
+ test "migration status" do
Dir.chdir(app_path) do
`bin/rails generate model user username:string password:string;
bin/rails generate migration add_email_to_users email:string;
@@ -117,7 +123,7 @@ module ApplicationTests
end
end
- test "test migration status after rollback and redo" do
+ test "migration status after rollback and redo" do
Dir.chdir(app_path) do
`bin/rails generate model user username:string password:string;
bin/rails generate migration add_email_to_users email:string;
@@ -142,6 +148,62 @@ module ApplicationTests
end
end
+ test "migration status after rollback and forward" do
+ Dir.chdir(app_path) do
+ `bin/rails generate model user username:string password:string;
+ bin/rails generate migration add_email_to_users email:string;
+ bin/rails db:migrate`
+
+ output = `bin/rails db:migrate:status`
+
+ assert_match(/up\s+\d{14}\s+Create users/, output)
+ assert_match(/up\s+\d{14}\s+Add email to users/, output)
+
+ `bin/rails db:rollback STEP=2`
+ output = `bin/rails db:migrate:status`
+
+ assert_match(/down\s+\d{14}\s+Create users/, output)
+ assert_match(/down\s+\d{14}\s+Add email to users/, output)
+
+ `bin/rails db:forward STEP=2`
+ output = `bin/rails db:migrate:status`
+
+ assert_match(/up\s+\d{14}\s+Create users/, output)
+ assert_match(/up\s+\d{14}\s+Add email to users/, output)
+ end
+ end
+
+ test "raise error on any move when current migration does not exist" do
+ Dir.chdir(app_path) do
+ `bin/rails generate model user username:string password:string;
+ bin/rails generate migration add_email_to_users email:string;
+ bin/rails db:migrate
+ rm db/migrate/*email*.rb`
+
+ output = `bin/rails db:migrate:status`
+ assert_match(/up\s+\d{14}\s+Create users/, output)
+ assert_match(/up\s+\d{14}\s+\** NO FILE \**/, output)
+
+ output = `bin/rails db:rollback 2>&1`
+ assert_match(/rails aborted!/, output)
+ assert_match(/ActiveRecord::UnknownMigrationVersionError:/, output)
+ assert_match(/No migration with version number\s\d{14}\./, output)
+
+ output = `bin/rails db:migrate:status`
+ assert_match(/up\s+\d{14}\s+Create users/, output)
+ assert_match(/up\s+\d{14}\s+\** NO FILE \**/, output)
+
+ output = `bin/rails db:forward 2>&1`
+ assert_match(/rails aborted!/, output)
+ assert_match(/ActiveRecord::UnknownMigrationVersionError:/, output)
+ assert_match(/No migration with version number\s\d{14}\./, output)
+
+ output = `bin/rails db:migrate:status`
+ assert_match(/up\s+\d{14}\s+Create users/, output)
+ assert_match(/up\s+\d{14}\s+\** NO FILE \**/, output)
+ end
+ end
+
test "migration status after rollback and redo without timestamps" do
add_to_config("config.active_record.timestamped_migrations = false")
@@ -224,7 +286,7 @@ module ApplicationTests
end
end
- test "test migration status migrated file is deleted" do
+ test "migration status migrated file is deleted" do
Dir.chdir(app_path) do
`bin/rails generate model user username:string password:string;
bin/rails generate migration add_email_to_users email:string;
@@ -232,7 +294,6 @@ module ApplicationTests
rm db/migrate/*email*.rb`
output = `bin/rails db:migrate:status`
- File.write("test.txt", output)
assert_match(/up\s+\d{14}\s+Create users/, output)
assert_match(/up\s+\d{14}\s+\** NO FILE \**/, output)
diff --git a/railties/test/application/rake/tmp_test.rb b/railties/test/application/rake/tmp_test.rb
new file mode 100644
index 0000000000..8423a98f84
--- /dev/null
+++ b/railties/test/application/rake/tmp_test.rb
@@ -0,0 +1,43 @@
+require "isolation/abstract_unit"
+
+module ApplicationTests
+ module RakeTests
+ class TmpTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def setup
+ build_app
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ test "tmp:clear clear cache, socket and screenshot files" do
+ Dir.chdir(app_path) do
+ FileUtils.mkdir_p("tmp/cache")
+ FileUtils.touch("tmp/cache/cache_file")
+
+ FileUtils.mkdir_p("tmp/sockets")
+ FileUtils.touch("tmp/sockets/socket_file")
+
+ FileUtils.mkdir_p("tmp/screenshots")
+ FileUtils.touch("tmp/screenshots/fail.png")
+
+ `rails tmp:clear`
+
+ assert_not File.exist?("tmp/cache/cache_file")
+ assert_not File.exist?("tmp/sockets/socket_file")
+ assert_not File.exist?("tmp/screenshots/fail.png")
+ end
+ end
+
+ test "tmp:clear should work if folder missing" do
+ FileUtils.remove_dir("#{app_path}/tmp")
+ errormsg = Dir.chdir(app_path) { `bin/rails tmp:clear` }
+ assert_predicate $?, :success?
+ assert_empty errormsg
+ end
+ end
+ end
+end
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index 1b64a0a1ca..5ae6ea925f 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -391,12 +391,5 @@ module ApplicationTests
assert_match(/Hello, World!/, output)
end
-
- def test_tmp_clear_should_work_if_folder_missing
- FileUtils.remove_dir("#{app_path}/tmp")
- errormsg = Dir.chdir(app_path) { `bin/rails tmp:clear` }
- assert_predicate $?, :success?
- assert_empty errormsg
- end
end
end
diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb
index a8e3a7ec5b..8e0712fca2 100644
--- a/railties/test/application/test_runner_test.rb
+++ b/railties/test/application/test_runner_test.rb
@@ -323,7 +323,7 @@ module ApplicationTests
assert true
end
- test "test line filter does not run this" do
+ test "line filter does not run this" do
assert true
end
end
@@ -469,7 +469,7 @@ module ApplicationTests
def test_run_app_without_rails_loaded
# Simulate a real Rails app boot.
app_file "config/boot.rb", <<-RUBY
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
require 'bundler/setup' # Set up gems listed in the Gemfile.
RUBY
diff --git a/railties/test/code_statistics_calculator_test.rb b/railties/test/code_statistics_calculator_test.rb
index 1bd4225f34..25a8a40d27 100644
--- a/railties/test/code_statistics_calculator_test.rb
+++ b/railties/test/code_statistics_calculator_test.rb
@@ -317,7 +317,7 @@ class Animal
private
def temp_file(name, content)
- dir = File.expand_path "../fixtures/tmp", __FILE__
+ dir = File.expand_path "fixtures/tmp", __dir__
path = "#{dir}/#{name}"
FileUtils.mkdir_p dir
diff --git a/railties/test/code_statistics_test.rb b/railties/test/code_statistics_test.rb
index 965b6eeb79..e6e3943117 100644
--- a/railties/test/code_statistics_test.rb
+++ b/railties/test/code_statistics_test.rb
@@ -3,7 +3,7 @@ require "rails/code_statistics"
class CodeStatisticsTest < ActiveSupport::TestCase
def setup
- @tmp_path = File.expand_path(File.join(File.dirname(__FILE__), "fixtures", "tmp"))
+ @tmp_path = File.expand_path("fixtures/tmp", __dir__)
@dir_js = File.join(@tmp_path, "lib.js")
FileUtils.mkdir_p(@dir_js)
end
diff --git a/railties/test/commands/secrets_test.rb b/railties/test/commands/secrets_test.rb
index 00b0343397..be610f3b47 100644
--- a/railties/test/commands/secrets_test.rb
+++ b/railties/test/commands/secrets_test.rb
@@ -18,11 +18,12 @@ class Rails::Command::SecretsCommandTest < ActiveSupport::TestCase
end
test "edit secrets" do
- run_setup_command
+ # Runs setup before first edit.
+ assert_match(/Adding config\/secrets\.yml\.key to store the encryption key/, run_edit_command)
# Run twice to ensure encrypted secrets can be reread after first edit pass.
2.times do
- assert_match(/external_api_key: 1466aac22e6a869134be3d09b9e89232fc2c2289…/, run_edit_command)
+ assert_match(/external_api_key: 1466aac22e6a869134be3d09b9e89232fc2c2289/, run_edit_command)
end
end
@@ -30,8 +31,4 @@ class Rails::Command::SecretsCommandTest < ActiveSupport::TestCase
def run_edit_command(editor: "cat")
Dir.chdir(app_path) { `EDITOR="#{editor}" bin/rails secrets:edit` }
end
-
- def run_setup_command
- Dir.chdir(app_path) { `bin/rails secrets:setup` }
- end
end
diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb
index 2d1f071969..722323efdc 100644
--- a/railties/test/commands/server_test.rb
+++ b/railties/test/commands/server_test.rb
@@ -140,6 +140,18 @@ class Rails::ServerTest < ActiveSupport::TestCase
end
def test_argument_precedence_over_environment_variable
+ switch_env "PORT", "1234" do
+ args = ["-p", "5678"]
+ options = parse_arguments(args)
+ assert_equal 5678, options[:Port]
+ end
+
+ switch_env "PORT", "1234" do
+ args = ["-p", "3000"]
+ options = parse_arguments(args)
+ assert_equal 3000, options[:Port]
+ end
+
switch_env "HOST", "1.2.3.4" do
args = ["-b", "127.0.0.1"]
options = parse_arguments(args)
@@ -153,6 +165,12 @@ class Rails::ServerTest < ActiveSupport::TestCase
server_options = parse_arguments(["--port", 3001])
assert_equal [:Port], server_options[:user_supplied_options]
+
+ server_options = parse_arguments(["-p3001", "-C", "--binding", "127.0.0.1"])
+ assert_equal [:Port, :Host, :caching], server_options[:user_supplied_options]
+
+ server_options = parse_arguments(["--port=3001"])
+ assert_equal [:Port], server_options[:user_supplied_options]
end
def test_default_options
diff --git a/railties/test/fixtures/lib/generators/usage_template/usage_template_generator.rb b/railties/test/fixtures/lib/generators/usage_template/usage_template_generator.rb
index 21b0ff6c28..701515440a 100644
--- a/railties/test/fixtures/lib/generators/usage_template/usage_template_generator.rb
+++ b/railties/test/fixtures/lib/generators/usage_template/usage_template_generator.rb
@@ -1,5 +1,5 @@
require "rails/generators"
class UsageTemplateGenerator < Rails::Generators::Base
- source_root File.expand_path("templates", File.dirname(__FILE__))
+ source_root File.expand_path("templates", __dir__)
end
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index 360e8e97d7..03b29be907 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -278,9 +278,12 @@ class ActionsTest < Rails::Generators::TestCase
end
def test_capify_should_run_the_capify_command
- assert_called_with(generator, :run, ["capify .", verbose: false]) do
- action :capify!
+ content = capture(:stderr) do
+ assert_called_with(generator, :run, ["capify .", verbose: false]) do
+ action :capify!
+ end
end
+ assert_match(/DEPRECATION WARNING: `capify!` is deprecated/, content)
end
def test_route_should_add_data_to_the_routes_block_in_config_routes
diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb
index 2edb39c8e8..a19e0f0dd8 100644
--- a/railties/test/generators/api_app_generator_test.rb
+++ b/railties/test/generators/api_app_generator_test.rb
@@ -70,7 +70,6 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase
assert_no_file "config/initializers/cookies_serializer.rb"
assert_no_file "config/initializers/assets.rb"
- assert_no_file "config/initializers/new_framework_defaults_5_1.rb"
end
def test_app_update_does_not_generate_unnecessary_bin_files
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 7965bd68d0..9364f11a98 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -157,7 +157,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
def test_new_application_doesnt_need_defaults
- assert_no_file "config/initializers/new_framework_defaults_5_1.rb"
+ assert_no_file "config/initializers/new_framework_defaults_5_2.rb"
end
def test_new_application_load_defaults
@@ -203,14 +203,14 @@ class AppGeneratorTest < Rails::Generators::TestCase
app_root = File.join(destination_root, "myapp")
run_generator [app_root]
- assert_no_file "#{app_root}/config/initializers/new_framework_defaults_5_1.rb"
+ assert_no_file "#{app_root}/config/initializers/new_framework_defaults_5_2.rb"
stub_rails_application(app_root) do
generator = Rails::Generators::AppGenerator.new ["rails"], { update: true }, destination_root: app_root, shell: @shell
generator.send(:app_const)
quietly { generator.send(:update_config_files) }
- assert_file "#{app_root}/config/initializers/new_framework_defaults_5_1.rb"
+ assert_file "#{app_root}/config/initializers/new_framework_defaults_5_2.rb"
end
end
@@ -410,14 +410,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_no_match(/config\.assets\.js_compressor = :uglifier/, content)
assert_no_match(/config\.assets\.css_compressor = :sass/, content)
end
- assert_no_file "config/initializers/new_framework_defaults_5_1.rb"
- end
-
- def test_generator_if_skip_yarn_is_given
- run_generator [destination_root, "--skip-yarn"]
-
- assert_no_file "package.json"
- assert_no_file "bin/yarn"
end
def test_generator_if_skip_action_cable_is_given
@@ -445,13 +437,24 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
def test_generator_if_skip_system_test_is_given
- run_generator [destination_root, "--skip_system_test"]
+ run_generator [destination_root, "--skip-system-test"]
assert_file "Gemfile" do |content|
assert_no_match(/capybara/, content)
assert_no_match(/selenium-webdriver/, content)
end
end
+ def test_does_not_generate_system_test_files_if_skip_system_test_is_given
+ run_generator [destination_root, "--skip-system-test"]
+
+ Dir.chdir(destination_root) do
+ quietly { `./bin/rails g scaffold User` }
+
+ assert_no_file("test/application_system_test_case.rb")
+ assert_no_file("test/system/users_test.rb")
+ end
+ end
+
def test_generator_if_api_is_given
run_generator [destination_root, "--api"]
assert_file "Gemfile" do |content|
@@ -465,7 +468,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
if defined?(JRUBY_VERSION)
assert_gem "therubyrhino"
else
- assert_file "Gemfile", /# gem 'therubyracer', platforms: :ruby/
+ assert_file "Gemfile", /# gem 'mini_racer', platforms: :ruby/
end
end
@@ -514,6 +517,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_generator_for_yarn_skipped
run_generator([destination_root, "--skip-yarn"])
assert_no_file "package.json"
+ assert_no_file "bin/yarn"
assert_file "config/initializers/assets.rb" do |content|
assert_no_match(/node_modules/, content)
@@ -559,9 +563,9 @@ class AppGeneratorTest < Rails::Generators::TestCase
run_generator
assert_file "config/environments/development.rb" do |content|
if RbConfig::CONFIG["host_os"] =~ /darwin|linux/
- assert_match(/^\s*config.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content)
+ assert_match(/^\s*config\.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content)
else
- assert_match(/^\s*# config.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content)
+ assert_match(/^\s*# config\.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content)
end
end
end
@@ -629,7 +633,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "Gemfile" do |content|
assert_match(/gem 'web-console',\s+github: 'rails\/web-console'/, content)
- assert_no_match(/\Agem 'web-console', '>= 3.3.0'\z/, content)
+ assert_no_match(/\Agem 'web-console', '>= 3\.3\.0'\z/, content)
end
end
@@ -638,7 +642,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "Gemfile" do |content|
assert_match(/gem 'web-console',\s+github: 'rails\/web-console'/, content)
- assert_no_match(/\Agem 'web-console', '>= 3.3.0'\z/, content)
+ assert_no_match(/\Agem 'web-console', '>= 3\.3\.0'\z/, content)
end
end
@@ -777,7 +781,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_psych_gem
run_generator
- gem_regex = /gem 'psych',\s+'~> 2.0',\s+platforms: :rbx/
+ gem_regex = /gem 'psych',\s+'~> 2\.0',\s+platforms: :rbx/
assert_file "Gemfile" do |content|
if defined?(Rubinius)
@@ -860,7 +864,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_gem "spring-watcher-listen"
assert_file "config/environments/development.rb" do |content|
- assert_match(/^\s*config.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content)
+ assert_match(/^\s*config\.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content)
end
end
@@ -870,7 +874,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
assert_file "config/environments/development.rb" do |content|
- assert_match(/^\s*# config.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content)
+ assert_match(/^\s*# config\.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content)
end
end
diff --git a/railties/test/generators/channel_generator_test.rb b/railties/test/generators/channel_generator_test.rb
index a1d54200ba..af68a9c49f 100644
--- a/railties/test/generators/channel_generator_test.rb
+++ b/railties/test/generators/channel_generator_test.rb
@@ -25,7 +25,7 @@ class ChannelGeneratorTest < Rails::Generators::TestCase
end
assert_file "app/assets/javascripts/channels/chat.js" do |channel|
- assert_match(/App.chat = App.cable.subscriptions.create\("ChatChannel/, channel)
+ assert_match(/App\.chat = App\.cable\.subscriptions\.create\("ChatChannel/, channel)
end
end
@@ -39,7 +39,7 @@ class ChannelGeneratorTest < Rails::Generators::TestCase
end
assert_file "app/assets/javascripts/channels/chat.js" do |channel|
- assert_match(/App.chat = App.cable.subscriptions.create\("ChatChannel/, channel)
+ assert_match(/App\.chat = App\.cable\.subscriptions\.create\("ChatChannel/, channel)
assert_match(/,\n\n speak/, channel)
assert_match(/,\n\n mute: function\(\) \{\n return this\.perform\('mute'\);\n \}\n\}\);/, channel)
end
diff --git a/railties/test/generators/create_migration_test.rb b/railties/test/generators/create_migration_test.rb
index ddd40e4d02..c7b0237f02 100644
--- a/railties/test/generators/create_migration_test.rb
+++ b/railties/test/generators/create_migration_test.rb
@@ -46,7 +46,7 @@ class CreateMigrationTest < Rails::Generators::TestCase
def test_invoke
create_migration
- assert_match(/create db\/migrate\/1_create_articles.rb\n/, invoke!)
+ assert_match(/create db\/migrate\/1_create_articles\.rb\n/, invoke!)
assert_file @migration.destination
end
@@ -67,7 +67,7 @@ class CreateMigrationTest < Rails::Generators::TestCase
migration_exists!
create_migration
- assert_match(/identical db\/migrate\/1_create_articles.rb\n/, invoke!)
+ assert_match(/identical db\/migrate\/1_create_articles\.rb\n/, invoke!)
assert @migration.identical?
end
@@ -84,8 +84,8 @@ class CreateMigrationTest < Rails::Generators::TestCase
create_migration(dest, force: true) { "different content" }
stdout = invoke!
- assert_match(/remove db\/migrate\/1_migration.rb\n/, stdout)
- assert_match(/create db\/migrate\/2_migration.rb\n/, stdout)
+ assert_match(/remove db\/migrate\/1_migration\.rb\n/, stdout)
+ assert_match(/create db\/migrate\/2_migration\.rb\n/, stdout)
assert_file @migration.destination
assert_no_file @existing_migration.destination
end
@@ -97,8 +97,8 @@ class CreateMigrationTest < Rails::Generators::TestCase
end
stdout = invoke!
- assert_match(/remove db\/migrate\/1_create_articles.rb\n/, stdout)
- assert_match(/create db\/migrate\/2_create_articles.rb\n/, stdout)
+ assert_match(/remove db\/migrate\/1_create_articles\.rb\n/, stdout)
+ assert_match(/create db\/migrate\/2_create_articles\.rb\n/, stdout)
assert_no_file @migration.destination
end
@@ -106,7 +106,7 @@ class CreateMigrationTest < Rails::Generators::TestCase
migration_exists!
create_migration(default_destination_path, {}, skip: true) { "different content" }
- assert_match(/skip db\/migrate\/2_create_articles.rb\n/, invoke!)
+ assert_match(/skip db\/migrate\/2_create_articles\.rb\n/, invoke!)
assert_no_file @migration.destination
end
@@ -114,7 +114,7 @@ class CreateMigrationTest < Rails::Generators::TestCase
migration_exists!
create_migration
- assert_match(/remove db\/migrate\/1_create_articles.rb\n/, revoke!)
+ assert_match(/remove db\/migrate\/1_create_articles\.rb\n/, revoke!)
assert_no_file @existing_migration.destination
end
@@ -122,13 +122,13 @@ class CreateMigrationTest < Rails::Generators::TestCase
migration_exists!
create_migration(default_destination_path, {}, pretend: true)
- assert_match(/remove db\/migrate\/1_create_articles.rb\n/, revoke!)
+ assert_match(/remove db\/migrate\/1_create_articles\.rb\n/, revoke!)
assert_file @existing_migration.destination
end
def test_revoke_when_no_exists
create_migration
- assert_match(/remove db\/migrate\/1_create_articles.rb\n/, revoke!)
+ assert_match(/remove db\/migrate\/1_create_articles\.rb\n/, revoke!)
end
end
diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb
index 2cdddc8713..5fb331e197 100644
--- a/railties/test/generators/generators_test_helper.rb
+++ b/railties/test/generators/generators_test_helper.rb
@@ -9,7 +9,7 @@ module Rails
class << self
remove_possible_method :root
def root
- @root ||= Pathname.new(File.expand_path("../../fixtures", __FILE__))
+ @root ||= Pathname.new(File.expand_path("../fixtures", __dir__))
end
end
end
@@ -41,7 +41,7 @@ module GeneratorsTestHelper
end
def copy_routes
- routes = File.expand_path("../../../lib/rails/generators/rails/app/templates/config/routes.rb", __FILE__)
+ routes = File.expand_path("../../lib/rails/generators/rails/app/templates/config/routes.rb", __dir__)
destination = File.join(destination_root, "config")
FileUtils.mkdir_p(destination)
FileUtils.cp routes, destination
diff --git a/railties/test/generators/mailer_generator_test.rb b/railties/test/generators/mailer_generator_test.rb
index 7d69d7470d..2ff03ea65e 100644
--- a/railties/test/generators/mailer_generator_test.rb
+++ b/railties/test/generators/mailer_generator_test.rb
@@ -9,13 +9,13 @@ class MailerGeneratorTest < Rails::Generators::TestCase
run_generator
assert_file "app/mailers/notifier_mailer.rb" do |mailer|
assert_match(/class NotifierMailer < ApplicationMailer/, mailer)
- assert_no_match(/default from: "from@example.com"/, mailer)
+ assert_no_match(/default from: "from@example\.com"/, mailer)
assert_no_match(/layout :mailer_notifier/, mailer)
end
assert_file "app/mailers/application_mailer.rb" do |mailer|
assert_match(/class ApplicationMailer < ActionMailer::Base/, mailer)
- assert_match(/default from: 'from@example.com'/, mailer)
+ assert_match(/default from: 'from@example\.com'/, mailer)
assert_match(/layout 'mailer'/, mailer)
end
end
@@ -48,11 +48,11 @@ class MailerGeneratorTest < Rails::Generators::TestCase
assert_match(/class NotifierMailerPreview < ActionMailer::Preview/, preview)
assert_match(/\# Preview this email at http:\/\/localhost\:3000\/rails\/mailers\/notifier_mailer\/foo/, preview)
assert_instance_method :foo, preview do |foo|
- assert_match(/NotifierMailer.foo/, foo)
+ assert_match(/NotifierMailer\.foo/, foo)
end
assert_match(/\# Preview this email at http:\/\/localhost\:3000\/rails\/mailers\/notifier_mailer\/bar/, preview)
assert_instance_method :bar, preview do |bar|
- assert_match(/NotifierMailer.bar/, bar)
+ assert_match(/NotifierMailer\.bar/, bar)
end
end
end
@@ -137,12 +137,12 @@ class MailerGeneratorTest < Rails::Generators::TestCase
assert_file "app/mailers/notifier_mailer.rb" do |mailer|
assert_instance_method :foo, mailer do |foo|
- assert_match(/mail to: "to@example.org"/, foo)
+ assert_match(/mail to: "to@example\.org"/, foo)
assert_match(/@greeting = "Hi"/, foo)
end
assert_instance_method :bar, mailer do |bar|
- assert_match(/mail to: "to@example.org"/, bar)
+ assert_match(/mail to: "to@example\.org"/, bar)
assert_match(/@greeting = "Hi"/, bar)
end
end
diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb
index f46278cefe..6fe6e4ca07 100644
--- a/railties/test/generators/migration_generator_test.rb
+++ b/railties/test/generators/migration_generator_test.rb
@@ -204,8 +204,8 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
assert_migration "db/migrate/#{migration}.rb" do |content|
assert_method :change, content do |change|
assert_match(/create_join_table :artists, :musics/, change)
- assert_match(/# t.index \[:artist_id, :music_id\]/, change)
- assert_match(/ t.index \[:music_id, :artist_id\], unique: true/, change)
+ assert_match(/# t\.index \[:artist_id, :music_id\]/, change)
+ assert_match(/ t\.index \[:music_id, :artist_id\], unique: true/, change)
end
end
end
@@ -265,8 +265,8 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
assert_migration "db/migrate/#{migration}.rb" do |content|
assert_method :change, content do |change|
assert_match(/create_join_table :artist, :music/, change)
- assert_match(/# t.index \[:artist_id, :music_id\]/, change)
- assert_match(/ t.index \[:music_id, :artist_id\], unique: true/, change)
+ assert_match(/# t\.index \[:artist_id, :music_id\]/, change)
+ assert_match(/ t\.index \[:music_id, :artist_id\], unique: true/, change)
end
end
end
diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb
index 99490af3a9..f41969fc46 100644
--- a/railties/test/generators/model_generator_test.rb
+++ b/railties/test/generators/model_generator_test.rb
@@ -10,7 +10,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
run_generator
assert_file "app/models/application_record.rb" do |record|
assert_match(/class ApplicationRecord < ActiveRecord::Base/, record)
- assert_match(/self.abstract_class = true/, record)
+ assert_match(/self\.abstract_class = true/, record)
end
end
@@ -253,7 +253,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
def test_migration_with_timestamps
run_generator
- assert_migration "db/migrate/create_accounts.rb", /t.timestamps/
+ assert_migration "db/migrate/create_accounts.rb", /t\.timestamps/
end
def test_migration_timestamps_are_skipped
@@ -261,7 +261,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
assert_migration "db/migrate/create_accounts.rb" do |m|
assert_method :change, m do |up|
- assert_no_match(/t.timestamps/, up)
+ assert_no_match(/t\.timestamps/, up)
end
end
end
@@ -269,19 +269,19 @@ class ModelGeneratorTest < Rails::Generators::TestCase
def test_migration_is_skipped_with_skip_option
run_generator
output = run_generator ["Account", "--skip"]
- assert_match %r{skip\s+db/migrate/\d+_create_accounts.rb}, output
+ assert_match %r{skip\s+db/migrate/\d+_create_accounts\.rb}, output
end
def test_migration_is_ignored_as_identical_with_skip_option
run_generator ["Account"]
output = run_generator ["Account", "--skip"]
- assert_match %r{identical\s+db/migrate/\d+_create_accounts.rb}, output
+ assert_match %r{identical\s+db/migrate/\d+_create_accounts\.rb}, output
end
def test_migration_is_skipped_on_skip_behavior
run_generator
output = run_generator ["Account"], behavior: :skip
- assert_match %r{skip\s+db/migrate/\d+_create_accounts.rb}, output
+ assert_match %r{skip\s+db/migrate/\d+_create_accounts\.rb}, output
end
def test_migration_error_is_not_shown_on_revoke
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index afb37b6a99..f8512f9157 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -48,7 +48,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
run_generator [File.join(destination_root, "hyphenated-name")]
assert_no_file "hyphenated-name/lib/hyphenated-name.rb"
assert_no_file "hyphenated-name/lib/hyphenated_name.rb"
- assert_file "hyphenated-name/lib/hyphenated/name.rb", /module Hyphenated\n module Name\n # Your code goes here...\n end\nend/
+ assert_file "hyphenated-name/lib/hyphenated/name.rb", /module Hyphenated\n module Name\n # Your code goes here\.\.\.\n end\nend/
end
def test_correct_file_in_lib_folder_of_camelcase_plugin_name
@@ -63,11 +63,13 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_no_file "config/routes.rb"
assert_no_file "app/assets/config/bukkits_manifest.js"
assert_file "test/test_helper.rb" do |content|
- assert_match(/require.+test\/dummy\/config\/environment/, content)
+ assert_match(/require_relative.+test\/dummy\/config\/environment/, content)
assert_match(/ActiveRecord::Migrator\.migrations_paths.+test\/dummy\/db\/migrate/, content)
assert_match(/Minitest\.backtrace_filter = Minitest::BacktraceFilter\.new/, content)
assert_match(/Rails::TestUnitReporter\.executable = 'bin\/test'/, content)
end
+ assert_file "lib/bukkits/railtie.rb", /module Bukkits\n class Railtie < ::Rails::Railtie\n end\nend/
+ assert_file "lib/bukkits.rb", /require "bukkits\/railtie"/
assert_file "test/bukkits_test.rb", /assert_kind_of Module, Bukkits/
assert_file "bin/test"
assert_no_file "bin/rails"
@@ -152,7 +154,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
run_generator [destination_root, "--skip-active-record"]
assert_file "bukkits.gemspec" do |contents|
- assert_no_match(/s.add_development_dependency "sqlite3"/, contents)
+ assert_no_match(/s\.add_development_dependency "sqlite3"/, contents)
end
end
@@ -294,7 +296,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "hyphenated-name/config/routes.rb", /Rails.application.routes.draw do/
assert_file "hyphenated-name/lib/hyphenated/name/engine.rb", /module Hyphenated\n module Name\n class Engine < ::Rails::Engine\n end\n end\nend/
assert_file "hyphenated-name/lib/hyphenated/name.rb", /require "hyphenated\/name\/engine"/
- assert_file "hyphenated-name/bin/rails", /\.\.\/\.\.\/lib\/hyphenated\/name\/engine/
+ assert_file "hyphenated-name/bin/rails", /\.\.\/lib\/hyphenated\/name\/engine/
end
def test_creating_engine_with_hyphenated_and_underscored_name_in_full_mode
@@ -308,14 +310,14 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "my_hyphenated-name/app/helpers"
assert_file "my_hyphenated-name/app/mailers"
assert_file "my_hyphenated-name/bin/rails"
- assert_file "my_hyphenated-name/config/routes.rb", /Rails.application.routes.draw do/
+ assert_file "my_hyphenated-name/config/routes.rb", /Rails\.application\.routes\.draw do/
assert_file "my_hyphenated-name/lib/my_hyphenated/name/engine.rb", /module MyHyphenated\n module Name\n class Engine < ::Rails::Engine\n end\n end\nend/
assert_file "my_hyphenated-name/lib/my_hyphenated/name.rb", /require "my_hyphenated\/name\/engine"/
- assert_file "my_hyphenated-name/bin/rails", /\.\.\/\.\.\/lib\/my_hyphenated\/name\/engine/
+ assert_file "my_hyphenated-name/bin/rails", /\.\.\/lib\/my_hyphenated\/name\/engine/
end
def test_being_quiet_while_creating_dummy_application
- assert_no_match(/create\s+config\/application.rb/, run_generator)
+ assert_no_match(/create\s+config\/application\.rb/, run_generator)
end
def test_create_mountable_application_with_mountable_option
@@ -323,13 +325,13 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "app/assets/javascripts/bukkits"
assert_file "app/assets/stylesheets/bukkits"
assert_file "app/assets/images/bukkits"
- assert_file "config/routes.rb", /Bukkits::Engine.routes.draw do/
+ assert_file "config/routes.rb", /Bukkits::Engine\.routes\.draw do/
assert_file "lib/bukkits/engine.rb", /isolate_namespace Bukkits/
assert_file "test/dummy/config/routes.rb", /mount Bukkits::Engine => "\/bukkits"/
assert_file "app/controllers/bukkits/application_controller.rb", /module Bukkits\n class ApplicationController < ActionController::Base/
assert_file "app/models/bukkits/application_record.rb", /module Bukkits\n class ApplicationRecord < ActiveRecord::Base/
assert_file "app/jobs/bukkits/application_job.rb", /module Bukkits\n class ApplicationJob < ActiveJob::Base/
- assert_file "app/mailers/bukkits/application_mailer.rb", /module Bukkits\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example.com'\n layout 'mailer'\n/
+ assert_file "app/mailers/bukkits/application_mailer.rb", /module Bukkits\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example\.com'\n layout 'mailer'\n/
assert_file "app/helpers/bukkits/application_helper.rb", /module Bukkits\n module ApplicationHelper/
assert_file "app/views/layouts/bukkits/application.html.erb" do |contents|
assert_match "<title>Bukkits</title>", contents
@@ -350,15 +352,15 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "hyphenated-name/app/assets/javascripts/hyphenated/name"
assert_file "hyphenated-name/app/assets/stylesheets/hyphenated/name"
assert_file "hyphenated-name/app/assets/images/hyphenated/name"
- assert_file "hyphenated-name/config/routes.rb", /Hyphenated::Name::Engine.routes.draw do/
- assert_file "hyphenated-name/lib/hyphenated/name/version.rb", /module Hyphenated\n module Name\n VERSION = '0.1.0'\n end\nend/
+ assert_file "hyphenated-name/config/routes.rb", /Hyphenated::Name::Engine\.routes\.draw do/
+ assert_file "hyphenated-name/lib/hyphenated/name/version.rb", /module Hyphenated\n module Name\n VERSION = '0\.1\.0'\n end\nend/
assert_file "hyphenated-name/lib/hyphenated/name/engine.rb", /module Hyphenated\n module Name\n class Engine < ::Rails::Engine\n isolate_namespace Hyphenated::Name\n end\n end\nend/
assert_file "hyphenated-name/lib/hyphenated/name.rb", /require "hyphenated\/name\/engine"/
assert_file "hyphenated-name/test/dummy/config/routes.rb", /mount Hyphenated::Name::Engine => "\/hyphenated-name"/
assert_file "hyphenated-name/app/controllers/hyphenated/name/application_controller.rb", /module Hyphenated\n module Name\n class ApplicationController < ActionController::Base\n protect_from_forgery with: :exception\n end\n end\nend\n/
assert_file "hyphenated-name/app/models/hyphenated/name/application_record.rb", /module Hyphenated\n module Name\n class ApplicationRecord < ActiveRecord::Base\n self\.abstract_class = true\n end\n end\nend/
assert_file "hyphenated-name/app/jobs/hyphenated/name/application_job.rb", /module Hyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/
- assert_file "hyphenated-name/app/mailers/hyphenated/name/application_mailer.rb", /module Hyphenated\n module Name\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example.com'\n layout 'mailer'\n end\n end\nend/
+ assert_file "hyphenated-name/app/mailers/hyphenated/name/application_mailer.rb", /module Hyphenated\n module Name\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example\.com'\n layout 'mailer'\n end\n end\nend/
assert_file "hyphenated-name/app/helpers/hyphenated/name/application_helper.rb", /module Hyphenated\n module Name\n module ApplicationHelper\n end\n end\nend/
assert_file "hyphenated-name/app/views/layouts/hyphenated/name/application.html.erb" do |contents|
assert_match "<title>Hyphenated name</title>", contents
@@ -372,15 +374,15 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "my_hyphenated-name/app/assets/javascripts/my_hyphenated/name"
assert_file "my_hyphenated-name/app/assets/stylesheets/my_hyphenated/name"
assert_file "my_hyphenated-name/app/assets/images/my_hyphenated/name"
- assert_file "my_hyphenated-name/config/routes.rb", /MyHyphenated::Name::Engine.routes.draw do/
- assert_file "my_hyphenated-name/lib/my_hyphenated/name/version.rb", /module MyHyphenated\n module Name\n VERSION = '0.1.0'\n end\nend/
+ assert_file "my_hyphenated-name/config/routes.rb", /MyHyphenated::Name::Engine\.routes\.draw do/
+ assert_file "my_hyphenated-name/lib/my_hyphenated/name/version.rb", /module MyHyphenated\n module Name\n VERSION = '0\.1\.0'\n end\nend/
assert_file "my_hyphenated-name/lib/my_hyphenated/name/engine.rb", /module MyHyphenated\n module Name\n class Engine < ::Rails::Engine\n isolate_namespace MyHyphenated::Name\n end\n end\nend/
assert_file "my_hyphenated-name/lib/my_hyphenated/name.rb", /require "my_hyphenated\/name\/engine"/
assert_file "my_hyphenated-name/test/dummy/config/routes.rb", /mount MyHyphenated::Name::Engine => "\/my_hyphenated-name"/
assert_file "my_hyphenated-name/app/controllers/my_hyphenated/name/application_controller.rb", /module MyHyphenated\n module Name\n class ApplicationController < ActionController::Base\n protect_from_forgery with: :exception\n end\n end\nend\n/
assert_file "my_hyphenated-name/app/models/my_hyphenated/name/application_record.rb", /module MyHyphenated\n module Name\n class ApplicationRecord < ActiveRecord::Base\n self\.abstract_class = true\n end\n end\nend/
assert_file "my_hyphenated-name/app/jobs/my_hyphenated/name/application_job.rb", /module MyHyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/
- assert_file "my_hyphenated-name/app/mailers/my_hyphenated/name/application_mailer.rb", /module MyHyphenated\n module Name\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example.com'\n layout 'mailer'\n end\n end\nend/
+ assert_file "my_hyphenated-name/app/mailers/my_hyphenated/name/application_mailer.rb", /module MyHyphenated\n module Name\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example\.com'\n layout 'mailer'\n end\n end\nend/
assert_file "my_hyphenated-name/app/helpers/my_hyphenated/name/application_helper.rb", /module MyHyphenated\n module Name\n module ApplicationHelper\n end\n end\nend/
assert_file "my_hyphenated-name/app/views/layouts/my_hyphenated/name/application.html.erb" do |contents|
assert_match "<title>My hyphenated name</title>", contents
@@ -394,15 +396,15 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "deep-hyphenated-name/app/assets/javascripts/deep/hyphenated/name"
assert_file "deep-hyphenated-name/app/assets/stylesheets/deep/hyphenated/name"
assert_file "deep-hyphenated-name/app/assets/images/deep/hyphenated/name"
- assert_file "deep-hyphenated-name/config/routes.rb", /Deep::Hyphenated::Name::Engine.routes.draw do/
- assert_file "deep-hyphenated-name/lib/deep/hyphenated/name/version.rb", /module Deep\n module Hyphenated\n module Name\n VERSION = '0.1.0'\n end\n end\nend/
+ assert_file "deep-hyphenated-name/config/routes.rb", /Deep::Hyphenated::Name::Engine\.routes\.draw do/
+ assert_file "deep-hyphenated-name/lib/deep/hyphenated/name/version.rb", /module Deep\n module Hyphenated\n module Name\n VERSION = '0\.1\.0'\n end\n end\nend/
assert_file "deep-hyphenated-name/lib/deep/hyphenated/name/engine.rb", /module Deep\n module Hyphenated\n module Name\n class Engine < ::Rails::Engine\n isolate_namespace Deep::Hyphenated::Name\n end\n end\n end\nend/
assert_file "deep-hyphenated-name/lib/deep/hyphenated/name.rb", /require "deep\/hyphenated\/name\/engine"/
assert_file "deep-hyphenated-name/test/dummy/config/routes.rb", /mount Deep::Hyphenated::Name::Engine => "\/deep-hyphenated-name"/
assert_file "deep-hyphenated-name/app/controllers/deep/hyphenated/name/application_controller.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationController < ActionController::Base\n protect_from_forgery with: :exception\n end\n end\n end\nend\n/
assert_file "deep-hyphenated-name/app/models/deep/hyphenated/name/application_record.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationRecord < ActiveRecord::Base\n self\.abstract_class = true\n end\n end\n end\nend/
assert_file "deep-hyphenated-name/app/jobs/deep/hyphenated/name/application_job.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/
- assert_file "deep-hyphenated-name/app/mailers/deep/hyphenated/name/application_mailer.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example.com'\n layout 'mailer'\n end\n end\n end\nend/
+ assert_file "deep-hyphenated-name/app/mailers/deep/hyphenated/name/application_mailer.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example\.com'\n layout 'mailer'\n end\n end\n end\nend/
assert_file "deep-hyphenated-name/app/helpers/deep/hyphenated/name/application_helper.rb", /module Deep\n module Hyphenated\n module Name\n module ApplicationHelper\n end\n end\n end\nend/
assert_file "deep-hyphenated-name/app/views/layouts/deep/hyphenated/name/application.html.erb" do |contents|
assert_match "<title>Deep hyphenated name</title>", contents
@@ -413,16 +415,16 @@ class PluginGeneratorTest < Rails::Generators::TestCase
def test_creating_gemspec
run_generator
- assert_file "bukkits.gemspec", /s.name\s+= "bukkits"/
- assert_file "bukkits.gemspec", /s.files = Dir\["\{app,config,db,lib\}\/\*\*\/\*", "MIT-LICENSE", "Rakefile", "README\.md"\]/
- assert_file "bukkits.gemspec", /s.version\s+ = Bukkits::VERSION/
+ assert_file "bukkits.gemspec", /s\.name\s+= "bukkits"/
+ assert_file "bukkits.gemspec", /s\.files = Dir\["\{app,config,db,lib\}\/\*\*\/\*", "MIT-LICENSE", "Rakefile", "README\.md"\]/
+ assert_file "bukkits.gemspec", /s\.version\s+ = Bukkits::VERSION/
end
def test_usage_of_engine_commands
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", /ENGINE_PATH = File\.expand_path\('\.\.\/lib\/bukkits\/engine', __dir__\)/
+ assert_file "bin/rails", /ENGINE_ROOT = File\.expand_path\('\.\.', __dir__\)/
+ assert_file "bin/rails", %r|APP_PATH = File\.expand_path\('\.\./test/dummy/config/application', __dir__\)|
assert_file "bin/rails", /require 'rails\/all'/
assert_file "bin/rails", /require 'rails\/engine\/commands'/
end
@@ -438,7 +440,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "spec/dummy/config/application.rb"
assert_no_file "test/dummy"
assert_file "test/test_helper.rb" do |content|
- assert_match(/require.+spec\/dummy\/config\/environment/, content)
+ assert_match(/require_relative.+spec\/dummy\/config\/environment/, content)
assert_match(/ActiveRecord::Migrator\.migrations_paths.+spec\/dummy\/db\/migrate/, content)
end
end
@@ -449,7 +451,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "spec/fake/config/application.rb"
assert_no_file "test/dummy"
assert_file "test/test_helper.rb" do |content|
- assert_match(/require.+spec\/fake\/config\/environment/, content)
+ assert_match(/require_relative.+spec\/fake\/config\/environment/, content)
assert_match(/ActiveRecord::Migrator\.migrations_paths.+spec\/fake\/db\/migrate/, content)
end
end
@@ -469,7 +471,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
run_generator
assert_file "test/dummy/config/environments/development.rb" do |contents|
- assert_match(/^\s*# config.file_watcher = ActiveSupport::EventedFileUpdateChecker/, contents)
+ assert_match(/^\s*# config\.file_watcher = ActiveSupport::EventedFileUpdateChecker/, contents)
end
end
@@ -687,7 +689,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "#{destination_root}/app/models/bukkits/application_record.rb" do |record|
assert_match(/module Bukkits/, record)
assert_match(/class ApplicationRecord < ActiveRecord::Base/, record)
- assert_match(/self.abstract_class = true/, record)
+ assert_match(/self\.abstract_class = true/, record)
end
end
@@ -741,7 +743,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
quietly { Rails::Engine::Updater.run(:create_bin_files) }
assert_file "#{destination_root}/bin/rails" do |content|
- assert_match(%r|APP_PATH = File\.expand_path\('\.\./\.\./test/dummy/config/application', __FILE__\)|, content)
+ assert_match(%r|APP_PATH = File\.expand_path\('\.\./test/dummy/config/application', __dir__\)|, content)
end
ensure
Object.send(:remove_const, "ENGINE_ROOT")
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index bc76cead18..9926d58d16 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -65,6 +65,9 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
# System tests
assert_file "test/system/product_lines_test.rb" do |test|
assert_match(/class ProductLinesTest < ApplicationSystemTestCase/, test)
+ assert_match(/visit product_lines_url/, test)
+ assert_match(/fill_in "Title", with: @product_line\.title/, test)
+ assert_match(/assert_text "Product line was successfully updated"/, test)
end
# Views
@@ -146,6 +149,9 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_no_match(/assert_redirected_to/, test)
end
+ # System tests
+ assert_no_file "test/system/product_lines_test.rb"
+
# Views
assert_no_file "app/views/layouts/product_lines.html.erb"
@@ -173,6 +179,16 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_system_tests_without_attributes
+ run_generator ["product_line"]
+
+ assert_file "test/system/product_lines_test.rb" do |content|
+ assert_match(/class ProductLinesTest < ApplicationSystemTestCase/, content)
+ assert_match(/test "visiting the index"/, content)
+ assert_no_match(/fill_in/, content)
+ end
+ end
+
def test_scaffold_on_revoke
run_generator
run_generator ["product_line"], behavior: :revoke
@@ -192,6 +208,9 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_no_file "app/controllers/product_lines_controller.rb"
assert_no_file "test/controllers/product_lines_controller_test.rb"
+ # System tests
+ assert_no_file "test/system/product_lines_test.rb"
+
# Views
assert_no_file "app/views/product_lines"
assert_no_file "app/views/layouts/product_lines.html.erb"
@@ -257,6 +276,9 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_file "test/controllers/admin/roles_controller_test.rb",
/class Admin::RolesControllerTest < ActionDispatch::IntegrationTest/
+ assert_file "test/system/admin/roles_test.rb",
+ /class Admin::RolesTest < ApplicationSystemTestCase/
+
# Views
%w(index edit new show _form).each do |view|
assert_file "app/views/admin/roles/#{view}.html.erb"
@@ -292,6 +314,9 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_no_file "app/controllers/admin/roles_controller.rb"
assert_no_file "test/controllers/admin/roles_controller_test.rb"
+ # System tests
+ assert_no_file "test/system/admin/roles_test.rb"
+
# Views
assert_no_file "app/views/admin/roles"
assert_no_file "app/views/layouts/admin/roles.html.erb"
@@ -478,6 +503,11 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_match(/password_confirmation: 'secret'/, content)
end
+ assert_file "test/system/users_test.rb" do |content|
+ assert_match(/fill_in "Password", with: 'secret'/, content)
+ assert_match(/fill_in "Password Confirmation", with: 'secret'/, content)
+ end
+
assert_file "test/fixtures/users.yml" do |content|
assert_match(/password_digest: <%= BCrypt::Password.create\('secret'\) %>/, content)
end
@@ -573,6 +603,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert File.exist?("app/controllers/bukkits/users_controller.rb")
assert File.exist?("test/controllers/bukkits/users_controller_test.rb")
+ assert File.exist?("test/system/bukkits/users_test.rb")
+
assert File.exist?("app/views/bukkits/users/index.html.erb")
assert File.exist?("app/views/bukkits/users/edit.html.erb")
assert File.exist?("app/views/bukkits/users/show.html.erb")
@@ -601,6 +633,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_not File.exist?("app/controllers/bukkits/users_controller.rb")
assert_not File.exist?("test/controllers/bukkits/users_controller_test.rb")
+ assert_not File.exist?("test/system/bukkits/users_test.rb")
+
assert_not File.exist?("app/views/bukkits/users/index.html.erb")
assert_not File.exist?("app/views/bukkits/users/edit.html.erb")
assert_not File.exist?("app/views/bukkits/users/show.html.erb")
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index cc9d3629e9..5e75879964 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -45,14 +45,14 @@ module SharedGeneratorTests
reserved_words = %w[application destroy plugin runner test]
reserved_words.each do |reserved|
content = capture(:stderr) { run_generator [File.join(destination_root, reserved)] }
- assert_match(/Invalid \w+ name #{reserved}. Please give a name which does not match one of the reserved rails words: application, destroy, plugin, runner, test\n/, content)
+ assert_match(/Invalid \w+ name #{reserved}\. Please give a name which does not match one of the reserved rails words: application, destroy, plugin, runner, test\n/, content)
end
end
def test_name_raises_an_error_if_name_already_used_constant
%w{ String Hash Class Module Set Symbol }.each do |ruby_class|
content = capture(:stderr) { run_generator [File.join(destination_root, ruby_class)] }
- assert_match(/Invalid \w+ name #{ruby_class}, constant #{ruby_class} is already in use. Please choose another \w+ name.\n/, content)
+ assert_match(/Invalid \w+ name #{ruby_class}, constant #{ruby_class} is already in use\. Please choose another \w+ name\.\n/, content)
end
end
diff --git a/railties/test/generators/system_test_generator_test.rb b/railties/test/generators/system_test_generator_test.rb
index e8e561ec49..4622360244 100644
--- a/railties/test/generators/system_test_generator_test.rb
+++ b/railties/test/generators/system_test_generator_test.rb
@@ -9,4 +9,9 @@ class SystemTestGeneratorTest < Rails::Generators::TestCase
run_generator
assert_file "test/system/users_test.rb", /class UsersTest < ApplicationSystemTestCase/
end
+
+ def test_namespaced_system_test_skeleton_is_created
+ run_generator %w(admin/user)
+ assert_file "test/system/admin/users_test.rb", /class Admin::UsersTest < ApplicationSystemTestCase/
+ end
end
diff --git a/railties/test/generators/test_runner_in_engine_test.rb b/railties/test/generators/test_runner_in_engine_test.rb
index 4b5fb3ba3f..680dc2608e 100644
--- a/railties/test/generators/test_runner_in_engine_test.rb
+++ b/railties/test/generators/test_runner_in_engine_test.rb
@@ -17,7 +17,7 @@ class TestRunnerInEngineTest < ActiveSupport::TestCase
create_test_file "post", pass: false
output = run_test_command("test/post_test.rb")
- expect = %r{Running:\n\nPostTest\nF\n\nFailure:\nPostTest#test_truth \[[^\]]+test/post_test.rb:6\]:\nwups!\n\nbin/rails test test/post_test.rb:4}
+ expect = %r{Running:\n\nPostTest\nF\n\nFailure:\nPostTest#test_truth \[[^\]]+test/post_test\.rb:6\]:\nwups!\n\nbin/rails test test/post_test\.rb:4}
assert_match expect, output
end
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index c3c16b6f86..e07627f36d 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -233,7 +233,7 @@ class GeneratorsTest < Rails::Generators::TestCase
end
def test_usage_with_embedded_ruby
- require File.expand_path("fixtures/lib/generators/usage_template/usage_template_generator", File.dirname(__FILE__))
+ require_relative "fixtures/lib/generators/usage_template/usage_template_generator"
output = capture(:stdout) { Rails::Generators.invoke :usage_template, ["--help"] }
assert_match(/:: 2 ::/, output)
end
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 924503a522..7496b5f84a 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -14,7 +14,7 @@ require "active_support/testing/autorun"
require "active_support/testing/stream"
require "active_support/test_case"
-RAILS_FRAMEWORK_ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../..")
+RAILS_FRAMEWORK_ROOT = File.expand_path("../../..", __dir__)
# These files do not require any others and are needed
# to run the tests
@@ -119,7 +119,7 @@ module TestHelpers
end
routes = File.read("#{app_path}/config/routes.rb")
- if routes =~ /(\n\s*end\s*)\Z/
+ if routes =~ /(\n\s*end\s*)\z/
File.open("#{app_path}/config/routes.rb", "w") do |f|
f.puts $` + "\nActiveSupport::Deprecation.silence { match ':controller(/:action(/:id))(.:format)', via: :all }\n" + $1
end
@@ -251,7 +251,7 @@ module TestHelpers
def add_to_config(str)
environment = File.read("#{app_path}/config/application.rb")
- if environment =~ /(\n\s*end\s*end\s*)\Z/
+ if environment =~ /(\n\s*end\s*end\s*)\z/
File.open("#{app_path}/config/application.rb", "w") do |f|
f.puts $` + "\n#{str}\n" + $1
end
@@ -260,7 +260,7 @@ module TestHelpers
def add_to_env_config(env, str)
environment = File.read("#{app_path}/config/environments/#{env}.rb")
- if environment =~ /(\n\s*end\s*)\Z/
+ if environment =~ /(\n\s*end\s*)\z/
File.open("#{app_path}/config/environments/#{env}.rb", "w") do |f|
f.puts $` + "\n#{str}\n" + $1
end
diff --git a/railties/test/rails_info_test.rb b/railties/test/rails_info_test.rb
index 9f4c5bb025..383adcc55d 100644
--- a/railties/test/rails_info_test.rb
+++ b/railties/test/rails_info_test.rb
@@ -39,7 +39,7 @@ class InfoTest < ActiveSupport::TestCase
def test_rails_version
assert_property "Rails version",
- File.read(File.realpath("../../../RAILS_VERSION", __FILE__)).chomp
+ File.read(File.realpath("../../RAILS_VERSION", __dir__)).chomp
end
def test_html_includes_middleware
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 52d691b73b..e382a7a873 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -89,16 +89,16 @@ module RailtiesTest
assert File.exist?("#{app_path}/db/migrate/2_create_users.bukkits.rb")
assert File.exist?("#{app_path}/db/migrate/3_add_last_name_to_users.bukkits.rb")
- assert_match(/Copied migration 2_create_users.bukkits.rb from bukkits/, output)
- assert_match(/Copied migration 3_add_last_name_to_users.bukkits.rb from bukkits/, output)
- assert_match(/NOTE: Migration 3_create_sessions.rb from bukkits has been skipped/, output)
+ assert_match(/Copied migration 2_create_users\.bukkits\.rb from bukkits/, output)
+ assert_match(/Copied migration 3_add_last_name_to_users\.bukkits\.rb from bukkits/, output)
+ assert_match(/NOTE: Migration 3_create_sessions\.rb from bukkits has been skipped/, output)
assert_equal 3, Dir["#{app_path}/db/migrate/*.rb"].length
output = `bundle exec rake railties:install:migrations`.split("\n")
assert_no_match(/2_create_users/, output.join("\n"))
- bukkits_migration_order = output.index(output.detect { |o| /NOTE: Migration 3_create_sessions.rb from bukkits has been skipped/ =~ o })
+ bukkits_migration_order = output.index(output.detect { |o| /NOTE: Migration 3_create_sessions\.rb from bukkits has been skipped/ =~ o })
assert_not_nil bukkits_migration_order, "Expected migration to be skipped"
migrations_count = Dir["#{app_path}/db/migrate/*.rb"].length
@@ -135,8 +135,8 @@ module RailtiesTest
Dir.chdir(app_path) do
output = `bundle exec rake railties:install:migrations`.split("\n")
- assert_match(/Copied migration \d+_create_users.bukkits.rb from bukkits/, output.first)
- assert_match(/Copied migration \d+_create_blogs.blog_engine.rb from blog_engine/, output.last)
+ assert_match(/Copied migration \d+_create_users\.bukkits\.rb from bukkits/, output.first)
+ assert_match(/Copied migration \d+_create_blogs\.blog_engine\.rb from blog_engine/, output.last)
end
end
@@ -171,8 +171,8 @@ module RailtiesTest
Dir.chdir(app_path) do
output = `bundle exec rake railties:install:migrations`.split("\n")
- assert_match(/Copied migration \d+_create_users.core_engine.rb from core_engine/, output.first)
- assert_match(/Copied migration \d+_create_keys.api_engine.rb from api_engine/, output.last)
+ assert_match(/Copied migration \d+_create_users\.core_engine\.rb from core_engine/, output.first)
+ assert_match(/Copied migration \d+_create_keys\.api_engine\.rb from api_engine/, output.last)
end
end
@@ -202,7 +202,7 @@ module RailtiesTest
Dir.chdir(@plugin.path) do
output = `bundle exec rake app:bukkits:install:migrations`
assert File.exist?("#{app_path}/db/migrate/0_add_first_name_to_users.bukkits.rb")
- assert_match(/Copied migration 0_add_first_name_to_users.bukkits.rb from bukkits/, output)
+ assert_match(/Copied migration 0_add_first_name_to_users\.bukkits\.rb from bukkits/, output)
assert_equal 1, Dir["#{app_path}/db/migrate/*.rb"].length
end
end
diff --git a/railties/test/secrets_test.rb b/railties/test/secrets_test.rb
index 953408f0b4..36c8ef1fd9 100644
--- a/railties/test/secrets_test.rb
+++ b/railties/test/secrets_test.rb
@@ -9,22 +9,22 @@ class Rails::SecretsTest < ActiveSupport::TestCase
def setup
build_app
-
- @old_read_encrypted_secrets, Rails::Secrets.read_encrypted_secrets =
- Rails::Secrets.read_encrypted_secrets, true
end
def teardown
- Rails::Secrets.read_encrypted_secrets = @old_read_encrypted_secrets
-
teardown_app
end
test "setting read to false skips parsing" do
- Rails::Secrets.read_encrypted_secrets = false
+ run_secrets_generator do
+ Rails::Secrets.write(<<-end_of_secrets)
+ test:
+ yeah_yeah: lets-walk-in-the-cool-evening-light
+ end_of_secrets
- Dir.chdir(app_path) do
- assert_equal Hash.new, Rails::Secrets.parse(%w( config/secrets.yml.enc ), env: "production")
+ Rails.application.config.read_encrypted_secrets = false
+ Rails.application.instance_variable_set(:@secrets, nil) # Dance around caching 💃🕺
+ assert_not Rails.application.secrets.yeah_yeah
end
end
@@ -90,11 +90,27 @@ class Rails::SecretsTest < ActiveSupport::TestCase
end_of_secrets
Rails.application.config.root = app_path
+ Rails.application.config.read_encrypted_secrets = true
Rails.application.instance_variable_set(:@secrets, nil) # Dance around caching 💃🕺
assert_equal "lets-walk-in-the-cool-evening-light", Rails.application.secrets.yeah_yeah
end
end
+ test "refer secrets inside env config" do
+ run_secrets_generator do
+ Rails::Secrets.write(<<-end_of_yaml)
+ production:
+ some_secret: yeah yeah
+ end_of_yaml
+
+ add_to_env_config "production", <<-end_of_config
+ config.dereferenced_secret = Rails.application.secrets.some_secret
+ end_of_config
+
+ assert_equal "yeah yeah\n", `bin/rails runner -e production "puts Rails.application.config.dereferenced_secret"`
+ end
+ end
+
private
def run_secrets_generator
Dir.chdir(app_path) do
diff --git a/railties/test/test_unit/reporter_test.rb b/railties/test/test_unit/reporter_test.rb
index e22c939981..98201394cd 100644
--- a/railties/test/test_unit/reporter_test.rb
+++ b/railties/test/test_unit/reporter_test.rb
@@ -16,7 +16,7 @@ class TestUnitReporterTest < ActiveSupport::TestCase
@reporter.record(failed_test)
@reporter.report
- assert_match %r{^bin/rails test .*test/test_unit/reporter_test.rb:\d+$}, @output.string
+ assert_match %r{^bin/rails test .*test/test_unit/reporter_test\.rb:\d+$}, @output.string
assert_rerun_snippet_count 1
end
@@ -52,7 +52,7 @@ class TestUnitReporterTest < ActiveSupport::TestCase
@reporter.record(failed_test)
@reporter.report
- assert_match %r{^bin/test .*test/test_unit/reporter_test.rb:\d+$}, @output.string
+ assert_match %r{^bin/test .*test/test_unit/reporter_test\.rb:\d+$}, @output.string
ensure
Rails::TestUnitReporter.executable = original_executable
end
@@ -62,7 +62,7 @@ class TestUnitReporterTest < ActiveSupport::TestCase
@reporter.record(failed_test)
@reporter.report
- expect = %r{\AF\n\nFailure:\nTestUnitReporterTest::ExampleTest#woot \[[^\]]+\]:\nboo\n\nbin/rails test test/test_unit/reporter_test.rb:\d+\n\n\z}
+ expect = %r{\AF\n\nFailure:\nTestUnitReporterTest::ExampleTest#woot \[[^\]]+\]:\nboo\n\nbin/rails test test/test_unit/reporter_test\.rb:\d+\n\n\z}
assert_match expect, @output.string
end
@@ -70,7 +70,7 @@ class TestUnitReporterTest < ActiveSupport::TestCase
@reporter.record(errored_test)
@reporter.report
- expect = %r{\AE\n\nError:\nTestUnitReporterTest::ExampleTest#woot:\nArgumentError: wups\n No backtrace\n\nbin/rails test .*test/test_unit/reporter_test.rb:\d+\n\n\z}
+ expect = %r{\AE\n\nError:\nTestUnitReporterTest::ExampleTest#woot:\nArgumentError: wups\n No backtrace\n\nbin/rails test .*test/test_unit/reporter_test\.rb:\d+\n\n\z}
assert_match expect, @output.string
end
@@ -79,7 +79,7 @@ class TestUnitReporterTest < ActiveSupport::TestCase
verbose.record(skipped_test)
verbose.report
- expect = %r{\ATestUnitReporterTest::ExampleTest#woot = 10\.00 s = S\n\n\nSkipped:\nTestUnitReporterTest::ExampleTest#woot \[[^\]]+\]:\nskipchurches, misstemples\n\nbin/rails test test/test_unit/reporter_test.rb:\d+\n\n\z}
+ expect = %r{\ATestUnitReporterTest::ExampleTest#woot = 10\.00 s = S\n\n\nSkipped:\nTestUnitReporterTest::ExampleTest#woot \[[^\]]+\]:\nskipchurches, misstemples\n\nbin/rails test test/test_unit/reporter_test\.rb:\d+\n\n\z}
assert_match expect, @output.string
end
diff --git a/tasks/release.rb b/tasks/release.rb
index b021535245..038fdc584a 100644
--- a/tasks/release.rb
+++ b/tasks/release.rb
@@ -1,7 +1,7 @@
FRAMEWORKS = %w( activesupport activemodel activerecord actionview actionpack activejob actionmailer actioncable railties )
FRAMEWORK_NAMES = Hash.new { |h, k| k.split(/(?<=active|action)/).map(&:capitalize).join(" ") }
-root = File.expand_path("../../", __FILE__)
+root = File.expand_path("..", __dir__)
version = File.read("#{root}/RAILS_VERSION").strip
tag = "v#{version}"
gem_version = Gem::Version.new(version)
diff --git a/tools/README.md b/tools/README.md
index b2e7e4b0ae..f133b27128 100644
--- a/tools/README.md
+++ b/tools/README.md
@@ -6,3 +6,5 @@ They aren't used by Rails apps directly.
* `console` drops you in irb and loads local Rails repos
* `profile` profiles `Kernel#require` to help reduce startup time
* `line_statistics` provides CodeTools module and LineStatistics class to count lines
+ * `test` is loaded by every major component of Rails to simplify testing, for example:
+ `cd ./actioncable; bin/test ./path/to/actioncable_test_with_line_number.rb:5`
diff --git a/tools/test.rb b/tools/test.rb
index 71349a5974..52e9c19198 100644
--- a/tools/test.rb
+++ b/tools/test.rb
@@ -7,11 +7,12 @@ require "rails/test_unit/minitest_plugin"
require "rails/test_unit/line_filtering"
require "active_support/test_case"
-module Rails
- # Necessary to get rerun-snippts working.
- def self.root
+class << Rails
+ # Necessary to get rerun-snippets working.
+ def root
@root ||= Pathname.new(COMPONENT_ROOT)
end
+ alias __root root
end
ActiveSupport::TestCase.extend Rails::LineFiltering