aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.rubocop.yml40
-rw-r--r--.travis.yml54
-rw-r--r--Gemfile27
-rw-r--r--Gemfile.lock258
-rw-r--r--MIT-LICENSE20
-rw-r--r--RELEASING_RAILS.md16
-rw-r--r--Rakefile4
-rw-r--r--actioncable/CHANGELOG.md24
-rw-r--r--actioncable/MIT-LICENSE2
-rw-r--r--actioncable/README.md18
-rw-r--r--actioncable/Rakefile5
-rw-r--r--actioncable/actioncable.gemspec2
-rw-r--r--actioncable/lib/action_cable.rb2
-rw-r--r--actioncable/lib/action_cable/channel/base.rb65
-rw-r--r--actioncable/lib/action_cable/channel/naming.rb2
-rw-r--r--actioncable/lib/action_cable/channel/streams.rb10
-rw-r--r--actioncable/lib/action_cable/connection.rb2
-rw-r--r--actioncable/lib/action_cable/connection/base.rb30
-rw-r--r--actioncable/lib/action_cable/connection/client_socket.rb2
-rw-r--r--actioncable/lib/action_cable/connection/faye_client_socket.rb48
-rw-r--r--actioncable/lib/action_cable/connection/faye_event_loop.rb44
-rw-r--r--actioncable/lib/action_cable/connection/message_buffer.rb2
-rw-r--r--actioncable/lib/action_cable/connection/stream.rb58
-rw-r--r--actioncable/lib/action_cable/connection/stream_event_loop.rb47
-rw-r--r--actioncable/lib/action_cable/connection/subscriptions.rb8
-rw-r--r--actioncable/lib/action_cable/connection/tagged_logger_proxy.rb4
-rw-r--r--actioncable/lib/action_cable/connection/web_socket.rb6
-rw-r--r--actioncable/lib/action_cable/engine.rb2
-rw-r--r--actioncable/lib/action_cable/helpers/action_cable_helper.rb2
-rw-r--r--actioncable/lib/action_cable/remote_connections.rb2
-rw-r--r--actioncable/lib/action_cable/server/base.rb12
-rw-r--r--actioncable/lib/action_cable/server/configuration.rb21
-rw-r--r--actioncable/lib/action_cable/server/worker.rb2
-rw-r--r--actioncable/lib/action_cable/subscription_adapter/evented_redis.rb4
-rw-r--r--actioncable/lib/action_cable/subscription_adapter/subscriber_map.rb2
-rw-r--r--actioncable/lib/rails/generators/channel/USAGE4
-rw-r--r--actioncable/lib/rails/generators/channel/channel_generator.rb2
-rw-r--r--actioncable/test/channel/base_test.rb21
-rw-r--r--actioncable/test/channel/periodic_timers_test.rb3
-rw-r--r--actioncable/test/channel/rejection_test.rb17
-rw-r--r--actioncable/test/channel/stream_test.rb31
-rw-r--r--actioncable/test/client_test.rb168
-rw-r--r--actioncable/test/connection/client_socket_test.rb16
-rw-r--r--actioncable/test/connection/cross_site_forgery_test.rb9
-rw-r--r--actioncable/test/connection/identifier_test.rb2
-rw-r--r--actioncable/test/connection/multiple_identifiers_test.rb2
-rw-r--r--actioncable/test/connection/stream_test.rb2
-rw-r--r--actioncable/test/connection/string_identifier_test.rb2
-rw-r--r--actioncable/test/javascript/src/test_helpers/consumer_test_helper.coffee10
-rw-r--r--actioncable/test/javascript/src/test_helpers/index.coffee3
-rw-r--r--actioncable/test/javascript/src/unit/subscription_test.coffee40
-rw-r--r--actioncable/test/javascript/src/unit/subscriptions_test.coffee25
-rw-r--r--actioncable/test/server/base_test.rb33
-rw-r--r--actioncable/test/stubs/room.rb2
-rw-r--r--actioncable/test/stubs/test_server.rb12
-rw-r--r--actioncable/test/subscription_adapter/common.rb4
-rw-r--r--actioncable/test/subscription_adapter/evented_redis_test.rb34
-rw-r--r--actioncable/test/test_helper.rb50
-rw-r--r--actioncable/test/worker_test.rb2
-rw-r--r--actionmailer/MIT-LICENSE2
-rw-r--r--actionmailer/Rakefile2
-rwxr-xr-xactionmailer/bin/test2
-rw-r--r--actionmailer/lib/action_mailer.rb2
-rw-r--r--actionmailer/lib/action_mailer/base.rb54
-rw-r--r--actionmailer/lib/action_mailer/delivery_methods.rb4
-rw-r--r--actionmailer/lib/action_mailer/inline_preview_interceptor.rb4
-rw-r--r--actionmailer/lib/action_mailer/message_delivery.rb6
-rw-r--r--actionmailer/lib/action_mailer/preview.rb25
-rw-r--r--actionmailer/lib/action_mailer/railtie.rb4
-rw-r--r--actionmailer/lib/action_mailer/test_case.rb15
-rw-r--r--actionmailer/lib/rails/generators/mailer/mailer_generator.rb5
-rw-r--r--actionmailer/test/assert_select_email_test.rb4
-rw-r--r--actionmailer/test/base_test.rb16
-rw-r--r--actionmailer/test/caching_test.rb4
-rw-r--r--actionmailer/test/delivery_methods_test.rb12
-rw-r--r--actionmailer/test/i18n_with_controller_test.rb4
-rw-r--r--actionmailer/test/mail_helper_test.rb2
-rw-r--r--actionmailer/test/mailers/base_mailer.rb18
-rw-r--r--actionmailer/test/message_delivery_test.rb2
-rw-r--r--actionmailer/test/url_test.rb10
-rw-r--r--actionpack/CHANGELOG.md248
-rw-r--r--actionpack/MIT-LICENSE2
-rw-r--r--actionpack/Rakefile2
-rwxr-xr-xactionpack/bin/test2
-rw-r--r--actionpack/lib/abstract_controller/base.rb8
-rw-r--r--actionpack/lib/abstract_controller/caching.rb4
-rw-r--r--actionpack/lib/abstract_controller/caching/fragments.rb2
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb41
-rw-r--r--actionpack/lib/abstract_controller/collector.rb2
-rw-r--r--actionpack/lib/abstract_controller/helpers.rb12
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb12
-rw-r--r--actionpack/lib/action_controller.rb1
-rw-r--r--actionpack/lib/action_controller/base.rb1
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb2
-rw-r--r--actionpack/lib/action_controller/metal.rb6
-rw-r--r--actionpack/lib/action_controller/metal/conditional_get.rb7
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb10
-rw-r--r--actionpack/lib/action_controller/metal/etag_with_flash.rb16
-rw-r--r--actionpack/lib/action_controller/metal/etag_with_template_digest.rb8
-rw-r--r--actionpack/lib/action_controller/metal/exceptions.rb14
-rw-r--r--actionpack/lib/action_controller/metal/flash.rb2
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb2
-rw-r--r--actionpack/lib/action_controller/metal/head.rb8
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb2
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb12
-rw-r--r--actionpack/lib/action_controller/metal/implicit_render.rb6
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb6
-rw-r--r--actionpack/lib/action_controller/metal/live.rb9
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb4
-rw-r--r--actionpack/lib/action_controller/metal/parameter_encoding.rb39
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb18
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb28
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb4
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb38
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb46
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb6
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb109
-rw-r--r--actionpack/lib/action_controller/metal/testing.rb2
-rw-r--r--actionpack/lib/action_controller/railtie.rb2
-rw-r--r--actionpack/lib/action_controller/renderer.rb6
-rw-r--r--actionpack/lib/action_controller/test_case.rb143
-rw-r--r--actionpack/lib/action_dispatch.rb3
-rw-r--r--actionpack/lib/action_dispatch/http/filter_parameters.rb10
-rw-r--r--actionpack/lib/action_dispatch/http/headers.rb6
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb12
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb30
-rw-r--r--actionpack/lib/action_dispatch/http/parameter_filter.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb31
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb9
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb31
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb6
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb90
-rw-r--r--actionpack/lib/action_dispatch/journey/formatter.rb20
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/builder.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/transition_table.rb4
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/builder.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/dot.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/transition_table.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/parser.rb307
-rw-r--r--actionpack/lib/action_dispatch/journey/parser.y5
-rw-r--r--actionpack/lib/action_dispatch/journey/parser_extras.rb6
-rw-r--r--actionpack/lib/action_dispatch/journey/route.rb25
-rw-r--r--actionpack/lib/action_dispatch/journey/router.rb10
-rw-r--r--actionpack/lib/action_dispatch/journey/router/utils.rb10
-rw-r--r--actionpack/lib/action_dispatch/journey/scanner.rb30
-rw-r--r--actionpack/lib/action_dispatch/journey/visitors.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb16
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb12
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_locks.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb28
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb5
-rw-r--r--actionpack/lib/action_dispatch/middleware/params_parser.rb45
-rw-r--r--actionpack/lib/action_dispatch/middleware/public_exceptions.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/request_id.rb7
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb23
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cache_store.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/ssl.rb36
-rw-r--r--actionpack/lib/action_dispatch/middleware/stack.rb21
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb9
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb4
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb8
-rw-r--r--actionpack/lib/action_dispatch/request/utils.rb11
-rw-r--r--actionpack/lib/action_dispatch/routing.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb324
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb6
-rw-r--r--actionpack/lib/action_dispatch/routing/redirection.rb8
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb66
-rw-r--r--actionpack/lib/action_dispatch/routing/routes_proxy.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb6
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb9
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb15
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb355
-rw-r--r--actionpack/lib/action_dispatch/testing/request_encoder.rb31
-rw-r--r--actionpack/lib/action_dispatch/testing/test_process.rb35
-rw-r--r--actionpack/lib/action_dispatch/testing/test_request.rb2
-rw-r--r--actionpack/lib/action_pack.rb2
-rw-r--r--actionpack/test/abstract/callbacks_test.rb48
-rw-r--r--actionpack/test/abstract/collector_test.rb2
-rw-r--r--actionpack/test/abstract_unit.rb12
-rw-r--r--actionpack/test/assertions/response_assertions_test.rb24
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb26
-rw-r--r--actionpack/test/controller/api/params_wrapper_test.rb2
-rw-r--r--actionpack/test/controller/api/renderers_test.rb12
-rw-r--r--actionpack/test/controller/base_test.rb2
-rw-r--r--actionpack/test/controller/caching_test.rb8
-rw-r--r--actionpack/test/controller/default_url_options_with_before_action_test.rb2
-rw-r--r--actionpack/test/controller/filters_test.rb92
-rw-r--r--actionpack/test/controller/flash_hash_test.rb40
-rw-r--r--actionpack/test/controller/flash_test.rb39
-rw-r--r--actionpack/test/controller/force_ssl_test.rb2
-rw-r--r--actionpack/test/controller/helper_test.rb24
-rw-r--r--actionpack/test/controller/http_digest_authentication_test.rb6
-rw-r--r--actionpack/test/controller/http_token_authentication_test.rb9
-rw-r--r--actionpack/test/controller/integration_test.rb343
-rw-r--r--actionpack/test/controller/live_stream_test.rb26
-rw-r--r--actionpack/test/controller/log_subscriber_test.rb2
-rw-r--r--actionpack/test/controller/mime/accept_format_test.rb2
-rw-r--r--actionpack/test/controller/mime/respond_to_test.rb18
-rw-r--r--actionpack/test/controller/new_base/bare_metal_test.rb2
-rw-r--r--actionpack/test/controller/new_base/base_test.rb2
-rw-r--r--actionpack/test/controller/new_base/render_context_test.rb14
-rw-r--r--actionpack/test/controller/new_base/render_file_test.rb2
-rw-r--r--actionpack/test/controller/new_base/render_streaming_test.rb6
-rw-r--r--actionpack/test/controller/new_base/render_text_test.rb188
-rw-r--r--actionpack/test/controller/parameter_encoding_test.rb43
-rw-r--r--actionpack/test/controller/parameters/accessors_test.rb10
-rw-r--r--actionpack/test/controller/parameters/dup_test.rb24
-rw-r--r--actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb8
-rw-r--r--actionpack/test/controller/parameters/mutators_test.rb6
-rw-r--r--actionpack/test/controller/parameters/nested_parameters_permit_test.rb4
-rw-r--r--actionpack/test/controller/parameters/parameters_permit_test.rb90
-rw-r--r--actionpack/test/controller/parameters/serialization_test.rb10
-rw-r--r--actionpack/test/controller/params_wrapper_test.rb20
-rw-r--r--actionpack/test/controller/redirect_test.rb48
-rw-r--r--actionpack/test/controller/render_json_test.rb4
-rw-r--r--actionpack/test/controller/render_test.rb39
-rw-r--r--actionpack/test/controller/renderer_test.rb8
-rw-r--r--actionpack/test/controller/renderers_test.rb2
-rw-r--r--actionpack/test/controller/request/test_request_test.rb15
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb34
-rw-r--r--actionpack/test/controller/required_params_test.rb10
-rw-r--r--actionpack/test/controller/rescue_test.rb4
-rw-r--r--actionpack/test/controller/resources_test.rb72
-rw-r--r--actionpack/test/controller/routing_test.rb286
-rw-r--r--actionpack/test/controller/send_file_test.rb25
-rw-r--r--actionpack/test/controller/test_case_test.rb231
-rw-r--r--actionpack/test/controller/url_for_integration_test.rb190
-rw-r--r--actionpack/test/controller/url_for_test.rb75
-rw-r--r--actionpack/test/controller/url_rewriter_test.rb2
-rw-r--r--actionpack/test/dispatch/cookies_test.rb95
-rw-r--r--actionpack/test/dispatch/debug_exceptions_test.rb25
-rw-r--r--actionpack/test/dispatch/header_test.rb40
-rw-r--r--actionpack/test/dispatch/mapper_test.rb8
-rw-r--r--actionpack/test/dispatch/middleware_stack_test.rb43
-rw-r--r--actionpack/test/dispatch/mime_type_test.rb14
-rw-r--r--actionpack/test/dispatch/mount_test.rb4
-rw-r--r--actionpack/test/dispatch/prefix_generation_test.rb6
-rw-r--r--actionpack/test/dispatch/reloader_test.rb4
-rw-r--r--actionpack/test/dispatch/request/json_params_parsing_test.rb26
-rw-r--r--actionpack/test/dispatch/request/multipart_params_parsing_test.rb12
-rw-r--r--actionpack/test/dispatch/request/query_string_parsing_test.rb58
-rw-r--r--actionpack/test/dispatch/request/session_test.rb4
-rw-r--r--actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb2
-rw-r--r--actionpack/test/dispatch/request_test.rb82
-rw-r--r--actionpack/test/dispatch/response_test.rb33
-rw-r--r--actionpack/test/dispatch/routing/inspector_test.rb4
-rw-r--r--actionpack/test/dispatch/routing/ipv6_redirect_test.rb2
-rw-r--r--actionpack/test/dispatch/routing_assertions_test.rb2
-rw-r--r--actionpack/test/dispatch/routing_test.rb144
-rw-r--r--actionpack/test/dispatch/runner_test.rb18
-rw-r--r--actionpack/test/dispatch/session/cache_store_test.rb8
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb6
-rw-r--r--actionpack/test/dispatch/session/mem_cache_store_test.rb2
-rw-r--r--actionpack/test/dispatch/show_exceptions_test.rb2
-rw-r--r--actionpack/test/dispatch/ssl_test.rb63
-rw-r--r--actionpack/test/dispatch/static_test.rb58
-rw-r--r--actionpack/test/dispatch/test_request_test.rb19
-rw-r--r--actionpack/test/dispatch/uploaded_file_test.rb8
-rw-r--r--actionpack/test/fixtures/load_me.rb2
-rw-r--r--actionpack/test/fixtures/multipart/mona_lisa.jpgbin159528 -> 0 bytes
-rw-r--r--actionpack/test/fixtures/multipart/ruby_on_rails.jpgbin0 -> 45142 bytes
-rw-r--r--actionpack/test/fixtures/session_autoload_test/session_autoload_test/foo.rb2
-rw-r--r--actionpack/test/journey/gtg/builder_test.rb10
-rw-r--r--actionpack/test/journey/gtg/transition_table_test.rb4
-rw-r--r--actionpack/test/journey/nfa/simulator_test.rb4
-rw-r--r--actionpack/test/journey/nfa/transition_table_test.rb4
-rw-r--r--actionpack/test/journey/path/pattern_test.rb2
-rw-r--r--actionpack/test/journey/route_test.rb16
-rw-r--r--actionpack/test/journey/router_test.rb52
-rw-r--r--actionpack/test/journey/routes_test.rb2
-rw-r--r--actionpack/test/lib/controller/fake_models.rb4
-rw-r--r--actionview/.gitignore2
-rw-r--r--actionview/CHANGELOG.md66
-rw-r--r--actionview/MIT-LICENSE2
-rw-r--r--actionview/RUNNING_UJS_TESTS.rdoc7
-rw-r--r--actionview/Rakefile19
-rw-r--r--actionview/app/assets/javascripts/config.coffee37
-rw-r--r--actionview/app/assets/javascripts/features/confirm.coffee26
-rw-r--r--actionview/app/assets/javascripts/features/disable.coffee78
-rw-r--r--actionview/app/assets/javascripts/features/method.coffee34
-rw-r--r--actionview/app/assets/javascripts/features/remote.coffee100
-rw-r--r--actionview/app/assets/javascripts/rails-ujs.coffee76
-rw-r--r--actionview/app/assets/javascripts/utils/ajax.coffee95
-rw-r--r--actionview/app/assets/javascripts/utils/csrf.coffee25
-rw-r--r--actionview/app/assets/javascripts/utils/dom.coffee28
-rw-r--r--actionview/app/assets/javascripts/utils/event.coffee40
-rw-r--r--actionview/app/assets/javascripts/utils/form.coffee61
-rwxr-xr-xactionview/bin/test2
-rw-r--r--actionview/blade.yml11
-rw-r--r--actionview/lib/action_view.rb2
-rw-r--r--actionview/lib/action_view/base.rb2
-rw-r--r--actionview/lib/action_view/context.rb2
-rw-r--r--actionview/lib/action_view/digestor.rb6
-rw-r--r--actionview/lib/action_view/flows.rb2
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb67
-rw-r--r--actionview/lib/action_view/helpers/asset_url_helper.rb105
-rw-r--r--actionview/lib/action_view/helpers/atom_feed_helper.rb10
-rw-r--r--actionview/lib/action_view/helpers/cache_helper.rb19
-rw-r--r--actionview/lib/action_view/helpers/capture_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/date_helper.rb44
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb392
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb67
-rw-r--r--actionview/lib/action_view/helpers/javascript_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/number_helper.rb6
-rw-r--r--actionview/lib/action_view/helpers/output_safety_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/tag_helper.rb6
-rw-r--r--actionview/lib/action_view/helpers/tags/base.rb39
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_check_boxes.rb4
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_helpers.rb18
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/label.rb4
-rw-r--r--actionview/lib/action_view/helpers/tags/password_field.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/select.rb8
-rw-r--r--actionview/lib/action_view/helpers/tags/translator.rb2
-rw-r--r--actionview/lib/action_view/helpers/text_helper.rb11
-rw-r--r--actionview/lib/action_view/helpers/translation_helper.rb1
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb27
-rw-r--r--actionview/lib/action_view/layouts.rb90
-rw-r--r--actionview/lib/action_view/log_subscriber.rb12
-rw-r--r--actionview/lib/action_view/lookup_context.rb18
-rw-r--r--actionview/lib/action_view/railtie.rb8
-rw-r--r--actionview/lib/action_view/record_identifier.rb4
-rw-r--r--actionview/lib/action_view/renderer/abstract_renderer.rb8
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer.rb38
-rw-r--r--actionview/lib/action_view/renderer/streaming_template_renderer.rb6
-rw-r--r--actionview/lib/action_view/renderer/template_renderer.rb22
-rw-r--r--actionview/lib/action_view/rendering.rb6
-rw-r--r--actionview/lib/action_view/routing_url_for.rb19
-rw-r--r--actionview/lib/action_view/template.rb46
-rw-r--r--actionview/lib/action_view/template/error.rb13
-rw-r--r--actionview/lib/action_view/template/handlers/builder.rb4
-rw-r--r--actionview/lib/action_view/template/handlers/erb.rb1
-rw-r--r--actionview/lib/action_view/template/html.rb4
-rw-r--r--actionview/lib/action_view/template/resolver.rb32
-rw-r--r--actionview/lib/action_view/template/text.rb11
-rw-r--r--actionview/lib/action_view/test_case.rb9
-rw-r--r--actionview/lib/action_view/testing/resolvers.rb7
-rw-r--r--actionview/lib/action_view/view_paths.rb18
-rw-r--r--actionview/package.json36
-rw-r--r--actionview/test/abstract_unit.rb8
-rw-r--r--actionview/test/actionpack/abstract/abstract_controller_test.rb8
-rw-r--r--actionview/test/actionpack/abstract/helper_test.rb2
-rw-r--r--actionview/test/actionpack/controller/layout_test.rb3
-rw-r--r--actionview/test/actionpack/controller/render_test.rb16
-rw-r--r--actionview/test/actionpack/controller/view_paths_test.rb2
-rw-r--r--actionview/test/active_record_unit.rb2
-rw-r--r--actionview/test/activerecord/debug_helper_test.rb2
-rw-r--r--actionview/test/activerecord/form_helper_activerecord_test.rb2
-rw-r--r--actionview/test/activerecord/polymorphic_routes_test.rb2
-rw-r--r--actionview/test/activerecord/relation_cache_test.rb4
-rw-r--r--actionview/test/fixtures/test/render_file_inspect_local_assigns.erb1
-rw-r--r--actionview/test/fixtures/test/render_file_unicode_local.erb1
-rw-r--r--actionview/test/fixtures/test/render_file_with_ruby_keyword_locals.erb1
-rw-r--r--actionview/test/fixtures/test/test_template_with_delegation_reserved_keywords.erb1
-rw-r--r--actionview/test/lib/controller/fake_models.rb4
-rw-r--r--actionview/test/template/asset_tag_helper_test.rb12
-rw-r--r--actionview/test/template/capture_helper_test.rb16
-rw-r--r--actionview/test/template/compiled_templates_test.rb29
-rw-r--r--actionview/test/template/date_helper_i18n_test.rb8
-rw-r--r--actionview/test/template/date_helper_test.rb124
-rw-r--r--actionview/test/template/dependency_tracker_test.rb4
-rw-r--r--actionview/test/template/digestor_test.rb6
-rw-r--r--actionview/test/template/form_collections_helper_test.rb34
-rw-r--r--actionview/test/template/form_helper/form_with_test.rb2231
-rw-r--r--actionview/test/template/form_helper_test.rb64
-rw-r--r--actionview/test/template/form_options_helper_test.rb80
-rw-r--r--actionview/test/template/form_tag_helper_test.rb23
-rw-r--r--actionview/test/template/javascript_helper_test.rb8
-rw-r--r--actionview/test/template/log_subscriber_test.rb2
-rw-r--r--actionview/test/template/lookup_context_test.rb6
-rw-r--r--actionview/test/template/number_helper_test.rb18
-rw-r--r--actionview/test/template/output_safety_helper_test.rb4
-rw-r--r--actionview/test/template/render_test.rb16
-rw-r--r--actionview/test/template/tag_helper_test.rb28
-rw-r--r--actionview/test/template/template_test.rb30
-rw-r--r--actionview/test/template/test_case_test.rb22
-rw-r--r--actionview/test/template/text_helper_test.rb28
-rw-r--r--actionview/test/template/text_test.rb14
-rw-r--r--actionview/test/template/url_helper_test.rb33
-rw-r--r--actionview/test/ujs/.gitignore1
-rw-r--r--actionview/test/ujs/config.ru3
-rw-r--r--actionview/test/ujs/public/test/.eslintrc.yml21
-rw-r--r--actionview/test/ujs/public/test/call-remote-callbacks.js469
-rw-r--r--actionview/test/ujs/public/test/call-remote.js247
-rw-r--r--actionview/test/ujs/public/test/csrf-refresh.js24
-rw-r--r--actionview/test/ujs/public/test/csrf-token.js27
-rw-r--r--actionview/test/ujs/public/test/data-confirm.js288
-rw-r--r--actionview/test/ujs/public/test/data-disable-with.js391
-rw-r--r--actionview/test/ujs/public/test/data-disable.js321
-rw-r--r--actionview/test/ujs/public/test/data-method.js85
-rw-r--r--actionview/test/ujs/public/test/data-remote.js400
-rw-r--r--actionview/test/ujs/public/test/override.js56
-rw-r--r--actionview/test/ujs/public/test/settings.js116
-rw-r--r--actionview/test/ujs/public/vendor/jquery.metadata.js122
-rw-r--r--actionview/test/ujs/public/vendor/qunit.css237
-rw-r--r--actionview/test/ujs/public/vendor/qunit.js2288
-rw-r--r--actionview/test/ujs/server.rb105
-rw-r--r--actionview/test/ujs/views/layouts/application.html.erb40
-rw-r--r--actionview/test/ujs/views/tests/index.html.erb25
-rw-r--r--activejob/CHANGELOG.md26
-rw-r--r--activejob/MIT-LICENSE2
-rw-r--r--activejob/Rakefile7
-rw-r--r--activejob/lib/active_job.rb2
-rw-r--r--activejob/lib/active_job/arguments.rb20
-rw-r--r--activejob/lib/active_job/configured_job.rb2
-rw-r--r--activejob/lib/active_job/core.rb2
-rw-r--r--activejob/lib/active_job/enqueuing.rb6
-rw-r--r--activejob/lib/active_job/exceptions.rb14
-rw-r--r--activejob/lib/active_job/logging.rb4
-rw-r--r--activejob/lib/active_job/queue_adapter.rb11
-rw-r--r--activejob/lib/active_job/queue_adapters.rb2
-rw-r--r--activejob/lib/active_job/queue_adapters/qu_adapter.rb2
-rw-r--r--activejob/lib/active_job/queue_adapters/resque_adapter.rb1
-rw-r--r--activejob/lib/active_job/queue_name.rb2
-rw-r--r--activejob/lib/active_job/queue_priority.rb2
-rw-r--r--activejob/lib/active_job/railtie.rb2
-rw-r--r--activejob/lib/active_job/test_case.rb2
-rw-r--r--activejob/lib/active_job/test_helper.rb60
-rw-r--r--activejob/test/cases/exceptions_test.rb2
-rw-r--r--activejob/test/cases/queue_adapter_test.rb13
-rw-r--r--activejob/test/cases/queue_naming_test.rb2
-rw-r--r--activejob/test/cases/queue_priority_test.rb4
-rw-r--r--activejob/test/cases/test_helper_test.rb15
-rw-r--r--activejob/test/helper.rb3
-rw-r--r--activejob/test/integration/queuing_test.rb7
-rw-r--r--activejob/test/jobs/application_job.rb2
-rw-r--r--activejob/test/jobs/inherited_job.rb5
-rw-r--r--activejob/test/jobs/retry_job.rb2
-rw-r--r--activejob/test/models/person.rb2
-rw-r--r--activejob/test/support/backburner/inline.rb2
-rw-r--r--activejob/test/support/delayed_job/delayed/backend/test.rb14
-rw-r--r--activejob/test/support/integration/adapters/que.rb2
-rw-r--r--activejob/test/support/integration/adapters/queue_classic.rb2
-rw-r--r--activejob/test/support/integration/adapters/sneakers.rb4
-rw-r--r--activejob/test/support/integration/dummy_app_template.rb3
-rw-r--r--activejob/test/support/integration/helper.rb1
-rw-r--r--activejob/test/support/integration/test_case_helpers.rb11
-rw-r--r--activemodel/CHANGELOG.md20
-rw-r--r--activemodel/MIT-LICENSE2
-rw-r--r--activemodel/Rakefile2
-rwxr-xr-xactivemodel/bin/test2
-rw-r--r--activemodel/lib/active_model.rb2
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb17
-rw-r--r--activemodel/lib/active_model/callbacks.rb6
-rw-r--r--activemodel/lib/active_model/conversion.rb13
-rw-r--r--activemodel/lib/active_model/dirty.rb18
-rw-r--r--activemodel/lib/active_model/errors.rb111
-rw-r--r--activemodel/lib/active_model/forbidden_attributes_protection.rb2
-rw-r--r--activemodel/lib/active_model/model.rb6
-rw-r--r--activemodel/lib/active_model/naming.rb3
-rw-r--r--activemodel/lib/active_model/serializers/json.rb2
-rw-r--r--activemodel/lib/active_model/type.rb6
-rw-r--r--activemodel/lib/active_model/type/date_time.rb4
-rw-r--r--activemodel/lib/active_model/type/decimal.rb25
-rw-r--r--activemodel/lib/active_model/type/decimal_without_scale.rb11
-rw-r--r--activemodel/lib/active_model/type/float.rb9
-rw-r--r--activemodel/lib/active_model/type/helpers/accepts_multiparameter_time.rb4
-rw-r--r--activemodel/lib/active_model/type/helpers/mutable.rb4
-rw-r--r--activemodel/lib/active_model/type/helpers/numeric.rb17
-rw-r--r--activemodel/lib/active_model/type/helpers/time_value.rb10
-rw-r--r--activemodel/lib/active_model/type/immutable_string.rb11
-rw-r--r--activemodel/lib/active_model/type/integer.rb2
-rw-r--r--activemodel/lib/active_model/type/registry.rb4
-rw-r--r--activemodel/lib/active_model/type/text.rb11
-rw-r--r--activemodel/lib/active_model/type/value.rb6
-rw-r--r--activemodel/lib/active_model/validations.rb12
-rw-r--r--activemodel/lib/active_model/validations/acceptance.rb4
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb4
-rw-r--r--activemodel/lib/active_model/validations/format.rb1
-rw-r--r--activemodel/lib/active_model/validations/length.rb35
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb24
-rw-r--r--activemodel/lib/active_model/validations/validates.rb8
-rw-r--r--activemodel/lib/active_model/validations/with.rb4
-rw-r--r--activemodel/lib/active_model/validator.rb2
-rw-r--r--activemodel/test/cases/attribute_assignment_test.rb2
-rw-r--r--activemodel/test/cases/attribute_methods_test.rb2
-rw-r--r--activemodel/test/cases/errors_test.rb138
-rw-r--r--activemodel/test/cases/secure_password_test.rb2
-rw-r--r--activemodel/test/cases/serialization_test.rb68
-rw-r--r--activemodel/test/cases/serializers/json_serialization_test.rb12
-rw-r--r--activemodel/test/cases/translation_test.rb8
-rw-r--r--activemodel/test/cases/type/big_integer_test.rb24
-rw-r--r--activemodel/test/cases/type/binary_test.rb15
-rw-r--r--activemodel/test/cases/type/boolean_test.rb39
-rw-r--r--activemodel/test/cases/type/date_test.rb19
-rw-r--r--activemodel/test/cases/type/date_time_test.rb38
-rw-r--r--activemodel/test/cases/type/float_test.rb22
-rw-r--r--activemodel/test/cases/type/immutable_string_test.rb21
-rw-r--r--activemodel/test/cases/type/integer_test.rb11
-rw-r--r--activemodel/test/cases/type/registry_test.rb52
-rw-r--r--activemodel/test/cases/type/string_test.rb37
-rw-r--r--activemodel/test/cases/type/time_test.rb21
-rw-r--r--activemodel/test/cases/type/value_test.rb14
-rw-r--r--activemodel/test/cases/types_test.rb125
-rw-r--r--activemodel/test/cases/validations/absence_validation_test.rb2
-rw-r--r--activemodel/test/cases/validations/acceptance_validation_test.rb4
-rw-r--r--activemodel/test/cases/validations/conditional_validation_test.rb4
-rw-r--r--activemodel/test/cases/validations/confirmation_validation_test.rb6
-rw-r--r--activemodel/test/cases/validations/i18n_validation_test.rb12
-rw-r--r--activemodel/test/cases/validations/inclusion_validation_test.rb2
-rw-r--r--activemodel/test/cases/validations/length_validation_test.rb62
-rw-r--r--activemodel/test/cases/validations/numericality_validation_test.rb25
-rw-r--r--activemodel/test/cases/validations/presence_validation_test.rb2
-rw-r--r--activemodel/test/cases/validations/validations_context_test.rb10
-rw-r--r--activemodel/test/cases/validations/with_validation_test.rb14
-rw-r--r--activemodel/test/cases/validations_test.rb22
-rw-r--r--activemodel/test/models/topic.rb4
-rw-r--r--activemodel/test/validators/email_validator.rb1
-rw-r--r--activerecord/CHANGELOG.md429
-rw-r--r--activerecord/MIT-LICENSE2
-rw-r--r--activerecord/Rakefile6
-rw-r--r--activerecord/activerecord.gemspec2
-rwxr-xr-xactiverecord/bin/test2
-rw-r--r--activerecord/examples/performance.rb4
-rw-r--r--activerecord/lib/active_record.rb4
-rw-r--r--activerecord/lib/active_record/aggregations.rb320
-rw-r--r--activerecord/lib/active_record/association_relation.rb5
-rw-r--r--activerecord/lib/active_record/associations.rb1803
-rw-r--r--activerecord/lib/active_record/associations/association.rb21
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb2
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb30
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb8
-rw-r--r--activerecord/lib/active_record/associations/builder/singular_association.rb11
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb229
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb179
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb13
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb15
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb14
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb36
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_part.rb4
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb34
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb28
-rw-r--r--activerecord/lib/active_record/associations/preloader/collection_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/preloader/singular_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/preloader/through_association.rb6
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb33
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb4
-rw-r--r--activerecord/lib/active_record/attribute.rb23
-rw-r--r--activerecord/lib/active_record/attribute/user_provided_default.rb2
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb16
-rw-r--r--activerecord/lib/active_record/attribute_decorators.rb22
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb10
-rw-r--r--activerecord/lib/active_record/attribute_methods/before_type_cast.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb184
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb15
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb55
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb29
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb26
-rw-r--r--activerecord/lib/active_record/attribute_mutation_tracker.rb52
-rw-r--r--activerecord/lib/active_record/attribute_set.rb2
-rw-r--r--activerecord/lib/active_record/attribute_set/builder.rb2
-rw-r--r--activerecord/lib/active_record/attribute_set/yaml_encoder.rb2
-rw-r--r--activerecord/lib/active_record/attributes.rb8
-rw-r--r--activerecord/lib/active_record/autosave_association.rb42
-rw-r--r--activerecord/lib/active_record/base.rb4
-rw-r--r--activerecord/lib/active_record/callbacks.rb17
-rw-r--r--activerecord/lib/active_record/coders/yaml_column.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb164
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb29
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb49
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb22
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb98
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb43
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb206
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb96
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/column.rb28
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb16
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb39
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb16
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb (renamed from activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb)19
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb28
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb29
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb36
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb153
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/utils.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb73
-rw-r--r--activerecord/lib/active_record/connection_adapters/schema_cache.rb22
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb23
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb174
-rw-r--r--activerecord/lib/active_record/connection_adapters/statement_pool.rb2
-rw-r--r--activerecord/lib/active_record/connection_handling.rb2
-rw-r--r--activerecord/lib/active_record/core.rb60
-rw-r--r--activerecord/lib/active_record/counter_cache.rb67
-rw-r--r--activerecord/lib/active_record/define_callbacks.rb20
-rw-r--r--activerecord/lib/active_record/dynamic_matchers.rb19
-rw-r--r--activerecord/lib/active_record/enum.rb2
-rw-r--r--activerecord/lib/active_record/errors.rb50
-rw-r--r--activerecord/lib/active_record/explain.rb1
-rw-r--r--activerecord/lib/active_record/explain_subscriber.rb7
-rw-r--r--activerecord/lib/active_record/fixture_set/file.rb9
-rw-r--r--activerecord/lib/active_record/fixtures.rb40
-rw-r--r--activerecord/lib/active_record/inheritance.rb41
-rw-r--r--activerecord/lib/active_record/integration.rb29
-rw-r--r--activerecord/lib/active_record/internal_metadata.rb2
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb34
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb40
-rw-r--r--activerecord/lib/active_record/migration.rb49
-rw-r--r--activerecord/lib/active_record/migration/command_recorder.rb12
-rw-r--r--activerecord/lib/active_record/migration/compatibility.rb49
-rw-r--r--activerecord/lib/active_record/model_schema.rb202
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb175
-rw-r--r--activerecord/lib/active_record/no_touching.rb4
-rw-r--r--activerecord/lib/active_record/null_relation.rb9
-rw-r--r--activerecord/lib/active_record/persistence.rb45
-rw-r--r--activerecord/lib/active_record/query_cache.rb24
-rw-r--r--activerecord/lib/active_record/querying.rb7
-rw-r--r--activerecord/lib/active_record/railtie.rb8
-rw-r--r--activerecord/lib/active_record/railties/controller_runtime.rb4
-rw-r--r--activerecord/lib/active_record/railties/databases.rake17
-rw-r--r--activerecord/lib/active_record/reflection.rb70
-rw-r--r--activerecord/lib/active_record/relation.rb93
-rw-r--r--activerecord/lib/active_record/relation/batches/batch_enumerator.rb2
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb64
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb12
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb289
-rw-r--r--activerecord/lib/active_record/relation/merger.rb12
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb46
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/array_handler.rb2
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb5
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/base_handler.rb2
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/class_handler.rb27
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb2
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb200
-rw-r--r--activerecord/lib/active_record/relation/record_fetch_warning.rb6
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb2
-rw-r--r--activerecord/lib/active_record/relation/where_clause.rb2
-rw-r--r--activerecord/lib/active_record/relation/where_clause_factory.rb6
-rw-r--r--activerecord/lib/active_record/result.rb4
-rw-r--r--activerecord/lib/active_record/sanitization.rb205
-rw-r--r--activerecord/lib/active_record/schema.rb4
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb61
-rw-r--r--activerecord/lib/active_record/schema_migration.rb2
-rw-r--r--activerecord/lib/active_record/scoping.rb2
-rw-r--r--activerecord/lib/active_record/scoping/default.rb102
-rw-r--r--activerecord/lib/active_record/scoping/named.rb14
-rw-r--r--activerecord/lib/active_record/secure_token.rb2
-rw-r--r--activerecord/lib/active_record/statement_cache.rb14
-rw-r--r--activerecord/lib/active_record/store.rb7
-rw-r--r--activerecord/lib/active_record/table_metadata.rb12
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb34
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb10
-rw-r--r--activerecord/lib/active_record/tasks/postgresql_database_tasks.rb30
-rw-r--r--activerecord/lib/active_record/tasks/sqlite_database_tasks.rb12
-rw-r--r--activerecord/lib/active_record/timestamp.rb62
-rw-r--r--activerecord/lib/active_record/touch_later.rb2
-rw-r--r--activerecord/lib/active_record/transactions.rb81
-rw-r--r--activerecord/lib/active_record/type.rb10
-rw-r--r--activerecord/lib/active_record/type/adapter_specific_registry.rb4
-rw-r--r--activerecord/lib/active_record/type/decimal_without_scale.rb9
-rw-r--r--activerecord/lib/active_record/type/internal/abstract_json.rb6
-rw-r--r--activerecord/lib/active_record/type/text.rb9
-rw-r--r--activerecord/lib/active_record/type/type_map.rb6
-rw-r--r--activerecord/lib/active_record/type/unsigned_integer.rb (renamed from activemodel/lib/active_model/type/unsigned_integer.rb)4
-rw-r--r--activerecord/lib/active_record/type_caster/connection.rb2
-rw-r--r--activerecord/lib/active_record/type_caster/map.rb2
-rw-r--r--activerecord/lib/active_record/validations.rb8
-rw-r--r--activerecord/lib/active_record/validations/associated.rb2
-rw-r--r--activerecord/lib/active_record/validations/presence.rb2
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb15
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/migration_generator.rb14
-rw-r--r--activerecord/lib/rails/generators/active_record/model/model_generator.rb2
-rw-r--r--activerecord/test/active_record/connection_adapters/fake_adapter.rb2
-rw-r--r--activerecord/test/assets/schema_dump_5_1.yml345
-rw-r--r--activerecord/test/cases/adapter_test.rb92
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb7
-rw-r--r--activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb29
-rw-r--r--activerecord/test/cases/adapters/mysql2/enum_test.rb5
-rw-r--r--activerecord/test/cases/adapters/mysql2/json_test.rb42
-rw-r--r--activerecord/test/cases/adapters/mysql2/legacy_migration_test.rb60
-rw-r--r--activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb13
-rw-r--r--activerecord/test/cases/adapters/mysql2/reserved_word_test.rb18
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_test.rb10
-rw-r--r--activerecord/test/cases/adapters/mysql2/transaction_test.rb24
-rw-r--r--activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb7
-rw-r--r--activerecord/test/cases/adapters/postgresql/active_schema_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/array_test.rb55
-rw-r--r--activerecord/test/cases/adapters/postgresql/bit_string_test.rb5
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/case_insensitive_test.rb26
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb18
-rw-r--r--activerecord/test/cases/adapters/postgresql/geometric_test.rb14
-rw-r--r--activerecord/test/cases/adapters/postgresql/hstore_test.rb91
-rw-r--r--activerecord/test/cases/adapters/postgresql/json_test.rb48
-rw-r--r--activerecord/test/cases/adapters/postgresql/legacy_migration_test.rb54
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/prepared_statements_disabled_test.rb25
-rw-r--r--activerecord/test/cases/adapters/postgresql/prepared_statements_test.rb21
-rw-r--r--activerecord/test/cases/adapters/postgresql/quoting_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/range_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/rename_table_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb10
-rw-r--r--activerecord/test/cases/adapters/postgresql/timestamp_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/transaction_test.rb71
-rw-r--r--activerecord/test/cases/adapters/postgresql/type_lookup_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/utils_test.rb12
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb66
-rw-r--r--activerecord/test/cases/adapters/sqlite3/copy_table_test.rb16
-rw-r--r--activerecord/test/cases/adapters/sqlite3/legacy_migration_test.rb59
-rw-r--r--activerecord/test/cases/adapters/sqlite3/quoting_test.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb26
-rw-r--r--activerecord/test/cases/ar_schema_test.rb20
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb56
-rw-r--r--activerecord/test/cases/associations/callbacks_test.rb10
-rw-r--r--activerecord/test/cases/associations/cascaded_eager_loading_test.rb38
-rw-r--r--activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb4
-rw-r--r--activerecord/test/cases/associations/eager_load_nested_include_test.rb8
-rw-r--r--activerecord/test/cases/associations/eager_test.rb160
-rw-r--r--activerecord/test/cases/associations/extension_test.rb4
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb63
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb173
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb102
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb47
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb28
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb14
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb53
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb70
-rw-r--r--activerecord/test/cases/associations/left_outer_join_association_test.rb32
-rw-r--r--activerecord/test/cases/associations_test.rb42
-rw-r--r--activerecord/test/cases/attribute_methods/read_test.rb6
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb53
-rw-r--r--activerecord/test/cases/attribute_set_test.rb2
-rw-r--r--activerecord/test/cases/attributes_test.rb16
-rw-r--r--activerecord/test/cases/autosave_association_test.rb81
-rw-r--r--activerecord/test/cases/base_test.rb82
-rw-r--r--activerecord/test/cases/batches_test.rb20
-rw-r--r--activerecord/test/cases/calculations_test.rb26
-rw-r--r--activerecord/test/cases/callbacks_test.rb20
-rw-r--r--activerecord/test/cases/collection_cache_key_test.rb6
-rw-r--r--activerecord/test/cases/column_definition_test.rb9
-rw-r--r--activerecord/test/cases/comment_test.rb3
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb4
-rw-r--r--activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb72
-rw-r--r--activerecord/test/cases/connection_adapters/quoting_test.rb13
-rw-r--r--activerecord/test/cases/connection_adapters/schema_cache_test.rb54
-rw-r--r--activerecord/test/cases/connection_management_test.rb2
-rw-r--r--activerecord/test/cases/connection_pool_test.rb126
-rw-r--r--activerecord/test/cases/connection_specification/resolver_test.rb20
-rw-r--r--activerecord/test/cases/counter_cache_test.rb142
-rw-r--r--activerecord/test/cases/database_statements_test.rb13
-rw-r--r--activerecord/test/cases/defaults_test.rb88
-rw-r--r--activerecord/test/cases/dirty_test.rb101
-rw-r--r--activerecord/test/cases/enum_test.rb40
-rw-r--r--activerecord/test/cases/errors_test.rb2
-rw-r--r--activerecord/test/cases/explain_test.rb4
-rw-r--r--activerecord/test/cases/finder_respond_to_test.rb2
-rw-r--r--activerecord/test/cases/finder_test.rb124
-rw-r--r--activerecord/test/cases/fixture_set/file_test.rb4
-rw-r--r--activerecord/test/cases/fixtures_test.rb45
-rw-r--r--activerecord/test/cases/forbidden_attributes_protection_test.rb6
-rw-r--r--activerecord/test/cases/helper.rb20
-rw-r--r--activerecord/test/cases/hot_compatibility_test.rb10
-rw-r--r--activerecord/test/cases/i18n_test.rb14
-rw-r--r--activerecord/test/cases/inheritance_test.rb20
-rw-r--r--activerecord/test/cases/integration_test.rb10
-rw-r--r--activerecord/test/cases/invertible_migration_test.rb58
-rw-r--r--activerecord/test/cases/json_serialization_test.rb18
-rw-r--r--activerecord/test/cases/locking_test.rb147
-rw-r--r--activerecord/test/cases/migration/change_schema_test.rb38
-rw-r--r--activerecord/test/cases/migration/change_table_test.rb17
-rw-r--r--activerecord/test/cases/migration/column_attributes_test.rb8
-rw-r--r--activerecord/test/cases/migration/columns_test.rb16
-rw-r--r--activerecord/test/cases/migration/command_recorder_test.rb26
-rw-r--r--activerecord/test/cases/migration/compatibility_test.rb18
-rw-r--r--activerecord/test/cases/migration/create_join_table_test.rb26
-rw-r--r--activerecord/test/cases/migration/foreign_key_test.rb8
-rw-r--r--activerecord/test/cases/migration/index_test.rb12
-rw-r--r--activerecord/test/cases/migration/references_foreign_key_test.rb12
-rw-r--r--activerecord/test/cases/migration/references_index_test.rb4
-rw-r--r--activerecord/test/cases/migration/references_statements_test.rb2
-rw-r--r--activerecord/test/cases/migration/rename_table_test.rb6
-rw-r--r--activerecord/test/cases/migration_test.rb39
-rw-r--r--activerecord/test/cases/migrator_test.rb12
-rw-r--r--activerecord/test/cases/modules_test.rb2
-rw-r--r--activerecord/test/cases/multiparameter_attributes_test.rb15
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb50
-rw-r--r--activerecord/test/cases/nested_attributes_with_callbacks_test.rb22
-rw-r--r--activerecord/test/cases/persistence_test.rb32
-rw-r--r--activerecord/test/cases/primary_keys_test.rb131
-rw-r--r--activerecord/test/cases/query_cache_test.rb193
-rw-r--r--activerecord/test/cases/quoting_test.rb36
-rw-r--r--activerecord/test/cases/reflection_test.rb20
-rw-r--r--activerecord/test/cases/relation/merging_test.rb6
-rw-r--r--activerecord/test/cases/relation/mutation_test.rb22
-rw-r--r--activerecord/test/cases/relation/or_test.rb6
-rw-r--r--activerecord/test/cases/relation/where_chain_test.rb2
-rw-r--r--activerecord/test/cases/relation/where_test.rb16
-rw-r--r--activerecord/test/cases/relation_test.rb27
-rw-r--r--activerecord/test/cases/relations_test.rb127
-rw-r--r--activerecord/test/cases/result_test.rb10
-rw-r--r--activerecord/test/cases/sanitize_test.rb4
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb59
-rw-r--r--activerecord/test/cases/schema_loading_test.rb2
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb19
-rw-r--r--activerecord/test/cases/scoping/named_scoping_test.rb28
-rw-r--r--activerecord/test/cases/scoping/relation_scoping_test.rb18
-rw-r--r--activerecord/test/cases/secure_token_test.rb2
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb15
-rw-r--r--activerecord/test/cases/suppressor_test.rb2
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb54
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb44
-rw-r--r--activerecord/test/cases/tasks/postgresql_rake_test.rb45
-rw-r--r--activerecord/test/cases/tasks/sqlite_rake_test.rb6
-rw-r--r--activerecord/test/cases/test_case.rb30
-rw-r--r--activerecord/test/cases/test_fixtures_test.rb20
-rw-r--r--activerecord/test/cases/timestamp_test.rb23
-rw-r--r--activerecord/test/cases/touch_later_test.rb9
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb97
-rw-r--r--activerecord/test/cases/transaction_isolation_test.rb6
-rw-r--r--activerecord/test/cases/transactions_test.rb25
-rw-r--r--activerecord/test/cases/type/date_time_test.rb2
-rw-r--r--activerecord/test/cases/type/unsigned_integer_test.rb (renamed from activemodel/test/cases/type/unsigned_integer_test.rb)5
-rw-r--r--activerecord/test/cases/validations/association_validation_test.rb2
-rw-r--r--activerecord/test/cases/validations/i18n_generate_message_validation_test.rb10
-rw-r--r--activerecord/test/cases/validations/i18n_validation_test.rb20
-rw-r--r--activerecord/test/cases/validations/length_validation_test.rb2
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb16
-rw-r--r--activerecord/test/cases/view_test.rb52
-rw-r--r--activerecord/test/cases/yaml_serialization_test.rb6
-rw-r--r--activerecord/test/config.example.yml3
-rw-r--r--activerecord/test/fixtures/all/namespaced/accounts.yml2
-rw-r--r--activerecord/test/fixtures/books.yml1
-rw-r--r--activerecord/test/fixtures/naked/yml/courses_with_invalid_key.yml3
-rw-r--r--activerecord/test/fixtures/other_dogs.yml2
-rw-r--r--activerecord/test/models/admin/user.rb4
-rw-r--r--activerecord/test/models/author.rb12
-rw-r--r--activerecord/test/models/book.rb3
-rw-r--r--activerecord/test/models/boolean.rb3
-rw-r--r--activerecord/test/models/bulb.rb6
-rw-r--r--activerecord/test/models/comment.rb4
-rw-r--r--activerecord/test/models/company.rb4
-rw-r--r--activerecord/test/models/company_in_module.rb2
-rw-r--r--activerecord/test/models/customer.rb2
-rw-r--r--activerecord/test/models/developer.rb2
-rw-r--r--activerecord/test/models/eye.rb4
-rw-r--r--activerecord/test/models/invoice.rb2
-rw-r--r--activerecord/test/models/member.rb2
-rw-r--r--activerecord/test/models/other_dog.rb5
-rw-r--r--activerecord/test/models/parrot.rb5
-rw-r--r--activerecord/test/models/pirate.rb16
-rw-r--r--activerecord/test/models/post.rb21
-rw-r--r--activerecord/test/models/project.rb8
-rw-r--r--activerecord/test/models/sponsor.rb2
-rw-r--r--activerecord/test/models/subject.rb2
-rw-r--r--activerecord/test/models/topic.rb2
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb1
-rw-r--r--activerecord/test/schema/schema.rb28
-rw-r--r--activerecord/test/support/config.rb7
-rw-r--r--activerecord/test/support/connection.rb1
-rw-r--r--activesupport/CHANGELOG.md149
-rw-r--r--activesupport/MIT-LICENSE2
-rw-r--r--activesupport/Rakefile2
-rwxr-xr-xactivesupport/bin/generate_tables12
-rwxr-xr-xactivesupport/bin/test2
-rw-r--r--activesupport/lib/active_support.rb2
-rw-r--r--activesupport/lib/active_support/backtrace_cleaner.rb6
-rw-r--r--activesupport/lib/active_support/cache.rb46
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb19
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb34
-rw-r--r--activesupport/lib/active_support/cache/memory_store.rb18
-rw-r--r--activesupport/lib/active_support/cache/null_store.rb8
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache.rb31
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb4
-rw-r--r--activesupport/lib/active_support/callbacks.rb376
-rw-r--r--activesupport/lib/active_support/concurrency/latch.rb25
-rw-r--r--activesupport/lib/active_support/concurrency/share_lock.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/array/conversions.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/class/subclasses.rb18
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/calculations.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/hash/compact.rb40
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/hash/reverse_merge.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/transform_values.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/debugger.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/reporting.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/load_error.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/module.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/module/aliasing.rb48
-rw-r--r--activesupport/lib/active_support/core_ext/module/attr_internal.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/module/introspection.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/module/method_transplanting.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/module/qualified_const.rb70
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/conversions.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/object/duplicable.rb112
-rw-r--r--activesupport/lib/active_support/core_ext/regexp.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/securerandom.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/string/access.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/string/indent.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/struct.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/time/marshal.rb3
-rw-r--r--activesupport/lib/active_support/dependencies.rb9
-rw-r--r--activesupport/lib/active_support/deprecation/instance_delegator.rb13
-rw-r--r--activesupport/lib/active_support/duration.rb37
-rw-r--r--activesupport/lib/active_support/duration/iso8601_parser.rb4
-rw-r--r--activesupport/lib/active_support/duration/iso8601_serializer.rb4
-rw-r--r--activesupport/lib/active_support/execution_wrapper.rb33
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb16
-rw-r--r--activesupport/lib/active_support/gzip.rb2
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb30
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb19
-rw-r--r--activesupport/lib/active_support/inflector/inflections.rb8
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb26
-rw-r--r--activesupport/lib/active_support/inflector/transliterate.rb6
-rw-r--r--activesupport/lib/active_support/json/encoding.rb5
-rw-r--r--activesupport/lib/active_support/key_generator.rb6
-rw-r--r--activesupport/lib/active_support/lazy_load_hooks.rb8
-rw-r--r--activesupport/lib/active_support/log_subscriber.rb4
-rw-r--r--activesupport/lib/active_support/log_subscriber/test_helper.rb2
-rw-r--r--activesupport/lib/active_support/logger.rb4
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb9
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb10
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb76
-rw-r--r--activesupport/lib/active_support/notifications.rb4
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb2
-rw-r--r--activesupport/lib/active_support/notifications/instrumenter.rb2
-rw-r--r--activesupport/lib/active_support/number_helper.rb4
-rw-r--r--activesupport/lib/active_support/number_helper/number_converter.rb10
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_human_converter.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb8
-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.rb2
-rw-r--r--activesupport/lib/active_support/ordered_hash.rb4
-rw-r--r--activesupport/lib/active_support/ordered_options.rb4
-rw-r--r--activesupport/lib/active_support/per_thread_registry.rb4
-rw-r--r--activesupport/lib/active_support/rails.rb6
-rw-r--r--activesupport/lib/active_support/rescuable.rb10
-rw-r--r--activesupport/lib/active_support/subscriber.rb9
-rw-r--r--activesupport/lib/active_support/tagged_logging.rb11
-rw-r--r--activesupport/lib/active_support/test_case.rb11
-rw-r--r--activesupport/lib/active_support/testing/assertions.rb15
-rw-r--r--activesupport/lib/active_support/testing/autorun.rb11
-rw-r--r--activesupport/lib/active_support/testing/declarative.rb2
-rw-r--r--activesupport/lib/active_support/testing/isolation.rb12
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb9
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb40
-rw-r--r--activesupport/lib/active_support/xml_mini.rb28
-rw-r--r--activesupport/lib/active_support/xml_mini/jdom.rb84
-rw-r--r--activesupport/lib/active_support/xml_mini/libxml.rb2
-rw-r--r--activesupport/lib/active_support/xml_mini/nokogiri.rb2
-rw-r--r--activesupport/lib/active_support/xml_mini/rexml.rb4
-rw-r--r--activesupport/test/autoload_test.rb12
-rw-r--r--activesupport/test/autoloading_fixtures/prepend.rb8
-rw-r--r--activesupport/test/autoloading_fixtures/prepend/sub_class_conflict.rb2
-rw-r--r--activesupport/test/benchmarkable_test.rb4
-rw-r--r--activesupport/test/broadcast_logger_test.rb24
-rw-r--r--activesupport/test/caching_test.rb78
-rw-r--r--activesupport/test/callback_inheritance_test.rb10
-rw-r--r--activesupport/test/callbacks_test.rb182
-rw-r--r--activesupport/test/class_cache_test.rb2
-rw-r--r--activesupport/test/concern_test.rb6
-rw-r--r--activesupport/test/configurable_test.rb4
-rw-r--r--activesupport/test/constantize_test_cases.rb5
-rw-r--r--activesupport/test/core_ext/array/conversions_test.rb53
-rw-r--r--activesupport/test/core_ext/array/grouping_test.rb6
-rw-r--r--activesupport/test/core_ext/class_test.rb10
-rw-r--r--activesupport/test/core_ext/date_and_time_behavior.rb294
-rw-r--r--activesupport/test/core_ext/date_and_time_compatibility_test.rb4
-rw-r--r--activesupport/test/core_ext/date_ext_test.rb142
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb202
-rw-r--r--activesupport/test/core_ext/duration_test.rb84
-rw-r--r--activesupport/test/core_ext/enumerable_test.rb34
-rw-r--r--activesupport/test/core_ext/hash/transform_keys_test.rb2
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb281
-rw-r--r--activesupport/test/core_ext/load_error_test.rb8
-rw-r--r--activesupport/test/core_ext/marshal_test.rb2
-rw-r--r--activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb6
-rw-r--r--activesupport/test/core_ext/module/attribute_accessor_test.rb2
-rw-r--r--activesupport/test/core_ext/module/concerning_test.rb8
-rw-r--r--activesupport/test/core_ext/module/qualified_const_test.rb118
-rw-r--r--activesupport/test/core_ext/module_test.rb253
-rw-r--r--activesupport/test/core_ext/numeric_ext_test.rb98
-rw-r--r--activesupport/test/core_ext/object/blank_test.rb2
-rw-r--r--activesupport/test/core_ext/object/deep_dup_test.rb8
-rw-r--r--activesupport/test/core_ext/object/duplicable_test.rb10
-rw-r--r--activesupport/test/core_ext/object/inclusion_test.rb6
-rw-r--r--activesupport/test/core_ext/object/instance_variables_test.rb2
-rw-r--r--activesupport/test/core_ext/object/to_query_test.rb8
-rw-r--r--activesupport/test/core_ext/range_ext_test.rb12
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb36
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb606
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb236
-rw-r--r--activesupport/test/dependencies_test.rb38
-rw-r--r--activesupport/test/deprecation_test.rb8
-rw-r--r--activesupport/test/descendants_tracker_test_cases.rb2
-rw-r--r--activesupport/test/evented_file_update_checker_test.rb4
-rw-r--r--activesupport/test/executor_test.rb69
-rw-r--r--activesupport/test/file_update_checker_shared_tests.rb18
-rw-r--r--activesupport/test/gzip_test.rb2
-rw-r--r--activesupport/test/inflector_test.rb64
-rw-r--r--activesupport/test/inflector_test_cases.rb2
-rw-r--r--activesupport/test/json/decoding_test.rb76
-rw-r--r--activesupport/test/json/encoding_test.rb92
-rw-r--r--activesupport/test/json/encoding_test_cases.rb24
-rw-r--r--activesupport/test/logger_test.rb42
-rw-r--r--activesupport/test/message_encryptor_test.rb12
-rw-r--r--activesupport/test/message_verifier_test.rb2
-rw-r--r--activesupport/test/multibyte_chars_test.rb42
-rw-r--r--activesupport/test/multibyte_conformance_test.rb6
-rw-r--r--activesupport/test/multibyte_grapheme_break_conformance_test.rb10
-rw-r--r--activesupport/test/multibyte_normalization_conformance_test.rb10
-rw-r--r--activesupport/test/multibyte_test_helpers.rb4
-rw-r--r--activesupport/test/notifications/evented_notification_test.rb2
-rw-r--r--activesupport/test/notifications/instrumenter_test.rb4
-rw-r--r--activesupport/test/notifications_test.rb4
-rw-r--r--activesupport/test/number_helper_i18n_test.rb10
-rw-r--r--activesupport/test/number_helper_test.rb89
-rw-r--r--activesupport/test/option_merger_test.rb6
-rw-r--r--activesupport/test/ordered_hash_test.rb24
-rw-r--r--activesupport/test/reloader_test.rb8
-rw-r--r--activesupport/test/rescuable_test.rb2
-rw-r--r--activesupport/test/safe_buffer_test.rb6
-rw-r--r--activesupport/test/share_lock_test.rb2
-rw-r--r--activesupport/test/test_case_test.rb22
-rw-r--r--activesupport/test/time_zone_test.rb124
-rw-r--r--activesupport/test/transliterate_test.rb4
-rw-r--r--activesupport/test/xml_mini/jdom_engine_test.rb157
-rw-r--r--activesupport/test/xml_mini/libxml_engine_test.rb202
-rw-r--r--activesupport/test/xml_mini/libxmlsax_engine_test.rb197
-rw-r--r--activesupport/test/xml_mini/nokogiri_engine_test.rb217
-rw-r--r--activesupport/test/xml_mini/nokogirisax_engine_test.rb218
-rw-r--r--activesupport/test/xml_mini/rexml_engine_test.rb43
-rw-r--r--activesupport/test/xml_mini/xml_mini_engine_test.rb257
-rw-r--r--activesupport/test/xml_mini_test.rb64
-rwxr-xr-xci/travis.rb2
-rw-r--r--guides/Rakefile5
-rw-r--r--guides/assets/stylesheets/main.css3
-rw-r--r--[-rwxr-xr-x]guides/assets/stylesheets/responsive-tables.css0
-rw-r--r--guides/assets/stylesheets/syntaxhighlighter/shCore.css2
-rw-r--r--guides/bug_report_templates/action_controller_master.rb2
-rw-r--r--guides/bug_report_templates/active_job_gem.rb32
-rw-r--r--guides/bug_report_templates/active_job_master.rb32
-rw-r--r--guides/bug_report_templates/active_record_master.rb1
-rw-r--r--guides/bug_report_templates/active_record_migrations_gem.rb65
-rw-r--r--guides/bug_report_templates/active_record_migrations_master.rb65
-rw-r--r--guides/bug_report_templates/benchmark.rb50
-rw-r--r--guides/bug_report_templates/generic_master.rb1
-rw-r--r--guides/rails_guides/generator.rb2
-rw-r--r--guides/rails_guides/helpers.rb4
-rw-r--r--guides/rails_guides/indexer.rb2
-rw-r--r--guides/rails_guides/kindle.rb32
-rw-r--r--guides/rails_guides/levenshtein.rb8
-rw-r--r--guides/rails_guides/markdown.rb2
-rw-r--r--guides/rails_guides/markdown/renderer.rb21
-rw-r--r--guides/source/2_2_release_notes.md1
-rw-r--r--guides/source/3_1_release_notes.md2
-rw-r--r--guides/source/5_0_release_notes.md47
-rw-r--r--guides/source/action_cable_overview.md14
-rw-r--r--guides/source/action_controller_overview.md18
-rw-r--r--guides/source/action_mailer_basics.md2
-rw-r--r--guides/source/action_view_overview.md16
-rw-r--r--guides/source/active_job_basics.md7
-rw-r--r--guides/source/active_record_callbacks.md8
-rw-r--r--guides/source/active_record_migrations.md24
-rw-r--r--guides/source/active_record_postgresql.md4
-rw-r--r--guides/source/active_record_querying.md16
-rw-r--r--guides/source/active_support_core_extensions.md107
-rw-r--r--guides/source/active_support_instrumentation.md31
-rw-r--r--guides/source/asset_pipeline.md59
-rw-r--r--guides/source/association_basics.md80
-rw-r--r--guides/source/caching_with_rails.md2
-rw-r--r--guides/source/command_line.md4
-rw-r--r--guides/source/configuring.md70
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md49
-rw-r--r--guides/source/credits.html.erb4
-rw-r--r--guides/source/development_dependencies_install.md47
-rw-r--r--guides/source/documents.yaml4
-rw-r--r--guides/source/engines.md6
-rw-r--r--guides/source/form_helpers.md2
-rw-r--r--guides/source/generators.md30
-rw-r--r--guides/source/getting_started.md28
-rw-r--r--guides/source/i18n.md7
-rw-r--r--guides/source/initialization.md167
-rw-r--r--guides/source/layout.html.erb2
-rw-r--r--guides/source/layouts_and_rendering.md8
-rw-r--r--guides/source/plugins.md2
-rw-r--r--guides/source/rails_on_rack.md1
-rw-r--r--guides/source/routing.md40
-rw-r--r--guides/source/security.md10
-rw-r--r--guides/source/testing.md51
-rw-r--r--guides/source/upgrading_ruby_on_rails.md32
-rw-r--r--guides/w3c_validator.rb4
-rw-r--r--railties/CHANGELOG.md52
-rw-r--r--railties/MIT-LICENSE2
-rw-r--r--railties/Rakefile1
-rwxr-xr-xrailties/bin/test4
-rw-r--r--railties/lib/rails.rb6
-rw-r--r--railties/lib/rails/app_loader.rb2
-rw-r--r--railties/lib/rails/application.rb10
-rw-r--r--railties/lib/rails/application/default_middleware_stack.rb3
-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.rb8
-rw-r--r--railties/lib/rails/command.rb99
-rw-r--r--railties/lib/rails/command/actions.rb42
-rw-r--r--railties/lib/rails/command/base.rb143
-rw-r--r--railties/lib/rails/command/behavior.rb123
-rw-r--r--railties/lib/rails/command/environment_argument.rb34
-rw-r--r--railties/lib/rails/commands.rb6
-rw-r--r--railties/lib/rails/commands/application/application_command.rb (renamed from railties/lib/rails/commands/application.rb)18
-rw-r--r--railties/lib/rails/commands/commands_tasks.rb180
-rw-r--r--railties/lib/rails/commands/console/console_command.rb (renamed from railties/lib/rails/commands/console.rb)51
-rw-r--r--railties/lib/rails/commands/console_helper.rb34
-rw-r--r--railties/lib/rails/commands/dbconsole/dbconsole_command.rb (renamed from railties/lib/rails/commands/dbconsole.rb)96
-rw-r--r--railties/lib/rails/commands/destroy.rb11
-rw-r--r--railties/lib/rails/commands/destroy/destroy_command.rb21
-rw-r--r--railties/lib/rails/commands/generate.rb13
-rw-r--r--railties/lib/rails/commands/generate/generate_command.rb23
-rw-r--r--railties/lib/rails/commands/help/USAGE27
-rw-r--r--railties/lib/rails/commands/help/help_command.rb13
-rw-r--r--railties/lib/rails/commands/new/new_command.rb15
-rw-r--r--railties/lib/rails/commands/plugin.rb24
-rw-r--r--railties/lib/rails/commands/plugin/plugin_command.rb43
-rw-r--r--railties/lib/rails/commands/rake/rake_command.rb49
-rw-r--r--railties/lib/rails/commands/rake_proxy.rb41
-rw-r--r--railties/lib/rails/commands/runner.rb71
-rw-r--r--railties/lib/rails/commands/runner/USAGE17
-rw-r--r--railties/lib/rails/commands/runner/runner_command.rb46
-rw-r--r--railties/lib/rails/commands/server.rb139
-rw-r--r--railties/lib/rails/commands/server/server_command.rb173
-rw-r--r--railties/lib/rails/commands/test.rb9
-rw-r--r--railties/lib/rails/commands/test/test_command.rb20
-rw-r--r--railties/lib/rails/commands/version/version_command.rb9
-rw-r--r--railties/lib/rails/configuration.rb4
-rw-r--r--railties/lib/rails/console/app.rb4
-rw-r--r--railties/lib/rails/engine.rb44
-rw-r--r--railties/lib/rails/engine/commands.rb6
-rw-r--r--railties/lib/rails/engine/commands_tasks.rb118
-rw-r--r--railties/lib/rails/engine/configuration.rb2
-rw-r--r--railties/lib/rails/generators.rb481
-rw-r--r--railties/lib/rails/generators/actions.rb32
-rw-r--r--railties/lib/rails/generators/actions/create_migration.rb6
-rw-r--r--railties/lib/rails/generators/active_model.rb6
-rw-r--r--railties/lib/rails/generators/app_base.rb87
-rw-r--r--railties/lib/rails/generators/base.rb40
-rw-r--r--railties/lib/rails/generators/erb.rb2
-rw-r--r--railties/lib/rails/generators/erb/mailer/mailer_generator.rb2
-rw-r--r--railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb2
-rw-r--r--railties/lib/rails/generators/generated_attribute.rb4
-rw-r--r--railties/lib/rails/generators/migration.rb2
-rw-r--r--railties/lib/rails/generators/named_base.rb84
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb50
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt10
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/setup.tt (renamed from railties/lib/rails/generators/rails/app/templates/bin/setup)6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/update.tt (renamed from railties/lib/rails/generators/rails/app/templates/bin/update)3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/yarn9
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt11
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/puma.rb21
-rw-r--r--railties/lib/rails/generators/rails/app/templates/gitignore5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/package.json5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/test_helper.rb1
-rw-r--r--railties/lib/rails/generators/rails/assets/assets_generator.rb2
-rw-r--r--railties/lib/rails/generators/rails/controller/controller_generator.rb27
-rw-r--r--railties/lib/rails/generators/rails/generator/generator_generator.rb2
-rw-r--r--railties/lib/rails/generators/rails/plugin/plugin_generator.rb10
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/bin/test.tt4
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb3
-rw-r--r--railties/lib/rails/generators/resource_helpers.rb6
-rw-r--r--railties/lib/rails/generators/test_unit/generator/generator_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb2
-rw-r--r--railties/lib/rails/generators/testing/behaviour.rb10
-rw-r--r--railties/lib/rails/info_controller.rb8
-rw-r--r--railties/lib/rails/initializable.rb2
-rw-r--r--railties/lib/rails/mailers_controller.rb16
-rw-r--r--railties/lib/rails/paths.rb3
-rw-r--r--railties/lib/rails/rack/logger.rb66
-rw-r--r--railties/lib/rails/railtie.rb31
-rw-r--r--railties/lib/rails/railtie/configurable.rb2
-rw-r--r--railties/lib/rails/source_annotation_extractor.rb8
-rw-r--r--railties/lib/rails/tasks/log.rake8
-rw-r--r--railties/lib/rails/tasks/routes.rake2
-rw-r--r--railties/lib/rails/tasks/statistics.rake3
-rw-r--r--railties/lib/rails/templates/rails/mailers/email.html.erb5
-rw-r--r--railties/lib/rails/test_unit/minitest_plugin.rb26
-rw-r--r--railties/lib/rails/test_unit/railtie.rb4
-rw-r--r--railties/test/app_loader_test.rb2
-rw-r--r--railties/test/application/asset_debugging_test.rb99
-rw-r--r--railties/test/application/assets_test.rb8
-rw-r--r--railties/test/application/bin_setup_test.rb1
-rw-r--r--railties/test/application/configuration/custom_test.rb2
-rw-r--r--railties/test/application/configuration_test.rb63
-rw-r--r--railties/test/application/console_test.rb2
-rw-r--r--railties/test/application/generators_test.rb27
-rw-r--r--railties/test/application/initializers/frameworks_test.rb10
-rw-r--r--railties/test/application/initializers/hooks_test.rb4
-rw-r--r--railties/test/application/initializers/i18n_test.rb10
-rw-r--r--railties/test/application/initializers/load_path_test.rb4
-rw-r--r--railties/test/application/loading_test.rb4
-rw-r--r--railties/test/application/middleware/cache_test.rb10
-rw-r--r--railties/test/application/middleware/session_test.rb36
-rw-r--r--railties/test/application/middleware_test.rb79
-rw-r--r--railties/test/application/paths_test.rb6
-rw-r--r--railties/test/application/rake/log_test.rb33
-rw-r--r--railties/test/application/rake/migrations_test.rb2
-rw-r--r--railties/test/application/rake/restart_test.rb14
-rw-r--r--railties/test/application/rake_test.rb62
-rw-r--r--railties/test/application/routing_test.rb30
-rw-r--r--railties/test/application/runner_test.rb19
-rw-r--r--railties/test/application/test_runner_test.rb12
-rw-r--r--railties/test/application/test_test.rb38
-rw-r--r--railties/test/application/url_generation_test.rb2
-rw-r--r--railties/test/backtrace_cleaner_test.rb2
-rw-r--r--railties/test/code_statistics_calculator_test.rb4
-rw-r--r--railties/test/commands/console_test.rb36
-rw-r--r--railties/test/commands/dbconsole_test.rb107
-rw-r--r--railties/test/commands/server_test.rb82
-rw-r--r--railties/test/generators/actions_test.rb8
-rw-r--r--railties/test/generators/api_app_generator_test.rb7
-rw-r--r--railties/test/generators/app_generator_test.rb70
-rw-r--r--railties/test/generators/controller_generator_test.rb4
-rw-r--r--railties/test/generators/generator_generator_test.rb8
-rw-r--r--railties/test/generators/generator_test.rb2
-rw-r--r--railties/test/generators/helper_generator_test.rb2
-rw-r--r--railties/test/generators/mailer_generator_test.rb6
-rw-r--r--railties/test/generators/model_generator_test.rb20
-rw-r--r--railties/test/generators/named_base_test.rb6
-rw-r--r--railties/test/generators/plugin_generator_test.rb25
-rw-r--r--railties/test/generators/plugin_test_runner_test.rb6
-rw-r--r--railties/test/generators/shared_generator_tests.rb10
-rw-r--r--railties/test/generators_test.rb26
-rw-r--r--railties/test/isolation/abstract_unit.rb16
-rw-r--r--railties/test/json_params_parsing_test.rb20
-rw-r--r--railties/test/path_generation_test.rb2
-rw-r--r--railties/test/paths_test.rb28
-rw-r--r--railties/test/rack_logger_test.rb2
-rw-r--r--railties/test/rails_info_test.rb10
-rw-r--r--railties/test/railties/engine_test.rb32
-rw-r--r--railties/test/railties/generators_test.rb2
-rw-r--r--railties/test/railties/mounted_engine_test.rb20
-rw-r--r--railties/test/railties/railtie_test.rb9
-rwxr-xr-xtools/profile6
-rw-r--r--tools/test.rb5
1268 files changed, 28271 insertions, 16462 deletions
diff --git a/.rubocop.yml b/.rubocop.yml
index dafdd063bf..629f5d86ea 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -1,11 +1,12 @@
AllCops:
- TargetRubyVersion: 2.3
+ TargetRubyVersion: 2.2
# RuboCop has a bunch of cops enabled by default. This setting tells RuboCop
# to ignore them, so only the ones explicitly set in this file are enabled.
DisabledByDefault: true
Exclude:
- '**/templates/**/*'
- '**/vendor/**/*'
+ - 'actionpack/lib/action_dispatch/journey/parser.rb'
# Prefer &&/|| over and/or.
Style/AndOr:
@@ -20,6 +21,10 @@ Style/BracesAroundHashParameters:
Style/CaseIndentation:
Enabled: true
+# Align comments with method definitions.
+Style/CommentIndentation:
+ Enabled: true
+
# No extra empty lines.
Style/EmptyLines:
Enabled: true
@@ -46,10 +51,43 @@ Style/IndentationConsistency:
Style/IndentationWidth:
Enabled: true
+Style/SpaceAfterColon:
+ Enabled: true
+
+Style/SpaceAfterComma:
+ Enabled: true
+
+Style/SpaceAroundEqualsInParameterDefault:
+ Enabled: true
+
+Style/SpaceAroundKeyword:
+ Enabled: true
+
+Style/SpaceAroundOperators:
+ Enabled: true
+
+Style/SpaceBeforeFirstArg:
+ Enabled: true
+
# Defining a method with parameters needs parentheses.
Style/MethodDefParentheses:
Enabled: true
+# Use `foo {}` not `foo{}`.
+Style/SpaceBeforeBlockBraces:
+ Enabled: true
+
+# Use `foo { bar }` not `foo {bar}`.
+Style/SpaceInsideBlockBraces:
+ Enabled: true
+
+# Use `{ a: 1 }` not `{a:1}`.
+Style/SpaceInsideHashLiteralBraces:
+ Enabled: true
+
+Style/SpaceInsideParens:
+ Enabled: true
+
# Check quotes usage according to lint rule below.
Style/StringLiterals:
Enabled: true
diff --git a/.travis.yml b/.travis.yml
index f01b58ecb3..ad65de48aa 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,7 +10,6 @@ cache:
services:
- memcached
- redis
- - rabbitmq
addons:
postgresql: "9.4"
@@ -18,12 +17,11 @@ addons:
bundler_args: --without test --jobs 3 --retry 3
before_install:
- "rm ${BUNDLE_GEMFILE}.lock"
+ - "gem update bundler"
- "[ -f /tmp/beanstalkd-1.10/Makefile ] || (curl -L https://github.com/kr/beanstalkd/archive/v1.10.tar.gz | tar xz -C /tmp)"
- "pushd /tmp/beanstalkd-1.10 && make && (./beanstalkd &); popd"
before_script:
- - bundle update
-
# Set Sauce Labs username and access key. Obfuscated, purposefully not encrypted.
# Decodes to e.g. `export VARIABLE=VALUE`
- $(base64 --decode <<< "ZXhwb3J0IFNBVUNFX0FDQ0VTU19LRVk9YTAzNTM0M2YtZTkyMi00MGIzLWFhM2MtMDZiM2VhNjM1YzQ4")
@@ -32,44 +30,62 @@ before_script:
script: 'ci/travis.rb'
env:
+ global:
+ - "JRUBY_OPTS='--dev -J-Xmx1024M'"
matrix:
- "GEM=railties"
- - "GEM=ap"
- - "GEM=ac"
- - "GEM=ac FAYE=1"
- - "GEM=ac:integration"
- - "GEM=ac:integration FAYE=1"
+ - "GEM=ap,ac"
- "GEM=am,amo,as,av,aj"
- "GEM=as PRESERVE_TIMEZONES=1"
- "GEM=ar:mysql2"
- "GEM=ar:sqlite3"
- "GEM=ar:postgresql"
- - "GEM=aj:integration"
- "GEM=guides"
+ - "GEM=ac:integration"
rvm:
- - 2.2.5
- - 2.3.1
+ - 2.2.6
+ - 2.3.3
+ - 2.4.0
- ruby-head
matrix:
include:
- # Latest compiled version in http://rubies.travis-ci.org
- - rvm: 2.3.1
+ - rvm: 2.2.6
+ env: "GEM=aj:integration"
+ services:
+ - memcached
+ - redis
+ - rabbitmq
+ - rvm: 2.3.3
+ env: "GEM=aj:integration"
+ services:
+ - memcached
+ - redis
+ - rabbitmq
+ - rvm: ruby-head
+ env: "GEM=aj:integration"
+ services:
+ - memcached
+ - redis
+ - rabbitmq
+ - rvm: 2.3.3
env:
- - "GEM=ar:mysql2"
+ - "GEM=ar:mysql2 MYSQL=mariadb"
addons:
mariadb: 10.0
- - rvm: jruby-9.0.5.0
+ - rvm: jruby-9.1.5.0
+ jdk: oraclejdk8
+ env:
+ - "GEM=ap"
+ - rvm: jruby-9.1.5.0
jdk: oraclejdk8
env:
- - "JRUBY_OPTS='--dev -J-Xmx1024M'"
- - "GEM='ap'"
+ - "GEM=am,aj"
allow_failures:
- rvm: ruby-head
- - rvm: jruby-9.0.5.0
+ - rvm: jruby-9.1.5.0
- env: "GEM=ac:integration"
- - env: "GEM=ac:integration FAYE=1"
fast_finish: true
notifications:
diff --git a/Gemfile b/Gemfile
index 83276212f7..f529677fbb 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,7 +1,14 @@
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
+
gemspec
+gem "arel", github: "rails/arel"
+
# We need a newish Rake since Active Job sets its test tasks' descriptions.
gem "rake", ">= 11.1"
@@ -24,22 +31,25 @@ gem "bcrypt", "~> 3.1.11", require: false
# sprockets.
gem "uglifier", ">= 1.3.0", require: false
-# Track stable branch of sass because it doesn't have circular require warnings.
-gem "sass", github: "sass/sass", branch: "stable", 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", require: false
+
group :doc do
- gem "sdoc", "~> 0.4.0"
+ gem "sdoc", "1.0.0.rc1"
gem "redcarpet", "~> 3.2.3", platforms: :ruby
gem "w3c_validators"
- gem "kindlerb", "0.1.1"
+ gem "kindlerb", "~> 1.2.0"
end
# Active Support.
gem "dalli", ">= 2.2.1"
-gem "listen", "~> 3.0.5", require: false
+gem "listen", ">= 3.0.5", "< 3.2", require: false
+gem "libxml-ruby", platforms: :ruby
# Active Job.
group :job do
@@ -67,10 +77,7 @@ group :cable do
gem "hiredis", require: false
gem "redis", require: false
- gem "faye-websocket", require: false
-
- # Lock to 1.1.1 until the fix for https://github.com/faye/faye/issues/394 is released
- gem "faye", "1.1.1", require: false
+ gem "websocket-client-simple", github: "matthewd/websocket-client-simple", branch: "close-race", require: false
gem "blade", require: false, platforms: [:ruby]
gem "blade-sauce_labs_plugin", require: false, platforms: [:ruby]
diff --git a/Gemfile.lock b/Gemfile.lock
index cf8bb0fec9..f256186e29 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,28 +1,28 @@
GIT
- remote: git://github.com/QueueClassic/queue_classic.git
- revision: c26f2c9f6f6133b946fbcdd7b7ec905a4aca9f94
+ remote: https://github.com/QueueClassic/queue_classic.git
+ revision: 51d56ca6fa2fdf1eeffdffd702ae1cc0940b5156
branch: master
specs:
queue_classic (3.2.0.RC1)
- pg (>= 0.17, < 0.19)
+ pg (>= 0.17, < 0.20)
GIT
- remote: git://github.com/collectiveidea/delayed_job.git
- revision: 71f1d5faf934d3057abca942f0d410327bc69087
+ remote: https://github.com/collectiveidea/delayed_job.git
+ revision: e3772d4f0c8470d0fcba00c86ca3bc4f5e876830
specs:
- delayed_job (4.1.1)
+ delayed_job (4.1.2)
activesupport (>= 3.0, < 5.1)
GIT
- remote: git://github.com/collectiveidea/delayed_job_active_record.git
- revision: 61e688e03b2ef4004b08de6d1e0a123fda8fffad
+ remote: https://github.com/collectiveidea/delayed_job_active_record.git
+ revision: 36f434c4fd660e8f11ce932be117e9c71dde7212
specs:
- delayed_job_active_record (4.1.0)
+ delayed_job_active_record (4.1.1)
activerecord (>= 3.0, < 5.1)
delayed_job (>= 3.0, < 5)
GIT
- remote: git://github.com/matthewd/rb-inotify.git
+ remote: https://github.com/matthewd/rb-inotify.git
revision: 90553518d1fb79aedc98a3036c59bd2b6731ac40
branch: close-handling
specs:
@@ -30,8 +30,23 @@ GIT
ffi (>= 0.5.0)
GIT
- remote: git://github.com/resque/resque.git
- revision: 06036388ec61e573c761ac5a25a2ef3c76537ec7
+ remote: https://github.com/matthewd/websocket-client-simple.git
+ revision: e161305f1a466b9398d86df3b1731b03362da91b
+ branch: close-race
+ specs:
+ websocket-client-simple (0.3.0)
+ event_emitter
+ websocket
+
+GIT
+ remote: https://github.com/rails/arel.git
+ revision: b26638ef041953992010590b31615c519fa0ea7d
+ specs:
+ arel (8.0.0)
+
+GIT
+ remote: https://github.com/resque/resque.git
+ revision: 835a4a7e61a2e33832dbf11975ecfab54af293c6
specs:
resque (1.27.0)
mono_logger (~> 1.0)
@@ -40,19 +55,12 @@ GIT
sinatra (>= 0.9.2)
vegas (~> 0.1.2)
-GIT
- remote: git://github.com/sass/sass.git
- revision: 3fda1cbe70d615e7ef96e28db4fd1f8a3ebb5505
- branch: stable
- specs:
- sass (3.4.22)
-
PATH
remote: .
specs:
actioncable (5.1.0.alpha)
actionpack (= 5.1.0.alpha)
- nio4r (~> 1.2)
+ nio4r (~> 2.0)
websocket-driver (~> 0.6.1)
actionmailer (5.1.0.alpha)
actionpack (= 5.1.0.alpha)
@@ -81,7 +89,7 @@ PATH
activerecord (5.1.0.alpha)
activemodel (= 5.1.0.alpha)
activesupport (= 5.1.0.alpha)
- arel (~> 7.0)
+ arel (~> 8.0)
activesupport (5.1.0.alpha)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (~> 0.7)
@@ -109,39 +117,40 @@ PATH
GEM
remote: https://rubygems.org/
specs:
- addressable (2.4.0)
+ addressable (2.5.0)
+ public_suffix (~> 2.0, >= 2.0.2)
amq-protocol (2.0.1)
- arel (7.1.1)
- backburner (1.3.0)
+ ast (2.3.0)
+ backburner (1.3.1)
beaneater (~> 1.0)
+ concurrent-ruby (~> 1.0.1)
dante (> 0.1.5)
bcrypt (3.1.11)
bcrypt (3.1.11-x64-mingw32)
bcrypt (3.1.11-x86-mingw32)
beaneater (1.0.0)
- benchmark-ips (2.6.1)
- blade (0.5.6)
+ benchmark-ips (2.7.2)
+ blade (0.7.0)
activesupport (>= 3.0.0)
- blade-qunit_adapter (~> 1.20.0)
+ blade-qunit_adapter (~> 2.0.1)
coffee-script
coffee-script-source
curses (~> 1.0.0)
eventmachine
faye
sprockets (>= 3.0)
- sprockets-export (~> 0.9.1)
thin (>= 1.6.0)
thor (~> 0.19.1)
useragent (~> 0.16.7)
- blade-qunit_adapter (1.20.0)
- blade-sauce_labs_plugin (0.5.2)
+ blade-qunit_adapter (2.0.1)
+ blade-sauce_labs_plugin (0.6.2)
childprocess
faraday
selenium-webdriver
builder (3.2.2)
- bunny (2.2.2)
+ bunny (2.6.2)
amq-protocol (>= 2.0.1)
- byebug (8.2.5)
+ byebug (9.0.6)
childprocess (0.5.9)
ffi (~> 1.0, >= 1.0.11)
coffee-rails (4.2.1)
@@ -150,33 +159,34 @@ GEM
coffee-script (2.4.1)
coffee-script-source
execjs
- coffee-script-source (1.10.0)
- concurrent-ruby (1.0.2)
- connection_pool (2.2.0)
- cookiejar (0.3.0)
+ coffee-script-source (1.12.2)
+ concurrent-ruby (1.0.4)
+ connection_pool (2.2.1)
+ cookiejar (0.3.3)
curses (1.0.2)
- daemons (1.2.3)
+ daemons (1.2.4)
dalli (2.7.6)
dante (0.2.0)
em-hiredis (0.3.1)
eventmachine (~> 1.0)
hiredis (~> 0.6.0)
- em-http-request (1.1.3)
+ em-http-request (1.1.5)
addressable (>= 2.3.4)
- cookiejar (<= 0.3.0)
+ cookiejar (!= 0.3.1)
em-socksify (>= 0.3)
eventmachine (>= 1.0.3)
http_parser.rb (>= 0.6.0)
em-socksify (0.3.1)
eventmachine (>= 1.0.0.beta.4)
erubis (2.7.0)
- eventmachine (1.2.0.1)
- eventmachine (1.2.0.1-x64-mingw32)
- eventmachine (1.2.0.1-x86-mingw32)
+ event_emitter (0.2.5)
+ eventmachine (1.2.1)
+ eventmachine (1.2.1-x64-mingw32)
+ eventmachine (1.2.1-x86-mingw32)
execjs (2.7.0)
- faraday (0.9.2)
+ faraday (0.10.0)
multipart-post (>= 1.2, < 3)
- faye (1.1.1)
+ faye (1.2.3)
cookiejar (>= 0.3.0)
em-http-request (>= 0.3.0)
eventmachine (>= 0.12.0)
@@ -184,37 +194,39 @@ GEM
multi_json (>= 1.0.0)
rack (>= 1.0.0)
websocket-driver (>= 0.5.1)
- faye-websocket (0.10.4)
+ faye-websocket (0.10.5)
eventmachine (>= 0.12.0)
websocket-driver (>= 0.5.1)
- ffi (1.9.10)
- ffi (1.9.10-x64-mingw32)
- ffi (1.9.10-x86-mingw32)
- globalid (0.3.6)
+ ffi (1.9.14)
+ ffi (1.9.14-x64-mingw32)
+ ffi (1.9.14-x86-mingw32)
+ globalid (0.3.7)
activesupport (>= 4.1.0)
hiredis (0.6.1)
http_parser.rb (0.6.0)
i18n (0.7.0)
- jquery-rails (4.1.1)
+ jquery-rails (4.2.1)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
- json (1.8.3)
- kindlerb (0.1.1)
+ json (2.0.2)
+ kindlerb (1.2.0)
mustache
nokogiri
- listen (3.0.7)
- rb-fsevent (>= 0.9.3)
- rb-inotify (>= 0.9.7)
+ libxml-ruby (2.9.0)
+ listen (3.1.5)
+ rb-fsevent (~> 0.9, >= 0.9.4)
+ rb-inotify (~> 0.9, >= 0.9.7)
+ ruby_dep (~> 1.2)
loofah (2.0.3)
nokogiri (>= 1.5.9)
mail (2.6.4)
mime-types (>= 1.16, < 4)
metaclass (0.0.4)
method_source (0.8.2)
- mime-types (3.0)
+ mime-types (3.1)
mime-types-data (~> 3.2015)
- mime-types-data (3.2016.0221)
+ mime-types-data (3.2016.0521)
mini_portile2 (2.1.0)
minitest (5.3.3)
mocha (0.14.0)
@@ -223,124 +235,136 @@ GEM
multi_json (1.12.1)
multipart-post (2.0.0)
mustache (1.0.3)
- mysql2 (0.4.4)
- mysql2 (0.4.4-x64-mingw32)
- mysql2 (0.4.4-x86-mingw32)
- nio4r (1.2.1)
- nokogiri (1.6.8)
+ mysql2 (0.4.5)
+ mysql2 (0.4.5-x64-mingw32)
+ mysql2 (0.4.5-x86-mingw32)
+ nio4r (2.0.0)
+ nokogiri (1.7.0)
mini_portile2 (~> 2.1.0)
- pkg-config (~> 1.1.7)
- nokogiri (1.6.8-x64-mingw32)
+ nokogiri (1.7.0-x64-mingw32)
mini_portile2 (~> 2.1.0)
- pkg-config (~> 1.1.7)
- nokogiri (1.6.8-x86-mingw32)
+ nokogiri (1.7.0-x86-mingw32)
mini_portile2 (~> 2.1.0)
- pkg-config (~> 1.1.7)
- pg (0.18.4)
- pg (0.18.4-x64-mingw32)
- pg (0.18.4-x86-mingw32)
- pkg-config (1.1.7)
- psych (2.1.0)
- puma (3.4.0)
+ parser (2.3.3.1)
+ ast (~> 2.2)
+ pg (0.19.0)
+ pg (0.19.0-x64-mingw32)
+ pg (0.19.0-x86-mingw32)
+ powerpack (0.1.1)
+ psych (2.2.2)
+ public_suffix (2.0.4)
+ puma (3.6.2)
qu (0.2.0)
multi_json
qu-redis (0.2.0)
qu (= 0.2.0)
redis-namespace
simple_uuid
- que (0.11.4)
+ que (0.12.0)
racc (1.4.14)
rack (2.0.1)
rack-cache (1.6.1)
rack (>= 0.4)
+ rack-protection (1.5.3)
+ rack
rack-test (0.6.3)
rack (>= 1.0)
- rails-dom-testing (2.0.0)
+ rails-dom-testing (2.0.2)
activesupport (>= 4.2.0, < 6.0)
- nokogiri (~> 1.6.0)
+ nokogiri (~> 1.6)
rails-html-sanitizer (1.0.3)
loofah (~> 2.0)
- rake (11.1.2)
- rb-fsevent (0.9.7)
- rdoc (4.2.2)
- json (~> 1.4)
+ rainbow (2.1.0)
+ rake (12.0.0)
+ rb-fsevent (0.9.8)
+ rdoc (5.0.0)
redcarpet (3.2.3)
- redis (3.3.0)
+ redis (3.3.2)
redis-namespace (1.5.2)
redis (~> 3.0, >= 3.0.4)
- resque-scheduler (4.2.0)
+ resque-scheduler (4.3.0)
mono_logger (~> 1.0)
- redis (~> 3.0)
- resque (~> 1.25)
+ redis (~> 3.3)
+ resque (~> 1.26)
rufus-scheduler (~> 3.2)
+ rubocop (0.46.0)
+ parser (>= 2.3.1.1, < 3.0)
+ powerpack (~> 0.1)
+ rainbow (>= 1.99.1, < 3.0)
+ ruby-progressbar (~> 1.7)
+ unicode-display_width (~> 1.0, >= 1.0.1)
+ ruby-progressbar (1.8.1)
+ ruby_dep (1.5.0)
rubyzip (1.2.0)
- rufus-scheduler (3.2.1)
- sass-rails (5.0.5)
+ rufus-scheduler (3.3.1)
+ tzinfo
+ sass (3.4.23)
+ sass-rails (5.0.6)
railties (>= 4.0.0, < 6)
sass (~> 3.1)
sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
- sdoc (0.4.1)
- json (~> 1.7, >= 1.7.7)
- rdoc (~> 4.0)
- selenium-webdriver (2.53.0)
+ sdoc (1.0.0.rc1)
+ rdoc (= 5.0.0)
+ selenium-webdriver (3.0.5)
childprocess (~> 0.5)
rubyzip (~> 1.0)
websocket (~> 1.0)
- sequel (4.34.0)
+ sequel (4.41.0)
serverengine (1.5.11)
sigdump (~> 0.2.2)
- sidekiq (4.1.2)
+ sidekiq (4.2.7)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
+ rack-protection (>= 1.5.0)
redis (~> 3.2, >= 3.2.1)
sigdump (0.2.4)
simple_uuid (0.4.0)
sinatra (1.0)
rack (>= 1.0)
- sneakers (2.3.5)
- bunny (~> 2.2.0)
+ sneakers (2.4.0)
+ bunny (~> 2.6)
serverengine (~> 1.5.11)
thor
thread (~> 0.1.7)
- sprockets (3.6.1)
+ sprockets (3.7.1)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
- sprockets-export (0.9.1)
- sprockets-rails (3.1.1)
+ sprockets-rails (3.2.0)
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
- sqlite3 (1.3.11)
- sqlite3 (1.3.11-x64-mingw32)
- sqlite3 (1.3.11-x86-mingw32)
- stackprof (0.2.9)
+ sqlite3 (1.3.12)
+ sqlite3 (1.3.12-x64-mingw32)
+ sqlite3 (1.3.12-x86-mingw32)
+ stackprof (0.2.10)
sucker_punch (2.0.2)
concurrent-ruby (~> 1.0.0)
thin (1.7.0)
daemons (~> 1.0, >= 1.0.9)
eventmachine (~> 1.0, >= 1.0.4)
rack (>= 1, < 3)
- thor (0.19.1)
+ thor (0.19.4)
thread (0.1.7)
thread_safe (0.3.5)
tilt (2.0.5)
- turbolinks (5.0.0)
+ turbolinks (5.0.1)
turbolinks-source (~> 5)
turbolinks-source (5.0.0)
tzinfo (1.2.2)
thread_safe (~> 0.1)
- tzinfo-data (1.2016.4)
+ tzinfo-data (1.2016.10)
tzinfo (>= 1.0.0)
- uglifier (3.0.0)
+ uglifier (3.0.4)
execjs (>= 0.3.0, < 3)
- useragent (0.16.7)
+ unicode-display_width (1.1.2)
+ useragent (0.16.8)
vegas (0.1.11)
rack (>= 1.0.0)
- w3c_validators (1.2)
- json
- nokogiri
+ w3c_validators (1.3.1)
+ json (~> 2.0)
+ nokogiri (~> 1.6)
wdm (0.1.1)
websocket (1.2.3)
websocket-driver (0.6.4)
@@ -356,6 +380,7 @@ DEPENDENCIES
activerecord-jdbcmysql-adapter (>= 1.3.0)
activerecord-jdbcpostgresql-adapter (>= 1.3.0)
activerecord-jdbcsqlite3-adapter (>= 1.3.0)
+ arel!
backburner
bcrypt (~> 3.1.11)
benchmark-ips
@@ -367,12 +392,12 @@ DEPENDENCIES
delayed_job!
delayed_job_active_record!
em-hiredis
- faye (= 1.1.1)
- faye-websocket
hiredis
jquery-rails
- kindlerb (= 0.1.1)
- listen (~> 3.0.5)
+ json (>= 2.0.0)
+ kindlerb (~> 1.2.0)
+ libxml-ruby
+ listen (>= 3.0.5, < 3.2)
minitest (< 5.3.4)
mocha (~> 0.14)
mysql2 (>= 0.4.4)
@@ -392,9 +417,9 @@ DEPENDENCIES
redis
resque!
resque-scheduler
- sass!
+ rubocop
sass-rails
- sdoc (~> 0.4.0)
+ sdoc (= 1.0.0.rc1)
sequel
sidekiq
sneakers
@@ -406,6 +431,7 @@ DEPENDENCIES
uglifier (>= 1.3.0)
w3c_validators
wdm (>= 0.1.0)
+ websocket-client-simple!
BUNDLED WITH
- 1.12.5
+ 1.13.7
diff --git a/MIT-LICENSE b/MIT-LICENSE
new file mode 100644
index 0000000000..6b3cead1a7
--- /dev/null
+++ b/MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2005-2017 David Heinemeier Hansson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/RELEASING_RAILS.md b/RELEASING_RAILS.md
index 4e6b811d3e..10a8bca3b3 100644
--- a/RELEASING_RAILS.md
+++ b/RELEASING_RAILS.md
@@ -103,17 +103,19 @@ branch.
Run `rake install` to generate the gems and install them locally. Then try
generating a new app and ensure that nothing explodes.
-Verify that Action Cable's package.json is updated with the RC version.
+Verify that Action Cable and Action View's package.json files are updated with
+the RC version.
This will stop you from looking silly when you push an RC to rubygems.org and
then realize it is broken.
### Release to RubyGems and NPM.
-IMPORTANT: The Action Cable client is released as an NPM package, so you must
-have Node.js installed, have an NPM account (npmjs.com), and be an actioncable
-package owner (`npm owner ls actioncable`) to do a full release. Do not release
-until you're set up with NPM!
+IMPORTANT: The Action Cable client and Action View's UJS adapter are released
+as NPM packages, so you must have Node.js installed, have an NPM account
+(npmjs.com), and be a package owner for `actioncable` and `rails-ujs` (you can
+check this via `npm owner ls actioncable` and `npm owner ls rails-ujs`) in
+order to do a full release. Do not release until you're set up with NPM!
Run `rake release`. This will populate the gemspecs and NPM package.json with
the current RAILS_VERSION, commit the changes, tag it, and push the gems to
@@ -179,7 +181,7 @@ more explanation on a particular step, see the RC steps.
Today, do this stuff in this order:
* Apply security patches to the release branch
-* Update CHANGELOG with security fixes.
+* Update CHANGELOG with security fixes
* Update RAILS_VERSION to remove the rc
* Build and test the gem
* Release the gems
@@ -206,7 +208,7 @@ so we need to give them the security fixes in patch form.
* Blog announcements
* Twitter announcements
-* Merge the release branch to the stable branch.
+* Merge the release branch to the stable branch
* Drink beer (or other cocktail)
## Misc
diff --git a/Rakefile b/Rakefile
index 3bc4cd19fb..202eb5e6fc 100644
--- a/Rakefile
+++ b/Rakefile
@@ -23,9 +23,6 @@ task default: %w(test test:isolated)
FRAMEWORKS.each do |project|
system(%(cd #{project} && #{$0} #{task_name} --trace)) || errors << project
end
- if task_name =~ /test/
- system(%(cd actioncable && env FAYE=1 #{$0} #{task_name} --trace)) || errors << "actioncable-faye"
- end
fail("Errors in #{errors.join(', ')}") unless errors.empty?
end
end
@@ -36,7 +33,6 @@ task :smoke do
system %(cd #{project} && #{$0} test:isolated --trace)
end
system %(cd activerecord && #{$0} sqlite3:isolated_test --trace)
- system %(cd actioncable && env FAYE=1 #{$0} test:isolated --trace)
end
desc "Install gems for all projects."
diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md
index dec6f7c027..4f17274a01 100644
--- a/actioncable/CHANGELOG.md
+++ b/actioncable/CHANGELOG.md
@@ -1,9 +1,29 @@
-* Protect against concurrent writes to a websocket connection from
+* Permit same-origin connections by default.
+
+ Added new option `config.action_cable.allow_same_origin_as_host = false`
+ to disable this behaviour.
+
+ *Dávid Halász*, *Matthew Draper*
+
+* Prevent race where the client could receive and act upon a
+ subscription confirmation before the channel's `subscribed` method
+ completed.
+
+ Fixes #25381.
+
+ *Vladimir Dementyev*
+
+* Buffer now writes to WebSocket connections, to avoid blocking threads
+ that could be doing more useful things.
+
+ *Matthew Draper*, *Tinco Andringa*
+
+* Protect against concurrent writes to a WebSocket connection from
multiple threads; the underlying OS write is not always threadsafe.
*Tinco Andringa*
-* Add ActiveSupport::Notifications hook to Broadcaster#broadcast.
+* Add `ActiveSupport::Notifications` hook to `Broadcaster#broadcast`.
*Matthew Wear*
diff --git a/actioncable/MIT-LICENSE b/actioncable/MIT-LICENSE
index 27a17cf41b..1a0e653b69 100644
--- a/actioncable/MIT-LICENSE
+++ b/actioncable/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2015-2016 Basecamp, LLC
+Copyright (c) 2015-2017 Basecamp, LLC
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actioncable/README.md b/actioncable/README.md
index 28e2602cbf..cccb55a196 100644
--- a/actioncable/README.md
+++ b/actioncable/README.md
@@ -51,7 +51,7 @@ module ApplicationCable
self.current_user = find_verified_user
end
- protected
+ private
def find_verified_user
if current_user = User.find_by(id: cookies.signed[:user_id])
current_user
@@ -167,7 +167,7 @@ App.cable.subscriptions.create "AppearanceChannel",
buttonSelector = "[data-behavior~=appear_away]"
install: ->
- $(document).on "page:change.appearance", =>
+ $(document).on "turbolinks:load.appearance", =>
@appear()
$(document).on "click.appearance", buttonSelector, =>
@@ -326,7 +326,10 @@ Rails.application.paths.add "config/cable", with: "somewhere/else/cable.yml"
### Allowed Request Origins
-Action Cable will only accept requests from specified origins, which are passed to the server config as an array. The origins can be instances of strings or regular expressions, against which a check for match will be performed.
+Action Cable will only accept requests from specific origins.
+
+By default, only an origin matching the cable server itself will be permitted.
+Additional origins can be specified using strings or regular expressions, provided in an array.
```ruby
Rails.application.config.action_cable.allowed_request_origins = ['http://rubyonrails.com', /http:\/\/ruby.*/]
@@ -334,12 +337,19 @@ Rails.application.config.action_cable.allowed_request_origins = ['http://rubyonr
When running in the development environment, this defaults to "http://localhost:3000".
-To disable and allow requests from any origin:
+To disable protection and allow requests from any origin:
```ruby
Rails.application.config.action_cable.disable_request_forgery_protection = true
```
+To disable automatic access for same-origin requests, and strictly allow
+only the configured origins:
+
+```ruby
+Rails.application.config.action_cable.allow_same_origin_as_host = false
+```
+
### Consumer Configuration
Once you have decided how to run your cable server (see below), you must provide the server URL (or path) to your client-side setup.
diff --git a/actioncable/Rakefile b/actioncable/Rakefile
index 34649547a2..87d443919c 100644
--- a/actioncable/Rakefile
+++ b/actioncable/Rakefile
@@ -1,7 +1,6 @@
require "rake/testtask"
require "pathname"
require "action_cable"
-require "blade"
dir = File.dirname(__FILE__)
@@ -21,10 +20,11 @@ namespace :test do
task :isolated do
Dir.glob("test/**/*_test.rb").all? do |file|
sh(Gem.ruby, "-w", "-Ilib:test", file)
- end or raise "Failures"
+ end || raise("Failures")
end
task :integration do
+ require "blade"
if ENV["CI"]
Blade.start(interface: :ci)
else
@@ -36,6 +36,7 @@ end
namespace :assets do
desc "Compile Action Cable assets"
task :compile do
+ require "blade"
Blade.build
end
end
diff --git a/actioncable/actioncable.gemspec b/actioncable/actioncable.gemspec
index e7f91d0e34..6d95f022fa 100644
--- a/actioncable/actioncable.gemspec
+++ b/actioncable/actioncable.gemspec
@@ -20,6 +20,6 @@ Gem::Specification.new do |s|
s.add_dependency "actionpack", version
- s.add_dependency "nio4r", "~> 1.2"
+ s.add_dependency "nio4r", "~> 2.0"
s.add_dependency "websocket-driver", "~> 0.6.1"
end
diff --git a/actioncable/lib/action_cable.rb b/actioncable/lib/action_cable.rb
index d353716636..c2d3550acb 100644
--- a/actioncable/lib/action_cable.rb
+++ b/actioncable/lib/action_cable.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2015-2016 Basecamp, LLC
+# Copyright (c) 2015-2017 Basecamp, LLC
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actioncable/lib/action_cable/channel/base.rb b/actioncable/lib/action_cable/channel/base.rb
index 8ab061171e..6739a62ba0 100644
--- a/actioncable/lib/action_cable/channel/base.rb
+++ b/actioncable/lib/action_cable/channel/base.rb
@@ -122,16 +122,16 @@ module ActionCable
end
end
- protected
+ private
# action_methods are cached and there is sometimes need to refresh
# them. ::clear_action_methods! allows you to do that, so next time
# you run action_methods, they will be recalculated.
- def clear_action_methods!
+ def clear_action_methods! # :doc:
@action_methods = nil
end
# Refresh the cached action_methods when a new action_method is added.
- def method_added(name)
+ def method_added(name) # :doc:
super
clear_action_methods!
end
@@ -144,13 +144,14 @@ module ActionCable
# When a channel is streaming via pubsub, we want to delay the confirmation
# transmission until pubsub subscription is confirmed.
- @defer_subscription_confirmation = false
+ #
+ # The counter starts at 1 because it's awaiting a call to #subscribe_to_channel
+ @defer_subscription_confirmation_counter = Concurrent::AtomicFixnum.new(1)
@reject_subscription = nil
@subscription_confirmation_sent = nil
delegate_connection_identifiers
- subscribe_to_channel
end
# Extract the action name from the passed data and process it via the channel. The process will ensure
@@ -169,6 +170,17 @@ module ActionCable
end
end
+ # This method is called after subscription has been added to the connection
+ # and confirms or rejects the subscription.
+ def subscribe_to_channel
+ run_callbacks :subscribe do
+ subscribed
+ end
+
+ reject_subscription if subscription_rejected?
+ ensure_confirmation_sent
+ end
+
# Called by the cable connection when it's cut, so the channel has a chance to cleanup with callbacks.
# This method is not intended to be called directly by the user. Instead, overwrite the #unsubscribed callback.
def unsubscribe_from_channel # :nodoc:
@@ -177,22 +189,22 @@ module ActionCable
end
end
- protected
+ private
# Called once a consumer has become a subscriber of the channel. Usually the place to setup any streams
# you want this channel to be sending to the subscriber.
- def subscribed
+ def subscribed # :doc:
# Override in subclasses
end
# Called once a consumer has cut its cable connection. Can be used for cleaning up connections or marking
# users as offline or the like.
- def unsubscribed
+ def unsubscribed # :doc:
# Override in subclasses
end
# Transmit a hash of data to the subscriber. The hash will automatically be wrapped in a JSON envelope with
# the proper channel identifier marked as the recipient.
- def transmit(data, via: nil)
+ def transmit(data, via: nil) # :doc:
logger.info "#{self.class.name} transmitting #{data.inspect.truncate(300)}".tap { |m| m << " (via #{via})" if via }
payload = { channel_class: self.class.name, data: data, via: via }
@@ -201,27 +213,32 @@ module ActionCable
end
end
- def defer_subscription_confirmation!
- @defer_subscription_confirmation = true
+ def ensure_confirmation_sent # :doc:
+ return if subscription_rejected?
+ @defer_subscription_confirmation_counter.decrement
+ transmit_subscription_confirmation unless defer_subscription_confirmation?
+ end
+
+ def defer_subscription_confirmation! # :doc:
+ @defer_subscription_confirmation_counter.increment
end
- def defer_subscription_confirmation?
- @defer_subscription_confirmation
+ def defer_subscription_confirmation? # :doc:
+ @defer_subscription_confirmation_counter.value > 0
end
- def subscription_confirmation_sent?
+ def subscription_confirmation_sent? # :doc:
@subscription_confirmation_sent
end
- def reject
+ def reject # :doc:
@reject_subscription = true
end
- def subscription_rejected?
+ def subscription_rejected? # :doc:
@reject_subscription
end
- private
def delegate_connection_identifiers
connection.identifiers.each do |identifier|
define_singleton_method(identifier) do
@@ -230,24 +247,12 @@ module ActionCable
end
end
- def subscribe_to_channel
- run_callbacks :subscribe do
- subscribed
- end
-
- if subscription_rejected?
- reject_subscription
- else
- transmit_subscription_confirmation unless defer_subscription_confirmation?
- end
- end
-
def extract_action(data)
(data["action"].presence || :receive).to_sym
end
def processable_action?(action)
- self.class.action_methods.include?(action.to_s)
+ self.class.action_methods.include?(action.to_s) unless subscription_rejected?
end
def dispatch_action(action, data)
diff --git a/actioncable/lib/action_cable/channel/naming.rb b/actioncable/lib/action_cable/channel/naming.rb
index b7e88bf73d..b565cb3cac 100644
--- a/actioncable/lib/action_cable/channel/naming.rb
+++ b/actioncable/lib/action_cable/channel/naming.rb
@@ -12,7 +12,7 @@ module ActionCable
# Chats::AppearancesChannel.channel_name # => 'chats:appearances'
# FooChats::BarAppearancesChannel.channel_name # => 'foo_chats:bar_appearances'
def channel_name
- @channel_name ||= name.sub(/Channel$/, "").gsub("::",":").underscore
+ @channel_name ||= name.sub(/Channel$/, "").gsub("::", ":").underscore
end
end
diff --git a/actioncable/lib/action_cable/channel/streams.rb b/actioncable/lib/action_cable/channel/streams.rb
index 561750d713..dbba333353 100644
--- a/actioncable/lib/action_cable/channel/streams.rb
+++ b/actioncable/lib/action_cable/channel/streams.rb
@@ -69,8 +69,8 @@ module ActionCable
# Start streaming from the named <tt>broadcasting</tt> pubsub queue. Optionally, you can pass a <tt>callback</tt> that'll be used
# instead of the default of just transmitting the updates straight to the subscriber.
- # Pass `coder: ActiveSupport::JSON` to decode messages as JSON before passing to the callback.
- # Defaults to `coder: nil` which does no decoding, passes raw messages.
+ # Pass <tt>coder: ActiveSupport::JSON</tt> to decode messages as JSON before passing to the callback.
+ # Defaults to <tt>coder: nil</tt> which does no decoding, passes raw messages.
def stream_from(broadcasting, callback = nil, coder: nil, &block)
broadcasting = String(broadcasting)
@@ -84,7 +84,7 @@ module ActionCable
connection.server.event_loop.post do
pubsub.subscribe(broadcasting, handler, lambda do
- transmit_subscription_confirmation
+ ensure_confirmation_sent
logger.info "#{self.class.name} is streaming from #{broadcasting}"
end)
end
@@ -94,8 +94,8 @@ module ActionCable
# <tt>callback</tt> that'll be used instead of the default of just transmitting the updates straight
# to the subscriber.
#
- # Pass `coder: ActiveSupport::JSON` to decode messages as JSON before passing to the callback.
- # Defaults to `coder: nil` which does no decoding, passes raw messages.
+ # Pass <tt>coder: ActiveSupport::JSON</tt> to decode messages as JSON before passing to the callback.
+ # Defaults to <tt>coder: nil</tt> which does no decoding, passes raw messages.
def stream_for(model, callback = nil, coder: nil, &block)
stream_from(broadcasting_for([ channel_name, model ]), callback || block, coder: coder)
end
diff --git a/actioncable/lib/action_cable/connection.rb b/actioncable/lib/action_cable/connection.rb
index 5f813cf8e0..902efb07e2 100644
--- a/actioncable/lib/action_cable/connection.rb
+++ b/actioncable/lib/action_cable/connection.rb
@@ -8,8 +8,6 @@ module ActionCable
autoload :ClientSocket
autoload :Identification
autoload :InternalChannel
- autoload :FayeClientSocket
- autoload :FayeEventLoop
autoload :MessageBuffer
autoload :Stream
autoload :StreamEventLoop
diff --git a/actioncable/lib/action_cable/connection/base.rb b/actioncable/lib/action_cable/connection/base.rb
index b0615b08a1..0a517a532d 100644
--- a/actioncable/lib/action_cable/connection/base.rb
+++ b/actioncable/lib/action_cable/connection/base.rb
@@ -22,13 +22,10 @@ module ActionCable
# # Any cleanup work needed when the cable connection is cut.
# end
#
- # protected
+ # private
# def find_verified_user
- # if current_user = User.find_by_identity cookies.signed[:identity_id]
- # current_user
- # else
+ # User.find_by_identity(cookies.signed[:identity_id]) ||
# reject_unauthorized_connection
- # end
# end
# end
# end
@@ -57,7 +54,7 @@ module ActionCable
@worker_pool = server.worker_pool
@logger = new_tagged_logger
- @websocket = ActionCable::Connection::WebSocket.new(env, self, event_loop, server.config.client_socket_class)
+ @websocket = ActionCable::Connection::WebSocket.new(env, self, event_loop)
@subscriptions = ActionCable::Connection::Subscriptions.new(self)
@message_buffer = ActionCable::Connection::MessageBuffer.new(self)
@@ -105,7 +102,7 @@ module ActionCable
worker_pool.async_invoke(self, method, *arguments)
end
- # Return a basic hash of statistics for the connection keyed with `identifier`, `started_at`, and `subscriptions`.
+ # Return a basic hash of statistics for the connection keyed with <tt>identifier</tt>, <tt>started_at</tt>, <tt>subscriptions</tt>, and <tt>request_id</tt>.
# This can be returned by a health check against the connection.
def statistics
{
@@ -136,9 +133,15 @@ module ActionCable
send_async :handle_close
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 :websocket
+ attr_reader :message_buffer
+
+ private
# The request that initiated the WebSocket connection is available here. This gives access to the environment, cookies, etc.
- def request
+ def request # :doc:
@request ||= begin
environment = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
ActionDispatch::Request.new(environment || env)
@@ -146,14 +149,10 @@ module ActionCable
end
# The cookies of the request that initiated the WebSocket connection. Useful for performing authorization checks.
- def cookies
+ def cookies # :doc:
request.cookie_jar
end
- attr_reader :websocket
- attr_reader :message_buffer
-
- private
def encode(cable_message)
@coder.encode cable_message
end
@@ -195,7 +194,10 @@ module ActionCable
def allow_request_origin?
return true if server.config.disable_request_forgery_protection
- if Array(server.config.allowed_request_origins).any? { |allowed_origin| allowed_origin === env["HTTP_ORIGIN"] }
+ proto = Rack::Request.new(env).ssl? ? "https" : "http"
+ if server.config.allow_same_origin_as_host && env["HTTP_ORIGIN"] == "#{proto}://#{env['HTTP_HOST']}"
+ true
+ elsif Array(server.config.allowed_request_origins).any? { |allowed_origin| allowed_origin === env["HTTP_ORIGIN"] }
true
else
logger.error("Request origin not allowed: #{env['HTTP_ORIGIN']}")
diff --git a/actioncable/lib/action_cable/connection/client_socket.rb b/actioncable/lib/action_cable/connection/client_socket.rb
index c48f000d9c..70a2bbecb1 100644
--- a/actioncable/lib/action_cable/connection/client_socket.rb
+++ b/actioncable/lib/action_cable/connection/client_socket.rb
@@ -89,7 +89,7 @@ module ActionCable
code ||= 1000
reason ||= ""
- unless code == 1000 or (code >= 3000 and code <= 4999)
+ unless code == 1000 || (code >= 3000 && code <= 4999)
raise ArgumentError, "Failed to execute 'close' on WebSocket: " +
"The code must be either 1000, or between 3000 and 4999. " +
"#{code} is neither."
diff --git a/actioncable/lib/action_cable/connection/faye_client_socket.rb b/actioncable/lib/action_cable/connection/faye_client_socket.rb
deleted file mode 100644
index 06e92c5d52..0000000000
--- a/actioncable/lib/action_cable/connection/faye_client_socket.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-require "faye/websocket"
-
-module ActionCable
- module Connection
- class FayeClientSocket
- def initialize(env, event_target, stream_event_loop, protocols)
- @env = env
- @event_target = event_target
- @protocols = protocols
-
- @faye = nil
- end
-
- def alive?
- @faye && @faye.ready_state == Faye::WebSocket::API::OPEN
- end
-
- def transmit(data)
- connect
- @faye.send data
- end
-
- def close
- @faye && @faye.close
- end
-
- def protocol
- @faye && @faye.protocol
- end
-
- def rack_response
- connect
- @faye.rack_response
- end
-
- private
- def connect
- return if @faye
- @faye = Faye::WebSocket.new(@env, @protocols)
-
- @faye.on(:open) { |event| @event_target.on_open }
- @faye.on(:message) { |event| @event_target.on_message(event.data) }
- @faye.on(:close) { |event| @event_target.on_close(event.reason, event.code) }
- @faye.on(:error) { |event| @event_target.on_error(event.message) }
- end
- end
- end
-end
diff --git a/actioncable/lib/action_cable/connection/faye_event_loop.rb b/actioncable/lib/action_cable/connection/faye_event_loop.rb
deleted file mode 100644
index cfbe26ee6a..0000000000
--- a/actioncable/lib/action_cable/connection/faye_event_loop.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-require "thread"
-
-require "eventmachine"
-EventMachine.epoll if EventMachine.epoll?
-EventMachine.kqueue if EventMachine.kqueue?
-
-module ActionCable
- module Connection
- class FayeEventLoop
- @@mutex = Mutex.new
-
- def timer(interval, &block)
- ensure_reactor_running
- EMTimer.new(::EM::PeriodicTimer.new(interval, &block))
- end
-
- def post(task = nil, &block)
- task ||= block
-
- ensure_reactor_running
- ::EM.next_tick(&task)
- end
-
- private
- def ensure_reactor_running
- return if EventMachine.reactor_running?
- @@mutex.synchronize do
- Thread.new { EventMachine.run } unless EventMachine.reactor_running?
- Thread.pass until EventMachine.reactor_running?
- end
- end
-
- class EMTimer
- def initialize(inner)
- @inner = inner
- end
-
- def shutdown
- @inner.cancel
- end
- end
- end
- end
-end
diff --git a/actioncable/lib/action_cable/connection/message_buffer.rb b/actioncable/lib/action_cable/connection/message_buffer.rb
index 6a80770cae..4ccd322644 100644
--- a/actioncable/lib/action_cable/connection/message_buffer.rb
+++ b/actioncable/lib/action_cable/connection/message_buffer.rb
@@ -28,6 +28,8 @@ module ActionCable
receive_buffered_messages
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 :connection
attr_reader :buffered_messages
diff --git a/actioncable/lib/action_cable/connection/stream.rb b/actioncable/lib/action_cable/connection/stream.rb
index 5a2aace0ba..e620b93845 100644
--- a/actioncable/lib/action_cable/connection/stream.rb
+++ b/actioncable/lib/action_cable/connection/stream.rb
@@ -14,6 +14,9 @@ module ActionCable
@rack_hijack_io = nil
@write_lock = Mutex.new
+
+ @write_head = nil
+ @write_buffer = Queue.new
end
def each(&callback)
@@ -30,14 +33,62 @@ module ActionCable
end
def write(data)
- @write_lock.synchronize do
- return @rack_hijack_io.write(data) if @rack_hijack_io
- return @stream_send.call(data) if @stream_send
+ if @stream_send
+ return @stream_send.call(data)
end
+
+ if @write_lock.try_lock
+ begin
+ if @write_head.nil? && @write_buffer.empty?
+ written = @rack_hijack_io.write_nonblock(data, exception: false)
+
+ case written
+ when :wait_writable
+ # proceed below
+ when data.bytesize
+ return data.bytesize
+ else
+ @write_head = data.byteslice(written, data.bytesize)
+ @event_loop.writes_pending @rack_hijack_io
+
+ return data.bytesize
+ end
+ end
+ ensure
+ @write_lock.unlock
+ end
+ end
+
+ @write_buffer << data
+ @event_loop.writes_pending @rack_hijack_io
+
+ data.bytesize
rescue EOFError, Errno::ECONNRESET
@socket_object.client_gone
end
+ def flush_write_buffer
+ @write_lock.synchronize do
+ loop do
+ if @write_head.nil?
+ return true if @write_buffer.empty?
+ @write_head = @write_buffer.pop
+ end
+
+ written = @rack_hijack_io.write_nonblock(@write_head, exception: false)
+ case written
+ when :wait_writable
+ return false
+ when @write_head.bytesize
+ @write_head = nil
+ else
+ @write_head = @write_head.byteslice(written, @write_head.bytesize)
+ return false
+ end
+ end
+ end
+ end
+
def receive(data)
@socket_object.parse(data)
end
@@ -55,7 +106,6 @@ module ActionCable
def clean_rack_hijack
return unless @rack_hijack_io
@event_loop.detach(@rack_hijack_io, self)
- @rack_hijack_io.close
@rack_hijack_io = nil
end
end
diff --git a/actioncable/lib/action_cable/connection/stream_event_loop.rb b/actioncable/lib/action_cable/connection/stream_event_loop.rb
index 106b948c45..2d1af0ff9f 100644
--- a/actioncable/lib/action_cable/connection/stream_event_loop.rb
+++ b/actioncable/lib/action_cable/connection/stream_event_loop.rb
@@ -5,7 +5,7 @@ module ActionCable
module Connection
class StreamEventLoop
def initialize
- @nio = @thread = nil
+ @nio = @executor = @thread = nil
@map = {}
@stopping = false
@todo = Queue.new
@@ -20,13 +20,14 @@ module ActionCable
def post(task = nil, &block)
task ||= block
- Concurrent.global_io_executor << task
+ spawn
+ @executor << task
end
def attach(io, stream)
@todo << lambda do
- @map[io] = stream
- @nio.register(io, :r)
+ @map[io] = @nio.register(io, :r)
+ @map[io].value = stream
end
wakeup
end
@@ -35,6 +36,16 @@ module ActionCable
@todo << lambda do
@nio.deregister io
@map.delete io
+ io.close
+ end
+ wakeup
+ end
+
+ def writes_pending(io)
+ @todo << lambda do
+ if monitor = @map[io]
+ monitor.interests = :rw
+ end
end
wakeup
end
@@ -52,6 +63,13 @@ module ActionCable
return if @thread && @thread.status
@nio ||= NIO::Selector.new
+
+ @executor ||= Concurrent::ThreadPoolExecutor.new(
+ min_threads: 1,
+ max_threads: 10,
+ max_queue: 0,
+ )
+
@thread = Thread.new { run }
return true
@@ -77,12 +95,25 @@ module ActionCable
monitors.each do |monitor|
io = monitor.io
- stream = @map[io]
+ stream = monitor.value
begin
- stream.receive io.read_nonblock(4096)
- rescue IO::WaitReadable
- next
+ if monitor.writable?
+ if stream.flush_write_buffer
+ monitor.interests = :r
+ end
+ next unless monitor.readable?
+ end
+
+ incoming = io.read_nonblock(4096, exception: false)
+ case incoming
+ when :wait_readable
+ next
+ when nil
+ stream.close
+ else
+ stream.receive incoming
+ end
rescue
# We expect one of EOFError or Errno::ECONNRESET in
# normal operation (when the client goes away). But if
diff --git a/actioncable/lib/action_cable/connection/subscriptions.rb b/actioncable/lib/action_cable/connection/subscriptions.rb
index 9060183249..abf42c99d5 100644
--- a/actioncable/lib/action_cable/connection/subscriptions.rb
+++ b/actioncable/lib/action_cable/connection/subscriptions.rb
@@ -26,10 +26,14 @@ module ActionCable
id_key = data["identifier"]
id_options = ActiveSupport::JSON.decode(id_key).with_indifferent_access
+ return if subscriptions.key?(id_key)
+
subscription_klass = id_options[:channel].safe_constantize
if subscription_klass && ActionCable::Channel::Base >= subscription_klass
- subscriptions[id_key] ||= subscription_klass.new(connection, id_key, id_options)
+ subscription = subscription_klass.new(connection, id_key, id_options)
+ subscriptions[id_key] = subscription
+ subscription.subscribe_to_channel
else
logger.error "Subscription class not found: #{id_options[:channel].inspect}"
end
@@ -57,6 +61,8 @@ module ActionCable
subscriptions.each { |id, channel| remove_subscription(channel) }
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 :connection, :subscriptions
diff --git a/actioncable/lib/action_cable/connection/tagged_logger_proxy.rb b/actioncable/lib/action_cable/connection/tagged_logger_proxy.rb
index 41afa9680a..aef549aa86 100644
--- a/actioncable/lib/action_cable/connection/tagged_logger_proxy.rb
+++ b/actioncable/lib/action_cable/connection/tagged_logger_proxy.rb
@@ -31,8 +31,8 @@ module ActionCable
end
end
- protected
- def log(type, message)
+ private
+ def log(type, message) # :doc:
tag(@logger) { @logger.send type, message }
end
end
diff --git a/actioncable/lib/action_cable/connection/web_socket.rb b/actioncable/lib/action_cable/connection/web_socket.rb
index 52d8daad4b..03eb6e2ea8 100644
--- a/actioncable/lib/action_cable/connection/web_socket.rb
+++ b/actioncable/lib/action_cable/connection/web_socket.rb
@@ -4,8 +4,8 @@ module ActionCable
module Connection
# Wrap the real socket to minimize the externally-presented API
class WebSocket
- def initialize(env, event_target, event_loop, client_socket_class, protocols: ActionCable::INTERNAL[:protocols])
- @websocket = ::WebSocket::Driver.websocket?(env) ? client_socket_class.new(env, event_target, event_loop, protocols) : nil
+ 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
def possible?
@@ -32,6 +32,8 @@ module ActionCable
websocket.rack_response
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 :websocket
end
diff --git a/actioncable/lib/action_cable/engine.rb b/actioncable/lib/action_cable/engine.rb
index 4c5c975cd8..e23527b84e 100644
--- a/actioncable/lib/action_cable/engine.rb
+++ b/actioncable/lib/action_cable/engine.rb
@@ -34,7 +34,7 @@ module ActionCable
previous_connection_class = self.connection_class
self.connection_class = -> { "ApplicationCable::Connection".safe_constantize || previous_connection_class.call }
- options.each { |k,v| send("#{k}=", v) }
+ options.each { |k, v| send("#{k}=", v) }
end
end
diff --git a/actioncable/lib/action_cable/helpers/action_cable_helper.rb b/actioncable/lib/action_cable/helpers/action_cable_helper.rb
index 2081a37db6..f53be0bc31 100644
--- a/actioncable/lib/action_cable/helpers/action_cable_helper.rb
+++ b/actioncable/lib/action_cable/helpers/action_cable_helper.rb
@@ -6,7 +6,7 @@ module ActionCable
#
# <head>
# <%= action_cable_meta_tag %>
- # <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
+ # <%= javascript_include_tag 'application', 'data-turbolinks-track' => 'reload' %>
# </head>
#
# This is then used by Action Cable to determine the URL of your WebSocket server.
diff --git a/actioncable/lib/action_cable/remote_connections.rb b/actioncable/lib/action_cable/remote_connections.rb
index 720ba52d19..d2856bc6ae 100644
--- a/actioncable/lib/action_cable/remote_connections.rb
+++ b/actioncable/lib/action_cable/remote_connections.rb
@@ -54,7 +54,7 @@ module ActionCable
def set_identifier_instance_vars(ids)
raise InvalidIdentifiersError unless valid_identifiers?(ids)
- ids.each { |k,v| instance_variable_set("@#{k}", v) }
+ ids.each { |k, v| instance_variable_set("@#{k}", v) }
end
def valid_identifiers?(ids)
diff --git a/actioncable/lib/action_cable/server/base.rb b/actioncable/lib/action_cable/server/base.rb
index c700297a8d..419eccd73c 100644
--- a/actioncable/lib/action_cable/server/base.rb
+++ b/actioncable/lib/action_cable/server/base.rb
@@ -37,9 +37,13 @@ module ActionCable
connections.each(&:close)
@mutex.synchronize do
- worker_pool.halt if @worker_pool
-
+ # Shutdown the worker pool
+ @worker_pool.halt if @worker_pool
@worker_pool = nil
+
+ # Shutdown the pub/sub adapter
+ @pubsub.shutdown if @pubsub
+ @pubsub = nil
end
end
@@ -49,12 +53,12 @@ module ActionCable
end
def event_loop
- @event_loop || @mutex.synchronize { @event_loop ||= config.event_loop_class.new }
+ @event_loop || @mutex.synchronize { @event_loop ||= ActionCable::Connection::StreamEventLoop.new }
end
# The worker pool is where we run connection callbacks and channel actions. We do as little as possible on the server's main thread.
# The worker pool is an executor service that's backed by a pool of threads working from a task queue. The thread pool size maxes out
- # at 4 worker threads by default. Tune the size yourself with `config.action_cable.worker_pool_size`.
+ # at 4 worker threads by default. Tune the size yourself with <tt>config.action_cable.worker_pool_size</tt>.
#
# Using Active Record, Redis, etc within your channel actions means you'll get a separate connection from each thread in the worker pool.
# Plan your deployment accordingly: 5 servers each running 5 Puma workers each running an 8-thread worker pool means at least 200 database
diff --git a/actioncable/lib/action_cable/server/configuration.rb b/actioncable/lib/action_cable/server/configuration.rb
index 7153593d4c..17e0dee064 100644
--- a/actioncable/lib/action_cable/server/configuration.rb
+++ b/actioncable/lib/action_cable/server/configuration.rb
@@ -4,8 +4,8 @@ module ActionCable
# in a Rails config initializer.
class Configuration
attr_accessor :logger, :log_tags
- attr_accessor :use_faye, :connection_class, :worker_pool_size
- attr_accessor :disable_request_forgery_protection, :allowed_request_origins
+ attr_accessor :connection_class, :worker_pool_size
+ attr_accessor :disable_request_forgery_protection, :allowed_request_origins, :allow_same_origin_as_host
attr_accessor :cable, :url, :mount_path
def initialize
@@ -15,6 +15,7 @@ module ActionCable
@worker_pool_size = 4
@disable_request_forgery_protection = false
+ @allow_same_origin_as_host = true
end
# Returns constant of subscription adapter specified in config/cable.yml.
@@ -35,22 +36,6 @@ module ActionCable
adapter = "PostgreSQL" if adapter == "Postgresql"
"ActionCable::SubscriptionAdapter::#{adapter}".constantize
end
-
- def event_loop_class
- if use_faye
- ActionCable::Connection::FayeEventLoop
- else
- ActionCable::Connection::StreamEventLoop
- end
- end
-
- def client_socket_class
- if use_faye
- ActionCable::Connection::FayeClientSocket
- else
- ActionCable::Connection::ClientSocket
- end
- end
end
end
end
diff --git a/actioncable/lib/action_cable/server/worker.rb b/actioncable/lib/action_cable/server/worker.rb
index 7460472551..43639c27af 100644
--- a/actioncable/lib/action_cable/server/worker.rb
+++ b/actioncable/lib/action_cable/server/worker.rb
@@ -25,7 +25,7 @@ module ActionCable
# Stop processing work: any work that has not already started
# running will be discarded from the queue
def halt
- @executor.kill
+ @executor.shutdown
end
def stopping?
diff --git a/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb b/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb
index bcd46d2a0e..c3018c5281 100644
--- a/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb
+++ b/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb
@@ -68,10 +68,10 @@ module ActionCable
end
def ensure_reactor_running
- return if EventMachine.reactor_running?
+ return if EventMachine.reactor_running? && EventMachine.reactor_thread
@@mutex.synchronize do
Thread.new { EventMachine.run } unless EventMachine.reactor_running?
- Thread.pass until EventMachine.reactor_running?
+ Thread.pass until EventMachine.reactor_running? && EventMachine.reactor_thread
end
end
end
diff --git a/actioncable/lib/action_cable/subscription_adapter/subscriber_map.rb b/actioncable/lib/action_cable/subscription_adapter/subscriber_map.rb
index 4ec513e3ba..4cce86dcca 100644
--- a/actioncable/lib/action_cable/subscription_adapter/subscriber_map.rb
+++ b/actioncable/lib/action_cable/subscription_adapter/subscriber_map.rb
@@ -2,7 +2,7 @@ module ActionCable
module SubscriptionAdapter
class SubscriberMap
def initialize
- @subscribers = Hash.new { |h,k| h[k] = [] }
+ @subscribers = Hash.new { |h, k| h[k] = [] }
@sync = Mutex.new
end
diff --git a/actioncable/lib/rails/generators/channel/USAGE b/actioncable/lib/rails/generators/channel/USAGE
index 6249553c22..dd109fda80 100644
--- a/actioncable/lib/rails/generators/channel/USAGE
+++ b/actioncable/lib/rails/generators/channel/USAGE
@@ -3,7 +3,7 @@ Description:
Stubs out a new cable channel for the server (in Ruby) and client (in CoffeeScript).
Pass the channel name, either CamelCased or under_scored, and an optional list of channel actions as arguments.
- Note: Turn on the cable connection in app/assets/javascript/cable.js after generating any channels.
+ Note: Turn on the cable connection in app/assets/javascripts/cable.js after generating any channels.
Example:
========
@@ -11,4 +11,4 @@ Example:
creates a Chat channel class and CoffeeScript asset:
Channel: app/channels/chat_channel.rb
- Assets: app/assets/javascript/channels/chat.coffee
+ Assets: app/assets/javascripts/channels/chat.coffee
diff --git a/actioncable/lib/rails/generators/channel/channel_generator.rb b/actioncable/lib/rails/generators/channel/channel_generator.rb
index 20d807c033..04b787c3a4 100644
--- a/actioncable/lib/rails/generators/channel/channel_generator.rb
+++ b/actioncable/lib/rails/generators/channel/channel_generator.rb
@@ -23,7 +23,7 @@ module Rails
generate_application_cable_files
end
- protected
+ private
def file_name
@_file_name ||= super.gsub(/_channel/i, "")
end
diff --git a/actioncable/test/channel/base_test.rb b/actioncable/test/channel/base_test.rb
index 69bbeb7818..9a3a3581e6 100644
--- a/actioncable/test/channel/base_test.rb
+++ b/actioncable/test/channel/base_test.rb
@@ -77,11 +77,13 @@ class ActionCable::Channel::BaseTest < ActiveSupport::TestCase
@channel = ChatChannel.new @connection, "{id: 1}", id: 1
end
- test "should subscribe to a channel on initialize" do
+ test "should subscribe to a channel" do
+ @channel.subscribe_to_channel
assert_equal 1, @channel.room.id
end
test "on subscribe callbacks" do
+ @channel.subscribe_to_channel
assert @channel.subscribed
end
@@ -90,6 +92,8 @@ class ActionCable::Channel::BaseTest < ActiveSupport::TestCase
end
test "unsubscribing from a channel" do
+ @channel.subscribe_to_channel
+
assert @channel.room
assert @channel.subscribed?
@@ -146,12 +150,17 @@ class ActionCable::Channel::BaseTest < ActiveSupport::TestCase
test "transmitting data" do
@channel.perform_action "action" => :get_latest
- expected = { "identifier" => "{id: 1}", "message" => { "data" => "latest" }}
+ expected = { "identifier" => "{id: 1}", "message" => { "data" => "latest" } }
assert_equal expected, @connection.last_transmission
end
- test "subscription confirmation" do
+ test "do not send subscription confirmation on initialize" do
+ assert_nil @connection.last_transmission
+ end
+
+ test "subscription confirmation on subscribe_to_channel" do
expected = { "identifier" => "{id: 1}", "type" => "confirm_subscription" }
+ @channel.subscribe_to_channel
assert_equal expected, @connection.last_transmission
end
@@ -173,7 +182,7 @@ class ActionCable::Channel::BaseTest < ActiveSupport::TestCase
events << ActiveSupport::Notifications::Event.new(*args)
end
- data = {"action" => :speak, "content" => "hello"}
+ data = { "action" => :speak, "content" => "hello" }
@channel.perform_action data
assert_equal 1, events.length
@@ -194,7 +203,7 @@ class ActionCable::Channel::BaseTest < ActiveSupport::TestCase
end
@channel.perform_action "action" => :get_latest
- expected_data = {data: "latest"}
+ expected_data = { data: "latest" }
assert_equal 1, events.length
assert_equal "transmit.action_cable", events[0].name
@@ -208,6 +217,8 @@ class ActionCable::Channel::BaseTest < ActiveSupport::TestCase
test "notification for transmit_subscription_confirmation" do
begin
+ @channel.subscribe_to_channel
+
events = []
ActiveSupport::Notifications.subscribe "transmit_subscription_confirmation.action_cable" do |*args|
events << ActiveSupport::Notifications::Event.new(*args)
diff --git a/actioncable/test/channel/periodic_timers_test.rb b/actioncable/test/channel/periodic_timers_test.rb
index 529abb9535..0cc4992ef6 100644
--- a/actioncable/test/channel/periodic_timers_test.rb
+++ b/actioncable/test/channel/periodic_timers_test.rb
@@ -32,7 +32,7 @@ class ActionCable::Channel::PeriodicTimersTest < ActiveSupport::TestCase
timers.each_with_index do |timer, i|
assert_kind_of Proc, timer[0]
- assert_equal i+1, timer[1][:every]
+ assert_equal i + 1, timer[1][:every]
end
end
@@ -62,6 +62,7 @@ class ActionCable::Channel::PeriodicTimersTest < ActiveSupport::TestCase
@connection.server.event_loop.expects(:timer).times(3).returns(stub(shutdown: nil))
channel = ChatChannel.new @connection, "{id: 1}", id: 1
+ channel.subscribe_to_channel
channel.unsubscribe_from_channel
assert_equal [], channel.send(:active_periodic_timers)
end
diff --git a/actioncable/test/channel/rejection_test.rb b/actioncable/test/channel/rejection_test.rb
index 0d2ac1c129..99c4a7603a 100644
--- a/actioncable/test/channel/rejection_test.rb
+++ b/actioncable/test/channel/rejection_test.rb
@@ -7,6 +7,9 @@ class ActionCable::Channel::RejectionTest < ActiveSupport::TestCase
def subscribed
reject if params[:id] > 0
end
+
+ def secret_action
+ end
end
setup do
@@ -17,8 +20,22 @@ class ActionCable::Channel::RejectionTest < ActiveSupport::TestCase
test "subscription rejection" do
@connection.expects(:subscriptions).returns mock().tap { |m| m.expects(:remove_subscription).with instance_of(SecretChannel) }
@channel = SecretChannel.new @connection, "{id: 1}", id: 1
+ @channel.subscribe_to_channel
+
+ expected = { "identifier" => "{id: 1}", "type" => "reject_subscription" }
+ assert_equal expected, @connection.last_transmission
+ end
+
+ test "does not execute action if subscription is rejected" do
+ @connection.expects(:subscriptions).returns mock().tap { |m| m.expects(:remove_subscription).with instance_of(SecretChannel) }
+ @channel = SecretChannel.new @connection, "{id: 1}", id: 1
+ @channel.subscribe_to_channel
expected = { "identifier" => "{id: 1}", "type" => "reject_subscription" }
assert_equal expected, @connection.last_transmission
+ assert_equal 1, @connection.transmissions.size
+
+ @channel.perform_action("action" => :secret_action)
+ assert_equal 1, @connection.transmissions.size
end
end
diff --git a/actioncable/test/channel/stream_test.rb b/actioncable/test/channel/stream_test.rb
index da26f81a5c..31dcde2e29 100644
--- a/actioncable/test/channel/stream_test.rb
+++ b/actioncable/test/channel/stream_test.rb
@@ -53,6 +53,7 @@ module ActionCable::StreamTests
connection = TestConnection.new
connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("test_room_1", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) }
channel = ChatChannel.new connection, "{id: 1}", id: 1
+ channel.subscribe_to_channel
connection.expects(:pubsub).returns mock().tap { |m| m.expects(:unsubscribe) }
channel.unsubscribe_from_channel
@@ -64,6 +65,7 @@ module ActionCable::StreamTests
connection = TestConnection.new
connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("channel", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) }
channel = SymbolChannel.new connection, ""
+ channel.subscribe_to_channel
connection.expects(:pubsub).returns mock().tap { |m| m.expects(:unsubscribe) }
channel.unsubscribe_from_channel
@@ -76,6 +78,7 @@ module ActionCable::StreamTests
connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("action_cable:stream_tests:chat:Room#1-Campfire", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) }
channel = ChatChannel.new connection, ""
+ channel.subscribe_to_channel
channel.stream_for Room.new(1)
end
end
@@ -84,7 +87,9 @@ module ActionCable::StreamTests
run_in_eventmachine do
connection = TestConnection.new
- ChatChannel.new connection, "{id: 1}", id: 1
+ channel = ChatChannel.new connection, "{id: 1}", id: 1
+ channel.subscribe_to_channel
+
assert_nil connection.last_transmission
wait_for_async
@@ -114,7 +119,7 @@ module ActionCable::StreamTests
end
end
- require "action_cable/subscription_adapter/inline"
+ require "action_cable/subscription_adapter/async"
class UserCallbackChannel < ActionCable::Channel::Base
def subscribed
@@ -124,9 +129,16 @@ module ActionCable::StreamTests
end
end
- class StreamEncodingTest < ActionCable::TestCase
+ class MultiChatChannel < ActionCable::Channel::Base
+ def subscribed
+ stream_from "main_room"
+ stream_from "test_all_rooms"
+ end
+ end
+
+ class StreamFromTest < ActionCable::TestCase
setup do
- @server = TestServer.new(subscription_adapter: ActionCable::SubscriptionAdapter::Inline)
+ @server = TestServer.new(subscription_adapter: ActionCable::SubscriptionAdapter::Async)
@server.config.allowed_request_origins = %w( http://rubyonrails.com )
end
@@ -153,6 +165,17 @@ module ActionCable::StreamTests
end
end
+ test "subscription confirmation should only be sent out once with muptiple stream_from" do
+ run_in_eventmachine do
+ connection = open_connection
+ expected = { "identifier" => { "channel" => MultiChatChannel.name }.to_json, "type" => "confirm_subscription" }
+ connection.websocket.expects(:transmit).with(expected.to_json)
+ receive(connection, command: "subscribe", channel: MultiChatChannel.name, identifiers: {})
+
+ wait_for_async
+ end
+ end
+
private
def subscribe_to(connection, identifiers:)
receive connection, command: "subscribe", identifiers: identifiers
diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb
index 16fe2abdee..98a114a5f4 100644
--- a/actioncable/test/client_test.rb
+++ b/actioncable/test/client_test.rb
@@ -1,13 +1,31 @@
require "test_helper"
require "concurrent"
-require "faye/websocket"
+require "websocket-client-simple"
require "json"
require "active_support/hash_with_indifferent_access"
+####
+# 😷 Warning suppression 😷
+WebSocket::Frame::Handler::Handler03.prepend Module.new {
+ def initialize(*)
+ @application_data_buffer = nil
+ super
+ end
+}
+
+WebSocket::Frame::Data.prepend Module.new {
+ def initialize(*)
+ @masking_key = nil
+ super
+ end
+}
+#
+####
+
class ClientTest < ActionCable::TestCase
- WAIT_WHEN_EXPECTING_EVENT = 8
+ WAIT_WHEN_EXPECTING_EVENT = 2
WAIT_WHEN_NOT_EXPECTING_EVENT = 0.5
class EchoChannel < ActionCable::Channel::Base
@@ -39,20 +57,9 @@ class ClientTest < ActionCable::TestCase
server.config.logger = Logger.new(StringIO.new).tap { |l| l.level = Logger::UNKNOWN }
server.config.cable = ActiveSupport::HashWithIndifferentAccess.new(adapter: "async")
- server.config.use_faye = ENV["FAYE"].present?
# and now the "real" setup for our test:
server.config.disable_request_forgery_protection = true
-
- Thread.new { EventMachine.run } unless EventMachine.reactor_running?
- Thread.pass until EventMachine.reactor_running?
-
- # faye-websocket is warning-rich
- @previous_verbose, $VERBOSE = $VERBOSE, nil
- end
-
- def teardown
- $VERBOSE = @previous_verbose
end
def with_puma_server(rack_app = ActionCable.server, port = 3099)
@@ -73,44 +80,49 @@ class ClientTest < ActionCable::TestCase
attr_reader :pings
def initialize(port)
- @ws = Faye::WebSocket::Client.new("ws://127.0.0.1:#{port}/")
- @messages = Queue.new
- @closed = Concurrent::Event.new
- @has_messages = Concurrent::Semaphore.new(0)
- @pings = 0
-
- open = Concurrent::Event.new
- error = nil
-
- @ws.on(:error) do |event|
- if open.set?
- @messages << RuntimeError.new(event.message)
- else
- error = event.message
- open.set
+ messages = @messages = Queue.new
+ closed = @closed = Concurrent::Event.new
+ has_messages = @has_messages = Concurrent::Semaphore.new(0)
+ pings = @pings = Concurrent::AtomicFixnum.new(0)
+
+ open = Concurrent::Promise.new
+
+ @ws = WebSocket::Client::Simple.connect("ws://127.0.0.1:#{port}/") do |ws|
+ ws.on(:error) do |event|
+ event = RuntimeError.new(event.message) unless event.is_a?(Exception)
+
+ if open.pending?
+ open.fail(event)
+ else
+ messages << event
+ has_messages.release
+ end
end
- end
- @ws.on(:open) do |event|
- open.set
- end
+ ws.on(:open) do |event|
+ open.set(true)
+ end
- @ws.on(:message) do |event|
- message = JSON.parse(event.data)
- if message["type"] == "ping"
- @pings += 1
- else
- @messages << message
- @has_messages.release
+ ws.on(:message) do |event|
+ if event.type == :close
+ closed.set
+ else
+ message = JSON.parse(event.data)
+ if message["type"] == "ping"
+ pings.increment
+ else
+ messages << message
+ has_messages.release
+ end
+ end
end
- end
- @ws.on(:close) do |event|
- @closed.set
+ ws.on(:close) do |event|
+ closed.set
+ end
end
- open.wait(WAIT_WHEN_EXPECTING_EVENT)
- raise error if error
+ open.wait!(WAIT_WHEN_EXPECTING_EVENT)
end
def read_message
@@ -161,76 +173,80 @@ class ClientTest < ActionCable::TestCase
end
end
- def faye_client(port)
+ def websocket_client(port)
SyncClient.new(port)
end
+ def concurrently(enum)
+ enum.map { |*x| Concurrent::Future.execute { yield(*x) } }.map(&:value!)
+ end
+
def test_single_client
with_puma_server do |port|
- c = faye_client(port)
- assert_equal({"type" => "welcome"}, c.read_message) # pop the first welcome message off the stack
+ c = websocket_client(port)
+ assert_equal({ "type" => "welcome" }, c.read_message) # pop the first welcome message off the stack
c.send_message command: "subscribe", identifier: JSON.generate(channel: "ClientTest::EchoChannel")
- assert_equal({"identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message)
+ assert_equal({ "identifier" => "{\"channel\":\"ClientTest::EchoChannel\"}", "type" => "confirm_subscription" }, c.read_message)
c.send_message command: "message", identifier: JSON.generate(channel: "ClientTest::EchoChannel"), data: JSON.generate(action: "ding", message: "hello")
- assert_equal({"identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "message"=>{"dong"=>"hello"}}, c.read_message)
+ assert_equal({ "identifier" => "{\"channel\":\"ClientTest::EchoChannel\"}", "message" => { "dong" => "hello" } }, c.read_message)
c.close
end
end
def test_interacting_clients
with_puma_server do |port|
- clients = 10.times.map { faye_client(port) }
+ clients = concurrently(10.times) { websocket_client(port) }
barrier_1 = Concurrent::CyclicBarrier.new(clients.size)
barrier_2 = Concurrent::CyclicBarrier.new(clients.size)
- clients.map {|c| Concurrent::Future.execute {
- assert_equal({"type" => "welcome"}, c.read_message) # pop the first welcome message off the stack
+ concurrently(clients) do |c|
+ assert_equal({ "type" => "welcome" }, c.read_message) # pop the first welcome message off the stack
c.send_message command: "subscribe", identifier: JSON.generate(channel: "ClientTest::EchoChannel")
- assert_equal({"identifier"=>'{"channel":"ClientTest::EchoChannel"}', "type"=>"confirm_subscription"}, c.read_message)
+ assert_equal({ "identifier" => '{"channel":"ClientTest::EchoChannel"}', "type" => "confirm_subscription" }, c.read_message)
c.send_message command: "message", identifier: JSON.generate(channel: "ClientTest::EchoChannel"), data: JSON.generate(action: "ding", message: "hello")
- assert_equal({"identifier"=>'{"channel":"ClientTest::EchoChannel"}', "message"=>{"dong"=>"hello"}}, c.read_message)
+ assert_equal({ "identifier" => '{"channel":"ClientTest::EchoChannel"}', "message" => { "dong" => "hello" } }, c.read_message)
barrier_1.wait WAIT_WHEN_EXPECTING_EVENT
c.send_message command: "message", identifier: JSON.generate(channel: "ClientTest::EchoChannel"), data: JSON.generate(action: "bulk", message: "hello")
barrier_2.wait WAIT_WHEN_EXPECTING_EVENT
assert_equal clients.size, c.read_messages(clients.size).size
- } }.each(&:wait!)
+ end
- clients.map {|c| Concurrent::Future.execute { c.close } }.each(&:wait!)
+ concurrently(clients, &:close)
end
end
def test_many_clients
with_puma_server do |port|
- clients = 100.times.map { faye_client(port) }
+ clients = concurrently(100.times) { websocket_client(port) }
- clients.map {|c| Concurrent::Future.execute {
- assert_equal({"type" => "welcome"}, c.read_message) # pop the first welcome message off the stack
+ concurrently(clients) do |c|
+ assert_equal({ "type" => "welcome" }, c.read_message) # pop the first welcome message off the stack
c.send_message command: "subscribe", identifier: JSON.generate(channel: "ClientTest::EchoChannel")
- assert_equal({"identifier"=>'{"channel":"ClientTest::EchoChannel"}', "type"=>"confirm_subscription"}, c.read_message)
+ assert_equal({ "identifier" => '{"channel":"ClientTest::EchoChannel"}', "type" => "confirm_subscription" }, c.read_message)
c.send_message command: "message", identifier: JSON.generate(channel: "ClientTest::EchoChannel"), data: JSON.generate(action: "ding", message: "hello")
- assert_equal({"identifier"=>'{"channel":"ClientTest::EchoChannel"}', "message"=>{"dong"=>"hello"}}, c.read_message)
- } }.each(&:wait!)
+ assert_equal({ "identifier" => '{"channel":"ClientTest::EchoChannel"}', "message" => { "dong" => "hello" } }, c.read_message)
+ end
- clients.map {|c| Concurrent::Future.execute { c.close } }.each(&:wait!)
+ concurrently(clients, &:close)
end
end
def test_disappearing_client
with_puma_server do |port|
- c = faye_client(port)
- assert_equal({"type" => "welcome"}, c.read_message) # pop the first welcome message off the stack
+ c = websocket_client(port)
+ assert_equal({ "type" => "welcome" }, c.read_message) # pop the first welcome message off the stack
c.send_message command: "subscribe", identifier: JSON.generate(channel: "ClientTest::EchoChannel")
- assert_equal({"identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message)
+ assert_equal({ "identifier" => "{\"channel\":\"ClientTest::EchoChannel\"}", "type" => "confirm_subscription" }, c.read_message)
c.send_message command: "message", identifier: JSON.generate(channel: "ClientTest::EchoChannel"), data: JSON.generate(action: "delay", message: "hello")
c.close # disappear before write
- c = faye_client(port)
- assert_equal({"type" => "welcome"}, c.read_message) # pop the first welcome message off the stack
+ c = websocket_client(port)
+ assert_equal({ "type" => "welcome" }, c.read_message) # pop the first welcome message off the stack
c.send_message command: "subscribe", identifier: JSON.generate(channel: "ClientTest::EchoChannel")
- assert_equal({"identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message)
+ assert_equal({ "identifier" => "{\"channel\":\"ClientTest::EchoChannel\"}", "type" => "confirm_subscription" }, c.read_message)
c.send_message command: "message", identifier: JSON.generate(channel: "ClientTest::EchoChannel"), data: JSON.generate(action: "ding", message: "hello")
- assert_equal({"identifier"=>'{"channel":"ClientTest::EchoChannel"}', "message"=>{"dong"=>"hello"}}, c.read_message)
+ assert_equal({ "identifier" => '{"channel":"ClientTest::EchoChannel"}', "message" => { "dong" => "hello" } }, c.read_message)
c.close # disappear before read
end
end
@@ -240,10 +256,10 @@ class ClientTest < ActionCable::TestCase
app = ActionCable.server
identifier = JSON.generate(channel: "ClientTest::EchoChannel")
- c = faye_client(port)
- assert_equal({"type" => "welcome"}, c.read_message)
+ c = websocket_client(port)
+ assert_equal({ "type" => "welcome" }, c.read_message)
c.send_message command: "subscribe", identifier: identifier
- assert_equal({"identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message)
+ assert_equal({ "identifier" => "{\"channel\":\"ClientTest::EchoChannel\"}", "type" => "confirm_subscription" }, c.read_message)
assert_equal(1, app.connections.count)
assert(app.remote_connections.where(identifier: identifier))
@@ -261,10 +277,10 @@ class ClientTest < ActionCable::TestCase
def test_server_restart
with_puma_server do |port|
- c = faye_client(port)
- assert_equal({"type" => "welcome"}, c.read_message)
+ c = websocket_client(port)
+ assert_equal({ "type" => "welcome" }, c.read_message)
c.send_message command: "subscribe", identifier: JSON.generate(channel: "ClientTest::EchoChannel")
- assert_equal({"identifier"=>"{\"channel\":\"ClientTest::EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message)
+ assert_equal({ "identifier" => "{\"channel\":\"ClientTest::EchoChannel\"}", "type" => "confirm_subscription" }, c.read_message)
ActionCable.server.restart
c.wait_for_close
diff --git a/actioncable/test/connection/client_socket_test.rb b/actioncable/test/connection/client_socket_test.rb
index 5043a63370..bc3ff6a3d7 100644
--- a/actioncable/test/connection/client_socket_test.rb
+++ b/actioncable/test/connection/client_socket_test.rb
@@ -33,8 +33,6 @@ class ActionCable::Connection::ClientSocketTest < ActionCable::TestCase
end
test "delegate socket errors to on_error handler" do
- skip if ENV["FAYE"].present?
-
run_in_eventmachine do
connection = open_connection
@@ -49,16 +47,16 @@ class ActionCable::Connection::ClientSocketTest < ActionCable::TestCase
end
test "closes hijacked i/o socket at shutdown" do
- skip if ENV["FAYE"].present?
-
run_in_eventmachine do
connection = open_connection
client = connection.websocket.send(:websocket)
+ event = Concurrent::Event.new
client.instance_variable_get("@stream")
.instance_variable_get("@rack_hijack_io")
- .expects(:close)
+ .define_singleton_method(:close) { event.set }
connection.close
+ event.wait
end
end
@@ -67,7 +65,13 @@ class ActionCable::Connection::ClientSocketTest < ActionCable::TestCase
env = Rack::MockRequest.env_for "/test",
"HTTP_CONNECTION" => "upgrade", "HTTP_UPGRADE" => "websocket",
"HTTP_HOST" => "localhost", "HTTP_ORIGIN" => "http://rubyonrails.com"
- env["rack.hijack"] = -> { env["rack.hijack_io"] = StringIO.new }
+ io = \
+ begin
+ Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM, 0).first
+ rescue
+ StringIO.new
+ end
+ env["rack.hijack"] = -> { env["rack.hijack_io"] = io }
Connection.new(@server, env).tap do |connection|
connection.process
diff --git a/actioncable/test/connection/cross_site_forgery_test.rb b/actioncable/test/connection/cross_site_forgery_test.rb
index 3bc59c9db9..37bedfd734 100644
--- a/actioncable/test/connection/cross_site_forgery_test.rb
+++ b/actioncable/test/connection/cross_site_forgery_test.rb
@@ -13,11 +13,13 @@ class ActionCable::Connection::CrossSiteForgeryTest < ActionCable::TestCase
setup do
@server = TestServer.new
@server.config.allowed_request_origins = %w( http://rubyonrails.com )
+ @server.config.allow_same_origin_as_host = false
end
teardown do
@server.config.disable_request_forgery_protection = false
@server.config.allowed_request_origins = []
+ @server.config.allow_same_origin_as_host = true
end
test "disable forgery protection" do
@@ -53,6 +55,13 @@ class ActionCable::Connection::CrossSiteForgeryTest < ActionCable::TestCase
assert_origin_not_allowed "http://rails.co.uk"
end
+ test "allow same origin as host" do
+ @server.config.allow_same_origin_as_host = true
+ assert_origin_allowed "http://#{HOST}"
+ assert_origin_not_allowed "http://hax.com"
+ assert_origin_not_allowed "http://rails.co.uk"
+ end
+
private
def assert_origin_allowed(origin)
response = connect_with_origin origin
diff --git a/actioncable/test/connection/identifier_test.rb b/actioncable/test/connection/identifier_test.rb
index a4dfcc06f0..f3d3bc0603 100644
--- a/actioncable/test/connection/identifier_test.rb
+++ b/actioncable/test/connection/identifier_test.rb
@@ -53,7 +53,7 @@ class ActionCable::Connection::IdentifierTest < ActionCable::TestCase
end
end
- protected
+ private
def open_connection_with_stubbed_pubsub
server = TestServer.new
server.stubs(:adapter).returns(stub_everything("adapter"))
diff --git a/actioncable/test/connection/multiple_identifiers_test.rb b/actioncable/test/connection/multiple_identifiers_test.rb
index 67e68355c5..ca1a08f4d6 100644
--- a/actioncable/test/connection/multiple_identifiers_test.rb
+++ b/actioncable/test/connection/multiple_identifiers_test.rb
@@ -19,7 +19,7 @@ class ActionCable::Connection::MultipleIdentifiersTest < ActionCable::TestCase
end
end
- protected
+ private
def open_connection_with_stubbed_pubsub
server = TestServer.new
server.stubs(:pubsub).returns(stub_everything("pubsub"))
diff --git a/actioncable/test/connection/stream_test.rb b/actioncable/test/connection/stream_test.rb
index 4128b32f15..36e1d3c095 100644
--- a/actioncable/test/connection/stream_test.rb
+++ b/actioncable/test/connection/stream_test.rb
@@ -34,8 +34,6 @@ class ActionCable::Connection::StreamTest < ActionCable::TestCase
[ EOFError, Errno::ECONNRESET ].each do |closed_exception|
test "closes socket on #{closed_exception}" do
- skip if ENV["FAYE"].present?
-
run_in_eventmachine do
connection = open_connection
diff --git a/actioncable/test/connection/string_identifier_test.rb b/actioncable/test/connection/string_identifier_test.rb
index 87484765e5..6d53e249cb 100644
--- a/actioncable/test/connection/string_identifier_test.rb
+++ b/actioncable/test/connection/string_identifier_test.rb
@@ -21,7 +21,7 @@ class ActionCable::Connection::StringIdentifierTest < ActionCable::TestCase
end
end
- protected
+ private
def open_connection_with_stubbed_pubsub
@server = TestServer.new
@server.stubs(:pubsub).returns(stub_everything("pubsub"))
diff --git a/actioncable/test/javascript/src/test_helpers/consumer_test_helper.coffee b/actioncable/test/javascript/src/test_helpers/consumer_test_helper.coffee
index 6b145dede8..a9e95c37f0 100644
--- a/actioncable/test/javascript/src/test_helpers/consumer_test_helper.coffee
+++ b/actioncable/test/javascript/src/test_helpers/consumer_test_helper.coffee
@@ -21,6 +21,16 @@ TestHelpers.consumerTest = (name, options = {}, callback) ->
assert.equal clients.length, 1
assert.equal clients[0].readyState, WebSocket.OPEN
+ server.broadcastTo = (subscription, data = {}, callback) ->
+ data.identifier = subscription.identifier
+
+ if data.message_type
+ data.type = ActionCable.INTERNAL.message_types[data.message_type]
+ delete data.message_type
+
+ server.send(JSON.stringify(data))
+ TestHelpers.defer(callback)
+
done = ->
consumer.disconnect()
server.close()
diff --git a/actioncable/test/javascript/src/test_helpers/index.coffee b/actioncable/test/javascript/src/test_helpers/index.coffee
index d36524d9cc..c84cbbcb2c 100644
--- a/actioncable/test/javascript/src/test_helpers/index.coffee
+++ b/actioncable/test/javascript/src/test_helpers/index.coffee
@@ -4,5 +4,8 @@
ActionCable.TestHelpers =
testURL: "ws://cable.example.com/"
+ defer: (callback) ->
+ setTimeout(callback, 1)
+
originalWebSocket = ActionCable.WebSocket
QUnit.testDone -> ActionCable.WebSocket = originalWebSocket
diff --git a/actioncable/test/javascript/src/unit/subscription_test.coffee b/actioncable/test/javascript/src/unit/subscription_test.coffee
new file mode 100644
index 0000000000..07027ed170
--- /dev/null
+++ b/actioncable/test/javascript/src/unit/subscription_test.coffee
@@ -0,0 +1,40 @@
+{module, test} = QUnit
+{consumerTest} = ActionCable.TestHelpers
+
+module "ActionCable.Subscription", ->
+ consumerTest "#initialized callback", ({server, consumer, assert, done}) ->
+ consumer.subscriptions.create "chat",
+ initialized: ->
+ assert.ok true
+ done()
+
+ consumerTest "#connected callback", ({server, consumer, assert, done}) ->
+ subscription = consumer.subscriptions.create "chat",
+ connected: ->
+ assert.ok true
+ done()
+
+ server.broadcastTo(subscription, message_type: "confirmation")
+
+ consumerTest "#disconnected callback", ({server, consumer, assert, done}) ->
+ subscription = consumer.subscriptions.create "chat",
+ disconnected: ->
+ assert.ok true
+ done()
+
+ server.broadcastTo subscription, message_type: "confirmation", ->
+ server.close()
+
+ consumerTest "#perform", ({consumer, server, assert, done}) ->
+ subscription = consumer.subscriptions.create "chat",
+ connected: ->
+ @perform(publish: "hi")
+
+ server.on "message", (message) ->
+ data = JSON.parse(message)
+ assert.equal data.identifier, subscription.identifier
+ assert.equal data.command, "message"
+ assert.deepEqual data.data, JSON.stringify(action: { publish: "hi" })
+ done()
+
+ server.broadcastTo(subscription, message_type: "confirmation")
diff --git a/actioncable/test/javascript/src/unit/subscriptions_test.coffee b/actioncable/test/javascript/src/unit/subscriptions_test.coffee
new file mode 100644
index 0000000000..170b370e4a
--- /dev/null
+++ b/actioncable/test/javascript/src/unit/subscriptions_test.coffee
@@ -0,0 +1,25 @@
+{module, test} = QUnit
+{consumerTest} = ActionCable.TestHelpers
+
+module "ActionCable.Subscriptions", ->
+ consumerTest "create subscription with channel string", ({consumer, server, assert, done}) ->
+ channel = "chat"
+
+ server.on "message", (message) ->
+ data = JSON.parse(message)
+ assert.equal data.command, "subscribe"
+ assert.equal data.identifier, JSON.stringify({channel})
+ done()
+
+ consumer.subscriptions.create(channel)
+
+ consumerTest "create subscription with channel object", ({consumer, server, assert, done}) ->
+ channel = channel: "chat", room: "action"
+
+ server.on "message", (message) ->
+ data = JSON.parse(message)
+ assert.equal data.command, "subscribe"
+ assert.equal data.identifier, JSON.stringify(channel)
+ done()
+
+ consumer.subscriptions.create(channel)
diff --git a/actioncable/test/server/base_test.rb b/actioncable/test/server/base_test.rb
new file mode 100644
index 0000000000..f0a51c5a7d
--- /dev/null
+++ b/actioncable/test/server/base_test.rb
@@ -0,0 +1,33 @@
+require "test_helper"
+require "stubs/test_server"
+require "active_support/core_ext/hash/indifferent_access"
+
+class BaseTest < ActiveSupport::TestCase
+ def setup
+ @server = ActionCable::Server::Base.new
+ @server.config.cable = { adapter: "async" }.with_indifferent_access
+ end
+
+ class FakeConnection
+ def close
+ end
+ end
+
+ test "#restart closes all open connections" do
+ conn = FakeConnection.new
+ @server.add_connection(conn)
+
+ conn.expects(:close)
+ @server.restart
+ end
+
+ test "#restart shuts down worker pool" do
+ @server.worker_pool.expects(:halt)
+ @server.restart
+ end
+
+ test "#restart shuts down pub/sub adapter" do
+ @server.pubsub.expects(:shutdown)
+ @server.restart
+ end
+end
diff --git a/actioncable/test/stubs/room.rb b/actioncable/test/stubs/room.rb
index 9e521cf3a6..1664b07d12 100644
--- a/actioncable/test/stubs/room.rb
+++ b/actioncable/test/stubs/room.rb
@@ -1,7 +1,7 @@
class Room
attr_reader :id, :name
- def initialize(id, name="Campfire")
+ def initialize(id, name = "Campfire")
@id = id
@name = name
end
diff --git a/actioncable/test/stubs/test_server.rb b/actioncable/test/stubs/test_server.rb
index b64ff33789..5bf2a151dc 100644
--- a/actioncable/test/stubs/test_server.rb
+++ b/actioncable/test/stubs/test_server.rb
@@ -10,12 +10,6 @@ class TestServer
@logger = ActiveSupport::TaggedLogging.new ActiveSupport::Logger.new(StringIO.new)
@config = OpenStruct.new(log_tags: [], subscription_adapter: subscription_adapter)
- @config.use_faye = ENV["FAYE"].present?
- @config.client_socket_class = if @config.use_faye
- ActionCable::Connection::FayeClientSocket
- else
- ActionCable::Connection::ClientSocket
- end
@mutex = Monitor.new
end
@@ -25,10 +19,8 @@ class TestServer
end
def event_loop
- @event_loop ||= if @config.use_faye
- ActionCable::Connection::FayeEventLoop.new
- else
- ActionCable::Connection::StreamEventLoop.new
+ @event_loop ||= ActionCable::Connection::StreamEventLoop.new.tap do |loop|
+ loop.instance_variable_set(:@executor, Concurrent.global_io_executor)
end
end
diff --git a/actioncable/test/subscription_adapter/common.rb b/actioncable/test/subscription_adapter/common.rb
index 1538157995..3aa88c2caa 100644
--- a/actioncable/test/subscription_adapter/common.rb
+++ b/actioncable/test/subscription_adapter/common.rb
@@ -11,7 +11,7 @@ module CommonSubscriptionAdapterTest
def setup
server = ActionCable::Server::Base.new
server.config.cable = cable_config.with_indifferent_access
- server.config.use_faye = ENV["FAYE"].present?
+ server.config.logger = Logger.new(StringIO.new).tap { |l| l.level = Logger::UNKNOWN }
adapter_klass = server.config.pubsub_adapter
@@ -20,7 +20,7 @@ module CommonSubscriptionAdapterTest
end
def teardown
- [@rx_adapter, @tx_adapter].uniq.each(&:shutdown)
+ [@rx_adapter, @tx_adapter].uniq.compact.each(&:shutdown)
end
def subscribe_as_queue(channel, adapter = @rx_adapter)
diff --git a/actioncable/test/subscription_adapter/evented_redis_test.rb b/actioncable/test/subscription_adapter/evented_redis_test.rb
index d6ca0e77cb..2401950aa7 100644
--- a/actioncable/test/subscription_adapter/evented_redis_test.rb
+++ b/actioncable/test/subscription_adapter/evented_redis_test.rb
@@ -12,9 +12,43 @@ class EventedRedisAdapterTest < ActionCable::TestCase
end
def teardown
+ super
+
+ # Ensure EM is shut down before we re-enable warnings
+ EventMachine.reactor_thread.tap do |thread|
+ EventMachine.stop
+ thread.join
+ end
+
$VERBOSE = @previous_verbose
end
+ def test_slow_eventmachine
+ require "eventmachine"
+ require "thread"
+
+ lock = Mutex.new
+
+ EventMachine.singleton_class.class_eval do
+ alias_method :delayed_initialize_event_machine, :initialize_event_machine
+ define_method(:initialize_event_machine) do
+ lock.synchronize do
+ sleep 0.5
+ delayed_initialize_event_machine
+ end
+ end
+ end
+
+ test_basic_broadcast
+ ensure
+ lock.synchronize do
+ EventMachine.singleton_class.class_eval do
+ alias_method :initialize_event_machine, :delayed_initialize_event_machine
+ remove_method :delayed_initialize_event_machine
+ end
+ end
+ end
+
def cable_config
{ adapter: "evented_redis", url: "redis://127.0.0.1:6379/12" }
end
diff --git a/actioncable/test/test_helper.rb b/actioncable/test/test_helper.rb
index 469d69adf9..a47032753b 100644
--- a/actioncable/test/test_helper.rb
+++ b/actioncable/test/test_helper.rb
@@ -11,43 +11,9 @@ rescue LoadError
end
# Require all the stubs and models
-Dir[File.dirname(__FILE__) + "/stubs/*.rb"].each {|file| require file }
+Dir[File.dirname(__FILE__) + "/stubs/*.rb"].each { |file| require file }
-if ENV["FAYE"].present?
- require "faye/websocket"
- class << Faye::WebSocket
- remove_method :ensure_reactor_running
-
- # We don't want Faye to start the EM reactor in tests because it makes testing much harder.
- # We want to be able to start and stop EM loop in tests to make things simpler.
- def ensure_reactor_running
- # no-op
- end
- end
-end
-
-module EventMachineConcurrencyHelpers
- def wait_for_async
- EM.run_deferred_callbacks
- end
-
- def run_in_eventmachine
- failure = nil
- EM.run do
- begin
- yield
- rescue => ex
- failure = ex
- ensure
- wait_for_async
- EM.stop if EM.reactor_running?
- end
- end
- raise failure if failure
- end
-end
-
-module ConcurrentRubyConcurrencyHelpers
+class ActionCable::TestCase < ActiveSupport::TestCase
def wait_for_async
wait_for_executor Concurrent.global_io_executor
end
@@ -56,18 +22,14 @@ module ConcurrentRubyConcurrencyHelpers
yield
wait_for_async
end
-end
-
-class ActionCable::TestCase < ActiveSupport::TestCase
- if ENV["FAYE"].present?
- include EventMachineConcurrencyHelpers
- else
- include ConcurrentRubyConcurrencyHelpers
- end
def wait_for_executor(executor)
+ # do not wait forever, wait 2s
+ timeout = 2
until executor.completed_task_count == executor.scheduled_task_count
sleep 0.1
+ timeout -= 0.1
+ raise "Executor could not complete all tasks in 2 seconds" unless timeout > 0
end
end
end
diff --git a/actioncable/test/worker_test.rb b/actioncable/test/worker_test.rb
index 63dc453840..3385593f74 100644
--- a/actioncable/test/worker_test.rb
+++ b/actioncable/test/worker_test.rb
@@ -9,7 +9,7 @@ class WorkerTest < ActiveSupport::TestCase
end
def process(message)
- @last_action = [ :process, message ]
+ @last_action = [ :process, message ]
end
def connection
diff --git a/actionmailer/MIT-LICENSE b/actionmailer/MIT-LICENSE
index 8573eb1225..ac810e86d0 100644
--- a/actionmailer/MIT-LICENSE
+++ b/actionmailer/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2016 David Heinemeier Hansson
+Copyright (c) 2004-2017 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile
index 059d98fb28..6f05d236d9 100644
--- a/actionmailer/Rakefile
+++ b/actionmailer/Rakefile
@@ -18,6 +18,6 @@ namespace :test do
task :isolated do
Dir.glob("test/**/*_test.rb").all? do |file|
sh(Gem.ruby, "-w", "-Ilib:test", file)
- end or raise "Failures"
+ end || raise("Failures")
end
end
diff --git a/actionmailer/bin/test b/actionmailer/bin/test
index 84a05bba08..a7beb14b27 100755
--- a/actionmailer/bin/test
+++ b/actionmailer/bin/test
@@ -2,5 +2,3 @@
COMPONENT_ROOT = File.expand_path("..", __dir__)
require File.expand_path("../tools/test", COMPONENT_ROOT)
-
-exit Minitest.run(ARGV)
diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb
index 668bc99435..cf2c0f37e3 100644
--- a/actionmailer/lib/action_mailer.rb
+++ b/actionmailer/lib/action_mailer.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2016 David Heinemeier Hansson
+# Copyright (c) 2004-2017 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index fb6ff819c9..19408f2a48 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -544,9 +544,9 @@ module ActionMailer
end
end
- protected
+ private
- def set_payload_for_mail(payload, mail) #:nodoc:
+ def set_payload_for_mail(payload, mail)
payload[:mailer] = name
payload[:message_id] = mail.message_id
payload[:subject] = mail.subject
@@ -558,7 +558,7 @@ module ActionMailer
payload[:mail] = mail.encoded
end
- def method_missing(method_name, *args) # :nodoc:
+ def method_missing(method_name, *args)
if action_methods.include?(method_name.to_s)
MessageDelivery.new(self, method_name, *args)
else
@@ -566,9 +566,7 @@ module ActionMailer
end
end
- private
-
- def respond_to_missing?(method, include_all = false) #:nodoc:
+ def respond_to_missing?(method, include_all = false)
action_methods.include?(method.to_s)
end
end
@@ -601,7 +599,7 @@ module ActionMailer
def body; "" end
def header; {} end
- def respond_to?(string, include_all=false)
+ def respond_to?(string, include_all = false)
true
end
@@ -830,18 +828,18 @@ module ActionMailer
message
end
- protected
+ private
- # Used by #mail to set the content type of the message.
- #
- # It will use the given +user_content_type+, or multipart if the mail
- # message has any attachments. If the attachments are inline, the content
- # type will be "multipart/related", otherwise "multipart/mixed".
- #
- # If there is no content type passed in via headers, and there are no
- # attachments, or the message is multipart, then the default content type is
- # used.
- def set_content_type(m, user_content_type, class_default)
+ # Used by #mail to set the content type of the message.
+ #
+ # It will use the given +user_content_type+, or multipart if the mail
+ # message has any attachments. If the attachments are inline, the content
+ # type will be "multipart/related", otherwise "multipart/mixed".
+ #
+ # If there is no content type passed in via headers, and there are no
+ # attachments, or the message is multipart, then the default content type is
+ # used.
+ def set_content_type(m, user_content_type, class_default) # :doc:
params = m.content_type_parameters || {}
case
when user_content_type.present?
@@ -859,22 +857,20 @@ module ActionMailer
end
end
- # Translates the +subject+ using Rails I18n class under <tt>[mailer_scope, action_name]</tt> scope.
- # If it does not find a translation for the +subject+ under the specified scope it will default to a
- # humanized version of the <tt>action_name</tt>.
- # If the subject has interpolations, you can pass them through the +interpolations+ parameter.
- def default_i18n_subject(interpolations = {})
+ # Translates the +subject+ using Rails I18n class under <tt>[mailer_scope, action_name]</tt> scope.
+ # If it does not find a translation for the +subject+ under the specified scope it will default to a
+ # humanized version of the <tt>action_name</tt>.
+ # If the subject has interpolations, you can pass them through the +interpolations+ parameter.
+ def default_i18n_subject(interpolations = {}) # :doc:
mailer_scope = self.class.mailer_name.tr("/", ".")
I18n.t(:subject, interpolations.merge(scope: [mailer_scope, action_name], default: action_name.humanize))
end
- # Emails do not support relative path links.
- def self.supports_path?
+ # Emails do not support relative path links.
+ def self.supports_path? # :doc:
false
end
- private
-
def apply_defaults(headers)
default_values = self.class.default.map do |key, value|
[
@@ -933,7 +929,7 @@ module ActionMailer
def create_parts_from_responses(m, responses)
if responses.size == 1 && !m.has_attachments?
- responses[0].each { |k,v| m[k] = v }
+ responses[0].each { |k, v| m[k] = v }
elsif responses.size > 1 && m.has_attachments?
container = Mail::Part.new
container.content_type = "multipart/alternative"
@@ -950,7 +946,7 @@ module ActionMailer
container.add_part(part)
end
- # This and #instrument_name is for caching instrument
+ # This and #instrument_name is for caching instrument
def instrument_payload(key)
{
mailer: mailer_name,
diff --git a/actionmailer/lib/action_mailer/delivery_methods.rb b/actionmailer/lib/action_mailer/delivery_methods.rb
index 6be2c91da6..be98f4c65e 100644
--- a/actionmailer/lib/action_mailer/delivery_methods.rb
+++ b/actionmailer/lib/action_mailer/delivery_methods.rb
@@ -52,13 +52,13 @@ module ActionMailer
# add_delivery_method :sendmail, Mail::Sendmail,
# location: '/usr/sbin/sendmail',
# arguments: '-i'
- def add_delivery_method(symbol, klass, default_options={})
+ def add_delivery_method(symbol, klass, default_options = {})
class_attribute(:"#{symbol}_settings") unless respond_to?(:"#{symbol}_settings")
send(:"#{symbol}_settings=", default_options)
self.delivery_methods = delivery_methods.merge(symbol.to_sym => klass).freeze
end
- def wrap_delivery_behavior(mail, method=nil, options=nil) # :nodoc:
+ def wrap_delivery_behavior(mail, method = nil, options = nil) # :nodoc:
method ||= self.delivery_method
mail.delivery_handler = self
diff --git a/actionmailer/lib/action_mailer/inline_preview_interceptor.rb b/actionmailer/lib/action_mailer/inline_preview_interceptor.rb
index fd9a7bce15..9087d335fa 100644
--- a/actionmailer/lib/action_mailer/inline_preview_interceptor.rb
+++ b/actionmailer/lib/action_mailer/inline_preview_interceptor.rb
@@ -11,7 +11,7 @@ module ActionMailer
# ActionMailer::Base.preview_interceptors.delete(ActionMailer::InlinePreviewInterceptor)
#
class InlinePreviewInterceptor
- PATTERN = /src=(?:"cid:[^"]+"|'cid:[^']+')/i
+ PATTERN = /src=(?:"cid:[^"]+"|'cid:[^']+')/i
include Base64
@@ -55,7 +55,7 @@ module ActionMailer
end
def find_part(cid)
- message.all_parts.find{ |p| p.attachment? && p.cid == cid }
+ message.all_parts.find { |p| p.attachment? && p.cid == cid }
end
end
end
diff --git a/actionmailer/lib/action_mailer/message_delivery.rb b/actionmailer/lib/action_mailer/message_delivery.rb
index 994d297768..cf7c57e6bf 100644
--- a/actionmailer/lib/action_mailer/message_delivery.rb
+++ b/actionmailer/lib/action_mailer/message_delivery.rb
@@ -56,7 +56,7 @@ module ActionMailer
# * <tt>:wait</tt> - Enqueue the email to be delivered with a delay
# * <tt>:wait_until</tt> - Enqueue the email to be delivered at (after) a specific date / time
# * <tt>:queue</tt> - Enqueue the email on the specified queue
- def deliver_later!(options={})
+ def deliver_later!(options = {})
enqueue_delivery :deliver_now!, options
end
@@ -72,7 +72,7 @@ module ActionMailer
# * <tt>:wait</tt> - Enqueue the email to be delivered with a delay.
# * <tt>:wait_until</tt> - Enqueue the email to be delivered at (after) a specific date / time.
# * <tt>:queue</tt> - Enqueue the email on the specified queue.
- def deliver_later(options={})
+ def deliver_later(options = {})
enqueue_delivery :deliver_now, options
end
@@ -106,7 +106,7 @@ module ActionMailer
end
end
- def enqueue_delivery(delivery_method, options={})
+ def enqueue_delivery(delivery_method, options = {})
if processed?
::Kernel.raise "You've accessed the message before asking to " \
"deliver it later, so you may have made local changes that would " \
diff --git a/actionmailer/lib/action_mailer/preview.rb b/actionmailer/lib/action_mailer/preview.rb
index 7612cddf22..c147ca78d0 100644
--- a/actionmailer/lib/action_mailer/preview.rb
+++ b/actionmailer/lib/action_mailer/preview.rb
@@ -34,11 +34,12 @@ module ActionMailer
# Either a class or a string can be passed in as the Interceptor. If a
# string is passed in it will be <tt>constantize</tt>d.
def register_preview_interceptor(interceptor)
- preview_interceptor = case interceptor
- when String, Symbol
- interceptor.to_s.camelize.constantize
+ preview_interceptor = \
+ case interceptor
+ when String, Symbol
+ interceptor.to_s.camelize.constantize
else
- interceptor
+ interceptor
end
unless preview_interceptors.include?(preview_interceptor)
@@ -80,12 +81,12 @@ module ActionMailer
# Returns true if the preview exists.
def exists?(preview)
- all.any?{ |p| p.preview_name == preview }
+ all.any? { |p| p.preview_name == preview }
end
# Find a mailer preview by its underscored class name.
def find(preview)
- all.find{ |p| p.preview_name == preview }
+ all.find { |p| p.preview_name == preview }
end
# Returns the underscored name of the mailer preview without the suffix.
@@ -93,22 +94,22 @@ module ActionMailer
name.sub(/Preview$/, "").underscore
end
- protected
- def load_previews #:nodoc:
+ private
+ def load_previews
if preview_path
- Dir["#{preview_path}/**/*_preview.rb"].each{ |file| require_dependency file }
+ Dir["#{preview_path}/**/*_preview.rb"].each { |file| require_dependency file }
end
end
- def preview_path #:nodoc:
+ def preview_path
Base.preview_path
end
- def show_previews #:nodoc:
+ def show_previews
Base.show_previews
end
- def inform_preview_interceptors(message) #:nodoc:
+ def inform_preview_interceptors(message)
Base.preview_interceptors.each do |interceptor|
interceptor.previewing_email(message)
end
diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb
index c47d7781cc..913df8cf93 100644
--- a/actionmailer/lib/action_mailer/railtie.rb
+++ b/actionmailer/lib/action_mailer/railtie.rb
@@ -28,7 +28,7 @@ module ActionMailer
options.cache_store ||= Rails.cache
if options.show_previews
- options.preview_path ||= defined?(Rails.root) ? "#{Rails.root}/test/mailers/previews" : nil
+ options.preview_path ||= defined?(Rails.root) ? "#{Rails.root}/test/mailers/previews" : nil
end
# make sure readers methods get compiled
@@ -44,7 +44,7 @@ module ActionMailer
register_preview_interceptors(options.delete(:preview_interceptors))
register_observers(options.delete(:observers))
- options.each { |k,v| send("#{k}=", v) }
+ options.each { |k, v| send("#{k}=", v) }
end
ActiveSupport.on_load(:action_dispatch_integration_test) { include ActionMailer::TestCase::ClearTestDeliveries }
diff --git a/actionmailer/lib/action_mailer/test_case.rb b/actionmailer/lib/action_mailer/test_case.rb
index d3eded547c..ebc7b37961 100644
--- a/actionmailer/lib/action_mailer/test_case.rb
+++ b/actionmailer/lib/action_mailer/test_case.rb
@@ -41,6 +41,7 @@ module ActionMailer
setup :initialize_test_deliveries
setup :set_expected_mail
teardown :restore_test_deliveries
+ ActiveSupport.run_load_hooks(:action_mailer_test_case, self)
end
module ClassMethods
@@ -72,38 +73,36 @@ module ActionMailer
end
end
- protected
+ private
- def initialize_test_deliveries # :nodoc:
+ def initialize_test_deliveries
set_delivery_method :test
@old_perform_deliveries = ActionMailer::Base.perform_deliveries
ActionMailer::Base.perform_deliveries = true
ActionMailer::Base.deliveries.clear
end
- def restore_test_deliveries # :nodoc:
+ def restore_test_deliveries
restore_delivery_method
ActionMailer::Base.perform_deliveries = @old_perform_deliveries
end
- def set_delivery_method(method) # :nodoc:
+ def set_delivery_method(method)
@old_delivery_method = ActionMailer::Base.delivery_method
ActionMailer::Base.delivery_method = method
end
- def restore_delivery_method # :nodoc:
+ def restore_delivery_method
ActionMailer::Base.deliveries.clear
ActionMailer::Base.delivery_method = @old_delivery_method
end
- def set_expected_mail # :nodoc:
+ def set_expected_mail
@expected = Mail.new
@expected.content_type ["text", "plain", { "charset" => charset }]
@expected.mime_version = "1.0"
end
- private
-
def charset
"UTF-8"
end
diff --git a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb
index 9dd7ee7a27..4a8d0178de 100644
--- a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb
+++ b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb
@@ -19,12 +19,11 @@ module Rails
hook_for :template_engine, :test_framework
- protected
- def file_name
+ private
+ def file_name # :doc:
@_file_name ||= super.gsub(/_mailer/i, "")
end
- private
def application_mailer_file_name
@_application_mailer_file_name ||= if mountable_engine?
"app/mailers/#{namespaced_path}/application_mailer.rb"
diff --git a/actionmailer/test/assert_select_email_test.rb b/actionmailer/test/assert_select_email_test.rb
index 820d7f34a7..bf14fe0853 100644
--- a/actionmailer/test/assert_select_email_test.rb
+++ b/actionmailer/test/assert_select_email_test.rb
@@ -11,8 +11,8 @@ class AssertSelectEmailTest < ActionMailer::TestCase
class AssertMultipartSelectMailer < ActionMailer::Base
def test(options)
mail subject: "Test e-mail", from: "test@test.host", to: "test <test@test.host>" do |format|
- format.text { render text: options[:text] }
- format.html { render text: options[:html] }
+ format.text { render plain: options[:text] }
+ format.html { render plain: options[:html] }
end
end
end
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index 8152f28663..91049e33f9 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -87,7 +87,7 @@ class BaseTest < ActiveSupport::TestCase
end
test "can pass random headers in as a hash to mail" do
- hash = {"X-Special-Domain-Specific-Header" => "SecretValue",
+ hash = { "X-Special-Domain-Specific-Header" => "SecretValue",
"In-Reply-To" => "1234@mikel.me.com" }
mail = BaseMailer.welcome(hash)
assert_equal("SecretValue", mail["X-Special-Domain-Specific-Header"].decoded)
@@ -95,7 +95,7 @@ class BaseTest < ActiveSupport::TestCase
end
test "can pass random headers in as a hash to headers" do
- hash = {"X-Special-Domain-Specific-Header" => "SecretValue",
+ hash = { "X-Special-Domain-Specific-Header" => "SecretValue",
"In-Reply-To" => "1234@mikel.me.com" }
mail = BaseMailer.welcome_with_headers(hash)
assert_equal("SecretValue", mail["X-Special-Domain-Specific-Header"].decoded)
@@ -215,7 +215,7 @@ class BaseTest < ActiveSupport::TestCase
email = BaseMailer.welcome(subject: nil)
assert_equal "Welcome", email.subject
- with_translation "en", base_mailer: {welcome: {subject: "New Subject!"}} do
+ with_translation "en", base_mailer: { welcome: { subject: "New Subject!" } } do
email = BaseMailer.welcome(subject: nil)
assert_equal "New Subject!", email.subject
end
@@ -223,14 +223,14 @@ class BaseTest < ActiveSupport::TestCase
end
test "default subject can have interpolations" do
- with_translation "en", base_mailer: {with_subject_interpolations: {subject: "Will the real %{rapper_or_impersonator} please stand up?"}} do
+ with_translation "en", base_mailer: { with_subject_interpolations: { subject: "Will the real %{rapper_or_impersonator} please stand up?" } } do
email = BaseMailer.with_subject_interpolations
assert_equal "Will the real Slim Shady please stand up?", email.subject
end
end
test "translations are scoped properly" do
- with_translation "en", base_mailer: {email_with_translations: {greet_user: "Hello %{name}!"}} do
+ with_translation "en", base_mailer: { email_with_translations: { greet_user: "Hello %{name}!" } } do
email = BaseMailer.email_with_translations
assert_equal "Hello lifo!", email.body.encoded
end
@@ -271,7 +271,7 @@ class BaseTest < ActiveSupport::TestCase
assert_nothing_raised { mail.message }
assert_equal ["image/jpeg; filename=controller_attachments.jpg",
- "image/jpeg; filename=attachments.jpg"], mail.attachments.inline.map {|a| a["Content-Type"].to_s }
+ "image/jpeg; filename=attachments.jpg"], mail.attachments.inline.map { |a| a["Content-Type"].to_s }
end
test "accessing attachments works after mail was called" do
@@ -798,7 +798,7 @@ class BaseTest < ActiveSupport::TestCase
test "default_from can be set" do
class DefaultFromMailer < ActionMailer::Base
default to: "system@test.lindsaar.net"
- self.default_options = {from: "robert.pankowecki@gmail.com"}
+ self.default_options = { from: "robert.pankowecki@gmail.com" }
def welcome
mail(subject: "subject", body: "hello world")
@@ -829,7 +829,7 @@ class BaseTest < ActiveSupport::TestCase
assert_equal "special indeed!", mail["X-Special-Header"].to_s
end
- protected
+ private
# Execute the block setting the given values and restoring old values after
# the block is executed.
diff --git a/actionmailer/test/caching_test.rb b/actionmailer/test/caching_test.rb
index d7e21d4c8e..cff49c8894 100644
--- a/actionmailer/test/caching_test.rb
+++ b/actionmailer/test/caching_test.rb
@@ -226,7 +226,7 @@ class CacheHelperOutputBufferTest < BaseCachingTest
cache_helper.stub :output_buffer, output_buffer do
assert_called_with cache_helper, :output_buffer=, [output_buffer.class.new(output_buffer)] do
assert_nothing_raised do
- cache_helper.send :fragment_for, "Test fragment name", "Test fragment", &Proc.new{ nil }
+ cache_helper.send :fragment_for, "Test fragment name", "Test fragment", &Proc.new { nil }
end
end
end
@@ -247,7 +247,7 @@ class CacheHelperOutputBufferTest < BaseCachingTest
cache_helper.stub :output_buffer, output_buffer do
assert_called_with cache_helper, :output_buffer=, [output_buffer.class.new(output_buffer)] do
assert_nothing_raised do
- cache_helper.send :fragment_for, "Test fragment name", "Test fragment", &Proc.new{ nil }
+ cache_helper.send :fragment_for, "Test fragment name", "Test fragment", &Proc.new { nil }
end
end
end
diff --git a/actionmailer/test/delivery_methods_test.rb b/actionmailer/test/delivery_methods_test.rb
index a508c0aae9..f64a69019f 100644
--- a/actionmailer/test/delivery_methods_test.rb
+++ b/actionmailer/test/delivery_methods_test.rb
@@ -32,7 +32,7 @@ class DefaultsDeliveryMethodsTest < ActiveSupport::TestCase
end
test "default file delivery settings (with Rails.root)" do
- settings = {location: "#{Rails.root}/tmp/mails"}
+ settings = { location: "#{Rails.root}/tmp/mails" }
assert_equal settings, ActionMailer::Base.file_settings
end
@@ -87,7 +87,7 @@ class MailDeliveryTest < ActiveSupport::TestCase
from: "jose@test.plataformatec.com"
}
- def welcome(hash={})
+ def welcome(hash = {})
mail(DEFAULT_HEADERS.merge(hash))
end
end
@@ -127,16 +127,16 @@ class MailDeliveryTest < ActiveSupport::TestCase
end
test "delivery method options default to class level options" do
- default_options = {a: "b"}
+ default_options = { a: "b" }
ActionMailer::Base.add_delivery_method :optioned, MyOptionedDelivery, default_options
mail_instance = DeliveryMailer.welcome(delivery_method: :optioned)
assert_equal default_options, mail_instance.delivery_method.options
end
test "delivery method options can be overridden per mail instance" do
- default_options = {a: "b"}
+ default_options = { a: "b" }
ActionMailer::Base.add_delivery_method :optioned, MyOptionedDelivery, default_options
- overridden_options = {a: "a"}
+ overridden_options = { a: "a" }
mail_instance = DeliveryMailer.welcome(delivery_method: :optioned, delivery_method_options: overridden_options)
assert_equal overridden_options, mail_instance.delivery_method.options
end
@@ -152,7 +152,7 @@ class MailDeliveryTest < ActiveSupport::TestCase
enable_starttls_auto: true
}
assert_equal settings, ActionMailer::Base.smtp_settings
- overridden_options = {user_name: "overridden", password: "somethingobtuse"}
+ overridden_options = { user_name: "overridden", password: "somethingobtuse" }
mail_instance = DeliveryMailer.welcome(delivery_method_options: overridden_options)
delivery_method_instance = mail_instance.delivery_method
assert_equal "overridden", delivery_method_instance.settings[:user_name]
diff --git a/actionmailer/test/i18n_with_controller_test.rb b/actionmailer/test/i18n_with_controller_test.rb
index 6370213043..a87a9c9861 100644
--- a/actionmailer/test/i18n_with_controller_test.rb
+++ b/actionmailer/test/i18n_with_controller_test.rb
@@ -18,7 +18,7 @@ end
class TestController < ActionController::Base
def send_mail
email = I18nTestMailer.mail_with_i18n_subject("test@localhost").deliver_now
- render text: "Mail sent - Subject: #{email.subject}"
+ render plain: "Mail sent - Subject: #{email.subject}"
end
end
@@ -66,7 +66,7 @@ class ActionMailerI18nWithControllerTest < ActionDispatch::IntegrationTest
end
end
- protected
+ private
def with_translation(locale, data)
I18n.backend.store_translations(locale, data)
diff --git a/actionmailer/test/mail_helper_test.rb b/actionmailer/test/mail_helper_test.rb
index 972629e477..a2e04b5f48 100644
--- a/actionmailer/test/mail_helper_test.rb
+++ b/actionmailer/test/mail_helper_test.rb
@@ -65,7 +65,7 @@ The second
end
end
- protected
+ private
def mail_with_defaults(&block)
mail(to: "test@localhost", from: "tester@example.com",
diff --git a/actionmailer/test/mailers/base_mailer.rb b/actionmailer/test/mailers/base_mailer.rb
index fd7397be84..2a8884959c 100644
--- a/actionmailer/test/mailers/base_mailer.rb
+++ b/actionmailer/test/mailers/base_mailer.rb
@@ -7,7 +7,7 @@ class BaseMailer < ActionMailer::Base
def welcome(hash = {})
headers["X-SPAM"] = "Not SPAM"
- mail({subject: "The first email on new API!"}.merge!(hash))
+ mail({ subject: "The first email on new API!" }.merge!(hash))
end
def welcome_with_headers(hash = {})
@@ -62,8 +62,8 @@ class BaseMailer < ActionMailer::Base
def explicit_multipart(hash = {})
attachments["invoice.pdf"] = "This is test File content" if hash.delete(:attachments)
mail(hash) do |format|
- format.text { render text: "TEXT Explicit Multipart" }
- format.html { render text: "HTML Explicit Multipart" }
+ format.text { render plain: "TEXT Explicit Multipart" }
+ format.html { render plain: "HTML Explicit Multipart" }
end
end
@@ -76,7 +76,7 @@ class BaseMailer < ActionMailer::Base
def explicit_multipart_with_any(hash = {})
mail(hash) do |format|
- format.any(:text, :html){ render text: "Format with any!" }
+ format.any(:text, :html) { render plain: "Format with any!" }
end
end
@@ -88,8 +88,8 @@ class BaseMailer < ActionMailer::Base
def explicit_multipart_with_options(include_html = false)
mail do |format|
- format.text(content_transfer_encoding: "base64"){ render "welcome" }
- format.html{ render "welcome" } if include_html
+ format.text(content_transfer_encoding: "base64") { render "welcome" }
+ format.html { render "welcome" } if include_html
end
end
@@ -100,18 +100,18 @@ class BaseMailer < ActionMailer::Base
end
end
- def implicit_different_template(template_name="")
+ def implicit_different_template(template_name = "")
mail(template_name: template_name)
end
- def explicit_different_template(template_name="")
+ def explicit_different_template(template_name = "")
mail do |format|
format.text { render template: "#{mailer_name}/#{template_name}" }
format.html { render template: "#{mailer_name}/#{template_name}" }
end
end
- def different_layout(layout_name="")
+ def different_layout(layout_name = "")
mail do |format|
format.text { render layout: layout_name }
format.html { render layout: layout_name }
diff --git a/actionmailer/test/message_delivery_test.rb b/actionmailer/test/message_delivery_test.rb
index 0eb81a8496..a79d77e1e5 100644
--- a/actionmailer/test/message_delivery_test.rb
+++ b/actionmailer/test/message_delivery_test.rb
@@ -76,7 +76,7 @@ class MessageDeliveryTest < ActiveSupport::TestCase
test "should enqueue a delivery with a delay" do
travel_to Time.new(2004, 11, 24, 01, 04, 44) do
- assert_performed_with(job: ActionMailer::DeliveryJob, at: Time.current.to_f+600.seconds, args: ["DelayedMailer", "test_message", "deliver_now", 1, 2, 3]) do
+ assert_performed_with(job: ActionMailer::DeliveryJob, at: Time.current.to_f + 600.seconds, args: ["DelayedMailer", "test_message", "deliver_now", 1, 2, 3]) do
@mail.deliver_later wait: 600.seconds
end
end
diff --git a/actionmailer/test/url_test.rb b/actionmailer/test/url_test.rb
index 9e393c9e49..6dbfb3a1ff 100644
--- a/actionmailer/test/url_test.rb
+++ b/actionmailer/test/url_test.rb
@@ -50,11 +50,11 @@ class ActionMailerUrlTest < ActionMailer::TestCase
end
end
- def encode( text, charset="UTF-8" )
- quoted_printable( text, charset )
+ def encode(text, charset = "UTF-8")
+ quoted_printable(text, charset)
end
- def new_mail( charset="UTF-8" )
+ def new_mail(charset = "UTF-8")
mail = Mail.new
mail.mime_version = "1.0"
if charset
@@ -81,7 +81,7 @@ class ActionMailerUrlTest < ActionMailer::TestCase
AppRoutes.draw do
ActiveSupport::Deprecation.silence do
get ":controller(/:action(/:id))"
- get "/welcome" => "foo#bar", as: "welcome"
+ get "/welcome" => "foo#bar", as: "welcome"
get "/dummy_model" => "foo#baz", as: "dummy_model"
end
end
@@ -94,7 +94,7 @@ class ActionMailerUrlTest < ActionMailer::TestCase
# hash
assert_url_for "/a/b/c", controller: "a", action: "b", id: "c"
- assert_url_for "/a/b/c", {controller: "a", action: "b", id: "c", only_path: true}, true
+ assert_url_for "/a/b/c", { controller: "a", action: "b", id: "c", only_path: true }, true
# model
assert_url_for "/dummy_model", DummyModel.new
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 1fb7e20417..a5f6426f21 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,251 @@
+* Make `with_routing` test helper work when testing controllers inheriting from `ActionController::API`
+
+ *Julia López*
+
+* Use accept header in integration tests with `as: :json`
+
+ Instead of appending the `format` to the request path, Rails will figure
+ out the format from the header instead.
+
+ This allows devs to use `:as` on routes that don't have a format.
+
+ Fixes #27144.
+
+ *Kasper Timm Hansen*
+
+* Reset a new session directly after its creation in `ActionDispatch::IntegrationTest#open_session`.
+
+ Fixes #22742.
+
+ *Tawan Sierek*
+
+* Fixes incorrect output from rails routes when using singular resources.
+
+ Fixes #26606.
+
+ *Erick Reyna*
+
+* Fixes multiple calls to `logger.fatal` instead of a single call,
+ for every line in an exception backtrace, when printing trace
+ from `DebugExceptions` middleware.
+
+ Fixes #26134.
+
+ *Vipul A M*
+
+* Add support for arbitrary hashes in strong parameters:
+
+ ```ruby
+ params.permit(preferences: {})
+ ```
+
+ *Xavier Noria*
+
+* Add `ActionController::Parameters#merge!`, which behaves the same as `Hash#merge!`.
+
+ *Yuji Yaginuma*
+
+* Allow keys not found in `RACK_KEY_TRANSLATION` for setting the environment when rendering
+ arbitrary templates.
+
+ *Sammy Larbi*
+
+* Remove deprecated support to non-keyword arguments in `ActionDispatch::IntegrationTest#process`,
+ `#get`, `#post`, `#patch`, `#put`, `#delete`, and `#head`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `ActionDispatch::IntegrationTest#*_via_redirect`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `ActionDispatch::IntegrationTest#xml_http_request`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated support for passing `:path` and route path as strings in `ActionDispatch::Routing::Mapper#match`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated support for passing path as `nil` in `ActionDispatch::Routing::Mapper#match`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `cache_control` argument from `ActionDispatch::Static#initialize`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated support to passing strings or symbols to the middleware stack.
+
+ *Rafael Mendonça França*
+
+* Change HSTS subdomain to true.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `host` and `port` ssl options.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `const_error` argument in
+ `ActionDispatch::Session::SessionRestoreError#initialize`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `#original_exception` in `ActionDispatch::Session::SessionRestoreError`.
+
+ *Rafael Mendonça França*
+
+* Deprecate `ActionDispatch::ParamsParser::ParseError` in favor of
+ `ActionDispatch::Http::Parameters::ParseError`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `ActionDispatch::ParamsParser`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `original_exception` and `message` arguments in
+ `ActionDispatch::ParamsParser::ParseError#initialize`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `#original_exception` in `ActionDispatch::ParamsParser::ParseError`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated access to mime types through constants.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated support to non-keyword arguments in `ActionController::TestCase#process`,
+ `#get`, `#post`, `#patch`, `#put`, `#delete`, and `#head`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `xml_http_request` and `xhr` methods in `ActionController::TestCase`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated methods in `ActionController::Parameters`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated support to comparing a `ActionController::Parameters`
+ with a `Hash`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated support to `:text` in `render`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated support to `:nothing` in `render`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated support to `:back` in `redirect_to`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated support to passing status as option `head`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated support to passing original exception to `ActionController::BadRequest`
+ and the `ActionController::BadRequest#original_exception` method.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated methods `skip_action_callback`, `skip_filter`, `before_filter`,
+ `prepend_before_filter`, `skip_before_filter`, `append_before_filter`, `around_filter`
+ `prepend_around_filter`, `skip_around_filter`, `append_around_filter`, `after_filter`,
+ `prepend_after_filter`, `skip_after_filter` and `append_after_filter`.
+
+ *Rafael Mendonça França*
+
+* Show an "unmatched constraints" error when params fail to match constraints
+ on a matched route, rather than a "missing keys" error.
+
+ Fixes #26470.
+
+ *Chris Carter*
+
+* Fix adding implicitly rendered template digests to ETags.
+
+ Fixes a case when modifying an implicitly rendered template for a
+ controller action using `fresh_when` or `stale?` would not result in a new
+ `ETag` value.
+
+ *Javan Makhmali*
+
+* Make `fixture_file_upload` work in integration tests.
+
+ *Yuji Yaginuma*
+
+* Add `to_param` to `ActionController::Parameters` deprecations.
+
+ In the future `ActionController::Parameters` are discouraged from being used
+ in URLs without explicit whitelisting. Go through `to_h` to use `to_param`.
+
+ *Kir Shatrov*
+
+* Fix nested multiple roots
+
+ The PR #20940 enabled the use of multiple roots with different constraints
+ at the top level but unfortunately didn't work when those roots were inside
+ a namespace and also broke the use of root inside a namespace after a top
+ level root was defined because the check for the existence of the named route
+ used the global :root name and not the namespaced name.
+
+ This is fixed by using the name_for_action method to expand the :root name to
+ the full namespaced name. We can pass nil for the second argument as we're not
+ dealing with resource definitions so don't need to handle the cases for edit
+ and new routes.
+
+ Fixes #26148.
+
+ *Ryo Hashimoto*, *Andrew White*
+
+* Include the content of the flash in the auto-generated etag. This solves the following problem:
+
+ 1. POST /messages
+ 2. redirect_to messages_url, notice: 'Message was created'
+ 3. GET /messages/1
+ 4. GET /messages
+
+ Step 4 would before still include the flash message, even though it's no longer relevant,
+ because the etag cache was recorded with the flash in place and didn't change when it was gone.
+
+ *DHH*
+
+* SSL: Changes redirect behavior for all non-GET and non-HEAD requests
+ (like POST/PUT/PATCH etc) to `http://` resources to redirect to `https://`
+ with a [307 status code](http://tools.ietf.org/html/rfc7231#section-6.4.7) instead of [301 status code](http://tools.ietf.org/html/rfc7231#section-6.4.2).
+
+ 307 status code instructs the HTTP clients to preserve the original
+ request method while redirecting. It has been part of HTTP RFC since
+ 1999 and is implemented/recognized by most (if not all) user agents.
+
+ # Before
+ POST http://example.com/articles (i.e. ArticlesContoller#create)
+ redirects to
+ GET https://example.com/articles (i.e. ArticlesContoller#index)
+
+ # After
+ POST http://example.com/articles (i.e. ArticlesContoller#create)
+ redirects to
+ POST https://example.com/articles (i.e. ArticlesContoller#create)
+
+ *Chirag Singhal*
+
+* Add `:as` option to `ActionController:TestCase#process` and related methods.
+
+ Specifying `as: mime_type` allows the `CONTENT_TYPE` header to be specified
+ in controller tests without manually doing this through `@request.headers['CONTENT_TYPE']`.
+
+ *Everest Stefan Munro-Zeisberger*
+
* Show cache hits and misses when rendering partials.
Partials using the `cache` helper will show whether a render hit or missed
diff --git a/actionpack/MIT-LICENSE b/actionpack/MIT-LICENSE
index 8573eb1225..ac810e86d0 100644
--- a/actionpack/MIT-LICENSE
+++ b/actionpack/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2016 David Heinemeier Hansson
+Copyright (c) 2004-2017 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index 4bbbcfe9ff..31dd1865f9 100644
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -21,7 +21,7 @@ namespace :test do
task :isolated do
test_files.all? do |file|
sh(Gem.ruby, "-w", "-Ilib:test", file)
- end or raise "Failures"
+ end || raise("Failures")
end
end
diff --git a/actionpack/bin/test b/actionpack/bin/test
index 84a05bba08..a7beb14b27 100755
--- a/actionpack/bin/test
+++ b/actionpack/bin/test
@@ -2,5 +2,3 @@
COMPONENT_ROOT = File.expand_path("..", __dir__)
require File.expand_path("../tools/test", COMPONENT_ROOT)
-
-exit Minitest.run(ARGV)
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index 8e588812f8..603c2e9ea7 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -215,7 +215,7 @@ module AbstractController
# ==== Returns
# * <tt>string</tt> - The name of the method that handles the action
# * false - No valid method name could be found.
- # Raise AbstractController::ActionNotFound.
+ # Raise +AbstractController::ActionNotFound+.
def _find_action_name(action_name)
_valid_action_name?(action_name) && method_for_action(action_name)
end
@@ -231,11 +231,11 @@ module AbstractController
# with a template matching the action name is considered to exist.
#
# If you override this method to handle additional cases, you may
- # also provide a method (like _handle_method_missing) to handle
+ # also provide a method (like +_handle_method_missing+) to handle
# the case.
#
- # If none of these conditions are true, and method_for_action
- # returns nil, an AbstractController::ActionNotFound exception will be raised.
+ # If none of these conditions are true, and +method_for_action+
+ # returns +nil+, an +AbstractController::ActionNotFound+ exception will be raised.
#
# ==== Parameters
# * <tt>action_name</tt> - An action name to find a method name for
diff --git a/actionpack/lib/abstract_controller/caching.rb b/actionpack/lib/abstract_controller/caching.rb
index d222880922..26e3f08bc1 100644
--- a/actionpack/lib/abstract_controller/caching.rb
+++ b/actionpack/lib/abstract_controller/caching.rb
@@ -52,9 +52,9 @@ module AbstractController
self.class._view_cache_dependencies.map { |dep| instance_exec(&dep) }.compact
end
- protected
+ private
# Convenience accessor.
- def cache(key, options = {}, &block)
+ def cache(key, options = {}, &block) # :doc:
if cache_configured?
cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block)
else
diff --git a/actionpack/lib/abstract_controller/caching/fragments.rb b/actionpack/lib/abstract_controller/caching/fragments.rb
index 3257a731ed..c85b4adba1 100644
--- a/actionpack/lib/abstract_controller/caching/fragments.rb
+++ b/actionpack/lib/abstract_controller/caching/fragments.rb
@@ -51,7 +51,7 @@ module AbstractController
# end
# end
def fragment_cache_key(value = nil, &key)
- self.fragment_cache_keys += [key || ->{ value }]
+ self.fragment_cache_keys += [key || -> { value }]
end
end
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index 873e88fcd5..ce4ecf17cc 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -49,30 +49,11 @@ module AbstractController
def _normalize_callback_option(options, from, to) # :nodoc:
if from = options[from]
_from = Array(from).map(&:to_s).to_set
- from = proc {|c| _from.include? c.action_name }
+ from = proc { |c| _from.include? c.action_name }
options[to] = Array(options[to]).unshift(from)
end
end
- # Skip before, after, and around action callbacks matching any of the names.
- #
- # ==== Parameters
- # * <tt>names</tt> - A list of valid names that could be used for
- # callbacks. Note that skipping uses Ruby equality, so it's
- # impossible to skip a callback defined using an anonymous proc
- # using #skip_action_callback.
- def skip_action_callback(*names)
- ActiveSupport::Deprecation.warn("`skip_action_callback` is deprecated and will be removed in Rails 5.1. Please use skip_before_action, skip_after_action or skip_around_action instead.")
- skip_before_action(*names, raise: false)
- skip_after_action(*names, raise: false)
- skip_around_action(*names, raise: false)
- end
-
- def skip_filter(*names)
- ActiveSupport::Deprecation.warn("`skip_filter` is deprecated and will be removed in Rails 5.1. Use skip_before_action, skip_after_action or skip_around_action instead.")
- skip_action_callback(*names)
- end
-
# Take callback names and an optional callback proc, normalize them,
# then call the block with each callback. This allows us to abstract
# the normalization across several methods that use it.
@@ -187,22 +168,12 @@ module AbstractController
end
end
- define_method "#{callback}_filter" do |*names, &blk|
- ActiveSupport::Deprecation.warn("#{callback}_filter is deprecated and will be removed in Rails 5.1. Use #{callback}_action instead.")
- send("#{callback}_action", *names, &blk)
- end
-
define_method "prepend_#{callback}_action" do |*names, &blk|
_insert_callbacks(names, blk) do |name, options|
set_callback(:process_action, callback, name, options.merge(prepend: true))
end
end
- define_method "prepend_#{callback}_filter" do |*names, &blk|
- ActiveSupport::Deprecation.warn("prepend_#{callback}_filter is deprecated and will be removed in Rails 5.1. Use prepend_#{callback}_action instead.")
- send("prepend_#{callback}_action", *names, &blk)
- end
-
# Skip a before, after or around callback. See _insert_callbacks
# for details on the allowed parameters.
define_method "skip_#{callback}_action" do |*names|
@@ -211,18 +182,8 @@ module AbstractController
end
end
- define_method "skip_#{callback}_filter" do |*names, &blk|
- ActiveSupport::Deprecation.warn("skip_#{callback}_filter is deprecated and will be removed in Rails 5.1. Use skip_#{callback}_action instead.")
- send("skip_#{callback}_action", *names, &blk)
- end
-
# *_action is the same as append_*_action
alias_method :"append_#{callback}_action", :"#{callback}_action"
-
- define_method "append_#{callback}_filter" do |*names, &blk|
- ActiveSupport::Deprecation.warn("append_#{callback}_filter is deprecated and will be removed in Rails 5.1. Use append_#{callback}_action instead.")
- send("append_#{callback}_action", *names, &blk)
- end
end
end
end
diff --git a/actionpack/lib/abstract_controller/collector.rb b/actionpack/lib/abstract_controller/collector.rb
index 57714b0588..40ae5aa1ca 100644
--- a/actionpack/lib/abstract_controller/collector.rb
+++ b/actionpack/lib/abstract_controller/collector.rb
@@ -19,7 +19,7 @@ module AbstractController
generate_method_for_mime(mime) unless instance_methods.include?(mime.to_sym)
end
- protected
+ private
def method_missing(symbol, &block)
unless mime_constant = Mime[symbol]
diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb
index cb76898f59..ef3be7af83 100644
--- a/actionpack/lib/abstract_controller/helpers.rb
+++ b/actionpack/lib/abstract_controller/helpers.rb
@@ -171,12 +171,12 @@ module AbstractController
end
private
- # Makes all the (instance) methods in the helper module available to templates
- # rendered through this controller.
- #
- # ==== Parameters
- # * <tt>module</tt> - The module to include into the current helper module
- # for the class
+ # Makes all the (instance) methods in the helper module available to templates
+ # rendered through this controller.
+ #
+ # ==== Parameters
+ # * <tt>module</tt> - The module to include into the current helper module
+ # for the class
def add_template_helper(mod)
_helpers.module_eval { include mod }
end
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index 2cb22cb53d..54af938a93 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -1,6 +1,4 @@
require "abstract_controller/error"
-require "active_support/concern"
-require "active_support/core_ext/class/attribute"
require "action_view"
require "action_view/view_paths"
require "set"
@@ -80,7 +78,7 @@ module AbstractController
# <tt>render :action => "foo"</tt> and <tt>render "foo/bar"</tt> to
# <tt>render :file => "foo/bar"</tt>.
# :api: plugin
- def _normalize_args(action=nil, options={})
+ def _normalize_args(action = nil, options = {})
if action.respond_to?(:permitted?)
if action.permitted?
action
@@ -111,6 +109,9 @@ module AbstractController
def _process_format(format)
end
+ def _process_variant(options)
+ end
+
def _set_html_content_type # :nodoc:
end
@@ -121,10 +122,7 @@ module AbstractController
# :api: private
def _normalize_render(*args, &block)
options = _normalize_args(*args, &block)
- #TODO: remove defined? when we restore AP <=> AV dependency
- if defined?(request) && !request.nil? && request.variant.present?
- options[:variant] = request.variant
- end
+ _process_variant(options)
_normalize_options(options)
options
end
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index fc86a907b3..50f20aa789 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -23,6 +23,7 @@ module ActionController
autoload :Cookies
autoload :DataStreaming
autoload :EtagWithTemplateDigest
+ autoload :EtagWithFlash
autoload :Flash
autoload :ForceSSL
autoload :Head
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 68a526eccb..ca8066cd82 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -213,6 +213,7 @@ module ActionController
Renderers::All,
ConditionalGet,
EtagWithTemplateDigest,
+ EtagWithFlash,
Caching,
MimeResponds,
ImplicitRender,
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index e09977d2cf..d29a5fe68f 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -51,7 +51,7 @@ module ActionController
def unpermitted_parameters(event)
debug do
unpermitted_keys = event.payload[:keys]
- "Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{unpermitted_keys.join(", ")}"
+ "Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{unpermitted_keys.map { |e| ":#{e}" }.join(", ")}"
end
end
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 075e4504c2..9dab7aeef4 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -55,7 +55,7 @@ module ActionController
list = except
end
- Middleware.new(get_class(klass), args, list, strategy, block)
+ Middleware.new(klass, args, list, strategy, block)
end
end
@@ -139,8 +139,8 @@ module ActionController
end
end
- def self.encoding_for_param(action, param) # :nodoc:
- ::Encoding::UTF_8
+ def self.binary_params_for?(action) # :nodoc:
+ false
end
# Delegates to the class' <tt>controller_name</tt>
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index eee17082b7..eb636fa3f6 100644
--- a/actionpack/lib/action_controller/metal/conditional_get.rb
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -238,12 +238,13 @@ module ActionController
)
options.delete(:private)
- response.cache_control[:extras] = options.map {|k,v| "#{k}=#{v}"}
+ response.cache_control[:extras] = options.map { |k, v| "#{k}=#{v}" }
response.date = Time.now unless response.date?
end
- # Sets an HTTP 1.1 Cache-Control header of <tt>no-cache</tt> so no caching should
- # occur by the browser or intermediate caches (like caching proxy servers).
+ # Sets an HTTP 1.1 Cache-Control header of <tt>no-cache</tt>. This means the
+ # resource will be marked as stale, so clients must always revalidate.
+ # Intermediate/browser caches may still store the asset.
def expires_now
response.cache_control.replace(no_cache: true)
end
diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb
index b598dd4d77..731e03e2fc 100644
--- a/actionpack/lib/action_controller/metal/data_streaming.rb
+++ b/actionpack/lib/action_controller/metal/data_streaming.rb
@@ -11,7 +11,7 @@ module ActionController #:nodoc:
DEFAULT_SEND_FILE_TYPE = "application/octet-stream".freeze #:nodoc:
DEFAULT_SEND_FILE_DISPOSITION = "attachment".freeze #:nodoc:
- protected
+ private
# Sends the file. This uses a server-appropriate method (such as X-Sendfile)
# via the Rack::Sendfile middleware. The header to use is set via
# +config.action_dispatch.x_sendfile_header+.
@@ -64,7 +64,7 @@ module ActionController #:nodoc:
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
# for the Cache-Control header spec.
def send_file(path, options = {}) #:doc:
- raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path)
+ raise MissingFile, "Cannot read file #{path}" unless File.file?(path) && File.readable?(path)
options[:filename] ||= File.basename(path) unless options[:url_based_filename]
send_file_headers! options
@@ -108,10 +108,12 @@ module ActionController #:nodoc:
render options.slice(:status, :content_type).merge(body: data)
end
- private
def send_file_headers!(options)
type_provided = options.has_key?(:type)
+ self.content_type = DEFAULT_SEND_FILE_TYPE
+ response.sending_file = true
+
content_type = options.fetch(:type, DEFAULT_SEND_FILE_TYPE)
raise ArgumentError, ":type option required" if content_type.nil?
@@ -136,8 +138,6 @@ module ActionController #:nodoc:
headers["Content-Transfer-Encoding"] = "binary"
- response.sending_file = true
-
# Fix a problem with IE 6.0 on opening downloaded files:
# If Cache-Control: no-cache is set (which Rails does by default),
# IE removes the file it just downloaded from its cache immediately
diff --git a/actionpack/lib/action_controller/metal/etag_with_flash.rb b/actionpack/lib/action_controller/metal/etag_with_flash.rb
new file mode 100644
index 0000000000..474d75f02e
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/etag_with_flash.rb
@@ -0,0 +1,16 @@
+module ActionController
+ # When you're using the flash, it's generally used as a conditional on the view.
+ # This means the content of the view depends on the flash. Which in turn means
+ # that the etag for a response should be computed with the content of the flash
+ # in mind. This does that by including the content of the flash as a component
+ # in the etag that's generated for a response.
+ module EtagWithFlash
+ extend ActiveSupport::Concern
+
+ include ActionController::ConditionalGet
+
+ included do
+ etag { flash unless flash.empty? }
+ end
+ end
+end
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 49b5f1090e..798564db96 100644
--- a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb
+++ b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb
@@ -39,10 +39,10 @@ module ActionController
end
end
- # Pick the template digest to include in the ETag. If the +:template+ option
- # is present, use the named template. If +:template+ is nil or absent, use
- # the default controller/action template. If +:template+ is false, omit the
- # template digest from the ETag.
+ # Pick the template digest to include in the ETag. If the +:template+ option
+ # is present, use the named template. If +:template+ is +nil+ or absent, use
+ # the default controller/action template. If +:template+ is false, omit the
+ # template digest from the ETag.
def pick_template_for_etag(options)
unless options[:template] == false
options[:template] || "#{controller_path}/#{action_name}"
diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb
index 56a4b085e2..175dd9eb9e 100644
--- a/actionpack/lib/action_controller/metal/exceptions.rb
+++ b/actionpack/lib/action_controller/metal/exceptions.rb
@@ -3,20 +3,10 @@ module ActionController
end
class BadRequest < ActionControllerError #:nodoc:
- def initialize(msg = nil, e = nil)
- if e
- ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \
- "Exceptions will automatically capture the original exception.", caller)
- end
-
+ def initialize(msg = nil)
super(msg)
set_backtrace $!.backtrace if $!
end
-
- def original_exception
- ActiveSupport::Deprecation.warn("#original_exception is deprecated. Use #cause instead.", caller)
- cause
- end
end
class RenderError < ActionControllerError #:nodoc:
@@ -24,7 +14,7 @@ module ActionController
class RoutingError < ActionControllerError #:nodoc:
attr_reader :failures
- def initialize(message, failures=[])
+ def initialize(message, failures = [])
super(message)
@failures = failures
end
diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb
index 65351284b9..347fbf0e74 100644
--- a/actionpack/lib/action_controller/metal/flash.rb
+++ b/actionpack/lib/action_controller/metal/flash.rb
@@ -42,7 +42,7 @@ module ActionController #:nodoc:
end
end
- protected
+ private
def redirect_to(options = {}, response_status_and_flash = {}) #:doc:
self.class._flash_types.each do |flash_type|
if type = response_status_and_flash.delete(flash_type)
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index b8976497a4..9d43e752ac 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -89,7 +89,7 @@ module ActionController
end
secure_url = ActionDispatch::Http::URL.url_for(options.slice(*URL_OPTIONS))
- flash.keep if respond_to?(:flash)
+ flash.keep if respond_to?(:flash) && request.respond_to?(:flash)
redirect_to secure_url, options.slice(*REDIRECT_OPTIONS)
end
end
diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb
index 86b5eb20d7..4dff23dd85 100644
--- a/actionpack/lib/action_controller/metal/head.rb
+++ b/actionpack/lib/action_controller/metal/head.rb
@@ -18,13 +18,7 @@ module ActionController
# See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list of valid +status+ symbols.
def head(status, options = {})
if status.is_a?(Hash)
- msg = status[:status] ? "The :status option" : "The implicit :ok status"
- options, status = status, status.delete(:status)
-
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- #{msg} on `head` has been deprecated and will be removed in Rails 5.1.
- Please pass the status as a separate parameter before the options, instead.
- MSG
+ raise ArgumentError, "#{status.inspect} is not a valid value for `status`."
end
status ||= :ok
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index 7257bbfa95..476d081239 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -108,7 +108,7 @@ module ActionController
end
private
- # Extract helper names from files in <tt>app/helpers/**/*_helper.rb</tt>
+ # Extract helper names from files in <tt>app/helpers/**/*_helper.rb</tt>
def all_application_helpers
all_helpers_from_path(helpers_path)
end
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 22aadb9dfa..0575360068 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -28,7 +28,7 @@ module ActionController
# class ApplicationController < ActionController::Base
# before_action :set_account, :authenticate
#
- # protected
+ # private
# def set_account
# @account = Account.find_by(url_name: request.subdomains.first)
# end
@@ -224,7 +224,7 @@ module ActionController
# Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+
# Optional parameter +password_is_ha1+ is set to +true+ by default, since best practice is to store ha1 digest instead
# of a plain-text password.
- def expected_response(http_method, uri, credentials, password, password_is_ha1=true)
+ def expected_response(http_method, uri, credentials, password, password_is_ha1 = true)
ha1 = password_is_ha1 ? password : ha1(credentials, password)
ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(":"))
::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(":"))
@@ -236,7 +236,7 @@ module ActionController
def encode_credentials(http_method, credentials, password, password_is_ha1)
credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password, password_is_ha1)
- "Digest " + credentials.sort_by {|x| x[0].to_s }.map {|v| "#{v[0]}='#{v[1]}'" }.join(", ")
+ "Digest " + credentials.sort_by { |x| x[0].to_s }.map { |v| "#{v[0]}='#{v[1]}'" }.join(", ")
end
def decode_credentials_header(request)
@@ -246,7 +246,7 @@ module ActionController
def decode_credentials(header)
ActiveSupport::HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/, "").split(",").map do |pair|
key, value = pair.split("=", 2)
- [key.strip, value.to_s.gsub(/^"|"$/,"").delete('\'')]
+ [key.strip, value.to_s.gsub(/^"|"$/, "").delete('\'')]
end]
end
@@ -314,7 +314,7 @@ module ActionController
# Can be much shorter if the Stale directive is implemented. This would
# allow a user to use new nonce without prompting the user again for their
# username and password.
- def validate_nonce(secret_key, request, value, seconds_to_timeout=5*60)
+ def validate_nonce(secret_key, request, value, seconds_to_timeout = 5 * 60)
return false if value.nil?
t = ::Base64.decode64(value).split(":").first.to_i
nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout
@@ -363,7 +363,7 @@ module ActionController
# class ApplicationController < ActionController::Base
# before_action :set_account, :authenticate
#
- # protected
+ # private
# def set_account
# @account = Account.find_by(url_name: request.subdomains.first)
# end
diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb
index c414527d63..8615c16c6f 100644
--- a/actionpack/lib/action_controller/metal/implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/implicit_render.rb
@@ -48,7 +48,7 @@ module ActionController
"NOTE! For XHR/Ajax or API requests, this action would normally " \
"respond with 204 No Content: an empty white screen. Since you're " \
"loading it in a web browser, we assume that you expected to " \
- "actually render a template, not… nothing, so we're showing an " \
+ "actually render a template, not nothing, so we're showing an " \
"error to be extra-clear. If you expect 204 No Content, carry on. " \
"That's what you'll get from an XHR or API request. Give it a shot."
@@ -61,8 +61,8 @@ module ActionController
def method_for_action(action_name)
super || if template_exists?(action_name.to_s, _prefixes)
- "default_render"
- end
+ "default_render"
+ end
end
private
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index 2ede96c667..924686218f 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -46,7 +46,7 @@ module ActionController
render_output
end
- def send_file(path, options={})
+ def send_file(path, options = {})
ActiveSupport::Notifications.instrument("send_file.action_controller",
options.merge(path: path)) do
super
@@ -83,14 +83,14 @@ module ActionController
# end
#
# :api: plugin
- def cleanup_view_runtime #:nodoc:
+ def cleanup_view_runtime
yield
end
# Every time after an action is processed, this method is invoked
# with the payload, so you can add more information.
# :api: plugin
- def append_info_to_payload(payload) #:nodoc:
+ def append_info_to_payload(payload)
payload[:view_runtime] = view_runtime
end
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index a18055c899..fed99e6c82 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -205,7 +205,12 @@ module ActionController
private
def each_chunk(&block)
- while str = @buf.pop
+ loop do
+ str = nil
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
+ str = @buf.pop
+ end
+ break unless str
yield str
end
end
@@ -242,7 +247,7 @@ module ActionController
# Since we're processing the view in a different thread, copy the
# thread locals from the main thread to the child thread. :'(
- locals.each { |k,v| t2[k] = v }
+ locals.each { |k, v| t2[k] = v }
begin
super(name)
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 0d998632fc..f6aabcb102 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -280,8 +280,8 @@ module ActionController #:nodoc:
def any(*args, &block)
if block_given?
- if args.any? && args.none?{ |a| a == @variant }
- args.each{ |v| @variants[v] = block }
+ if args.any? && args.none? { |a| a == @variant }
+ args.each { |v| @variants[v] = block }
else
@variants[:any] = block
end
diff --git a/actionpack/lib/action_controller/metal/parameter_encoding.rb b/actionpack/lib/action_controller/metal/parameter_encoding.rb
index a278c5d011..962532ff09 100644
--- a/actionpack/lib/action_controller/metal/parameter_encoding.rb
+++ b/actionpack/lib/action_controller/metal/parameter_encoding.rb
@@ -1,5 +1,5 @@
module ActionController
- # Allows encoding to be specified per parameter per action.
+ # Specify binary encoding for parameters for a given action.
module ParameterEncoding
extend ActiveSupport::Concern
@@ -13,17 +13,36 @@ module ActionController
@_parameter_encodings = {}
end
- def encoding_for_param(action, param) # :nodoc:
- if @_parameter_encodings[action.to_s] && @_parameter_encodings[action.to_s][param.to_s]
- @_parameter_encodings[action.to_s][param.to_s]
- else
- ::Encoding::UTF_8
- end
+ def binary_params_for?(action) # :nodoc:
+ @_parameter_encodings[action.to_s]
end
- def parameter_encoding(action, param_name, encoding)
- @_parameter_encodings[action.to_s] ||= {}
- @_parameter_encodings[action.to_s][param_name.to_s] = encoding
+ # Specify that a given action's parameters should all be encoded as
+ # ASCII-8BIT (it "skips" the encoding default of UTF-8).
+ #
+ # For example, a controller would use it like this:
+ #
+ # class RepositoryController < ActionController::Base
+ # skip_parameter_encoding :show
+ #
+ # def show
+ # @repo = Repository.find_by_filesystem_path params[:file_path]
+ #
+ # # `repo_name` is guaranteed to be UTF-8, but was ASCII-8BIT, so
+ # # tag it as such
+ # @repo_name = params[:repo_name].force_encoding 'UTF-8'
+ # end
+ #
+ # def index
+ # @repositories = Repository.all
+ # end
+ # end
+ #
+ # The show action in the above controller would have all parameter values
+ # encoded as ASCII-8BIT. This is useful in the case where an application
+ # must handle data but encoding of the data is unknown, like file system data.
+ def skip_parameter_encoding(action)
+ @_parameter_encodings[action.to_s] = true
end
end
end
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index 745f449a05..7fc898f034 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -128,14 +128,14 @@ module ActionController
end
private
- # Determine the wrapper model from the controller's name. By convention,
- # this could be done by trying to find the defined model that has the
- # same singular name as the controller. For example, +UsersController+
- # will try to find if the +User+ model exists.
- #
- # This method also does namespace lookup. Foo::Bar::UsersController will
- # try to find Foo::Bar::User, Foo::User and finally User.
- def _default_wrap_model #:nodoc:
+ # Determine the wrapper model from the controller's name. By convention,
+ # this could be done by trying to find the defined model that has the
+ # same singular name as the controller. For example, +UsersController+
+ # will try to find if the +User+ model exists.
+ #
+ # This method also does namespace lookup. Foo::Bar::UsersController will
+ # try to find Foo::Bar::User, Foo::User and finally User.
+ def _default_wrap_model
return nil if klass.anonymous?
model_name = klass.name.sub(/Controller$/, "").classify
@@ -205,7 +205,7 @@ module ActionController
model = name_or_model_or_options
end
- opts = Options.from_hash _wrapper_options.to_h.slice(:format).merge(options)
+ opts = Options.from_hash _wrapper_options.to_h.slice(:format).merge(options)
opts.model = model
opts.klass = self
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index 3c7cc15627..30798c1d99 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -1,12 +1,4 @@
module ActionController
- class RedirectBackError < AbstractController::Error #:nodoc:
- DEFAULT_MESSAGE = 'No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify request.env["HTTP_REFERER"].'
-
- def initialize(message = nil)
- super(message || DEFAULT_MESSAGE)
- end
- end
-
module Redirecting
extend ActiveSupport::Concern
@@ -24,10 +16,10 @@ module ActionController
# === Examples:
#
# redirect_to action: "show", id: 5
- # redirect_to post
+ # redirect_to @post
# redirect_to "http://www.rubyonrails.org"
# redirect_to "/images/screenshot.jpg"
- # redirect_to articles_url
+ # redirect_to posts_url
# redirect_to proc { edit_post_url(@post) }
#
# The redirection happens as a "302 Found" header unless otherwise specified using the <tt>:status</tt> option:
@@ -77,11 +69,11 @@ module ActionController
# is missing this header, the <tt>fallback_location</tt> will be used.
#
# redirect_back fallback_location: { action: "show", id: 5 }
- # redirect_back fallback_location: post
+ # redirect_back fallback_location: @post
# redirect_back fallback_location: "http://www.rubyonrails.org"
- # redirect_back fallback_location: "/images/screenshot.jpg"
- # redirect_back fallback_location: articles_url
- # redirect_back fallback_location: proc { edit_post_url(@post) }
+ # redirect_back fallback_location: "/images/screenshot.jpg"
+ # redirect_back fallback_location: posts_url
+ # redirect_back fallback_location: proc { edit_post_url(@post) }
#
# All options that can be passed to <tt>redirect_to</tt> are accepted as
# options and the behavior is identical.
@@ -104,14 +96,6 @@ module ActionController
options
when String
request.protocol + request.host_with_port + options
- when :back
- ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
- `redirect_to :back` is deprecated and will be removed from Rails 5.1.
- Please use `redirect_back(fallback_location: fallback_location)` where
- `fallback_location` represents the location to use if the request has
- no HTTP referer information.
- MESSAGE
- request.headers["Referer"] or raise RedirectBackError
when Proc
_compute_redirect_to_location request, options.call
else
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index 15377ddcb9..733aca195d 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -71,8 +71,6 @@ module ActionController
# format.csv { render csv: @csvable, filename: @csvable.name }
# end
# end
- # To use renderers and their mime types in more concise ways, see
- # <tt>ActionController::MimeResponds::ClassMethods.respond_to</tt>
def self.add(key, &block)
define_method(_render_with_renderer_method_name(key), &block)
RENDERERS << key.to_sym
@@ -106,7 +104,7 @@ module ActionController
#
# Since <tt>ActionController::Metal</tt> controllers cannot render, the controller
# must include <tt>AbstractController::Rendering</tt>, <tt>ActionController::Rendering</tt>,
- # and <tt>ActionController::Renderers</tt>, and have at lest one renderer.
+ # and <tt>ActionController::Renderers</tt>, and have at least one renderer.
#
# Rather than including <tt>ActionController::Renderers::All</tt> and including all renderers,
# you may specify which renderers to include by passing the renderer name or names to
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index ac17d61b96..4c01891d4c 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -4,7 +4,7 @@ module ActionController
module Rendering
extend ActiveSupport::Concern
- RENDER_FORMATS_IN_PRIORITY = [:body, :text, :plain, :html]
+ RENDER_FORMATS_IN_PRIORITY = [:body, :plain, :html]
module ClassMethods
# Documentation at ActionController::Renderer#render
@@ -54,6 +54,12 @@ module ActionController
private
+ def _process_variant(options)
+ if defined?(request) && !request.nil? && request.variant.present?
+ options[:variant] = request.variant
+ end
+ end
+
def _render_in_priorities(options)
RENDER_FORMATS_IN_PRIORITY.each do |format|
return options[format] if options.key?(format)
@@ -67,42 +73,26 @@ module ActionController
end
def _set_rendered_content_type(format)
- unless response.content_type
+ if format && !response.content_type
self.content_type = format.to_s
end
end
- # Normalize arguments by catching blocks and setting them on :update.
- def _normalize_args(action=nil, options={}, &blk) #:nodoc:
+ # Normalize arguments by catching blocks and setting them on :update.
+ def _normalize_args(action = nil, options = {}, &blk)
options = super
options[:update] = blk if block_given?
options
end
- # Normalize both text and status options.
- def _normalize_options(options) #:nodoc:
+ # Normalize both text and status options.
+ def _normalize_options(options)
_normalize_text(options)
- if options[:text]
- ActiveSupport::Deprecation.warn <<-WARNING.squish
- `render :text` is deprecated because it does not actually render a
- `text/plain` response. Switch to `render plain: 'plain text'` to
- render as `text/plain`, `render html: '<strong>HTML</strong>'` to
- render as `text/html`, or `render body: 'raw'` to match the deprecated
- behavior and render with the default Content-Type, which is
- `text/plain`.
- WARNING
- end
-
if options[:html]
options[:html] = ERB::Util.html_escape(options[:html])
end
- if options.delete(:nothing)
- ActiveSupport::Deprecation.warn("`:nothing` option is deprecated and will be removed in Rails 5.1. Use `head` method to respond with empty response body.")
- options[:body] = nil
- end
-
if options[:status]
options[:status] = Rack::Utils.status_code(options[:status])
end
@@ -118,8 +108,8 @@ module ActionController
end
end
- # Process controller specific options, as status, content-type and location.
- def _process_options(options) #:nodoc:
+ # Process controller specific options, as status, content-type and location.
+ def _process_options(options)
status, content_type, location = options.values_at(:status, :content_type, :location)
self.status = status if status
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 3d3c121280..e8965a6561 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -152,7 +152,7 @@ module ActionController #:nodoc:
request.cookie_jar = NullCookieJar.build(request, {})
end
- protected
+ private
class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc:
def initialize(req)
@@ -197,7 +197,7 @@ module ActionController #:nodoc:
end
end
- protected
+ private
# The actual before_action that is used to verify the CSRF token.
# Don't override this directly. Provide your own forgery protection
# strategy instead. If you override, you'll disable same-origin
@@ -208,7 +208,7 @@ module ActionController #:nodoc:
# enabled on an action, this before_action flags its after_action to
# verify that JavaScript responses are for XHR requests, ensuring they
# follow the browser's same-origin policy.
- def verify_authenticity_token
+ def verify_authenticity_token # :doc:
mark_for_same_origin_verification!
if !verified_request?
@@ -219,7 +219,7 @@ module ActionController #:nodoc:
end
end
- def handle_unverified_request
+ def handle_unverified_request # :doc:
forgery_protection_strategy.new(self).handle_unverified_request
end
@@ -233,7 +233,7 @@ module ActionController #:nodoc:
# If `verify_authenticity_token` was run (indicating that we have
# forgery protection enabled for this request) then also verify that
# we aren't serving an unauthorized cross-origin response.
- def verify_same_origin_request
+ def verify_same_origin_request # :doc:
if marked_for_same_origin_verification? && non_xhr_javascript_response?
if logger && log_warning_on_csrf_failure
logger.warn CROSS_ORIGIN_JAVASCRIPT_WARNING
@@ -243,18 +243,18 @@ module ActionController #:nodoc:
end
# GET requests are checked for cross-origin JavaScript after rendering.
- def mark_for_same_origin_verification!
+ def mark_for_same_origin_verification! # :doc:
@marked_for_same_origin_verification = request.get?
end
# If the `verify_authenticity_token` before_action ran, verify that
# JavaScript responses are only served to same-origin GET requests.
- def marked_for_same_origin_verification?
+ def marked_for_same_origin_verification? # :doc:
@marked_for_same_origin_verification ||= false
end
# Check for cross-origin JavaScript responses.
- def non_xhr_javascript_response?
+ def non_xhr_javascript_response? # :doc:
content_type =~ %r(\Atext/javascript) && !request.xhr?
end
@@ -265,20 +265,20 @@ module ActionController #:nodoc:
# * Is it a GET or HEAD request? Gets should be safe and idempotent
# * Does the form_authenticity_token match the given token value from the params?
# * Does the X-CSRF-Token header match the form_authenticity_token
- def verified_request?
+ def verified_request? # :doc:
!protect_against_forgery? || request.get? || request.head? ||
(valid_request_origin? && any_authenticity_token_valid?)
end
# Checks if any of the authenticity tokens from the request are valid.
- def any_authenticity_token_valid?
+ def any_authenticity_token_valid? # :doc:
request_authenticity_tokens.any? do |token|
valid_authenticity_token?(session, token)
end
end
# Possible authenticity tokens sent in the request.
- def request_authenticity_tokens
+ def request_authenticity_tokens # :doc:
[form_authenticity_param, request.x_csrf_token]
end
@@ -290,7 +290,7 @@ module ActionController #:nodoc:
# Creates a masked version of the authenticity token that varies
# on each request. The masking is used to mitigate SSL attacks
# like BREACH.
- def masked_authenticity_token(session, form_options: {})
+ def masked_authenticity_token(session, form_options: {}) # :doc:
action, method = form_options.values_at(:action, :method)
raw_token = if per_form_csrf_tokens && action && method
@@ -309,7 +309,7 @@ module ActionController #:nodoc:
# Checks the client's masked token to see if it matches the
# session token. Essentially the inverse of
# +masked_authenticity_token+.
- def valid_authenticity_token?(session, encoded_masked_token)
+ def valid_authenticity_token?(session, encoded_masked_token) # :doc:
if encoded_masked_token.nil? || encoded_masked_token.empty? || !encoded_masked_token.is_a?(String)
return false
end
@@ -340,7 +340,7 @@ module ActionController #:nodoc:
end
end
- def unmask_token(masked_token)
+ def unmask_token(masked_token) # :doc:
# Split the token into the one-time pad and the encrypted
# value and decrypt it
one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH]
@@ -348,11 +348,11 @@ module ActionController #:nodoc:
xor_byte_strings(one_time_pad, encrypted_csrf_token)
end
- def compare_with_real_token(token, session)
+ def compare_with_real_token(token, session) # :doc:
ActiveSupport::SecurityUtils.secure_compare(token, real_csrf_token(session))
end
- def valid_per_form_csrf_token?(token, session)
+ def valid_per_form_csrf_token?(token, session) # :doc:
if per_form_csrf_tokens
correct_token = per_form_csrf_token(
session,
@@ -366,12 +366,12 @@ module ActionController #:nodoc:
end
end
- def real_csrf_token(session)
+ def real_csrf_token(session) # :doc:
session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
Base64.strict_decode64(session[:_csrf_token])
end
- def per_form_csrf_token(session, action_path, method)
+ def per_form_csrf_token(session, action_path, method) # :doc:
OpenSSL::HMAC.digest(
OpenSSL::Digest::SHA256.new,
real_csrf_token(session),
@@ -379,25 +379,25 @@ module ActionController #:nodoc:
)
end
- def xor_byte_strings(s1, s2)
+ def xor_byte_strings(s1, s2) # :doc:
s2_bytes = s2.bytes
s1.each_byte.with_index { |c1, i| s2_bytes[i] ^= c1 }
s2_bytes.pack("C*")
end
# The form's authenticity parameter. Override to provide your own.
- def form_authenticity_param
+ def form_authenticity_param # :doc:
params[request_forgery_protection_token]
end
# Checks if the controller allows forgery protection.
- def protect_against_forgery?
+ def protect_against_forgery? # :doc:
allow_forgery_protection
end
# Checks if the request originated from the same origin by looking at the
# Origin header.
- def valid_request_origin?
+ def valid_request_origin? # :doc:
if forgery_protection_origin_check
# We accept blank origin headers because some user agents don't send it.
request.origin.nil? || request.origin == request.base_url
@@ -406,7 +406,7 @@ module ActionController #:nodoc:
end
end
- def normalize_action_path(action_path)
+ def normalize_action_path(action_path) # :doc:
uri = URI.parse(action_path)
uri.path.chomp("/")
end
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index 481f19f1ef..877a08b222 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -193,10 +193,10 @@ module ActionController #:nodoc:
module Streaming
extend ActiveSupport::Concern
- protected
+ private
# Set proper cache control and transfer encoding when streaming
- def _process_options(options) #:nodoc:
+ def _process_options(options)
super
if options[:stream]
if request.version == "HTTP/1.0"
@@ -210,7 +210,7 @@ module ActionController #:nodoc:
end
# Call render_body if we are streaming instead of usual +render+.
- def _render_template(options) #:nodoc:
+ def _render_template(options)
if options.delete(:stream)
Rack::Chunked::Body.new view_renderer.render_body(view_context, options)
else
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index 7f5144bc49..acfeca1fcb 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -2,6 +2,7 @@ require "active_support/core_ext/hash/indifferent_access"
require "active_support/core_ext/hash/transform_values"
require "active_support/core_ext/array/wrap"
require "active_support/core_ext/string/filters"
+require "active_support/core_ext/object/to_query"
require "active_support/rescuable"
require "action_dispatch/http/upload"
require "rack/test"
@@ -32,13 +33,13 @@ module ActionController
#
# params = ActionController::Parameters.new(a: "123", b: "456")
# params.permit(:c)
- # # => ActionController::UnpermittedParameters: found unpermitted parameters: a, b
+ # # => ActionController::UnpermittedParameters: found unpermitted parameters: :a, :b
class UnpermittedParameters < IndexError
attr_reader :params # :nodoc:
def initialize(params) # :nodoc:
@params = params
- super("found unpermitted parameter#{'s' if params.size > 1 }: #{params.join(", ")}")
+ super("found unpermitted parameter#{'s' if params.size > 1 }: #{params.map { |e| ":#{e}" }.join(", ")}")
end
end
@@ -70,8 +71,8 @@ module ActionController
# * +permit_all_parameters+ - If it's +true+, all the parameters will be
# permitted by default. The default is +false+.
# * +action_on_unpermitted_parameters+ - Allow to control the behavior when parameters
- # that are not explicitly permitted are found. The values can be <tt>:log</tt> to
- # write a message on the logger or <tt>:raise</tt> to raise
+ # that are not explicitly permitted are found. The values can be +false+ to just filter them
+ # out, <tt>:log</tt> to additionally write a message on the logger, or <tt>:raise</tt> to raise
# ActionController::UnpermittedParameters exception. The default value is <tt>:log</tt>
# in test and development environments, +false+ otherwise.
#
@@ -150,15 +151,6 @@ module ActionController
def ==(other)
if other.respond_to?(:permitted?)
self.permitted? == other.permitted? && self.parameters == other.parameters
- elsif other.is_a?(Hash)
- ActiveSupport::Deprecation.warn <<-WARNING.squish
- Comparing equality between `ActionController::Parameters` and a
- `Hash` is deprecated and will be removed in Rails 5.1. Please only do
- comparisons between instances of `ActionController::Parameters`. If
- you need to compare to a hash, first convert it using
- `ActionController::Parameters#new`.
- WARNING
- @parameters == other.with_indifferent_access
else
@parameters == other
end
@@ -342,6 +334,15 @@ module ActionController
# params = ActionController::Parameters.new(tags: ['rails', 'parameters'])
# params.permit(tags: [])
#
+ # Sometimes it is not possible or convenient to declare the valid keys of
+ # a hash parameter or its internal structure. Just map to an empty hash:
+ #
+ # params.permit(preferences: {})
+ #
+ # but be careful because this opens the door to arbitrary input. In this
+ # case, +permit+ ensures values in the returned structure are permitted
+ # scalars and filters out anything else.
+ #
# You can also use +permit+ on nested parameters, like:
#
# params = ActionController::Parameters.new({
@@ -390,14 +391,15 @@ module ActionController
case filter
when Symbol, String
permitted_scalar_filter(params, filter)
- when Hash then
+ when Hash
hash_filter(params, filter)
end
end
unpermitted_parameters!(params) if self.class.action_on_unpermitted_parameters
- params.permit!
+ params.permitted = true
+ params
end
# Returns a parameter for the given +key+. If not found,
@@ -547,7 +549,7 @@ module ActionController
new_instance_with_inherited_permitted_status(@parameters.select(&block))
end
- # Equivalent to Hash#keep_if, but returns nil if no changes were made.
+ # Equivalent to Hash#keep_if, but returns +nil+ if no changes were made.
def select!(&block)
@parameters.select!(&block)
self
@@ -577,10 +579,17 @@ module ActionController
# +other_hash+ merges into current hash.
def merge(other_hash)
new_instance_with_inherited_permitted_status(
- @parameters.merge(other_hash)
+ @parameters.merge(other_hash.to_h)
)
end
+ # Returns current <tt>ActionController::Parameters</tt> instance which
+ # +other_hash+ merges into current hash.
+ def merge!(other_hash)
+ @parameters.merge!(other_hash.to_h)
+ self
+ end
+
# This is required by ActiveModel attribute assignment, so that user can
# pass +Parameters+ to a mass assignment methods in a model. It should not
# matter as we are using +HashWithIndifferentAccess+ internally.
@@ -619,21 +628,12 @@ module ActionController
end
end
- def method_missing(method_sym, *args, &block)
- if @parameters.respond_to?(method_sym)
- message = <<-DEPRECATE.squish
- Method #{method_sym} is deprecated and will be removed in Rails 5.1,
- as `ActionController::Parameters` no longer inherits from
- hash. Using this deprecated behavior exposes potential security
- problems. If you continue to use this method you may be creating
- a security vulnerability in your app that can be exploited. Instead,
- consider using one of these documented methods which are not
- deprecated: http://api.rubyonrails.org/v#{ActionPack.version}/classes/ActionController/Parameters.html
- DEPRECATE
- ActiveSupport::Deprecation.warn(message)
- @parameters.public_send(method_sym, *args, &block)
- else
- super
+ undef_method :to_param
+
+ # Returns duplicate of object including all parameters
+ def deep_dup
+ self.class.new(@parameters.deep_dup).tap do |duplicate|
+ duplicate.permitted = @permitted
end
end
@@ -697,7 +697,7 @@ module ActionController
when Parameters
if object.fields_for_style?
hash = object.class.new
- object.each { |k,v| hash[k] = yield v }
+ object.each { |k, v| hash[k] = yield v }
hash
else
yield object
@@ -750,7 +750,7 @@ module ActionController
]
def permitted_scalar?(value)
- PERMITTED_SCALAR_TYPES.any? {|type| value.is_a?(type)}
+ PERMITTED_SCALAR_TYPES.any? { |type| value.is_a?(type) }
end
def permitted_scalar_filter(params, key)
@@ -766,7 +766,7 @@ module ActionController
end
def array_of_permitted_scalars?(value)
- if value.is_a?(Array) && value.all? {|element| permitted_scalar?(element)}
+ if value.is_a?(Array) && value.all? { |element| permitted_scalar?(element) }
yield value
end
end
@@ -776,6 +776,7 @@ module ActionController
end
EMPTY_ARRAY = []
+ EMPTY_HASH = {}
def hash_filter(params, filter)
filter = filter.with_indifferent_access
@@ -789,6 +790,11 @@ module ActionController
array_of_permitted_scalars?(self[key]) do |val|
params[key] = val
end
+ elsif filter[key] == EMPTY_HASH
+ # Declaration { preferences: {} }.
+ if value.is_a?(Parameters)
+ params[key] = permit_any_in_parameters(value)
+ end
elsif non_scalar?(value)
# Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
params[key] = each_element(value) do |element|
@@ -798,6 +804,39 @@ module ActionController
end
end
+ def permit_any_in_parameters(params)
+ self.class.new.tap do |sanitized|
+ params.each do |key, value|
+ case value
+ when ->(v) { permitted_scalar?(v) }
+ sanitized[key] = value
+ when Array
+ sanitized[key] = permit_any_in_array(value)
+ when Parameters
+ sanitized[key] = permit_any_in_parameters(value)
+ else
+ # Filter this one out.
+ end
+ end
+ sanitized.permitted = true
+ end
+ end
+
+ def permit_any_in_array(array)
+ [].tap do |sanitized|
+ array.each do |element|
+ case element
+ when ->(e) { permitted_scalar?(e) }
+ sanitized << element
+ when Parameters
+ sanitized << permit_any_in_parameters(element)
+ else
+ # Filter this one out.
+ end
+ end
+ end
+ end
+
def initialize_copy(source)
super
@parameters = @parameters.dup
diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb
index ac37b00010..9bb416178a 100644
--- a/actionpack/lib/action_controller/metal/testing.rb
+++ b/actionpack/lib/action_controller/metal/testing.rb
@@ -13,7 +13,7 @@ module ActionController
module ClassMethods
def before_filters
- _process_action_callbacks.find_all{|x| x.kind == :before}.map(&:name)
+ _process_action_callbacks.find_all { |x| x.kind == :before }.map(&:name)
end
end
end
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index 6513a556ee..a7cdfe6a98 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -51,7 +51,7 @@ module ActionController
extend ::AbstractController::Railties::RoutesHelpers.with(app.routes)
extend ::ActionController::Railties::Helpers
- options.each do |k,v|
+ options.each do |k, v|
k = "#{k}="
if respond_to?(k)
send(k, v)
diff --git a/actionpack/lib/action_controller/renderer.rb b/actionpack/lib/action_controller/renderer.rb
index 0739f16965..3ff80e6a39 100644
--- a/actionpack/lib/action_controller/renderer.rb
+++ b/actionpack/lib/action_controller/renderer.rb
@@ -83,7 +83,7 @@ module ActionController
private
def normalize_keys(env)
new_env = {}
- env.each_pair { |k,v| new_env[rack_key_for(k)] = rack_value_for(k, v) }
+ env.each_pair { |k, v| new_env[rack_key_for(k)] = rack_value_for(k, v) }
new_env
end
@@ -102,7 +102,9 @@ module ActionController
method: ->(v) { v.upcase },
}
- def rack_key_for(key); RACK_KEY_TRANSLATION[key]; end
+ def rack_key_for(key)
+ RACK_KEY_TRANSLATION.fetch(key, key.to_s)
+ end
def rack_value_for(key, value)
RACK_VALUE_TRANSLATION.fetch(key, IDENTITY).call value
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 3a6bc92b3c..441667e556 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -3,11 +3,11 @@ require "active_support/core_ext/hash/conversions"
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 "rails-dom-testing"
module ActionController
- # :stopdoc:
class Metal
include Testing::Functional
end
@@ -52,7 +52,7 @@ module ActionController
super(env)
self.session = session
- self.session_options = TestSession::DEFAULT_OPTIONS
+ self.session_options = TestSession::DEFAULT_OPTIONS.dup
@controller_class = controller_class
@custom_param_parsers = {
xml: lambda { |raw_post| Hash.from_xml(raw_post)["hash"] }
@@ -113,8 +113,9 @@ module ActionController
end
end
- set_header "CONTENT_LENGTH", data.length.to_s
- set_header "rack.input", StringIO.new(data)
+ data_stream = StringIO.new(data)
+ set_header "CONTENT_LENGTH", data_stream.length.to_s
+ set_header "rack.input", data_stream
end
fetch_header("PATH_INFO") do |k|
@@ -211,10 +212,18 @@ module ActionController
end
# Superclass for ActionController functional tests. Functional tests allow you to
- # test a single controller action per test method. This should not be confused with
- # integration tests (see ActionDispatch::IntegrationTest), which are more like
- # "stories" that can involve multiple controllers and multiple actions (i.e. multiple
- # different HTTP requests).
+ # test a single controller action per test method.
+ #
+ # == Use integration style controller tests over functional style controller tests.
+ #
+ # Rails discourages the use of functional tests in favor of integration tests
+ # (use ActionDispatch::IntegrationTest).
+ #
+ # New Rails applications no longer generate functional style controller tests and they should
+ # only be used for backward compatibility. Integration style controller tests perform actual
+ # requests, whereas functional style controller tests merely simulate a request. Besides,
+ # integration tests are as fast as functional tests and provide lot of helpers such as +as+,
+ # +parsed_body+ for effective testing of controller actions including even API endpoints.
#
# == Basic example
#
@@ -379,56 +388,41 @@ module ActionController
#
# Note that the request method is not verified. The different methods are
# available to make the tests more expressive.
- def get(action, *args)
- res = process_with_kwargs("GET", action, *args)
+ def get(action, **args)
+ res = process(action, method: "GET", **args)
cookies.update res.cookies
res
end
# Simulate a POST request with the given parameters and set/volley the response.
# See +get+ for more details.
- def post(action, *args)
- process_with_kwargs("POST", action, *args)
+ def post(action, **args)
+ process(action, method: "POST", **args)
end
# Simulate a PATCH request with the given parameters and set/volley the response.
# See +get+ for more details.
- def patch(action, *args)
- process_with_kwargs("PATCH", action, *args)
+ def patch(action, **args)
+ process(action, method: "PATCH", **args)
end
# Simulate a PUT request with the given parameters and set/volley the response.
# See +get+ for more details.
- def put(action, *args)
- process_with_kwargs("PUT", action, *args)
+ def put(action, **args)
+ process(action, method: "PUT", **args)
end
# Simulate a DELETE request with the given parameters and set/volley the response.
# See +get+ for more details.
- def delete(action, *args)
- process_with_kwargs("DELETE", action, *args)
+ def delete(action, **args)
+ process(action, method: "DELETE", **args)
end
# Simulate a HEAD request with the given parameters and set/volley the response.
# See +get+ for more details.
- def head(action, *args)
- process_with_kwargs("HEAD", action, *args)
- end
-
- def xml_http_request(*args)
- ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc)
- xhr and xml_http_request methods are deprecated in favor of
- `get :index, xhr: true` and `post :create, xhr: true`
- MSG
-
- @request.env["HTTP_X_REQUESTED_WITH"] = "XMLHttpRequest"
- @request.env["HTTP_ACCEPT"] ||= [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ")
- __send__(*args).tap do
- @request.env.delete "HTTP_X_REQUESTED_WITH"
- @request.env.delete "HTTP_ACCEPT"
- end
+ def head(action, **args)
+ process(action, method: "HEAD", **args)
end
- alias xhr :xml_http_request
# Simulate an HTTP request to +action+ by specifying request method,
# parameters and set/volley the response.
@@ -442,6 +436,8 @@ module ActionController
# - +session+: A hash of parameters to store in the session. This may be +nil+.
# - +flash+: A hash of parameters to store in the flash. This may be +nil+.
# - +format+: Request format. Defaults to +nil+. Can be string or symbol.
+ # - +as+: Content type. Defaults to +nil+. Must be a symbol that corresponds
+ # to a mime type.
#
# Example calling +create+ action and sending two params:
#
@@ -458,40 +454,14 @@ module ActionController
# respectively which will make tests more expressive.
#
# Note that the request method is not verified.
- def process(action, *args)
+ def process(action, method: "GET", params: {}, session: nil, body: nil, flash: {}, format: nil, xhr: false, as: nil)
check_required_ivars
- if kwarg_request?(args)
- parameters, session, body, flash, http_method, format, xhr = args[0].values_at(:params, :session, :body, :flash, :method, :format, :xhr)
- else
- http_method, parameters, session, flash = args
- format = nil
-
- if parameters.is_a?(String) && http_method != "HEAD"
- body = parameters
- parameters = nil
- end
-
- if parameters || session || flash
- non_kwarg_request_warning
- end
- end
-
if body
@request.set_header "RAW_POST_DATA", body
end
- if http_method
- http_method = http_method.to_s.upcase
- else
- http_method = "GET"
- end
-
- parameters ||= {}
-
- if format
- parameters[:format] = format
- end
+ http_method = method.to_s.upcase
@html_document = nil
@@ -507,7 +477,16 @@ module ActionController
@request.set_header "REQUEST_METHOD", http_method
- parameters = parameters.symbolize_keys
+ if as
+ @request.content_type = Mime[as].to_s
+ format ||= as
+ end
+
+ parameters = params.symbolize_keys
+
+ if format
+ parameters[:format] = format
+ end
generated_extras = @routes.generate_extras(parameters.merge(controller: controller_class_name, action: action.to_s))
generated_path = generated_path(generated_extras)
@@ -536,8 +515,6 @@ module ActionController
@request = @controller.request
@response = @controller.response
- @request.delete_header "HTTP_COOKIE"
-
if @request.have_cookie_jar?
unless @request.cookie_jar.committed?
@request.cookie_jar.write(@response)
@@ -613,6 +590,7 @@ module ActionController
include ActionDispatch::Assertions
class_attribute :_controller_class
setup :setup_controller_request_and_response
+ ActiveSupport.run_load_hooks(:action_controller_test_case, self)
end
private
@@ -626,38 +604,6 @@ module ActionController
env
end
- def process_with_kwargs(http_method, action, *args)
- if kwarg_request?(args)
- args.first.merge!(method: http_method)
- process(action, *args)
- else
- non_kwarg_request_warning if args.any?
-
- args = args.unshift(http_method)
- process(action, *args)
- end
- end
-
- REQUEST_KWARGS = %i(params session flash method body xhr)
- def kwarg_request?(args)
- args[0].respond_to?(:keys) && (
- (args[0].key?(:format) && args[0].keys.size == 1) ||
- args[0].keys.any? { |k| REQUEST_KWARGS.include?(k) }
- )
- end
-
- def non_kwarg_request_warning
- ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc)
- ActionController::TestCase HTTP request methods will accept only
- keyword arguments in future Rails versions.
-
- Examples:
-
- get :show, params: { id: 1 }, session: { user_id: 1 }
- process :update, method: :post, params: { id: 1 }
- MSG
- end
-
def document_root_element
html_document.root
end
@@ -675,5 +621,4 @@ module ActionController
include Behavior
end
- # :startdoc:
end
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 60cee6acbd..028177ace2 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2016 David Heinemeier Hansson
+# Copyright (c) 2004-2017 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -54,7 +54,6 @@ module ActionDispatch
autoload :ExceptionWrapper
autoload :Executor
autoload :Flash
- autoload :ParamsParser
autoload :PublicExceptions
autoload :Reloader
autoload :RemoteIp
diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb
index e5874a39f6..e584b84d92 100644
--- a/actionpack/lib/action_dispatch/http/filter_parameters.rb
+++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb
@@ -51,28 +51,28 @@ module ActionDispatch
@filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}"
end
- protected
+ private
- def parameter_filter
+ def parameter_filter # :doc:
parameter_filter_for fetch_header("action_dispatch.parameter_filter") {
return NULL_PARAM_FILTER
}
end
- def env_filter
+ def env_filter # :doc:
user_key = fetch_header("action_dispatch.parameter_filter") {
return NULL_ENV_FILTER
}
parameter_filter_for(Array(user_key) + ENV_MATCH)
end
- def parameter_filter_for(filters)
+ def parameter_filter_for(filters) # :doc:
ParameterFilter.new(filters)
end
KV_RE = "[^&;=]+"
PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})}
- def filtered_query_string
+ def filtered_query_string # :doc:
query_string.gsub(PAIR_RE) do |_|
parameter_filter.filter([[$1, $2]]).first.join("=")
end
diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb
index d5eef2987d..3c03976f03 100644
--- a/actionpack/lib/action_dispatch/http/headers.rb
+++ b/actionpack/lib/action_dispatch/http/headers.rb
@@ -86,7 +86,7 @@ module ActionDispatch
@req.fetch_header(env_name(key)) do
return default unless default == DEFAULT
return yield if block_given?
- raise NameError, key
+ raise KeyError, key
end
end
@@ -115,8 +115,8 @@ module ActionDispatch
private
- # Converts an HTTP header name to an environment variable name if it is
- # not contained within the headers hash.
+ # Converts an HTTP header name to an environment variable name if it is
+ # not contained within the headers hash.
def env_name(key)
key = key.to_s
if key =~ HTTP_HEADER
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index d0c9413efa..c4fe3a5c09 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -29,8 +29,8 @@ module ActionDispatch
content_mime_type && content_mime_type.to_s
end
- def has_content_type?
- has_header? "CONTENT_TYPE"
+ def has_content_type? # :nodoc:
+ get_header "CONTENT_TYPE"
end
# Returns the accepted MIME type for the request.
@@ -150,20 +150,20 @@ module ActionDispatch
order.include?(Mime::ALL) ? format : nil
end
- protected
+ private
BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
- def valid_accept_header
+ def valid_accept_header # :doc:
(xhr? && (accept.present? || content_mime_type)) ||
(accept.present? && accept !~ BROWSER_LIKE_ACCEPTS)
end
- def use_accept_header
+ def use_accept_header # :doc:
!self.class.ignore_accept_header
end
- def format_from_path_extension
+ def format_from_path_extension # :doc:
path = get_header("action_dispatch.original_path") || get_header("PATH_INFO")
if match = path && path.match(/\.(\w+)\z/)
Mime[match.captures.first]
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index b9121a577c..6b718e3682 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -45,32 +45,6 @@ module Mime
return type if type.is_a?(Type)
EXTENSION_LOOKUP.fetch(type.to_s) { |k| yield k }
end
-
- def const_missing(sym)
- ext = sym.downcase
- if Mime[ext]
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Accessing mime types via constants is deprecated.
- Please change `Mime::#{sym}` to `Mime[:#{ext}]`.
- MSG
- Mime[ext]
- else
- super
- end
- end
-
- def const_defined?(sym, inherit = true)
- ext = sym.downcase
- if Mime[ext]
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Accessing mime types via constants is deprecated.
- Please change `Mime.const_defined?(#{sym})` to `Mime[:#{ext}]`.
- MSG
- true
- else
- super
- end
- end
end
# Encapsulates the notion of a mime type. Can be used at render time, for example, with:
@@ -304,6 +278,8 @@ module Mime
def all?; false; 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 :string, :synonyms
@@ -321,7 +297,7 @@ module Mime
end
end
- def respond_to_missing?(method, include_private = false) #:nodoc:
+ def respond_to_missing?(method, include_private = false)
method.to_s.ends_with? "?"
end
end
diff --git a/actionpack/lib/action_dispatch/http/parameter_filter.rb b/actionpack/lib/action_dispatch/http/parameter_filter.rb
index 01fe35f5c6..889f55a52a 100644
--- a/actionpack/lib/action_dispatch/http/parameter_filter.rb
+++ b/actionpack/lib/action_dispatch/http/parameter_filter.rb
@@ -50,7 +50,7 @@ module ActionDispatch
def initialize(regexps, deep_regexps, blocks)
@regexps = regexps
@deep_regexps = deep_regexps.any? ? deep_regexps : nil
- @blocks = blocks
+ @blocks = blocks
end
def call(original_params, parents = [])
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index 42e80b9bf5..ad4aadacf5 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -8,10 +8,18 @@ module ActionDispatch
DEFAULT_PARSERS = {
Mime[:json].symbol => -> (raw_post) {
data = ActiveSupport::JSON.decode(raw_post)
- data.is_a?(Hash) ? data : {_json: data}
+ data.is_a?(Hash) ? data : { _json: data }
}
}
+ # Raised when raw data from the request cannot be parsed by the parser
+ # defined for request's content mime type.
+ class ParseError < StandardError
+ def initialize
+ super($!.message)
+ end
+ end
+
included do
class << self
attr_reader :parameter_parsers
@@ -37,7 +45,7 @@ module ActionDispatch
query_parameters.dup
end
params.merge!(path_parameters)
- params = set_custom_encoding(params)
+ params = set_binary_encoding(params)
set_header("action_dispatch.request.parameters", params)
params
end
@@ -65,21 +73,16 @@ module ActionDispatch
private
- def set_custom_encoding(params)
+ def set_binary_encoding(params)
action = params[:action]
- params.each do |k, v|
- if v.is_a?(String) && v.encoding != encoding_template(action, k)
- params[k] = v.force_encoding(encoding_template(action, k))
+ if controller_class.binary_params_for?(action)
+ ActionDispatch::Request::Utils.each_param_value(params) do |param|
+ param.force_encoding ::Encoding::ASCII_8BIT
end
end
-
params
end
- def encoding_template(action, param)
- controller_class.encoding_for_param(action, param)
- end
-
def parse_formatted_parameters(parsers)
return yield if content_length.zero? || content_mime_type.nil?
@@ -91,7 +94,7 @@ module ActionDispatch
my_logger = logger || ActiveSupport::Logger.new($stderr)
my_logger.debug "Error occurred while parsing request parameters.\nContents:\n\n#{raw_post}"
- raise ParamsParser::ParseError
+ raise ParseError
end
end
@@ -100,4 +103,8 @@ module ActionDispatch
end
end
end
+
+ module ParamsParser
+ ParseError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("ActionDispatch::ParamsParser::ParseError", "ActionDispatch::Http::Parameters::ParseError")
+ end
end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index e7cc6d5f31..19fa42ce12 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -68,8 +68,8 @@ module ActionDispatch
PASS_NOT_FOUND = Class.new { # :nodoc:
def self.action(_); self; end
- def self.call(_); [404, {"X-Cascade" => "pass"}, []]; end
- def self.encoding_for_param(action, param); ::Encoding::UTF_8; end
+ def self.call(_); [404, { "X-Cascade" => "pass" }, []]; end
+ def self.binary_params_for?(action); false; end
}
def controller_class
@@ -85,6 +85,9 @@ module ActionDispatch
end
end
+ # Returns true if the request has a header matching the given key parameter.
+ #
+ # request.key? :ip_spoofing_check # => true
def key?(key)
has_header? key
end
@@ -357,7 +360,7 @@ module ActionDispatch
end
self.request_parameters = Request::Utils.normalize_encode_params(pr)
end
- rescue ParamsParser::ParseError # one of the parse strategies blew up
+ rescue Http::Parameters::ParseError # one of the parse strategies blew up
self.request_parameters = Request::Utils.normalize_encode_params(super || {})
raise
rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 9788431943..516a2af69a 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -39,7 +39,7 @@ module ActionDispatch # :nodoc:
super(header)
end
- def []=(k,v)
+ def []=(k, v)
if @response.sending? || @response.sent?
raise ActionDispatch::IllegalStateError, "header already sent"
end
@@ -224,8 +224,12 @@ module ActionDispatch # :nodoc:
# Sets the HTTP content type.
def content_type=(content_type)
- header_info = parse_content_type
- set_content_type content_type.to_s, header_info.charset || self.class.default_charset
+ return unless content_type
+ new_header_info = parse_content_type(content_type.to_s)
+ prev_header_info = parsed_content_type_header
+ charset = new_header_info.charset || prev_header_info.charset
+ charset ||= self.class.default_charset unless prev_header_info.mime_type
+ set_content_type new_header_info.mime_type, charset
end
# Sets the HTTP response's content MIME type. For example, in the controller
@@ -238,7 +242,7 @@ module ActionDispatch # :nodoc:
# information.
def content_type
- parse_content_type.mime_type
+ parsed_content_type_header.mime_type
end
def sending_file=(v)
@@ -247,13 +251,13 @@ module ActionDispatch # :nodoc:
end
end
- # Sets the HTTP character set. In case of nil parameter
+ # Sets the HTTP character set. In case of +nil+ parameter
# it sets the charset to utf-8.
#
# response.charset = 'utf-16' # => 'utf-16'
# response.charset = nil # => 'utf-8'
def charset=(charset)
- header_info = parse_content_type
+ header_info = parsed_content_type_header
if false == charset
set_header CONTENT_TYPE, header_info.mime_type
else
@@ -265,7 +269,7 @@ module ActionDispatch # :nodoc:
# The charset of the response. HTML wants to know the encoding of the
# content you're giving them, so we need to send that along.
def charset
- header_info = parse_content_type
+ header_info = parsed_content_type_header
header_info.charset || self.class.default_charset
end
@@ -403,17 +407,22 @@ module ActionDispatch # :nodoc:
ContentTypeHeader = Struct.new :mime_type, :charset
NullContentTypeHeader = ContentTypeHeader.new nil, nil
- def parse_content_type
- content_type = get_header CONTENT_TYPE
+ def parse_content_type(content_type)
if content_type
type, charset = content_type.split(/;\s*charset=/)
- type = nil if type.empty?
+ type = nil if type && type.empty?
ContentTypeHeader.new(type, charset)
else
NullContentTypeHeader
end
end
+ # Small internal convenience method to get the parsed version of the current
+ # content type header.
+ def parsed_content_type_header
+ parse_content_type(get_header(CONTENT_TYPE))
+ end
+
def set_content_type(content_type, charset)
type = (content_type || "").dup
type << "; charset=#{charset}" if charset
@@ -450,7 +459,7 @@ module ActionDispatch # :nodoc:
def assign_default_content_type_and_charset!
return if content_type
- ct = parse_content_type
+ ct = parsed_content_type_header
set_content_type(ct.mime_type || Mime[:html].to_s,
ct.charset || self.class.default_charset)
end
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index 9aa73c862b..61ba052e45 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -24,7 +24,7 @@ module ActionDispatch
attr_accessor :headers
def initialize(hash) # :nodoc:
- @tempfile = hash[:tempfile]
+ @tempfile = hash[:tempfile]
raise(ArgumentError, ":tempfile is required") unless @tempfile
@original_filename = hash[:filename]
@@ -40,7 +40,7 @@ module ActionDispatch
end
# Shortcut for +tempfile.read+.
- def read(length=nil, buffer=nil)
+ def read(length = nil, buffer = nil)
@tempfile.read(length, buffer)
end
@@ -50,7 +50,7 @@ module ActionDispatch
end
# Shortcut for +tempfile.close+.
- def close(unlink_now=false)
+ def close(unlink_now = false)
@tempfile.close(unlink_now)
end
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index e85ea90b94..a6937d54ff 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -66,7 +66,7 @@ module ActionDispatch
end
def path_for(options)
- path = options[:script_name].to_s.chomp("/".freeze)
+ path = options[:script_name].to_s.chomp("/".freeze)
path << options[:path] if options.key?(:path)
add_trailing_slash(path) if options[:trailing_slash]
@@ -80,7 +80,7 @@ module ActionDispatch
def add_params(path, params)
params = { params: params } unless params.is_a?(Hash)
- params.reject! { |_,v| v.to_param.nil? }
+ params.reject! { |_, v| v.to_param.nil? }
query = params.to_query
path << "?#{query}" unless query.empty?
end
@@ -192,11 +192,7 @@ module ActionDispatch
# Returns the complete URL used for this request.
#
- # class Request < Rack::Request
- # include ActionDispatch::Http::URL
- # end
- #
- # req = Request.new 'HTTP_HOST' => 'example.com'
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
# req.url # => "http://example.com"
def url
protocol + host_with_port + fullpath
@@ -204,14 +200,10 @@ module ActionDispatch
# Returns 'https://' if this is an SSL request and 'http://' otherwise.
#
- # class Request < Rack::Request
- # include ActionDispatch::Http::URL
- # end
- #
- # req = Request.new 'HTTP_HOST' => 'example.com'
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
# req.protocol # => "http://"
#
- # req = Request.new 'HTTP_HOST' => 'example.com', 'HTTPS' => 'on'
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com', 'HTTPS' => 'on'
# req.protocol # => "https://"
def protocol
@protocol ||= ssl? ? "https://" : "http://"
@@ -219,17 +211,13 @@ module ActionDispatch
# Returns the \host and port for this request, such as "example.com:8080".
#
- # class Request < Rack::Request
- # include ActionDispatch::Http::URL
- # end
- #
- # req = Request.new 'HTTP_HOST' => 'example.com'
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
# req.raw_host_with_port # => "example.com"
#
- # req = Request.new 'HTTP_HOST' => 'example.com:80'
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
# req.raw_host_with_port # => "example.com:80"
#
- # req = Request.new 'HTTP_HOST' => 'example.com:8080'
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
# req.raw_host_with_port # => "example.com:8080"
def raw_host_with_port
if forwarded = x_forwarded_host.presence
@@ -241,11 +229,7 @@ module ActionDispatch
# Returns the host for this request, such as "example.com".
#
- # class Request < Rack::Request
- # include ActionDispatch::Http::URL
- # end
- #
- # req = Request.new 'HTTP_HOST' => 'example.com:8080'
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
# req.host # => "example.com"
def host
raw_host_with_port.sub(/:\d+$/, "".freeze)
@@ -255,17 +239,13 @@ module ActionDispatch
# "example.com:8080". Port is only included if it is not a default port
# (80 or 443)
#
- # class Request < Rack::Request
- # include ActionDispatch::Http::URL
- # end
- #
- # req = Request.new 'HTTP_HOST' => 'example.com'
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
# req.host_with_port # => "example.com"
#
- # req = Request.new 'HTTP_HOST' => 'example.com:80'
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
# req.host_with_port # => "example.com"
#
- # req = Request.new 'HTTP_HOST' => 'example.com:8080'
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
# req.host_with_port # => "example.com:8080"
def host_with_port
"#{host}#{port_string}"
@@ -273,14 +253,10 @@ module ActionDispatch
# Returns the port number of this request as an integer.
#
- # class Request < Rack::Request
- # include ActionDispatch::Http::URL
- # end
- #
- # req = Request.new 'HTTP_HOST' => 'example.com'
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
# req.port # => 80
#
- # req = Request.new 'HTTP_HOST' => 'example.com:8080'
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
# req.port # => 8080
def port
@port ||= begin
@@ -294,11 +270,7 @@ module ActionDispatch
# Returns the standard \port number for this request's protocol.
#
- # class Request < Rack::Request
- # include ActionDispatch::Http::URL
- # end
- #
- # req = Request.new 'HTTP_HOST' => 'example.com:8080'
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
# req.standard_port # => 80
def standard_port
case protocol
@@ -309,14 +281,10 @@ module ActionDispatch
# Returns whether this request is using the standard port
#
- # class Request < Rack::Request
- # include ActionDispatch::Http::URL
- # end
- #
- # req = Request.new 'HTTP_HOST' => 'example.com:80'
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
# req.standard_port? # => true
#
- # req = Request.new 'HTTP_HOST' => 'example.com:8080'
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
# req.standard_port? # => false
def standard_port?
port == standard_port
@@ -325,14 +293,10 @@ module ActionDispatch
# Returns a number \port suffix like 8080 if the \port number of this request
# is not the default HTTP \port 80 or HTTPS \port 443.
#
- # class Request < Rack::Request
- # include ActionDispatch::Http::URL
- # end
- #
- # req = Request.new 'HTTP_HOST' => 'example.com:80'
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
# req.optional_port # => nil
#
- # req = Request.new 'HTTP_HOST' => 'example.com:8080'
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
# req.optional_port # => 8080
def optional_port
standard_port? ? nil : port
@@ -341,14 +305,10 @@ module ActionDispatch
# Returns a string \port suffix, including colon, like ":8080" if the \port
# number of this request is not the default HTTP \port 80 or HTTPS \port 443.
#
- # class Request < Rack::Request
- # include ActionDispatch::Http::URL
- # end
- #
- # req = Request.new 'HTTP_HOST' => 'example.com:80'
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
# req.port_string # => ""
#
- # req = Request.new 'HTTP_HOST' => 'example.com:8080'
+ # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
# req.port_string # => ":8080"
def port_string
standard_port? ? "" : ":#{port}"
@@ -356,14 +316,10 @@ module ActionDispatch
# Returns the requested port, such as 8080, based on SERVER_PORT
#
- # class Request < Rack::Request
- # include ActionDispatch::Http::URL
- # end
- #
- # req = Request.new 'SERVER_PORT' => '80'
+ # req = ActionDispatch::Request.new 'SERVER_PORT' => '80'
# req.server_port # => 80
#
- # req = Request.new 'SERVER_PORT' => '8080'
+ # req = ActionDispatch::Request.new 'SERVER_PORT' => '8080'
# req.server_port # => 8080
def server_port
get_header("SERVER_PORT").to_i
diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb
index d3d79f8750..f3b8e82d32 100644
--- a/actionpack/lib/action_dispatch/journey/formatter.rb
+++ b/actionpack/lib/action_dispatch/journey/formatter.rb
@@ -1,10 +1,11 @@
require "action_controller/metal/exceptions"
module ActionDispatch
+ # :stopdoc:
module Journey
# The Formatter class is used for formatting URLs. For example, parameters
# passed to +url_for+ in Rails will eventually call Formatter#generate.
- class Formatter # :nodoc:
+ class Formatter
attr_reader :routes
def initialize(routes)
@@ -35,7 +36,7 @@ module ActionDispatch
route.parts.reverse_each do |key|
break if defaults[key].nil? && parameterized_parts[key].present?
- break if parameterized_parts[key].to_s != defaults[key].to_s
+ next if parameterized_parts[key].to_s != defaults[key].to_s
break if required_parts.include?(key)
parameterized_parts.delete(key)
@@ -44,8 +45,12 @@ module ActionDispatch
return [route.format(parameterized_parts), params]
end
- message = "No route matches #{Hash[constraints.sort_by{|k,v| k.to_s}].inspect}"
- message << " missing required keys: #{missing_keys.sort.inspect}" if missing_keys && !missing_keys.empty?
+ unmatched_keys = (missing_keys || []) & constraints.keys
+ missing_keys = (missing_keys || []) - unmatched_keys
+
+ message = "No route matches #{Hash[constraints.sort_by { |k, v| k.to_s }].inspect}"
+ message << ", missing required keys: #{missing_keys.sort.inspect}" if missing_keys && !missing_keys.empty?
+ message << ", possible unmatched constraints: #{unmatched_keys.sort.inspect}" if unmatched_keys && !unmatched_keys.empty?
raise ActionController::UrlGenerationError, message
end
@@ -87,7 +92,11 @@ module ActionDispatch
else
routes = non_recursive(cache, options)
- hash = routes.group_by { |_, r| r.score(options) }
+ supplied_keys = options.each_with_object({}) do |(k, v), h|
+ h[k.to_s] = true if v
+ end
+
+ hash = routes.group_by { |_, r| r.score(supplied_keys) }
hash.keys.sort.reverse_each do |score|
break if score < 0
@@ -174,4 +183,5 @@ module ActionDispatch
end
end
end
+ # :startdoc:
end
diff --git a/actionpack/lib/action_dispatch/journey/gtg/builder.rb b/actionpack/lib/action_dispatch/journey/gtg/builder.rb
index 9990c66627..0f8bed89bf 100644
--- a/actionpack/lib/action_dispatch/journey/gtg/builder.rb
+++ b/actionpack/lib/action_dispatch/journey/gtg/builder.rb
@@ -17,7 +17,7 @@ module ActionDispatch
def transition_table
dtrans = TransitionTable.new
marked = {}
- state_id = Hash.new { |h,k| h[k] = h.length }
+ state_id = Hash.new { |h, k| h[k] = h.length }
start = firstpos(root)
dstates = [start]
diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
index 0be18dc26f..beb9f1ef3b 100644
--- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
+++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
@@ -12,7 +12,7 @@ module ActionDispatch
@regexp_states = {}
@string_states = {}
@accepting = {}
- @memos = Hash.new { |h,k| h[k] = [] }
+ @memos = Hash.new { |h, k| h[k] = [] }
end
def add_accepting(state)
@@ -56,7 +56,7 @@ module ActionDispatch
end
def as_json(options = nil)
- simple_regexp = Hash.new { |h,k| h[k] = {} }
+ simple_regexp = Hash.new { |h, k| h[k] = {} }
@regexp_states.each do |from, hash|
hash.each do |re, to|
diff --git a/actionpack/lib/action_dispatch/journey/nfa/builder.rb b/actionpack/lib/action_dispatch/journey/nfa/builder.rb
index 19e5752ae5..532f765094 100644
--- a/actionpack/lib/action_dispatch/journey/nfa/builder.rb
+++ b/actionpack/lib/action_dispatch/journey/nfa/builder.rb
@@ -36,7 +36,7 @@ module ActionDispatch
def visit_OR(node)
from = @i += 1
children = node.children.map { |c| visit(c) }
- to = @i += 1
+ to = @i += 1
children.each do |child|
@tt[from, child.first] = nil
diff --git a/actionpack/lib/action_dispatch/journey/nfa/dot.rb b/actionpack/lib/action_dispatch/journey/nfa/dot.rb
index 0cd71b726a..8119e5d9da 100644
--- a/actionpack/lib/action_dispatch/journey/nfa/dot.rb
+++ b/actionpack/lib/action_dispatch/journey/nfa/dot.rb
@@ -26,7 +26,7 @@ digraph nfa {
node [shape = circle];
#{edges.join "\n"}
}
- eodot
+ eodot
end
end
end
diff --git a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb
index 4737adc724..543a670da0 100644
--- a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb
+++ b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb
@@ -10,7 +10,7 @@ module ActionDispatch
attr_reader :memos
def initialize
- @table = Hash.new { |h,f| h[f] = {} }
+ @table = Hash.new { |h, f| h[f] = {} }
@memos = {}
@accepting = nil
@inverted = nil
diff --git a/actionpack/lib/action_dispatch/journey/parser.rb b/actionpack/lib/action_dispatch/journey/parser.rb
index 7ec9d63859..db42b64c4b 100644
--- a/actionpack/lib/action_dispatch/journey/parser.rb
+++ b/actionpack/lib/action_dispatch/journey/parser.rb
@@ -1,10 +1,12 @@
#
# DO NOT MODIFY!!!!
-# This file is automatically generated by Racc 1.4.11
+# This file is automatically generated by Racc 1.4.14
# from Racc grammer file "".
#
-require "racc/parser.rb"
+require 'racc/parser.rb'
+
+# :stopdoc:
require "action_dispatch/journey/parser_extras"
module ActionDispatch
@@ -12,133 +14,133 @@ module ActionDispatch
class Parser < Racc::Parser
##### State transition tables begin ###
- racc_action_table = [
- 13, 15, 14, 7, 21, 16, 8, 19, 13, 15,
- 14, 7, 17, 16, 8, 13, 15, 14, 7, 24,
- 16, 8, 13, 15, 14, 7, 19, 16, 8 ]
-
- racc_action_check = [
- 2, 2, 2, 2, 17, 2, 2, 2, 0, 0,
- 0, 0, 1, 0, 0, 19, 19, 19, 19, 20,
- 19, 19, 7, 7, 7, 7, 22, 7, 7 ]
-
- racc_action_pointer = [
- 6, 12, -2, nil, nil, nil, nil, 20, nil, nil,
- nil, nil, nil, nil, nil, nil, nil, 4, nil, 13,
- 13, nil, 17, nil, nil ]
-
- racc_action_default = [
- -19, -19, -2, -3, -4, -5, -6, -19, -10, -11,
- -12, -13, -14, -15, -16, -17, -18, -19, -1, -19,
- -19, 25, -8, -9, -7 ]
-
- racc_goto_table = [
- 1, 22, 18, 23, nil, nil, nil, 20 ]
-
- racc_goto_check = [
- 1, 2, 1, 3, nil, nil, nil, 1 ]
-
- racc_goto_pointer = [
- nil, 0, -18, -16, nil, nil, nil, nil, nil, nil,
- nil ]
-
- racc_goto_default = [
- nil, nil, 2, 3, 4, 5, 6, 9, 10, 11,
- 12 ]
-
- racc_reduce_table = [
- 0, 0, :racc_error,
- 2, 11, :_reduce_1,
- 1, 11, :_reduce_2,
- 1, 11, :_reduce_none,
- 1, 12, :_reduce_none,
- 1, 12, :_reduce_none,
- 1, 12, :_reduce_none,
- 3, 15, :_reduce_7,
- 3, 13, :_reduce_8,
- 3, 13, :_reduce_9,
- 1, 16, :_reduce_10,
- 1, 14, :_reduce_none,
- 1, 14, :_reduce_none,
- 1, 14, :_reduce_none,
- 1, 14, :_reduce_none,
- 1, 19, :_reduce_15,
- 1, 17, :_reduce_16,
- 1, 18, :_reduce_17,
- 1, 20, :_reduce_18 ]
-
- racc_reduce_n = 19
-
- racc_shift_n = 25
-
- racc_token_table = {
- false => 0,
- :error => 1,
- :SLASH => 2,
- :LITERAL => 3,
- :SYMBOL => 4,
- :LPAREN => 5,
- :RPAREN => 6,
- :DOT => 7,
- :STAR => 8,
- :OR => 9 }
-
- racc_nt_base = 10
-
- racc_use_result_var = false
-
- Racc_arg = [
- racc_action_table,
- racc_action_check,
- racc_action_default,
- racc_action_pointer,
- racc_goto_table,
- racc_goto_check,
- racc_goto_default,
- racc_goto_pointer,
- racc_nt_base,
- racc_reduce_table,
- racc_token_table,
- racc_shift_n,
- racc_reduce_n,
- racc_use_result_var ]
-
- Racc_token_to_s_table = [
- "$end",
- "error",
- "SLASH",
- "LITERAL",
- "SYMBOL",
- "LPAREN",
- "RPAREN",
- "DOT",
- "STAR",
- "OR",
- "$start",
- "expressions",
- "expression",
- "or",
- "terminal",
- "group",
- "star",
- "symbol",
- "literal",
- "slash",
- "dot" ]
-
- Racc_debug_parser = false
+racc_action_table = [
+ 13, 15, 14, 7, 19, 16, 8, 19, 13, 15,
+ 14, 7, 17, 16, 8, 13, 15, 14, 7, 21,
+ 16, 8, 13, 15, 14, 7, 24, 16, 8 ]
+
+racc_action_check = [
+ 2, 2, 2, 2, 22, 2, 2, 2, 19, 19,
+ 19, 19, 1, 19, 19, 7, 7, 7, 7, 17,
+ 7, 7, 0, 0, 0, 0, 20, 0, 0 ]
+
+racc_action_pointer = [
+ 20, 12, -2, nil, nil, nil, nil, 13, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, 19, nil, 6,
+ 20, nil, -5, nil, nil ]
+
+racc_action_default = [
+ -19, -19, -2, -3, -4, -5, -6, -19, -10, -11,
+ -12, -13, -14, -15, -16, -17, -18, -19, -1, -19,
+ -19, 25, -8, -9, -7 ]
+
+racc_goto_table = [
+ 1, 22, 18, 23, nil, nil, nil, 20 ]
+
+racc_goto_check = [
+ 1, 2, 1, 3, nil, nil, nil, 1 ]
+
+racc_goto_pointer = [
+ nil, 0, -18, -16, nil, nil, nil, nil, nil, nil,
+ nil ]
+
+racc_goto_default = [
+ nil, nil, 2, 3, 4, 5, 6, 9, 10, 11,
+ 12 ]
+
+racc_reduce_table = [
+ 0, 0, :racc_error,
+ 2, 11, :_reduce_1,
+ 1, 11, :_reduce_2,
+ 1, 11, :_reduce_none,
+ 1, 12, :_reduce_none,
+ 1, 12, :_reduce_none,
+ 1, 12, :_reduce_none,
+ 3, 15, :_reduce_7,
+ 3, 13, :_reduce_8,
+ 3, 13, :_reduce_9,
+ 1, 16, :_reduce_10,
+ 1, 14, :_reduce_none,
+ 1, 14, :_reduce_none,
+ 1, 14, :_reduce_none,
+ 1, 14, :_reduce_none,
+ 1, 19, :_reduce_15,
+ 1, 17, :_reduce_16,
+ 1, 18, :_reduce_17,
+ 1, 20, :_reduce_18 ]
+
+racc_reduce_n = 19
+
+racc_shift_n = 25
+
+racc_token_table = {
+ false => 0,
+ :error => 1,
+ :SLASH => 2,
+ :LITERAL => 3,
+ :SYMBOL => 4,
+ :LPAREN => 5,
+ :RPAREN => 6,
+ :DOT => 7,
+ :STAR => 8,
+ :OR => 9 }
+
+racc_nt_base = 10
+
+racc_use_result_var = false
+
+Racc_arg = [
+ racc_action_table,
+ racc_action_check,
+ racc_action_default,
+ racc_action_pointer,
+ racc_goto_table,
+ racc_goto_check,
+ racc_goto_default,
+ racc_goto_pointer,
+ racc_nt_base,
+ racc_reduce_table,
+ racc_token_table,
+ racc_shift_n,
+ racc_reduce_n,
+ racc_use_result_var ]
+
+Racc_token_to_s_table = [
+ "$end",
+ "error",
+ "SLASH",
+ "LITERAL",
+ "SYMBOL",
+ "LPAREN",
+ "RPAREN",
+ "DOT",
+ "STAR",
+ "OR",
+ "$start",
+ "expressions",
+ "expression",
+ "or",
+ "terminal",
+ "group",
+ "star",
+ "symbol",
+ "literal",
+ "slash",
+ "dot" ]
+
+Racc_debug_parser = false
##### State transition tables end #####
# reduce 0 omitted
- def _reduce_1(val, _values)
- Cat.new(val.first, val.last)
- end
+def _reduce_1(val, _values)
+ Cat.new(val.first, val.last)
+end
- def _reduce_2(val, _values)
- val.first
- end
+def _reduce_2(val, _values)
+ val.first
+end
# reduce 3 omitted
@@ -148,21 +150,21 @@ module ActionDispatch
# reduce 6 omitted
- def _reduce_7(val, _values)
- Group.new(val[1])
- end
+def _reduce_7(val, _values)
+ Group.new(val[1])
+end
- def _reduce_8(val, _values)
- Or.new([val.first, val.last])
- end
+def _reduce_8(val, _values)
+ Or.new([val.first, val.last])
+end
- def _reduce_9(val, _values)
- Or.new([val.first, val.last])
- end
+def _reduce_9(val, _values)
+ Or.new([val.first, val.last])
+end
- def _reduce_10(val, _values)
- Star.new(Symbol.new(val.last))
- end
+def _reduce_10(val, _values)
+ Star.new(Symbol.new(val.last))
+end
# reduce 11 omitted
@@ -172,25 +174,26 @@ module ActionDispatch
# reduce 14 omitted
- def _reduce_15(val, _values)
- Slash.new("/")
- end
+def _reduce_15(val, _values)
+ Slash.new(val.first)
+end
+
+def _reduce_16(val, _values)
+ Symbol.new(val.first)
+end
- def _reduce_16(val, _values)
- Symbol.new(val.first)
- end
+def _reduce_17(val, _values)
+ Literal.new(val.first)
+end
- def _reduce_17(val, _values)
- Literal.new(val.first)
- end
+def _reduce_18(val, _values)
+ Dot.new(val.first)
+end
- def _reduce_18(val, _values)
- Dot.new(val.first)
- end
+def _reduce_none(val, _values)
+ val[0]
+end
- def _reduce_none(val, _values)
- val[0]
- end
end # class Parser
- end # module Journey
-end # module ActionDispatch
+ end # module Journey
+ end # module ActionDispatch
diff --git a/actionpack/lib/action_dispatch/journey/parser.y b/actionpack/lib/action_dispatch/journey/parser.y
index d3f7c4d765..f9b1a7a958 100644
--- a/actionpack/lib/action_dispatch/journey/parser.y
+++ b/actionpack/lib/action_dispatch/journey/parser.y
@@ -30,7 +30,7 @@ rule
| dot
;
slash
- : SLASH { Slash.new('/') }
+ : SLASH { Slash.new(val.first) }
;
symbol
: SYMBOL { Symbol.new(val.first) }
@@ -45,5 +45,6 @@ rule
end
---- header
+# :stopdoc:
-require 'action_dispatch/journey/parser_extras'
+require "action_dispatch/journey/parser_extras"
diff --git a/actionpack/lib/action_dispatch/journey/parser_extras.rb b/actionpack/lib/action_dispatch/journey/parser_extras.rb
index ec26e634e8..4c7e82d93c 100644
--- a/actionpack/lib/action_dispatch/journey/parser_extras.rb
+++ b/actionpack/lib/action_dispatch/journey/parser_extras.rb
@@ -2,8 +2,9 @@ require "action_dispatch/journey/scanner"
require "action_dispatch/journey/nodes/node"
module ActionDispatch
- module Journey # :nodoc:
- class Parser < Racc::Parser # :nodoc:
+ # :stopdoc:
+ module Journey
+ class Parser < Racc::Parser
include Journey::Nodes
def self.parse(string)
@@ -24,4 +25,5 @@ module ActionDispatch
end
end
end
+ # :startdoc:
end
diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb
index a9713ff292..f2ac4818d8 100644
--- a/actionpack/lib/action_dispatch/journey/route.rb
+++ b/actionpack/lib/action_dispatch/journey/route.rb
@@ -1,6 +1,7 @@
module ActionDispatch
- module Journey # :nodoc:
- class Route # :nodoc:
+ # :stopdoc:
+ module Journey
+ class Route
attr_reader :app, :path, :defaults, :name, :precedence
attr_reader :constraints, :internal
@@ -80,9 +81,9 @@ module ActionDispatch
end
end
- def requirements # :nodoc:
+ def requirements
# needed for rails `rails routes`
- @defaults.merge(path.requirements).delete_if { |_,v|
+ @defaults.merge(path.requirements).delete_if { |_, v|
/.+?/ == v
}
end
@@ -95,13 +96,18 @@ module ActionDispatch
required_parts + required_defaults.keys
end
- def score(constraints)
+ def score(supplied_keys)
required_keys = path.required_names
- supplied_keys = constraints.map { |k,v| v && k.to_s }.compact
- return -1 unless (required_keys - supplied_keys).empty?
+ required_keys.each do |k|
+ return -1 unless supplied_keys.include?(k)
+ end
+
+ score = 0
+ path.names.each do |k|
+ score += 1 if supplied_keys.include?(k)
+ end
- score = (supplied_keys & path.names).length
score + (required_defaults.length * 2)
end
@@ -123,7 +129,7 @@ module ActionDispatch
end
def required_defaults
- @required_defaults ||= @defaults.dup.delete_if do |k,_|
+ @required_defaults ||= @defaults.dup.delete_if do |k, _|
parts.include?(k) || !required_default?(k)
end
end
@@ -176,4 +182,5 @@ module ActionDispatch
end
end
end
+ # :startdoc:
end
diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb
index 727763c972..084ae9325e 100644
--- a/actionpack/lib/action_dispatch/journey/router.rb
+++ b/actionpack/lib/action_dispatch/journey/router.rb
@@ -48,7 +48,7 @@ module ActionDispatch
return [status, headers, body]
end
- return [404, {"X-Cascade" => "pass"}, ["Not Found"]]
+ return [404, { "X-Cascade" => "pass" }, ["Not Found"]]
end
def recognize(rails_req)
@@ -72,7 +72,9 @@ module ActionDispatch
private
def partitioned_routes
- routes.partitioned_routes
+ routes.partition { |r|
+ r.path.anchored && r.ast.grep(Nodes::Symbol).all? { |n| n.default_regexp? }
+ }
end
def ast
@@ -107,9 +109,9 @@ module ActionDispatch
routes.sort_by!(&:precedence)
routes.map! { |r|
- match_data = r.path.match(req.path_info)
+ match_data = r.path.match(req.path_info)
path_parameters = r.defaults.dup
- match_data.names.zip(match_data.captures) { |name,val|
+ match_data.names.zip(match_data.captures) { |name, val|
path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
}
[match_data, path_parameters, r]
diff --git a/actionpack/lib/action_dispatch/journey/router/utils.rb b/actionpack/lib/action_dispatch/journey/router/utils.rb
index e72db2e8f6..d641642338 100644
--- a/actionpack/lib/action_dispatch/journey/router/utils.rb
+++ b/actionpack/lib/action_dispatch/journey/router/utils.rb
@@ -28,7 +28,7 @@ module ActionDispatch
US_ASCII = Encoding::US_ASCII
UTF_8 = Encoding::UTF_8
EMPTY = "".force_encoding(US_ASCII).freeze
- DEC2HEX = (0..255).to_a.map{ |i| ENCODE % i }.map{ |s| s.force_encoding(US_ASCII) }
+ DEC2HEX = (0..255).to_a.map { |i| ENCODE % i }.map { |s| s.force_encoding(US_ASCII) }
ALPHA = "a-zA-Z".freeze
DIGIT = "0-9".freeze
@@ -58,12 +58,12 @@ module ActionDispatch
uri.gsub(ESCAPED) { |match| [match[1, 2].hex].pack("C") }.force_encoding(encoding)
end
- protected
- def escape(component, pattern)
- component.gsub(pattern){ |unsafe| percent_encode(unsafe) }.force_encoding(US_ASCII)
+ private
+ def escape(component, pattern) # :doc:
+ component.gsub(pattern) { |unsafe| percent_encode(unsafe) }.force_encoding(US_ASCII)
end
- def percent_encode(unsafe)
+ def percent_encode(unsafe) # :doc:
safe = EMPTY.dup
unsafe.each_byte { |b| safe << DEC2HEX[b] }
safe
diff --git a/actionpack/lib/action_dispatch/journey/scanner.rb b/actionpack/lib/action_dispatch/journey/scanner.rb
index 4b8c8ab063..7dbb39b26d 100644
--- a/actionpack/lib/action_dispatch/journey/scanner.rb
+++ b/actionpack/lib/action_dispatch/journey/scanner.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
require "strscan"
module ActionDispatch
@@ -35,22 +36,23 @@ module ActionDispatch
def scan
case
# /
- when text = @ss.scan(/\//)
- [:SLASH, text]
+ when @ss.skip(/\//)
+ [:SLASH, "/"]
+ when @ss.skip(/\(/)
+ [:LPAREN, "("]
+ when @ss.skip(/\)/)
+ [:RPAREN, ")"]
+ when @ss.skip(/\|/)
+ [:OR, "|"]
+ when @ss.skip(/\./)
+ [:DOT, "."]
+ when text = @ss.scan(/:\w+/)
+ [:SYMBOL, text]
when text = @ss.scan(/\*\w+/)
[:STAR, text]
- when text = @ss.scan(/(?<!\\)\(/)
- [:LPAREN, text]
- when text = @ss.scan(/(?<!\\)\)/)
- [:RPAREN, text]
- when text = @ss.scan(/\|/)
- [:OR, text]
- when text = @ss.scan(/\./)
- [:DOT, text]
- when text = @ss.scan(/(?<!\\):\w+/)
- [:SYMBOL, text]
- when text = @ss.scan(/(?:[\w%\-~!$&'*+,;=@]|\\:|\\\(|\\\))+/)
- [:LITERAL, text.tr('\\', "")]
+ when text = @ss.scan(/(?:[\w%\-~!$&'*+,;=@]|\\[:()])+/)
+ text.tr! "\\", ""
+ [:LITERAL, text]
# any char
when text = @ss.scan(/./)
[:LITERAL, text]
diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb
index 452dc84cc5..cda859cba4 100644
--- a/actionpack/lib/action_dispatch/journey/visitors.rb
+++ b/actionpack/lib/action_dispatch/journey/visitors.rb
@@ -1,5 +1,6 @@
module ActionDispatch
- module Journey # :nodoc:
+ # :stopdoc:
+ module Journey
class Format
ESCAPE_PATH = ->(value) { Router::Utils.escape_path(value) }
ESCAPE_SEGMENT = ->(value) { Router::Utils.escape_segment(value) }
@@ -21,7 +22,7 @@ module ActionDispatch
@children = []
@parameters = []
- parts.each_with_index do |object,i|
+ parts.each_with_index do |object, i|
case object
when Journey::Format
@children << i
@@ -261,4 +262,5 @@ module ActionDispatch
end
end
end
+ # :startdoc:
end
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index bebf220488..956c53e813 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -179,7 +179,7 @@ module ActionDispatch
# Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
# the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
- # cookie was tampered with by the user (or a 3rd party), nil will be returned.
+ # cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
#
# If +secrets.secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
# legacy cookies signed with the old key generator will be transparently upgraded.
@@ -202,7 +202,7 @@ module ActionDispatch
end
# Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
- # If the cookie was tampered with by the user (or a 3rd party), nil will be returned.
+ # If the cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
#
# If +secrets.secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
# legacy cookies signed with the old key generator will be transparently upgraded.
@@ -332,13 +332,13 @@ module ActionDispatch
def update_cookies_from_jar
request_jar = @request.cookie_jar.instance_variable_get(:@cookies)
- set_cookies = request_jar.reject { |k,_| @delete_cookies.key?(k) }
+ set_cookies = request_jar.reject { |k, _| @delete_cookies.key?(k) }
@cookies.update set_cookies if set_cookies
end
def to_header
- @cookies.map { |k,v| "#{escape(k)}=#{escape(v)}" }.join "; "
+ @cookies.map { |k, v| "#{escape(k)}=#{escape(v)}" }.join "; "
end
def handle_options(options) #:nodoc:
@@ -355,7 +355,7 @@ module ActionDispatch
end
elsif options[:domain].is_a? Array
# if host matches one of the supplied domains without a dot in front of it
- options[:domain] = options[:domain].find {|domain| request.host.include? domain.sub(/^\./, "") }
+ options[:domain] = options[:domain].find { |domain| request.host.include? domain.sub(/^\./, "") }
end
end
@@ -372,7 +372,7 @@ module ActionDispatch
handle_options(options)
- if @cookies[name.to_s] != value or options[:expires]
+ if @cookies[name.to_s] != value || options[:expires]
@cookies[name.to_s] = value
@set_cookies[name.to_s] = options
@delete_cookies.delete(name.to_s)
@@ -406,7 +406,7 @@ module ActionDispatch
# Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie
def clear(options = {})
- @cookies.each_key{ |k| delete(k, options) }
+ @cookies.each_key { |k| delete(k, options) }
end
def write(headers)
@@ -576,7 +576,7 @@ module ActionDispatch
"Read the upgrade documentation to learn more about this new config option."
end
- secret = key_generator.generate_key(request.encrypted_cookie_salt || "")
+ 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)
end
diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
index d585e7042e..1c720c5a8e 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -38,7 +38,9 @@ module ActionDispatch
end
def render(*)
- if logger = ActionView::Base.logger
+ logger = ActionView::Base.logger
+
+ if logger && logger.respond_to?(:silence)
logger.silence { super }
else
super
@@ -151,7 +153,7 @@ module ActionDispatch
end
def render(status, body, format)
- [status, {"Content-Type" => "#{format}; charset=#{Response.default_charset}", "Content-Length" => body.bytesize.to_s}, [body]]
+ [status, { "Content-Type" => "#{format}; charset=#{Response.default_charset}", "Content-Length" => body.bytesize.to_s }, [body]]
end
def log_error(request, wrapper)
@@ -173,7 +175,11 @@ module ActionDispatch
end
def log_array(logger, array)
- array.map { |line| logger.fatal line }
+ if logger.formatter && logger.formatter.respond_to?(:tags_text)
+ logger.fatal array.join("\n#{logger.formatter.tags_text}")
+ else
+ logger.fatal array.join("\n")
+ end
end
def logger(request)
diff --git a/actionpack/lib/action_dispatch/middleware/debug_locks.rb b/actionpack/lib/action_dispatch/middleware/debug_locks.rb
index 91c2fbac01..74b952528e 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_locks.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_locks.rb
@@ -86,11 +86,11 @@ module ActionDispatch
end
blockers = threads.values.select { |binfo| blocked_by?(info, binfo, threads.values) }
- msg << " blocked by: #{blockers.map {|i| i[:index] }.join(', ')}\n" if blockers.any?
+ msg << " blocked by: #{blockers.map { |i| i[:index] }.join(', ')}\n" if blockers.any?
end
blockees = threads.values.select { |binfo| blocked_by?(binfo, info, threads.values) }
- msg << " blocking: #{blockees.map {|i| i[:index] }.join(', ')}\n" if blockees.any?
+ msg << " blocking: #{blockees.map { |i| i[:index] }.join(', ')}\n" if blockees.any?
msg << "\n#{info[:backtrace].join("\n")}\n" if info[:backtrace]
end.join("\n\n---\n\n\n")
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index 9b44c4483e..397f0a8b92 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -6,19 +6,19 @@ module ActionDispatch
cattr_accessor :rescue_responses
@@rescue_responses = Hash.new(:internal_server_error)
@@rescue_responses.merge!(
- "ActionController::RoutingError" => :not_found,
- "AbstractController::ActionNotFound" => :not_found,
- "ActionController::MethodNotAllowed" => :method_not_allowed,
- "ActionController::UnknownHttpMethod" => :method_not_allowed,
- "ActionController::NotImplemented" => :not_implemented,
- "ActionController::UnknownFormat" => :not_acceptable,
- "ActionController::InvalidAuthenticityToken" => :unprocessable_entity,
- "ActionController::InvalidCrossOriginRequest" => :unprocessable_entity,
- "ActionDispatch::ParamsParser::ParseError" => :bad_request,
- "ActionController::BadRequest" => :bad_request,
- "ActionController::ParameterMissing" => :bad_request,
- "Rack::QueryParser::ParameterTypeError" => :bad_request,
- "Rack::QueryParser::InvalidParameterError" => :bad_request
+ "ActionController::RoutingError" => :not_found,
+ "AbstractController::ActionNotFound" => :not_found,
+ "ActionController::MethodNotAllowed" => :method_not_allowed,
+ "ActionController::UnknownHttpMethod" => :method_not_allowed,
+ "ActionController::NotImplemented" => :not_implemented,
+ "ActionController::UnknownFormat" => :not_acceptable,
+ "ActionController::InvalidAuthenticityToken" => :unprocessable_entity,
+ "ActionController::InvalidCrossOriginRequest" => :unprocessable_entity,
+ "ActionDispatch::Http::Parameters::ParseError" => :bad_request,
+ "ActionController::BadRequest" => :bad_request,
+ "ActionController::ParameterMissing" => :bad_request,
+ "Rack::QueryParser::ParameterTypeError" => :bad_request,
+ "Rack::QueryParser::InvalidParameterError" => :bad_request
)
cattr_accessor :rescue_templates
@@ -127,7 +127,7 @@ module ActionDispatch
File.open(full_path, "r") do |file|
start = [line - 3, 0].max
lines = file.each_line.drop(start).take(6)
- Hash[*(start+1..(lines.count+start)).zip(lines).flatten]
+ Hash[*(start + 1..(lines.count + start)).zip(lines).flatten]
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index 6900934712..cbe2f4be4d 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -129,7 +129,7 @@ module ActionDispatch
end
# Builds a hash containing the flashes to keep for the next request.
- # If there are none to keep, returns nil.
+ # If there are none to keep, returns +nil+.
def to_session_value #:nodoc:
flashes_to_keep = @flashes.except(*@discard)
return nil if flashes_to_keep.empty?
@@ -281,7 +281,8 @@ module ActionDispatch
@now
end
- def stringify_array(array)
+ private
+ def stringify_array(array) # :doc:
array.map do |item|
item.kind_of?(Symbol) ? item.to_s : item
end
diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb
deleted file mode 100644
index 5f96b80e87..0000000000
--- a/actionpack/lib/action_dispatch/middleware/params_parser.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-require "action_dispatch/http/request"
-
-module ActionDispatch
- # ActionDispatch::ParamsParser works for all the requests having any Content-Length
- # (like POST). It takes raw data from the request and puts it through the parser
- # that is picked based on Content-Type header.
- #
- # In case of any error while parsing data ParamsParser::ParseError is raised.
- class ParamsParser
- # Raised when raw data from the request cannot be parsed by the parser
- # defined for request's content mime type.
- class ParseError < StandardError
- def initialize(message = nil, original_exception = nil)
- if message
- ActiveSupport::Deprecation.warn("Passing #message is deprecated and has no effect. " \
- "#{self.class} will automatically capture the message " \
- "of the original exception.", caller)
- end
-
- if original_exception
- ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \
- "Exceptions will automatically capture the original exception.", caller)
- end
-
- super($!.message)
- end
-
- def original_exception
- ActiveSupport::Deprecation.warn("#original_exception is deprecated. Use #cause instead.", caller)
- cause
- end
- end
-
- # Create a new +ParamsParser+ middleware instance.
- #
- # The +parsers+ argument can take Hash of parsers where key is identifying
- # content mime type, and value is a lambda that is going to process data.
- def self.new(app, parsers = {})
- ActiveSupport::Deprecation.warn("ActionDispatch::ParamsParser is deprecated and will be removed in Rails 5.1. Configure the parameter parsing in ActionDispatch::Request.parameter_parsers.")
- parsers = parsers.transform_keys { |key| key.respond_to?(:symbol) ? key.symbol : key }
- ActionDispatch::Request.parameter_parsers = ActionDispatch::Request::DEFAULT_PARSERS.merge(parsers)
- app
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
index e3555ce638..46f0f675b9 100644
--- a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
@@ -37,8 +37,8 @@ module ActionDispatch
end
def render_format(status, content_type, body)
- [status, {"Content-Type" => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
- "Content-Length" => body.bytesize.to_s}, [body]]
+ [status, { "Content-Type" => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
+ "Content-Length" => body.bytesize.to_s }, [body]]
end
def render_html(status)
diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
index 523eeb5b05..9f1ae80b97 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -153,9 +153,9 @@ module ActionDispatch
@ip ||= calculate_ip
end
- protected
+ private
- def ips_from(header)
+ def ips_from(header) # :doc:
return [] unless header
# Split the comma-separated list into an array of strings
ips = header.strip.split(/[,\s]+/)
@@ -171,7 +171,7 @@ module ActionDispatch
end
end
- def filter_proxies(ips)
+ def filter_proxies(ips) # :doc:
ips.reject do |ip|
@proxies.any? { |proxy| proxy === ip }
end
diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb
index bd4c781267..1925ffd9dd 100644
--- a/actionpack/lib/action_dispatch/middleware/request_id.rb
+++ b/actionpack/lib/action_dispatch/middleware/request_id.rb
@@ -2,8 +2,9 @@ require "securerandom"
require "active_support/core_ext/string/access"
module ActionDispatch
- # Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through
- # ActionDispatch::Request#uuid or the alias ActionDispatch::Request#request_id) and sends the same id to the client via the X-Request-Id header.
+ # Makes a unique request id available to the +action_dispatch.request_id+ env variable (which is then accessible
+ # through <tt>ActionDispatch::Request#request_id</tt> or the alias <tt>ActionDispatch::Request#uuid</tt>) and sends
+ # the same id to the client via the X-Request-Id header.
#
# The unique request id is either based on the X-Request-Id header in the request, which would typically be generated
# by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the
@@ -12,7 +13,7 @@ module ActionDispatch
# The unique request id can be used to trace a request end-to-end and would typically end up being part of log files
# from multiple pieces of the stack.
class RequestId
- X_REQUEST_ID = "X-Request-Id".freeze # :nodoc:
+ X_REQUEST_ID = "X-Request-Id".freeze #:nodoc:
def initialize(app)
@app = app
diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
index 60920ea6c8..97c937b0b1 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -7,22 +7,12 @@ require "action_dispatch/request/session"
module ActionDispatch
module Session
class SessionRestoreError < StandardError #:nodoc:
- def initialize(const_error = nil)
- if const_error
- ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \
- "Exceptions will automatically capture the original exception.", caller)
- end
-
+ def initialize
super("Session contains objects whose class definition isn't available.\n" +
"Remember to require the classes for all objects kept in the session.\n" +
"(Original exception: #{$!.message} [#{$!.class}])\n")
set_backtrace $!.backtrace
end
-
- def original_exception
- ActiveSupport::Deprecation.warn("#original_exception is deprecated. Use #cause instead.", caller)
- cause
- end
end
module Compatibility
@@ -37,17 +27,16 @@ module ActionDispatch
sid
end
- protected
+ private
- def initialize_sid
+ def initialize_sid # :doc:
@default_options.delete(:sidbits)
@default_options.delete(:secure_random)
end
- private
- def make_request(env)
- ActionDispatch::Request.new env
- end
+ def make_request(env)
+ ActionDispatch::Request.new env
+ end
end
module StaleSessionCheck
diff --git a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
index d99c2fd411..71274bc13a 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
@@ -19,7 +19,7 @@ module ActionDispatch
# Get a session from the cache.
def find_session(env, sid)
- unless sid and session = @cache.read(cache_key(sid))
+ unless sid && (session = @cache.read(cache_key(sid)))
sid, session = generate_sid, {}
end
[sid, session]
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index 8409109ede..57d325a9d8 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -63,7 +63,7 @@ module ActionDispatch
# Other useful options include <tt>:key</tt>, <tt>:secure</tt> and
# <tt>:httponly</tt>.
class CookieStore < AbstractStore
- def initialize(app, options={})
+ def initialize(app, options = {})
super(app, options.merge!(cookie_only: true))
end
@@ -102,7 +102,7 @@ module ActionDispatch
end
end
- def persistent_session_id!(data, sid=nil)
+ def persistent_session_id!(data, sid = nil)
data ||= {}
data["session_id"] ||= sid || generate_sid
data
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index 5aff130bc3..90f26a1c33 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -54,7 +54,7 @@ module ActionDispatch
end
def pass_response(status)
- [status, {"Content-Type" => "text/html; charset=#{Response.default_charset}", "Content-Length" => "0"}, []]
+ [status, { "Content-Type" => "text/html; charset=#{Response.default_charset}", "Content-Length" => "0" }, []]
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb
index 0b81d0ad43..557721c301 100644
--- a/actionpack/lib/action_dispatch/middleware/ssl.rb
+++ b/actionpack/lib/action_dispatch/middleware/ssl.rb
@@ -23,7 +23,7 @@ module ActionDispatch
# `180.days` (recommended).
# * `subdomains`: Set to `true` to tell the browser to apply these settings
# to all subdomains. This protects your cookies from interception by a
- # vulnerable site on a subdomain. Defaults to `false`.
+ # vulnerable site on a subdomain. Defaults to `true`.
# * `preload`: Advertise that this site may be included in browsers'
# preloaded HSTS lists. HSTS protects your site on every visit *except the
# first visit* since it hasn't seen your HSTS header yet. To close this
@@ -45,35 +45,17 @@ module ActionDispatch
HSTS_EXPIRES_IN = 15552000
def self.default_hsts_options
- { expires: HSTS_EXPIRES_IN, subdomains: false, preload: false }
+ { expires: HSTS_EXPIRES_IN, subdomains: true, preload: false }
end
- def initialize(app, redirect: {}, hsts: {}, secure_cookies: true, **options)
+ def initialize(app, redirect: {}, hsts: {}, secure_cookies: true)
@app = app
- if options[:host] || options[:port]
- ActiveSupport::Deprecation.warn <<-end_warning.strip_heredoc
- The `:host` and `:port` options are moving within `:redirect`:
- `config.ssl_options = { redirect: { host: …, port: … } }`.
- end_warning
- @redirect = options.slice(:host, :port)
- else
- @redirect = redirect
- end
+ @redirect = redirect
@exclude = @redirect && @redirect[:exclude] || proc { !@redirect }
@secure_cookies = secure_cookies
- if hsts != true && hsts != false && hsts[:subdomains].nil?
- hsts[:subdomains] = false
-
- ActiveSupport::Deprecation.warn <<-end_warning.strip_heredoc
- In Rails 5.1, The `:subdomains` option of HSTS config will be treated as true if
- unspecified. Set `config.ssl_options = { hsts: { subdomains: false } }` to opt out
- of this behavior.
- end_warning
- end
-
@hsts_header = build_hsts_header(normalize_hsts_options(hsts))
end
@@ -133,12 +115,20 @@ module ActionDispatch
end
def redirect_to_https(request)
- [ @redirect.fetch(:status, 301),
+ [ @redirect.fetch(:status, redirection_status(request)),
{ "Content-Type" => "text/html",
"Location" => https_location_for(request) },
@redirect.fetch(:body, []) ]
end
+ def redirection_status(request)
+ if request.get? || request.head?
+ 301 # Issue a permanent redirect via a GET request.
+ else
+ 307 # Issue a fresh request redirect to preserve the HTTP method.
+ end
+ end
+
def https_location_for(request)
host = @redirect[:host] || request.host
port = @redirect[:port] || request.port
diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb
index e49bf54115..6949b31e75 100644
--- a/actionpack/lib/action_dispatch/middleware/stack.rb
+++ b/actionpack/lib/action_dispatch/middleware/stack.rb
@@ -88,7 +88,6 @@ module ActionDispatch
end
def delete(target)
- target = get_class target
middlewares.delete_if { |m| m.klass == target }
end
@@ -103,31 +102,13 @@ module ActionDispatch
private
def assert_index(index, where)
- index = get_class index
i = index.is_a?(Integer) ? index : middlewares.index { |m| m.klass == index }
raise "No such middleware to insert #{where}: #{index.inspect}" unless i
i
end
- def get_class(klass)
- if klass.is_a?(String) || klass.is_a?(Symbol)
- classcache = ActiveSupport::Dependencies::Reference
- converted_klass = classcache[klass.to_s]
- ActiveSupport::Deprecation.warn <<-eowarn
-Passing strings or symbols to the middleware builder is deprecated, please change
-them to actual class references. For example:
-
- "#{klass}" => #{converted_klass}
-
- eowarn
- converted_klass
- else
- klass
- end
- end
-
def build_middleware(klass, args, block)
- Middleware.new(get_class(klass), args, block)
+ Middleware.new(klass, args, block)
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index fbf2a5fd0b..5c71f0fc48 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -106,14 +106,7 @@ module ActionDispatch
# produce a directory traversal using this middleware. Only 'GET' and 'HEAD'
# requests will result in a file being returned.
class Static
- def initialize(app, path, deprecated_cache_control = :not_set, index: "index", headers: {})
- if deprecated_cache_control != :not_set
- ActiveSupport::Deprecation.warn("The `cache_control` argument is deprecated," \
- "replaced by `headers: { 'Cache-Control' => #{deprecated_cache_control} }`, " \
- " and will be removed in Rails 5.1.")
- headers["Cache-Control".freeze] = deprecated_cache_control
- end
-
+ def initialize(app, path, index: "index", headers: {})
@app = app
@file_handler = FileHandler.new(path, index: index, headers: headers)
end
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index ea948076fa..48cc91bbfa 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -8,8 +8,8 @@ module ActionDispatch
config.action_dispatch.show_exceptions = true
config.action_dispatch.tld_length = 1
config.action_dispatch.ignore_accept_header = false
- config.action_dispatch.rescue_templates = { }
- config.action_dispatch.rescue_responses = { }
+ config.action_dispatch.rescue_templates = {}
+ config.action_dispatch.rescue_responses = {}
config.action_dispatch.default_charset = nil
config.action_dispatch.rack_cache = false
config.action_dispatch.http_auth_salt = "http authentication"
diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb
index b883ca0f61..a2a80f39fc 100644
--- a/actionpack/lib/action_dispatch/request/session.rb
+++ b/actionpack/lib/action_dispatch/request/session.rb
@@ -53,7 +53,7 @@ module ActionDispatch
}
end
- def []=(k,v); @delegate[k] = v; end
+ def []=(k, v); @delegate[k] = v; end
def to_hash; @delegate.dup; end
def values_at(*args); @delegate.values_at(*args); end
end
@@ -85,7 +85,7 @@ module ActionDispatch
end
# Returns value of the key stored in the session or
- # nil if the given key is not found in the session.
+ # +nil+ if the given key is not found in the session.
def [](key)
load_for_read!
@delegate[key.to_s]
@@ -124,7 +124,7 @@ module ActionDispatch
# Returns the session as Hash.
def to_hash
load_for_read!
- @delegate.dup.delete_if { |_,v| v.nil? }
+ @delegate.dup.delete_if { |_, v| v.nil? }
end
# Updates the session with given Hash.
@@ -162,7 +162,7 @@ module ActionDispatch
# :bar
# end
# # => :bar
- def fetch(key, default=Unspecified, &block)
+ def fetch(key, default = Unspecified, &block)
load_for_read!
if default == Unspecified
@delegate.fetch(key.to_s, &block)
diff --git a/actionpack/lib/action_dispatch/request/utils.rb b/actionpack/lib/action_dispatch/request/utils.rb
index 282bdbd2be..01bc871e5f 100644
--- a/actionpack/lib/action_dispatch/request/utils.rb
+++ b/actionpack/lib/action_dispatch/request/utils.rb
@@ -4,6 +4,17 @@ module ActionDispatch
mattr_accessor :perform_deep_munge
self.perform_deep_munge = true
+ def self.each_param_value(params, &block)
+ case params
+ when Array
+ params.each { |element| each_param_value(element, &block) }
+ when Hash
+ params.each_value { |value| each_param_value(value, &block) }
+ when String
+ block.call params
+ end
+ end
+
def self.normalize_encode_params(params)
if perform_deep_munge
NoNilParamEncoder.normalize_encode_params params
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 61ebd0b8db..c554ce98bc 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -1,3 +1,5 @@
+require "active_support/core_ext/string/filters"
+
module ActionDispatch
# The routing module provides URL rewriting in native Ruby. It's a way to
# redirect incoming requests to controllers and actions. This replaces
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index abbf6b06b6..089aa9f78e 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1,7 +1,6 @@
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"
@@ -41,7 +40,7 @@ module ActionDispatch
end
def serve(req)
- return [ 404, {"X-Cascade" => "pass"}, [] ] unless matches?(req)
+ return [ 404, { "X-Cascade" => "pass" }, [] ] unless matches?(req)
@strategy.call @app, req
end
@@ -270,7 +269,7 @@ module ActionDispatch
{ requirements: { format: Regexp.compile(formatted) },
defaults: { format: formatted } }
else
- { requirements: { }, defaults: { } }
+ { requirements: {}, defaults: {} }
end
end
@@ -568,7 +567,7 @@ module ActionDispatch
# [:format]
# Allows you to specify the default value for optional +format+
# segment or disable it by supplying +false+.
- def match(path, options=nil)
+ def match(path, options = nil)
end
# Mount a Rack-based application to be used within the application.
@@ -716,11 +715,7 @@ module ActionDispatch
def map_method(method, args, &block)
options = args.extract_options!
options[:via] = method
- if options.key?(:defaults)
- defaults(options.delete(:defaults)) { match(*args, options, &block) }
- else
- match(*args, options, &block)
- end
+ match(*args, options, &block)
self
end
end
@@ -1001,65 +996,65 @@ module ActionDispatch
end
private
- def merge_path_scope(parent, child) #:nodoc:
+ def merge_path_scope(parent, child)
Mapper.normalize_path("#{parent}/#{child}")
end
- def merge_shallow_path_scope(parent, child) #:nodoc:
+ def merge_shallow_path_scope(parent, child)
Mapper.normalize_path("#{parent}/#{child}")
end
- def merge_as_scope(parent, child) #:nodoc:
+ def merge_as_scope(parent, child)
parent ? "#{parent}_#{child}" : child
end
- def merge_shallow_prefix_scope(parent, child) #:nodoc:
+ def merge_shallow_prefix_scope(parent, child)
parent ? "#{parent}_#{child}" : child
end
- def merge_module_scope(parent, child) #:nodoc:
+ def merge_module_scope(parent, child)
parent ? "#{parent}/#{child}" : child
end
- def merge_controller_scope(parent, child) #:nodoc:
+ def merge_controller_scope(parent, child)
child
end
- def merge_action_scope(parent, child) #:nodoc:
+ def merge_action_scope(parent, child)
child
end
- def merge_via_scope(parent, child) #:nodoc:
+ def merge_via_scope(parent, child)
child
end
- def merge_format_scope(parent, child) #:nodoc:
+ def merge_format_scope(parent, child)
child
end
- def merge_path_names_scope(parent, child) #:nodoc:
+ def merge_path_names_scope(parent, child)
merge_options_scope(parent, child)
end
- def merge_constraints_scope(parent, child) #:nodoc:
+ def merge_constraints_scope(parent, child)
merge_options_scope(parent, child)
end
- def merge_defaults_scope(parent, child) #:nodoc:
+ def merge_defaults_scope(parent, child)
merge_options_scope(parent, child)
end
- def merge_blocks_scope(parent, child) #:nodoc:
+ def merge_blocks_scope(parent, child)
merged = parent ? parent.dup : []
merged << child if child
merged
end
- def merge_options_scope(parent, child) #:nodoc:
+ def merge_options_scope(parent, child)
(parent || {}).merge(child)
end
- def merge_shallow_scope(parent, child) #:nodoc:
+ def merge_shallow_scope(parent, child)
child ? true : false
end
@@ -1249,11 +1244,11 @@ module ActionDispatch
# the plural):
#
# GET /profile/new
- # POST /profile
# GET /profile
# GET /profile/edit
# PATCH/PUT /profile
# DELETE /profile
+ # POST /profile
#
# === Options
# Takes same options as +resources+.
@@ -1271,15 +1266,15 @@ module ActionDispatch
concerns(options[:concerns]) if options[:concerns]
- collection do
- post :create
- end if parent_resource.actions.include?(:create)
-
new do
get :new
end if parent_resource.actions.include?(:new)
set_member_mappings_for_resource
+
+ collection do
+ post :create
+ end if parent_resource.actions.include?(:create)
end
end
@@ -1557,16 +1552,12 @@ module ActionDispatch
# match 'path' => 'controller#action', via: patch
# match 'path', to: 'controller#action', via: :post
# match 'path', 'otherpath', on: :member, via: :get
- def match(path, *rest)
+ def match(path, *rest, &block)
if rest.empty? && Hash === path
options = path
path, to = options.find { |name, _value| name.is_a?(String) }
- if path.nil?
- ActiveSupport::Deprecation.warn "Omitting the route path is deprecated. "\
- "Specify the path with a String or a Symbol instead."
- path = ""
- end
+ raise ArgumentError, "Route path not specified" if path.nil?
case to
when Symbol
@@ -1588,114 +1579,13 @@ module ActionDispatch
paths = [path] + rest
end
- if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
- raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
- end
-
- if @scope[:to]
- options[:to] ||= @scope[:to]
- end
-
- if @scope[:controller] && @scope[:action]
- options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
- end
-
- controller = options.delete(:controller) || @scope[:controller]
- option_path = options.delete :path
- to = options.delete :to
- via = Mapping.check_via Array(options.delete(:via) {
- @scope[:via]
- })
- formatted = options.delete(:format) { @scope[:format] }
- anchor = options.delete(:anchor) { true }
- options_constraints = options.delete(:constraints) || {}
-
- path_types = paths.group_by(&:class)
- path_types.fetch(String, []).each do |_path|
- route_options = options.dup
- if _path && option_path
- ActiveSupport::Deprecation.warn <<-eowarn
-Specifying strings for both :path and the route path is deprecated. Change things like this:
-
- match #{_path.inspect}, :path => #{option_path.inspect}
-
-to this:
-
- match #{option_path.inspect}, :as => #{_path.inspect}, :action => #{path.inspect}
- eowarn
- route_options[:action] = _path
- route_options[:as] = _path
- _path = option_path
- end
- to = get_to_from_path(_path, to, route_options[:action])
- decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints)
- end
-
- path_types.fetch(Symbol, []).each do |action|
- route_options = options.dup
- decomposed_match(action, controller, route_options, option_path, to, via, formatted, anchor, options_constraints)
- end
-
- self
- end
-
- def get_to_from_path(path, to, action)
- return to if to || action
-
- path_without_format = path.sub(/\(\.:format\)$/, "")
- if using_match_shorthand?(path_without_format)
- path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1').tr("-", "_")
- else
- nil
- end
- end
-
- def using_match_shorthand?(path)
- path =~ %r{^/?[-\w]+/[-\w/]+$}
- end
-
- def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc:
- if on = options.delete(:on)
- send(on) { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
+ if options.key?(:defaults)
+ defaults(options.delete(:defaults)) { map_match(paths, options, &block) }
else
- case @scope.scope_level
- when :resources
- nested { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
- when :resource
- member { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
- else
- add_route(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
- end
+ map_match(paths, options, &block)
end
end
- def add_route(action, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc:
- path = path_for_action(action, _path)
- raise ArgumentError, "path is required" if path.blank?
-
- action = action.to_s
-
- default_action = options.delete(:action) || @scope[:action]
-
- if action =~ /^[\w\-\/]+$/
- default_action ||= action.tr("-", "_") unless action.include?("/")
- else
- action = nil
- end
-
- as = if !options.fetch(:as, true) # if it's set to nil or false
- options.delete(:as)
- else
- name_for_action(options.delete(:as), action)
- end
-
- path = Mapping.normalize_path URI.parser.escape(path), formatted
- ast = Journey::Parser.parse path
-
- mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
- @set.add_route(mapping, ast, as, anchor)
- end
-
# You can specify what Rails should route "/" to with the root method:
#
# root to: 'pages#main'
@@ -1712,7 +1602,7 @@ to this:
def root(path, options = {})
if path.is_a?(String)
options[:to] = path
- elsif path.is_a?(Hash) and options.empty?
+ elsif path.is_a?(Hash) && options.empty?
options = path
else
raise ArgumentError, "must be called with a path and/or options"
@@ -1729,13 +1619,13 @@ to this:
end
end
- protected
+ private
- def parent_resource #:nodoc:
+ def parent_resource
@scope[:scope_level_resource]
end
- def apply_common_behavior_for(method, resources, options, &block) #:nodoc:
+ def apply_common_behavior_for(method, resources, options, &block)
if resources.length > 1
resources.each { |r| send(method, r, options, &block) }
return true
@@ -1768,39 +1658,39 @@ to this:
false
end
- def apply_action_options(options) # :nodoc:
+ def apply_action_options(options)
return options if action_options? options
options.merge scope_action_options
end
- def action_options?(options) #:nodoc:
+ def action_options?(options)
options[:only] || options[:except]
end
- def scope_action_options #:nodoc:
+ def scope_action_options
@scope[:action_options] || {}
end
- def resource_scope? #:nodoc:
+ def resource_scope?
@scope.resource_scope?
end
- def resource_method_scope? #:nodoc:
+ def resource_method_scope?
@scope.resource_method_scope?
end
- def nested_scope? #:nodoc:
+ def nested_scope?
@scope.nested?
end
- def with_scope_level(kind)
+ def with_scope_level(kind) # :doc:
@scope = @scope.new_level(kind)
yield
ensure
@scope = @scope.parent
end
- def resource_scope(resource) #:nodoc:
+ def resource_scope(resource)
@scope = @scope.new(scope_level_resource: resource)
controller(resource.resource_scope) { yield }
@@ -1808,7 +1698,7 @@ to this:
@scope = @scope.parent
end
- def nested_options #:nodoc:
+ def nested_options
options = { as: parent_resource.member_name }
options[:constraints] = {
parent_resource.nested_param => param_constraint
@@ -1817,25 +1707,25 @@ to this:
options
end
- def shallow_nesting_depth #:nodoc:
+ def shallow_nesting_depth
@scope.find_all { |node|
node.frame[:scope_level_resource]
}.count { |node| node.frame[:scope_level_resource].shallow? }
end
- def param_constraint? #:nodoc:
+ def param_constraint?
@scope[:constraints] && @scope[:constraints][parent_resource.param].is_a?(Regexp)
end
- def param_constraint #:nodoc:
+ def param_constraint
@scope[:constraints][parent_resource.param]
end
- def canonical_action?(action) #:nodoc:
+ def canonical_action?(action)
resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
end
- def shallow_scope #:nodoc:
+ def shallow_scope
scope = { as: @scope[:shallow_prefix],
path: @scope[:shallow_path] }
@scope = @scope.new scope
@@ -1845,7 +1735,7 @@ to this:
@scope = @scope.parent
end
- def path_for_action(action, path) #:nodoc:
+ def path_for_action(action, path)
return "#{@scope[:path]}/#{path}" if path
if canonical_action?(action)
@@ -1855,11 +1745,11 @@ to this:
end
end
- def action_path(name) #:nodoc:
+ def action_path(name)
@scope[:path_names][name.to_sym] || name
end
- def prefix_name_for_action(as, action) #:nodoc:
+ def prefix_name_for_action(as, action)
if as
prefix = as
elsif !canonical_action?(action)
@@ -1871,7 +1761,7 @@ to this:
end
end
- def name_for_action(as, action) #:nodoc:
+ def name_for_action(as, action)
prefix = prefix_name_for_action(as, action)
name_prefix = @scope[:as]
@@ -1897,7 +1787,7 @@ to this:
end
end
- def set_member_mappings_for_resource
+ def set_member_mappings_for_resource # :doc:
member do
get :edit if parent_resource.actions.include?(:edit)
get :show if parent_resource.actions.include?(:show)
@@ -1909,10 +1799,9 @@ to this:
end
end
- def api_only?
+ def api_only? # :doc:
@set.api_only?
end
- private
def path_scope(path)
@scope = @scope.new(path: merge_path_scope(@scope[:path], path))
@@ -1921,17 +1810,110 @@ to this:
@scope = @scope.parent
end
- def match_root_route(options)
- name = has_named_route?(:root) ? nil : :root
- defaults_option = options.delete(:defaults)
- args = ["/", { as: name, via: :get }.merge!(options)]
+ def map_match(paths, options)
+ if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
+ raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
+ end
+
+ if @scope[:to]
+ options[:to] ||= @scope[:to]
+ end
+
+ if @scope[:controller] && @scope[:action]
+ options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
+ end
+
+ controller = options.delete(:controller) || @scope[:controller]
+ option_path = options.delete :path
+ to = options.delete :to
+ via = Mapping.check_via Array(options.delete(:via) {
+ @scope[:via]
+ })
+ formatted = options.delete(:format) { @scope[:format] }
+ anchor = options.delete(:anchor) { true }
+ options_constraints = options.delete(:constraints) || {}
+
+ path_types = paths.group_by(&:class)
+ 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."
+ end
+ to = get_to_from_path(_path, to, route_options[:action])
+ decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints)
+ end
+
+ path_types.fetch(Symbol, []).each do |action|
+ route_options = options.dup
+ decomposed_match(action, controller, route_options, option_path, to, via, formatted, anchor, options_constraints)
+ end
- if defaults_option
- defaults(defaults_option) { match(*args) }
+ self
+ end
+
+ def get_to_from_path(path, to, action)
+ return to if to || action
+
+ path_without_format = path.sub(/\(\.:format\)$/, "")
+ if using_match_shorthand?(path_without_format)
+ path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1').tr("-", "_")
else
- match(*args)
+ nil
end
end
+
+ def using_match_shorthand?(path)
+ path =~ %r{^/?[-\w]+/[-\w/]+$}
+ end
+
+ def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
+ if on = options.delete(:on)
+ send(on) { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
+ else
+ case @scope.scope_level
+ when :resources
+ nested { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
+ when :resource
+ member { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
+ else
+ add_route(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
+ end
+ end
+ end
+
+ def add_route(action, controller, options, _path, to, via, formatted, anchor, options_constraints)
+ path = path_for_action(action, _path)
+ raise ArgumentError, "path is required" if path.blank?
+
+ action = action.to_s
+
+ default_action = options.delete(:action) || @scope[:action]
+
+ if action =~ /^[\w\-\/]+$/
+ default_action ||= action.tr("-", "_") unless action.include?("/")
+ else
+ action = nil
+ end
+
+ as = if !options.fetch(:as, true) # if it's set to nil or false
+ options.delete(:as)
+ else
+ name_for_action(options.delete(:as), action)
+ end
+
+ path = Mapping.normalize_path URI.parser.escape(path), formatted
+ ast = Journey::Parser.parse path
+
+ mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
+ @set.add_route(mapping, ast, as, anchor)
+ end
+
+ def match_root_route(options)
+ name = has_named_route?(name_for_action(:root, nil)) ? nil : :root
+ args = ["/", { as: name, via: :get }.merge!(options)]
+
+ match(*args)
+ end
end
# Routing Concerns allow you to declare common routes that can be reused
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index 4f1aaeefc8..432b9bf4c1 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -160,7 +160,7 @@ module ActionDispatch
CACHE = { "path" => {}, "url" => {} }
def self.get(action, type)
- type = type.to_s
+ type = type.to_s
CACHE[type].fetch(action) { build action, type }
end
@@ -266,7 +266,7 @@ module ActionDispatch
args = []
- route = record_list.map { |parent|
+ route = record_list.map { |parent|
case parent
when Symbol, String
parent.to_s
@@ -304,7 +304,7 @@ module ActionDispatch
private
def get_method_for_class(klass)
- name = @key_strategy.call klass.model_name
+ name = @key_strategy.call klass.model_name
get_method_for_string name
end
diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb
index abb51ce357..4e2318a45e 100644
--- a/actionpack/lib/action_dispatch/routing/redirection.rb
+++ b/actionpack/lib/action_dispatch/routing/redirection.rb
@@ -61,15 +61,15 @@ module ActionDispatch
end
def escape(params)
- Hash[params.map{ |k,v| [k, Rack::Utils.escape(v)] }]
+ Hash[params.map { |k, v| [k, Rack::Utils.escape(v)] }]
end
def escape_fragment(params)
- Hash[params.map{ |k,v| [k, Journey::Router::Utils.escape_fragment(v)] }]
+ Hash[params.map { |k, v| [k, Journey::Router::Utils.escape_fragment(v)] }]
end
def escape_path(params)
- Hash[params.map{ |k,v| [k, Journey::Router::Utils.escape_path(v)] }]
+ Hash[params.map { |k, v| [k, Journey::Router::Utils.escape_path(v)] }]
end
end
@@ -128,7 +128,7 @@ module ActionDispatch
end
def inspect
- "redirect(#{status}, #{options.map{ |k,v| "#{k}: #{v}" }.join(', ')})"
+ "redirect(#{status}, #{options.map { |k, v| "#{k}: #{v}" }.join(', ')})"
end
end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index b9eda039a1..5853adb110 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -1,5 +1,4 @@
require "action_dispatch/journey"
-require "active_support/concern"
require "active_support/core_ext/object/to_query"
require "active_support/core_ext/hash/slice"
require "active_support/core_ext/module/remove_method"
@@ -34,7 +33,7 @@ module ActionDispatch
if @raise_on_name_error
raise
else
- return [404, {"X-Cascade" => "pass"}, []]
+ return [404, { "X-Cascade" => "pass" }, []]
end
end
@@ -71,7 +70,7 @@ module ActionDispatch
private :routes
def initialize
- @routes = {}
+ @routes = {}
@path_helpers = Set.new
@url_helpers = Set.new
@url_helpers_module = Module.new
@@ -208,9 +207,9 @@ module ActionDispatch
params = parameterize_args(args) { |missing_key|
missing_keys << missing_key
}
- constraints = Hash[@route.requirements.merge(params).sort_by{|k,v| k.to_s}]
+ constraints = Hash[@route.requirements.merge(params).sort_by { |k, v| k.to_s }]
message = "No route matches #{constraints.inspect}"
- message << " missing required keys: #{missing_keys.sort.inspect}"
+ message << ", missing required keys: #{missing_keys.sort.inspect}"
raise ActionController::UrlGenerationError, message
end
@@ -264,34 +263,35 @@ module ActionDispatch
end
private
- # Create a url helper allowing ordered parameters to be associated
- # with corresponding dynamic segments, so you can do:
- #
- # foo_url(bar, baz, bang)
- #
- # Instead of:
- #
- # foo_url(bar: bar, baz: baz, bang: bang)
- #
- # Also allow options hash, so you can do:
- #
- # foo_url(bar, baz, bang, sort_by: 'baz')
- #
+ # Create a url helper allowing ordered parameters to be associated
+ # with corresponding dynamic segments, so you can do:
+ #
+ # foo_url(bar, baz, bang)
+ #
+ # Instead of:
+ #
+ # foo_url(bar: bar, baz: baz, bang: bang)
+ #
+ # Also allow options hash, so you can do:
+ #
+ # foo_url(bar, baz, bang, sort_by: 'baz')
+ #
def define_url_helper(mod, route, name, opts, route_key, url_strategy)
helper = UrlHelper.create(route, opts, route_key, url_strategy)
mod.module_eval do
define_method(name) do |*args|
last = args.last
- options = case last
- when Hash
- args.pop
- when ActionController::Parameters
- if last.permitted?
- args.pop.to_h
- else
- raise ArgumentError, ActionDispatch::Routing::INSECURE_URL_PARAMETERS_MESSAGE
- end
- end
+ options = \
+ case last
+ when Hash
+ args.pop
+ when ActionController::Parameters
+ if last.permitted?
+ args.pop.to_h
+ else
+ raise ArgumentError, ActionDispatch::Routing::INSECURE_URL_PARAMETERS_MESSAGE
+ end
+ end
helper.call self, args, options
end
end
@@ -596,8 +596,8 @@ module ActionDispatch
# :controller, :action or :id is not found, don't pull any
# more keys from the recall.
def normalize_controller_action_id!
- use_recall_for(:controller) or return
- use_recall_for(:action) or return
+ use_recall_for(:controller) || return
+ use_recall_for(:action) || return
use_recall_for(:id)
end
@@ -646,11 +646,11 @@ module ActionDispatch
# Generate the path indicated by the arguments, and return an array of
# the keys that were not used to generate it.
- def extra_keys(options, recall={})
+ def extra_keys(options, recall = {})
generate_extras(options, recall).last
end
- def generate_extras(options, recall={})
+ def generate_extras(options, recall = {})
route_key = options.delete :use_route
path, params = generate(route_key, options, recall)
return path, params.keys
@@ -692,7 +692,7 @@ module ActionDispatch
password = options.delete :password
end
- recall = options.delete(:_recall) { {} }
+ recall = options.delete(:_recall) { {} }
original_script_name = options.delete(:original_script_name)
script_name = find_script_name options
diff --git a/actionpack/lib/action_dispatch/routing/routes_proxy.rb b/actionpack/lib/action_dispatch/routing/routes_proxy.rb
index 3592683136..ee847eaeed 100644
--- a/actionpack/lib/action_dispatch/routing/routes_proxy.rb
+++ b/actionpack/lib/action_dispatch/routing/routes_proxy.rb
@@ -19,7 +19,7 @@ module ActionDispatch
end
end
- def respond_to?(method, include_private = false)
+ def respond_to_missing?(method, include_private = false)
super || @helpers.respond_to?(method)
end
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index a1ac5a2b6c..3e564f13d8 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -198,14 +198,16 @@ module ActionDispatch
_routes.optimize_routes_generation? && default_url_options.empty?
end
- def _with_routes(routes)
+ private
+
+ def _with_routes(routes) # :doc:
old_routes, @_routes = @_routes, routes
yield
ensure
@_routes = old_routes
end
- def _routes_context
+ def _routes_context # :doc:
self
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index a00602ed70..817737341c 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -50,7 +50,7 @@ module ActionDispatch
#
# # Asserts that the redirection matches the regular expression
# assert_redirected_to %r(\Ahttp://example.org)
- def assert_redirected_to(options = {}, message=nil)
+ def assert_redirected_to(options = {}, message = nil)
assert_response(:redirect, message)
return true if options === @response.location
@@ -79,7 +79,12 @@ 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(location_if_redirected).concat(response_body_if_short)
+ end
+
+ def response_body_if_short
+ return "" if @response.body.size > 500
+ "\nResponse body: #{@response.body}"
end
def location_if_redirected
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index 2ea4a6c130..37c1ca02b6 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -37,7 +37,7 @@ module ActionDispatch
#
# # Test a custom route
# assert_recognizes({controller: 'items', action: 'show', id: '1'}, 'view/item1')
- def assert_recognizes(expected_options, path, extras={}, msg=nil)
+ def assert_recognizes(expected_options, path, extras = {}, msg = nil)
if path.is_a?(Hash) && path[:method].to_s == "all"
[:get, :post, :put, :delete].each do |method|
assert_recognizes(expected_options, path.merge(method: method), extras, msg)
@@ -75,7 +75,7 @@ module ActionDispatch
#
# # Asserts that the generated route gives us our custom route
# assert_generates "changesets/12", { controller: 'scm', action: 'show_diff', revision: "12" }
- def assert_generates(expected_path, options, defaults={}, extras={}, message=nil)
+ def assert_generates(expected_path, options, defaults = {}, extras = {}, message = nil)
if expected_path =~ %r{://}
fail_on(URI::InvalidURIError, message) do
uri = URI.parse(expected_path)
@@ -119,7 +119,7 @@ module ActionDispatch
#
# # Tests a route with an HTTP method
# assert_routing({ method: 'put', path: '/product/321' }, { controller: "product", action: "update", id: "321" })
- def assert_routing(path, options, defaults={}, extras={}, message=nil)
+ def assert_routing(path, options, defaults = {}, extras = {}, message = nil)
assert_recognizes(options, path, extras, message)
controller, default_controller = options[:controller], defaults[:controller]
@@ -127,7 +127,7 @@ module ActionDispatch
options[:controller] = "/#{controller}"
end
- generate_options = options.dup.delete_if{ |k, _| defaults.key?(k) }
+ generate_options = options.dup.delete_if { |k, _| defaults.key?(k) }
assert_generates(path.is_a?(Hash) ? path[:path] : path, generate_options, defaults, extras, message)
end
@@ -152,8 +152,11 @@ module ActionDispatch
_routes = @routes
@controller.singleton_class.include(_routes.url_helpers)
- @controller.view_context_class = Class.new(@controller.view_context_class) do
- include _routes.url_helpers
+
+ if @controller.respond_to? :view_context_class
+ @controller.view_context_class = Class.new(@controller.view_context_class) do
+ include _routes.url_helpers
+ end
end
end
yield @routes
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 13f7fc6fa6..021ffec862 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -11,99 +11,42 @@ require "action_dispatch/testing/request_encoder"
module ActionDispatch
module Integration #:nodoc:
module RequestHelpers
- # Performs a GET request with the given parameters.
- #
- # - +path+: The URI (as a String) on which you want to perform a GET
- # request.
- # - +params+: The HTTP parameters that you want to pass. This may
- # be +nil+,
- # a Hash, or a String that is appropriately encoded
- # (<tt>application/x-www-form-urlencoded</tt> or
- # <tt>multipart/form-data</tt>).
- # - +headers+: Additional headers to pass, as a Hash. The headers will be
- # merged into the Rack env hash.
- # - +env+: Additional env to pass, as a Hash. The headers will be
- # merged into the Rack env hash.
- #
- # This method returns a Response object, which one can use to
- # inspect the details of the response. Furthermore, if this method was
- # called from an ActionDispatch::IntegrationTest object, then that
- # object's <tt>@response</tt> instance variable will point to the same
- # response object.
- #
- # You can also perform POST, PATCH, PUT, DELETE, and HEAD requests with
- # +#post+, +#patch+, +#put+, +#delete+, and +#head+.
- #
- # Example:
- #
- # get '/feed', params: { since: 201501011400 }
- # post '/profile', headers: { "X-Test-Header" => "testvalue" }
- def get(path, *args)
- process_with_kwargs(:get, path, *args)
+ # Performs a GET request with the given parameters. See +#process+ for more
+ # details.
+ def get(path, **args)
+ process(:get, path, **args)
end
- # Performs a POST request with the given parameters. See +#get+ for more
+ # Performs a POST request with the given parameters. See +#process+ for more
# details.
- def post(path, *args)
- process_with_kwargs(:post, path, *args)
+ def post(path, **args)
+ process(:post, path, **args)
end
- # Performs a PATCH request with the given parameters. See +#get+ for more
+ # Performs a PATCH request with the given parameters. See +#process+ for more
# details.
- def patch(path, *args)
- process_with_kwargs(:patch, path, *args)
+ def patch(path, **args)
+ process(:patch, path, **args)
end
- # Performs a PUT request with the given parameters. See +#get+ for more
+ # Performs a PUT request with the given parameters. See +#process+ for more
# details.
- def put(path, *args)
- process_with_kwargs(:put, path, *args)
+ def put(path, **args)
+ process(:put, path, **args)
end
- # Performs a DELETE request with the given parameters. See +#get+ for
+ # Performs a DELETE request with the given parameters. See +#process+ for
# more details.
- def delete(path, *args)
- process_with_kwargs(:delete, path, *args)
+ def delete(path, **args)
+ process(:delete, path, **args)
end
- # Performs a HEAD request with the given parameters. See +#get+ for more
+ # Performs a HEAD request with the given parameters. See +#process+ for more
# details.
def head(path, *args)
- process_with_kwargs(:head, path, *args)
+ process(:head, path, *args)
end
- # Performs an XMLHttpRequest request with the given parameters, mirroring
- # an AJAX request made from JavaScript.
- #
- # The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or
- # +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart
- # string; the headers are a hash.
- #
- # Example:
- #
- # xhr :get, '/feed', params: { since: 201501011400 }
- def xml_http_request(request_method, path, *args)
- if kwarg_request?(args)
- params, headers, env = args.first.values_at(:params, :headers, :env)
- else
- params = args[0]
- headers = args[1]
- env = {}
-
- if params.present? || headers.present?
- non_kwarg_request_warning
- end
- end
-
- ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc)
- xhr and xml_http_request methods are deprecated in favor of
- `get "/posts", xhr: true` and `post "/posts/1", xhr: true`.
- MSG
-
- process(request_method, path, params: params, headers: headers, xhr: true)
- end
- alias xhr :xml_http_request
-
# Follow a single redirect response. If the last response was not a
# redirect, an exception will be raised. Otherwise, the redirect is
# performed on the location header.
@@ -112,59 +55,6 @@ module ActionDispatch
get(response.location)
status
end
-
- # Performs a request using the specified method, following any subsequent
- # redirect. Note that the redirects are followed until the response is
- # not a redirect--this means you may run into an infinite loop if your
- # redirect loops back to itself.
- #
- # Example:
- #
- # request_via_redirect :post, '/welcome',
- # params: { ref_id: 14 },
- # headers: { "X-Test-Header" => "testvalue" }
- def request_via_redirect(http_method, path, *args)
- ActiveSupport::Deprecation.warn("`request_via_redirect` is deprecated and will be removed in Rails 5.1. Please use `follow_redirect!` manually after the request call for the same behavior.")
- process_with_kwargs(http_method, path, *args)
-
- follow_redirect! while redirect?
- status
- end
-
- # Performs a GET request, following any subsequent redirect.
- # See +request_via_redirect+ for more information.
- def get_via_redirect(path, *args)
- ActiveSupport::Deprecation.warn("`get_via_redirect` is deprecated and will be removed in Rails 5.1. Please use `follow_redirect!` manually after the request call for the same behavior.")
- request_via_redirect(:get, path, *args)
- end
-
- # Performs a POST request, following any subsequent redirect.
- # See +request_via_redirect+ for more information.
- def post_via_redirect(path, *args)
- ActiveSupport::Deprecation.warn("`post_via_redirect` is deprecated and will be removed in Rails 5.1. Please use `follow_redirect!` manually after the request call for the same behavior.")
- request_via_redirect(:post, path, *args)
- end
-
- # Performs a PATCH request, following any subsequent redirect.
- # See +request_via_redirect+ for more information.
- def patch_via_redirect(path, *args)
- ActiveSupport::Deprecation.warn("`patch_via_redirect` is deprecated and will be removed in Rails 5.1. Please use `follow_redirect!` manually after the request call for the same behavior.")
- request_via_redirect(:patch, path, *args)
- end
-
- # Performs a PUT request, following any subsequent redirect.
- # See +request_via_redirect+ for more information.
- def put_via_redirect(path, *args)
- ActiveSupport::Deprecation.warn("`put_via_redirect` is deprecated and will be removed in Rails 5.1. Please use `follow_redirect!` manually after the request call for the same behavior.")
- request_via_redirect(:put, path, *args)
- end
-
- # Performs a DELETE request, following any subsequent redirect.
- # See +request_via_redirect+ for more information.
- def delete_via_redirect(path, *args)
- ActiveSupport::Deprecation.warn("`delete_via_redirect` is deprecated and will be removed in Rails 5.1. Please use `follow_redirect!` manually after the request call for the same behavior.")
- request_via_redirect(:delete, path, *args)
- end
end
# An instance of this class represents a set of requests and responses
@@ -283,132 +173,126 @@ module ActionDispatch
@https
end
- # Set the host name to use in the next request.
+ # Performs the actual request.
#
- # session.host! "www.example.com"
- alias :host! :host=
-
- private
- def _mock_session
- @_mock_session ||= Rack::MockSession.new(@app, host)
- end
-
- def process_with_kwargs(http_method, path, *args)
- if kwarg_request?(args)
- process(http_method, path, *args)
- else
- non_kwarg_request_warning if args.any?
- process(http_method, path, params: args[0], headers: args[1])
- end
- end
-
- REQUEST_KWARGS = %i(params headers env xhr as)
- def kwarg_request?(args)
- args[0].respond_to?(:keys) && args[0].keys.any? { |k| REQUEST_KWARGS.include?(k) }
- end
-
- def non_kwarg_request_warning
- ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc)
- ActionDispatch::IntegrationTest HTTP request methods will accept only
- the following keyword arguments in future Rails versions:
- #{REQUEST_KWARGS.join(', ')}
-
- Examples:
-
- get '/profile',
- params: { id: 1 },
- headers: { 'X-Extra-Header' => '123' },
- env: { 'action_dispatch.custom' => 'custom' },
- xhr: true,
- as: :json
- MSG
+ # - +method+: The HTTP method (GET, POST, PATCH, PUT, DELETE, HEAD, OPTIONS)
+ # as a symbol.
+ # - +path+: The URI (as a String) on which you want to perform the
+ # request.
+ # - +params+: The HTTP parameters that you want to pass. This may
+ # be +nil+,
+ # a Hash, or a String that is appropriately encoded
+ # (<tt>application/x-www-form-urlencoded</tt> or
+ # <tt>multipart/form-data</tt>).
+ # - +headers+: Additional headers to pass, as a Hash. The headers will be
+ # merged into the Rack env hash.
+ # - +env+: Additional env to pass, as a Hash. The headers will be
+ # merged into the Rack env hash.
+ #
+ # This method is rarely used directly. Use +#get+, +#post+, or other standard
+ # HTTP methods in integration tests. +#process+ is only required when using a
+ # request method that doesn't have a method defined in the integration tests.
+ #
+ # This method returns a Response object, which one can use to
+ # inspect the details of the response. Furthermore, if this method was
+ # called from an ActionDispatch::IntegrationTest object, then that
+ # object's <tt>@response</tt> instance variable will point to the same
+ # response object.
+ #
+ # Example:
+ # process :get, '/author', params: { since: 201501011400 }
+ def process(method, path, params: nil, headers: nil, env: nil, xhr: false, as: nil)
+ request_encoder = RequestEncoder.encoder(as)
+ headers ||= {}
+
+ if method == :get && as == :json && params
+ headers["X-Http-Method-Override"] = "GET"
+ method = :post
end
- # Performs the actual request.
- def process(method, path, params: nil, headers: nil, env: nil, xhr: false, as: nil)
- request_encoder = RequestEncoder.encoder(as)
- headers ||= {}
+ if path =~ %r{://}
+ path = build_expanded_path(path) do |location|
+ https! URI::HTTPS === location if location.scheme
- if method == :get && as == :json && params
- headers["X-Http-Method-Override"] = "GET"
- method = :post
+ if url_host = location.host
+ default = Rack::Request::DEFAULT_PORTS[location.scheme]
+ url_host += ":#{location.port}" if default != location.port
+ host! url_host
+ end
end
+ end
- if path =~ %r{://}
- path = build_expanded_path(path, request_encoder) do |location|
- https! URI::HTTPS === location if location.scheme
+ hostname, port = host.split(":")
- if url_host = location.host
- default = Rack::Request::DEFAULT_PORTS[location.scheme]
- url_host += ":#{location.port}" if default != location.port
- host! url_host
- end
- end
- elsif as
- path = build_expanded_path(path, request_encoder)
- end
+ request_env = {
+ :method => method,
+ :params => request_encoder.encode_params(params),
- hostname, port = host.split(":")
+ "SERVER_NAME" => hostname,
+ "SERVER_PORT" => port || (https? ? "443" : "80"),
+ "HTTPS" => https? ? "on" : "off",
+ "rack.url_scheme" => https? ? "https" : "http",
- request_env = {
- :method => method,
- :params => request_encoder.encode_params(params),
+ "REQUEST_URI" => path,
+ "HTTP_HOST" => host,
+ "REMOTE_ADDR" => remote_addr,
+ "CONTENT_TYPE" => request_encoder.content_type,
+ "HTTP_ACCEPT" => request_encoder.accept_header || accept
+ }
- "SERVER_NAME" => hostname,
- "SERVER_PORT" => port || (https? ? "443" : "80"),
- "HTTPS" => https? ? "on" : "off",
- "rack.url_scheme" => https? ? "https" : "http",
+ wrapped_headers = Http::Headers.from_hash({})
+ wrapped_headers.merge!(headers) if headers
- "REQUEST_URI" => path,
- "HTTP_HOST" => host,
- "REMOTE_ADDR" => remote_addr,
- "CONTENT_TYPE" => request_encoder.content_type,
- "HTTP_ACCEPT" => accept
- }
+ if xhr
+ wrapped_headers["HTTP_X_REQUESTED_WITH"] = "XMLHttpRequest"
+ wrapped_headers["HTTP_ACCEPT"] ||= [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ")
+ end
- wrapped_headers = Http::Headers.from_hash({})
- wrapped_headers.merge!(headers) if headers
+ # this modifies the passed request_env directly
+ if wrapped_headers.present?
+ Http::Headers.from_hash(request_env).merge!(wrapped_headers)
+ end
+ if env.present?
+ Http::Headers.from_hash(request_env).merge!(env)
+ end
- if xhr
- wrapped_headers["HTTP_X_REQUESTED_WITH"] = "XMLHttpRequest"
- wrapped_headers["HTTP_ACCEPT"] ||= [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ")
- end
+ session = Rack::Test::Session.new(_mock_session)
- # this modifies the passed request_env directly
- if wrapped_headers.present?
- Http::Headers.from_hash(request_env).merge!(wrapped_headers)
- end
- if env.present?
- Http::Headers.from_hash(request_env).merge!(env)
- end
+ # NOTE: rack-test v0.5 doesn't build a default uri correctly
+ # Make sure requested path is always a full uri
+ session.request(build_full_uri(path, request_env), request_env)
- session = Rack::Test::Session.new(_mock_session)
+ @request_count += 1
+ @request = ActionDispatch::Request.new(session.last_request.env)
+ response = _mock_session.last_response
+ @response = ActionDispatch::TestResponse.from_response(response)
+ @response.request = @request
+ @html_document = nil
+ @url_options = nil
- # NOTE: rack-test v0.5 doesn't build a default uri correctly
- # Make sure requested path is always a full uri
- session.request(build_full_uri(path, request_env), request_env)
+ @controller = @request.controller_instance
- @request_count += 1
- @request = ActionDispatch::Request.new(session.last_request.env)
- response = _mock_session.last_response
- @response = ActionDispatch::TestResponse.from_response(response)
- @response.request = @request
- @html_document = nil
- @url_options = nil
+ response.status
+ end
- @controller = @request.controller_instance
+ # Set the host name to use in the next request.
+ #
+ # session.host! "www.example.com"
+ alias :host! :host=
- response.status
+ private
+ def _mock_session
+ @_mock_session ||= Rack::MockSession.new(@app, host)
end
def build_full_uri(path, env)
"#{env['rack.url_scheme']}://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{path}"
end
- def build_expanded_path(path, request_encoder)
+ def build_expanded_path(path)
location = URI.parse(path)
yield location if block_given?
- path = request_encoder.append_format_to location.path
+ path = location.path
location.query ? "#{path}?#{location.query}" : path
end
end
@@ -420,9 +304,13 @@ module ActionDispatch
attr_reader :app
+ def initialize(*args, &blk)
+ super(*args, &blk)
+ @integration_session = nil
+ end
+
def before_setup # :nodoc:
@app = nil
- @integration_session = nil
super
end
@@ -478,6 +366,7 @@ module ActionDispatch
# simultaneously.
def open_session
dup.tap do |session|
+ session.reset!
yield session if block_given?
end
end
@@ -498,7 +387,7 @@ module ActionDispatch
integration_session.default_url_options = options
end
- def respond_to?(method, include_private = false)
+ def respond_to_missing?(method, include_private = false)
integration_session.respond_to?(method, include_private) || super
end
@@ -688,13 +577,15 @@ module ActionDispatch
# end
# end
#
- # The +as+ option sets the format to JSON, sets the content type to
+ # The +as+ option passes an "application/json" Accept header (thereby setting
+ # the request format to JSON unless overridden), sets the content type to
# "application/json" and encodes the parameters as JSON.
#
# Calling +parsed_body+ on the response parses the response body based on the
# last response MIME type.
#
- # For any custom MIME types you've registered, you can even add your own encoders with:
+ # Out of the box, only <tt>:json</tt> is supported. But for any custom MIME
+ # types you've registered, you can add your own encoders with:
#
# ActionDispatch::IntegrationTest.register_encoder :wibble,
# param_encoder: -> params { params.to_wibble },
@@ -707,6 +598,8 @@ module ActionDispatch
# Consult the Rails Testing Guide for more.
class IntegrationTest < ActiveSupport::TestCase
+ include TestProcess::FixtureFile
+
module UrlOptions
extend ActiveSupport::Concern
def url_options
diff --git a/actionpack/lib/action_dispatch/testing/request_encoder.rb b/actionpack/lib/action_dispatch/testing/request_encoder.rb
index b0b994b2d0..8c27e9ecb7 100644
--- a/actionpack/lib/action_dispatch/testing/request_encoder.rb
+++ b/actionpack/lib/action_dispatch/testing/request_encoder.rb
@@ -1,10 +1,17 @@
module ActionDispatch
class RequestEncoder # :nodoc:
- @encoders = {}
+ class IdentityEncoder
+ def content_type; end
+ def accept_header; end
+ def encode_params(params); params; end
+ def response_parser; -> body { body }; end
+ end
+
+ @encoders = { identity: IdentityEncoder.new }
attr_reader :response_parser
- def initialize(mime_name, param_encoder, response_parser, url_encoded_form = false)
+ def initialize(mime_name, param_encoder, response_parser)
@mime = Mime[mime_name]
unless @mime
@@ -12,21 +19,15 @@ module ActionDispatch
"unregistered MIME Type: #{mime_name}. See `Mime::Type.register`."
end
- @url_encoded_form = url_encoded_form
- @path_format = ".#{@mime.symbol}" unless @url_encoded_form
- @response_parser = response_parser || -> body { body }
- @param_encoder = param_encoder || :"to_#{@mime.symbol}".to_proc
+ @response_parser = response_parser || -> body { body }
+ @param_encoder = param_encoder || :"to_#{@mime.symbol}".to_proc
end
- def append_format_to(path)
- if @url_encoded_form
- path
- else
- path + @path_format
- end
+ def content_type
+ @mime.to_s
end
- def content_type
+ def accept_header
@mime.to_s
end
@@ -40,7 +41,7 @@ module ActionDispatch
end
def self.encoder(name)
- @encoders[name] || WWWFormEncoder
+ @encoders[name] || @encoders[:identity]
end
def self.register_encoder(mime_name, param_encoder: nil, response_parser: nil)
@@ -48,7 +49,5 @@ module ActionDispatch
end
register_encoder :json, response_parser: -> body { JSON.parse(body) }
-
- WWWFormEncoder = new(:url_encoded_form, -> params { params }, nil, true)
end
end
diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb
index 1456a0afcf..0282eb15c3 100644
--- a/actionpack/lib/action_dispatch/testing/test_process.rb
+++ b/actionpack/lib/action_dispatch/testing/test_process.rb
@@ -3,6 +3,26 @@ require "action_dispatch/middleware/flash"
module ActionDispatch
module TestProcess
+ module FixtureFile
+ # Shortcut for <tt>Rack::Test::UploadedFile.new(File.join(ActionDispatch::IntegrationTest.fixture_path, path), type)</tt>:
+ #
+ # post :change_avatar, avatar: fixture_file_upload('files/spongebob.png', 'image/png')
+ #
+ # To upload binary files on Windows, pass <tt>:binary</tt> as the last parameter.
+ # This will not affect other platforms:
+ #
+ # post :change_avatar, avatar: fixture_file_upload('files/spongebob.png', 'image/png', :binary)
+ def fixture_file_upload(path, mime_type = nil, binary = false)
+ if self.class.respond_to?(:fixture_path) && self.class.fixture_path &&
+ !File.exist?(path)
+ path = File.join(self.class.fixture_path, path)
+ end
+ Rack::Test::UploadedFile.new(path, mime_type, binary)
+ end
+ end
+
+ include FixtureFile
+
def assigns(key = nil)
raise NoMethodError,
"assigns has been extracted to a gem. To continue using it,
@@ -24,20 +44,5 @@ module ActionDispatch
def redirect_to_url
@response.redirect_url
end
-
- # Shortcut for <tt>Rack::Test::UploadedFile.new(File.join(ActionDispatch::IntegrationTest.fixture_path, path), type)</tt>:
- #
- # post :change_avatar, avatar: fixture_file_upload('files/spongebob.png', 'image/png')
- #
- # To upload binary files on Windows, pass <tt>:binary</tt> as the last parameter.
- # This will not affect other platforms:
- #
- # post :change_avatar, avatar: fixture_file_upload('files/spongebob.png', 'image/png', :binary)
- def fixture_file_upload(path, mime_type = nil, binary = false)
- if self.class.respond_to?(:fixture_path) && self.class.fixture_path
- path = File.join(self.class.fixture_path, path)
- end
- Rack::Test::UploadedFile.new(path, mime_type, binary)
- end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb
index d0beb72a41..91b25ec155 100644
--- a/actionpack/lib/action_dispatch/testing/test_request.rb
+++ b/actionpack/lib/action_dispatch/testing/test_request.rb
@@ -22,7 +22,7 @@ module ActionDispatch
private_class_method :default_env
def request_method=(method)
- set_header("REQUEST_METHOD", method.to_s.upcase)
+ super(method.to_s.upcase)
end
def host=(host)
diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb
index ee6dabd133..eec622e085 100644
--- a/actionpack/lib/action_pack.rb
+++ b/actionpack/lib/action_pack.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2016 David Heinemeier Hansson
+# Copyright (c) 2004-2017 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionpack/test/abstract/callbacks_test.rb b/actionpack/test/abstract/callbacks_test.rb
index a0f2efa330..9c2261bf76 100644
--- a/actionpack/test/abstract/callbacks_test.rb
+++ b/actionpack/test/abstract/callbacks_test.rb
@@ -264,53 +264,5 @@ module AbstractController
assert_equal "Hello world Howdy!", controller.response_body
end
end
-
- class AliasedCallbacks < ControllerWithCallbacks
- ActiveSupport::Deprecation.silence do
- before_filter :first
- after_filter :second
- around_filter :aroundz
- end
-
- def first
- @text = "Hello world"
- end
-
- def second
- @second = "Goodbye"
- end
-
- def aroundz
- @aroundz = "FIRST"
- yield
- @aroundz << "SECOND"
- end
-
- def index
- @text ||= nil
- self.response_body = @text.to_s
- end
- end
-
- class TestAliasedCallbacks < ActiveSupport::TestCase
- def setup
- @controller = AliasedCallbacks.new
- end
-
- test "before_filter works" do
- @controller.process(:index)
- assert_equal "Hello world", @controller.response_body
- end
-
- test "after_filter works" do
- @controller.process(:index)
- assert_equal "Goodbye", @controller.instance_variable_get("@second")
- end
-
- test "around_filter works" do
- @controller.process(:index)
- assert_equal "FIRSTSECOND", @controller.instance_variable_get("@aroundz")
- end
- end
end
end
diff --git a/actionpack/test/abstract/collector_test.rb b/actionpack/test/abstract/collector_test.rb
index 7fe19e6b10..1cd3526483 100644
--- a/actionpack/test/abstract/collector_test.rb
+++ b/actionpack/test/abstract/collector_test.rb
@@ -55,7 +55,7 @@ module AbstractController
collector.js(:bar) { :baz }
assert_equal [Mime[:html], [], nil], collector.responses[0]
assert_equal [Mime[:text], [:foo], nil], collector.responses[1]
- assert_equal [Mime[:js], [:bar]], collector.responses[2][0,2]
+ assert_equal [Mime[:js], [:bar]], collector.responses[2][0, 2]
assert_equal :baz, collector.responses[2][2].call
end
end
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 7bff21b5a2..11a9092527 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -123,7 +123,7 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
# simply return the controller#action as Rack::Body.
class NullController < ::ActionController::Metal
def self.dispatch(action, req, res)
- [200, {"Content-Type" => "text/html"}, ["#{req.params[:controller]}##{action}"]]
+ [200, { "Content-Type" => "text/html" }, ["#{req.params[:controller]}##{action}"]]
end
end
@@ -164,7 +164,7 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
ActiveSupport::Dependencies.autoload_paths << path
yield
ensure
- ActiveSupport::Dependencies.autoload_paths.reject! {|p| p == path}
+ ActiveSupport::Dependencies.autoload_paths.reject! { |p| p == path }
ActiveSupport::Dependencies.clear
end
end
@@ -246,7 +246,7 @@ module ActionDispatch
class DebugExceptions
private
remove_method :stderr_logger
- # Silence logger
+ # Silence logger
def stderr_logger
nil
end
@@ -259,9 +259,9 @@ module ActionDispatch
host = uri_or_host.host unless path
path ||= uri_or_host.path
- params = {"PATH_INFO" => path,
+ params = { "PATH_INFO" => path,
"REQUEST_METHOD" => method,
- "HTTP_HOST" => host}
+ "HTTP_HOST" => host }
routes.call(params)
end
@@ -351,7 +351,7 @@ class ResourcesController < ActionController::Base
end
class CommentsController < ResourcesController; end
-class AccountsController < ResourcesController; end
+class AccountsController < ResourcesController; end
class ImagesController < ResourcesController; end
# Skips the current run on Rubinius using Minitest::Assertions#skip
diff --git a/actionpack/test/assertions/response_assertions_test.rb b/actionpack/test/assertions/response_assertions_test.rb
index 17517f8965..14a04ccdb1 100644
--- a/actionpack/test/assertions/response_assertions_test.rb
+++ b/actionpack/test/assertions/response_assertions_test.rb
@@ -6,10 +6,11 @@ module ActionDispatch
class ResponseAssertionsTest < ActiveSupport::TestCase
include ResponseAssertions
- FakeResponse = Struct.new(:response_code, :location) do
+ FakeResponse = Struct.new(:response_code, :location, :body) do
def initialize(*)
super
self.location ||= "http://test.example.com/posts"
+ self.body ||= ""
end
[:successful, :not_found, :redirection, :server_error].each do |sym|
@@ -112,6 +113,27 @@ module ActionDispatch
" redirect to <http://test.host/posts/redirect/2>"
assert_match expected, error.message
end
+
+ def test_error_message_shows_short_response_body
+ @response = ActionDispatch::Response.new
+ @response.status = 400
+ @response.body = "not too long"
+ error = assert_raises(Minitest::Assertion) { assert_response 200 }
+ expected = "Expected response to be a <200: OK>,"\
+ " but was a <400: Bad Request>" \
+ "\nResponse body: not too long"
+ assert_match expected, error.message
+ end
+
+ def test_error_message_does_not_show_long_response_body
+ @response = ActionDispatch::Response.new
+ @response.status = 400
+ @response.body = "not too long" * 50
+ error = assert_raises(Minitest::Assertion) { assert_response 200 }
+ expected = "Expected response to be a <200: OK>,"\
+ " but was a <400: Bad Request>"
+ assert_match expected, error.message
+ end
end
end
end
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index 55b4c1e6a6..9ab152fc5c 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -128,6 +128,16 @@ module Admin
end
end
+class ApiOnlyController < ActionController::API
+ def nothing
+ head :ok
+ end
+
+ def redirect_to_new_route
+ redirect_to new_route_url
+ end
+end
+
class ActionPackAssertionsControllerTest < ActionController::TestCase
def test_render_file_absolute_path
get :render_file_absolute_path
@@ -165,11 +175,25 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
def test_string_constraint
with_routing do |set|
set.draw do
- get "photos", to: "action_pack_assertions#nothing", constraints: {subdomain: "admin"}
+ get "photos", to: "action_pack_assertions#nothing", constraints: { subdomain: "admin" }
end
end
end
+ def test_with_routing_works_with_api_only_controllers
+ @controller = ApiOnlyController.new
+
+ with_routing do |set|
+ set.draw do
+ get "new_route", to: "api_only#nothing"
+ get "redirect_to_new_route", to: "api_only#redirect_to_new_route"
+ end
+
+ process :redirect_to_new_route
+ assert_redirected_to "http://test.host/new_route"
+ end
+ end
+
def test_assert_redirect_to_named_route_failure
with_routing do |set|
set.draw do
diff --git a/actionpack/test/controller/api/params_wrapper_test.rb b/actionpack/test/controller/api/params_wrapper_test.rb
index cdf5acecae..a1da852040 100644
--- a/actionpack/test/controller/api/params_wrapper_test.rb
+++ b/actionpack/test/controller/api/params_wrapper_test.rb
@@ -20,7 +20,7 @@ class ParamsWrapperForApiTest < ActionController::TestCase
@request.env["CONTENT_TYPE"] = "application/json"
post :test, params: { "username" => "sikachu" }
- expected = { "username" => "sikachu", "person" => { "username" => "sikachu" }}
+ expected = { "username" => "sikachu", "person" => { "username" => "sikachu" } }
assert_equal expected, @controller.last_parameters
end
end
diff --git a/actionpack/test/controller/api/renderers_test.rb b/actionpack/test/controller/api/renderers_test.rb
index 7eecc1c680..04e34a1f8f 100644
--- a/actionpack/test/controller/api/renderers_test.rb
+++ b/actionpack/test/controller/api/renderers_test.rb
@@ -23,10 +23,6 @@ class RenderersApiController < ActionController::API
def plain
render plain: "Hi from plain", status: 500
end
-
- def text
- render text: "Hi from text", status: 500
- end
end
class RenderersApiTest < ActionController::TestCase
@@ -49,12 +45,4 @@ class RenderersApiTest < ActionController::TestCase
assert_response :internal_server_error
assert_equal("Hi from plain", @response.body)
end
-
- def test_render_text
- assert_deprecated do
- get :text
- end
- assert_response :internal_server_error
- assert_equal("Hi from text", @response.body)
- end
end
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
index 2edcbc7433..42a5157010 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -194,7 +194,7 @@ class UrlOptionsTest < ActionController::TestCase
get "account/overview"
end
- assert !@controller.class.action_methods.include?("account_overview_path")
+ assert_not_includes @controller.class.action_methods, "account_overview_path"
end
end
end
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index 5f4857c553..fa8d9dc09a 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -49,7 +49,7 @@ class FragmentCachingTest < ActionController::TestCase
@controller = FragmentCachingTestController.new
@controller.perform_caching = true
@controller.cache_store = @store
- @params = {controller: "posts", action: "index"}
+ @params = { controller: "posts", action: "index" }
@controller.params = @params
@controller.request = @request
@controller.response = @response
@@ -58,7 +58,7 @@ class FragmentCachingTest < ActionController::TestCase
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")
+ @controller.fragment_cache_key(controller: "fragment_caching_test", action: "some_action")
end
def test_read_fragment_with_caching_enabled
@@ -324,7 +324,7 @@ class CacheHelperOutputBufferTest < ActionController::TestCase
cache_helper.stub :output_buffer, output_buffer do
assert_called_with cache_helper, :output_buffer=, [output_buffer.class.new(output_buffer)] do
assert_nothing_raised do
- cache_helper.send :fragment_for, "Test fragment name", "Test fragment", &Proc.new{ nil }
+ cache_helper.send :fragment_for, "Test fragment name", "Test fragment", &Proc.new { nil }
end
end
end
@@ -345,7 +345,7 @@ class CacheHelperOutputBufferTest < ActionController::TestCase
cache_helper.stub :output_buffer, output_buffer do
assert_called_with cache_helper, :output_buffer=, [output_buffer.class.new(output_buffer)] do
assert_nothing_raised do
- cache_helper.send :fragment_for, "Test fragment name", "Test fragment", &Proc.new{ nil }
+ cache_helper.send :fragment_for, "Test fragment name", "Test fragment", &Proc.new { nil }
end
end
end
diff --git a/actionpack/test/controller/default_url_options_with_before_action_test.rb b/actionpack/test/controller/default_url_options_with_before_action_test.rb
index 703f9f1fde..e3fe7a6495 100644
--- a/actionpack/test/controller/default_url_options_with_before_action_test.rb
+++ b/actionpack/test/controller/default_url_options_with_before_action_test.rb
@@ -13,7 +13,7 @@ class ControllerWithBeforeActionAndDefaultUrlOptions < ActionController::Base
end
def default_url_options
- {locale: "de"}
+ { locale: "de" }
end
end
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
index 237711452a..f9701585a9 100644
--- a/actionpack/test/controller/filters_test.rb
+++ b/actionpack/test/controller/filters_test.rb
@@ -2,7 +2,7 @@ require "abstract_unit"
class ActionController::Base
class << self
- %w(append_around_action prepend_after_action prepend_around_action prepend_before_action skip_after_action skip_before_action skip_action_callback).each do |pending|
+ %w(append_around_action prepend_after_action prepend_around_action prepend_before_action skip_after_action skip_before_action).each do |pending|
define_method(pending) do |*args|
$stderr.puts "#{pending} unimplemented: #{args.inspect}"
end unless method_defined?(pending)
@@ -55,7 +55,7 @@ class FilterTest < ActionController::TestCase
end
end
- protected
+ private
(1..3).each do |i|
define_method "try_#{i}" do
instance_variable_set :@try, i
@@ -174,11 +174,11 @@ class FilterTest < ActionController::TestCase
end
class OnlyConditionProcController < ConditionalFilterController
- before_action(only: :show) {|c| c.instance_variable_set(:"@ran_proc_action", true) }
+ before_action(only: :show) { |c| c.instance_variable_set(:"@ran_proc_action", true) }
end
class ExceptConditionProcController < ConditionalFilterController
- before_action(except: :show_without_action) {|c| c.instance_variable_set(:"@ran_proc_action", true) }
+ before_action(except: :show_without_action) { |c| c.instance_variable_set(:"@ran_proc_action", true) }
end
class ConditionalClassFilter
@@ -194,11 +194,11 @@ class FilterTest < ActionController::TestCase
end
class AnomolousYetValidConditionController < ConditionalFilterController
- before_action(ConditionalClassFilter, :ensure_login, Proc.new {|c| c.instance_variable_set(:"@ran_proc_action1", true)}, except: :show_without_action) { |c| c.instance_variable_set(:"@ran_proc_action2", true)}
+ before_action(ConditionalClassFilter, :ensure_login, Proc.new { |c| c.instance_variable_set(:"@ran_proc_action1", true) }, except: :show_without_action) { |c| c.instance_variable_set(:"@ran_proc_action2", true) }
end
class OnlyConditionalOptionsFilter < ConditionalFilterController
- before_action :ensure_login, only: :index, if: Proc.new {|c| c.instance_variable_set(:"@ran_conditional_index_proc", true) }
+ before_action :ensure_login, only: :index, if: Proc.new { |c| c.instance_variable_set(:"@ran_conditional_index_proc", true) }
end
class ConditionalOptionsFilter < ConditionalFilterController
@@ -296,7 +296,7 @@ class FilterTest < ActionController::TestCase
render inline: "ran action"
end
- protected
+ private
def find_user
@ran_filter ||= []
@ran_filter << "find_user"
@@ -428,7 +428,7 @@ class FilterTest < ActionController::TestCase
render plain: "bar"
end
- protected
+ private
def first
@first = true
end
@@ -506,20 +506,20 @@ class FilterTest < ActionController::TestCase
private
def filter_one
- @filters ||= []
- @filters << "filter_one"
+ @filters ||= []
+ @filters << "filter_one"
end
def action_two
- @filters << "action_two"
+ @filters << "action_two"
end
def non_yielding_action
- @filters << "it didn't yield"
+ @filters << "it didn't yield"
end
def action_three
- @filters << "action_three"
+ @filters << "action_three"
end
end
@@ -911,7 +911,7 @@ class ControllerWithFilterInstance < PostsController
end
class ControllerWithProcFilter < PostsController
- around_action(only: :no_raise) do |c,b|
+ around_action(only: :no_raise) do |c, b|
c.instance_variable_set(:"@before", true)
b.call
c.instance_variable_set(:"@after", true)
@@ -956,46 +956,39 @@ class ControllerWithTwoLessFilters < ControllerWithAllTypesOfFilters
skip_after_action :after
end
-class SkipFilterUsingSkipActionCallback < ControllerWithAllTypesOfFilters
- ActiveSupport::Deprecation.silence do
- skip_action_callback :around_again
- skip_action_callback :after
- end
-end
-
class YieldingAroundFiltersTest < ActionController::TestCase
include PostsController::AroundExceptions
def test_base
controller = PostsController
- assert_nothing_raised { test_process(controller,"no_raise") }
- assert_nothing_raised { test_process(controller,"raises_before") }
- assert_nothing_raised { test_process(controller,"raises_after") }
- assert_nothing_raised { test_process(controller,"no_action") }
+ assert_nothing_raised { test_process(controller, "no_raise") }
+ assert_nothing_raised { test_process(controller, "raises_before") }
+ assert_nothing_raised { test_process(controller, "raises_after") }
+ assert_nothing_raised { test_process(controller, "no_action") }
end
def test_with_symbol
controller = ControllerWithSymbolAsFilter
- assert_nothing_raised { test_process(controller,"no_raise") }
- assert_raise(Before) { test_process(controller,"raises_before") }
- assert_raise(After) { test_process(controller,"raises_after") }
- assert_nothing_raised { test_process(controller,"no_raise") }
+ assert_nothing_raised { test_process(controller, "no_raise") }
+ assert_raise(Before) { test_process(controller, "raises_before") }
+ assert_raise(After) { test_process(controller, "raises_after") }
+ assert_nothing_raised { test_process(controller, "no_raise") }
end
def test_with_class
controller = ControllerWithFilterClass
- assert_nothing_raised { test_process(controller,"no_raise") }
- assert_raise(After) { test_process(controller,"raises_after") }
+ assert_nothing_raised { test_process(controller, "no_raise") }
+ assert_raise(After) { test_process(controller, "raises_after") }
end
def test_with_instance
controller = ControllerWithFilterInstance
- assert_nothing_raised { test_process(controller,"no_raise") }
- assert_raise(After) { test_process(controller,"raises_after") }
+ assert_nothing_raised { test_process(controller, "no_raise") }
+ assert_raise(After) { test_process(controller, "raises_after") }
end
def test_with_proc
- test_process(ControllerWithProcFilter,"no_raise")
+ test_process(ControllerWithProcFilter, "no_raise")
assert @controller.instance_variable_get(:@before)
assert @controller.instance_variable_get(:@after)
end
@@ -1004,25 +997,25 @@ class YieldingAroundFiltersTest < ActionController::TestCase
controller = ControllerWithNestedFilters
assert_nothing_raised do
begin
- test_process(controller,"raises_both")
+ test_process(controller, "raises_both")
rescue Before, After
end
end
assert_raise Before do
begin
- test_process(controller,"raises_both")
+ test_process(controller, "raises_both")
rescue After
end
end
end
def test_action_order_with_all_action_types
- test_process(ControllerWithAllTypesOfFilters,"no_raise")
+ test_process(ControllerWithAllTypesOfFilters, "no_raise")
assert_equal "before around (before yield) around_again (before yield) around_again (after yield) after around (after yield)", @controller.instance_variable_get(:@ran_filter).join(" ")
end
def test_action_order_with_skip_action_method
- test_process(ControllerWithTwoLessFilters,"no_raise")
+ test_process(ControllerWithTwoLessFilters, "no_raise")
assert_equal "before around (before yield) around (after yield)", @controller.instance_variable_get(:@ran_filter).join(" ")
end
@@ -1047,28 +1040,7 @@ class YieldingAroundFiltersTest < ActionController::TestCase
assert_equal 3, controller.instance_variable_get(:@try)
end
- def test_skipping_with_skip_action_callback
- test_process(SkipFilterUsingSkipActionCallback,"no_raise")
- assert_equal "before around (before yield) around (after yield)", @controller.instance_variable_get(:@ran_filter).join(" ")
- end
-
- def test_deprecated_skip_action_callback
- assert_deprecated do
- Class.new(PostsController) do
- skip_action_callback :clean_up
- end
- end
- end
-
- def test_deprecated_skip_filter
- assert_deprecated do
- Class.new(PostsController) do
- skip_filter :clean_up
- end
- end
- end
-
- protected
+ private
def test_process(controller, action = "show")
@controller = controller.is_a?(Class) ? controller.new : controller
process(action)
diff --git a/actionpack/test/controller/flash_hash_test.rb b/actionpack/test/controller/flash_hash_test.rb
index 16651a3fd7..45b598a594 100644
--- a/actionpack/test/controller/flash_hash_test.rb
+++ b/actionpack/test/controller/flash_hash_test.rb
@@ -48,11 +48,11 @@ module ActionDispatch
def test_to_hash
@hash["foo"] = "bar"
- assert_equal({"foo" => "bar"}, @hash.to_hash)
+ assert_equal({ "foo" => "bar" }, @hash.to_hash)
@hash.to_hash["zomg"] = "aaron"
assert !@hash.key?("zomg")
- assert_equal({"foo" => "bar"}, @hash.to_hash)
+ assert_equal({ "foo" => "bar" }, @hash.to_hash)
end
def test_to_session_value
@@ -63,10 +63,10 @@ module ActionDispatch
assert_equal({ "flashes" => { "foo" => "bar" }, "discard" => [] }, @hash.to_session_value)
@hash.discard("foo")
- assert_equal(nil, @hash.to_session_value)
+ assert_nil(@hash.to_session_value)
@hash.sweep
- assert_equal(nil, @hash.to_session_value)
+ assert_nil(@hash.to_session_value)
end
def test_from_session_value
@@ -74,8 +74,8 @@ module ActionDispatch
rails_3_2_cookie = "BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJWY4ZTFiODE1MmJhNzYwOWMyOGJiYjE3ZWM5MjYzYmE3BjsAVEkiCmZsYXNoBjsARm86JUFjdGlvbkRpc3BhdGNoOjpGbGFzaDo6Rmxhc2hIYXNoCToKQHVzZWRvOghTZXQGOgpAaGFzaHsGSSINZmFyZXdlbGwGOwBUVDoMQGNsb3NlZEY6DUBmbGFzaGVzewdJIg1ncmVldGluZwY7AFRJIgpIZWxsbwY7AFRJIg1mYXJld2VsbAY7AFRJIgxHb29kYnllBjsAVDoJQG5vdzA="
session = Marshal.load(Base64.decode64(rails_3_2_cookie))
hash = Flash::FlashHash.from_session_value(session["flash"])
- assert_equal({"greeting" => "Hello"}, hash.to_hash)
- assert_equal(nil, hash.to_session_value)
+ assert_equal({ "greeting" => "Hello" }, hash.to_hash)
+ assert_nil(hash.to_session_value)
end
def test_from_session_value_on_json_serializer
@@ -83,8 +83,8 @@ module ActionDispatch
session = ActionDispatch::Cookies::JsonSerializer.load(decrypted_data)
hash = Flash::FlashHash.from_session_value(session["flash"])
- assert_equal({"greeting" => "Hello"}, hash.to_hash)
- assert_equal(nil, hash.to_session_value)
+ assert_equal({ "greeting" => "Hello" }, hash.to_hash)
+ assert_nil(hash.to_session_value)
assert_equal "Hello", hash[:greeting]
assert_equal "Hello", hash["greeting"]
end
@@ -102,8 +102,8 @@ module ActionDispatch
@hash["foo"] = "bar"
things = []
- @hash.each do |k,v|
- things << [k,v]
+ @hash.each do |k, v|
+ things << [k, v]
end
assert_equal([%w{ hello world }, %w{ foo bar }].sort, things.sort)
@@ -112,7 +112,7 @@ module ActionDispatch
def test_replace
@hash["hello"] = "world"
@hash.replace("omg" => "aaron")
- assert_equal({"omg" => "aaron"}, @hash.to_hash)
+ assert_equal({ "omg" => "aaron" }, @hash.to_hash)
end
def test_discard_no_args
@@ -129,14 +129,14 @@ module ActionDispatch
@hash.discard "hello"
@hash.sweep
- assert_equal({"omg" => "world"}, @hash.to_hash)
+ assert_equal({ "omg" => "world" }, @hash.to_hash)
end
def test_keep_sweep
@hash["hello"] = "world"
@hash.sweep
- assert_equal({"hello" => "world"}, @hash.to_hash)
+ assert_equal({ "hello" => "world" }, @hash.to_hash)
end
def test_update_sweep
@@ -144,7 +144,7 @@ module ActionDispatch
@hash.update("hi" => "mom")
@hash.sweep
- assert_equal({"hello" => "world", "hi" => "mom"}, @hash.to_hash)
+ assert_equal({ "hello" => "world", "hi" => "mom" }, @hash.to_hash)
end
def test_update_delete_sweep
@@ -153,7 +153,7 @@ module ActionDispatch
@hash.update("hello" => "mom")
@hash.sweep
- assert_equal({"hello" => "mom"}, @hash.to_hash)
+ assert_equal({ "hello" => "mom" }, @hash.to_hash)
end
def test_delete_sweep
@@ -162,7 +162,7 @@ module ActionDispatch
@hash.delete "hi"
@hash.sweep
- assert_equal({"hello" => "world"}, @hash.to_hash)
+ assert_equal({ "hello" => "world" }, @hash.to_hash)
end
def test_clear_sweep
@@ -178,7 +178,7 @@ module ActionDispatch
@hash.replace("hi" => "mom")
@hash.sweep
- assert_equal({"hi" => "mom"}, @hash.to_hash)
+ assert_equal({ "hi" => "mom" }, @hash.to_hash)
end
def test_discard_then_add
@@ -188,7 +188,7 @@ module ActionDispatch
@hash["hello"] = "world"
@hash.sweep
- assert_equal({"omg" => "world", "hello" => "world"}, @hash.to_hash)
+ assert_equal({ "omg" => "world", "hello" => "world" }, @hash.to_hash)
end
def test_keep_all_sweep
@@ -198,14 +198,14 @@ module ActionDispatch
@hash.keep
@hash.sweep
- assert_equal({"omg" => "world", "hello" => "world"}, @hash.to_hash)
+ assert_equal({ "omg" => "world", "hello" => "world" }, @hash.to_hash)
end
def test_double_sweep
@hash["hello"] = "world"
@hash.sweep
- assert_equal({"hello" => "world"}, @hash.to_hash)
+ assert_equal({ "hello" => "world" }, @hash.to_hash)
@hash.sweep
assert_equal({}, @hash.to_hash)
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index 4e046b2fc8..dc641c19ab 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -176,13 +176,13 @@ class FlashTest < ActionController::TestCase
assert_equal(:foo_indeed, flash.discard(:foo)) # valid key passed
assert_nil flash.discard(:unknown) # non existent key passed
- assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.discard().to_hash) # nothing passed
- assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.discard(nil).to_hash) # nothing passed
+ assert_equal({ "foo" => :foo_indeed, "bar" => :bar_indeed }, flash.discard().to_hash) # nothing passed
+ assert_equal({ "foo" => :foo_indeed, "bar" => :bar_indeed }, flash.discard(nil).to_hash) # nothing passed
assert_equal(:foo_indeed, flash.keep(:foo)) # valid key passed
assert_nil flash.keep(:unknown) # non existent key passed
- assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.keep().to_hash) # nothing passed
- assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.keep(nil).to_hash) # nothing passed
+ assert_equal({ "foo" => :foo_indeed, "bar" => :bar_indeed }, flash.keep().to_hash) # nothing passed
+ assert_equal({ "foo" => :foo_indeed, "bar" => :bar_indeed }, flash.keep(nil).to_hash) # nothing passed
end
def test_redirect_to_with_alert
@@ -227,7 +227,7 @@ class FlashTest < ActionController::TestCase
add_flash_types :foo
end
subclass_controller_with_no_flash_type = Class.new(test_controller_with_flash_type_foo)
- assert subclass_controller_with_no_flash_type._flash_types.include?(:foo)
+ assert_includes subclass_controller_with_no_flash_type._flash_types, :foo
end
def test_does_not_add_flash_type_to_parent_class
@@ -263,6 +263,13 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest
flash[:bar] = "for great justice"
head :ok
end
+
+ def set_flash_optionally
+ flash.now.notice = params[:flash]
+ if stale? etag: "abe"
+ render inline: "maybe flash"
+ end
+ end
end
def test_flash
@@ -310,6 +317,28 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest
end
end
+ def test_flash_factored_into_etag
+ with_test_route_set do
+ get "/set_flash_optionally"
+ no_flash_etag = response.etag
+
+ get "/set_flash_optionally", params: { flash: "hello!" }
+ hello_flash_etag = response.etag
+
+ assert_not_equal no_flash_etag, hello_flash_etag
+
+ get "/set_flash_optionally", params: { flash: "hello!" }
+ another_hello_flash_etag = response.etag
+
+ assert_equal another_hello_flash_etag, hello_flash_etag
+
+ get "/set_flash_optionally", params: { flash: "goodbye!" }
+ goodbye_flash_etag = response.etag
+
+ assert_not_equal another_hello_flash_etag, goodbye_flash_etag
+ end
+ end
+
private
# Overwrite get to send SessionSecret in env hash
diff --git a/actionpack/test/controller/force_ssl_test.rb b/actionpack/test/controller/force_ssl_test.rb
index 8d5e3ce2fe..2b3859aa57 100644
--- a/actionpack/test/controller/force_ssl_test.rb
+++ b/actionpack/test/controller/force_ssl_test.rb
@@ -229,14 +229,12 @@ class ForceSSLFlashTest < ActionController::TestCase
assert_response 302
assert_equal "http://test.host/force_ssl_flash/cheeseburger", redirect_to_url
- # FIXME: AC::TestCase#build_request_uri doesn't build a new uri if PATH_INFO exists
@request.env.delete("PATH_INFO")
get :cheeseburger
assert_response 301
assert_equal "https://test.host/force_ssl_flash/cheeseburger", redirect_to_url
- # FIXME: AC::TestCase#build_request_uri doesn't build a new uri if PATH_INFO exists
@request.env.delete("PATH_INFO")
get :use_flash
diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb
index 981b67f685..4c6a772062 100644
--- a/actionpack/test/controller/helper_test.rb
+++ b/actionpack/test/controller/helper_test.rb
@@ -125,13 +125,13 @@ class HelperTest < ActiveSupport::TestCase
def test_helper_method
assert_nothing_raised { @controller_class.helper_method :delegate_method }
- assert master_helper_methods.include?(:delegate_method)
+ assert_includes master_helper_methods, :delegate_method
end
def test_helper_attr
assert_nothing_raised { @controller_class.helper_attr :delegate_attr }
- assert master_helper_methods.include?(:delegate_attr)
- assert master_helper_methods.include?(:delegate_attr=)
+ assert_includes master_helper_methods, :delegate_attr
+ assert_includes master_helper_methods, :delegate_attr=
end
def call_controller(klass, action)
@@ -168,13 +168,13 @@ class HelperTest < ActiveSupport::TestCase
methods = AllHelpersController._helpers.instance_methods
# abc_helper.rb
- assert methods.include?(:bare_a)
+ assert_includes methods, :bare_a
# fun/games_helper.rb
- assert methods.include?(:stratego)
+ assert_includes methods, :stratego
# fun/pdf_helper.rb
- assert methods.include?(:foobar)
+ assert_includes methods, :foobar
end
def test_all_helpers_with_alternate_helper_dir
@@ -185,26 +185,26 @@ class HelperTest < ActiveSupport::TestCase
@controller_class.helper :all
# helpers/abc_helper.rb should not be included
- assert !master_helper_methods.include?(:bare_a)
+ assert_not_includes master_helper_methods, :bare_a
# alternate_helpers/foo_helper.rb
- assert master_helper_methods.include?(:baz)
+ assert_includes master_helper_methods, :baz
end
def test_helper_proxy
methods = AllHelpersController.helpers.methods
# Action View
- assert methods.include?(:pluralize)
+ assert_includes methods, :pluralize
# abc_helper.rb
- assert methods.include?(:bare_a)
+ assert_includes methods, :bare_a
# fun/games_helper.rb
- assert methods.include?(:stratego)
+ assert_includes methods, :stratego
# fun/pdf_helper.rb
- assert methods.include?(:foobar)
+ assert_includes methods, :foobar
end
def test_helper_proxy_in_instance
diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb
index 5e9b801f8b..0b59e123d7 100644
--- a/actionpack/test/controller/http_digest_authentication_test.rb
+++ b/actionpack/test/controller/http_digest_authentication_test.rb
@@ -7,7 +7,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
before_action :authenticate_with_request, only: :display
USERS = { "lifo" => "world", "pretty" => "please",
- "dhh" => ::Digest::MD5::hexdigest(["dhh","SuperSecret","secret"].join(":"))}
+ "dhh" => ::Digest::MD5::hexdigest(["dhh", "SuperSecret", "secret"].join(":")) }
def index
render plain: "Hello Secret"
@@ -180,7 +180,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
test "authentication request with password stored as ha1 digest hash" do
@request.env["HTTP_AUTHORIZATION"] = encode_credentials(username: "dhh",
- password: ::Digest::MD5::hexdigest(["dhh","SuperSecret","secret"].join(":")),
+ password: ::Digest::MD5::hexdigest(["dhh", "SuperSecret", "secret"].join(":")),
password_is_ha1: true)
get :display
@@ -199,7 +199,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
test "validate_digest_response should fail with nil returning password_procedure" do
@request.env["HTTP_AUTHORIZATION"] = encode_credentials(username: nil, password: nil)
- assert !ActionController::HttpAuthentication::Digest.validate_digest_response(@request, "SuperSecret"){nil}
+ assert !ActionController::HttpAuthentication::Digest.validate_digest_response(@request, "SuperSecret") { nil }
end
test "authentication request with request-uri ending in '/'" do
diff --git a/actionpack/test/controller/http_token_authentication_test.rb b/actionpack/test/controller/http_token_authentication_test.rb
index b7a4c550d7..09d2793c9a 100644
--- a/actionpack/test/controller/http_token_authentication_test.rb
+++ b/actionpack/test/controller/http_token_authentication_test.rb
@@ -156,18 +156,17 @@ class HttpTokenAuthenticationTest < ActionController::TestCase
test "token_and_options returns correct token with nounce option" do
token = "rcHu+HzSFw89Ypyhn/896A="
- nonce_hash = {nonce: "123abc"}
+ nonce_hash = { nonce: "123abc" }
actual = ActionController::HttpAuthentication::Token.token_and_options(sample_request(token, nonce_hash))
expected_token = token
- expected_nonce = {"nonce" => nonce_hash[:nonce]}
+ expected_nonce = { "nonce" => nonce_hash[:nonce] }
assert_equal(expected_token, actual.first)
assert_equal(expected_nonce, actual.last)
end
test "token_and_options returns nil with no value after the equal sign" do
actual = ActionController::HttpAuthentication::Token.token_and_options(malformed_request).first
- expected = nil
- assert_equal(expected, actual)
+ assert_nil actual
end
test "raw_params returns a tuple of two key value pair strings" do
@@ -190,7 +189,7 @@ class HttpTokenAuthenticationTest < ActionController::TestCase
private
- def sample_request(token, options = {nonce: "def"})
+ def sample_request(token, options = { nonce: "def" })
authorization = options.inject([%{Token token="#{token}"}]) do |arr, (k, v)|
arr << "#{k}=\"#{v}\""
end.join(", ")
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index 4abbccfbe5..4fae4071b1 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -4,7 +4,7 @@ require "rails/engine"
class SessionTest < ActiveSupport::TestCase
StubApp = lambda { |env|
- [200, {"Content-Type" => "text/html", "Content-Length" => "13"}, ["Hello, World!"]]
+ [200, { "Content-Type" => "text/html", "Content-Length" => "13" }, ["Hello, World!"]]
}
def setup
@@ -31,95 +31,6 @@ class SessionTest < ActiveSupport::TestCase
end
end
- def test_request_via_redirect_uses_given_method
- path = "/somepath"; args = {id: "1"}; headers = {"X-Test-Header" => "testvalue"}
- assert_called_with @session, :process, [:put, path, params: args, headers: headers] do
- @session.stub :redirect?, false do
- assert_deprecated { @session.request_via_redirect(:put, path, params: args, headers: headers) }
- end
- end
- end
-
- def test_deprecated_request_via_redirect_uses_given_method
- path = "/somepath"; args = { id: "1" }; headers = { "X-Test-Header" => "testvalue" }
- assert_called_with @session, :process, [:put, path, params: args, headers: headers] do
- @session.stub :redirect?, false do
- assert_deprecated { @session.request_via_redirect(:put, path, args, headers) }
- end
- end
- end
-
- def test_request_via_redirect_follows_redirects
- path = "/somepath"; args = {id: "1"}; headers = {"X-Test-Header" => "testvalue"}
- value_series = [true, true, false]
- assert_called @session, :follow_redirect!, times: 2 do
- @session.stub :redirect?, ->{ value_series.shift } do
- assert_deprecated { @session.request_via_redirect(:get, path, params: args, headers: headers) }
- end
- end
- end
-
- def test_request_via_redirect_returns_status
- path = "/somepath"; args = {id: "1"}; headers = {"X-Test-Header" => "testvalue"}
- @session.stub :redirect?, false do
- @session.stub :status, 200 do
- assert_deprecated do
- assert_equal 200, @session.request_via_redirect(:get, path, params: args, headers: headers)
- end
- end
- end
- end
-
- def test_deprecated_get_via_redirect
- path = "/somepath"; args = { id: "1" }; headers = { "X-Test-Header" => "testvalue" }
-
- assert_called_with @session, :request_via_redirect, [:get, path, args, headers] do
- assert_deprecated do
- @session.get_via_redirect(path, args, headers)
- end
- end
- end
-
- def test_deprecated_post_via_redirect
- path = "/somepath"; args = { id: "1" }; headers = { "X-Test-Header" => "testvalue" }
-
- assert_called_with @session, :request_via_redirect, [:post, path, args, headers] do
- assert_deprecated do
- @session.post_via_redirect(path, args, headers)
- end
- end
- end
-
- def test_deprecated_patch_via_redirect
- path = "/somepath"; args = { id: "1" }; headers = { "X-Test-Header" => "testvalue" }
-
- assert_called_with @session, :request_via_redirect, [:patch, path, args, headers] do
- assert_deprecated do
- @session.patch_via_redirect(path, args, headers)
- end
- end
- end
-
- def test_deprecated_put_via_redirect
- path = "/somepath"; args = { id: "1" }; headers = { "X-Test-Header" => "testvalue" }
-
- assert_called_with @session, :request_via_redirect, [:put, path, args, headers] do
- assert_deprecated do
- @session.put_via_redirect(path, args, headers)
- end
- end
- end
-
- def test_deprecated_delete_via_redirect
- path = "/somepath"; args = { id: "1" }; headers = { "X-Test-Header" => "testvalue" }
-
- assert_called_with @session, :request_via_redirect, [:delete, path, args, headers] do
- assert_deprecated do
- @session.delete_via_redirect(path, args, headers)
- end
- end
- end
-
def test_get
path = "/index"; params = "blah"; headers = { location: "blah" }
@@ -135,28 +46,9 @@ class SessionTest < ActiveSupport::TestCase
end
end
- def test_deprecated_get
- path = "/index"; params = "blah"; headers = { location: "blah" }
-
- assert_called_with @session, :process, [:get, path, params: params, headers: headers] do
- assert_deprecated {
- @session.get(path, params, headers)
- }
- end
- end
-
def test_post
path = "/index"; params = "blah"; headers = { location: "blah" }
assert_called_with @session, :process, [:post, path, params: params, headers: headers] do
- assert_deprecated {
- @session.post(path, params, headers)
- }
- end
- end
-
- def test_deprecated_post
- path = "/index"; params = "blah"; headers = { location: "blah" }
- assert_called_with @session, :process, [:post, path, params: params, headers: headers] do
@session.post(path, params: params, headers: headers)
end
end
@@ -168,15 +60,6 @@ class SessionTest < ActiveSupport::TestCase
end
end
- def test_deprecated_patch
- path = "/index"; params = "blah"; headers = { location: "blah" }
- assert_called_with @session, :process, [:patch, path, params: params, headers: headers] do
- assert_deprecated {
- @session.patch(path, params, headers)
- }
- end
- end
-
def test_put
path = "/index"; params = "blah"; headers = { location: "blah" }
assert_called_with @session, :process, [:put, path, params: params, headers: headers] do
@@ -184,27 +67,9 @@ class SessionTest < ActiveSupport::TestCase
end
end
- def test_deprecated_put
- path = "/index"; params = "blah"; headers = { location: "blah" }
- assert_called_with @session, :process, [:put, path, params: params, headers: headers] do
- assert_deprecated {
- @session.put(path, params, headers)
- }
- end
- end
-
def test_delete
path = "/index"; params = "blah"; headers = { location: "blah" }
assert_called_with @session, :process, [:delete, path, params: params, headers: headers] do
- assert_deprecated {
- @session.delete(path,params,headers)
- }
- end
- end
-
- def test_deprecated_delete
- path = "/index"; params = "blah"; headers = { location: "blah" }
- assert_called_with @session, :process, [:delete, path, params: params, headers: headers] do
@session.delete(path, params: params, headers: headers)
end
end
@@ -216,15 +81,6 @@ class SessionTest < ActiveSupport::TestCase
end
end
- def deprecated_test_head
- path = "/index"; params = "blah"; headers = { location: "blah" }
- assert_called_with @session, :process, [:head, path, params: params, headers: headers] do
- assert_deprecated {
- @session.head(path, params, headers)
- }
- end
- end
-
def test_xml_http_request_get
path = "/index"; params = "blah"; headers = { location: "blah" }
assert_called_with @session, :process, [:get, path, params: params, headers: headers, xhr: true] do
@@ -232,22 +88,6 @@ class SessionTest < ActiveSupport::TestCase
end
end
- def test_deprecated_xml_http_request_get
- path = "/index"; params = "blah"; headers = { location: "blah" }
- assert_called_with @session, :process, [:get, path, params: params, headers: headers, xhr: true] do
- @session.get(path, params: params, headers: headers, xhr: true)
- end
- end
-
- def test_deprecated_args_xml_http_request_get
- path = "/index"; params = "blah"; headers = { location: "blah" }
- assert_called_with @session, :process, [:get, path, params: params, headers: headers, xhr: true] do
- assert_deprecated(/xml_http_request/) {
- @session.xml_http_request(:get, path, params, headers)
- }
- end
- end
-
def test_xml_http_request_post
path = "/index"; params = "blah"; headers = { location: "blah" }
assert_called_with @session, :process, [:post, path, params: params, headers: headers, xhr: true] do
@@ -255,20 +95,6 @@ class SessionTest < ActiveSupport::TestCase
end
end
- def test_deprecated_xml_http_request_post
- path = "/index"; params = "blah"; headers = { location: "blah" }
- assert_called_with @session, :process, [:post, path, params: params, headers: headers, xhr: true] do
- @session.post(path, params: params, headers: headers, xhr: true)
- end
- end
-
- def test_deprecated_args_xml_http_request_post
- path = "/index"; params = "blah"; headers = { location: "blah" }
- assert_called_with @session, :process, [:post, path, params: params, headers: headers, xhr: true] do
- assert_deprecated(/xml_http_request/) { @session.xml_http_request(:post,path,params,headers) }
- end
- end
-
def test_xml_http_request_patch
path = "/index"; params = "blah"; headers = { location: "blah" }
assert_called_with @session, :process, [:patch, path, params: params, headers: headers, xhr: true] do
@@ -276,20 +102,6 @@ class SessionTest < ActiveSupport::TestCase
end
end
- def test_deprecated_xml_http_request_patch
- path = "/index"; params = "blah"; headers = { location: "blah" }
- assert_called_with @session, :process, [:patch, path, params: params, headers: headers, xhr: true] do
- @session.patch(path, params: params, headers: headers, xhr: true)
- end
- end
-
- def test_deprecated_args_xml_http_request_patch
- path = "/index"; params = "blah"; headers = { location: "blah" }
- assert_called_with @session, :process, [:patch, path, params: params, headers: headers, xhr: true] do
- assert_deprecated(/xml_http_request/) { @session.xml_http_request(:patch,path,params,headers) }
- end
- end
-
def test_xml_http_request_put
path = "/index"; params = "blah"; headers = { location: "blah" }
assert_called_with @session, :process, [:put, path, params: params, headers: headers, xhr: true] do
@@ -297,20 +109,6 @@ class SessionTest < ActiveSupport::TestCase
end
end
- def test_deprecated_xml_http_request_put
- path = "/index"; params = "blah"; headers = { location: "blah" }
- assert_called_with @session, :process, [:put, path, params: params, headers: headers, xhr: true] do
- @session.put(path, params: params, headers: headers, xhr: true)
- end
- end
-
- def test_deprecated_args_xml_http_request_put
- path = "/index"; params = "blah"; headers = { location: "blah" }
- assert_called_with @session, :process, [:put, path, params: params, headers: headers, xhr: true] do
- assert_deprecated(/xml_http_request/) { @session.xml_http_request(:put, path, params, headers) }
- end
- end
-
def test_xml_http_request_delete
path = "/index"; params = "blah"; headers = { location: "blah" }
assert_called_with @session, :process, [:delete, path, params: params, headers: headers, xhr: true] do
@@ -318,40 +116,12 @@ class SessionTest < ActiveSupport::TestCase
end
end
- def test_deprecated_xml_http_request_delete
- path = "/index"; params = "blah"; headers = { location: "blah" }
- assert_called_with @session, :process, [:delete, path, params: params, headers: headers, xhr: true] do
- assert_deprecated { @session.xml_http_request(:delete, path, params: params, headers: headers) }
- end
- end
-
- def test_deprecated_args_xml_http_request_delete
- path = "/index"; params = "blah"; headers = { location: "blah" }
- assert_called_with @session, :process, [:delete, path, params: params, headers: headers, xhr: true] do
- assert_deprecated(/xml_http_request/) { @session.xml_http_request(:delete, path, params, headers) }
- end
- end
-
def test_xml_http_request_head
path = "/index"; params = "blah"; headers = { location: "blah" }
assert_called_with @session, :process, [:head, path, params: params, headers: headers, xhr: true] do
@session.head(path, params: params, headers: headers, xhr: true)
end
end
-
- def test_deprecated_xml_http_request_head
- path = "/index"; params = "blah"; headers = { location: "blah" }
- assert_called_with @session, :process, [:head, path, params: params, headers: headers, xhr: true] do
- assert_deprecated(/xml_http_request/) { @session.xml_http_request(:head, path, params: params, headers: headers) }
- end
- end
-
- def test_deprecated_args_xml_http_request_head
- path = "/index"; params = "blah"; headers = { location: "blah" }
- assert_called_with @session, :process, [:head, path, params: params, headers: headers, xhr: true] do
- assert_deprecated { @session.xml_http_request(:head, path, params, headers) }
- end
- end
end
class IntegrationTestTest < ActiveSupport::TestCase
@@ -375,7 +145,7 @@ class IntegrationTestTest < ActiveSupport::TestCase
name.to_s == "foo" ? "pass" : super
end
end
- @test.class.superclass.__send__(:include, mixin)
+ @test.class.superclass.include(mixin)
begin
assert_equal "pass", @test.foo
ensure
@@ -466,7 +236,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
def test_get_xml_rss_atom
%w[ application/xml application/rss+xml application/atom+xml ].each do |mime_string|
with_test_route_set do
- get "/get", headers: {"HTTP_ACCEPT" => mime_string}
+ get "/get", headers: { "HTTP_ACCEPT" => mime_string }
assert_equal 200, status
assert_equal "OK", status_message
assert_response 200
@@ -503,7 +273,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
self.cookies["cookie_2"] = "oatmeal"
get "/cookie_monster"
assert_equal "cookie_1=; path=/\ncookie_3=chocolate; path=/", headers["Set-Cookie"]
- assert_equal({"cookie_1"=>"", "cookie_2"=>"oatmeal", "cookie_3"=>"chocolate"}, cookies.to_hash)
+ assert_equal({ "cookie_1" => "", "cookie_2" => "oatmeal", "cookie_3" => "chocolate" }, cookies.to_hash)
end
end
@@ -513,14 +283,14 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
assert_response :success
assert_equal "foo=bar; path=/", headers["Set-Cookie"]
- assert_equal({"foo"=>"bar"}, cookies.to_hash)
+ assert_equal({ "foo" => "bar" }, cookies.to_hash)
get "/get_cookie"
assert_response :success
assert_equal "bar", body
- assert_equal nil, headers["Set-Cookie"]
- assert_equal({"foo"=>"bar"}, cookies.to_hash)
+ assert_nil headers["Set-Cookie"]
+ assert_equal({ "foo" => "bar" }, cookies.to_hash)
end
end
@@ -532,14 +302,14 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
assert_response :success
assert_equal "foo=bar; path=/", headers["Set-Cookie"]
- assert_equal({"foo"=>"bar"}, cookies.to_hash)
+ assert_equal({ "foo" => "bar" }, cookies.to_hash)
get "/get_cookie"
assert_response :success
assert_equal "bar", body
- assert_equal nil, headers["Set-Cookie"]
- assert_equal({"foo"=>"bar"}, cookies.to_hash)
+ assert_nil headers["Set-Cookie"]
+ assert_equal({ "foo" => "bar" }, cookies.to_hash)
end
end
@@ -577,18 +347,6 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
end
end
- def test_deprecated_xml_http_request_get
- with_test_route_set do
- assert_deprecated { xhr :get, "/get" }
- assert_equal 200, status
- assert_equal "OK", status_message
- assert_response 200
- assert_response :success
- assert_response :ok
- assert_equal "JS OK", response.body
- end
- end
-
def test_request_with_bad_format
with_test_route_set do
get "/get.php", xhr: true
@@ -598,6 +356,14 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
end
end
+ test "creation of multiple integration sessions" do
+ integration_session # initialize first session
+ a = open_session
+ b = open_session
+
+ refute_same(a.integration_session, b.integration_session)
+ end
+
def test_get_with_query_string
with_test_route_set do
get "/get_with_params?foo=bar"
@@ -778,9 +544,9 @@ class MetalIntegrationTest < ActionDispatch::IntegrationTest
class Poller
def self.call(env)
if env["PATH_INFO"] =~ /^\/success/
- [200, {"Content-Type" => "text/plain", "Content-Length" => "12"}, ["Hello World!"]]
+ [200, { "Content-Type" => "text/plain", "Content-Length" => "12" }, ["Hello World!"]]
else
- [404, {"Content-Type" => "text/plain", "Content-Length" => "0"}, []]
+ [404, { "Content-Type" => "text/plain", "Content-Length" => "0" }, []]
end
end
end
@@ -809,7 +575,7 @@ class MetalIntegrationTest < ActionDispatch::IntegrationTest
end
def test_pass_headers
- get "/success", headers: {"Referer" => "http://www.example.com/foo", "Host" => "http://nohost.com"}
+ get "/success", headers: { "Referer" => "http://www.example.com/foo", "Host" => "http://nohost.com" }
assert_equal "http://nohost.com", @request.env["HTTP_HOST"]
assert_equal "http://www.example.com/foo", @request.env["HTTP_REFERER"]
@@ -885,7 +651,7 @@ class ApplicationIntegrationTest < ActionDispatch::IntegrationTest
get "bar", to: "application_integration_test/test#index", as: :bar
mount MountedApp => "/mounted", :as => "mounted"
- get "fooz" => proc { |env| [ 200, {"X-Cascade" => "pass"}, [ "omg" ] ] }, :anchor => false
+ get "fooz" => proc { |env| [ 200, { "X-Cascade" => "pass" }, [ "omg" ] ] }, :anchor => false
get "fooz", to: "application_integration_test/test#index"
end
@@ -1158,12 +924,16 @@ class IntegrationRequestsWithSessionSetup < ActionDispatch::IntegrationTest
def test_cookies_set_in_setup_are_persisted_through_the_session
get "/foo"
- assert_equal({"user_name"=>"david"}, cookies.to_hash)
+ assert_equal({ "user_name" => "david" }, cookies.to_hash)
end
end
class IntegrationRequestEncodersTest < ActionDispatch::IntegrationTest
class FooController < ActionController::Base
+ def foos
+ render plain: "ok"
+ end
+
def foos_json
render json: params.permit(:foo)
end
@@ -1192,13 +962,30 @@ class IntegrationRequestEncodersTest < ActionDispatch::IntegrationTest
def test_encoding_as_json
post_to_foos as: :json do
assert_response :success
- assert_match "foos_json.json", request.path
assert_equal "application/json", request.content_type
+ assert_equal "application/json", request.accepts.first.to_s
+ assert_equal :json, request.format.ref
assert_equal({ "foo" => "fighters" }, request.request_parameters)
assert_equal({ "foo" => "fighters" }, response.parsed_body)
end
end
+ def test_doesnt_mangle_request_path
+ with_routing do |routes|
+ routes.draw do
+ ActiveSupport::Deprecation.silence do
+ post ":action" => FooController
+ end
+ end
+
+ post "/foos"
+ assert_equal "/foos", request.path
+
+ post "/foos_json", as: :json
+ assert_equal "/foos_json", request.path
+ end
+ end
+
def test_encoding_as_without_mime_registration
assert_raise ArgumentError do
ActionDispatch::IntegrationTest.register_encoder :wibble
@@ -1213,8 +1000,10 @@ class IntegrationRequestEncodersTest < ActionDispatch::IntegrationTest
post_to_foos as: :wibble do
assert_response :success
- assert_match "foos_wibble.wibble", request.path
+ assert_equal "/foos_wibble", request.path
assert_equal "text/wibble", request.content_type
+ assert_equal "text/wibble", request.accepts.first.to_s
+ assert_equal :wibble, request.format.ref
assert_equal Hash.new, request.request_parameters # Unregistered MIME Type can't be parsed.
assert_equal "ok", response.parsed_body
end
@@ -1281,3 +1070,39 @@ class IntegrationRequestEncodersTest < ActionDispatch::IntegrationTest
end
end
end
+
+class IntegrationFileUploadTest < ActionDispatch::IntegrationTest
+ class IntegrationController < ActionController::Base
+ def test_file_upload
+ render plain: params[:file].size
+ end
+ end
+
+ def self.routes
+ @routes ||= ActionDispatch::Routing::RouteSet.new
+ end
+
+ def self.call(env)
+ routes.call(env)
+ end
+
+ def app
+ self.class
+ end
+
+ def self.fixture_path
+ File.dirname(__FILE__) + "/../fixtures/multipart"
+ end
+
+ routes.draw do
+ post "test_file_upload", to: "integration_file_upload_test/integration#test_file_upload"
+ end
+
+ def test_fixture_file_upload
+ post "/test_file_upload",
+ params: {
+ file: fixture_file_upload("/ruby_on_rails.jpg", "image/jpg")
+ }
+ assert_equal "45142", @response.body
+ end
+end
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
index 3ea02f0a19..e76628b936 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -150,6 +150,22 @@ module ActionController
response.stream.close
end
+ def write_sleep_autoload
+ path = File.join(File.dirname(__FILE__), "../fixtures")
+ ActiveSupport::Dependencies.autoload_paths << path
+
+ response.headers["Content-Type"] = "text/event-stream"
+ response.stream.write "before load"
+ sleep 0.01
+ silence_warning do
+ ::LoadMe
+ end
+ response.stream.close
+ latch.count_down
+
+ ActiveSupport::Dependencies.autoload_paths.reject! { |p| p == path }
+ end
+
def thread_locals
tc.assert_equal "aaron", Thread.current[:setting]
@@ -271,7 +287,7 @@ module ActionController
def test_set_cookie
get :set_cookie
- assert_equal({"hello" => "world"}, @response.cookies)
+ assert_equal({ "hello" => "world" }, @response.cookies)
assert_equal "hello world", @response.body
end
@@ -281,6 +297,14 @@ module ActionController
assert_equal "text/event-stream", @response.headers["Content-Type"]
end
+ def test_delayed_autoload_after_write_within_interlock_hook
+ # Simulate InterlockHook
+ ActiveSupport::Dependencies.interlock.start_running
+ res = get :write_sleep_autoload
+ res.each {}
+ ActiveSupport::Dependencies.interlock.done_running
+ end
+
def test_async_stream
rubinius_skip "https://github.com/rubinius/rubinius/issues/2934"
diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb
index 231283acdf..45a120acb6 100644
--- a/actionpack/test/controller/log_subscriber_test.rb
+++ b/actionpack/test/controller/log_subscriber_test.rb
@@ -138,7 +138,7 @@ class ACLogSubscriberTest < ActionController::TestCase
def test_process_action_without_parameters
get :show
wait
- assert_nil logs.detect {|l| l =~ /Parameters/ }
+ assert_nil logs.detect { |l| l =~ /Parameters/ }
end
def test_process_action_with_parameters
diff --git a/actionpack/test/controller/mime/accept_format_test.rb b/actionpack/test/controller/mime/accept_format_test.rb
index a2834c9f39..a22fa39051 100644
--- a/actionpack/test/controller/mime/accept_format_test.rb
+++ b/actionpack/test/controller/mime/accept_format_test.rb
@@ -40,7 +40,7 @@ class PostController < AbstractPostController
respond_to(:html, :iphone, :js)
end
-protected
+private
def with_iphone
request.format = "iphone" if request.env["HTTP_ACCEPT"] == "text/iphone"
diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb
index 2b6d319999..818dc119eb 100644
--- a/actionpack/test/controller/mime/respond_to_test.rb
+++ b/actionpack/test/controller/mime/respond_to_test.rb
@@ -76,7 +76,7 @@ class RespondToController < ActionController::Base
def missing_templates
respond_to do |type|
# This test requires a block that is empty
- type.json { }
+ type.json {}
type.xml
end
end
@@ -221,7 +221,7 @@ class RespondToController < ActionController::Base
def variant_any
respond_to do |format|
format.html do |variant|
- variant.any(:tablet, :phablet){ render body: "any" }
+ variant.any(:tablet, :phablet) { render body: "any" }
variant.phone { render body: "phone" }
end
end
@@ -238,7 +238,7 @@ class RespondToController < ActionController::Base
def variant_inline_any
respond_to do |format|
- format.html.any(:tablet, :phablet){ render body: "any" }
+ format.html.any(:tablet, :phablet) { render body: "any" }
format.html.phone { render body: "phone" }
end
end
@@ -259,7 +259,7 @@ class RespondToController < ActionController::Base
def variant_any_with_none
respond_to do |format|
- format.html.any(:none, :phone){ render body: "none or phone" }
+ format.html.any(:none, :phone) { render body: "none or phone" }
end
end
@@ -267,13 +267,13 @@ class RespondToController < ActionController::Base
respond_to do |format|
format.html { render body: "HTML" }
format.any(:js, :xml) do |variant|
- variant.phone{ render body: "phone" }
- variant.any(:tablet, :phablet){ render body: "tablet" }
+ variant.phone { render body: "phone" }
+ variant.any(:tablet, :phablet) { render body: "tablet" }
end
end
end
- protected
+ private
def set_layout
case action_name
when "all_types_with_layout", "iphone_with_html_response_type"
@@ -674,7 +674,7 @@ class RespondToControllerTest < ActionController::TestCase
get :variant_without_implicit_template_rendering, xhr: true, params: { v: :does_not_matter }
assert_response :no_content
- assert_equal 1, logger.logged(:info).select{ |s| s == NO_CONTENT_WARNING }.size, "Implicit head :no_content not logged"
+ assert_equal 1, logger.logged(:info).select { |s| s == NO_CONTENT_WARNING }.size, "Implicit head :no_content not logged"
ensure
ActionController::Base.logger = old_logger
end
@@ -686,7 +686,7 @@ class RespondToControllerTest < ActionController::TestCase
get :variant_without_implicit_template_rendering, format: "json", params: { v: :does_not_matter }
assert_response :no_content
- assert_equal 1, logger.logged(:info).select{ |s| s == NO_CONTENT_WARNING }.size, "Implicit head :no_content not logged"
+ assert_equal 1, logger.logged(:info).select { |s| s == NO_CONTENT_WARNING }.size, "Implicit head :no_content not logged"
ensure
ActionController::Base.logger = old_logger
end
diff --git a/actionpack/test/controller/new_base/bare_metal_test.rb b/actionpack/test/controller/new_base/bare_metal_test.rb
index 9956f0b107..054757fab3 100644
--- a/actionpack/test/controller/new_base/bare_metal_test.rb
+++ b/actionpack/test/controller/new_base/bare_metal_test.rb
@@ -52,7 +52,7 @@ module BareMetalTest
controller.set_request!(ActionDispatch::Request.empty)
controller.set_response!(BareController.make_response!(controller.request))
controller.index
- assert_equal nil, controller.response_body
+ assert_nil controller.response_body
end
end
diff --git a/actionpack/test/controller/new_base/base_test.rb b/actionpack/test/controller/new_base/base_test.rb
index 3765d406fc..b891df4c0f 100644
--- a/actionpack/test/controller/new_base/base_test.rb
+++ b/actionpack/test/controller/new_base/base_test.rb
@@ -25,7 +25,7 @@ module Dispatching
render body: "actions: #{action_methods.to_a.sort.join(', ')}"
end
- protected
+ private
def authenticate
end
end
diff --git a/actionpack/test/controller/new_base/render_context_test.rb b/actionpack/test/controller/new_base/render_context_test.rb
index 40149a41d3..25b73ac78c 100644
--- a/actionpack/test/controller/new_base/render_context_test.rb
+++ b/actionpack/test/controller/new_base/render_context_test.rb
@@ -26,16 +26,14 @@ module RenderContext
render action: "hello_world", layout: "basic"
end
- protected
+ protected def __controller_method__
+ "controller context!"
+ end
# 3) Set view_context to self
- def view_context
- self
- end
-
- def __controller_method__
- "controller context!"
- end
+ private def view_context
+ self
+ end
end
class RenderContextTest < Rack::TestCase
diff --git a/actionpack/test/controller/new_base/render_file_test.rb b/actionpack/test/controller/new_base/render_file_test.rb
index 9618157657..6d651e0104 100644
--- a/actionpack/test/controller/new_base/render_file_test.rb
+++ b/actionpack/test/controller/new_base/render_file_test.rb
@@ -30,7 +30,7 @@ module RenderFile
def with_locals
path = File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_locals")
- render file: path, locals: {secret: "in the sauce"}
+ render file: path, locals: { secret: "in the sauce" }
end
end
diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb
index 5cd8f82323..1177b8b03e 100644
--- a/actionpack/test/controller/new_base/render_streaming_test.rb
+++ b/actionpack/test/controller/new_base/render_streaming_test.rb
@@ -101,12 +101,12 @@ module RenderStreaming
assert_body "Hello world, I'm here!"
assert_status 200
assert_equal "22", headers["Content-Length"]
- assert_equal nil, headers["Transfer-Encoding"]
+ assert_nil headers["Transfer-Encoding"]
end
- def assert_streaming!(cache="no-cache")
+ def assert_streaming!(cache = "no-cache")
assert_status 200
- assert_equal nil, headers["Content-Length"]
+ assert_nil headers["Content-Length"]
assert_equal "chunked", headers["Transfer-Encoding"]
assert_equal cache, headers["Cache-Control"]
end
diff --git a/actionpack/test/controller/new_base/render_text_test.rb b/actionpack/test/controller/new_base/render_text_test.rb
deleted file mode 100644
index 6f55c497b4..0000000000
--- a/actionpack/test/controller/new_base/render_text_test.rb
+++ /dev/null
@@ -1,188 +0,0 @@
-require "abstract_unit"
-
-module RenderText
- class MinimalController < ActionController::Metal
- include AbstractController::Rendering
- include ActionController::Rendering
-
- def index
- render text: "Hello World!"
- end
- end
-
- class SimpleController < ActionController::Base
- self.view_paths = [ActionView::FixtureResolver.new]
-
- def index
- render text: "hello david"
- end
- end
-
- class WithLayoutController < ::ApplicationController
- self.view_paths = [ActionView::FixtureResolver.new(
- "layouts/application.html.erb" => "<%= yield %>, I'm here!",
- "layouts/greetings.html.erb" => "<%= yield %>, I wish thee well.",
- "layouts/ivar.html.erb" => "<%= yield %>, <%= @ivar %>"
- )]
-
- def index
- render text: "hello david"
- end
-
- def custom_code
- render text: "hello world", status: 404
- end
-
- def with_custom_code_as_string
- render text: "hello world", status: "404 Not Found"
- end
-
- def with_nil
- render text: nil
- end
-
- def with_nil_and_status
- render text: nil, status: 403
- end
-
- def with_false
- render text: false
- end
-
- def with_layout_true
- render text: "hello world", layout: true
- end
-
- def with_layout_false
- render text: "hello world", layout: false
- end
-
- def with_layout_nil
- render text: "hello world", layout: nil
- end
-
- def with_custom_layout
- render text: "hello world", layout: "greetings"
- end
-
- def with_ivar_in_layout
- @ivar = "hello world"
- render text: "hello world", layout: "ivar"
- end
- end
-
- class RenderTextTest < Rack::TestCase
- test "rendering text from a minimal controller" do
- ActiveSupport::Deprecation.silence do
- get "/render_text/minimal/index"
- end
-
- assert_body "Hello World!"
- assert_status 200
- end
-
- test "rendering text from an action with default options renders the text with the layout" do
- with_routing do |set|
- set.draw { ActiveSupport::Deprecation.silence { get ":controller", action: "index" } }
-
- ActiveSupport::Deprecation.silence do
- get "/render_text/simple"
- end
-
- assert_body "hello david"
- assert_status 200
- end
- end
-
- test "rendering text from an action with default options renders the text without the layout" do
- with_routing do |set|
- set.draw { ActiveSupport::Deprecation.silence { get ":controller", action: "index" } }
-
- ActiveSupport::Deprecation.silence do
- get "/render_text/with_layout"
- end
-
- assert_body "hello david"
- assert_status 200
- end
- end
-
- test "rendering text, while also providing a custom status code" do
- ActiveSupport::Deprecation.silence do
- get "/render_text/with_layout/custom_code"
- end
-
- assert_body "hello world"
- assert_status 404
- end
-
- test "rendering text with nil returns an empty body" do
- ActiveSupport::Deprecation.silence do
- get "/render_text/with_layout/with_nil"
- end
-
- assert_body ""
- assert_status 200
- end
-
- test "Rendering text with nil and custom status code returns an empty body and the status" do
- ActiveSupport::Deprecation.silence do
- get "/render_text/with_layout/with_nil_and_status"
- end
-
- assert_body ""
- assert_status 403
- end
-
- test "rendering text with false returns the string 'false'" do
- ActiveSupport::Deprecation.silence do
- get "/render_text/with_layout/with_false"
- end
-
- assert_body "false"
- assert_status 200
- end
-
- test "rendering text with layout: true" do
- ActiveSupport::Deprecation.silence do
- get "/render_text/with_layout/with_layout_true"
- end
-
- assert_body "hello world, I'm here!"
- assert_status 200
- end
-
- test "rendering text with layout: 'greetings'" do
- ActiveSupport::Deprecation.silence do
- get "/render_text/with_layout/with_custom_layout"
- end
-
- assert_body "hello world, I wish thee well."
- assert_status 200
- end
-
- test "rendering text with layout: false" do
- ActiveSupport::Deprecation.silence do
- get "/render_text/with_layout/with_layout_false"
- end
-
- assert_body "hello world"
- assert_status 200
- end
-
- test "rendering text with layout: nil" do
- ActiveSupport::Deprecation.silence do
- get "/render_text/with_layout/with_layout_nil"
- end
-
- assert_body "hello world"
- assert_status 200
- end
-
- test "rendering text displays deprecation warning" do
- assert_deprecated do
- get "/render_text/with_layout/with_layout_nil"
- end
- end
- end
-end
diff --git a/actionpack/test/controller/parameter_encoding_test.rb b/actionpack/test/controller/parameter_encoding_test.rb
index 69a72c000b..234d0bddd1 100644
--- a/actionpack/test/controller/parameter_encoding_test.rb
+++ b/actionpack/test/controller/parameter_encoding_test.rb
@@ -1,9 +1,8 @@
require "abstract_unit"
class ParameterEncodingController < ActionController::Base
- parameter_encoding :test_bar, :bar, Encoding::ASCII_8BIT
- parameter_encoding :test_baz, :baz, Encoding::ISO_8859_1
- parameter_encoding :test_baz_to_ascii, :baz, Encoding::ASCII_8BIT
+ skip_parameter_encoding :test_bar
+ skip_parameter_encoding :test_all_values_encoding
def test_foo
render body: params[:foo].encoding
@@ -13,16 +12,8 @@ class ParameterEncodingController < ActionController::Base
render body: params[:bar].encoding
end
- def test_baz
- render body: params[:baz].encoding
- end
-
- def test_no_change_to_baz
- render body: params[:baz].encoding
- end
-
- def test_baz_to_ascii
- render body: params[:baz].encoding
+ def test_all_values_encoding
+ render body: ::JSON.dump(params.values.map(&:encoding).map(&:name))
end
end
@@ -30,38 +21,24 @@ class ParameterEncodingTest < ActionController::TestCase
tests ParameterEncodingController
test "properly transcodes UTF8 parameters into declared encodings" do
- post :test_foo, params: {"foo" => "foo", "bar" => "bar", "baz" => "baz"}
+ post :test_foo, params: { "foo" => "foo", "bar" => "bar", "baz" => "baz" }
assert_response :success
assert_equal "UTF-8", @response.body
end
- test "properly transcodes ASCII_8BIT parameters into declared encodings" do
- post :test_bar, params: {"foo" => "foo", "bar" => "bar", "baz" => "baz"}
+ test "properly encodes ASCII_8BIT parameters into binary" do
+ post :test_bar, params: { "foo" => "foo", "bar" => "bar", "baz" => "baz" }
assert_response :success
assert_equal "ASCII-8BIT", @response.body
end
- test "properly transcodes ISO_8859_1 parameters into declared encodings" do
- post :test_baz, params: {"foo" => "foo", "bar" => "bar", "baz" => "baz"}
-
- assert_response :success
- assert_equal "ISO-8859-1", @response.body
- end
-
- test "does not transcode parameters when not specified" do
- post :test_no_change_to_baz, params: {"foo" => "foo", "bar" => "bar", "baz" => "baz"}
+ test "properly encodes all ASCII_8BIT parameters into binary" do
+ post :test_all_values_encoding, params: { "foo" => "foo", "bar" => "bar", "baz" => "baz" }
assert_response :success
- assert_equal "UTF-8", @response.body
- end
-
- test "respects different encoding declarations for a param per action" do
- post :test_baz_to_ascii, params: {"foo" => "foo", "bar" => "bar", "baz" => "baz"}
-
- assert_response :success
- assert_equal "ASCII-8BIT", @response.body
+ assert_equal ["ASCII-8BIT"], JSON.parse(@response.body).uniq
end
test "does not raise an error when passed a param declared as ASCII-8BIT that contains invalid bytes" do
diff --git a/actionpack/test/controller/parameters/accessors_test.rb b/actionpack/test/controller/parameters/accessors_test.rb
index a63512e8bd..2893eb7b91 100644
--- a/actionpack/test/controller/parameters/accessors_test.rb
+++ b/actionpack/test/controller/parameters/accessors_test.rb
@@ -13,7 +13,7 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
first: "David",
last: "Heinemeier Hansson"
},
- addresses: [{city: "Chicago", state: "Illinois"}]
+ addresses: [{ city: "Chicago", state: "Illinois" }]
}
)
end
@@ -131,14 +131,6 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
assert_not @params[:person].values_at(:name).first.permitted?
end
- test "equality with a hash is deprecated" do
- hash1 = { foo: :bar }
- params1 = ActionController::Parameters.new(hash1)
- assert_deprecated("will be removed in Rails 5.1") do
- assert(params1 == hash1)
- end
- end
-
test "is equal to Parameters instance with same params" do
params1 = ActionController::Parameters.new(a: 1, b: 2)
params2 = ActionController::Parameters.new(a: 1, b: 2)
diff --git a/actionpack/test/controller/parameters/dup_test.rb b/actionpack/test/controller/parameters/dup_test.rb
index 152f608266..fb707a1354 100644
--- a/actionpack/test/controller/parameters/dup_test.rb
+++ b/actionpack/test/controller/parameters/dup_test.rb
@@ -1,5 +1,6 @@
require "abstract_unit"
require "action_controller/metal/strong_parameters"
+require "active_support/core_ext/object/deep_dup"
class ParametersDupTest < ActiveSupport::TestCase
setup do
@@ -12,7 +13,7 @@ class ParametersDupTest < ActiveSupport::TestCase
first: "David",
last: "Heinemeier Hansson"
},
- addresses: [{city: "Chicago", state: "Illinois"}]
+ addresses: [{ city: "Chicago", state: "Illinois" }]
}
)
end
@@ -40,4 +41,25 @@ class ParametersDupTest < ActiveSupport::TestCase
dupped_params.permit!
assert_not_equal @params, dupped_params
end
+
+ test "deep_dup content" do
+ dupped_params = @params.deep_dup
+ dupped_params[:person][:age] = "45"
+ dupped_params[:person][:addresses].clear
+
+ assert_not_equal @params[:person][:age], dupped_params[:person][:age]
+ assert_not_equal @params[:person][:addresses], dupped_params[:person][:addresses]
+ end
+
+ test "deep_dup @permitted" do
+ dupped_params = @params.deep_dup
+ dupped_params.permit!
+
+ assert_not @params.permitted?
+ end
+
+ test "deep_dup @permitted is being copied" do
+ @params.permit!
+ assert @params.deep_dup.permitted?
+ end
end
diff --git a/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb b/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb
index 0358fd9976..c800c1d3df 100644
--- a/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb
+++ b/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb
@@ -15,7 +15,7 @@ class LogOnUnpermittedParamsTest < ActiveSupport::TestCase
book: { pages: 65 },
fishing: "Turnips")
- assert_logged("Unpermitted parameter: fishing") do
+ assert_logged("Unpermitted parameter: :fishing") do
params.permit(book: [:pages])
end
end
@@ -26,7 +26,7 @@ class LogOnUnpermittedParamsTest < ActiveSupport::TestCase
fishing: "Turnips",
car: "Mersedes")
- assert_logged("Unpermitted parameters: fishing, car") do
+ assert_logged("Unpermitted parameters: :fishing, :car") do
params.permit(book: [:pages])
end
end
@@ -35,7 +35,7 @@ class LogOnUnpermittedParamsTest < ActiveSupport::TestCase
params = ActionController::Parameters.new(
book: { pages: 65, title: "Green Cats and where to find then." })
- assert_logged("Unpermitted parameter: title") do
+ assert_logged("Unpermitted parameter: :title") do
params.permit(book: [:pages])
end
end
@@ -44,7 +44,7 @@ class LogOnUnpermittedParamsTest < ActiveSupport::TestCase
params = ActionController::Parameters.new(
book: { pages: 65, title: "Green Cats and where to find then.", author: "G. A. Dog" })
- assert_logged("Unpermitted parameters: title, author") do
+ assert_logged("Unpermitted parameters: :title, :author") do
params.permit(book: [:pages])
end
end
diff --git a/actionpack/test/controller/parameters/mutators_test.rb b/actionpack/test/controller/parameters/mutators_test.rb
index a2dc84f2f7..e61bbdbe13 100644
--- a/actionpack/test/controller/parameters/mutators_test.rb
+++ b/actionpack/test/controller/parameters/mutators_test.rb
@@ -11,7 +11,7 @@ class ParametersMutatorsTest < ActiveSupport::TestCase
first: "David",
last: "Heinemeier Hansson"
},
- addresses: [{city: "Chicago", state: "Illinois"}]
+ addresses: [{ city: "Chicago", state: "Illinois" }]
}
)
end
@@ -45,11 +45,11 @@ class ParametersMutatorsTest < ActiveSupport::TestCase
test "keep_if retains permitted status" do
@params.permit!
- assert @params.keep_if { |k,v| k == "person" }.permitted?
+ assert @params.keep_if { |k, v| k == "person" }.permitted?
end
test "keep_if retains unpermitted status" do
- assert_not @params.keep_if { |k,v| k == "person" }.permitted?
+ assert_not @params.keep_if { |k, v| k == "person" }.permitted?
end
test "reject! retains permitted status" do
diff --git a/actionpack/test/controller/parameters/nested_parameters_permit_test.rb b/actionpack/test/controller/parameters/nested_parameters_permit_test.rb
index f0155477c4..5f86901e30 100644
--- a/actionpack/test/controller/parameters/nested_parameters_permit_test.rb
+++ b/actionpack/test/controller/parameters/nested_parameters_permit_test.rb
@@ -73,7 +73,7 @@ class NestedParametersPermitTest < ActiveSupport::TestCase
genres: ["Tragedy"]
})
- permitted = params.permit book: {genres: []}
+ permitted = params.permit book: { genres: [] }
assert_equal ["Tragedy"], permitted[:book][:genres]
end
@@ -85,7 +85,7 @@ class NestedParametersPermitTest < ActiveSupport::TestCase
},
magazine: "Shakespeare Today")
- permitted = params.permit({book: ["title", :author]}, "magazine")
+ permitted = params.permit({ book: ["title", :author] }, "magazine")
assert_equal "Romeo and Juliet", permitted[:book][:title]
assert_equal "William Shakespeare", permitted[:book][:author]
assert_equal "Shakespeare Today", permitted[:magazine]
diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb
index b42288636e..b62a3d6d7b 100644
--- a/actionpack/test/controller/parameters/parameters_permit_test.rb
+++ b/actionpack/test/controller/parameters/parameters_permit_test.rb
@@ -15,7 +15,7 @@ class ParametersPermitTest < ActiveSupport::TestCase
first: "David",
last: "Heinemeier Hansson"
},
- addresses: [{city: "Chicago", state: "Illinois"}]
+ addresses: [{ city: "Chicago", state: "Illinois" }]
}
)
@@ -28,7 +28,7 @@ class ParametersPermitTest < ActiveSupport::TestCase
end
def walk_permitted(params)
- params.each do |k,v|
+ params.each do |k, v|
case v
when ActionController::Parameters
walk_permitted v
@@ -39,13 +39,13 @@ class ParametersPermitTest < ActiveSupport::TestCase
end
test "iteration should not impact permit" do
- hash = {"foo"=>{"bar"=>{"0"=>{"baz"=>"hello", "zot"=>"1"}}}}
+ hash = { "foo" => { "bar" => { "0" => { "baz" => "hello", "zot" => "1" } } } }
params = ActionController::Parameters.new(hash)
walk_permitted params
sanitized = params[:foo].permit(bar: [:baz])
- assert_equal({"0"=>{"baz"=>"hello"}}, sanitized[:bar].to_unsafe_h)
+ assert_equal({ "0" => { "baz" => "hello" } }, sanitized[:bar].to_unsafe_h)
end
test "if nothing is permitted, the hash becomes empty" do
@@ -98,7 +98,7 @@ class ParametersPermitTest < ActiveSupport::TestCase
end
test "key: hashes are filtered out" do
- [{}, {foo: 1}, {foo: "bar"}].each do |hash|
+ [{}, { foo: 1 }, { foo: "bar" }].each do |hash|
params = ActionController::Parameters.new(id: hash)
permitted = params.permit(:id)
assert_filtered_out permitted, :id
@@ -141,7 +141,7 @@ class ParametersPermitTest < ActiveSupport::TestCase
permitted = params.permit(:a, c: [], b: [])
assert_equal 1, permitted[:a]
assert_equal [1, 2, 3], permitted[:b]
- assert_equal nil, permitted[:c]
+ assert_nil permitted[:c]
end
test "key to empty array: arrays of permitted scalars pass" do
@@ -161,13 +161,51 @@ class ParametersPermitTest < ActiveSupport::TestCase
end
test "key to empty array: arrays of non-permitted scalar do not pass" do
- [[Object.new], [[]], [[1]], [{}], [{id: "1"}]].each do |non_permitted_scalar|
+ [[Object.new], [[]], [[1]], [{}], [{ id: "1" }]].each do |non_permitted_scalar|
params = ActionController::Parameters.new(id: non_permitted_scalar)
permitted = params.permit(id: [])
assert_filtered_out permitted, :id
end
end
+ test "key to empty hash: arbitrary hashes are permitted" do
+ params = ActionController::Parameters.new(
+ username: "fxn",
+ preferences: {
+ scheme: "Marazul",
+ font: {
+ name: "Source Code Pro",
+ size: 12
+ },
+ tabstops: [4, 8, 12, 16],
+ suspicious: [true, Object.new, false, /yo!/],
+ dubious: [{ a: :a, b: /wtf!/ }, { c: :c }],
+ injected: Object.new
+ },
+ hacked: 1 # not a hash
+ )
+
+ permitted = params.permit(:username, preferences: {}, hacked: {})
+
+ assert permitted.permitted?
+ assert permitted[:preferences].permitted?
+ assert permitted[:preferences][:font].permitted?
+ assert permitted[:preferences][:dubious].all?(&:permitted?)
+
+ assert_equal "fxn", permitted[:username]
+ assert_equal "Marazul", permitted[:preferences][:scheme]
+ assert_equal "Source Code Pro", permitted[:preferences][:font][:name]
+ assert_equal 12, permitted[:preferences][:font][:size]
+ assert_equal [4, 8, 12, 16], permitted[:preferences][:tabstops]
+ assert_equal [true, false], permitted[:preferences][:suspicious]
+ assert_equal :a, permitted[:preferences][:dubious][0][:a]
+ assert_equal :c, permitted[:preferences][:dubious][1][:c]
+
+ assert_filtered_out permitted[:preferences][:dubious][0], :b
+ assert_filtered_out permitted[:preferences], :injected
+ assert_filtered_out permitted, :hacked
+ end
+
test "fetch raises ParameterMissing exception" do
e = assert_raises(ActionController::ParameterMissing) do
@params.fetch :foo
@@ -178,7 +216,7 @@ class ParametersPermitTest < ActiveSupport::TestCase
test "fetch with a default value of a hash does not mutate the object" do
params = ActionController::Parameters.new({})
params.fetch :foo, {}
- assert_equal nil, params[:foo]
+ assert_nil params[:foo]
end
test "hashes in array values get wrapped" do
@@ -203,10 +241,10 @@ class ParametersPermitTest < ActiveSupport::TestCase
# the cache does not get fooled, the hash is still wrapped as strong params,
# and not permitted.
test "mutated arrays are detected" do
- params = ActionController::Parameters.new(users: [{id: 1}])
+ params = ActionController::Parameters.new(users: [{ id: 1 }])
permitted = params.permit(users: [:id])
- permitted[:users] << {injected: 1}
+ permitted[:users] << { injected: 1 }
assert_not permitted[:users].last.permitted?
end
@@ -216,8 +254,8 @@ class ParametersPermitTest < ActiveSupport::TestCase
end
test "fetch doesnt raise ParameterMissing exception if there is a default that is nil" do
- assert_equal nil, @params.fetch(:foo, nil)
- assert_equal nil, @params.fetch(:foo) { nil }
+ assert_nil @params.fetch(:foo, nil)
+ assert_nil @params.fetch(:foo) { nil }
end
test "KeyError in fetch block should not be covered up" do
@@ -237,6 +275,30 @@ class ParametersPermitTest < ActiveSupport::TestCase
assert @params.merge(a: "b").permitted?
end
+ test "merge with parameters" do
+ other_params = ActionController::Parameters.new(id: "1234").permit!
+ merged_params = @params.merge(other_params)
+
+ assert merged_params[:id]
+ end
+
+ test "not permitted is sticky beyond merge!" do
+ assert_not @params.merge!(a: "b").permitted?
+ end
+
+ test "permitted is sticky beyond merge!" do
+ @params.permit!
+ assert @params.merge!(a: "b").permitted?
+ end
+
+ test "merge! with parameters" do
+ other_params = ActionController::Parameters.new(id: "1234").permit!
+ @params.merge!(other_params)
+
+ assert_equal "1234", @params[:id]
+ assert_equal "32", @params[:person][:age]
+ end
+
test "modifying the parameters" do
@params[:person][:hometown] = "Chicago"
@params[:person][:family] = { brother: "Jonas" }
@@ -315,8 +377,8 @@ class ParametersPermitTest < ActiveSupport::TestCase
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"]}}
+ params = ActionController::Parameters.new("f" => { "language_facet" => ["Tibetan"] })
+ expected = { "f" => { "language_facet" => ["Tibetan"] } }
assert params["f"].is_a? ActionController::Parameters
assert_equal expected, params.to_unsafe_h
diff --git a/actionpack/test/controller/parameters/serialization_test.rb b/actionpack/test/controller/parameters/serialization_test.rb
index 4fb1564c68..6fba2fde91 100644
--- a/actionpack/test/controller/parameters/serialization_test.rb
+++ b/actionpack/test/controller/parameters/serialization_test.rb
@@ -14,12 +14,10 @@ class ParametersSerializationTest < ActiveSupport::TestCase
test "yaml serialization" do
params = ActionController::Parameters.new(key: :value)
- assert_equal <<-end_of_yaml.strip_heredoc, YAML.dump(params)
- --- !ruby/object:ActionController::Parameters
- parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
- key: :value
- permitted: false
- end_of_yaml
+ yaml_dump = YAML.dump(params)
+ assert_match("--- !ruby/object:ActionController::Parameters", yaml_dump)
+ assert_match(/parameters: !ruby\/hash:ActiveSupport::HashWithIndifferentAccess\n\s+key: :value/, yaml_dump)
+ assert_match("permitted: false", yaml_dump)
end
test "yaml deserialization" do
diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb
index 7800ff26d0..faa57c4559 100644
--- a/actionpack/test/controller/params_wrapper_test.rb
+++ b/actionpack/test/controller/params_wrapper_test.rb
@@ -50,7 +50,7 @@ class ParamsWrapperTest < ActionController::TestCase
with_default_wrapper_options do
@request.env["CONTENT_TYPE"] = "application/json"
post :parse, params: { "username" => "sikachu" }
- assert_equal @request.filtered_parameters, "controller" => "params_wrapper_test/users", "action" => "parse", "username" => "sikachu", "user" => { "username" => "sikachu" }
+ assert_equal({ "controller" => "params_wrapper_test/users", "action" => "parse", "username" => "sikachu", "user" => { "username" => "sikachu" } }, @request.filtered_parameters)
end
end
@@ -150,7 +150,7 @@ class ParamsWrapperTest < ActionController::TestCase
def test_no_double_wrap_if_key_exists
with_default_wrapper_options do
@request.env["CONTENT_TYPE"] = "application/json"
- post :parse, params: { "user" => { "username" => "sikachu" }}
+ post :parse, params: { "user" => { "username" => "sikachu" } }
assert_parameters("user" => { "username" => "sikachu" })
end
end
@@ -158,8 +158,8 @@ class ParamsWrapperTest < ActionController::TestCase
def test_nested_params
with_default_wrapper_options do
@request.env["CONTENT_TYPE"] = "application/json"
- post :parse, params: { "person" => { "username" => "sikachu" }}
- assert_parameters("person" => { "username" => "sikachu" }, "user" => {"person" => { "username" => "sikachu" }})
+ post :parse, params: { "person" => { "username" => "sikachu" } }
+ assert_parameters("person" => { "username" => "sikachu" }, "user" => { "person" => { "username" => "sikachu" } })
end
end
@@ -208,10 +208,20 @@ class ParamsWrapperTest < ActionController::TestCase
@request.env["CONTENT_TYPE"] = "application/json"
post :parse, params: {}
assert_parameters(
- "user" => { }
+ "user" => {}
)
end
end
+
+ def test_handles_empty_content_type
+ with_default_wrapper_options do
+ @request.env["CONTENT_TYPE"] = nil
+ _controller_class.dispatch(:parse, @request, @response)
+
+ assert_equal 200, @response.status
+ assert_equal "", @response.body
+ end
+ end
end
class NamespacedParamsWrapperTest < ActionController::TestCase
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index 0539580e14..e4e968dfdb 100644
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -33,7 +33,7 @@ class RedirectController < ActionController::Base
end
def redirect_with_status_hash
- redirect_to({action: "hello_world"}, status: 301)
+ redirect_to({ action: "hello_world" }, status: 301)
end
def redirect_with_protocol
@@ -56,10 +56,6 @@ class RedirectController < ActionController::Base
redirect_to("/things/stuff", status: 301)
end
- def redirect_to_back_with_status
- redirect_to :back, status: 307
- end
-
def redirect_back_with_status
redirect_back(fallback_location: "/things/stuff", status: 307)
end
@@ -88,10 +84,6 @@ class RedirectController < ActionController::Base
redirect_to "//www.rubyonrails.org/"
end
- def redirect_to_back
- redirect_to :back
- end
-
def redirect_to_existing_record
redirect_to Workshop.new(5)
end
@@ -118,7 +110,7 @@ class RedirectController < ActionController::Base
end
def redirect_to_with_block_and_options
- redirect_to proc { {action: "hello_world"} }
+ redirect_to proc { { action: "hello_world" } }
end
def redirect_with_header_break
@@ -131,7 +123,7 @@ class RedirectController < ActionController::Base
def rescue_errors(e) raise e end
- protected
+ private
def dashbord_url(id, message)
url_for action: "dashboard", params: { "id" => id, "message" => message }
end
@@ -206,17 +198,6 @@ class RedirectTest < ActionController::TestCase
assert_equal "http://test.host/things/stuff", redirect_to_url
end
- def test_redirect_to_back_with_status
- @request.env["HTTP_REFERER"] = "http://www.example.com/coming/from"
-
- assert_deprecated do
- get :redirect_to_back_with_status
- end
-
- assert_response 307
- assert_equal "http://www.example.com/coming/from", redirect_to_url
- end
-
def test_simple_redirect_using_options
get :host_redirect
assert_response :redirect
@@ -259,29 +240,6 @@ class RedirectTest < ActionController::TestCase
assert_equal "//www.rubyonrails.org/", redirect_to_url
end
- def test_redirect_to_back
- @request.env["HTTP_REFERER"] = "http://www.example.com/coming/from"
-
- assert_deprecated do
- get :redirect_to_back
- end
-
- assert_response :redirect
- assert_equal "http://www.example.com/coming/from", redirect_to_url
- end
-
- def test_redirect_to_back_with_no_referer
- assert_raise(ActionController::RedirectBackError) {
- @request.env["HTTP_REFERER"] = nil
-
- assert_deprecated do
- get :redirect_to_back
- end
-
- get :redirect_to_back
- }
- end
-
def test_redirect_back
referer = "http://www.example.com/coming/from"
@request.env["HTTP_REFERER"] = referer
diff --git a/actionpack/test/controller/render_json_test.rb b/actionpack/test/controller/render_json_test.rb
index 31b11588fd..79552ec8f1 100644
--- a/actionpack/test/controller/render_json_test.rb
+++ b/actionpack/test/controller/render_json_test.rb
@@ -5,7 +5,7 @@ require "pathname"
class RenderJsonTest < ActionController::TestCase
class JsonRenderable
- def as_json(options={})
+ def as_json(options = {})
hash = { a: :b, c: :d, e: :f }
hash.except!(*options[:except]) if options[:except]
hash
@@ -52,7 +52,7 @@ class RenderJsonTest < ActionController::TestCase
end
def render_json_with_render_to_string
- render json: {hello: render_to_string(partial: "partial")}
+ render json: { hello: render_to_string(partial: "partial") }
end
def render_json_with_extra_options
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index ba556c1bff..0c0f18f200 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -160,10 +160,6 @@ class TestController < ActionController::Base
render action: "hello_world"
end
- def respond_with_empty_body
- render nothing: true
- end
-
def conditional_hello_with_bangs
render action: "hello_world"
end
@@ -173,14 +169,6 @@ class TestController < ActionController::Base
fresh_when(last_modified: Time.now.utc.beginning_of_day, etag: [ :foo, 123 ])
end
- def head_with_status_hash
- head status: :created
- end
-
- def head_with_hash_does_not_include_status
- head warning: :deprecated
- end
-
def head_created
head :created
end
@@ -226,7 +214,7 @@ class TestController < ActionController::Base
end
def head_and_return
- head :ok and return
+ head(:ok) && return
raise "should not reach this line"
end
@@ -379,14 +367,8 @@ class ExpiresInRenderTest < ActionController::TestCase
assert_match(/no-transform/, @response.headers["Cache-Control"])
end
- def test_render_nothing_deprecated
- assert_deprecated do
- get :respond_with_empty_body
- end
- end
-
def test_date_header_when_expires_in
- time = Time.mktime(2011,10,30)
+ time = Time.mktime(2011, 10, 30)
Time.stub :now, time do
get :conditional_hello_with_expires_in
assert_equal Time.now.httpdate, @response.headers["Date"]
@@ -670,19 +652,6 @@ class HeadRenderTest < ActionController::TestCase
assert_response :created
end
- def test_passing_hash_to_head_as_first_parameter_deprecated
- assert_deprecated do
- get :head_with_status_hash
- end
- end
-
- def test_head_with_default_value_is_deprecated
- assert_deprecated do
- get :head_with_hash_does_not_include_status
- assert_response :ok
- end
- end
-
def test_head_created_with_application_json_content_type
post :head_created_with_application_json_content_type
assert @response.body.blank?
@@ -746,7 +715,7 @@ class HeadRenderTest < ActionController::TestCase
get :head_with_symbolic_status, params: { status: "no_content" }
assert_equal 204, @response.status
- assert !@response.headers.include?("Content-Length")
+ assert_not_includes @response.headers, "Content-Length"
assert_response :no_content
Rack::Utils::SYMBOL_TO_STATUS_CODE.each do |status, code|
@@ -805,7 +774,7 @@ class HttpCacheForeverTest < ActionController::TestCase
tests HttpCacheForeverController
def test_cache_with_public
- get :cache_me_forever, params: {public: true}
+ get :cache_me_forever, params: { public: true }
assert_response :ok
assert_equal "max-age=#{100.years}, public", @response.headers["Cache-Control"]
assert_not_nil @response.etag
diff --git a/actionpack/test/controller/renderer_test.rb b/actionpack/test/controller/renderer_test.rb
index d6f09f2d90..866600b935 100644
--- a/actionpack/test/controller/renderer_test.rb
+++ b/actionpack/test/controller/renderer_test.rb
@@ -60,6 +60,14 @@ class RendererTest < ActiveSupport::TestCase
assert_equal "true", content
end
+ test "rendering with custom env using a key that is not in RACK_KEY_TRANSLATION" do
+ value = "warden is here"
+ renderer = ApplicationController.renderer.new warden: value
+ content = renderer.render inline: "<%= request.env['warden'] %>"
+
+ assert_equal value, content
+ end
+
test "rendering with defaults" do
renderer = ApplicationController.renderer.new https: true
content = renderer.render inline: "<%= request.ssl? %>"
diff --git a/actionpack/test/controller/renderers_test.rb b/actionpack/test/controller/renderers_test.rb
index 122f5be549..ccc700d79c 100644
--- a/actionpack/test/controller/renderers_test.rb
+++ b/actionpack/test/controller/renderers_test.rb
@@ -10,7 +10,7 @@ class RenderersTest < ActionController::TestCase
end
end
class JsonRenderable
- def as_json(options={})
+ def as_json(options = {})
hash = { a: :b, c: :d, e: :f }
hash.except!(*options[:except]) if options[:except]
hash
diff --git a/actionpack/test/controller/request/test_request_test.rb b/actionpack/test/controller/request/test_request_test.rb
index 6e0ca957c3..b67ae72c0a 100644
--- a/actionpack/test/controller/request/test_request_test.rb
+++ b/actionpack/test/controller/request/test_request_test.rb
@@ -6,6 +6,21 @@ class ActionController::TestRequestTest < ActionController::TestCase
assert @request.session_options
end
+ def test_mutating_session_options_does_not_affect_default_options
+ @request.session_options[:myparam] = 123
+ assert_nil ActionController::TestSession::DEFAULT_OPTIONS[:myparam]
+ end
+
+ def test_content_length_has_bytes_count_value
+ non_ascii_parameters = { data: { content: "Latin + Кириллица" } }
+ @request.set_header "REQUEST_METHOD", "POST"
+ @request.set_header "CONTENT_TYPE", "application/json"
+ @request.assign_parameters(@routes, "test", "create", non_ascii_parameters,
+ "/test", [:data, :controller, :action])
+ assert_equal(StringIO.new(non_ascii_parameters.to_json).length.to_s,
+ @request.get_header("CONTENT_LENGTH"))
+ end
+
ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS.each_key do |option|
test "rack default session options #{option} exists in session options and is default" do
assert_equal(ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS[option],
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index 03f112c34d..d645ddfdbe 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -92,7 +92,7 @@ class PrependProtectForgeryBaseController < ActionController::Base
render inline: "OK"
end
- protected
+ private
def add_called_callback(name)
@called_callbacks ||= []
@@ -398,7 +398,7 @@ module RequestForgeryProtectionTests
end
assert_cross_origin_not_blocked { get :same_origin_js, xhr: true }
- assert_cross_origin_not_blocked { get :same_origin_js, xhr: true, format: "js"}
+ assert_cross_origin_not_blocked { get :same_origin_js, xhr: true, format: "js" }
assert_cross_origin_not_blocked do
@request.accept = "text/javascript"
get :negotiate_same_origin, xhr: true
@@ -443,7 +443,7 @@ module RequestForgeryProtectionTests
assert_cross_origin_not_blocked { post :same_origin_js, params: { format: "js", custom_authenticity_token: @token } }
assert_cross_origin_not_blocked do
@request.accept = "text/javascript"
- post :negotiate_same_origin, params: { custom_authenticity_token: @token}
+ post :negotiate_same_origin, params: { custom_authenticity_token: @token }
end
end
@@ -613,7 +613,7 @@ class FreeCookieControllerTest < ActionController::TestCase
def test_should_allow_all_methods_without_token
SecureRandom.stub :base64, @token do
[:post, :patch, :put, :delete].each do |method|
- assert_nothing_raised { send(method, :index)}
+ assert_nothing_raised { send(method, :index) }
end
end
end
@@ -692,7 +692,7 @@ class PerFormTokensControllerTest < ActionController::TestCase
# This is required because PATH_INFO isn't reset between requests.
@request.env["PATH_INFO"] = "/per_form_tokens/post_one"
assert_nothing_raised do
- post :post_one, params: {custom_authenticity_token: form_token}
+ post :post_one, params: { custom_authenticity_token: form_token }
end
assert_response :success
end
@@ -707,7 +707,7 @@ class PerFormTokensControllerTest < ActionController::TestCase
# This is required because PATH_INFO isn't reset between requests.
@request.env["PATH_INFO"] = "/per_form_tokens/post_two"
assert_raises(ActionController::InvalidAuthenticityToken) do
- post :post_two, params: {custom_authenticity_token: form_token}
+ post :post_two, params: { custom_authenticity_token: form_token }
end
end
@@ -721,7 +721,7 @@ class PerFormTokensControllerTest < ActionController::TestCase
# This is required because PATH_INFO isn't reset between requests.
@request.env["PATH_INFO"] = "/per_form_tokens/post_one"
assert_raises(ActionController::InvalidAuthenticityToken) do
- patch :post_one, params: {custom_authenticity_token: form_token}
+ patch :post_one, params: { custom_authenticity_token: form_token }
end
end
@@ -777,13 +777,13 @@ class PerFormTokensControllerTest < ActionController::TestCase
# This is required because PATH_INFO isn't reset between requests.
@request.env["PATH_INFO"] = "/per_form_tokens/post_one"
assert_nothing_raised do
- post :post_one, params: {custom_authenticity_token: token}
+ post :post_one, params: { custom_authenticity_token: token }
end
assert_response :success
end
def test_ignores_params
- get :index, params: {form_path: "/per_form_tokens/post_one?foo=bar"}
+ get :index, params: { form_path: "/per_form_tokens/post_one?foo=bar" }
form_token = assert_presence_and_fetch_form_csrf_token
@@ -792,33 +792,33 @@ class PerFormTokensControllerTest < ActionController::TestCase
# This is required because PATH_INFO isn't reset between requests.
@request.env["PATH_INFO"] = "/per_form_tokens/post_one?foo=baz"
assert_nothing_raised do
- post :post_one, params: {custom_authenticity_token: form_token, baz: "foo"}
+ post :post_one, params: { custom_authenticity_token: form_token, baz: "foo" }
end
assert_response :success
end
def test_ignores_trailing_slash_during_generation
- get :index, params: {form_path: "/per_form_tokens/post_one/"}
+ get :index, params: { form_path: "/per_form_tokens/post_one/" }
form_token = assert_presence_and_fetch_form_csrf_token
# This is required because PATH_INFO isn't reset between requests.
@request.env["PATH_INFO"] = "/per_form_tokens/post_one"
assert_nothing_raised do
- post :post_one, params: {custom_authenticity_token: form_token}
+ post :post_one, params: { custom_authenticity_token: form_token }
end
assert_response :success
end
def test_ignores_origin_during_generation
- get :index, params: {form_path: "https://example.com/per_form_tokens/post_one/"}
+ get :index, params: { form_path: "https://example.com/per_form_tokens/post_one/" }
form_token = assert_presence_and_fetch_form_csrf_token
# This is required because PATH_INFO isn't reset between requests.
@request.env["PATH_INFO"] = "/per_form_tokens/post_one"
assert_nothing_raised do
- post :post_one, params: {custom_authenticity_token: form_token}
+ post :post_one, params: { custom_authenticity_token: form_token }
end
assert_response :success
end
@@ -831,19 +831,19 @@ class PerFormTokensControllerTest < ActionController::TestCase
# This is required because PATH_INFO isn't reset between requests.
@request.env["PATH_INFO"] = "/per_form_tokens/post_one/"
assert_nothing_raised do
- post :post_one, params: {custom_authenticity_token: form_token}
+ post :post_one, params: { custom_authenticity_token: form_token }
end
assert_response :success
end
def test_method_is_case_insensitive
- get :index, params: {form_method: "POST"}
+ get :index, params: { form_method: "POST" }
form_token = assert_presence_and_fetch_form_csrf_token
# This is required because PATH_INFO isn't reset between requests.
@request.env["PATH_INFO"] = "/per_form_tokens/post_one/"
assert_nothing_raised do
- post :post_one, params: {custom_authenticity_token: form_token}
+ post :post_one, params: { custom_authenticity_token: form_token }
end
assert_response :success
end
diff --git a/actionpack/test/controller/required_params_test.rb b/actionpack/test/controller/required_params_test.rb
index c9b0ac874d..dd07c2486b 100644
--- a/actionpack/test/controller/required_params_test.rb
+++ b/actionpack/test/controller/required_params_test.rb
@@ -49,7 +49,7 @@ class ParametersRequireTest < ActiveSupport::TestCase
end
test "require array when all required params are present" do
- safe_params = ActionController::Parameters.new(person: {first_name: "Gaurish", title: "Mjallo", city: "Barcelona"})
+ safe_params = ActionController::Parameters.new(person: { first_name: "Gaurish", title: "Mjallo", city: "Barcelona" })
.require(:person)
.require([:first_name, :title])
@@ -59,7 +59,7 @@ class ParametersRequireTest < ActiveSupport::TestCase
test "require array when a required param is missing" do
assert_raises(ActionController::ParameterMissing) do
- ActionController::Parameters.new(person: {first_name: "Gaurish", title: nil})
+ ActionController::Parameters.new(person: { first_name: "Gaurish", title: nil })
.require(:person)
.require([:first_name, :title])
end
@@ -72,9 +72,9 @@ class ParametersRequireTest < ActiveSupport::TestCase
assert params.value?("cinco")
end
- test "Deprecated methods are deprecated" do
- assert_deprecated do
- ActionController::Parameters.new(foo: "bar").merge!(bar: "foo")
+ test "to_query is not supported" do
+ assert_raises(NoMethodError) do
+ ActionController::Parameters.new(foo: "bar").to_param
end
end
end
diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb
index a98e6479b7..9ae22c4554 100644
--- a/actionpack/test/controller/rescue_test.rb
+++ b/actionpack/test/controller/rescue_test.rb
@@ -149,7 +149,7 @@ class RescueController < ActionController::Base
raise RangeError
end
- protected
+ private
def deny_access
head :forbidden
end
@@ -327,7 +327,7 @@ class RescueTest < ActionDispatch::IntegrationTest
raise "b00m"
end
- protected
+ private
def show_errors(exception)
render plain: exception.message
end
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index 91d3072fdb..fad34dacce 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -27,8 +27,8 @@ class ResourcesTest < ActionController::TestCase
def test_override_paths_for_member_and_collection_methods
collection_methods = { rss: :get, reorder: :post, csv: :post }
- member_methods = { rss: :get, atom: :get, upload: :post, fix: :post }
- path_names = {new: "nuevo", rss: "canal", fix: "corrigir" }
+ member_methods = { rss: :get, atom: :get, upload: :post, fix: :post }
+ path_names = { new: "nuevo", rss: "canal", fix: "corrigir" }
with_restful_routing :messages,
collection: collection_methods,
@@ -77,7 +77,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_multiple_resources_with_options
- expected_options = {controller: "threads", action: "index"}
+ expected_options = { controller: "threads", action: "index" }
with_restful_routing :messages, :comments, expected_options.slice(:controller) do
assert_recognizes(expected_options, path: "comments")
@@ -92,7 +92,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_irregular_id_with_no_constraints_should_raise_error
- expected_options = {controller: "messages", action: "show", id: "1.1.1"}
+ expected_options = { controller: "messages", action: "show", id: "1.1.1" }
with_restful_routing :messages do
assert_raise(Assertion) do
@@ -102,24 +102,24 @@ class ResourcesTest < ActionController::TestCase
end
def test_irregular_id_with_constraints_should_pass
- expected_options = {controller: "messages", action: "show", id: "1.1.1"}
+ expected_options = { controller: "messages", action: "show", id: "1.1.1" }
- with_restful_routing(:messages, constraints: {id: /[0-9]\.[0-9]\.[0-9]/}) do
+ with_restful_routing(:messages, constraints: { id: /[0-9]\.[0-9]\.[0-9]/ }) do
assert_recognizes(expected_options, path: "messages/1.1.1", method: :get)
end
end
def test_with_path_prefix_constraints
- expected_options = {controller: "messages", action: "show", thread_id: "1.1.1", id: "1"}
- with_restful_routing :messages, path_prefix: "/thread/:thread_id", constraints: {thread_id: /[0-9]\.[0-9]\.[0-9]/} do
+ expected_options = { controller: "messages", action: "show", thread_id: "1.1.1", id: "1" }
+ with_restful_routing :messages, path_prefix: "/thread/:thread_id", constraints: { thread_id: /[0-9]\.[0-9]\.[0-9]/ } do
assert_recognizes(expected_options, path: "thread/1.1.1/messages/1", method: :get)
end
end
def test_irregular_id_constraints_should_get_passed_to_member_actions
- expected_options = {controller: "messages", action: "custom", id: "1.1.1"}
+ expected_options = { controller: "messages", action: "custom", id: "1.1.1" }
- with_restful_routing(:messages, member: {custom: :get}, constraints: {id: /[0-9]\.[0-9]\.[0-9]/}) do
+ with_restful_routing(:messages, member: { custom: :get }, constraints: { id: /[0-9]\.[0-9]\.[0-9]/ }) do
assert_recognizes(expected_options, path: "messages/1.1.1/custom", method: :get)
end
end
@@ -261,7 +261,7 @@ class ResourcesTest < ActionController::TestCase
def test_with_member_action
[:patch, :put, :post].each do |method|
with_restful_routing :messages, member: { mark: method } do
- mark_options = {action: "mark", id: "1"}
+ mark_options = { action: "mark", id: "1" }
mark_path = "/messages/1/mark"
assert_restful_routes_for :messages do |options|
assert_recognizes(options.merge(mark_options), path: mark_path, method: method)
@@ -275,24 +275,24 @@ class ResourcesTest < ActionController::TestCase
end
def test_with_member_action_and_requirement
- expected_options = {controller: "messages", action: "mark", id: "1.1.1"}
+ expected_options = { controller: "messages", action: "mark", id: "1.1.1" }
- with_restful_routing(:messages, constraints: {id: /[0-9]\.[0-9]\.[0-9]/}, member: { mark: :get }) do
+ with_restful_routing(:messages, constraints: { id: /[0-9]\.[0-9]\.[0-9]/ }, member: { mark: :get }) do
assert_recognizes(expected_options, path: "messages/1.1.1/mark", method: :get)
end
end
def test_member_when_override_paths_for_default_restful_actions_with
[:patch, :put, :post].each do |method|
- with_restful_routing :messages, member: { mark: method }, path_names: {new: "nuevo"} do
- mark_options = {action: "mark", id: "1", controller: "messages"}
+ with_restful_routing :messages, member: { mark: method }, path_names: { new: "nuevo" } do
+ mark_options = { action: "mark", id: "1", controller: "messages" }
mark_path = "/messages/1/mark"
- assert_restful_routes_for :messages, path_names: {new: "nuevo"} do |options|
+ assert_restful_routes_for :messages, path_names: { new: "nuevo" } do |options|
assert_recognizes(options.merge(mark_options), path: mark_path, method: method)
end
- assert_restful_named_routes_for :messages, path_names: {new: "nuevo"} do
+ assert_restful_named_routes_for :messages, path_names: { new: "nuevo" } do
assert_named_route mark_path, :mark_message_path, mark_options
end
end
@@ -312,7 +312,7 @@ class ResourcesTest < ActionController::TestCase
end
%w(mark unmark).each do |action|
- action_options = {action: action, id: "1"}
+ action_options = { action: action, id: "1" }
action_path = "/messages/1/#{action}"
assert_restful_routes_for :messages do |options|
assert_recognizes(options.merge(action_options), path: action_path, method: method)
@@ -359,7 +359,7 @@ class ResourcesTest < ActionController::TestCase
end
end
- preview_options = {action: "preview"}
+ preview_options = { action: "preview" }
preview_path = "/messages/new/preview"
assert_restful_routes_for :messages do |options|
assert_recognizes(options.merge(preview_options), path: preview_path, method: :post)
@@ -381,7 +381,7 @@ class ResourcesTest < ActionController::TestCase
end
end
- preview_options = {action: "preview", thread_id: "1"}
+ preview_options = { action: "preview", thread_id: "1" }
preview_path = "/threads/1/messages/new/preview"
assert_restful_routes_for :messages, path_prefix: "threads/1/", name_prefix: "thread_", options: { thread_id: "1" } do |options|
assert_recognizes(options.merge(preview_options), path: preview_path, method: :post)
@@ -403,7 +403,7 @@ class ResourcesTest < ActionController::TestCase
end
end
- preview_options = {action: "preview", thread_id: "1", format: "xml"}
+ preview_options = { action: "preview", thread_id: "1", format: "xml" }
preview_path = "/threads/1/messages/new/preview.xml"
assert_restful_routes_for :messages, path_prefix: "threads/1/", name_prefix: "thread_", options: { thread_id: "1" } do |options|
assert_recognizes(options.merge(preview_options), path: preview_path, method: :post)
@@ -561,7 +561,7 @@ class ResourcesTest < ActionController::TestCase
end
end
- reset_options = {action: "reset"}
+ reset_options = { action: "reset" }
reset_path = "/account/reset"
assert_singleton_routes_for :account do |options|
assert_recognizes(options.merge(reset_options), path: reset_path, method: method)
@@ -585,7 +585,7 @@ class ResourcesTest < ActionController::TestCase
end
%w(reset disable).each do |action|
- action_options = {action: action}
+ action_options = { action: action }
action_path = "/account/#{action}"
assert_singleton_routes_for :account do |options|
assert_recognizes(options.merge(action_options), path: action_path, method: method)
@@ -743,7 +743,7 @@ class ResourcesTest < ActionController::TestCase
end
end
- assert_simply_restful_for :images, controller: "backoffice/images", name_prefix: "backoffice_product_", path_prefix: "backoffice/products/1/", options: {product_id: "1"}
+ assert_simply_restful_for :images, controller: "backoffice/images", name_prefix: "backoffice_product_", path_prefix: "backoffice/products/1/", options: { product_id: "1" }
end
end
@@ -759,15 +759,15 @@ class ResourcesTest < ActionController::TestCase
end
end
- assert_simply_restful_for :images, controller: "backoffice/admin/images", name_prefix: "backoffice_admin_product_", path_prefix: "backoffice/admin/products/1/", options: {product_id: "1"}
+ assert_simply_restful_for :images, controller: "backoffice/admin/images", name_prefix: "backoffice_admin_product_", path_prefix: "backoffice/admin/products/1/", options: { product_id: "1" }
end
end
def test_with_path_segment
with_restful_routing :messages do
assert_simply_restful_for :messages
- assert_recognizes({controller: "messages", action: "index"}, "/messages")
- assert_recognizes({controller: "messages", action: "index"}, "/messages/")
+ assert_recognizes({ controller: "messages", action: "index" }, "/messages")
+ assert_recognizes({ controller: "messages", action: "index" }, "/messages/")
end
with_routing do |set|
@@ -775,8 +775,8 @@ class ResourcesTest < ActionController::TestCase
resources :messages, path: "reviews"
end
assert_simply_restful_for :messages, as: "reviews"
- assert_recognizes({controller: "messages", action: "index"}, "/reviews")
- assert_recognizes({controller: "messages", action: "index"}, "/reviews/")
+ assert_recognizes({ controller: "messages", action: "index" }, "/reviews")
+ assert_recognizes({ controller: "messages", action: "index" }, "/reviews/")
end
end
@@ -791,13 +791,13 @@ class ResourcesTest < ActionController::TestCase
end
end
- assert_simply_restful_for :product_reviews, controller: "messages", as: "reviews", name_prefix: "product_", path_prefix: "products/1/", options: {product_id: "1"}
- assert_simply_restful_for :tutor_reviews,controller: "comments", as: "reviews", name_prefix: "tutor_", path_prefix: "tutors/1/", options: {tutor_id: "1"}
+ assert_simply_restful_for :product_reviews, controller: "messages", as: "reviews", name_prefix: "product_", path_prefix: "products/1/", options: { product_id: "1" }
+ assert_simply_restful_for :tutor_reviews, controller: "comments", as: "reviews", name_prefix: "tutor_", path_prefix: "tutors/1/", options: { tutor_id: "1" }
end
end
def test_with_path_segment_path_prefix_constraints
- expected_options = {controller: "messages", action: "show", thread_id: "1.1.1", id: "1"}
+ expected_options = { controller: "messages", action: "show", thread_id: "1.1.1", id: "1" }
with_routing do |set|
set.draw do
scope "/thread/:thread_id", constraints: { thread_id: /[0-9]\.[0-9]\.[0-9]/ } do
@@ -1089,7 +1089,7 @@ class ResourcesTest < ActionController::TestCase
end
end
- protected
+ private
def with_restful_routing(*args)
options = args.extract_options!
collection_methods = options.delete(:collection)
@@ -1125,7 +1125,7 @@ class ResourcesTest < ActionController::TestCase
def with_singleton_resources(*args)
with_routing do |set|
- set.draw {resource(*args) }
+ set.draw { resource(*args) }
yield
end
end
@@ -1306,14 +1306,14 @@ class ResourcesTest < ActionController::TestCase
end
def assert_named_route(expected, route, options)
- actual = @controller.send(route, options) rescue $!.class.name
+ actual = @controller.send(route, options) rescue $!.class.name
assert_equal expected, actual, "Error on route: #{route}(#{options.inspect})"
end
def assert_resource_methods(expected, resource, action_method, method)
assert_equal expected.length, resource.send("#{action_method}_methods")[method].size, "#{resource.send("#{action_method}_methods")[method].inspect}"
expected.each do |action|
- assert resource.send("#{action_method}_methods")[method].include?(action),
+ assert_includes resource.send("#{action_method}_methods")[method], action,
"#{method} not in #{action_method} methods: #{resource.send("#{action_method}_methods")[method].inspect}"
end
end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 9f0e3bff15..56b39510bb 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -91,7 +91,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
end
hash = ActiveSupport::JSON.decode get(URI("http://example.org/journey/faithfully-omg"))
- assert_equal({"artist"=>"journey", "song"=>"faithfully"}, hash)
+ assert_equal({ "artist" => "journey", "song" => "faithfully" }, hash)
end
def test_id_with_dash
@@ -103,7 +103,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
end
hash = ActiveSupport::JSON.decode get(URI("http://example.org/journey/faithfully-omg"))
- assert_equal({"id"=>"faithfully-omg"}, hash)
+ assert_equal({ "id" => "faithfully-omg" }, hash)
end
def test_dash_with_custom_regexp
@@ -115,7 +115,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
end
hash = ActiveSupport::JSON.decode get(URI("http://example.org/journey/123-omg"))
- assert_equal({"artist"=>"journey", "song"=>"123"}, hash)
+ assert_equal({ "artist" => "journey", "song" => "123" }, hash)
assert_equal "Not Found", get(URI("http://example.org/journey/faithfully-omg"))
end
@@ -128,7 +128,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
end
hash = ActiveSupport::JSON.decode get(URI("http://example.org/journey/omg-faithfully"))
- assert_equal({"artist"=>"journey", "song"=>"faithfully"}, hash)
+ assert_equal({ "artist" => "journey", "song" => "faithfully" }, hash)
end
def test_pre_dash_with_custom_regexp
@@ -140,7 +140,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
end
hash = ActiveSupport::JSON.decode get(URI("http://example.org/journey/omg-123"))
- assert_equal({"artist"=>"journey", "song"=>"123"}, hash)
+ assert_equal({ "artist" => "journey", "song" => "123" }, hash)
assert_equal "Not Found", get(URI("http://example.org/journey/omg-faithfully"))
end
@@ -211,7 +211,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_class_and_lambda_constraints
subdomain = Class.new {
def matches?(request)
- request.subdomain.present? and request.subdomain != "clients"
+ request.subdomain.present? && request.subdomain != "clients"
end
}
@@ -229,7 +229,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_lambda_constraints
rs.draw do
get "/", constraints: lambda { |req|
- req.subdomain.present? and req.subdomain != "clients" },
+ req.subdomain.present? && req.subdomain != "clients" },
to: lambda { |env| [200, {}, %w{default}] }
get "/", constraints: lambda { |req|
@@ -286,7 +286,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
}
end
assert_equal "foo", get(URI("http://example.org/posts/1/foo.js"))
- assert_equal({id: "1", filters: "foo", format: "js"}, params)
+ assert_equal({ id: "1", filters: "foo", format: "js" }, params)
end
def test_specific_controller_action_failure
@@ -301,11 +301,11 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_default_setup
rs.draw { ActiveSupport::Deprecation.silence { get "/:controller(/:action(/:id))" } }
- assert_equal({controller: "content", action: "index"}, rs.recognize_path("/content"))
- assert_equal({controller: "content", action: "list"}, rs.recognize_path("/content/list"))
- assert_equal({controller: "content", action: "show", id: "10"}, rs.recognize_path("/content/show/10"))
+ assert_equal({ controller: "content", action: "index" }, rs.recognize_path("/content"))
+ assert_equal({ controller: "content", action: "list" }, rs.recognize_path("/content/list"))
+ assert_equal({ controller: "content", action: "show", id: "10" }, rs.recognize_path("/content/show/10"))
- assert_equal({controller: "admin/user", action: "show", id: "10"}, rs.recognize_path("/admin/user/show/10"))
+ assert_equal({ controller: "admin/user", action: "show", id: "10" }, rs.recognize_path("/admin/user/show/10"))
assert_equal "/admin/user/show/10", url_for(rs, controller: "admin/user", action: "show", id: 10)
@@ -330,7 +330,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
get ":url", controller: "content", action: "translate"
end
- assert_equal({controller: "content", action: "translate", url: "example"}, rs.recognize_path("/example"))
+ assert_equal({ controller: "content", action: "translate", url: "example" }, rs.recognize_path("/example"))
end
def test_route_with_regexp_for_action
@@ -351,9 +351,9 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
end
end
- assert_equal({controller: "admin/user", admintoken: "foo", action: "index"},
+ assert_equal({ controller: "admin/user", admintoken: "foo", action: "index" },
rs.recognize_path("/admin/user/foo"))
- assert_equal({controller: "content", action: "foo"},
+ assert_equal({ controller: "content", action: "foo" },
rs.recognize_path("/content/foo"))
assert_equal "/admin/user/foo", url_for(rs, controller: "admin/user", admintoken: "foo", action: "index")
@@ -366,8 +366,8 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
get "/:controller(/:action(/:id))", controller: /admin\/(accounts|users)/
end
end
- assert_equal({controller: "admin/accounts", action: "index"}, rs.recognize_path("/admin/accounts"))
- assert_equal({controller: "admin/users", action: "index"}, rs.recognize_path("/admin/users"))
+ assert_equal({ controller: "admin/accounts", action: "index" }, rs.recognize_path("/admin/accounts"))
+ assert_equal({ controller: "admin/users", action: "index" }, rs.recognize_path("/admin/users"))
assert_raise(ActionController::RoutingError) { rs.recognize_path("/admin/products") }
end
@@ -377,22 +377,22 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
get ":controller/:action/:file",
controller: /admin|user/,
action: /upload|download/,
- defaults: {file: nil},
- constraints: {file: %r{[^/]+(\.[^/]+)?}}
+ defaults: { file: nil },
+ constraints: { file: %r{[^/]+(\.[^/]+)?} }
end
end
# Without a file extension
assert_equal "/user/download/file",
url_for(rs, controller: "user", action: "download", file: "file")
- assert_equal({controller: "user", action: "download", file: "file"},
+ assert_equal({ controller: "user", action: "download", file: "file" },
rs.recognize_path("/user/download/file"))
# Now, let's try a file with an extension, really a dot (.)
assert_equal "/user/download/file.jpg",
url_for(rs, controller: "user", action: "download", file: "file.jpg")
- assert_equal({controller: "user", action: "download", file: "file.jpg"},
+ assert_equal({ controller: "user", action: "download", file: "file.jpg" },
rs.recognize_path("/user/download/file.jpg"))
end
@@ -603,7 +603,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
end
get URI("http://test.host/pages/boo")
- assert_equal({controller: "content", action: "show_file", path: "pages/boo"},
+ assert_equal({ controller: "content", action: "show_file", path: "pages/boo" },
controller.request.path_parameters)
assert_equal "/pages/boo",
@@ -638,9 +638,9 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
assert_equal "/page", url_for(rs, controller: "content", action: "show_page", id: "1")
assert_equal "/page/10", url_for(rs, controller: "content", action: "show_page", id: 10)
- assert_equal({controller: "content", action: "show_page", id: 1 }, rs.recognize_path("/page"))
- assert_equal({controller: "content", action: "show_page", id: "1"}, rs.recognize_path("/page/1"))
- assert_equal({controller: "content", action: "show_page", id: "10"}, rs.recognize_path("/page/10"))
+ assert_equal({ controller: "content", action: "show_page", id: 1 }, rs.recognize_path("/page"))
+ assert_equal({ controller: "content", action: "show_page", id: "1" }, rs.recognize_path("/page/1"))
+ assert_equal({ controller: "content", action: "show_page", id: "10" }, rs.recognize_path("/page/10"))
end
# For newer revision
@@ -672,7 +672,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_requirement_should_prevent_optional_id
rs.draw do
- get "post/:id" => "post#show", :constraints => {id: /\d+/}, :as => "post"
+ get "post/:id" => "post#show", :constraints => { id: /\d+/ }, :as => "post"
end
assert_equal "/post/10", url_for(rs, controller: "post", action: "show", id: 10)
@@ -1111,8 +1111,8 @@ class RouteSetTest < ActiveSupport::TestCase
assert_equal "/users/show/10", url_for(set, controller: "users", action: "show", id: 10)
assert_equal "/users/index/10", url_for(set, controller: "users", id: 10)
- assert_equal({controller: "users", action: "index", id: "10"}, set.recognize_path("/users/index/10"))
- assert_equal({controller: "users", action: "index", id: "10"}, set.recognize_path("/users/index/10/"))
+ assert_equal({ controller: "users", action: "index", id: "10" }, set.recognize_path("/users/index/10"))
+ assert_equal({ controller: "users", action: "index", id: "10" }, set.recognize_path("/users/index/10/"))
end
def test_route_with_parameter_shell
@@ -1124,12 +1124,12 @@ class RouteSetTest < ActiveSupport::TestCase
end
end
- assert_equal({controller: "pages", action: "index"}, request_path_params("/pages"))
- assert_equal({controller: "pages", action: "index"}, request_path_params("/pages/index"))
- assert_equal({controller: "pages", action: "list"}, request_path_params("/pages/list"))
+ assert_equal({ controller: "pages", action: "index" }, request_path_params("/pages"))
+ assert_equal({ controller: "pages", action: "index" }, request_path_params("/pages/index"))
+ assert_equal({ controller: "pages", action: "list" }, request_path_params("/pages/list"))
- assert_equal({controller: "pages", action: "show", id: "10"}, request_path_params("/pages/show/10"))
- assert_equal({controller: "pages", action: "show", id: "10"}, request_path_params("/page/10"))
+ assert_equal({ controller: "pages", action: "show", id: "10" }, request_path_params("/pages/show/10"))
+ assert_equal({ controller: "pages", action: "show", id: "10" }, request_path_params("/page/10"))
end
def test_route_constraints_on_request_object_with_anchors_are_valid
@@ -1189,8 +1189,8 @@ class RouteSetTest < ActiveSupport::TestCase
get "page/:id" => "pages#show", :id => /[a-zA-Z0-9\+]+/
end
- assert_equal({controller: "pages", action: "show", id: "10"}, request_path_params("/page/10"))
- assert_equal({controller: "pages", action: "show", id: "hello+world"}, request_path_params("/page/hello+world"))
+ assert_equal({ controller: "pages", action: "show", id: "10" }, request_path_params("/page/10"))
+ assert_equal({ controller: "pages", action: "show", id: "hello+world" }, request_path_params("/page/hello+world"))
end
def test_recognize_with_http_methods
@@ -1500,7 +1500,7 @@ class RouteSetTest < ActiveSupport::TestCase
end
get URI("http://test.host/posts.xml")
- assert_equal({controller: "posts", action: "index", format: "xml"},
+ assert_equal({ controller: "posts", action: "index", format: "xml" },
controller.request.path_parameters)
assert_equal "/posts", controller.url_for(
@@ -1591,12 +1591,12 @@ class RouteSetTest < ActiveSupport::TestCase
def test_route_with_subdomain_and_constraints_must_receive_params
name_param = nil
set.draw do
- get "page/:name" => "pages#show", :constraints => lambda {|request|
+ get "page/:name" => "pages#show", :constraints => lambda { |request|
name_param = request.params[:name]
return true
}
end
- assert_equal({controller: "pages", action: "show", name: "mypage"},
+ assert_equal({ controller: "pages", action: "show", name: "mypage" },
set.recognize_path("http://subdomain.example.org/page/mypage"))
assert_equal(name_param, "mypage")
end
@@ -1604,19 +1604,19 @@ class RouteSetTest < ActiveSupport::TestCase
def test_route_requirement_recognize_with_ignore_case
set.draw do
get "page/:name" => "pages#show",
- :constraints => {name: /(david|jamis)/i}
+ :constraints => { name: /(david|jamis)/i }
end
- assert_equal({controller: "pages", action: "show", name: "jamis"}, set.recognize_path("/page/jamis"))
+ assert_equal({ controller: "pages", action: "show", name: "jamis" }, set.recognize_path("/page/jamis"))
assert_raise ActionController::RoutingError do
set.recognize_path("/page/davidjamis")
end
- assert_equal({controller: "pages", action: "show", name: "DAVID"}, set.recognize_path("/page/DAVID"))
+ assert_equal({ controller: "pages", action: "show", name: "DAVID" }, set.recognize_path("/page/DAVID"))
end
def test_route_requirement_generate_with_ignore_case
set.draw do
get "page/:name" => "pages#show",
- :constraints => {name: /(david|jamis)/i}
+ :constraints => { name: /(david|jamis)/i }
end
url = url_for(set, controller: "pages", action: "show", name: "david")
@@ -1631,15 +1631,15 @@ class RouteSetTest < ActiveSupport::TestCase
def test_route_requirement_recognize_with_extended_syntax
set.draw do
get "page/:name" => "pages#show",
- :constraints => {name: / # Desperately overcommented regexp
+ :constraints => { name: / # Desperately overcommented regexp
( #Either
david #The Creator
| #Or
jamis #The Deployer
- )/x}
+ )/x }
end
- assert_equal({controller: "pages", action: "show", name: "jamis"}, set.recognize_path("/page/jamis"))
- assert_equal({controller: "pages", action: "show", name: "david"}, set.recognize_path("/page/david"))
+ assert_equal({ controller: "pages", action: "show", name: "jamis" }, set.recognize_path("/page/jamis"))
+ assert_equal({ controller: "pages", action: "show", name: "david" }, set.recognize_path("/page/david"))
assert_raise ActionController::RoutingError do
set.recognize_path("/page/david #The Creator")
end
@@ -1651,15 +1651,15 @@ class RouteSetTest < ActiveSupport::TestCase
def test_route_requirement_with_xi_modifiers
set.draw do
get "page/:name" => "pages#show",
- :constraints => {name: / # Desperately overcommented regexp
+ :constraints => { name: / # Desperately overcommented regexp
( #Either
david #The Creator
| #Or
jamis #The Deployer
- )/xi}
+ )/xi }
end
- assert_equal({controller: "pages", action: "show", name: "JAMIS"},
+ assert_equal({ controller: "pages", action: "show", name: "JAMIS" },
set.recognize_path("/page/JAMIS"))
assert_equal "/page/JAMIS",
@@ -1671,8 +1671,8 @@ class RouteSetTest < ActiveSupport::TestCase
get "unnamed", controller: :pages, action: :show, name: :as_symbol
get "named" , controller: :pages, action: :show, name: :as_symbol, as: :named
end
- assert_equal({controller: "pages", action: "show", name: :as_symbol}, set.recognize_path("/unnamed"))
- assert_equal({controller: "pages", action: "show", name: :as_symbol}, set.recognize_path("/named"))
+ assert_equal({ controller: "pages", action: "show", name: :as_symbol }, set.recognize_path("/unnamed"))
+ assert_equal({ controller: "pages", action: "show", name: :as_symbol }, set.recognize_path("/named"))
end
def test_regexp_chunk_should_add_question_mark_for_optionals
@@ -1684,8 +1684,8 @@ class RouteSetTest < ActiveSupport::TestCase
assert_equal "/", url_for(set, controller: "foo")
assert_equal "/hello", url_for(set, controller: "bar")
- assert_equal({controller: "foo", action: "index"}, set.recognize_path("/"))
- assert_equal({controller: "bar", action: "index"}, set.recognize_path("/hello"))
+ assert_equal({ controller: "foo", action: "index" }, set.recognize_path("/"))
+ assert_equal({ controller: "bar", action: "index" }, set.recognize_path("/hello"))
end
def test_assign_route_options_with_anchor_chars
@@ -1697,7 +1697,7 @@ class RouteSetTest < ActiveSupport::TestCase
assert_equal "/cars/buy/1/2", url_for(set, controller: "cars", action: "buy", person: "1", car: "2")
- assert_equal({controller: "cars", action: "buy", person: "1", car: "2"}, set.recognize_path("/cars/buy/1/2"))
+ assert_equal({ controller: "cars", action: "buy", person: "1", car: "2" }, set.recognize_path("/cars/buy/1/2"))
end
def test_segmentation_of_dot_path
@@ -1709,7 +1709,7 @@ class RouteSetTest < ActiveSupport::TestCase
assert_equal "/books/list.rss", url_for(set, controller: "books", action: "list")
- assert_equal({controller: "books", action: "list"}, set.recognize_path("/books/list.rss"))
+ assert_equal({ controller: "books", action: "list" }, set.recognize_path("/books/list.rss"))
end
def test_segmentation_of_dynamic_dot_path
@@ -1724,10 +1724,10 @@ class RouteSetTest < ActiveSupport::TestCase
assert_equal "/books/list", url_for(set, controller: "books", action: "list")
assert_equal "/books", url_for(set, controller: "books", action: "index")
- assert_equal({controller: "books", action: "list", format: "rss"}, set.recognize_path("/books/list.rss"))
- assert_equal({controller: "books", action: "list", format: "xml"}, set.recognize_path("/books/list.xml"))
- assert_equal({controller: "books", action: "list"}, set.recognize_path("/books/list"))
- assert_equal({controller: "books", action: "index"}, set.recognize_path("/books"))
+ assert_equal({ controller: "books", action: "list", format: "rss" }, set.recognize_path("/books/list.rss"))
+ assert_equal({ controller: "books", action: "list", format: "xml" }, set.recognize_path("/books/list.xml"))
+ assert_equal({ controller: "books", action: "list" }, set.recognize_path("/books/list"))
+ assert_equal({ controller: "books", action: "index" }, set.recognize_path("/books"))
end
def test_slashes_are_implied
@@ -1737,14 +1737,14 @@ class RouteSetTest < ActiveSupport::TestCase
assert_equal "/content/list", url_for(set, controller: "content", action: "list")
assert_equal "/content/show/1", url_for(set, controller: "content", action: "show", id: "1")
- assert_equal({controller: "content", action: "index"}, set.recognize_path("/content"))
- assert_equal({controller: "content", action: "index"}, set.recognize_path("/content/index"))
- assert_equal({controller: "content", action: "list"}, set.recognize_path("/content/list"))
- assert_equal({controller: "content", action: "show", id: "1"}, set.recognize_path("/content/show/1"))
+ assert_equal({ controller: "content", action: "index" }, set.recognize_path("/content"))
+ assert_equal({ controller: "content", action: "index" }, set.recognize_path("/content/index"))
+ assert_equal({ controller: "content", action: "list" }, set.recognize_path("/content/list"))
+ assert_equal({ controller: "content", action: "show", id: "1" }, set.recognize_path("/content/show/1"))
end
def test_default_route_recognition
- expected = {controller: "pages", action: "show", id: "10"}
+ expected = { controller: "pages", action: "show", id: "10" }
assert_equal expected, default_route_set.recognize_path("/pages/show/10")
assert_equal expected, default_route_set.recognize_path("/pages/show/10/")
@@ -1860,19 +1860,19 @@ class RouteSetTest < ActiveSupport::TestCase
controller.request.path_parameters
}
- assert_equal({controller: "blog", action: "index"}, recognize_path.("/blog"))
- assert_equal({controller: "blog", action: "show", id: "123"}, recognize_path.("/blog/show/123"))
- assert_equal({controller: "blog", action: "show_date", year: "2004", day: nil, month: nil }, recognize_path.("/blog/2004"))
- assert_equal({controller: "blog", action: "show_date", year: "2004", month: "12", day: nil }, recognize_path.("/blog/2004/12"))
- assert_equal({controller: "blog", action: "show_date", year: "2004", month: "12", day: "25"}, recognize_path.("/blog/2004/12/25"))
- assert_equal({controller: "articles", action: "edit", id: "123"}, recognize_path.("/blog/articles/edit/123"))
- assert_equal({controller: "articles", action: "show_stats"}, recognize_path.("/blog/articles/show_stats"))
- assert_equal({controller: "blog", action: "unknown_request", anything: "blog/wibble"}, recognize_path.("/blog/wibble"))
- assert_equal({controller: "blog", action: "unknown_request", anything: "junk"}, recognize_path.("/junk"))
+ assert_equal({ controller: "blog", action: "index" }, recognize_path.("/blog"))
+ assert_equal({ controller: "blog", action: "show", id: "123" }, recognize_path.("/blog/show/123"))
+ assert_equal({ controller: "blog", action: "show_date", year: "2004", day: nil, month: nil }, recognize_path.("/blog/2004"))
+ assert_equal({ controller: "blog", action: "show_date", year: "2004", month: "12", day: nil }, recognize_path.("/blog/2004/12"))
+ assert_equal({ controller: "blog", action: "show_date", year: "2004", month: "12", day: "25" }, recognize_path.("/blog/2004/12/25"))
+ assert_equal({ controller: "articles", action: "edit", id: "123" }, recognize_path.("/blog/articles/edit/123"))
+ assert_equal({ controller: "articles", action: "show_stats" }, recognize_path.("/blog/articles/show_stats"))
+ assert_equal({ controller: "blog", action: "unknown_request", anything: "blog/wibble" }, recognize_path.("/blog/wibble"))
+ assert_equal({ controller: "blog", action: "unknown_request", anything: "junk" }, recognize_path.("/junk"))
get URI("http://example.org/blog/2006/07/28")
- assert_equal({controller: "blog", action: "show_date", year: "2006", month: "07", day: "28"}, controller.request.path_parameters)
+ assert_equal({ controller: "blog", action: "show_date", year: "2006", month: "07", day: "28" }, controller.request.path_parameters)
assert_equal("/blog/2006/07/25", controller.url_for(day: 25, only_path: true))
assert_equal("/blog/2005", controller.url_for(year: 2005, only_path: true))
assert_equal("/blog/show/123", controller.url_for(action: "show" , id: 123, only_path: true))
@@ -1931,7 +1931,7 @@ class RackMountIntegrationTests < ActiveSupport::TestCase
get "clients" => "projects#index"
get "ignorecase/geocode/:postalcode" => "geocode#show", :postalcode => /hx\d\d-\d[a-z]{2}/i
- get "extended/geocode/:postalcode" => "geocode#show",:constraints => {
+ get "extended/geocode/:postalcode" => "geocode#show", :constraints => {
postalcode: /# Postcode format
\d{5} #Prefix
(-\d{4})? #Suffix
@@ -1966,76 +1966,76 @@ class RackMountIntegrationTests < ActiveSupport::TestCase
end
def test_recognize_path
- assert_equal({controller: "admin/users", action: "index"}, @routes.recognize_path("/admin/users", method: :get))
- assert_equal({controller: "admin/users", action: "create"}, @routes.recognize_path("/admin/users", method: :post))
- assert_equal({controller: "admin/users", action: "new"}, @routes.recognize_path("/admin/users/new", method: :get))
- assert_equal({controller: "admin/users", action: "show", id: "1"}, @routes.recognize_path("/admin/users/1", method: :get))
- assert_equal({controller: "admin/users", action: "update", id: "1"}, @routes.recognize_path("/admin/users/1", method: :put))
- assert_equal({controller: "admin/users", action: "destroy", id: "1"}, @routes.recognize_path("/admin/users/1", method: :delete))
- assert_equal({controller: "admin/users", action: "edit", id: "1"}, @routes.recognize_path("/admin/users/1/edit", method: :get))
-
- assert_equal({controller: "admin/posts", action: "index"}, @routes.recognize_path("/admin/posts", method: :get))
- assert_equal({controller: "admin/posts", action: "new"}, @routes.recognize_path("/admin/posts/new", method: :get))
-
- assert_equal({controller: "api/users", action: "index"}, @routes.recognize_path("/api", method: :get))
- assert_equal({controller: "api/users", action: "index"}, @routes.recognize_path("/api/", method: :get))
-
- assert_equal({controller: "posts", action: "show_date", year: "2009", month: nil, day: nil }, @routes.recognize_path("/blog/2009", method: :get))
- assert_equal({controller: "posts", action: "show_date", year: "2009", month: "01", day: nil }, @routes.recognize_path("/blog/2009/01", method: :get))
- assert_equal({controller: "posts", action: "show_date", year: "2009", month: "01", day: "01"}, @routes.recognize_path("/blog/2009/01/01", method: :get))
-
- assert_equal({controller: "archive", action: "index", year: "2010"}, @routes.recognize_path("/archive/2010"))
- assert_equal({controller: "archive", action: "index"}, @routes.recognize_path("/archive"))
-
- assert_equal({controller: "people", action: "index"}, @routes.recognize_path("/people", method: :get))
- assert_equal({controller: "people", action: "index", format: "xml"}, @routes.recognize_path("/people.xml", method: :get))
- assert_equal({controller: "people", action: "create"}, @routes.recognize_path("/people", method: :post))
- assert_equal({controller: "people", action: "new"}, @routes.recognize_path("/people/new", method: :get))
- assert_equal({controller: "people", action: "show", id: "1"}, @routes.recognize_path("/people/1", method: :get))
- assert_equal({controller: "people", action: "show", id: "1", format: "xml"}, @routes.recognize_path("/people/1.xml", method: :get))
- assert_equal({controller: "people", action: "update", id: "1"}, @routes.recognize_path("/people/1", method: :put))
- assert_equal({controller: "people", action: "destroy", id: "1"}, @routes.recognize_path("/people/1", method: :delete))
- assert_equal({controller: "people", action: "edit", id: "1"}, @routes.recognize_path("/people/1/edit", method: :get))
- assert_equal({controller: "people", action: "edit", id: "1", format: "xml"}, @routes.recognize_path("/people/1/edit.xml", method: :get))
-
- assert_equal({controller: "symbols", action: "show", name: :as_symbol}, @routes.recognize_path("/symbols"))
- assert_equal({controller: "foo", action: "id_default", id: "1"}, @routes.recognize_path("/id_default/1"))
- assert_equal({controller: "foo", action: "id_default", id: "2"}, @routes.recognize_path("/id_default/2"))
- assert_equal({controller: "foo", action: "id_default", id: 1 }, @routes.recognize_path("/id_default"))
- assert_equal({controller: "foo", action: "get_or_post"}, @routes.recognize_path("/get_or_post", method: :get))
- assert_equal({controller: "foo", action: "get_or_post"}, @routes.recognize_path("/get_or_post", method: :post))
+ assert_equal({ controller: "admin/users", action: "index" }, @routes.recognize_path("/admin/users", method: :get))
+ assert_equal({ controller: "admin/users", action: "create" }, @routes.recognize_path("/admin/users", method: :post))
+ assert_equal({ controller: "admin/users", action: "new" }, @routes.recognize_path("/admin/users/new", method: :get))
+ assert_equal({ controller: "admin/users", action: "show", id: "1" }, @routes.recognize_path("/admin/users/1", method: :get))
+ assert_equal({ controller: "admin/users", action: "update", id: "1" }, @routes.recognize_path("/admin/users/1", method: :put))
+ assert_equal({ controller: "admin/users", action: "destroy", id: "1" }, @routes.recognize_path("/admin/users/1", method: :delete))
+ assert_equal({ controller: "admin/users", action: "edit", id: "1" }, @routes.recognize_path("/admin/users/1/edit", method: :get))
+
+ assert_equal({ controller: "admin/posts", action: "index" }, @routes.recognize_path("/admin/posts", method: :get))
+ assert_equal({ controller: "admin/posts", action: "new" }, @routes.recognize_path("/admin/posts/new", method: :get))
+
+ assert_equal({ controller: "api/users", action: "index" }, @routes.recognize_path("/api", method: :get))
+ assert_equal({ controller: "api/users", action: "index" }, @routes.recognize_path("/api/", method: :get))
+
+ assert_equal({ controller: "posts", action: "show_date", year: "2009", month: nil, day: nil }, @routes.recognize_path("/blog/2009", method: :get))
+ assert_equal({ controller: "posts", action: "show_date", year: "2009", month: "01", day: nil }, @routes.recognize_path("/blog/2009/01", method: :get))
+ assert_equal({ controller: "posts", action: "show_date", year: "2009", month: "01", day: "01" }, @routes.recognize_path("/blog/2009/01/01", method: :get))
+
+ assert_equal({ controller: "archive", action: "index", year: "2010" }, @routes.recognize_path("/archive/2010"))
+ assert_equal({ controller: "archive", action: "index" }, @routes.recognize_path("/archive"))
+
+ assert_equal({ controller: "people", action: "index" }, @routes.recognize_path("/people", method: :get))
+ assert_equal({ controller: "people", action: "index", format: "xml" }, @routes.recognize_path("/people.xml", method: :get))
+ assert_equal({ controller: "people", action: "create" }, @routes.recognize_path("/people", method: :post))
+ assert_equal({ controller: "people", action: "new" }, @routes.recognize_path("/people/new", method: :get))
+ assert_equal({ controller: "people", action: "show", id: "1" }, @routes.recognize_path("/people/1", method: :get))
+ assert_equal({ controller: "people", action: "show", id: "1", format: "xml" }, @routes.recognize_path("/people/1.xml", method: :get))
+ assert_equal({ controller: "people", action: "update", id: "1" }, @routes.recognize_path("/people/1", method: :put))
+ assert_equal({ controller: "people", action: "destroy", id: "1" }, @routes.recognize_path("/people/1", method: :delete))
+ assert_equal({ controller: "people", action: "edit", id: "1" }, @routes.recognize_path("/people/1/edit", method: :get))
+ assert_equal({ controller: "people", action: "edit", id: "1", format: "xml" }, @routes.recognize_path("/people/1/edit.xml", method: :get))
+
+ assert_equal({ controller: "symbols", action: "show", name: :as_symbol }, @routes.recognize_path("/symbols"))
+ assert_equal({ controller: "foo", action: "id_default", id: "1" }, @routes.recognize_path("/id_default/1"))
+ assert_equal({ controller: "foo", action: "id_default", id: "2" }, @routes.recognize_path("/id_default/2"))
+ assert_equal({ controller: "foo", action: "id_default", id: 1 }, @routes.recognize_path("/id_default"))
+ assert_equal({ controller: "foo", action: "get_or_post" }, @routes.recognize_path("/get_or_post", method: :get))
+ assert_equal({ controller: "foo", action: "get_or_post" }, @routes.recognize_path("/get_or_post", method: :post))
assert_raise(ActionController::RoutingError) { @routes.recognize_path("/get_or_post", method: :put) }
assert_raise(ActionController::RoutingError) { @routes.recognize_path("/get_or_post", method: :delete) }
- assert_equal({controller: "posts", action: "index", optional: "bar"}, @routes.recognize_path("/optional/bar"))
+ assert_equal({ controller: "posts", action: "index", optional: "bar" }, @routes.recognize_path("/optional/bar"))
assert_raise(ActionController::RoutingError) { @routes.recognize_path("/optional") }
- assert_equal({controller: "posts", action: "show", id: "1", ws: true}, @routes.recognize_path("/ws/posts/show/1", method: :get))
- assert_equal({controller: "posts", action: "list", ws: true}, @routes.recognize_path("/ws/posts/list", method: :get))
- assert_equal({controller: "posts", action: "index", ws: true}, @routes.recognize_path("/ws/posts", method: :get))
+ assert_equal({ controller: "posts", action: "show", id: "1", ws: true }, @routes.recognize_path("/ws/posts/show/1", method: :get))
+ assert_equal({ controller: "posts", action: "list", ws: true }, @routes.recognize_path("/ws/posts/list", method: :get))
+ assert_equal({ controller: "posts", action: "index", ws: true }, @routes.recognize_path("/ws/posts", method: :get))
- assert_equal({controller: "account", action: "subscription"}, @routes.recognize_path("/account", method: :get))
- assert_equal({controller: "account", action: "subscription"}, @routes.recognize_path("/account/subscription", method: :get))
- assert_equal({controller: "account", action: "billing"}, @routes.recognize_path("/account/billing", method: :get))
+ assert_equal({ controller: "account", action: "subscription" }, @routes.recognize_path("/account", method: :get))
+ assert_equal({ controller: "account", action: "subscription" }, @routes.recognize_path("/account/subscription", method: :get))
+ assert_equal({ controller: "account", action: "billing" }, @routes.recognize_path("/account/billing", method: :get))
- assert_equal({page_id: "1", controller: "notes", action: "index"}, @routes.recognize_path("/pages/1/notes", method: :get))
- assert_equal({page_id: "1", controller: "notes", action: "list"}, @routes.recognize_path("/pages/1/notes/list", method: :get))
- assert_equal({page_id: "1", controller: "notes", action: "show", id: "2"}, @routes.recognize_path("/pages/1/notes/show/2", method: :get))
+ assert_equal({ page_id: "1", controller: "notes", action: "index" }, @routes.recognize_path("/pages/1/notes", method: :get))
+ assert_equal({ page_id: "1", controller: "notes", action: "list" }, @routes.recognize_path("/pages/1/notes/list", method: :get))
+ assert_equal({ page_id: "1", controller: "notes", action: "show", id: "2" }, @routes.recognize_path("/pages/1/notes/show/2", method: :get))
- assert_equal({controller: "posts", action: "ping"}, @routes.recognize_path("/posts/ping", method: :get))
- assert_equal({controller: "posts", action: "index"}, @routes.recognize_path("/posts", method: :get))
- assert_equal({controller: "posts", action: "index"}, @routes.recognize_path("/posts/index", method: :get))
- assert_equal({controller: "posts", action: "show"}, @routes.recognize_path("/posts/show", method: :get))
- assert_equal({controller: "posts", action: "show", id: "1"}, @routes.recognize_path("/posts/show/1", method: :get))
- assert_equal({controller: "posts", action: "create"}, @routes.recognize_path("/posts/create", method: :post))
+ assert_equal({ controller: "posts", action: "ping" }, @routes.recognize_path("/posts/ping", method: :get))
+ assert_equal({ controller: "posts", action: "index" }, @routes.recognize_path("/posts", method: :get))
+ assert_equal({ controller: "posts", action: "index" }, @routes.recognize_path("/posts/index", method: :get))
+ assert_equal({ controller: "posts", action: "show" }, @routes.recognize_path("/posts/show", method: :get))
+ assert_equal({ controller: "posts", action: "show", id: "1" }, @routes.recognize_path("/posts/show/1", method: :get))
+ assert_equal({ controller: "posts", action: "create" }, @routes.recognize_path("/posts/create", method: :post))
- assert_equal({controller: "geocode", action: "show", postalcode: "hx12-1az"}, @routes.recognize_path("/ignorecase/geocode/hx12-1az"))
- assert_equal({controller: "geocode", action: "show", postalcode: "hx12-1AZ"}, @routes.recognize_path("/ignorecase/geocode/hx12-1AZ"))
- assert_equal({controller: "geocode", action: "show", postalcode: "12345-1234"}, @routes.recognize_path("/extended/geocode/12345-1234"))
- assert_equal({controller: "geocode", action: "show", postalcode: "12345"}, @routes.recognize_path("/extended/geocode/12345"))
+ assert_equal({ controller: "geocode", action: "show", postalcode: "hx12-1az" }, @routes.recognize_path("/ignorecase/geocode/hx12-1az"))
+ assert_equal({ controller: "geocode", action: "show", postalcode: "hx12-1AZ" }, @routes.recognize_path("/ignorecase/geocode/hx12-1AZ"))
+ assert_equal({ controller: "geocode", action: "show", postalcode: "12345-1234" }, @routes.recognize_path("/extended/geocode/12345-1234"))
+ assert_equal({ controller: "geocode", action: "show", postalcode: "12345" }, @routes.recognize_path("/extended/geocode/12345"))
- assert_equal({controller: "news", action: "index" }, @routes.recognize_path("/", method: :get))
- assert_equal({controller: "news", action: "index", format: "rss"}, @routes.recognize_path("/news.rss", method: :get))
+ assert_equal({ controller: "news", action: "index" }, @routes.recognize_path("/", method: :get))
+ assert_equal({ controller: "news", action: "index", format: "rss" }, @routes.recognize_path("/news.rss", method: :get))
assert_raise(ActionController::RoutingError) { @routes.recognize_path("/none", method: :get) }
end
@@ -2059,25 +2059,25 @@ class RackMountIntegrationTests < ActiveSupport::TestCase
end
def test_extras
- params = {controller: "people"}
+ params = { controller: "people" }
assert_equal [], @routes.extra_keys(params)
- assert_equal({controller: "people", action: "index"}, params)
+ assert_equal({ controller: "people", action: "index" }, params)
- params = {controller: "people", foo: "bar"}
+ params = { controller: "people", foo: "bar" }
assert_equal [:foo], @routes.extra_keys(params)
- assert_equal({controller: "people", action: "index", foo: "bar"}, params)
+ assert_equal({ controller: "people", action: "index", foo: "bar" }, params)
- params = {controller: "people", action: "create", person: { name: "Josh"}}
+ params = { controller: "people", action: "create", person: { name: "Josh" } }
assert_equal [:person], @routes.extra_keys(params)
- assert_equal({controller: "people", action: "create", person: { name: "Josh"}}, params)
+ assert_equal({ controller: "people", action: "create", person: { name: "Josh" } }, params)
end
def test_unicode_path
- assert_equal({controller: "news", action: "index"}, @routes.recognize_path(URI.parser.escape("こんにちは/世界"), method: :get))
+ assert_equal({ controller: "news", action: "index" }, @routes.recognize_path(URI.parser.escape("こんにちは/世界"), method: :get))
end
def test_downcased_unicode_path
- assert_equal({controller: "news", action: "index"}, @routes.recognize_path(URI.parser.escape("こんにちは/世界").downcase, method: :get))
+ assert_equal({ controller: "news", action: "index" }, @routes.recognize_path(URI.parser.escape("こんにちは/世界").downcase, method: :get))
end
private
diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb
index 3fb0ee8059..9e6b975fe2 100644
--- a/actionpack/test/controller/send_file_test.rb
+++ b/actionpack/test/controller/send_file_test.rb
@@ -180,7 +180,7 @@ class SendFileTest < ActionController::TestCase
"file.zip" => "application/zip",
"file.unk" => "application/octet-stream",
"zip" => "application/octet-stream"
- }.each do |filename,expected_type|
+ }.each do |filename, expected_type|
get __method__, params: { filename: filename }
assert_equal expected_type, response.content_type
end
@@ -192,7 +192,7 @@ class SendFileTest < ActionController::TestCase
end
def test_send_file_without_content_disposition_header
- @controller.options = {disposition: nil}
+ @controller.options = { disposition: nil }
process("data")
assert_nil @controller.headers["Content-Disposition"]
end
@@ -233,4 +233,25 @@ class SendFileTest < ActionController::TestCase
response = process("file")
assert_equal 200, response.status
end
+
+ def test_send_file_charset_with_type_options_key
+ @controller = SendFileWithActionControllerLive.new
+ @controller.options = { type: "text/calendar; charset=utf-8" }
+ response = process("file")
+ assert_equal "text/calendar; charset=utf-8", response.headers["Content-Type"]
+ end
+
+ def test_send_file_charset_with_type_options_key_without_charset
+ @controller = SendFileWithActionControllerLive.new
+ @controller.options = { type: "image/png" }
+ response = process("file")
+ assert_equal "image/png", response.headers["Content-Type"]
+ end
+
+ def test_send_file_charset_with_content_type_options_key
+ @controller = SendFileWithActionControllerLive.new
+ @controller.options = { content_type: "text/calendar" }
+ response = process("file")
+ assert_equal "text/calendar", response.headers["Content-Type"]
+ end
end
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index 45bc770e86..874f9c3c42 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -100,11 +100,11 @@ HTML
end
def test_xml_output
- response.content_type = "application/xml"
+ response.content_type = params[:response_as]
render plain: <<XML
<?xml version="1.0" encoding="UTF-8"?>
<root>
- <area>area is an empty tag in HTML, raising an error if not in xml mode</area>
+ <area><p>area is an empty tag in HTML, so it won't contain this content</p></area>
</root>
XML
end
@@ -229,17 +229,9 @@ XML
assert_equal params.to_query, @response.body
end
- def test_deprecated_body_stream
- params = Hash[:page, { name: "page name" }, "some key", 123]
-
- assert_deprecated { post :render_body, params.dup }
-
- assert_equal params.to_query, @response.body
- end
-
def test_document_body_and_params_with_post
post :test_params, params: { id: 1 }
- assert_equal({"id"=>"1", "controller"=>"test_case_test/test", "action"=>"test_params"}, ::JSON.parse(@response.body))
+ assert_equal({ "id" => "1", "controller" => "test_case_test/test", "action" => "test_params" }, ::JSON.parse(@response.body))
end
def test_document_body_with_post
@@ -247,21 +239,11 @@ XML
assert_equal "document body", @response.body
end
- def test_deprecated_document_body_with_post
- assert_deprecated { post :render_body, "document body" }
- assert_equal "document body", @response.body
- end
-
def test_document_body_with_put
put :render_body, body: "document body"
assert_equal "document body", @response.body
end
- def test_deprecated_document_body_with_put
- assert_deprecated { put :render_body, "document body" }
- assert_equal "document body", @response.body
- end
-
def test_head
head :test_params
assert_equal 200, @response.status
@@ -272,11 +254,6 @@ XML
assert_equal "><", flash["test"]
end
- def test_deprecated_process_with_flash
- assert_deprecated { process :set_flash, "GET", nil, nil, "test" => "value" }
- assert_equal ">value<", flash["test"]
- end
-
def test_process_with_flash
process :set_flash,
method: "GET",
@@ -284,11 +261,6 @@ XML
assert_equal ">value<", flash["test"]
end
- def test_deprecated_process_with_flash_now
- assert_deprecated { process :set_flash_now, "GET", nil, nil, "test_now" => "value_now" }
- assert_equal ">value_now<", flash["test_now"]
- end
-
def test_process_with_flash_now
process :set_flash_now,
method: "GET",
@@ -311,14 +283,6 @@ XML
assert_equal "it works", session[:symbol], "Test session hash should allow indifferent access"
end
- def test_process_with_session_arg
- assert_deprecated { process :no_op, "GET", nil, "string" => "value1", symbol: "value2" }
- assert_equal "value1", session["string"]
- assert_equal "value1", session[:string]
- assert_equal "value2", session["symbol"]
- assert_equal "value2", session[:symbol]
- end
-
def test_process_with_session_kwarg
process :no_op, method: "GET", session: { "string" => "value1", symbol: "value2" }
assert_equal "value1", session["string"]
@@ -327,15 +291,6 @@ XML
assert_equal "value2", session[:symbol]
end
- def test_deprecated_process_merges_session_arg
- session[:foo] = "bar"
- assert_deprecated {
- get :no_op, nil, bar: "baz"
- }
- assert_equal "bar", session[:foo]
- assert_equal "baz", session[:bar]
- end
-
def test_process_merges_session_arg
session[:foo] = "bar"
get :no_op, session: { bar: "baz" }
@@ -343,15 +298,6 @@ XML
assert_equal "baz", session[:bar]
end
- def test_deprecated_merged_session_arg_is_retained_across_requests
- assert_deprecated {
- get :no_op, nil, foo: "bar"
- }
- assert_equal "bar", session[:foo]
- get :no_op
- assert_equal "bar", session[:foo]
- end
-
def test_merged_session_arg_is_retained_across_requests
get :no_op, session: { foo: "bar" }
assert_equal "bar", session[:foo]
@@ -393,11 +339,6 @@ XML
assert_equal "/test_case_test/test/test_uri", @response.body
end
- def test_deprecated_process_with_request_uri_with_params
- assert_deprecated { process :test_uri, "GET", id: 7 }
- assert_equal "/test_case_test/test/test_uri/7", @response.body
- end
-
def test_process_with_request_uri_with_params
process :test_uri,
method: "GET",
@@ -406,12 +347,6 @@ XML
assert_equal "/test_case_test/test/test_uri/7", @response.body
end
- def test_deprecated_process_with_request_uri_with_params_with_explicit_uri
- @request.env["PATH_INFO"] = "/explicit/uri"
- assert_deprecated { process :test_uri, "GET", id: 7 }
- assert_equal "/explicit/uri", @response.body
- end
-
def test_process_with_request_uri_with_params_with_explicit_uri
@request.env["PATH_INFO"] = "/explicit/uri"
process :test_uri, method: "GET", params: { id: 7 }
@@ -439,18 +374,18 @@ XML
assert_equal "OK", @response.body
end
- def test_should_not_impose_childless_html_tags_in_xml
- process :test_xml_output
+ def test_should_impose_childless_html_tags_in_html
+ process :test_xml_output, params: { response_as: "text/html" }
- begin
- $stderr = StringIO.new
- assert_select "area" #This will cause a warning if content is processed as HTML
- $stderr.rewind && err = $stderr.read
- ensure
- $stderr = STDERR
- end
+ # <area> auto-closes, so the <p> becomes a sibling
+ assert_select "root > area + p"
+ end
+
+ def test_should_not_impose_childless_html_tags_in_xml
+ process :test_xml_output, params: { response_as: "application/xml" }
- assert err.empty?
+ # <area> is not special, so the <p> is its child
+ assert_select "root > area > p"
end
def test_assert_generates
@@ -491,20 +426,6 @@ XML
end
end
- def test_deprecated_params_passing
- assert_deprecated {
- get :test_params, page: { name: "Page name", month: "4", year: "2004", day: "6" }
- }
- parsed_params = ::JSON.parse(@response.body)
- assert_equal(
- {
- "controller" => "test_case_test/test", "action" => "test_params",
- "page" => { "name" => "Page name", "month" => "4", "year" => "2004", "day" => "6" }
- },
- parsed_params
- )
- end
-
def test_params_passing
get :test_params, params: {
page: {
@@ -525,15 +446,15 @@ XML
end
def test_query_param_named_action
- get :test_query_parameters, params: {action: "foobar"}
+ get :test_query_parameters, params: { action: "foobar" }
parsed_params = JSON.parse(@response.body)
- assert_equal({"action" => "foobar"}, parsed_params)
+ assert_equal({ "action" => "foobar" }, parsed_params)
end
def test_request_param_named_action
- post :test_request_parameters, params: {action: "foobar"}
+ post :test_request_parameters, params: { action: "foobar" }
parsed_params = eval(@response.body)
- assert_equal({"action" => "foobar"}, parsed_params)
+ assert_equal({ "action" => "foobar" }, parsed_params)
end
def test_kwarg_params_passing_with_session_and_flash
@@ -548,8 +469,8 @@ XML
parsed_params = ::JSON.parse(@response.body)
assert_equal(
- {"controller" => "test_case_test/test", "action" => "test_params",
- "page" => {"name" => "Page name", "month" => "4", "year" => "2004", "day" => "6"}},
+ { "controller" => "test_case_test/test", "action" => "test_params",
+ "page" => { "name" => "Page name", "month" => "4", "year" => "2004", "day" => "6" } },
parsed_params
)
@@ -563,8 +484,8 @@ XML
}
parsed_params = ::JSON.parse(@response.body)
assert_equal(
- {"controller" => "test_case_test/test", "action" => "test_params",
- "page" => {"name" => "Page name", "month" => "4", "year" => "2004", "day" => "6"}},
+ { "controller" => "test_case_test/test", "action" => "test_params",
+ "page" => { "name" => "Page name", "month" => "4", "year" => "2004", "day" => "6" } },
parsed_params
)
end
@@ -573,7 +494,7 @@ XML
get :test_params, params: { format: "json", count: 999 }
parsed_params = ::JSON.parse(@response.body)
assert_equal(
- {"controller" => "test_case_test/test", "action" => "test_params",
+ { "controller" => "test_case_test/test", "action" => "test_params",
"format" => "json", "count" => "999" },
parsed_params
)
@@ -583,17 +504,7 @@ XML
get :test_params, params: { format: "json", id: 1 }
parsed_params = ::JSON.parse(@response.body)
assert_equal(
- {"controller" => "test_case_test/test", "action" => "test_params",
- "format" => "json", "id" => "1" },
- parsed_params
- )
- end
-
- def test_deprecated_params_passing_path_parameter_is_string_when_not_html_request
- assert_deprecated { get :test_params, format: "json", id: 1 }
- parsed_params = ::JSON.parse(@response.body)
- assert_equal(
- {"controller" => "test_case_test/test", "action" => "test_params",
+ { "controller" => "test_case_test/test", "action" => "test_params",
"format" => "json", "id" => "1" },
parsed_params
)
@@ -607,8 +518,8 @@ XML
end
parsed_params = ::JSON.parse(@response.body)
assert_equal(
- {"controller" => "test_case_test/test", "action" => "test_params",
- "frozen" => "icy", "frozens" => ["icy"], "deepfreeze" => { "frozen" => "icy" }},
+ { "controller" => "test_case_test/test", "action" => "test_params",
+ "frozen" => "icy", "frozens" => ["icy"], "deepfreeze" => { "frozen" => "icy" } },
parsed_params
)
end
@@ -637,6 +548,20 @@ XML
assert_equal "application/json", parsed_env["CONTENT_TYPE"]
end
+ def test_using_as_json_sets_request_content_type_to_json
+ post :render_body, params: { bool_value: true, str_value: "string", num_value: 2 }, as: :json
+
+ assert_equal "application/json", @request.headers["CONTENT_TYPE"]
+ assert_equal true, @request.request_parameters[:bool_value]
+ assert_equal "string", @request.request_parameters[:str_value]
+ assert_equal 2, @request.request_parameters[:num_value]
+ end
+
+ def test_using_as_json_sets_format_json
+ post :render_body, params: { bool_value: true, str_value: "string", num_value: 2 }, as: :json
+ assert_equal "json", @request.format
+ end
+
def test_mutating_content_type_headers_for_plain_text_files_sets_the_header
@request.headers["Content-Type"] = "text/plain"
post :render_body, params: { name: "foo.txt" }
@@ -669,11 +594,6 @@ XML
assert_kind_of String, @request.path_parameters[:id]
end
- def test_deprecared_id_converted_to_string
- assert_deprecated { get :test_params, id: 20, foo: Object.new}
- assert_kind_of String, @request.path_parameters[:id]
- end
-
def test_array_path_parameter_handled_properly
with_routing do |set|
set.draw do
@@ -728,16 +648,10 @@ XML
assert_nil @request.env["HTTP_ACCEPT"]
end
- def test_deprecated_xhr_with_params
- assert_deprecated { xhr :get, :test_params, params: { id: 1 } }
-
- assert_equal({"id"=>"1", "controller"=>"test_case_test/test", "action"=>"test_params"}, ::JSON.parse(@response.body))
- end
-
def test_xhr_with_params
get :test_params, params: { id: 1 }, xhr: true
- assert_equal({"id"=>"1", "controller"=>"test_case_test/test", "action"=>"test_params"}, ::JSON.parse(@response.body))
+ assert_equal({ "id" => "1", "controller" => "test_case_test/test", "action" => "test_params" }, ::JSON.parse(@response.body))
end
def test_xhr_with_session
@@ -749,23 +663,6 @@ XML
assert_equal "it works", session[:symbol], "Test session hash should allow indifferent access"
end
- def test_deprecated_xhr_with_session
- assert_deprecated { xhr :get, :set_session }
-
- assert_equal "A wonder", session["string"], "A value stored in the session should be available by string key"
- assert_equal "A wonder", session[:string], "Test session hash should allow indifferent access"
- assert_equal "it works", session["symbol"], "Test session hash should allow indifferent access"
- assert_equal "it works", session[:symbol], "Test session hash should allow indifferent access"
- end
-
- def test_deprecated_params_reset_between_post_requests
- assert_deprecated { post :no_op, foo: "bar" }
- assert_equal "bar", @request.params[:foo]
-
- post :no_op
- assert @request.params[:foo].blank?
- end
-
def test_params_reset_between_post_requests
post :no_op, params: { foo: "bar" }
assert_equal "bar", @request.params[:foo]
@@ -856,10 +753,10 @@ XML
def test_multiple_mixed_method_process_should_scrub_rack_input
post :test_params, params: { id: 1, foo: "an foo" }
- assert_equal({"id"=>"1", "foo" => "an foo", "controller"=>"test_case_test/test", "action"=>"test_params"}, ::JSON.parse(@response.body))
+ assert_equal({ "id" => "1", "foo" => "an foo", "controller" => "test_case_test/test", "action" => "test_params" }, ::JSON.parse(@response.body))
get :test_params, params: { bar: "an bar" }
- assert_equal({"bar"=>"an bar", "controller"=>"test_case_test/test", "action"=>"test_params"}, ::JSON.parse(@response.body))
+ assert_equal({ "bar" => "an bar", "controller" => "test_case_test/test", "action" => "test_params" }, ::JSON.parse(@response.body))
end
%w(controller response request).each do |variable|
@@ -884,7 +781,7 @@ XML
READ_PLAIN = "r:binary"
def test_test_uploaded_file
- filename = "mona_lisa.jpg"
+ filename = "ruby_on_rails.jpg"
path = "#{FILES_DIR}/#{filename}"
content_type = "image/png"
expected = File.read(path)
@@ -904,13 +801,13 @@ XML
def test_fixture_path_is_accessed_from_self_instead_of_active_support_test_case
TestCaseTest.stub :fixture_path, FILES_DIR do
- uploaded_file = fixture_file_upload("/mona_lisa.jpg", "image/png")
- assert_equal File.open("#{FILES_DIR}/mona_lisa.jpg", READ_PLAIN).read, uploaded_file.read
+ uploaded_file = fixture_file_upload("/ruby_on_rails.jpg", "image/png")
+ assert_equal File.open("#{FILES_DIR}/ruby_on_rails.jpg", READ_PLAIN).read, uploaded_file.read
end
end
def test_test_uploaded_file_with_binary
- filename = "mona_lisa.jpg"
+ filename = "ruby_on_rails.jpg"
path = "#{FILES_DIR}/#{filename}"
content_type = "image/png"
@@ -922,7 +819,7 @@ XML
end
def test_fixture_file_upload_with_binary
- filename = "mona_lisa.jpg"
+ filename = "ruby_on_rails.jpg"
path = "#{FILES_DIR}/#{filename}"
content_type = "image/jpg"
@@ -934,46 +831,44 @@ XML
end
def test_fixture_file_upload_should_be_able_access_to_tempfile
- file = fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg")
+ file = fixture_file_upload(FILES_DIR + "/ruby_on_rails.jpg", "image/jpg")
assert file.respond_to?(:tempfile), "expected tempfile should respond on fixture file object, got nothing"
end
def test_fixture_file_upload
post :test_file_upload,
params: {
- file: fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg")
+ file: fixture_file_upload(FILES_DIR + "/ruby_on_rails.jpg", "image/jpg")
}
- assert_equal "159528", @response.body
+ assert_equal "45142", @response.body
end
def test_fixture_file_upload_relative_to_fixture_path
TestCaseTest.stub :fixture_path, FILES_DIR do
- uploaded_file = fixture_file_upload("mona_lisa.jpg", "image/jpg")
- assert_equal File.open("#{FILES_DIR}/mona_lisa.jpg", READ_PLAIN).read, uploaded_file.read
+ uploaded_file = fixture_file_upload("ruby_on_rails.jpg", "image/jpg")
+ assert_equal File.open("#{FILES_DIR}/ruby_on_rails.jpg", READ_PLAIN).read, uploaded_file.read
end
end
- def test_fixture_file_upload_ignores_nil_fixture_path
- uploaded_file = fixture_file_upload("#{FILES_DIR}/mona_lisa.jpg", "image/jpg")
- assert_equal File.open("#{FILES_DIR}/mona_lisa.jpg", READ_PLAIN).read, uploaded_file.read
+ def test_fixture_file_upload_ignores_fixture_path_given_full_path
+ TestCaseTest.stub :fixture_path, File.dirname(__FILE__) 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
end
- def test_deprecated_action_dispatch_uploaded_file_upload
- filename = "mona_lisa.jpg"
- path = "#{FILES_DIR}/#{filename}"
- assert_deprecated {
- post :test_file_upload, file: Rack::Test::UploadedFile.new(path, "image/jpg", true)
- }
- assert_equal "159528", @response.body
+ def test_fixture_file_upload_ignores_nil_fixture_path
+ 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
def test_action_dispatch_uploaded_file_upload
- filename = "mona_lisa.jpg"
+ filename = "ruby_on_rails.jpg"
path = "#{FILES_DIR}/#{filename}"
post :test_file_upload, params: {
file: Rack::Test::UploadedFile.new(path, "image/jpg", true)
}
- assert_equal "159528", @response.body
+ assert_equal "45142", @response.body
end
def test_test_uploaded_file_exception_when_file_doesnt_exist
@@ -1208,7 +1103,7 @@ class RoutingDefaultsTest < ActionController::TestCase
end
def test_route_option_can_be_passed_via_process
- get :post, params: { id: 1, bucket_type: "post"}
+ get :post, params: { id: 1, bucket_type: "post" }
assert_equal "/posts/1", @response.body
end
diff --git a/actionpack/test/controller/url_for_integration_test.rb b/actionpack/test/controller/url_for_integration_test.rb
index fc746add9b..f640e77b99 100644
--- a/actionpack/test/controller/url_for_integration_test.rb
+++ b/actionpack/test/controller/url_for_integration_test.rb
@@ -43,7 +43,7 @@ module ActionPack
get "clients" => "projects#index"
get "ignorecase/geocode/:postalcode" => "geocode#show", :postalcode => /hx\d\d-\d[a-z]{2}/i
- get "extended/geocode/:postalcode" => "geocode#show",:constraints => {
+ get "extended/geocode/:postalcode" => "geocode#show", :constraints => {
postalcode: /# Postcode format
\d{5} #Prefix
(-\d{4})? #Suffix
@@ -73,102 +73,102 @@ module ActionPack
end
[
- ["/admin/users",[ { use_route: "admin_users" }]],
- ["/admin/users",[ { controller: "admin/users" }]],
- ["/admin/users",[ { controller: "admin/users", action: "index" }]],
- ["/admin/users",[ { action: "index" }, { controller: "admin/users", action: "index" }, "/admin/users"]],
- ["/admin/users",[ { controller: "users", action: "index" }, { controller: "admin/accounts", action: "show", id: "1" }, "/admin/accounts/show/1"]],
- ["/people",[ { controller: "/people", action: "index" }, {controller: "admin/accounts", action: "foo", id: "bar"}, "/admin/accounts/foo/bar"]],
-
- ["/admin/posts",[ { controller: "admin/posts" }]],
- ["/admin/posts/new",[ { controller: "admin/posts", action: "new" }]],
-
- ["/blog/2009",[ { controller: "posts", action: "show_date", year: 2009 }]],
- ["/blog/2009/1",[ { controller: "posts", action: "show_date", year: 2009, month: 1 }]],
- ["/blog/2009/1/1",[ { controller: "posts", action: "show_date", year: 2009, month: 1, day: 1 }]],
-
- ["/archive/2010",[ { controller: "archive", action: "index", year: "2010" }]],
- ["/archive",[ { controller: "archive", action: "index" }]],
- ["/archive?year=january",[ { controller: "archive", action: "index", year: "january" }]],
-
- ["/people",[ { controller: "people", action: "index" }]],
- ["/people",[ { action: "index" }, { controller: "people", action: "index" }, "/people"]],
- ["/people",[ { action: "index" }, { controller: "people", action: "show", id: "1" }, "/people/show/1"]],
- ["/people",[ { controller: "people", action: "index" }, { controller: "people", action: "show", id: "1" }, "/people/show/1"]],
- ["/people",[ {}, { controller: "people", action: "index" }, "/people"]],
- ["/people/1",[ { controller: "people", action: "show" }, { controller: "people", action: "show", id: "1" }, "/people/show/1"]],
- ["/people/new",[ { use_route: "new_person" }]],
- ["/people/new",[ { controller: "people", action: "new" }]],
- ["/people/1",[ { use_route: "person", id: "1" }]],
- ["/people/1",[ { controller: "people", action: "show", id: "1" }]],
- ["/people/1.xml",[ { controller: "people", action: "show", id: "1", format: "xml" }]],
- ["/people/1",[ { controller: "people", action: "show", id: 1 }]],
- ["/people/1",[ { controller: "people", action: "show", id: Model.new("1") }]],
- ["/people/1",[ { action: "show", id: "1" }, { controller: "people", action: "index" }, "/people"]],
- ["/people/1",[ { action: "show", id: 1 }, { controller: "people", action: "show", id: "1" }, "/people/show/1"]],
- ["/people",[ { controller: "people", action: "index" }, { controller: "people", action: "show", id: "1" }, "/people/show/1"]],
- ["/people/1",[ {}, { controller: "people", action: "show", id: "1" }, "/people/show/1"]],
- ["/people/1",[ { controller: "people", action: "show" }, { controller: "people", action: "index", id: "1" }, "/people/index/1"]],
- ["/people/1/edit",[ { controller: "people", action: "edit", id: "1" }]],
- ["/people/1/edit.xml",[ { controller: "people", action: "edit", id: "1", format: "xml" }]],
- ["/people/1/edit",[ { use_route: "edit_person", id: "1" }]],
- ["/people/1?legacy=true",[ { controller: "people", action: "show", id: "1", legacy: "true" }]],
- ["/people?legacy=true",[ { controller: "people", action: "index", legacy: "true" }]],
-
- ["/id_default/2",[ { controller: "foo", action: "id_default", id: "2" }]],
- ["/id_default",[ { controller: "foo", action: "id_default", id: "1" }]],
- ["/id_default",[ { controller: "foo", action: "id_default", id: 1 }]],
- ["/id_default",[ { controller: "foo", action: "id_default" }]],
- ["/optional/bar",[ { controller: "posts", action: "index", optional: "bar" }]],
- ["/posts",[ { controller: "posts", action: "index" }]],
-
- ["/project",[ { controller: "project", action: "index" }]],
- ["/projects/1",[ { controller: "project", action: "index", project_id: "1" }]],
- ["/projects/1",[ { controller: "project", action: "index"}, {project_id: "1", controller: "project", action: "index" }, "/projects/1"]],
- ["/projects/1",[ { use_route: "project", controller: "project", action: "index", project_id: "1" }]],
- ["/projects/1",[ { use_route: "project", controller: "project", action: "index" }, { controller: "project", action: "index", project_id: "1" }, "/projects/1"]],
-
- ["/clients",[ { controller: "projects", action: "index" }]],
- ["/clients?project_id=1",[ { controller: "projects", action: "index", project_id: "1" }]],
- ["/clients",[ { controller: "projects", action: "index" }, { project_id: "1", controller: "project", action: "index" }, "/projects/1"]],
-
- ["/comment/20",[ { id: 20 }, { controller: "comments", action: "show" }, "/comments/show"]],
- ["/comment/20",[ { controller: "comments", id: 20, action: "show" }]],
- ["/comments/boo",[ { controller: "comments", action: "boo" }]],
-
- ["/ws/posts/show/1",[ { controller: "posts", action: "show", id: "1", ws: true }]],
- ["/ws/posts",[ { controller: "posts", action: "index", ws: true }]],
-
- ["/account",[ { controller: "account", action: "subscription" }]],
- ["/account/billing",[ { controller: "account", action: "billing" }]],
-
- ["/pages/1/notes/show/1",[ { page_id: "1", controller: "notes", action: "show", id: "1" }]],
- ["/pages/1/notes/list",[ { page_id: "1", controller: "notes", action: "list" }]],
- ["/pages/1/notes",[ { page_id: "1", controller: "notes", action: "index" }]],
- ["/pages/1/notes",[ { page_id: "1", controller: "notes" }]],
- ["/notes",[ { page_id: nil, controller: "notes" }]],
- ["/notes",[ { controller: "notes" }]],
- ["/notes/print",[ { controller: "notes", action: "print" }]],
- ["/notes/print",[ {}, { controller: "notes", action: "print" }, "/notes/print"]],
-
- ["/notes/index/1",[ { controller: "notes" }, { controller: "notes", action: "index", id: "1" }, "/notes/index/1"]],
- ["/notes/index/1",[ { controller: "notes" }, { controller: "notes", id: "1", action: "index" }, "/notes/index/1"]],
- ["/notes/index/1",[ { action: "index" }, { controller: "notes", id: "1", action: "index" }, "/notes/index/1"]],
- ["/notes/index/1",[ {}, { controller: "notes", id: "1", action: "index" }, "/notes/index/1"]],
- ["/notes/show/1",[ {}, { controller: "notes", action: "show", id: "1" }, "/notes/show/1"]],
- ["/posts",[ { controller: "posts" }, { controller: "notes", action: "show", id: "1" }, "/notes/show/1"]],
- ["/notes/list",[ { action: "list" }, { controller: "notes", action: "show", id: "1" }, "/notes/show/1"]],
-
- ["/posts/ping",[ { controller: "posts", action: "ping" }]],
- ["/posts/show/1",[ { controller: "posts", action: "show", id: "1" }]],
- ["/posts/show/1",[ { controller: "posts", action: "show", id: "1", format: "" }]],
- ["/posts",[ { controller: "posts" }]],
- ["/posts",[ { controller: "posts", action: "index" }]],
- ["/posts/create",[ { action: "create" }, {day: nil, month: nil, controller: "posts", action: "show_date"}, "/blog"]],
- ["/posts?foo=bar",[ { controller: "posts", foo: "bar" }]],
+ ["/admin/users", [ { use_route: "admin_users" }]],
+ ["/admin/users", [ { controller: "admin/users" }]],
+ ["/admin/users", [ { controller: "admin/users", action: "index" }]],
+ ["/admin/users", [ { action: "index" }, { controller: "admin/users", action: "index" }, "/admin/users"]],
+ ["/admin/users", [ { controller: "users", action: "index" }, { controller: "admin/accounts", action: "show", id: "1" }, "/admin/accounts/show/1"]],
+ ["/people", [ { controller: "/people", action: "index" }, { controller: "admin/accounts", action: "foo", id: "bar" }, "/admin/accounts/foo/bar"]],
+
+ ["/admin/posts", [ { controller: "admin/posts" }]],
+ ["/admin/posts/new", [ { controller: "admin/posts", action: "new" }]],
+
+ ["/blog/2009", [ { controller: "posts", action: "show_date", year: 2009 }]],
+ ["/blog/2009/1", [ { controller: "posts", action: "show_date", year: 2009, month: 1 }]],
+ ["/blog/2009/1/1", [ { controller: "posts", action: "show_date", year: 2009, month: 1, day: 1 }]],
+
+ ["/archive/2010", [ { controller: "archive", action: "index", year: "2010" }]],
+ ["/archive", [ { controller: "archive", action: "index" }]],
+ ["/archive?year=january", [ { controller: "archive", action: "index", year: "january" }]],
+
+ ["/people", [ { controller: "people", action: "index" }]],
+ ["/people", [ { action: "index" }, { controller: "people", action: "index" }, "/people"]],
+ ["/people", [ { action: "index" }, { controller: "people", action: "show", id: "1" }, "/people/show/1"]],
+ ["/people", [ { controller: "people", action: "index" }, { controller: "people", action: "show", id: "1" }, "/people/show/1"]],
+ ["/people", [ {}, { controller: "people", action: "index" }, "/people"]],
+ ["/people/1", [ { controller: "people", action: "show" }, { controller: "people", action: "show", id: "1" }, "/people/show/1"]],
+ ["/people/new", [ { use_route: "new_person" }]],
+ ["/people/new", [ { controller: "people", action: "new" }]],
+ ["/people/1", [ { use_route: "person", id: "1" }]],
+ ["/people/1", [ { controller: "people", action: "show", id: "1" }]],
+ ["/people/1.xml", [ { controller: "people", action: "show", id: "1", format: "xml" }]],
+ ["/people/1", [ { controller: "people", action: "show", id: 1 }]],
+ ["/people/1", [ { controller: "people", action: "show", id: Model.new("1") }]],
+ ["/people/1", [ { action: "show", id: "1" }, { controller: "people", action: "index" }, "/people"]],
+ ["/people/1", [ { action: "show", id: 1 }, { controller: "people", action: "show", id: "1" }, "/people/show/1"]],
+ ["/people", [ { controller: "people", action: "index" }, { controller: "people", action: "show", id: "1" }, "/people/show/1"]],
+ ["/people/1", [ {}, { controller: "people", action: "show", id: "1" }, "/people/show/1"]],
+ ["/people/1", [ { controller: "people", action: "show" }, { controller: "people", action: "index", id: "1" }, "/people/index/1"]],
+ ["/people/1/edit", [ { controller: "people", action: "edit", id: "1" }]],
+ ["/people/1/edit.xml", [ { controller: "people", action: "edit", id: "1", format: "xml" }]],
+ ["/people/1/edit", [ { use_route: "edit_person", id: "1" }]],
+ ["/people/1?legacy=true", [ { controller: "people", action: "show", id: "1", legacy: "true" }]],
+ ["/people?legacy=true", [ { controller: "people", action: "index", legacy: "true" }]],
+
+ ["/id_default/2", [ { controller: "foo", action: "id_default", id: "2" }]],
+ ["/id_default", [ { controller: "foo", action: "id_default", id: "1" }]],
+ ["/id_default", [ { controller: "foo", action: "id_default", id: 1 }]],
+ ["/id_default", [ { controller: "foo", action: "id_default" }]],
+ ["/optional/bar", [ { controller: "posts", action: "index", optional: "bar" }]],
+ ["/posts", [ { controller: "posts", action: "index" }]],
+
+ ["/project", [ { controller: "project", action: "index" }]],
+ ["/projects/1", [ { controller: "project", action: "index", project_id: "1" }]],
+ ["/projects/1", [ { controller: "project", action: "index" }, { project_id: "1", controller: "project", action: "index" }, "/projects/1"]],
+ ["/projects/1", [ { use_route: "project", controller: "project", action: "index", project_id: "1" }]],
+ ["/projects/1", [ { use_route: "project", controller: "project", action: "index" }, { controller: "project", action: "index", project_id: "1" }, "/projects/1"]],
+
+ ["/clients", [ { controller: "projects", action: "index" }]],
+ ["/clients?project_id=1", [ { controller: "projects", action: "index", project_id: "1" }]],
+ ["/clients", [ { controller: "projects", action: "index" }, { project_id: "1", controller: "project", action: "index" }, "/projects/1"]],
+
+ ["/comment/20", [ { id: 20 }, { controller: "comments", action: "show" }, "/comments/show"]],
+ ["/comment/20", [ { controller: "comments", id: 20, action: "show" }]],
+ ["/comments/boo", [ { controller: "comments", action: "boo" }]],
+
+ ["/ws/posts/show/1", [ { controller: "posts", action: "show", id: "1", ws: true }]],
+ ["/ws/posts", [ { controller: "posts", action: "index", ws: true }]],
+
+ ["/account", [ { controller: "account", action: "subscription" }]],
+ ["/account/billing", [ { controller: "account", action: "billing" }]],
+
+ ["/pages/1/notes/show/1", [ { page_id: "1", controller: "notes", action: "show", id: "1" }]],
+ ["/pages/1/notes/list", [ { page_id: "1", controller: "notes", action: "list" }]],
+ ["/pages/1/notes", [ { page_id: "1", controller: "notes", action: "index" }]],
+ ["/pages/1/notes", [ { page_id: "1", controller: "notes" }]],
+ ["/notes", [ { page_id: nil, controller: "notes" }]],
+ ["/notes", [ { controller: "notes" }]],
+ ["/notes/print", [ { controller: "notes", action: "print" }]],
+ ["/notes/print", [ {}, { controller: "notes", action: "print" }, "/notes/print"]],
+
+ ["/notes/index/1", [ { controller: "notes" }, { controller: "notes", action: "index", id: "1" }, "/notes/index/1"]],
+ ["/notes/index/1", [ { controller: "notes" }, { controller: "notes", id: "1", action: "index" }, "/notes/index/1"]],
+ ["/notes/index/1", [ { action: "index" }, { controller: "notes", id: "1", action: "index" }, "/notes/index/1"]],
+ ["/notes/index/1", [ {}, { controller: "notes", id: "1", action: "index" }, "/notes/index/1"]],
+ ["/notes/show/1", [ {}, { controller: "notes", action: "show", id: "1" }, "/notes/show/1"]],
+ ["/posts", [ { controller: "posts" }, { controller: "notes", action: "show", id: "1" }, "/notes/show/1"]],
+ ["/notes/list", [ { action: "list" }, { controller: "notes", action: "show", id: "1" }, "/notes/show/1"]],
+
+ ["/posts/ping", [ { controller: "posts", action: "ping" }]],
+ ["/posts/show/1", [ { controller: "posts", action: "show", id: "1" }]],
+ ["/posts/show/1", [ { controller: "posts", action: "show", id: "1", format: "" }]],
+ ["/posts", [ { controller: "posts" }]],
+ ["/posts", [ { controller: "posts", action: "index" }]],
+ ["/posts/create", [ { action: "create" }, { day: nil, month: nil, controller: "posts", action: "show_date" }, "/blog"]],
+ ["/posts?foo=bar", [ { controller: "posts", foo: "bar" }]],
["/posts?foo%5B%5D=bar&foo%5B%5D=baz", [{ controller: "posts", foo: ["bar", "baz"] }]],
["/posts?page=2", [{ controller: "posts", page: 2 }]],
- ["/posts?q%5Bfoo%5D%5Ba%5D=b", [{ controller: "posts", q: { foo: { a: "b"}} }]],
+ ["/posts?q%5Bfoo%5D%5Ba%5D=b", [{ controller: "posts", q: { foo: { a: "b" } } }]],
["/news.rss", [{ controller: "news", action: "index", format: "rss" }]],
].each_with_index do |(url, params), i|
diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb
index ad4acf3462..382b4e273d 100644
--- a/actionpack/test/controller/url_for_test.rb
+++ b/actionpack/test/controller/url_for_test.rb
@@ -222,27 +222,27 @@ module AbstractController
def test_trailing_slash
add_host!
- options = {controller: "foo", trailing_slash: true, action: "bar", id: "33"}
- assert_equal("http://www.basecamphq.com/foo/bar/33/", W.new.url_for(options) )
+ options = { controller: "foo", trailing_slash: true, action: "bar", id: "33" }
+ assert_equal("http://www.basecamphq.com/foo/bar/33/", W.new.url_for(options))
end
def test_trailing_slash_with_protocol
add_host!
- options = { trailing_slash: true,protocol: "https", controller: "foo", action: "bar", id: "33"}
- assert_equal("https://www.basecamphq.com/foo/bar/33/", W.new.url_for(options) )
+ options = { trailing_slash: true, protocol: "https", controller: "foo", action: "bar", id: "33" }
+ assert_equal("https://www.basecamphq.com/foo/bar/33/", W.new.url_for(options))
assert_equal "https://www.basecamphq.com/foo/bar/33/?query=string", W.new.url_for(options.merge(query: "string"))
end
def test_trailing_slash_with_only_path
- options = {controller: "foo", trailing_slash: true}
+ options = { controller: "foo", trailing_slash: true }
assert_equal "/foo/", W.new.url_for(options.merge(only_path: true))
options.update(action: "bar", id: "33")
assert_equal "/foo/bar/33/", W.new.url_for(options.merge(only_path: true))
- assert_equal "/foo/bar/33/?query=string", W.new.url_for(options.merge(query: "string",only_path: true))
+ assert_equal "/foo/bar/33/?query=string", W.new.url_for(options.merge(query: "string", only_path: true))
end
def test_trailing_slash_with_anchor
- options = {trailing_slash: true, controller: "foo", action: "bar", id: "33", only_path: true, anchor: "chapter7"}
+ options = { trailing_slash: true, controller: "foo", action: "bar", id: "33", only_path: true, anchor: "chapter7" }
assert_equal "/foo/bar/33/#chapter7", W.new.url_for(options)
assert_equal "/foo/bar/33/?query=string#chapter7", W.new.url_for(options.merge(query: "string"))
end
@@ -250,8 +250,8 @@ module AbstractController
def test_trailing_slash_with_params
url = W.new.url_for(trailing_slash: true, only_path: true, controller: "cont", action: "act", p1: "cafe", p2: "link")
params = extract_params(url)
- assert_equal({p1: "cafe"}.to_query, params[0])
- assert_equal({p2: "link"}.to_query, params[1])
+ assert_equal({ p1: "cafe" }.to_query, params[0])
+ assert_equal({ p2: "link" }.to_query, params[1])
end
def test_relative_url_root_is_respected
@@ -348,40 +348,40 @@ module AbstractController
def test_two_parameters
url = W.new.url_for(only_path: true, controller: "c", action: "a", p1: "X1", p2: "Y2")
params = extract_params(url)
- assert_equal({p1: "X1"}.to_query, params[0])
- assert_equal({p2: "Y2"}.to_query, params[1])
+ assert_equal({ p1: "X1" }.to_query, params[0])
+ assert_equal({ p2: "Y2" }.to_query, params[1])
end
def test_hash_parameter
- url = W.new.url_for(only_path: true, controller: "c", action: "a", query: {name: "Bob", category: "prof"})
+ url = W.new.url_for(only_path: true, controller: "c", action: "a", query: { name: "Bob", category: "prof" })
params = extract_params(url)
- assert_equal({"query[category]" => "prof"}.to_query, params[0])
- assert_equal({"query[name]" => "Bob"}.to_query, params[1])
+ assert_equal({ "query[category]" => "prof" }.to_query, params[0])
+ assert_equal({ "query[name]" => "Bob" }.to_query, params[1])
end
def test_array_parameter
url = W.new.url_for(only_path: true, controller: "c", action: "a", query: ["Bob", "prof"])
params = extract_params(url)
- assert_equal({"query[]" => "Bob"}.to_query, params[0])
- assert_equal({"query[]" => "prof"}.to_query, params[1])
+ assert_equal({ "query[]" => "Bob" }.to_query, params[0])
+ assert_equal({ "query[]" => "prof" }.to_query, params[1])
end
def test_hash_recursive_parameters
- url = W.new.url_for(only_path: true, controller: "c", action: "a", query: {person: {name: "Bob", position: "prof"}, hobby: "piercing"})
+ url = W.new.url_for(only_path: true, controller: "c", action: "a", query: { person: { name: "Bob", position: "prof" }, hobby: "piercing" })
params = extract_params(url)
- assert_equal({"query[hobby]" => "piercing"}.to_query, params[0])
- assert_equal({"query[person][name]" => "Bob" }.to_query, params[1])
- assert_equal({"query[person][position]" => "prof" }.to_query, params[2])
+ assert_equal({ "query[hobby]" => "piercing" }.to_query, params[0])
+ assert_equal({ "query[person][name]" => "Bob" }.to_query, params[1])
+ assert_equal({ "query[person][position]" => "prof" }.to_query, params[2])
end
def test_hash_recursive_and_array_parameters
- url = W.new.url_for(only_path: true, controller: "c", action: "a", id: 101, query: {person: {name: "Bob", position: ["prof", "art director"]}, hobby: "piercing"})
+ url = W.new.url_for(only_path: true, controller: "c", action: "a", id: 101, query: { person: { name: "Bob", position: ["prof", "art director"] }, hobby: "piercing" })
assert_match(%r(^/c/a/101), url)
params = extract_params(url)
- assert_equal({"query[hobby]" => "piercing" }.to_query, params[0])
- assert_equal({"query[person][name]" => "Bob" }.to_query, params[1])
- assert_equal({"query[person][position][]" => "art director"}.to_query, params[2])
- assert_equal({"query[person][position][]" => "prof" }.to_query, params[3])
+ assert_equal({ "query[hobby]" => "piercing" }.to_query, params[0])
+ assert_equal({ "query[person][name]" => "Bob" }.to_query, params[1])
+ assert_equal({ "query[person][position][]" => "art director" }.to_query, params[2])
+ assert_equal({ "query[person][position][]" => "prof" }.to_query, params[3])
end
def test_url_action_controller_parameters
@@ -407,7 +407,7 @@ module AbstractController
kls.default_url_options[:host] = "www.basecamphq.com"
controller = kls.new
- params = {action: :index, controller: :posts, format: :xml}
+ params = { action: :index, controller: :posts, format: :xml }
assert_equal("http://www.basecamphq.com/posts.xml", controller.send(:url_for, params))
params[:format] = nil
assert_equal("http://www.basecamphq.com/", controller.send(:url_for, params))
@@ -423,7 +423,7 @@ module AbstractController
first_class.default_url_options[:host] = first_host
second_class.default_url_options[:host] = second_host
- assert_equal first_host, first_class.default_url_options[:host]
+ assert_equal first_host, first_class.default_url_options[:host]
assert_equal second_host, second_class.default_url_options[:host]
end
@@ -487,6 +487,27 @@ module AbstractController
end
end
+ def test_default_params_first_empty
+ with_routing do |set|
+ set.draw do
+ get "(:param1)/test(/:param2)" => "index#index",
+ defaults: {
+ param1: 1,
+ param2: 2
+ },
+ constraints: {
+ param1: /\d*/,
+ param2: /\d+/
+ }
+ end
+
+ kls = Class.new { include set.url_helpers }
+ kls.default_url_options[:host] = "www.basecamphq.com"
+
+ assert_equal "http://www.basecamphq.com/test", kls.new.url_for(controller: "index", param1: "1")
+ end
+ end
+
private
def extract_params(url)
url.split("?", 2).last.split("&").sort
diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb
index d64ff4d6ce..a055e6d177 100644
--- a/actionpack/test/controller/url_rewriter_test.rb
+++ b/actionpack/test/controller/url_rewriter_test.rb
@@ -79,7 +79,7 @@ class UrlRewriterTests < ActionController::TestCase
end
def test_trailing_slash
- options = {controller: "foo", action: "bar", id: "3", only_path: true}
+ options = { controller: "foo", action: "bar", id: "3", only_path: true }
assert_equal "/foo/bar/3", @rewriter.rewrite(@routes, options)
assert_equal "/foo/bar/3?query=string", @rewriter.rewrite(@routes, options.merge(query: "string"))
options.update(trailing_slash: true)
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 06a54591b8..664faa31bb 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -43,7 +43,7 @@ class CookieJarTest < ActiveSupport::TestCase
def test_each
request.cookie_jar["foo"] = :bar
list = []
- request.cookie_jar.each do |k,v|
+ request.cookie_jar.each do |k, v|
list << [k, v]
end
@@ -52,7 +52,7 @@ class CookieJarTest < ActiveSupport::TestCase
def test_enumerable
request.cookie_jar["foo"] = :bar
- actual = request.cookie_jar.map { |k,v| [k.to_s, v.to_s] }
+ actual = request.cookie_jar.map { |k, v| [k.to_s, v.to_s] }
assert_equal [["foo", "bar"]], actual
end
@@ -68,7 +68,7 @@ class CookieJarTest < ActiveSupport::TestCase
def test_write_doesnt_set_a_nil_header
headers = {}
request.cookie_jar.write(headers)
- assert !headers.include?("Set-Cookie")
+ assert_not_includes headers, "Set-Cookie"
end
end
@@ -95,17 +95,17 @@ class CookiesTest < ActionController::TestCase
end
def authenticate_for_fourteen_days
- cookies["user_name"] = { "value" => "david", "expires" => Time.utc(2005, 10, 10,5) }
+ cookies["user_name"] = { "value" => "david", "expires" => Time.utc(2005, 10, 10, 5) }
head :ok
end
def authenticate_for_fourteen_days_with_symbols
- cookies[:user_name] = { value: "david", expires: Time.utc(2005, 10, 10,5) }
+ cookies[:user_name] = { value: "david", expires: Time.utc(2005, 10, 10, 5) }
head :ok
end
def set_multiple_cookies
- cookies["user_name"] = { "value" => "david", "expires" => Time.utc(2005, 10, 10,5) }
+ cookies["user_name"] = { "value" => "david", "expires" => Time.utc(2005, 10, 10, 5) }
cookies["login"] = "XJ-122"
head :ok
end
@@ -205,17 +205,17 @@ class CookiesTest < ActionController::TestCase
def delete_and_set_cookie
cookies.delete :user_name
- cookies[:user_name] = { value: "david", expires: Time.utc(2005, 10, 10,5) }
+ cookies[:user_name] = { value: "david", expires: Time.utc(2005, 10, 10, 5) }
head :ok
end
def set_cookie_with_domain
- cookies[:user_name] = {value: "rizwanreza", domain: :all}
+ cookies[:user_name] = { value: "rizwanreza", domain: :all }
head :ok
end
def set_cookie_with_domain_all_as_string
- cookies[:user_name] = {value: "rizwanreza", domain: "all"}
+ cookies[:user_name] = { value: "rizwanreza", domain: "all" }
head :ok
end
@@ -230,7 +230,7 @@ class CookiesTest < ActionController::TestCase
end
def set_cookie_with_domain_and_tld
- cookies[:user_name] = {value: "rizwanreza", domain: :all, tld_length: 2}
+ cookies[:user_name] = { value: "rizwanreza", domain: :all, tld_length: 2 }
head :ok
end
@@ -240,7 +240,7 @@ class CookiesTest < ActionController::TestCase
end
def set_cookie_with_domains
- cookies[:user_name] = {value: "rizwanreza", domain: %w(example1.com example2.com .example3.com)}
+ cookies[:user_name] = { value: "rizwanreza", domain: %w(example1.com example2.com .example3.com) }
head :ok
end
@@ -272,6 +272,10 @@ class CookiesTest < ActionController::TestCase
def noop
head :ok
end
+
+ def encrypted_cookie
+ cookies.encrypted["foo"]
+ end
end
tests TestController
@@ -293,7 +297,7 @@ class CookiesTest < ActionController::TestCase
def test_setting_cookie
get :authenticate
assert_cookie_header "user_name=david; path=/"
- assert_equal({"user_name" => "david"}, @response.cookies)
+ assert_equal({ "user_name" => "david" }, @response.cookies)
end
def test_setting_the_same_value_to_cookie
@@ -305,45 +309,45 @@ class CookiesTest < ActionController::TestCase
def test_setting_the_same_value_to_permanent_cookie
request.cookies[:user_name] = "Jamie"
get :set_permanent_cookie
- assert_equal({"user_name" => "Jamie"}, response.cookies)
+ assert_equal({ "user_name" => "Jamie" }, response.cookies)
end
def test_setting_with_escapable_characters
get :set_with_with_escapable_characters
assert_cookie_header "that+%26+guy=foo+%26+bar+%3D%3E+baz; path=/"
- assert_equal({"that & guy" => "foo & bar => baz"}, @response.cookies)
+ assert_equal({ "that & guy" => "foo & bar => baz" }, @response.cookies)
end
def test_setting_cookie_for_fourteen_days
get :authenticate_for_fourteen_days
assert_cookie_header "user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000"
- assert_equal({"user_name" => "david"}, @response.cookies)
+ assert_equal({ "user_name" => "david" }, @response.cookies)
end
def test_setting_cookie_for_fourteen_days_with_symbols
get :authenticate_for_fourteen_days_with_symbols
assert_cookie_header "user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000"
- assert_equal({"user_name" => "david"}, @response.cookies)
+ assert_equal({ "user_name" => "david" }, @response.cookies)
end
def test_setting_cookie_with_http_only
get :authenticate_with_http_only
assert_cookie_header "user_name=david; path=/; HttpOnly"
- assert_equal({"user_name" => "david"}, @response.cookies)
+ assert_equal({ "user_name" => "david" }, @response.cookies)
end
def test_setting_cookie_with_secure
@request.env["HTTPS"] = "on"
get :authenticate_with_secure
assert_cookie_header "user_name=david; path=/; secure"
- assert_equal({"user_name" => "david"}, @response.cookies)
+ assert_equal({ "user_name" => "david" }, @response.cookies)
end
def test_setting_cookie_with_secure_when_always_write_cookie_is_true
old_cookie, @request.cookie_jar.always_write_cookie = @request.cookie_jar.always_write_cookie, true
get :authenticate_with_secure
assert_cookie_header "user_name=david; path=/; secure"
- assert_equal({"user_name" => "david"}, @response.cookies)
+ assert_equal({ "user_name" => "david" }, @response.cookies)
ensure
@request.cookie_jar.always_write_cookie = old_cookie
end
@@ -351,14 +355,14 @@ class CookiesTest < ActionController::TestCase
def test_not_setting_cookie_with_secure
get :authenticate_with_secure
assert_not_cookie_header "user_name=david; path=/; secure"
- assert_not_equal({"user_name" => "david"}, @response.cookies)
+ assert_not_equal({ "user_name" => "david" }, @response.cookies)
end
def test_multiple_cookies
get :set_multiple_cookies
assert_equal 2, @response.cookies.size
assert_cookie_header "user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000\nlogin=XJ-122; path=/"
- assert_equal({"login" => "XJ-122", "user_name" => "david"}, @response.cookies)
+ assert_equal({ "login" => "XJ-122", "user_name" => "david" }, @response.cookies)
end
def test_setting_test_cookie
@@ -369,7 +373,7 @@ class CookiesTest < ActionController::TestCase
request.cookies[:user_name] = "Joe"
get :logout
assert_cookie_header "user_name=; path=/; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"
- assert_equal({"user_name" => nil}, @response.cookies)
+ assert_equal({ "user_name" => nil }, @response.cookies)
end
def test_delete_cookie_with_path
@@ -603,7 +607,7 @@ class CookiesTest < ActionController::TestCase
secret = key_generator.generate_key(encrypted_cookie_salt)
sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt)
- marshal_value = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: Marshal).encrypt_and_sign("bar")
+ 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
@@ -612,7 +616,7 @@ class CookiesTest < ActionController::TestCase
assert_not_equal "bar", cookies[:foo]
assert_equal "bar", cookies.encrypted[:foo]
- encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON)
+ encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON)
assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
end
@@ -624,7 +628,7 @@ class CookiesTest < ActionController::TestCase
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, sign_secret, serializer: JSON).encrypt_and_sign("bar")
+ 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
@@ -636,6 +640,19 @@ class CookiesTest < ActionController::TestCase
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}"
+
+ 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
+
def test_accessing_nonexistent_encrypted_cookie_should_not_raise_invalid_message
get :set_encrypted_cookie
assert_nil @controller.send(:cookies).encrypted[:non_existent_attribute]
@@ -656,7 +673,7 @@ class CookiesTest < ActionController::TestCase
request.cookies[:user_name] = "Joe"
get :delete_and_set_cookie
assert_cookie_header "user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000"
- assert_equal({"user_name" => "david"}, @response.cookies)
+ assert_equal({ "user_name" => "david" }, @response.cookies)
end
def test_raise_data_overflow
@@ -799,7 +816,7 @@ class CookiesTest < ActionController::TestCase
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, sign_secret)
+ encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret)
assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
end
@@ -838,7 +855,7 @@ class CookiesTest < ActionController::TestCase
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, sign_secret, serializer: JSON)
+ encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON)
assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
end
@@ -877,7 +894,7 @@ class CookiesTest < ActionController::TestCase
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, sign_secret, serializer: JSON)
+ encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON)
assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
end
@@ -916,7 +933,7 @@ class CookiesTest < ActionController::TestCase
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, sign_secret, serializer: JSON)
+ encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON)
assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
end
@@ -927,8 +944,8 @@ class CookiesTest < ActionController::TestCase
@request.headers["Cookie"] = "user_id=45"
get :get_signed_cookie
- assert_equal nil, @controller.send(:cookies).signed[:user_id]
- assert_equal nil, @response.cookies["user_id"]
+ assert_nil @controller.send(:cookies).signed[:user_id]
+ assert_nil @response.cookies["user_id"]
end
def test_legacy_signed_cookie_is_treated_as_nil_by_encrypted_cookie_jar_if_tampered
@@ -938,8 +955,8 @@ class CookiesTest < ActionController::TestCase
@request.headers["Cookie"] = "foo=baz"
get :get_encrypted_cookie
- assert_equal nil, @controller.send(:cookies).encrypted[:foo]
- assert_equal nil, @response.cookies["foo"]
+ assert_nil @controller.send(:cookies).encrypted[:foo]
+ assert_nil @response.cookies["foo"]
end
def test_cookie_with_all_domain_option
@@ -1102,11 +1119,11 @@ class CookiesTest < ActionController::TestCase
assert_equal "david", cookies[:user_name]
get :noop
- assert !@response.headers.include?("Set-Cookie")
+ assert_not_includes @response.headers, "Set-Cookie"
assert_equal "david", cookies[:user_name]
get :noop
- assert !@response.headers.include?("Set-Cookie")
+ assert_not_includes @response.headers, "Set-Cookie"
assert_equal "david", cookies[:user_name]
end
@@ -1176,6 +1193,12 @@ class CookiesTest < ActionController::TestCase
assert_equal "david", cookies[:user_name]
end
+ def test_cookies_are_not_cleared
+ cookies.encrypted["foo"] = "bar"
+ get :noop
+ assert_equal "bar", @controller.encrypted_cookie
+ end
+
private
def assert_cookie_header(expected)
header = @response.headers["Set-Cookie"]
diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb
index b55bd9b204..ea477e8908 100644
--- a/actionpack/test/dispatch/debug_exceptions_test.rb
+++ b/actionpack/test/dispatch/debug_exceptions_test.rb
@@ -156,7 +156,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
test "rescue with text error for xhr request" do
@app = DevelopmentApp
- xhr_request_env = {"action_dispatch.show_exceptions" => true, "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest"}
+ xhr_request_env = { "action_dispatch.show_exceptions" => true, "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest" }
get "/", headers: xhr_request_env
assert_response 500
@@ -287,7 +287,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
test "does not show filtered parameters" do
@app = DevelopmentApp
- get "/", params: { "foo"=>"bar" }, headers: { "action_dispatch.show_exceptions" => true,
+ get "/", params: { "foo" => "bar" }, headers: { "action_dispatch.show_exceptions" => true,
"action_dispatch.parameter_filter" => [:foo] }
assert_response 500
assert_match("&quot;foo&quot;=&gt;&quot;[FILTERED]&quot;", body)
@@ -384,6 +384,23 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
end
end
+ test "logs with non active support loggers" do
+ @app = DevelopmentApp
+ io = StringIO.new
+ logger = Logger.new(io)
+
+ _old, ActionView::Base.logger = ActionView::Base.logger, logger
+ begin
+ assert_nothing_raised do
+ get "/", headers: { "action_dispatch.show_exceptions" => true, "action_dispatch.logger" => logger }
+ end
+ ensure
+ ActionView::Base.logger = _old
+ end
+
+ assert_match(/puke/, io.rewind && io.read)
+ end
+
test "uses backtrace cleaner from env" do
@app = DevelopmentApp
backtrace_cleaner = ActiveSupport::BacktraceCleaner.new
@@ -399,9 +416,9 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
backtrace_cleaner = ActiveSupport::BacktraceCleaner.new
backtrace_cleaner.add_silencer { true }
- env = {"action_dispatch.show_exceptions" => true,
+ env = { "action_dispatch.show_exceptions" => true,
"action_dispatch.logger" => Logger.new(output),
- "action_dispatch.backtrace_cleaner" => backtrace_cleaner}
+ "action_dispatch.backtrace_cleaner" => backtrace_cleaner }
get "/", headers: env
assert_operator((output.rewind && output.read).lines.count, :>, 10)
diff --git a/actionpack/test/dispatch/header_test.rb b/actionpack/test/dispatch/header_test.rb
index 12f1c2712d..958450072e 100644
--- a/actionpack/test/dispatch/header_test.rb
+++ b/actionpack/test/dispatch/header_test.rb
@@ -18,14 +18,14 @@ class HeaderTest < ActiveSupport::TestCase
"HTTP_REFERER" => "/some/page",
"Host" => "http://test.com")
- assert_equal({"Content-Type" => "application/json",
+ assert_equal({ "Content-Type" => "application/json",
"HTTP_REFERER" => "/some/page",
- "Host" => "http://test.com"}, headers.env)
+ "Host" => "http://test.com" }, headers.env)
end
test "#env returns the headers as env variables" do
- assert_equal({"CONTENT_TYPE" => "text/plain",
- "HTTP_REFERER" => "/some/page"}, @headers.env)
+ assert_equal({ "CONTENT_TYPE" => "text/plain",
+ "HTTP_REFERER" => "/some/page" }, @headers.env)
end
test "#each iterates through the env variables" do
@@ -76,9 +76,9 @@ class HeaderTest < ActiveSupport::TestCase
test "key?" do
assert @headers.key?("CONTENT_TYPE")
- assert @headers.include?("CONTENT_TYPE")
+ assert_includes @headers, "CONTENT_TYPE"
assert @headers.key?("Content-Type")
- assert @headers.include?("Content-Type")
+ assert_includes @headers, "Content-Type"
end
test "fetch with block" do
@@ -105,28 +105,28 @@ class HeaderTest < ActiveSupport::TestCase
test "#merge! headers with mutation" do
@headers.merge!("Host" => "http://example.test",
"Content-Type" => "text/html")
- assert_equal({"HTTP_HOST" => "http://example.test",
+ assert_equal({ "HTTP_HOST" => "http://example.test",
"CONTENT_TYPE" => "text/html",
- "HTTP_REFERER" => "/some/page"}, @headers.env)
+ "HTTP_REFERER" => "/some/page" }, @headers.env)
end
test "#merge! env with mutation" do
@headers.merge!("HTTP_HOST" => "http://first.com",
"CONTENT_TYPE" => "text/html")
- assert_equal({"HTTP_HOST" => "http://first.com",
+ assert_equal({ "HTTP_HOST" => "http://first.com",
"CONTENT_TYPE" => "text/html",
- "HTTP_REFERER" => "/some/page"}, @headers.env)
+ "HTTP_REFERER" => "/some/page" }, @headers.env)
end
test "merge without mutation" do
combined = @headers.merge("HTTP_HOST" => "http://example.com",
"CONTENT_TYPE" => "text/html")
- assert_equal({"HTTP_HOST" => "http://example.com",
+ assert_equal({ "HTTP_HOST" => "http://example.com",
"CONTENT_TYPE" => "text/html",
- "HTTP_REFERER" => "/some/page"}, combined.env)
+ "HTTP_REFERER" => "/some/page" }, combined.env)
- assert_equal({"CONTENT_TYPE" => "text/plain",
- "HTTP_REFERER" => "/some/page"}, @headers.env)
+ assert_equal({ "CONTENT_TYPE" => "text/plain",
+ "HTTP_REFERER" => "/some/page" }, @headers.env)
end
test "env variables with . are not modified" do
@@ -151,11 +151,17 @@ class HeaderTest < ActiveSupport::TestCase
end
test "headers directly modifies the passed environment" do
- env = {"HTTP_REFERER" => "/"}
+ env = { "HTTP_REFERER" => "/" }
headers = make_headers(env)
headers["Referer"] = "http://example.com/"
headers.merge! "CONTENT_TYPE" => "text/plain"
- assert_equal({"HTTP_REFERER"=>"http://example.com/",
- "CONTENT_TYPE"=>"text/plain"}, env)
+ assert_equal({ "HTTP_REFERER" => "http://example.com/",
+ "CONTENT_TYPE" => "text/plain" }, env)
+ end
+
+ test "fetch exception" do
+ assert_raises KeyError do
+ @headers.fetch(:some_key_that_doesnt_exist)
+ end
end
end
diff --git a/actionpack/test/dispatch/mapper_test.rb b/actionpack/test/dispatch/mapper_test.rb
index 8d2699fef6..1596d23b1e 100644
--- a/actionpack/test/dispatch/mapper_test.rb
+++ b/actionpack/test/dispatch/mapper_test.rb
@@ -58,7 +58,7 @@ module ActionDispatch
fakeset = FakeSet.new
mapper = Mapper.new fakeset
mapper.get "/foo", to: "posts#index", as: :main, format: true
- assert_equal({controller: "posts", action: "index"},
+ assert_equal({ controller: "posts", action: "index" },
fakeset.defaults.first)
assert_equal "/foo.:format", fakeset.asts.first.to_s
end
@@ -69,7 +69,7 @@ module ActionDispatch
mapper.scope(format: true) do
mapper.get "/foo", to: "posts#index", as: :main
end
- assert_equal({controller: "posts", action: "index"},
+ assert_equal({ controller: "posts", action: "index" },
fakeset.defaults.first)
assert_equal "/foo.:format", fakeset.asts.first.to_s
end
@@ -80,13 +80,13 @@ module ActionDispatch
mapper.scope(omg: :awesome) do
mapper.get "/", to: "posts#index", as: :main
end
- assert_equal({omg: :awesome, controller: "posts", action: "index"},
+ assert_equal({ omg: :awesome, controller: "posts", action: "index" },
fakeset.defaults.first)
assert_equal("GET", fakeset.routes.first.verb)
end
def test_mapping_requirements
- options = { }
+ options = {}
scope = Mapper::Scope.new({})
ast = Journey::Parser.parse "/store/:name(*rest)"
m = Mapper::Mapping.build(scope, FakeSet.new, ast, "foo", "bar", nil, [:get], nil, {}, true, options)
diff --git a/actionpack/test/dispatch/middleware_stack_test.rb b/actionpack/test/dispatch/middleware_stack_test.rb
index b00488edd5..481aa22b10 100644
--- a/actionpack/test/dispatch/middleware_stack_test.rb
+++ b/actionpack/test/dispatch/middleware_stack_test.rb
@@ -18,14 +18,6 @@ class MiddlewareStackTest < ActiveSupport::TestCase
@stack.use BarMiddleware
end
- def test_delete_with_string_is_deprecated
- assert_deprecated do
- assert_difference "@stack.size", -1 do
- @stack.delete FooMiddleware.name
- end
- end
- end
-
def test_delete_works
assert_difference "@stack.size", -1 do
@stack.delete FooMiddleware
@@ -39,30 +31,12 @@ class MiddlewareStackTest < ActiveSupport::TestCase
assert_equal BazMiddleware, @stack.last.klass
end
- test "use should push middleware as a string onto the stack" do
- assert_deprecated do
- assert_difference "@stack.size" do
- @stack.use "MiddlewareStackTest::BazMiddleware"
- end
- assert_equal BazMiddleware, @stack.last.klass
- end
- end
-
- test "use should push middleware as a symbol onto the stack" do
- assert_deprecated do
- assert_difference "@stack.size" do
- @stack.use :"MiddlewareStackTest::BazMiddleware"
- end
- assert_equal BazMiddleware, @stack.last.klass
- end
- end
-
test "use should push middleware class with arguments onto the stack" do
assert_difference "@stack.size" do
@stack.use BazMiddleware, true, foo: "bar"
end
assert_equal BazMiddleware, @stack.last.klass
- assert_equal([true, {foo: "bar"}], @stack.last.args)
+ assert_equal([true, { foo: "bar" }], @stack.last.args)
end
test "use should push middleware class with block arguments onto the stack" do
@@ -107,10 +81,8 @@ class MiddlewareStackTest < ActiveSupport::TestCase
end
test "unshift adds a new middleware at the beginning of the stack" do
- assert_deprecated do
- @stack.unshift :"MiddlewareStackTest::BazMiddleware"
- assert_equal BazMiddleware, @stack.first.klass
- end
+ @stack.unshift MiddlewareStackTest::BazMiddleware
+ assert_equal BazMiddleware, @stack.first.klass
end
test "raise an error on invalid index" do
@@ -123,15 +95,6 @@ class MiddlewareStackTest < ActiveSupport::TestCase
end
end
- test "lazy evaluates middleware class" do
- assert_deprecated do
- assert_difference "@stack.size" do
- @stack.use "MiddlewareStackTest::BazMiddleware"
- end
- assert_equal BazMiddleware, @stack.last.klass
- end
- end
-
test "can check if Middleware are equal - Class" do
assert_equal @stack.last, BarMiddleware
end
diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb
index 421ae6e133..2ca03c535a 100644
--- a/actionpack/test/dispatch/mime_type_test.rb
+++ b/actionpack/test/dispatch/mime_type_test.rb
@@ -89,7 +89,7 @@ class MimeTypeTest < ActiveSupport::TestCase
# (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; InfoPath.1)
test "parse other broken acceptlines" do
accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, , pronto/1.00.00, sslvpn/1.00.00.00, */*"
- expect = ["image/gif", "image/x-xbitmap", "image/jpeg","image/pjpeg", "application/x-shockwave-flash", "application/vnd.ms-excel", "application/vnd.ms-powerpoint", "application/msword", "pronto/1.00.00", "sslvpn/1.00.00.00", "*/*"]
+ expect = ["image/gif", "image/x-xbitmap", "image/jpeg", "image/pjpeg", "application/x-shockwave-flash", "application/vnd.ms-excel", "application/vnd.ms-powerpoint", "application/msword", "pronto/1.00.00", "sslvpn/1.00.00.00", "*/*"]
assert_equal expect.map(&:to_s), Mime::Type.parse(accept).map(&:to_s)
end
@@ -167,18 +167,6 @@ class MimeTypeTest < ActiveSupport::TestCase
end
end
- test "deprecated lookup" do
- assert_deprecated do
- Mime::HTML
- end
- end
-
- test "deprecated const_defined?" do
- assert_deprecated do
- Mime.const_defined? :HTML
- end
- end
-
test "references gives preference to symbols before strings" do
assert_equal :html, Mime[:html].ref
another = Mime::Type.lookup("foo/bar")
diff --git a/actionpack/test/dispatch/mount_test.rb b/actionpack/test/dispatch/mount_test.rb
index 179b65b236..a7d5ba2345 100644
--- a/actionpack/test/dispatch/mount_test.rb
+++ b/actionpack/test/dispatch/mount_test.rb
@@ -15,13 +15,13 @@ class TestRoutingMount < ActionDispatch::IntegrationTest
def self.routes; Object.new; end
def self.call(env)
- [200, {"Content-Type" => "text/html"}, ["OK"]]
+ [200, { "Content-Type" => "text/html" }, ["OK"]]
end
end
Router.draw do
SprocketsApp = lambda { |env|
- [200, {"Content-Type" => "text/html"}, ["#{env["SCRIPT_NAME"]} -- #{env["PATH_INFO"]}"]]
+ [200, { "Content-Type" => "text/html" }, ["#{env["SCRIPT_NAME"]} -- #{env["PATH_INFO"]}"]]
}
mount SprocketsApp, at: "/sprockets"
diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb
index ea11a943f2..bb2fc53add 100644
--- a/actionpack/test/dispatch/prefix_generation_test.rb
+++ b/actionpack/test/dispatch/prefix_generation_test.rb
@@ -238,7 +238,7 @@ module TestGenerationPrefix
end
test "[APP] generating engine's route includes default_url_options[:script_name]" do
- RailsApplication.routes.default_url_options = {script_name: "/something"}
+ RailsApplication.routes.default_url_options = { script_name: "/something" }
get "/generate"
assert_equal "/something/awesome/blog/posts/1", last_response.body
end
@@ -277,7 +277,7 @@ module TestGenerationPrefix
end
test "[OBJECT] generating engine's route includes default_url_options[:script_name]" do
- RailsApplication.routes.default_url_options = {script_name: "/something"}
+ RailsApplication.routes.default_url_options = { script_name: "/something" }
assert_equal "/something/pure-awesomeness/blog/posts/3", engine_object.post_path(id: 3, omg: "pure-awesomeness")
end
@@ -286,7 +286,7 @@ module TestGenerationPrefix
end
test "[OBJECT] generating application's route includes default_url_options[:script_name]" do
- RailsApplication.routes.default_url_options = {script_name: "/something"}
+ RailsApplication.routes.default_url_options = { script_name: "/something" }
assert_equal "/something/", app_object.root_path
end
diff --git a/actionpack/test/dispatch/reloader_test.rb b/actionpack/test/dispatch/reloader_test.rb
index 761237d5dc..db68549b84 100644
--- a/actionpack/test/dispatch/reloader_test.rb
+++ b/actionpack/test/dispatch/reloader_test.rb
@@ -178,7 +178,7 @@ class ReloaderTest < ActiveSupport::TestCase
def test_cleanup_callbacks_are_called_on_exceptions
cleaned = false
assert_deprecated do
- Reloader.to_cleanup { cleaned = true }
+ Reloader.to_cleanup { cleaned = true }
end
begin
@@ -197,7 +197,7 @@ class ReloaderTest < ActiveSupport::TestCase
x.check = lambda { true }
@response ||= "response"
- @reloader ||= Reloader.new(block || proc {[200, {}, @response]}, x)
+ @reloader ||= Reloader.new(block || proc { [200, {}, @response] }, x)
@reloader.call("rack.input" => StringIO.new(""))[2]
end
end
diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb
index e24b1e12e3..10234a4815 100644
--- a/actionpack/test/dispatch/request/json_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb
@@ -18,21 +18,21 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
test "parses json params for application json" do
assert_parses(
- {"person" => {"name" => "David"}},
+ { "person" => { "name" => "David" } },
"{\"person\": {\"name\": \"David\"}}", "CONTENT_TYPE" => "application/json"
)
end
test "parses boolean and number json params for application json" do
assert_parses(
- {"item" => {"enabled" => false, "count" => 10}},
+ { "item" => { "enabled" => false, "count" => 10 } },
"{\"item\": {\"enabled\": false, \"count\": 10}}", "CONTENT_TYPE" => "application/json"
)
end
test "parses json params for application jsonrequest" do
assert_parses(
- {"person" => {"name" => "David"}},
+ { "person" => { "name" => "David" } },
"{\"person\": {\"name\": \"David\"}}", "CONTENT_TYPE" => "application/jsonrequest"
)
end
@@ -46,15 +46,15 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
test "nils are stripped from collections" do
assert_parses(
- {"person" => []},
+ { "person" => [] },
"{\"person\":[null]}", "CONTENT_TYPE" => "application/json"
)
assert_parses(
- {"person" => ["foo"]},
+ { "person" => ["foo"] },
"{\"person\":[\"foo\",null]}", "CONTENT_TYPE" => "application/json"
)
assert_parses(
- {"person" => []},
+ { "person" => [] },
"{\"person\":[null, null]}", "CONTENT_TYPE" => "application/json"
)
end
@@ -75,7 +75,9 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
begin
$stderr = StringIO.new # suppress the log
json = "[\"person]\": {\"name\": \"David\"}}"
- exception = assert_raise(ActionDispatch::ParamsParser::ParseError) { post "/parse", json, "CONTENT_TYPE" => "application/json", "action_dispatch.show_exceptions" => false }
+ exception = assert_raise(ActionDispatch::Http::Parameters::ParseError) do
+ post "/parse", params: json, headers: { "CONTENT_TYPE" => "application/json", "action_dispatch.show_exceptions" => false }
+ end
assert_equal JSON::ParserError, exception.cause.class
assert_equal exception.cause.message, exception.message
ensure
@@ -133,21 +135,21 @@ class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest
test "parses json params for application json" do
assert_parses(
- {"user" => {"username" => "sikachu"}, "username" => "sikachu"},
+ { "user" => { "username" => "sikachu" }, "username" => "sikachu" },
"{\"username\": \"sikachu\"}", "CONTENT_TYPE" => "application/json"
)
end
test "parses json params for application jsonrequest" do
assert_parses(
- {"user" => {"username" => "sikachu"}, "username" => "sikachu"},
+ { "user" => { "username" => "sikachu" }, "username" => "sikachu" },
"{\"username\": \"sikachu\"}", "CONTENT_TYPE" => "application/jsonrequest"
)
end
test "parses json with non-object JSON content" do
assert_parses(
- {"user" => {"_json" => "string content" }, "_json" => "string content" },
+ { "user" => { "_json" => "string content" }, "_json" => "string content" },
"\"string content\"", "CONTENT_TYPE" => "application/json"
)
end
@@ -157,7 +159,7 @@ class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest
Mime::Type.unregister :json
Mime::Type.register "application/json", :json, %w(application/vnd.rails+json)
assert_parses(
- {"user" => {"username" => "meinac"}, "username" => "meinac"},
+ { "user" => { "username" => "meinac" }, "username" => "meinac" },
"{\"username\": \"meinac\"}", "CONTENT_TYPE" => "application/json"
)
ensure
@@ -171,7 +173,7 @@ class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest
Mime::Type.unregister :json
Mime::Type.register "application/json", :json, %w(application/vnd.rails+json)
assert_parses(
- {"user" => {"username" => "meinac"}, "username" => "meinac"},
+ { "user" => { "username" => "meinac" }, "username" => "meinac" },
"{\"username\": \"meinac\"}", "CONTENT_TYPE" => "application/vnd.rails+json"
)
ensure
diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
index 2714b7f50b..01c5ff1429 100644
--- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
@@ -32,11 +32,11 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
end
test "parses bracketed parameters" do
- assert_equal({ "foo" => { "baz" => "bar"}}, parse_multipart("bracketed_param"))
+ assert_equal({ "foo" => { "baz" => "bar" } }, parse_multipart("bracketed_param"))
end
test "parse single utf8 parameter" do
- assert_equal({ "Iñtërnâtiônàlizætiøn_name" => "Iñtërnâtiônàlizætiøn_value"},
+ assert_equal({ "Iñtërnâtiônàlizætiøn_name" => "Iñtërnâtiônàlizætiøn_value" },
parse_multipart("single_utf8_param"), "request.request_parameters")
assert_equal(
"Iñtërnâtiônàlizætiøn_value",
@@ -45,10 +45,10 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
test "parse bracketed utf8 parameter" do
assert_equal({ "Iñtërnâtiônàlizætiøn_name" => {
- "Iñtërnâtiônàlizætiøn_nested_name" => "Iñtërnâtiônàlizætiøn_value"} },
+ "Iñtërnâtiônàlizætiøn_nested_name" => "Iñtërnâtiônàlizætiøn_value" } },
parse_multipart("bracketed_utf8_param"), "request.request_parameters")
assert_equal(
- {"Iñtërnâtiônàlizætiøn_nested_name" => "Iñtërnâtiônàlizætiøn_value"},
+ { "Iñtërnâtiônàlizætiøn_nested_name" => "Iñtërnâtiônàlizætiøn_value" },
TestController.last_parameters["Iñtërnâtiônàlizætiøn_name"], "request.parameters")
end
@@ -129,7 +129,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
params = parse_multipart("none")
assert_equal %w(submit-name), params.keys.sort
assert_equal "Larry", params["submit-name"]
- assert_equal nil, params["files"]
+ assert_nil params["files"]
end
test "parses empty upload file" do
@@ -142,7 +142,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
test "uploads and reads binary file" do
with_test_routing do
- fixture = FIXTURE_PATH + "/mona_lisa.jpg"
+ fixture = FIXTURE_PATH + "/ruby_on_rails.jpg"
params = { uploaded_data: fixture_file_upload(fixture, "image/jpg") }
post "/read", params: params
end
diff --git a/actionpack/test/dispatch/request/query_string_parsing_test.rb b/actionpack/test/dispatch/request/query_string_parsing_test.rb
index 6763dbf243..2499c33cef 100644
--- a/actionpack/test/dispatch/request/query_string_parsing_test.rb
+++ b/actionpack/test/dispatch/request/query_string_parsing_test.rb
@@ -29,92 +29,92 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest
test "query string" do
assert_parses(
- {"action" => "create_customer", "full_name" => "David Heinemeier Hansson", "customerId" => "1"},
+ { "action" => "create_customer", "full_name" => "David Heinemeier Hansson", "customerId" => "1" },
"action=create_customer&full_name=David%20Heinemeier%20Hansson&customerId=1"
)
end
test "deep query string" do
assert_parses(
- {"x" => {"y" => {"z" => "10"}}},
+ { "x" => { "y" => { "z" => "10" } } },
"x[y][z]=10"
)
end
test "deep query string with array" do
- assert_parses({"x" => {"y" => {"z" => ["10"]}}}, "x[y][z][]=10")
- assert_parses({"x" => {"y" => {"z" => ["10", "5"]}}}, "x[y][z][]=10&x[y][z][]=5")
+ assert_parses({ "x" => { "y" => { "z" => ["10"] } } }, "x[y][z][]=10")
+ assert_parses({ "x" => { "y" => { "z" => ["10", "5"] } } }, "x[y][z][]=10&x[y][z][]=5")
end
test "deep query string with array of hash" do
- assert_parses({"x" => {"y" => [{"z" => "10"}]}}, "x[y][][z]=10")
- assert_parses({"x" => {"y" => [{"z" => "10", "w" => "10"}]}}, "x[y][][z]=10&x[y][][w]=10")
- assert_parses({"x" => {"y" => [{"z" => "10", "v" => {"w" => "10"}}]}}, "x[y][][z]=10&x[y][][v][w]=10")
+ assert_parses({ "x" => { "y" => [{ "z" => "10" }] } }, "x[y][][z]=10")
+ assert_parses({ "x" => { "y" => [{ "z" => "10", "w" => "10" }] } }, "x[y][][z]=10&x[y][][w]=10")
+ assert_parses({ "x" => { "y" => [{ "z" => "10", "v" => { "w" => "10" } }] } }, "x[y][][z]=10&x[y][][v][w]=10")
end
test "deep query string with array of hashes with one pair" do
- assert_parses({"x" => {"y" => [{"z" => "10"}, {"z" => "20"}]}}, "x[y][][z]=10&x[y][][z]=20")
+ assert_parses({ "x" => { "y" => [{ "z" => "10" }, { "z" => "20" }] } }, "x[y][][z]=10&x[y][][z]=20")
end
test "deep query string with array of hashes with multiple pairs" do
assert_parses(
- {"x" => {"y" => [{"z" => "10", "w" => "a"}, {"z" => "20", "w" => "b"}]}},
+ { "x" => { "y" => [{ "z" => "10", "w" => "a" }, { "z" => "20", "w" => "b" }] } },
"x[y][][z]=10&x[y][][w]=a&x[y][][z]=20&x[y][][w]=b"
)
end
test "query string with nil" do
assert_parses(
- { "action" => "create_customer", "full_name" => ""},
+ { "action" => "create_customer", "full_name" => "" },
"action=create_customer&full_name="
)
end
test "query string with array" do
assert_parses(
- { "action" => "create_customer", "selected" => ["1", "2", "3"]},
+ { "action" => "create_customer", "selected" => ["1", "2", "3"] },
"action=create_customer&selected[]=1&selected[]=2&selected[]=3"
)
end
test "query string with amps" do
assert_parses(
- { "action" => "create_customer", "name" => "Don't & Does"},
+ { "action" => "create_customer", "name" => "Don't & Does" },
"action=create_customer&name=Don%27t+%26+Does"
)
end
test "query string with many equal" do
assert_parses(
- { "action" => "create_customer", "full_name" => "abc=def=ghi"},
+ { "action" => "create_customer", "full_name" => "abc=def=ghi" },
"action=create_customer&full_name=abc=def=ghi"
)
end
test "query string without equal" do
- assert_parses({"action" => nil}, "action")
- assert_parses({"action" => {"foo" => nil}}, "action[foo]")
- assert_parses({"action" => {"foo" => { "bar" => nil }}}, "action[foo][bar]")
- assert_parses({"action" => {"foo" => { "bar" => [] }}}, "action[foo][bar][]")
- assert_parses({"action" => {"foo" => [] }}, "action[foo][]")
- assert_parses({"action"=>{"foo"=>[{"bar"=>nil}]}}, "action[foo][][bar]")
+ assert_parses({ "action" => nil }, "action")
+ assert_parses({ "action" => { "foo" => nil } }, "action[foo]")
+ assert_parses({ "action" => { "foo" => { "bar" => nil } } }, "action[foo][bar]")
+ assert_parses({ "action" => { "foo" => { "bar" => [] } } }, "action[foo][bar][]")
+ assert_parses({ "action" => { "foo" => [] } }, "action[foo][]")
+ assert_parses({ "action" => { "foo" => [{ "bar" => nil }] } }, "action[foo][][bar]")
end
def test_array_parses_without_nil
- assert_parses({"action" => ["1"]}, "action[]=1&action[]")
+ assert_parses({ "action" => ["1"] }, "action[]=1&action[]")
end
test "perform_deep_munge" do
old_perform_deep_munge = ActionDispatch::Request::Utils.perform_deep_munge
ActionDispatch::Request::Utils.perform_deep_munge = false
begin
- assert_parses({"action" => nil}, "action")
- assert_parses({"action" => {"foo" => nil}}, "action[foo]")
- assert_parses({"action" => {"foo" => {"bar" => nil}}}, "action[foo][bar]")
- assert_parses({"action" => {"foo" => {"bar" => [nil]}}}, "action[foo][bar][]")
- assert_parses({"action" => {"foo" => [nil]}}, "action[foo][]")
- assert_parses({"action" => {"foo" => [{"bar" => nil}]}}, "action[foo][][bar]")
- assert_parses({"action" => ["1",nil]}, "action[]=1&action[]")
+ assert_parses({ "action" => nil }, "action")
+ assert_parses({ "action" => { "foo" => nil } }, "action[foo]")
+ assert_parses({ "action" => { "foo" => { "bar" => nil } } }, "action[foo][bar]")
+ assert_parses({ "action" => { "foo" => { "bar" => [nil] } } }, "action[foo][bar][]")
+ assert_parses({ "action" => { "foo" => [nil] } }, "action[foo][]")
+ assert_parses({ "action" => { "foo" => [{ "bar" => nil }] } }, "action[foo][][bar]")
+ assert_parses({ "action" => ["1", nil] }, "action[]=1&action[]")
ensure
ActionDispatch::Request::Utils.perform_deep_munge = old_perform_deep_munge
end
@@ -129,14 +129,14 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest
test "query string with many ampersands" do
assert_parses(
- { "action" => "create_customer", "full_name" => "David Heinemeier Hansson"},
+ { "action" => "create_customer", "full_name" => "David Heinemeier Hansson" },
"&action=create_customer&&&full_name=David%20Heinemeier%20Hansson"
)
end
test "unbalanced query string with array" do
assert_parses(
- {"location" => ["1", "2"], "age_group" => ["2"]},
+ { "location" => ["1", "2"], "age_group" => ["2"] },
"location[]=1&location[]=2&age_group[]=2"
)
end
diff --git a/actionpack/test/dispatch/request/session_test.rb b/actionpack/test/dispatch/request/session_test.rb
index cc3c4d48f3..311b80ea0a 100644
--- a/actionpack/test/dispatch/request/session_test.rb
+++ b/actionpack/test/dispatch/request/session_test.rb
@@ -19,7 +19,7 @@ module ActionDispatch
s = Session.create(store, req, {})
s["foo"] = "bar"
assert_equal "bar", s["foo"]
- assert_equal({"foo" => "bar"}, s.to_hash)
+ assert_equal({ "foo" => "bar" }, s.to_hash)
end
def test_create_merges_old
@@ -98,7 +98,7 @@ module ActionDispatch
assert_equal "2", session.fetch(:two, "2")
assert_nil session.fetch(:two, nil)
- assert_equal "three", session.fetch(:three) {|el| el.to_s }
+ assert_equal "three", session.fetch(:three) { |el| el.to_s }
assert_raise KeyError do
session.fetch(:three)
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 5c7558e48d..1169bf0cdb 100644
--- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
@@ -55,7 +55,7 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest
"products[second]=Pc",
"=Save"
].join("&")
- expected = {
+ expected = {
"customers" => {
"boston" => {
"first" => {
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 0dc8a2508e..e11b93b4f0 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -18,11 +18,11 @@ class BaseRequestTest < ActiveSupport::TestCase
ActionDispatch::Http::URL.url_for(options)
end
- protected
+ private
def stub_request(env = {})
ip_spoofing_check = env.key?(:ip_spoofing_check) ? env.delete(:ip_spoofing_check) : true
@trusted_proxies ||= nil
- ip_app = ActionDispatch::RemoteIp.new(Proc.new { }, ip_spoofing_check, @trusted_proxies)
+ ip_app = ActionDispatch::RemoteIp.new(Proc.new {}, ip_spoofing_check, @trusted_proxies)
ActionDispatch::Http::URL.tld_length = env.delete(:tld_length) if env.key?(:tld_length)
ip_app.call(env)
@@ -94,13 +94,13 @@ class RequestIP < BaseRequestTest
assert_equal "3.4.5.6", request.remote_ip
request = stub_request "HTTP_X_FORWARDED_FOR" => "unknown,192.168.0.1"
- assert_equal nil, request.remote_ip
+ assert_nil request.remote_ip
request = stub_request "HTTP_X_FORWARDED_FOR" => "9.9.9.9, 3.4.5.6, 172.31.4.4, 10.0.0.1"
assert_equal "3.4.5.6", request.remote_ip
request = stub_request "HTTP_X_FORWARDED_FOR" => "not_ip_address"
- assert_equal nil, request.remote_ip
+ assert_nil request.remote_ip
end
test "remote ip spoof detection" do
@@ -154,7 +154,7 @@ class RequestIP < BaseRequestTest
assert_equal "fe80:0000:0000:0000:0202:b3ff:fe1e:8329", request.remote_ip
request = stub_request "HTTP_X_FORWARDED_FOR" => "unknown,::1"
- assert_equal nil, request.remote_ip
+ assert_nil request.remote_ip
request = stub_request "HTTP_X_FORWARDED_FOR" => "2001:0db8:85a3:0000:0000:8a2e:0370:7334, fe80:0000:0000:0000:0202:b3ff:fe1e:8329, ::1, fc00::, fc01::, fdff"
assert_equal "fe80:0000:0000:0000:0202:b3ff:fe1e:8329", request.remote_ip
@@ -163,7 +163,7 @@ class RequestIP < BaseRequestTest
assert_equal "FE00::", request.remote_ip
request = stub_request "HTTP_X_FORWARDED_FOR" => "not_ip_address"
- assert_equal nil, request.remote_ip
+ assert_nil request.remote_ip
end
test "remote ip v6 spoof detection" do
@@ -200,7 +200,7 @@ class RequestIP < BaseRequestTest
assert_equal "3.4.5.6", request.remote_ip
request = stub_request "HTTP_X_FORWARDED_FOR" => "67.205.106.73,unknown"
- assert_equal nil, request.remote_ip
+ assert_nil request.remote_ip
request = stub_request "HTTP_X_FORWARDED_FOR" => "9.9.9.9, 3.4.5.6, 10.0.0.1, 67.205.106.73"
assert_equal "3.4.5.6", request.remote_ip
@@ -222,7 +222,7 @@ class RequestIP < BaseRequestTest
assert_equal "::1", request.remote_ip
request = stub_request "HTTP_X_FORWARDED_FOR" => "unknown,fe80:0000:0000:0000:0202:b3ff:fe1e:8329"
- assert_equal nil, request.remote_ip
+ assert_nil request.remote_ip
request = stub_request "HTTP_X_FORWARDED_FOR" => "fe80:0000:0000:0000:0202:b3ff:fe1e:8329,2001:0db8:85a3:0000:0000:8a2e:0370:7334"
assert_equal "2001:0db8:85a3:0000:0000:8a2e:0370:7334", request.remote_ip
@@ -345,7 +345,7 @@ class RequestPort < BaseRequestTest
test "optional port" do
request = stub_request "HTTP_HOST" => "www.example.org:80"
- assert_equal nil, request.optional_port
+ assert_nil request.optional_port
request = stub_request "HTTP_HOST" => "www.example.org:8080"
assert_equal 8080, request.optional_port
@@ -537,7 +537,7 @@ class RequestCGI < BaseRequestTest
assert_equal "Basic", request.auth_type
assert_equal 0, request.content_length
- assert_equal nil, request.content_mime_type
+ assert_nil request.content_mime_type
assert_equal "CGI/1.1", request.gateway_interface
assert_equal "*/*", request.accept
assert_equal "UTF-8", request.accept_charset
@@ -581,7 +581,7 @@ class RequestCookie < BaseRequestTest
# some Nokia phone browsers omit the space after the semicolon separator.
# some developers have grown accustomed to using comma in cookie values.
- request = stub_request("HTTP_COOKIE"=>"_session_id=c84ace847,96670c052c6ceb2451fb0f2;is_admin=yes")
+ request = stub_request("HTTP_COOKIE" => "_session_id=c84ace847,96670c052c6ceb2451fb0f2;is_admin=yes")
assert_equal "c84ace847", request.cookies["_session_id"], request.cookies.inspect
assert_equal "yes", request.cookies["is_admin"], request.cookies.inspect
end
@@ -596,7 +596,7 @@ class RequestParamsParsing < BaseRequestTest
"rack.input" => StringIO.new("flamenco=love")
)
- assert_equal({"flamenco"=> "love"}, request.request_parameters)
+ assert_equal({ "flamenco" => "love" }, request.request_parameters)
end
test "doesnt interpret request uri as query string when missing" do
@@ -774,8 +774,8 @@ class RequestMethod < BaseRequestTest
ensure
# Reset original acronym set
ActiveSupport::Inflector.inflections do |inflect|
- inflect.send(:instance_variable_set,"@acronyms",existing_acrnoyms)
- inflect.send(:instance_variable_set,"@acronym_regex",existing_acrnoym_regex)
+ inflect.send(:instance_variable_set, "@acronyms", existing_acrnoyms)
+ inflect.send(:instance_variable_set, "@acronym_regex", existing_acrnoym_regex)
end
end
end
@@ -784,21 +784,21 @@ end
class RequestFormat < BaseRequestTest
test "xml format" do
request = stub_request
- assert_called(request, :parameters, times: 2, returns: {format: :xml}) do
+ assert_called(request, :parameters, times: 2, returns: { format: :xml }) do
assert_equal Mime[:xml], request.format
end
end
test "xhtml format" do
request = stub_request
- assert_called(request, :parameters, times: 2, returns: {format: :xhtml}) do
+ assert_called(request, :parameters, times: 2, returns: { format: :xhtml }) do
assert_equal Mime[:html], request.format
end
end
test "txt format" do
request = stub_request
- assert_called(request, :parameters, times: 2, returns: {format: :txt}) do
+ assert_called(request, :parameters, times: 2, returns: { format: :txt }) do
assert_equal Mime[:text], request.format
end
end
@@ -817,14 +817,14 @@ class RequestFormat < BaseRequestTest
test "can override format with parameter negative" do
request = stub_request
- assert_called(request, :parameters, times: 2, returns: {format: :txt}) do
+ assert_called(request, :parameters, times: 2, returns: { format: :txt }) do
assert !request.format.xml?
end
end
test "can override format with parameter positive" do
request = stub_request
- assert_called(request, :parameters, times: 2, returns: {format: :xml}) do
+ assert_called(request, :parameters, times: 2, returns: { format: :xml }) do
assert request.format.xml?
end
end
@@ -852,21 +852,21 @@ class RequestFormat < BaseRequestTest
test "formats format:text with accept header" do
request = stub_request
- assert_called(request, :parameters, times: 2, returns: {format: :txt}) do
+ assert_called(request, :parameters, times: 2, returns: { format: :txt }) do
assert_equal [Mime[:text]], request.formats
end
end
test "formats format:unknown with accept header" do
request = stub_request
- assert_called(request, :parameters, times: 2, returns: {format: :unknown}) do
+ assert_called(request, :parameters, times: 2, returns: { format: :unknown }) do
assert_instance_of Mime::NullType, request.format
end
end
test "format is not nil with unknown format" do
request = stub_request
- assert_called(request, :parameters, times: 2, returns: {format: :hello}) do
+ assert_called(request, :parameters, times: 2, returns: { format: :hello }) do
assert request.format.nil?
assert_not request.format.html?
assert_not request.format.xml?
@@ -921,7 +921,7 @@ class RequestFormat < BaseRequestTest
request = stub_request "HTTP_ACCEPT" => "application/xml",
"HTTP_X_REQUESTED_WITH" => "XMLHttpRequest"
- assert_called(request, :parameters, times: 2, returns: {format: :json}) do
+ assert_called(request, :parameters, times: 2, returns: { format: :json }) do
assert_equal [ Mime[:json] ], request.formats
end
ensure
@@ -957,7 +957,7 @@ class RequestMimeType < BaseRequestTest
end
test "no content type" do
- assert_equal nil, stub_request.content_mime_type
+ assert_nil stub_request.content_mime_type
end
test "content type is XML" do
@@ -978,7 +978,7 @@ class RequestMimeType < BaseRequestTest
"HTTP_X_REQUESTED_WITH" => "XMLHttpRequest"
)
- assert_equal nil, request.negotiate_mime([Mime[:xml], Mime[:json]])
+ assert_nil request.negotiate_mime([Mime[:xml], Mime[:json]])
assert_equal Mime[:html], request.negotiate_mime([Mime[:xml], Mime[:html]])
assert_equal Mime[:html], request.negotiate_mime([Mime[:xml], Mime::ALL])
end
@@ -997,11 +997,11 @@ class RequestParameters < BaseRequestTest
test "parameters" do
request = stub_request
- assert_called(request, :request_parameters, times: 2, returns: {"foo" => 1}) do
- assert_called(request, :query_parameters, times: 2, returns: {"bar" => 2}) do
- assert_equal({"foo" => 1, "bar" => 2}, request.parameters)
- assert_equal({"foo" => 1}, request.request_parameters)
- assert_equal({"bar" => 2}, request.query_parameters)
+ assert_called(request, :request_parameters, times: 2, returns: { "foo" => 1 }) do
+ assert_called(request, :query_parameters, times: 2, returns: { "bar" => 2 }) do
+ assert_equal({ "foo" => 1, "bar" => 2 }, request.parameters)
+ assert_equal({ "foo" => 1 }, request.request_parameters)
+ assert_equal({ "bar" => 2 }, request.query_parameters)
end
end
end
@@ -1072,14 +1072,14 @@ end
class RequestParameterFilter < BaseRequestTest
test "process parameter filter" do
test_hashes = [
- [{"foo"=>"bar"},{"foo"=>"bar"},%w'food'],
- [{"foo"=>"bar"},{"foo"=>"[FILTERED]"},%w'foo'],
- [{"foo"=>"bar", "bar"=>"foo"},{"foo"=>"[FILTERED]", "bar"=>"foo"},%w'foo baz'],
- [{"foo"=>"bar", "baz"=>"foo"},{"foo"=>"[FILTERED]", "baz"=>"[FILTERED]"},%w'foo baz'],
- [{"bar"=>{"foo"=>"bar","bar"=>"foo"}},{"bar"=>{"foo"=>"[FILTERED]","bar"=>"foo"}},%w'fo'],
- [{"foo"=>{"foo"=>"bar","bar"=>"foo"}},{"foo"=>"[FILTERED]"},%w'f banana'],
- [{"deep"=>{"cc"=>{"code"=>"bar","bar"=>"foo"},"ss"=>{"code"=>"bar"}}},{"deep"=>{"cc"=>{"code"=>"[FILTERED]","bar"=>"foo"},"ss"=>{"code"=>"bar"}}},%w'deep.cc.code'],
- [{"baz"=>[{"foo"=>"baz"}, "1"]}, {"baz"=>[{"foo"=>"[FILTERED]"}, "1"]}, [/foo/]]]
+ [{ "foo" => "bar" }, { "foo" => "bar" }, %w'food'],
+ [{ "foo" => "bar" }, { "foo" => "[FILTERED]" }, %w'foo'],
+ [{ "foo" => "bar", "bar" => "foo" }, { "foo" => "[FILTERED]", "bar" => "foo" }, %w'foo baz'],
+ [{ "foo" => "bar", "baz" => "foo" }, { "foo" => "[FILTERED]", "baz" => "[FILTERED]" }, %w'foo baz'],
+ [{ "bar" => { "foo" => "bar", "bar" => "foo" } }, { "bar" => { "foo" => "[FILTERED]", "bar" => "foo" } }, %w'fo'],
+ [{ "foo" => { "foo" => "bar", "bar" => "foo" } }, { "foo" => "[FILTERED]" }, %w'f banana'],
+ [{ "deep" => { "cc" => { "code" => "bar", "bar" => "foo" }, "ss" => { "code" => "bar" } } }, { "deep" => { "cc" => { "code" => "[FILTERED]", "bar" => "foo" }, "ss" => { "code" => "bar" } } }, %w'deep.cc.code'],
+ [{ "baz" => [{ "foo" => "baz" }, "1"] }, { "baz" => [{ "foo" => "[FILTERED]" }, "1"] }, [/foo/]]]
test_hashes.each do |before_filter, after_filter, filter_words|
parameter_filter = ActionDispatch::Http::ParameterFilter.new(filter_words)
@@ -1091,8 +1091,8 @@ class RequestParameterFilter < BaseRequestTest
}
parameter_filter = ActionDispatch::Http::ParameterFilter.new(filter_words)
- before_filter["barg"] = {:bargain=>"gain", "blah"=>"bar", "bar"=>{"bargain"=>{"blah"=>"foo"}}}
- after_filter["barg"] = {:bargain=>"niag", "blah"=>"[FILTERED]", "bar"=>{"bargain"=>{"blah"=>"[FILTERED]"}}}
+ before_filter["barg"] = { :bargain => "gain", "blah" => "bar", "bar" => { "bargain" => { "blah" => "foo" } } }
+ after_filter["barg"] = { :bargain => "niag", "blah" => "[FILTERED]", "bar" => { "bargain" => { "blah" => "[FILTERED]" } } }
assert_equal after_filter, parameter_filter.filter(before_filter)
end
@@ -1192,7 +1192,7 @@ class RequestEtag < BaseRequestTest
test "doesn't match absent If-None-Match" do
request = stub_request
- assert_equal nil, request.if_none_match
+ assert_nil request.if_none_match
assert_equal [], request.if_none_match_etags
assert_not request.etag_matches?("foo")
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index d539092244..7433c5ce0c 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -40,7 +40,7 @@ class ResponseTest < ActiveSupport::TestCase
def test_each_isnt_called_if_str_body_is_written
# Controller writes and reads response body
each_counter = 0
- @response.body = Object.new.tap {|o| o.singleton_class.send(:define_method, :each) { |&block| each_counter += 1; block.call "foo" } }
+ @response.body = Object.new.tap { |o| o.singleton_class.send(:define_method, :each) { |&block| each_counter += 1; block.call "foo" } }
@response["X-Foo"] = @response.body
assert_equal 1, each_counter, "#each was not called once"
@@ -74,7 +74,7 @@ class ResponseTest < ActiveSupport::TestCase
@response.body = "Hello, World!"
# even though there's no explicitly set content-type,
- assert_equal nil, @response.content_type
+ assert_nil @response.content_type
# after the action reads back @response.body,
assert_equal "Hello, World!", @response.body
@@ -110,6 +110,11 @@ class ResponseTest < ActiveSupport::TestCase
assert_equal "application/aaron", @response.content_type
end
+ def test_empty_content_type_returns_nil
+ @response.headers["Content-Type"] = ""
+ assert_nil @response.content_type
+ end
+
test "simple output" do
@response.body = "Hello, World!"
@@ -131,7 +136,7 @@ class ResponseTest < ActiveSupport::TestCase
def test_only_set_charset_still_defaults_to_text_html
response = ActionDispatch::Response.new
response.charset = "utf-16"
- _,headers,_ = response.to_a
+ _, headers, _ = response.to_a
assert_equal "text/html; charset=utf-16", headers["Content-Type"]
end
@@ -224,22 +229,22 @@ class ResponseTest < ActiveSupport::TestCase
@response.set_cookie("user_name", value: "david", path: "/")
_status, headers, _body = @response.to_a
assert_equal "user_name=david; path=/", headers["Set-Cookie"]
- assert_equal({"user_name" => "david"}, @response.cookies)
+ assert_equal({ "user_name" => "david" }, @response.cookies)
end
test "multiple cookies" do
@response.set_cookie("user_name", value: "david", path: "/")
- @response.set_cookie("login", value: "foo&bar", path: "/", expires: Time.utc(2005, 10, 10,5))
+ @response.set_cookie("login", value: "foo&bar", path: "/", expires: Time.utc(2005, 10, 10, 5))
_status, headers, _body = @response.to_a
assert_equal "user_name=david; path=/\nlogin=foo%26bar; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000", headers["Set-Cookie"]
- assert_equal({"login" => "foo&bar", "user_name" => "david"}, @response.cookies)
+ assert_equal({ "login" => "foo&bar", "user_name" => "david" }, @response.cookies)
end
test "delete cookies" do
@response.set_cookie("user_name", value: "david", path: "/")
- @response.set_cookie("login", value: "foo&bar", path: "/", expires: Time.utc(2005, 10, 10,5))
+ @response.set_cookie("login", value: "foo&bar", path: "/", expires: Time.utc(2005, 10, 10, 5))
@response.delete_cookie("login")
- assert_equal({"user_name" => "david", "login" => nil}, @response.cookies)
+ assert_equal({ "user_name" => "david", "login" => nil }, @response.cookies)
end
test "read ETag and Cache-Control" do
@@ -254,7 +259,7 @@ class ResponseTest < ActiveSupport::TestCase
assert resp.weak_etag?
assert_not resp.strong_etag?
assert_equal('W/"202cb962ac59075b964b07152d234b70"', resp.etag)
- assert_equal({public: true}, resp.cache_control)
+ assert_equal({ public: true }, resp.cache_control)
assert_equal("public", resp.headers["Cache-Control"])
assert_equal('W/"202cb962ac59075b964b07152d234b70"', resp.headers["ETag"])
@@ -461,14 +466,14 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest
assert_equal('W/"202cb962ac59075b964b07152d234b70"', @response.headers["ETag"])
assert_equal('W/"202cb962ac59075b964b07152d234b70"', @response.etag)
- assert_equal({public: true}, @response.cache_control)
+ assert_equal({ public: true }, @response.cache_control)
end
test "response cache control from rackish app" do
@app = lambda { |env|
[200,
- {"ETag" => 'W/"202cb962ac59075b964b07152d234b70"',
- "Cache-Control" => "public"}, ["Hello"]]
+ { "ETag" => 'W/"202cb962ac59075b964b07152d234b70"',
+ "Cache-Control" => "public" }, ["Hello"]]
}
get "/"
@@ -478,7 +483,7 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest
assert_equal('W/"202cb962ac59075b964b07152d234b70"', @response.headers["ETag"])
assert_equal('W/"202cb962ac59075b964b07152d234b70"', @response.etag)
- assert_equal({public: true}, @response.cache_control)
+ assert_equal({ public: true }, @response.cache_control)
end
test "response charset and content type from railsish app" do
@@ -503,7 +508,7 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest
test "response charset and content type from rackish app" do
@app = lambda { |env|
[200,
- {"Content-Type" => "application/xml; charset=utf-16"},
+ { "Content-Type" => "application/xml; charset=utf-16" },
["Hello"]]
}
diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb
index 97137a4762..a4babf8554 100644
--- a/actionpack/test/dispatch/routing/inspector_test.rb
+++ b/actionpack/test/dispatch/routing/inspector_test.rb
@@ -172,7 +172,7 @@ module ActionDispatch
def test_rails_routes_shows_route_with_defaults
output = draw do
- get "photos/:id" => "photos#show", :defaults => {format: "jpg"}
+ get "photos/:id" => "photos#show", :defaults => { format: "jpg" }
end
assert_equal [
@@ -291,7 +291,7 @@ module ActionDispatch
output = draw do
get "/foo" => redirect("/foo/bar"), :constraints => { subdomain: "admin" }
get "/bar" => redirect(path: "/foo/bar", status: 307)
- get "/foobar" => redirect{ "/foo/bar" }
+ get "/foobar" => redirect { "/foo/bar" }
end
assert_equal [
diff --git a/actionpack/test/dispatch/routing/ipv6_redirect_test.rb b/actionpack/test/dispatch/routing/ipv6_redirect_test.rb
index 4987ed84e4..179aee9ba7 100644
--- a/actionpack/test/dispatch/routing/ipv6_redirect_test.rb
+++ b/actionpack/test/dispatch/routing/ipv6_redirect_test.rb
@@ -7,7 +7,7 @@ class IPv6IntegrationTest < ActionDispatch::IntegrationTest
class ::BadRouteRequestController < ActionController::Base
include Routes.url_helpers
def index
- render text: foo_path
+ render plain: foo_path
end
def foo
diff --git a/actionpack/test/dispatch/routing_assertions_test.rb b/actionpack/test/dispatch/routing_assertions_test.rb
index 56ab4e883f..917ce7e668 100644
--- a/actionpack/test/dispatch/routing_assertions_test.rb
+++ b/actionpack/test/dispatch/routing_assertions_test.rb
@@ -98,7 +98,7 @@ class RoutingAssertionsTest < ActionController::TestCase
end
def test_assert_routing_with_extras
- assert_routing("/articles", { controller: "articles", action: "index", page: "1" }, { }, page: "1")
+ assert_routing("/articles", { controller: "articles", action: "index", page: "1" }, {}, page: "1")
end
def test_assert_routing_with_hash_constraint
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index e64dedf817..474d053af6 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -4,7 +4,7 @@ require "controller/fake_controllers"
class TestRoutingMapper < ActionDispatch::IntegrationTest
SprocketsApp = lambda { |env|
- [200, {"Content-Type" => "text/html"}, ["javascripts"]]
+ [200, { "Content-Type" => "text/html" }, ["javascripts"]]
}
class IpRestrictor
@@ -245,7 +245,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
def test_redirect_proc
draw do
- get "account/proc/:name", to: redirect {|params, req| "/#{params[:name].pluralize}" }
+ get "account/proc/:name", to: redirect { |params, req| "/#{params[:name].pluralize}" }
end
get "/account/proc/person"
@@ -254,7 +254,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
def test_redirect_proc_with_request
draw do
- get "account/proc_req" => redirect {|params, req| "/#{req.method}" }
+ get "account/proc_req" => redirect { |params, req| "/#{req.method}" }
end
get "/account/proc_req"
@@ -364,18 +364,13 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_pagemarks
- tc = self
draw do
scope "pagemark", controller: "pagemarks", as: :pagemark do
- tc.assert_deprecated do
- get "new", path: "build"
- end
+ get "build", action: "new", as: "new"
post "create", as: ""
put "update"
get "remove", action: :destroy, as: :remove
- tc.assert_deprecated do
- get action: :show, as: :show
- end
+ get "", action: :show, as: :show
end
end
@@ -480,7 +475,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
# without dup, additional (and possibly unwanted) values will be present in the options (eg. :host)
- original_options = {controller: "projects", action: "status"}
+ original_options = { controller: "projects", action: "status" }
options = original_options.dup
url_for options
@@ -495,7 +490,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
controller = "/projects"
- options = {controller: controller, action: "status", only_path: true}
+ options = { controller: controller, action: "status", only_path: true }
url = url_for(options)
assert_equal "/projects/status", url
@@ -958,15 +953,15 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_resource_does_not_modify_passed_options
- options = {id: /.+?/, format: /json|xml/}
+ options = { id: /.+?/, format: /json|xml/ }
draw { resource :user, options }
- assert_equal({id: /.+?/, format: /json|xml/}, options)
+ assert_equal({ id: /.+?/, format: /json|xml/ }, options)
end
def test_resources_does_not_modify_passed_options
- options = {id: /.+?/, format: /json|xml/}
+ options = { id: /.+?/, format: /json|xml/ }
draw { resources :users, options }
- assert_equal({id: /.+?/, format: /json|xml/}, options)
+ assert_equal({ id: /.+?/, format: /json|xml/ }, options)
end
def test_path_names
@@ -1759,6 +1754,24 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 1, @request.params[:page]
end
+ def test_keyed_default_string_params_with_match
+ draw do
+ match "/", to: "pages#show", via: :get, defaults: { id: "home" }
+ end
+
+ get "/"
+ assert_equal "home", @request.params[:id]
+ end
+
+ def test_default_string_params_with_match
+ draw do
+ match "/", to: "pages#show", via: :get, id: "home"
+ end
+
+ get "/"
+ assert_equal "home", @request.params[:id]
+ end
+
def test_keyed_default_string_params_with_root
draw do
root to: "pages#show", defaults: { id: "home" }
@@ -1920,7 +1933,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
post :preview, on: :new
end
- resource :admin, path_names: { new: "novo" }, path: "administrador" do
+ resource :admin, path_names: { new: "novo" }, path: "administrador" do
post :preview, on: :new
end
@@ -2557,7 +2570,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get "/movies/00001"
assert_equal "Not Found", @response.body
- assert_raises(ActionController::UrlGenerationError){ movie_path(id: "00001") }
+ assert_raises(ActionController::UrlGenerationError) { movie_path(id: "00001") }
get "/movies/0001/reviews"
assert_equal "reviews#index", @response.body
@@ -2565,7 +2578,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get "/movies/00001/reviews"
assert_equal "Not Found", @response.body
- assert_raises(ActionController::UrlGenerationError){ movie_reviews_path(movie_id: "00001") }
+ assert_raises(ActionController::UrlGenerationError) { movie_reviews_path(movie_id: "00001") }
get "/movies/0001/reviews/0001"
assert_equal "reviews#show", @response.body
@@ -2573,7 +2586,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get "/movies/00001/reviews/0001"
assert_equal "Not Found", @response.body
- assert_raises(ActionController::UrlGenerationError){ movie_path(movie_id: "00001", id: "00001") }
+ assert_raises(ActionController::UrlGenerationError) { movie_path(movie_id: "00001", id: "00001") }
get "/movies/0001/trailer"
assert_equal "trailers#show", @response.body
@@ -2581,7 +2594,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get "/movies/00001/trailer"
assert_equal "Not Found", @response.body
- assert_raises(ActionController::UrlGenerationError){ movie_trailer_path(movie_id: "00001") }
+ assert_raises(ActionController::UrlGenerationError) { movie_trailer_path(movie_id: "00001") }
end
def test_only_should_be_read_from_scope
@@ -3009,7 +3022,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get "/cities", to: "countries#cities"
end
- get "/countries/:country/(*other)", to: redirect{ |params, req| params[:other] ? "/countries/all/#{params[:other]}" : "/countries/all" }
+ get "/countries/:country/(*other)", to: redirect { |params, req| params[:other] ? "/countries/all/#{params[:other]}" : "/countries/all" }
end
get "/countries/France"
@@ -3030,7 +3043,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
scope "/italians" do
get "/writers", to: "italians#writers", constraints: ::TestRoutingMapper::IpRestrictor
get "/sculptors", to: "italians#sculptors"
- get "/painters/:painter", to: "italians#painters", constraints: {painter: /michelangelo/}
+ get "/painters/:painter", to: "italians#painters", constraints: { painter: /michelangelo/ }
end
end
@@ -3120,7 +3133,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get "/lists/2/todos/1"
assert_equal "Not Found", @response.body
- assert_raises(ActionController::UrlGenerationError){ list_todo_path(list_id: "2", id: "1") }
+ assert_raises(ActionController::UrlGenerationError) { list_todo_path(list_id: "2", id: "1") }
end
def test_redirect_argument_error
@@ -3651,6 +3664,48 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_multiple_roots
+ draw do
+ namespace :foo do
+ root "pages#index", constraints: { host: "www.example.com" }
+ root "admin/pages#index", constraints: { host: "admin.example.com" }
+ end
+
+ root "pages#index", constraints: { host: "www.example.com" }
+ root "admin/pages#index", constraints: { host: "admin.example.com" }
+ end
+
+ get "http://www.example.com/foo"
+ assert_equal "foo/pages#index", @response.body
+
+ get "http://admin.example.com/foo"
+ assert_equal "foo/admin/pages#index", @response.body
+
+ get "http://www.example.com/"
+ assert_equal "pages#index", @response.body
+
+ get "http://admin.example.com/"
+ assert_equal "admin/pages#index", @response.body
+ end
+
+ def test_multiple_namespaced_roots
+ draw do
+ namespace :foo do
+ root "test#index"
+ end
+
+ root "test#index"
+
+ namespace :bar do
+ root "test#index"
+ end
+ end
+
+ assert_equal "/foo", foo_root_path
+ assert_equal "/", root_path
+ assert_equal "/bar", bar_root_path
+ end
+
private
def draw(&block)
@@ -3681,7 +3736,7 @@ private
https!(old_https)
end
- def verify_redirect(url, status=301)
+ def verify_redirect(url, status = 301)
assert_equal status, @response.status
assert_equal url, @response.headers["Location"]
assert_equal expected_redirect_body(url), @response.body
@@ -3720,13 +3775,13 @@ class TestAltApp < ActionDispatch::IntegrationTest
class XHeader
def call(env)
- [200, {"Content-Type" => "text/html"}, ["XHeader"]]
+ [200, { "Content-Type" => "text/html" }, ["XHeader"]]
end
end
class AltApp
def call(env)
- [200, {"Content-Type" => "text/html"}, ["Alternative App"]]
+ [200, { "Content-Type" => "text/html" }, ["Alternative App"]]
end
end
@@ -3736,7 +3791,7 @@ class TestAltApp < ActionDispatch::IntegrationTest
end
}.new
AltRoutes.draw do
- get "/" => TestAltApp::XHeader.new, :constraints => {x_header: /HEADER/}
+ get "/" => TestAltApp::XHeader.new, :constraints => { x_header: /HEADER/ }
get "/" => TestAltApp::AltApp.new
end
@@ -3903,7 +3958,7 @@ class TestDefaultScope < ActionDispatch::IntegrationTest
end
DefaultScopeRoutes = ActionDispatch::Routing::RouteSet.new
- DefaultScopeRoutes.default_scope = {module: :blog}
+ DefaultScopeRoutes.default_scope = { module: :blog }
DefaultScopeRoutes.draw do
resources :posts
end
@@ -4108,7 +4163,7 @@ class TestRedirectInterpolation < ActionDispatch::IntegrationTest
end
private
- def verify_redirect(url, status=301)
+ def verify_redirect(url, status = 301)
assert_equal status, @response.status
assert_equal url, @response.headers["Location"]
assert_equal expected_redirect_body(url), @response.body
@@ -4134,7 +4189,7 @@ class TestConstraintsAccessingParameters < ActionDispatch::IntegrationTest
test "parameters are reset between constraint checks" do
get "/bar"
- assert_equal nil, @request.params[:foo]
+ assert_nil @request.params[:foo]
assert_equal "bar", @request.params[:bar]
end
end
@@ -4144,7 +4199,7 @@ class TestGlobRoutingMapper < ActionDispatch::IntegrationTest
app.draw do
ok = lambda { |env| [200, { "Content-Type" => "text/plain" }, []] }
- get "/*id" => redirect("/not_cars"), :constraints => {id: /dummy/}
+ get "/*id" => redirect("/not_cars"), :constraints => { id: /dummy/ }
get "/cars" => ok
end
end
@@ -4624,29 +4679,32 @@ class TestUrlGenerationErrors < ActionDispatch::IntegrationTest
include Routes.url_helpers
- test "url helpers raise a helpful error message when generation fails" do
+ test "url helpers raise a 'missing keys' error for a nil param with optimized helpers" do
url, missing = { action: "show", controller: "products", id: nil }, [:id]
- message = "No route matches #{url.inspect} missing required keys: #{missing.inspect}"
+ message = "No route matches #{url.inspect}, missing required keys: #{missing.inspect}"
- # Optimized url helper
- error = assert_raises(ActionController::UrlGenerationError){ product_path(nil) }
+ error = assert_raises(ActionController::UrlGenerationError) { product_path(nil) }
assert_equal message, error.message
+ end
- # Non-optimized url helper
- error = assert_raises(ActionController::UrlGenerationError, message){ product_path(id: nil) }
+ test "url helpers raise a 'constraint failure' error for a nil param with non-optimized helpers" do
+ url, missing = { action: "show", controller: "products", id: nil }, [:id]
+ message = "No route matches #{url.inspect}, possible unmatched constraints: #{missing.inspect}"
+
+ error = assert_raises(ActionController::UrlGenerationError, message) { product_path(id: nil) }
assert_equal message, error.message
end
- test "url helpers raise message with mixed parameters when generation fails " do
- url, missing = { action: "show", controller: "products", id: nil, "id"=>"url-tested"}, [:id]
- message = "No route matches #{url.inspect} missing required keys: #{missing.inspect}"
+ test "url helpers raise message with mixed parameters when generation fails" do
+ url, missing = { action: "show", controller: "products", id: nil, "id" => "url-tested" }, [:id]
+ message = "No route matches #{url.inspect}, possible unmatched constraints: #{missing.inspect}"
# Optimized url helper
- error = assert_raises(ActionController::UrlGenerationError){ product_path(nil, "id"=>"url-tested") }
+ error = assert_raises(ActionController::UrlGenerationError) { product_path(nil, "id" => "url-tested") }
assert_equal message, error.message
# Non-optimized url helper
- error = assert_raises(ActionController::UrlGenerationError, message){ product_path(id: nil, "id"=>"url-tested") }
+ error = assert_raises(ActionController::UrlGenerationError, message) { product_path(id: nil, "id" => "url-tested") }
assert_equal message, error.message
end
end
diff --git a/actionpack/test/dispatch/runner_test.rb b/actionpack/test/dispatch/runner_test.rb
new file mode 100644
index 0000000000..969933c9ed
--- /dev/null
+++ b/actionpack/test/dispatch/runner_test.rb
@@ -0,0 +1,18 @@
+require "abstract_unit"
+
+class RunnerTest < ActiveSupport::TestCase
+ test "runner preserves the setting of integration_session" do
+ runner = Class.new do
+ def before_setup
+
+ end
+ end.new
+
+ runner.extend(ActionDispatch::Integration::Runner)
+ runner.integration_session.host! "lvh.me"
+
+ runner.before_setup
+
+ assert_equal "lvh.me", runner.integration_session.host
+ end
+end
diff --git a/actionpack/test/dispatch/session/cache_store_test.rb b/actionpack/test/dispatch/session/cache_store_test.rb
index 5252d93850..859059063f 100644
--- a/actionpack/test/dispatch/session/cache_store_test.rb
+++ b/actionpack/test/dispatch/session/cache_store_test.rb
@@ -142,21 +142,21 @@ class CacheStoreTest < ActionDispatch::IntegrationTest
get "/get_session_value"
assert_response :success
- assert_equal nil, headers["Set-Cookie"], "should not resend the cookie again if session_id cookie is already exists"
+ assert_nil headers["Set-Cookie"], "should not resend the cookie again if session_id cookie is already exists"
end
end
def test_prevents_session_fixation
with_test_route_set do
- assert_equal nil, @cache.read("_session_id:0xhax")
+ assert_nil @cache.read("_session_id:0xhax")
cookies["_session_id"] = "0xhax"
get "/set_session_value"
assert_response :success
assert_not_equal "0xhax", cookies["_session_id"]
- assert_equal nil, @cache.read("_session_id:0xhax")
- assert_equal({"foo" => "bar"}, @cache.read("_session_id:#{cookies['_session_id']}"))
+ assert_nil @cache.read("_session_id:0xhax")
+ assert_equal({ "foo" => "bar" }, @cache.read("_session_id:#{cookies['_session_id']}"))
end
end
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index 013d289c6d..2a1053be16 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -108,7 +108,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
with_test_route_set(secure: true) do
get "/set_session_value"
assert_response :success
- assert_equal nil, headers["Set-Cookie"]
+ assert_nil headers["Set-Cookie"]
end
end
@@ -169,7 +169,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
with_test_route_set do
get "/no_session_access"
assert_response :success
- assert_equal nil, headers["Set-Cookie"]
+ assert_nil headers["Set-Cookie"]
end
end
@@ -179,7 +179,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
"fef868465920f415f2c0652d6910d3af288a0367"
get "/no_session_access"
assert_response :success
- assert_equal nil, headers["Set-Cookie"]
+ assert_nil headers["Set-Cookie"]
end
end
diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb
index c2d0719b4e..121e9ebef7 100644
--- a/actionpack/test/dispatch/session/mem_cache_store_test.rb
+++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb
@@ -157,7 +157,7 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
get "/get_session_value"
assert_response :success
- assert_equal nil, headers["Set-Cookie"], "should not resend the cookie again if session_id cookie is already exists"
+ assert_nil headers["Set-Cookie"], "should not resend the cookie again if session_id cookie is already exists"
end
rescue Dalli::RingError => ex
skip ex.message, ex.backtrace
diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb
index d8f673c212..3513534d72 100644
--- a/actionpack/test/dispatch/show_exceptions_test.rb
+++ b/actionpack/test/dispatch/show_exceptions_test.rb
@@ -11,7 +11,7 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
begin
raise StandardError.new
rescue
- raise ActionDispatch::ParamsParser::ParseError
+ raise ActionDispatch::Http::Parameters::ParseError
end
when "/method_not_allowed"
raise ActionController::MethodNotAllowed, "PUT"
diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb
index ccddb90bb5..e29ffa750c 100644
--- a/actionpack/test/dispatch/ssl_test.rb
+++ b/actionpack/test/dispatch/ssl_test.rb
@@ -12,25 +12,16 @@ class SSLTest < ActionDispatch::IntegrationTest
end
class RedirectSSLTest < SSLTest
- def assert_not_redirected(url, headers: {}, redirect: {}, deprecated_host: nil,
- deprecated_port: nil)
-
- self.app = build_app ssl_options: { redirect: redirect,
- host: deprecated_host, port: deprecated_port
- }
-
+ def assert_not_redirected(url, headers: {}, redirect: {})
+ self.app = build_app ssl_options: { redirect: redirect }
get url, headers: headers
assert_response :ok
end
- def assert_redirected(redirect: {}, deprecated_host: nil, deprecated_port: nil,
- from: "http://a/b?c=d", to: from.sub("http", "https"))
-
+ def assert_redirected(redirect: {}, from: "http://a/b?c=d", to: from.sub("http", "https"))
redirect = { status: 301, body: [] }.merge(redirect)
- self.app = build_app ssl_options: { redirect: redirect,
- host: deprecated_host, port: deprecated_port
- }
+ self.app = build_app ssl_options: { redirect: redirect }
get from
assert_response redirect[:status] || 301
@@ -38,6 +29,16 @@ class RedirectSSLTest < SSLTest
assert_equal redirect[:body].join, @response.body
end
+ def assert_post_redirected(redirect: {}, from: "http://a/b?c=d",
+ to: from.sub("http", "https"))
+
+ self.app = build_app ssl_options: { redirect: redirect }
+
+ post from
+ assert_response redirect[:status] || 307
+ assert_redirected_to to
+ end
+
test "exclude can avoid redirect" do
excluding = { exclude: -> request { request.path =~ /healthcheck/ } }
@@ -57,6 +58,10 @@ class RedirectSSLTest < SSLTest
assert_redirected
end
+ test "http POST is redirected to https with status 307" do
+ assert_post_redirected
+ end
+
test "redirect with non-301 status" do
assert_redirected redirect: { status: 307 }
end
@@ -85,18 +90,6 @@ class RedirectSSLTest < SSLTest
assert_redirected redirect: { host: "ssl:443" }, to: "https://ssl:443/b?c=d"
end
- test ":host is deprecated, moved within redirect: { host: … }" do
- assert_deprecated do
- assert_redirected deprecated_host: "foo", to: "https://foo/b?c=d"
- end
- end
-
- test ":port is deprecated, moved within redirect: { port: … }" do
- assert_deprecated do
- assert_redirected deprecated_port: 1, to: "https://a:1/b?c=d"
- end
- end
-
test "no redirect with redirect set to false" do
assert_not_redirected "http://example.org", redirect: false
end
@@ -125,23 +118,19 @@ class StrictTransportSecurityTest < SSLTest
end
test "hsts: true enables default settings" do
- assert_hsts EXPECTED, hsts: true
+ assert_hsts EXPECTED_WITH_SUBDOMAINS, hsts: true
end
test "hsts: false sets max-age to zero, clearing browser HSTS settings" do
- assert_hsts "max-age=0", hsts: false
+ assert_hsts "max-age=0; includeSubDomains", hsts: false
end
test ":expires sets max-age" do
- assert_deprecated do
- assert_hsts "max-age=500", hsts: { expires: 500 }
- end
+ assert_hsts "max-age=500; includeSubDomains", hsts: { expires: 500 }
end
test ":expires supports AS::Duration arguments" do
- assert_deprecated do
- assert_hsts "max-age=31557600", hsts: { expires: 1.year }
- end
+ assert_hsts "max-age=31557600; includeSubDomains", hsts: { expires: 1.year }
end
test "include subdomains" do
@@ -153,15 +142,11 @@ class StrictTransportSecurityTest < SSLTest
end
test "opt in to browser preload lists" do
- assert_deprecated do
- assert_hsts "#{EXPECTED}; preload", hsts: { preload: true }
- end
+ assert_hsts "#{EXPECTED_WITH_SUBDOMAINS}; preload", hsts: { preload: true }
end
test "opt out of browser preload lists" do
- assert_deprecated do
- assert_hsts EXPECTED, hsts: { preload: false }
- end
+ assert_hsts EXPECTED_WITH_SUBDOMAINS, hsts: { preload: false }
end
end
diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb
index e8f0ce21e9..d8bc96e3e0 100644
--- a/actionpack/test/dispatch/static_test.rb
+++ b/actionpack/test/dispatch/static_test.rb
@@ -3,7 +3,7 @@ require "zlib"
module StaticTests
DummyApp = lambda { |env|
- [200, {"Content-Type" => "text/plain"}, ["Hello, World!"]]
+ [200, { "Content-Type" => "text/plain" }, ["Hello, World!"]]
}
def setup
@@ -44,16 +44,6 @@ module StaticTests
assert_equal "Hello, World!", get("/doorkeeper%00").body
end
- def test_sets_cache_control
- app = assert_deprecated do
- ActionDispatch::Static.new(DummyApp, @root, "public, max-age=60")
- end
- response = Rack::MockRequest.new(app).request("GET", "/index.html")
-
- assert_html "/index.html", response
- assert_equal "public, max-age=60", response.headers["Cache-Control"]
- end
-
def test_serves_static_index_at_root
assert_html "/index.html", get("/index.html")
assert_html "/index.html", get("/index")
@@ -153,22 +143,22 @@ module StaticTests
assert_equal "Accept-Encoding", response.headers["Vary"]
assert_equal "gzip", response.headers["Content-Encoding"]
- response = get(file_name, "HTTP_ACCEPT_ENCODING" => "Gzip")
- assert_gzip file_name, response
+ response = get(file_name, "HTTP_ACCEPT_ENCODING" => "Gzip")
+ assert_gzip file_name, response
- response = get(file_name, "HTTP_ACCEPT_ENCODING" => "GZIP")
- assert_gzip file_name, response
+ response = get(file_name, "HTTP_ACCEPT_ENCODING" => "GZIP")
+ assert_gzip file_name, response
- response = get(file_name, "HTTP_ACCEPT_ENCODING" => "compress;q=0.5, gzip;q=1.0")
- assert_gzip file_name, response
+ response = get(file_name, "HTTP_ACCEPT_ENCODING" => "compress;q=0.5, gzip;q=1.0")
+ assert_gzip file_name, response
- response = get(file_name, "HTTP_ACCEPT_ENCODING" => "")
+ response = get(file_name, "HTTP_ACCEPT_ENCODING" => "")
assert_not_equal "gzip", response.headers["Content-Encoding"]
end
def test_does_not_modify_path_info
file_name = "/gzip/application-a71b3024f80aea3181c09774ca17e712.js"
- env = {"PATH_INFO" => file_name, "HTTP_ACCEPT_ENCODING" => "gzip", "REQUEST_METHOD" => "POST"}
+ env = { "PATH_INFO" => file_name, "HTTP_ACCEPT_ENCODING" => "gzip", "REQUEST_METHOD" => "POST" }
@app.call(env)
assert_equal file_name, env["PATH_INFO"]
end
@@ -176,7 +166,7 @@ module StaticTests
def test_serves_gzip_with_propper_content_type_fallback
file_name = "/gzip/foo.zoo"
response = get(file_name, "HTTP_ACCEPT_ENCODING" => "gzip")
- assert_gzip file_name, response
+ assert_gzip file_name, response
default_response = get(file_name) # no gzip
assert_equal default_response.headers["Content-Type"], response.headers["Content-Type"]
@@ -187,9 +177,9 @@ module StaticTests
last_modified = File.mtime(File.join(@root, "#{file_name}.gz"))
response = get(file_name, "HTTP_ACCEPT_ENCODING" => "gzip", "HTTP_IF_MODIFIED_SINCE" => last_modified.httpdate)
assert_equal 304, response.status
- assert_equal nil, response.headers["Content-Type"]
- assert_equal nil, response.headers["Content-Encoding"]
- assert_equal nil, response.headers["Vary"]
+ assert_nil response.headers["Content-Type"]
+ assert_nil response.headers["Content-Encoding"]
+ assert_nil response.headers["Vary"]
end
def test_serves_files_with_headers
@@ -214,7 +204,7 @@ module StaticTests
end
# Windows doesn't allow \ / : * ? " < > | in filenames
- unless RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
+ unless Gem.win_platform?
def test_serves_static_file_with_colon
with_static_file "/foo/foo:bar.html" do |file|
assert_html file, get("/foo/foo%3Abar.html")
@@ -266,7 +256,7 @@ class StaticTest < ActiveSupport::TestCase
def setup
super
@root = "#{FIXTURE_LOAD_PATH}/public"
- @app = ActionDispatch::Static.new(DummyApp, @root, headers: {"Cache-Control" => "public, max-age=60"})
+ @app = ActionDispatch::Static.new(DummyApp, @root, headers: { "Cache-Control" => "public, max-age=60" })
end
def public_path
@@ -279,14 +269,14 @@ class StaticTest < ActiveSupport::TestCase
filename = "shared.html.erb"
assert File.exist?(File.join(@root, "..", filename))
env = {
- "REQUEST_METHOD"=>"GET",
- "REQUEST_PATH"=>"/..%2F#{filename}",
- "PATH_INFO"=>"/..%2F#{filename}",
- "REQUEST_URI"=>"/..%2F#{filename}",
- "HTTP_VERSION"=>"HTTP/1.1",
- "SERVER_NAME"=>"localhost",
- "SERVER_PORT"=>"8080",
- "QUERY_STRING"=>""
+ "REQUEST_METHOD" => "GET",
+ "REQUEST_PATH" => "/..%2F#{filename}",
+ "PATH_INFO" => "/..%2F#{filename}",
+ "REQUEST_URI" => "/..%2F#{filename}",
+ "HTTP_VERSION" => "HTTP/1.1",
+ "SERVER_NAME" => "localhost",
+ "SERVER_PORT" => "8080",
+ "QUERY_STRING" => ""
}
assert_equal(DummyApp.call(nil), @app.call(env))
end
@@ -308,7 +298,7 @@ class StaticEncodingTest < StaticTest
def setup
super
@root = "#{FIXTURE_LOAD_PATH}/公共"
- @app = ActionDispatch::Static.new(DummyApp, @root, headers: {"Cache-Control" => "public, max-age=60"})
+ @app = ActionDispatch::Static.new(DummyApp, @root, headers: { "Cache-Control" => "public, max-age=60" })
end
def public_path
diff --git a/actionpack/test/dispatch/test_request_test.rb b/actionpack/test/dispatch/test_request_test.rb
index 9beab1ee07..85a6df4975 100644
--- a/actionpack/test/dispatch/test_request_test.rb
+++ b/actionpack/test/dispatch/test_request_test.rb
@@ -30,27 +30,27 @@ class TestRequestTest < ActiveSupport::TestCase
req = ActionDispatch::TestRequest.create({})
assert_equal({}, req.cookies)
- assert_equal nil, req.env["HTTP_COOKIE"]
+ assert_nil req.env["HTTP_COOKIE"]
req.cookie_jar["user_name"] = "david"
- assert_cookies({"user_name" => "david"}, req.cookie_jar)
+ assert_cookies({ "user_name" => "david" }, req.cookie_jar)
req.cookie_jar["login"] = "XJ-122"
- assert_cookies({"user_name" => "david", "login" => "XJ-122"}, req.cookie_jar)
+ assert_cookies({ "user_name" => "david", "login" => "XJ-122" }, req.cookie_jar)
assert_nothing_raised do
req.cookie_jar["login"] = nil
- assert_cookies({"user_name" => "david", "login" => nil}, req.cookie_jar)
+ assert_cookies({ "user_name" => "david", "login" => nil }, req.cookie_jar)
end
req.cookie_jar.delete(:login)
- assert_cookies({"user_name" => "david"}, req.cookie_jar)
+ assert_cookies({ "user_name" => "david" }, req.cookie_jar)
req.cookie_jar.clear
assert_cookies({}, req.cookie_jar)
req.cookie_jar.update(user_name: "david")
- assert_cookies({"user_name" => "david"}, req.cookie_jar)
+ assert_cookies({ "user_name" => "david" }, req.cookie_jar)
end
test "does not complain when there is no application config" do
@@ -88,6 +88,13 @@ class TestRequestTest < ActiveSupport::TestCase
assert_equal "GoogleBot", req.user_agent
end
+ test "request_method getter and setter" do
+ req = ActionDispatch::TestRequest.create
+ req.request_method # to reproduce bug caused by memoization
+ req.request_method = "POST"
+ assert_equal "POST", req.request_method
+ end
+
test "setter methods" do
req = ActionDispatch::TestRequest.create({})
get = "GET"
diff --git a/actionpack/test/dispatch/uploaded_file_test.rb b/actionpack/test/dispatch/uploaded_file_test.rb
index 60d0246a68..51680216e4 100644
--- a/actionpack/test/dispatch/uploaded_file_test.rb
+++ b/actionpack/test/dispatch/uploaded_file_test.rb
@@ -58,25 +58,25 @@ module ActionDispatch
end
def test_delegates_close_to_tempfile
- tf = Class.new { def close(unlink_now=false); "thunderhorse" end }
+ tf = Class.new { def close(unlink_now = false); "thunderhorse" end }
uf = Http::UploadedFile.new(tempfile: tf.new)
assert_equal "thunderhorse", uf.close
end
def test_close_accepts_parameter
- tf = Class.new { def close(unlink_now=false); "thunderhorse: #{unlink_now}" end }
+ tf = Class.new { def close(unlink_now = false); "thunderhorse: #{unlink_now}" end }
uf = Http::UploadedFile.new(tempfile: tf.new)
assert_equal "thunderhorse: true", uf.close(true)
end
def test_delegates_read_to_tempfile
- tf = Class.new { def read(length=nil, buffer=nil); "thunderhorse" end }
+ tf = Class.new { def read(length = nil, buffer = nil); "thunderhorse" end }
uf = Http::UploadedFile.new(tempfile: tf.new)
assert_equal "thunderhorse", uf.read
end
def test_delegates_read_to_tempfile_with_params
- tf = Class.new { def read(length=nil, buffer=nil); [length, buffer] end }
+ tf = Class.new { def read(length = nil, buffer = nil); [length, buffer] end }
uf = Http::UploadedFile.new(tempfile: tf.new)
assert_equal %w{ thunder horse }, uf.read(*%w{ thunder horse })
end
diff --git a/actionpack/test/fixtures/load_me.rb b/actionpack/test/fixtures/load_me.rb
new file mode 100644
index 0000000000..e516512a4e
--- /dev/null
+++ b/actionpack/test/fixtures/load_me.rb
@@ -0,0 +1,2 @@
+class LoadMe
+end
diff --git a/actionpack/test/fixtures/multipart/mona_lisa.jpg b/actionpack/test/fixtures/multipart/mona_lisa.jpg
deleted file mode 100644
index 5cf3bef3d0..0000000000
--- a/actionpack/test/fixtures/multipart/mona_lisa.jpg
+++ /dev/null
Binary files differ
diff --git a/actionpack/test/fixtures/multipart/ruby_on_rails.jpg b/actionpack/test/fixtures/multipart/ruby_on_rails.jpg
new file mode 100644
index 0000000000..ed284ea0ba
--- /dev/null
+++ b/actionpack/test/fixtures/multipart/ruby_on_rails.jpg
Binary files differ
diff --git a/actionpack/test/fixtures/session_autoload_test/session_autoload_test/foo.rb b/actionpack/test/fixtures/session_autoload_test/session_autoload_test/foo.rb
index cd0be6d1b5..18fa5cd923 100644
--- a/actionpack/test/fixtures/session_autoload_test/session_autoload_test/foo.rb
+++ b/actionpack/test/fixtures/session_autoload_test/session_autoload_test/foo.rb
@@ -1,6 +1,6 @@
module SessionAutoloadTest
class Foo
- def initialize(bar="baz")
+ def initialize(bar = "baz")
@bar = bar
end
def inspect
diff --git a/actionpack/test/journey/gtg/builder_test.rb b/actionpack/test/journey/gtg/builder_test.rb
index 2b314cdd4e..aa8427b265 100644
--- a/actionpack/test/journey/gtg/builder_test.rb
+++ b/actionpack/test/journey/gtg/builder_test.rb
@@ -5,18 +5,18 @@ module ActionDispatch
module GTG
class TestBuilder < ActiveSupport::TestCase
def test_following_states_multi
- table = tt ["a|a"]
+ table = tt ["a|a"]
assert_equal 1, table.move([0], "a").length
end
def test_following_states_multi_regexp
- table = tt [":a|b"]
+ table = tt [":a|b"]
assert_equal 1, table.move([0], "fooo").length
assert_equal 2, table.move([0], "b").length
end
def test_multi_path
- table = tt ["/:a/d", "/b/c"]
+ table = tt ["/:a/d", "/b/c"]
[
[1, "/"],
@@ -38,7 +38,7 @@ module ActionDispatch
/articles/:id(.:format)
}
- sim = NFA::Simulator.new table
+ sim = NFA::Simulator.new table
match = sim.match "/articles/new"
assert_equal 2, match.memos.length
@@ -52,7 +52,7 @@ module ActionDispatch
/articles/new(.:format)
}
- sim = NFA::Simulator.new table
+ sim = NFA::Simulator.new table
match = sim.match "/articles/new"
assert_equal 2, match.memos.length
diff --git a/actionpack/test/journey/gtg/transition_table_test.rb b/actionpack/test/journey/gtg/transition_table_test.rb
index 23b05cc5f6..c7315c0338 100644
--- a/actionpack/test/journey/gtg/transition_table_test.rb
+++ b/actionpack/test/journey/gtg/transition_table_test.rb
@@ -92,10 +92,10 @@ module ActionDispatch
private
def asts(paths)
- parser = Journey::Parser.new
+ parser = Journey::Parser.new
paths.map { |x|
ast = parser.parse x
- ast.each { |n| n.memo = ast}
+ ast.each { |n| n.memo = ast }
ast
}
end
diff --git a/actionpack/test/journey/nfa/simulator_test.rb b/actionpack/test/journey/nfa/simulator_test.rb
index 918b971fec..38f99398cb 100644
--- a/actionpack/test/journey/nfa/simulator_test.rb
+++ b/actionpack/test/journey/nfa/simulator_test.rb
@@ -49,7 +49,7 @@ module ActionDispatch
parser = Journey::Parser.new
asts = paths.map { |x|
ast = parser.parse x
- ast.each { |n| n.memo = ast}
+ ast.each { |n| n.memo = ast }
ast
}
@@ -80,7 +80,7 @@ module ActionDispatch
ast = Nodes::Or.new routes
- nfa = Journey::NFA::Builder.new ast
+ nfa = Journey::NFA::Builder.new ast
sim = Simulator.new nfa.transition_table
md = sim.match "/articles"
assert_equal [asts.first], md.memos
diff --git a/actionpack/test/journey/nfa/transition_table_test.rb b/actionpack/test/journey/nfa/transition_table_test.rb
index f3cf36a064..0bc6bc1cf8 100644
--- a/actionpack/test/journey/nfa/transition_table_test.rb
+++ b/actionpack/test/journey/nfa/transition_table_test.rb
@@ -53,10 +53,10 @@ module ActionDispatch
end
def test_alphabet
- table = tt "a|:a"
+ table = tt "a|:a"
assert_equal [/[^\.\/\?]+/, "a"], table.alphabet
- table = tt "a|a"
+ table = tt "a|a"
assert_equal ["a"], table.alphabet
end
diff --git a/actionpack/test/journey/path/pattern_test.rb b/actionpack/test/journey/path/pattern_test.rb
index 224ab689fc..d61a8c023a 100644
--- a/actionpack/test/journey/path/pattern_test.rb
+++ b/actionpack/test/journey/path/pattern_test.rb
@@ -194,7 +194,7 @@ module ActionDispatch
end
def test_to_regexp_with_strexp
- path = Pattern.build("/:controller", { }, SEPARATORS, true)
+ path = Pattern.build("/:controller", {}, SEPARATORS, true)
x = %r{\A/([^/.?]+)\Z}
assert_equal(x.source, path.source)
diff --git a/actionpack/test/journey/route_test.rb b/actionpack/test/journey/route_test.rb
index 886cf857e8..8fd73970b8 100644
--- a/actionpack/test/journey/route_test.rb
+++ b/actionpack/test/journey/route_test.rb
@@ -26,7 +26,7 @@ module ActionDispatch
end
def test_path_requirements_override_defaults
- path = Path::Pattern.build(":name", { name: /love/ }, "/", true)
+ path = Path::Pattern.build(":name", { name: /love/ }, "/", true)
defaults = { name: "tender" }
route = Route.build("name", nil, path, {}, [], defaults)
assert_equal(/love/, route.requirements[:name])
@@ -34,7 +34,7 @@ module ActionDispatch
def test_ip_address
path = Path::Pattern.from_string "/messages/:id(.:format)"
- route = Route.build("name", nil, path, {ip: "192.168.1.1"}, [],
+ route = Route.build("name", nil, path, { ip: "192.168.1.1" }, [],
controller: "foo", action: "bar")
assert_equal "192.168.1.1", route.ip
end
@@ -57,7 +57,7 @@ module ActionDispatch
def test_connects_all_match
path = Path::Pattern.from_string "/:controller(/:action(/:id(.:format)))"
- route = Route.build("name", nil, path, {action: "bar"}, [], controller: "foo")
+ route = Route.build("name", nil, path, { action: "bar" }, [], controller: "foo")
assert_equal "/foo/bar/10", route.format(
controller: "foo",
@@ -67,28 +67,28 @@ module ActionDispatch
def test_extras_are_not_included_if_optional
path = Path::Pattern.from_string "/page/:id(/:action)"
- route = Route.build("name", nil, path, { }, [], action: "show")
+ route = Route.build("name", nil, path, {}, [], action: "show")
assert_equal "/page/10", route.format(id: 10)
end
def test_extras_are_not_included_if_optional_with_parameter
path = Path::Pattern.from_string "(/sections/:section)/pages/:id"
- route = Route.build("name", nil, path, { }, [], action: "show")
+ route = Route.build("name", nil, path, {}, [], action: "show")
assert_equal "/pages/10", route.format(id: 10)
end
def test_extras_are_not_included_if_optional_parameter_is_nil
path = Path::Pattern.from_string "(/sections/:section)/pages/:id"
- route = Route.build("name", nil, path, { }, [], action: "show")
+ route = Route.build("name", nil, path, {}, [], action: "show")
assert_equal "/pages/10", route.format(id: 10, section: nil)
end
def test_score
constraints = {}
- defaults = {controller: "pages", action: "show"}
+ defaults = { controller: "pages", action: "show" }
path = Path::Pattern.from_string "/page/:id(/:action)(.:format)"
specific = Route.build "name", nil, path, constraints, [:controller, :action], defaults
@@ -96,7 +96,7 @@ module ActionDispatch
path = Path::Pattern.from_string "/:controller(/:action(/:id))(.:format)"
generic = Route.build "name", nil, path, constraints, [], {}
- knowledge = {id: 20, controller: "pages", action: "show"}
+ knowledge = { "id" => true, "controller" => true, "action" => true }
routes = [specific, generic]
diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb
index c75475bffb..f223a125a3 100644
--- a/actionpack/test/journey/router_test.rb
+++ b/actionpack/test/journey/router_test.rb
@@ -6,8 +6,8 @@ module ActionDispatch
attr_reader :mapper, :routes, :route_set, :router
def setup
- @app = Routing::RouteSet::Dispatcher.new({})
- @route_set = ActionDispatch::Routing::RouteSet.new
+ @app = Routing::RouteSet::Dispatcher.new({})
+ @route_set = ActionDispatch::Routing::RouteSet.new
@routes = @route_set.router.routes
@router = @route_set.router
@formatter = @route_set.formatter
@@ -58,31 +58,31 @@ module ActionDispatch
get "/foo/:id", id: /\d/, anchor: false, to: "foo#bar"
assert_raises(ActionController::UrlGenerationError) do
- @formatter.generate(nil, { controller: "foo", action: "bar", id: "10" }, { })
+ @formatter.generate(nil, { controller: "foo", action: "bar", id: "10" }, {})
end
end
def test_required_parts_are_verified_when_building
get "/foo/:id", id: /\d+/, anchor: false, to: "foo#bar"
- path, _ = @formatter.generate(nil, { controller: "foo", action: "bar", id: "10" }, { })
+ path, _ = @formatter.generate(nil, { controller: "foo", action: "bar", id: "10" }, {})
assert_equal "/foo/10", path
assert_raises(ActionController::UrlGenerationError) do
- @formatter.generate(nil, { id: "aa" }, { })
+ @formatter.generate(nil, { id: "aa" }, {})
end
end
def test_only_required_parts_are_verified
get "/foo(/:id)", id: /\d/, to: "foo#bar"
- path, _ = @formatter.generate(nil, { controller: "foo", action: "bar", id: "10" }, { })
+ path, _ = @formatter.generate(nil, { controller: "foo", action: "bar", id: "10" }, {})
assert_equal "/foo/10", path
- path, _ = @formatter.generate(nil, { controller: "foo", action: "bar" }, { })
+ path, _ = @formatter.generate(nil, { controller: "foo", action: "bar" }, {})
assert_equal "/foo", path
- path, _ = @formatter.generate(nil, { controller: "foo", action: "bar", id: "aa" }, { })
+ path, _ = @formatter.generate(nil, { controller: "foo", action: "bar", id: "aa" }, {})
assert_equal "/foo/aa", path
end
@@ -91,7 +91,7 @@ module ActionDispatch
get "/foo/:id", as: route_name, id: /\d+/, to: "foo#bar"
error = assert_raises(ActionController::UrlGenerationError) do
- @formatter.generate(route_name, { }, { })
+ @formatter.generate(route_name, {}, {})
end
assert_match(/missing required keys: \[:id\]/, error.message)
@@ -101,7 +101,7 @@ module ActionDispatch
route_name = "gorby_thunderhorse"
error = assert_raises(ActionController::UrlGenerationError) do
- @formatter.generate(route_name, { }, { })
+ @formatter.generate(route_name, {}, {})
end
assert_no_match(/missing required keys: \[\]/, error.message)
@@ -116,7 +116,7 @@ module ActionDispatch
end
def test_clear_trailing_slash_from_script_name_on_root_unanchored_routes
- app = lambda { |env| [200, {}, ["success!"]] }
+ app = lambda { |env| [200, {}, ["success!"]] }
get "/weblog", to: app
env = rack_env("SCRIPT_NAME" => "", "PATH_INFO" => "/weblog")
@@ -130,12 +130,12 @@ module ActionDispatch
env = rails_env "PATH_INFO" => "/foo/10"
router.recognize(env) do |r, params|
- assert_equal({id: "10", controller: "foo", action: "bar"}, params)
+ assert_equal({ id: "10", controller: "foo", action: "bar" }, params)
end
env = rails_env "PATH_INFO" => "/foo"
router.recognize(env) do |r, params|
- assert_equal({id: nil, controller: "foo", action: "bar"}, params)
+ assert_equal({ id: nil, controller: "foo", action: "bar" }, params)
end
end
@@ -233,7 +233,7 @@ module ActionDispatch
nil,
Hash[params],
{},
- lambda { |k,v| parameterized << [k,v]; v })
+ lambda { |k, v| parameterized << [k, v]; v })
assert_equal params.map(&:to_s).sort, parameterized.map(&:to_s).sort
end
@@ -242,9 +242,9 @@ module ActionDispatch
get "/:controller(/:action)", to: "foo#bar"
path, params = @formatter.generate(
- nil, {id: 1, controller: "tasks", action: "show"}, {})
+ nil, { id: 1, controller: "tasks", action: "show" }, {})
assert_equal "/tasks/show", path
- assert_equal({id: 1}, params)
+ assert_equal({ id: 1 }, params)
end
def test_generate_escapes
@@ -277,7 +277,7 @@ module ActionDispatch
relative_url_root: nil
}, {})
assert_equal "/tasks/show", path
- assert_equal({id: 1, relative_url_root: nil}, params)
+ assert_equal({ id: 1, relative_url_root: nil }, params)
end
def test_generate_missing_keys_no_matches_different_format_keys
@@ -289,15 +289,15 @@ module ActionDispatch
relative_url_root: nil
}
redirection_parameters = {
- "action"=>"show",
+ "action" => "show",
}
missing_key = "name"
- missing_parameters ={
+ missing_parameters = {
missing_key => "task_1"
}
request_parameters = primarty_parameters.merge(redirection_parameters).merge(missing_parameters)
- message = "No route matches #{Hash[request_parameters.sort_by{|k,v|k.to_s}].inspect} missing required keys: #{[missing_key.to_sym].inspect}"
+ message = "No route matches #{Hash[request_parameters.sort_by { |k, v|k.to_s }].inspect}, missing required keys: #{[missing_key.to_sym].inspect}"
error = assert_raises(ActionController::UrlGenerationError) do
@formatter.generate(
@@ -311,7 +311,7 @@ module ActionDispatch
path, params = @formatter.generate(
nil,
- {controller: "tasks", id: 10},
+ { controller: "tasks", id: 10 },
action: "index")
assert_equal "/tasks/index/10", path
assert_equal({}, params)
@@ -322,7 +322,7 @@ module ActionDispatch
path, params = @formatter.generate(
"tasks",
- {controller: "tasks"},
+ { controller: "tasks" },
controller: "tasks", action: "index")
assert_equal "/tasks", path
assert_equal({}, params)
@@ -338,7 +338,7 @@ module ActionDispatch
route = @routes.first
env = rails_env "PATH_INFO" => request_path
- called = false
+ called = false
router.recognize(env) do |r, params|
assert_equal route, r
@@ -358,7 +358,7 @@ module ActionDispatch
get "/:segment/*splat", to: "foo#bar"
env = rails_env "PATH_INFO" => request_path
- called = false
+ called = false
route = @routes.first
router.recognize(env) do |r, params|
@@ -395,7 +395,7 @@ module ActionDispatch
get "/books(/:action(.:format))", controller: "books"
route = @routes.first
- env = rails_env "PATH_INFO" => "/books/list.rss"
+ env = rails_env "PATH_INFO" => "/books/list.rss"
expected = { controller: "books", action: "list", format: "rss" }
called = false
router.recognize(env) do |r, params|
@@ -427,7 +427,7 @@ module ActionDispatch
get "/books(/:action(.:format))", to: "foo#bar"
env = rails_env "PATH_INFO" => "/books/list.rss",
- "REQUEST_METHOD" => "HEAD"
+ "REQUEST_METHOD" => "HEAD"
called = false
router.recognize(env) do |r, params|
diff --git a/actionpack/test/journey/routes_test.rb b/actionpack/test/journey/routes_test.rb
index ca735ea022..d8db5ffad1 100644
--- a/actionpack/test/journey/routes_test.rb
+++ b/actionpack/test/journey/routes_test.rb
@@ -6,7 +6,7 @@ module ActionDispatch
attr_reader :routes, :mapper
def setup
- @route_set = ActionDispatch::Routing::RouteSet.new
+ @route_set = ActionDispatch::Routing::RouteSet.new
@routes = @route_set.router.routes
@router = @route_set.router
@mapper = ActionDispatch::Routing::Mapper.new @route_set
diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb
index ce9522d12a..046b4167bb 100644
--- a/actionpack/test/lib/controller/fake_models.rb
+++ b/actionpack/test/lib/controller/fake_models.rb
@@ -6,7 +6,7 @@ class Customer < Struct.new(:name, :id)
undef_method :to_json
- def to_xml(options={})
+ def to_xml(options = {})
if options[:builder]
options[:builder].name name
else
@@ -14,7 +14,7 @@ class Customer < Struct.new(:name, :id)
end
end
- def to_js(options={})
+ def to_js(options = {})
"name: #{name.inspect}"
end
alias :to_text :to_js
diff --git a/actionview/.gitignore b/actionview/.gitignore
new file mode 100644
index 0000000000..0a04b29786
--- /dev/null
+++ b/actionview/.gitignore
@@ -0,0 +1,2 @@
+/lib/assets/compiled
+/tmp
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index 8bd4e1e56c..2a38ca7b63 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,69 @@
+* Return correct object name in form helper method after `fields_for`.
+
+ Fixes #26931.
+
+ *Yuji Yaginuma*
+
+* Use `ActionView::Resolver.caching?` (`config.action_view.cache_template_loading`)
+ to enable template recompilation.
+
+ Before it was enabled by `consider_all_requests_local`, which caused
+ recompilation in tests.
+
+ *Max Melentiev*
+
+* Add `form_with` to unify `form_tag` and `form_for` usage.
+
+ Used like `form_tag` (where just the open tag is output):
+
+ ```erb
+ <%= form_with scope: :post, url: super_special_posts_path %>
+ ```
+
+ Used like `form_for`:
+
+ ```erb
+ <%= form_with model: @post do |form| %>
+ <%= form.text_field :title %>
+ <% end %>
+ ```
+
+ *Kasper Timm Hansen*, *Marek Kirejczyk*
+
+* Add `fields` form helper method.
+
+ ```erb
+ <%= fields :comment, model: @comment do |fields| %>
+ <%= fields.text_field :title %>
+ <% end %>
+ ```
+
+ Can also be used within form helpers such as `form_with`.
+
+ *Kasper Timm Hansen*
+
+* Removed deprecated `#original_exception` in `ActionView::Template::Error`.
+
+ *Rafael Mendonça França*
+
+* Render now accepts any keys for locals, including reserved keywords.
+
+ Only locals with valid variable names get set directly. Others
+ will still be available in `local_assigns`.
+
+ Example of render with reserved keywords:
+
+ ```erb
+ <%= render "example", class: "text-center", message: "Hello world!" %>
+
+ <!-- _example.html.erb: -->
+ <%= tag.div class: local_assigns[:class] do %>
+ <p><%= message %></p>
+ <% end %>
+ ```
+
+ *Peter Schilling*, *Matthew Draper*
+
* Show cache hits and misses when rendering partials.
Partials using the `cache` helper will show whether a render hit or missed
diff --git a/actionview/MIT-LICENSE b/actionview/MIT-LICENSE
index 8573eb1225..ac810e86d0 100644
--- a/actionview/MIT-LICENSE
+++ b/actionview/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2016 David Heinemeier Hansson
+Copyright (c) 2004-2017 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionview/RUNNING_UJS_TESTS.rdoc b/actionview/RUNNING_UJS_TESTS.rdoc
new file mode 100644
index 0000000000..a575624a06
--- /dev/null
+++ b/actionview/RUNNING_UJS_TESTS.rdoc
@@ -0,0 +1,7 @@
+== Running UJS tests
+
+Ensure that you can build the project and run tests.
+Run rake ujs:server first, and then run the web tests by
+visiting http://localhost:4567 in your browser.
+
+rake ujs:server
diff --git a/actionview/Rakefile b/actionview/Rakefile
index f9a7112ade..cba4684076 100644
--- a/actionview/Rakefile
+++ b/actionview/Rakefile
@@ -3,7 +3,7 @@ require "rake/testtask"
desc "Default Task"
task default: :test
-task :package
+task package: "assets:compile"
# Run the unit tests
@@ -14,7 +14,7 @@ namespace :test do
task :isolated do
Dir.glob("test/{actionpack,activerecord,template}/**/*_test.rb").all? do |file|
sh(Gem.ruby, "-w", "-Ilib:test", file)
- end or raise "Failures"
+ end || raise("Failures")
end
Rake::TestTask.new(:template) do |t|
@@ -46,6 +46,21 @@ namespace :test do
end
end
+namespace :ujs do
+ desc "Starts the test server"
+ task :server do
+ system "bundle exec rackup test/ujs/config.ru -p 4567 -s puma"
+ end
+end
+
+namespace :assets do
+ desc "Compile Action View assets"
+ task :compile do
+ require "blade"
+ Blade.build
+ end
+end
+
task :lines do
load File.expand_path("..", File.dirname(__FILE__)) + "/tools/line_statistics"
files = FileList["lib/**/*.rb"]
diff --git a/actionview/app/assets/javascripts/config.coffee b/actionview/app/assets/javascripts/config.coffee
new file mode 100644
index 0000000000..3d4706b0e1
--- /dev/null
+++ b/actionview/app/assets/javascripts/config.coffee
@@ -0,0 +1,37 @@
+#= export Rails
+
+@Rails =
+ # Link elements bound by jquery-ujs
+ linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]'
+
+ # Button elements bound by jquery-ujs
+ buttonClickSelector:
+ selector: 'button[data-remote]:not([form]), button[data-confirm]:not([form])'
+ exclude: 'form button'
+
+ # Select elements bound by jquery-ujs
+ inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]'
+
+ # Form elements bound by jquery-ujs
+ formSubmitSelector: 'form'
+
+ # Form input elements bound by jquery-ujs
+ formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type]), input[type=submit][form], input[type=image][form], button[type=submit][form], button[form]:not([type])'
+
+ # Form input elements disabled during form submission
+ formDisableSelector: 'input[data-disable-with]:enabled, button[data-disable-with]:enabled, textarea[data-disable-with]:enabled, input[data-disable]:enabled, button[data-disable]:enabled, textarea[data-disable]:enabled'
+
+ # Form input elements re-enabled after form submission
+ formEnableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled, input[data-disable]:disabled, button[data-disable]:disabled, textarea[data-disable]:disabled'
+
+ # Form required input elements
+ requiredInputSelector: 'input[name][required]:not([disabled]), textarea[name][required]:not([disabled])'
+
+ # Form file input elements
+ fileInputSelector: 'input[name][type=file]:not([disabled])'
+
+ # Link onClick disable selector with possible reenable after remote submission
+ linkDisableSelector: 'a[data-disable-with], a[data-disable]'
+
+ # Button onClick disable selector with possible reenable after remote submission
+ buttonDisableSelector: 'button[data-remote][data-disable-with], button[data-remote][data-disable]'
diff --git a/actionview/app/assets/javascripts/features/confirm.coffee b/actionview/app/assets/javascripts/features/confirm.coffee
new file mode 100644
index 0000000000..72b5aaa218
--- /dev/null
+++ b/actionview/app/assets/javascripts/features/confirm.coffee
@@ -0,0 +1,26 @@
+#= require_tree ../utils
+
+{ fire, stopEverything } = Rails
+
+Rails.handleConfirm = (e) ->
+ stopEverything(e) unless allowAction(this)
+
+# For 'data-confirm' attribute:
+# - Fires `confirm` event
+# - Shows the confirmation dialog
+# - Fires the `confirm:complete` event
+#
+# Returns `true` if no function stops the chain and user chose yes `false` otherwise.
+# Attaching a handler to the element's `confirm` event that returns a `falsy` value cancels the confirmation dialog.
+# Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes this function
+# return false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog.
+allowAction = (element) ->
+ message = element.getAttribute('data-confirm')
+ return true unless message
+
+ answer = false
+ if fire(element, 'confirm')
+ try answer = confirm(message)
+ callback = fire(element, 'confirm:complete', [answer])
+
+ answer and callback
diff --git a/actionview/app/assets/javascripts/features/disable.coffee b/actionview/app/assets/javascripts/features/disable.coffee
new file mode 100644
index 0000000000..e8cce7da40
--- /dev/null
+++ b/actionview/app/assets/javascripts/features/disable.coffee
@@ -0,0 +1,78 @@
+#= require_tree ../utils
+
+{ matches, getData, setData, stopEverything, formElements } = Rails
+
+# Unified function to enable an element (link, button and form)
+Rails.enableElement = (e) ->
+ element = if e instanceof Event then e.target else e
+ if matches(element, Rails.linkDisableSelector)
+ enableLinkElement(element)
+ else if matches(element, Rails.buttonDisableSelector) or matches(element, Rails.formEnableSelector)
+ enableFormElement(element)
+ else if matches(element, Rails.formSubmitSelector)
+ enableFormElements(element)
+
+# Unified function to disable an element (link, button and form)
+Rails.disableElement = (e) ->
+ element = if e instanceof Event then e.target else e
+ if matches(element, Rails.linkDisableSelector)
+ disableLinkElement(element)
+ else if matches(element, Rails.buttonDisableSelector) or matches(element, Rails.formDisableSelector)
+ disableFormElement(element)
+ else if matches(element, Rails.formSubmitSelector)
+ disableFormElements(element)
+
+# Replace element's html with the 'data-disable-with' after storing original html
+# and prevent clicking on it
+disableLinkElement = (element) ->
+ replacement = element.getAttribute('data-disable-with')
+ if replacement?
+ setData(element, 'ujs:enable-with', element.innerHTML) # store enabled state
+ element.innerHTML = replacement
+ element.addEventListener('click', stopEverything) # prevent further clicking
+ setData(element, 'ujs:disabled', true)
+
+# Restore element to its original state which was disabled by 'disableLinkElement' above
+enableLinkElement = (element) ->
+ originalText = getData(element, 'ujs:enable-with')
+ if originalText?
+ element.innerHTML = originalText # set to old enabled state
+ setData(element, 'ujs:enable-with', null) # clean up cache
+ element.removeEventListener('click', stopEverything) # enable element
+ setData(element, 'ujs:disabled', null)
+
+# Disables form elements:
+# - Caches element value in 'ujs:enable-with' data store
+# - Replaces element text with value of 'data-disable-with' attribute
+# - Sets disabled property to true
+disableFormElements = (form) ->
+ formElements(form, Rails.formDisableSelector).forEach(disableFormElement)
+
+disableFormElement = (element) ->
+ replacement = element.getAttribute('data-disable-with')
+ if replacement?
+ if matches(element, 'button')
+ setData(element, 'ujs:enable-with', element.innerHTML)
+ element.innerHTML = replacement
+ else
+ setData(element, 'ujs:enable-with', element.value)
+ element.value = replacement
+ element.disabled = true
+ setData(element, 'ujs:disabled', true)
+
+# Re-enables disabled form elements:
+# - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`)
+# - Sets disabled property to false
+enableFormElements = (form) ->
+ formElements(form, Rails.formEnableSelector).forEach(enableFormElement)
+
+enableFormElement = (element) ->
+ originalText = getData(element, 'ujs:enable-with')
+ if originalText?
+ if matches(element, 'button')
+ element.innerHTML = originalText
+ else
+ element.value = originalText
+ setData(element, 'ujs:enable-with', null) # clean up cache
+ element.disabled = false
+ setData(element, 'ujs:disabled', null)
diff --git a/actionview/app/assets/javascripts/features/method.coffee b/actionview/app/assets/javascripts/features/method.coffee
new file mode 100644
index 0000000000..d04d9414dd
--- /dev/null
+++ b/actionview/app/assets/javascripts/features/method.coffee
@@ -0,0 +1,34 @@
+#= require_tree ../utils
+
+{ stopEverything } = Rails
+
+# Handles "data-method" on links such as:
+# <a href="/users/5" data-method="delete" rel="nofollow" data-confirm="Are you sure?">Delete</a>
+Rails.handleMethod = (e) ->
+ link = this
+ method = link.getAttribute('data-method')
+ return unless method
+
+ href = Rails.href(link)
+ csrfToken = Rails.csrfToken()
+ csrfParam = Rails.csrfParam()
+ form = document.createElement('form')
+ formContent = "<input name='_method' value='#{method}' type='hidden' />"
+
+ if csrfParam? and csrfToken? and not Rails.isCrossDomain(href)
+ formContent += "<input name='#{csrfParam}' value='#{csrfToken}' type='hidden' />"
+
+ # Must trigger submit by click on a button, else "submit" event handler won't work!
+ # https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit
+ formContent += '<input type="submit" />'
+
+ form.method = 'post'
+ form.action = href
+ form.target = link.target
+ form.innerHTML = formContent
+ form.style.display = 'none'
+
+ document.body.appendChild(form)
+ form.querySelector('[type="submit"]').click()
+
+ stopEverything(e)
diff --git a/actionview/app/assets/javascripts/features/remote.coffee b/actionview/app/assets/javascripts/features/remote.coffee
new file mode 100644
index 0000000000..30a5dc21fa
--- /dev/null
+++ b/actionview/app/assets/javascripts/features/remote.coffee
@@ -0,0 +1,100 @@
+#= require_tree ../utils
+
+{
+ matches, getData, setData
+ fire, stopEverything
+ ajax, isCrossDomain
+ blankInputs, serializeElement
+} = Rails
+
+# Checks "data-remote" if true to handle the request through a XHR request.
+isRemote = (element) ->
+ value = element.getAttribute('data-remote')
+ value? and value isnt 'false'
+
+# Submits "remote" forms and links with ajax
+Rails.handleRemote = (e) ->
+ element = this
+
+ return true unless isRemote(element)
+ unless fire(element, 'ajax:before')
+ fire(element, 'ajax:stopped')
+ return false
+
+ withCredentials = element.getAttribute('data-with-credentials')
+ dataType = element.getAttribute('data-type') or 'script'
+
+ if matches(element, Rails.formSubmitSelector)
+ # memoized value from clicked submit button
+ button = getData(element, 'ujs:submit-button')
+ method = getData(element, 'ujs:submit-button-formmethod') or element.method
+ url = getData(element, 'ujs:submit-button-formaction') or element.getAttribute('action') or location.href
+
+ # strip query string if it's a GET request
+ url = url.replace(/\?.*$/, '') if method.toUpperCase() is 'GET'
+
+ if element.enctype is 'multipart/form-data'
+ data = new FormData(element)
+ data.append(button.name, button.value) if button?
+ else
+ data = serializeElement(element, button)
+
+ setData(element, 'ujs:submit-button', null)
+ setData(element, 'ujs:submit-button-formmethod', null)
+ setData(element, 'ujs:submit-button-formaction', null)
+ else if matches(element, Rails.buttonClickSelector) or matches(element, Rails.inputChangeSelector)
+ method = element.getAttribute('data-method')
+ url = element.getAttribute('data-url')
+ data = serializeElement(element, element.getAttribute('data-params'))
+ else
+ method = element.getAttribute('data-method')
+ url = Rails.href(element)
+ data = element.getAttribute('data-params')
+
+ ajax(
+ type: method or 'GET'
+ url: url
+ data: data
+ dataType: dataType
+ # stopping the "ajax:beforeSend" event will cancel the ajax request
+ beforeSend: (xhr, options) ->
+ if fire(element, 'ajax:beforeSend', [xhr, options])
+ fire(element, 'ajax:send', [xhr])
+ else
+ fire(element, 'ajax:stopped')
+ xhr.abort()
+ success: (args...) -> fire(element, 'ajax:success', args)
+ error: (args...) -> fire(element, 'ajax:error', args)
+ complete: (args...) -> fire(element, 'ajax:complete', args)
+ crossDomain: isCrossDomain(url)
+ withCredentials: withCredentials? and withCredentials isnt 'false'
+ )
+ stopEverything(e)
+
+# Check whether any required fields are empty
+# In both ajax mode and normal mode
+Rails.validateForm = (e) ->
+ form = this
+ return if form.noValidate or getData(form, 'ujs:formnovalidate-button')
+ # Skip other logic when required values are missing or file upload is present
+ blankRequiredInputs = blankInputs(form, Rails.requiredInputSelector, false)
+ if blankRequiredInputs.length > 0 and fire(form, 'ajax:aborted:required', [blankRequiredInputs])
+ stopEverything(e)
+
+Rails.formSubmitButtonClick = (e) ->
+ button = this
+ form = button.form
+ return unless form
+ # Register the pressed submit button
+ setData(form, 'ujs:submit-button', name: button.name, value: button.value) if button.name
+ # Save attributes from button
+ setData(form, 'ujs:formnovalidate-button', button.formNoValidate)
+ setData(form, 'ujs:submit-button-formaction', button.getAttribute('formaction'))
+ setData(form, 'ujs:submit-button-formmethod', button.getAttribute('formmethod'))
+
+Rails.handleMetaClick = (e) ->
+ link = this
+ method = (link.getAttribute('data-method') or 'GET').toUpperCase()
+ data = link.getAttribute('data-params')
+ metaClick = e.metaKey or e.ctrlKey
+ e.stopImmediatePropagation() if metaClick and method is 'GET' and not data
diff --git a/actionview/app/assets/javascripts/rails-ujs.coffee b/actionview/app/assets/javascripts/rails-ujs.coffee
new file mode 100644
index 0000000000..f96d2eb6fd
--- /dev/null
+++ b/actionview/app/assets/javascripts/rails-ujs.coffee
@@ -0,0 +1,76 @@
+#
+# Unobtrusive JavaScript
+# https://github.com/rails/rails-ujs
+#
+# Released under the MIT license
+#
+#= require ./config
+#= require_tree ./utils
+#= require_tree ./features
+
+{
+ fire, delegate
+ getData, $
+ refreshCSRFTokens, CSRFProtection
+ enableElement, disableElement
+ handleConfirm
+ handleRemote, validateForm, formSubmitButtonClick, handleMetaClick
+ handleMethod
+} = Rails
+
+# For backward compatibility
+if jQuery? and not jQuery.rails
+ jQuery.rails = Rails
+ jQuery.ajaxPrefilter (options, originalOptions, xhr) ->
+ CSRFProtection(xhr) unless options.crossDomain
+
+Rails.start = ->
+ # Cut down on the number of issues from people inadvertently including jquery_ujs twice
+ # by detecting and raising an error when it happens.
+ throw new Error('jquery-ujs has already been loaded!') if window._rails_loaded
+
+ # This event works the same as the load event, except that it fires every
+ # time the page is loaded.
+ # See https://github.com/rails/jquery-ujs/issues/357
+ # See https://developer.mozilla.org/en-US/docs/Using_Firefox_1.5_caching
+ window.addEventListener 'pageshow', ->
+ $(Rails.formEnableSelector).forEach (el) ->
+ enableElement(el) if getData(el, 'ujs:disabled')
+ $(Rails.linkDisableSelector).forEach (el) ->
+ enableElement(el) if getData(el, 'ujs:disabled')
+
+ delegate document, Rails.linkDisableSelector, 'ajax:complete', enableElement
+ delegate document, Rails.linkDisableSelector, 'ajax:stopped', enableElement
+ delegate document, Rails.buttonDisableSelector, 'ajax:complete', enableElement
+ delegate document, Rails.buttonDisableSelector, 'ajax:stopped', enableElement
+
+ delegate document, Rails.linkClickSelector, 'click', handleConfirm
+ delegate document, Rails.linkClickSelector, 'click', handleMetaClick
+ delegate document, Rails.linkClickSelector, 'click', disableElement
+ delegate document, Rails.linkClickSelector, 'click', handleRemote
+ delegate document, Rails.linkClickSelector, 'click', handleMethod
+
+ delegate document, Rails.buttonClickSelector, 'click', handleConfirm
+ delegate document, Rails.buttonClickSelector, 'click', disableElement
+ delegate document, Rails.buttonClickSelector, 'click', handleRemote
+
+ delegate document, Rails.inputChangeSelector, 'change', handleConfirm
+ delegate document, Rails.inputChangeSelector, 'change', handleRemote
+
+ delegate document, Rails.formSubmitSelector, 'submit', handleConfirm
+ delegate document, Rails.formSubmitSelector, 'submit', validateForm
+ delegate document, Rails.formSubmitSelector, 'submit', handleRemote
+ # Normal mode submit
+ # Slight timeout so that the submit button gets properly serialized
+ delegate document, Rails.formSubmitSelector, 'submit', (e) -> setTimeout((-> disableElement(e)), 13)
+ delegate document, Rails.formSubmitSelector, 'ajax:send', disableElement
+ delegate document, Rails.formSubmitSelector, 'ajax:complete', enableElement
+
+ delegate document, Rails.formInputClickSelector, 'click', handleConfirm
+ delegate document, Rails.formInputClickSelector, 'click', formSubmitButtonClick
+
+ document.addEventListener('DOMContentLoaded', refreshCSRFTokens)
+ window._rails_loaded = true
+
+if window.Rails is Rails and fire(document, 'rails:attachBindings')
+ Rails.start()
diff --git a/actionview/app/assets/javascripts/utils/ajax.coffee b/actionview/app/assets/javascripts/utils/ajax.coffee
new file mode 100644
index 0000000000..9af515beda
--- /dev/null
+++ b/actionview/app/assets/javascripts/utils/ajax.coffee
@@ -0,0 +1,95 @@
+#= require ./csrf
+#= require ./event
+
+{ CSRFProtection, fire } = Rails
+
+AcceptHeaders =
+ '*': '*/*'
+ text: 'text/plain'
+ html: 'text/html'
+ xml: 'application/xml, text/xml'
+ json: 'application/json, text/javascript'
+ script: 'text/javascript, application/javascript, application/ecmascript, application/x-ecmascript'
+
+Rails.ajax = (options) ->
+ options = prepareOptions(options)
+ xhr = createXHR options, ->
+ response = processResponse(xhr.response, xhr.getResponseHeader('Content-Type'))
+ if xhr.status // 100 == 2
+ options.success?(response, xhr.statusText, xhr)
+ else
+ options.error?(response, xhr.statusText, xhr)
+ options.complete?(xhr, xhr.statusText)
+ # Call beforeSend hook
+ options.beforeSend?(xhr, options)
+ # Send the request
+ if xhr.readyState is XMLHttpRequest.OPENED
+ xhr.send(options.data)
+ else
+ fire(document, 'ajaxStop') # to be compatible with jQuery.ajax
+
+prepareOptions = (options) ->
+ options.type = options.type.toUpperCase()
+ # append data to url if it's a GET request
+ if options.type is 'GET' and options.data
+ if options.url.indexOf('?') < 0
+ options.url += '?' + options.data
+ else
+ options.url += '&' + options.data
+ # Use "*" as default dataType
+ options.dataType = '*' unless AcceptHeaders[options.dataType]?
+ options.accept = AcceptHeaders[options.dataType]
+ options.accept += ', */*; q=0.01' if options.dataType isnt '*'
+ options
+
+createXHR = (options, done) ->
+ xhr = new XMLHttpRequest()
+ # Open and setup xhr
+ xhr.open(options.type, options.url, true)
+ xhr.setRequestHeader('Accept', options.accept)
+ # Set Content-Type only when sending a string
+ # Sending FormData will automatically set Content-Type to multipart/form-data
+ if typeof options.data is 'string'
+ xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8')
+ xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest') unless options.crossDomain
+ # Add X-CSRF-Token
+ CSRFProtection(xhr)
+ xhr.withCredentials = !!options.withCredentials
+ xhr.onreadystatechange = ->
+ done(xhr) if xhr.readyState is XMLHttpRequest.DONE
+ xhr
+
+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/)
+ script = document.createElement('script')
+ script.innerHTML = response
+ document.body.appendChild(script)
+ else if type.match(/\b(xml|html|svg)\b/)
+ parser = new DOMParser()
+ type = type.replace(/;.+/, '') # remove something like ';charset=utf-8'
+ try response = parser.parseFromString(response, type)
+ response
+
+# Default way to get an element's href. May be overridden at Rails.href.
+Rails.href = (element) -> element.href
+
+# Determines if the request is a cross domain request.
+Rails.isCrossDomain = (url) ->
+ originAnchor = document.createElement('a')
+ originAnchor.href = location.href
+ urlAnchor = document.createElement('a')
+ try
+ urlAnchor.href = url
+ # If URL protocol is false or is a string containing a single colon
+ # *and* host are false, assume it is not a cross-domain request
+ # (should only be the case for IE7 and IE compatibility mode).
+ # Otherwise, evaluate protocol and host of the URL against the origin
+ # protocol and host.
+ !(((!urlAnchor.protocol || urlAnchor.protocol == ':') && !urlAnchor.host) ||
+ (originAnchor.protocol + '//' + originAnchor.host == urlAnchor.protocol + '//' + urlAnchor.host))
+ catch e
+ # If there is an error parsing the URL, assume it is crossDomain.
+ true
diff --git a/actionview/app/assets/javascripts/utils/csrf.coffee b/actionview/app/assets/javascripts/utils/csrf.coffee
new file mode 100644
index 0000000000..4eb5ebb414
--- /dev/null
+++ b/actionview/app/assets/javascripts/utils/csrf.coffee
@@ -0,0 +1,25 @@
+#= require ./dom
+
+{ $ } = Rails
+
+# Up-to-date Cross-Site Request Forgery token
+csrfToken = Rails.csrfToken = ->
+ meta = document.querySelector('meta[name=csrf-token]')
+ meta and meta.content
+
+# URL param that must contain the CSRF token
+csrfParam = Rails.csrfParam = ->
+ meta = document.querySelector('meta[name=csrf-param]')
+ meta and meta.content
+
+# Make sure that every Ajax request sends the CSRF token
+Rails.CSRFProtection = (xhr) ->
+ token = csrfToken()
+ xhr.setRequestHeader('X-CSRF-Token', token) if token?
+
+# Make sure that all forms have actual up-to-date tokens (cached forms contain old ones)
+Rails.refreshCSRFTokens = ->
+ token = csrfToken()
+ param = csrfParam()
+ if token? and param?
+ $('form input[name="' + param + '"]').forEach (input) -> input.value = token
diff --git a/actionview/app/assets/javascripts/utils/dom.coffee b/actionview/app/assets/javascripts/utils/dom.coffee
new file mode 100644
index 0000000000..6bef618147
--- /dev/null
+++ b/actionview/app/assets/javascripts/utils/dom.coffee
@@ -0,0 +1,28 @@
+m = Element.prototype.matches or
+ Element.prototype.matchesSelector or
+ Element.prototype.mozMatchesSelector or
+ Element.prototype.msMatchesSelector or
+ Element.prototype.oMatchesSelector or
+ Element.prototype.webkitMatchesSelector
+
+Rails.matches = (element, selector) ->
+ if selector.exclude?
+ m.call(element, selector.selector) and not m.call(element, selector.exclude)
+ else
+ m.call(element, selector)
+
+# get and set data on a given element using "expando properties"
+# See: https://developer.mozilla.org/en-US/docs/Glossary/Expando
+expando = '_ujsData'
+
+Rails.getData = (element, key) ->
+ element[expando]?[key]
+
+Rails.setData = (element, key, value) ->
+ element[expando] ?= {}
+ element[expando][key] = value
+
+# a wrapper for document.querySelectorAll
+# returns an Array
+Rails.$ = (selector) ->
+ Array.prototype.slice.call(document.querySelectorAll(selector))
diff --git a/actionview/app/assets/javascripts/utils/event.coffee b/actionview/app/assets/javascripts/utils/event.coffee
new file mode 100644
index 0000000000..049b2a3ecd
--- /dev/null
+++ b/actionview/app/assets/javascripts/utils/event.coffee
@@ -0,0 +1,40 @@
+#= require ./dom
+
+{ matches } = Rails
+
+# Polyfill for CustomEvent in IE9+
+# https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill
+CustomEvent = window.CustomEvent
+
+if typeof CustomEvent is 'function'
+ CustomEvent = (event, params) ->
+ evt = document.createEvent('CustomEvent')
+ evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail)
+ evt
+ CustomEvent.prototype = window.Event.prototype
+
+# Triggers an custom event on an element and returns false if the event result is false
+fire = Rails.fire = (obj, name, data) ->
+ event = new CustomEvent(
+ name,
+ bubbles: true,
+ cancelable: true,
+ detail: data,
+ )
+ obj.dispatchEvent(event)
+ !event.defaultPrevented
+
+# Helper function, needed to provide consistent behavior in IE
+Rails.stopEverything = (e) ->
+ fire(e.target, 'ujs:everythingStopped')
+ e.preventDefault()
+ e.stopPropagation()
+ e.stopImmediatePropagation()
+
+Rails.delegate = (element, selector, eventType, handler) ->
+ element.addEventListener eventType, (e) ->
+ target = e.target
+ target = target.parentNode until not (target instanceof Element) or matches(target, selector)
+ if target instanceof Element and handler.call(target, e) == false
+ e.preventDefault()
+ e.stopPropagation()
diff --git a/actionview/app/assets/javascripts/utils/form.coffee b/actionview/app/assets/javascripts/utils/form.coffee
new file mode 100644
index 0000000000..251113deda
--- /dev/null
+++ b/actionview/app/assets/javascripts/utils/form.coffee
@@ -0,0 +1,61 @@
+#= require ./dom
+
+{ matches } = Rails
+
+toArray = (e) -> Array.prototype.slice.call(e)
+
+Rails.serializeElement = (element, additionalParam) ->
+ inputs = [element]
+ inputs = toArray(element.elements) if matches(element, 'form')
+ params = []
+
+ inputs.forEach (input) ->
+ return unless input.name
+ if matches(input, 'select')
+ toArray(input.options).forEach (option) ->
+ params.push(name: input.name, value: option.value) if option.selected
+ else if input.type isnt 'radio' and input.type isnt 'checkbox' or input.checked
+ params.push(name: input.name, value: input.value)
+
+ params.push(additionalParam) if additionalParam
+
+ params.map (param) ->
+ if param.name?
+ "#{encodeURIComponent(param.name)}=#{encodeURIComponent(param.value)}"
+ else
+ param
+ .join('&')
+
+# Helper function that returns form elements that match the specified CSS selector
+# If form is actually a "form" element this will return associated elements outside the from that have
+# the html form attribute set
+Rails.formElements = (form, selector) ->
+ if matches(form, 'form')
+ toArray(form.elements).filter (el) -> matches(el, selector)
+ else
+ toArray(form.querySelectorAll(selector))
+
+# Helper function which checks for blank inputs in a form that match the specified CSS selector
+Rails.blankInputs = (form, selector, nonBlank) ->
+ foundInputs = []
+ requiredInputs = toArray(form.querySelectorAll(selector or 'input, textarea'))
+ checkedRadioButtonNames = {}
+
+ requiredInputs.forEach (input) ->
+ if input.type is 'radio'
+ # Don't count unchecked required radio as blank if other radio with same name is checked,
+ # regardless of whether same-name radio input has required attribute or not. The spec
+ # states https://www.w3.org/TR/html5/forms.html#the-required-attribute
+ radioName = input.name
+ # Skip if we've already seen the radio with this name.
+ unless checkedRadioButtonNames[radioName]
+ # If none checked
+ if form.querySelectorAll("input[type=radio][name='#{radioName}']:checked").length == 0
+ radios = form.querySelectorAll("input[type=radio][name='#{radioName}']")
+ foundInputs = foundInputs.concat(toArray(radios))
+ # We only need to check each name once.
+ checkedRadioButtonNames[radioName] = radioName
+ else
+ valueToCheck = if input.type is 'checkbox' then input.checked else !!input.value
+ foundInputs.push(input) if valueToCheck is nonBlank
+ foundInputs
diff --git a/actionview/bin/test b/actionview/bin/test
index 84a05bba08..a7beb14b27 100755
--- a/actionview/bin/test
+++ b/actionview/bin/test
@@ -2,5 +2,3 @@
COMPONENT_ROOT = File.expand_path("..", __dir__)
require File.expand_path("../tools/test", COMPONENT_ROOT)
-
-exit Minitest.run(ARGV)
diff --git a/actionview/blade.yml b/actionview/blade.yml
new file mode 100644
index 0000000000..9e5eb953a4
--- /dev/null
+++ b/actionview/blade.yml
@@ -0,0 +1,11 @@
+load_paths:
+ - app/assets/javascripts
+
+logical_paths:
+ - rails-ujs.js
+
+build:
+ logical_paths:
+ - rails-ujs.js
+ path: lib/assets/compiled
+ clean: true
diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb
index ba6755be82..ca586f1d52 100644
--- a/actionview/lib/action_view.rb
+++ b/actionview/lib/action_view.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2016 David Heinemeier Hansson
+# Copyright (c) 2004-2017 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb
index d9a9d3d8ce..b7c05fdb88 100644
--- a/actionview/lib/action_view/base.rb
+++ b/actionview/lib/action_view/base.rb
@@ -141,7 +141,7 @@ module ActionView #:nodoc:
# 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 }
+ @@field_error_proc = 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.
diff --git a/actionview/lib/action_view/context.rb b/actionview/lib/action_view/context.rb
index ee263df484..31aa73a0cf 100644
--- a/actionview/lib/action_view/context.rb
+++ b/actionview/lib/action_view/context.rb
@@ -28,7 +28,7 @@ module ActionView
# returns the correct buffer on +yield+. This is usually
# overwritten by helpers to add more behavior.
# :api: plugin
- def _layout_for(name=nil)
+ def _layout_for(name = nil)
name ||= :layout
view_flow.get(name).html_safe
end
diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb
index 2d6ad8f6d9..0658d8601d 100644
--- a/actionview/lib/action_view/digestor.rb
+++ b/actionview/lib/action_view/digestor.rb
@@ -6,6 +6,12 @@ module ActionView
class Digestor
@@digest_mutex = Mutex.new
+ module PerExecutionDigestCacheExpiry
+ def self.before(target)
+ ActionView::LookupContext::DetailsKey.clear
+ end
+ end
+
class << self
# Supported options:
#
diff --git a/actionview/lib/action_view/flows.rb b/actionview/lib/action_view/flows.rb
index 16874c1194..6d5f57a570 100644
--- a/actionview/lib/action_view/flows.rb
+++ b/actionview/lib/action_view/flows.rb
@@ -5,7 +5,7 @@ module ActionView
attr_reader :content
def initialize
- @content = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new }
+ @content = Hash.new { |h, k| h[k] = ActiveSupport::SafeBuffer.new }
end
# Called by _layout_for to read stored values.
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index 6cac4bc0ad..72a094c629 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -1,6 +1,5 @@
require "active_support/core_ext/array/extract_options"
require "active_support/core_ext/hash/keys"
-require "active_support/core_ext/regexp"
require "action_view/helpers/asset_url_helper"
require "action_view/helpers/tag_helper"
@@ -36,18 +35,37 @@ module ActionView
# When the Asset Pipeline is enabled, you can pass the name of your manifest as
# source, and include other JavaScript or CoffeeScript files inside the manifest.
#
+ # ==== Options
+ #
+ # When the last parameter is a hash you can add HTML attributes using that
+ # parameter. The following options are supported:
+ #
+ # * <tt>:extname</tt> - Append a extention to the generated url unless the extension
+ # already exists. This only applies for relative urls.
+ # * <tt>:protocol</tt> - Sets the protocol of the generated url, this option only
+ # applies when a relative url and +host+ options are provided.
+ # * <tt>:host</tt> - When a relative url is provided the host is added to the
+ # that path.
+ # * <tt>:skip_pipeline</tt> - This option is used to bypass the asset pipeline
+ # when it is set to true.
+ #
+ # ==== Examples
+ #
# javascript_include_tag "xmlhr"
- # # => <script src="/assets/xmlhr.js?1284139606"></script>
+ # # => <script src="/assets/xmlhr.debug-1284139606.js"></script>
+ #
+ # javascript_include_tag "xmlhr", host: "localhost", protocol: "https"
+ # # => <script src="https://localhost/assets/xmlhr.debug-1284139606.js"></script>
#
# javascript_include_tag "template.jst", extname: false
- # # => <script src="/assets/template.jst?1284139606"></script>
+ # # => <script src="/assets/template.debug-1284139606.jst"></script>
#
# javascript_include_tag "xmlhr.js"
- # # => <script src="/assets/xmlhr.js?1284139606"></script>
+ # # => <script src="/assets/xmlhr.debug-1284139606.js"></script>
#
# javascript_include_tag "common.javascript", "/elsewhere/cools"
- # # => <script src="/assets/common.javascript?1284139606"></script>
- # # <script src="/elsewhere/cools.js?1423139606"></script>
+ # # => <script src="/assets/common.javascript.debug-1284139606.js"></script>
+ # # <script src="/elsewhere/cools.debug-1284139606.js"></script>
#
# javascript_include_tag "http://www.example.com/xmlhr"
# # => <script src="http://www.example.com/xmlhr"></script>
@@ -56,7 +74,7 @@ module ActionView
# # => <script src="http://www.example.com/xmlhr.js"></script>
def javascript_include_tag(*sources)
options = sources.extract_options!.stringify_keys
- path_options = options.extract!("protocol", "extname", "host").symbolize_keys
+ path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
sources.uniq.map { |source|
tag_options = {
"src" => path_to_javascript(source, path_options)
@@ -92,8 +110,7 @@ module ActionView
# # <link href="/css/stylish.css" media="screen" rel="stylesheet" />
def stylesheet_link_tag(*sources)
options = sources.extract_options!.stringify_keys
- path_options = options.extract!("protocol", "host").symbolize_keys
-
+ path_options = options.extract!("protocol", "host", "skip_pipeline").symbolize_keys
sources.uniq.map { |source|
tag_options = {
"rel" => "stylesheet",
@@ -170,11 +187,11 @@ module ActionView
#
# favicon_link_tag 'mb-icon.png', rel: 'apple-touch-icon', type: 'image/png'
# # => <link href="/assets/mb-icon.png" rel="apple-touch-icon" type="image/png" />
- def favicon_link_tag(source="favicon.ico", options={})
+ def favicon_link_tag(source = "favicon.ico", options = {})
tag("link", {
rel: "shortcut icon",
type: "image/x-icon",
- href: path_to_image(source)
+ href: path_to_image(source, skip_pipeline: options.delete(:skip_pipeline))
}.merge!(options.symbolize_keys))
end
@@ -208,11 +225,11 @@ 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" />
- def image_tag(source, options={})
+ def image_tag(source, options = {})
options = options.symbolize_keys
check_for_image_tag_errors(options)
- src = options[:src] = path_to_image(source)
+ src = options[:src] = path_to_image(source, skip_pipeline: options.delete(:skip_pipeline))
unless src.start_with?("cid:") || src.start_with?("data:") || src.blank?
options[:alt] = options.fetch(:alt) { image_alt(src) }
@@ -258,6 +275,8 @@ module ActionView
# * <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>:poster_skip_pipeline</tt> will bypass the asset pipeline when using
+ # the <tt>:poster</tt> option instead using an asset in the public folder.
#
# ==== Examples
#
@@ -269,6 +288,8 @@ module ActionView
# # => <video preload="none" controls="controls" src="/videos/trailer.ogg" ></video>
# video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png")
# # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png"></video>
+ # video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png", poster_skip_pipeline: true)
+ # # => <video src="/videos/trailer.m4v" width="16" height="10" poster="screenshot.png"></video>
# video_tag("/trailers/hd.avi", size: "16x16")
# # => <video src="/trailers/hd.avi" width="16" height="16"></video>
# video_tag("/trailers/hd.avi", size: "16")
@@ -282,9 +303,12 @@ module ActionView
# video_tag(["trailer.ogg", "trailer.flv"], size: "160x120")
# # => <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
def video_tag(*sources)
- multiple_sources_tag("video", sources) do |options|
- options[:poster] = path_to_image(options[:poster]) if options[:poster]
- options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size]
+ options = sources.extract_options!.symbolize_keys
+ public_poster_folder = options.delete(:poster_skip_pipeline)
+ sources << options
+ multiple_sources_tag_builder("video", sources) do |tag_options|
+ tag_options[:poster] = path_to_image(tag_options[:poster], skip_pipeline: public_poster_folder) if tag_options[:poster]
+ tag_options[:width], tag_options[:height] = extract_dimensions(tag_options.delete(:size)) if tag_options[:size]
end
end
@@ -301,22 +325,23 @@ module ActionView
# audio_tag("sound.wav", "sound.mid")
# # => <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio>
def audio_tag(*sources)
- multiple_sources_tag("audio", sources)
+ multiple_sources_tag_builder("audio", sources)
end
private
- def multiple_sources_tag(type, sources)
- options = sources.extract_options!.symbolize_keys
+ def multiple_sources_tag_builder(type, sources)
+ options = sources.extract_options!.symbolize_keys
+ skip_pipeline = options.delete(:skip_pipeline)
sources.flatten!
yield options if block_given?
if sources.size > 1
content_tag(type, options) do
- safe_join sources.map { |source| tag("source", src: send("path_to_#{type}", source)) }
+ safe_join sources.map { |source| tag("source", src: send("path_to_#{type}", source, skip_pipeline: skip_pipeline)) }
end
else
- options[:src] = send("path_to_#{type}", sources.first)
+ options[:src] = send("path_to_#{type}", sources.first, skip_pipeline: skip_pipeline)
content_tag(type, nil, options)
end
end
diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb
index 7bd3027880..c6a5e04aba 100644
--- a/actionview/lib/action_view/helpers/asset_url_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_url_helper.rb
@@ -1,5 +1,4 @@
require "zlib"
-require "active_support/core_ext/regexp"
module ActionView
# = Action View Asset URL Helpers
@@ -37,7 +36,7 @@ module ActionView
# some asset downloads to wait for previous assets to finish before they can
# begin. You can use the <tt>%d</tt> wildcard in the +asset_host+ to
# distribute the requests over four hosts. For example,
- # <tt>assets%d.example.com<tt> will spread the asset requests over
+ # <tt>assets%d.example.com</tt> will spread the asset requests over
# "assets0.example.com", ..., "assets3.example.com".
#
# image_tag("rails.png")
@@ -97,8 +96,8 @@ module ActionView
# have SSL certificates for each of the asset hosts this technique allows you
# to avoid warnings in the client about mixed media.
# Note that the request parameter might not be supplied, e.g. when the assets
- # are precompiled via a Rake task. Make sure to use a Proc instead of a lambda,
- # since a Proc allows missing parameters and sets them to nil.
+ # are precompiled via a Rake task. Make sure to use a +Proc+ instead of a lambda,
+ # since a +Proc+ allows missing parameters and sets them to +nil+.
#
# config.action_controller.asset_host = Proc.new { |source, request|
# if request && request.ssl?
@@ -118,16 +117,67 @@ module ActionView
module AssetUrlHelper
URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//}i
- # Computes the path to asset in public directory. If :type
- # options is set, a file extension will be appended and scoped
- # to the corresponding public directory.
+ # This is the entry point for all assets.
+ # When using the asset pipeline (i.e. sprockets and sprockets-rails), the
+ # behavior is "enhanced". You can bypass the asset pipeline by passing in
+ # <tt>skip_pipeline: true</tt> to the options.
#
# All other asset *_path helpers delegate through this method.
#
- # asset_path "application.js" # => /assets/application.js
- # asset_path "application", type: :javascript # => /assets/application.js
- # asset_path "application", type: :stylesheet # => /assets/application.css
- # asset_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
+ # === With the asset pipeline
+ #
+ # All options passed to +asset_path+ will be passed to +compute_asset_path+
+ # which is implemented by sprockets-rails.
+ #
+ # asset_path("application.js") # => "/assets/application-60aa4fdc5cea14baf5400fba1abf4f2a46a5166bad4772b1effe341570f07de9.js"
+ #
+ # === Without the asset pipeline (<tt>skip_pipeline: true</tt>)
+ #
+ # Accepts a <tt>type</tt> option that can specify the asset's extension. No error
+ # checking is done to verify the source passed into +asset_path+ is valid
+ # and that the file exists on disk.
+ #
+ # asset_path("application.js", skip_pipeline: true) # => "application.js"
+ # asset_path("filedoesnotexist.png", skip_pipeline: true) # => "filedoesnotexist.png"
+ # asset_path("application", type: :javascript, skip_pipeline: true) # => "/javascripts/application.js"
+ # asset_path("application", type: :stylesheet, skip_pipeline: true) # => "/stylesheets/application.css"
+ #
+ # === Options applying to all assets
+ #
+ # Below lists scenarios that apply to +asset_path+ whether or not you're
+ # using the asset pipeline.
+ #
+ # - All fully qualified urls are returned immediately. This bypasses the
+ # asset pipeline and all other behavior described.
+ #
+ # asset_path("http://www.example.com/js/xmlhr.js") # => "http://www.example.com/js/xmlhr.js"
+ #
+ # - All assets that begin with a forward slash are assumed to be full
+ # urls and will not be expanded. This will bypass the asset pipeline.
+ #
+ # asset_path("/foo.png") # => "/foo.png"
+ #
+ # - All blank strings will be returned immediately. This bypasses the
+ # asset pipeline and all other behavior described.
+ #
+ # asset_path("") # => ""
+ #
+ # - If <tt>config.relative_url_root</tt> is specified, all assets will have that
+ # root prepended.
+ #
+ # Rails.application.config.relative_url_root = "bar"
+ # asset_path("foo.js", skip_pipeline: true) # => "bar/foo.js"
+ #
+ # - A different asset host can be specified via <tt>config.action_controller.asset_host</tt>
+ # this is commonly used in conjunction with a CDN.
+ #
+ # Rails.application.config.action_controller.asset_host = "assets.example.com"
+ # asset_path("foo.js", skip_pipeline: true) # => "http://assets.example.com/foo.js"
+ #
+ # - An extension name can be specified manually with <tt>extname</tt>.
+ #
+ # asset_path("foo", skip_pipeline: true, extname: ".js") # => "/foo.js"
+ # asset_path("foo.css", skip_pipeline: true, extname: ".js") # => "/foo.css.js"
def asset_path(source, options = {})
raise ArgumentError, "nil is not a valid asset source" if source.nil?
@@ -142,7 +192,11 @@ module ActionView
end
if source[0] != ?/
- source = compute_asset_path(source, options)
+ if options[:skip_pipeline]
+ source = public_compute_asset_path(source, options)
+ else
+ source = compute_asset_path(source, options)
+ end
end
relative_url_root = defined?(config.relative_url_root) && config.relative_url_root
@@ -178,7 +232,7 @@ module ActionView
stylesheet: ".css"
}
- # Compute extname to append to asset path. Returns nil if
+ # Compute extname to append to asset path. Returns +nil+ if
# nothing should be added.
def compute_asset_extname(source, options = {})
return if options[:extname] == false
@@ -203,6 +257,7 @@ module ActionView
dir = ASSET_PUBLIC_DIRECTORIES[options[:type]] || ""
File.join(dir, source)
end
+ alias :public_compute_asset_path :compute_asset_path
# Pick an asset host for this source. Returns +nil+ if no host is set,
# the host if no wildcard is set, the host interpolated with the
@@ -254,7 +309,7 @@ module ActionView
# javascript_path "http://www.example.com/js/xmlhr" # => http://www.example.com/js/xmlhr
# javascript_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
def javascript_path(source, options = {})
- path_to_asset(source, {type: :javascript}.merge!(options))
+ path_to_asset(source, { type: :javascript }.merge!(options))
end
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
@@ -266,7 +321,7 @@ module ActionView
# javascript_url "js/xmlhr.js", host: "http://stage.example.com" # => http://stage.example.com/assets/dir/xmlhr.js
#
def javascript_url(source, options = {})
- url_to_asset(source, {type: :javascript}.merge!(options))
+ url_to_asset(source, { type: :javascript }.merge!(options))
end
alias_method :url_to_javascript, :javascript_url # aliased to avoid conflicts with a javascript_url named route
@@ -281,7 +336,7 @@ module ActionView
# stylesheet_path "http://www.example.com/css/style" # => http://www.example.com/css/style
# stylesheet_path "http://www.example.com/css/style.css" # => http://www.example.com/css/style.css
def stylesheet_path(source, options = {})
- path_to_asset(source, {type: :stylesheet}.merge!(options))
+ path_to_asset(source, { type: :stylesheet }.merge!(options))
end
alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
@@ -293,7 +348,7 @@ module ActionView
# stylesheet_url "css/style.css", host: "http://stage.example.com" # => http://stage.example.com/css/style.css
#
def stylesheet_url(source, options = {})
- url_to_asset(source, {type: :stylesheet}.merge!(options))
+ url_to_asset(source, { type: :stylesheet }.merge!(options))
end
alias_method :url_to_stylesheet, :stylesheet_url # aliased to avoid conflicts with a stylesheet_url named route
@@ -311,7 +366,7 @@ module ActionView
# The alias +path_to_image+ is provided to avoid that. Rails uses the alias internally, and
# plugin authors are encouraged to do so.
def image_path(source, options = {})
- path_to_asset(source, {type: :image}.merge!(options))
+ path_to_asset(source, { type: :image }.merge!(options))
end
alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
@@ -323,7 +378,7 @@ module ActionView
# image_url "edit.png", host: "http://stage.example.com" # => http://stage.example.com/edit.png
#
def image_url(source, options = {})
- url_to_asset(source, {type: :image}.merge!(options))
+ url_to_asset(source, { type: :image }.merge!(options))
end
alias_method :url_to_image, :image_url # aliased to avoid conflicts with an image_url named route
@@ -337,7 +392,7 @@ module ActionView
# video_path("/trailers/hd.avi") # => /trailers/hd.avi
# video_path("http://www.example.com/vid/hd.avi") # => http://www.example.com/vid/hd.avi
def video_path(source, options = {})
- path_to_asset(source, {type: :video}.merge!(options))
+ path_to_asset(source, { type: :video }.merge!(options))
end
alias_method :path_to_video, :video_path # aliased to avoid conflicts with a video_path named route
@@ -349,7 +404,7 @@ module ActionView
# video_url "hd.avi", host: "http://stage.example.com" # => http://stage.example.com/hd.avi
#
def video_url(source, options = {})
- url_to_asset(source, {type: :video}.merge!(options))
+ url_to_asset(source, { type: :video }.merge!(options))
end
alias_method :url_to_video, :video_url # aliased to avoid conflicts with an video_url named route
@@ -363,7 +418,7 @@ module ActionView
# audio_path("/sounds/horse.wav") # => /sounds/horse.wav
# audio_path("http://www.example.com/sounds/horse.wav") # => http://www.example.com/sounds/horse.wav
def audio_path(source, options = {})
- path_to_asset(source, {type: :audio}.merge!(options))
+ path_to_asset(source, { type: :audio }.merge!(options))
end
alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route
@@ -375,7 +430,7 @@ module ActionView
# audio_url "horse.wav", host: "http://stage.example.com" # => http://stage.example.com/horse.wav
#
def audio_url(source, options = {})
- url_to_asset(source, {type: :audio}.merge!(options))
+ url_to_asset(source, { type: :audio }.merge!(options))
end
alias_method :url_to_audio, :audio_url # aliased to avoid conflicts with an audio_url named route
@@ -388,7 +443,7 @@ module ActionView
# font_path("/dir/font.ttf") # => /dir/font.ttf
# font_path("http://www.example.com/dir/font.ttf") # => http://www.example.com/dir/font.ttf
def font_path(source, options = {})
- path_to_asset(source, {type: :font}.merge!(options))
+ path_to_asset(source, { type: :font }.merge!(options))
end
alias_method :path_to_font, :font_path # aliased to avoid conflicts with an font_path named route
@@ -400,7 +455,7 @@ module ActionView
# font_url "font.ttf", host: "http://stage.example.com" # => http://stage.example.com/font.ttf
#
def font_url(source, options = {})
- url_to_asset(source, {type: :font}.merge!(options))
+ url_to_asset(source, { type: :font }.merge!(options))
end
alias_method :url_to_font, :font_url # aliased to avoid conflicts with an font_url named route
end
diff --git a/actionview/lib/action_view/helpers/atom_feed_helper.rb b/actionview/lib/action_view/helpers/atom_feed_helper.rb
index e5d063e03c..3538515aee 100644
--- a/actionview/lib/action_view/helpers/atom_feed_helper.rb
+++ b/actionview/lib/action_view/helpers/atom_feed_helper.rb
@@ -103,7 +103,7 @@ module ActionView
xml = options.delete(:xml) || eval("xml", block.binding)
xml.instruct!
if options[:instruct]
- options[:instruct].each do |target,attrs|
+ options[:instruct].each do |target, attrs|
if attrs.respond_to?(:keys)
xml.instruct!(target, attrs)
elsif attrs.respond_to?(:each)
@@ -112,8 +112,8 @@ module ActionView
end
end
- feed_opts = {"xml:lang" => options[:language] || "en-US", "xmlns" => "http://www.w3.org/2005/Atom"}
- feed_opts.merge!(options).reject!{|k,v| !k.to_s.match(/^xml/)}
+ feed_opts = { "xml:lang" => options[:language] || "en-US", "xmlns" => "http://www.w3.org/2005/Atom" }
+ feed_opts.merge!(options).reject! { |k, v| !k.to_s.match(/^xml/) }
xml.feed(feed_opts) do
xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}")
@@ -163,7 +163,7 @@ module ActionView
@xml, @view, @feed_options = xml, view, feed_options
end
- # Accepts a Date or Time object and inserts it in the proper format. If nil is passed, current time in UTC is used.
+ # Accepts a Date or Time object and inserts it in the proper format. If +nil+ is passed, current time in UTC is used.
def updated(date_or_time = nil)
@xml.updated((date_or_time || Time.now.utc).xmlschema)
end
@@ -174,7 +174,7 @@ module ActionView
#
# * <tt>:published</tt>: Time first published. Defaults to the created_at attribute on the record if one such exists.
# * <tt>:updated</tt>: Time of update. Defaults to the updated_at attribute on the record if one such exists.
- # * <tt>:url</tt>: The URL for this entry or false or nil for not having a link tag. Defaults to the polymorphic_url for the record.
+ # * <tt>:url</tt>: The URL for this entry or +false+ or +nil+ for not having a link tag. Defaults to the +polymorphic_url+ for the record.
# * <tt>:id</tt>: The ID for this entry. Defaults to "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}"
# * <tt>:type</tt>: The TYPE for this entry. Defaults to "text/html".
def entry(record, options = {})
diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb
index b598469d01..7fdf0fd0e1 100644
--- a/actionview/lib/action_view/helpers/cache_helper.rb
+++ b/actionview/lib/action_view/helpers/cache_helper.rb
@@ -88,7 +88,7 @@ module ActionView
#
# === Explicit dependencies
#
- # Some times you'll have template dependencies that can't be derived at all. This is typically
+ # Sometimes you'll have template dependencies that can't be derived at all. This is typically
# the case when you have template rendering that happens in helpers. Here's an example:
#
# <%= render_sortable_todolists @project.todolists %>
@@ -211,12 +211,14 @@ module ActionView
end
end
+ attr_reader :cache_hit # :nodoc:
+
private
- def fragment_name_with_digest(name, virtual_path) #:nodoc:
+ 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)
+ 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 ]
else
@@ -224,22 +226,21 @@ module ActionView
end
end
- # TODO: Create an object that has caching read/write on it
- def fragment_for(name = {}, options = nil, &block) #:nodoc:
+ def fragment_for(name = {}, options = nil, &block)
if content = read_fragment_for(name, options)
- @log_payload_for_partial_render[:cache_hit] = true if defined?(@log_payload_for_partial_render)
+ @cache_hit = true
content
else
- @log_payload_for_partial_render[:cache_hit] = false if defined?(@log_payload_for_partial_render)
+ @cache_hit = false
write_fragment_for(name, options, &block)
end
end
- def read_fragment_for(name, options) #:nodoc:
+ def read_fragment_for(name, options)
controller.read_fragment(name, options)
end
- def write_fragment_for(name, options) #:nodoc:
+ def write_fragment_for(name, options)
# VIEW TODO: Make #capture usable outside of ERB
# This dance is needed because Builder can't use capture
pos = output_buffer.length
diff --git a/actionview/lib/action_view/helpers/capture_helper.rb b/actionview/lib/action_view/helpers/capture_helper.rb
index 5b569b5923..719592b5c5 100644
--- a/actionview/lib/action_view/helpers/capture_helper.rb
+++ b/actionview/lib/action_view/helpers/capture_helper.rb
@@ -37,7 +37,7 @@ module ActionView
def capture(*args)
value = nil
buffer = with_output_buffer { value = yield(*args) }
- if string = buffer.presence || value and string.is_a?(String)
+ if (string = buffer.presence || value) && string.is_a?(String)
ERB::Util.html_escape string
end
end
diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb
index 99db209d63..09dc6ef6bd 100644
--- a/actionview/lib/action_view/helpers/date_helper.rb
+++ b/actionview/lib/action_view/helpers/date_helper.rb
@@ -98,7 +98,7 @@ module ActionView
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, to_time = to_time, from_time if from_time > to_time
- distance_in_minutes = ((to_time - from_time)/60.0).round
+ distance_in_minutes = ((to_time - from_time) / 60.0).round
distance_in_seconds = (to_time - from_time).round
I18n.with_options locale: options[:locale], scope: options[:scope] do |locale|
@@ -135,7 +135,7 @@ module ActionView
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)}
+ 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
@@ -220,7 +220,7 @@ module ActionView
# the respective locale (e.g. [:year, :month, :day] in the en locale that ships with Rails).
# * <tt>:include_blank</tt> - Include a blank option in every select field so it's possible to set empty
# dates.
- # * <tt>:default</tt> - Set a default date if the affected date isn't set or is nil.
+ # * <tt>:default</tt> - Set a default date if the affected date isn't set or is +nil+.
# * <tt>:selected</tt> - Set a date that overrides the actual value.
# * <tt>:disabled</tt> - Set to true if you want show the select fields as disabled.
# * <tt>:prompt</tt> - Set to true (for a generic prompt), a prompt string or a hash of prompt strings
@@ -267,7 +267,7 @@ module ActionView
# date_select("article", "written_on", default: 3.days.from_now)
#
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute
- # # which is set in the form with todays date, regardless of the value in the Active Record object.
+ # # which is set in the form with today's date, regardless of the value in the Active Record object.
# date_select("article", "written_on", selected: Date.today)
#
# # Generates a date select that when POSTed is stored in the credit_card variable, in the bill_due attribute
@@ -303,7 +303,7 @@ module ActionView
# # the sunrise attribute.
# time_select("article", "start_time", include_seconds: true)
#
- # # You can set the <tt>:minute_step</tt> to 15 which will give you: 00, 15, 30 and 45.
+ # # You can set the <tt>:minute_step</tt> to 15 which will give you: 00, 15, 30, and 45.
# time_select 'game', 'game_time', {minute_step: 15}
#
# # Creates a time select tag with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
@@ -866,7 +866,7 @@ module ActionView
end
# Returns translated month names, but also ensures that a custom month
- # name array has a leading nil element.
+ # name array has a leading +nil+ element.
def month_names
@month_names ||= begin
month_names = @options[:use_month_names] || translated_month_names
@@ -918,7 +918,7 @@ module ActionView
elsif @options[:add_month_numbers]
"#{number} - #{month_names[number]}"
elsif format_string = @options[:month_format_string]
- format_string % {number: number, name: month_names[number]}
+ format_string % { number: number, name: month_names[number] }
else
month_names[number]
end
@@ -1011,12 +1011,13 @@ module ActionView
# css_class_attribute(:year, 'date optional', { year: 'my-year' })
# => "date optional my-year"
def css_class_attribute(type, html_options_class, options) # :nodoc:
- css_class = case options
- when Hash
- options[type.to_sym]
- else
- type
- end
+ css_class = \
+ case options
+ when Hash
+ options[type.to_sym]
+ else
+ type
+ end
[html_options_class, css_class].compact.join(" ")
end
@@ -1025,15 +1026,16 @@ module ActionView
# prompt_option_tag(:month, prompt: 'Select month')
# => "<option value="">Select month</option>"
def prompt_option_tag(type, options)
- prompt = case options
- when Hash
- default_options = {year: false, month: false, day: false, hour: false, minute: false, second: false}
- default_options.merge!(options)[type.to_sym]
- when String
- options
+ prompt = \
+ case options
+ when Hash
+ default_options = { year: false, month: false, day: false, hour: false, minute: false, second: false }
+ default_options.merge!(options)[type.to_sym]
+ when String
+ options
else
- I18n.translate(:"datetime.prompts.#{type}", locale: @options[:locale])
- end
+ I18n.translate(:"datetime.prompts.#{type}", locale: @options[:locale])
+ end
prompt ? content_tag("option".freeze, prompt, value: "") : ""
end
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 7d574f0a14..26a625e4fe 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -474,6 +474,266 @@ module ActionView
end
private :apply_form_for_options!
+ # Creates a form tag based on mixing URLs, scopes, or models.
+ #
+ # # Using just a URL:
+ # <%= form_with url: posts_path do |form| %>
+ # <%= form.text_field :title %>
+ # <% end %>
+ # # =>
+ # <form action="/posts" method="post" data-remote="true">
+ # <input type="text" name="title">
+ # </form>
+ #
+ # # Adding a scope prefixes the input field names:
+ # <%= form_with scope: :post, url: posts_path do |form| %>
+ # <%= form.text_field :title %>
+ # <% end %>
+ # # =>
+ # <form action="/posts" method="post" data-remote="true">
+ # <input type="text" name="post[title]">
+ # </form>
+ #
+ # # Using a model infers both the URL and scope:
+ # <%= form_with model: Post.new do |form| %>
+ # <%= form.text_field :title %>
+ # <% end %>
+ # # =>
+ # <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:
+ # <%= form_with model: Post.first do |form| %>
+ # <%= form.text_field :title %>
+ # <% end %>
+ # # =>
+ # <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>
+ #
+ # # Though the fields don't have to correspond to model attributes:
+ # <%= form_with model: Cat.new do |form| %>
+ # <%= form.text_field :cats_dont_have_gills %>
+ # <%= form.text_field :but_in_forms_they_can %>
+ # <% end %>
+ # # =>
+ # <form action="/cats" method="post" data-remote="true">
+ # <input type="text" name="cat[cats_dont_have_gills]">
+ # <input type="text" name="cat[but_in_forms_they_can]">
+ # </form>
+ #
+ # The parameters in the forms are accessible in controllers according to
+ # their name nesting. So inputs named +title+ and <tt>post[title]</tt> are
+ # accessible as <tt>params[:title]</tt> and <tt>params[:post][:title]</tt>
+ # respectively.
+ #
+ # By default +form_with+ attaches the <tt>data-remote</tt> attribute
+ # submitting the form via an XMLHTTPRequest in the background if an
+ # Unobtrusive JavaScript driver, like rails-ujs, is used. See the
+ # <tt>:local</tt> option for more.
+ #
+ # For ease of comparison the examples above left out the submit button,
+ # as well as the auto generated hidden fields that enable UTF-8 support
+ # and adds an authenticity token needed for cross site request forgery
+ # protection.
+ #
+ # ==== +form_with+ options
+ #
+ # * <tt>:url</tt> - The URL the form submits to. Akin to values passed to
+ # +url_for+ or +link_to+. For example, you may use a named route
+ # directly. When a <tt>:scope</tt> is passed without a <tt>:url</tt> the
+ # form just submits to the current URL.
+ # * <tt>:method</tt> - The method to use when submitting the form, usually
+ # either "get" or "post". If "patch", "put", "delete", or another verb
+ # is used, a hidden input named <tt>_method</tt> is added to
+ # simulate the verb over post.
+ # * <tt>:format</tt> - The format of the route the form submits to.
+ # Useful when submitting to another resource type, like <tt>:json</tt>.
+ # Skipped if a <tt>:url</tt> is passed.
+ # * <tt>:scope</tt> - The scope to prefix input field names with and
+ # thereby how the submitted parameters are grouped in controllers.
+ # * <tt>:model</tt> - A model object to infer the <tt>:url</tt> and
+ # <tt>:scope</tt> by, plus fill out input field values.
+ # So if a +title+ attribute is set to "Ahoy!" then a +title+ input
+ # field's value would be "Ahoy!".
+ # If the model is a new record a create form is generated, if an
+ # existing record, however, an update form is generated.
+ # Pass <tt>:scope</tt> or <tt>:url</tt> to override the defaults.
+ # E.g. turn <tt>params[:post]</tt> into <tt>params[:article]</tt>.
+ # * <tt>:authenticity_token</tt> - Authenticity token to use in the form.
+ # Override with a custom authenticity token or pass <tt>false</tt> to
+ # skip the authenticity token field altogether.
+ # Useful when submitting to an external resource like a payment gateway
+ # that might limit the valid fields.
+ # Remote forms may omit the embedded authenticity token by setting
+ # <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>.
+ # This is helpful when fragment-caching the form. Remote forms
+ # get the authenticity token from the <tt>meta</tt> tag, so embedding is
+ # unnecessary unless you support browsers without JavaScript.
+ # * <tt>:local</tt> - By default form submits are remote and unobstrusive XHRs.
+ # Disable remote submits with <tt>local: true</tt>.
+ # * <tt>:skip_enforcing_utf8</tt> - By default a hidden field named +utf8+
+ # is output to enforce UTF-8 submits. Set to true to skip the field.
+ # * <tt>:builder</tt> - Override the object used to build the form.
+ # * <tt>:id</tt> - Optional HTML id attribute.
+ # * <tt>:class</tt> - Optional HTML class attribute.
+ # * <tt>:data</tt> - Optional HTML data attributes.
+ # * <tt>:html</tt> - Other optional HTML attributes for the form tag.
+ #
+ # === Examples
+ #
+ # When not passing a block, +form_with+ just generates an opening form tag.
+ #
+ # <%= form_with(model: @post, url: super_posts_path) %>
+ # <%= form_with(model: @post, scope: :article) %>
+ # <%= form_with(model: @post, format: :json) %>
+ # <%= form_with(model: @post, authenticity_token: false) %> # Disables the token.
+ #
+ # For namespaced routes, like +admin_post_url+:
+ #
+ # <%= form_with(model: [ :admin, @post ]) do |form| %>
+ # ...
+ # <% end %>
+ #
+ # If your resource has associations defined, for example, you want to add comments
+ # to the document given that the routes are set correctly:
+ #
+ # <%= form_with(model: [ @document, Comment.new ]) do |form| %>
+ # ...
+ # <% end %>
+ #
+ # Where <tt>@document = Document.find(params[:id])</tt>.
+ #
+ # When using labels +form_with+ requires setting the id on the field being
+ # labelled:
+ #
+ # <%= form_with(model: @post) do |form| %>
+ # <%= form.label :title %>
+ # <%= form.text_field :title, id: :post_title %>
+ # <% end %>
+ #
+ # See +label+ for more on how the +for+ attribute is derived.
+ #
+ # === Mixing with other form helpers
+ #
+ # While +form_with+ uses a FormBuilder object it's possible to mix and
+ # match the stand-alone FormHelper methods and methods
+ # from FormTagHelper:
+ #
+ # <%= form_with scope: :person do |form| %>
+ # <%= form.text_field :first_name %>
+ # <%= form.text_field :last_name %>
+ #
+ # <%= text_area :person, :biography %>
+ # <%= check_box_tag "person[admin]", "1", @person.company.admin? %>
+ #
+ # <%= form.submit %>
+ # <% end %>
+ #
+ # Same goes for the methods in FormOptionHelper and DateHelper designed
+ # to work with an object as a base, like
+ # FormOptionHelper#collection_select and DateHelper#datetime_select.
+ #
+ # === Setting the method
+ #
+ # You can force the form to use the full array of HTTP verbs by setting
+ #
+ # method: (:get|:post|:patch|:put|:delete)
+ #
+ # in the options hash. If the verb is not GET or POST, which are natively
+ # supported by HTML forms, the form will be set to POST and a hidden input
+ # called _method will carry the intended verb for the server to interpret.
+ #
+ # === Setting HTML options
+ #
+ # You can set data attributes directly in a data hash, but HTML options
+ # besides id and class must be wrapped in an HTML key:
+ #
+ # <%= form_with(model: @post, data: { behavior: "autosave" }, html: { name: "go" }) do |form| %>
+ # ...
+ # <% end %>
+ #
+ # generates
+ #
+ # <form action="/posts/123" method="post" data-behavior="autosave" name="go">
+ # <input name="_method" type="hidden" value="patch" />
+ # ...
+ # </form>
+ #
+ # === Removing hidden model id's
+ #
+ # The +form_with+ method automatically includes the model id as a hidden field in the form.
+ # This is used to maintain the correlation between the form data and its associated model.
+ # Some ORM systems do not use IDs on nested models so in this case you want to be able
+ # to disable the hidden id.
+ #
+ # In the following example the Post model has many Comments stored within it in a NoSQL database,
+ # thus there is no primary key for comments.
+ #
+ # <%= form_with(model: @post) do |form| %>
+ # <%= form.fields(:comments, skip_id: true) do |fields| %>
+ # ...
+ # <% end %>
+ # <% end %>
+ #
+ # === Customized form builders
+ #
+ # You can also build forms using a customized FormBuilder class. Subclass
+ # FormBuilder and override or define some more helpers, then use your
+ # custom builder. For example, let's say you made a helper to
+ # automatically add labels to form inputs.
+ #
+ # <%= form_with model: @person, url: { action: "create" }, builder: LabellingFormBuilder do |form| %>
+ # <%= form.text_field :first_name %>
+ # <%= form.text_field :last_name %>
+ # <%= form.text_area :biography %>
+ # <%= form.check_box :admin %>
+ # <%= form.submit %>
+ # <% end %>
+ #
+ # In this case, if you use:
+ #
+ # <%= render form %>
+ #
+ # The rendered template is <tt>people/_labelling_form</tt> and the local
+ # variable referencing the form builder is called
+ # <tt>labelling_form</tt>.
+ #
+ # The custom FormBuilder class is automatically merged with the options
+ # of a nested +fields+ call, unless it's explicitly set.
+ #
+ # In many cases you will want to wrap the above in another helper, so you
+ # could do something like the following:
+ #
+ # def labelled_form_with(**options, &block)
+ # form_with(**options.merge(builder: LabellingFormBuilder), &block)
+ # end
+ def form_with(model: nil, scope: nil, url: nil, format: nil, **options)
+ options[:allow_method_names_outside_object] = true
+ options[:skip_default_ids] = true
+
+ if model
+ url ||= polymorphic_path(model, format: format)
+
+ model = model.last if model.is_a?(Array)
+ scope ||= model_name_from_record_or_class(model).param_key
+ end
+
+ if block_given?
+ builder = instantiate_builder(scope, model, options)
+ output = capture(builder, &Proc.new)
+ options[:multipart] ||= builder.multipart?
+
+ html_options = html_options_for_form_with(url, model, options)
+ form_tag_with_body(html_options, output)
+ else
+ html_options = html_options_for_form_with(url, model, options)
+ form_tag_html(html_options)
+ end
+ end
+
# Creates a scope around a specific model object like form_for, but
# doesn't create the form tags themselves. This makes fields_for suitable
# for specifying additional model objects in the same form.
@@ -720,6 +980,74 @@ module ActionView
capture(builder, &block)
end
+ # Scopes input fields with either an explicit scope or model.
+ # Like +form_with+ does with <tt>:scope</tt> or <tt>:model</tt>,
+ # except it doesn't output the form tags.
+ #
+ # # Using a scope prefixes the input field names:
+ # <%= fields :comment do |fields| %>
+ # <%= fields.text_field :body %>
+ # <% end %>
+ # # => <input type="text" name="comment[body]>
+ #
+ # # Using a model infers the scope and assigns field values:
+ # <%= fields model: Comment.new(body: "full bodied") do |fields| %<
+ # <%= fields.text_field :body %>
+ # <% end %>
+ # # =>
+ # <input type="text" name="comment[body] value="full bodied">
+ #
+ # # Using +fields+ with +form_with+:
+ # <%= form_with model: @post do |form| %>
+ # <%= form.text_field :title %>
+ #
+ # <%= form.fields :comment do |fields| %>
+ # <%= fields.text_field :body %>
+ # <% end %>
+ # <% end %>
+ #
+ # Much like +form_with+ a FormBuilder instance associated with the scope
+ # or model is yielded, so any generated field names are prefixed with
+ # either the passed scope or the scope inferred from the <tt>:model</tt>.
+ #
+ # When using labels +fields+ requires setting the id on the field being
+ # labelled:
+ #
+ # <%= fields :comment do |fields| %>
+ # <%= fields.label :body %>
+ # <%= fields.text_field :body, id: :comment_body %>
+ # <% end %>
+ #
+ # See +label+ for more on how the +for+ attribute is derived.
+ #
+ # === Mixing with other form helpers
+ #
+ # While +form_with+ uses a FormBuilder object it's possible to mix and
+ # match the stand-alone FormHelper methods and methods
+ # from FormTagHelper:
+ #
+ # <%= fields model: @comment do |fields| %>
+ # <%= fields.text_field :body %>
+ #
+ # <%= text_area :commenter, :biography %>
+ # <%= check_box_tag "comment[all_caps]", "1", @comment.commenter.hulk_mode? %>
+ # <% end %>
+ #
+ # Same goes for the methods in FormOptionHelper and DateHelper designed
+ # to work with an object as a base, like
+ # FormOptionHelper#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
+
+ if model
+ scope ||= model_name_from_record_or_class(model).param_key
+ end
+
+ builder = instantiate_builder(scope, model, options)
+ capture(builder, &block)
+ end
+
# Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
# assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
# is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
@@ -965,6 +1293,7 @@ module ActionView
# # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
# # <input type="radio" id="post_category_java" name="post[category]" value="java" />
#
+ # # Let's say that @user.receive_newsletter returns "no":
# radio_button("user", "receive_newsletter", "yes")
# radio_button("user", "receive_newsletter", "no")
# # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
@@ -1174,6 +1503,32 @@ module ActionView
end
private
+ def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: false,
+ 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?
+ html_options[:enforce_utf8] = !skip_enforcing_utf8
+
+ html_options[:enctype] = "multipart/form-data" if html_options.delete(:multipart)
+
+ # The following URL is unescaped, this is just a hash of options, and it is the
+ # responsibility of the caller to escape all the values.
+ html_options[:action] = url_for(url_for_options || {})
+ 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,
+ # but we needed the true value to override the default of no authenticity_token on data-remote.
+ html_options[:authenticity_token] = nil
+ end
+
+ html_options.stringify_keys!
+ end
def instantiate_builder(record_name, record_object, options)
case record_name
@@ -1182,7 +1537,7 @@ module ActionView
object_name = record_name
else
object = record_name
- object_name = model_name_from_record_or_class(object).param_key
+ object_name = model_name_from_record_or_class(object).param_key if object
end
builder = options[:builder] || default_form_builder_class
@@ -1248,7 +1603,7 @@ module ActionView
# The methods which wrap a form helper call.
class_attribute :field_helpers
- self.field_helpers = [:fields_for, :label, :text_field, :password_field,
+ 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,
@@ -1284,19 +1639,23 @@ module ActionView
def initialize(object_name, object, template, options)
@nested_child_index = {}
@object_name, @object, @template, @options = object_name, object, template, options
- @default_options = @options ? @options.slice(:index, :namespace) : {}
+ @default_options = @options ? @options.slice(:index, :namespace, :skip_default_ids, :allow_method_names_outside_object) : {}
+
+ convert_to_legacy_options(@options)
+
if @object_name.to_s.match(/\[\]$/)
- if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
+ if (object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}")) && object.respond_to?(:to_param)
@auto_index = object.to_param
else
raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
end
end
+
@multipart = nil
@index = options[:index] || options[:child_index]
end
- (field_helpers - [:label, :check_box, :radio_button, :fields_for, :hidden_field, :file_field]).each do |selector|
+ (field_helpers - [:label, :check_box, :radio_button, :fields_for, :fields, :hidden_field, :file_field]).each do |selector|
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{selector}(method, options = {}) # def text_field(method, options = {})
@template.send( # @template.send(
@@ -1565,10 +1924,11 @@ module ActionView
record_name = model_name_from_record_or_class(record_object).param_key
end
+ object_name = @object_name
index = if options.has_key?(:index)
options[:index]
elsif defined?(@auto_index)
- self.object_name = @object_name.to_s.sub(/\[\]$/,"")
+ object_name = object_name.to_s.sub(/\[\]$/, "")
@auto_index
end
@@ -1585,6 +1945,16 @@ module ActionView
@template.fields_for(record_name, record_object, fields_options, &block)
end
+ # See the docs for the <tt>ActionView::FormHelper.fields</tt> helper method.
+ def fields(scope = nil, model: nil, **options, &block)
+ options[:allow_method_names_outside_object] = true
+ options[:skip_default_ids] = true
+
+ convert_to_legacy_options(options)
+
+ fields_for(scope || model, model, **options, &block)
+ end
+
# Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
# assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
# is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
@@ -1711,7 +2081,7 @@ module ActionView
# # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
# # <input type="radio" id="post_category_java" name="post[category]" value="java" />
#
- # # Let's say that @user.category returns "no":
+ # # Let's say that @user.receive_newsletter returns "no":
# radio_button("receive_newsletter", "yes")
# radio_button("receive_newsletter", "no")
# # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
@@ -1808,7 +2178,7 @@ module ActionView
# post:
# create: "Add %{model}"
#
- def submit(value=nil, options={})
+ def submit(value = nil, options = {})
value, options = nil, value if value.is_a?(Hash)
value ||= submit_default_value
@template.submit_tag(value, options)
@@ -1933,6 +2303,12 @@ module ActionView
@nested_child_index[name] ||= -1
@nested_child_index[name] += 1
end
+
+ def convert_to_legacy_options(options)
+ if options.key?(:skip_id)
+ options[:include_id] = !options.delete(:skip_id)
+ end
+ end
end
end
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index a8abe6b7c4..ffc64e7118 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -442,26 +442,14 @@ module ActionView
# # => <input name='commit' type='submit' value='Save' data-disable-with="Save" data-confirm="Are you sure?" />
#
def submit_tag(value = "Save changes", options = {})
- options = options.stringify_keys
+ options = options.deep_stringify_keys
tag_options = { "type" => "submit", "name" => "commit", "value" => value }.update(options)
-
- if ActionView::Base.automatically_disable_submit_tag
- unless tag_options["data-disable-with"] == false || (tag_options["data"] && tag_options["data"][:disable_with] == false)
- disable_with_text = tag_options["data-disable-with"]
- disable_with_text ||= tag_options["data"][:disable_with] if tag_options["data"]
- disable_with_text ||= value.to_s.clone
- tag_options.deep_merge!("data" => { "disable_with" => disable_with_text })
- else
- tag_options["data"].delete(:disable_with) if tag_options["data"]
- end
- tag_options.delete("data-disable-with")
- end
-
+ set_default_disable_with value, tag_options
tag :input, tag_options
end
# Creates a button element that defines a <tt>submit</tt> button,
- # <tt>reset</tt>button or a generic button which can be used in
+ # <tt>reset</tt> button or a generic button which can be used in
# JavaScript, for example. You can use the button tag as a regular
# submit tag but it isn't supported in legacy browsers. However,
# the button tag does allow for richer labels such as images and emphasis,
@@ -857,23 +845,24 @@ module ActionView
authenticity_token = html_options.delete("authenticity_token")
method = html_options.delete("method").to_s.downcase
- method_tag = case method
- when "get"
- html_options["method"] = "get"
- ""
- when "post", ""
- html_options["method"] = "post"
- token_tag(authenticity_token, form_options: {
- action: html_options["action"],
- method: "post"
- })
+ method_tag = \
+ case method
+ when "get"
+ html_options["method"] = "get"
+ ""
+ when "post", ""
+ html_options["method"] = "post"
+ token_tag(authenticity_token, form_options: {
+ action: html_options["action"],
+ method: "post"
+ })
else
- html_options["method"] = "post"
- method_tag(method) + token_tag(authenticity_token, form_options: {
- action: html_options["action"],
- method: method
- })
- end
+ html_options["method"] = "post"
+ method_tag(method) + token_tag(authenticity_token, form_options: {
+ action: html_options["action"],
+ method: method
+ })
+ end
if html_options.delete("enforce_utf8") { true }
utf8_enforcer_tag + method_tag
@@ -897,6 +886,22 @@ module ActionView
def sanitize_to_id(name)
name.to_s.delete("]").tr("^-a-zA-Z0-9:.", "_")
end
+
+ def set_default_disable_with(value, tag_options)
+ return unless ActionView::Base.automatically_disable_submit_tag
+ data = tag_options["data"]
+
+ unless tag_options["data-disable-with"] == false || (data && data["disable_with"] == false)
+ disable_with_text = tag_options["data-disable-with"]
+ disable_with_text ||= data["disable_with"] if data
+ disable_with_text ||= value.to_s.clone
+ tag_options.deep_merge!("data" => { "disable_with" => disable_with_text })
+ else
+ data.delete("disable_with") if data
+ end
+
+ tag_options.delete("data-disable-with")
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/javascript_helper.rb b/actionview/lib/action_view/helpers/javascript_helper.rb
index 8f7be4905d..22e1e74ad6 100644
--- a/actionview/lib/action_view/helpers/javascript_helper.rb
+++ b/actionview/lib/action_view/helpers/javascript_helper.rb
@@ -24,7 +24,7 @@ module ActionView
# $('some_element').replaceWith('<%= j render 'some/element_template' %>');
def escape_javascript(javascript)
if javascript
- result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) {|match| JS_ESCAPE_MAP[match] }
+ result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) { |match| JS_ESCAPE_MAP[match] }
javascript.html_safe? ? result.html_safe : result
else
""
diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb
index 75b898c3e9..9e80f0b2ee 100644
--- a/actionview/lib/action_view/helpers/number_helper.rb
+++ b/actionview/lib/action_view/helpers/number_helper.rb
@@ -171,6 +171,9 @@ module ActionView
# to ",").
# * <tt>:separator</tt> - Sets the separator between the
# fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter_pattern</tt> - Sets a custom regular expression used for
+ # deriving the placement of delimiter. Helpful when using currency formats
+ # like INR.
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
# the argument is invalid.
#
@@ -187,6 +190,9 @@ module ActionView
# number_with_delimiter(98765432.98, delimiter: " ", separator: ",")
# # => 98 765 432,98
#
+ # number_with_delimiter("123456.78",
+ # delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/) # => "1,23,456.78"
+ #
# number_with_delimiter("112a", raise: true) # => raise InvalidNumberError
def number_with_delimiter(number, options = {})
delegate_number_helper_method(:number_to_delimited, number, options)
diff --git a/actionview/lib/action_view/helpers/output_safety_helper.rb b/actionview/lib/action_view/helpers/output_safety_helper.rb
index 8e63e59fac..9f1a890f6a 100644
--- a/actionview/lib/action_view/helpers/output_safety_helper.rb
+++ b/actionview/lib/action_view/helpers/output_safety_helper.rb
@@ -28,7 +28,7 @@ module ActionView #:nodoc:
# safe_join([raw("<p>foo</p>"), raw("<p>bar</p>")], raw("<br />")
# # => "<p>foo</p><br /><p>bar</p>"
#
- def safe_join(array, sep=$,)
+ def safe_join(array, sep = $,)
sep = ERB::Util.unwrapped_html_escape(sep)
array.flatten.map! { |i| ERB::Util.unwrapped_html_escape(i) }.join(sep).html_safe
diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb
index 4950f272a4..306b71c85e 100644
--- a/actionview/lib/action_view/helpers/tag_helper.rb
+++ b/actionview/lib/action_view/helpers/tag_helper.rb
@@ -86,11 +86,11 @@ module ActionView
def tag_option(key, value, escape)
if value.is_a?(Array)
- value = escape ? safe_join(value, " ") : value.join(" ")
+ value = escape ? safe_join(value, " ".freeze) : value.join(" ".freeze)
else
value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
end
- %(#{key}="#{value.gsub(/"/, '&quot;'.freeze)}")
+ %(#{key}="#{value.gsub('"'.freeze, '&quot;'.freeze)}")
end
private
@@ -138,7 +138,7 @@ module ActionView
#
# ==== Options
#
- # Any passed options become attributes on the generated tag.
+ # Use symbol keyed options to add attributes to the generated tag.
#
# tag.section class: %w( kitties puppies )
# # => <section class="kitties puppies"></section>
diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb
index 43335023ce..74d6324771 100644
--- a/actionview/lib/action_view/helpers/tags/base.rb
+++ b/actionview/lib/action_view/helpers/tags/base.rb
@@ -11,8 +11,10 @@ module ActionView
@object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
@template_object = template_object
- @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
+ @object_name.sub!(/\[\]$/, "") || @object_name.sub!(/\[\]\]$/, "]")
@object = retrieve_object(options.delete(:object))
+ @skip_default_ids = options.delete(:skip_default_ids)
+ @allow_method_names_outside_object = options.delete(:allow_method_names_outside_object)
@options = options
@auto_index = Regexp.last_match ? retrieve_autoindex(Regexp.last_match.pre_match) : nil
end
@@ -25,7 +27,11 @@ module ActionView
private
def value(object)
- object.public_send @method_name if object
+ if @allow_method_names_outside_object
+ object.public_send @method_name if object && object.respond_to?(@method_name)
+ else
+ object.public_send @method_name if object
+ end
end
def value_before_type_cast(object)
@@ -80,16 +86,22 @@ module ActionView
def add_default_name_and_id(options)
index = name_and_id_index(options)
- options["name"] = options.fetch("name"){ tag_name(options["multiple"], index) }
- options["id"] = options.fetch("id"){ tag_id(index) }
- if namespace = options.delete("namespace")
- options["id"] = options["id"] ? "#{namespace}_#{options['id']}" : namespace
+ options["name"] = options.fetch("name") { tag_name(options["multiple"], index) }
+
+ unless skip_default_ids?
+ options["id"] = options.fetch("id") { tag_id(index) }
+ if namespace = options.delete("namespace")
+ options["id"] = options["id"] ? "#{namespace}_#{options['id']}" : namespace
+ end
end
end
def tag_name(multiple = false, index = nil)
# a little duplication to construct less strings
- if index
+ case
+ when @object_name.empty?
+ "#{sanitized_method_name}#{"[]" if multiple}"
+ when index
"#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}"
else
"#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}"
@@ -98,7 +110,10 @@ module ActionView
def tag_id(index = nil)
# a little duplication to construct less strings
- if index
+ case
+ when @object_name.empty?
+ sanitized_method_name.dup
+ when index
"#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
else
"#{sanitized_object_name}_#{sanitized_method_name}"
@@ -110,7 +125,7 @@ module ActionView
end
def sanitized_method_name
- @sanitized_method_name ||= @method_name.sub(/\?$/,"")
+ @sanitized_method_name ||= @method_name.sub(/\?$/, "")
end
def sanitized_value(value)
@@ -152,7 +167,11 @@ module ActionView
end
def name_and_id_index(options)
- options.key?("index") ? options.delete("index") || "" : @auto_index
+ options.key?("index") ? options.delete("index") || "" : @auto_index
+ end
+
+ def skip_default_ids?
+ @skip_default_ids
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
index 2a6bf49567..7252d4f2d9 100644
--- a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
@@ -7,7 +7,7 @@ module ActionView
include CollectionHelpers
class CheckBoxBuilder < Builder # :nodoc:
- def check_box(extra_html_options={})
+ def check_box(extra_html_options = {})
html_options = extra_html_options.merge(@input_html_options)
html_options[:multiple] = true
@template_object.check_box(@object_name, @method_name, html_options, @value, nil)
@@ -24,7 +24,7 @@ module ActionView
builder.check_box + builder.label
end
- def hidden_field_name #:nodoc:
+ def hidden_field_name
"#{super}[]"
end
end
diff --git a/actionview/lib/action_view/helpers/tags/collection_helpers.rb b/actionview/lib/action_view/helpers/tags/collection_helpers.rb
index 70d7c484eb..75d237eb35 100644
--- a/actionview/lib/action_view/helpers/tags/collection_helpers.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_helpers.rb
@@ -17,7 +17,7 @@ module ActionView
@input_html_options = input_html_options
end
- def label(label_html_options={}, &block)
+ def label(label_html_options = {}, &block)
html_options = @input_html_options.slice(:index, :namespace).merge(label_html_options)
html_options[:for] ||= @input_html_options[:id] if @input_html_options[:id]
@@ -41,9 +41,9 @@ module ActionView
sanitize_attribute_name(value), text, value, html_options)
end
- # Generate default options for collection helpers, such as :checked and
- # :disabled.
- def default_html_options_for_collection(item, value) #:nodoc:
+ # Generate default options for collection helpers, such as :checked and
+ # :disabled.
+ def default_html_options_for_collection(item, value)
html_options = @html_options.dup
[:checked, :selected, :disabled, :readonly].each do |option|
@@ -67,11 +67,11 @@ module ActionView
html_options
end
- def sanitize_attribute_name(value) #:nodoc:
+ def sanitize_attribute_name(value)
"#{sanitized_method_name}_#{sanitized_value(value)}"
end
- def render_collection #:nodoc:
+ def render_collection
@collection.map do |item|
value = value_for_collection(item, @value_method)
text = value_for_collection(item, @text_method)
@@ -82,7 +82,7 @@ module ActionView
end.join.html_safe
end
- def render_collection_for(builder_class, &block) #:nodoc:
+ def render_collection_for(builder_class, &block)
options = @options.stringify_keys
rendered_collection = render_collection do |item, value, text, default_html_options|
builder = instantiate_builder(builder_class, item, value, text, default_html_options)
@@ -103,12 +103,12 @@ module ActionView
end
end
- def hidden_field #:nodoc:
+ def hidden_field
hidden_name = @html_options[:name] || hidden_field_name
@template_object.hidden_field_tag(hidden_name, "", id: nil)
end
- def hidden_field_name #:nodoc:
+ def hidden_field_name
"#{tag_name(false, @options[:index])}"
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 eed7941cd6..a5f72af9ff 100644
--- a/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb
@@ -7,7 +7,7 @@ module ActionView
include CollectionHelpers
class RadioButtonBuilder < Builder # :nodoc:
- def radio_button(extra_html_options={})
+ def radio_button(extra_html_options = {})
html_options = extra_html_options.merge(@input_html_options)
@template_object.radio_button(@object_name, @method_name, @value, html_options)
end
diff --git a/actionview/lib/action_view/helpers/tags/label.rb b/actionview/lib/action_view/helpers/tags/label.rb
index b31d5fda66..cab15ae201 100644
--- a/actionview/lib/action_view/helpers/tags/label.rb
+++ b/actionview/lib/action_view/helpers/tags/label.rb
@@ -73,6 +73,10 @@ module ActionView
def render_component(builder)
builder.translation
end
+
+ def skip_default_ids?
+ false # The id is used as the `for` attribute.
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/password_field.rb b/actionview/lib/action_view/helpers/tags/password_field.rb
index 274c27df82..444ef65074 100644
--- a/actionview/lib/action_view/helpers/tags/password_field.rb
+++ b/actionview/lib/action_view/helpers/tags/password_field.rb
@@ -3,7 +3,7 @@ module ActionView
module Tags # :nodoc:
class PasswordField < TextField # :nodoc:
def render
- @options = {value: nil}.merge!(@options)
+ @options = { value: nil }.merge!(@options)
super
end
end
diff --git a/actionview/lib/action_view/helpers/tags/select.rb b/actionview/lib/action_view/helpers/tags/select.rb
index 8cc34e3180..667c7e945a 100644
--- a/actionview/lib/action_view/helpers/tags/select.rb
+++ b/actionview/lib/action_view/helpers/tags/select.rb
@@ -28,10 +28,10 @@ module ActionView
private
- # Grouped choices look like this:
- #
- # [nil, []]
- # { nil => [] }
+ # Grouped choices look like this:
+ #
+ # [nil, []]
+ # { nil => [] }
def grouped_choices?
!@choices.empty? && @choices.first.respond_to?(:last) && Array === @choices.first.last
end
diff --git a/actionview/lib/action_view/helpers/tags/translator.rb b/actionview/lib/action_view/helpers/tags/translator.rb
index 62b1df81c6..ced835eaa8 100644
--- a/actionview/lib/action_view/helpers/tags/translator.rb
+++ b/actionview/lib/action_view/helpers/tags/translator.rb
@@ -14,6 +14,8 @@ module ActionView
translated_attribute || human_attribute_name
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 :object_name, :method_and_value, :scope, :model
diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb
index 0fea4df09c..07dccf5b41 100644
--- a/actionview/lib/action_view/helpers/text_helper.rb
+++ b/actionview/lib/action_view/helpers/text_helper.rb
@@ -151,7 +151,7 @@ module ActionView
# defined in <tt>:radius</tt> (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+,
# then the <tt>:omission</tt> option (which defaults to "...") will be prepended/appended accordingly. Use the
# <tt>:separator</tt> option to choose the delimitation. The resulting string will be stripped in any case. If the +phrase+
- # isn't found, nil is returned.
+ # isn't found, +nil+ is returned.
#
# excerpt('This is an example', 'an', radius: 5)
# # => ...s is an exam...
@@ -225,14 +225,7 @@ module ActionView
#
# pluralize(2, 'Person', locale: :de)
# # => 2 Personen
- def pluralize(count, singular, deprecated_plural = nil, plural: nil, locale: I18n.locale)
- if deprecated_plural
- ActiveSupport::Deprecation.warn("Passing plural as a positional argument " \
- "is deprecated and will be removed in Rails 5.1. Use e.g. " \
- "pluralize(1, 'person', plural: 'people') instead.")
- plural ||= deprecated_plural
- end
-
+ def pluralize(count, singular, plural_arg = nil, plural: plural_arg, locale: I18n.locale)
word = if (count == 1 || count =~ /^1(\.0+)?$/)
singular
else
diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb
index cbabaf5757..47ed41a129 100644
--- a/actionview/lib/action_view/helpers/translation_helper.rb
+++ b/actionview/lib/action_view/helpers/translation_helper.rb
@@ -1,6 +1,5 @@
require "action_view/helpers/tag_helper"
require "active_support/core_ext/string/access"
-require "active_support/core_ext/regexp"
require "i18n/exceptions"
module ActionView
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index 82aeeedad9..58a4a04dcb 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -2,7 +2,6 @@ require "action_view/helpers/javascript_helper"
require "active_support/core_ext/array/access"
require "active_support/core_ext/hash/keys"
require "active_support/core_ext/string/output_safety"
-require "active_support/core_ext/regexp"
module ActionView
# = Action View URL Helpers
@@ -106,10 +105,9 @@ module ActionView
# driver to prompt with the question specified (in this case, the
# resulting text would be <tt>question?</tt>. If the user accepts, the
# link is processed normally, otherwise no action is taken.
- # * <tt>:disable_with</tt> - Value of this parameter will be
- # used as the value for a disabled version of the submit
- # button when the form is submitted. This feature is provided
- # by the unobtrusive JavaScript driver.
+ # * <tt>:disable_with</tt> - Value of this parameter will be used as the
+ # name for a disabled version of the link. This feature is provided by
+ # the unobtrusive JavaScript driver.
#
# ==== Examples
# Because it relies on +url_for+, +link_to+ supports both older-style controller/action/id arguments
@@ -564,13 +562,13 @@ module ActionView
html_options = html_options.stringify_keys
html_options["data-remote"] = "true".freeze if link_to_remote_options?(options) || link_to_remote_options?(html_options)
- method = html_options.delete("method".freeze)
+ method = html_options.delete("method".freeze)
add_method_to_attributes!(html_options, method) if method
html_options
else
- link_to_remote_options?(options) ? {"data-remote" => "true".freeze} : {}
+ link_to_remote_options?(options) ? { "data-remote" => "true".freeze } : {}
end
end
@@ -587,7 +585,7 @@ module ActionView
html_options["data-method".freeze] = method
end
- def token_tag(token=nil, form_options: {})
+ def token_tag(token = nil, form_options: {})
if token != false && protect_against_forgery?
token ||= form_authenticity_token(form_options: form_options)
tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: token)
@@ -616,7 +614,18 @@ module ActionView
#
# to_form_params({ name: 'Denmark' }, 'country')
# # => [{name: 'country[name]', value: 'Denmark'}]
- def to_form_params(attribute, namespace = nil) # :nodoc:
+ def to_form_params(attribute, namespace = nil)
+ attribute = if attribute.respond_to?(:permitted?)
+ unless attribute.permitted?
+ raise ArgumentError, "Attempting to generate a buttom from non-sanitized request parameters!" \
+ " Whitelist and sanitize passed parameters to be secure."
+ end
+
+ attribute.to_h
+ else
+ attribute
+ end
+
params = []
case attribute
when Hash
diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb
index b2fcd823d2..18e395a67f 100644
--- a/actionview/lib/action_view/layouts.rb
+++ b/actionview/lib/action_view/layouts.rb
@@ -1,6 +1,5 @@
require "action_view/rendering"
require "active_support/core_ext/module/remove_method"
-require "active_support/core_ext/regexp"
module ActionView
# Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
@@ -92,16 +91,16 @@ module ActionView
# layout false
#
# In these examples, we have three implicit lookup scenarios:
- # * The BankController uses the "bank" layout.
- # * The ExchangeController uses the "exchange" layout.
- # * The CurrencyController inherits the layout from BankController.
+ # * The +BankController+ uses the "bank" layout.
+ # * The +ExchangeController+ uses the "exchange" layout.
+ # * The +CurrencyController+ inherits the layout from BankController.
#
# However, when a layout is explicitly set, the explicitly set layout wins:
- # * The InformationController uses the "information" layout, explicitly set.
- # * The TellerController also uses the "information" layout, because the parent explicitly set it.
- # * The EmployeeController uses the "employee" layout, because it set the layout to nil, resetting the parent configuration.
- # * The VaultController chooses a layout dynamically by calling the <tt>access_level_layout</tt> method.
- # * The TillController does not use a layout at all.
+ # * The +InformationController+ uses the "information" layout, explicitly set.
+ # * The +TellerController+ also uses the "information" layout, because the parent explicitly set it.
+ # * The +EmployeeController+ uses the "employee" layout, because it set the layout to +nil+, resetting the parent configuration.
+ # * The +VaultController+ chooses a layout dynamically by calling the <tt>access_level_layout</tt> method.
+ # * The +TillController+ does not use a layout at all.
#
# == Types of layouts
#
@@ -149,8 +148,8 @@ module ActionView
# The template will be looked always in <tt>app/views/layouts/</tt> folder. But you can point
# <tt>layouts</tt> folder direct also. <tt>layout "layouts/demo"</tt> is the same as <tt>layout "demo"</tt>.
#
- # Setting the layout to nil forces it to be looked up in the filesystem and fallbacks to the parent behavior if none exists.
- # Setting it to nil is useful to re-enable template lookup overriding a previous configuration set in the parent:
+ # Setting the layout to +nil+ forces it to be looked up in the filesystem and fallbacks to the parent behavior if none exists.
+ # Setting it to +nil+ is useful to re-enable template lookup overriding a previous configuration set in the parent:
#
# class ApplicationController < ActionController::Base
# layout "application"
@@ -224,12 +223,12 @@ module ActionView
module LayoutConditions # :nodoc:
private
- # Determines whether the current action has a layout definition by
- # checking the action name against the :only and :except conditions
- # set by the <tt>layout</tt> method.
- #
- # ==== Returns
- # * <tt>Boolean</tt> - True if the action has a layout definition, false otherwise.
+ # Determines whether the current action has a layout definition by
+ # checking the action name against the :only and :except conditions
+ # set by the <tt>layout</tt> method.
+ #
+ # ==== Returns
+ # * <tt>Boolean</tt> - True if the action has a layout definition, false otherwise.
def _conditional_layout?
return unless super
@@ -255,7 +254,7 @@ module ActionView
# true:: raise an ArgumentError
# nil:: Force default layout behavior with inheritance
#
- # Return value of Proc & Symbol arguments should be String, false, true or nil
+ # Return value of +Proc+ and +Symbol+ arguments should be +String+, +false+, +true+ or +nil+
# with the same meaning as described above.
# ==== Parameters
# * <tt>layout</tt> - The layout to use.
@@ -266,7 +265,7 @@ module ActionView
def layout(layout, conditions = {})
include LayoutConditions unless conditions.empty?
- conditions.each {|k, v| conditions[k] = Array(v).map(&:to_s) }
+ conditions.each { |k, v| conditions[k] = Array(v).map(&:to_s) }
self._layout_conditions = conditions
self._layout = layout
@@ -290,11 +289,12 @@ module ActionView
RUBY
end
- layout_definition = case _layout
- when String
- _layout.inspect
- when Symbol
- <<-RUBY
+ layout_definition = \
+ case _layout
+ when String
+ _layout.inspect
+ when Symbol
+ <<-RUBY
#{_layout}.tap do |layout|
return #{default_behavior} if layout.nil?
unless layout.is_a?(String) || !layout
@@ -303,21 +303,21 @@ module ActionView
end
end
RUBY
- when Proc
- define_method :_layout_from_proc, &_layout
- protected :_layout_from_proc
- <<-RUBY
+ when Proc
+ define_method :_layout_from_proc, &_layout
+ protected :_layout_from_proc
+ <<-RUBY
result = _layout_from_proc(#{_layout.arity == 0 ? '' : 'self'})
return #{default_behavior} if result.nil?
result
RUBY
- when false
- nil
- when true
- raise ArgumentError, "Layouts must be specified as a String, Symbol, Proc, false, or nil"
- when nil
- name_clause
- end
+ when false
+ nil
+ when true
+ raise ArgumentError, "Layouts must be specified as a String, Symbol, Proc, false, or nil"
+ when nil
+ name_clause
+ end
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
def _layout(formats)
@@ -333,12 +333,12 @@ module ActionView
private
- # If no layout is supplied, look for a template named the return
- # value of this method.
- #
- # ==== Returns
- # * <tt>String</tt> - A template name
- def _implied_layout_name # :nodoc:
+ # If no layout is supplied, look for a template named the return
+ # value of this method.
+ #
+ # ==== Returns
+ # * <tt>String</tt> - A template name
+ def _implied_layout_name
controller_path
end
end
@@ -404,11 +404,11 @@ module ActionView
#
# ==== Parameters
# * <tt>formats</tt> - The formats accepted to this layout
- # * <tt>require_layout</tt> - If set to true and layout is not found,
- # an +ArgumentError+ exception is raised (defaults to false)
+ # * <tt>require_layout</tt> - If set to +true+ and layout is not found,
+ # an +ArgumentError+ exception is raised (defaults to +false+)
#
# ==== Returns
- # * <tt>template</tt> - The template object for the default layout (or nil)
+ # * <tt>template</tt> - The template object for the default layout (or +nil+)
def _default_layout(formats, require_layout = false)
begin
value = _layout(formats) if action_has_layout?
@@ -425,7 +425,7 @@ module ActionView
end
def _include_layout?(options)
- (options.keys & [:body, :text, :plain, :html, :inline, :partial]).empty? || options.key?(:layout)
+ (options.keys & [:body, :plain, :html, :inline, :partial]).empty? || options.key?(:layout)
end
end
end
diff --git a/actionview/lib/action_view/log_subscriber.rb b/actionview/lib/action_view/log_subscriber.rb
index c9f308c2a2..d03e1a51b8 100644
--- a/actionview/lib/action_view/log_subscriber.rb
+++ b/actionview/lib/action_view/log_subscriber.rb
@@ -51,20 +51,20 @@ module ActionView
ActionView::Base.logger
end
- protected
+ private
EMPTY = ""
- def from_rails_root(string)
+ def from_rails_root(string) # :doc:
string = string.sub(rails_root, EMPTY)
string.sub!(VIEWS_PATTERN, EMPTY)
string
end
- def rails_root
+ def rails_root # :doc:
@root ||= "#{Rails.root}/"
end
- def render_count(payload)
+ def render_count(payload) # :doc:
if payload[:cache_hits]
"[#{payload[:cache_hits]} / #{payload[:count]} cache hits]"
else
@@ -72,7 +72,7 @@ module ActionView
end
end
- def cache_message(payload)
+ def cache_message(payload) # :doc:
if payload[:cache_hit]
"[cache hit]"
else
@@ -80,8 +80,6 @@ module ActionView
end
end
- private
-
def log_rendering_start(payload)
info do
message = " Rendering #{from_rails_root(payload[:identifier])}"
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index 9d6c762cc4..f385a7cd04 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -93,9 +93,9 @@ module ActionView
@cache = old_value
end
- protected
+ private
- def _set_detail(key, value)
+ def _set_detail(key, value) # :doc:
@details = @details.dup if @details_key
@details_key = nil
@details[key] = value
@@ -149,16 +149,16 @@ module ActionView
added_resolvers.times { view_paths.pop }
end
- protected
+ private
- def args_for_lookup(name, prefixes, partial, keys, details_options) #:nodoc:
+ def args_for_lookup(name, prefixes, partial, keys, details_options)
name, prefixes = normalize_name(name, prefixes)
details, details_key = detail_args_for(details_options)
[name, prefixes, partial || false, details, details_key, keys]
end
# Compute details hash and key according to user options (e.g. passed from #render).
- def detail_args_for(options)
+ def detail_args_for(options) # :doc:
return @details, details_key if options.empty? # most common path.
user_details = @details.merge(options)
@@ -171,13 +171,13 @@ module ActionView
[user_details, details_key]
end
- def args_for_any(name, prefixes, partial) # :nodoc:
+ def args_for_any(name, prefixes, partial)
name, prefixes = normalize_name(name, prefixes)
details, details_key = detail_args_for_any
[name, prefixes, partial || false, details, details_key]
end
- def detail_args_for_any # :nodoc:
+ def detail_args_for_any
@detail_args_for_any ||= begin
details = {}
@@ -200,11 +200,11 @@ module ActionView
# Support legacy foo.erb names even though we now ignore .erb
# as well as incorrectly putting part of the path in the template
# name instead of the prefix.
- def normalize_name(name, prefixes) #:nodoc:
+ def normalize_name(name, prefixes)
prefixes = prefixes.presence
parts = name.to_s.split("/".freeze)
parts.shift if parts.first.empty?
- name = parts.pop
+ name = parts.pop
return name, prefixes || [""] if parts.empty?
diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb
index dfb99f4ea9..d344d98f4b 100644
--- a/actionview/lib/action_view/railtie.rb
+++ b/actionview/lib/action_view/railtie.rb
@@ -3,7 +3,7 @@ require "rails"
module ActionView
# = Action View Railtie
- class Railtie < Rails::Railtie # :nodoc:
+ class Railtie < Rails::Engine # :nodoc:
config.action_view = ActiveSupport::OrderedOptions.new
config.action_view.embed_authenticity_token_in_remote_forms = false
config.action_view.debug_missing_translation = true
@@ -23,7 +23,7 @@ module ActionView
initializer "action_view.set_configs" do |app|
ActiveSupport.on_load(:action_view) do
- app.config.action_view.each do |k,v|
+ app.config.action_view.each do |k, v|
send "#{k}=", v
end
end
@@ -39,8 +39,8 @@ module ActionView
initializer "action_view.per_request_digest_cache" do |app|
ActiveSupport.on_load(:action_view) do
- if app.config.consider_all_requests_local
- app.executor.to_run { ActionView::LookupContext::DetailsKey.clear }
+ unless ActionView::Resolver.caching?
+ app.executor.to_run ActionView::Digestor::PerExecutionDigestCacheExpiry
end
end
end
diff --git a/actionview/lib/action_view/record_identifier.rb b/actionview/lib/action_view/record_identifier.rb
index b39acfa0b5..48bea315a9 100644
--- a/actionview/lib/action_view/record_identifier.rb
+++ b/actionview/lib/action_view/record_identifier.rb
@@ -92,7 +92,7 @@ module ActionView
end
end
- protected
+ private
# Returns a string representation of the key attribute(s) that is suitable for use in an HTML DOM id.
# This can be overwritten to customize the default generated string representation if desired.
@@ -102,7 +102,7 @@ module ActionView
# overwritten version of the method. By default, this implementation passes the key string through a
# method that replaces all characters that are invalid inside DOM ids, with valid ones. You need to
# make sure yourself that your dom ids are valid, in case you overwrite this method.
- def record_key_for_dom_id(record)
+ def record_key_for_dom_id(record) # :doc:
key = convert_to_model(record).to_key
key ? key.join(JOIN) : key
end
diff --git a/actionview/lib/action_view/renderer/abstract_renderer.rb b/actionview/lib/action_view/renderer/abstract_renderer.rb
index 3c85be49cd..0b315eb569 100644
--- a/actionview/lib/action_view/renderer/abstract_renderer.rb
+++ b/actionview/lib/action_view/renderer/abstract_renderer.rb
@@ -25,9 +25,9 @@ module ActionView
raise NotImplementedError
end
- protected
+ private
- def extract_details(options)
+ def extract_details(options) # :doc:
@lookup_context.registered_details.each_with_object({}) do |key, details|
value = options[key]
@@ -35,7 +35,7 @@ module ActionView
end
end
- def instrument(name, **options)
+ def instrument(name, **options) # :doc:
options[:identifier] ||= (@template && @template.identifier) || @path
ActiveSupport::Notifications.instrument("render_#{name}.action_view", options) do |payload|
@@ -43,7 +43,7 @@ module ActionView
end
end
- def prepend_formats(formats)
+ def prepend_formats(formats) # :doc:
formats = Array(formats)
return if formats.empty? || @lookup_context.html_fallback_for_js
diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb
index 92bd0ccf91..b8a79da97f 100644
--- a/actionview/lib/action_view/renderer/partial_renderer.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer.rb
@@ -1,5 +1,4 @@
require "concurrent/map"
-require "active_support/core_ext/regexp"
require "action_view/renderer/partial_renderer/collection_caching"
module ActionView
@@ -99,7 +98,7 @@ module ActionView
#
# <%= render partial: "ad", collection: @advertisements, spacer_template: "ad_divider" %>
#
- # If the given <tt>:collection</tt> is nil or empty, <tt>render</tt> will return nil. This will allow you
+ # If the given <tt>:collection</tt> is +nil+ or empty, <tt>render</tt> will return nil. This will allow you
# to specify a text which will displayed instead by using this form:
#
# <%= render(partial: "ad", collection: @advertisements) || "There's no ad to be displayed" %>
@@ -333,8 +332,6 @@ module ActionView
view, locals, block = @view, @locals, @block
object, as = @object, @variable
- view.instance_variable_set(:@log_payload_for_partial_render, payload)
-
if !block && (layout = @options[:layout])
layout = find_template(layout.to_s, @template_keys)
end
@@ -346,20 +343,21 @@ module ActionView
view._layout_for(*name, &block)
end
- content = layout.render(view, locals){ content } if layout
+ content = layout.render(view, locals) { content } if layout
+ payload[:cache_hit] = view.cache_hit
content
end
end
- # Sets up instance variables needed for rendering a partial. This method
- # finds the options and details and extracts them. The method also contains
- # logic that handles the type of object passed in as the partial.
- #
- # If +options[:partial]+ is a string, then the +@path+ instance variable is
- # set to that string. Otherwise, the +options[:partial]+ object must
- # respond to +to_partial_path+ in order to setup the path.
+ # Sets up instance variables needed for rendering a partial. This method
+ # finds the options and details and extracts them. The method also contains
+ # logic that handles the type of object passed in as the partial.
+ #
+ # If +options[:partial]+ is a string, then the +@path+ instance variable is
+ # set to that string. Otherwise, the +options[:partial]+ object must
+ # respond to +to_partial_path+ in order to setup the path.
def setup(context, options, block)
- @view = context
+ @view = context
@options = options
@block = block
@@ -467,13 +465,13 @@ module ActionView
end
end
- # Obtains the path to where the object's partial is located. If the object
- # responds to +to_partial_path+, then +to_partial_path+ will be called and
- # will provide the path. If the object does not respond to +to_partial_path+,
- # then an +ArgumentError+ is raised.
- #
- # If +prefix_partial_path_with_controller_namespace+ is true, then this
- # method will prefix the partial paths with a namespace.
+ # Obtains the path to where the object's partial is located. If the object
+ # responds to +to_partial_path+, then +to_partial_path+ will be called and
+ # will provide the path. If the object does not respond to +to_partial_path+,
+ # then an +ArgumentError+ is raised.
+ #
+ # If +prefix_partial_path_with_controller_namespace+ is true, then this
+ # method will prefix the partial paths with a namespace.
def partial_path(object = @object)
object = object.to_model if object.respond_to?(:to_model)
diff --git a/actionview/lib/action_view/renderer/streaming_template_renderer.rb b/actionview/lib/action_view/renderer/streaming_template_renderer.rb
index 0323ebef6a..7ede034492 100644
--- a/actionview/lib/action_view/renderer/streaming_template_renderer.rb
+++ b/actionview/lib/action_view/renderer/streaming_template_renderer.rb
@@ -27,9 +27,9 @@ module ActionView
private
- # This is the same logging logic as in ShowExceptions middleware.
- # TODO Once "exceptron" is in, refactor this piece to simply re-use exceptron.
- def log_error(exception) #:nodoc:
+ # This is the same logging logic as in ShowExceptions middleware.
+ # TODO Once "exceptron" is in, refactor this piece to simply re-use exceptron.
+ def log_error(exception)
logger = ActionView::Base.logger
return unless logger
diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb
index 0151653b73..54317199de 100644
--- a/actionview/lib/action_view/renderer/template_renderer.rb
+++ b/actionview/lib/action_view/renderer/template_renderer.rb
@@ -16,14 +16,12 @@ module ActionView
private
- # Determine the template to be rendered using the given options.
+ # Determine the template to be rendered using the given options.
def determine_template(options)
keys = options.has_key?(:locals) ? options[:locals].keys : []
if options.key?(:body)
Template::Text.new(options[:body])
- elsif options.key?(:text)
- Template::Text.new(options[:text], formats.first)
elsif options.key?(:plain)
Template::Text.new(options[:plain])
elsif options.key?(:html)
@@ -40,13 +38,13 @@ module ActionView
find_template(options[:template], options[:prefixes], false, keys, @details)
end
else
- raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :html, :text or :body option."
+ raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :html or :body option."
end
end
- # Renders the given template. A string representing the layout can be
- # supplied as well.
- def render_template(template, layout_name = nil, locals = nil) #:nodoc:
+ # Renders the given template. A string representing the layout can be
+ # supplied as well.
+ def render_template(template, layout_name = nil, locals = nil)
view, locals = @view, locals || {}
render_with_layout(layout_name, locals) do |layout|
@@ -56,22 +54,22 @@ module ActionView
end
end
- def render_with_layout(path, locals) #:nodoc:
+ def render_with_layout(path, locals)
layout = path && find_layout(path, locals.keys, [formats.first])
content = yield(layout)
if layout
view = @view
view.view_flow.set(:layout, content)
- layout.render(view, locals){ |*name| view._layout_for(*name) }
+ layout.render(view, locals) { |*name| view._layout_for(*name) }
else
content
end
end
- # This is the method which actually finds the layout using details in the lookup
- # context object. If no layout is found, it checks if at least a layout with
- # the given name exists across all details before raising the error.
+ # This is the method which actually finds the layout using details in the lookup
+ # context object. If no layout is found, it checks if at least a layout with
+ # the given name exists across all details before raising the error.
def find_layout(layout, keys, formats)
resolve_layout(layout, keys, formats)
end
diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb
index 3ca7f9d220..0e72316eb7 100644
--- a/actionview/lib/action_view/rendering.rb
+++ b/actionview/lib/action_view/rendering.rb
@@ -91,7 +91,7 @@ module ActionView
# Find and render a template based on the options given.
# :api: private
- def _render_template(options) #:nodoc:
+ def _render_template(options)
variant = options.delete(:variant)
assigns = options.delete(:assigns)
context = view_context
@@ -104,7 +104,7 @@ module ActionView
end
# Assign the rendered format to look up context.
- def _process_format(format) #:nodoc:
+ def _process_format(format)
super
lookup_context.formats = [format.to_sym]
lookup_context.rendered_format = lookup_context.formats.first
@@ -113,7 +113,7 @@ module ActionView
# Normalize args by converting render "foo" to render :action => "foo" and
# render "foo/bar" to render :template => "foo/bar".
# :api: private
- def _normalize_args(action=nil, options={})
+ def _normalize_args(action = nil, options = {})
options = super(action, options)
case action
when NilClass
diff --git a/actionview/lib/action_view/routing_url_for.rb b/actionview/lib/action_view/routing_url_for.rb
index 669cffab1a..687ba7c1b4 100644
--- a/actionview/lib/action_view/routing_url_for.rb
+++ b/actionview/lib/action_view/routing_url_for.rb
@@ -122,18 +122,15 @@ module ActionView
controller.url_options
end
- def _routes_context #:nodoc:
- controller
- end
- protected :_routes_context
-
- def optimize_routes_generation? #:nodoc:
- controller.respond_to?(:optimize_routes_generation?, true) ?
- controller.optimize_routes_generation? : super
- end
- protected :optimize_routes_generation?
-
private
+ def _routes_context
+ controller
+ end
+
+ def optimize_routes_generation?
+ controller.respond_to?(:optimize_routes_generation?, true) ?
+ controller.optimize_routes_generation? : super
+ end
def _generate_paths_by_default
true
diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb
index 5480501988..4b793c3b16 100644
--- a/actionview/lib/action_view/template.rb
+++ b/actionview/lib/action_view/template.rb
@@ -140,7 +140,7 @@ module ActionView
end
# Returns whether the underlying handler supports streaming. If so,
- # a streaming buffer *may* be passed when it start rendering.
+ # a streaming buffer *may* be passed when it starts rendering.
def supports_streaming?
handler.respond_to?(:supports_streaming?) && handler.supports_streaming?
end
@@ -151,8 +151,8 @@ module ActionView
# This method is instrumented as "!render_template.action_view". Notice that
# we use a bang in this instrumentation because you don't want to
# consume this in production. This is only slow if it's being listened to.
- def render(view, locals, buffer=nil, &block)
- instrument("!render_template".freeze) do
+ def render(view, locals, buffer = nil, &block)
+ instrument_render_template do
compile!(view)
view.send(method_name, locals, buffer, &block)
end
@@ -231,11 +231,11 @@ module ActionView
end
end
- protected
+ private
# Compile a template. This method ensures a template is compiled
# just once and removes the source after it is compiled.
- def compile!(view) #:nodoc:
+ def compile!(view)
return if @compiled
# Templates can be used concurrently in threaded environments
@@ -276,9 +276,8 @@ module ActionView
# encode the source into <tt>Encoding.default_internal</tt>.
# In general, this means that templates will be UTF-8 inside of Rails,
# regardless of the original source encoding.
- def compile(mod) #:nodoc:
+ def compile(mod)
encode!
- method_name = self.method_name
code = @handler.call(self)
# Make sure that the resulting String to be eval'd is in the
@@ -309,7 +308,7 @@ module ActionView
ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
end
- def handle_render_error(view, e) #:nodoc:
+ def handle_render_error(view, e)
if e.is_a?(Template::Error)
e.sub_template_of(self)
raise e
@@ -323,12 +322,17 @@ module ActionView
end
end
- def locals_code #:nodoc:
+ def locals_code
+ # Only locals with valid variable names get set directly. Others will
+ # still be available in local_assigns.
+ locals = @locals - Module::RUBY_RESERVED_KEYWORDS
+ locals = locals.grep(/\A(?![A-Z0-9])(?:[[:alnum:]_]|[^\0-\177])+\z/)
+
# 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("") { |key, code| code << "#{key} = #{key} = local_assigns[:#{key}];" }
end
- def method_name #:nodoc:
+ def method_name
@method_name ||= begin
m = "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}"
m.tr!("-".freeze, "_".freeze)
@@ -336,18 +340,20 @@ module ActionView
end
end
- def identifier_method_name #:nodoc:
+ def identifier_method_name
inspect.tr("^a-z_".freeze, "_".freeze)
end
- def instrument(action, &block)
- payload = { virtual_path: @virtual_path, identifier: @identifier }
- case action
- when "!render_template".freeze
- ActiveSupport::Notifications.instrument("!render_template.action_view".freeze, payload, &block)
- else
- ActiveSupport::Notifications.instrument("#{action}.action_view".freeze, payload, &block)
- end
+ def instrument(action, &block) # :doc:
+ ActiveSupport::Notifications.instrument("#{action}.action_view".freeze, instrument_payload, &block)
+ end
+
+ def instrument_render_template(&block)
+ ActiveSupport::Notifications.instrument("!render_template.action_view".freeze, instrument_payload, &block)
+ end
+
+ def instrument_payload
+ { virtual_path: @virtual_path, identifier: @identifier }
end
end
end
diff --git a/actionview/lib/action_view/template/error.rb b/actionview/lib/action_view/template/error.rb
index b95e5236a0..cc90477190 100644
--- a/actionview/lib/action_view/template/error.rb
+++ b/actionview/lib/action_view/template/error.rb
@@ -1,5 +1,4 @@
require "active_support/core_ext/enumerable"
-require "active_support/core_ext/regexp"
module ActionView
# = Action View Errors
@@ -63,23 +62,13 @@ module ActionView
# Override to prevent #cause resetting during re-raise.
attr_reader :cause
- def initialize(template, original_exception = nil)
- if original_exception
- ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \
- "Exceptions will automatically capture the original exception.", caller)
- end
-
+ def initialize(template)
super($!.message)
set_backtrace($!.backtrace)
@cause = $!
@template, @sub_templates = template, nil
end
- def original_exception
- ActiveSupport::Deprecation.warn("#original_exception is deprecated. Use #cause instead.", caller)
- cause
- end
-
def file_name
@template.identifier
end
diff --git a/actionview/lib/action_view/template/handlers/builder.rb b/actionview/lib/action_view/template/handlers/builder.rb
index e08a5b5db8..494b543152 100644
--- a/actionview/lib/action_view/template/handlers/builder.rb
+++ b/actionview/lib/action_view/template/handlers/builder.rb
@@ -13,9 +13,9 @@ module ActionView
";xml.target!;"
end
- protected
+ private
- def require_engine
+ def require_engine # :doc:
@required ||= begin
require "builder"
true
diff --git a/actionview/lib/action_view/template/handlers/erb.rb b/actionview/lib/action_view/template/handlers/erb.rb
index 6f07de1813..5d047a6991 100644
--- a/actionview/lib/action_view/template/handlers/erb.rb
+++ b/actionview/lib/action_view/template/handlers/erb.rb
@@ -1,5 +1,4 @@
require "erubis"
-require "active_support/core_ext/regexp"
module ActionView
class Template
diff --git a/actionview/lib/action_view/template/html.rb b/actionview/lib/action_view/template/html.rb
index ddbd2ea36a..0ffae10432 100644
--- a/actionview/lib/action_view/template/html.rb
+++ b/actionview/lib/action_view/template/html.rb
@@ -14,9 +14,7 @@ module ActionView #:nodoc:
"html template"
end
- def inspect
- "html template"
- end
+ alias_method :inspect, :identifier
def to_str
ERB::Util.h(@string)
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index 33d60be144..9da13663d7 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -42,10 +42,10 @@ module ActionView
end
# preallocate all the default blocks for performance/memory consumption reasons
- PARTIAL_BLOCK = lambda {|cache, partial| cache[partial] = SmallCache.new}
- PREFIX_BLOCK = lambda {|cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK)}
- NAME_BLOCK = lambda {|cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK)}
- KEY_BLOCK = lambda {|cache, key| cache[key] = SmallCache.new(&NAME_BLOCK)}
+ PARTIAL_BLOCK = lambda { |cache, partial| cache[partial] = SmallCache.new }
+ PREFIX_BLOCK = lambda { |cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK) }
+ NAME_BLOCK = lambda { |cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK) }
+ KEY_BLOCK = lambda { |cache, key| cache[key] = SmallCache.new(&NAME_BLOCK) }
# usually a majority of template look ups return nothing, use this canonical preallocated array to save memory
NO_TEMPLATES = [].freeze
@@ -141,13 +141,13 @@ module ActionView
end
# Normalizes the arguments and passes it on to find_templates.
- def find_all(name, prefix=nil, partial=false, details={}, key=nil, locals=[])
+ def find_all(name, prefix = nil, partial = false, details = {}, key = nil, locals = [])
cached(key, [name, prefix, partial], details, locals) do
find_templates(name, prefix, partial, details)
end
end
- def find_all_anywhere(name, prefix, partial=false, details={}, key=nil, locals=[])
+ def find_all_anywhere(name, prefix, partial = false, details = {}, key = nil, locals = [])
cached(key, [name, prefix, partial], details, locals) do
find_templates(name, prefix, partial, details, true)
end
@@ -177,7 +177,7 @@ module ActionView
# always check the cache before hitting the resolver. Otherwise,
# it always hits the resolver but if the key is present, check if the
# resolver is fresher before returning it.
- def cached(key, path_info, details, locals) #:nodoc:
+ def cached(key, path_info, details, locals)
name, prefix, partial = path_info
locals = locals.map(&:to_s).sort!
@@ -191,7 +191,7 @@ module ActionView
end
# Ensures all the resolver information is set in the template.
- def decorate(templates, path_info, details, locals) #:nodoc:
+ def decorate(templates, path_info, details, locals)
cached = nil
templates.each do |t|
t.locals = locals
@@ -207,7 +207,7 @@ module ActionView
EXTENSIONS = { locale: ".", formats: ".", variants: "+", handlers: "." }
DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"
- def initialize(pattern=nil)
+ def initialize(pattern = nil)
@pattern = pattern || DEFAULT_PATTERN
super()
end
@@ -256,7 +256,7 @@ module ActionView
filename.start_with?(path)
end
- # Helper for building query glob string based on resolver's pattern.
+ # Helper for building query glob string based on resolver's pattern.
def build_query(path, details)
query = @pattern.dup
@@ -281,14 +281,14 @@ module ActionView
entry.gsub(/[*?{}\[\]]/, '\\\\\\&'.freeze)
end
- # Returns the file mtime from the filesystem.
+ # Returns the file mtime from the filesystem.
def mtime(p)
File.mtime(p)
end
- # Extract handler, formats and variant from path. If a format cannot be found neither
- # from the path, or the handler, we should return the array of formats given
- # to the resolver.
+ # Extract handler, formats and variant from path. If a format cannot be found neither
+ # from the path, or the handler, we should return the array of formats given
+ # to the resolver.
def extract_handler_and_format_and_variant(path, default_formats)
pieces = File.basename(path).split(".".freeze)
pieces.shift
@@ -297,7 +297,7 @@ module ActionView
handler = Template.handler_for_extension(extension)
format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last
- format &&= Template::Types[format]
+ format &&= Template::Types[format]
[handler, format, variant]
end
@@ -342,7 +342,7 @@ module ActionView
# * <tt>:handlers</tt> - possible handlers (for example erb, haml, builder...)
#
class FileSystemResolver < PathResolver
- def initialize(path, pattern=nil)
+ def initialize(path, pattern = nil)
raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
super(pattern)
@path = File.expand_path(path)
diff --git a/actionview/lib/action_view/template/text.rb b/actionview/lib/action_view/template/text.rb
index 898593e702..380528d6ef 100644
--- a/actionview/lib/action_view/template/text.rb
+++ b/actionview/lib/action_view/template/text.rb
@@ -4,19 +4,16 @@ module ActionView #:nodoc:
class Text #:nodoc:
attr_accessor :type
- def initialize(string, type = nil)
+ def initialize(string)
@string = string.to_s
- @type = Types[type] || type if type
- @type ||= Types[:text]
+ @type = Types[:text]
end
def identifier
"text template"
end
- def inspect
- "text template"
- end
+ alias_method :inspect, :identifier
def to_str
@string
@@ -27,7 +24,7 @@ module ActionView #:nodoc:
end
def formats
- [@type.respond_to?(:ref) ? @type.ref : @type.to_s]
+ [@type.ref]
end
end
end
diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb
index 2945aceb3e..5fb7bb54b5 100644
--- a/actionview/lib/action_view/test_case.rb
+++ b/actionview/lib/action_view/test_case.rb
@@ -18,7 +18,7 @@ module ActionView
end
def controller_path=(path)
- self.class.controller_path=(path)
+ self.class.controller_path = (path)
end
def initialize
@@ -145,13 +145,14 @@ module ActionView
def view_rendered?(view, expected_locals)
locals_for(view).any? do |actual_locals|
- expected_locals.all? {|key, value| value == actual_locals[key] }
+ expected_locals.all? { |key, value| value == actual_locals[key] }
end
end
end
included do
setup :setup_with_controller
+ ActiveSupport.run_load_hooks(:action_view_test_case, self)
end
private
@@ -269,8 +270,8 @@ module ActionView
end
if routes &&
- ( routes.named_routes.route_defined?(selector) ||
- routes.mounted_helpers.method_defined?(selector) )
+ (routes.named_routes.route_defined?(selector) ||
+ routes.mounted_helpers.method_defined?(selector))
@controller.__send__(selector, *args)
else
super
diff --git a/actionview/lib/action_view/testing/resolvers.rb b/actionview/lib/action_view/testing/resolvers.rb
index 1a92dd71b1..f4a7a9138c 100644
--- a/actionview/lib/action_view/testing/resolvers.rb
+++ b/actionview/lib/action_view/testing/resolvers.rb
@@ -1,4 +1,3 @@
-require "active_support/core_ext/regexp"
require "action_view/template/resolver"
module ActionView #:nodoc:
@@ -9,7 +8,7 @@ module ActionView #:nodoc:
class FixtureResolver < PathResolver
attr_reader :hash
- def initialize(hash = {}, pattern=nil)
+ def initialize(hash = {}, pattern = nil)
super(pattern)
@hash = hash
end
@@ -23,7 +22,7 @@ module ActionView #:nodoc:
def query(path, exts, formats, _)
query = ""
EXTENSIONS.each_key do |ext|
- query << "(" << exts[ext].map {|e| e && Regexp.escape(".#{e}") }.join("|") << "|)"
+ query << "(" << exts[ext].map { |e| e && Regexp.escape(".#{e}") }.join("|") << "|)"
end
query = /^(#{Regexp.escape(path)})#{query}$/
@@ -40,7 +39,7 @@ module ActionView #:nodoc:
)
end
- templates.sort_by {|t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size }
+ templates.sort_by { |t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size }
end
end
diff --git a/actionview/lib/action_view/view_paths.rb b/actionview/lib/action_view/view_paths.rb
index 6b6cbcf06a..a9638d1e6d 100644
--- a/actionview/lib/action_view/view_paths.rb
+++ b/actionview/lib/action_view/view_paths.rb
@@ -22,8 +22,8 @@ module ActionView
private
- # Override this method in your controller if you want to change paths prefixes for finding views.
- # Prefixes defined here will still be added to parents' <tt>._prefixes</tt>.
+ # Override this method in your controller if you want to change paths prefixes for finding views.
+ # Prefixes defined here will still be added to parents' <tt>._prefixes</tt>.
def local_prefixes
[controller_path]
end
@@ -43,13 +43,25 @@ module ActionView
end
def details_for_lookup
- { }
+ {}
end
+ # Append a path to the list of view paths for the current <tt>LookupContext</tt>.
+ #
+ # ==== Parameters
+ # * <tt>path</tt> - If a String is provided, it gets converted into
+ # the default view path. You may also provide a custom view path
+ # (see ActionView::PathSet for more information)
def append_view_path(path)
lookup_context.view_paths.push(*path)
end
+ # Prepend a path to the list of view paths for the current <tt>LookupContext</tt>.
+ #
+ # ==== Parameters
+ # * <tt>path</tt> - If a String is provided, it gets converted into
+ # the default view path. You may also provide a custom view path
+ # (see ActionView::PathSet for more information)
def prepend_view_path(path)
lookup_context.view_paths.unshift(*path)
end
diff --git a/actionview/package.json b/actionview/package.json
new file mode 100644
index 0000000000..ec3306c299
--- /dev/null
+++ b/actionview/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "rails-ujs",
+ "version": "0.0.1",
+ "description": "Ruby on Rails unobtrusive scripting adapter",
+ "main": "lib/assets/compiled/rails-ujs.js",
+ "files": [
+ "lib/assets/compiled/*.js"
+ ],
+ "directories": {
+ "test": "test"
+ },
+ "scripts": {
+ "build": "bundle exec blade build",
+ "test": "echo \"See the README: https://github.com/rails/rails-ujs#how-to-run-tests\" && exit 1",
+ "lint": "coffeelint src && eslint test/public/test",
+ },
+ "repository": {
+ "type": "git",
+ "url": "rails/rails"
+ },
+ "contributors": [
+ "Stephen St. Martin",
+ "Steve Schwartz",
+ "Dangyi Liu",
+ "All contributors"
+ ],
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/rails/rails/issues"
+ },
+ "homepage": "http://rubyonrails.org/",
+ "devDependencies": {
+ "coffeelint": "^1.15.7",
+ "eslint": "^2.13.1"
+ }
+}
diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb
index 1d1e938557..5bc4151087 100644
--- a/actionview/test/abstract_unit.rb
+++ b/actionview/test/abstract_unit.rb
@@ -163,13 +163,13 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
# Stub Rails dispatcher so it does not get controller references and
# simply return the controller#action as Rack::Body.
class StubDispatcher < ::ActionDispatch::Routing::RouteSet::Dispatcher
- protected
+ private
def controller_reference(controller_param)
controller_param
end
def dispatch(controller, action, env)
- [200, {"Content-Type" => "text/html"}, ["#{controller}##{action}"]]
+ [200, { "Content-Type" => "text/html" }, ["#{controller}##{action}"]]
end
end
@@ -204,7 +204,7 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
ActiveSupport::Dependencies.autoload_paths << path
yield
ensure
- ActiveSupport::Dependencies.autoload_paths.reject! {|p| p == path}
+ ActiveSupport::Dependencies.autoload_paths.reject! { |p| p == path }
ActiveSupport::Dependencies.clear
end
end
@@ -264,7 +264,7 @@ module ActionDispatch
class DebugExceptions
private
remove_method :stderr_logger
- # Silence logger
+ # Silence logger
def stderr_logger
nil
end
diff --git a/actionview/test/actionpack/abstract/abstract_controller_test.rb b/actionview/test/actionpack/abstract/abstract_controller_test.rb
index b3dbf921b5..a2cd3deb58 100644
--- a/actionview/test/actionpack/abstract/abstract_controller_test.rb
+++ b/actionview/test/actionpack/abstract/abstract_controller_test.rb
@@ -37,7 +37,7 @@ module AbstractController
def render(options = {})
if options.is_a?(String)
- options = {_template_name: options}
+ options = { _template_name: options }
end
super
end
@@ -189,10 +189,10 @@ module AbstractController
private
def self.layout(formats)
- find_template(name.underscore, {formats: formats}, _prefixes: ["layouts"])
+ find_template(name.underscore, { formats: formats }, _prefixes: ["layouts"])
rescue ActionView::MissingTemplate
begin
- find_template("application", {formats: formats}, _prefixes: ["layouts"])
+ find_template("application", { formats: formats }, _prefixes: ["layouts"])
rescue ActionView::MissingTemplate
end
end
@@ -227,7 +227,7 @@ module AbstractController
end
class ActionMissingRespondToActionController < AbstractController::Base
- # No actions
+ # No actions
private
def action_missing(action_name)
self.response_body = "success"
diff --git a/actionview/test/actionpack/abstract/helper_test.rb b/actionview/test/actionpack/abstract/helper_test.rb
index 5a2f0839e5..83237518d7 100644
--- a/actionview/test/actionpack/abstract/helper_test.rb
+++ b/actionview/test/actionpack/abstract/helper_test.rb
@@ -109,7 +109,7 @@ module AbstractController
class InvalidHelpersTest < ActiveSupport::TestCase
def test_controller_raise_error_about_real_require_problem
e = assert_raise(LoadError) { AbstractInvalidHelpers.helper(:invalid_require) }
- assert_equal "No such file to load -- very_invalid_file_name", e.message
+ assert_equal "No such file to load -- very_invalid_file_name.rb", e.message
end
def test_controller_raise_error_about_missing_helper
diff --git a/actionview/test/actionpack/controller/layout_test.rb b/actionview/test/actionpack/controller/layout_test.rb
index 00147d31f3..b79835ff34 100644
--- a/actionview/test/actionpack/controller/layout_test.rb
+++ b/actionview/test/actionpack/controller/layout_test.rb
@@ -1,6 +1,5 @@
require "abstract_unit"
require "active_support/core_ext/array/extract_options"
-require "active_support/core_ext/regexp"
# The view_paths array must be set on Base and not LayoutTest so that LayoutTest's inherited
# method has access to the view_paths array when looking for a layout to automatically assign.
@@ -253,7 +252,7 @@ class LayoutStatusIsRenderedTest < ActionController::TestCase
end
end
-unless /mswin|mingw/.match?(RbConfig::CONFIG["host_os"])
+unless Gem.win_platform?
class LayoutSymlinkedTest < LayoutTest
layout "symlinked/symlinked_layout"
end
diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb
index 453c2e75d6..b4a757d6b8 100644
--- a/actionview/test/actionpack/controller/render_test.rb
+++ b/actionview/test/actionpack/controller/render_test.rb
@@ -11,7 +11,7 @@ class Customer < Struct.new(:name, :id)
undef_method :to_json
- def to_xml(options={})
+ def to_xml(options = {})
if options[:builder]
options[:builder].name name
else
@@ -19,7 +19,7 @@ class Customer < Struct.new(:name, :id)
end
end
- def to_js(options={})
+ def to_js(options = {})
"name: #{name.inspect}"
end
alias :to_text :to_js
@@ -186,12 +186,12 @@ class TestController < ApplicationController
def render_file_with_locals
path = File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_locals")
- render file: path, locals: {secret: "in the sauce"}
+ 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"))
- render file: path, locals: {secret: "in the sauce"}
+ render file: path, locals: { secret: "in the sauce" }
end
def accessing_request_in_template
@@ -249,7 +249,7 @@ class TestController < ApplicationController
end
def render_line_offset
- render inline: "<% raise %>", locals: {foo: "bar"}
+ render inline: "<% raise %>", locals: { foo: "bar" }
end
def heading
@@ -593,7 +593,7 @@ class TestController < ApplicationController
end
def partial_with_hash_object
- render partial: "hash_object", object: {first_name: "Sam"}
+ render partial: "hash_object", object: { first_name: "Sam" }
end
def partial_with_nested_object
@@ -605,11 +605,11 @@ class TestController < ApplicationController
end
def partial_hash_collection
- render partial: "hash_object", collection: [ {first_name: "Pratik"}, {first_name: "Amy"} ]
+ render partial: "hash_object", collection: [ { first_name: "Pratik" }, { first_name: "Amy" } ]
end
def partial_hash_collection_with_locals
- render partial: "hash_greeting", collection: [ {first_name: "Pratik"}, {first_name: "Amy"} ], locals: { greeting: "Hola" }
+ render partial: "hash_greeting", collection: [ { first_name: "Pratik" }, { first_name: "Amy" } ], locals: { greeting: "Hola" }
end
def partial_with_implicit_local_assignment
diff --git a/actionview/test/actionpack/controller/view_paths_test.rb b/actionview/test/actionpack/controller/view_paths_test.rb
index 9471c76921..4c58b959a9 100644
--- a/actionview/test/actionpack/controller/view_paths_test.rb
+++ b/actionview/test/actionpack/controller/view_paths_test.rb
@@ -34,7 +34,7 @@ class ViewLoadPathsTest < ActionController::TestCase
end
def expand(array)
- array.map {|x| File.expand_path(x.to_s)}
+ array.map { |x| File.expand_path(x.to_s) }
end
def assert_paths(*paths)
diff --git a/actionview/test/active_record_unit.rb b/actionview/test/active_record_unit.rb
index c62d300830..7f94b7ebb4 100644
--- a/actionview/test/active_record_unit.rb
+++ b/actionview/test/active_record_unit.rb
@@ -64,7 +64,7 @@ class ActiveRecordTestConnector
end
def require_fixture_models
- Dir.glob(File.dirname(__FILE__) + "/fixtures/*.rb").each {|f| require f}
+ Dir.glob(File.dirname(__FILE__) + "/fixtures/*.rb").each { |f| require f }
end
end
end
diff --git a/actionview/test/activerecord/debug_helper_test.rb b/actionview/test/activerecord/debug_helper_test.rb
index 69f9d5d6d6..06ae555a03 100644
--- a/actionview/test/activerecord/debug_helper_test.rb
+++ b/actionview/test/activerecord/debug_helper_test.rb
@@ -11,7 +11,7 @@ class DebugHelperTest < ActionView::TestCase
end
def test_debug_with_marshal_error
- obj = -> { }
+ obj = -> {}
assert_match obj.inspect, Nokogiri.XML(debug(obj)).content
end
end
diff --git a/actionview/test/activerecord/form_helper_activerecord_test.rb b/actionview/test/activerecord/form_helper_activerecord_test.rb
index 6152ec4720..0f7960b408 100644
--- a/actionview/test/activerecord/form_helper_activerecord_test.rb
+++ b/actionview/test/activerecord/form_helper_activerecord_test.rb
@@ -52,7 +52,7 @@ class FormHelperActiveRecordTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
- protected
+ private
def hidden_fields(method = nil)
txt = %{<input name="utf8" type="hidden" value="&#x2713;" />}
diff --git a/actionview/test/activerecord/polymorphic_routes_test.rb b/actionview/test/activerecord/polymorphic_routes_test.rb
index 952ed72aa9..8495949975 100644
--- a/actionview/test/activerecord/polymorphic_routes_test.rb
+++ b/actionview/test/activerecord/polymorphic_routes_test.rb
@@ -598,7 +598,7 @@ class PolymorphicRoutesTest < ActionController::TestCase
end
end
- # Tests for uncountable names
+ # Tests for uncountable names
def test_uncountable_resource
with_test_routes do
@series.save
diff --git a/actionview/test/activerecord/relation_cache_test.rb b/actionview/test/activerecord/relation_cache_test.rb
index 15ddcdd5f6..43f7242ee9 100644
--- a/actionview/test/activerecord/relation_cache_test.rb
+++ b/actionview/test/activerecord/relation_cache_test.rb
@@ -1,6 +1,6 @@
require "active_record_unit"
-class RelationCacheTest < ActionView::TestCase
+class RelationCacheTest < ActionView::TestCase
tests ActionView::Helpers::CacheHelper
def setup
@@ -9,7 +9,7 @@ class RelationCacheTest < ActionView::TestCase
end
def test_cache_relation_other
- cache(Project.all){ concat("Hello World") }
+ cache(Project.all) { concat("Hello World") }
assert_equal "Hello World", controller.cache_store.read("views/projects-#{Project.count}/")
end
diff --git a/actionview/test/fixtures/test/render_file_inspect_local_assigns.erb b/actionview/test/fixtures/test/render_file_inspect_local_assigns.erb
new file mode 100644
index 0000000000..aea5c351c5
--- /dev/null
+++ b/actionview/test/fixtures/test/render_file_inspect_local_assigns.erb
@@ -0,0 +1 @@
+<%= local_assigns.inspect.html_safe %> \ No newline at end of file
diff --git a/actionview/test/fixtures/test/render_file_unicode_local.erb b/actionview/test/fixtures/test/render_file_unicode_local.erb
new file mode 100644
index 0000000000..cbfd040a76
--- /dev/null
+++ b/actionview/test/fixtures/test/render_file_unicode_local.erb
@@ -0,0 +1 @@
+<%= 🎃 %> \ No newline at end of file
diff --git a/actionview/test/fixtures/test/render_file_with_ruby_keyword_locals.erb b/actionview/test/fixtures/test/render_file_with_ruby_keyword_locals.erb
new file mode 100644
index 0000000000..7e3fe6c6d9
--- /dev/null
+++ b/actionview/test/fixtures/test/render_file_with_ruby_keyword_locals.erb
@@ -0,0 +1 @@
+The class is <%= local_assigns[:class] %> \ No newline at end of file
diff --git a/actionview/test/fixtures/test/test_template_with_delegation_reserved_keywords.erb b/actionview/test/fixtures/test/test_template_with_delegation_reserved_keywords.erb
new file mode 100644
index 0000000000..edfe52e422
--- /dev/null
+++ b/actionview/test/fixtures/test/test_template_with_delegation_reserved_keywords.erb
@@ -0,0 +1 @@
+<%= _ %> <%= arg %> <%= args %> <%= block %> \ No newline at end of file
diff --git a/actionview/test/lib/controller/fake_models.rb b/actionview/test/lib/controller/fake_models.rb
index cc5f5c1d59..80649db88b 100644
--- a/actionview/test/lib/controller/fake_models.rb
+++ b/actionview/test/lib/controller/fake_models.rb
@@ -6,7 +6,7 @@ class Customer < Struct.new(:name, :id)
undef_method :to_json
- def to_xml(options={})
+ def to_xml(options = {})
if options[:builder]
options[:builder].name name
else
@@ -14,7 +14,7 @@ class Customer < Struct.new(:name, :id)
end
end
- def to_js(options={})
+ def to_js(options = {})
"name: #{name.inspect}"
end
alias :to_text :to_js
diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb
index 135c3b5fab..07a6452cc1 100644
--- a/actionview/test/template/asset_tag_helper_test.rb
+++ b/actionview/test/template/asset_tag_helper_test.rb
@@ -470,9 +470,9 @@ class AssetTagHelperTest < ActionView::TestCase
end
def test_image_tag_does_not_modify_options
- options = {size: "16x10"}
+ options = { size: "16x10" }
image_tag("icon", options)
- assert_equal({size: "16x10"}, options)
+ assert_equal({ size: "16x10" }, options)
end
def test_image_tag_raises_an_error_for_competing_size_arguments
@@ -532,11 +532,11 @@ class AssetTagHelperTest < ActionView::TestCase
end
def test_video_audio_tag_does_not_modify_options
- options = {autoplay: true}
+ options = { autoplay: true }
video_tag("video", options)
- assert_equal({autoplay: true}, options)
+ assert_equal({ autoplay: true }, options)
audio_tag("audio", options)
- assert_equal({autoplay: true}, options)
+ assert_equal({ autoplay: true }, options)
end
def test_image_tag_interpreting_email_cid_correctly
@@ -630,7 +630,7 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase
end
def test_should_return_nothing_if_asset_host_isnt_configured
- assert_equal nil, compute_asset_host("foo")
+ assert_nil compute_asset_host("foo")
end
def test_should_current_request_host_is_always_returned_for_request
diff --git a/actionview/test/template/capture_helper_test.rb b/actionview/test/template/capture_helper_test.rb
index 84f5b721b0..7f37523eeb 100644
--- a/actionview/test/template/capture_helper_test.rb
+++ b/actionview/test/template/capture_helper_test.rb
@@ -42,7 +42,7 @@ class CaptureHelperTest < ActionView::TestCase
content_for :foo, "foo"
assert_equal "foo", content_for(:foo)
- content_for(:bar){ "bar" }
+ content_for(:bar) { "bar" }
assert_equal "bar", content_for(:bar)
end
@@ -127,18 +127,18 @@ class CaptureHelperTest < ActionView::TestCase
def test_content_for_returns_nil_when_writing
assert ! content_for?(:title)
- assert_equal nil, content_for(:title, "foo")
- assert_equal nil, content_for(:title) { output_buffer << "bar"; nil }
- assert_equal nil, content_for(:title) { output_buffer << " \n "; nil }
+ assert_nil content_for(:title, "foo")
+ assert_nil content_for(:title) { output_buffer << "bar"; nil }
+ assert_nil content_for(:title) { output_buffer << " \n "; nil }
assert_equal "foobar", content_for(:title)
- assert_equal nil, content_for(:title, "foo", flush: true)
- assert_equal nil, content_for(:title, flush: true) { output_buffer << "bar"; nil }
- assert_equal nil, content_for(:title, flush: true) { output_buffer << " \n "; nil }
+ assert_nil content_for(:title, "foo", flush: true)
+ assert_nil content_for(:title, flush: true) { output_buffer << "bar"; nil }
+ assert_nil content_for(:title, flush: true) { output_buffer << " \n "; nil }
assert_equal "bar", content_for(:title)
end
def test_content_for_returns_nil_when_content_missing
- assert_equal nil, content_for(:some_missing_key)
+ assert_nil content_for(:some_missing_key)
end
def test_content_for_question_mark
diff --git a/actionview/test/template/compiled_templates_test.rb b/actionview/test/template/compiled_templates_test.rb
index 7e3e5883b4..40ac867b38 100644
--- a/actionview/test/template/compiled_templates_test.rb
+++ b/actionview/test/template/compiled_templates_test.rb
@@ -9,6 +9,35 @@ class CompiledTemplatesTest < ActiveSupport::TestCase
assert_equal "This is nil: \n", render(template: "test/nil_return")
end
+ def test_template_with_ruby_keyword_locals
+ assert_equal "The class is foo",
+ render(file: "test/render_file_with_ruby_keyword_locals", locals: { class: "foo" })
+ end
+
+ def test_template_with_invalid_identifier_locals
+ locals = {
+ foo: "bar",
+ Foo: "bar",
+ "d-a-s-h-e-s": "",
+ "white space": "",
+ }
+ assert_equal locals.inspect, render(file: "test/render_file_inspect_local_assigns", locals: locals)
+ end
+
+ def test_template_with_delegation_reserved_keywords
+ locals = {
+ _: "one",
+ arg: "two",
+ args: "three",
+ block: "four",
+ }
+ assert_equal "one two three four", render(file: "test/test_template_with_delegation_reserved_keywords", locals: locals)
+ end
+
+ def test_template_with_unicode_identifier
+ assert_equal "🎂", render(file: "test/render_file_unicode_local", locals: { 🎃: "🎂" })
+ end
+
def test_template_gets_recompiled_when_using_different_keys_in_local_assigns
assert_equal "one", render(file: "test/render_file_with_locals_and_default")
assert_equal "two", render(file: "test/render_file_with_locals_and_default", locals: { secret: "two" })
diff --git a/actionview/test/template/date_helper_i18n_test.rb b/actionview/test/template/date_helper_i18n_test.rb
index a6559bd54f..207c8a683e 100644
--- a/actionview/test/template/date_helper_i18n_test.rb
+++ b/actionview/test/template/date_helper_i18n_test.rb
@@ -112,8 +112,8 @@ class DateHelperSelectTagsI18nTests < ActiveSupport::TestCase
end
def test_date_or_time_select_translates_prompts
- prompt_defaults = {year: "Year", month: "Month", day: "Day", hour: "Hour", minute: "Minute", second: "Seconds"}
- defaults = {[:'date.order', locale: "en", default: []] => %w(year month day)}
+ prompt_defaults = { year: "Year", month: "Month", day: "Day", hour: "Hour", minute: "Minute", second: "Seconds" }
+ defaults = { [:'date.order', locale: "en", default: []] => %w(year month day) }
prompt_defaults.each do |key, prompt|
defaults[[("datetime.prompts." + key.to_s).to_sym, locale: "en"]] = prompt
@@ -143,7 +143,7 @@ class DateHelperSelectTagsI18nTests < ActiveSupport::TestCase
end
def test_date_or_time_select_given_no_order_options_translates_order
- assert_called_with(I18n, :translate, [ [:'date.order', locale: "en", default: []], [:"date.month_names", {locale: "en"}] ], returns: %w(year month day)) do
+ assert_called_with(I18n, :translate, [ [:'date.order', locale: "en", default: []], [:"date.month_names", { locale: "en" }] ], returns: %w(year month day)) do
datetime_select("post", "updated_at", locale: "en")
end
end
@@ -157,7 +157,7 @@ class DateHelperSelectTagsI18nTests < ActiveSupport::TestCase
end
def test_date_or_time_select_given_symbol_keys
- assert_called_with(I18n, :translate, [ [:'date.order', locale: "en", default: []], [:"date.month_names", {locale: "en"}] ], returns: [:year, :month, :day]) do
+ assert_called_with(I18n, :translate, [ [:'date.order', locale: "en", default: []], [:"date.month_names", { locale: "en" }] ], returns: [:year, :month, :day]) do
datetime_select("post", "updated_at", locale: "en")
end
end
diff --git a/actionview/test/template/date_helper_test.rb b/actionview/test/template/date_helper_test.rb
index d83be49f40..a84ac18bee 100644
--- a/actionview/test/template/date_helper_test.rb
+++ b/actionview/test/template/date_helper_test.rb
@@ -18,7 +18,7 @@ class DateHelperTest < ActionView::TestCase
end
end
- def assert_distance_of_time_in_words(from, to=nil)
+ def assert_distance_of_time_in_words(from, to = nil)
to ||= from
# 0..1 minute with :include_seconds => true
@@ -167,9 +167,9 @@ class DateHelperTest < ActionView::TestCase
def test_distance_in_words_with_integers
assert_equal "1 minute", distance_of_time_in_words(59)
- assert_equal "about 1 hour", distance_of_time_in_words(60*60)
+ assert_equal "about 1 hour", distance_of_time_in_words(60 * 60)
assert_equal "1 minute", distance_of_time_in_words(0, 59)
- assert_equal "about 1 hour", distance_of_time_in_words(60*60, 0)
+ assert_equal "about 1 hour", distance_of_time_in_words(60 * 60, 0)
assert_equal "about 3 years", distance_of_time_in_words(10**8)
assert_equal "about 3 years", distance_of_time_in_words(0, 10**8)
end
@@ -385,7 +385,7 @@ class DateHelperTest < ActionView::TestCase
month_names = %w(Januar Februar Marts April Maj Juni Juli August September Oktober November December)
expected = %(<select id="date_month" name="date[month]">\n)
- 1.upto(12) { |month| expected << %(<option value="#{month}"#{' selected="selected"' if month == 8}>#{month_names[month-1]}</option>\n) }
+ 1.upto(12) { |month| expected << %(<option value="#{month}"#{' selected="selected"' if month == 8}>#{month_names[month - 1]}</option>\n) }
expected << "</select>\n"
assert_dom_equal expected, select_month(Time.mktime(2003, 8, 16), use_month_names: month_names)
@@ -500,7 +500,7 @@ class DateHelperTest < ActionView::TestCase
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
- assert_dom_equal expected, select_year(Time.mktime(2003, 8, 16), {start_year: 2003, end_year: 2005}, class: "selector", accesskey: "M")
+ assert_dom_equal expected, select_year(Time.mktime(2003, 8, 16), { start_year: 2003, end_year: 2005 }, class: "selector", accesskey: "M")
end
def test_select_year_with_default_prompt
@@ -848,7 +848,7 @@ class DateHelperTest < ActionView::TestCase
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"
- expected << %(<select id="date_first_year" name="date[first][year]">\n)
+ expected << %(<select id="date_first_year" name="date[first][year]">\n)
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,8 +881,8 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_date_with_no_start_year
- expected = %(<select id="date_first_year" name="date[first][year]">\n)
- (Date.today.year-5).upto(Date.today.year+1) do |y|
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ (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)
else
@@ -900,12 +900,12 @@ class DateHelperTest < ActionView::TestCase
expected << "</select>\n"
assert_dom_equal expected, select_date(
- Time.mktime(Date.today.year, 8, 16), end_year: Date.today.year+1, prefix: "date[first]"
+ Time.mktime(Date.today.year, 8, 16), end_year: Date.today.year + 1, prefix: "date[first]"
)
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)
2003.upto(2008) do |y|
if y == 2003
expected << %(<option value="#{y}" selected="selected">#{y}</option>\n)
@@ -929,8 +929,8 @@ 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)
- (Date.today.year-5).upto(Date.today.year+5) do |y|
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ (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)
else
@@ -969,8 +969,8 @@ 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)
- (Date.today.year-5).upto(Date.today.year+1) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ (Date.today.year - 5).upto(Date.today.year + 1) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
expected << "</select>\n"
expected << %(<select id="date_first_month" name="date[first][month]">\n)
@@ -981,7 +981,7 @@ class DateHelperTest < ActionView::TestCase
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">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"
- assert_dom_equal expected, select_date(0, end_year: Date.today.year+1, prefix: "date[first]")
+ assert_dom_equal expected, select_date(0, end_year: Date.today.year + 1, prefix: "date[first]")
end
def test_select_date_with_zero_value_and_no_end_year
@@ -1002,8 +1002,8 @@ 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)
- (Date.today.year-5).upto(Date.today.year+5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ (Date.today.year - 5).upto(Date.today.year + 5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
expected << "</select>\n"
expected << %(<select id="date_first_month" name="date[first][month]">\n)
@@ -1018,8 +1018,8 @@ 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)
- (Date.today.year-5).upto(Date.today.year+5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ (Date.today.year - 5).upto(Date.today.year + 5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
expected << "</select>\n"
expected << %(<select id="date_first_month" name="date[first][month]">\n)
@@ -1046,7 +1046,7 @@ class DateHelperTest < ActionView::TestCase
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"
- assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), {start_year: 2003, end_year: 2005, prefix: "date[first]"}, class: "selector")
+ assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { start_year: 2003, end_year: 2005, prefix: "date[first]" }, class: "selector")
end
def test_select_date_with_separator
@@ -1150,7 +1150,7 @@ class DateHelperTest < ActionView::TestCase
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"
- assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), {start_year: 2003, end_year: 2005, prefix: "date[first]", with_css_classes: true}, class: "datetime optional")
+ assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { start_year: 2003, end_year: 2005, prefix: "date[first]", with_css_classes: true }, class: "datetime optional")
end
def test_select_date_with_custom_with_css_classes_and_html_class_option
@@ -1166,7 +1166,7 @@ class DateHelperTest < ActionView::TestCase
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"
- assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), {start_year: 2003, end_year: 2005, with_css_classes: { year: "my-year", month: "my-month", day: "my-day" }}, class: "date optional")
+ assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { start_year: 2003, end_year: 2005, with_css_classes: { year: "my-year", month: "my-month", day: "my-day" } }, class: "date optional")
end
def test_select_date_with_partial_with_css_classes_and_html_class_option
@@ -1182,7 +1182,7 @@ class DateHelperTest < ActionView::TestCase
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"
- assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), {start_year: 2003, end_year: 2005, with_css_classes: { month: "my-month custom-grid" }}, class: "date optional")
+ assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { start_year: 2003, end_year: 2005, with_css_classes: { month: "my-month custom-grid" } }, class: "date optional")
end
def test_select_date_with_html_class_option
@@ -1286,8 +1286,8 @@ 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)
- (Date.today.year-5).upto(Date.today.year+5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ (Date.today.year - 5).upto(Date.today.year + 5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
expected << "</select>\n"
expected << %(<select id="date_first_month" name="date[first][month]">\n)
@@ -1338,7 +1338,7 @@ class DateHelperTest < ActionView::TestCase
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"
- assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), {start_year: 2003, end_year: 2005, prefix: "date[first]"}, class: "selector")
+ assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), { start_year: 2003, end_year: 2005, prefix: "date[first]" }, class: "selector")
end
def test_select_datetime_with_all_separators
@@ -1370,7 +1370,7 @@ class DateHelperTest < ActionView::TestCase
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"
- assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), { datetime_separator: "&mdash;", date_separator: "/", time_separator: ":", start_year: 2003, end_year: 2005, prefix: "date[first]"}, class: "selector")
+ assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), { datetime_separator: "&mdash;", date_separator: "/", time_separator: ":", start_year: 2003, end_year: 2005, prefix: "date[first]" }, class: "selector")
end
def test_select_datetime_should_work_with_date
@@ -1433,7 +1433,7 @@ class DateHelperTest < ActionView::TestCase
expected << "</select>\n"
assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), start_year: 2003, end_year: 2005, prefix: "date[first]",
- prompt: {day: "Choose day", month: "Choose month", year: "Choose year", hour: "Choose hour", minute: "Choose minute"})
+ prompt: { day: "Choose day", month: "Choose month", year: "Choose year", hour: "Choose hour", minute: "Choose minute" })
end
def test_select_datetime_with_generic_with_css_classes
@@ -1518,7 +1518,7 @@ class DateHelperTest < ActionView::TestCase
expected << "</select>\n"
assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), start_year: 2003, end_year: 2005, start_hour: 1, end_hour: 9, prefix: "date[first]",
- prompt: {day: "Choose day", month: "Choose month", year: "Choose year", hour: "Choose hour", minute: "Choose minute"})
+ prompt: { day: "Choose day", month: "Choose month", year: "Choose year", hour: "Choose hour", minute: "Choose minute" })
end
def test_select_datetime_with_hidden
@@ -1652,7 +1652,7 @@ class DateHelperTest < ActionView::TestCase
expected << "</select>\n"
assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), {}, class: "selector")
- assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), {include_seconds: false}, class: "selector")
+ assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), { include_seconds: false }, class: "selector")
end
def test_select_time_should_work_with_date
@@ -1705,11 +1705,11 @@ class DateHelperTest < ActionView::TestCase
expected << "</select>\n"
assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), include_seconds: true,
- prompt: {hour: "Choose hour", minute: "Choose minute", second: "Choose seconds"})
+ prompt: { hour: "Choose hour", minute: "Choose minute", second: "Choose seconds" })
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)
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)
@@ -1733,7 +1733,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)
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)
@@ -1824,7 +1824,7 @@ class DateHelperTest < ActionView::TestCase
expected << "</select>\n"
- assert_dom_equal expected, date_select("post", "written_on", selected: {day: 10, month: 07, year: 2004})
+ assert_dom_equal expected, date_select("post", "written_on", selected: { day: 10, month: 07, year: 2004 })
end
def test_date_select_with_selected_nil
@@ -1851,7 +1851,7 @@ class DateHelperTest < ActionView::TestCase
expected = "<input type=\"hidden\" id=\"post_written_on_3i\" name=\"post[written_on(3i)]\" value=\"1\" />\n"
- expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n}
+ 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}
expected << "</select>\n"
@@ -1882,7 +1882,7 @@ class DateHelperTest < ActionView::TestCase
expected = "<input type=\"hidden\" id=\"post_written_on_3i\" name=\"post[written_on(3i)]\" value=\"1\" />\n"
- expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n}
+ 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}
expected << "</select>\n"
@@ -1901,7 +1901,7 @@ class DateHelperTest < ActionView::TestCase
expected = "<input type=\"hidden\" id=\"post_written_on_3i\" disabled=\"disabled\" name=\"post[written_on(3i)]\" value=\"1\" />\n"
- expected << %{<select id="post_written_on_2i" disabled="disabled" name="post[written_on(2i)]">\n}
+ 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}
expected << "</select>\n"
@@ -2003,7 +2003,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}
1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 15}>#{i}</option>\n) }
expected << "</select>\n"
@@ -2011,7 +2011,7 @@ class DateHelperTest < ActionView::TestCase
1.upto(12) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 6}>#{Date::MONTHNAMES[i]}</option>\n) }
expected << "</select>\n"
- 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}
1999.upto(2009) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2004}>#{i}</option>\n) }
expected << "</select>\n"
@@ -2021,8 +2021,8 @@ class DateHelperTest < ActionView::TestCase
def test_date_select_with_nil
@post = Post.new
- start_year = Time.now.year-5
- end_year = Time.now.year+5
+ 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}
start_year.upto(end_year) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == Time.now.year}>#{i}</option>\n) }
expected << "</select>\n"
@@ -2041,8 +2041,8 @@ class DateHelperTest < ActionView::TestCase
def test_date_select_with_nil_and_blank
@post = Post.new
- start_year = Time.now.year-5
- end_year = Time.now.year+5
+ 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 << "<option value=\"\"></option>\n"
start_year.upto(end_year) { |i| expected << %(<option value="#{i}">#{i}</option>\n) }
@@ -2064,11 +2064,11 @@ class DateHelperTest < ActionView::TestCase
def test_date_select_with_nil_and_blank_and_order
@post = Post.new
- start_year = Time.now.year-5
- end_year = Time.now.year+5
+ start_year = Time.now.year - 5
+ end_year = Time.now.year + 5
expected = '<input name="post[written_on(3i)]" type="hidden" id="post_written_on_3i" value="1"/>' + "\n"
- 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}
expected << "<option value=\"\"></option>\n"
start_year.upto(end_year) { |i| expected << %(<option value="#{i}">#{i}</option>\n) }
expected << "</select>\n"
@@ -2084,8 +2084,8 @@ class DateHelperTest < ActionView::TestCase
def test_date_select_with_nil_and_blank_and_discard_month
@post = Post.new
- start_year = Time.now.year-5
- end_year = Time.now.year+5
+ 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 << "<option value=\"\"></option>\n"
@@ -2280,7 +2280,7 @@ class DateHelperTest < ActionView::TestCase
expected << "</select>\n"
- assert_dom_equal expected, date_select("post", "written_on", prompt: {year: "Choose year", month: "Choose month", day: "Choose day"})
+ assert_dom_equal expected, date_select("post", "written_on", prompt: { year: "Choose year", month: "Choose month", day: "Choose day" })
end
def test_date_select_with_generic_with_css_classes
@@ -2526,7 +2526,7 @@ class DateHelperTest < ActionView::TestCase
0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) }
expected << "</select>\n"
- assert_dom_equal expected, time_select("post", "written_on", prompt: {hour: "Choose hour", minute: "Choose minute"})
+ assert_dom_equal expected, time_select("post", "written_on", prompt: { hour: "Choose hour", minute: "Choose minute" })
end
def test_time_select_with_generic_with_css_classes
@@ -2782,7 +2782,7 @@ class DateHelperTest < ActionView::TestCase
def test_datetime_select_with_infinity # Float
@post = Post.new
- @post.updated_at = (-1.0/0)
+ @post.updated_at = (-1.0 / 0)
datetime_select("post", "updated_at")
end
@@ -2841,7 +2841,7 @@ class DateHelperTest < ActionView::TestCase
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">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"
- assert_dom_equal expected, datetime_select("post", "updated_at", start_year: 1999, end_year: 2009, prompt: {year: "Choose year", month: "Choose month", day: "Choose day", hour: "Choose hour", minute: "Choose minute"})
+ assert_dom_equal expected, datetime_select("post", "updated_at", start_year: 1999, end_year: 2009, prompt: { year: "Choose year", month: "Choose month", day: "Choose day", hour: "Choose hour", minute: "Choose minute" })
end
def test_datetime_select_with_generic_with_css_classes
@@ -2903,8 +2903,8 @@ 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)
- (Date.today.year-5).upto(Date.today.year+1) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ (Date.today.year - 5).upto(Date.today.year + 1) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
expected << "</select>\n"
expected << %(<select id="date_first_month" name="date[first][month]">\n)
@@ -2915,7 +2915,7 @@ class DateHelperTest < ActionView::TestCase
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">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"
- assert_dom_equal expected, select_date(0, end_year: Date.today.year+1, prefix: "date[first]")
+ assert_dom_equal expected, select_date(0, end_year: Date.today.year + 1, prefix: "date[first]")
end
def test_date_select_with_zero_value_and_no_end_year
@@ -2936,8 +2936,8 @@ 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)
- (Date.today.year-5).upto(Date.today.year+5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ (Date.today.year - 5).upto(Date.today.year + 5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
expected << "</select>\n"
expected << %(<select id="date_first_month" name="date[first][month]">\n)
@@ -2952,8 +2952,8 @@ 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)
- (Date.today.year-5).upto(Date.today.year+5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ (Date.today.year - 5).upto(Date.today.year + 5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
expected << "</select>\n"
expected << %(<select id="date_first_month" name="date[first][month]">\n)
@@ -2968,8 +2968,8 @@ 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)
- (Date.today.year-5).upto(Date.today.year+5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ (Date.today.year - 5).upto(Date.today.year + 5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
expected << "</select>\n"
expected << %(<select id="date_first_month" name="date[first][month]">\n)
@@ -3612,7 +3612,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_time_tag_with_given_block
- assert_match(/<time.*><span>Right now<\/span><\/time>/, time_tag(Time.now){ raw("<span>Right now</span>") })
+ assert_match(/<time.*><span>Right now<\/span><\/time>/, time_tag(Time.now) { raw("<span>Right now</span>") })
end
def test_time_tag_with_different_format
diff --git a/actionview/test/template/dependency_tracker_test.rb b/actionview/test/template/dependency_tracker_test.rb
index a077d4c448..89917035ff 100644
--- a/actionview/test/template/dependency_tracker_test.rb
+++ b/actionview/test/template/dependency_tracker_test.rb
@@ -15,8 +15,8 @@ class FakeTemplate
end
end
-Neckbeard = lambda {|template| template.source }
-Bowtie = lambda {|template| template.source }
+Neckbeard = lambda { |template| template.source }
+Bowtie = lambda { |template| template.source }
class DependencyTrackerTest < ActionView::TestCase
def tracker
diff --git a/actionview/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb
index dec75268aa..a814cab686 100644
--- a/actionview/test/template/digestor_test.rb
+++ b/actionview/test/template/digestor_test.rb
@@ -139,7 +139,7 @@ class TemplateDigestorTest < ActionView::TestCase
end
def test_getting_of_doubly_nested_dependencies
- doubly_nested = [{"comments/comments"=>["comments/comment"]}, "messages/message"]
+ doubly_nested = [{ "comments/comments" => ["comments/comment"] }, "messages/message"]
assert_equal doubly_nested, nested_dependencies("messages/peek")
end
@@ -150,13 +150,13 @@ class TemplateDigestorTest < ActionView::TestCase
end
def test_nested_template_deps
- nested_deps = ["messages/header", {"comments/comments"=>["comments/comment"]}, "messages/actions/move", "events/event", "messages/something_missing", "messages/something_missing_1", "messages/message", "messages/form"]
+ nested_deps = ["messages/header", { "comments/comments" => ["comments/comment"] }, "messages/actions/move", "events/event", "messages/something_missing", "messages/something_missing_1", "messages/message", "messages/form"]
assert_equal nested_deps, nested_dependencies("messages/show")
end
def test_nested_template_deps_with_non_default_rendered_format
finder.rendered_format = nil
- nested_deps = [{"comments/comments"=>["comments/comment"]}]
+ nested_deps = [{ "comments/comments" => ["comments/comment"] }]
assert_equal nested_deps, nested_dependencies("messages/thread")
end
diff --git a/actionview/test/template/form_collections_helper_test.rb b/actionview/test/template/form_collections_helper_test.rb
index 9eb4d8f58f..3774dcf872 100644
--- a/actionview/test/template/form_collections_helper_test.rb
+++ b/actionview/test/template/form_collections_helper_test.rb
@@ -94,7 +94,7 @@ class FormCollectionsHelperTest < ActionView::TestCase
end
test "collection radio accepts html options as the last element of array" do
- collection = [[1, true, {class: "foo"}], [0, false, {class: "bar"}]]
+ collection = [[1, true, { class: "foo" }], [0, false, { class: "bar" }]]
with_collection_radio_buttons :user, :active, collection, :second, :first
assert_select "input[type=radio][value=true].foo#user_active_true"
@@ -102,7 +102,7 @@ class FormCollectionsHelperTest < ActionView::TestCase
end
test "collection radio sets the label class defined inside the block" do
- collection = [[1, true, {class: "foo"}], [0, false, {class: "bar"}]]
+ collection = [[1, true, { class: "foo" }], [0, false, { class: "bar" }]]
with_collection_radio_buttons :user, :active, collection, :second, :first do |b|
b.label(class: "collection_radio_buttons")
end
@@ -112,7 +112,7 @@ class FormCollectionsHelperTest < ActionView::TestCase
end
test "collection radio does not include the input class in the respective label" do
- collection = [[1, true, {class: "foo"}], [0, false, {class: "bar"}]]
+ collection = [[1, true, { class: "foo" }], [0, false, { class: "bar" }]]
with_collection_radio_buttons :user, :active, collection, :second, :first
assert_no_select "label.foo[for=user_active_true]"
@@ -299,7 +299,7 @@ class FormCollectionsHelperTest < ActionView::TestCase
end
test "collection check boxes accepts html options as the last element of array" do
- collection = [[1, "Category 1", {class: "foo"}], [2, "Category 2", {class: "bar"}]]
+ collection = [[1, "Category 1", { class: "foo" }], [2, "Category 2", { class: "bar" }]]
with_collection_check_boxes :user, :active, collection, :first, :second
assert_select 'input[type=checkbox][value="1"].foo'
@@ -307,7 +307,7 @@ class FormCollectionsHelperTest < ActionView::TestCase
end
test "collection check boxes propagates input id to the label for attribute" do
- collection = [[1, "Category 1", {id: "foo"}], [2, "Category 2", {id: "bar"}]]
+ collection = [[1, "Category 1", { id: "foo" }], [2, "Category 2", { id: "bar" }]]
with_collection_check_boxes :user, :active, collection, :first, :second
assert_select 'input[type=checkbox][value="1"]#foo'
@@ -318,7 +318,7 @@ class FormCollectionsHelperTest < ActionView::TestCase
end
test "collection check boxes sets the label class defined inside the block" do
- collection = [[1, "Category 1", {class: "foo"}], [2, "Category 2", {class: "bar"}]]
+ collection = [[1, "Category 1", { class: "foo" }], [2, "Category 2", { class: "bar" }]]
with_collection_check_boxes :user, :active, collection, :second, :first do |b|
b.label(class: "collection_check_boxes")
end
@@ -328,7 +328,7 @@ class FormCollectionsHelperTest < ActionView::TestCase
end
test "collection check boxes does not include the input class in the respective label" do
- collection = [[1, "Category 1", {class: "foo"}], [2, "Category 2", {class: "bar"}]]
+ collection = [[1, "Category 1", { class: "foo" }], [2, "Category 2", { class: "bar" }]]
with_collection_check_boxes :user, :active, collection, :second, :first
assert_no_select "label.foo[for=user_active_category_1]"
@@ -336,7 +336,7 @@ class FormCollectionsHelperTest < ActionView::TestCase
end
test "collection check boxes accepts selected values as :checked option" do
- collection = (1..3).map{|i| [i, "Category #{i}"] }
+ collection = (1..3).map { |i| [i, "Category #{i}"] }
with_collection_check_boxes :user, :category_ids, collection, :first, :last, checked: [1, 3]
assert_select 'input[type=checkbox][value="1"][checked=checked]'
@@ -345,7 +345,7 @@ class FormCollectionsHelperTest < ActionView::TestCase
end
test "collection check boxes accepts selected string values as :checked option" do
- collection = (1..3).map{|i| [i, "Category #{i}"] }
+ collection = (1..3).map { |i| [i, "Category #{i}"] }
with_collection_check_boxes :user, :category_ids, collection, :first, :last, checked: ["1", "3"]
assert_select 'input[type=checkbox][value="1"][checked=checked]'
@@ -354,7 +354,7 @@ class FormCollectionsHelperTest < ActionView::TestCase
end
test "collection check boxes accepts a single checked value" do
- collection = (1..3).map{|i| [i, "Category #{i}"] }
+ collection = (1..3).map { |i| [i, "Category #{i}"] }
with_collection_check_boxes :user, :category_ids, collection, :first, :last, checked: 3
assert_select 'input[type=checkbox][value="3"][checked=checked]'
@@ -364,7 +364,7 @@ class FormCollectionsHelperTest < ActionView::TestCase
test "collection check boxes accepts selected values as :checked option and override the model values" do
user = Struct.new(:category_ids).new(2)
- collection = (1..3).map{|i| [i, "Category #{i}"] }
+ collection = (1..3).map { |i| [i, "Category #{i}"] }
@output_buffer = fields_for(:user, user) do |p|
p.collection_check_boxes :category_ids, collection, :first, :last, checked: [1, 3]
@@ -376,7 +376,7 @@ class FormCollectionsHelperTest < ActionView::TestCase
end
test "collection check boxes accepts multiple disabled items" do
- collection = (1..3).map{|i| [i, "Category #{i}"] }
+ collection = (1..3).map { |i| [i, "Category #{i}"] }
with_collection_check_boxes :user, :category_ids, collection, :first, :last, disabled: [1, 3]
assert_select 'input[type=checkbox][value="1"][disabled=disabled]'
@@ -385,7 +385,7 @@ class FormCollectionsHelperTest < ActionView::TestCase
end
test "collection check boxes accepts single disabled item" do
- collection = (1..3).map{|i| [i, "Category #{i}"] }
+ collection = (1..3).map { |i| [i, "Category #{i}"] }
with_collection_check_boxes :user, :category_ids, collection, :first, :last, disabled: 1
assert_select 'input[type=checkbox][value="1"][disabled=disabled]'
@@ -394,7 +394,7 @@ class FormCollectionsHelperTest < ActionView::TestCase
end
test "collection check boxes accepts a proc to disabled items" do
- collection = (1..3).map{|i| [i, "Category #{i}"] }
+ collection = (1..3).map { |i| [i, "Category #{i}"] }
with_collection_check_boxes :user, :category_ids, collection, :first, :last, disabled: proc { |i| i.first == 1 }
assert_select 'input[type=checkbox][value="1"][disabled=disabled]'
@@ -403,7 +403,7 @@ class FormCollectionsHelperTest < ActionView::TestCase
end
test "collection check boxes accepts multiple readonly items" do
- collection = (1..3).map{|i| [i, "Category #{i}"] }
+ collection = (1..3).map { |i| [i, "Category #{i}"] }
with_collection_check_boxes :user, :category_ids, collection, :first, :last, readonly: [1, 3]
assert_select 'input[type=checkbox][value="1"][readonly=readonly]'
@@ -412,7 +412,7 @@ class FormCollectionsHelperTest < ActionView::TestCase
end
test "collection check boxes accepts single readonly item" do
- collection = (1..3).map{|i| [i, "Category #{i}"] }
+ collection = (1..3).map { |i| [i, "Category #{i}"] }
with_collection_check_boxes :user, :category_ids, collection, :first, :last, readonly: 1
assert_select 'input[type=checkbox][value="1"][readonly=readonly]'
@@ -421,7 +421,7 @@ class FormCollectionsHelperTest < ActionView::TestCase
end
test "collection check boxes accepts a proc to readonly items" do
- collection = (1..3).map{|i| [i, "Category #{i}"] }
+ collection = (1..3).map { |i| [i, "Category #{i}"] }
with_collection_check_boxes :user, :category_ids, collection, :first, :last, readonly: proc { |i| i.first == 1 }
assert_select 'input[type=checkbox][value="1"][readonly=readonly]'
diff --git a/actionview/test/template/form_helper/form_with_test.rb b/actionview/test/template/form_helper/form_with_test.rb
new file mode 100644
index 0000000000..3a91c7dce7
--- /dev/null
+++ b/actionview/test/template/form_helper/form_with_test.rb
@@ -0,0 +1,2231 @@
+require "abstract_unit"
+require "controller/fake_models"
+
+class FormWithTest < ActionView::TestCase
+ include RenderERBUtils
+end
+
+class FormWithActsLikeFormTagTest < FormWithTest
+ tests ActionView::Helpers::FormTagHelper
+
+ setup do
+ @controller = BasicController.new
+ end
+
+ def hidden_fields(options = {})
+ method = options[:method]
+ skip_enforcing_utf8 = options.fetch(:skip_enforcing_utf8, false)
+
+ "".tap do |txt|
+ unless skip_enforcing_utf8
+ txt << %{<input name="utf8" type="hidden" value="&#x2713;" />}
+ end
+
+ if method && !%w(get post).include?(method.to_s)
+ txt << %{<input name="_method" type="hidden" value="#{method}" />}
+ end
+ end
+ end
+
+ def form_text(action = "http://www.example.com", local: false, **options)
+ enctype, html_class, id, method = options.values_at(:enctype, :html_class, :id, :method)
+
+ method = method.to_s == "get" ? "get" : "post"
+
+ txt = %{<form accept-charset="UTF-8" action="#{action}"}
+ txt << %{ enctype="multipart/form-data"} if enctype
+ txt << %{ data-remote="true"} unless local
+ txt << %{ class="#{html_class}"} if html_class
+ txt << %{ id="#{id}"} if id
+ txt << %{ method="#{method}">}
+ end
+
+ def whole_form(action = "http://www.example.com", options = {})
+ out = form_text(action, options) + hidden_fields(options)
+
+ if block_given?
+ out << yield << "</form>"
+ end
+
+ out
+ end
+
+ def url_for(options)
+ if options.is_a?(Hash)
+ "http://www.example.com"
+ else
+ super
+ end
+ end
+
+ def test_form_with_multipart
+ actual = form_with(multipart: true)
+
+ expected = whole_form("http://www.example.com", enctype: true)
+ assert_dom_equal expected, actual
+ end
+
+ def test_form_with_with_method_patch
+ actual = form_with(method: :patch)
+
+ expected = whole_form("http://www.example.com", method: :patch)
+ assert_dom_equal expected, actual
+ end
+
+ def test_form_with_with_method_put
+ actual = form_with(method: :put)
+
+ expected = whole_form("http://www.example.com", method: :put)
+ assert_dom_equal expected, actual
+ end
+
+ def test_form_with_with_method_delete
+ actual = form_with(method: :delete)
+
+ expected = whole_form("http://www.example.com", method: :delete)
+ assert_dom_equal expected, actual
+ end
+
+ def test_form_with_with_local_true
+ actual = form_with(local: true)
+
+ expected = whole_form("http://www.example.com", local: true)
+ assert_dom_equal expected, actual
+ end
+
+ def test_form_with_skip_enforcing_utf8_true
+ actual = form_with(skip_enforcing_utf8: true)
+ expected = whole_form("http://www.example.com", skip_enforcing_utf8: true)
+ assert_dom_equal expected, actual
+ assert actual.html_safe?
+ end
+
+ def test_form_with_with_block_in_erb
+ output_buffer = render_erb("<%= form_with(url: 'http://www.example.com') do %>Hello world!<% end %>")
+
+ expected = whole_form { "Hello world!" }
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_block_and_method_in_erb
+ output_buffer = render_erb("<%= form_with(url: 'http://www.example.com', method: :put) do %>Hello world!<% end %>")
+
+ expected = whole_form("http://www.example.com", method: "put") do
+ "Hello world!"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_block_in_erb_and_local_true
+ output_buffer = render_erb("<%= form_with(url: 'http://www.example.com', local: true) do %>Hello world!<% end %>")
+
+ expected = whole_form("http://www.example.com", local: true) do
+ "Hello world!"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+end
+
+class FormWithActsLikeFormForTest < FormWithTest
+ def form_with(*)
+ @output_buffer = super
+ end
+
+ teardown do
+ I18n.backend.reload!
+ end
+
+ setup do
+ # Create "label" locale for testing I18n label helpers
+ I18n.backend.store_translations "label",
+ activemodel: {
+ attributes: {
+ post: {
+ cost: "Total cost"
+ },
+ "post/language": {
+ spanish: "Espanol"
+ }
+ }
+ },
+ helpers: {
+ label: {
+ post: {
+ body: "Write entire text here",
+ color: {
+ red: "Rojo"
+ },
+ comments: {
+ body: "Write body here"
+ }
+ },
+ tag: {
+ value: "Tag"
+ },
+ post_delegate: {
+ title: "Delegate model_name title"
+ }
+ }
+ }
+
+ # Create "submit" locale for testing I18n submit helpers
+ I18n.backend.store_translations "submit",
+ helpers: {
+ submit: {
+ create: "Create %{model}",
+ update: "Confirm %{model} changes",
+ submit: "Save changes",
+ another_post: {
+ update: "Update your %{model}"
+ }
+ }
+ }
+
+ I18n.backend.store_translations "placeholder",
+ activemodel: {
+ attributes: {
+ post: {
+ cost: "Total cost"
+ },
+ "post/cost": {
+ uk: "Pounds"
+ }
+ }
+ },
+ helpers: {
+ placeholder: {
+ post: {
+ title: "What is this about?",
+ written_on: {
+ spanish: "Escrito en"
+ },
+ comments: {
+ body: "Write body here"
+ }
+ },
+ post_delegate: {
+ title: "Delegate model_name title"
+ },
+ tag: {
+ value: "Tag"
+ }
+ }
+ }
+
+ @post = Post.new
+ @comment = Comment.new
+ def @post.errors()
+ Class.new {
+ def [](field); field == "author_name" ? ["can't be empty"] : [] end
+ def empty?() false end
+ def count() 1 end
+ def full_messages() ["Author name can't be empty"] end
+ }.new
+ end
+ def @post.to_key; [123]; end
+ def @post.id; 0; end
+ def @post.id_before_type_cast; "omg"; end
+ def @post.id_came_from_user?; true; end
+ def @post.to_param; "123"; end
+
+ @post.persisted = true
+ @post.title = "Hello World"
+ @post.author_name = ""
+ @post.body = "Back to the hill and over it again!"
+ @post.secret = 1
+ @post.written_on = Date.new(2004, 6, 15)
+
+ @post.comments = []
+ @post.comments << @comment
+
+ @post.tags = []
+ @post.tags << Tag.new
+
+ @post_delegator = PostDelegator.new
+
+ @post_delegator.title = "Hello World"
+
+ @car = Car.new("#000FFF")
+ end
+
+ Routes = ActionDispatch::Routing::RouteSet.new
+ Routes.draw do
+ resources :posts do
+ resources :comments
+ end
+
+ namespace :admin do
+ resources :posts do
+ resources :comments
+ end
+ end
+
+ get "/foo", to: "controller#action"
+ root to: "main#index"
+ end
+
+ def _routes
+ Routes
+ end
+
+ include Routes.url_helpers
+
+ def url_for(object)
+ @url_for_options = object
+
+ if object.is_a?(Hash) && object[:use_route].blank? && object[:controller].blank?
+ object.merge!(controller: "main", action: "index")
+ end
+
+ super
+ end
+
+ def test_form_with_requires_arguments
+ error = assert_raises(ArgumentError) do
+ form_for(nil, html: { id: "create-post" }) do
+ end
+ end
+ assert_equal "First argument in form cannot contain nil or be empty", error.message
+
+ error = assert_raises(ArgumentError) do
+ form_for([nil, nil], html: { id: "create-post" }) do
+ end
+ end
+ assert_equal "First argument in form cannot contain nil or be empty", error.message
+ end
+
+ def test_form_with
+ form_with(model: @post, id: "create-post") do |f|
+ concat f.label(:title) { "The Title" }
+ concat f.text_field(:title)
+ concat f.text_area(:body)
+ concat f.check_box(:secret)
+ concat f.submit("Create post")
+ concat f.button("Create post")
+ concat f.button {
+ concat content_tag(:span, "Create post")
+ }
+ end
+
+ expected = whole_form("/posts/123", "create-post", method: "patch") do
+ "<label for='post_title'>The Title</label>" +
+ "<input name='post[title]' type='text' value='Hello World' />" +
+ "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" +
+ "<input name='post[secret]' type='hidden' value='0' />" +
+ "<input name='post[secret]' checked='checked' type='checkbox' value='1' />" +
+ "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />" +
+ "<button name='button' type='submit'>Create post</button>" +
+ "<button name='button' type='submit'><span>Create post</span></button>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_only_url_on_create
+ form_with(url: "/posts") do |f|
+ concat f.label :title, "Label me"
+ concat f.text_field :title
+ end
+
+ expected = whole_form("/posts") do
+ '<label for="title">Label me</label>' +
+ '<input type="text" name="title">'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_only_url_on_update
+ form_with(url: "/posts/123") do |f|
+ concat f.label :title, "Label me"
+ concat f.text_field :title
+ end
+
+ expected = whole_form("/posts/123") do
+ '<label for="title">Label me</label>' +
+ '<input type="text" name="title">'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_general_attributes
+ form_with(url: "/posts/123") do |f|
+ concat f.text_field :no_model_to_back_this_badboy
+ end
+
+ expected = whole_form("/posts/123") do
+ '<input type="text" name="no_model_to_back_this_badboy">'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_attribute_not_on_model
+ form_with(model: @post) do |f|
+ concat f.text_field :this_dont_exist_on_post
+ end
+
+ expected = whole_form("/posts/123", method: :patch) do
+ '<input type="text" name="post[this_dont_exist_on_post]">'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_doesnt_call_private_or_protected_properties_on_form_object_skipping_value
+ obj = Class.new do
+ private def private_property
+ "That would be great."
+ end
+
+ protected def protected_property
+ "I believe you have my stapler."
+ end
+ end.new
+
+ form_with(model: obj, scope: "other_name", url: "/", id: "edit-other-name") do |f|
+ assert_dom_equal '<input type="hidden" name="other_name[private_property]">', f.hidden_field(:private_property)
+ assert_dom_equal '<input type="hidden" name="other_name[protected_property]">', f.hidden_field(:protected_property)
+ end
+ end
+
+ def test_form_with_with_collection_radio_buttons
+ post = Post.new
+ def post.active; false; end
+ form_with(model: post) do |f|
+ concat f.collection_radio_buttons(:active, [true, false], :to_s, :to_s)
+ end
+
+ expected = whole_form("/posts") do
+ "<input type='hidden' name='post[active]' value='' />" +
+ "<input name='post[active]' type='radio' value='true' />" +
+ "<label for='post_active_true'>true</label>" +
+ "<input checked='checked' name='post[active]' type='radio' value='false' />" +
+ "<label for='post_active_false'>false</label>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_collection_radio_buttons_with_custom_builder_block
+ post = Post.new
+ def post.active; false; end
+
+ form_with(model: post) do |f|
+ rendered_radio_buttons = f.collection_radio_buttons(:active, [true, false], :to_s, :to_s) do |b|
+ b.label { b.radio_button + b.text }
+ end
+ concat rendered_radio_buttons
+ end
+
+ expected = whole_form("/posts") do
+ "<input type='hidden' name='post[active]' value='' />" +
+ "<label for='post_active_true'>" +
+ "<input name='post[active]' type='radio' value='true' />" +
+ "true</label>" +
+ "<label for='post_active_false'>" +
+ "<input checked='checked' name='post[active]' type='radio' value='false' />" +
+ "false</label>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_collection_radio_buttons_with_custom_builder_block_does_not_leak_the_template
+ post = Post.new
+ def post.active; false; end
+ def post.id; 1; end
+
+ form_with(model: post) do |f|
+ rendered_radio_buttons = f.collection_radio_buttons(:active, [true, false], :to_s, :to_s) do |b|
+ b.label { b.radio_button + b.text }
+ end
+ concat rendered_radio_buttons
+ concat f.hidden_field :id
+ end
+
+ expected = whole_form("/posts") do
+ "<input type='hidden' name='post[active]' value='' />" +
+ "<label for='post_active_true'>" +
+ "<input name='post[active]' type='radio' value='true' />" +
+ "true</label>" +
+ "<label for='post_active_false'>" +
+ "<input checked='checked' name='post[active]' type='radio' value='false' />" +
+ "false</label>" +
+ "<input name='post[id]' type='hidden' value='1' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_index_and_with_collection_radio_buttons
+ post = Post.new
+ def post.active; false; end
+
+ form_with(model: post, index: "1") do |f|
+ concat f.collection_radio_buttons(:active, [true, false], :to_s, :to_s)
+ end
+
+ expected = whole_form("/posts") do
+ "<input type='hidden' name='post[1][active]' value='' />" +
+ "<input name='post[1][active]' type='radio' value='true' />" +
+ "<label for='post_1_active_true'>true</label>" +
+ "<input checked='checked' name='post[1][active]' type='radio' value='false' />" +
+ "<label for='post_1_active_false'>false</label>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_collection_check_boxes
+ post = Post.new
+ def post.tag_ids; [1, 3]; end
+ collection = (1..3).map { |i| [i, "Tag #{i}"] }
+ form_with(model: post) do |f|
+ concat f.collection_check_boxes(:tag_ids, collection, :first, :last)
+ end
+
+ expected = whole_form("/posts") do
+ "<input name='post[tag_ids][]' type='hidden' value='' />" +
+ "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='1' />" +
+ "<label for='post_tag_ids_1'>Tag 1</label>" +
+ "<input name='post[tag_ids][]' type='checkbox' value='2' />" +
+ "<label for='post_tag_ids_2'>Tag 2</label>" +
+ "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='3' />" +
+ "<label for='post_tag_ids_3'>Tag 3</label>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_collection_check_boxes_with_custom_builder_block
+ post = Post.new
+ def post.tag_ids; [1, 3]; end
+ collection = (1..3).map { |i| [i, "Tag #{i}"] }
+ form_with(model: post) do |f|
+ rendered_check_boxes = f.collection_check_boxes(:tag_ids, collection, :first, :last) do |b|
+ b.label { b.check_box + b.text }
+ end
+ concat rendered_check_boxes
+ end
+
+ expected = whole_form("/posts") do
+ "<input name='post[tag_ids][]' type='hidden' value='' />" +
+ "<label for='post_tag_ids_1'>" +
+ "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='1' />" +
+ "Tag 1</label>" +
+ "<label for='post_tag_ids_2'>" +
+ "<input name='post[tag_ids][]' type='checkbox' value='2' />" +
+ "Tag 2</label>" +
+ "<label for='post_tag_ids_3'>" +
+ "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='3' />" +
+ "Tag 3</label>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_collection_check_boxes_with_custom_builder_block_does_not_leak_the_template
+ post = Post.new
+ def post.tag_ids; [1, 3]; end
+ def post.id; 1; end
+ collection = (1..3).map { |i| [i, "Tag #{i}"] }
+
+ form_with(model: post) do |f|
+ rendered_check_boxes = f.collection_check_boxes(:tag_ids, collection, :first, :last) do |b|
+ b.label { b.check_box + b.text }
+ end
+ concat rendered_check_boxes
+ concat f.hidden_field :id
+ end
+
+ expected = whole_form("/posts") do
+ "<input name='post[tag_ids][]' type='hidden' value='' />" +
+ "<label for='post_tag_ids_1'>" +
+ "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='1' />" +
+ "Tag 1</label>" +
+ "<label for='post_tag_ids_2'>" +
+ "<input name='post[tag_ids][]' type='checkbox' value='2' />" +
+ "Tag 2</label>" +
+ "<label for='post_tag_ids_3'>" +
+ "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='3' />" +
+ "Tag 3</label>" +
+ "<input name='post[id]' type='hidden' value='1' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_index_and_with_collection_check_boxes
+ post = Post.new
+ def post.tag_ids; [1]; end
+ collection = [[1, "Tag 1"]]
+
+ form_with(model: post, index: "1") do |f|
+ concat f.collection_check_boxes(:tag_ids, collection, :first, :last)
+ end
+
+ expected = whole_form("/posts") do
+ "<input name='post[1][tag_ids][]' type='hidden' value='' />" +
+ "<input checked='checked' name='post[1][tag_ids][]' type='checkbox' value='1' />" +
+ "<label for='post_1_tag_ids_1'>Tag 1</label>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_file_field_generate_multipart
+ form_with(model: @post, id: "create-post") do |f|
+ concat f.file_field(:file)
+ end
+
+ expected = whole_form("/posts/123", "create-post", method: "patch", multipart: true) do
+ "<input name='post[file]' type='file' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_fields_with_file_field_generate_multipart
+ form_with(model: @post) do |f|
+ concat f.fields(:comment, model: @post) { |c|
+ concat c.file_field(:file)
+ }
+ end
+
+ expected = whole_form("/posts/123", method: "patch", multipart: true) do
+ "<input name='post[comment][file]' type='file' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_format
+ form_with(model: @post, format: :json, id: "edit_post_123", class: "edit_post") do |f|
+ concat f.label(:title)
+ end
+
+ expected = whole_form("/posts/123.json", "edit_post_123", "edit_post", method: "patch") do
+ "<label for='post_title'>Title</label>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_format_and_url
+ form_with(model: @post, format: :json, url: "/") do |f|
+ concat f.label(:title)
+ end
+
+ expected = whole_form("/", method: "patch") do
+ "<label for='post_title'>Title</label>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_model_using_relative_model_naming
+ blog_post = Blog::Post.new("And his name will be forty and four.", 44)
+
+ form_with(model: blog_post) do |f|
+ concat f.text_field :title
+ concat f.submit("Edit post")
+ end
+
+ expected = whole_form("/posts/44", method: "patch") do
+ "<input name='post[title]' type='text' value='And his name will be forty and four.' />" +
+ "<input name='commit' data-disable-with='Edit post' type='submit' value='Edit post' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_symbol_scope
+ form_with(model: @post, scope: "other_name", id: "create-post") do |f|
+ concat f.label(:title, class: "post_title")
+ concat f.text_field(:title)
+ concat f.text_area(:body)
+ concat f.check_box(:secret)
+ concat f.submit("Create post")
+ end
+
+ expected = whole_form("/posts/123", "create-post", method: "patch") do
+ "<label for='other_name_title' class='post_title'>Title</label>" +
+ "<input name='other_name[title]' value='Hello World' type='text' />" +
+ "<textarea name='other_name[body]'>\nBack to the hill and over it again!</textarea>" +
+ "<input name='other_name[secret]' value='0' type='hidden' />" +
+ "<input name='other_name[secret]' checked='checked' value='1' type='checkbox' />" +
+ "<input name='commit' value='Create post' data-disable-with='Create post' type='submit' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_method_as_part_of_html_options
+ form_with(model: @post, url: "/", id: "create-post", html: { method: :delete }) do |f|
+ concat f.text_field(:title)
+ concat f.text_area(:body)
+ concat f.check_box(:secret)
+ end
+
+ expected = whole_form("/", "create-post", method: "delete") do
+ "<input name='post[title]' type='text' value='Hello World' />" +
+ "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" +
+ "<input name='post[secret]' type='hidden' value='0' />" +
+ "<input name='post[secret]' checked='checked' type='checkbox' value='1' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_method
+ form_with(model: @post, url: "/", method: :delete, id: "create-post") do |f|
+ concat f.text_field(:title)
+ concat f.text_area(:body)
+ concat f.check_box(:secret)
+ end
+
+ expected = whole_form("/", "create-post", method: "delete") do
+ "<input name='post[title]' type='text' value='Hello World' />" +
+ "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" +
+ "<input name='post[secret]' type='hidden' value='0' />" +
+ "<input name='post[secret]' checked='checked' type='checkbox' value='1' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_search_field
+ # Test case for bug which would emit an "object" attribute
+ # when used with form_for using a search_field form helper
+ form_with(model: Post.new, url: "/search", id: "search-post", method: :get) do |f|
+ concat f.search_field(:title)
+ end
+
+ expected = whole_form("/search", "search-post", method: "get") do
+ "<input name='post[title]' type='search' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_enables_remote_by_default
+ 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") 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_form_with_skip_enforcing_utf8_true
+ form_with(scope: :post, skip_enforcing_utf8: true) do |f|
+ concat f.text_field(:title)
+ end
+
+ expected = whole_form("/", skip_enforcing_utf8: true) do
+ "<input name='post[title]' type='text' value='Hello World' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_skip_enforcing_utf8_false
+ form_with(scope: :post, skip_enforcing_utf8: false) do |f|
+ concat f.text_field(:title)
+ end
+
+ expected = whole_form("/", skip_enforcing_utf8: false) do
+ "<input name='post[title]' type='text' value='Hello World' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_without_object
+ form_with(scope: :post, id: "create-post") do |f|
+ concat f.text_field(:title)
+ concat f.text_area(:body)
+ concat f.check_box(:secret)
+ end
+
+ expected = whole_form("/", "create-post") do
+ "<input name='post[title]' type='text' value='Hello World' />" +
+ "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" +
+ "<input name='post[secret]' type='hidden' value='0' />" +
+ "<input name='post[secret]' checked='checked' type='checkbox' value='1' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_index
+ form_with(model: @post, scope: "post[]") do |f|
+ concat f.label(:title)
+ concat f.text_field(:title)
+ concat f.text_area(:body)
+ concat f.check_box(:secret)
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ "<label for='post_123_title'>Title</label>" +
+ "<input name='post[123][title]' type='text' value='Hello World' />" +
+ "<textarea name='post[123][body]'>\nBack to the hill and over it again!</textarea>" +
+ "<input name='post[123][secret]' type='hidden' value='0' />" +
+ "<input name='post[123][secret]' checked='checked' type='checkbox' value='1' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_nil_index_option_override
+ form_with(model: @post, scope: "post[]", index: nil) do |f|
+ concat f.text_field(:title)
+ concat f.text_area(:body)
+ concat f.check_box(:secret)
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ "<input name='post[][title]' type='text' value='Hello World' />" +
+ "<textarea name='post[][body]'>\nBack to the hill and over it again!</textarea>" +
+ "<input name='post[][secret]' type='hidden' value='0' />" +
+ "<input name='post[][secret]' checked='checked' type='checkbox' value='1' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_label_error_wrapping
+ form_with(model: @post) do |f|
+ concat f.label(:author_name, class: "label")
+ concat f.text_field(:author_name)
+ concat f.submit("Create post")
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ "<div class='field_with_errors'><label for='post_author_name' class='label'>Author name</label></div>" +
+ "<div class='field_with_errors'><input name='post[author_name]' type='text' value='' /></div>" +
+ "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_label_error_wrapping_without_conventional_instance_variable
+ post = remove_instance_variable :@post
+
+ form_with(model: post) do |f|
+ concat f.label(:author_name, class: "label")
+ concat f.text_field(:author_name)
+ concat f.submit("Create post")
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ "<div class='field_with_errors'><label for='post_author_name' class='label'>Author name</label></div>" +
+ "<div class='field_with_errors'><input name='post[author_name]' type='text' value='' /></div>" +
+ "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_label_error_wrapping_block_and_non_block_versions
+ form_with(model: @post) do |f|
+ concat f.label(:author_name, "Name", class: "label")
+ concat f.label(:author_name, class: "label") { "Name" }
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ "<div class='field_with_errors'><label for='post_author_name' class='label'>Name</label></div>" +
+ "<div class='field_with_errors'><label for='post_author_name' class='label'>Name</label></div>"
+ end
+
+ 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
+ @post.stub(:to_key, nil) do
+ form_with(model: @post) do |f|
+ concat f.submit
+ end
+
+ expected = whole_form("/posts") do
+ "<input name='commit' data-disable-with='Create Post' type='submit' value='Create Post' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+ end
+ end
+
+ def test_submit_with_object_as_existing_record_and_locale_strings
+ with_locale :submit do
+ form_with(model: @post) do |f|
+ concat f.submit
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ "<input name='commit' data-disable-with='Confirm Post changes' type='submit' value='Confirm Post changes' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+ end
+
+ def test_submit_without_object_and_locale_strings
+ with_locale :submit do
+ form_with(scope: :post) do |f|
+ concat f.submit class: "extra"
+ end
+
+ expected = whole_form do
+ "<input name='commit' class='extra' data-disable-with='Save changes' type='submit' value='Save changes' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+ end
+
+ def test_submit_with_object_and_nested_lookup
+ with_locale :submit do
+ form_with(model: @post, scope: :another_post) do |f|
+ concat f.submit
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ "<input name='commit' data-disable-with='Update your Post' type='submit' value='Update your Post' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+ end
+
+ def test_fields_with_attributes_not_on_model
+ form_with(model: @post) do |f|
+ concat f.fields(:comment) { |c|
+ concat c.text_field :dont_exist_on_model
+ }
+ end
+
+ expected = whole_form("/posts/123", method: :patch) do
+ '<input type="text" name="post[comment][dont_exist_on_model]">'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_fields_with_attributes_not_on_model_deep_nested
+ @comment.save
+ form_with(scope: :posts) do |f|
+ f.fields("post[]", model: @post) do |f2|
+ f2.text_field(:id)
+ @post.comments.each do |comment|
+ concat f2.fields("comment[]", model: comment) { |c|
+ concat c.text_field(:dont_exist_on_model)
+ }
+ end
+ end
+ end
+
+ expected = whole_form do
+ '<input name="posts[post][0][comment][1][dont_exist_on_model]" type="text">'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields
+ @comment.body = "Hello World"
+ form_with(model: @post) do |f|
+ concat f.fields(model: @comment) { |c|
+ concat c.text_field(:body)
+ }
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ "<input name='post[comment][body]' type='text' value='Hello World' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_deep_nested_fields
+ @comment.save
+ form_with(scope: :posts) do |f|
+ f.fields("post[]", model: @post) do |f2|
+ f2.text_field(:id)
+ @post.comments.each do |comment|
+ concat f2.fields("comment[]", model: comment) { |c|
+ concat c.text_field(:name)
+ }
+ end
+ end
+ end
+
+ expected = whole_form do
+ "<input name='posts[post][0][comment][1][name]' type='text' value='comment #1' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_with_nested_collections
+ form_with(model: @post, scope: "post[]") do |f|
+ concat f.text_field(:title)
+ concat f.fields("comment[]", model: @comment) { |c|
+ concat c.text_field(:name)
+ }
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ "<input name='post[123][title]' type='text' value='Hello World' />" +
+ "<input name='post[123][comment][][name]' type='text' value='new comment' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_with_index_and_parent_fields
+ form_with(model: @post, index: 1) do |c|
+ concat c.text_field(:title)
+ concat c.fields("comment", model: @comment, index: 1) { |r|
+ concat r.text_field(:name)
+ }
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ "<input name='post[1][title]' type='text' value='Hello World' />" +
+ "<input name='post[1][comment][1][name]' type='text' value='new comment' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_index_and_nested_fields
+ output_buffer = form_with(model: @post, index: 1) do |f|
+ concat f.fields(:comment, model: @post) { |c|
+ concat c.text_field(:title)
+ }
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ "<input name='post[1][comment][title]' type='text' value='Hello World' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_with_index_on_both
+ form_with(model: @post, index: 1) do |f|
+ concat f.fields(:comment, model: @post, index: 5) { |c|
+ concat c.text_field(:title)
+ }
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ "<input name='post[1][comment][5][title]' type='text' value='Hello World' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_with_auto_index
+ form_with(model: @post, scope: "post[]") do |f|
+ concat f.fields(:comment, model: @post) { |c|
+ concat c.text_field(:title)
+ }
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ "<input name='post[123][comment][title]' type='text' value='Hello World' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_with_index_radio_button
+ form_with(model: @post) do |f|
+ concat f.fields(:comment, model: @post, index: 5) { |c|
+ concat c.radio_button(:title, "hello")
+ }
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ "<input name='post[comment][5][title]' type='radio' value='hello' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_with_auto_index_on_both
+ form_with(model: @post, scope: "post[]") do |f|
+ concat f.fields("comment[]", model: @post) { |c|
+ concat c.text_field(:title)
+ }
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ "<input name='post[123][comment][123][title]' type='text' value='Hello World' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_with_index_and_auto_index
+ output_buffer = form_with(model: @post, scope: "post[]") do |f|
+ concat f.fields(:comment, model: @post, index: 5) { |c|
+ concat c.text_field(:title)
+ }
+ end
+
+ output_buffer << form_with(model: @post, index: 1) do |f|
+ concat f.fields("comment[]", model: @post) { |c|
+ concat c.text_field(:title)
+ }
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ "<input name='post[123][comment][5][title]' type='text' value='Hello World' />"
+ end + whole_form("/posts/123", method: "patch") do
+ "<input name='post[1][comment][123][title]' type='text' value='Hello World' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_with_a_new_record_on_a_nested_attributes_one_to_one_association
+ @post.author = Author.new
+
+ form_with(model: @post) do |f|
+ concat f.text_field(:title)
+ concat f.fields(:author) { |af|
+ concat af.text_field(:name)
+ }
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ '<input name="post[title]" type="text" value="Hello World" />' +
+ '<input name="post[author_attributes][name]" type="text" value="new author" />'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_with_explicitly_passed_object_on_a_nested_attributes_one_to_one_association
+ form_with(model: @post) do |f|
+ f.fields(:author, model: Author.new(123)) do |af|
+ assert_not_nil af.object
+ assert_equal 123, af.object.id
+ end
+ end
+ end
+
+ def test_nested_fields_with_an_existing_record_on_a_nested_attributes_one_to_one_association
+ @post.author = Author.new(321)
+
+ form_with(model: @post) do |f|
+ concat f.text_field(:title)
+ concat f.fields(:author) { |af|
+ concat af.text_field(:name)
+ }
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ '<input name="post[title]" type="text" value="Hello World" />' +
+ '<input name="post[author_attributes][name]" type="text" value="author #321" />' +
+ '<input name="post[author_attributes][id]" type="hidden" value="321" />'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_with_an_existing_record_on_a_nested_attributes_one_to_one_association_using_erb_and_inline_block
+ @post.author = Author.new(321)
+
+ form_with(model: @post) do |f|
+ concat f.text_field(:title)
+ concat f.fields(:author) { |af|
+ af.text_field(:name)
+ }
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ '<input name="post[title]" type="text" value="Hello World" />' +
+ '<input name="post[author_attributes][name]" type="text" value="author #321" />' +
+ '<input name="post[author_attributes][id]" type="hidden" value="321" />'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_with_an_existing_record_on_a_nested_attributes_one_to_one_association_with_disabled_hidden_id
+ @post.author = Author.new(321)
+
+ form_with(model: @post) do |f|
+ concat f.text_field(:title)
+ concat f.fields(:author, skip_id: true) { |af|
+ af.text_field(:name)
+ }
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ '<input name="post[title]" type="text" value="Hello World" />' +
+ '<input name="post[author_attributes][name]" type="text" value="author #321" />'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_with_an_existing_record_on_a_nested_attributes_one_to_one_association_with_disabled_hidden_id_inherited
+ @post.author = Author.new(321)
+
+ form_with(model: @post, skip_id: true) do |f|
+ concat f.text_field(:title)
+ concat f.fields(:author) { |af|
+ af.text_field(:name)
+ }
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ '<input name="post[title]" type="text" value="Hello World" />' +
+ '<input name="post[author_attributes][name]" type="text" value="author #321" />'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_with_an_existing_record_on_a_nested_attributes_one_to_one_association_with_disabled_hidden_id_override
+ @post.author = Author.new(321)
+
+ form_with(model: @post, skip_id: true) do |f|
+ concat f.text_field(:title)
+ concat f.fields(:author, skip_id: false) { |af|
+ af.text_field(:name)
+ }
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ '<input name="post[title]" type="text" value="Hello World" />' +
+ '<input name="post[author_attributes][name]" type="text" value="author #321" />' +
+ '<input name="post[author_attributes][id]" type="hidden" value="321" />'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_with_existing_records_on_a_nested_attributes_one_to_one_association_with_explicit_hidden_field_placement
+ @post.author = Author.new(321)
+
+ form_with(model: @post) do |f|
+ concat f.text_field(:title)
+ concat f.fields(:author) { |af|
+ concat af.hidden_field(:id)
+ concat af.text_field(:name)
+ }
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ '<input name="post[title]" type="text" value="Hello World" />' +
+ '<input name="post[author_attributes][id]" type="hidden" value="321" />' +
+ '<input name="post[author_attributes][name]" type="text" value="author #321" />'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_with_existing_records_on_a_nested_attributes_collection_association
+ @post.comments = Array.new(2) { |id| Comment.new(id + 1) }
+
+ form_with(model: @post) do |f|
+ concat f.text_field(:title)
+ @post.comments.each do |comment|
+ concat f.fields(:comments, model: comment) { |cf|
+ concat cf.text_field(:name)
+ }
+ end
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ '<input name="post[title]" type="text" value="Hello World" />' +
+ '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
+ '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
+ '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' +
+ '<input name="post[comments_attributes][1][id]" type="hidden" value="2" />'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_with_existing_records_on_a_nested_attributes_collection_association_with_disabled_hidden_id
+ @post.comments = Array.new(2) { |id| Comment.new(id + 1) }
+ @post.author = Author.new(321)
+
+ form_with(model: @post) do |f|
+ concat f.text_field(:title)
+ concat f.fields(:author) { |af|
+ concat af.text_field(:name)
+ }
+ @post.comments.each do |comment|
+ concat f.fields(:comments, model: comment, skip_id: true) { |cf|
+ concat cf.text_field(:name)
+ }
+ end
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ '<input name="post[title]" type="text" value="Hello World" />' +
+ '<input name="post[author_attributes][name]" type="text" value="author #321" />' +
+ '<input name="post[author_attributes][id]" type="hidden" value="321" />' +
+ '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
+ '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_with_existing_records_on_a_nested_attributes_collection_association_with_disabled_hidden_id_inherited
+ @post.comments = Array.new(2) { |id| Comment.new(id + 1) }
+ @post.author = Author.new(321)
+
+ form_with(model: @post, skip_id: true) do |f|
+ concat f.text_field(:title)
+ concat f.fields(:author) { |af|
+ concat af.text_field(:name)
+ }
+ @post.comments.each do |comment|
+ concat f.fields(:comments, model: comment) { |cf|
+ concat cf.text_field(:name)
+ }
+ end
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ '<input name="post[title]" type="text" value="Hello World" />' +
+ '<input name="post[author_attributes][name]" type="text" value="author #321" />' +
+ '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
+ '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_with_existing_records_on_a_nested_attributes_collection_association_with_disabled_hidden_id_override
+ @post.comments = Array.new(2) { |id| Comment.new(id + 1) }
+ @post.author = Author.new(321)
+
+ form_with(model: @post, skip_id: true) do |f|
+ concat f.text_field(:title)
+ concat f.fields(:author, skip_id: false) { |af|
+ concat af.text_field(:name)
+ }
+ @post.comments.each do |comment|
+ concat f.fields(:comments, model: comment) { |cf|
+ concat cf.text_field(:name)
+ }
+ end
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ '<input name="post[title]" type="text" value="Hello World" />' +
+ '<input name="post[author_attributes][name]" type="text" value="author #321" />' +
+ '<input name="post[author_attributes][id]" type="hidden" value="321" />' +
+ '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
+ '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_with_existing_records_on_a_nested_attributes_collection_association_using_erb_and_inline_block
+ @post.comments = Array.new(2) { |id| Comment.new(id + 1) }
+
+ form_with(model: @post) do |f|
+ concat f.text_field(:title)
+ @post.comments.each do |comment|
+ concat f.fields(:comments, model: comment) { |cf|
+ cf.text_field(:name)
+ }
+ end
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ '<input name="post[title]" type="text" value="Hello World" />' +
+ '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
+ '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
+ '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' +
+ '<input name="post[comments_attributes][1][id]" type="hidden" value="2" />'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_with_existing_records_on_a_nested_attributes_collection_association_with_explicit_hidden_field_placement
+ @post.comments = Array.new(2) { |id| Comment.new(id + 1) }
+
+ form_with(model: @post) do |f|
+ concat f.text_field(:title)
+ @post.comments.each do |comment|
+ concat f.fields(:comments, model: comment) { |cf|
+ concat cf.hidden_field(:id)
+ concat cf.text_field(:name)
+ }
+ end
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ '<input name="post[title]" type="text" value="Hello World" />' +
+ '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
+ '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
+ '<input name="post[comments_attributes][1][id]" type="hidden" value="2" />' +
+ '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_with_new_records_on_a_nested_attributes_collection_association
+ @post.comments = [Comment.new, Comment.new]
+
+ form_with(model: @post) do |f|
+ concat f.text_field(:title)
+ @post.comments.each do |comment|
+ concat f.fields(:comments, model: comment) { |cf|
+ concat cf.text_field(:name)
+ }
+ end
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ '<input name="post[title]" type="text" value="Hello World" />' +
+ '<input name="post[comments_attributes][0][name]" type="text" value="new comment" />' +
+ '<input name="post[comments_attributes][1][name]" type="text" value="new comment" />'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_with_existing_and_new_records_on_a_nested_attributes_collection_association
+ @post.comments = [Comment.new(321), Comment.new]
+
+ form_with(model: @post) do |f|
+ concat f.text_field(:title)
+ @post.comments.each do |comment|
+ concat f.fields(:comments, model: comment) { |cf|
+ concat cf.text_field(:name)
+ }
+ end
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ '<input name="post[title]" type="text" value="Hello World" />' +
+ '<input name="post[comments_attributes][0][name]" type="text" value="comment #321" />' +
+ '<input name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
+ '<input name="post[comments_attributes][1][name]" type="text" value="new comment" />'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_with_an_empty_supplied_attributes_collection
+ form_with(model: @post) do |f|
+ concat f.text_field(:title)
+ f.fields(:comments, model: []) do |cf|
+ concat cf.text_field(:name)
+ end
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ '<input name="post[title]" type="text" value="Hello World" />'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_with_existing_records_on_a_supplied_nested_attributes_collection
+ @post.comments = Array.new(2) { |id| Comment.new(id + 1) }
+
+ form_with(model: @post) do |f|
+ concat f.text_field(:title)
+ concat f.fields(:comments, model: @post.comments) { |cf|
+ concat cf.text_field(:name)
+ }
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ '<input name="post[title]" type="text" value="Hello World" />' +
+ '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
+ '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
+ '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' +
+ '<input name="post[comments_attributes][1][id]" type="hidden" value="2" />'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_arel_like
+ @post.comments = ArelLike.new
+
+ form_with(model: @post) do |f|
+ concat f.text_field(:title)
+ concat f.fields(:comments, model: @post.comments) { |cf|
+ concat cf.text_field(:name)
+ }
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ '<input name="post[title]" type="text" value="Hello World" />' +
+ '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
+ '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
+ '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' +
+ '<input name="post[comments_attributes][1][id]" type="hidden" value="2" />'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_label_translation_with_more_than_10_records
+ @post.comments = Array.new(11) { |id| Comment.new(id + 1) }
+
+ params = 11.times.map { ["post.comments.body", default: [:"comment.body", ""], scope: "helpers.label"] }
+ assert_called_with(I18n, :t, params, returns: "Write body here") do
+ form_with(model: @post) do |f|
+ f.fields(:comments) do |cf|
+ concat cf.label(:body)
+ end
+ end
+ end
+ end
+
+ def test_nested_fields_with_existing_records_on_a_supplied_nested_attributes_collection_different_from_record_one
+ comments = Array.new(2) { |id| Comment.new(id + 1) }
+ @post.comments = []
+
+ form_with(model: @post) do |f|
+ concat f.text_field(:title)
+ concat f.fields(:comments, model: comments) { |cf|
+ concat cf.text_field(:name)
+ }
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ '<input name="post[title]" type="text" value="Hello World" />' +
+ '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
+ '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
+ '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' +
+ '<input name="post[comments_attributes][1][id]" type="hidden" value="2" />'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_on_a_nested_attributes_collection_association_yields_only_builder
+ @post.comments = [Comment.new(321), Comment.new]
+ yielded_comments = []
+
+ form_with(model: @post) do |f|
+ concat f.text_field(:title)
+ concat f.fields(:comments) { |cf|
+ concat cf.text_field(:name)
+ yielded_comments << cf.object
+ }
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ '<input name="post[title]" type="text" value="Hello World" />' +
+ '<input name="post[comments_attributes][0][name]" type="text" value="comment #321" />' +
+ '<input name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
+ '<input name="post[comments_attributes][1][name]" type="text" value="new comment" />'
+ end
+
+ assert_dom_equal expected, output_buffer
+ assert_equal yielded_comments, @post.comments
+ end
+
+ def test_nested_fields_with_child_index_option_override_on_a_nested_attributes_collection_association
+ @post.comments = []
+
+ form_with(model: @post) do |f|
+ concat f.fields(:comments, model: Comment.new(321), child_index: "abc") { |cf|
+ concat cf.text_field(:name)
+ }
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ '<input name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' +
+ '<input name="post[comments_attributes][abc][id]" type="hidden" value="321" />'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_with_child_index_as_lambda_option_override_on_a_nested_attributes_collection_association
+ @post.comments = []
+
+ form_with(model: @post) do |f|
+ concat f.fields(:comments, model: Comment.new(321), child_index: -> { "abc" }) { |cf|
+ concat cf.text_field(:name)
+ }
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ '<input name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' +
+ '<input name="post[comments_attributes][abc][id]" type="hidden" value="321" />'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ class FakeAssociationProxy
+ def to_ary
+ [1, 2, 3]
+ end
+ end
+
+ def test_nested_fields_with_child_index_option_override_on_a_nested_attributes_collection_association_with_proxy
+ @post.comments = FakeAssociationProxy.new
+
+ form_with(model: @post) do |f|
+ concat f.fields(:comments, model: Comment.new(321), child_index: "abc") { |cf|
+ concat cf.text_field(:name)
+ }
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ '<input name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' +
+ '<input name="post[comments_attributes][abc][id]" type="hidden" value="321" />'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_index_method_with_existing_records_on_a_nested_attributes_collection_association
+ @post.comments = Array.new(2) { |id| Comment.new(id + 1) }
+
+ form_with(model: @post) do |f|
+ expected = 0
+ @post.comments.each do |comment|
+ f.fields(:comments, model: comment) { |cf|
+ assert_equal cf.index, expected
+ expected += 1
+ }
+ end
+ end
+ end
+
+ def test_nested_fields_index_method_with_existing_and_new_records_on_a_nested_attributes_collection_association
+ @post.comments = [Comment.new(321), Comment.new]
+
+ form_with(model: @post) do |f|
+ expected = 0
+ @post.comments.each do |comment|
+ f.fields(:comments, model: comment) { |cf|
+ assert_equal cf.index, expected
+ expected += 1
+ }
+ end
+ end
+ end
+
+ def test_nested_fields_index_method_with_existing_records_on_a_supplied_nested_attributes_collection
+ @post.comments = Array.new(2) { |id| Comment.new(id + 1) }
+
+ form_with(model: @post) do |f|
+ expected = 0
+ f.fields(:comments, model: @post.comments) { |cf|
+ assert_equal cf.index, expected
+ expected += 1
+ }
+ end
+ end
+
+ def test_nested_fields_index_method_with_child_index_option_override_on_a_nested_attributes_collection_association
+ @post.comments = []
+
+ form_with(model: @post) do |f|
+ f.fields(:comments, model: Comment.new(321), child_index: "abc") { |cf|
+ assert_equal cf.index, "abc"
+ }
+ end
+ end
+
+ def test_nested_fields_uses_unique_indices_for_different_collection_associations
+ @post.comments = [Comment.new(321)]
+ @post.tags = [Tag.new(123), Tag.new(456)]
+ @post.comments[0].relevances = []
+ @post.tags[0].relevances = []
+ @post.tags[1].relevances = []
+
+ form_with(model: @post) do |f|
+ concat f.fields(:comments, model: @post.comments[0]) { |cf|
+ concat cf.text_field(:name)
+ concat cf.fields(:relevances, model: CommentRelevance.new(314)) { |crf|
+ concat crf.text_field(:value)
+ }
+ }
+ concat f.fields(:tags, model: @post.tags[0]) { |tf|
+ concat tf.text_field(:value)
+ concat tf.fields(:relevances, model: TagRelevance.new(3141)) { |trf|
+ concat trf.text_field(:value)
+ }
+ }
+ concat f.fields("tags", model: @post.tags[1]) { |tf|
+ concat tf.text_field(:value)
+ concat tf.fields(:relevances, model: TagRelevance.new(31415)) { |trf|
+ concat trf.text_field(:value)
+ }
+ }
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ '<input name="post[comments_attributes][0][name]" type="text" value="comment #321" />' +
+ '<input name="post[comments_attributes][0][relevances_attributes][0][value]" type="text" value="commentrelevance #314" />' +
+ '<input name="post[comments_attributes][0][relevances_attributes][0][id]" type="hidden" value="314" />' +
+ '<input name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
+ '<input name="post[tags_attributes][0][value]" type="text" value="tag #123" />' +
+ '<input name="post[tags_attributes][0][relevances_attributes][0][value]" type="text" value="tagrelevance #3141" />' +
+ '<input name="post[tags_attributes][0][relevances_attributes][0][id]" type="hidden" value="3141" />' +
+ '<input name="post[tags_attributes][0][id]" type="hidden" value="123" />' +
+ '<input name="post[tags_attributes][1][value]" type="text" value="tag #456" />' +
+ '<input name="post[tags_attributes][1][relevances_attributes][0][value]" type="text" value="tagrelevance #31415" />' +
+ '<input name="post[tags_attributes][1][relevances_attributes][0][id]" type="hidden" value="31415" />' +
+ '<input name="post[tags_attributes][1][id]" type="hidden" value="456" />'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_with_hash_like_model
+ @author = HashBackedAuthor.new
+
+ form_with(model: @post) do |f|
+ concat f.fields(:author, model: @author) { |af|
+ concat af.text_field(:name)
+ }
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ '<input name="post[author_attributes][name]" type="text" value="hash backed author" />'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_fields
+ output_buffer = fields(:post, model: @post) do |f|
+ concat f.text_field(:title)
+ concat f.text_area(:body)
+ concat f.check_box(:secret)
+ end
+
+ expected =
+ "<input name='post[title]' type='text' value='Hello World' />" +
+ "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" +
+ "<input name='post[secret]' type='hidden' value='0' />" +
+ "<input name='post[secret]' checked='checked' type='checkbox' value='1' />"
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_fields_with_index
+ output_buffer = fields("post[]", model: @post) do |f|
+ concat f.text_field(:title)
+ concat f.text_area(:body)
+ concat f.check_box(:secret)
+ end
+
+ expected =
+ "<input name='post[123][title]' type='text' value='Hello World' />" +
+ "<textarea name='post[123][body]'>\nBack to the hill and over it again!</textarea>" +
+ "<input name='post[123][secret]' type='hidden' value='0' />" +
+ "<input name='post[123][secret]' checked='checked' type='checkbox' value='1' />"
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_fields_with_nil_index_option_override
+ output_buffer = fields("post[]", model: @post, index: nil) do |f|
+ concat f.text_field(:title)
+ concat f.text_area(:body)
+ concat f.check_box(:secret)
+ end
+
+ expected =
+ "<input name='post[][title]' type='text' value='Hello World' />" +
+ "<textarea name='post[][body]'>\nBack to the hill and over it again!</textarea>" +
+ "<input name='post[][secret]' type='hidden' value='0' />" +
+ "<input name='post[][secret]' checked='checked' type='checkbox' value='1' />"
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_fields_with_index_option_override
+ output_buffer = fields("post[]", model: @post, index: "abc") do |f|
+ concat f.text_field(:title)
+ concat f.text_area(:body)
+ concat f.check_box(:secret)
+ end
+
+ expected =
+ "<input name='post[abc][title]' type='text' value='Hello World' />" +
+ "<textarea name='post[abc][body]'>\nBack to the hill and over it again!</textarea>" +
+ "<input name='post[abc][secret]' type='hidden' value='0' />" +
+ "<input name='post[abc][secret]' checked='checked' type='checkbox' value='1' />"
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_fields_without_object
+ output_buffer = fields(:post) do |f|
+ concat f.text_field(:title)
+ concat f.text_area(:body)
+ concat f.check_box(:secret)
+ end
+
+ expected =
+ "<input name='post[title]' type='text' value='Hello World' />" +
+ "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" +
+ "<input name='post[secret]' type='hidden' value='0' />" +
+ "<input name='post[secret]' checked='checked' type='checkbox' value='1' />"
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_fields_with_only_object
+ output_buffer = fields(model: @post) do |f|
+ concat f.text_field(:title)
+ concat f.text_area(:body)
+ concat f.check_box(:secret)
+ end
+
+ expected =
+ "<input name='post[title]' type='text' value='Hello World' />" +
+ "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" +
+ "<input name='post[secret]' type='hidden' value='0' />" +
+ "<input name='post[secret]' checked='checked' type='checkbox' value='1' />"
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_fields_object_with_bracketed_name
+ output_buffer = fields("author[post]", model: @post) do |f|
+ concat f.label(:title)
+ concat f.text_field(:title)
+ end
+
+ assert_dom_equal "<label for=\"author_post_title\">Title</label>" +
+ "<input name='author[post][title]' type='text' value='Hello World' />",
+ output_buffer
+ end
+
+ def test_fields_object_with_bracketed_name_and_index
+ output_buffer = fields("author[post]", model: @post, index: 1) do |f|
+ concat f.label(:title)
+ concat f.text_field(:title)
+ end
+
+ assert_dom_equal "<label for=\"author_post_1_title\">Title</label>" +
+ "<input name='author[post][1][title]' type='text' value='Hello World' />",
+ output_buffer
+ end
+
+ def test_form_builder_does_not_have_form_with_method
+ assert_not_includes ActionView::Helpers::FormBuilder.instance_methods, :form_with
+ end
+
+ def test_form_with_and_fields
+ form_with(model: @post, scope: :post, id: "create-post") do |post_form|
+ concat post_form.text_field(:title)
+ concat post_form.text_area(:body)
+
+ concat fields(:parent_post, model: @post) { |parent_fields|
+ concat parent_fields.check_box(:secret)
+ }
+ end
+
+ expected = whole_form("/posts/123", "create-post", method: "patch") do
+ "<input name='post[title]' type='text' value='Hello World' />" +
+ "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" +
+ "<input name='parent_post[secret]' type='hidden' value='0' />" +
+ "<input name='parent_post[secret]' checked='checked' type='checkbox' value='1' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_and_fields_with_object
+ form_with(model: @post, scope: :post, id: "create-post") do |post_form|
+ concat post_form.text_field(:title)
+ concat post_form.text_area(:body)
+
+ concat post_form.fields(model: @comment) { |comment_fields|
+ concat comment_fields.text_field(:name)
+ }
+ end
+
+ expected = whole_form("/posts/123", "create-post", method: "patch") do
+ "<input name='post[title]' type='text' value='Hello World' />" +
+ "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" +
+ "<input name='post[comment][name]' type='text' value='new comment' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_and_fields_with_non_nested_association_and_without_object
+ form_with(model: @post) do |f|
+ concat f.fields(:category) { |c|
+ concat c.text_field(:name)
+ }
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ "<input name='post[category][name]' type='text' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ class LabelledFormBuilder < ActionView::Helpers::FormBuilder
+ (field_helpers - %w(hidden_field)).each do |selector|
+ class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
+ def #{selector}(field, *args, &proc)
+ ("<label for='\#{field}'>\#{field.to_s.humanize}:</label> " + super + "<br/>").html_safe
+ end
+ RUBY_EVAL
+ end
+ end
+
+ def test_form_with_with_labelled_builder
+ form_with(model: @post, builder: LabelledFormBuilder) do |f|
+ concat f.text_field(:title)
+ concat f.text_area(:body)
+ concat f.check_box(:secret)
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' /><br/>" +
+ "<label for='body'>Body:</label> <textarea name='post[body]'>\nBack to the hill and over it again!</textarea><br/>" +
+ "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' value='1' /><br/>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_default_form_builder
+ old_default_form_builder, ActionView::Base.default_form_builder =
+ ActionView::Base.default_form_builder, LabelledFormBuilder
+
+ form_with(model: @post) do |f|
+ concat f.text_field(:title)
+ concat f.text_area(:body)
+ concat f.check_box(:secret)
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' /><br/>" +
+ "<label for='body'>Body:</label> <textarea name='post[body]'>\nBack to the hill and over it again!</textarea><br/>" +
+ "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' value='1' /><br/>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ ensure
+ ActionView::Base.default_form_builder = old_default_form_builder
+ end
+
+ def test_lazy_loading_default_form_builder
+ old_default_form_builder, ActionView::Base.default_form_builder =
+ ActionView::Base.default_form_builder, "FormWithActsLikeFormForTest::LabelledFormBuilder"
+
+ form_with(model: @post) do |f|
+ concat f.text_field(:title)
+ end
+
+ expected = whole_form("/posts/123", method: "patch") do
+ "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' /><br/>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ ensure
+ ActionView::Base.default_form_builder = old_default_form_builder
+ end
+
+ def test_form_builder_override
+ self.default_form_builder = LabelledFormBuilder
+
+ output_buffer = fields(:post, model: @post) do |f|
+ concat f.text_field(:title)
+ end
+
+ expected = "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' /><br/>"
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_lazy_loading_form_builder_override
+ self.default_form_builder = "FormWithActsLikeFormForTest::LabelledFormBuilder"
+
+ output_buffer = fields(:post, model: @post) do |f|
+ concat f.text_field(:title)
+ end
+
+ expected = "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' /><br/>"
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_fields_with_labelled_builder
+ output_buffer = fields(:post, model: @post, builder: LabelledFormBuilder) do |f|
+ concat f.text_field(:title)
+ concat f.text_area(:body)
+ concat f.check_box(:secret)
+ end
+
+ expected =
+ "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' /><br/>" +
+ "<label for='body'>Body:</label> <textarea name='post[body]'>\nBack to the hill and over it again!</textarea><br/>" +
+ "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' value='1' /><br/>"
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_labelled_builder_with_nested_fields_without_options_hash
+ klass = nil
+
+ form_with(model: @post, builder: LabelledFormBuilder) do |f|
+ f.fields(:comments, model: Comment.new) do |nested_fields|
+ klass = nested_fields.class
+ ""
+ end
+ end
+
+ assert_equal LabelledFormBuilder, klass
+ end
+
+ def test_form_with_with_labelled_builder_with_nested_fields_with_options_hash
+ klass = nil
+
+ form_with(model: @post, builder: LabelledFormBuilder) do |f|
+ f.fields(:comments, model: Comment.new, index: "foo") do |nested_fields|
+ klass = nested_fields.class
+ ""
+ end
+ end
+
+ assert_equal LabelledFormBuilder, klass
+ end
+
+ def test_form_with_with_labelled_builder_path
+ path = nil
+
+ form_with(model: @post, builder: LabelledFormBuilder) do |f|
+ path = f.to_partial_path
+ ""
+ end
+
+ assert_equal "labelled_form", path
+ end
+
+ class LabelledFormBuilderSubclass < LabelledFormBuilder; end
+
+ def test_form_with_with_labelled_builder_with_nested_fields_with_custom_builder
+ klass = nil
+
+ form_with(model: @post, builder: LabelledFormBuilder) do |f|
+ f.fields(:comments, model: Comment.new, builder: LabelledFormBuilderSubclass) do |nested_fields|
+ klass = nested_fields.class
+ ""
+ end
+ end
+
+ assert_equal LabelledFormBuilderSubclass, klass
+ end
+
+ def test_form_with_with_html_options_adds_options_to_form_tag
+ form_with(model: @post, html: { id: "some_form", class: "some_class", multipart: true }) do |f| end
+ expected = whole_form("/posts/123", "some_form", "some_class", method: "patch", multipart: "multipart/form-data")
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_string_url_option
+ form_with(model: @post, url: "http://www.otherdomain.com") do |f| end
+
+ assert_dom_equal whole_form("http://www.otherdomain.com", method: "patch"), output_buffer
+ end
+
+ def test_form_with_with_hash_url_option
+ form_with(model: @post, url: { controller: "controller", action: "action" }) do |f| end
+
+ assert_equal "controller", @url_for_options[:controller]
+ assert_equal "action", @url_for_options[:action]
+ end
+
+ def test_form_with_with_record_url_option
+ form_with(model: @post, url: @post) do |f| end
+
+ expected = whole_form("/posts/123", method: "patch")
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_existing_object
+ form_with(model: @post) do |f| end
+
+ expected = whole_form("/posts/123", method: "patch")
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_new_object
+ post = Post.new
+ post.persisted = false
+ def post.to_key; nil; end
+
+ form_with(model: post) {}
+
+ expected = whole_form("/posts")
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_existing_object_in_list
+ @comment.save
+ form_with(model: [@post, @comment]) {}
+
+ expected = whole_form(post_comment_path(@post, @comment), method: "patch")
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_new_object_in_list
+ form_with(model: [@post, @comment]) {}
+
+ expected = whole_form(post_comments_path(@post))
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_existing_object_and_namespace_in_list
+ @comment.save
+ form_with(model: [:admin, @post, @comment]) {}
+
+ expected = whole_form(admin_post_comment_path(@post, @comment), method: "patch")
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_new_object_and_namespace_in_list
+ form_with(model: [:admin, @post, @comment]) {}
+
+ expected = whole_form(admin_post_comments_path(@post))
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_existing_object_and_custom_url
+ form_with(model: @post, url: "/super_posts") do |f| end
+
+ expected = whole_form("/super_posts", method: "patch")
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_default_method_as_patch
+ form_with(model: @post) {}
+ expected = whole_form("/posts/123", method: "patch")
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_with_data_attributes
+ form_with(model: @post, data: { behavior: "stuff" }) {}
+ assert_match %r|data-behavior="stuff"|, output_buffer
+ assert_match %r|data-remote="true"|, output_buffer
+ end
+
+ def test_fields_returns_block_result
+ output = fields(model: Post.new) { |f| "fields" }
+ assert_equal "fields", output
+ end
+
+ def test_form_with_only_instantiates_builder_once
+ initialization_count = 0
+ builder_class = Class.new(ActionView::Helpers::FormBuilder) do
+ define_method :initialize do |*args|
+ super(*args)
+ initialization_count += 1
+ end
+ end
+
+ form_with(model: @post, builder: builder_class) {}
+ assert_equal 1, initialization_count, "form builder instantiated more than once"
+ end
+
+ private
+ def hidden_fields(options = {})
+ method = options[:method]
+
+ if options.fetch(:skip_enforcing_utf8, false)
+ txt = ""
+ else
+ txt = %{<input name="utf8" type="hidden" value="&#x2713;" />}
+ end
+
+ if method && !%w(get post).include?(method.to_s)
+ txt << %{<input name="_method" type="hidden" value="#{method}" />}
+ end
+
+ txt
+ 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 << %{ enctype="multipart/form-data"} if multipart
+ txt << %{ data-remote="true"} unless local
+ txt << %{ class="#{html_class}"} if html_class
+ txt << %{ id="#{id}"} if id
+ method = method.to_s == "get" ? "get" : "post"
+ txt << %{ method="#{method}">}
+ end
+
+ def whole_form(action = "/", id = nil, html_class = nil, local: false, **options)
+ contents = block_given? ? yield : ""
+
+ method, multipart = options.values_at(:method, :multipart)
+
+ form_text(action, id, html_class, local, multipart, method) + hidden_fields(options.slice :method, :skip_enforcing_utf8) + contents + "</form>"
+ end
+
+ def protect_against_forgery?
+ false
+ end
+
+ def with_locale(testing_locale = :label)
+ old_locale, I18n.locale = I18n.locale, testing_locale
+ yield
+ ensure
+ I18n.locale = old_locale
+ end
+end
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index 70f690478d..2bc0434771 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -257,7 +257,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_label_with_non_active_record_object
- form_for(OpenStruct.new(name:"ok"), as: "person", url: "an_url", html: { id: "create-person" }) do |f|
+ form_for(OpenStruct.new(name: "ok"), as: "person", url: "an_url", html: { id: "create-person" }) do |f|
f.label(:name)
end
@@ -600,7 +600,7 @@ class FormHelperTest < ActionView::TestCase
def test_check_box_checked_if_option_checked_is_present
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />',
- check_box("post", "secret", "checked"=>"checked")
+ check_box("post", "secret", "checked" => "checked")
)
end
@@ -746,7 +746,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_check_box_with_multiple_behavior
- @post.comment_ids = [2,3]
+ @post.comment_ids = [2, 3]
assert_dom_equal(
'<input name="post[comment_ids][]" type="hidden" value="0" /><input id="post_comment_ids_1" name="post[comment_ids][]" type="checkbox" value="1" />',
check_box("post", "comment_ids", { multiple: true }, 1)
@@ -758,7 +758,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_check_box_with_multiple_behavior_and_index
- @post.comment_ids = [2,3]
+ @post.comment_ids = [2, 3]
assert_dom_equal(
'<input name="post[foo][comment_ids][]" type="hidden" value="0" /><input id="post_foo_comment_ids_1" name="post[foo][comment_ids][]" type="checkbox" value="1" />',
check_box("post", "comment_ids", { multiple: true, index: "foo" }, 1)
@@ -1033,7 +1033,7 @@ class FormHelperTest < ActionView::TestCase
def test_date_field_with_value_attr
expected = %{<input id="post_written_on" name="post[written_on]" type="date" value="2013-06-29" />}
- value = Date.new(2013,6,29)
+ value = Date.new(2013, 6, 29)
assert_dom_equal(expected, date_field("post", "written_on", value: value))
end
@@ -1141,7 +1141,7 @@ class FormHelperTest < ActionView::TestCase
def test_datetime_field_with_value_attr
expected = %{<input id="post_written_on" name="post[written_on]" type="datetime-local" value="2013-06-29T13:37:00+00:00" />}
- value = DateTime.new(2013,6,29,13,37)
+ value = DateTime.new(2013, 6, 29, 13, 37)
assert_dom_equal(expected, datetime_field("post", "written_on", value: value))
end
@@ -1436,7 +1436,7 @@ class FormHelperTest < ActionView::TestCase
)
assert_dom_equal(
%{<input id="post_#{pid}_title" name="post[#{pid}][title]" type="text" value="Hello World" />},
- text_field("post[]","title")
+ text_field("post[]", "title")
)
assert_dom_equal(
%{<textarea id="post_#{pid}_body" name="post[#{pid}][body]">\nBack to the hill and over it again!</textarea>},
@@ -1559,10 +1559,10 @@ class FormHelperTest < ActionView::TestCase
expected = whole_form("/posts", "new_post", "new_post") do
"<input type='hidden' name='post[active]' value='' />" +
- "<label for='post_active_true'>"+
+ "<label for='post_active_true'>" +
"<input id='post_active_true' name='post[active]' type='radio' value='true' />" +
"true</label>" +
- "<label for='post_active_false'>"+
+ "<label for='post_active_false'>" +
"<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" +
"false</label>"
end
@@ -1585,12 +1585,12 @@ class FormHelperTest < ActionView::TestCase
expected = whole_form("/posts", "new_post_1", "new_post") do
"<input type='hidden' name='post[active]' value='' />" +
- "<label for='post_active_true'>"+
+ "<label for='post_active_true'>" +
"<input id='post_active_true' name='post[active]' type='radio' value='true' />" +
"true</label>" +
- "<label for='post_active_false'>"+
+ "<label for='post_active_false'>" +
"<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" +
- "false</label>"+
+ "false</label>" +
"<input id='post_id' name='post[id]' type='hidden' value='1' />"
end
@@ -1698,7 +1698,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "new_post_1", "new_post") do
- "<input name='post[tag_ids][]' type='hidden' value='' />"+
+ "<input name='post[tag_ids][]' type='hidden' value='' />" +
"<label for='post_tag_ids_1'>" +
"<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" +
"Tag 1</label>" +
@@ -1751,8 +1751,6 @@ class FormHelperTest < ActionView::TestCase
end
def test_form_for_with_file_field_generate_multipart
- Post.send :attr_accessor, :file
-
form_for(@post, html: { id: "create-post" }) do |f|
concat f.file_field(:file)
end
@@ -1765,8 +1763,6 @@ class FormHelperTest < ActionView::TestCase
end
def test_fields_for_with_file_field_generate_multipart
- Comment.send :attr_accessor, :file
-
form_for(@post) do |f|
concat f.fields_for(:comment, @post) { |c|
concat c.file_field(:file)
@@ -1808,7 +1804,7 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
- def test_form_for_with_symbol_object_name
+ def test_form_for_with_symbol_as
form_for(@post, as: "other_name", html: { id: "create-post" }) do |f|
concat f.label(:title, class: "post_title")
concat f.text_field(:title)
@@ -1817,7 +1813,7 @@ class FormHelperTest < ActionView::TestCase
concat f.submit("Create post")
end
- expected = whole_form("/posts/123", "create-post", "edit_other_name", method: "patch") do
+ expected = whole_form("/posts/123", "create-post", "edit_other_name", method: "patch") do
"<label for='other_name_title' class='post_title'>Title</label>" +
"<input name='other_name[title]' id='other_name_title' value='Hello World' type='text' />" +
"<textarea name='other_name[body]' id='other_name_body'>\nBack to the hill and over it again!</textarea>" +
@@ -1833,9 +1829,9 @@ class FormHelperTest < ActionView::TestCase
obj = Class.new do
private
- def private_property
- raise "This method should not be called."
- end
+ def private_property
+ raise "This method should not be called."
+ end
end.new
form_for(obj, as: "other_name", url: "/", html: { id: "edit-other-name" }) do |f|
@@ -1850,7 +1846,7 @@ class FormHelperTest < ActionView::TestCase
concat f.check_box(:secret)
end
- expected = whole_form("/", "create-post", "edit_post", method: "delete") do
+ expected = whole_form("/", "create-post", "edit_post", method: "delete") do
"<input name='post[title]' type='text' id='post_title' value='Hello World' />" +
"<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
@@ -1867,7 +1863,7 @@ class FormHelperTest < ActionView::TestCase
concat f.check_box(:secret)
end
- expected = whole_form("/", "create-post", "edit_post", method: "delete") do
+ expected = whole_form("/", "create-post", "edit_post", method: "delete") do
"<input name='post[title]' type='text' id='post_title' value='Hello World' />" +
"<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
@@ -1884,7 +1880,7 @@ class FormHelperTest < ActionView::TestCase
concat f.search_field(:title)
end
- expected = whole_form("/search", "search-post", "new_post", method: "get") do
+ expected = whole_form("/search", "search-post", "new_post", method: "get") do
"<input name='post[title]' type='search' id='post_title' />"
end
@@ -1898,7 +1894,7 @@ class FormHelperTest < ActionView::TestCase
concat f.check_box(:secret)
end
- expected = whole_form("/", "create-post", "edit_post", method: "patch", remote: true) do
+ expected = whole_form("/", "create-post", "edit_post", method: "patch", remote: true) do
"<input name='post[title]' type='text' id='post_title' value='Hello World' />" +
"<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
@@ -1939,7 +1935,7 @@ class FormHelperTest < ActionView::TestCase
concat f.check_box(:secret)
end
- expected = whole_form("/", "create-post", "edit_post", method: "patch", remote: true) do
+ expected = whole_form("/", "create-post", "edit_post", method: "patch", remote: true) do
"<input name='post[title]' type='text' id='post_title' value='Hello World' />" +
"<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
@@ -1958,7 +1954,7 @@ class FormHelperTest < ActionView::TestCase
concat f.check_box(:secret)
end
- expected = whole_form("/posts", "new_post", "new_post", remote: true) do
+ expected = whole_form("/posts", "new_post", "new_post", remote: true) do
"<input name='post[title]' type='text' id='post_title' value='Hello World' />" +
"<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
@@ -2266,11 +2262,13 @@ class FormHelperTest < ActionView::TestCase
concat f.fields_for("comment[]", @comment) { |c|
concat c.text_field(:name)
}
+ concat f.text_field(:body)
end
expected = whole_form("/posts/123", "edit_post[]", "edit_post[]", method: "patch") do
"<input name='post[123][title]' type='text' id='post_123_title' value='Hello World' />" +
- "<input name='post[123][comment][][name]' type='text' id='post_123_comment__name' value='new comment' />"
+ "<input name='post[123][comment][][name]' type='text' id='post_123_comment__name' value='new comment' />" +
+ "<input name='post[123][body]' type='text' id='post_123_body' value='Back to the hill and over it again!' />"
end
assert_dom_equal expected, output_buffer
@@ -2853,7 +2851,7 @@ class FormHelperTest < ActionView::TestCase
@post.comments = []
form_for(@post) do |f|
- concat f.fields_for(:comments, Comment.new(321), child_index: -> { "abc" } ) { |cf|
+ concat f.fields_for(:comments, Comment.new(321), child_index: -> { "abc" }) { |cf|
concat cf.text_field(:name)
}
end
@@ -3120,7 +3118,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_form_builder_does_not_have_form_for_method
- assert !ActionView::Helpers::FormBuilder.instance_methods.include?(:form_for)
+ assert_not_includes ActionView::Helpers::FormBuilder.instance_methods, :form_for
end
def test_form_for_and_fields_for
@@ -3439,11 +3437,11 @@ class FormHelperTest < ActionView::TestCase
end
end
- form_for(@post, builder: builder_class) { }
+ form_for(@post, builder: builder_class) {}
assert_equal 1, initialization_count, "form builder instantiated more than once"
end
- protected
+ private
def hidden_fields(options = {})
method = options[:method]
diff --git a/actionview/test/template/form_options_helper_test.rb b/actionview/test/template/form_options_helper_test.rb
index 18dfec1b37..51f5d25958 100644
--- a/actionview/test/template/form_options_helper_test.rb
+++ b/actionview/test/template/form_options_helper_test.rb
@@ -74,7 +74,7 @@ class FormOptionsHelperTest < ActionView::TestCase
def test_collection_options_with_proc_for_selected
assert_dom_equal(
"<option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\" selected=\"selected\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option>",
- options_from_collection_for_select(dummy_posts, "author_name", "title", lambda{|p| p.author_name == "Babe" })
+ options_from_collection_for_select(dummy_posts, "author_name", "title", lambda { |p| p.author_name == "Babe" })
)
end
@@ -102,7 +102,7 @@ class FormOptionsHelperTest < ActionView::TestCase
def test_collection_options_with_proc_for_disabled
assert_dom_equal(
"<option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\" disabled=\"disabled\">Babe went home</option>\n<option value=\"Cabe\" disabled=\"disabled\">Cabe went home</option>",
- options_from_collection_for_select(dummy_posts, "author_name", "title", disabled: lambda {|p| %w(Babe Cabe).include?(p.author_name)})
+ options_from_collection_for_select(dummy_posts, "author_name", "title", disabled: lambda { |p| %w(Babe Cabe).include?(p.author_name) })
)
end
@@ -258,7 +258,7 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_collection_options_with_preselected_value_as_string_and_option_value_is_integer
- albums = [ Album.new(1, "first","rap"), Album.new(2, "second","pop")]
+ albums = [ Album.new(1, "first", "rap"), Album.new(2, "second", "pop")]
assert_dom_equal(
%(<option selected="selected" value="1">rap</option>\n<option value="2">pop</option>),
options_from_collection_for_select(albums, "id", "genre", selected: "1")
@@ -266,7 +266,7 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_collection_options_with_preselected_value_as_integer_and_option_value_is_string
- albums = [ Album.new("1", "first","rap"), Album.new("2", "second","pop")]
+ albums = [ Album.new("1", "first", "rap"), Album.new("2", "second", "pop")]
assert_dom_equal(
%(<option selected="selected" value="1">rap</option>\n<option value="2">pop</option>),
@@ -275,7 +275,7 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_collection_options_with_preselected_value_as_string_and_option_value_is_float
- albums = [ Album.new(1.0, "first","rap"), Album.new(2.0, "second","pop")]
+ albums = [ Album.new(1.0, "first", "rap"), Album.new(2.0, "second", "pop")]
assert_dom_equal(
%(<option value="1.0">rap</option>\n<option value="2.0" selected="selected">pop</option>),
@@ -284,7 +284,7 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_collection_options_with_preselected_value_as_nil
- albums = [ Album.new(1.0, "first","rap"), Album.new(2.0, "second","pop")]
+ albums = [ Album.new(1.0, "first", "rap"), Album.new(2.0, "second", "pop")]
assert_dom_equal(
%(<option value="1.0">rap</option>\n<option value="2.0">pop</option>),
@@ -293,7 +293,7 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_collection_options_with_disabled_value_as_nil
- albums = [ Album.new(1.0, "first","rap"), Album.new(2.0, "second","pop")]
+ albums = [ Album.new(1.0, "first", "rap"), Album.new(2.0, "second", "pop")]
assert_dom_equal(
%(<option value="1.0">rap</option>\n<option value="2.0">pop</option>),
@@ -302,7 +302,7 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_collection_options_with_disabled_value_as_array
- albums = [ Album.new(1.0, "first","rap"), Album.new(2.0, "second","pop")]
+ albums = [ Album.new(1.0, "first", "rap"), Album.new(2.0, "second", "pop")]
assert_dom_equal(
%(<option disabled="disabled" value="1.0">rap</option>\n<option disabled="disabled" value="2.0">pop</option>),
@@ -311,11 +311,11 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_collection_options_with_preselected_values_as_string_array_and_option_value_is_float
- albums = [ Album.new(1.0, "first","rap"), Album.new(2.0, "second","pop"), Album.new(3.0, "third","country") ]
+ albums = [ Album.new(1.0, "first", "rap"), Album.new(2.0, "second", "pop"), Album.new(3.0, "third", "country") ]
assert_dom_equal(
%(<option value="1.0" selected="selected">rap</option>\n<option value="2.0">pop</option>\n<option value="3.0" selected="selected">country</option>),
- options_from_collection_for_select(albums, "id", "genre", ["1.0","3.0"])
+ options_from_collection_for_select(albums, "id", "genre", ["1.0", "3.0"])
)
end
@@ -335,9 +335,9 @@ class FormOptionsHelperTest < ActionView::TestCase
"<optgroup label=\"North America\"><option value=\"US\">United States</option>\n<option value=\"Canada\">Canada</option></optgroup><optgroup label=\"Europe\"><option value=\"GB\">Great Britain</option>\n<option value=\"Germany\">Germany</option></optgroup>",
grouped_options_for_select([
["North America",
- [["United States","US"],"Canada"]],
+ [["United States", "US"], "Canada"]],
["Europe",
- [["Great Britain","GB"], "Germany"]]
+ [["Great Britain", "GB"], "Germany"]]
])
)
end
@@ -346,8 +346,8 @@ class FormOptionsHelperTest < ActionView::TestCase
assert_dom_equal(
"<optgroup label=\"North America\" data-foo=\"bar\"><option value=\"US\">United States</option>\n<option value=\"Canada\">Canada</option></optgroup><optgroup label=\"Europe\" disabled=\"disabled\"><option value=\"GB\">Great Britain</option>\n<option value=\"Germany\">Germany</option></optgroup>",
grouped_options_for_select([
- ["North America", [["United States","US"],"Canada"], data: { foo: "bar" }],
- ["Europe", [["Great Britain","GB"], "Germany"], disabled: "disabled"]
+ ["North America", [["United States", "US"], "Canada"], data: { foo: "bar" }],
+ ["Europe", [["Great Britain", "GB"], "Germany"], disabled: "disabled"]
])
)
end
@@ -356,38 +356,38 @@ class FormOptionsHelperTest < ActionView::TestCase
assert_dom_equal(
"<optgroup label=\"----------\"><option value=\"US\">US</option>\n<option value=\"Canada\">Canada</option></optgroup><optgroup label=\"----------\"><option value=\"GB\">GB</option>\n<option value=\"Germany\">Germany</option></optgroup>",
- grouped_options_for_select([["US","Canada"] , ["GB", "Germany"]], nil, divider: "----------")
+ grouped_options_for_select([["US", "Canada"] , ["GB", "Germany"]], nil, divider: "----------")
)
end
def test_grouped_options_for_select_with_selected_and_prompt
assert_dom_equal(
"<option value=\"\">Choose a product...</option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option selected=\"selected\" value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>",
- grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], "Cowboy Hat", prompt: "Choose a product...")
+ grouped_options_for_select([["Hats", ["Baseball Cap", "Cowboy Hat"]]], "Cowboy Hat", prompt: "Choose a product...")
)
end
def test_grouped_options_for_select_with_selected_and_prompt_true
assert_dom_equal(
"<option value=\"\">Please select</option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option selected=\"selected\" value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>",
- grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], "Cowboy Hat", prompt: true)
+ grouped_options_for_select([["Hats", ["Baseball Cap", "Cowboy Hat"]]], "Cowboy Hat", prompt: true)
)
end
def test_grouped_options_for_select_returns_html_safe_string
- assert grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]]).html_safe?
+ assert grouped_options_for_select([["Hats", ["Baseball Cap", "Cowboy Hat"]]]).html_safe?
end
def test_grouped_options_for_select_with_prompt_returns_html_escaped_string
assert_dom_equal(
"<option value=\"\">&lt;Choose One&gt;</option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>",
- grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], nil, prompt: "<Choose One>"))
+ grouped_options_for_select([["Hats", ["Baseball Cap", "Cowboy Hat"]]], nil, prompt: "<Choose One>"))
end
def test_optgroups_with_with_options_with_hash
assert_dom_equal(
"<optgroup label=\"North America\"><option value=\"United States\">United States</option>\n<option value=\"Canada\">Canada</option></optgroup><optgroup label=\"Europe\"><option value=\"Denmark\">Denmark</option>\n<option value=\"Germany\">Germany</option></optgroup>",
- grouped_options_for_select("North America" => ["United States","Canada"], "Europe" => ["Denmark","Germany"])
+ grouped_options_for_select("North America" => ["United States", "Canada"], "Europe" => ["Denmark", "Germany"])
)
end
@@ -402,7 +402,7 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_time_zone_options_with_selected
- opts = time_zone_options_for_select( "D" )
+ opts = time_zone_options_for_select("D")
assert_dom_equal "<option value=\"A\">A</option>\n" +
"<option value=\"B\">B</option>\n" +
"<option value=\"C\">C</option>\n" +
@@ -412,7 +412,7 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_time_zone_options_with_unknown_selected
- opts = time_zone_options_for_select( "K" )
+ opts = time_zone_options_for_select("K")
assert_dom_equal "<option value=\"A\">A</option>\n" +
"<option value=\"B\">B</option>\n" +
"<option value=\"C\">C</option>\n" +
@@ -422,8 +422,8 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_time_zone_options_with_priority_zones
- zones = [ ActiveSupport::TimeZone.new( "B" ), ActiveSupport::TimeZone.new( "E" ) ]
- opts = time_zone_options_for_select( nil, zones )
+ zones = [ ActiveSupport::TimeZone.new("B"), ActiveSupport::TimeZone.new("E") ]
+ opts = time_zone_options_for_select(nil, zones)
assert_dom_equal "<option value=\"B\">B</option>\n" +
"<option value=\"E\">E</option>" +
"<option value=\"\" disabled=\"disabled\">-------------</option>\n" +
@@ -434,8 +434,8 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_time_zone_options_with_selected_priority_zones
- zones = [ ActiveSupport::TimeZone.new( "B" ), ActiveSupport::TimeZone.new( "E" ) ]
- opts = time_zone_options_for_select( "E", zones )
+ zones = [ ActiveSupport::TimeZone.new("B"), ActiveSupport::TimeZone.new("E") ]
+ opts = time_zone_options_for_select("E", zones)
assert_dom_equal "<option value=\"B\">B</option>\n" +
"<option value=\"E\" selected=\"selected\">E</option>" +
"<option value=\"\" disabled=\"disabled\">-------------</option>\n" +
@@ -446,8 +446,8 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_time_zone_options_with_unselected_priority_zones
- zones = [ ActiveSupport::TimeZone.new( "B" ), ActiveSupport::TimeZone.new( "E" ) ]
- opts = time_zone_options_for_select( "C", zones )
+ zones = [ ActiveSupport::TimeZone.new("B"), ActiveSupport::TimeZone.new("E") ]
+ opts = time_zone_options_for_select("C", zones)
assert_dom_equal "<option value=\"B\">B</option>\n" +
"<option value=\"E\">E</option>" +
"<option value=\"\" disabled=\"disabled\">-------------</option>\n" +
@@ -459,7 +459,7 @@ class FormOptionsHelperTest < ActionView::TestCase
def test_time_zone_options_with_priority_zones_does_not_mutate_time_zones
original_zones = ActiveSupport::TimeZone.all.dup
- zones = [ ActiveSupport::TimeZone.new( "B" ), ActiveSupport::TimeZone.new( "E" ) ]
+ zones = [ ActiveSupport::TimeZone.new("B"), ActiveSupport::TimeZone.new("E") ]
time_zone_options_for_select(nil, zones)
assert_equal original_zones, ActiveSupport::TimeZone.all
end
@@ -628,7 +628,7 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_select_with_multiple_to_add_hidden_input
- output_buffer = select(:post, :category, "", {}, multiple: true)
+ output_buffer = select(:post, :category, "", {}, multiple: true)
assert_dom_equal(
"<input type=\"hidden\" name=\"post[category][]\" value=\"\"/><select multiple=\"multiple\" id=\"post_category\" name=\"post[category][]\"></select>",
output_buffer
@@ -636,7 +636,7 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_select_with_multiple_and_without_hidden_input
- output_buffer = select(:post, :category, "", {include_hidden: false}, multiple: true)
+ output_buffer = select(:post, :category, "", { include_hidden: false }, multiple: true)
assert_dom_equal(
"<select multiple=\"multiple\" id=\"post_category\" name=\"post[category][]\"></select>",
output_buffer
@@ -644,7 +644,7 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_select_with_multiple_and_with_explicit_name_ending_with_brackets
- output_buffer = select(:post, :category, [], {include_hidden: false}, multiple: true, name: "post[category][]")
+ output_buffer = select(:post, :category, [], { include_hidden: false }, multiple: true, name: "post[category][]")
assert_dom_equal(
"<select multiple=\"multiple\" id=\"post_category\" name=\"post[category][]\"></select>",
output_buffer
@@ -652,7 +652,7 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_select_with_multiple_and_disabled_to_add_disabled_hidden_input
- output_buffer = select(:post, :category, "", {}, multiple: true, disabled: true)
+ output_buffer = select(:post, :category, "", {}, multiple: true, disabled: true)
assert_dom_equal(
"<input disabled=\"disabled\"type=\"hidden\" name=\"post[category][]\" value=\"\"/><select multiple=\"multiple\" disabled=\"disabled\" id=\"post_category\" name=\"post[category][]\"></select>",
output_buffer
@@ -1030,7 +1030,7 @@ class FormOptionsHelperTest < ActionView::TestCase
def test_time_zone_select
@firm = Firm.new("D")
- html = time_zone_select( "firm", "time_zone" )
+ html = time_zone_select("firm", "time_zone")
assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
"<option value=\"A\">A</option>\n" +
"<option value=\"B\">B</option>\n" +
@@ -1180,7 +1180,7 @@ class FormOptionsHelperTest < ActionView::TestCase
def test_time_zone_select_with_priority_zones
@firm = Firm.new("D")
zones = [ ActiveSupport::TimeZone.new("A"), ActiveSupport::TimeZone.new("D") ]
- html = time_zone_select("firm", "time_zone", zones )
+ html = time_zone_select("firm", "time_zone", zones)
assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
"<option value=\"A\">A</option>\n" +
"<option value=\"D\" selected=\"selected\">D</option>" +
@@ -1236,7 +1236,7 @@ class FormOptionsHelperTest < ActionView::TestCase
@firm = Firm.new()
@firm.time_zone = nil
- html = time_zone_select( "firm", "time_zone", nil, default: "B" )
+ html = time_zone_select("firm", "time_zone", nil, default: "B")
assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
"<option value=\"A\">A</option>\n" +
"<option value=\"B\" selected=\"selected\">B</option>\n" +
@@ -1250,7 +1250,7 @@ class FormOptionsHelperTest < ActionView::TestCase
def test_time_zone_select_with_default_time_zone_and_value
@firm = Firm.new("D")
- html = time_zone_select( "firm", "time_zone", nil, default: "B" )
+ html = time_zone_select("firm", "time_zone", nil, default: "B")
assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
"<option value=\"A\">A</option>\n" +
"<option value=\"B\">B</option>\n" +
@@ -1313,21 +1313,21 @@ class FormOptionsHelperTest < ActionView::TestCase
def test_option_html_attributes_with_single_element_hash
assert_equal(
- {class: "fancy"},
+ { class: "fancy" },
option_html_attributes([ "foo", "bar", { class: "fancy" } ])
)
end
def test_option_html_attributes_with_multiple_element_hash
assert_equal(
- {:class => "fancy", "onclick" => "alert('Hello World');"},
+ { :class => "fancy", "onclick" => "alert('Hello World');" },
option_html_attributes([ "foo", "bar", { :class => "fancy", "onclick" => "alert('Hello World');" } ])
)
end
def test_option_html_attributes_with_multiple_hashes
assert_equal(
- {:class => "fancy", "onclick" => "alert('Hello World');"},
+ { :class => "fancy", "onclick" => "alert('Hello World');" },
option_html_attributes([ "foo", "bar", { class: "fancy" }, { "onclick" => "alert('Hello World');" } ])
)
end
diff --git a/actionview/test/template/form_tag_helper_test.rb b/actionview/test/template/form_tag_helper_test.rb
index a639b56b0b..084c540139 100644
--- a/actionview/test/template/form_tag_helper_test.rb
+++ b/actionview/test/template/form_tag_helper_test.rb
@@ -65,13 +65,13 @@ class FormTagHelperTest < ActionView::TestCase
end
def test_check_box_tag_disabled
- actual = check_box_tag "admin","1", false, disabled: true
+ actual = check_box_tag "admin", "1", false, disabled: true
expected = %(<input id="admin" disabled="disabled" name="admin" type="checkbox" value="1" />)
assert_dom_equal expected, actual
end
def test_check_box_tag_default_checked
- actual = check_box_tag "admin","1", true
+ actual = check_box_tag "admin", "1", true
expected = %(<input id="admin" checked="checked" name="admin" type="checkbox" value="1" />)
assert_dom_equal expected, actual
end
@@ -183,7 +183,7 @@ class FormTagHelperTest < ActionView::TestCase
end
def test_multiple_field_tags_with_same_options
- options = {class: "important"}
+ options = { class: "important" }
assert_dom_equal %(<input name="title" type="file" id="title" class="important"/>), file_field_tag("title", options)
assert_dom_equal %(<input type="password" name="title" id="title" value="Hello!" class="important" />), password_field_tag("title", "Hello!", options)
assert_dom_equal %(<input type="text" name="title" id="title" value="Hello!" class="important" />), text_field_tag("title", "Hello!", options)
@@ -510,6 +510,13 @@ class FormTagHelperTest < ActionView::TestCase
)
end
+ def test_submit_tag_doesnt_have_data_disable_with_twice_with_hash
+ assert_equal(
+ %(<input type="submit" name="commit" value="Save" data-disable-with="Processing..." />),
+ submit_tag("Save", data: { disable_with: "Processing..." })
+ )
+ end
+
def test_submit_tag_with_symbol_value
assert_dom_equal(
%(<input data-disable-with="Save" name='commit' type="submit" value="Save" />),
@@ -694,31 +701,31 @@ class FormTagHelperTest < ActionView::TestCase
def test_text_area_tag_options_symbolize_keys_side_effects
options = { option: "random_option" }
text_area_tag "body", "hello world", options
- assert_equal options, option: "random_option"
+ assert_equal({ option: "random_option" }, options)
end
def test_submit_tag_options_symbolize_keys_side_effects
options = { option: "random_option" }
submit_tag "submit value", options
- assert_equal options, option: "random_option"
+ assert_equal({ option: "random_option" }, options)
end
def test_button_tag_options_symbolize_keys_side_effects
options = { option: "random_option" }
button_tag "button value", options
- assert_equal options, option: "random_option"
+ assert_equal({ option: "random_option" }, options)
end
def test_image_submit_tag_options_symbolize_keys_side_effects
options = { option: "random_option" }
image_submit_tag "submit source", options
- assert_equal options, option: "random_option"
+ assert_equal({ option: "random_option" }, options)
end
def test_image_label_tag_options_symbolize_keys_side_effects
options = { option: "random_option" }
label_tag "submit source", "title", options
- assert_equal options, option: "random_option"
+ assert_equal({ option: "random_option" }, options)
end
def protect_against_forgery?
diff --git a/actionview/test/template/javascript_helper_test.rb b/actionview/test/template/javascript_helper_test.rb
index 0468c845d2..c7670b056b 100644
--- a/actionview/test/template/javascript_helper_test.rb
+++ b/actionview/test/template/javascript_helper_test.rb
@@ -7,7 +7,7 @@ class JavaScriptHelperTest < ActionView::TestCase
setup do
@old_escape_html_entities_in_json = ActiveSupport.escape_html_entities_in_json
- ActiveSupport.escape_html_entities_in_json = true
+ ActiveSupport.escape_html_entities_in_json = true
@template = self
end
@@ -18,7 +18,7 @@ class JavaScriptHelperTest < ActionView::TestCase
def test_escape_javascript
assert_equal "", escape_javascript(nil)
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 %(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!)
@@ -47,8 +47,8 @@ class JavaScriptHelperTest < ActionView::TestCase
# Setting the :extname option will control what extension (if any) is appended to the url for assets
def test_javascript_include_tag
assert_dom_equal "<script src='/foo.js'></script>", javascript_include_tag("/foo")
- assert_dom_equal "<script src='/foo'></script>", javascript_include_tag("/foo", extname: false )
- assert_dom_equal "<script src='/foo.bar'></script>", javascript_include_tag("/foo", extname: ".bar" )
+ assert_dom_equal "<script src='/foo'></script>", javascript_include_tag("/foo", extname: false)
+ assert_dom_equal "<script src='/foo.bar'></script>", javascript_include_tag("/foo", extname: ".bar")
end
def test_javascript_tag_with_options
diff --git a/actionview/test/template/log_subscriber_test.rb b/actionview/test/template/log_subscriber_test.rb
index ece059484c..7f358add7e 100644
--- a/actionview/test/template/log_subscriber_test.rb
+++ b/actionview/test/template/log_subscriber_test.rb
@@ -55,7 +55,7 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
def test_render_text_template
Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do
- @view.render(text: "TEXT")
+ @view.render(plain: "TEXT")
wait
assert_equal 2, @logger.logged(:info).size
diff --git a/actionview/test/template/lookup_context_test.rb b/actionview/test/template/lookup_context_test.rb
index 77fab5652f..b47d92df34 100644
--- a/actionview/test/template/lookup_context_test.rb
+++ b/actionview/test/template/lookup_context_test.rb
@@ -120,8 +120,8 @@ class LookupContextTest < ActiveSupport::TestCase
@lookup_context.with_fallbacks do
assert_equal 3, @lookup_context.view_paths.size
- assert @lookup_context.view_paths.include?(ActionView::FallbackFileSystemResolver.new(""))
- assert @lookup_context.view_paths.include?(ActionView::FallbackFileSystemResolver.new("/"))
+ assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.new("")
+ assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.new("/")
end
end
@@ -279,7 +279,7 @@ class TestMissingTemplate < ActiveSupport::TestCase
test "if a single prefix is passed as a string and the lookup fails, MissingTemplate accepts it" do
e = assert_raise ActionView::MissingTemplate do
- details = {handlers: [], formats: [], variants: [], locale: []}
+ details = { handlers: [], formats: [], variants: [], locale: [] }
@lookup_context.view_paths.find("foo", "parent", true, details)
end
assert_match %r{Missing partial parent/_foo with .* Searched in:\n \* "/Path/to/views"\n}, e.message
diff --git a/actionview/test/template/number_helper_test.rb b/actionview/test/template/number_helper_test.rb
index d55651bd4a..678120a9c9 100644
--- a/actionview/test/template/number_helper_test.rb
+++ b/actionview/test/template/number_helper_test.rb
@@ -4,7 +4,7 @@ class NumberHelperTest < ActionView::TestCase
tests ActionView::Helpers::NumberHelper
def test_number_to_phone
- assert_equal nil, number_to_phone(nil)
+ assert_nil number_to_phone(nil)
assert_equal "555-1234", number_to_phone(5551234)
assert_equal "(800) 555-1212 x 123", number_to_phone(8005551212, area_code: true, extension: 123)
assert_equal "+18005551212", number_to_phone(8005551212, country_code: 1, delimiter: "")
@@ -13,7 +13,7 @@ class NumberHelperTest < ActionView::TestCase
end
def test_number_to_currency
- assert_equal nil, number_to_currency(nil)
+ assert_nil number_to_currency(nil)
assert_equal "$1,234,567,890.50", number_to_currency(1234567890.50)
assert_equal "$1,234,567,892", number_to_currency(1234567891.50, precision: 0)
assert_equal "1,234,567,890.50 - K&#269;", number_to_currency("-1234567890.50", unit: raw("K&#269;"), format: "%n %u", negative_format: "%n - %u")
@@ -25,7 +25,7 @@ class NumberHelperTest < ActionView::TestCase
end
def test_number_to_percentage
- assert_equal nil, number_to_percentage(nil)
+ assert_nil number_to_percentage(nil)
assert_equal "100.000%", number_to_percentage(100)
assert_equal "100.000 %", number_to_percentage(100, format: "%n %")
assert_equal "&lt;b&gt;100.000&lt;/b&gt; %", number_to_percentage(100, format: "<b>%n</b> %")
@@ -43,13 +43,13 @@ class NumberHelperTest < ActionView::TestCase
end
def test_number_with_delimiter
- assert_equal nil, number_with_delimiter(nil)
+ assert_nil number_with_delimiter(nil)
assert_equal "12,345,678", number_with_delimiter(12345678)
assert_equal "0", number_with_delimiter(0)
end
def test_number_with_precision
- assert_equal nil, number_with_precision(nil)
+ assert_nil number_with_precision(nil)
assert_equal "-111.235", number_with_precision(-111.2346)
assert_equal "111.00", number_with_precision(111, precision: 2)
assert_equal "0.00100", number_with_precision(0.001, precision: 5)
@@ -57,13 +57,13 @@ class NumberHelperTest < ActionView::TestCase
end
def test_number_to_human_size
- assert_equal nil, number_to_human_size(nil)
+ assert_nil number_to_human_size(nil)
assert_equal "3 Bytes", number_to_human_size(3.14159265)
assert_equal "1.2 MB", number_to_human_size(1234567, precision: 2)
end
def test_number_to_human
- assert_equal nil, number_to_human(nil)
+ assert_nil number_to_human(nil)
assert_equal "0", number_to_human(0)
assert_equal "1.23 Thousand", number_to_human(1234)
assert_equal "489.0 Thousand", number_to_human(489000, precision: 4, strip_insignificant_zeros: false)
@@ -80,7 +80,7 @@ class NumberHelperTest < ActionView::TestCase
#Including fractionals
distance = { mili: "<b>mm</b>", centi: "<b>cm</b>", deci: "<b>dm</b>", unit: "<b>m</b>",
ten: "<b>dam</b>", hundred: "<b>hm</b>", thousand: "<b>km</b>",
- micro: "<b>um</b>", nano: "<b>nm</b>", pico: "<b>pm</b>", femto: "<b>fm</b>"}
+ micro: "<b>um</b>", nano: "<b>nm</b>", pico: "<b>pm</b>", femto: "<b>fm</b>" }
assert_equal "1.23 &lt;b&gt;mm&lt;/b&gt;", number_to_human(0.00123, units: distance)
assert_equal "1.23 &lt;b&gt;cm&lt;/b&gt;", number_to_human(0.0123, units: distance)
assert_equal "1.23 &lt;b&gt;dm&lt;/b&gt;", number_to_human(0.123, units: distance)
@@ -117,7 +117,7 @@ class NumberHelperTest < ActionView::TestCase
def test_number_to_human_with_custom_translation_scope
I18n.backend.store_translations "ts",
- custom_units_for_number_to_human: {mili: "mm", centi: "cm", deci: "dm", unit: "m", ten: "dam", hundred: "hm", thousand: "km"}
+ custom_units_for_number_to_human: { mili: "mm", centi: "cm", deci: "dm", unit: "m", ten: "dam", hundred: "hm", thousand: "km" }
assert_equal "1.01 cm", number_to_human(0.0101, locale: "ts", units: :custom_units_for_number_to_human)
ensure
I18n.reload!
diff --git a/actionview/test/template/output_safety_helper_test.rb b/actionview/test/template/output_safety_helper_test.rb
index 98b1938276..8691bb11ee 100644
--- a/actionview/test/template/output_safety_helper_test.rb
+++ b/actionview/test/template/output_safety_helper_test.rb
@@ -26,10 +26,10 @@ class OutputSafetyHelperTest < ActionView::TestCase
end
test "safe_join should work recursively similarly to Array.join" do
- joined = safe_join(["a",["b","c"]], ":")
+ joined = safe_join(["a", ["b", "c"]], ":")
assert_equal "a:b:c", joined
- joined = safe_join(['"a"',["<b>","<c>"]], " <br/> ")
+ joined = safe_join(['"a"', ["<b>", "<c>"]], " <br/> ")
assert_equal "&quot;a&quot; &lt;br/&gt; &lt;b&gt; &lt;br/&gt; &lt;c&gt;", joined
end
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index 173c2c4cec..e7e0b147c7 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -362,12 +362,12 @@ module RenderTestCases
def test_render_partial_with_object_uses_render_partial_path
assert_equal "Hello: lifo",
- @controller_view.render(partial: Customer.new("lifo"), locals: {greeting: "Hello"})
+ @controller_view.render(partial: Customer.new("lifo"), locals: { greeting: "Hello" })
end
def test_render_partial_with_object_and_format_uses_render_partial_path
assert_equal "<greeting>Hello</greeting><name>lifo</name>",
- @controller_view.render(partial: Customer.new("lifo"), formats: :xml, locals: {greeting: "Hello"})
+ @controller_view.render(partial: Customer.new("lifo"), formats: :xml, locals: { greeting: "Hello" })
end
def test_render_partial_using_object
@@ -460,7 +460,7 @@ module RenderTestCases
def test_render_knows_about_types_registered_when_extensions_are_checked_earlier_in_initialization
ActionView::Template::Handlers.extensions
ActionView::Template.register_template_handler :foo, CustomHandler
- assert ActionView::Template::Handlers.extensions.include?(:foo)
+ assert_includes ActionView::Template::Handlers.extensions, :foo
ensure
ActionView::Template.unregister_template_handler :foo
end
@@ -520,12 +520,12 @@ module RenderTestCases
def test_render_partial_and_layout_without_block_with_locals
assert_equal %(Before (Foo!)\npartial html\nAfter),
- @view.render(partial: "test/partial", layout: "test/layout_for_partial", locals: { name: "Foo!"})
+ @view.render(partial: "test/partial", layout: "test/layout_for_partial", locals: { name: "Foo!" })
end
def test_render_partial_and_layout_without_block_with_locals_and_rendering_another_partial
assert_equal %(Before (Foo!)\npartial html\npartial with partial\n\nAfter),
- @view.render(partial: "test/partial_with_partial", layout: "test/layout_for_partial", locals: { name: "Foo!"})
+ @view.render(partial: "test/partial_with_partial", layout: "test/layout_for_partial", locals: { name: "Foo!" })
end
def test_render_partial_shortcut_with_block_content
@@ -535,17 +535,17 @@ module RenderTestCases
def test_render_layout_with_a_nested_render_layout_call
assert_equal %(Before (Foo!)\nBefore (Bar!)\npartial html\nAfter\npartial with layout\n\nAfter),
- @view.render(partial: "test/partial_with_layout", layout: "test/layout_for_partial", locals: { name: "Foo!"})
+ @view.render(partial: "test/partial_with_layout", layout: "test/layout_for_partial", locals: { name: "Foo!" })
end
def test_render_layout_with_a_nested_render_layout_call_using_block_with_render_partial
assert_equal %(Before (Foo!)\nBefore (Bar!)\n\n partial html\n\nAfterpartial with layout\n\nAfter),
- @view.render(partial: "test/partial_with_layout_block_partial", layout: "test/layout_for_partial", locals: { name: "Foo!"})
+ @view.render(partial: "test/partial_with_layout_block_partial", layout: "test/layout_for_partial", locals: { name: "Foo!" })
end
def test_render_layout_with_a_nested_render_layout_call_using_block_with_render_content
assert_equal %(Before (Foo!)\nBefore (Bar!)\n\n Content from inside layout!\n\nAfterpartial with layout\n\nAfter),
- @view.render(partial: "test/partial_with_layout_block_content", layout: "test/layout_for_partial", locals: { name: "Foo!"})
+ @view.render(partial: "test/partial_with_layout_block_content", layout: "test/layout_for_partial", locals: { name: "Foo!" })
end
def test_render_partial_with_layout_raises_descriptive_error
diff --git a/actionview/test/template/tag_helper_test.rb b/actionview/test/template/tag_helper_test.rb
index d07312ace3..f1e5946e14 100644
--- a/actionview/test/template/tag_helper_test.rb
+++ b/actionview/test/template/tag_helper_test.rb
@@ -171,7 +171,7 @@ class TagHelperTest < ActionView::TestCase
content_tag("p") { content_tag("b", "Hello") },
output_buffer
assert_equal tag.p(tag.b("Hello")),
- tag.p {tag.b("Hello") },
+ tag.p { tag.b("Hello") },
output_buffer
end
@@ -203,10 +203,10 @@ class TagHelperTest < ActionView::TestCase
end
def test_content_tag_with_unescaped_array_class
- str = content_tag("p", "limelight", {class: ["song", "play>"]}, false)
+ str = content_tag("p", "limelight", { class: ["song", "play>"] }, false)
assert_equal "<p class=\"song play>\">limelight</p>", str
- str = content_tag("p", "limelight", {class: ["song", ["play>"]]}, false)
+ str = content_tag("p", "limelight", { class: ["song", ["play>"]] }, false)
assert_equal "<p class=\"song play>\">limelight</p>", str
end
@@ -228,7 +228,7 @@ class TagHelperTest < ActionView::TestCase
end
def test_content_tag_with_unescaped_empty_array_class
- str = content_tag("p", "limelight", {class: []}, false)
+ str = content_tag("p", "limelight", { class: [] }, false)
assert_equal '<p class="">limelight</p>', str
end
@@ -284,12 +284,12 @@ class TagHelperTest < ActionView::TestCase
def test_tag_does_not_honor_html_safe_double_quotes_as_attributes
assert_dom_equal '<p title="&quot;">content</p>',
- content_tag('p', "content", title: '"'.html_safe)
+ content_tag("p", "content", title: '"'.html_safe)
end
def test_data_tag_does_not_honor_html_safe_double_quotes_as_attributes
assert_dom_equal '<p data-title="&quot;">content</p>',
- content_tag('p', "content", data: { title: '"'.html_safe })
+ content_tag("p", "content", data: { title: '"'.html_safe })
end
def test_skip_invalid_escaped_attributes
@@ -305,39 +305,39 @@ class TagHelperTest < ActionView::TestCase
def test_tag_builder_disable_escaping
assert_equal '<a href="&amp;"></a>', tag.a(href: "&amp;", escape_attributes: false)
- assert_equal '<a href="&amp;">cnt</a>', tag.a(href: "&amp;" , escape_attributes: false) { "cnt"}
+ assert_equal '<a href="&amp;">cnt</a>', tag.a(href: "&amp;" , escape_attributes: false) { "cnt" }
assert_equal '<br data-hidden="&amp;">', tag.br("data-hidden": "&amp;" , escape_attributes: false)
assert_equal '<a href="&amp;">content</a>', tag.a("content", href: "&amp;", escape_attributes: false)
- assert_equal '<a href="&amp;">content</a>', tag.a(href: "&amp;", escape_attributes: false) { "content"}
+ assert_equal '<a href="&amp;">content</a>', tag.a(href: "&amp;", escape_attributes: false) { "content" }
end
def test_data_attributes
["data", :data].each { |data|
assert_dom_equal '<a data-a-float="3.14" data-a-big-decimal="-123.456" data-a-number="1" data-array="[1,2,3]" data-hash="{&quot;key&quot;:&quot;value&quot;}" data-string-with-quotes="double&quot;quote&quot;party&quot;" data-string="hello" data-symbol="foo" />',
- tag("a", data => { a_float: 3.14, a_big_decimal: BigDecimal.new("-123.456"), a_number: 1, string: "hello", symbol: :foo, array: [1, 2, 3], hash: { key: "value"}, string_with_quotes: 'double"quote"party"' })
+ tag("a", data => { a_float: 3.14, a_big_decimal: BigDecimal.new("-123.456"), a_number: 1, string: "hello", symbol: :foo, array: [1, 2, 3], hash: { key: "value" }, string_with_quotes: 'double"quote"party"' })
assert_dom_equal '<a data-a-float="3.14" data-a-big-decimal="-123.456" data-a-number="1" data-array="[1,2,3]" data-hash="{&quot;key&quot;:&quot;value&quot;}" data-string-with-quotes="double&quot;quote&quot;party&quot;" data-string="hello" data-symbol="foo" />',
- tag.a(data: { a_float: 3.14, a_big_decimal: BigDecimal.new("-123.456"), a_number: 1, string: "hello", symbol: :foo, array: [1, 2, 3], hash: { key: "value"}, string_with_quotes: 'double"quote"party"' })
+ tag.a(data: { a_float: 3.14, a_big_decimal: BigDecimal.new("-123.456"), a_number: 1, string: "hello", symbol: :foo, array: [1, 2, 3], hash: { key: "value" }, string_with_quotes: 'double"quote"party"' })
}
end
def test_aria_attributes
["aria", :aria].each { |aria|
assert_dom_equal '<a aria-a-float="3.14" aria-a-big-decimal="-123.456" aria-a-number="1" aria-array="[1,2,3]" aria-hash="{&quot;key&quot;:&quot;value&quot;}" aria-string-with-quotes="double&quot;quote&quot;party&quot;" aria-string="hello" aria-symbol="foo" />',
- tag("a", aria => { a_float: 3.14, a_big_decimal: BigDecimal.new("-123.456"), a_number: 1, string: "hello", symbol: :foo, array: [1, 2, 3], hash: { key: "value"}, string_with_quotes: 'double"quote"party"' })
+ tag("a", aria => { a_float: 3.14, a_big_decimal: BigDecimal.new("-123.456"), a_number: 1, string: "hello", symbol: :foo, array: [1, 2, 3], hash: { key: "value" }, string_with_quotes: 'double"quote"party"' })
assert_dom_equal '<a aria-a-float="3.14" aria-a-big-decimal="-123.456" aria-a-number="1" aria-array="[1,2,3]" aria-hash="{&quot;key&quot;:&quot;value&quot;}" aria-string-with-quotes="double&quot;quote&quot;party&quot;" aria-string="hello" aria-symbol="foo" />',
- tag.a(aria: { a_float: 3.14, a_big_decimal: BigDecimal.new("-123.456"), a_number: 1, string: "hello", symbol: :foo, array: [1, 2, 3], hash: { key: "value"}, string_with_quotes: 'double"quote"party"' })
+ tag.a(aria: { a_float: 3.14, a_big_decimal: BigDecimal.new("-123.456"), a_number: 1, string: "hello", symbol: :foo, array: [1, 2, 3], hash: { key: "value" }, string_with_quotes: 'double"quote"party"' })
}
end
def test_link_to_data_nil_equal
div_type1 = content_tag(:div, "test", "data-tooltip" => nil)
- div_type2 = content_tag(:div, "test", data: {tooltip: nil})
+ div_type2 = content_tag(:div, "test", data: { tooltip: nil })
assert_dom_equal div_type1, div_type2
end
def test_tag_builder_link_to_data_nil_equal
div_type1 = tag.div "test", 'data-tooltip': nil
- div_type2 = tag.div "test", data: {tooltip: nil}
+ div_type2 = tag.div "test", data: { tooltip: nil }
assert_dom_equal div_type1, div_type2
end
diff --git a/actionview/test/template/template_test.rb b/actionview/test/template/template_test.rb
index 533c1c3219..9d31a98703 100644
--- a/actionview/test/template/template_test.rb
+++ b/actionview/test/template/template_test.rb
@@ -35,7 +35,7 @@ class TestERBTemplate < ActiveSupport::TestCase
"<%= @virtual_path %>",
"partial",
ERBHandler,
- :virtual_path => "partial"
+ virtual_path: "partial"
)
end
@@ -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, "hello template", details.fetch(:handler) { ERBHandler }, { virtual_path: "hello" }.merge!(details))
end
def render(locals = {})
@@ -80,7 +80,7 @@ class TestERBTemplate < ActiveSupport::TestCase
end
def test_raw_template
- @template = new_template("<%= hello %>", :handler => ActionView::Template::Handlers::Raw.new)
+ @template = new_template("<%= hello %>", handler: ActionView::Template::Handlers::Raw.new)
assert_equal "<%= hello %>", render
end
@@ -91,7 +91,7 @@ class TestERBTemplate < ActiveSupport::TestCase
end
def test_template_does_not_lose_its_source_after_rendering_if_it_does_not_have_a_virtual_path
- @template = new_template("Hello", :virtual_path => nil)
+ @template = new_template("Hello", virtual_path: nil)
render
assert_equal "Hello", @template.source
end
@@ -99,7 +99,7 @@ class TestERBTemplate < ActiveSupport::TestCase
def test_locals
@template = new_template("<%= my_local %>")
@template.locals = [:my_local]
- assert_equal "I am a local", render(:my_local => "I am a local")
+ assert_equal "I am a local", render(my_local: "I am a local")
end
def test_restores_buffer
@@ -116,23 +116,23 @@ class TestERBTemplate < ActiveSupport::TestCase
end
def test_refresh_with_templates
- @template = new_template("Hello", :virtual_path => "test/foo/bar")
+ @template = new_template("Hello", virtual_path: "test/foo/bar")
@template.locals = [:key]
- assert_called_with(@context.lookup_context, :find_template,["bar", %w(test/foo), false, [:key]], returns: "template") do
+ assert_called_with(@context.lookup_context, :find_template, ["bar", %w(test/foo), false, [:key]], returns: "template") do
assert_equal "template", @template.refresh(@context)
end
end
def test_refresh_with_partials
- @template = new_template("Hello", :virtual_path => "test/_foo")
+ @template = new_template("Hello", virtual_path: "test/_foo")
@template.locals = [:key]
- assert_called_with(@context.lookup_context, :find_template,[ "foo", %w(test), true, [:key]], returns: "partial") do
+ assert_called_with(@context.lookup_context, :find_template, ["foo", %w(test), true, [:key]], returns: "partial") do
assert_equal "partial", @template.refresh(@context)
end
end
def test_refresh_raises_an_error_without_virtual_path
- @template = new_template("Hello", :virtual_path => nil)
+ @template = new_template("Hello", virtual_path: nil)
assert_raise RuntimeError do
@template.refresh(@context)
end
@@ -171,14 +171,14 @@ class TestERBTemplate < ActiveSupport::TestCase
# inside Rails.
def test_lying_with_magic_comment
assert_raises(ActionView::Template::Error) do
- @template = new_template("# encoding: UTF-8\nhello \xFCmlat", :virtual_path => nil)
+ @template = new_template("# encoding: UTF-8\nhello \xFCmlat", virtual_path: nil)
render
end
end
def test_encoding_can_be_specified_with_magic_comment_in_erb
with_external_encoding Encoding::UTF_8 do
- @template = new_template("<%# encoding: ISO-8859-1 %>hello \xFCmlat", :virtual_path => nil)
+ @template = new_template("<%# encoding: ISO-8859-1 %>hello \xFCmlat", virtual_path: nil)
assert_equal Encoding::UTF_8, render.encoding
assert_equal "hello \u{fc}mlat", render
end
@@ -186,10 +186,12 @@ class TestERBTemplate < ActiveSupport::TestCase
def test_error_when_template_isnt_valid_utf8
e = assert_raises ActionView::Template::Error do
- @template = new_template("hello \xFCmlat", :virtual_path => nil)
+ @template = new_template("hello \xFCmlat", virtual_path: nil)
render
end
- assert_match(/\xFC/, e.message)
+ # Hack: We write the regexp this way because the parser of RuboCop
+ # errs with /\xFC/.
+ assert_match(Regexp.new("\xFC"), e.message)
end
def with_external_encoding(encoding)
diff --git a/actionview/test/template/test_case_test.rb b/actionview/test/template/test_case_test.rb
index 0b9d78d668..3deddd5706 100644
--- a/actionview/test/template/test_case_test.rb
+++ b/actionview/test/template/test_case_test.rb
@@ -25,7 +25,7 @@ module ActionView
def self.included(test_case)
test_case.class_eval do
test "helpers defined on ActionView::TestCase are available" do
- assert test_case.ancestors.include?(ASharedTestHelper)
+ assert_includes test_case.ancestors, ASharedTestHelper
assert_equal "Holla!", from_shared_helper
end
end
@@ -50,7 +50,7 @@ module ActionView
end
test "retrieve non existing config values" do
- assert_equal nil, ActionView::Base.new.config.something_odd
+ assert_nil ActionView::Base.new.config.something_odd
end
test "works without testing a helper module" do
@@ -59,12 +59,12 @@ module ActionView
test "can render a layout with block" do
assert_equal "Before (ChrisCruft)\n!\nAfter",
- render(layout: "test/layout_for_partial", locals: {name: "ChrisCruft"}) {"!"}
+ render(layout: "test/layout_for_partial", locals: { name: "ChrisCruft" }) { "!" }
end
helper AnotherTestHelper
test "additional helper classes can be specified as in a controller" do
- assert test_case.ancestors.include?(AnotherTestHelper)
+ assert_includes test_case.ancestors, AnotherTestHelper
assert_equal "Howdy!", from_another_helper
end
@@ -96,12 +96,12 @@ module ActionView
tests ATestHelper
test "tests the specified helper module" do
assert_equal ATestHelper, test_case.helper_class
- assert test_case.ancestors.include?(ATestHelper)
+ assert_includes test_case.ancestors, ATestHelper
end
helper AnotherTestHelper
test "additional helper classes can be specified as in a controller" do
- assert test_case.ancestors.include?(AnotherTestHelper)
+ assert_includes test_case.ancestors, AnotherTestHelper
assert_equal "Howdy!", from_another_helper
test_case.helper_class.module_eval do
@@ -155,13 +155,13 @@ module ActionView
test "view_assigns returns a Hash of user defined ivars" do
@a = "b"
@c = "d"
- assert_equal({a: "b", c: "d"}, view_assigns)
+ assert_equal({ a: "b", c: "d" }, view_assigns)
end
test "view_assigns excludes internal ivars" do
INTERNAL_IVARS.each do |ivar|
assert defined?(ivar), "expected #{ivar} to be defined"
- assert !view_assigns.keys.include?(ivar.to_s.tr("@", "").to_sym), "expected #{ivar} to be excluded from view_assigns"
+ assert_not_includes view_assigns.keys, ivar.to_s.tr("@", "").to_sym, "expected #{ivar} to be excluded from view_assigns"
end
end
end
@@ -200,7 +200,7 @@ module ActionView
test "inflects the name of the helper module to test from the test case class" do
assert_equal ATestHelper, test_case.helper_class
- assert test_case.ancestors.include?(ATestHelper)
+ assert_includes test_case.ancestors, ATestHelper
end
test "a configured test controller is available" do
@@ -209,7 +209,7 @@ module ActionView
end
test "no additional helpers should shared across test cases" do
- assert !test_case.ancestors.include?(AnotherTestHelper)
+ assert_not_includes test_case.ancestors, AnotherTestHelper
assert_raise(NoMethodError) { send :from_another_helper }
end
@@ -285,7 +285,7 @@ module ActionView
class AssertionsTest < ActionView::TestCase
def render_from_helper
form_tag("/foo") do
- safe_concat render(text: "<ul><li>foo</li></ul>")
+ safe_concat render(plain: "<ul><li>foo</li></ul>")
end
end
helper_method :render_from_helper
diff --git a/actionview/test/template/text_helper_test.rb b/actionview/test/template/text_helper_test.rb
index 9f57e6a6ae..251c98230f 100644
--- a/actionview/test/template/text_helper_test.rb
+++ b/actionview/test/template/text_helper_test.rb
@@ -71,7 +71,7 @@ class TextHelperTest < ActionView::TestCase
end
def test_simple_format_does_not_modify_the_html_options_hash
- options = { class: "foobar"}
+ options = { class: "foobar" }
passed_options = options.dup
simple_format("some text", passed_options)
assert_equal options, passed_options
@@ -316,7 +316,7 @@ class TextHelperTest < ActionView::TestCase
end
def test_excerpt_with_omission
- assert_equal("[...]is a beautiful morn[...]", excerpt("This is a beautiful morning", "beautiful", omission: "[...]",radius: 5))
+ assert_equal("[...]is a beautiful morn[...]", excerpt("This is a beautiful morning", "beautiful", omission: "[...]", radius: 5))
assert_equal(
"This is the ultimate supercalifragilisticexpialidoceous very looooooooooooooooooong looooooooooooong beautiful morning with amazing sunshine and awesome tempera[...]",
excerpt("This is the ultimate supercalifragilisticexpialidoceous very looooooooooooooooooong looooooooooooong beautiful morning with amazing sunshine and awesome temperatures. So what are you gonna do about it?", "very",
@@ -329,7 +329,7 @@ class TextHelperTest < ActionView::TestCase
end
def test_excerpt_does_not_modify_the_options_hash
- options = { omission: "[...]",radius: 5 }
+ options = { omission: "[...]", radius: 5 }
passed_options = options.dup
excerpt("This is a beautiful morning", "beautiful", passed_options)
assert_equal options, passed_options
@@ -379,6 +379,8 @@ class TextHelperTest < ActionView::TestCase
assert_equal("1.25 counts", pluralize("1.25", "count"))
assert_equal("1.0 count", pluralize("1.0", "count"))
assert_equal("1.00 count", pluralize("1.00", "count"))
+ assert_equal("2 counters", pluralize(2, "count", "counters"))
+ assert_equal("0 counters", pluralize(nil, "count", "counters"))
assert_equal("2 counters", pluralize(2, "count", plural: "counters"))
assert_equal("0 counters", pluralize(nil, "count", plural: "counters"))
assert_equal("2 people", pluralize(2, "person"))
@@ -405,12 +407,6 @@ class TextHelperTest < ActionView::TestCase
end
end
- def test_deprecated_plural_as_positional_argument
- assert_deprecated do
- pluralize(2, "count", "counters")
- end
- end
-
def test_cycle_class
value = Cycle.new("one", 2, "3")
assert_equal("one", value.to_s)
@@ -467,11 +463,11 @@ class TextHelperTest < ActionView::TestCase
end
def test_current_cycle_with_default_name
- cycle("even","odd")
+ cycle("even", "odd")
assert_equal "even", current_cycle
- cycle("even","odd")
+ cycle("even", "odd")
assert_equal "odd", current_cycle
- cycle("even","odd")
+ cycle("even", "odd")
assert_equal "even", current_cycle
end
@@ -490,13 +486,13 @@ class TextHelperTest < ActionView::TestCase
end
def test_current_cycle_with_more_than_two_names
- cycle(1,2,3)
+ cycle(1, 2, 3)
assert_equal "1", current_cycle
- cycle(1,2,3)
+ cycle(1, 2, 3)
assert_equal "2", current_cycle
- cycle(1,2,3)
+ cycle(1, 2, 3)
assert_equal "3", current_cycle
- cycle(1,2,3)
+ cycle(1, 2, 3)
assert_equal "1", current_cycle
end
diff --git a/actionview/test/template/text_test.rb b/actionview/test/template/text_test.rb
index 6510688f97..ee526dc367 100644
--- a/actionview/test/template/text_test.rb
+++ b/actionview/test/template/text_test.rb
@@ -1,17 +1,7 @@
require "abstract_unit"
class TextTest < ActiveSupport::TestCase
- test "formats returns symbol for recognized MIME type" do
- assert_equal [:text], ActionView::Template::Text.new("", :text).formats
- end
-
- test "formats returns string for recognized MIME type when MIME does not have symbol" do
- foo = Mime::Type.lookup("foo")
- assert_nil foo.to_sym
- assert_equal ["foo"], ActionView::Template::Text.new("", foo).formats
- end
-
- test "formats returns string for unknown MIME type" do
- assert_equal ["foo"], ActionView::Template::Text.new("", "foo").formats
+ test "formats always return :text" do
+ assert_equal [:text], ActionView::Template::Text.new("").formats
end
end
diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb
index 49ada352e0..1e64385b52 100644
--- a/actionview/test/template/url_helper_test.rb
+++ b/actionview/test/template/url_helper_test.rb
@@ -94,7 +94,7 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_to_form_params_with_namespace
assert_equal(
[{ name: "country[name]", value: "Denmark" }],
- to_form_params({name: "Denmark"}, "country")
+ to_form_params({ name: "Denmark" }, "country")
)
end
@@ -221,6 +221,33 @@ class UrlHelperTest < ActiveSupport::TestCase
)
end
+ class FakeParams
+ def initialize(permitted = true)
+ @permitted = permitted
+ end
+
+ def permitted?
+ @permitted
+ end
+
+ def to_h
+ { foo: :bar, baz: "quux" }
+ end
+ end
+
+ def test_button_to_with_permited_strong_params
+ assert_dom_equal(
+ %{<form action="http://www.example.com" class="button_to" method="post"><input type="submit" value="Hello" /><input type="hidden" name="baz" value="quux" /><input type="hidden" name="foo" value="bar" /></form>},
+ button_to("Hello", "http://www.example.com", params: FakeParams.new)
+ )
+ end
+
+ def test_button_to_with_unpermited_strong_params
+ assert_raises(ArgumentError) do
+ button_to("Hello", "http://www.example.com", params: FakeParams.new(false))
+ end
+ end
+
def test_button_to_with_nested_hash_params
assert_dom_equal(
%{<form action="http://www.example.com" class="button_to" method="post"><input type="submit" value="Hello" /><input type="hidden" name="foo[bar]" value="baz" /></form>},
@@ -260,7 +287,7 @@ class UrlHelperTest < ActiveSupport::TestCase
end
def test_link_tag_with_back
- env = {"HTTP_REFERER" => "http://www.example.com/referer"}
+ env = { "HTTP_REFERER" => "http://www.example.com/referer" }
@controller = Struct.new(:request).new(Struct.new(:env).new(env))
expected = %{<a href="#{env["HTTP_REFERER"]}">go back</a>}
assert_dom_equal expected, link_to("go back", :back)
@@ -783,7 +810,7 @@ class TasksController < ActionController::Base
render_default
end
- protected
+ private
def render_default
render inline: "<%= link_to_unless_current('tasks', tasks_path) %>\n" +
"<%= link_to_unless_current('tasks', tasks_url) %>"
diff --git a/actionview/test/ujs/.gitignore b/actionview/test/ujs/.gitignore
new file mode 100644
index 0000000000..31dbbff57c
--- /dev/null
+++ b/actionview/test/ujs/.gitignore
@@ -0,0 +1 @@
+/log
diff --git a/actionview/test/ujs/config.ru b/actionview/test/ujs/config.ru
new file mode 100644
index 0000000000..414c2063c3
--- /dev/null
+++ b/actionview/test/ujs/config.ru
@@ -0,0 +1,3 @@
+$LOAD_PATH.unshift File.expand_path("..", __FILE__)
+require "server"
+run UJS::Server
diff --git a/actionview/test/ujs/public/test/.eslintrc.yml b/actionview/test/ujs/public/test/.eslintrc.yml
new file mode 100644
index 0000000000..06d7dd36ea
--- /dev/null
+++ b/actionview/test/ujs/public/test/.eslintrc.yml
@@ -0,0 +1,21 @@
+env:
+ browser: true
+extends: eslint:recommended
+rules:
+ no-undef: off
+ no-unused-vars: off
+ indent: off
+ linebreak-style: ['error', 'unix']
+ quotes: ['error', 'single']
+ semi: ['error', 'never']
+ no-shadow: ['error'] # Prevent potential errors
+ no-console: 'off'
+ # styles
+ space-before-function-paren: ['error', 'never']
+ space-before-blocks: 'error'
+ brace-style: ['error', '1tbs', { allowSingleLine: true }]
+ key-spacing: 'error'
+ array-bracket-spacing: 'error'
+ comma-spacing: 'error'
+ comma-dangle: 'off'
+ eol-last: 'error'
diff --git a/actionview/test/ujs/public/test/call-remote-callbacks.js b/actionview/test/ujs/public/test/call-remote-callbacks.js
new file mode 100644
index 0000000000..082d10bfbd
--- /dev/null
+++ b/actionview/test/ujs/public/test/call-remote-callbacks.js
@@ -0,0 +1,469 @@
+(function() {
+
+module('call-remote-callbacks', {
+ setup: function() {
+ $('#qunit-fixture').append($('<form />', {
+ action: '/echo', method: 'get', 'data-remote': 'true'
+ }))
+ },
+ teardown: function() {
+ $(document).undelegate('form[data-remote]', 'ajax:beforeSend')
+ $(document).undelegate('form[data-remote]', 'ajax:before')
+ $(document).undelegate('form[data-remote]', 'ajax:send')
+ $(document).undelegate('form[data-remote]', 'ajax:complete')
+ $(document).undelegate('form[data-remote]', 'ajax:success')
+ $(document).unbind('ajaxStop')
+ $(document).unbind('iframe:loading')
+ }
+})
+
+function start_after_submit(form) {
+ form.bindNative('ajax:complete', function() {
+ ok(true, 'ajax:complete')
+ start()
+ })
+}
+
+function submit(fn) {
+ var form = $('form')
+ start_after_submit(form)
+
+ if (fn) fn(form)
+ form.triggerNative('submit')
+}
+
+function submit_with_button(submit_button) {
+ var form = $('form')
+ start_after_submit(form)
+
+ submit_button.triggerNative('click')
+}
+
+asyncTest('modifying form fields with "ajax:before" sends modified data in request', 4, function() {
+ $('form[data-remote]')
+ .append($('<input type="text" name="user_name" value="john">'))
+ .append($('<input type="text" name="removed_user_name" value="john">'))
+ .bindNative('ajax:before', function() {
+ var form = $(this)
+ form
+ .append($('<input />', {name: 'other_user_name', value: 'jonathan'}))
+ .find('input[name="removed_user_name"]').remove()
+ form
+ .find('input[name="user_name"]').val('steve')
+ })
+
+ submit(function(form) {
+ form.bindNative('ajax:success', function(e, data, status, xhr) {
+ equal(data.params.user_name, 'steve', 'modified field value should have been submitted')
+ equal(data.params.other_user_name, 'jonathan', 'added field value should have been submitted')
+ equal(data.params.removed_user_name, undefined, 'removed field value should be undefined')
+ })
+ })
+})
+
+asyncTest('modifying data("type") with "ajax:before" requests new dataType in request', 2, function() {
+ $('form[data-remote]').data('type', 'html')
+ .bindNative('ajax:before', function() {
+ this.setAttribute('data-type', 'xml')
+ })
+
+ submit(function(form) {
+ form.bindNative('ajax:beforeSend', function(e, xhr, settings) {
+ equal(settings.dataType, 'xml', 'modified dataType should have been requested')
+ })
+ })
+})
+
+asyncTest('setting data("with-credentials",true) with "ajax:before" uses new setting in request', 2, function() {
+ $('form[data-remote]').data('with-credentials', false)
+ .bindNative('ajax:before', function() {
+ this.setAttribute('data-with-credentials', true)
+ })
+
+ submit(function(form) {
+ form.bindNative('ajax:beforeSend', function(e, xhr, settings) {
+ equal(settings.withCredentials, true, 'setting modified in ajax:before should have forced withCredentials request')
+ })
+ })
+})
+
+asyncTest('stopping the "ajax:beforeSend" event aborts the request', 1, function() {
+ submit(function(form) {
+ form.bindNative('ajax:beforeSend', function() {
+ ok(true, 'aborting request in ajax:beforeSend')
+ return false
+ })
+ form.unbind('ajax:send').bindNative('ajax:send', function() {
+ ok(false, 'ajax:send should not run')
+ })
+ form.unbind('ajax:complete').bindNative('ajax:complete', function() {
+ ok(false, 'ajax:complete should not run')
+ })
+ form.bindNative('ajax:error', function(e, xhr, status, error) {
+ ok(false, 'ajax:error should not run')
+ })
+ $(document).bindNative('ajaxStop', function() {
+ start()
+ })
+ })
+})
+
+asyncTest('blank required form input field should abort request and trigger "ajax:aborted:required" event', 5, function() {
+ $(document).bind('iframe:loading', function() {
+ ok(false, 'form should not get submitted')
+ })
+
+ var form = $('form[data-remote]')
+ .append($('<input type="text" name="user_name" required="required">'))
+ .append($('<textarea name="user_bio" required="required"></textarea>'))
+ .bindNative('ajax:beforeSend', function() {
+ ok(false, 'ajax:beforeSend should not run')
+ })
+ .bindNative('ajax:aborted:required', function(e, data) {
+ data = $(data)
+ ok(data.length == 2, 'ajax:aborted:required event is passed all blank required inputs (jQuery objects)')
+ ok(data.first().is('input[name="user_name"]'), 'ajax:aborted:required adds blank required input to data')
+ ok(data.last().is('textarea[name="user_bio"]'), 'ajax:aborted:required adds blank required textarea to data')
+ ok(true, 'ajax:aborted:required should run')
+ })
+ .triggerNative('submit')
+
+ setTimeout(function() {
+ form.find('input[required],textarea[required]').val('Tyler')
+ form.unbind('ajax:beforeSend')
+ submit()
+ }, 13)
+})
+
+asyncTest('blank required form input for non-remote form should abort normal submission', 1, function() {
+ var form = $('form[data-remote]')
+ .append($('<input type="text" name="user_name" required="required">'))
+ .removeAttr('data-remote')
+ .bindNative('ujs:everythingStopped', function() {
+ ok(true, 'ujs:everythingStopped should run')
+ })
+ .triggerNative('submit')
+
+ setTimeout(function() {
+ start()
+ }, 13)
+})
+
+asyncTest('form should be submitted with blank required fields if handler is bound to "ajax:aborted:required" event that returns false', 1, function() {
+ var form = $('form[data-remote]')
+ .append($('<input type="text" name="user_name" required="required">'))
+ .bindNative('ajax:beforeSend', function() {
+ ok(true, 'ajax:beforeSend should run')
+ })
+ .bindNative('ajax:aborted:required', function() {
+ return false
+ })
+ .triggerNative('submit')
+
+ setTimeout(function() {
+ start()
+ }, 13)
+})
+
+asyncTest('disabled fields should not be included in blank required check', 2, function() {
+ var form = $('form[data-remote]')
+ .append($('<input type="text" name="user_name" required="required" disabled="disabled">'))
+ .append($('<textarea name="user_bio" required="required" disabled="disabled"></textarea>'))
+ .bindNative('ajax:beforeSend', function() {
+ ok(true, 'ajax:beforeSend should run')
+ })
+ .bindNative('ajax:aborted:required', function() {
+ ok(false, 'ajax:aborted:required should not run')
+ })
+
+ submit()
+})
+
+asyncTest('form should be submitted with blank required fields if it has the "novalidate" attribute', 2, function() {
+ var form = $('form[data-remote]')
+ .append($('<input type="text" name="user_name" required="required">'))
+ .attr('novalidate', 'novalidate')
+ .bindNative('ajax:beforeSend', function() {
+ ok(true, 'ajax:beforeSend should run')
+ })
+ .bindNative('ajax:aborted:required', function() {
+ ok(false, 'ajax:aborted:required should not run')
+ })
+
+ submit()
+})
+
+asyncTest('form should be submitted with blank required fields if the button has the "formnovalidate" attribute', 2, function() {
+ var submit_button = $('<input type="submit" formnovalidate>')
+ var form = $('form[data-remote]')
+ .append($('<input type="text" name="user_name" required="required">'))
+ .append(submit_button)
+ .bindNative('ajax:beforeSend', function() {
+ ok(true, 'ajax:beforeSend should run')
+ })
+ .bindNative('ajax:aborted:required', function() {
+ ok(false, 'ajax:aborted:required should not run')
+ })
+
+ submit_with_button(submit_button)
+})
+
+asyncTest('blank required form input for non-remote form with "novalidate" attribute should not abort normal submission', 1, function() {
+ $(document).bind('iframe:loading', function() {
+ ok(true, 'form should get submitted')
+ })
+
+ var form = $('form[data-remote]')
+ .append($('<input type="text" name="user_name" required="required">'))
+ .removeAttr('data-remote')
+ .attr('novalidate', 'novalidate')
+ .triggerNative('submit')
+
+ setTimeout(function() {
+ start()
+ }, 13)
+})
+
+asyncTest('unchecked required checkbox should abort form submission', 1, function() {
+ var form = $('form[data-remote]')
+ .append($('<input type="checkbox" name="agree" required="required">'))
+ .removeAttr('data-remote')
+ .bindNative('ujs:everythingStopped', function() {
+ ok(true, 'ujs:everythingStopped should run')
+ })
+ .triggerNative('submit')
+
+ setTimeout(function() {
+ start()
+ }, 13)
+})
+
+asyncTest('unchecked required radio should abort form submission', 3, function() {
+ var form = $('form[data-remote]')
+ .append($('<input type="radio" name="yes_no_none" required="required" value=1>'))
+ .append($('<input type="radio" name="yes_no_none" required="required" value=2>'))
+ .removeAttr('data-remote')
+ .bindNative('ujs:everythingStopped', function() {
+ ok(true, 'ujs:everythingStopped should run')
+ })
+ .bindNative('ajax:aborted:required', function(e, data) {
+ data = $(data)
+ equal(data.length, 2, 'blankRequiredInputs should include both radios')
+ ok(data.first().is('input[type=radio][value=1]'), 'blankRequiredInputs[0] should be the first radio')
+ })
+ .triggerNative('submit')
+
+ setTimeout(function() {
+ start()
+ }, 13)
+})
+
+asyncTest('required radio should only require one to be checked', 1, function() {
+ $(document).bind('iframe:loading', function() {
+ ok(true, 'form should get submitted')
+ })
+
+ var form = $('form[data-remote]')
+ .append($('<input type="radio" name="yes_no" required="required" value=1 id="checkme">'))
+ .append($('<input type="radio" name="yes_no" required="required" value=2>'))
+ .removeAttr('data-remote')
+ .bindNative('ujs:everythingStopped', function() {
+ ok(false, 'ujs:everythingStopped should not run')
+ })
+ .find('#checkme').prop('checked', true)
+ .end()
+ .triggerNative('submit')
+
+ setTimeout(function() {
+ start()
+ }, 13)
+})
+
+asyncTest('required radio should only require one to be checked if not all radios are required', 1, function() {
+ $(document).bind('iframe:loading', function() {
+ ok(true, 'form should get submitted')
+ })
+
+ var form = $('form[data-remote]')
+ // Check the radio that is not required
+ .append($('<input type="radio" name="yes_no_maybe" value=1 >'))
+ // Check the radio that is not required
+ .append($('<input type="radio" name="yes_no_maybe" value=2 id="checkme">'))
+ // Only one needs to be required
+ .append($('<input type="radio" name="yes_no_maybe" required="required" value=3>'))
+ .removeAttr('data-remote')
+ .bindNative('ujs:everythingStopped', function() {
+ ok(false, 'ujs:everythingStopped should not run')
+ })
+ .find('#checkme').prop('checked', true)
+ .end()
+ .triggerNative('submit')
+
+ setTimeout(function() {
+ start()
+ }, 13)
+})
+
+function skipIt() {
+ // This test cannot work due to the security feature in browsers which makes the value
+ // attribute of file input fields readonly, so it cannot be set with default value.
+ // This is what the test would look like though if browsers let us automate this test.
+ asyncTest('non-blank file form input field should abort remote request, but submit normally', 5, function() {
+ var form = $('form[data-remote]')
+ .append($('<input type="file" name="attachment" value="default.png">'))
+ .bindNative('ajax:beforeSend', function() {
+ ok(false, 'ajax:beforeSend should not run')
+ })
+ .bind('iframe:loading', function() {
+ ok(true, 'form should get submitted')
+ })
+ .bindNative('ajax:aborted:file', function(e, data) {
+ ok(data.length == 1, 'ajax:aborted:file event is passed all non-blank file inputs (jQuery objects)')
+ ok(data.first().is('input[name="attachment"]'), 'ajax:aborted:file adds non-blank file input to data')
+ ok(true, 'ajax:aborted:file event should run')
+ })
+ .triggerNative('submit')
+
+ setTimeout(function() {
+ form.find('input[type="file"]').val('')
+ form.unbind('ajax:beforeSend')
+ submit()
+ }, 13)
+ })
+
+ asyncTest('file form input field should not abort remote request if file form input does not have a name attribute', 5, function() {
+ var form = $('form[data-remote]')
+ .append($('<input type="file" value="default.png">'))
+ .bindNative('ajax:beforeSend', function() {
+ ok(true, 'ajax:beforeSend should run')
+ })
+ .bind('iframe:loading', function() {
+ ok(true, 'form should get submitted')
+ })
+ .bindNative('ajax:aborted:file', function(e, data) {
+ ok(false, 'ajax:aborted:file should not run')
+ })
+ .triggerNative('submit')
+
+ setTimeout(function() {
+ form.find('input[type="file"]').val('')
+ form.unbind('ajax:beforeSend')
+ submit()
+ }, 13)
+ })
+
+ asyncTest('blank file input field should abort request entirely if handler bound to "ajax:aborted:file" event that returns false', 1, function() {
+ var form = $('form[data-remote]')
+ .append($('<input type="file" name="attachment" value="default.png">'))
+ .bindNative('ajax:beforeSend', function() {
+ ok(false, 'ajax:beforeSend should not run')
+ })
+ .bind('iframe:loading', function() {
+ ok(false, 'form should not get submitted')
+ })
+ .bindNative('ajax:aborted:file', function() {
+ return false
+ })
+ .triggerNative('submit')
+
+ setTimeout(function() {
+ form.find('input[type="file"]').val('')
+ form.unbind('ajax:beforeSend')
+ submit()
+ }, 13)
+ })
+}
+
+asyncTest('"ajax:beforeSend" can be observed and stopped with event delegation', 1, function() {
+ $(document).delegate('form[data-remote]', 'ajax:beforeSend', function() {
+ ok(true, 'ajax:beforeSend observed with event delegation')
+ return false
+ })
+
+ submit(function(form) {
+ form.unbind('ajax:send').bindNative('ajax:send', function() {
+ ok(false, 'ajax:send should not run')
+ })
+ form.unbind('ajax:complete').bindNative('ajax:complete', function() {
+ ok(false, 'ajax:complete should not run')
+ })
+ $(document).bindNative('ajaxStop', function() {
+ start()
+ })
+ })
+})
+
+asyncTest('"ajax:beforeSend", "ajax:send", "ajax:success" and "ajax:complete" are triggered', 9, function() {
+ submit(function(form) {
+ form.bindNative('ajax:beforeSend', function(e, xhr, settings) {
+ ok(xhr.setRequestHeader, 'first argument to "ajax:beforeSend" should be an XHR object')
+ equal(settings.url, '/echo', 'second argument to "ajax:beforeSend" should be a settings object')
+ })
+ form.bindNative('ajax:send', function(e, xhr) {
+ ok(xhr.abort, 'first argument to "ajax:send" should be an XHR object')
+ })
+ form.bindNative('ajax:success', function(e, data, status, xhr) {
+ ok(data.REQUEST_METHOD, 'first argument to ajax:success should be a data object')
+ equal(status, 'OK', 'second argument to ajax:success should be a status string')
+ ok(xhr.getResponseHeader, 'third argument to "ajax:success" should be an XHR object')
+ })
+ form.bindNative('ajax:complete', function(e, xhr, status) {
+ ok(xhr.getResponseHeader, 'first argument to "ajax:complete" should be an XHR object')
+ equal(status, 'OK', 'second argument to ajax:complete should be a status string')
+ })
+ })
+})
+
+if(window.phantom !== undefined) {
+ asyncTest('"ajax:beforeSend", "ajax:send", "ajax:error" and "ajax:complete" are triggered on error', 7, function() {
+ submit(function(form) {
+ form.attr('action', '/error')
+ form.bindNative('ajax:beforeSend', function(arg) { ok(true, 'ajax:beforeSend') })
+ form.bindNative('ajax:send', function(arg) { ok(true, 'ajax:send') })
+ form.bindNative('ajax:error', function(e, xhr, status, error) {
+ ok(xhr.getResponseHeader, 'first argument to "ajax:error" should be an XHR object')
+ equal(status, 'error', 'second argument to ajax:error should be a status string')
+ // Firefox 8 returns "Forbidden " with trailing space
+ equal($.trim(error), 'Forbidden', 'third argument to ajax:error should be an HTTP status response')
+ // Opera returns "0" for HTTP code
+ equal(xhr.status, window.opera ? 0 : 403, 'status code should be 403')
+ })
+ })
+ })
+}
+
+// IF THIS TEST IS FAILING, TRY INCREASING THE TIMEOUT AT THE BOTTOM TO > 100
+asyncTest('binding to ajax callbacks via .delegate() triggers handlers properly', 4, function() {
+ $(document)
+ .delegate('form[data-remote]', 'ajax:beforeSend', function() {
+ ok(true, 'ajax:beforeSend handler is triggered')
+ })
+ .delegate('form[data-remote]', 'ajax:send', function() {
+ ok(true, 'ajax:send handler is triggered')
+ })
+ .delegate('form[data-remote]', 'ajax:complete', function() {
+ ok(true, 'ajax:complete handler is triggered')
+ })
+ .delegate('form[data-remote]', 'ajax:success', function() {
+ ok(true, 'ajax:success handler is triggered')
+ })
+ $('form[data-remote]').triggerNative('submit')
+
+ setTimeout(function() {
+ start()
+ }, 63)
+})
+
+asyncTest('binding to ajax:send event to call jquery methods on ajax object', 2, function() {
+ $('form[data-remote]')
+ .bindNative('ajax:send', function(e, xhr) {
+ ok(true, 'event should fire')
+ equal(typeof(xhr.abort), 'function', 'event should pass jqXHR object')
+ xhr.abort()
+ })
+ .triggerNative('submit')
+
+ setTimeout(function() { start() }, 35)
+})
+
+})()
diff --git a/actionview/test/ujs/public/test/call-remote.js b/actionview/test/ujs/public/test/call-remote.js
new file mode 100644
index 0000000000..dbeb8ad832
--- /dev/null
+++ b/actionview/test/ujs/public/test/call-remote.js
@@ -0,0 +1,247 @@
+(function() {
+
+function buildForm(attrs) {
+ attrs = $.extend({ action: '/echo', 'data-remote': 'true' }, attrs)
+
+ $('#qunit-fixture').append($('<form />', attrs))
+ .find('form').append($('<input type="text" name="user_name" value="john">'))
+}
+
+module('call-remote')
+
+function submit(fn) {
+ $('form')
+ .bindNative('ajax:success', fn)
+ .bindNative('ajax:complete', function() { start() })
+ .triggerNative('submit')
+}
+
+asyncTest('form method is read from "method" and not from "data-method"', 1, function() {
+ buildForm({ method: 'post', 'data-method': 'get' })
+
+ submit(function(e, data, status, xhr) {
+ App.assertPostRequest(data)
+ })
+})
+
+asyncTest('form method is not read from "data-method" attribute in case of missing "method"', 1, function() {
+ buildForm({ 'data-method': 'put' })
+
+ submit(function(e, data, status, xhr) {
+ App.assertGetRequest(data)
+ })
+})
+
+asyncTest('form method is read from submit button "formmethod" if submit is triggered by that button', 1, function() {
+ var submitButton = $('<input type="submit" formmethod="get">')
+ buildForm({ method: 'post' })
+
+ $('#qunit-fixture').find('form').append(submitButton)
+ .bindNative('ajax:success', function(e, data, status, xhr) {
+ App.assertGetRequest(data)
+ })
+ .bindNative('ajax:complete', function() { start() })
+
+ submitButton.triggerNative('click')
+})
+
+asyncTest('form default method is GET', 1, function() {
+ buildForm()
+
+ submit(function(e, data, status, xhr) {
+ App.assertGetRequest(data)
+ })
+})
+
+asyncTest('form url is picked up from "action"', 1, function() {
+ buildForm({ method: 'post' })
+
+ submit(function(e, data, status, xhr) {
+ App.assertRequestPath(data, '/echo')
+ })
+})
+
+asyncTest('form url is read from "action" not "href"', 1, function() {
+ buildForm({ method: 'post', href: '/echo2' })
+
+ submit(function(e, data, status, xhr) {
+ App.assertRequestPath(data, '/echo')
+ })
+})
+
+asyncTest('form url is read from submit button "formaction" if submit is triggered by that button', 1, function() {
+ var submitButton = $('<input type="submit" formaction="/echo">')
+ buildForm({ method: 'post', href: '/echo2' })
+
+ $('#qunit-fixture').find('form').append(submitButton)
+ .bindNative('ajax:success', function(e, data, status, xhr) {
+ App.assertRequestPath(data, '/echo')
+ })
+ .bindNative('ajax:complete', function() { start() })
+
+ submitButton.triggerNative('click')
+})
+
+asyncTest('prefer JS, but accept any format', 1, function() {
+ buildForm({ method: 'post' })
+
+ submit(function(e, data, status, xhr) {
+ var accept = data.HTTP_ACCEPT
+ ok(accept.match(/text\/javascript.+\*\/\*/), 'Accept: ' + accept)
+ })
+})
+
+asyncTest('JS code should be executed', 1, function() {
+ 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="ok(true, \'remote code should be run\')">')
+
+ submit()
+})
+
+asyncTest('XML document should be parsed', 1, function() {
+ buildForm({ method: 'post', 'data-type': 'html' })
+
+ $('form').append('<input type="text" name="content_type" value="application/xml">')
+ $('form').append('<input type="text" name="content" value="<p>hello</p>">')
+
+ submit(function(e, data, status, xhr) {
+ ok(data instanceof Document, 'returned data should be an XML document')
+ })
+})
+
+asyncTest('accept application/json if "data-type" is json', 1, function() {
+ buildForm({ method: 'post', 'data-type': 'json' })
+
+ submit(function(e, data, status, xhr) {
+ equal(data.HTTP_ACCEPT, 'application/json, text/javascript, */*; q=0.01')
+ })
+})
+
+asyncTest('allow empty "data-remote" attribute', 1, function() {
+ var form = $('#qunit-fixture').append($('<form action="/echo" data-remote />')).find('form')
+
+ submit(function() {
+ ok(true, 'form with empty "data-remote" attribute is also allowed')
+ })
+})
+
+asyncTest('query string in form action should be stripped in a GET request in normal submit', 1, function() {
+ buildForm({ action: '/echo?param1=abc', 'data-remote': 'false' })
+
+ $(document).one('iframe:loaded', function(e, data) {
+ equal(data.params.param1, undefined, '"param1" should not be passed to server')
+ start()
+ })
+
+ $('#qunit-fixture form').triggerNative('submit')
+})
+
+asyncTest('query string in form action should be stripped in a GET request in ajax submit', 1, function() {
+ buildForm({ action: '/echo?param1=abc' })
+
+ submit(function(e, data, status, xhr) {
+ equal(data.params.param1, undefined, '"param1" should not be passed to server')
+ })
+})
+
+asyncTest('query string in form action should not be stripped in a POST request in normal submit', 1, function() {
+ buildForm({ action: '/echo?param1=abc', method: 'post', 'data-remote': 'false' })
+
+ $(document).one('iframe:loaded', function(e, data) {
+ equal(data.params.param1, 'abc', '"param1" should be passed to server')
+ start()
+ })
+
+ $('#qunit-fixture form').triggerNative('submit')
+})
+
+asyncTest('query string in form action should not be stripped in a POST request in ajax submit', 1, function() {
+ buildForm({ action: '/echo?param1=abc', method: 'post' })
+
+ submit(function(e, data, status, xhr) {
+ equal(data.params.param1, 'abc', '"param1" should be passed to server')
+ })
+})
+
+asyncTest('allow empty form "action"', 1, function() {
+ var currentLocation, ajaxLocation
+
+ buildForm({ action: '' })
+
+ $('#qunit-fixture').find('form')
+ .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
+ }
+ currentLocation = currentLocation.replace(/\?.*$/, '')
+
+ // Actual location (strip out settings.data that jQuery serializes and appends)
+ // HACK: can no longer use settings.data below to see what was appended to URL, as of
+ // jQuery 1.6.3 (see http://bugs.jquery.com/ticket/10202 and https://github.com/jquery/jquery/pull/544)
+ ajaxLocation = settings.url.replace('user_name=john', '').replace(/&$/, '').replace(/\?$/, '')
+ equal(ajaxLocation.match(/^(.*)/)[1], currentLocation, 'URL should be current page by default')
+
+ // Prevent the request from actually getting sent to the current page and
+ // causing an error.
+ return false
+ })
+ .triggerNative('submit')
+
+ setTimeout(function() { start() }, 13)
+})
+
+asyncTest('sends CSRF token in custom header', 1, function() {
+ buildForm({ method: 'post' })
+ $('#qunit-fixture').append('<meta name="csrf-token" content="cf50faa3fe97702ca1ae" />')
+
+ submit(function(e, data, status, xhr) {
+ equal(data.HTTP_X_CSRF_TOKEN, 'cf50faa3fe97702ca1ae', 'X-CSRF-Token header should be sent')
+ })
+})
+
+asyncTest('intelligently guesses crossDomain behavior when target URL has a different protocol and/or hostname', 1, function() {
+
+ // Don't set data-cross-domain here, just set action to be a different domain than localhost
+ buildForm({ action: 'http://www.alfajango.com' })
+ $('#qunit-fixture').append('<meta name="csrf-token" content="cf50faa3fe97702ca1ae" />')
+
+ $('#qunit-fixture').find('form')
+ .bindNative('ajax:beforeSend', function(evt, req, settings) {
+
+ equal(settings.crossDomain, true, 'crossDomain should be set to true')
+
+ // prevent request from actually getting sent off-domain
+ return false
+ })
+ .triggerNative('submit')
+
+ setTimeout(function() { start() }, 13)
+})
+
+asyncTest('intelligently guesses crossDomain behavior when target URL consists of only a path', 1, function() {
+
+ // Don't set data-cross-domain here, just set action to be a different domain than localhost
+ buildForm({ action: '/just/a/path' })
+ $('#qunit-fixture').append('<meta name="csrf-token" content="cf50faa3fe97702ca1ae" />')
+
+ $('#qunit-fixture').find('form')
+ .bindNative('ajax:beforeSend', function(evt, req, settings) {
+
+ equal(settings.crossDomain, false, 'crossDomain should be set to false')
+
+ // prevent request from actually getting sent off-domain
+ return false
+ })
+ .triggerNative('submit')
+
+ setTimeout(function() { start() }, 13)
+})
+
+})()
diff --git a/actionview/test/ujs/public/test/csrf-refresh.js b/actionview/test/ujs/public/test/csrf-refresh.js
new file mode 100644
index 0000000000..e302042542
--- /dev/null
+++ b/actionview/test/ujs/public/test/csrf-refresh.js
@@ -0,0 +1,24 @@
+(function() {
+
+module('csrf-refresh', {})
+
+asyncTest('refresh all csrf tokens', 1, function() {
+ var correctToken = 'cf50faa3fe97702ca1ae'
+
+ var form = $('<form />')
+ var input = $('<input>').attr({ type: 'hidden', name: 'authenticity_token', id: 'authenticity_token', value: 'foo' })
+ input.appendTo(form)
+
+ $('#qunit-fixture')
+ .append('<meta name="csrf-param" content="authenticity_token"/>')
+ .append('<meta name="csrf-token" content="' + correctToken + '"/>')
+ .append(form)
+
+ $.rails.refreshCSRFTokens()
+ currentToken = $('#qunit-fixture #authenticity_token').val()
+
+ start()
+ equal(currentToken, correctToken)
+})
+
+})()
diff --git a/actionview/test/ujs/public/test/csrf-token.js b/actionview/test/ujs/public/test/csrf-token.js
new file mode 100644
index 0000000000..388b40e057
--- /dev/null
+++ b/actionview/test/ujs/public/test/csrf-token.js
@@ -0,0 +1,27 @@
+(function() {
+
+module('csrf-token', {})
+
+asyncTest('find csrf token', 1, function() {
+ var correctToken = 'cf50faa3fe97702ca1ae'
+
+ $('#qunit-fixture').append('<meta name="csrf-token" content="' + correctToken + '"/>')
+
+ currentToken = $.rails.csrfToken()
+
+ start()
+ equal(currentToken, correctToken)
+})
+
+asyncTest('find csrf param', 1, function() {
+ var correctParam = 'authenticity_token'
+
+ $('#qunit-fixture').append('<meta name="csrf-param" content="' + correctParam + '"/>')
+
+ currentParam = $.rails.csrfParam()
+
+ start()
+ equal(currentParam, correctParam)
+})
+
+})()
diff --git a/actionview/test/ujs/public/test/data-confirm.js b/actionview/test/ujs/public/test/data-confirm.js
new file mode 100644
index 0000000000..28190c2250
--- /dev/null
+++ b/actionview/test/ujs/public/test/data-confirm.js
@@ -0,0 +1,288 @@
+module('data-confirm', {
+ setup: function() {
+ $('#qunit-fixture').append($('<a />', {
+ href: '/echo',
+ 'data-remote': 'true',
+ 'data-confirm': 'Are you absolutely sure?',
+ text: 'my social security number'
+ }))
+
+ $('#qunit-fixture').append($('<button />', {
+ 'data-url': '/echo',
+ 'data-remote': 'true',
+ 'data-confirm': 'Are you absolutely sure?',
+ text: 'Click me'
+ }))
+
+ $('#qunit-fixture').append($('<form />', {
+ id: 'confirm',
+ action: '/echo',
+ 'data-remote': 'true'
+ }))
+
+ $('#qunit-fixture').append($('<input />', {
+ type: 'submit',
+ form: 'confirm',
+ 'data-confirm': 'Are you absolutely sure?'
+ }))
+
+ this.windowConfirm = window.confirm
+ },
+ teardown: function() {
+ window.confirm = this.windowConfirm
+ }
+})
+
+asyncTest('clicking on a link with data-confirm attribute. Confirm yes.', 6, function() {
+ var message
+ // auto-confirm:
+ window.confirm = function(msg) { message = msg; return true }
+
+ $('a[data-confirm]')
+ .bindNative('confirm:complete', function(e, data) {
+ App.assertCallbackInvoked('confirm:complete')
+ ok(data == true, 'confirm:complete passes in confirm answer (true)')
+ })
+ .bindNative('ajax:success', function(e, data, status, xhr) {
+ App.assertCallbackInvoked('ajax:success')
+ App.assertRequestPath(data, '/echo')
+ App.assertGetRequest(data)
+
+ equal(message, 'Are you absolutely sure?')
+ start()
+ })
+ .triggerNative('click')
+})
+
+asyncTest('clicking on a button with data-confirm attribute. Confirm yes.', 6, function() {
+ var message
+ // auto-confirm:
+ window.confirm = function(msg) { message = msg; return true }
+
+ $('button[data-confirm]')
+ .bindNative('confirm:complete', function(e, data) {
+ App.assertCallbackInvoked('confirm:complete')
+ ok(data == true, 'confirm:complete passes in confirm answer (true)')
+ })
+ .bindNative('ajax:success', function(e, data, status, xhr) {
+ App.assertCallbackInvoked('ajax:success')
+ App.assertRequestPath(data, '/echo')
+ App.assertGetRequest(data)
+
+ equal(message, 'Are you absolutely sure?')
+ start()
+ })
+ .triggerNative('click')
+})
+
+asyncTest('clicking on a link with data-confirm attribute. Confirm No.', 3, function() {
+ var message
+ // auto-decline:
+ window.confirm = function(msg) { message = msg; return false }
+
+ $('a[data-confirm]')
+ .bindNative('confirm:complete', function(e, data) {
+ App.assertCallbackInvoked('confirm:complete')
+ ok(data == false, 'confirm:complete passes in confirm answer (false)')
+ })
+ .bindNative('ajax:beforeSend', function(e, data, status, xhr) {
+ App.assertCallbackNotInvoked('ajax:beforeSend')
+ })
+ .triggerNative('click')
+
+ setTimeout(function() {
+ equal(message, 'Are you absolutely sure?')
+ start()
+ }, 50)
+})
+
+asyncTest('clicking on a button with data-confirm attribute. Confirm No.', 3, function() {
+ var message
+ // auto-decline:
+ window.confirm = function(msg) { message = msg; return false }
+
+ $('button[data-confirm]')
+ .bindNative('confirm:complete', function(e, data) {
+ App.assertCallbackInvoked('confirm:complete')
+ ok(data == false, 'confirm:complete passes in confirm answer (false)')
+ })
+ .bindNative('ajax:beforeSend', function(e, data, status, xhr) {
+ App.assertCallbackNotInvoked('ajax:beforeSend')
+ })
+ .triggerNative('click')
+
+ setTimeout(function() {
+ equal(message, 'Are you absolutely sure?')
+ start()
+ }, 50)
+})
+
+asyncTest('clicking on a button with data-confirm attribute. Confirm error.', 3, function() {
+ var message
+ // auto-decline:
+ window.confirm = function(msg) { message = msg; throw 'some random error' }
+
+ $('button[data-confirm]')
+ .bindNative('confirm:complete', function(e, data) {
+ App.assertCallbackInvoked('confirm:complete')
+ ok(data == false, 'confirm:complete passes in confirm answer (false)')
+ })
+ .bindNative('ajax:beforeSend', function(e, data, status, xhr) {
+ App.assertCallbackNotInvoked('ajax:beforeSend')
+ })
+ .triggerNative('click')
+
+ setTimeout(function() {
+ equal(message, 'Are you absolutely sure?')
+ start()
+ }, 50)
+})
+
+asyncTest('clicking on a submit button with form and data-confirm attributes. Confirm No.', 3, function() {
+ var message
+ // auto-decline:
+ window.confirm = function(msg) { message = msg; return false }
+
+ $('input[type=submit][form]')
+ .bindNative('confirm:complete', function(e, data) {
+ App.assertCallbackInvoked('confirm:complete')
+ ok(data == false, 'confirm:complete passes in confirm answer (false)')
+ })
+ .bindNative('ajax:beforeSend', function(e, data, status, xhr) {
+ App.assertCallbackNotInvoked('ajax:beforeSend')
+ })
+ .triggerNative('click')
+
+ setTimeout(function() {
+ equal(message, 'Are you absolutely sure?')
+ start()
+ }, 50)
+})
+
+asyncTest('binding to confirm event of a link and returning false', 1, function() {
+ // redefine confirm function so we can make sure it's not called
+ window.confirm = function(msg) {
+ ok(false, 'confirm dialog should not be called')
+ }
+
+ $('a[data-confirm]')
+ .bindNative('confirm', function() {
+ App.assertCallbackInvoked('confirm')
+ return false
+ })
+ .bindNative('confirm:complete', function() {
+ App.assertCallbackNotInvoked('confirm:complete')
+ })
+ .triggerNative('click')
+
+ setTimeout(function() {
+ start()
+ }, 50)
+})
+
+asyncTest('binding to confirm event of a button and returning false', 1, function() {
+ // redefine confirm function so we can make sure it's not called
+ window.confirm = function(msg) {
+ ok(false, 'confirm dialog should not be called')
+ }
+
+ $('button[data-confirm]')
+ .bindNative('confirm', function() {
+ App.assertCallbackInvoked('confirm')
+ return false
+ })
+ .bindNative('confirm:complete', function() {
+ App.assertCallbackNotInvoked('confirm:complete')
+ })
+ .triggerNative('click')
+
+ setTimeout(function() {
+ start()
+ }, 50)
+})
+
+asyncTest('binding to confirm:complete event of a link and returning false', 2, function() {
+ // auto-confirm:
+ window.confirm = function(msg) {
+ ok(true, 'confirm dialog should be called')
+ return true
+ }
+
+ $('a[data-confirm]')
+ .bindNative('confirm:complete', function() {
+ App.assertCallbackInvoked('confirm:complete')
+ return false
+ })
+ .bindNative('ajax:beforeSend', function() {
+ App.assertCallbackNotInvoked('ajax:beforeSend')
+ })
+ .triggerNative('click')
+
+ setTimeout(function() {
+ start()
+ }, 50)
+})
+
+asyncTest('binding to confirm:complete event of a button and returning false', 2, function() {
+ // auto-confirm:
+ window.confirm = function(msg) {
+ ok(true, 'confirm dialog should be called')
+ return true
+ }
+
+ $('button[data-confirm]')
+ .bindNative('confirm:complete', function() {
+ App.assertCallbackInvoked('confirm:complete')
+ return false
+ })
+ .bindNative('ajax:beforeSend', function() {
+ App.assertCallbackNotInvoked('ajax:beforeSend')
+ })
+ .triggerNative('click')
+
+ setTimeout(function() {
+ start()
+ }, 50)
+})
+
+asyncTest('a button inside a form only confirms once', 1, function() {
+ var confirmations = 0
+ window.confirm = function(msg) {
+ confirmations++
+ return true
+ }
+
+ $('#qunit-fixture').append($('<form />').append($('<button />', {
+ 'data-remote': 'true',
+ 'data-confirm': 'Are you absolutely sure?',
+ text: 'Click me'
+ })))
+
+ $('form > button[data-confirm]').triggerNative('click')
+
+ ok(confirmations === 1, 'confirmation counter should be 1, but it was ' + confirmations)
+ start()
+})
+
+asyncTest('clicking on the children of a link should also trigger a confirm', 6, function() {
+ var message
+ // auto-confirm:
+ window.confirm = function(msg) { message = msg; return true }
+
+ $('a[data-confirm]')
+ .html('<strong>Click me</strong>')
+ .bindNative('confirm:complete', function(e, data) {
+ App.assertCallbackInvoked('confirm:complete')
+ ok(data == true, 'confirm:complete passes in confirm answer (true)')
+ })
+ .bindNative('ajax:success', function(e, data, status, xhr) {
+ App.assertCallbackInvoked('ajax:success')
+ App.assertRequestPath(data, '/echo')
+ App.assertGetRequest(data)
+
+ equal(message, 'Are you absolutely sure?')
+ start()
+ })
+ .find('strong')
+ .triggerNative('click')
+})
diff --git a/actionview/test/ujs/public/test/data-disable-with.js b/actionview/test/ujs/public/test/data-disable-with.js
new file mode 100644
index 0000000000..b29cbbc867
--- /dev/null
+++ b/actionview/test/ujs/public/test/data-disable-with.js
@@ -0,0 +1,391 @@
+module('data-disable-with', {
+ setup: function() {
+ $('#qunit-fixture').append($('<form />', {
+ action: '/echo',
+ 'data-remote': 'true',
+ method: 'post'
+ }))
+ .find('form')
+ .append($('<input type="text" data-disable-with="processing ..." name="user_name" value="john" />'))
+
+ $('#qunit-fixture').append($('<form />', {
+ action: '/echo',
+ method: 'post',
+ id: 'not_remote'
+ }))
+ .find('form:last')
+ // WEEIRDD: the form won't submit to an iframe if the button is name="submit" (??!)
+ .append($('<input type="submit" data-disable-with="submitting ..." name="submit2" value="Submit" />'))
+
+ $('#qunit-fixture').append($('<a />', {
+ text: 'Click me',
+ href: '/echo',
+ 'data-disable-with': 'clicking...'
+ }))
+
+ $('#qunit-fixture').append($('<input />', {
+ type: 'submit',
+ form: 'not_remote',
+ 'data-disable-with': 'form attr submitting',
+ name: 'submit3',
+ value: 'Form Attr Submit'
+ }))
+
+ $('#qunit-fixture').append($('<button />', {
+ text: 'Click me',
+ 'data-remote': true,
+ 'data-url': '/echo',
+ 'data-disable-with': 'clicking...'
+ }))
+ },
+ teardown: function() {
+ $(document).unbind('iframe:loaded')
+ }
+})
+
+asyncTest('form input field with "data-disable-with" attribute', 7, function() {
+ var form = $('form[data-remote]'), input = form.find('input[type=text]')
+
+ App.checkEnabledState(input, 'john')
+
+ form.bindNative('ajax:success', function(e, data) {
+ setTimeout(function() {
+ App.checkEnabledState(input, 'john')
+ equal(data.params.user_name, 'john')
+ start()
+ }, 13)
+ })
+ form.triggerNative('submit')
+
+ App.checkDisabledState(input, 'processing ...')
+})
+
+asyncTest('blank form input field with "data-disable-with" attribute', 7, function() {
+ var form = $('form[data-remote]'), input = form.find('input[type=text]')
+
+ input.val('')
+ App.checkEnabledState(input, '')
+
+ form.bindNative('ajax:success', function(e, data) {
+ setTimeout(function() {
+ App.checkEnabledState(input, '')
+ equal(data.params.user_name, '')
+ start()
+ }, 13)
+ })
+ form.triggerNative('submit')
+
+ App.checkDisabledState(input, 'processing ...')
+})
+
+asyncTest('form button with "data-disable-with" attribute', 6, function() {
+ var form = $('form[data-remote]'), button = $('<button data-disable-with="submitting ..." name="submit2">Submit</button>')
+ form.append(button)
+
+ App.checkEnabledState(button, 'Submit')
+
+ form.bindNative('ajax:success', function(e, data) {
+ setTimeout(function() {
+ App.checkEnabledState(button, 'Submit')
+ start()
+ }, 13)
+ })
+ form.triggerNative('submit')
+
+ App.checkDisabledState(button, 'submitting ...')
+})
+
+asyncTest('form input[type=submit][data-disable-with] disables', 6, function() {
+ var form = $('form:not([data-remote])'), input = form.find('input[type=submit]')
+
+ App.checkEnabledState(input, 'Submit')
+
+ $(document).bind('iframe:loaded', function(e, data) {
+ setTimeout(function() {
+ App.checkDisabledState(input, 'submitting ...')
+ start()
+ }, 30)
+ })
+ form.triggerNative('submit')
+
+ setTimeout(function() {
+ App.checkDisabledState(input, 'submitting ...')
+ }, 30)
+})
+
+test('form input[type=submit][data-disable-with] re-enables when `pageshow` event is triggered', function() {
+ var form = $('form:not([data-remote])'), input = form.find('input[type=submit]')
+
+ App.checkEnabledState(input, 'Submit')
+
+ // Emulate the disabled state without submitting the form at all, what is the
+ // state after going back on firefox after submitting a form.
+ //
+ // See https://github.com/rails/jquery-ujs/issues/357
+ $.rails.disableElement(form[0])
+
+ App.checkDisabledState(input, 'submitting ...')
+
+ $(window).triggerNative('pageshow')
+
+ App.checkEnabledState(input, 'Submit')
+})
+
+asyncTest('form[data-remote] input[type=submit][data-disable-with] is replaced in ajax callback', 2, function() {
+ var form = $('form:not([data-remote])').attr('data-remote', 'true'), origFormContents = form.html()
+
+ form.bindNative('ajax:success', function() {
+ form.html(origFormContents)
+
+ setTimeout(function() {
+ var input = form.find('input[type=submit]')
+ App.checkEnabledState(input, 'Submit')
+ start()
+ }, 30)
+ }).triggerNative('submit')
+})
+
+asyncTest('form[data-remote] input[data-disable-with] is replaced with disabled field in ajax callback', 2, function() {
+ var form = $('form:not([data-remote])').attr('data-remote', 'true'), input = form.find('input[type=submit]'),
+ newDisabledInput = input.clone().attr('disabled', 'disabled')
+
+ form.bindNative('ajax:success', function() {
+ input.replaceWith(newDisabledInput)
+
+ setTimeout(function() {
+ App.checkEnabledState(newDisabledInput, 'Submit')
+ start()
+ }, 30)
+ }).triggerNative('submit')
+})
+
+asyncTest('form input[type=submit][data-disable-with] using "form" attribute disables', 6, function() {
+ var form = $('#not_remote'), input = $('input[form=not_remote]')
+ App.checkEnabledState(input, 'Form Attr Submit')
+
+ $(document).bind('iframe:loaded', function(e, data) {
+ setTimeout(function() {
+ App.checkDisabledState(input, 'form attr submitting')
+ start()
+ }, 30)
+ })
+ form.triggerNative('submit')
+
+ setTimeout(function() {
+ App.checkDisabledState(input, 'form attr submitting')
+ }, 30)
+
+})
+
+asyncTest('form[data-remote] textarea[data-disable-with] attribute', 3, function() {
+ var form = $('form[data-remote]'),
+ textarea = $('<textarea data-disable-with="processing ..." name="user_bio">born, lived, died.</textarea>').appendTo(form)
+
+ form.bindNative('ajax:success', function(e, data) {
+ setTimeout(function() {
+ equal(data.params.user_bio, 'born, lived, died.')
+ start()
+ }, 13)
+ })
+ form.triggerNative('submit')
+
+ App.checkDisabledState(textarea, 'processing ...')
+})
+
+asyncTest('a[data-disable-with] disables', 4, function() {
+ var link = $('a[data-disable-with]')
+
+ App.checkEnabledState(link, 'Click me')
+
+ link.triggerNative('click')
+ App.checkDisabledState(link, 'clicking...')
+ start()
+})
+
+test('a[data-disable-with] re-enables when `pageshow` event is triggered', function() {
+ var link = $('a[data-disable-with]')
+
+ App.checkEnabledState(link, 'Click me')
+
+ link.triggerNative('click')
+ App.checkDisabledState(link, 'clicking...')
+
+ $(window).triggerNative('pageshow')
+ App.checkEnabledState(link, 'Click me')
+})
+
+asyncTest('a[data-remote][data-disable-with] disables and re-enables', 6, function() {
+ var link = $('a[data-disable-with]').attr('data-remote', true)
+
+ App.checkEnabledState(link, 'Click me')
+
+ link
+ .bindNative('ajax:beforeSend', function() {
+ App.checkDisabledState(link, 'clicking...')
+ })
+ .bindNative('ajax:complete', function() {
+ setTimeout( function() {
+ App.checkEnabledState(link, 'Click me')
+ start()
+ }, 15)
+ })
+ .triggerNative('click')
+})
+
+asyncTest('a[data-remote][data-disable-with] re-enables when `ajax:before` event is cancelled', 6, function() {
+ var link = $('a[data-disable-with]').attr('data-remote', true)
+
+ App.checkEnabledState(link, 'Click me')
+
+ link
+ .bindNative('ajax:before', function() {
+ App.checkDisabledState(link, 'clicking...')
+ return false
+ })
+ .triggerNative('click')
+
+ setTimeout(function() {
+ App.checkEnabledState(link, 'Click me')
+ start()
+ }, 30)
+})
+
+asyncTest('a[data-remote][data-disable-with] re-enables when `ajax:beforeSend` event is cancelled', 6, function() {
+ var link = $('a[data-disable-with]').attr('data-remote', true)
+
+ App.checkEnabledState(link, 'Click me')
+
+ link
+ .bindNative('ajax:beforeSend', function() {
+ App.checkDisabledState(link, 'clicking...')
+ return false
+ })
+ .triggerNative('click')
+
+ setTimeout(function() {
+ App.checkEnabledState(link, 'Click me')
+ start()
+ }, 30)
+})
+
+asyncTest('a[data-remote][data-disable-with] re-enables when `ajax:error` event is triggered', 6, function() {
+ var link = $('a[data-disable-with]').attr('data-remote', true).attr('href', '/error')
+
+ App.checkEnabledState(link, 'Click me')
+
+ link
+ .bindNative('ajax:beforeSend', function() {
+ App.checkDisabledState(link, 'clicking...')
+ })
+ .triggerNative('click')
+
+ setTimeout(function() {
+ App.checkEnabledState(link, 'Click me')
+ start()
+ }, 30)
+})
+
+asyncTest('form[data-remote] input|button|textarea[data-disable-with] does not disable when `ajax:beforeSend` event is cancelled', 8, function() {
+ var form = $('form[data-remote]'),
+ input = form.find('input:text'),
+ button = $('<button data-disable-with="submitting ..." name="submit2">Submit</button>').appendTo(form),
+ textarea = $('<textarea data-disable-with="processing ..." name="user_bio">born, lived, died.</textarea>').appendTo(form),
+ submit = $('<input type="submit" data-disable-with="submitting ..." name="submit2" value="Submit" />').appendTo(form)
+
+ form
+ .bindNative('ajax:beforeSend', function() {
+ return false
+ })
+ .triggerNative('submit')
+
+ App.checkEnabledState(input, 'john')
+ App.checkEnabledState(button, 'Submit')
+ App.checkEnabledState(textarea, 'born, lived, died.')
+ App.checkEnabledState(submit, 'Submit')
+
+ start()
+})
+
+asyncTest('ctrl-clicking on a link does not disables the link', 6, function() {
+ var link = $('a[data-disable-with]')
+
+ App.checkEnabledState(link, 'Click me')
+
+ link.triggerNative('click', { metaKey: true })
+ App.checkEnabledState(link, 'Click me')
+
+ link.triggerNative('click', { metaKey: true })
+ App.checkEnabledState(link, 'Click me')
+ start()
+})
+
+asyncTest('button[data-remote][data-disable-with] disables and re-enables', 6, function() {
+ var button = $('button[data-remote][data-disable-with]')
+
+ App.checkEnabledState(button, 'Click me')
+
+ button
+ .bindNative('ajax:send', function() {
+ App.checkDisabledState(button, 'clicking...')
+ })
+ .bindNative('ajax:complete', function() {
+ setTimeout( function() {
+ App.checkEnabledState(button, 'Click me')
+ start()
+ }, 15)
+ })
+ .triggerNative('click')
+})
+
+asyncTest('button[data-remote][data-disable-with] re-enables when `ajax:before` event is cancelled', 6, function() {
+ var button = $('button[data-remote][data-disable-with]')
+
+ App.checkEnabledState(button, 'Click me')
+
+ button
+ .bindNative('ajax:before', function() {
+ App.checkDisabledState(button, 'clicking...')
+ return false
+ })
+ .triggerNative('click')
+
+ setTimeout(function() {
+ App.checkEnabledState(button, 'Click me')
+ start()
+ }, 30)
+})
+
+asyncTest('button[data-remote][data-disable-with] re-enables when `ajax:beforeSend` event is cancelled', 6, function() {
+ var button = $('button[data-remote][data-disable-with]')
+
+ App.checkEnabledState(button, 'Click me')
+
+ button
+ .bindNative('ajax:beforeSend', function() {
+ App.checkDisabledState(button, 'clicking...')
+ return false
+ })
+ .triggerNative('click')
+
+ setTimeout(function() {
+ App.checkEnabledState(button, 'Click me')
+ start()
+ }, 30)
+})
+
+asyncTest('button[data-remote][data-disable-with] re-enables when `ajax:error` event is triggered', 6, function() {
+ var button = $('a[data-disable-with]').attr('data-remote', true).attr('href', '/error')
+
+ App.checkEnabledState(button, 'Click me')
+
+ button
+ .bindNative('ajax:send', function() {
+ App.checkDisabledState(button, 'clicking...')
+ })
+ .triggerNative('click')
+
+ setTimeout(function() {
+ App.checkEnabledState(button, 'Click me')
+ start()
+ }, 30)
+})
diff --git a/actionview/test/ujs/public/test/data-disable.js b/actionview/test/ujs/public/test/data-disable.js
new file mode 100644
index 0000000000..ccc38cf9ae
--- /dev/null
+++ b/actionview/test/ujs/public/test/data-disable.js
@@ -0,0 +1,321 @@
+module('data-disable', {
+ setup: function() {
+ $('#qunit-fixture').append($('<form />', {
+ action: '/echo',
+ 'data-remote': 'true',
+ method: 'post'
+ }))
+ .find('form')
+ .append($('<input type="text" data-disable name="user_name" value="john" />'))
+
+ $('#qunit-fixture').append($('<form />', {
+ action: '/echo',
+ method: 'post'
+ }))
+ .find('form:last')
+ // WEEIRDD: the form won't submit to an iframe if the button is name="submit" (??!)
+ .append($('<input type="submit" data-disable name="submit2" value="Submit" />'))
+
+ $('#qunit-fixture').append($('<a />', {
+ text: 'Click me',
+ href: '/echo',
+ 'data-disable': 'true'
+ }))
+
+ $('#qunit-fixture').append($('<button />', {
+ text: 'Click me',
+ 'data-remote': true,
+ 'data-url': '/echo',
+ 'data-disable': 'true'
+ }))
+ },
+ teardown: function() {
+ $(document).unbind('iframe:loaded')
+ }
+})
+
+asyncTest('form input field with "data-disable" attribute', 7, function() {
+ var form = $('form[data-remote]'), input = form.find('input[type=text]')
+
+ App.checkEnabledState(input, 'john')
+
+ form.bindNative('ajax:success', function(e, data) {
+ setTimeout(function() {
+ App.checkEnabledState(input, 'john')
+ equal(data.params.user_name, 'john')
+ start()
+ }, 13)
+ })
+ form.triggerNative('submit')
+
+ App.checkDisabledState(input, 'john')
+})
+
+asyncTest('form button with "data-disable" attribute', 7, function() {
+ var form = $('form[data-remote]'), button = $('<button data-disable name="submit2">Submit</button>')
+ form.append(button)
+
+ App.checkEnabledState(button, 'Submit')
+
+ form.bindNative('ajax:success', function(e, data) {
+ setTimeout(function() {
+ App.checkEnabledState(button, 'Submit')
+ start()
+ }, 13)
+ })
+ form.triggerNative('submit')
+
+ App.checkDisabledState(button, 'Submit')
+ equal(button.data('ujs:enable-with'), undefined)
+})
+
+asyncTest('form input[type=submit][data-disable] disables', 6, function() {
+ var form = $('form:not([data-remote])'), input = form.find('input[type=submit]')
+
+ App.checkEnabledState(input, 'Submit')
+
+ // WEEIRDD: attaching this handler makes the test work in IE7
+ $(document).bind('iframe:loading', function(e, f) {})
+
+ $(document).bind('iframe:loaded', function(e, data) {
+ setTimeout(function() {
+ App.checkDisabledState(input, 'Submit')
+ start()
+ }, 30)
+ })
+ form.triggerNative('submit')
+
+ setTimeout(function() {
+ App.checkDisabledState(input, 'Submit')
+ }, 30)
+})
+
+asyncTest('form[data-remote] input[type=submit][data-disable] is replaced in ajax callback', 2, function() {
+ var form = $('form:not([data-remote])').attr('data-remote', 'true'), origFormContents = form.html()
+
+ form.bindNative('ajax:success', function() {
+ form.html(origFormContents)
+
+ setTimeout(function() {
+ var input = form.find('input[type=submit]')
+ App.checkEnabledState(input, 'Submit')
+ start()
+ }, 30)
+ }).triggerNative('submit')
+})
+
+asyncTest('form[data-remote] input[data-disable] is replaced with disabled field in ajax callback', 2, function() {
+ var form = $('form:not([data-remote])').attr('data-remote', 'true'), input = form.find('input[type=submit]'),
+ newDisabledInput = input.clone().attr('disabled', 'disabled')
+
+ form.bindNative('ajax:success', function() {
+ input.replaceWith(newDisabledInput)
+
+ setTimeout(function() {
+ App.checkEnabledState(newDisabledInput, 'Submit')
+ start()
+ }, 30)
+ }).triggerNative('submit')
+})
+
+asyncTest('form[data-remote] textarea[data-disable] attribute', 3, function() {
+ var form = $('form[data-remote]'),
+ textarea = $('<textarea data-disable name="user_bio">born, lived, died.</textarea>').appendTo(form)
+
+ form.bindNative('ajax:success', function(e, data) {
+ setTimeout(function() {
+ equal(data.params.user_bio, 'born, lived, died.')
+ start()
+ }, 13)
+ })
+ form.triggerNative('submit')
+
+ App.checkDisabledState(textarea, 'born, lived, died.')
+})
+
+asyncTest('a[data-disable] disables', 5, function() {
+ var link = $('a[data-disable]')
+
+ App.checkEnabledState(link, 'Click me')
+
+ link.triggerNative('click')
+ App.checkDisabledState(link, 'Click me')
+ equal(link.data('ujs:enable-with'), undefined)
+ start()
+})
+
+asyncTest('a[data-remote][data-disable] disables and re-enables', 6, function() {
+ var link = $('a[data-disable]').attr('data-remote', true)
+
+ App.checkEnabledState(link, 'Click me')
+
+ link
+ .bindNative('ajax:send', function() {
+ App.checkDisabledState(link, 'Click me')
+ })
+ .bindNative('ajax:complete', function() {
+ setTimeout( function() {
+ App.checkEnabledState(link, 'Click me')
+ start()
+ }, 15)
+ })
+ .triggerNative('click')
+})
+
+asyncTest('a[data-remote][data-disable] re-enables when `ajax:before` event is cancelled', 6, function() {
+ var link = $('a[data-disable]').attr('data-remote', true)
+
+ App.checkEnabledState(link, 'Click me')
+
+ link
+ .bindNative('ajax:before', function() {
+ App.checkDisabledState(link, 'Click me')
+ return false
+ })
+ .triggerNative('click')
+
+ setTimeout(function() {
+ App.checkEnabledState(link, 'Click me')
+ start()
+ }, 30)
+})
+
+asyncTest('a[data-remote][data-disable] re-enables when `ajax:beforeSend` event is cancelled', 6, function() {
+ var link = $('a[data-disable]').attr('data-remote', true)
+
+ App.checkEnabledState(link, 'Click me')
+
+ link
+ .bindNative('ajax:beforeSend', function() {
+ App.checkDisabledState(link, 'Click me')
+ return false
+ })
+ .triggerNative('click')
+
+ setTimeout(function() {
+ App.checkEnabledState(link, 'Click me')
+ start()
+ }, 30)
+})
+
+asyncTest('a[data-remote][data-disable] re-enables when `ajax:error` event is triggered', 6, function() {
+ var link = $('a[data-disable]').attr('data-remote', true).attr('href', '/error')
+
+ App.checkEnabledState(link, 'Click me')
+
+ link
+ .bindNative('ajax:send', function() {
+ App.checkDisabledState(link, 'Click me')
+ })
+ .triggerNative('click')
+
+ setTimeout(function() {
+ App.checkEnabledState(link, 'Click me')
+ start()
+ }, 30)
+})
+
+asyncTest('form[data-remote] input|button|textarea[data-disable] does not disable when `ajax:beforeSend` event is cancelled', 8, function() {
+ var form = $('form[data-remote]'),
+ input = form.find('input:text'),
+ button = $('<button data-disable="submitting ..." name="submit2">Submit</button>').appendTo(form),
+ textarea = $('<textarea data-disable name="user_bio">born, lived, died.</textarea>').appendTo(form),
+ submit = $('<input type="submit" data-disable="submitting ..." name="submit2" value="Submit" />').appendTo(form)
+
+ form
+ .bindNative('ajax:beforeSend', function() {
+ return false
+ })
+ .triggerNative('submit')
+
+ App.checkEnabledState(input, 'john')
+ App.checkEnabledState(button, 'Submit')
+ App.checkEnabledState(textarea, 'born, lived, died.')
+ App.checkEnabledState(submit, 'Submit')
+
+ start()
+})
+
+asyncTest('ctrl-clicking on a link does not disables the link', 6, function() {
+ var link = $('a[data-disable]')
+
+ App.checkEnabledState(link, 'Click me')
+
+ link.triggerNative('click', { metaKey: true })
+ App.checkEnabledState(link, 'Click me')
+
+ link.triggerNative('click', { ctrlKey: true })
+ App.checkEnabledState(link, 'Click me')
+ start()
+})
+
+asyncTest('button[data-remote][data-disable] disables and re-enables', 6, function() {
+ var button = $('button[data-remote][data-disable]')
+
+ App.checkEnabledState(button, 'Click me')
+
+ button
+ .bindNative('ajax:send', function() {
+ App.checkDisabledState(button, 'Click me')
+ })
+ .bindNative('ajax:complete', function() {
+ setTimeout( function() {
+ App.checkEnabledState(button, 'Click me')
+ start()
+ }, 15)
+ })
+ .triggerNative('click')
+})
+
+asyncTest('button[data-remote][data-disable] re-enables when `ajax:before` event is cancelled', 6, function() {
+ var button = $('button[data-remote][data-disable]')
+
+ App.checkEnabledState(button, 'Click me')
+
+ button
+ .bindNative('ajax:before', function() {
+ App.checkDisabledState(button, 'Click me')
+ return false
+ })
+ .triggerNative('click')
+
+ setTimeout(function() {
+ App.checkEnabledState(button, 'Click me')
+ start()
+ }, 30)
+})
+
+asyncTest('button[data-remote][data-disable] re-enables when `ajax:beforeSend` event is cancelled', 6, function() {
+ var button = $('button[data-remote][data-disable]')
+
+ App.checkEnabledState(button, 'Click me')
+
+ button
+ .bindNative('ajax:beforeSend', function() {
+ App.checkDisabledState(button, 'Click me')
+ return false
+ })
+ .triggerNative('click')
+
+ setTimeout(function() {
+ App.checkEnabledState(button, 'Click me')
+ start()
+ }, 30)
+})
+
+asyncTest('button[data-remote][data-disable] re-enables when `ajax:error` event is triggered', 6, function() {
+ var button = $('a[data-disable]').attr('data-remote', true).attr('href', '/error')
+
+ App.checkEnabledState(button, 'Click me')
+
+ button
+ .bindNative('ajax:send', function() {
+ App.checkDisabledState(button, 'Click me')
+ })
+ .triggerNative('click')
+
+ setTimeout(function() {
+ App.checkEnabledState(button, 'Click me')
+ start()
+ }, 30)
+})
diff --git a/actionview/test/ujs/public/test/data-method.js b/actionview/test/ujs/public/test/data-method.js
new file mode 100644
index 0000000000..47d940c577
--- /dev/null
+++ b/actionview/test/ujs/public/test/data-method.js
@@ -0,0 +1,85 @@
+(function() {
+
+module('data-method', {
+ setup: function() {
+ $('#qunit-fixture').append($('<a />', {
+ href: '/echo', 'data-method': 'delete', text: 'destroy!'
+ }))
+ },
+ teardown: function() {
+ $(document).unbind('iframe:loaded')
+ }
+})
+
+function submit(fn, options) {
+ $(document).bind('iframe:loaded', function(e, data) {
+ fn(data)
+ start()
+ })
+
+ $('#qunit-fixture').find('a')
+ .triggerNative('click')
+}
+
+asyncTest('link with "data-method" set to "delete"', 3, function() {
+ submit(function(data) {
+ equal(data.REQUEST_METHOD, 'DELETE')
+ strictEqual(data.params.authenticity_token, undefined)
+ strictEqual(data.HTTP_X_CSRF_TOKEN, undefined)
+ })
+})
+
+asyncTest('click on the child of link with "data-method"', 3, function() {
+ $(document).bind('iframe:loaded', function(e, data) {
+ equal(data.REQUEST_METHOD, 'DELETE')
+ strictEqual(data.params.authenticity_token, undefined)
+ strictEqual(data.HTTP_X_CSRF_TOKEN, undefined)
+ start()
+ })
+ $('#qunit-fixture a').html('<strong>destroy!</strong>').find('strong').triggerNative('click')
+})
+
+asyncTest('link with "data-method" and CSRF', 1, function() {
+ $('#qunit-fixture')
+ .append('<meta name="csrf-param" content="authenticity_token"/>')
+ .append('<meta name="csrf-token" content="cf50faa3fe97702ca1ae"/>')
+
+ submit(function(data) {
+ equal(data.params.authenticity_token, 'cf50faa3fe97702ca1ae')
+ })
+})
+
+asyncTest('link "target" should be carried over to generated form', 1, function() {
+ $('a[data-method]').attr('target', 'super-special-frame')
+ submit(function(data) {
+ equal(data.params._target, 'super-special-frame')
+ })
+})
+
+asyncTest('link with "data-method" and cross origin', 1, function() {
+ var data = {}
+
+ $('#qunit-fixture')
+ .append('<meta name="csrf-param" content="authenticity_token"/>')
+ .append('<meta name="csrf-token" content="cf50faa3fe97702ca1ae"/>')
+
+ $(document).on('submit', 'form', function(e) {
+ $(e.currentTarget).serializeArray().map(function(item) {
+ data[item.name] = item.value
+ })
+
+ return false
+ })
+
+ var link = $('#qunit-fixture').find('a')
+
+ link.attr('href', 'http://www.alfajango.com')
+
+ link.triggerNative('click')
+
+ start()
+
+ notEqual(data.authenticity_token, 'cf50faa3fe97702ca1ae')
+})
+
+})()
diff --git a/actionview/test/ujs/public/test/data-remote.js b/actionview/test/ujs/public/test/data-remote.js
new file mode 100644
index 0000000000..a51aa10417
--- /dev/null
+++ b/actionview/test/ujs/public/test/data-remote.js
@@ -0,0 +1,400 @@
+module('data-remote', {
+ setup: function() {
+ $('#qunit-fixture')
+ .append($('<a />', {
+ href: '/echo',
+ 'data-remote': 'true',
+ 'data-params': 'data1=value1&data2=value2',
+ text: 'my address'
+ }))
+ .append($('<button />', {
+ 'data-url': '/echo',
+ 'data-remote': 'true',
+ 'data-params': 'data1=value1&data2=value2',
+ text: 'my button'
+ }))
+ .append($('<form />', {
+ action: '/echo',
+ 'data-remote': 'true',
+ method: 'post',
+ id: 'my-remote-form'
+ }))
+ .append($('<a />', {
+ href: '/echo',
+ 'data-remote': 'true',
+ disabled: 'disabled',
+ text: 'Disabed link'
+ }))
+ .find('form').append($('<input type="text" name="user_name" value="john">'))
+
+ }
+})
+
+asyncTest('ctrl-clicking on a link does not fire ajaxyness', 0, function() {
+ var link = $('a[data-remote]')
+
+ // Ideally, we'd setup an iframe to intercept normal link clicks
+ // and add a test to make sure the iframe:loaded event is triggered.
+ // However, jquery doesn't actually cause a native `click` event and
+ // follow links using `trigger('click')`, it only fires bindings.
+ link
+ .removeAttr('data-params')
+ .bindNative('ajax:beforeSend', function() {
+ ok(false, 'ajax should not be triggered')
+ })
+
+ link.triggerNative('click', { metaKey: true })
+ link.triggerNative('click', { ctrlKey: true })
+
+ setTimeout(function() { start() }, 13)
+})
+
+asyncTest('ctrl-clicking on a link still fires ajax for non-GET links and for links with "data-params"', 2, function() {
+ var link = $('a[data-remote]')
+
+ link
+ .removeAttr('data-params')
+ .attr('data-method', 'POST')
+ .bindNative('ajax:beforeSend', function() {
+ ok(true, 'ajax should be triggered')
+ })
+ .triggerNative('click', { metaKey: true })
+
+ link
+ .removeAttr('data-method')
+ .attr('data-params', 'name=steve')
+ .triggerNative('click', { metaKey: true })
+
+ setTimeout(function() { start() }, 13)
+})
+
+asyncTest('clicking on a link with data-remote attribute', 5, function() {
+ $('a[data-remote]')
+ .bindNative('ajax:success', function(e, data, status, xhr) {
+ App.assertCallbackInvoked('ajax:success')
+ App.assertRequestPath(data, '/echo')
+ console.log(data.params)
+ equal(data.params.data1, 'value1', 'ajax arguments should have key data1 with right value')
+ equal(data.params.data2, 'value2', 'ajax arguments should have key data2 with right value')
+ App.assertGetRequest(data)
+ })
+ .bindNative('ajax:complete', function() { start() })
+ .triggerNative('click')
+})
+
+asyncTest('clicking on a link with both query string in href and data-params', 4, function() {
+ $('a[data-remote]')
+ .attr('href', '/echo?data3=value3')
+ .bindNative('ajax:success', function(e, data, status, xhr) {
+ App.assertGetRequest(data)
+ equal(data.params.data1, 'value1', 'ajax arguments should have key data1 with right value')
+ equal(data.params.data2, 'value2', 'ajax arguments should have key data2 with right value')
+ equal(data.params.data3, 'value3', 'query string in url should be passed to server with right value')
+ })
+ .bindNative('ajax:complete', function() { start() })
+ .triggerNative('click')
+})
+
+asyncTest('clicking on a link with both query string in href and data-params with POST method', 4, function() {
+ $('a[data-remote]')
+ .attr('href', '/echo?data3=value3')
+ .attr('data-method', 'post')
+ .bindNative('ajax:success', function(e, data, status, xhr) {
+ App.assertPostRequest(data)
+ equal(data.params.data1, 'value1', 'ajax arguments should have key data1 with right value')
+ equal(data.params.data2, 'value2', 'ajax arguments should have key data2 with right value')
+ equal(data.params.data3, 'value3', 'query string in url should be passed to server with right value')
+ })
+ .bindNative('ajax:complete', function() { start() })
+ .triggerNative('click')
+})
+
+asyncTest('clicking on a link with disabled attribute', 0, function() {
+ $('a[disabled]')
+ .bindNative('ajax:before', function(e, data, status, xhr) {
+ App.assertCallbackNotInvoked('ajax:success')
+ })
+ .bindNative('ajax:complete', function() { start() })
+ .triggerNative('click')
+
+ setTimeout(function() {
+ start()
+ }, 13)
+})
+
+asyncTest('clicking on a button with data-remote attribute', 5, function() {
+ $('button[data-remote]')
+ .bindNative('ajax:success', function(e, data, status, xhr) {
+ App.assertCallbackInvoked('ajax:success')
+ App.assertRequestPath(data, '/echo')
+ equal(data.params.data1, 'value1', 'ajax arguments should have key data1 with right value')
+ equal(data.params.data2, 'value2', 'ajax arguments should have key data2 with right value')
+ App.assertGetRequest(data)
+ })
+ .bindNative('ajax:complete', function() { start() })
+ .triggerNative('click')
+})
+
+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'}))
+ )
+
+ $('select[data-remote]')
+ .bindNative('ajax:success', function(e, data, status, xhr) {
+ App.assertCallbackInvoked('ajax:success')
+ App.assertRequestPath(data, '/echo')
+ equal(data.params.user_data, 'optionValue2', 'ajax arguments should have key term with right value')
+ equal(data.params.data1, 'value1', 'ajax arguments should have key data1 with right value')
+ App.assertGetRequest(data)
+ })
+ .bindNative('ajax:complete', function() { start() })
+ .val('optionValue2')
+ .triggerNative('change')
+})
+
+asyncTest('submitting form with data-remote attribute', 4, function() {
+ $('form[data-remote]')
+ .bindNative('ajax:success', function(e, data, status, xhr) {
+ App.assertCallbackInvoked('ajax:success')
+ App.assertRequestPath(data, '/echo')
+ equal(data.params.user_name, 'john', 'ajax arguments should have key user_name with right value')
+ App.assertPostRequest(data)
+ })
+ .bindNative('ajax:complete', function() { start() })
+ .triggerNative('submit')
+})
+
+asyncTest('submitting form with data-remote attribute should include inputs in a fieldset only once', 3, function() {
+ $('form[data-remote]')
+ .append('<fieldset><input name="items[]" value="Item" /></fieldset>')
+ .bindNative('ajax:success', function(e, data, status, xhr) {
+ App.assertCallbackInvoked('ajax:success')
+ equal(data.params.items.length, 1, 'ajax arguments should only have the item once')
+ App.assertPostRequest(data)
+ })
+ .bindNative('ajax:complete', function() {
+ $('form[data-remote], fieldset').remove()
+ start()
+ })
+ .triggerNative('submit')
+})
+
+asyncTest('submitting form with data-remote attribute submits input with matching [form] attribute', 5, function() {
+ $('#qunit-fixture')
+ .append($('<input type="text" name="user_data" value="value1" form="my-remote-form">'))
+
+ $('form[data-remote]')
+ .bindNative('ajax:success', function(e, data, status, xhr) {
+ App.assertCallbackInvoked('ajax:success')
+ App.assertRequestPath(data, '/echo')
+ equal(data.params.user_name, 'john', 'ajax arguments should have key user_name with right value')
+ equal(data.params.user_data, 'value1', 'ajax arguments should have key user_data with right value')
+ App.assertPostRequest(data)
+ })
+ .bindNative('ajax:complete', function() { start() })
+ .triggerNative('submit')
+})
+
+asyncTest('submitting form with data-remote attribute by clicking button with matching [form] attribute', 5, function() {
+ $('form[data-remote]')
+ .bindNative('ajax:success', function(e, data, status, xhr) {
+ App.assertCallbackInvoked('ajax:success')
+ App.assertRequestPath(data, '/echo')
+ equal(data.params.user_name, 'john', 'ajax arguments should have key user_name with right value')
+ equal(data.params.user_data, 'value2', 'ajax arguments should have key user_data with right value')
+ App.assertPostRequest(data)
+ })
+ .bindNative('ajax:complete', function() { start() })
+
+ $('<button />', {
+ type: 'submit',
+ name: 'user_data',
+ value: 'value1',
+ form: 'my-remote-form'
+ })
+ .appendTo($('#qunit-fixture'))
+
+ $('<button />', {
+ type: 'submit',
+ name: 'user_data',
+ value: 'value2',
+ form: 'my-remote-form'
+ })
+ .appendTo($('#qunit-fixture'))
+ .triggerNative('click')
+})
+
+asyncTest('form\'s submit bindings in browsers that don\'t support submit bubbling', 5, function() {
+ var form = $('form[data-remote]'), directBindingCalled = false
+
+ ok(!directBindingCalled, 'nothing is called')
+
+ form
+ .append($('<input type="submit" />'))
+ .bindNative('submit', function(event) {
+ ok(event.type == 'submit', 'submit event handlers are called with submit event')
+ ok(true, 'binding handler is called')
+ directBindingCalled = true
+ })
+ .bindNative('ajax:beforeSend', function() {
+ ok(true, 'form being submitted via ajax')
+ ok(directBindingCalled, 'binding handler already called')
+ })
+ .bindNative('ajax:complete', function() {
+ start()
+ })
+
+ if(!$.support.submitBubbles) {
+ // Must indrectly submit form via click to trigger jQuery's manual submit bubbling in IE
+ form.find('input[type=submit]')
+ .triggerNative('click')
+ } else {
+ form.triggerNative('submit')
+ }
+})
+
+asyncTest('returning false in form\'s submit bindings in non-submit-bubbling browsers', 1, function() {
+ var form = $('form[data-remote]')
+
+ form
+ .append($('<input type="submit" />'))
+ .bindNative('submit', function() {
+ ok(true, 'binding handler is called')
+ return false
+ })
+ .bindNative('ajax:beforeSend', function() {
+ ok(false, 'form should not be submitted')
+ })
+
+ if (!$.support.submitBubbles) {
+ // Must indrectly submit form via click to trigger jQuery's manual submit bubbling in IE
+ form.find('input[type=submit]').triggerNative('click')
+ } else {
+ form.triggerNative('submit')
+ }
+
+ setTimeout(function() { start() }, 13)
+})
+
+asyncTest('clicking on a link with falsy "data-remote" attribute does not fire ajaxyness', 0, function() {
+ $('a[data-remote]')
+ .attr('data-remote', 'false')
+ .bindNative('ajax:beforeSend', function() {
+ ok(false, 'ajax should not be triggered')
+ })
+ .bindNative('click', function() {
+ return false
+ })
+ .triggerNative('click')
+
+ setTimeout(function() { start() }, 20)
+})
+
+asyncTest('ctrl-clicking on a link with falsy "data-remote" attribute does not fire ajaxyness even if "data-params" present', 0, function() {
+ var link = $('a[data-remote]')
+
+ link
+ .removeAttr('data-params')
+ .attr('data-remote', 'false')
+ .attr('data-method', 'POST')
+ .bindNative('ajax:beforeSend', function() {
+ ok(false, 'ajax should not be triggered')
+ })
+ .bindNative('click', function() {
+ return false
+ })
+ .triggerNative('click', { metaKey: true })
+
+ link
+ .removeAttr('data-method')
+ .attr('data-params', 'name=steve')
+ .triggerNative('click', { metaKey: true })
+
+ setTimeout(function() { start() }, 20)
+})
+
+asyncTest('clicking on a button with falsy "data-remote" attribute', 0, function() {
+ $('button[data-remote]:first')
+ .attr('data-remote', 'false')
+ .bindNative('ajax:beforeSend', function() {
+ ok(false, 'ajax should not be triggered')
+ })
+ .bindNative('click', function() {
+ return false
+ })
+ .triggerNative('click')
+
+ setTimeout(function() { start() }, 20)
+})
+
+asyncTest('submitting a form with falsy "data-remote" attribute', 0, function() {
+ $('form[data-remote]:first')
+ .attr('data-remote', 'false')
+ .bindNative('ajax:beforeSend', function() {
+ ok(false, 'ajax should not be triggered')
+ })
+ .bindNative('submit', function() {
+ return false
+ })
+ .triggerNative('submit')
+
+ setTimeout(function() { start() }, 20)
+})
+
+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'}))
+ )
+
+ $('select[data-remote=false]:first')
+ .bindNative('ajax:beforeSend', function() {
+ ok(false, 'ajax should not be triggered')
+ })
+ .val('optionValue2')
+ .triggerNative('change')
+
+ setTimeout(function() { start() }, 20)
+})
+
+asyncTest('form should be serialized correctly', 6, function() {
+ $('form')
+ .append('<textarea name="textarea">textarea</textarea>')
+ .append('<input type="checkbox" name="checkbox[]" value="0" />')
+ .append('<input type="checkbox" checked="checked" name="checkbox[]" value="1" />')
+ .append('<input type="radio" checked="checked" name="radio" value="0" />')
+ .append('<input type="radio" name="radio" value="1" />')
+ .append('<select multiple="multiple" name="select[]">\
+ <option value="1" selected>1</option>\
+ <option value="2" selected>2</option>\
+ <option value="3">3</option>\
+ <option selected>4</option>\
+ </select>')
+ .bindNative('ajax:success', function(e, data, status, xhr) {
+ equal(data.params.checkbox.length, 1)
+ equal(data.params.checkbox[0], '1')
+ equal(data.params.radio, '0')
+ equal(data.params.select.length, 3)
+ equal(data.params.select[2], '4')
+ equal(data.params.textarea, 'textarea')
+
+ start()
+ })
+ .triggerNative('submit')
+})
diff --git a/actionview/test/ujs/public/test/override.js b/actionview/test/ujs/public/test/override.js
new file mode 100644
index 0000000000..be6ec7749b
--- /dev/null
+++ b/actionview/test/ujs/public/test/override.js
@@ -0,0 +1,56 @@
+(function() {
+
+var realHref
+
+module('override', {
+ setup: function() {
+ realHref = $.rails.href
+ $('#qunit-fixture')
+ .append($('<a />', {
+ href: '/real/href', 'data-remote': 'true', 'data-method': 'delete', 'data-href': '/data/href'
+ }))
+ },
+ teardown: function() {
+ $.rails.href = realHref
+ }
+})
+
+asyncTest('the getter for an element\'s href is publicly accessible', 1, function() {
+ ok($.rails.href)
+ start()
+})
+
+asyncTest('the getter for an element\'s href is overridable', 1, function() {
+ $.rails.href = function(element) { return $(element).data('href') }
+ $('#qunit-fixture a')
+ .bindNative('ajax:beforeSend', function(e, xhr, options) {
+ equal('/data/href', options.url)
+ return false
+ })
+ .triggerNative('click')
+ start()
+})
+
+asyncTest('the getter for an element\'s href works normally if not overridden', 1, function() {
+ $('#qunit-fixture a')
+ .bindNative('ajax:beforeSend', function(e, xhr, options) {
+ equal(location.protocol + '//' + location.host + '/real/href', options.url)
+ return false
+ })
+ .triggerNative('click')
+ start()
+})
+
+asyncTest('the event selector strings are overridable', 1, function() {
+ ok($.rails.linkClickSelector.indexOf(', a[data-custom-remote-link]') != -1, 'linkClickSelector contains custom selector')
+ start()
+})
+
+asyncTest('including jquery-ujs multiple times throws error', 1, function() {
+ throws(function() {
+ Rails.start()
+ }, 'appending rails.js again throws error')
+ setTimeout(function() { start() }, 50)
+})
+
+})()
diff --git a/actionview/test/ujs/public/test/settings.js b/actionview/test/ujs/public/test/settings.js
new file mode 100644
index 0000000000..3375456f11
--- /dev/null
+++ b/actionview/test/ujs/public/test/settings.js
@@ -0,0 +1,116 @@
+var App = App || {}
+
+App.assertCallbackInvoked = function(callbackName) {
+ ok(true, callbackName + ' callback should have been invoked')
+}
+
+App.assertCallbackNotInvoked = function(callbackName) {
+ ok(false, callbackName + ' callback should not have been invoked')
+}
+
+App.assertGetRequest = function(requestEnv) {
+ equal(requestEnv['REQUEST_METHOD'], 'GET', 'request type should be GET')
+}
+
+App.assertPostRequest = function(requestEnv) {
+ equal(requestEnv['REQUEST_METHOD'], 'POST', 'request type should be POST')
+}
+
+App.assertRequestPath = function(requestEnv, path) {
+ equal(requestEnv['PATH_INFO'], path, 'request should be sent to right url')
+}
+
+App.getVal = function(el) {
+ return el.is('input,textarea,select') ? el.val() : el.text()
+}
+
+App.disabled = function(el) {
+ return el.is('input,textarea,select,button') ?
+ (el.is(':disabled') && $.rails.getData(el[0], 'ujs:disabled')) :
+ $.rails.getData(el[0], 'ujs:disabled')
+}
+
+App.checkEnabledState = function(el, text) {
+ ok(!App.disabled(el), el.get(0).tagName + ' should not be disabled')
+ equal(App.getVal(el), text, el.get(0).tagName + ' text should be original value')
+}
+
+App.checkDisabledState = function(el, text) {
+ ok(App.disabled(el), el.get(0).tagName + ' should be disabled')
+ equal(App.getVal(el), text, el.get(0).tagName + ' text should be disabled value')
+}
+
+// hijacks normal form submit; lets it submit to an iframe to prevent
+// navigating away from the test suite
+$(document).bind('submit', function(e) {
+ if (!e.isDefaultPrevented()) {
+ var form = $(e.target), action = form.attr('action'),
+ name = 'form-frame' + jQuery.guid++,
+ iframe = $('<iframe name="' + name + '" />'),
+ iframeInput = '<input name="iframe" value="true" type="hidden" />'
+ targetInput = '<input name="_target" value="' + (form.attr('target') || '') + '" type="hidden" />'
+
+ if (action && action.indexOf('iframe') < 0) {
+ if (action.indexOf('?') < 0) {
+ form.attr('action', action + '?iframe=true')
+ } else {
+ form.attr('action', action + '&iframe=true')
+ }
+ }
+ form.attr('target', name).append(iframeInput, targetInput)
+ $('#qunit-fixture').append(iframe)
+ $.event.trigger('iframe:loading', form)
+ }
+})
+
+var MouseEvent = window.MouseEvent
+
+try {
+ new MouseEvent()
+} catch (e) {
+ MouseEvent = function(type, options) {
+ var evt = document.createEvent('MouseEvents')
+ evt.initMouseEvent(type, options.bubbles, options.cancelable, window, options.detail, 0, 0, 80, 20, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, null)
+ return evt
+ }
+}
+
+$.fn.extend({
+ // trigger an native click event
+ triggerNative: function(type, options) {
+ var el = this[0],
+ event,
+ Evt = {
+ 'click': MouseEvent,
+ 'change': Event,
+ 'pageshow': PageTransitionEvent,
+ 'submit': Event
+ }[type]
+
+ options = options || {}
+ options.bubbles = true
+ options.cancelable = true
+
+ event = new Evt(type, options)
+
+ el.dispatchEvent(event)
+
+ if (type === 'submit' && !event.defaultPrevented) {
+ el.submit()
+ }
+ return this
+ },
+ bindNative: function(event, handler) {
+ if (!handler) return this
+
+ this.bind(event, function(e) {
+ var args = []
+ if (e.originalEvent.detail) {
+ args = e.originalEvent.detail.slice()
+ }
+ args.unshift(e)
+ return handler.apply(this, args)
+ })
+ return this
+ }
+})
diff --git a/actionview/test/ujs/public/vendor/jquery.metadata.js b/actionview/test/ujs/public/vendor/jquery.metadata.js
new file mode 100644
index 0000000000..ad8bfba404
--- /dev/null
+++ b/actionview/test/ujs/public/vendor/jquery.metadata.js
@@ -0,0 +1,122 @@
+/*
+ * Metadata - jQuery plugin for parsing metadata from elements
+ *
+ * Copyright (c) 2006 John Resig, Yehuda Katz, J�örn Zaefferer, Paul McLanahan
+ *
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * Revision: $Id: jquery.metadata.js 4187 2007-12-16 17:15:27Z joern.zaefferer $
+ *
+ */
+
+/**
+ * Sets the type of metadata to use. Metadata is encoded in JSON, and each property
+ * in the JSON will become a property of the element itself.
+ *
+ * There are three supported types of metadata storage:
+ *
+ * attr: Inside an attribute. The name parameter indicates *which* attribute.
+ *
+ * class: Inside the class attribute, wrapped in curly braces: { }
+ *
+ * elem: Inside a child element (e.g. a script tag). The
+ * name parameter indicates *which* element.
+ *
+ * The metadata for an element is loaded the first time the element is accessed via jQuery.
+ *
+ * As a result, you can define the metadata type, use $(expr) to load the metadata into the elements
+ * matched by expr, then redefine the metadata type and run another $(expr) for other elements.
+ *
+ * @name $.metadata.setType
+ *
+ * @example <p id="one" class="some_class {item_id: 1, item_label: 'Label'}">This is a p</p>
+ * @before $.metadata.setType("class")
+ * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
+ * @desc Reads metadata from the class attribute
+ *
+ * @example <p id="one" class="some_class" data="{item_id: 1, item_label: 'Label'}">This is a p</p>
+ * @before $.metadata.setType("attr", "data")
+ * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
+ * @desc Reads metadata from a "data" attribute
+ *
+ * @example <p id="one" class="some_class"><script>{item_id: 1, item_label: 'Label'}</script>This is a p</p>
+ * @before $.metadata.setType("elem", "script")
+ * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
+ * @desc Reads metadata from a nested script element
+ *
+ * @param String type The encoding type
+ * @param String name The name of the attribute to be used to get metadata (optional)
+ * @cat Plugins/Metadata
+ * @descr Sets the type of encoding to be used when loading metadata for the first time
+ * @type undefined
+ * @see metadata()
+ */
+
+(function($) {
+
+$.extend({
+ metadata : {
+ defaults : {
+ type: 'class',
+ name: 'metadata',
+ cre: /({.*})/,
+ single: 'metadata'
+ },
+ setType: function( type, name ){
+ this.defaults.type = type;
+ this.defaults.name = name;
+ },
+ get: function( elem, opts ){
+ var settings = $.extend({},this.defaults,opts);
+ // check for empty string in single property
+ if ( !settings.single.length ) settings.single = 'metadata';
+
+ var data = $.data(elem, settings.single);
+ // returned cached data if it already exists
+ if ( data ) return data;
+
+ data = "{}";
+
+ if ( settings.type == "class" ) {
+ var m = settings.cre.exec( elem.className );
+ if ( m )
+ data = m[1];
+ } else if ( settings.type == "elem" ) {
+ if( !elem.getElementsByTagName )
+ return undefined;
+ var e = elem.getElementsByTagName(settings.name);
+ if ( e.length )
+ data = $.trim(e[0].innerHTML);
+ } else if ( elem.getAttribute != undefined ) {
+ var attr = elem.getAttribute( settings.name );
+ if ( attr )
+ data = attr;
+ }
+
+ if ( data.indexOf( '{' ) <0 )
+ data = "{" + data + "}";
+
+ data = eval("(" + data + ")");
+
+ $.data( elem, settings.single, data );
+ return data;
+ }
+ }
+});
+
+/**
+ * Returns the metadata object for the first member of the jQuery object.
+ *
+ * @name metadata
+ * @descr Returns element's metadata object
+ * @param Object opts An object contianing settings to override the defaults
+ * @type jQuery
+ * @cat Plugins/Metadata
+ */
+$.fn.metadata = function( opts ){
+ return $.metadata.get( this[0], opts );
+};
+
+})(jQuery); \ No newline at end of file
diff --git a/actionview/test/ujs/public/vendor/qunit.css b/actionview/test/ujs/public/vendor/qunit.css
new file mode 100644
index 0000000000..93026e3ba3
--- /dev/null
+++ b/actionview/test/ujs/public/vendor/qunit.css
@@ -0,0 +1,237 @@
+/*!
+ * QUnit 1.14.0
+ * http://qunitjs.com/
+ *
+ * Copyright 2013 jQuery Foundation and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2014-01-31T16:40Z
+ */
+
+/** Font Family and Sizes */
+
+#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
+ font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
+}
+
+#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
+#qunit-tests { font-size: smaller; }
+
+
+/** Resets */
+
+#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
+ margin: 0;
+ padding: 0;
+}
+
+
+/** Header */
+
+#qunit-header {
+ padding: 0.5em 0 0.5em 1em;
+
+ color: #8699A4;
+ background-color: #0D3349;
+
+ font-size: 1.5em;
+ line-height: 1em;
+ font-weight: 400;
+
+ border-radius: 5px 5px 0 0;
+}
+
+#qunit-header a {
+ text-decoration: none;
+ color: #C2CCD1;
+}
+
+#qunit-header a:hover,
+#qunit-header a:focus {
+ color: #FFF;
+}
+
+#qunit-testrunner-toolbar label {
+ display: inline-block;
+ padding: 0 0.5em 0 0.1em;
+}
+
+#qunit-banner {
+ height: 5px;
+}
+
+#qunit-testrunner-toolbar {
+ padding: 0.5em 0 0.5em 2em;
+ color: #5E740B;
+ background-color: #EEE;
+ overflow: hidden;
+}
+
+#qunit-userAgent {
+ padding: 0.5em 0 0.5em 2.5em;
+ background-color: #2B81AF;
+ color: #FFF;
+ text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
+}
+
+#qunit-modulefilter-container {
+ float: right;
+}
+
+/** Tests: Pass/Fail */
+
+#qunit-tests {
+ list-style-position: inside;
+}
+
+#qunit-tests li {
+ padding: 0.4em 0.5em 0.4em 2.5em;
+ border-bottom: 1px solid #FFF;
+ list-style-position: inside;
+}
+
+#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
+ display: none;
+}
+
+#qunit-tests li strong {
+ cursor: pointer;
+}
+
+#qunit-tests li a {
+ padding: 0.5em;
+ color: #C2CCD1;
+ text-decoration: none;
+}
+#qunit-tests li a:hover,
+#qunit-tests li a:focus {
+ color: #000;
+}
+
+#qunit-tests li .runtime {
+ float: right;
+ font-size: smaller;
+}
+
+.qunit-assert-list {
+ margin-top: 0.5em;
+ padding: 0.5em;
+
+ background-color: #FFF;
+
+ border-radius: 5px;
+}
+
+.qunit-collapsed {
+ display: none;
+}
+
+#qunit-tests table {
+ border-collapse: collapse;
+ margin-top: 0.2em;
+}
+
+#qunit-tests th {
+ text-align: right;
+ vertical-align: top;
+ padding: 0 0.5em 0 0;
+}
+
+#qunit-tests td {
+ vertical-align: top;
+}
+
+#qunit-tests pre {
+ margin: 0;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+
+#qunit-tests del {
+ background-color: #E0F2BE;
+ color: #374E0C;
+ text-decoration: none;
+}
+
+#qunit-tests ins {
+ background-color: #FFCACA;
+ color: #500;
+ text-decoration: none;
+}
+
+/*** Test Counts */
+
+#qunit-tests b.counts { color: #000; }
+#qunit-tests b.passed { color: #5E740B; }
+#qunit-tests b.failed { color: #710909; }
+
+#qunit-tests li li {
+ padding: 5px;
+ background-color: #FFF;
+ border-bottom: none;
+ list-style-position: inside;
+}
+
+/*** Passing Styles */
+
+#qunit-tests li li.pass {
+ color: #3C510C;
+ background-color: #FFF;
+ border-left: 10px solid #C6E746;
+}
+
+#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
+#qunit-tests .pass .test-name { color: #366097; }
+
+#qunit-tests .pass .test-actual,
+#qunit-tests .pass .test-expected { color: #999; }
+
+#qunit-banner.qunit-pass { background-color: #C6E746; }
+
+/*** Failing Styles */
+
+#qunit-tests li li.fail {
+ color: #710909;
+ background-color: #FFF;
+ border-left: 10px solid #EE5757;
+ white-space: pre;
+}
+
+#qunit-tests > li:last-child {
+ border-radius: 0 0 5px 5px;
+}
+
+#qunit-tests .fail { color: #000; background-color: #EE5757; }
+#qunit-tests .fail .test-name,
+#qunit-tests .fail .module-name { color: #000; }
+
+#qunit-tests .fail .test-actual { color: #EE5757; }
+#qunit-tests .fail .test-expected { color: #008000; }
+
+#qunit-banner.qunit-fail { background-color: #EE5757; }
+
+
+/** Result */
+
+#qunit-testresult {
+ padding: 0.5em 0.5em 0.5em 2.5em;
+
+ color: #2B81AF;
+ background-color: #D2E0E6;
+
+ border-bottom: 1px solid #FFF;
+}
+#qunit-testresult .module-name {
+ font-weight: 700;
+}
+
+/** Fixture */
+
+#qunit-fixture {
+ position: absolute;
+ top: -10000px;
+ left: -10000px;
+ width: 1000px;
+ height: 1000px;
+}
diff --git a/actionview/test/ujs/public/vendor/qunit.js b/actionview/test/ujs/public/vendor/qunit.js
new file mode 100644
index 0000000000..0e279fde17
--- /dev/null
+++ b/actionview/test/ujs/public/vendor/qunit.js
@@ -0,0 +1,2288 @@
+/*!
+ * QUnit 1.14.0
+ * http://qunitjs.com/
+ *
+ * Copyright 2013 jQuery Foundation and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2014-01-31T16:40Z
+ */
+
+(function( window ) {
+
+var QUnit,
+ assert,
+ config,
+ onErrorFnPrev,
+ testId = 0,
+ fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),
+ toString = Object.prototype.toString,
+ hasOwn = Object.prototype.hasOwnProperty,
+ // Keep a local reference to Date (GH-283)
+ Date = window.Date,
+ setTimeout = window.setTimeout,
+ clearTimeout = window.clearTimeout,
+ defined = {
+ document: typeof window.document !== "undefined",
+ setTimeout: typeof window.setTimeout !== "undefined",
+ sessionStorage: (function() {
+ var x = "qunit-test-string";
+ try {
+ sessionStorage.setItem( x, x );
+ sessionStorage.removeItem( x );
+ return true;
+ } catch( e ) {
+ return false;
+ }
+ }())
+ },
+ /**
+ * Provides a normalized error string, correcting an issue
+ * with IE 7 (and prior) where Error.prototype.toString is
+ * not properly implemented
+ *
+ * Based on http://es5.github.com/#x15.11.4.4
+ *
+ * @param {String|Error} error
+ * @return {String} error message
+ */
+ errorString = function( error ) {
+ var name, message,
+ errorString = error.toString();
+ if ( errorString.substring( 0, 7 ) === "[object" ) {
+ name = error.name ? error.name.toString() : "Error";
+ message = error.message ? error.message.toString() : "";
+ if ( name && message ) {
+ return name + ": " + message;
+ } else if ( name ) {
+ return name;
+ } else if ( message ) {
+ return message;
+ } else {
+ return "Error";
+ }
+ } else {
+ return errorString;
+ }
+ },
+ /**
+ * Makes a clone of an object using only Array or Object as base,
+ * and copies over the own enumerable properties.
+ *
+ * @param {Object} obj
+ * @return {Object} New object with only the own properties (recursively).
+ */
+ objectValues = function( obj ) {
+ // Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392.
+ /*jshint newcap: false */
+ var key, val,
+ vals = QUnit.is( "array", obj ) ? [] : {};
+ for ( key in obj ) {
+ if ( hasOwn.call( obj, key ) ) {
+ val = obj[key];
+ vals[key] = val === Object(val) ? objectValues(val) : val;
+ }
+ }
+ return vals;
+ };
+
+
+// Root QUnit object.
+// `QUnit` initialized at top of scope
+QUnit = {
+
+ // call on start of module test to prepend name to all tests
+ module: function( name, testEnvironment ) {
+ config.currentModule = name;
+ config.currentModuleTestEnvironment = testEnvironment;
+ config.modules[name] = true;
+ },
+
+ asyncTest: function( testName, expected, callback ) {
+ if ( arguments.length === 2 ) {
+ callback = expected;
+ expected = null;
+ }
+
+ QUnit.test( testName, expected, callback, true );
+ },
+
+ test: function( testName, expected, callback, async ) {
+ var test,
+ nameHtml = "<span class='test-name'>" + escapeText( testName ) + "</span>";
+
+ if ( arguments.length === 2 ) {
+ callback = expected;
+ expected = null;
+ }
+
+ if ( config.currentModule ) {
+ nameHtml = "<span class='module-name'>" + escapeText( config.currentModule ) + "</span>: " + nameHtml;
+ }
+
+ test = new Test({
+ nameHtml: nameHtml,
+ testName: testName,
+ expected: expected,
+ async: async,
+ callback: callback,
+ module: config.currentModule,
+ moduleTestEnvironment: config.currentModuleTestEnvironment,
+ stack: sourceFromStacktrace( 2 )
+ });
+
+ if ( !validTest( test ) ) {
+ return;
+ }
+
+ test.queue();
+ },
+
+ // Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through.
+ expect: function( asserts ) {
+ if (arguments.length === 1) {
+ config.current.expected = asserts;
+ } else {
+ return config.current.expected;
+ }
+ },
+
+ start: function( count ) {
+ // QUnit hasn't been initialized yet.
+ // Note: RequireJS (et al) may delay onLoad
+ if ( config.semaphore === undefined ) {
+ QUnit.begin(function() {
+ // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first
+ setTimeout(function() {
+ QUnit.start( count );
+ });
+ });
+ return;
+ }
+
+ config.semaphore -= count || 1;
+ // don't start until equal number of stop-calls
+ if ( config.semaphore > 0 ) {
+ return;
+ }
+ // ignore if start is called more often then stop
+ if ( config.semaphore < 0 ) {
+ config.semaphore = 0;
+ QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) );
+ return;
+ }
+ // A slight delay, to avoid any current callbacks
+ if ( defined.setTimeout ) {
+ setTimeout(function() {
+ if ( config.semaphore > 0 ) {
+ return;
+ }
+ if ( config.timeout ) {
+ clearTimeout( config.timeout );
+ }
+
+ config.blocking = false;
+ process( true );
+ }, 13);
+ } else {
+ config.blocking = false;
+ process( true );
+ }
+ },
+
+ stop: function( count ) {
+ config.semaphore += count || 1;
+ config.blocking = true;
+
+ if ( config.testTimeout && defined.setTimeout ) {
+ clearTimeout( config.timeout );
+ config.timeout = setTimeout(function() {
+ QUnit.ok( false, "Test timed out" );
+ config.semaphore = 1;
+ QUnit.start();
+ }, config.testTimeout );
+ }
+ }
+};
+
+// We use the prototype to distinguish between properties that should
+// be exposed as globals (and in exports) and those that shouldn't
+(function() {
+ function F() {}
+ F.prototype = QUnit;
+ QUnit = new F();
+ // Make F QUnit's constructor so that we can add to the prototype later
+ QUnit.constructor = F;
+}());
+
+/**
+ * Config object: Maintain internal state
+ * Later exposed as QUnit.config
+ * `config` initialized at top of scope
+ */
+config = {
+ // The queue of tests to run
+ queue: [],
+
+ // block until document ready
+ blocking: true,
+
+ // when enabled, show only failing tests
+ // gets persisted through sessionStorage and can be changed in UI via checkbox
+ hidepassed: false,
+
+ // by default, run previously failed tests first
+ // very useful in combination with "Hide passed tests" checked
+ reorder: true,
+
+ // by default, modify document.title when suite is done
+ altertitle: true,
+
+ // by default, scroll to top of the page when suite is done
+ scrolltop: true,
+
+ // when enabled, all tests must call expect()
+ requireExpects: false,
+
+ // add checkboxes that are persisted in the query-string
+ // when enabled, the id is set to `true` as a `QUnit.config` property
+ urlConfig: [
+ {
+ id: "noglobals",
+ label: "Check for Globals",
+ tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings."
+ },
+ {
+ id: "notrycatch",
+ label: "No try-catch",
+ tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings."
+ }
+ ],
+
+ // Set of all modules.
+ modules: {},
+
+ // logging callback queues
+ begin: [],
+ done: [],
+ log: [],
+ testStart: [],
+ testDone: [],
+ moduleStart: [],
+ moduleDone: []
+};
+
+// Initialize more QUnit.config and QUnit.urlParams
+(function() {
+ var i, current,
+ location = window.location || { search: "", protocol: "file:" },
+ params = location.search.slice( 1 ).split( "&" ),
+ length = params.length,
+ urlParams = {};
+
+ if ( params[ 0 ] ) {
+ for ( i = 0; i < length; i++ ) {
+ current = params[ i ].split( "=" );
+ current[ 0 ] = decodeURIComponent( current[ 0 ] );
+
+ // allow just a key to turn on a flag, e.g., test.html?noglobals
+ current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
+ if ( urlParams[ current[ 0 ] ] ) {
+ urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] );
+ } else {
+ urlParams[ current[ 0 ] ] = current[ 1 ];
+ }
+ }
+ }
+
+ QUnit.urlParams = urlParams;
+
+ // String search anywhere in moduleName+testName
+ config.filter = urlParams.filter;
+
+ // Exact match of the module name
+ config.module = urlParams.module;
+
+ config.testNumber = [];
+ if ( urlParams.testNumber ) {
+
+ // Ensure that urlParams.testNumber is an array
+ urlParams.testNumber = [].concat( urlParams.testNumber );
+ for ( i = 0; i < urlParams.testNumber.length; i++ ) {
+ current = urlParams.testNumber[ i ];
+ config.testNumber.push( parseInt( current, 10 ) );
+ }
+ }
+
+ // Figure out if we're running the tests from a server or not
+ QUnit.isLocal = location.protocol === "file:";
+}());
+
+extend( QUnit, {
+
+ config: config,
+
+ // Initialize the configuration options
+ init: function() {
+ extend( config, {
+ stats: { all: 0, bad: 0 },
+ moduleStats: { all: 0, bad: 0 },
+ started: +new Date(),
+ updateRate: 1000,
+ blocking: false,
+ autostart: true,
+ autorun: false,
+ filter: "",
+ queue: [],
+ semaphore: 1
+ });
+
+ var tests, banner, result,
+ qunit = id( "qunit" );
+
+ if ( qunit ) {
+ qunit.innerHTML =
+ "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
+ "<h2 id='qunit-banner'></h2>" +
+ "<div id='qunit-testrunner-toolbar'></div>" +
+ "<h2 id='qunit-userAgent'></h2>" +
+ "<ol id='qunit-tests'></ol>";
+ }
+
+ tests = id( "qunit-tests" );
+ banner = id( "qunit-banner" );
+ result = id( "qunit-testresult" );
+
+ if ( tests ) {
+ tests.innerHTML = "";
+ }
+
+ if ( banner ) {
+ banner.className = "";
+ }
+
+ if ( result ) {
+ result.parentNode.removeChild( result );
+ }
+
+ if ( tests ) {
+ result = document.createElement( "p" );
+ result.id = "qunit-testresult";
+ result.className = "result";
+ tests.parentNode.insertBefore( result, tests );
+ result.innerHTML = "Running...<br/>&nbsp;";
+ }
+ },
+
+ // Resets the test setup. Useful for tests that modify the DOM.
+ /*
+ DEPRECATED: Use multiple tests instead of resetting inside a test.
+ Use testStart or testDone for custom cleanup.
+ This method will throw an error in 2.0, and will be removed in 2.1
+ */
+ reset: function() {
+ var fixture = id( "qunit-fixture" );
+ if ( fixture ) {
+ fixture.innerHTML = config.fixture;
+ }
+ },
+
+ // Safe object type checking
+ is: function( type, obj ) {
+ return QUnit.objectType( obj ) === type;
+ },
+
+ objectType: function( obj ) {
+ if ( typeof obj === "undefined" ) {
+ return "undefined";
+ }
+
+ // Consider: typeof null === object
+ if ( obj === null ) {
+ return "null";
+ }
+
+ var match = toString.call( obj ).match(/^\[object\s(.*)\]$/),
+ type = match && match[1] || "";
+
+ switch ( type ) {
+ case "Number":
+ if ( isNaN(obj) ) {
+ return "nan";
+ }
+ return "number";
+ case "String":
+ case "Boolean":
+ case "Array":
+ case "Date":
+ case "RegExp":
+ case "Function":
+ return type.toLowerCase();
+ }
+ if ( typeof obj === "object" ) {
+ return "object";
+ }
+ return undefined;
+ },
+
+ push: function( result, actual, expected, message ) {
+ if ( !config.current ) {
+ throw new Error( "assertion outside test context, was " + sourceFromStacktrace() );
+ }
+
+ var output, source,
+ details = {
+ module: config.current.module,
+ name: config.current.testName,
+ result: result,
+ message: message,
+ actual: actual,
+ expected: expected
+ };
+
+ message = escapeText( message ) || ( result ? "okay" : "failed" );
+ message = "<span class='test-message'>" + message + "</span>";
+ output = message;
+
+ if ( !result ) {
+ expected = escapeText( QUnit.jsDump.parse(expected) );
+ actual = escapeText( QUnit.jsDump.parse(actual) );
+ output += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + expected + "</pre></td></tr>";
+
+ if ( actual !== expected ) {
+ output += "<tr class='test-actual'><th>Result: </th><td><pre>" + actual + "</pre></td></tr>";
+ output += "<tr class='test-diff'><th>Diff: </th><td><pre>" + QUnit.diff( expected, actual ) + "</pre></td></tr>";
+ }
+
+ source = sourceFromStacktrace();
+
+ if ( source ) {
+ details.source = source;
+ output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>";
+ }
+
+ output += "</table>";
+ }
+
+ runLoggingCallbacks( "log", QUnit, details );
+
+ config.current.assertions.push({
+ result: !!result,
+ message: output
+ });
+ },
+
+ pushFailure: function( message, source, actual ) {
+ if ( !config.current ) {
+ throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) );
+ }
+
+ var output,
+ details = {
+ module: config.current.module,
+ name: config.current.testName,
+ result: false,
+ message: message
+ };
+
+ message = escapeText( message ) || "error";
+ message = "<span class='test-message'>" + message + "</span>";
+ output = message;
+
+ output += "<table>";
+
+ if ( actual ) {
+ output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeText( actual ) + "</pre></td></tr>";
+ }
+
+ if ( source ) {
+ details.source = source;
+ output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>";
+ }
+
+ output += "</table>";
+
+ runLoggingCallbacks( "log", QUnit, details );
+
+ config.current.assertions.push({
+ result: false,
+ message: output
+ });
+ },
+
+ url: function( params ) {
+ params = extend( extend( {}, QUnit.urlParams ), params );
+ var key,
+ querystring = "?";
+
+ for ( key in params ) {
+ if ( hasOwn.call( params, key ) ) {
+ querystring += encodeURIComponent( key ) + "=" +
+ encodeURIComponent( params[ key ] ) + "&";
+ }
+ }
+ return window.location.protocol + "//" + window.location.host +
+ window.location.pathname + querystring.slice( 0, -1 );
+ },
+
+ extend: extend,
+ id: id,
+ addEvent: addEvent,
+ addClass: addClass,
+ hasClass: hasClass,
+ removeClass: removeClass
+ // load, equiv, jsDump, diff: Attached later
+});
+
+/**
+ * @deprecated: Created for backwards compatibility with test runner that set the hook function
+ * into QUnit.{hook}, instead of invoking it and passing the hook function.
+ * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here.
+ * Doing this allows us to tell if the following methods have been overwritten on the actual
+ * QUnit object.
+ */
+extend( QUnit.constructor.prototype, {
+
+ // Logging callbacks; all receive a single argument with the listed properties
+ // run test/logs.html for any related changes
+ begin: registerLoggingCallback( "begin" ),
+
+ // done: { failed, passed, total, runtime }
+ done: registerLoggingCallback( "done" ),
+
+ // log: { result, actual, expected, message }
+ log: registerLoggingCallback( "log" ),
+
+ // testStart: { name }
+ testStart: registerLoggingCallback( "testStart" ),
+
+ // testDone: { name, failed, passed, total, runtime }
+ testDone: registerLoggingCallback( "testDone" ),
+
+ // moduleStart: { name }
+ moduleStart: registerLoggingCallback( "moduleStart" ),
+
+ // moduleDone: { name, failed, passed, total }
+ moduleDone: registerLoggingCallback( "moduleDone" )
+});
+
+if ( !defined.document || document.readyState === "complete" ) {
+ config.autorun = true;
+}
+
+QUnit.load = function() {
+ runLoggingCallbacks( "begin", QUnit, {} );
+
+ // Initialize the config, saving the execution queue
+ var banner, filter, i, j, label, len, main, ol, toolbar, val, selection,
+ urlConfigContainer, moduleFilter, userAgent,
+ numModules = 0,
+ moduleNames = [],
+ moduleFilterHtml = "",
+ urlConfigHtml = "",
+ oldconfig = extend( {}, config );
+
+ QUnit.init();
+ extend(config, oldconfig);
+
+ config.blocking = false;
+
+ len = config.urlConfig.length;
+
+ for ( i = 0; i < len; i++ ) {
+ val = config.urlConfig[i];
+ if ( typeof val === "string" ) {
+ val = {
+ id: val,
+ label: val
+ };
+ }
+ config[ val.id ] = QUnit.urlParams[ val.id ];
+ if ( !val.value || typeof val.value === "string" ) {
+ urlConfigHtml += "<input id='qunit-urlconfig-" + escapeText( val.id ) +
+ "' name='" + escapeText( val.id ) +
+ "' type='checkbox'" +
+ ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
+ ( config[ val.id ] ? " checked='checked'" : "" ) +
+ " title='" + escapeText( val.tooltip ) +
+ "'><label for='qunit-urlconfig-" + escapeText( val.id ) +
+ "' title='" + escapeText( val.tooltip ) + "'>" + val.label + "</label>";
+ } else {
+ urlConfigHtml += "<label for='qunit-urlconfig-" + escapeText( val.id ) +
+ "' title='" + escapeText( val.tooltip ) +
+ "'>" + val.label +
+ ": </label><select id='qunit-urlconfig-" + escapeText( val.id ) +
+ "' name='" + escapeText( val.id ) +
+ "' title='" + escapeText( val.tooltip ) +
+ "'><option></option>";
+ selection = false;
+ if ( QUnit.is( "array", val.value ) ) {
+ for ( j = 0; j < val.value.length; j++ ) {
+ urlConfigHtml += "<option value='" + escapeText( val.value[j] ) + "'" +
+ ( config[ val.id ] === val.value[j] ?
+ (selection = true) && " selected='selected'" :
+ "" ) +
+ ">" + escapeText( val.value[j] ) + "</option>";
+ }
+ } else {
+ for ( j in val.value ) {
+ if ( hasOwn.call( val.value, j ) ) {
+ urlConfigHtml += "<option value='" + escapeText( j ) + "'" +
+ ( config[ val.id ] === j ?
+ (selection = true) && " selected='selected'" :
+ "" ) +
+ ">" + escapeText( val.value[j] ) + "</option>";
+ }
+ }
+ }
+ if ( config[ val.id ] && !selection ) {
+ urlConfigHtml += "<option value='" + escapeText( config[ val.id ] ) +
+ "' selected='selected' disabled='disabled'>" +
+ escapeText( config[ val.id ] ) +
+ "</option>";
+ }
+ urlConfigHtml += "</select>";
+ }
+ }
+ for ( i in config.modules ) {
+ if ( config.modules.hasOwnProperty( i ) ) {
+ moduleNames.push(i);
+ }
+ }
+ numModules = moduleNames.length;
+ moduleNames.sort( function( a, b ) {
+ return a.localeCompare( b );
+ });
+ moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label><select id='qunit-modulefilter' name='modulefilter'><option value='' " +
+ ( config.module === undefined ? "selected='selected'" : "" ) +
+ ">< All Modules ></option>";
+
+
+ for ( i = 0; i < numModules; i++) {
+ moduleFilterHtml += "<option value='" + escapeText( encodeURIComponent(moduleNames[i]) ) + "' " +
+ ( config.module === moduleNames[i] ? "selected='selected'" : "" ) +
+ ">" + escapeText(moduleNames[i]) + "</option>";
+ }
+ moduleFilterHtml += "</select>";
+
+ // `userAgent` initialized at top of scope
+ userAgent = id( "qunit-userAgent" );
+ if ( userAgent ) {
+ userAgent.innerHTML = navigator.userAgent;
+ }
+
+ // `banner` initialized at top of scope
+ banner = id( "qunit-header" );
+ if ( banner ) {
+ banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) + "'>" + banner.innerHTML + "</a> ";
+ }
+
+ // `toolbar` initialized at top of scope
+ toolbar = id( "qunit-testrunner-toolbar" );
+ if ( toolbar ) {
+ // `filter` initialized at top of scope
+ filter = document.createElement( "input" );
+ filter.type = "checkbox";
+ filter.id = "qunit-filter-pass";
+
+ addEvent( filter, "click", function() {
+ var tmp,
+ ol = id( "qunit-tests" );
+
+ if ( filter.checked ) {
+ ol.className = ol.className + " hidepass";
+ } else {
+ tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
+ ol.className = tmp.replace( / hidepass /, " " );
+ }
+ if ( defined.sessionStorage ) {
+ if (filter.checked) {
+ sessionStorage.setItem( "qunit-filter-passed-tests", "true" );
+ } else {
+ sessionStorage.removeItem( "qunit-filter-passed-tests" );
+ }
+ }
+ });
+
+ if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) {
+ filter.checked = true;
+ // `ol` initialized at top of scope
+ ol = id( "qunit-tests" );
+ ol.className = ol.className + " hidepass";
+ }
+ toolbar.appendChild( filter );
+
+ // `label` initialized at top of scope
+ label = document.createElement( "label" );
+ label.setAttribute( "for", "qunit-filter-pass" );
+ label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." );
+ label.innerHTML = "Hide passed tests";
+ toolbar.appendChild( label );
+
+ urlConfigContainer = document.createElement("span");
+ urlConfigContainer.innerHTML = urlConfigHtml;
+ // For oldIE support:
+ // * Add handlers to the individual elements instead of the container
+ // * Use "click" instead of "change" for checkboxes
+ // * Fallback from event.target to event.srcElement
+ addEvents( urlConfigContainer.getElementsByTagName("input"), "click", function( event ) {
+ var params = {},
+ target = event.target || event.srcElement;
+ params[ target.name ] = target.checked ?
+ target.defaultValue || true :
+ undefined;
+ window.location = QUnit.url( params );
+ });
+ addEvents( urlConfigContainer.getElementsByTagName("select"), "change", function( event ) {
+ var params = {},
+ target = event.target || event.srcElement;
+ params[ target.name ] = target.options[ target.selectedIndex ].value || undefined;
+ window.location = QUnit.url( params );
+ });
+ toolbar.appendChild( urlConfigContainer );
+
+ if (numModules > 1) {
+ moduleFilter = document.createElement( "span" );
+ moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
+ moduleFilter.innerHTML = moduleFilterHtml;
+ addEvent( moduleFilter.lastChild, "change", function() {
+ var selectBox = moduleFilter.getElementsByTagName("select")[0],
+ selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value);
+
+ window.location = QUnit.url({
+ module: ( selectedModule === "" ) ? undefined : selectedModule,
+ // Remove any existing filters
+ filter: undefined,
+ testNumber: undefined
+ });
+ });
+ toolbar.appendChild(moduleFilter);
+ }
+ }
+
+ // `main` initialized at top of scope
+ main = id( "qunit-fixture" );
+ if ( main ) {
+ config.fixture = main.innerHTML;
+ }
+
+ if ( config.autostart ) {
+ QUnit.start();
+ }
+};
+
+if ( defined.document ) {
+ addEvent( window, "load", QUnit.load );
+}
+
+// `onErrorFnPrev` initialized at top of scope
+// Preserve other handlers
+onErrorFnPrev = window.onerror;
+
+// Cover uncaught exceptions
+// Returning true will suppress the default browser handler,
+// returning false will let it run.
+window.onerror = function ( error, filePath, linerNr ) {
+ var ret = false;
+ if ( onErrorFnPrev ) {
+ ret = onErrorFnPrev( error, filePath, linerNr );
+ }
+
+ // Treat return value as window.onerror itself does,
+ // Only do our handling if not suppressed.
+ if ( ret !== true ) {
+ if ( QUnit.config.current ) {
+ if ( QUnit.config.current.ignoreGlobalErrors ) {
+ return true;
+ }
+ QUnit.pushFailure( error, filePath + ":" + linerNr );
+ } else {
+ QUnit.test( "global failure", extend( function() {
+ QUnit.pushFailure( error, filePath + ":" + linerNr );
+ }, { validTest: validTest } ) );
+ }
+ return false;
+ }
+
+ return ret;
+};
+
+function done() {
+ config.autorun = true;
+
+ // Log the last module results
+ if ( config.previousModule ) {
+ runLoggingCallbacks( "moduleDone", QUnit, {
+ name: config.previousModule,
+ failed: config.moduleStats.bad,
+ passed: config.moduleStats.all - config.moduleStats.bad,
+ total: config.moduleStats.all
+ });
+ }
+ delete config.previousModule;
+
+ var i, key,
+ banner = id( "qunit-banner" ),
+ tests = id( "qunit-tests" ),
+ runtime = +new Date() - config.started,
+ passed = config.stats.all - config.stats.bad,
+ html = [
+ "Tests completed in ",
+ runtime,
+ " milliseconds.<br/>",
+ "<span class='passed'>",
+ passed,
+ "</span> assertions of <span class='total'>",
+ config.stats.all,
+ "</span> passed, <span class='failed'>",
+ config.stats.bad,
+ "</span> failed."
+ ].join( "" );
+
+ if ( banner ) {
+ banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" );
+ }
+
+ if ( tests ) {
+ id( "qunit-testresult" ).innerHTML = html;
+ }
+
+ if ( config.altertitle && defined.document && document.title ) {
+ // show ✖ for good, ✔ for bad suite result in title
+ // use escape sequences in case file gets loaded with non-utf-8-charset
+ document.title = [
+ ( config.stats.bad ? "\u2716" : "\u2714" ),
+ document.title.replace( /^[\u2714\u2716] /i, "" )
+ ].join( " " );
+ }
+
+ // clear own sessionStorage items if all tests passed
+ if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) {
+ // `key` & `i` initialized at top of scope
+ for ( i = 0; i < sessionStorage.length; i++ ) {
+ key = sessionStorage.key( i++ );
+ if ( key.indexOf( "qunit-test-" ) === 0 ) {
+ sessionStorage.removeItem( key );
+ }
+ }
+ }
+
+ // scroll back to top to show results
+ if ( config.scrolltop && window.scrollTo ) {
+ window.scrollTo(0, 0);
+ }
+
+ runLoggingCallbacks( "done", QUnit, {
+ failed: config.stats.bad,
+ passed: passed,
+ total: config.stats.all,
+ runtime: runtime
+ });
+}
+
+/** @return Boolean: true if this test should be ran */
+function validTest( test ) {
+ var include,
+ filter = config.filter && config.filter.toLowerCase(),
+ module = config.module && config.module.toLowerCase(),
+ fullName = ( test.module + ": " + test.testName ).toLowerCase();
+
+ // Internally-generated tests are always valid
+ if ( test.callback && test.callback.validTest === validTest ) {
+ delete test.callback.validTest;
+ return true;
+ }
+
+ if ( config.testNumber.length > 0 ) {
+ if ( inArray( test.testNumber, config.testNumber ) < 0 ) {
+ return false;
+ }
+ }
+
+ if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) {
+ return false;
+ }
+
+ if ( !filter ) {
+ return true;
+ }
+
+ include = filter.charAt( 0 ) !== "!";
+ if ( !include ) {
+ filter = filter.slice( 1 );
+ }
+
+ // If the filter matches, we need to honour include
+ if ( fullName.indexOf( filter ) !== -1 ) {
+ return include;
+ }
+
+ // Otherwise, do the opposite
+ return !include;
+}
+
+// so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions)
+// Later Safari and IE10 are supposed to support error.stack as well
+// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
+function extractStacktrace( e, offset ) {
+ offset = offset === undefined ? 3 : offset;
+
+ var stack, include, i;
+
+ if ( e.stacktrace ) {
+ // Opera
+ return e.stacktrace.split( "\n" )[ offset + 3 ];
+ } else if ( e.stack ) {
+ // Firefox, Chrome
+ stack = e.stack.split( "\n" );
+ if (/^error$/i.test( stack[0] ) ) {
+ stack.shift();
+ }
+ if ( fileName ) {
+ include = [];
+ for ( i = offset; i < stack.length; i++ ) {
+ if ( stack[ i ].indexOf( fileName ) !== -1 ) {
+ break;
+ }
+ include.push( stack[ i ] );
+ }
+ if ( include.length ) {
+ return include.join( "\n" );
+ }
+ }
+ return stack[ offset ];
+ } else if ( e.sourceURL ) {
+ // Safari, PhantomJS
+ // hopefully one day Safari provides actual stacktraces
+ // exclude useless self-reference for generated Error objects
+ if ( /qunit.js$/.test( e.sourceURL ) ) {
+ return;
+ }
+ // for actual exceptions, this is useful
+ return e.sourceURL + ":" + e.line;
+ }
+}
+function sourceFromStacktrace( offset ) {
+ try {
+ throw new Error();
+ } catch ( e ) {
+ return extractStacktrace( e, offset );
+ }
+}
+
+/**
+ * Escape text for attribute or text content.
+ */
+function escapeText( s ) {
+ if ( !s ) {
+ return "";
+ }
+ s = s + "";
+ // Both single quotes and double quotes (for attributes)
+ return s.replace( /['"<>&]/g, function( s ) {
+ switch( s ) {
+ case "'":
+ return "&#039;";
+ case "\"":
+ return "&quot;";
+ case "<":
+ return "&lt;";
+ case ">":
+ return "&gt;";
+ case "&":
+ return "&amp;";
+ }
+ });
+}
+
+function synchronize( callback, last ) {
+ config.queue.push( callback );
+
+ if ( config.autorun && !config.blocking ) {
+ process( last );
+ }
+}
+
+function process( last ) {
+ function next() {
+ process( last );
+ }
+ var start = new Date().getTime();
+ config.depth = config.depth ? config.depth + 1 : 1;
+
+ while ( config.queue.length && !config.blocking ) {
+ if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) {
+ config.queue.shift()();
+ } else {
+ setTimeout( next, 13 );
+ break;
+ }
+ }
+ config.depth--;
+ if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
+ done();
+ }
+}
+
+function saveGlobal() {
+ config.pollution = [];
+
+ if ( config.noglobals ) {
+ for ( var key in window ) {
+ if ( hasOwn.call( window, key ) ) {
+ // in Opera sometimes DOM element ids show up here, ignore them
+ if ( /^qunit-test-output/.test( key ) ) {
+ continue;
+ }
+ config.pollution.push( key );
+ }
+ }
+ }
+}
+
+function checkPollution() {
+ var newGlobals,
+ deletedGlobals,
+ old = config.pollution;
+
+ saveGlobal();
+
+ newGlobals = diff( config.pollution, old );
+ if ( newGlobals.length > 0 ) {
+ QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") );
+ }
+
+ deletedGlobals = diff( old, config.pollution );
+ if ( deletedGlobals.length > 0 ) {
+ QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") );
+ }
+}
+
+// returns a new Array with the elements that are in a but not in b
+function diff( a, b ) {
+ var i, j,
+ result = a.slice();
+
+ for ( i = 0; i < result.length; i++ ) {
+ for ( j = 0; j < b.length; j++ ) {
+ if ( result[i] === b[j] ) {
+ result.splice( i, 1 );
+ i--;
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+function extend( a, b ) {
+ for ( var prop in b ) {
+ if ( hasOwn.call( b, prop ) ) {
+ // Avoid "Member not found" error in IE8 caused by messing with window.constructor
+ if ( !( prop === "constructor" && a === window ) ) {
+ if ( b[ prop ] === undefined ) {
+ delete a[ prop ];
+ } else {
+ a[ prop ] = b[ prop ];
+ }
+ }
+ }
+ }
+
+ return a;
+}
+
+/**
+ * @param {HTMLElement} elem
+ * @param {string} type
+ * @param {Function} fn
+ */
+function addEvent( elem, type, fn ) {
+ if ( elem.addEventListener ) {
+
+ // Standards-based browsers
+ elem.addEventListener( type, fn, false );
+ } else if ( elem.attachEvent ) {
+
+ // support: IE <9
+ elem.attachEvent( "on" + type, fn );
+ } else {
+
+ // Caller must ensure support for event listeners is present
+ throw new Error( "addEvent() was called in a context without event listener support" );
+ }
+}
+
+/**
+ * @param {Array|NodeList} elems
+ * @param {string} type
+ * @param {Function} fn
+ */
+function addEvents( elems, type, fn ) {
+ var i = elems.length;
+ while ( i-- ) {
+ addEvent( elems[i], type, fn );
+ }
+}
+
+function hasClass( elem, name ) {
+ return (" " + elem.className + " ").indexOf(" " + name + " ") > -1;
+}
+
+function addClass( elem, name ) {
+ if ( !hasClass( elem, name ) ) {
+ elem.className += (elem.className ? " " : "") + name;
+ }
+}
+
+function removeClass( elem, name ) {
+ var set = " " + elem.className + " ";
+ // Class name may appear multiple times
+ while ( set.indexOf(" " + name + " ") > -1 ) {
+ set = set.replace(" " + name + " " , " ");
+ }
+ // If possible, trim it for prettiness, but not necessarily
+ elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, "");
+}
+
+function id( name ) {
+ return defined.document && document.getElementById && document.getElementById( name );
+}
+
+function registerLoggingCallback( key ) {
+ return function( callback ) {
+ config[key].push( callback );
+ };
+}
+
+// Supports deprecated method of completely overwriting logging callbacks
+function runLoggingCallbacks( key, scope, args ) {
+ var i, callbacks;
+ if ( QUnit.hasOwnProperty( key ) ) {
+ QUnit[ key ].call(scope, args );
+ } else {
+ callbacks = config[ key ];
+ for ( i = 0; i < callbacks.length; i++ ) {
+ callbacks[ i ].call( scope, args );
+ }
+ }
+}
+
+// from jquery.js
+function inArray( elem, array ) {
+ if ( array.indexOf ) {
+ return array.indexOf( elem );
+ }
+
+ for ( var i = 0, length = array.length; i < length; i++ ) {
+ if ( array[ i ] === elem ) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+function Test( settings ) {
+ extend( this, settings );
+ this.assertions = [];
+ this.testNumber = ++Test.count;
+}
+
+Test.count = 0;
+
+Test.prototype = {
+ init: function() {
+ var a, b, li,
+ tests = id( "qunit-tests" );
+
+ if ( tests ) {
+ b = document.createElement( "strong" );
+ b.innerHTML = this.nameHtml;
+
+ // `a` initialized at top of scope
+ a = document.createElement( "a" );
+ a.innerHTML = "Rerun";
+ a.href = QUnit.url({ testNumber: this.testNumber });
+
+ li = document.createElement( "li" );
+ li.appendChild( b );
+ li.appendChild( a );
+ li.className = "running";
+ li.id = this.id = "qunit-test-output" + testId++;
+
+ tests.appendChild( li );
+ }
+ },
+ setup: function() {
+ if (
+ // Emit moduleStart when we're switching from one module to another
+ this.module !== config.previousModule ||
+ // They could be equal (both undefined) but if the previousModule property doesn't
+ // yet exist it means this is the first test in a suite that isn't wrapped in a
+ // module, in which case we'll just emit a moduleStart event for 'undefined'.
+ // Without this, reporters can get testStart before moduleStart which is a problem.
+ !hasOwn.call( config, "previousModule" )
+ ) {
+ if ( hasOwn.call( config, "previousModule" ) ) {
+ runLoggingCallbacks( "moduleDone", QUnit, {
+ name: config.previousModule,
+ failed: config.moduleStats.bad,
+ passed: config.moduleStats.all - config.moduleStats.bad,
+ total: config.moduleStats.all
+ });
+ }
+ config.previousModule = this.module;
+ config.moduleStats = { all: 0, bad: 0 };
+ runLoggingCallbacks( "moduleStart", QUnit, {
+ name: this.module
+ });
+ }
+
+ config.current = this;
+
+ this.testEnvironment = extend({
+ setup: function() {},
+ teardown: function() {}
+ }, this.moduleTestEnvironment );
+
+ this.started = +new Date();
+ runLoggingCallbacks( "testStart", QUnit, {
+ name: this.testName,
+ module: this.module
+ });
+
+ /*jshint camelcase:false */
+
+
+ /**
+ * Expose the current test environment.
+ *
+ * @deprecated since 1.12.0: Use QUnit.config.current.testEnvironment instead.
+ */
+ QUnit.current_testEnvironment = this.testEnvironment;
+
+ /*jshint camelcase:true */
+
+ if ( !config.pollution ) {
+ saveGlobal();
+ }
+ if ( config.notrycatch ) {
+ this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert );
+ return;
+ }
+ try {
+ this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert );
+ } catch( e ) {
+ QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
+ }
+ },
+ run: function() {
+ config.current = this;
+
+ var running = id( "qunit-testresult" );
+
+ if ( running ) {
+ running.innerHTML = "Running: <br/>" + this.nameHtml;
+ }
+
+ if ( this.async ) {
+ QUnit.stop();
+ }
+
+ this.callbackStarted = +new Date();
+
+ if ( config.notrycatch ) {
+ this.callback.call( this.testEnvironment, QUnit.assert );
+ this.callbackRuntime = +new Date() - this.callbackStarted;
+ return;
+ }
+
+ try {
+ this.callback.call( this.testEnvironment, QUnit.assert );
+ this.callbackRuntime = +new Date() - this.callbackStarted;
+ } catch( e ) {
+ this.callbackRuntime = +new Date() - this.callbackStarted;
+
+ QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
+ // else next test will carry the responsibility
+ saveGlobal();
+
+ // Restart the tests if they're blocking
+ if ( config.blocking ) {
+ QUnit.start();
+ }
+ }
+ },
+ teardown: function() {
+ config.current = this;
+ if ( config.notrycatch ) {
+ if ( typeof this.callbackRuntime === "undefined" ) {
+ this.callbackRuntime = +new Date() - this.callbackStarted;
+ }
+ this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert );
+ return;
+ } else {
+ try {
+ this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert );
+ } catch( e ) {
+ QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
+ }
+ }
+ checkPollution();
+ },
+ finish: function() {
+ config.current = this;
+ if ( config.requireExpects && this.expected === null ) {
+ QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack );
+ } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
+ QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack );
+ } else if ( this.expected === null && !this.assertions.length ) {
+ QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack );
+ }
+
+ var i, assertion, a, b, time, li, ol,
+ test = this,
+ good = 0,
+ bad = 0,
+ tests = id( "qunit-tests" );
+
+ this.runtime = +new Date() - this.started;
+ config.stats.all += this.assertions.length;
+ config.moduleStats.all += this.assertions.length;
+
+ if ( tests ) {
+ ol = document.createElement( "ol" );
+ ol.className = "qunit-assert-list";
+
+ for ( i = 0; i < this.assertions.length; i++ ) {
+ assertion = this.assertions[i];
+
+ li = document.createElement( "li" );
+ li.className = assertion.result ? "pass" : "fail";
+ li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" );
+ ol.appendChild( li );
+
+ if ( assertion.result ) {
+ good++;
+ } else {
+ bad++;
+ config.stats.bad++;
+ config.moduleStats.bad++;
+ }
+ }
+
+ // store result when possible
+ if ( QUnit.config.reorder && defined.sessionStorage ) {
+ if ( bad ) {
+ sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad );
+ } else {
+ sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName );
+ }
+ }
+
+ if ( bad === 0 ) {
+ addClass( ol, "qunit-collapsed" );
+ }
+
+ // `b` initialized at top of scope
+ b = document.createElement( "strong" );
+ b.innerHTML = this.nameHtml + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
+
+ addEvent(b, "click", function() {
+ var next = b.parentNode.lastChild,
+ collapsed = hasClass( next, "qunit-collapsed" );
+ ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" );
+ });
+
+ addEvent(b, "dblclick", function( e ) {
+ var target = e && e.target ? e.target : window.event.srcElement;
+ if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) {
+ target = target.parentNode;
+ }
+ if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
+ window.location = QUnit.url({ testNumber: test.testNumber });
+ }
+ });
+
+ // `time` initialized at top of scope
+ time = document.createElement( "span" );
+ time.className = "runtime";
+ time.innerHTML = this.runtime + " ms";
+
+ // `li` initialized at top of scope
+ li = id( this.id );
+ li.className = bad ? "fail" : "pass";
+ li.removeChild( li.firstChild );
+ a = li.firstChild;
+ li.appendChild( b );
+ li.appendChild( a );
+ li.appendChild( time );
+ li.appendChild( ol );
+
+ } else {
+ for ( i = 0; i < this.assertions.length; i++ ) {
+ if ( !this.assertions[i].result ) {
+ bad++;
+ config.stats.bad++;
+ config.moduleStats.bad++;
+ }
+ }
+ }
+
+ runLoggingCallbacks( "testDone", QUnit, {
+ name: this.testName,
+ module: this.module,
+ failed: bad,
+ passed: this.assertions.length - bad,
+ total: this.assertions.length,
+ runtime: this.runtime,
+ // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
+ duration: this.runtime
+ });
+
+ QUnit.reset();
+
+ config.current = undefined;
+ },
+
+ queue: function() {
+ var bad,
+ test = this;
+
+ synchronize(function() {
+ test.init();
+ });
+ function run() {
+ // each of these can by async
+ synchronize(function() {
+ test.setup();
+ });
+ synchronize(function() {
+ test.run();
+ });
+ synchronize(function() {
+ test.teardown();
+ });
+ synchronize(function() {
+ test.finish();
+ });
+ }
+
+ // `bad` initialized at top of scope
+ // defer when previous test run passed, if storage is available
+ bad = QUnit.config.reorder && defined.sessionStorage &&
+ +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName );
+
+ if ( bad ) {
+ run();
+ } else {
+ synchronize( run, true );
+ }
+ }
+};
+
+// `assert` initialized at top of scope
+// Assert helpers
+// All of these must either call QUnit.push() or manually do:
+// - runLoggingCallbacks( "log", .. );
+// - config.current.assertions.push({ .. });
+assert = QUnit.assert = {
+ /**
+ * Asserts rough true-ish result.
+ * @name ok
+ * @function
+ * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
+ */
+ ok: function( result, msg ) {
+ if ( !config.current ) {
+ throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) );
+ }
+ result = !!result;
+ msg = msg || ( result ? "okay" : "failed" );
+
+ var source,
+ details = {
+ module: config.current.module,
+ name: config.current.testName,
+ result: result,
+ message: msg
+ };
+
+ msg = "<span class='test-message'>" + escapeText( msg ) + "</span>";
+
+ if ( !result ) {
+ source = sourceFromStacktrace( 2 );
+ if ( source ) {
+ details.source = source;
+ msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" +
+ escapeText( source ) +
+ "</pre></td></tr></table>";
+ }
+ }
+ runLoggingCallbacks( "log", QUnit, details );
+ config.current.assertions.push({
+ result: result,
+ message: msg
+ });
+ },
+
+ /**
+ * Assert that the first two arguments are equal, with an optional message.
+ * Prints out both actual and expected values.
+ * @name equal
+ * @function
+ * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
+ */
+ equal: function( actual, expected, message ) {
+ /*jshint eqeqeq:false */
+ QUnit.push( expected == actual, actual, expected, message );
+ },
+
+ /**
+ * @name notEqual
+ * @function
+ */
+ notEqual: function( actual, expected, message ) {
+ /*jshint eqeqeq:false */
+ QUnit.push( expected != actual, actual, expected, message );
+ },
+
+ /**
+ * @name propEqual
+ * @function
+ */
+ propEqual: function( actual, expected, message ) {
+ actual = objectValues(actual);
+ expected = objectValues(expected);
+ QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
+ },
+
+ /**
+ * @name notPropEqual
+ * @function
+ */
+ notPropEqual: function( actual, expected, message ) {
+ actual = objectValues(actual);
+ expected = objectValues(expected);
+ QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
+ },
+
+ /**
+ * @name deepEqual
+ * @function
+ */
+ deepEqual: function( actual, expected, message ) {
+ QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
+ },
+
+ /**
+ * @name notDeepEqual
+ * @function
+ */
+ notDeepEqual: function( actual, expected, message ) {
+ QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
+ },
+
+ /**
+ * @name strictEqual
+ * @function
+ */
+ strictEqual: function( actual, expected, message ) {
+ QUnit.push( expected === actual, actual, expected, message );
+ },
+
+ /**
+ * @name notStrictEqual
+ * @function
+ */
+ notStrictEqual: function( actual, expected, message ) {
+ QUnit.push( expected !== actual, actual, expected, message );
+ },
+
+ "throws": function( block, expected, message ) {
+ var actual,
+ expectedOutput = expected,
+ ok = false;
+
+ // 'expected' is optional
+ if ( !message && typeof expected === "string" ) {
+ message = expected;
+ expected = null;
+ }
+
+ config.current.ignoreGlobalErrors = true;
+ try {
+ block.call( config.current.testEnvironment );
+ } catch (e) {
+ actual = e;
+ }
+ config.current.ignoreGlobalErrors = false;
+
+ if ( actual ) {
+
+ // we don't want to validate thrown error
+ if ( !expected ) {
+ ok = true;
+ expectedOutput = null;
+
+ // expected is an Error object
+ } else if ( expected instanceof Error ) {
+ ok = actual instanceof Error &&
+ actual.name === expected.name &&
+ actual.message === expected.message;
+
+ // expected is a regexp
+ } else if ( QUnit.objectType( expected ) === "regexp" ) {
+ ok = expected.test( errorString( actual ) );
+
+ // expected is a string
+ } else if ( QUnit.objectType( expected ) === "string" ) {
+ ok = expected === errorString( actual );
+
+ // expected is a constructor
+ } else if ( actual instanceof expected ) {
+ ok = true;
+
+ // expected is a validation function which returns true is validation passed
+ } else if ( expected.call( {}, actual ) === true ) {
+ expectedOutput = null;
+ ok = true;
+ }
+
+ QUnit.push( ok, actual, expectedOutput, message );
+ } else {
+ QUnit.pushFailure( message, null, "No exception was thrown." );
+ }
+ }
+};
+
+/**
+ * @deprecated since 1.8.0
+ * Kept assertion helpers in root for backwards compatibility.
+ */
+extend( QUnit.constructor.prototype, assert );
+
+/**
+ * @deprecated since 1.9.0
+ * Kept to avoid TypeErrors for undefined methods.
+ */
+QUnit.constructor.prototype.raises = function() {
+ QUnit.push( false, false, false, "QUnit.raises has been deprecated since 2012 (fad3c1ea), use QUnit.throws instead" );
+};
+
+/**
+ * @deprecated since 1.0.0, replaced with error pushes since 1.3.0
+ * Kept to avoid TypeErrors for undefined methods.
+ */
+QUnit.constructor.prototype.equals = function() {
+ QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" );
+};
+QUnit.constructor.prototype.same = function() {
+ QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" );
+};
+
+// Test for equality any JavaScript type.
+// Author: Philippe Rathé <prathe@gmail.com>
+QUnit.equiv = (function() {
+
+ // Call the o related callback with the given arguments.
+ function bindCallbacks( o, callbacks, args ) {
+ var prop = QUnit.objectType( o );
+ if ( prop ) {
+ if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
+ return callbacks[ prop ].apply( callbacks, args );
+ } else {
+ return callbacks[ prop ]; // or undefined
+ }
+ }
+ }
+
+ // the real equiv function
+ var innerEquiv,
+ // stack to decide between skip/abort functions
+ callers = [],
+ // stack to avoiding loops from circular referencing
+ parents = [],
+ parentsB = [],
+
+ getProto = Object.getPrototypeOf || function ( obj ) {
+ /*jshint camelcase:false */
+ return obj.__proto__;
+ },
+ callbacks = (function () {
+
+ // for string, boolean, number and null
+ function useStrictEquality( b, a ) {
+ /*jshint eqeqeq:false */
+ if ( b instanceof a.constructor || a instanceof b.constructor ) {
+ // to catch short annotation VS 'new' annotation of a
+ // declaration
+ // e.g. var i = 1;
+ // var j = new Number(1);
+ return a == b;
+ } else {
+ return a === b;
+ }
+ }
+
+ return {
+ "string": useStrictEquality,
+ "boolean": useStrictEquality,
+ "number": useStrictEquality,
+ "null": useStrictEquality,
+ "undefined": useStrictEquality,
+
+ "nan": function( b ) {
+ return isNaN( b );
+ },
+
+ "date": function( b, a ) {
+ return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
+ },
+
+ "regexp": function( b, a ) {
+ return QUnit.objectType( b ) === "regexp" &&
+ // the regex itself
+ a.source === b.source &&
+ // and its modifiers
+ a.global === b.global &&
+ // (gmi) ...
+ a.ignoreCase === b.ignoreCase &&
+ a.multiline === b.multiline &&
+ a.sticky === b.sticky;
+ },
+
+ // - skip when the property is a method of an instance (OOP)
+ // - abort otherwise,
+ // initial === would have catch identical references anyway
+ "function": function() {
+ var caller = callers[callers.length - 1];
+ return caller !== Object && typeof caller !== "undefined";
+ },
+
+ "array": function( b, a ) {
+ var i, j, len, loop, aCircular, bCircular;
+
+ // b could be an object literal here
+ if ( QUnit.objectType( b ) !== "array" ) {
+ return false;
+ }
+
+ len = a.length;
+ if ( len !== b.length ) {
+ // safe and faster
+ return false;
+ }
+
+ // track reference to avoid circular references
+ parents.push( a );
+ parentsB.push( b );
+ for ( i = 0; i < len; i++ ) {
+ loop = false;
+ for ( j = 0; j < parents.length; j++ ) {
+ aCircular = parents[j] === a[i];
+ bCircular = parentsB[j] === b[i];
+ if ( aCircular || bCircular ) {
+ if ( a[i] === b[i] || aCircular && bCircular ) {
+ loop = true;
+ } else {
+ parents.pop();
+ parentsB.pop();
+ return false;
+ }
+ }
+ }
+ if ( !loop && !innerEquiv(a[i], b[i]) ) {
+ parents.pop();
+ parentsB.pop();
+ return false;
+ }
+ }
+ parents.pop();
+ parentsB.pop();
+ return true;
+ },
+
+ "object": function( b, a ) {
+ /*jshint forin:false */
+ var i, j, loop, aCircular, bCircular,
+ // Default to true
+ eq = true,
+ aProperties = [],
+ bProperties = [];
+
+ // comparing constructors is more strict than using
+ // instanceof
+ if ( a.constructor !== b.constructor ) {
+ // Allow objects with no prototype to be equivalent to
+ // objects with Object as their constructor.
+ if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) ||
+ ( getProto(b) === null && getProto(a) === Object.prototype ) ) ) {
+ return false;
+ }
+ }
+
+ // stack constructor before traversing properties
+ callers.push( a.constructor );
+
+ // track reference to avoid circular references
+ parents.push( a );
+ parentsB.push( b );
+
+ // be strict: don't ensure hasOwnProperty and go deep
+ for ( i in a ) {
+ loop = false;
+ for ( j = 0; j < parents.length; j++ ) {
+ aCircular = parents[j] === a[i];
+ bCircular = parentsB[j] === b[i];
+ if ( aCircular || bCircular ) {
+ if ( a[i] === b[i] || aCircular && bCircular ) {
+ loop = true;
+ } else {
+ eq = false;
+ break;
+ }
+ }
+ }
+ aProperties.push(i);
+ if ( !loop && !innerEquiv(a[i], b[i]) ) {
+ eq = false;
+ break;
+ }
+ }
+
+ parents.pop();
+ parentsB.pop();
+ callers.pop(); // unstack, we are done
+
+ for ( i in b ) {
+ bProperties.push( i ); // collect b's properties
+ }
+
+ // Ensures identical properties name
+ return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
+ }
+ };
+ }());
+
+ innerEquiv = function() { // can take multiple arguments
+ var args = [].slice.apply( arguments );
+ if ( args.length < 2 ) {
+ return true; // end transition
+ }
+
+ return (function( a, b ) {
+ if ( a === b ) {
+ return true; // catch the most you can
+ } else if ( a === null || b === null || typeof a === "undefined" ||
+ typeof b === "undefined" ||
+ QUnit.objectType(a) !== QUnit.objectType(b) ) {
+ return false; // don't lose time with error prone cases
+ } else {
+ return bindCallbacks(a, callbacks, [ b, a ]);
+ }
+
+ // apply transition with (1..n) arguments
+ }( args[0], args[1] ) && innerEquiv.apply( this, args.splice(1, args.length - 1 )) );
+ };
+
+ return innerEquiv;
+}());
+
+/**
+ * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com |
+ * http://flesler.blogspot.com Licensed under BSD
+ * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008
+ *
+ * @projectDescription Advanced and extensible data dumping for Javascript.
+ * @version 1.0.0
+ * @author Ariel Flesler
+ * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
+ */
+QUnit.jsDump = (function() {
+ function quote( str ) {
+ return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\"";
+ }
+ function literal( o ) {
+ return o + "";
+ }
+ function join( pre, arr, post ) {
+ var s = jsDump.separator(),
+ base = jsDump.indent(),
+ inner = jsDump.indent(1);
+ if ( arr.join ) {
+ arr = arr.join( "," + s + inner );
+ }
+ if ( !arr ) {
+ return pre + post;
+ }
+ return [ pre, inner + arr, base + post ].join(s);
+ }
+ function array( arr, stack ) {
+ var i = arr.length, ret = new Array(i);
+ this.up();
+ while ( i-- ) {
+ ret[i] = this.parse( arr[i] , undefined , stack);
+ }
+ this.down();
+ return join( "[", ret, "]" );
+ }
+
+ var reName = /^function (\w+)/,
+ jsDump = {
+ // type is used mostly internally, you can fix a (custom)type in advance
+ parse: function( obj, type, stack ) {
+ stack = stack || [ ];
+ var inStack, res,
+ parser = this.parsers[ type || this.typeOf(obj) ];
+
+ type = typeof parser;
+ inStack = inArray( obj, stack );
+
+ if ( inStack !== -1 ) {
+ return "recursion(" + (inStack - stack.length) + ")";
+ }
+ if ( type === "function" ) {
+ stack.push( obj );
+ res = parser.call( this, obj, stack );
+ stack.pop();
+ return res;
+ }
+ return ( type === "string" ) ? parser : this.parsers.error;
+ },
+ typeOf: function( obj ) {
+ var type;
+ if ( obj === null ) {
+ type = "null";
+ } else if ( typeof obj === "undefined" ) {
+ type = "undefined";
+ } else if ( QUnit.is( "regexp", obj) ) {
+ type = "regexp";
+ } else if ( QUnit.is( "date", obj) ) {
+ type = "date";
+ } else if ( QUnit.is( "function", obj) ) {
+ type = "function";
+ } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) {
+ type = "window";
+ } else if ( obj.nodeType === 9 ) {
+ type = "document";
+ } else if ( obj.nodeType ) {
+ type = "node";
+ } else if (
+ // native arrays
+ toString.call( obj ) === "[object Array]" ||
+ // NodeList objects
+ ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) )
+ ) {
+ type = "array";
+ } else if ( obj.constructor === Error.prototype.constructor ) {
+ type = "error";
+ } else {
+ type = typeof obj;
+ }
+ return type;
+ },
+ separator: function() {
+ return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&nbsp;" : " ";
+ },
+ // extra can be a number, shortcut for increasing-calling-decreasing
+ indent: function( extra ) {
+ if ( !this.multiline ) {
+ return "";
+ }
+ var chr = this.indentChar;
+ if ( this.HTML ) {
+ chr = chr.replace( /\t/g, " " ).replace( / /g, "&nbsp;" );
+ }
+ return new Array( this.depth + ( extra || 0 ) ).join(chr);
+ },
+ up: function( a ) {
+ this.depth += a || 1;
+ },
+ down: function( a ) {
+ this.depth -= a || 1;
+ },
+ setParser: function( name, parser ) {
+ this.parsers[name] = parser;
+ },
+ // The next 3 are exposed so you can use them
+ quote: quote,
+ literal: literal,
+ join: join,
+ //
+ depth: 1,
+ // This is the list of parsers, to modify them, use jsDump.setParser
+ parsers: {
+ window: "[Window]",
+ document: "[Document]",
+ error: function(error) {
+ return "Error(\"" + error.message + "\")";
+ },
+ unknown: "[Unknown]",
+ "null": "null",
+ "undefined": "undefined",
+ "function": function( fn ) {
+ var ret = "function",
+ // functions never have name in IE
+ name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];
+
+ if ( name ) {
+ ret += " " + name;
+ }
+ ret += "( ";
+
+ ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" );
+ return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" );
+ },
+ array: array,
+ nodelist: array,
+ "arguments": array,
+ object: function( map, stack ) {
+ /*jshint forin:false */
+ var ret = [ ], keys, key, val, i;
+ QUnit.jsDump.up();
+ keys = [];
+ for ( key in map ) {
+ keys.push( key );
+ }
+ keys.sort();
+ for ( i = 0; i < keys.length; i++ ) {
+ key = keys[ i ];
+ val = map[ key ];
+ ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) );
+ }
+ QUnit.jsDump.down();
+ return join( "{", ret, "}" );
+ },
+ node: function( node ) {
+ var len, i, val,
+ open = QUnit.jsDump.HTML ? "&lt;" : "<",
+ close = QUnit.jsDump.HTML ? "&gt;" : ">",
+ tag = node.nodeName.toLowerCase(),
+ ret = open + tag,
+ attrs = node.attributes;
+
+ if ( attrs ) {
+ for ( i = 0, len = attrs.length; i < len; i++ ) {
+ val = attrs[i].nodeValue;
+ // IE6 includes all attributes in .attributes, even ones not explicitly set.
+ // Those have values like undefined, null, 0, false, "" or "inherit".
+ if ( val && val !== "inherit" ) {
+ ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" );
+ }
+ }
+ }
+ ret += close;
+
+ // Show content of TextNode or CDATASection
+ if ( node.nodeType === 3 || node.nodeType === 4 ) {
+ ret += node.nodeValue;
+ }
+
+ return ret + open + "/" + tag + close;
+ },
+ // function calls it internally, it's the arguments part of the function
+ functionArgs: function( fn ) {
+ var args,
+ l = fn.length;
+
+ if ( !l ) {
+ return "";
+ }
+
+ args = new Array(l);
+ while ( l-- ) {
+ // 97 is 'a'
+ args[l] = String.fromCharCode(97+l);
+ }
+ return " " + args.join( ", " ) + " ";
+ },
+ // object calls it internally, the key part of an item in a map
+ key: quote,
+ // function calls it internally, it's the content of the function
+ functionCode: "[code]",
+ // node calls it internally, it's an html attribute value
+ attribute: quote,
+ string: quote,
+ date: quote,
+ regexp: literal,
+ number: literal,
+ "boolean": literal
+ },
+ // if true, entities are escaped ( <, >, \t, space and \n )
+ HTML: false,
+ // indentation unit
+ indentChar: " ",
+ // if true, items in a collection, are separated by a \n, else just a space.
+ multiline: true
+ };
+
+ return jsDump;
+}());
+
+/*
+ * Javascript Diff Algorithm
+ * By John Resig (http://ejohn.org/)
+ * Modified by Chu Alan "sprite"
+ *
+ * Released under the MIT license.
+ *
+ * More Info:
+ * http://ejohn.org/projects/javascript-diff-algorithm/
+ *
+ * Usage: QUnit.diff(expected, actual)
+ *
+ * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over"
+ */
+QUnit.diff = (function() {
+ /*jshint eqeqeq:false, eqnull:true */
+ function diff( o, n ) {
+ var i,
+ ns = {},
+ os = {};
+
+ for ( i = 0; i < n.length; i++ ) {
+ if ( !hasOwn.call( ns, n[i] ) ) {
+ ns[ n[i] ] = {
+ rows: [],
+ o: null
+ };
+ }
+ ns[ n[i] ].rows.push( i );
+ }
+
+ for ( i = 0; i < o.length; i++ ) {
+ if ( !hasOwn.call( os, o[i] ) ) {
+ os[ o[i] ] = {
+ rows: [],
+ n: null
+ };
+ }
+ os[ o[i] ].rows.push( i );
+ }
+
+ for ( i in ns ) {
+ if ( hasOwn.call( ns, i ) ) {
+ if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) {
+ n[ ns[i].rows[0] ] = {
+ text: n[ ns[i].rows[0] ],
+ row: os[i].rows[0]
+ };
+ o[ os[i].rows[0] ] = {
+ text: o[ os[i].rows[0] ],
+ row: ns[i].rows[0]
+ };
+ }
+ }
+ }
+
+ for ( i = 0; i < n.length - 1; i++ ) {
+ if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null &&
+ n[ i + 1 ] == o[ n[i].row + 1 ] ) {
+
+ n[ i + 1 ] = {
+ text: n[ i + 1 ],
+ row: n[i].row + 1
+ };
+ o[ n[i].row + 1 ] = {
+ text: o[ n[i].row + 1 ],
+ row: i + 1
+ };
+ }
+ }
+
+ for ( i = n.length - 1; i > 0; i-- ) {
+ if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null &&
+ n[ i - 1 ] == o[ n[i].row - 1 ]) {
+
+ n[ i - 1 ] = {
+ text: n[ i - 1 ],
+ row: n[i].row - 1
+ };
+ o[ n[i].row - 1 ] = {
+ text: o[ n[i].row - 1 ],
+ row: i - 1
+ };
+ }
+ }
+
+ return {
+ o: o,
+ n: n
+ };
+ }
+
+ return function( o, n ) {
+ o = o.replace( /\s+$/, "" );
+ n = n.replace( /\s+$/, "" );
+
+ var i, pre,
+ str = "",
+ out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ),
+ oSpace = o.match(/\s+/g),
+ nSpace = n.match(/\s+/g);
+
+ if ( oSpace == null ) {
+ oSpace = [ " " ];
+ }
+ else {
+ oSpace.push( " " );
+ }
+
+ if ( nSpace == null ) {
+ nSpace = [ " " ];
+ }
+ else {
+ nSpace.push( " " );
+ }
+
+ if ( out.n.length === 0 ) {
+ for ( i = 0; i < out.o.length; i++ ) {
+ str += "<del>" + out.o[i] + oSpace[i] + "</del>";
+ }
+ }
+ else {
+ if ( out.n[0].text == null ) {
+ for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) {
+ str += "<del>" + out.o[n] + oSpace[n] + "</del>";
+ }
+ }
+
+ for ( i = 0; i < out.n.length; i++ ) {
+ if (out.n[i].text == null) {
+ str += "<ins>" + out.n[i] + nSpace[i] + "</ins>";
+ }
+ else {
+ // `pre` initialized at top of scope
+ pre = "";
+
+ for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) {
+ pre += "<del>" + out.o[n] + oSpace[n] + "</del>";
+ }
+ str += " " + out.n[i].text + nSpace[i] + pre;
+ }
+ }
+ }
+
+ return str;
+ };
+}());
+
+// For browser, export only select globals
+if ( typeof window !== "undefined" ) {
+ extend( window, QUnit.constructor.prototype );
+ window.QUnit = QUnit;
+}
+
+// For CommonJS environments, export everything
+if ( typeof module !== "undefined" && module.exports ) {
+ module.exports = QUnit;
+}
+
+
+// Get a reference to the global object, like window in browsers
+}( (function() {
+ return this;
+})() ));
diff --git a/actionview/test/ujs/server.rb b/actionview/test/ujs/server.rb
new file mode 100644
index 0000000000..25f70baf5f
--- /dev/null
+++ b/actionview/test/ujs/server.rb
@@ -0,0 +1,105 @@
+require "rails"
+require "action_controller/railtie"
+require "action_view/railtie"
+require "blade"
+require "json"
+
+JQUERY_VERSIONS = %w[ 1.8.0 1.8.1 1.8.2 1.8.3 1.9.0 1.9.1 1.10.0 1.10.1 1.10.2 1.11.0 2.0.0 2.1.0].freeze
+
+module UJS
+ class Server < Rails::Application
+ routes.append do
+ get "/rails-ujs.js" => Blade::Assets.environment
+ get "/" => "tests#index"
+ match "/echo" => "tests#echo", via: :all
+ get "/error" => proc { |env| [403, {}, []] }
+ end
+
+ config.cache_classes = false
+ config.eager_load = false
+ config.secret_key_base = "59d7a4dbd349fa3838d79e330e39690fc22b931e7dc17d9162f03d633d526fbb92dfdb2dc9804c8be3e199631b9c1fbe43fc3e4fc75730b515851849c728d5c7"
+ config.paths["app/views"].unshift("#{Rails.root / "views"}")
+ config.public_file_server.enabled = true
+ config.logger = Logger.new(STDOUT)
+ config.log_level = :error
+ end
+end
+
+module TestsHelper
+ def jquery_link(version)
+ if params[:version] == version
+ "[#{version}]"
+ else
+ "<a href='/?version=#{version}&cdn=#{params[:cdn]}'>#{version}</a>".html_safe
+ end
+ end
+
+ def cdn_link(cdn)
+ if params[:cdn] == cdn
+ "[#{cdn}]"
+ else
+ "<a href='/?version=#{params[:version]}&cdn=#{cdn}'>#{cdn}</a>".html_safe
+ end
+ end
+
+ def jquery_src
+ if params[:version] == "edge"
+ "/vendor/jquery.js"
+ elsif params[:cdn] && params[:cdn] == "googleapis"
+ "https://ajax.googleapis.com/ajax/libs/jquery/#{params[:version]}/jquery.min.js"
+ else
+ "http://code.jquery.com/jquery-#{params[:version]}.js"
+ end
+ end
+
+ def test_to(*names)
+ names = ["/vendor/qunit.js", "settings"] + names
+ names.map { |name| script_tag name }.join("\n").html_safe
+ end
+
+ def script_tag(src)
+ src = "/test/#{src}.js" unless src.index("/")
+ %(<script src="#{src}" type="text/javascript"></script>).html_safe
+ end
+
+ def jquery_versions
+ JQUERY_VERSIONS
+ end
+end
+
+class TestsController < ActionController::Base
+ helper TestsHelper
+ layout "application"
+
+ def index
+ params[:version] ||= ENV["JQUERY_VERSION"] || "1.11.0"
+ params[:cdn] ||= "jquery"
+ render :index
+ end
+
+ def echo
+ data = { params: params.to_unsafe_h }.update(request.env)
+
+ if params[:content_type] && params[:content]
+ render inline: params[:content], content_type: params[:content_type]
+ elsif request.xhr?
+ render json: JSON.generate(data)
+ elsif params[:iframe]
+ payload = JSON.generate(data).gsub("<", "&lt;").gsub(">", "&gt;")
+ html = <<-HTML
+ <script>
+ if (window.top && window.top !== window)
+ window.top.jQuery.event.trigger('iframe:loaded', #{payload})
+ </script>
+ <p>You shouldn't be seeing this. <a href="#{request.env['HTTP_REFERER']}">Go back</a></p>
+ HTML
+
+ render html: html.html_safe
+ else
+ render plain: "ERROR: #{request.path} requested without ajax", status: 404
+ end
+ end
+end
+
+Blade.initialize!
+UJS::Server.initialize!
diff --git a/actionview/test/ujs/views/layouts/application.html.erb b/actionview/test/ujs/views/layouts/application.html.erb
new file mode 100644
index 0000000000..74fa3bd06d
--- /dev/null
+++ b/actionview/test/ujs/views/layouts/application.html.erb
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html id="html">
+ <head>
+ <title><%= @title %></title>
+ <link href="/vendor/qunit.css" media="screen" rel="stylesheet" type="text/css" media="screen, projection" />
+ <style>
+ #jquery-cdn, #jquery-version {
+ padding: 0 2em .8em 0;
+ text-align: right;
+ font-family: sans-serif;
+ line-height: 1;
+ color: #8699A4;
+ background-color: #0d3349;
+ }
+ #jquery-cdn a, #jquery-version a {
+ color: white;
+ text-decoration: underline;
+ }
+ </style>
+
+ <%= script_tag jquery_src %>
+ <script>
+ // This is for test in override.js.
+ // Must go after jQuery is loaded, but before jquery-ujs.
+ $(document).bind('rails:attachBindings', function() {
+ $.rails.linkClickSelector += ', a[data-custom-remote-link]';
+ // Hijacks link click before ujs binds any handlers
+ // This is only used for ctrl-clicking test on remote links
+ $.rails.delegate(document, '#qunit-fixture a', 'click', function(e) {
+ e.preventDefault();
+ });
+ });
+ </script>
+ <%= script_tag "/rails-ujs.js" %>
+ </head>
+
+ <body id="body">
+ <%= yield %>
+ </body>
+</html>
diff --git a/actionview/test/ujs/views/tests/index.html.erb b/actionview/test/ujs/views/tests/index.html.erb
new file mode 100644
index 0000000000..393a5ee235
--- /dev/null
+++ b/actionview/test/ujs/views/tests/index.html.erb
@@ -0,0 +1,25 @@
+<% @title = "jquery-ujs test" %>
+
+<%= test_to 'data-confirm', 'data-remote', 'data-disable', 'data-disable-with', 'call-remote', 'call-remote-callbacks', 'data-method', 'override', 'csrf-refresh', 'csrf-token' %>
+
+<h1 id="qunit-header"><%= @title %></h1>
+<div id="jquery-cdn">
+ CDN:
+ <%= cdn_link 'jquery' %> •
+ <%= cdn_link 'googleapis' %>
+</div>
+<div id="jquery-version">
+ jQuery version:
+
+ <% jquery_versions.each do |v| %>
+ <%= ' • ' if v != jquery_versions.first %>
+ <%= jquery_link v %>
+ <% end %>
+ <%= (' • ' + jquery_link('edge')) if File.exist?(Rails.root + '/public/vendor/jquery.js') %>
+</div>
+<h2 id="qunit-banner"></h2>
+<div id="qunit-testrunner-toolbar"></div>
+<h2 id="qunit-userAgent"></h2>
+<ol id="qunit-tests"></ol>
+
+<div id="qunit-fixture"></div>
diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md
index 12f9e1cc17..5e8d8cb5c9 100644
--- a/activejob/CHANGELOG.md
+++ b/activejob/CHANGELOG.md
@@ -1,23 +1,41 @@
-## Rails 5.1.0.alpha ##
+* Removed deprecated support to passing the adapter class to `.queue_adapter`.
-* Added declarative exception handling via `ActiveJob::Base.retry_on` and `ActiveJob::Base.discard_on`.
+ *Rafael Mendonça França*
+
+* Removed deprecated `#original_exception` in `ActiveJob::DeserializationError`.
+
+ *Rafael Mendonça França*
+
+* Added instance variable `@queue` to JobWrapper.
+
+ This will fix issues in [resque-scheduler](https://github.com/resque/resque-scheduler) `#job_to_hash` method,
+ so we can use `#enqueue_delayed_selection`, `#remove_delayed` method in resque-scheduler smoothly.
+
+ *mu29*
+
+* Yield the job instance so you have access to things like `job.arguments` on the custom logic after retries fail.
+
+ *DHH*
+
+* Added declarative exception handling via `ActiveJob::Base.retry_on` and `ActiveJob::Base.discard_on`.
Examples:
class RemoteServiceJob < ActiveJob::Base
retry_on CustomAppException # defaults to 3s wait, 5 attempts
retry_on AnotherCustomAppException, wait: ->(executions) { executions * 2 }
- retry_on ActiveRecord::StatementInvalid, wait: 5.seconds, attempts: 3
+ retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3
retry_on Net::OpenTimeout, wait: :exponentially_longer, attempts: 10
discard_on ActiveJob::DeserializationError
def perform(*args)
# Might raise CustomAppException or AnotherCustomAppException for something domain specific
- # Might raise ActiveRecord::StatementInvalid when a local db deadlock is detected
+ # Might raise ActiveRecord::Deadlocked when a local db deadlock is detected
# Might raise Net::OpenTimeout when the remote service is down
end
end
*DHH*
+
Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/activejob/CHANGELOG.md) for previous changes.
diff --git a/activejob/MIT-LICENSE b/activejob/MIT-LICENSE
index a3ffb46b3f..daa726b9f0 100644
--- a/activejob/MIT-LICENSE
+++ b/activejob/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2014-2016 David Heinemeier Hansson
+Copyright (c) 2014-2017 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activejob/Rakefile b/activejob/Rakefile
index da153c8a49..41ff76135e 100644
--- a/activejob/Rakefile
+++ b/activejob/Rakefile
@@ -1,8 +1,9 @@
require "rake/testtask"
#TODO: add qu back to the list after it support Rails 5.1
-ACTIVEJOB_ADAPTERS = %w(async inline delayed_job que queue_classic resque sidekiq sneakers sucker_punch backburner test)
-ACTIVEJOB_ADAPTERS -= %w(queue_classic) if defined?(JRUBY_VERSION)
+#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.delete("queue_classic") if defined?(JRUBY_VERSION)
task default: :test
task test: "test:default"
@@ -46,7 +47,7 @@ namespace :test 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)
- end or raise "Failures"
+ end || raise("Failures")
end
end
diff --git a/activejob/lib/active_job.rb b/activejob/lib/active_job.rb
index 20ca7085c6..8b7aef65a2 100644
--- a/activejob/lib/active_job.rb
+++ b/activejob/lib/active_job.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2014-2016 David Heinemeier Hansson
+# Copyright (c) 2014-2017 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb
index d94b015014..523a0e7f33 100644
--- a/activejob/lib/active_job/arguments.rb
+++ b/activejob/lib/active_job/arguments.rb
@@ -5,22 +5,10 @@ module ActiveJob
#
# Wraps the original exception raised as +cause+.
class DeserializationError < StandardError
- def initialize(e = nil) #:nodoc:
- if e
- ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \
- "Exceptions will automatically capture the original exception.", caller)
- end
-
+ def initialize #:nodoc:
super("Error while trying to deserialize arguments: #{$!.message}")
set_backtrace $!.backtrace
end
-
- # The original exception that was raised during deserialization of job
- # arguments.
- def original_exception
- ActiveSupport::Deprecation.warn("#original_exception is deprecated. Use #cause instead.", caller)
- cause
- end
end
# Raised when an unsupported argument type is set as a job argument. We
@@ -34,8 +22,8 @@ module ActiveJob
module Arguments
extend self
# :nodoc:
- # Calls #uniq since Integer, Fixnum, and Bignum are all the same class on Ruby 2.4+
- TYPE_WHITELIST = [ NilClass, String, Integer, Fixnum, Bignum, Float, BigDecimal, TrueClass, FalseClass ].uniq
+ TYPE_WHITELIST = [ NilClass, String, Integer, Float, BigDecimal, TrueClass, FalseClass ]
+ TYPE_WHITELIST.push(Fixnum, Bignum) unless 1.class == Integer
# Serializes a set of arguments. Whitelisted types are returned
# as-is. Arrays/Hashes are serialized element by element.
@@ -104,7 +92,7 @@ module ActiveJob
end
def serialized_global_id?(hash)
- hash.size == 1 and hash.include?(GLOBALID_KEY)
+ hash.size == 1 && hash.include?(GLOBALID_KEY)
end
def deserialize_global_id(hash)
diff --git a/activejob/lib/active_job/configured_job.rb b/activejob/lib/active_job/configured_job.rb
index 979280b910..2ff31f2dae 100644
--- a/activejob/lib/active_job/configured_job.rb
+++ b/activejob/lib/active_job/configured_job.rb
@@ -1,6 +1,6 @@
module ActiveJob
class ConfiguredJob #:nodoc:
- def initialize(job_class, options={})
+ def initialize(job_class, options = {})
@options = options
@job_class = job_class
end
diff --git a/activejob/lib/active_job/core.rb b/activejob/lib/active_job/core.rb
index a338061766..548ec89ee2 100644
--- a/activejob/lib/active_job/core.rb
+++ b/activejob/lib/active_job/core.rb
@@ -59,7 +59,7 @@ module ActiveJob
# VideoJob.set(queue: :some_queue, wait: 5.minutes).perform_later(Video.last)
# VideoJob.set(queue: :some_queue, wait_until: Time.now.tomorrow).perform_later(Video.last)
# VideoJob.set(queue: :some_queue, wait: 5.minutes, priority: 10).perform_later(Video.last)
- def set(options={})
+ def set(options = {})
ConfiguredJob.new(self, options)
end
end
diff --git a/activejob/lib/active_job/enqueuing.rb b/activejob/lib/active_job/enqueuing.rb
index 18051a7d65..c73117e7f3 100644
--- a/activejob/lib/active_job/enqueuing.rb
+++ b/activejob/lib/active_job/enqueuing.rb
@@ -18,8 +18,8 @@ module ActiveJob
job_or_instantiate(*args).enqueue
end
- protected
- def job_or_instantiate(*args)
+ private
+ def job_or_instantiate(*args) # :doc:
args.first.is_a?(self) ? args.first : new(*args)
end
end
@@ -39,7 +39,7 @@ module ActiveJob
# my_job_instance.enqueue queue: :important
# my_job_instance.enqueue wait_until: Date.tomorrow.midnight
# my_job_instance.enqueue priority: 10
- def enqueue(options={})
+ def enqueue(options = {})
self.scheduled_at = options[:wait].seconds.from_now.to_f if options[:wait]
self.scheduled_at = options[:wait_until].to_f if options[:wait_until]
self.queue_name = self.class.queue_name_from_part(options[:queue]) if options[:queue]
diff --git a/activejob/lib/active_job/exceptions.rb b/activejob/lib/active_job/exceptions.rb
index a8ff55fbe4..c1b5d35313 100644
--- a/activejob/lib/active_job/exceptions.rb
+++ b/activejob/lib/active_job/exceptions.rb
@@ -12,12 +12,12 @@ module ActiveJob
# holding queue for inspection.
#
# You can also pass a block that'll be invoked if the retry attempts fail for custom logic rather than letting
- # the exception bubble up.
+ # the exception bubble up. This block is yielded with the job instance as the first and the error instance as the second parameter.
#
# ==== Options
# * <tt>:wait</tt> - Re-enqueues the job with a delay specified either in seconds (default: 3 seconds),
# as a computing proc that the number of executions so far as an argument, or as a symbol reference of
- # <tt>:exponentially_longer<>, which applies the wait algorithm of <tt>(executions ** 4) + 2</tt>
+ # <tt>:exponentially_longer</tt>, which applies the wait algorithm of <tt>(executions ** 4) + 2</tt>
# (first wait 3s, then 18s, then 83s, etc)
# * <tt>:attempts</tt> - Re-enqueues the job the specified number of times (default: 5 attempts)
# * <tt>:queue</tt> - Re-enqueues the job on a different queue
@@ -28,15 +28,15 @@ module ActiveJob
# class RemoteServiceJob < ActiveJob::Base
# retry_on CustomAppException # defaults to 3s wait, 5 attempts
# retry_on AnotherCustomAppException, wait: ->(executions) { executions * 2 }
- # retry_on(YetAnotherCustomAppException) do |exception|
+ # retry_on(YetAnotherCustomAppException) do |job, exception|
# ExceptionNotifier.caught(exception)
# end
- # retry_on ActiveRecord::StatementInvalid, wait: 5.seconds, attempts: 3
+ # retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3
# retry_on Net::OpenTimeout, wait: :exponentially_longer, attempts: 10
#
# def perform(*args)
# # Might raise CustomAppException, AnotherCustomAppException, or YetAnotherCustomAppException for something domain specific
- # # Might raise ActiveRecord::StatementInvalid when a local db deadlock is detected
+ # # Might raise ActiveRecord::Deadlocked when a local db deadlock is detected
# # Might raise Net::OpenTimeout when the remote service is down
# end
# end
@@ -47,7 +47,7 @@ module ActiveJob
retry_job wait: determine_delay(wait), queue: queue, priority: priority
else
if block_given?
- yield exception
+ yield self, exception
else
logger.error "Stopped retrying #{self.class} due to a #{exception}, which reoccurred on #{executions} attempts. The original exception was #{error.cause.inspect}."
raise error
@@ -104,7 +104,7 @@ module ActiveJob
def determine_delay(seconds_or_duration_or_algorithm)
case seconds_or_duration_or_algorithm
when :exponentially_longer
- (executions ** 4) + 2
+ (executions**4) + 2
when ActiveSupport::Duration
duration = seconds_or_duration_or_algorithm
duration.to_i
diff --git a/activejob/lib/active_job/logging.rb b/activejob/lib/active_job/logging.rb
index 9900e10181..aa97ab2e22 100644
--- a/activejob/lib/active_job/logging.rb
+++ b/activejob/lib/active_job/logging.rb
@@ -18,7 +18,7 @@ module ActiveJob
around_perform do |job, block, _|
tag_logger(job.class.name, job.job_id) do
- payload = {adapter: job.class.queue_adapter, job: job}
+ payload = { adapter: job.class.queue_adapter, job: job }
ActiveSupport::Notifications.instrument("perform_start.active_job", payload.dup)
ActiveSupport::Notifications.instrument("perform.active_job", payload) do
block.call
@@ -41,7 +41,7 @@ module ActiveJob
def tag_logger(*tags)
if logger.respond_to?(:tagged)
tags.unshift "ActiveJob" unless logger_tagged_by_active_job?
- logger.tagged(*tags){ yield }
+ logger.tagged(*tags) { yield }
else
yield
end
diff --git a/activejob/lib/active_job/queue_adapter.rb b/activejob/lib/active_job/queue_adapter.rb
index 7f9a2da4b0..bcc555d33e 100644
--- a/activejob/lib/active_job/queue_adapter.rb
+++ b/activejob/lib/active_job/queue_adapter.rb
@@ -1,5 +1,4 @@
require "active_job/queue_adapters/inline_adapter"
-require "active_support/core_ext/class/attribute"
require "active_support/core_ext/string/inflections"
module ActiveJob
@@ -37,12 +36,6 @@ module ActiveJob
else
if queue_adapter?(name_or_adapter_or_class)
name_or_adapter_or_class
- elsif queue_adapter_class?(name_or_adapter_or_class)
- ActiveSupport::Deprecation.warn "Passing an adapter class is deprecated " \
- "and will be removed in Rails 5.1. Please pass an adapter name " \
- "(.queue_adapter = :#{name_or_adapter_or_class.name.demodulize.remove('Adapter').underscore}) " \
- "or an instance (.queue_adapter = #{name_or_adapter_or_class.name}.new) instead."
- name_or_adapter_or_class.new
else
raise ArgumentError
end
@@ -54,10 +47,6 @@ module ActiveJob
def queue_adapter?(object)
QUEUE_ADAPTER_METHODS.all? { |meth| object.respond_to?(meth) }
end
-
- def queue_adapter_class?(object)
- object.is_a?(Class) && QUEUE_ADAPTER_METHODS.all? { |meth| object.public_method_defined?(meth) }
- end
end
end
end
diff --git a/activejob/lib/active_job/queue_adapters.rb b/activejob/lib/active_job/queue_adapters.rb
index 86cc880b14..c8eedb6156 100644
--- a/activejob/lib/active_job/queue_adapters.rb
+++ b/activejob/lib/active_job/queue_adapters.rb
@@ -8,7 +8,7 @@ module ActiveJob
# * {Qu}[https://github.com/bkeepers/qu]
# * {Que}[https://github.com/chanks/que]
# * {queue_classic}[https://github.com/QueueClassic/queue_classic]
- # * {Resque 1.x}[https://github.com/resque/resque/tree/1-x-stable]
+ # * {Resque}[https://github.com/resque/resque]
# * {Sidekiq}[http://sidekiq.org]
# * {Sneakers}[https://github.com/jondot/sneakers]
# * {Sucker Punch}[https://github.com/brandonhilkert/sucker_punch]
diff --git a/activejob/lib/active_job/queue_adapters/qu_adapter.rb b/activejob/lib/active_job/queue_adapters/qu_adapter.rb
index 20cc97ebc7..e8994533e4 100644
--- a/activejob/lib/active_job/queue_adapters/qu_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/qu_adapter.rb
@@ -32,7 +32,7 @@ module ActiveJob
class JobWrapper < Qu::Job #:nodoc:
def initialize(job_data)
- @job_data = job_data
+ @job_data = job_data
end
def perform
diff --git a/activejob/lib/active_job/queue_adapters/resque_adapter.rb b/activejob/lib/active_job/queue_adapters/resque_adapter.rb
index 8620401cc1..2df157ef89 100644
--- a/activejob/lib/active_job/queue_adapters/resque_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/resque_adapter.rb
@@ -27,6 +27,7 @@ module ActiveJob
# Rails.application.config.active_job.queue_adapter = :resque
class ResqueAdapter
def enqueue(job) #:nodoc:
+ JobWrapper.instance_variable_set(:@queue, job.queue_name)
Resque.enqueue_to job.queue_name, JobWrapper, job.serialize
end
diff --git a/activejob/lib/active_job/queue_name.rb b/activejob/lib/active_job/queue_name.rb
index 143fac9888..352cf62424 100644
--- a/activejob/lib/active_job/queue_name.rb
+++ b/activejob/lib/active_job/queue_name.rb
@@ -16,7 +16,7 @@ module ActiveJob
# post.to_feed!
# end
# end
- def queue_as(part_name=nil, &block)
+ def queue_as(part_name = nil, &block)
if block_given?
self.queue_name = block
else
diff --git a/activejob/lib/active_job/queue_priority.rb b/activejob/lib/active_job/queue_priority.rb
index a48e53b0ef..b02202fcc8 100644
--- a/activejob/lib/active_job/queue_priority.rb
+++ b/activejob/lib/active_job/queue_priority.rb
@@ -17,7 +17,7 @@ module ActiveJob
# end
#
# Specify either an argument or a block.
- def queue_with_priority(priority=nil, &block)
+ def queue_with_priority(priority = nil, &block)
if block_given?
self.priority = block
else
diff --git a/activejob/lib/active_job/railtie.rb b/activejob/lib/active_job/railtie.rb
index e4198a40a5..4a8bf04d70 100644
--- a/activejob/lib/active_job/railtie.rb
+++ b/activejob/lib/active_job/railtie.rb
@@ -15,7 +15,7 @@ module ActiveJob
options.queue_adapter ||= :async
ActiveSupport.on_load(:active_job) do
- options.each { |k,v| send("#{k}=", v) }
+ options.each { |k, v| send("#{k}=", v) }
end
end
diff --git a/activejob/lib/active_job/test_case.rb b/activejob/lib/active_job/test_case.rb
index 0d97e00bfa..a5ec45e4a7 100644
--- a/activejob/lib/active_job/test_case.rb
+++ b/activejob/lib/active_job/test_case.rb
@@ -3,5 +3,7 @@ require "active_support/test_case"
module ActiveJob
class TestCase < ActiveSupport::TestCase
include ActiveJob::TestHelper
+
+ ActiveSupport.run_load_hooks(:active_job_test_case, self)
end
end
diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb
index 35fee7452c..d01795f0c5 100644
--- a/activejob/lib/active_job/test_helper.rb
+++ b/activejob/lib/active_job/test_helper.rb
@@ -11,7 +11,7 @@ module ActiveJob
def before_setup # :nodoc:
test_adapter = queue_adapter_for_test
- @old_queue_adapters = (ActiveJob::Base.subclasses << ActiveJob::Base).select do |klass|
+ @old_queue_adapters = (ActiveJob::Base.descendants << ActiveJob::Base).select do |klass|
# only override explicitly set adapters, a quirk of `class_attribute`
klass.singleton_class.public_instance_methods(false).include?(:_queue_adapter)
end.map do |klass|
@@ -232,16 +232,16 @@ module ActiveJob
# MyJob.set(wait_until: Date.tomorrow.noon).perform_later
# end
# end
- def assert_enqueued_with(args = {})
+ def assert_enqueued_with(job: nil, args: nil, at: nil, queue: nil)
original_enqueued_jobs_count = enqueued_jobs.count
- args.assert_valid_keys(:job, :args, :at, :queue)
- serialized_args = serialize_args_for_assertion(args)
+ expected = { job: job, args: args, at: at, queue: queue }.compact
+ serialized_args = serialize_args_for_assertion(expected)
yield
in_block_jobs = enqueued_jobs.drop(original_enqueued_jobs_count)
- matching_job = in_block_jobs.find do |job|
- serialized_args.all? { |key, value| value == job[key] }
+ matching_job = in_block_jobs.find do |in_block_job|
+ serialized_args.all? { |key, value| value == in_block_job[key] }
end
- assert matching_job, "No enqueued job found with #{args}"
+ assert matching_job, "No enqueued job found with #{expected}"
instantiate_job(matching_job)
end
@@ -256,19 +256,38 @@ module ActiveJob
# MyJob.set(wait_until: Date.tomorrow.noon).perform_later
# end
# end
- def assert_performed_with(args = {})
+ def assert_performed_with(job: nil, args: nil, at: nil, queue: nil)
original_performed_jobs_count = performed_jobs.count
- args.assert_valid_keys(:job, :args, :at, :queue)
- serialized_args = serialize_args_for_assertion(args)
+ expected = { job: job, args: args, at: at, queue: queue }.compact
+ serialized_args = serialize_args_for_assertion(expected)
perform_enqueued_jobs { yield }
in_block_jobs = performed_jobs.drop(original_performed_jobs_count)
- matching_job = in_block_jobs.find do |job|
- serialized_args.all? { |key, value| value == job[key] }
+ matching_job = in_block_jobs.find do |in_block_job|
+ serialized_args.all? { |key, value| value == in_block_job[key] }
end
- assert matching_job, "No performed job found with #{args}"
+ assert matching_job, "No performed job found with #{expected}"
instantiate_job(matching_job)
end
+ # Performs all enqueued jobs in the duration of the block.
+ #
+ # def test_perform_enqueued_jobs
+ # perform_enqueued_jobs do
+ # MyJob.perform_later(1, 2, 3)
+ # end
+ # assert_performed_jobs 1
+ # end
+ #
+ # This method also supports filtering. If the +:only+ option is specified,
+ # then only the listed job(s) will be performed.
+ #
+ # def test_perform_enqueued_jobs_with_only
+ # perform_enqueued_jobs(only: MyJob) do
+ # MyJob.perform_later(1, 2, 3) # will be performed
+ # HelloJob.perform_later(1, 2, 3) # will not be perfomed
+ # end
+ # assert_performed_jobs 1
+ # end
def perform_enqueued_jobs(only: nil)
old_perform_enqueued_jobs = queue_adapter.perform_enqueued_jobs
old_perform_enqueued_at_jobs = queue_adapter.perform_enqueued_at_jobs
@@ -286,20 +305,25 @@ module ActiveJob
end
end
+ # Accesses the queue_adapter set by ActiveJob::Base.
+ #
+ # def test_assert_job_has_custom_queue_adapter_set
+ # assert_instance_of CustomQueueAdapter, HelloJob.queue_adapter
+ # end
def queue_adapter
ActiveJob::Base.queue_adapter
end
private
- def clear_enqueued_jobs # :nodoc:
+ def clear_enqueued_jobs
enqueued_jobs.clear
end
- def clear_performed_jobs # :nodoc:
+ def clear_performed_jobs
performed_jobs.clear
end
- def enqueued_jobs_size(only: nil) # :nodoc:
+ def enqueued_jobs_size(only: nil)
if only
enqueued_jobs.count { |job| Array(only).include?(job.fetch(:job)) }
else
@@ -307,14 +331,14 @@ module ActiveJob
end
end
- def serialize_args_for_assertion(args) # :nodoc:
+ def serialize_args_for_assertion(args)
args.dup.tap do |serialized_args|
serialized_args[:args] = ActiveJob::Arguments.serialize(serialized_args[:args]) if serialized_args[:args]
serialized_args[:at] = serialized_args[:at].to_f if serialized_args[:at]
end
end
- def instantiate_job(payload) # :nodoc:
+ def instantiate_job(payload)
job = payload[:job].new(*payload[:args])
job.scheduled_at = Time.at(payload[:at]) if payload.key?(:at)
job.queue_name = payload[:queue]
diff --git a/activejob/test/cases/exceptions_test.rb b/activejob/test/cases/exceptions_test.rb
index 30e43c99cb..dbe55f7430 100644
--- a/activejob/test/cases/exceptions_test.rb
+++ b/activejob/test/cases/exceptions_test.rb
@@ -59,7 +59,7 @@ class ExceptionsTest < ActiveJob::TestCase
test "custom handling of job that exceeds retry attempts" do
perform_enqueued_jobs do
RetryJob.perform_later "CustomCatchError", 6
- assert_equal "Dealt with a job that failed to retry in a custom way", JobBuffer.last_value
+ assert_equal "Dealt with a job that failed to retry in a custom way after 6 attempts", JobBuffer.last_value
end
end
diff --git a/activejob/test/cases/queue_adapter_test.rb b/activejob/test/cases/queue_adapter_test.rb
index dc862450aa..f1e0cf78ad 100644
--- a/activejob/test/cases/queue_adapter_test.rb
+++ b/activejob/test/cases/queue_adapter_test.rb
@@ -20,19 +20,6 @@ class QueueAdapterTest < ActiveJob::TestCase
assert_raises(ArgumentError) { ActiveJob::Base.queue_adapter = Mutex.new }
end
- test "should warn on passing an adapter class" do
- klass = Class.new do
- def self.name
- "fake"
- end
-
- def enqueue(*); end
- def enqueue_at(*); end
- end
-
- assert_deprecated { ActiveJob::Base.queue_adapter = klass }
- end
-
test "should allow overriding the queue_adapter at the child class level without affecting the parent or its sibling" do
base_queue_adapter = ActiveJob::Base.queue_adapter
diff --git a/activejob/test/cases/queue_naming_test.rb b/activejob/test/cases/queue_naming_test.rb
index 7777e557c9..0e1e0decd1 100644
--- a/activejob/test/cases/queue_naming_test.rb
+++ b/activejob/test/cases/queue_naming_test.rb
@@ -56,7 +56,7 @@ class QueueNamingTest < ActiveSupport::TestCase
original_queue_name = HelloJob.queue_name
begin
- HelloJob.queue_as { self.arguments.first=="1" ? :one : :two }
+ HelloJob.queue_as { self.arguments.first == "1" ? :one : :two }
assert_equal "one", HelloJob.new("1").queue_name
assert_equal "two", HelloJob.new("3").queue_name
ensure
diff --git a/activejob/test/cases/queue_priority_test.rb b/activejob/test/cases/queue_priority_test.rb
index ab4a1bdf7b..b0d0e903fc 100644
--- a/activejob/test/cases/queue_priority_test.rb
+++ b/activejob/test/cases/queue_priority_test.rb
@@ -3,7 +3,7 @@ require "jobs/hello_job"
class QueuePriorityTest < ActiveSupport::TestCase
test "priority unset by default" do
- assert_equal nil, HelloJob.priority
+ assert_nil HelloJob.priority
end
test "uses given priority" do
@@ -32,7 +32,7 @@ class QueuePriorityTest < ActiveSupport::TestCase
original_priority = HelloJob.priority
begin
- HelloJob.queue_with_priority { self.arguments.first=="1" ? 99 : 11 }
+ HelloJob.queue_with_priority { self.arguments.first == "1" ? 99 : 11 }
assert_equal 99, HelloJob.new("1").priority
assert_equal 11, HelloJob.new("3").priority
ensure
diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb
index 2398402d5d..51fc6cc0c4 100644
--- a/activejob/test/cases/test_helper_test.rb
+++ b/activejob/test/cases/test_helper_test.rb
@@ -5,6 +5,7 @@ require "jobs/hello_job"
require "jobs/logging_job"
require "jobs/nested_job"
require "jobs/rescue_job"
+require "jobs/inherited_job"
require "models/person"
class EnqueuedJobsTest < ActiveJob::TestCase
@@ -249,17 +250,17 @@ class EnqueuedJobsTest < ActiveJob::TestCase
HelloJob.perform_later
end
- assert_equal 2, ActiveJob::Base.queue_adapter.enqueued_jobs.count
+ assert_equal 2, queue_adapter.enqueued_jobs.count
end
end
class PerformedJobsTest < ActiveJob::TestCase
def test_performed_enqueue_jobs_with_only_option_doesnt_leak_outside_the_block
- assert_equal nil, queue_adapter.filter
+ assert_nil queue_adapter.filter
perform_enqueued_jobs only: HelloJob do
assert_equal HelloJob, queue_adapter.filter
end
- assert_equal nil, queue_adapter.filter
+ assert_nil queue_adapter.filter
end
def test_assert_performed_jobs
@@ -506,7 +507,7 @@ class PerformedJobsTest < ActiveJob::TestCase
HelloJob.perform_later
end
- assert_equal 2, ActiveJob::Base.queue_adapter.performed_jobs.count
+ assert_equal 2, queue_adapter.performed_jobs.count
end
end
@@ -521,3 +522,9 @@ class OverrideQueueAdapterTest < ActiveJob::TestCase
assert_instance_of CustomQueueAdapter, HelloJob.queue_adapter
end
end
+
+class InheritedJobTest < ActiveJob::TestCase
+ def test_queue_adapter_is_test_adapter
+ assert_instance_of ActiveJob::QueueAdapters::TestAdapter, InheritedJob.queue_adapter
+ end
+end
diff --git a/activejob/test/helper.rb b/activejob/test/helper.rb
index dbc7dad109..776f7788de 100644
--- a/activejob/test/helper.rb
+++ b/activejob/test/helper.rb
@@ -4,7 +4,8 @@ require "support/job_buffer"
ActiveSupport.halt_callback_chains_on_return_false = false
GlobalID.app = "aj"
-@adapter = ENV["AJ_ADAPTER"] || "inline"
+@adapter = ENV["AJ_ADAPTER"] || "inline"
+puts "Using #{@adapter}"
if ENV["AJ_INTEGRATION_TESTS"]
require "support/integration/helper"
diff --git a/activejob/test/integration/queuing_test.rb b/activejob/test/integration/queuing_test.rb
index 41b6b9cd6b..2669c52a1c 100644
--- a/activejob/test/integration/queuing_test.rb
+++ b/activejob/test/integration/queuing_test.rb
@@ -43,6 +43,13 @@ class QueuingTest < ActiveSupport::TestCase
end
end
+ test "resque JobWrapper should have instance variable queue" do
+ skip unless adapter_is?(:resque)
+ job = ::HelloJob.set(wait: 5.seconds).perform_later
+ hash = Resque.decode(Resque.find_delayed_selection { true }[0])
+ assert_equal hash["queue"], job.queue_name
+ end
+
test "should not run job enqueued in the future" do
begin
TestJob.set(wait: 10.minutes).perform_later @id
diff --git a/activejob/test/jobs/application_job.rb b/activejob/test/jobs/application_job.rb
new file mode 100644
index 0000000000..a009ace51c
--- /dev/null
+++ b/activejob/test/jobs/application_job.rb
@@ -0,0 +1,2 @@
+class ApplicationJob < ActiveJob::Base
+end
diff --git a/activejob/test/jobs/inherited_job.rb b/activejob/test/jobs/inherited_job.rb
new file mode 100644
index 0000000000..60fca66f88
--- /dev/null
+++ b/activejob/test/jobs/inherited_job.rb
@@ -0,0 +1,5 @@
+require_relative "application_job"
+
+class InheritedJob < ApplicationJob
+ self.queue_adapter = :inline
+end
diff --git a/activejob/test/jobs/retry_job.rb b/activejob/test/jobs/retry_job.rb
index be20156984..c02febc50c 100644
--- a/activejob/test/jobs/retry_job.rb
+++ b/activejob/test/jobs/retry_job.rb
@@ -15,7 +15,7 @@ class RetryJob < ActiveJob::Base
retry_on ShortWaitTenAttemptsError, wait: 1.second, attempts: 10
retry_on ExponentialWaitTenAttemptsError, wait: :exponentially_longer, attempts: 10
retry_on CustomWaitTenAttemptsError, wait: ->(executions) { executions * 2 }, attempts: 10
- retry_on(CustomCatchError) { |exception| JobBuffer.add("Dealt with a job that failed to retry in a custom way") }
+ retry_on(CustomCatchError) { |job, exception| JobBuffer.add("Dealt with a job that failed to retry in a custom way after #{job.arguments.second} attempts") }
discard_on DiscardableError
def perform(raising, attempts)
diff --git a/activejob/test/models/person.rb b/activejob/test/models/person.rb
index 76a8f40616..b5d68ad9c1 100644
--- a/activejob/test/models/person.rb
+++ b/activejob/test/models/person.rb
@@ -6,7 +6,7 @@ class Person
attr_reader :id
def self.find(id)
- raise RecordNotFound.new("Cannot find person with ID=404") if id.to_i==404
+ raise RecordNotFound.new("Cannot find person with ID=404") if id.to_i == 404
new(id)
end
diff --git a/activejob/test/support/backburner/inline.rb b/activejob/test/support/backburner/inline.rb
index 647dbf317f..9758332b6f 100644
--- a/activejob/test/support/backburner/inline.rb
+++ b/activejob/test/support/backburner/inline.rb
@@ -2,7 +2,7 @@ require "backburner"
Backburner::Worker.class_eval do
class << self; alias_method :original_enqueue, :enqueue; end
- def self.enqueue(job_class, args=[], opts={})
+ def self.enqueue(job_class, args = [], opts = {})
job_class.perform(*args)
end
end
diff --git a/activejob/test/support/delayed_job/delayed/backend/test.rb b/activejob/test/support/delayed_job/delayed/backend/test.rb
index e4aca625cd..98d731ff1e 100644
--- a/activejob/test/support/delayed_job/delayed/backend/test.rb
+++ b/activejob/test/support/delayed_job/delayed/backend/test.rb
@@ -26,7 +26,7 @@ module Delayed
self.attempts = 0
self.priority = 0
self.id = (self.class.id += 1)
- hash.each{|k,v| send(:"#{k}=", v)}
+ hash.each { |k, v| send(:"#{k}=", v) }
end
@jobs = []
@@ -49,7 +49,7 @@ module Delayed
def self.create!(*args); create(*args); end
def self.clear_locks!(worker_name)
- all.select{|j| j.locked_by == worker_name}.each {|j| j.locked_by = nil; j.locked_at = nil}
+ all.select { |j| j.locked_by == worker_name }.each { |j| j.locked_by = nil; j.locked_at = nil }
end
# Find a few candidate jobs to run (in case some immediately get locked by others).
@@ -60,10 +60,10 @@ module Delayed
!j.failed?
end
- jobs = jobs.select{|j| Worker.queues.include?(j.queue)} if Worker.queues.any?
- jobs = jobs.select{|j| j.priority >= Worker.min_priority} if Worker.min_priority
- jobs = jobs.select{|j| j.priority <= Worker.max_priority} if Worker.max_priority
- jobs.sort_by{|j| [j.priority, j.run_at]}[0..limit-1]
+ jobs = jobs.select { |j| Worker.queues.include?(j.queue) } if Worker.queues.any?
+ jobs = jobs.select { |j| j.priority >= Worker.min_priority } if Worker.min_priority
+ jobs = jobs.select { |j| j.priority <= Worker.max_priority } if Worker.max_priority
+ jobs.sort_by { |j| [j.priority, j.run_at] }[0..limit - 1]
end
# Lock this job for this worker.
@@ -84,7 +84,7 @@ module Delayed
end
def update_attributes(attrs = {})
- attrs.each{|k,v| send(:"#{k}=", v)}
+ attrs.each { |k, v| send(:"#{k}=", v) }
save
end
diff --git a/activejob/test/support/integration/adapters/que.rb b/activejob/test/support/integration/adapters/que.rb
index 3d35a0439f..20faee3427 100644
--- a/activejob/test/support/integration/adapters/que.rb
+++ b/activejob/test/support/integration/adapters/que.rb
@@ -13,7 +13,7 @@ module QueJobsManager
def start_workers
que_url = ENV["QUE_DATABASE_URL"] || "postgres:///active_jobs_que_int_test"
uri = URI.parse(que_url)
- user = uri.user||ENV["USER"]
+ user = uri.user || ENV["USER"]
pass = uri.password
db = uri.path[1..-1]
%x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'drop database if exists "#{db}"' -U #{user} -t template1}
diff --git a/activejob/test/support/integration/adapters/queue_classic.rb b/activejob/test/support/integration/adapters/queue_classic.rb
index b5d831428e..369693e947 100644
--- a/activejob/test/support/integration/adapters/queue_classic.rb
+++ b/activejob/test/support/integration/adapters/queue_classic.rb
@@ -12,7 +12,7 @@ module QueueClassicJobsManager
def start_workers
uri = URI.parse(ENV["QC_DATABASE_URL"])
- user = uri.user||ENV["USER"]
+ user = uri.user || ENV["USER"]
pass = uri.password
db = uri.path[1..-1]
%x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'drop database if exists "#{db}"' -U #{user} -t template1}
diff --git a/activejob/test/support/integration/adapters/sneakers.rb b/activejob/test/support/integration/adapters/sneakers.rb
index 08743c1f05..1c8dfaca59 100644
--- a/activejob/test/support/integration/adapters/sneakers.rb
+++ b/activejob/test/support/integration/adapters/sneakers.rb
@@ -39,7 +39,7 @@ module SneakersJobsManager
@pid = fork do
queues = %w(integration_tests)
workers = queues.map do |q|
- worker_klass = "ActiveJobWorker"+Digest::MD5.hexdigest(q)
+ worker_klass = "ActiveJobWorker" + Digest::MD5.hexdigest(q)
Sneakers.const_set(worker_klass, Class.new(ActiveJob::QueueAdapters::SneakersAdapter::JobWrapper) do
from_queue q
end)
@@ -73,7 +73,7 @@ module SneakersJobsManager
true
end
- protected
+ private
def bunny_publisher
@bunny_publisher ||= begin
p = ActiveJob::QueueAdapters::SneakersAdapter::JobWrapper.send(:publisher)
diff --git a/activejob/test/support/integration/dummy_app_template.rb b/activejob/test/support/integration/dummy_app_template.rb
index 62f6fa13f6..29a5691f30 100644
--- a/activejob/test/support/integration/dummy_app_template.rb
+++ b/activejob/test/support/integration/dummy_app_template.rb
@@ -18,12 +18,13 @@ class TestJob < ActiveJob::Base
queue_as :integration_tests
def perform(x)
- File.open(Rails.root.join("tmp/\#{x}"), "wb+") do |f|
+ File.open(Rails.root.join("tmp/\#{x}.new"), "wb+") do |f|
f.write Marshal.dump({
"locale" => I18n.locale.to_s || "en",
"executed_at" => Time.now.to_r
})
end
+ File.rename(Rails.root.join("tmp/\#{x}.new"), Rails.root.join("tmp/\#{x}"))
end
end
CODE
diff --git a/activejob/test/support/integration/helper.rb b/activejob/test/support/integration/helper.rb
index 1aaee2c809..626b932cce 100644
--- a/activejob/test/support/integration/helper.rb
+++ b/activejob/test/support/integration/helper.rb
@@ -5,6 +5,7 @@ ActiveJob::Base.queue_name_prefix = nil
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__)
args = Rails::Generators::ARGVScrubber.new(["new", dummy_app_path, "--skip-gemfile", "--skip-bundle",
diff --git a/activejob/test/support/integration/test_case_helpers.rb b/activejob/test/support/integration/test_case_helpers.rb
index 4c4c56c9da..41bf9c89d1 100644
--- a/activejob/test/support/integration/test_case_helpers.rb
+++ b/activejob/test/support/integration/test_case_helpers.rb
@@ -1,4 +1,3 @@
-require "active_support/concern"
require "active_support/core_ext/string/inflections"
require "support/integration/jobs_manager"
@@ -18,7 +17,7 @@ module TestCaseHelpers
end
end
- protected
+ private
def jobs_manager
JobsManager.current_manager
@@ -33,7 +32,7 @@ module TestCaseHelpers
adapter_class_symbols.map(&:to_s).include? adapter
end
- def wait_for_jobs_to_finish_for(seconds=60)
+ def wait_for_jobs_to_finish_for(seconds = 60)
begin
Timeout.timeout(seconds) do
while !job_executed do
@@ -48,7 +47,7 @@ module TestCaseHelpers
Dummy::Application.root.join("tmp/#{id}")
end
- def job_executed(id=@id)
+ def job_executed(id = @id)
job_file(id).exist?
end
@@ -56,11 +55,11 @@ module TestCaseHelpers
Marshal.load(File.binread(job_file(id)))
end
- def job_executed_at(id=@id)
+ def job_executed_at(id = @id)
job_data(id)["executed_at"]
end
- def job_executed_in_locale(id=@id)
+ def job_executed_in_locale(id = @id)
job_data(id)["locale"]
end
end
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 206699c036..853a1e7d9d 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,2 +1,22 @@
+* Moved DecimalWithoutScale, Text, and UnsignedInteger from Active Model to Active Record
+
+ *Iain Beeston*
+
+* Allow indifferent access in `ActiveModel::Errors`.
+
+ `#include?`, `#has_key?`, `#key?`, `#delete` and `#full_messages_for`.
+
+ *Kenichi Kamiya*
+
+* Removed deprecated `:tokenizer` in the length validator.
+
+ *Rafael Mendonça França*
+
+* Removed deprecated methods in `ActiveModel::Errors`.
+
+ `#get`, `#set`, `[]=`, `add_on_empty` and `add_on_blank`.
+
+ *Rafael Mendonça França*
+
Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/activemodel/CHANGELOG.md) for previous changes.
diff --git a/activemodel/MIT-LICENSE b/activemodel/MIT-LICENSE
index 8573eb1225..ac810e86d0 100644
--- a/activemodel/MIT-LICENSE
+++ b/activemodel/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2016 David Heinemeier Hansson
+Copyright (c) 2004-2017 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activemodel/Rakefile b/activemodel/Rakefile
index d62c7b46c8..c7f97a4258 100644
--- a/activemodel/Rakefile
+++ b/activemodel/Rakefile
@@ -18,6 +18,6 @@ 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)
- end or raise "Failures"
+ end || raise("Failures")
end
end
diff --git a/activemodel/bin/test b/activemodel/bin/test
index 84a05bba08..a7beb14b27 100755
--- a/activemodel/bin/test
+++ b/activemodel/bin/test
@@ -2,5 +2,3 @@
COMPONENT_ROOT = File.expand_path("..", __dir__)
require File.expand_path("../tools/test", COMPONENT_ROOT)
-
-exit Minitest.run(ARGV)
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index a9b0663940..8163856a46 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2016 David Heinemeier Hansson
+# Copyright (c) 2004-2017 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 96709486f3..09825cf861 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -1,6 +1,5 @@
require "concurrent/map"
require "mutex_m"
-require "active_support/core_ext/regexp"
module ActiveModel
# Raised when an attribute is not defined.
@@ -335,12 +334,11 @@ module ActiveModel
}.tap { |mod| include mod }
end
- protected
- def instance_method_already_implemented?(method_name) #:nodoc:
+ private
+ def instance_method_already_implemented?(method_name)
generated_attribute_methods.method_defined?(method_name)
end
- private
# The methods +method_missing+ and +respond_to?+ of this module are
# invoked often in a typical rails, both of which invoke the method
# +matched_attribute_method+. The latter method iterates through an
@@ -350,11 +348,11 @@ module ActiveModel
# used to alleviate the GC, which ultimately also speeds up the app
# significantly (in our case our test suite finishes 10% faster with
# this cache).
- def attribute_method_matchers_cache #:nodoc:
+ def attribute_method_matchers_cache
@attribute_method_matchers_cache ||= Concurrent::Map.new(initial_capacity: 4)
end
- def attribute_method_matchers_matching(method_name) #:nodoc:
+ def attribute_method_matchers_matching(method_name)
attribute_method_matchers_cache.compute_if_absent(method_name) do
# Must try to match prefixes/suffixes first, or else the matcher with no prefix/suffix
# will match every time.
@@ -366,7 +364,7 @@ module ActiveModel
# Define a method `name` in `mod` that dispatches to `send`
# using the given `extra` args. This falls back on `define_method`
# and `send` if the given names cannot be compiled.
- def define_proxy_call(include_private, mod, name, send, *extra) #:nodoc:
+ def define_proxy_call(include_private, mod, name, send, *extra)
defn = if NAME_COMPILABLE_REGEXP.match?(name)
"def #{name}(*args)"
else
@@ -459,12 +457,11 @@ module ActiveModel
end
end
- protected
- def attribute_method?(attr_name) #:nodoc:
+ private
+ def attribute_method?(attr_name)
respond_to_without_attributes?(:attributes) && attributes.include?(attr_name)
end
- private
# Returns a struct representing the matching attribute method.
# The struct's attributes are prefix, base and suffix.
def matched_attribute_method(method_name)
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index 283090e380..e99bfab6fd 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -122,19 +122,19 @@ module ActiveModel
private
- def _define_before_model_callback(klass, callback) #:nodoc:
+ def _define_before_model_callback(klass, callback)
klass.define_singleton_method("before_#{callback}") do |*args, &block|
set_callback(:"#{callback}", :before, *args, &block)
end
end
- def _define_around_model_callback(klass, callback) #:nodoc:
+ def _define_around_model_callback(klass, callback)
klass.define_singleton_method("around_#{callback}") do |*args, &block|
set_callback(:"#{callback}", :around, *args, &block)
end
end
- def _define_after_model_callback(klass, callback) #:nodoc:
+ def _define_after_model_callback(klass, callback)
klass.define_singleton_method("after_#{callback}") do |*args, &block|
options = args.extract_options!
options[:prepend] = true
diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb
index 8bcad6db0f..12687c70d3 100644
--- a/activemodel/lib/active_model/conversion.rb
+++ b/activemodel/lib/active_model/conversion.rb
@@ -46,9 +46,13 @@ module ActiveModel
# class Person
# include ActiveModel::Conversion
# attr_accessor :id
+ #
+ # def initialize(id)
+ # @id = id
+ # end
# end
#
- # person = Person.create(id: 1)
+ # person = Person.new(1)
# person.to_key # => [1]
def to_key
key = respond_to?(:id) && id
@@ -61,12 +65,17 @@ module ActiveModel
# class Person
# include ActiveModel::Conversion
# attr_accessor :id
+ #
+ # def initialize(id)
+ # @id = id
+ # end
+ #
# def persisted?
# true
# end
# end
#
- # person = Person.create(id: 1)
+ # person = Person.new(1)
# person.to_param # => "1"
def to_param
(persisted? && key = to_key) ? key.join("-") : nil
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index f87973301b..6e0af99ad7 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -26,8 +26,8 @@ module ActiveModel
#
# define_attribute_methods :name
#
- # def initialize(name)
- # @name = name
+ # def initialize
+ # @name = nil
# end
#
# def name
@@ -58,7 +58,7 @@ module ActiveModel
#
# A newly instantiated +Person+ object is unchanged:
#
- # person = Person.new("Uncle Bob")
+ # person = Person.new
# person.changed? # => false
#
# Change the name:
@@ -66,11 +66,11 @@ module ActiveModel
# person.name = 'Bob'
# person.changed? # => true
# person.name_changed? # => true
- # person.name_changed?(from: "Uncle Bob", to: "Bob") # => true
- # person.name_was # => "Uncle Bob"
- # person.name_change # => ["Uncle Bob", "Bob"]
+ # person.name_changed?(from: nil, to: "Bob") # => true
+ # person.name_was # => nil
+ # person.name_change # => [nil, "Bob"]
# person.name = 'Bill'
- # person.name_change # => ["Uncle Bob", "Bill"]
+ # person.name_change # => [nil, "Bill"]
#
# Save the changes:
#
@@ -80,9 +80,9 @@ module ActiveModel
#
# Reset the changes:
#
- # person.previous_changes # => {"name" => ["Uncle Bob", "Bill"]}
+ # person.previous_changes # => {"name" => [nil, "Bill"]}
# person.name_previously_changed? # => true
- # person.name_previous_change # => ["Uncle Bob", "Bill"]
+ # person.name_previous_change # => [nil, "Bill"]
# person.reload!
# person.previous_changes # => {}
#
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 45ef14013a..9df4ca51fe 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -110,49 +110,21 @@ module ActiveModel
# person.errors.include?(:name) # => true
# person.errors.include?(:age) # => false
def include?(attribute)
+ attribute = attribute.to_sym
messages.key?(attribute) && messages[attribute].present?
end
alias :has_key? :include?
alias :key? :include?
- # Get messages for +key+.
- #
- # person.errors.messages # => {:name=>["cannot be nil"]}
- # person.errors.get(:name) # => ["cannot be nil"]
- # person.errors.get(:age) # => []
- def get(key)
- ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
- ActiveModel::Errors#get is deprecated and will be removed in Rails 5.1.
-
- To achieve the same use model.errors[:#{key}].
- MESSAGE
-
- messages[key]
- end
-
- # Set messages for +key+ to +value+.
- #
- # person.errors[:name] # => ["cannot be nil"]
- # person.errors.set(:name, ["can't be nil"])
- # person.errors[:name] # => ["can't be nil"]
- def set(key, value)
- ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
- ActiveModel::Errors#set is deprecated and will be removed in Rails 5.1.
-
- Use model.errors.add(:#{key}, #{value.inspect}) instead.
- MESSAGE
-
- messages[key] = value
- end
-
# Delete messages for +key+. Returns the deleted messages.
#
# person.errors[:name] # => ["cannot be nil"]
# person.errors.delete(:name) # => ["cannot be nil"]
# person.errors[:name] # => []
def delete(key)
- details.delete(key)
- messages.delete(key)
+ attribute = key.to_sym
+ details.delete(attribute)
+ messages.delete(attribute)
end
# When passed a symbol or a name of a method, returns an array of errors
@@ -173,20 +145,6 @@ module ActiveModel
messages[attribute.to_sym]
end
- # Adds to the supplied attribute the supplied error message.
- #
- # person.errors[:name] = "must be set"
- # person.errors[:name] # => ['must be set']
- def []=(attribute, error)
- ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
- ActiveModel::Errors#[]= is deprecated and will be removed in Rails 5.1.
-
- Use model.errors.add(:#{attribute}, #{error.inspect}) instead.
- MESSAGE
-
- messages[attribute.to_sym] << error
- end
-
# Iterates through each error key, value pair in the error messages hash.
# Yields the attribute and the error for that attribute. If the attribute
# has more than one error message, yields once for each error message.
@@ -255,7 +213,7 @@ module ActiveModel
# # <error>name can't be blank</error>
# # <error>name must be specified</error>
# # </errors>
- def to_xml(options={})
+ def to_xml(options = {})
to_a.to_xml({ root: "errors", skip_types: true }.merge!(options))
end
@@ -265,7 +223,7 @@ module ActiveModel
#
# person.errors.as_json # => {:name=>["cannot be nil"]}
# person.errors.as_json(full_messages: true) # => {:name=>["name cannot be nil"]}
- def as_json(options=nil)
+ def as_json(options = nil)
to_hash(options && options[:full_messages])
end
@@ -280,7 +238,7 @@ module ActiveModel
messages[attribute] = array.map { |message| full_message(attribute, message) }
end
else
- messages.dup
+ without_default_proc(messages)
end
end
@@ -338,49 +296,6 @@ module ActiveModel
messages[attribute.to_sym] << message
end
- # Will add an error message to each of the attributes in +attributes+
- # that is empty.
- #
- # person.errors.add_on_empty(:name)
- # person.errors.messages
- # # => {:name=>["can't be empty"]}
- def add_on_empty(attributes, options = {})
- ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
- ActiveModel::Errors#add_on_empty is deprecated and will be removed in Rails 5.1.
-
- To achieve the same use:
-
- errors.add(attribute, :empty, options) if value.nil? || value.empty?
- MESSAGE
-
- Array(attributes).each do |attribute|
- value = @base.send(:read_attribute_for_validation, attribute)
- is_empty = value.respond_to?(:empty?) ? value.empty? : false
- add(attribute, :empty, options) if value.nil? || is_empty
- end
- end
-
- # Will add an error message to each of the attributes in +attributes+ that
- # is blank (using Object#blank?).
- #
- # person.errors.add_on_blank(:name)
- # person.errors.messages
- # # => {:name=>["can't be blank"]}
- def add_on_blank(attributes, options = {})
- ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
- ActiveModel::Errors#add_on_blank is deprecated and will be removed in Rails 5.1.
-
- To achieve the same use:
-
- errors.add(attribute, :empty, options) if value.blank?
- MESSAGE
-
- Array(attributes).each do |attribute|
- value = @base.send(:read_attribute_for_validation, attribute)
- add(attribute, :blank, options) if value.blank?
- end
- end
-
# Returns +true+ if an error on the attribute with the given message is
# present, or +false+ otherwise. +message+ is treated the same as for +add+.
#
@@ -429,6 +344,7 @@ module ActiveModel
# person.errors.full_messages_for(:name)
# # => ["Name is too short (minimum is 5 characters)", "Name can't be blank"]
def full_messages_for(attribute)
+ attribute = attribute.to_sym
messages[attribute].map { |message| full_message(attribute, message) }
end
@@ -503,16 +419,23 @@ module ActiveModel
I18n.translate(key, options)
end
- def marshal_dump
+ def marshal_dump # :nodoc:
[@base, without_default_proc(@messages), without_default_proc(@details)]
end
- def marshal_load(array)
+ def marshal_load(array) # :nodoc:
@base, @messages, @details = array
apply_default_array(@messages)
apply_default_array(@details)
end
+ def init_with(coder) # :nodoc:
+ coder.map.each { |k, v| instance_variable_set(:"@#{k}", v) }
+ @details ||= {}
+ apply_default_array(@messages)
+ apply_default_array(@details)
+ end
+
private
def normalize_message(attribute, message, options)
case message
diff --git a/activemodel/lib/active_model/forbidden_attributes_protection.rb b/activemodel/lib/active_model/forbidden_attributes_protection.rb
index d2c6a89cc2..45ab8a2ca1 100644
--- a/activemodel/lib/active_model/forbidden_attributes_protection.rb
+++ b/activemodel/lib/active_model/forbidden_attributes_protection.rb
@@ -15,7 +15,7 @@ module ActiveModel
end
module ForbiddenAttributesProtection # :nodoc:
- protected
+ private
def sanitize_for_mass_assignment(attributes)
if attributes.respond_to?(:permitted?)
raise ActiveModel::ForbiddenAttributesError if !attributes.permitted?
diff --git a/activemodel/lib/active_model/model.rb b/activemodel/lib/active_model/model.rb
index f5765c0c54..945a5402a3 100644
--- a/activemodel/lib/active_model/model.rb
+++ b/activemodel/lib/active_model/model.rb
@@ -2,10 +2,10 @@ module ActiveModel
# == Active \Model \Basic \Model
#
# Includes the required interface for an object to interact with
- # <tt>ActionPack</tt>, using different <tt>ActiveModel</tt> modules.
+ # Action Pack and Action View, using different Active Model modules.
# It includes model name introspections, conversions, translations and
# validations. Besides that, it allows you to initialize the object with a
- # hash of attributes, pretty much like <tt>ActiveRecord</tt> does.
+ # hash of attributes, pretty much like Active Record does.
#
# A minimal implementation could be:
#
@@ -75,7 +75,7 @@ module ActiveModel
# person = Person.new(name: 'bob', age: '18')
# person.name # => "bob"
# person.age # => "18"
- def initialize(attributes={})
+ def initialize(attributes = {})
assign_attributes(attributes) if attributes
super()
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index 3830d1486d..9532c63b65 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -1,7 +1,6 @@
require "active_support/core_ext/hash/except"
require "active_support/core_ext/module/introspection"
require "active_support/core_ext/module/remove_method"
-require "active_support/core_ext/module/delegation"
module ActiveModel
class Name
@@ -174,7 +173,7 @@ module ActiveModel
# BlogPost.model_name.human # => "Blog post"
#
# Specify +options+ with additional translating options.
- def human(options={})
+ def human(options = {})
return @human unless @klass.respond_to?(:lookup_ancestors) &&
@klass.respond_to?(:i18n_scope)
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index b5e030a59b..a9d92eb92a 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -134,7 +134,7 @@ module ActiveModel
# person.name # => "bob"
# person.age # => 22
# person.awesome # => true
- def from_json(json, include_root=include_root_in_json)
+ def from_json(json, include_root = include_root_in_json)
hash = ActiveSupport::JSON.decode(json)
hash = hash.values.first if include_root
self.attributes = hash
diff --git a/activemodel/lib/active_model/type.rb b/activemodel/lib/active_model/type.rb
index 313f17830f..b8e6d2376b 100644
--- a/activemodel/lib/active_model/type.rb
+++ b/activemodel/lib/active_model/type.rb
@@ -7,14 +7,11 @@ 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/decimal_without_scale"
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/text"
require "active_model/type/time"
-require "active_model/type/unsigned_integer"
require "active_model/type/registry"
@@ -27,7 +24,7 @@ module ActiveModel
delegate :add_modifier, to: :registry
# Add a new type to the registry, allowing it to be referenced as a
- # symbol by ActiveModel::Attributes::ClassMethods#attribute. If your
+ # symbol by ActiveRecord::Attributes::ClassMethods#attribute. If your
# type is only meant to be used with a specific database adapter, you can
# do so by passing +adapter: :postgresql+. If your type has the same
# name as a native type for the current adapter, an exception will be
@@ -53,7 +50,6 @@ module ActiveModel
register(:immutable_string, Type::ImmutableString)
register(:integer, Type::Integer)
register(:string, Type::String)
- register(:text, Type::Text)
register(:time, Type::Time)
end
end
diff --git a/activemodel/lib/active_model/type/date_time.rb b/activemodel/lib/active_model/type/date_time.rb
index f88c9e5071..5cb0077e45 100644
--- a/activemodel/lib/active_model/type/date_time.rb
+++ b/activemodel/lib/active_model/type/date_time.rb
@@ -19,8 +19,8 @@ module ActiveModel
fast_string_to_time(value) || fallback_string_to_time(value)
end
- # '0.123456' -> 123456
- # '1.123456' -> 123456
+ # '0.123456' -> 123456
+ # '1.123456' -> 123456
def microseconds(time)
time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0
end
diff --git a/activemodel/lib/active_model/type/decimal.rb b/activemodel/lib/active_model/type/decimal.rb
index 6266933636..6c5c0451c6 100644
--- a/activemodel/lib/active_model/type/decimal.rb
+++ b/activemodel/lib/active_model/type/decimal.rb
@@ -16,18 +16,19 @@ module ActiveModel
private
def cast_value(value)
- casted_value = case value
- when ::Float
- convert_float_to_big_decimal(value)
- when ::Numeric, ::String
- BigDecimal(value, precision.to_i)
- else
- if value.respond_to?(:to_d)
- value.to_d
- else
- cast_value(value.to_s)
- end
- end
+ casted_value = \
+ case value
+ when ::Float
+ convert_float_to_big_decimal(value)
+ when ::Numeric, ::String
+ BigDecimal(value, precision.to_i)
+ else
+ if value.respond_to?(:to_d)
+ value.to_d
+ else
+ cast_value(value.to_s)
+ end
+ end
apply_scale(casted_value)
end
diff --git a/activemodel/lib/active_model/type/decimal_without_scale.rb b/activemodel/lib/active_model/type/decimal_without_scale.rb
deleted file mode 100644
index 985e1038ed..0000000000
--- a/activemodel/lib/active_model/type/decimal_without_scale.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-require "active_model/type/big_integer"
-
-module ActiveModel
- module Type
- class DecimalWithoutScale < BigInteger # :nodoc:
- def type
- :decimal
- end
- end
- end
-end
diff --git a/activemodel/lib/active_model/type/float.rb b/activemodel/lib/active_model/type/float.rb
index 94bb7e700c..4d0d2771a0 100644
--- a/activemodel/lib/active_model/type/float.rb
+++ b/activemodel/lib/active_model/type/float.rb
@@ -7,6 +7,15 @@ module ActiveModel
:float
end
+ def type_cast_for_schema(value)
+ return "::Float::NAN" if value.try(:nan?)
+ case value
+ when ::Float::INFINITY then "::Float::INFINITY"
+ when -::Float::INFINITY then "-::Float::INFINITY"
+ else super
+ end
+ end
+
alias serialize cast
private
diff --git a/activemodel/lib/active_model/type/helpers/accepts_multiparameter_time.rb b/activemodel/lib/active_model/type/helpers/accepts_multiparameter_time.rb
index facea12704..f783d286c5 100644
--- a/activemodel/lib/active_model/type/helpers/accepts_multiparameter_time.rb
+++ b/activemodel/lib/active_model/type/helpers/accepts_multiparameter_time.rb
@@ -1,7 +1,7 @@
module ActiveModel
module Type
- module Helpers
- class AcceptsMultiparameterTime < Module # :nodoc:
+ module Helpers # :nodoc: all
+ class AcceptsMultiparameterTime < Module
def initialize(defaults: {})
define_method(:cast) do |value|
if value.is_a?(Hash)
diff --git a/activemodel/lib/active_model/type/helpers/mutable.rb b/activemodel/lib/active_model/type/helpers/mutable.rb
index 4dddbe4e5e..f3a17a1698 100644
--- a/activemodel/lib/active_model/type/helpers/mutable.rb
+++ b/activemodel/lib/active_model/type/helpers/mutable.rb
@@ -1,7 +1,7 @@
module ActiveModel
module Type
- module Helpers
- module Mutable # :nodoc:
+ module Helpers # :nodoc: all
+ module Mutable
def cast(value)
deserialize(serialize(value))
end
diff --git a/activemodel/lib/active_model/type/helpers/numeric.rb b/activemodel/lib/active_model/type/helpers/numeric.rb
index ec1d1daf1a..275822b738 100644
--- a/activemodel/lib/active_model/type/helpers/numeric.rb
+++ b/activemodel/lib/active_model/type/helpers/numeric.rb
@@ -1,14 +1,15 @@
module ActiveModel
module Type
- module Helpers
- module Numeric # :nodoc:
+ module Helpers # :nodoc: all
+ module Numeric
def cast(value)
- value = case value
- when true then 1
- when false then 0
- when ::String then value.presence
- else value
- end
+ value = \
+ case value
+ when true then 1
+ when false then 0
+ when ::String then value.presence
+ else value
+ end
super(value)
end
diff --git a/activemodel/lib/active_model/type/helpers/time_value.rb b/activemodel/lib/active_model/type/helpers/time_value.rb
index 64780ebbdc..e57a52104b 100644
--- a/activemodel/lib/active_model/type/helpers/time_value.rb
+++ b/activemodel/lib/active_model/type/helpers/time_value.rb
@@ -2,8 +2,8 @@ require "active_support/core_ext/time/zones"
module ActiveModel
module Type
- module Helpers
- module TimeValue # :nodoc:
+ module Helpers # :nodoc: all
+ module TimeValue
def serialize(value)
value = apply_seconds_precision(value)
@@ -33,8 +33,8 @@ module ActiveModel
def apply_seconds_precision(value)
return value unless precision && value.respond_to?(:usec)
number_of_insignificant_digits = 6 - precision
- round_power = 10 ** number_of_insignificant_digits
- value.change(usec: value.usec / round_power * round_power)
+ round_power = 10**number_of_insignificant_digits
+ value.change(usec: value.usec - value.usec % round_power)
end
def type_cast_for_schema(value)
@@ -64,7 +64,7 @@ module ActiveModel
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
- # Doesn't handle time zones.
+ # Doesn't handle time zones.
def fast_string_to_time(string)
if string =~ ISO_DATETIME
microsec = ($7.to_r * 1_000_000).to_i
diff --git a/activemodel/lib/active_model/type/immutable_string.rb b/activemodel/lib/active_model/type/immutable_string.rb
index 41e4a2a1ac..58268540e5 100644
--- a/activemodel/lib/active_model/type/immutable_string.rb
+++ b/activemodel/lib/active_model/type/immutable_string.rb
@@ -17,11 +17,12 @@ module ActiveModel
private
def cast_value(value)
- result = case value
- when true then "t"
- when false then "f"
- else value.to_s
- end
+ result = \
+ case value
+ when true then "t"
+ when false then "f"
+ else value.to_s
+ end
result.freeze
end
end
diff --git a/activemodel/lib/active_model/type/integer.rb b/activemodel/lib/active_model/type/integer.rb
index 41dd655a5c..230e45ba60 100644
--- a/activemodel/lib/active_model/type/integer.rb
+++ b/activemodel/lib/active_model/type/integer.rb
@@ -29,6 +29,8 @@ module ActiveModel
result
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 :range
diff --git a/activemodel/lib/active_model/type/registry.rb b/activemodel/lib/active_model/type/registry.rb
index d25f1a129e..2d5dd366eb 100644
--- a/activemodel/lib/active_model/type/registry.rb
+++ b/activemodel/lib/active_model/type/registry.rb
@@ -21,6 +21,8 @@ module ActiveModel
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 :registrations
@@ -55,6 +57,8 @@ module ActiveModel
type_name == name
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 :name, :block
diff --git a/activemodel/lib/active_model/type/text.rb b/activemodel/lib/active_model/type/text.rb
deleted file mode 100644
index 7c0d647706..0000000000
--- a/activemodel/lib/active_model/type/text.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-require "active_model/type/string"
-
-module ActiveModel
- module Type
- class Text < String # :nodoc:
- def type
- :text
- end
- end
- end
-end
diff --git a/activemodel/lib/active_model/type/value.rb b/activemodel/lib/active_model/type/value.rb
index 4d3a1b29d6..7e9ae92245 100644
--- a/activemodel/lib/active_model/type/value.rb
+++ b/activemodel/lib/active_model/type/value.rb
@@ -105,9 +105,9 @@ module ActiveModel
private
- # Convenience method for types which do not need separate type casting
- # behavior for user and database inputs. Called by Value#cast for
- # values except +nil+.
+ # Convenience method for types which do not need separate type casting
+ # behavior for user and database inputs. Called by Value#cast for
+ # values except +nil+.
def cast_value(value) # :doc:
value
end
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index df6d3f2bcb..d460068830 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -50,7 +50,7 @@ module ActiveModel
define_callbacks :validate, scope: :name
class_attribute :_validators, instance_writer: false
- self._validators = Hash.new { |h,k| h[k] = [] }
+ self._validators = Hash.new { |h, k| h[k] = [] }
end
module ClassMethods
@@ -68,7 +68,7 @@ module ActiveModel
#
# Options:
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
- # Runs in all validation contexts by default (nil). You can pass a symbol
+ # Runs in all validation contexts by default +nil+. You can pass a symbol
# or an array of symbols. (e.g. <tt>on: :create</tt> or
# <tt>on: :custom_validation_context</tt> or
# <tt>on: [:create, :custom_validation_context]</tt>)
@@ -134,7 +134,7 @@ module ActiveModel
#
# Options:
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
- # Runs in all validation contexts by default (nil). You can pass a symbol
+ # Runs in all validation contexts by default +nil+. You can pass a symbol
# or an array of symbols. (e.g. <tt>on: :create</tt> or
# <tt>on: :custom_validation_context</tt> or
# <tt>on: [:create, :custom_validation_context]</tt>)
@@ -399,14 +399,14 @@ module ActiveModel
# end
alias :read_attribute_for_validation :send
- protected
+ private
- def run_validations! #:nodoc:
+ def run_validations!
_run_validate_callbacks
errors.empty?
end
- def raise_validation_error
+ def raise_validation_error # :doc:
raise(ValidationError.new(self))
end
end
diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb
index 4fadc795cd..e11005b9ba 100644
--- a/activemodel/lib/active_model/validations/acceptance.rb
+++ b/activemodel/lib/active_model/validations/acceptance.rb
@@ -24,7 +24,7 @@ module ActiveModel
class LazilyDefineAttributes < Module
def initialize(attribute_definition)
- define_method(:respond_to_missing?) do |method_name, include_private=false|
+ define_method(:respond_to_missing?) do |method_name, include_private = false|
super(method_name, include_private) || attribute_definition.matches?(method_name)
end
@@ -56,6 +56,8 @@ module ActiveModel
klass.send(:attr_writer, *attr_writers)
end
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
+ # Workaround for Ruby 2.2 "private attribute?" warning.
protected
attr_reader :attributes
diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb
index a201f72ed0..70bc1a0624 100644
--- a/activemodel/lib/active_model/validations/callbacks.rb
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -104,10 +104,10 @@ module ActiveModel
end
end
- protected
+ private
# Overwrite run validations to include callbacks.
- def run_validations! #:nodoc:
+ def run_validations!
_run_validation_callbacks { super }
end
end
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index e4ea42f8f4..fa183885ab 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -1,4 +1,3 @@
-require "active_support/core_ext/regexp"
module ActiveModel
module Validations
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index c924c8d2f8..de1524829e 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -6,7 +6,7 @@ module ActiveModel
MESSAGES = { is: :wrong_length, minimum: :too_short, maximum: :too_long }.freeze
CHECKS = { is: :==, minimum: :>=, maximum: :<= }.freeze
- RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :tokenizer, :too_short, :too_long]
+ RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :too_short, :too_long]
def initialize(options)
if range = (options.delete(:in) || options.delete(:within))
@@ -18,27 +18,6 @@ module ActiveModel
options[:minimum] = 1
end
- if options[:tokenizer]
- ActiveSupport::Deprecation.warn(<<-EOS.strip_heredoc)
- The `:tokenizer` option is deprecated, and will be removed in Rails 5.1.
- You can achieve the same functionality by defining an instance method
- with the value that you want to validate the length of. For example,
-
- validates_length_of :essay, minimum: 100,
- tokenizer: ->(str) { str.scan(/\w+/) }
-
- should be written as
-
- validates_length_of :words_in_essay, minimum: 100
-
- private
-
- def words_in_essay
- essay.scan(/\w+/)
- end
- EOS
- end
-
super
end
@@ -59,7 +38,6 @@ module ActiveModel
end
def validate_each(record, attribute, value)
- value = tokenize(record, value)
value_length = value.respond_to?(:length) ? value.length : value.to_s.length
errors_options = options.except(*RESERVED_OPTIONS)
@@ -80,17 +58,6 @@ module ActiveModel
end
private
- def tokenize(record, value)
- tokenizer = options[:tokenizer]
- if tokenizer && value.kind_of?(String)
- if tokenizer.kind_of?(Proc)
- tokenizer.call(value)
- elsif record.respond_to?(tokenizer)
- record.send(tokenizer, value)
- end
- end || value
- end
-
def skip_nil_check?(key)
key == :maximum && options[:allow_nil].nil? && options[:allow_blank].nil?
end
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index af27955554..2297faaee5 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -26,8 +26,6 @@ module ActiveModel
raw_value = value
end
- return if options[:allow_nil] && raw_value.nil?
-
unless is_number?(raw_value)
record.errors.add(attr_name, :not_a_number, filtered_options(raw_value))
return
@@ -63,29 +61,29 @@ module ActiveModel
end
end
- protected
+ private
- def is_number?(raw_value)
+ def is_number?(raw_value) # :doc:
!parse_raw_value_as_a_number(raw_value).nil?
rescue ArgumentError, TypeError
false
end
- def parse_raw_value_as_a_number(raw_value)
+ def parse_raw_value_as_a_number(raw_value) # :doc:
Kernel.Float(raw_value) if raw_value !~ /\A0[xX]/
end
- def is_integer?(raw_value)
+ def is_integer?(raw_value) # :doc:
/\A[+-]?\d+\z/ === raw_value.to_s
end
- def filtered_options(value)
+ def filtered_options(value) # :doc:
filtered = options.except(*RESERVED_OPTIONS)
filtered[:value] = value
filtered
end
- def allow_only_integer?(record)
+ def allow_only_integer?(record) # :doc:
case options[:only_integer]
when Symbol
record.send(options[:only_integer])
@@ -96,12 +94,10 @@ module ActiveModel
end
end
- private
-
- def record_attribute_changed_in_place?(record, attr_name)
- record.respond_to?(:attribute_changed_in_place?) &&
- record.attribute_changed_in_place?(attr_name.to_s)
- end
+ def record_attribute_changed_in_place?(record, attr_name)
+ record.respond_to?(:attribute_changed_in_place?) &&
+ record.attribute_changed_in_place?(attr_name.to_s)
+ end
end
module HelperMethods
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index c3cbb6e291..0ce5935f3a 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -72,7 +72,7 @@ module ActiveModel
# There is also a list of options that could be used along with validators:
#
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
- # Runs in all validation contexts by default (nil). You can pass a symbol
+ # Runs in all validation contexts by default +nil+. You can pass a symbol
# or an array of symbols. (e.g. <tt>on: :create</tt> or
# <tt>on: :custom_validation_context</tt> or
# <tt>on: [:create, :custom_validation_context]</tt>)
@@ -148,15 +148,15 @@ module ActiveModel
validates(*(attributes << options))
end
- protected
+ private
# When creating custom validators, it might be useful to be able to specify
# additional default keys. This can be done by overwriting this method.
- def _validates_default_keys # :nodoc:
+ def _validates_default_keys
[:if, :unless, :on, :allow_blank, :allow_nil , :strict]
end
- def _parse_validates_options(options) # :nodoc:
+ def _parse_validates_options(options)
case options
when TrueClass
{}
diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb
index 6de01b3392..e3f7a9bcb2 100644
--- a/activemodel/lib/active_model/validations/with.rb
+++ b/activemodel/lib/active_model/validations/with.rb
@@ -1,3 +1,5 @@
+require "active_support/core_ext/array/extract_options"
+
module ActiveModel
module Validations
class WithValidator < EachValidator # :nodoc:
@@ -43,7 +45,7 @@ module ActiveModel
#
# Configuration options:
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
- # Runs in all validation contexts by default (nil). You can pass a symbol
+ # Runs in all validation contexts by default +nil+. You can pass a symbol
# or an array of symbols. (e.g. <tt>on: :create</tt> or
# <tt>on: :custom_validation_context</tt> or
# <tt>on: [:create, :custom_validation_context]</tt>)
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index 9c6065f61f..8212744170 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -104,7 +104,7 @@ module ActiveModel
# Accepts options that will be made available through the +options+ reader.
def initialize(options = {})
- @options = options.except(:class).freeze
+ @options = options.except(:class).freeze
end
# Returns the kind for this validator.
diff --git a/activemodel/test/cases/attribute_assignment_test.rb b/activemodel/test/cases/attribute_assignment_test.rb
index 8eb389d331..fa41d1b95f 100644
--- a/activemodel/test/cases/attribute_assignment_test.rb
+++ b/activemodel/test/cases/attribute_assignment_test.rb
@@ -16,6 +16,8 @@ class AttributeAssignmentTest < ActiveModel::TestCase
raise ErrorFromAttributeWriter
end
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
+ # Workaround for Ruby 2.2 "private attribute?" warning.
protected
attr_writer :metadata
diff --git a/activemodel/test/cases/attribute_methods_test.rb b/activemodel/test/cases/attribute_methods_test.rb
index f59180cceb..4767accb7c 100644
--- a/activemodel/test/cases/attribute_methods_test.rb
+++ b/activemodel/test/cases/attribute_methods_test.rb
@@ -48,7 +48,7 @@ class ModelWithAttributesWithSpaces
include ActiveModel::AttributeMethods
def attributes
- { 'foo bar': "value of foo bar"}
+ { 'foo bar': "value of foo bar" }
end
private
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index da99a0eca0..e6ba06301d 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require "yaml"
class ErrorsTest < ActiveModel::TestCase
class Person
@@ -30,14 +31,15 @@ class ErrorsTest < ActiveModel::TestCase
def test_delete
errors = ActiveModel::Errors.new(self)
errors[:foo] << "omg"
- errors.delete(:foo)
+ errors.delete("foo")
assert_empty errors[:foo]
end
def test_include?
errors = ActiveModel::Errors.new(self)
errors[:foo] << "omg"
- assert errors.include?(:foo), "errors should include :foo"
+ assert_includes errors, :foo, "errors should include :foo"
+ assert_includes errors, "foo", "errors should include 'foo' as :foo"
end
def test_dup
@@ -52,6 +54,7 @@ class ErrorsTest < ActiveModel::TestCase
errors = ActiveModel::Errors.new(self)
errors[:foo] << "omg"
assert_equal true, errors.has_key?(:foo), "errors should have key :foo"
+ assert_equal true, errors.has_key?("foo"), "errors should have key 'foo' as :foo"
end
def test_has_no_key
@@ -63,6 +66,7 @@ class ErrorsTest < ActiveModel::TestCase
errors = ActiveModel::Errors.new(self)
errors[:foo] << "omg"
assert_equal true, errors.key?(:foo), "errors should have key :foo"
+ assert_equal true, errors.key?("foo"), "errors should have key 'foo' as :foo"
end
def test_no_key
@@ -79,24 +83,6 @@ class ErrorsTest < ActiveModel::TestCase
assert person.errors.empty?
end
- test "get returns the errors for the provided key" do
- errors = ActiveModel::Errors.new(self)
- errors[:foo] << "omg"
-
- assert_deprecated do
- assert_equal ["omg"], errors.get(:foo)
- end
- end
-
- test "sets the error with the provided key" do
- errors = ActiveModel::Errors.new(self)
- assert_deprecated do
- errors.set(:foo, "omg")
- end
-
- assert_equal({ foo: "omg" }, errors.messages)
- end
-
test "error access is indifferent" do
errors = ActiveModel::Errors.new(self)
errors[:foo] << "omg"
@@ -125,7 +111,7 @@ class ErrorsTest < ActiveModel::TestCase
person.errors[:foo]
assert person.errors.empty?
assert person.errors.blank?
- assert !person.errors.include?(:foo)
+ assert_not_includes person.errors, :foo
end
test "include? does not add a key to messages hash" do
@@ -142,14 +128,6 @@ class ErrorsTest < ActiveModel::TestCase
assert_equal ["cannot be nil"], person.errors[:name]
end
- test "assign error" do
- person = Person.new
- assert_deprecated do
- person.errors[:name] = "should not be nil"
- end
- assert_equal ["should not be nil"], person.errors[:name]
- end
-
test "add an error message on a specific attribute" do
person = Person.new
person.errors.add(:name, "cannot be blank")
@@ -176,10 +154,11 @@ class ErrorsTest < ActiveModel::TestCase
assert_equal ["cannot be blank"], person.errors[:name]
end
- test "added? detects if a specific error was added to the object" do
+ test "added? detects indifferent if a specific error was added to the object" do
person = Person.new
person.errors.add(:name, "cannot be blank")
assert person.errors.added?(:name, "cannot be blank")
+ assert person.errors.added?("name", "cannot be blank")
end
test "added? handles symbol message" do
@@ -250,6 +229,16 @@ class ErrorsTest < ActiveModel::TestCase
assert_equal({ name: ["cannot be blank"] }, person.errors.to_hash)
end
+ test "to_hash returns a hash without default proc" do
+ person = Person.new
+ assert_nil person.errors.to_hash.default_proc
+ end
+
+ test "as_json returns a hash without default proc" do
+ person = Person.new
+ assert_nil person.errors.as_json.default_proc
+ end
+
test "full_messages creates a list of error messages with the attribute name included" do
person = Person.new
person.errors.add(:name, "cannot be blank")
@@ -257,7 +246,7 @@ class ErrorsTest < ActiveModel::TestCase
assert_equal ["name cannot be blank", "name cannot be nil"], person.errors.full_messages
end
- test "full_messages_for contains all the error messages for the given attribute" do
+ test "full_messages_for contains all the error messages for the given attribute indifferent" do
person = Person.new
person.errors.add(:name, "cannot be blank")
person.errors.add(:name, "cannot be nil")
@@ -269,6 +258,7 @@ class ErrorsTest < ActiveModel::TestCase
person.errors.add(:name, "cannot be blank")
person.errors.add(:email, "cannot be blank")
assert_equal ["name cannot be blank"], person.errors.full_messages_for(:name)
+ assert_equal ["name cannot be blank"], person.errors.full_messages_for("name")
end
test "full_messages_for returns an empty list in case there are no errors for the given attribute" do
@@ -310,72 +300,6 @@ class ErrorsTest < ActiveModel::TestCase
}
end
- test "add_on_empty generates message" do
- person = Person.new
- assert_called_with(person.errors, :generate_message, [:name, :empty, {}]) do
- assert_deprecated do
- person.errors.add_on_empty :name
- end
- end
- end
-
- test "add_on_empty generates message for multiple attributes" do
- person = Person.new
- expected_calls = [ [:name, :empty, {}], [:age, :empty, {}] ]
- assert_called_with(person.errors, :generate_message, expected_calls) do
- assert_deprecated do
- person.errors.add_on_empty [:name, :age]
- end
- end
- end
-
- test "add_on_empty generates message with custom default message" do
- person = Person.new
- assert_called_with(person.errors, :generate_message, [:name, :empty, { message: "custom" }]) do
- assert_deprecated do
- person.errors.add_on_empty :name, message: "custom"
- end
- end
- end
-
- test "add_on_empty generates message with empty string value" do
- person = Person.new
- person.name = ""
- assert_called_with(person.errors, :generate_message, [:name, :empty, {}]) do
- assert_deprecated do
- person.errors.add_on_empty :name
- end
- end
- end
-
- test "add_on_blank generates message" do
- person = Person.new
- assert_called_with(person.errors, :generate_message, [:name, :blank, {}]) do
- assert_deprecated do
- person.errors.add_on_blank :name
- end
- end
- end
-
- test "add_on_blank generates message for multiple attributes" do
- person = Person.new
- expected_calls = [ [:name, :blank, {}], [:age, :blank, {}] ]
- assert_called_with(person.errors, :generate_message, expected_calls) do
- assert_deprecated do
- person.errors.add_on_blank [:name, :age]
- end
- end
- end
-
- test "add_on_blank generates message with custom default message" do
- person = Person.new
- assert_called_with(person.errors, :generate_message, [:name, :blank, { message: "custom" }]) do
- assert_deprecated do
- person.errors.add_on_blank :name, message: "custom"
- end
- end
- end
-
test "details returns added error detail" do
person = Person.new
person.errors.add(:name, :invalid)
@@ -442,4 +366,24 @@ class ErrorsTest < ActiveModel::TestCase
assert_equal errors.messages, serialized.messages
assert_equal errors.details, serialized.details
end
+
+ test "errors are backward compatible with the Rails 4.2 format" do
+ yaml = <<-CODE.strip_heredoc
+ --- !ruby/object:ActiveModel::Errors
+ base: &1 !ruby/object:ErrorsTest::Person
+ errors: !ruby/object:ActiveModel::Errors
+ base: *1
+ messages: {}
+ messages: {}
+ CODE
+
+ errors = YAML.load(yaml)
+ errors.add(:name, :invalid)
+ assert_equal({ name: ["is invalid"] }, errors.messages)
+ assert_equal({ name: [{ error: :invalid }] }, errors.details)
+
+ errors.clear
+ assert_equal({}, errors.messages)
+ assert_equal({}, errors.details)
+ end
end
diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb
index 9423df2c09..77ce86f392 100644
--- a/activemodel/test/cases/secure_password_test.rb
+++ b/activemodel/test/cases/secure_password_test.rb
@@ -179,7 +179,7 @@ class SecurePasswordTest < ActiveModel::TestCase
test "setting a nil password should clear an existing password" do
@existing_user.password = nil
- assert_equal nil, @existing_user.password_digest
+ assert_nil @existing_user.password_digest
end
test "authenticate" do
diff --git a/activemodel/test/cases/serialization_test.rb b/activemodel/test/cases/serialization_test.rb
index cead0bee22..f78efd2f0c 100644
--- a/activemodel/test/cases/serialization_test.rb
+++ b/activemodel/test/cases/serialization_test.rb
@@ -51,32 +51,32 @@ class SerializationTest < ActiveModel::TestCase
end
def test_method_serializable_hash_should_work
- expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"}
+ expected = { "name" => "David", "gender" => "male", "email" => "david@example.com" }
assert_equal expected, @user.serializable_hash
end
def test_method_serializable_hash_should_work_with_only_option
- expected = {"name"=>"David"}
+ expected = { "name" => "David" }
assert_equal expected, @user.serializable_hash(only: [:name])
end
def test_method_serializable_hash_should_work_with_except_option
- expected = {"gender"=>"male", "email"=>"david@example.com"}
+ expected = { "gender" => "male", "email" => "david@example.com" }
assert_equal expected, @user.serializable_hash(except: [:name])
end
def test_method_serializable_hash_should_work_with_methods_option
- expected = {"name"=>"David", "gender"=>"male", "foo"=>"i_am_foo", "bar"=>"i_am_bar", "email"=>"david@example.com"}
+ expected = { "name" => "David", "gender" => "male", "foo" => "i_am_foo", "bar" => "i_am_bar", "email" => "david@example.com" }
assert_equal expected, @user.serializable_hash(methods: [:foo, :bar])
end
def test_method_serializable_hash_should_work_with_only_and_methods
- expected = {"foo"=>"i_am_foo", "bar"=>"i_am_bar"}
+ expected = { "foo" => "i_am_foo", "bar" => "i_am_bar" }
assert_equal expected, @user.serializable_hash(only: [], methods: [:foo, :bar])
end
def test_method_serializable_hash_should_work_with_except_and_methods
- expected = {"gender"=>"male", "foo"=>"i_am_foo", "bar"=>"i_am_bar"}
+ expected = { "gender" => "male", "foo" => "i_am_foo", "bar" => "i_am_bar" }
assert_equal expected, @user.serializable_hash(except: [:name, :email], methods: [:foo, :bar])
end
@@ -94,21 +94,21 @@ class SerializationTest < ActiveModel::TestCase
end
def test_include_option_with_singular_association
- expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com",
- "address"=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}}
+ expected = { "name" => "David", "gender" => "male", "email" => "david@example.com",
+ "address" => { "street" => "123 Lane", "city" => "Springfield", "state" => "CA", "zip" => 11111 } }
assert_equal expected, @user.serializable_hash(include: :address)
end
def test_include_option_with_plural_association
- expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
- "friends"=>[{"name"=>"Joe", "email"=>"joe@example.com", "gender"=>"male"},
- {"name"=>"Sue", "email"=>"sue@example.com", "gender"=>"female"}]}
+ expected = { "email" => "david@example.com", "gender" => "male", "name" => "David",
+ "friends" => [{ "name" => "Joe", "email" => "joe@example.com", "gender" => "male" },
+ { "name" => "Sue", "email" => "sue@example.com", "gender" => "female" }] }
assert_equal expected, @user.serializable_hash(include: :friends)
end
def test_include_option_with_empty_association
@user.friends = []
- expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", "friends"=>[]}
+ expected = { "email" => "david@example.com", "gender" => "male", "name" => "David", "friends" => [] }
assert_equal expected, @user.serializable_hash(include: :friends)
end
@@ -124,52 +124,52 @@ class SerializationTest < ActiveModel::TestCase
def test_include_option_with_ary
@user.friends = FriendList.new(@user.friends)
- expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
- "friends"=>[{"name"=>"Joe", "email"=>"joe@example.com", "gender"=>"male"},
- {"name"=>"Sue", "email"=>"sue@example.com", "gender"=>"female"}]}
+ expected = { "email" => "david@example.com", "gender" => "male", "name" => "David",
+ "friends" => [{ "name" => "Joe", "email" => "joe@example.com", "gender" => "male" },
+ { "name" => "Sue", "email" => "sue@example.com", "gender" => "female" }] }
assert_equal expected, @user.serializable_hash(include: :friends)
end
def test_multiple_includes
- expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
- "address"=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111},
- "friends"=>[{"name"=>"Joe", "email"=>"joe@example.com", "gender"=>"male"},
- {"name"=>"Sue", "email"=>"sue@example.com", "gender"=>"female"}]}
+ expected = { "email" => "david@example.com", "gender" => "male", "name" => "David",
+ "address" => { "street" => "123 Lane", "city" => "Springfield", "state" => "CA", "zip" => 11111 },
+ "friends" => [{ "name" => "Joe", "email" => "joe@example.com", "gender" => "male" },
+ { "name" => "Sue", "email" => "sue@example.com", "gender" => "female" }] }
assert_equal expected, @user.serializable_hash(include: [:address, :friends])
end
def test_include_with_options
- expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
- "address"=>{"street"=>"123 Lane"}}
+ expected = { "email" => "david@example.com", "gender" => "male", "name" => "David",
+ "address" => { "street" => "123 Lane" } }
assert_equal expected, @user.serializable_hash(include: { address: { only: "street" } })
end
def test_nested_include
@user.friends.first.friends = [@user]
- expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
- "friends"=>[{"name"=>"Joe", "email"=>"joe@example.com", "gender"=>"male",
- "friends"=> [{"email"=>"david@example.com", "gender"=>"male", "name"=>"David"}]},
- {"name"=>"Sue", "email"=>"sue@example.com", "gender"=>"female", "friends"=> []}]}
+ expected = { "email" => "david@example.com", "gender" => "male", "name" => "David",
+ "friends" => [{ "name" => "Joe", "email" => "joe@example.com", "gender" => "male",
+ "friends" => [{ "email" => "david@example.com", "gender" => "male", "name" => "David" }] },
+ { "name" => "Sue", "email" => "sue@example.com", "gender" => "female", "friends" => [] }] }
assert_equal expected, @user.serializable_hash(include: { friends: { include: :friends } })
end
def test_only_include
- expected = {"name"=>"David", "friends" => [{"name" => "Joe"}, {"name" => "Sue"}]}
+ expected = { "name" => "David", "friends" => [{ "name" => "Joe" }, { "name" => "Sue" }] }
assert_equal expected, @user.serializable_hash(only: :name, include: { friends: { only: :name } })
end
def test_except_include
- expected = {"name"=>"David", "email"=>"david@example.com",
- "friends"=> [{"name" => "Joe", "email" => "joe@example.com"},
- {"name" => "Sue", "email" => "sue@example.com"}]}
+ expected = { "name" => "David", "email" => "david@example.com",
+ "friends" => [{ "name" => "Joe", "email" => "joe@example.com" },
+ { "name" => "Sue", "email" => "sue@example.com" }] }
assert_equal expected, @user.serializable_hash(except: :gender, include: { friends: { except: :gender } })
end
def test_multiple_includes_with_options
- expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
- "address"=>{"street"=>"123 Lane"},
- "friends"=>[{"name"=>"Joe", "email"=>"joe@example.com", "gender"=>"male"},
- {"name"=>"Sue", "email"=>"sue@example.com", "gender"=>"female"}]}
- assert_equal expected, @user.serializable_hash(include: [{ address: {only: "street" } }, :friends])
+ expected = { "email" => "david@example.com", "gender" => "male", "name" => "David",
+ "address" => { "street" => "123 Lane" },
+ "friends" => [{ "name" => "Joe", "email" => "joe@example.com", "gender" => "male" },
+ { "name" => "Sue", "email" => "sue@example.com", "gender" => "female" }] }
+ assert_equal expected, @user.serializable_hash(include: [{ address: { only: "street" } }, :friends])
end
end
diff --git a/activemodel/test/cases/serializers/json_serialization_test.rb b/activemodel/test/cases/serializers/json_serialization_test.rb
index 45c166f33a..d15ba64eb0 100644
--- a/activemodel/test/cases/serializers/json_serialization_test.rb
+++ b/activemodel/test/cases/serializers/json_serialization_test.rb
@@ -18,7 +18,7 @@ class JsonSerializationTest < ActiveModel::TestCase
assert_no_match %r{^\{"contact":\{}, json
assert_match %r{"name":"Konata Izumi"}, json
assert_match %r{"age":16}, json
- assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
+ assert_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})
assert_match %r{"awesome":true}, json
assert_match %r{"preferences":\{"shows":"anime"\}}, json
end
@@ -32,7 +32,7 @@ class JsonSerializationTest < ActiveModel::TestCase
assert_match %r{^\{"contact":\{}, json
assert_match %r{"name":"Konata Izumi"}, json
assert_match %r{"age":16}, json
- assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
+ assert_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})
assert_match %r{"awesome":true}, json
assert_match %r{"preferences":\{"shows":"anime"\}}, json
ensure
@@ -58,7 +58,7 @@ class JsonSerializationTest < ActiveModel::TestCase
assert_match %r{^\{"json_contact":\{}, json
assert_match %r{"name":"Konata Izumi"}, json
assert_match %r{"age":16}, json
- assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
+ assert_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})
assert_match %r{"awesome":true}, json
assert_match %r{"preferences":\{"shows":"anime"\}}, json
end
@@ -68,7 +68,7 @@ class JsonSerializationTest < ActiveModel::TestCase
assert_match %r{"name":"Konata Izumi"}, json
assert_match %r{"age":16}, json
- assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
+ assert_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})
assert_match %r{"awesome":true}, json
assert_match %r{"preferences":\{"shows":"anime"\}}, json
end
@@ -79,7 +79,7 @@ class JsonSerializationTest < ActiveModel::TestCase
assert_match %r{"name":"Konata Izumi"}, json
assert_match %r{"age":16}, json
assert_no_match %r{"awesome":true}, json
- assert !json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
+ assert_not_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})
assert_no_match %r{"preferences":\{"shows":"anime"\}}, json
end
@@ -89,7 +89,7 @@ class JsonSerializationTest < ActiveModel::TestCase
assert_no_match %r{"name":"Konata Izumi"}, json
assert_no_match %r{"age":16}, json
assert_match %r{"awesome":true}, json
- assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
+ assert_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})
assert_match %r{"preferences":\{"shows":"anime"\}}, json
end
diff --git a/activemodel/test/cases/translation_test.rb b/activemodel/test/cases/translation_test.rb
index 1717e0c7ce..9972f9daea 100644
--- a/activemodel/test/cases/translation_test.rb
+++ b/activemodel/test/cases/translation_test.rb
@@ -38,23 +38,23 @@ class ActiveModelI18nTests < ActiveModel::TestCase
end
def test_translated_model_attributes_with_symbols
- I18n.backend.store_translations "en", activemodel: { attributes: { person: { name: "person name attribute"} } }
+ I18n.backend.store_translations "en", activemodel: { attributes: { person: { name: "person name attribute" } } }
assert_equal "person name attribute", Person.human_attribute_name(:name)
end
def test_translated_model_attributes_with_ancestor
- I18n.backend.store_translations "en", activemodel: { attributes: { child: { name: "child name attribute"} } }
+ I18n.backend.store_translations "en", activemodel: { attributes: { child: { name: "child name attribute" } } }
assert_equal "child name attribute", Child.human_attribute_name("name")
end
def test_translated_model_attributes_with_ancestors_fallback
- I18n.backend.store_translations "en", activemodel: { attributes: { person: { name: "person name attribute"} } }
+ I18n.backend.store_translations "en", activemodel: { attributes: { person: { name: "person name attribute" } } }
assert_equal "person name attribute", Child.human_attribute_name("name")
end
def test_translated_model_attributes_with_attribute_matching_namespaced_model_name
I18n.backend.store_translations "en", activemodel: { attributes: {
- person: { gender: "person gender"},
+ person: { gender: "person gender" },
"person/gender": { attribute: "person gender attribute" }
} }
diff --git a/activemodel/test/cases/type/big_integer_test.rb b/activemodel/test/cases/type/big_integer_test.rb
new file mode 100644
index 0000000000..56002b7cc6
--- /dev/null
+++ b/activemodel/test/cases/type/big_integer_test.rb
@@ -0,0 +1,24 @@
+require "cases/helper"
+require "active_model/type"
+
+module ActiveModel
+ module Type
+ class BigIntegerTest < ActiveModel::TestCase
+ def test_type_cast_big_integer
+ type = Type::BigInteger.new
+ assert_equal 1, type.cast(1)
+ assert_equal 1, type.cast("1")
+ end
+
+ def test_small_values
+ type = Type::BigInteger.new
+ assert_equal(-9999999999999999999999999999999, type.serialize(-9999999999999999999999999999999))
+ end
+
+ def test_large_values
+ type = Type::BigInteger.new
+ assert_equal 9999999999999999999999999999999, type.serialize(9999999999999999999999999999999)
+ end
+ end
+ end
+end
diff --git a/activemodel/test/cases/type/binary_test.rb b/activemodel/test/cases/type/binary_test.rb
new file mode 100644
index 0000000000..e9c2ccfca4
--- /dev/null
+++ b/activemodel/test/cases/type/binary_test.rb
@@ -0,0 +1,15 @@
+require "cases/helper"
+require "active_model/type"
+
+module ActiveModel
+ module Type
+ class BinaryTest < ActiveModel::TestCase
+ def test_type_cast_binary
+ type = Type::Binary.new
+ assert_nil type.cast(nil)
+ assert_equal "1", type.cast("1")
+ assert_equal 1, type.cast(1)
+ end
+ end
+ end
+end
diff --git a/activemodel/test/cases/type/boolean_test.rb b/activemodel/test/cases/type/boolean_test.rb
new file mode 100644
index 0000000000..92e5aebfb7
--- /dev/null
+++ b/activemodel/test/cases/type/boolean_test.rb
@@ -0,0 +1,39 @@
+require "cases/helper"
+require "active_model/type"
+
+module ActiveModel
+ module Type
+ class BooleanTest < ActiveModel::TestCase
+ def test_type_cast_boolean
+ type = Type::Boolean.new
+ assert type.cast("").nil?
+ assert type.cast(nil).nil?
+
+ assert type.cast(true)
+ assert type.cast(1)
+ assert type.cast("1")
+ assert type.cast("t")
+ assert type.cast("T")
+ assert type.cast("true")
+ assert type.cast("TRUE")
+ assert type.cast("on")
+ assert type.cast("ON")
+ assert type.cast(" ")
+ assert type.cast("\u3000\r\n")
+ assert type.cast("\u0000")
+ assert type.cast("SOMETHING RANDOM")
+
+ # explicitly check for false vs nil
+ assert_equal false, type.cast(false)
+ assert_equal false, type.cast(0)
+ assert_equal false, type.cast("0")
+ assert_equal false, type.cast("f")
+ assert_equal false, type.cast("F")
+ assert_equal false, type.cast("false")
+ assert_equal false, type.cast("FALSE")
+ assert_equal false, type.cast("off")
+ assert_equal false, type.cast("OFF")
+ end
+ end
+ end
+end
diff --git a/activemodel/test/cases/type/date_test.rb b/activemodel/test/cases/type/date_test.rb
new file mode 100644
index 0000000000..0cc90e99d3
--- /dev/null
+++ b/activemodel/test/cases/type/date_test.rb
@@ -0,0 +1,19 @@
+require "cases/helper"
+require "active_model/type"
+
+module ActiveModel
+ module Type
+ class DateTest < ActiveModel::TestCase
+ def test_type_cast_date
+ type = Type::Date.new
+ assert_nil type.cast(nil)
+ assert_nil type.cast("")
+ assert_nil type.cast(" ")
+ assert_nil type.cast("ABC")
+
+ date_string = ::Time.now.utc.strftime("%F")
+ assert_equal date_string, type.cast(date_string).strftime("%F")
+ end
+ end
+ end
+end
diff --git a/activemodel/test/cases/type/date_time_test.rb b/activemodel/test/cases/type/date_time_test.rb
new file mode 100644
index 0000000000..75a7fc686e
--- /dev/null
+++ b/activemodel/test/cases/type/date_time_test.rb
@@ -0,0 +1,38 @@
+require "cases/helper"
+require "active_model/type"
+
+module ActiveModel
+ module Type
+ class DateTimeTest < ActiveModel::TestCase
+ def test_type_cast_datetime_and_timestamp
+ type = Type::DateTime.new
+ assert_nil type.cast(nil)
+ assert_nil type.cast("")
+ assert_nil type.cast(" ")
+ assert_nil type.cast("ABC")
+
+ datetime_string = ::Time.now.utc.strftime("%FT%T")
+ assert_equal datetime_string, type.cast(datetime_string).strftime("%FT%T")
+ end
+
+ def test_string_to_time_with_timezone
+ ["UTC", "US/Eastern"].each do |zone|
+ with_timezone_config default: zone do
+ type = Type::DateTime.new
+ assert_equal ::Time.utc(2013, 9, 4, 0, 0, 0), type.cast("Wed, 04 Sep 2013 03:00:00 EAT")
+ end
+ end
+ end
+
+ private
+
+ def with_timezone_config(default:)
+ old_zone_default = ::Time.zone_default
+ ::Time.zone_default = ::Time.find_zone(default)
+ yield
+ ensure
+ ::Time.zone_default = old_zone_default
+ end
+ end
+ end
+end
diff --git a/activemodel/test/cases/type/float_test.rb b/activemodel/test/cases/type/float_test.rb
new file mode 100644
index 0000000000..2e34f57f7e
--- /dev/null
+++ b/activemodel/test/cases/type/float_test.rb
@@ -0,0 +1,22 @@
+require "cases/helper"
+require "active_model/type"
+
+module ActiveModel
+ module Type
+ class FloatTest < ActiveModel::TestCase
+ def test_type_cast_float
+ type = Type::Float.new
+ assert_equal 1.0, type.cast("1")
+ end
+
+ def test_changing_float
+ type = Type::Float.new
+
+ assert type.changed?(5.0, 5.0, "5wibble")
+ assert_not type.changed?(5.0, 5.0, "5")
+ assert_not type.changed?(5.0, 5.0, "5.0")
+ assert_not type.changed?(nil, nil, nil)
+ end
+ end
+ end
+end
diff --git a/activemodel/test/cases/type/immutable_string_test.rb b/activemodel/test/cases/type/immutable_string_test.rb
new file mode 100644
index 0000000000..23e58974fb
--- /dev/null
+++ b/activemodel/test/cases/type/immutable_string_test.rb
@@ -0,0 +1,21 @@
+require "cases/helper"
+require "active_model/type"
+
+module ActiveModel
+ module Type
+ class ImmutableStringTest < ActiveModel::TestCase
+ test "cast strings are frozen" do
+ s = "foo"
+ type = Type::ImmutableString.new
+ assert_equal true, type.cast(s).frozen?
+ end
+
+ test "immutable strings are not duped coming out" do
+ s = "foo"
+ type = Type::ImmutableString.new
+ assert_same s, type.cast(s)
+ assert_same s, type.deserialize(s)
+ end
+ end
+ end
+end
diff --git a/activemodel/test/cases/type/integer_test.rb b/activemodel/test/cases/type/integer_test.rb
index e00246b602..2b9b03f3cf 100644
--- a/activemodel/test/cases/type/integer_test.rb
+++ b/activemodel/test/cases/type/integer_test.rb
@@ -1,5 +1,6 @@
require "cases/helper"
require "active_model/type"
+require "active_support/core_ext/numeric/time"
module ActiveModel
module Type
@@ -19,7 +20,7 @@ module ActiveModel
test "random objects cast to nil" do
type = Type::Integer.new
- assert_nil type.cast([1,2])
+ assert_nil type.cast([1, 2])
assert_nil type.cast(1 => 2)
assert_nil type.cast(1..2)
end
@@ -32,7 +33,7 @@ module ActiveModel
test "casting nan and infinity" do
type = Type::Integer.new
assert_nil type.cast(::Float::NAN)
- assert_nil type.cast(1.0/0.0)
+ assert_nil type.cast(1.0 / 0.0)
end
test "casting booleans for database" do
@@ -41,6 +42,12 @@ module ActiveModel
assert_equal 0, type.serialize(false)
end
+ test "casting duration" do
+ type = Type::Integer.new
+ assert_equal 1800, type.cast(30.minutes)
+ assert_equal 7200, type.cast(2.hours)
+ end
+
test "changed?" do
type = Type::Integer.new
diff --git a/activemodel/test/cases/type/registry_test.rb b/activemodel/test/cases/type/registry_test.rb
index 2a48998a62..927b6d0307 100644
--- a/activemodel/test/cases/type/registry_test.rb
+++ b/activemodel/test/cases/type/registry_test.rb
@@ -2,38 +2,40 @@ require "cases/helper"
require "active_model/type"
module ActiveModel
- class RegistryTest < ActiveModel::TestCase
- test "a class can be registered for a symbol" do
- registry = Type::Registry.new
- registry.register(:foo, ::String)
- registry.register(:bar, ::Array)
+ module Type
+ class RegistryTest < ActiveModel::TestCase
+ test "a class can be registered for a symbol" do
+ registry = Type::Registry.new
+ registry.register(:foo, ::String)
+ registry.register(:bar, ::Array)
- assert_equal "", registry.lookup(:foo)
- assert_equal [], registry.lookup(:bar)
- end
-
- test "a block can be registered" do
- registry = Type::Registry.new
- registry.register(:foo) do |*args|
- [*args, "block for foo"]
+ assert_equal "", registry.lookup(:foo)
+ assert_equal [], registry.lookup(:bar)
end
- registry.register(:bar) do |*args|
- [*args, "block for bar"]
+
+ test "a block can be registered" do
+ registry = Type::Registry.new
+ registry.register(:foo) do |*args|
+ [*args, "block for foo"]
+ end
+ registry.register(:bar) do |*args|
+ [*args, "block for bar"]
+ end
+
+ assert_equal [:foo, 1, "block for foo"], registry.lookup(:foo, 1)
+ assert_equal [:foo, 2, "block for foo"], registry.lookup(:foo, 2)
+ assert_equal [:bar, 1, 2, 3, "block for bar"], registry.lookup(:bar, 1, 2, 3)
end
- assert_equal [:foo, 1, "block for foo"], registry.lookup(:foo, 1)
- assert_equal [:foo, 2, "block for foo"], registry.lookup(:foo, 2)
- assert_equal [:bar, 1, 2, 3, "block for bar"], registry.lookup(:bar, 1, 2, 3)
- end
+ test "a reasonable error is given when no type is found" do
+ registry = Type::Registry.new
- test "a reasonable error is given when no type is found" do
- registry = Type::Registry.new
+ e = assert_raises(ArgumentError) do
+ registry.lookup(:foo)
+ end
- e = assert_raises(ArgumentError) do
- registry.lookup(:foo)
+ assert_equal "Unknown type :foo", e.message
end
-
- assert_equal "Unknown type :foo", e.message
end
end
end
diff --git a/activemodel/test/cases/type/string_test.rb b/activemodel/test/cases/type/string_test.rb
index 7b25a1ef74..222083817e 100644
--- a/activemodel/test/cases/type/string_test.rb
+++ b/activemodel/test/cases/type/string_test.rb
@@ -2,26 +2,27 @@ require "cases/helper"
require "active_model/type"
module ActiveModel
- class StringTypeTest < ActiveModel::TestCase
- test "type casting" do
- type = Type::String.new
- assert_equal "t", type.cast(true)
- assert_equal "f", type.cast(false)
- assert_equal "123", type.cast(123)
- end
+ module Type
+ class StringTest < ActiveModel::TestCase
+ test "type casting" do
+ type = Type::String.new
+ assert_equal "t", type.cast(true)
+ assert_equal "f", type.cast(false)
+ assert_equal "123", type.cast(123)
+ end
- test "immutable strings are not duped coming out" do
- s = "foo"
- type = Type::ImmutableString.new
- assert_same s, type.cast(s)
- assert_same s, type.deserialize(s)
- end
+ test "cast strings are mutable" do
+ s = "foo"
+ type = Type::String.new
+ assert_equal false, type.cast(s).frozen?
+ end
- test "values are duped coming out" do
- s = "foo"
- type = Type::String.new
- assert_not_same s, type.cast(s)
- assert_not_same s, type.deserialize(s)
+ test "values are duped coming out" do
+ s = "foo"
+ type = Type::String.new
+ assert_not_same s, type.cast(s)
+ assert_not_same s, type.deserialize(s)
+ end
end
end
end
diff --git a/activemodel/test/cases/type/time_test.rb b/activemodel/test/cases/type/time_test.rb
new file mode 100644
index 0000000000..0cc4d33caa
--- /dev/null
+++ b/activemodel/test/cases/type/time_test.rb
@@ -0,0 +1,21 @@
+require "cases/helper"
+require "active_model/type"
+
+module ActiveModel
+ module Type
+ class TimeTest < ActiveModel::TestCase
+ def test_type_cast_time
+ type = Type::Time.new
+ assert_nil type.cast(nil)
+ assert_nil type.cast("")
+ assert_nil type.cast("ABC")
+
+ time_string = ::Time.now.utc.strftime("%T")
+ assert_equal time_string, type.cast(time_string).strftime("%T")
+
+ assert_equal ::Time.utc(2000, 1, 1, 16, 45, 54), type.cast("2015-06-13T19:45:54+03:00")
+ assert_equal ::Time.utc(1999, 12, 31, 21, 7, 8), type.cast("06:07:08+09:00")
+ end
+ end
+ end
+end
diff --git a/activemodel/test/cases/type/value_test.rb b/activemodel/test/cases/type/value_test.rb
new file mode 100644
index 0000000000..d8b3e7f164
--- /dev/null
+++ b/activemodel/test/cases/type/value_test.rb
@@ -0,0 +1,14 @@
+require "cases/helper"
+require "active_model/type"
+
+module ActiveModel
+ module Type
+ class ValueTest < ActiveModel::TestCase
+ def test_type_equality
+ assert_equal Type::Value.new, Type::Value.new
+ assert_not_equal Type::Value.new, Type::Integer.new
+ assert_not_equal Type::Value.new(precision: 1), Type::Value.new(precision: 2)
+ end
+ end
+ end
+end
diff --git a/activemodel/test/cases/types_test.rb b/activemodel/test/cases/types_test.rb
deleted file mode 100644
index c46775cb41..0000000000
--- a/activemodel/test/cases/types_test.rb
+++ /dev/null
@@ -1,125 +0,0 @@
-require "cases/helper"
-require "active_model/type"
-require "active_support/core_ext/numeric/time"
-
-module ActiveModel
- class TypesTest < ActiveModel::TestCase
- def test_type_cast_boolean
- type = Type::Boolean.new
- assert type.cast("").nil?
- assert type.cast(nil).nil?
-
- assert type.cast(true)
- assert type.cast(1)
- assert type.cast("1")
- assert type.cast("t")
- assert type.cast("T")
- assert type.cast("true")
- assert type.cast("TRUE")
- assert type.cast("on")
- assert type.cast("ON")
- assert type.cast(" ")
- assert type.cast("\u3000\r\n")
- assert type.cast("\u0000")
- assert type.cast("SOMETHING RANDOM")
-
- # explicitly check for false vs nil
- assert_equal false, type.cast(false)
- assert_equal false, type.cast(0)
- assert_equal false, type.cast("0")
- assert_equal false, type.cast("f")
- assert_equal false, type.cast("F")
- assert_equal false, type.cast("false")
- assert_equal false, type.cast("FALSE")
- assert_equal false, type.cast("off")
- assert_equal false, type.cast("OFF")
- end
-
- def test_type_cast_float
- type = Type::Float.new
- assert_equal 1.0, type.cast("1")
- end
-
- def test_changing_float
- type = Type::Float.new
-
- assert type.changed?(5.0, 5.0, "5wibble")
- assert_not type.changed?(5.0, 5.0, "5")
- assert_not type.changed?(5.0, 5.0, "5.0")
- assert_not type.changed?(nil, nil, nil)
- end
-
- def test_type_cast_binary
- type = Type::Binary.new
- assert_equal nil, type.cast(nil)
- assert_equal "1", type.cast("1")
- assert_equal 1, type.cast(1)
- end
-
- def test_type_cast_time
- type = Type::Time.new
- assert_equal nil, type.cast(nil)
- assert_equal nil, type.cast("")
- assert_equal nil, type.cast("ABC")
-
- time_string = Time.now.utc.strftime("%T")
- assert_equal time_string, type.cast(time_string).strftime("%T")
-
- assert_equal ::Time.utc(2000, 1, 1, 16, 45, 54), type.cast("2015-06-13T19:45:54+03:00")
- assert_equal ::Time.utc(1999, 12, 31, 21, 7, 8), type.cast("06:07:08+09:00")
- end
-
- def test_type_cast_datetime_and_timestamp
- type = Type::DateTime.new
- assert_equal nil, type.cast(nil)
- assert_equal nil, type.cast("")
- assert_equal nil, type.cast(" ")
- assert_equal nil, type.cast("ABC")
-
- datetime_string = Time.now.utc.strftime("%FT%T")
- assert_equal datetime_string, type.cast(datetime_string).strftime("%FT%T")
- end
-
- def test_type_cast_date
- type = Type::Date.new
- assert_equal nil, type.cast(nil)
- assert_equal nil, type.cast("")
- assert_equal nil, type.cast(" ")
- assert_equal nil, type.cast("ABC")
-
- date_string = Time.now.utc.strftime("%F")
- assert_equal date_string, type.cast(date_string).strftime("%F")
- end
-
- def test_type_cast_duration_to_integer
- type = Type::Integer.new
- assert_equal 1800, type.cast(30.minutes)
- assert_equal 7200, type.cast(2.hours)
- end
-
- def test_string_to_time_with_timezone
- ["UTC", "US/Eastern"].each do |zone|
- with_timezone_config default: zone do
- type = Type::DateTime.new
- assert_equal Time.utc(2013, 9, 4, 0, 0, 0), type.cast("Wed, 04 Sep 2013 03:00:00 EAT")
- end
- end
- end
-
- def test_type_equality
- assert_equal Type::Value.new, Type::Value.new
- assert_not_equal Type::Value.new, Type::Integer.new
- assert_not_equal Type::Value.new(precision: 1), Type::Value.new(precision: 2)
- end
-
- private
-
- def with_timezone_config(default:)
- old_zone_default = ::Time.zone_default
- ::Time.zone_default = ::Time.find_zone(default)
- yield
- ensure
- ::Time.zone_default = old_zone_default
- end
- end
-end
diff --git a/activemodel/test/cases/validations/absence_validation_test.rb b/activemodel/test/cases/validations/absence_validation_test.rb
index 3e48e591e9..833f694c5a 100644
--- a/activemodel/test/cases/validations/absence_validation_test.rb
+++ b/activemodel/test/cases/validations/absence_validation_test.rb
@@ -19,7 +19,7 @@ class AbsenceValidationTest < ActiveModel::TestCase
assert_equal ["must be blank"], t.errors[:title]
assert_equal ["must be blank"], t.errors[:content]
t.title = ""
- t.content = "something"
+ t.content = "something"
assert t.invalid?
assert_equal ["must be blank"], t.errors[:content]
assert_equal [], t.errors[:title]
diff --git a/activemodel/test/cases/validations/acceptance_validation_test.rb b/activemodel/test/cases/validations/acceptance_validation_test.rb
index 55ab213498..fbd994e914 100644
--- a/activemodel/test/cases/validations/acceptance_validation_test.rb
+++ b/activemodel/test/cases/validations/acceptance_validation_test.rb
@@ -19,7 +19,7 @@ class AcceptanceValidationTest < ActiveModel::TestCase
def test_terms_of_service_agreement
Topic.validates_acceptance_of(:terms_of_service)
- t = Topic.new("title" => "We should be confirmed","terms_of_service" => "")
+ t = Topic.new("title" => "We should be confirmed", "terms_of_service" => "")
assert t.invalid?
assert_equal ["must be accepted"], t.errors[:terms_of_service]
@@ -30,7 +30,7 @@ class AcceptanceValidationTest < ActiveModel::TestCase
def test_eula
Topic.validates_acceptance_of(:eula, message: "must be abided")
- t = Topic.new("title" => "We should be confirmed","eula" => "")
+ t = Topic.new("title" => "We should be confirmed", "eula" => "")
assert t.invalid?
assert_equal ["must be abided"], t.errors[:eula]
diff --git a/activemodel/test/cases/validations/conditional_validation_test.rb b/activemodel/test/cases/validations/conditional_validation_test.rb
index 8c2f656838..4881008017 100644
--- a/activemodel/test/cases/validations/conditional_validation_test.rb
+++ b/activemodel/test/cases/validations/conditional_validation_test.rb
@@ -97,7 +97,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_if_validation_using_block_false
# When the block returns false
Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}",
- if: Proc.new { |r| r.title != "uhohuhoh"})
+ if: Proc.new { |r| r.title != "uhohuhoh" })
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert_empty t.errors[:title]
@@ -106,7 +106,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_unless_validation_using_block_false
# When the block returns false
Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}",
- unless: Proc.new { |r| r.title != "uhohuhoh"} )
+ unless: Proc.new { |r| r.title != "uhohuhoh" })
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
diff --git a/activemodel/test/cases/validations/confirmation_validation_test.rb b/activemodel/test/cases/validations/confirmation_validation_test.rb
index c13017d825..7ddf3ad273 100644
--- a/activemodel/test/cases/validations/confirmation_validation_test.rb
+++ b/activemodel/test/cases/validations/confirmation_validation_test.rb
@@ -28,7 +28,7 @@ class ConfirmationValidationTest < ActiveModel::TestCase
def test_title_confirmation
Topic.validates_confirmation_of(:title)
- t = Topic.new("title" => "We should be confirmed","title_confirmation" => "")
+ t = Topic.new("title" => "We should be confirmed", "title_confirmation" => "")
assert t.invalid?
t.title_confirmation = "We should be confirmed"
@@ -57,11 +57,11 @@ class ConfirmationValidationTest < ActiveModel::TestCase
I18n.backend = I18n::Backend::Simple.new
I18n.backend.store_translations("en",
errors: { messages: { confirmation: "doesn't match %{attribute}" } },
- activemodel: { attributes: { topic: { title: "Test Title"} } })
+ activemodel: { attributes: { topic: { title: "Test Title" } } })
Topic.validates_confirmation_of(:title)
- t = Topic.new("title" => "We should be confirmed","title_confirmation" => "")
+ t = Topic.new("title" => "We should be confirmed", "title_confirmation" => "")
assert t.invalid?
assert_equal ["doesn't match Test Title"], t.errors[:title_confirmation]
ensure
diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb
index 9c0071fad7..f28cfc0ef5 100644
--- a/activemodel/test/cases/validations/i18n_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_validation_test.rb
@@ -46,11 +46,11 @@ class I18nValidationTest < ActiveModel::TestCase
# are used to generate tests to keep things DRY
#
COMMON_CASES = [
- # [ case, validation_options, generate_message_options]
+ # [ case, validation_options, generate_message_options]
[ "given no options", {}, {}],
[ "given custom message", { message: "custom" }, { message: "custom" }],
- [ "given if condition", { if: lambda { true }}, {}],
- [ "given unless condition", { unless: lambda { false }}, {}],
+ [ "given if condition", { if: lambda { true } }, {}],
+ [ "given unless condition", { unless: lambda { false } }, {}],
[ "given option that is not reserved", { format: "jpg" }, { format: "jpg" }]
]
@@ -225,7 +225,7 @@ class I18nValidationTest < ActiveModel::TestCase
test "#{validation} finds custom model key translation when #{error_type}" do
I18n.backend.store_translations "en", activemodel: { errors: { models: { person: { attributes: { attribute => { error_type => "custom message" } } } } } }
- I18n.backend.store_translations "en", errors: { messages: { error_type => "global message"}}
+ I18n.backend.store_translations "en", errors: { messages: { error_type => "global message" } }
yield(@person, {})
@person.valid?
@@ -234,7 +234,7 @@ class I18nValidationTest < ActiveModel::TestCase
test "#{validation} finds custom model key translation with interpolation when #{error_type}" do
I18n.backend.store_translations "en", activemodel: { errors: { models: { person: { attributes: { attribute => { error_type => "custom message with %{extra}" } } } } } }
- I18n.backend.store_translations "en", errors: { messages: {error_type => "global message"} }
+ I18n.backend.store_translations "en", errors: { messages: { error_type => "global message" } }
yield(@person, { extra: "extra information" })
@person.valid?
@@ -242,7 +242,7 @@ class I18nValidationTest < ActiveModel::TestCase
end
test "#{validation} finds global default key translation when #{error_type}" do
- I18n.backend.store_translations "en", errors: { messages: {error_type => "global message"} }
+ I18n.backend.store_translations "en", errors: { messages: { error_type => "global message" } }
yield(@person, {})
@person.valid?
diff --git a/activemodel/test/cases/validations/inclusion_validation_test.rb b/activemodel/test/cases/validations/inclusion_validation_test.rb
index 732a8d362a..5aa43ea4a9 100644
--- a/activemodel/test/cases/validations/inclusion_validation_test.rb
+++ b/activemodel/test/cases/validations/inclusion_validation_test.rb
@@ -121,7 +121,7 @@ class InclusionValidationTest < ActiveModel::TestCase
end
def test_validates_inclusion_of_with_lambda
- Topic.validates_inclusion_of :title, in: lambda{ |topic| topic.author_name == "sikachu" ? %w( monkey elephant ) : %w( abe wasabi ) }
+ Topic.validates_inclusion_of :title, in: lambda { |topic| topic.author_name == "sikachu" ? %w( monkey elephant ) : %w( abe wasabi ) }
t = Topic.new
t.title = "wasabi"
diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb
index 63326aca69..95ee87b401 100644
--- a/activemodel/test/cases/validations/length_validation_test.rb
+++ b/activemodel/test/cases/validations/length_validation_test.rb
@@ -9,7 +9,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_with_allow_nil
- Topic.validates_length_of( :title, is: 5, allow_nil: true )
+ Topic.validates_length_of(:title, is: 5, allow_nil: true)
assert Topic.new("title" => "ab").invalid?
assert Topic.new("title" => "").invalid?
@@ -18,7 +18,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_with_allow_blank
- Topic.validates_length_of( :title, is: 5, allow_blank: true )
+ Topic.validates_length_of(:title, is: 5, allow_blank: true)
assert Topic.new("title" => "ab").invalid?
assert Topic.new("title" => "").valid?
@@ -104,7 +104,7 @@ class LengthValidationTest < ActiveModel::TestCase
assert_equal ["is too short (minimum is 3 characters)"], t.errors[:content]
t.title = "abe"
- t.content = "mad"
+ t.content = "mad"
assert t.valid?
end
@@ -161,8 +161,8 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_using_bignum
- bigmin = 2 ** 30
- bigmax = 2 ** 32
+ bigmin = 2**30
+ bigmax = 2**32
bigrange = bigmin...bigmax
assert_nothing_raised do
Topic.validates_length_of :title, is: bigmin + 5
@@ -183,7 +183,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_custom_errors_for_minimum_with_message
- Topic.validates_length_of( :title, minimum: 5, message: "boo %{count}" )
+ Topic.validates_length_of(:title, minimum: 5, message: "boo %{count}")
t = Topic.new("title" => "uhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -191,7 +191,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_custom_errors_for_minimum_with_too_short
- Topic.validates_length_of( :title, minimum: 5, too_short: "hoo %{count}" )
+ Topic.validates_length_of(:title, minimum: 5, too_short: "hoo %{count}")
t = Topic.new("title" => "uhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -199,7 +199,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_custom_errors_for_maximum_with_message
- Topic.validates_length_of( :title, maximum: 5, message: "boo %{count}" )
+ Topic.validates_length_of(:title, maximum: 5, message: "boo %{count}")
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -220,7 +220,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_custom_errors_for_maximum_with_too_long
- Topic.validates_length_of( :title, maximum: 5, too_long: "hoo %{count}" )
+ Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}")
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -242,7 +242,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_custom_errors_for_is_with_message
- Topic.validates_length_of( :title, is: 5, message: "boo %{count}" )
+ Topic.validates_length_of(:title, is: 5, message: "boo %{count}")
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -250,7 +250,7 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_custom_errors_for_is_with_wrong_length
- Topic.validates_length_of( :title, is: 5, wrong_length: "hoo %{count}" )
+ Topic.validates_length_of(:title, is: 5, wrong_length: "hoo %{count}")
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -289,7 +289,7 @@ class LengthValidationTest < ActiveModel::TestCase
assert_equal ["is too short (minimum is 3 characters)"], t.errors[:title]
assert_equal ["is too long (maximum is 5 characters)"], t.errors[:content]
t.title = "一二三"
- t.content = "12三"
+ t.content = "12三"
assert t.valid?
end
@@ -318,42 +318,6 @@ class LengthValidationTest < ActiveModel::TestCase
assert_equal ["is the wrong length (should be 5 characters)"], t.errors["title"]
end
- def test_validates_length_of_with_block
- assert_deprecated do
- Topic.validates_length_of(
- :content,
- minimum: 5,
- too_short: "Your essay must be at least %{count} words.",
- tokenizer: lambda {|str| str.scan(/\w+/) },
- )
- end
- t = Topic.new(content: "this content should be long enough")
- assert t.valid?
-
- t.content = "not long enough"
- assert t.invalid?
- assert t.errors[:content].any?
- assert_equal ["Your essay must be at least 5 words."], t.errors[:content]
- end
-
- def test_validates_length_of_with_symbol
- assert_deprecated do
- Topic.validates_length_of(
- :content,
- minimum: 5,
- too_short: "Your essay must be at least %{count} words.",
- tokenizer: :my_word_tokenizer,
- )
- end
- t = Topic.new(content: "this content should be long enough")
- assert t.valid?
-
- t.content = "not long enough"
- assert t.invalid?
- assert t.errors[:content].any?
- assert_equal ["Your essay must be at least 5 words."], t.errors[:content]
- end
-
def test_validates_length_of_for_integer
Topic.validates_length_of(:approved, is: 4)
@@ -439,7 +403,7 @@ class LengthValidationTest < ActiveModel::TestCase
def test_validates_with_diff_in_option
Topic.validates_length_of(:title, is: 5)
- Topic.validates_length_of(:title, is: 5, if: Proc.new { false } )
+ Topic.validates_length_of(:title, is: 5, if: Proc.new { false })
assert Topic.new("title" => "david").valid?
assert Topic.new("title" => "david2").invalid?
diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb
index 71ef449503..a1be2de578 100644
--- a/activemodel/test/cases/validations/numericality_validation_test.rb
+++ b/activemodel/test/cases/validations/numericality_validation_test.rb
@@ -20,7 +20,7 @@ class NumericalityValidationTest < ActiveModel::TestCase
INTEGERS = [0, 10, -10] + INTEGER_STRINGS
BIGDECIMAL = BIGDECIMAL_STRINGS.collect! { |bd| BigDecimal.new(bd) }
JUNK = ["not a number", "42 not a number", "0xdeadbeef", "0xinvalidhex", "0Xdeadbeef", "00-1", "--3", "+-3", "+3-1", "-+019.0", "12.12.13.12", "123\nnot a number"]
- INFINITY = [1.0/0.0]
+ INFINITY = [1.0 / 0.0]
def test_default_validates_numericality_of
Topic.validates_numericality_of :approved
@@ -35,6 +35,13 @@ class NumericalityValidationTest < ActiveModel::TestCase
valid!(NIL + FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
end
+ def test_validates_numericality_of_with_blank_allowed
+ Topic.validates_numericality_of :approved, allow_blank: true
+
+ invalid!(JUNK)
+ valid!(NIL + BLANK + FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
+ end
+
def test_validates_numericality_of_with_integer_only
Topic.validates_numericality_of :approved, only_integer: true
@@ -75,7 +82,7 @@ class NumericalityValidationTest < ActiveModel::TestCase
Topic.validates_numericality_of :approved, greater_than: BigDecimal.new("97.18")
invalid!([-97.18, BigDecimal.new("97.18"), BigDecimal("-97.18")], "must be greater than 97.18")
- valid!([97.18, 98, BigDecimal.new("98")]) # Notice the 97.18 as a float is greater than 97.18 as a BigDecimal due to floating point precision
+ valid!([97.19, 98, BigDecimal.new("98"), BigDecimal.new("97.19")])
end
def test_validates_numericality_with_greater_than_using_string_value
@@ -116,7 +123,7 @@ class NumericalityValidationTest < ActiveModel::TestCase
def test_validates_numericality_with_equal_to_using_differing_numeric_types
Topic.validates_numericality_of :approved, equal_to: BigDecimal.new("97.18")
- invalid!([-97.18, 97.18], "must be equal to 97.18")
+ invalid!([-97.18], "must be equal to 97.18")
valid!([BigDecimal.new("97.18")])
end
@@ -158,7 +165,7 @@ class NumericalityValidationTest < ActiveModel::TestCase
def test_validates_numericality_with_less_than_or_equal_to_using_differing_numeric_types
Topic.validates_numericality_of :approved, less_than_or_equal_to: BigDecimal.new("97.18")
- invalid!([97.18, 98], "must be less than or equal to 97.18")
+ invalid!([97.19, 98], "must be less than or equal to 97.18")
valid!([-97.18, BigDecimal.new("-97.18"), BigDecimal.new("97.18")])
end
@@ -254,11 +261,11 @@ class NumericalityValidationTest < ActiveModel::TestCase
end
def test_validates_numericality_with_invalid_args
- assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, greater_than_or_equal_to: "foo" }
- assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, less_than_or_equal_to: "foo" }
- assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, greater_than: "foo" }
- assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, less_than: "foo" }
- assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, equal_to: "foo" }
+ assert_raise(ArgumentError) { Topic.validates_numericality_of :approved, greater_than_or_equal_to: "foo" }
+ assert_raise(ArgumentError) { Topic.validates_numericality_of :approved, less_than_or_equal_to: "foo" }
+ assert_raise(ArgumentError) { Topic.validates_numericality_of :approved, greater_than: "foo" }
+ assert_raise(ArgumentError) { Topic.validates_numericality_of :approved, less_than: "foo" }
+ assert_raise(ArgumentError) { Topic.validates_numericality_of :approved, equal_to: "foo" }
end
private
diff --git a/activemodel/test/cases/validations/presence_validation_test.rb b/activemodel/test/cases/validations/presence_validation_test.rb
index feda817698..642dd0f144 100644
--- a/activemodel/test/cases/validations/presence_validation_test.rb
+++ b/activemodel/test/cases/validations/presence_validation_test.rb
@@ -20,7 +20,7 @@ class PresenceValidationTest < ActiveModel::TestCase
assert_equal ["can't be blank"], t.errors[:content]
t.title = "something"
- t.content = " "
+ t.content = " "
assert t.invalid?
assert_equal ["can't be blank"], t.errors[:content]
diff --git a/activemodel/test/cases/validations/validations_context_test.rb b/activemodel/test/cases/validations/validations_context_test.rb
index e5690da89a..25c37a572f 100644
--- a/activemodel/test/cases/validations/validations_context_test.rb
+++ b/activemodel/test/cases/validations/validations_context_test.rb
@@ -38,7 +38,7 @@ class ValidationsContextTest < ActiveModel::TestCase
Topic.validates_with(ValidatorThatAddsErrors, on: :create)
topic = Topic.new
assert topic.invalid?(:create), "Validation does run on create if 'on' is set to create"
- assert topic.errors[:base].include?(ERROR_MESSAGE)
+ assert_includes topic.errors[:base], ERROR_MESSAGE
end
test "with a class that adds errors on multiple contexts and validating a new model" do
@@ -48,10 +48,10 @@ class ValidationsContextTest < ActiveModel::TestCase
assert topic.valid?, "Validation ran with no context given when 'on' is set to context1 and context2"
assert topic.invalid?(:context1), "Validation did not run on context1 when 'on' is set to context1 and context2"
- assert topic.errors[:base].include?(ERROR_MESSAGE)
+ assert_includes topic.errors[:base], ERROR_MESSAGE
assert topic.invalid?(:context2), "Validation did not run on context2 when 'on' is set to context1 and context2"
- assert topic.errors[:base].include?(ERROR_MESSAGE)
+ assert_includes topic.errors[:base], ERROR_MESSAGE
end
test "with a class that validating a model for a multiple contexts" do
@@ -62,7 +62,7 @@ class ValidationsContextTest < ActiveModel::TestCase
assert topic.valid?, "Validation ran with no context given when 'on' is set to context1 and context2"
assert topic.invalid?([:context1, :context2]), "Validation did not run on context1 when 'on' is set to context1 and context2"
- assert topic.errors[:base].include?(ERROR_MESSAGE)
- assert topic.errors[:base].include?(ANOTHER_ERROR_MESSAGE)
+ assert_includes topic.errors[:base], ERROR_MESSAGE
+ assert_includes topic.errors[:base], ANOTHER_ERROR_MESSAGE
end
end
diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb
index 6b2c998ba4..20c11dd852 100644
--- a/activemodel/test/cases/validations/with_validation_test.rb
+++ b/activemodel/test/cases/validations/with_validation_test.rb
@@ -51,7 +51,7 @@ class ValidatesWithTest < ActiveModel::TestCase
Topic.validates_with(ValidatorThatAddsErrors)
topic = Topic.new
assert topic.invalid?, "A class that adds errors causes the record to be invalid"
- assert topic.errors[:base].include?(ERROR_MESSAGE)
+ assert_includes topic.errors[:base], ERROR_MESSAGE
end
test "with a class that returns valid" do
@@ -64,8 +64,8 @@ class ValidatesWithTest < ActiveModel::TestCase
Topic.validates_with(ValidatorThatAddsErrors, OtherValidatorThatAddsErrors)
topic = Topic.new
assert topic.invalid?
- assert topic.errors[:base].include?(ERROR_MESSAGE)
- assert topic.errors[:base].include?(OTHER_ERROR_MESSAGE)
+ assert_includes topic.errors[:base], ERROR_MESSAGE
+ assert_includes topic.errors[:base], OTHER_ERROR_MESSAGE
end
test "with if statements that return false" do
@@ -78,7 +78,7 @@ class ValidatesWithTest < ActiveModel::TestCase
Topic.validates_with(ValidatorThatAddsErrors, if: "1 == 1")
topic = Topic.new
assert topic.invalid?
- assert topic.errors[:base].include?(ERROR_MESSAGE)
+ assert_includes topic.errors[:base], ERROR_MESSAGE
end
test "with unless statements that return true" do
@@ -91,13 +91,13 @@ class ValidatesWithTest < ActiveModel::TestCase
Topic.validates_with(ValidatorThatAddsErrors, unless: "1 == 2")
topic = Topic.new
assert topic.invalid?
- assert topic.errors[:base].include?(ERROR_MESSAGE)
+ assert_includes topic.errors[:base], ERROR_MESSAGE
end
test "passes all configuration options to the validator class" do
topic = Topic.new
validator = Minitest::Mock.new
- validator.expect(:new, validator, [{foo: :bar, if: "1 == 1", class: Topic}])
+ validator.expect(:new, validator, [{ foo: :bar, if: "1 == 1", class: Topic }])
validator.expect(:validate, nil, [topic])
validator.expect(:is_a?, false, [Symbol])
validator.expect(:is_a?, false, [String])
@@ -111,7 +111,7 @@ class ValidatesWithTest < ActiveModel::TestCase
Topic.validates_with(ValidatorThatValidatesOptions, field: :first_name)
topic = Topic.new
assert topic.invalid?
- assert topic.errors[:base].include?(ERROR_MESSAGE)
+ assert_includes topic.errors[:base], ERROR_MESSAGE
end
test "validates_with each validator" do
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index 51607ba955..6647191205 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -51,10 +51,10 @@ class ValidationsTest < ActiveModel::TestCase
r = Reply.new
r.valid?
- errors = r.errors.collect {|attr, messages| [attr.to_s, messages]}
+ errors = r.errors.collect { |attr, messages| [attr.to_s, messages] }
- assert errors.include?(["title", "is Empty"])
- assert errors.include?(["content", "is Empty"])
+ assert_includes errors, ["title", "is Empty"]
+ assert_includes errors, ["content", "is Empty"]
end
def test_multiple_errors_per_attr_iteration_with_full_error_composition
@@ -86,8 +86,8 @@ class ValidationsTest < ActiveModel::TestCase
assert_equal ["Reply is not dignifying"], r.errors[:base]
- assert errors.include?("Title is Empty")
- assert errors.include?("Reply is not dignifying")
+ assert_includes errors, "Title is Empty"
+ assert_includes errors, "Reply is not dignifying"
assert_equal 2, r.errors.count
end
@@ -101,8 +101,8 @@ class ValidationsTest < ActiveModel::TestCase
assert_equal ["is invalid"], r.errors[:base]
- assert errors.include?("Title is Empty")
- assert errors.include?("is invalid")
+ assert_includes errors, "Title is Empty"
+ assert_includes errors, "is invalid"
assert_equal 2, r.errors.count
end
@@ -198,9 +198,9 @@ class ValidationsTest < ActiveModel::TestCase
end
assert_nothing_raised do
- klass.validate :validator_a, if: ->{ true }
+ klass.validate :validator_a, if: -> { true }
klass.validate :validator_b, prepend: true
- klass.validate :validator_c, unless: ->{ true }
+ klass.validate :validator_c, unless: -> { true }
end
t = klass.new
@@ -233,7 +233,7 @@ class ValidationsTest < ActiveModel::TestCase
assert t.invalid?
assert_equal "can't be blank", t.errors["title"].first
Topic.validates_presence_of :title, :author_name
- Topic.validate {errors.add("author_email_address", "will never be valid")}
+ Topic.validate { errors.add("author_email_address", "will never be valid") }
Topic.validates_length_of :title, :content, minimum: 2
t = Topic.new title: ""
@@ -251,7 +251,7 @@ class ValidationsTest < ActiveModel::TestCase
end
def test_validation_with_if_and_on
- Topic.validates_presence_of :title, if: Proc.new{|x| x.author_name = "bad"; true }, on: :update
+ Topic.validates_presence_of :title, if: Proc.new { |x| x.author_name = "bad"; true }, on: :update
t = Topic.new(title: "")
diff --git a/activemodel/test/models/topic.rb b/activemodel/test/models/topic.rb
index 3924741acc..192786c096 100644
--- a/activemodel/test/models/topic.rb
+++ b/activemodel/test/models/topic.rb
@@ -36,8 +36,4 @@ class Topic
def my_validation_with_arg(attr)
errors.add attr, "is missing" unless send(attr)
end
-
- def my_word_tokenizer(str)
- str.scan(/\w+/)
- end
end
diff --git a/activemodel/test/validators/email_validator.rb b/activemodel/test/validators/email_validator.rb
index 43011b277b..9b74d83b37 100644
--- a/activemodel/test/validators/email_validator.rb
+++ b/activemodel/test/validators/email_validator.rb
@@ -1,4 +1,3 @@
-require "active_support/core_ext/regexp"
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index d0e900c305..743a38b087 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,424 @@
+* Compare deserialized values for `PostgreSQL::OID::Hstore` types when
+ calling `ActiveRecord::Dirty#changed_in_place?`
+
+ Fixes #27502.
+
+ *Jon Moss*
+
+* Raise `ArgumentError` when passing an `ActiveRecord::Base` instance to `.find`
+ and `.exists?`.
+
+ *Rafael Mendonça França*
+
+* Respect precision option for arrays of timestamps.
+
+ Fixes #27514.
+
+ *Sean Griffin*
+
+* Optimize slow model instantiation when using STI and `store_full_sti_class = false` option.
+
+ *Konstantin Lazarev*
+
+* Add `touch` option to counter cache modifying methods.
+
+ Works when updating, resetting, incrementing and decrementing counters:
+
+ # Touches `updated_at`/`updated_on`.
+ Topic.increment_counter(:messages_count, 1, touch: true)
+ Topic.decrement_counter(:messages_count, 1, touch: true)
+
+ # Touches `last_discussed_at`.
+ Topic.reset_counters(18, :messages, touch: :last_discussed_at)
+
+ # Touches `updated_at` and `last_discussed_at`.
+ Topic.update_counters(18, messages_count: 5, touch: %i( updated_at last_discussed_at ))
+
+ Fixes #26724.
+
+ *Jarred Trost*
+
+* Remove deprecated `#uniq`, `#uniq!`, and `#uniq_value`.
+
+ *Ryuta Kamizono*
+
+* Remove deprecated `#insert_sql`, `#update_sql`, and `#delete_sql`.
+
+ *Ryuta Kamizono*
+
+* Remove deprecated `#use_transactional_fixtures` configuration.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `#raise_in_transactional_callbacks` configuration.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `#load_schema_for`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated conditions parameter from `#destroy_all` and `#delete_all`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated support to passing arguments to `#select` when a block is provided.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated support to query using commas on LIMIT.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated support to passing a class as a value in a query.
+
+ *Rafael Mendonça França*
+
+* Raise `ActiveRecord::IrreversibleOrderError` when using `last` with an irreversible
+ order.
+
+ *Rafael Mendonça França*
+
+* Raise when a `has_many :through` association has an ambiguous reflection name.
+
+ *Rafael Mendonça França*
+
+* Raise when `ActiveRecord::Migration` is inherited from directly.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `original_exception` argument in `ActiveRecord::StatementInvalid#initialize`
+ and `ActiveRecord::StatementInvalid#original_exception`.
+
+ *Rafael Mendonça França*
+
+* `#tables` and `#table_exists?` return only tables and not views.
+
+ All the deprecations on those methods were removed.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `name` argument from `#tables`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated support to passing a column to `#quote`.
+
+ *Rafael Mendonça França*
+
+* Set `:time` as a timezone aware type and remove deprecation when
+ `config.active_record.time_zone_aware_types` is not explictly set.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated force reload argument in singular and collection association readers.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `activerecord.errors.messages.restrict_dependent_destroy.one` and
+ `activerecord.errors.messages.restrict_dependent_destroy.many` i18n scopes.
+
+ *Rafael Mendonça França*
+
+* Allow passing extra flags to `db:structure:load` and `db:structure:dump`
+
+ Introduces `ActiveRecord::Tasks::DatabaseTasks.structure_(load|dump)_flags` to customize the
+ eventual commands run against the database, e.g. mysqldump/pg_dump.
+
+ *Kir Shatrov*
+
+* Notifications see frozen SQL string.
+
+ Fixes #23774.
+
+ *Richard Monette*
+
+* RuntimeErrors are no longer translated to `ActiveRecord::StatementInvalid`.
+
+ *Richard Monette*
+
+* Change the schema cache format to use YAML instead of Marshal.
+
+ *Kir Shatrov*
+
+* Support index length and order options using both string and symbol
+ column names.
+
+ Fixes #27243.
+
+ *Ryuta Kamizono*
+
+* Raise `ActiveRecord::RangeError` when values that executed are out of range.
+
+ *Ryuta Kamizono*
+
+* Raise `ActiveRecord::NotNullViolation` when a record cannot be inserted
+ or updated because it would violate a not null constraint.
+
+ *Ryuta Kamizono*
+
+* Emulate db trigger behaviour for after_commit :destroy, :update.
+
+ Race conditions can occur when an ActiveRecord is destroyed
+ twice or destroyed and updated. The callbacks should only be
+ triggered once, similar to a SQL database trigger.
+
+ *Stefan Budeanu*
+
+* Moved `DecimalWithoutScale`, `Text`, and `UnsignedInteger` from Active Model to Active Record.
+
+ *Iain Beeston*
+
+* Fix `write_attribute` method to check whether an attribute is aliased or not, and
+ use the aliased attribute name if needed.
+
+ *Prathamesh Sonpatki*
+
+* Fix `read_attribute` method to check whether an attribute is aliased or not, and
+ use the aliased attribute name if needed.
+
+ Fixes #26417.
+
+ *Prathamesh Sonpatki*
+
+* PostgreSQL & MySQL: Use big integer as primary key type for new tables.
+
+ *Jon McCartie*, *Pavel Pravosud*
+
+* Change the type argument of `ActiveRecord::Base#attribute` to be optional.
+ The default is now `ActiveRecord::Type::Value.new`, which provides no type
+ casting behavior.
+
+ *Sean Griffin*
+
+* Fix that unsigned with zerofill is treated as signed.
+
+ Fixes #27125.
+
+ *Ryuta Kamizono*
+
+* Fix the uniqueness validation scope with a polymorphic association.
+
+ *Sergey Alekseev*
+
+* Raise `ActiveRecord::RecordNotFound` from collection `*_ids` setters
+ for unknown IDs with a better error message.
+
+ Changes the collection `*_ids` setters to cast provided IDs the data
+ type of the primary key set in the association, not the model
+ primary key.
+
+ *Dominic Cleal*
+
+* For PostgreSQL >= 9.4 use `pgcrypto`'s `gen_random_uuid()` instead of
+ `uuid-ossp`'s UUID generation function.
+
+ *Yuji Yaginuma*, *Yaw Boakye*
+
+* Introduce `Model#reload_<association>` to bring back the behavior
+ of `Article.category(true)` where `category` is a singular
+ association.
+
+ The force reloading of the association reader was deprecated
+ in #20888. Unfortunately the suggested alternative of
+ `article.reload.category` does not expose the same behavior.
+
+ This patch adds a reader method with the prefix `reload_` for
+ singular associations. This method has the same semantics as
+ passing true to the association reader used to have.
+
+ *Yves Senn*
+
+* Make sure eager loading `ActiveRecord::Associations` also loads
+ constants defined in `ActiveRecord::Associations::Preloader`.
+
+ *Yves Senn*
+
+* Allow `ActionController::Parameters`-like objects to be passed as
+ values for Postgres HStore columns.
+
+ Fixes #26904.
+
+ *Jon Moss*
+
+* Added `stat` method to `ActiveRecord::ConnectionAdapters::ConnectionPool`.
+
+ Example:
+
+ ActiveRecord::Base.connection_pool.stat # =>
+ { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
+
+ *Pavel Evstigneev*
+
+* Avoid `unscope(:order)` when `limit_value` is presented for `count`
+ and `exists?`.
+
+ If `limit_value` is presented, records fetching order is very important
+ for performance. We should not unscope the order in the case.
+
+ *Ryuta Kamizono*
+
+* Fix an Active Record `DateTime` field `NoMethodError` caused by incomplete
+ datetime.
+
+ Fixes #24195.
+
+ *Sen Zhang*
+
+* Allow `slice` to take an array of methods(without the need for splatting).
+
+ *Cohen Carlisle*
+
+* Improved partial writes with HABTM and has many through associations
+ to fire database query only if relation has been changed.
+
+ Fixes #19663.
+
+ *Mehmet Emin İNAÇ*
+
+* Deprecate passing arguments and block at the same time to
+ `ActiveRecord::QueryMethods#select`.
+
+ *Prathamesh Sonpatki*
+
+* Optimistic locking: Added ability to update `locking_column` value.
+ Ignore optimistic locking if trying to update with new `locking_column` value.
+
+ *bogdanvlviv*
+
+* Fixed: Optimistic locking does not work well with `null` in the database.
+
+ Fixes #26024.
+
+ *bogdanvlviv*
+
+* Fixed support for case insensitive comparisons of `text` columns in
+ PostgreSQL.
+
+ *Edho Arief*
+
+* Serialize JSON attribute value `nil` as SQL `NULL`, not JSON `null`.
+
+ *Trung Duc Tran*
+
+* Return `true` from `update_attribute` when the value of the attribute
+ to be updated is unchanged.
+
+ Fixes #26593.
+
+ *Prathamesh Sonpatki*
+
+* Always store errors details information with symbols.
+
+ When the association is autosaved we were storing the details with
+ string keys. This was creating inconsistency with other details that are
+ added using the `Errors#add` method. It was also inconsistent with the
+ `Errors#messages` storage.
+
+ To fix this inconsistency we are always storing with symbols. This will
+ cause a small breaking change because in those cases the details could
+ be accessed as strings keys but now it can not.
+
+ Fix #26499.
+
+ *Rafael Mendonça França*, *Marcus Vieira*
+
+* Calling `touch` on a model using optimistic locking will now leave the model
+ in a non-dirty state with no attribute changes.
+
+ Fixes #26496.
+
+ *Jakob Skjerning*
+
+* Using a mysql2 connection after it fails to reconnect will now have an error message
+ saying the connection is closed rather than an undefined method error message.
+
+ *Dylan Thacker-Smith*
+
+* PostgreSQL array columns will now respect the encoding of strings contained
+ in the array.
+
+ Fixes #26326.
+
+ *Sean Griffin*
+
+* Inverse association instances will now be set before `after_find` or
+ `after_initialize` callbacks are run.
+
+ Fixes #26320.
+
+ *Sean Griffin*
+
+* Remove unnecessarily association load when a `belongs_to` association has already been
+ loaded then the foreign key is changed directly and the record saved.
+
+ *James Coleman*
+
+* Remove standardized column types/arguments spaces in schema dump.
+
+ *Tim Petricola*
+
+* Avoid loading records from database when they are already loaded using
+ the `pluck` method on a collection.
+
+ Fixes #25921.
+
+ *Ryuta Kamizono*
+
+* Remove text default treated as an empty string in non-strict mode for
+ consistency with other types.
+
+ Strict mode controls how MySQL handles invalid or missing values in
+ data-change statements such as INSERT or UPDATE. If strict mode is not
+ in effect, MySQL inserts adjusted values for invalid or missing values
+ and produces warnings.
+
+ def test_mysql_not_null_defaults_non_strict
+ using_strict(false) do
+ with_mysql_not_null_table do |klass|
+ record = klass.new
+ assert_nil record.non_null_integer
+ assert_nil record.non_null_string
+ assert_nil record.non_null_text
+ assert_nil record.non_null_blob
+
+ record.save!
+ record.reload
+
+ assert_equal 0, record.non_null_integer
+ assert_equal "", record.non_null_string
+ assert_equal "", record.non_null_text
+ assert_equal "", record.non_null_blob
+ end
+ end
+ end
+
+ https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sql-mode-strict
+
+ *Ryuta Kamizono*
+
+* Sqlite3 migrations to add a column to an existing table can now be
+ successfully rolled back when the column was given and invalid column
+ type.
+
+ Fixes #26087.
+
+ *Travis O'Neill*
+
+* Deprecate `sanitize_conditions`. Use `sanitize_sql` instead.
+
+ *Ryuta Kamizono*
+
+* Doing count on relations that contain LEFT OUTER JOIN Arel node no longer
+ force a DISTINCT. This solves issues when using count after a left_joins.
+
+ *Maxime Handfield Lapointe*
+
+* RecordNotFound raised by association.find exposes `id`, `primary_key` and
+ `model` methods to be consistent with RecordNotFound raised by Record.find.
+
+ *Michel Pigassou*
+
* Hashes can once again be passed to setters of `composed_of`, if all of the
mapping methods are methods implemented on `Hash`.
@@ -10,7 +431,7 @@
*Takeshi Akima*
* Virtual attributes will no longer raise when read on models loaded from the
- database
+ database.
*Sean Griffin*
@@ -47,8 +468,8 @@
*Xavier Noria*
* Using `group` with an attribute that has a custom type will properly cast
- the hash keys after calling a calculation method like `count`.
-
+ the hash keys after calling a calculation method like `count`.
+
Fixes #25595.
*Sean Griffin*
@@ -83,7 +504,7 @@
*Sean Griffin*
* Ensure hashes can be assigned to attributes created using `composed_of`.
-
+
Fixes #25210.
*Sean Griffin*
diff --git a/activerecord/MIT-LICENSE b/activerecord/MIT-LICENSE
index 1f496cf280..f9e4444f07 100644
--- a/activerecord/MIT-LICENSE
+++ b/activerecord/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2016 David Heinemeier Hansson
+Copyright (c) 2004-2017 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index cb100a87d8..7be3d851f1 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -50,7 +50,7 @@ end
Rake::TestTask.new(adapter => "#{adapter}:env") { |t|
adapter_short = adapter == "db2" ? adapter : adapter[/^[a-z0-9]+/]
t.libs << "test"
- t.test_files = (Dir.glob( "test/cases/**/*_test.rb" ).reject {
+ t.test_files = (Dir.glob("test/cases/**/*_test.rb").reject {
|x| x.include?("/adapters/")
} + Dir.glob("test/cases/adapters/#{adapter_short}/**/*_test.rb"))
@@ -66,8 +66,8 @@ end
(Dir["test/cases/**/*_test.rb"].reject {
|x| x.include?("/adapters/")
} + Dir["test/cases/adapters/#{adapter_short}/**/*_test.rb"]).all? do |file|
- sh(Gem.ruby, "-w" ,"-Itest", file)
- end or raise "Failures"
+ sh(Gem.ruby, "-w" , "-Itest", file)
+ end || raise("Failures")
end
end
end
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index 2cd8f179dd..0b37e9076c 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -24,5 +24,5 @@ Gem::Specification.new do |s|
s.add_dependency "activesupport", version
s.add_dependency "activemodel", version
- s.add_dependency "arel", "~> 7.0"
+ s.add_dependency "arel", "~> 8.0"
end
diff --git a/activerecord/bin/test b/activerecord/bin/test
index 23add35d45..3a9547e5c1 100755
--- a/activerecord/bin/test
+++ b/activerecord/bin/test
@@ -17,5 +17,3 @@ module Minitest
end
Minitest.extensions.unshift "active_record"
-
-exit Minitest.run(ARGV)
diff --git a/activerecord/examples/performance.rb b/activerecord/examples/performance.rb
index f2fe8875b9..3257dd4ad7 100644
--- a/activerecord/examples/performance.rb
+++ b/activerecord/examples/performance.rb
@@ -2,7 +2,7 @@ require "active_record"
require "benchmark/ips"
TIME = (ENV["BENCHMARK_TIME"] || 20).to_i
-RECORDS = (ENV["BENCHMARK_RECORDS"] || TIME*1000).to_i
+RECORDS = (ENV["BENCHMARK_RECORDS"] || TIME * 1000).to_i
conn = { adapter: "sqlite3", database: ":memory:" }
@@ -42,7 +42,7 @@ class Exhibit < ActiveRecord::Base
def self.feel(exhibits) exhibits.each(&:feel) end
end
-def progress_bar(int); print "." if (int%100).zero? ; end
+def progress_bar(int); print "." if (int % 100).zero? ; end
puts "Generating data..."
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index fb7b8df01d..250f48fad9 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2016 David Heinemeier Hansson
+# Copyright (c) 2004-2017 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -68,7 +68,6 @@ module ActiveRecord
autoload :StatementCache
autoload :Store
autoload :Suppressor
- autoload :TableMetadata
autoload :Timestamp
autoload :Transactions
autoload :Translation
@@ -101,6 +100,7 @@ module ActiveRecord
end
autoload :Result
+ autoload :TableMetadata
end
module Coders
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index 55076c4314..10cbd5429c 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -15,170 +15,170 @@ module ActiveRecord
private
- def clear_aggregation_cache # :nodoc:
+ def clear_aggregation_cache
@aggregation_cache.clear if persisted?
end
- def init_internals # :nodoc:
+ def init_internals
@aggregation_cache = {}
super
end
- # Active Record implements aggregation through a macro-like class method called #composed_of
- # for representing attributes as value objects. It expresses relationships like "Account [is]
- # composed of Money [among other things]" or "Person [is] composed of [an] address". Each call
- # to the macro adds a description of how the value objects are created from the attributes of
- # the entity object (when the entity is initialized either as a new object or from finding an
- # existing object) and how it can be turned back into attributes (when the entity is saved to
- # the database).
- #
- # class Customer < ActiveRecord::Base
- # composed_of :balance, class_name: "Money", mapping: %w(amount currency)
- # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
- # end
- #
- # The customer class now has the following methods to manipulate the value objects:
- # * <tt>Customer#balance, Customer#balance=(money)</tt>
- # * <tt>Customer#address, Customer#address=(address)</tt>
- #
- # These methods will operate with value objects like the ones described below:
- #
- # class Money
- # include Comparable
- # attr_reader :amount, :currency
- # EXCHANGE_RATES = { "USD_TO_DKK" => 6 }
- #
- # def initialize(amount, currency = "USD")
- # @amount, @currency = amount, currency
- # end
- #
- # def exchange_to(other_currency)
- # exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor
- # Money.new(exchanged_amount, other_currency)
- # end
- #
- # def ==(other_money)
- # amount == other_money.amount && currency == other_money.currency
- # end
- #
- # def <=>(other_money)
- # if currency == other_money.currency
- # amount <=> other_money.amount
- # else
- # amount <=> other_money.exchange_to(currency).amount
- # end
- # end
- # end
- #
- # class Address
- # attr_reader :street, :city
- # def initialize(street, city)
- # @street, @city = street, city
- # end
- #
- # def close_to?(other_address)
- # city == other_address.city
- # end
- #
- # def ==(other_address)
- # city == other_address.city && street == other_address.street
- # end
- # end
- #
- # Now it's possible to access attributes from the database through the value objects instead. If
- # you choose to name the composition the same as the attribute's name, it will be the only way to
- # access that attribute. That's the case with our +balance+ attribute. You interact with the value
- # objects just like you would with any other attribute:
- #
- # customer.balance = Money.new(20) # sets the Money value object and the attribute
- # customer.balance # => Money value object
- # customer.balance.exchange_to("DKK") # => Money.new(120, "DKK")
- # customer.balance > Money.new(10) # => true
- # customer.balance == Money.new(20) # => true
- # customer.balance < Money.new(5) # => false
- #
- # Value objects can also be composed of multiple attributes, such as the case of Address. The order
- # of the mappings will determine the order of the parameters.
- #
- # customer.address_street = "Hyancintvej"
- # customer.address_city = "Copenhagen"
- # customer.address # => Address.new("Hyancintvej", "Copenhagen")
- #
- # customer.address = Address.new("May Street", "Chicago")
- # customer.address_street # => "May Street"
- # customer.address_city # => "Chicago"
- #
- # == Writing value objects
- #
- # Value objects are immutable and interchangeable objects that represent a given value, such as
- # a Money object representing $5. Two Money objects both representing $5 should be equal (through
- # methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking makes sense). This is
- # unlike entity objects where equality is determined by identity. An entity class such as Customer can
- # easily have two different objects that both have an address on Hyancintvej. Entity identity is
- # determined by object or relational unique identifiers (such as primary keys). Normal
- # ActiveRecord::Base classes are entity objects.
- #
- # It's also important to treat the value objects as immutable. Don't allow the Money object to have
- # its amount changed after creation. Create a new Money object with the new value instead. The
- # <tt>Money#exchange_to</tt> method is an example of this. It returns a new value object instead of changing
- # its own values. Active Record won't persist value objects that have been changed through means
- # other than the writer method.
- #
- # The immutable requirement is enforced by Active Record by freezing any object assigned as a value
- # object. Attempting to change it afterwards will result in a +RuntimeError+.
- #
- # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not
- # keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
- #
- # == Custom constructors and converters
- #
- # By default value objects are initialized by calling the <tt>new</tt> constructor of the value
- # class passing each of the mapped attributes, in the order specified by the <tt>:mapping</tt>
- # option, as arguments. If the value class doesn't support this convention then #composed_of allows
- # a custom constructor to be specified.
- #
- # When a new value is assigned to the value object, the default assumption is that the new value
- # is an instance of the value class. Specifying a custom converter allows the new value to be automatically
- # converted to an instance of value class if necessary.
- #
- # For example, the +NetworkResource+ model has +network_address+ and +cidr_range+ attributes that should be
- # aggregated using the +NetAddr::CIDR+ value class (http://www.rubydoc.info/gems/netaddr/1.5.0/NetAddr/CIDR).
- # The constructor for the value class is called +create+ and it expects a CIDR address string as a parameter.
- # New values can be assigned to the value object using either another +NetAddr::CIDR+ object, a string
- # or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet
- # these requirements:
- #
- # class NetworkResource < ActiveRecord::Base
- # composed_of :cidr,
- # class_name: 'NetAddr::CIDR',
- # mapping: [ %w(network_address network), %w(cidr_range bits) ],
- # allow_nil: true,
- # constructor: Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") },
- # converter: Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) }
- # end
- #
- # # This calls the :constructor
- # network_resource = NetworkResource.new(network_address: '192.168.0.1', cidr_range: 24)
- #
- # # These assignments will both use the :converter
- # network_resource.cidr = [ '192.168.2.1', 8 ]
- # network_resource.cidr = '192.168.0.1/24'
- #
- # # This assignment won't use the :converter as the value is already an instance of the value class
- # network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8')
- #
- # # Saving and then reloading will use the :constructor on reload
- # network_resource.save
- # network_resource.reload
- #
- # == Finding records by a value object
- #
- # Once a #composed_of relationship is specified for a model, records can be loaded from the database
- # by specifying an instance of the value object in the conditions hash. The following example
- # finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD":
- #
- # Customer.where(balance: Money.new(20, "USD"))
- #
+ # Active Record implements aggregation through a macro-like class method called #composed_of
+ # for representing attributes as value objects. It expresses relationships like "Account [is]
+ # composed of Money [among other things]" or "Person [is] composed of [an] address". Each call
+ # to the macro adds a description of how the value objects are created from the attributes of
+ # the entity object (when the entity is initialized either as a new object or from finding an
+ # existing object) and how it can be turned back into attributes (when the entity is saved to
+ # the database).
+ #
+ # class Customer < ActiveRecord::Base
+ # composed_of :balance, class_name: "Money", mapping: %w(amount currency)
+ # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
+ # end
+ #
+ # The customer class now has the following methods to manipulate the value objects:
+ # * <tt>Customer#balance, Customer#balance=(money)</tt>
+ # * <tt>Customer#address, Customer#address=(address)</tt>
+ #
+ # These methods will operate with value objects like the ones described below:
+ #
+ # class Money
+ # include Comparable
+ # attr_reader :amount, :currency
+ # EXCHANGE_RATES = { "USD_TO_DKK" => 6 }
+ #
+ # def initialize(amount, currency = "USD")
+ # @amount, @currency = amount, currency
+ # end
+ #
+ # def exchange_to(other_currency)
+ # exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor
+ # Money.new(exchanged_amount, other_currency)
+ # end
+ #
+ # def ==(other_money)
+ # amount == other_money.amount && currency == other_money.currency
+ # end
+ #
+ # def <=>(other_money)
+ # if currency == other_money.currency
+ # amount <=> other_money.amount
+ # else
+ # amount <=> other_money.exchange_to(currency).amount
+ # end
+ # end
+ # end
+ #
+ # class Address
+ # attr_reader :street, :city
+ # def initialize(street, city)
+ # @street, @city = street, city
+ # end
+ #
+ # def close_to?(other_address)
+ # city == other_address.city
+ # end
+ #
+ # def ==(other_address)
+ # city == other_address.city && street == other_address.street
+ # end
+ # end
+ #
+ # Now it's possible to access attributes from the database through the value objects instead. If
+ # you choose to name the composition the same as the attribute's name, it will be the only way to
+ # access that attribute. That's the case with our +balance+ attribute. You interact with the value
+ # objects just like you would with any other attribute:
+ #
+ # customer.balance = Money.new(20) # sets the Money value object and the attribute
+ # customer.balance # => Money value object
+ # customer.balance.exchange_to("DKK") # => Money.new(120, "DKK")
+ # customer.balance > Money.new(10) # => true
+ # customer.balance == Money.new(20) # => true
+ # customer.balance < Money.new(5) # => false
+ #
+ # Value objects can also be composed of multiple attributes, such as the case of Address. The order
+ # of the mappings will determine the order of the parameters.
+ #
+ # customer.address_street = "Hyancintvej"
+ # customer.address_city = "Copenhagen"
+ # customer.address # => Address.new("Hyancintvej", "Copenhagen")
+ #
+ # customer.address = Address.new("May Street", "Chicago")
+ # customer.address_street # => "May Street"
+ # customer.address_city # => "Chicago"
+ #
+ # == Writing value objects
+ #
+ # Value objects are immutable and interchangeable objects that represent a given value, such as
+ # a Money object representing $5. Two Money objects both representing $5 should be equal (through
+ # methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking makes sense). This is
+ # unlike entity objects where equality is determined by identity. An entity class such as Customer can
+ # easily have two different objects that both have an address on Hyancintvej. Entity identity is
+ # determined by object or relational unique identifiers (such as primary keys). Normal
+ # ActiveRecord::Base classes are entity objects.
+ #
+ # It's also important to treat the value objects as immutable. Don't allow the Money object to have
+ # its amount changed after creation. Create a new Money object with the new value instead. The
+ # <tt>Money#exchange_to</tt> method is an example of this. It returns a new value object instead of changing
+ # its own values. Active Record won't persist value objects that have been changed through means
+ # other than the writer method.
+ #
+ # The immutable requirement is enforced by Active Record by freezing any object assigned as a value
+ # object. Attempting to change it afterwards will result in a +RuntimeError+.
+ #
+ # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not
+ # keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
+ #
+ # == Custom constructors and converters
+ #
+ # By default value objects are initialized by calling the <tt>new</tt> constructor of the value
+ # class passing each of the mapped attributes, in the order specified by the <tt>:mapping</tt>
+ # option, as arguments. If the value class doesn't support this convention then #composed_of allows
+ # a custom constructor to be specified.
+ #
+ # When a new value is assigned to the value object, the default assumption is that the new value
+ # is an instance of the value class. Specifying a custom converter allows the new value to be automatically
+ # converted to an instance of value class if necessary.
+ #
+ # For example, the +NetworkResource+ model has +network_address+ and +cidr_range+ attributes that should be
+ # aggregated using the +NetAddr::CIDR+ value class (http://www.rubydoc.info/gems/netaddr/1.5.0/NetAddr/CIDR).
+ # The constructor for the value class is called +create+ and it expects a CIDR address string as a parameter.
+ # New values can be assigned to the value object using either another +NetAddr::CIDR+ object, a string
+ # or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet
+ # these requirements:
+ #
+ # class NetworkResource < ActiveRecord::Base
+ # composed_of :cidr,
+ # class_name: 'NetAddr::CIDR',
+ # mapping: [ %w(network_address network), %w(cidr_range bits) ],
+ # allow_nil: true,
+ # constructor: Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") },
+ # converter: Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) }
+ # end
+ #
+ # # This calls the :constructor
+ # network_resource = NetworkResource.new(network_address: '192.168.0.1', cidr_range: 24)
+ #
+ # # These assignments will both use the :converter
+ # network_resource.cidr = [ '192.168.2.1', 8 ]
+ # network_resource.cidr = '192.168.0.1/24'
+ #
+ # # This assignment won't use the :converter as the value is already an instance of the value class
+ # network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8')
+ #
+ # # Saving and then reloading will use the :constructor on reload
+ # network_resource.save
+ # network_resource.reload
+ #
+ # == Finding records by a value object
+ #
+ # Once a #composed_of relationship is specified for a model, records can be loaded from the database
+ # by specifying an instance of the value object in the conditions hash. The following example
+ # finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD":
+ #
+ # Customer.where(balance: Money.new(20, "USD"))
+ #
module ClassMethods
# Adds reader and writer methods for manipulating a value object:
# <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods.
@@ -206,7 +206,7 @@ module ActiveRecord
# or a Proc that is called when a new value is assigned to the value object. The converter is
# passed the single value that is used in the assignment and is only called if the new value is
# not an instance of <tt>:class_name</tt>. If <tt>:allow_nil</tt> is set to true, the converter
- # can return nil to skip the assignment.
+ # can return +nil+ to skip the assignment.
#
# Option examples:
# composed_of :temperature, mapping: %w(reading celsius)
@@ -242,8 +242,8 @@ module ActiveRecord
private
def reader_method(name, class_name, mapping, allow_nil, constructor)
define_method(name) do
- if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|key, _| !_read_attribute(key).nil? })
- attrs = mapping.collect {|key, _| _read_attribute(key)}
+ if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? { |key, _| !_read_attribute(key).nil? })
+ attrs = mapping.collect { |key, _| _read_attribute(key) }
object = constructor.respond_to?(:call) ?
constructor.call(*attrs) :
class_name.constantize.send(constructor, *attrs)
diff --git a/activerecord/lib/active_record/association_relation.rb b/activerecord/lib/active_record/association_relation.rb
index 2da2d968b9..de2d03cd0b 100644
--- a/activerecord/lib/active_record/association_relation.rb
+++ b/activerecord/lib/active_record/association_relation.rb
@@ -29,7 +29,10 @@ module ActiveRecord
private
def exec_queries
- super.each { |r| @association.set_inverse_instance r }
+ super do |r|
+ @association.set_inverse_instance r
+ yield r if block_given?
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 659e512ce2..1db5fc0dd1 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -107,6 +107,21 @@ module ActiveRecord
end
end
+ class AmbiguousSourceReflectionForThroughAssociation < ActiveRecordError # :nodoc:
+ def initialize(klass, macro, association_name, options, possible_sources)
+ example_options = options.dup
+ example_options[:source] = possible_sources.first
+
+ super("Ambiguous source reflection for through association. Please " \
+ "specify a :source directive on your declaration like:\n" \
+ "\n" \
+ " class #{klass} < ActiveRecord::Base\n" \
+ " #{macro} :#{association_name}, #{example_options}\n" \
+ " end"
+ )
+ end
+ end
+
class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ThroughCantAssociateThroughHasOneOrManyReflection #:nodoc:
end
@@ -224,6 +239,11 @@ module ActiveRecord
autoload :AliasTracker
end
+ def self.eager_load!
+ super
+ Preloader.eager_load!
+ end
+
# Returns the association instance for the given name, instantiating it if it doesn't already exist
def association(name) #:nodoc:
association = association_instance_get(name)
@@ -255,16 +275,16 @@ module ActiveRecord
private
# Clears out the association cache.
- def clear_association_cache # :nodoc:
+ def clear_association_cache
@association_cache.clear if persisted?
end
- def init_internals # :nodoc:
+ def init_internals
@association_cache = {}
super
end
- # Returns the specified association instance if it exists, nil otherwise.
+ # Returns the specified association instance if it exists, +nil+ otherwise.
def association_instance_get(name)
@association_cache[name]
end
@@ -274,882 +294,882 @@ module ActiveRecord
@association_cache[name] = association
end
- # \Associations are a set of macro-like class methods for tying objects together through
- # foreign keys. They express relationships like "Project has one Project Manager"
- # or "Project belongs to a Portfolio". Each macro adds a number of methods to the
- # class which are specialized according to the collection or association symbol and the
- # options hash. It works much the same way as Ruby's own <tt>attr*</tt>
- # methods.
- #
- # class Project < ActiveRecord::Base
- # belongs_to :portfolio
- # has_one :project_manager
- # has_many :milestones
- # has_and_belongs_to_many :categories
- # end
- #
- # The project class now has the following methods (and more) to ease the traversal and
- # manipulation of its relationships:
- # * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt>
- # * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt>
- # * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt>
- # <tt>Project#milestones.delete(milestone), Project#milestones.destroy(milestone), Project#milestones.find(milestone_id),</tt>
- # <tt>Project#milestones.build, Project#milestones.create</tt>
- # * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt>
- # <tt>Project#categories.delete(category1), Project#categories.destroy(category1)</tt>
- #
- # === A word of warning
- #
- # Don't create associations that have the same name as {instance methods}[rdoc-ref:ActiveRecord::Core] of
- # <tt>ActiveRecord::Base</tt>. Since the association adds a method with that name to
- # its model, using an association with the same name as one provided by <tt>ActiveRecord::Base</tt> will override the method inherited through <tt>ActiveRecord::Base</tt> and will break things.
- # For instance, +attributes+ and +connection+ would be bad choices for association names, because those names already exist in the list of <tt>ActiveRecord::Base</tt> instance methods.
- #
- # == Auto-generated methods
- # See also Instance Public methods below for more details.
- #
- # === Singular associations (one-to-one)
- # | | belongs_to |
- # generated methods | belongs_to | :polymorphic | has_one
- # ----------------------------------+------------+--------------+---------
- # other(force_reload=false) | X | X | X
- # other=(other) | X | X | X
- # build_other(attributes={}) | X | | X
- # create_other(attributes={}) | X | | X
- # create_other!(attributes={}) | X | | X
- #
- # === Collection associations (one-to-many / many-to-many)
- # | | | has_many
- # generated methods | habtm | has_many | :through
- # ----------------------------------+-------+----------+----------
- # others(force_reload=false) | X | X | X
- # others=(other,other,...) | X | X | X
- # other_ids | X | X | X
- # other_ids=(id,id,...) | X | X | X
- # others<< | X | X | X
- # others.push | X | X | X
- # others.concat | X | X | X
- # others.build(attributes={}) | X | X | X
- # others.create(attributes={}) | X | X | X
- # others.create!(attributes={}) | X | X | X
- # others.size | X | X | X
- # others.length | X | X | X
- # others.count | X | X | X
- # others.sum(*args) | X | X | X
- # others.empty? | X | X | X
- # others.clear | X | X | X
- # others.delete(other,other,...) | X | X | X
- # others.delete_all | X | X | X
- # others.destroy(other,other,...) | X | X | X
- # others.destroy_all | X | X | X
- # others.find(*args) | X | X | X
- # others.exists? | X | X | X
- # others.distinct | X | X | X
- # others.reset | X | X | X
- #
- # === Overriding generated methods
- #
- # Association methods are generated in a module that is included into the model class,
- # which allows you to easily override with your own methods and call the original
- # generated method with +super+. For example:
- #
- # class Car < ActiveRecord::Base
- # belongs_to :owner
- # belongs_to :old_owner
- # def owner=(new_owner)
- # self.old_owner = self.owner
- # super
- # end
- # end
- #
- # If your model class is <tt>Project</tt>, then the module is
- # named <tt>Project::GeneratedAssociationMethods</tt>. The +GeneratedAssociationMethods+ module is
- # included in the model class immediately after the (anonymous) generated attributes methods
- # module, meaning an association will override the methods for an attribute with the same name.
- #
- # == Cardinality and associations
- #
- # Active Record associations can be used to describe one-to-one, one-to-many and many-to-many
- # relationships between models. Each model uses an association to describe its role in
- # the relation. The #belongs_to association is always used in the model that has
- # the foreign key.
- #
- # === One-to-one
- #
- # Use #has_one in the base, and #belongs_to in the associated model.
- #
- # class Employee < ActiveRecord::Base
- # has_one :office
- # end
- # class Office < ActiveRecord::Base
- # belongs_to :employee # foreign key - employee_id
- # end
- #
- # === One-to-many
- #
- # Use #has_many in the base, and #belongs_to in the associated model.
- #
- # class Manager < ActiveRecord::Base
- # has_many :employees
- # end
- # class Employee < ActiveRecord::Base
- # belongs_to :manager # foreign key - manager_id
- # end
- #
- # === Many-to-many
- #
- # There are two ways to build a many-to-many relationship.
- #
- # The first way uses a #has_many association with the <tt>:through</tt> option and a join model, so
- # there are two stages of associations.
- #
- # class Assignment < ActiveRecord::Base
- # belongs_to :programmer # foreign key - programmer_id
- # belongs_to :project # foreign key - project_id
- # end
- # class Programmer < ActiveRecord::Base
- # has_many :assignments
- # has_many :projects, through: :assignments
- # end
- # class Project < ActiveRecord::Base
- # has_many :assignments
- # has_many :programmers, through: :assignments
- # end
- #
- # For the second way, use #has_and_belongs_to_many in both models. This requires a join table
- # that has no corresponding model or primary key.
- #
- # class Programmer < ActiveRecord::Base
- # has_and_belongs_to_many :projects # foreign keys in the join table
- # end
- # class Project < ActiveRecord::Base
- # has_and_belongs_to_many :programmers # foreign keys in the join table
- # end
- #
- # Choosing which way to build a many-to-many relationship is not always simple.
- # If you need to work with the relationship model as its own entity,
- # use #has_many <tt>:through</tt>. Use #has_and_belongs_to_many when working with legacy schemas or when
- # you never work directly with the relationship itself.
- #
- # == Is it a #belongs_to or #has_one association?
- #
- # Both express a 1-1 relationship. The difference is mostly where to place the foreign
- # key, which goes on the table for the class declaring the #belongs_to relationship.
- #
- # class User < ActiveRecord::Base
- # # I reference an account.
- # belongs_to :account
- # end
- #
- # class Account < ActiveRecord::Base
- # # One user references me.
- # has_one :user
- # end
- #
- # The tables for these classes could look something like:
- #
- # CREATE TABLE users (
- # id int NOT NULL auto_increment,
- # account_id int default NULL,
- # name varchar default NULL,
- # PRIMARY KEY (id)
- # )
- #
- # CREATE TABLE accounts (
- # id int NOT NULL auto_increment,
- # name varchar default NULL,
- # PRIMARY KEY (id)
- # )
- #
- # == Unsaved objects and associations
- #
- # You can manipulate objects and associations before they are saved to the database, but
- # there is some special behavior you should be aware of, mostly involving the saving of
- # associated objects.
- #
- # You can set the <tt>:autosave</tt> option on a #has_one, #belongs_to,
- # #has_many, or #has_and_belongs_to_many association. Setting it
- # to +true+ will _always_ save the members, whereas setting it to +false+ will
- # _never_ save the members. More details about <tt>:autosave</tt> option is available at
- # AutosaveAssociation.
- #
- # === One-to-one associations
- #
- # * Assigning an object to a #has_one association automatically saves that object and
- # the object being replaced (if there is one), in order to update their foreign
- # keys - except if the parent object is unsaved (<tt>new_record? == true</tt>).
- # * If either of these saves fail (due to one of the objects being invalid), an
- # ActiveRecord::RecordNotSaved exception is raised and the assignment is
- # cancelled.
- # * If you wish to assign an object to a #has_one association without saving it,
- # use the <tt>#build_association</tt> method (documented below). The object being
- # replaced will still be saved to update its foreign key.
- # * Assigning an object to a #belongs_to association does not save the object, since
- # the foreign key field belongs on the parent. It does not save the parent either.
- #
- # === Collections
- #
- # * Adding an object to a collection (#has_many or #has_and_belongs_to_many) automatically
- # saves that object, except if the parent object (the owner of the collection) is not yet
- # stored in the database.
- # * If saving any of the objects being added to a collection (via <tt>push</tt> or similar)
- # fails, then <tt>push</tt> returns +false+.
- # * If saving fails while replacing the collection (via <tt>association=</tt>), an
- # ActiveRecord::RecordNotSaved exception is raised and the assignment is
- # cancelled.
- # * You can add an object to a collection without automatically saving it by using the
- # <tt>collection.build</tt> method (documented below).
- # * All unsaved (<tt>new_record? == true</tt>) members of the collection are automatically
- # saved when the parent is saved.
- #
- # == Customizing the query
- #
- # \Associations are built from <tt>Relation</tt> objects, and you can use the Relation syntax
- # to customize them. For example, to add a condition:
- #
- # class Blog < ActiveRecord::Base
- # has_many :published_posts, -> { where(published: true) }, class_name: 'Post'
- # end
- #
- # Inside the <tt>-> { ... }</tt> block you can use all of the usual Relation methods.
- #
- # === Accessing the owner object
- #
- # Sometimes it is useful to have access to the owner object when building the query. The owner
- # is passed as a parameter to the block. For example, the following association would find all
- # events that occur on the user's birthday:
- #
- # class User < ActiveRecord::Base
- # has_many :birthday_events, ->(user) { where(starts_on: user.birthday) }, class_name: 'Event'
- # end
- #
- # Note: Joining, eager loading and preloading of these associations is not fully possible.
- # These operations happen before instance creation and the scope will be called with a +nil+ argument.
- # This can lead to unexpected behavior and is deprecated.
- #
- # == Association callbacks
- #
- # Similar to the normal callbacks that hook into the life cycle of an Active Record object,
- # you can also define callbacks that get triggered when you add an object to or remove an
- # object from an association collection.
- #
- # class Project
- # has_and_belongs_to_many :developers, after_add: :evaluate_velocity
- #
- # def evaluate_velocity(developer)
- # ...
- # end
- # end
- #
- # It's possible to stack callbacks by passing them as an array. Example:
- #
- # class Project
- # has_and_belongs_to_many :developers,
- # after_add: [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
- # end
- #
- # Possible callbacks are: +before_add+, +after_add+, +before_remove+ and +after_remove+.
- #
- # If any of the +before_add+ callbacks throw an exception, the object will not be
- # added to the collection.
- #
- # Similarly, if any of the +before_remove+ callbacks throw an exception, the object
- # will not be removed from the collection.
- #
- # == Association extensions
- #
- # The proxy objects that control the access to associations can be extended through anonymous
- # modules. This is especially beneficial for adding new finders, creators, and other
- # factory-type methods that are only used as part of this association.
- #
- # class Account < ActiveRecord::Base
- # has_many :people do
- # def find_or_create_by_name(name)
- # first_name, last_name = name.split(" ", 2)
- # find_or_create_by(first_name: first_name, last_name: last_name)
- # end
- # end
- # end
- #
- # person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson")
- # person.first_name # => "David"
- # person.last_name # => "Heinemeier Hansson"
- #
- # If you need to share the same extensions between many associations, you can use a named
- # extension module.
- #
- # module FindOrCreateByNameExtension
- # def find_or_create_by_name(name)
- # first_name, last_name = name.split(" ", 2)
- # find_or_create_by(first_name: first_name, last_name: last_name)
- # end
- # end
- #
- # class Account < ActiveRecord::Base
- # has_many :people, -> { extending FindOrCreateByNameExtension }
- # end
- #
- # class Company < ActiveRecord::Base
- # has_many :people, -> { extending FindOrCreateByNameExtension }
- # end
- #
- # Some extensions can only be made to work with knowledge of the association's internals.
- # Extensions can access relevant state using the following methods (where +items+ is the
- # name of the association):
- #
- # * <tt>record.association(:items).owner</tt> - Returns the object the association is part of.
- # * <tt>record.association(:items).reflection</tt> - Returns the reflection object that describes the association.
- # * <tt>record.association(:items).target</tt> - Returns the associated object for #belongs_to and #has_one, or
- # the collection of associated objects for #has_many and #has_and_belongs_to_many.
- #
- # However, inside the actual extension code, you will not have access to the <tt>record</tt> as
- # above. In this case, you can access <tt>proxy_association</tt>. For example,
- # <tt>record.association(:items)</tt> and <tt>record.items.proxy_association</tt> will return
- # the same object, allowing you to make calls like <tt>proxy_association.owner</tt> inside
- # association extensions.
- #
- # == Association Join Models
- #
- # Has Many associations can be configured with the <tt>:through</tt> option to use an
- # explicit join model to retrieve the data. This operates similarly to a
- # #has_and_belongs_to_many association. The advantage is that you're able to add validations,
- # callbacks, and extra attributes on the join model. Consider the following schema:
- #
- # class Author < ActiveRecord::Base
- # has_many :authorships
- # has_many :books, through: :authorships
- # end
- #
- # class Authorship < ActiveRecord::Base
- # belongs_to :author
- # belongs_to :book
- # end
- #
- # @author = Author.first
- # @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to
- # @author.books # selects all books by using the Authorship join model
- #
- # You can also go through a #has_many association on the join model:
- #
- # class Firm < ActiveRecord::Base
- # has_many :clients
- # has_many :invoices, through: :clients
- # end
- #
- # class Client < ActiveRecord::Base
- # belongs_to :firm
- # has_many :invoices
- # end
- #
- # class Invoice < ActiveRecord::Base
- # belongs_to :client
- # end
- #
- # @firm = Firm.first
- # @firm.clients.flat_map { |c| c.invoices } # select all invoices for all clients of the firm
- # @firm.invoices # selects all invoices by going through the Client join model
- #
- # Similarly you can go through a #has_one association on the join model:
- #
- # class Group < ActiveRecord::Base
- # has_many :users
- # has_many :avatars, through: :users
- # end
- #
- # class User < ActiveRecord::Base
- # belongs_to :group
- # has_one :avatar
- # end
- #
- # class Avatar < ActiveRecord::Base
- # belongs_to :user
- # end
- #
- # @group = Group.first
- # @group.users.collect { |u| u.avatar }.compact # select all avatars for all users in the group
- # @group.avatars # selects all avatars by going through the User join model.
- #
- # An important caveat with going through #has_one or #has_many associations on the
- # join model is that these associations are *read-only*. For example, the following
- # would not work following the previous example:
- #
- # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around
- # @group.avatars.delete(@group.avatars.last) # so would this
- #
- # == Setting Inverses
- #
- # If you are using a #belongs_to on the join model, it is a good idea to set the
- # <tt>:inverse_of</tt> option on the #belongs_to, which will mean that the following example
- # works correctly (where <tt>tags</tt> is a #has_many <tt>:through</tt> association):
- #
- # @post = Post.first
- # @tag = @post.tags.build name: "ruby"
- # @tag.save
- #
- # The last line ought to save the through record (a <tt>Tagging</tt>). This will only work if the
- # <tt>:inverse_of</tt> is set:
- #
- # class Tagging < ActiveRecord::Base
- # belongs_to :post
- # belongs_to :tag, inverse_of: :taggings
- # end
- #
- # If you do not set the <tt>:inverse_of</tt> record, the association will
- # do its best to match itself up with the correct inverse. Automatic
- # inverse detection only works on #has_many, #has_one, and
- # #belongs_to associations.
- #
- # Extra options on the associations, as defined in the
- # <tt>AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS</tt> constant, will
- # also prevent the association's inverse from being found automatically.
- #
- # The automatic guessing of the inverse association uses a heuristic based
- # on the name of the class, so it may not work for all associations,
- # especially the ones with non-standard names.
- #
- # You can turn off the automatic detection of inverse associations by setting
- # the <tt>:inverse_of</tt> option to <tt>false</tt> like so:
- #
- # class Tagging < ActiveRecord::Base
- # belongs_to :tag, inverse_of: false
- # end
- #
- # == Nested \Associations
- #
- # You can actually specify *any* association with the <tt>:through</tt> option, including an
- # association which has a <tt>:through</tt> option itself. For example:
- #
- # class Author < ActiveRecord::Base
- # has_many :posts
- # has_many :comments, through: :posts
- # has_many :commenters, through: :comments
- # end
- #
- # class Post < ActiveRecord::Base
- # has_many :comments
- # end
- #
- # class Comment < ActiveRecord::Base
- # belongs_to :commenter
- # end
- #
- # @author = Author.first
- # @author.commenters # => People who commented on posts written by the author
- #
- # An equivalent way of setting up this association this would be:
- #
- # class Author < ActiveRecord::Base
- # has_many :posts
- # has_many :commenters, through: :posts
- # end
- #
- # class Post < ActiveRecord::Base
- # has_many :comments
- # has_many :commenters, through: :comments
- # end
- #
- # class Comment < ActiveRecord::Base
- # belongs_to :commenter
- # end
- #
- # When using a nested association, you will not be able to modify the association because there
- # is not enough information to know what modification to make. For example, if you tried to
- # add a <tt>Commenter</tt> in the example above, there would be no way to tell how to set up the
- # intermediate <tt>Post</tt> and <tt>Comment</tt> objects.
- #
- # == Polymorphic \Associations
- #
- # Polymorphic associations on models are not restricted on what types of models they
- # can be associated with. Rather, they specify an interface that a #has_many association
- # must adhere to.
- #
- # class Asset < ActiveRecord::Base
- # belongs_to :attachable, polymorphic: true
- # end
- #
- # class Post < ActiveRecord::Base
- # has_many :assets, as: :attachable # The :as option specifies the polymorphic interface to use.
- # end
- #
- # @asset.attachable = @post
- #
- # This works by using a type column in addition to a foreign key to specify the associated
- # record. In the Asset example, you'd need an +attachable_id+ integer column and an
- # +attachable_type+ string column.
- #
- # Using polymorphic associations in combination with single table inheritance (STI) is
- # a little tricky. In order for the associations to work as expected, ensure that you
- # store the base model for the STI models in the type column of the polymorphic
- # association. To continue with the asset example above, suppose there are guest posts
- # and member posts that use the posts table for STI. In this case, there must be a +type+
- # column in the posts table.
- #
- # Note: The <tt>attachable_type=</tt> method is being called when assigning an +attachable+.
- # The +class_name+ of the +attachable+ is passed as a String.
- #
- # class Asset < ActiveRecord::Base
- # belongs_to :attachable, polymorphic: true
- #
- # def attachable_type=(class_name)
- # super(class_name.constantize.base_class.to_s)
- # end
- # end
- #
- # class Post < ActiveRecord::Base
- # # because we store "Post" in attachable_type now dependent: :destroy will work
- # has_many :assets, as: :attachable, dependent: :destroy
- # end
- #
- # class GuestPost < Post
- # end
- #
- # class MemberPost < Post
- # end
- #
- # == Caching
- #
- # All of the methods are built on a simple caching principle that will keep the result
- # of the last query around unless specifically instructed not to. The cache is even
- # shared across methods to make it even cheaper to use the macro-added methods without
- # worrying too much about performance at the first go.
- #
- # project.milestones # fetches milestones from the database
- # project.milestones.size # uses the milestone cache
- # project.milestones.empty? # uses the milestone cache
- # project.milestones(true).size # fetches milestones from the database
- # project.milestones # uses the milestone cache
- #
- # == Eager loading of associations
- #
- # Eager loading is a way to find objects of a certain class and a number of named associations.
- # It is one of the easiest ways to prevent the dreaded N+1 problem in which fetching 100
- # posts that each need to display their author triggers 101 database queries. Through the
- # use of eager loading, the number of queries will be reduced from 101 to 2.
- #
- # class Post < ActiveRecord::Base
- # belongs_to :author
- # has_many :comments
- # end
- #
- # Consider the following loop using the class above:
- #
- # Post.all.each do |post|
- # puts "Post: " + post.title
- # puts "Written by: " + post.author.name
- # puts "Last comment on: " + post.comments.first.created_on
- # end
- #
- # To iterate over these one hundred posts, we'll generate 201 database queries. Let's
- # first just optimize it for retrieving the author:
- #
- # Post.includes(:author).each do |post|
- #
- # This references the name of the #belongs_to association that also used the <tt>:author</tt>
- # symbol. After loading the posts, +find+ will collect the +author_id+ from each one and load
- # all of the referenced authors with one query. Doing so will cut down the number of queries
- # from 201 to 102.
- #
- # We can improve upon the situation further by referencing both associations in the finder with:
- #
- # Post.includes(:author, :comments).each do |post|
- #
- # This will load all comments with a single query. This reduces the total number of queries
- # to 3. In general, the number of queries will be 1 plus the number of associations
- # named (except if some of the associations are polymorphic #belongs_to - see below).
- #
- # To include a deep hierarchy of associations, use a hash:
- #
- # Post.includes(:author, { comments: { author: :gravatar } }).each do |post|
- #
- # The above code will load all the comments and all of their associated
- # authors and gravatars. You can mix and match any combination of symbols,
- # arrays, and hashes to retrieve the associations you want to load.
- #
- # All of this power shouldn't fool you into thinking that you can pull out huge amounts
- # of data with no performance penalty just because you've reduced the number of queries.
- # The database still needs to send all the data to Active Record and it still needs to
- # be processed. So it's no catch-all for performance problems, but it's a great way to
- # cut down on the number of queries in a situation as the one described above.
- #
- # Since only one table is loaded at a time, conditions or orders cannot reference tables
- # other than the main one. If this is the case, Active Record falls back to the previously
- # used <tt>LEFT OUTER JOIN</tt> based strategy. For example:
- #
- # Post.includes([:author, :comments]).where(['comments.approved = ?', true])
- #
- # This will result in a single SQL query with joins along the lines of:
- # <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and
- # <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Note that using conditions
- # like this can have unintended consequences.
- # In the above example, posts with no approved comments are not returned at all because
- # the conditions apply to the SQL statement as a whole and not just to the association.
- #
- # You must disambiguate column references for this fallback to happen, for example
- # <tt>order: "author.name DESC"</tt> will work but <tt>order: "name DESC"</tt> will not.
- #
- # If you want to load all posts (including posts with no approved comments), then write
- # your own <tt>LEFT OUTER JOIN</tt> query using <tt>ON</tt>:
- #
- # Post.joins("LEFT OUTER JOIN comments ON comments.post_id = posts.id AND comments.approved = '1'")
- #
- # In this case, it is usually more natural to include an association which has conditions defined on it:
- #
- # class Post < ActiveRecord::Base
- # has_many :approved_comments, -> { where(approved: true) }, class_name: 'Comment'
- # end
- #
- # Post.includes(:approved_comments)
- #
- # This will load posts and eager load the +approved_comments+ association, which contains
- # only those comments that have been approved.
- #
- # If you eager load an association with a specified <tt>:limit</tt> option, it will be ignored,
- # returning all the associated objects:
- #
- # class Picture < ActiveRecord::Base
- # has_many :most_recent_comments, -> { order('id DESC').limit(10) }, class_name: 'Comment'
- # end
- #
- # Picture.includes(:most_recent_comments).first.most_recent_comments # => returns all associated comments.
- #
- # Eager loading is supported with polymorphic associations.
- #
- # class Address < ActiveRecord::Base
- # belongs_to :addressable, polymorphic: true
- # end
- #
- # A call that tries to eager load the addressable model
- #
- # Address.includes(:addressable)
- #
- # This will execute one query to load the addresses and load the addressables with one
- # query per addressable type.
- # For example, if all the addressables are either of class Person or Company, then a total
- # of 3 queries will be executed. The list of addressable types to load is determined on
- # the back of the addresses loaded. This is not supported if Active Record has to fallback
- # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError.
- # The reason is that the parent model's type is a column value so its corresponding table
- # name cannot be put in the +FROM+/+JOIN+ clauses of that query.
- #
- # == Table Aliasing
- #
- # Active Record uses table aliasing in the case that a table is referenced multiple times
- # in a join. If a table is referenced only once, the standard table name is used. The
- # second time, the table is aliased as <tt>#{reflection_name}_#{parent_table_name}</tt>.
- # Indexes are appended for any more successive uses of the table name.
- #
- # Post.joins(:comments)
- # # => SELECT ... FROM posts INNER JOIN comments ON ...
- # Post.joins(:special_comments) # STI
- # # => SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment'
- # Post.joins(:comments, :special_comments) # special_comments is the reflection name, posts is the parent table name
- # # => SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts
- #
- # Acts as tree example:
- #
- # TreeMixin.joins(:children)
- # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
- # TreeMixin.joins(children: :parent)
- # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
- # INNER JOIN parents_mixins ...
- # TreeMixin.joins(children: {parent: :children})
- # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
- # INNER JOIN parents_mixins ...
- # INNER JOIN mixins childrens_mixins_2
- #
- # Has and Belongs to Many join tables use the same idea, but add a <tt>_join</tt> suffix:
- #
- # Post.joins(:categories)
- # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
- # Post.joins(categories: :posts)
- # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
- # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
- # Post.joins(categories: {posts: :categories})
- # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
- # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
- # INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2
- #
- # If you wish to specify your own custom joins using ActiveRecord::QueryMethods#joins method, those table
- # names will take precedence over the eager associations:
- #
- # Post.joins(:comments).joins("inner join comments ...")
- # # => SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ...
- # Post.joins(:comments, :special_comments).joins("inner join comments ...")
- # # => SELECT ... FROM posts INNER JOIN comments comments_posts ON ...
- # INNER JOIN comments special_comments_posts ...
- # INNER JOIN comments ...
- #
- # Table aliases are automatically truncated according to the maximum length of table identifiers
- # according to the specific database.
- #
- # == Modules
- #
- # By default, associations will look for objects within the current module scope. Consider:
- #
- # module MyApplication
- # module Business
- # class Firm < ActiveRecord::Base
- # has_many :clients
- # end
- #
- # class Client < ActiveRecord::Base; end
- # end
- # end
- #
- # When <tt>Firm#clients</tt> is called, it will in turn call
- # <tt>MyApplication::Business::Client.find_all_by_firm_id(firm.id)</tt>.
- # If you want to associate with a class in another module scope, this can be done by
- # specifying the complete class name.
- #
- # module MyApplication
- # module Business
- # class Firm < ActiveRecord::Base; end
- # end
- #
- # module Billing
- # class Account < ActiveRecord::Base
- # belongs_to :firm, class_name: "MyApplication::Business::Firm"
- # end
- # end
- # end
- #
- # == Bi-directional associations
- #
- # When you specify an association, there is usually an association on the associated model
- # that specifies the same relationship in reverse. For example, with the following models:
- #
- # class Dungeon < ActiveRecord::Base
- # has_many :traps
- # has_one :evil_wizard
- # end
- #
- # class Trap < ActiveRecord::Base
- # belongs_to :dungeon
- # end
- #
- # class EvilWizard < ActiveRecord::Base
- # belongs_to :dungeon
- # end
- #
- # The +traps+ association on +Dungeon+ and the +dungeon+ association on +Trap+ are
- # the inverse of each other, and the inverse of the +dungeon+ association on +EvilWizard+
- # is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default,
- # Active Record can guess the inverse of the association based on the name
- # of the class. The result is the following:
- #
- # d = Dungeon.first
- # t = d.traps.first
- # d.object_id == t.dungeon.object_id # => true
- #
- # The +Dungeon+ instances +d+ and <tt>t.dungeon</tt> in the above example refer to
- # the same in-memory instance since the association matches the name of the class.
- # The result would be the same if we added +:inverse_of+ to our model definitions:
- #
- # class Dungeon < ActiveRecord::Base
- # has_many :traps, inverse_of: :dungeon
- # has_one :evil_wizard, inverse_of: :dungeon
- # end
- #
- # class Trap < ActiveRecord::Base
- # belongs_to :dungeon, inverse_of: :traps
- # end
- #
- # class EvilWizard < ActiveRecord::Base
- # belongs_to :dungeon, inverse_of: :evil_wizard
- # end
- #
- # There are limitations to <tt>:inverse_of</tt> support:
- #
- # * does not work with <tt>:through</tt> associations.
- # * does not work with <tt>:polymorphic</tt> associations.
- # * inverse associations for #belongs_to associations #has_many are ignored.
- #
- # For more information, see the documentation for the +:inverse_of+ option.
- #
- # == Deleting from associations
- #
- # === Dependent associations
- #
- # #has_many, #has_one, and #belongs_to associations support the <tt>:dependent</tt> option.
- # This allows you to specify that associated records should be deleted when the owner is
- # deleted.
- #
- # For example:
- #
- # class Author
- # has_many :posts, dependent: :destroy
- # end
- # Author.find(1).destroy # => Will destroy all of the author's posts, too
- #
- # The <tt>:dependent</tt> option can have different values which specify how the deletion
- # is done. For more information, see the documentation for this option on the different
- # specific association types. When no option is given, the behavior is to do nothing
- # with the associated records when destroying a record.
- #
- # Note that <tt>:dependent</tt> is implemented using Rails' callback
- # system, which works by processing callbacks in order. Therefore, other
- # callbacks declared either before or after the <tt>:dependent</tt> option
- # can affect what it does.
- #
- # Note that <tt>:dependent</tt> option is ignored for #has_one <tt>:through</tt> associations.
- #
- # === Delete or destroy?
- #
- # #has_many and #has_and_belongs_to_many associations have the methods <tt>destroy</tt>,
- # <tt>delete</tt>, <tt>destroy_all</tt> and <tt>delete_all</tt>.
- #
- # For #has_and_belongs_to_many, <tt>delete</tt> and <tt>destroy</tt> are the same: they
- # cause the records in the join table to be removed.
- #
- # For #has_many, <tt>destroy</tt> and <tt>destroy_all</tt> will always call the <tt>destroy</tt> method of the
- # record(s) being removed so that callbacks are run. However <tt>delete</tt> and <tt>delete_all</tt> will either
- # do the deletion according to the strategy specified by the <tt>:dependent</tt> option, or
- # if no <tt>:dependent</tt> option is given, then it will follow the default strategy.
- # The default strategy is to do nothing (leave the foreign keys with the parent ids set), except for
- # #has_many <tt>:through</tt>, where the default strategy is <tt>delete_all</tt> (delete
- # the join records, without running their callbacks).
- #
- # There is also a <tt>clear</tt> method which is the same as <tt>delete_all</tt>, except that
- # it returns the association rather than the records which have been deleted.
- #
- # === What gets deleted?
- #
- # There is a potential pitfall here: #has_and_belongs_to_many and #has_many <tt>:through</tt>
- # associations have records in join tables, as well as the associated records. So when we
- # call one of these deletion methods, what exactly should be deleted?
- #
- # The answer is that it is assumed that deletion on an association is about removing the
- # <i>link</i> between the owner and the associated object(s), rather than necessarily the
- # associated objects themselves. So with #has_and_belongs_to_many and #has_many
- # <tt>:through</tt>, the join records will be deleted, but the associated records won't.
- #
- # This makes sense if you think about it: if you were to call <tt>post.tags.delete(Tag.find_by(name: 'food'))</tt>
- # you would want the 'food' tag to be unlinked from the post, rather than for the tag itself
- # to be removed from the database.
- #
- # However, there are examples where this strategy doesn't make sense. For example, suppose
- # a person has many projects, and each project has many tasks. If we deleted one of a person's
- # tasks, we would probably not want the project to be deleted. In this scenario, the delete method
- # won't actually work: it can only be used if the association on the join model is a
- # #belongs_to. In other situations you are expected to perform operations directly on
- # either the associated records or the <tt>:through</tt> association.
- #
- # With a regular #has_many there is no distinction between the "associated records"
- # and the "link", so there is only one choice for what gets deleted.
- #
- # With #has_and_belongs_to_many and #has_many <tt>:through</tt>, if you want to delete the
- # associated records themselves, you can always do something along the lines of
- # <tt>person.tasks.each(&:destroy)</tt>.
- #
- # == Type safety with ActiveRecord::AssociationTypeMismatch
- #
- # If you attempt to assign an object to an association that doesn't match the inferred
- # or specified <tt>:class_name</tt>, you'll get an ActiveRecord::AssociationTypeMismatch.
- #
- # == Options
- #
- # All of the association macros can be specialized through options. This makes cases
- # more complex than the simple and guessable ones possible.
+ # \Associations are a set of macro-like class methods for tying objects together through
+ # foreign keys. They express relationships like "Project has one Project Manager"
+ # or "Project belongs to a Portfolio". Each macro adds a number of methods to the
+ # class which are specialized according to the collection or association symbol and the
+ # options hash. It works much the same way as Ruby's own <tt>attr*</tt>
+ # methods.
+ #
+ # class Project < ActiveRecord::Base
+ # belongs_to :portfolio
+ # has_one :project_manager
+ # has_many :milestones
+ # has_and_belongs_to_many :categories
+ # end
+ #
+ # The project class now has the following methods (and more) to ease the traversal and
+ # manipulation of its relationships:
+ # * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt>
+ # * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt>
+ # * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt>
+ # <tt>Project#milestones.delete(milestone), Project#milestones.destroy(milestone), Project#milestones.find(milestone_id),</tt>
+ # <tt>Project#milestones.build, Project#milestones.create</tt>
+ # * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt>
+ # <tt>Project#categories.delete(category1), Project#categories.destroy(category1)</tt>
+ #
+ # === A word of warning
+ #
+ # Don't create associations that have the same name as {instance methods}[rdoc-ref:ActiveRecord::Core] of
+ # <tt>ActiveRecord::Base</tt>. Since the association adds a method with that name to
+ # its model, using an association with the same name as one provided by <tt>ActiveRecord::Base</tt> will override the method inherited through <tt>ActiveRecord::Base</tt> and will break things.
+ # For instance, +attributes+ and +connection+ would be bad choices for association names, because those names already exist in the list of <tt>ActiveRecord::Base</tt> instance methods.
+ #
+ # == Auto-generated methods
+ # See also Instance Public methods below for more details.
+ #
+ # === Singular associations (one-to-one)
+ # | | belongs_to |
+ # generated methods | belongs_to | :polymorphic | has_one
+ # ----------------------------------+------------+--------------+---------
+ # other(force_reload=false) | X | X | X
+ # other=(other) | X | X | X
+ # build_other(attributes={}) | X | | X
+ # create_other(attributes={}) | X | | X
+ # create_other!(attributes={}) | X | | X
+ #
+ # === Collection associations (one-to-many / many-to-many)
+ # | | | has_many
+ # generated methods | habtm | has_many | :through
+ # ----------------------------------+-------+----------+----------
+ # others(force_reload=false) | X | X | X
+ # others=(other,other,...) | X | X | X
+ # other_ids | X | X | X
+ # other_ids=(id,id,...) | X | X | X
+ # others<< | X | X | X
+ # others.push | X | X | X
+ # others.concat | X | X | X
+ # others.build(attributes={}) | X | X | X
+ # others.create(attributes={}) | X | X | X
+ # others.create!(attributes={}) | X | X | X
+ # others.size | X | X | X
+ # others.length | X | X | X
+ # others.count | X | X | X
+ # others.sum(*args) | X | X | X
+ # others.empty? | X | X | X
+ # others.clear | X | X | X
+ # others.delete(other,other,...) | X | X | X
+ # others.delete_all | X | X | X
+ # others.destroy(other,other,...) | X | X | X
+ # others.destroy_all | X | X | X
+ # others.find(*args) | X | X | X
+ # others.exists? | X | X | X
+ # others.distinct | X | X | X
+ # others.reset | X | X | X
+ #
+ # === Overriding generated methods
+ #
+ # Association methods are generated in a module included into the model
+ # class, making overrides easy. The original generated method can thus be
+ # called with +super+:
+ #
+ # class Car < ActiveRecord::Base
+ # belongs_to :owner
+ # belongs_to :old_owner
+ #
+ # def owner=(new_owner)
+ # self.old_owner = self.owner
+ # super
+ # end
+ # end
+ #
+ # The association methods module is included immediately after the
+ # generated attributes methods module, meaning an association will
+ # override the methods for an attribute with the same name.
+ #
+ # == Cardinality and associations
+ #
+ # Active Record associations can be used to describe one-to-one, one-to-many and many-to-many
+ # relationships between models. Each model uses an association to describe its role in
+ # the relation. The #belongs_to association is always used in the model that has
+ # the foreign key.
+ #
+ # === One-to-one
+ #
+ # Use #has_one in the base, and #belongs_to in the associated model.
+ #
+ # class Employee < ActiveRecord::Base
+ # has_one :office
+ # end
+ # class Office < ActiveRecord::Base
+ # belongs_to :employee # foreign key - employee_id
+ # end
+ #
+ # === One-to-many
+ #
+ # Use #has_many in the base, and #belongs_to in the associated model.
+ #
+ # class Manager < ActiveRecord::Base
+ # has_many :employees
+ # end
+ # class Employee < ActiveRecord::Base
+ # belongs_to :manager # foreign key - manager_id
+ # end
+ #
+ # === Many-to-many
+ #
+ # There are two ways to build a many-to-many relationship.
+ #
+ # The first way uses a #has_many association with the <tt>:through</tt> option and a join model, so
+ # there are two stages of associations.
+ #
+ # class Assignment < ActiveRecord::Base
+ # belongs_to :programmer # foreign key - programmer_id
+ # belongs_to :project # foreign key - project_id
+ # end
+ # class Programmer < ActiveRecord::Base
+ # has_many :assignments
+ # has_many :projects, through: :assignments
+ # end
+ # class Project < ActiveRecord::Base
+ # has_many :assignments
+ # has_many :programmers, through: :assignments
+ # end
+ #
+ # For the second way, use #has_and_belongs_to_many in both models. This requires a join table
+ # that has no corresponding model or primary key.
+ #
+ # class Programmer < ActiveRecord::Base
+ # has_and_belongs_to_many :projects # foreign keys in the join table
+ # end
+ # class Project < ActiveRecord::Base
+ # has_and_belongs_to_many :programmers # foreign keys in the join table
+ # end
+ #
+ # Choosing which way to build a many-to-many relationship is not always simple.
+ # If you need to work with the relationship model as its own entity,
+ # use #has_many <tt>:through</tt>. Use #has_and_belongs_to_many when working with legacy schemas or when
+ # you never work directly with the relationship itself.
+ #
+ # == Is it a #belongs_to or #has_one association?
+ #
+ # Both express a 1-1 relationship. The difference is mostly where to place the foreign
+ # key, which goes on the table for the class declaring the #belongs_to relationship.
+ #
+ # class User < ActiveRecord::Base
+ # # I reference an account.
+ # belongs_to :account
+ # end
+ #
+ # class Account < ActiveRecord::Base
+ # # One user references me.
+ # has_one :user
+ # end
+ #
+ # The tables for these classes could look something like:
+ #
+ # CREATE TABLE users (
+ # id int NOT NULL auto_increment,
+ # account_id int default NULL,
+ # name varchar default NULL,
+ # PRIMARY KEY (id)
+ # )
+ #
+ # CREATE TABLE accounts (
+ # id int NOT NULL auto_increment,
+ # name varchar default NULL,
+ # PRIMARY KEY (id)
+ # )
+ #
+ # == Unsaved objects and associations
+ #
+ # You can manipulate objects and associations before they are saved to the database, but
+ # there is some special behavior you should be aware of, mostly involving the saving of
+ # associated objects.
+ #
+ # You can set the <tt>:autosave</tt> option on a #has_one, #belongs_to,
+ # #has_many, or #has_and_belongs_to_many association. Setting it
+ # to +true+ will _always_ save the members, whereas setting it to +false+ will
+ # _never_ save the members. More details about <tt>:autosave</tt> option is available at
+ # AutosaveAssociation.
+ #
+ # === One-to-one associations
+ #
+ # * Assigning an object to a #has_one association automatically saves that object and
+ # the object being replaced (if there is one), in order to update their foreign
+ # keys - except if the parent object is unsaved (<tt>new_record? == true</tt>).
+ # * If either of these saves fail (due to one of the objects being invalid), an
+ # ActiveRecord::RecordNotSaved exception is raised and the assignment is
+ # cancelled.
+ # * If you wish to assign an object to a #has_one association without saving it,
+ # use the <tt>#build_association</tt> method (documented below). The object being
+ # replaced will still be saved to update its foreign key.
+ # * Assigning an object to a #belongs_to association does not save the object, since
+ # the foreign key field belongs on the parent. It does not save the parent either.
+ #
+ # === Collections
+ #
+ # * Adding an object to a collection (#has_many or #has_and_belongs_to_many) automatically
+ # saves that object, except if the parent object (the owner of the collection) is not yet
+ # stored in the database.
+ # * If saving any of the objects being added to a collection (via <tt>push</tt> or similar)
+ # fails, then <tt>push</tt> returns +false+.
+ # * If saving fails while replacing the collection (via <tt>association=</tt>), an
+ # ActiveRecord::RecordNotSaved exception is raised and the assignment is
+ # cancelled.
+ # * You can add an object to a collection without automatically saving it by using the
+ # <tt>collection.build</tt> method (documented below).
+ # * All unsaved (<tt>new_record? == true</tt>) members of the collection are automatically
+ # saved when the parent is saved.
+ #
+ # == Customizing the query
+ #
+ # \Associations are built from <tt>Relation</tt> objects, and you can use the Relation syntax
+ # to customize them. For example, to add a condition:
+ #
+ # class Blog < ActiveRecord::Base
+ # has_many :published_posts, -> { where(published: true) }, class_name: 'Post'
+ # end
+ #
+ # Inside the <tt>-> { ... }</tt> block you can use all of the usual Relation methods.
+ #
+ # === Accessing the owner object
+ #
+ # Sometimes it is useful to have access to the owner object when building the query. The owner
+ # is passed as a parameter to the block. For example, the following association would find all
+ # events that occur on the user's birthday:
+ #
+ # class User < ActiveRecord::Base
+ # has_many :birthday_events, ->(user) { where(starts_on: user.birthday) }, class_name: 'Event'
+ # end
+ #
+ # Note: Joining, eager loading and preloading of these associations is not fully possible.
+ # These operations happen before instance creation and the scope will be called with a +nil+ argument.
+ # This can lead to unexpected behavior and is deprecated.
+ #
+ # == Association callbacks
+ #
+ # Similar to the normal callbacks that hook into the life cycle of an Active Record object,
+ # you can also define callbacks that get triggered when you add an object to or remove an
+ # object from an association collection.
+ #
+ # class Project
+ # has_and_belongs_to_many :developers, after_add: :evaluate_velocity
+ #
+ # def evaluate_velocity(developer)
+ # ...
+ # end
+ # end
+ #
+ # It's possible to stack callbacks by passing them as an array. Example:
+ #
+ # class Project
+ # has_and_belongs_to_many :developers,
+ # after_add: [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
+ # end
+ #
+ # Possible callbacks are: +before_add+, +after_add+, +before_remove+ and +after_remove+.
+ #
+ # If any of the +before_add+ callbacks throw an exception, the object will not be
+ # added to the collection.
+ #
+ # Similarly, if any of the +before_remove+ callbacks throw an exception, the object
+ # will not be removed from the collection.
+ #
+ # == Association extensions
+ #
+ # The proxy objects that control the access to associations can be extended through anonymous
+ # modules. This is especially beneficial for adding new finders, creators, and other
+ # factory-type methods that are only used as part of this association.
+ #
+ # class Account < ActiveRecord::Base
+ # has_many :people do
+ # def find_or_create_by_name(name)
+ # first_name, last_name = name.split(" ", 2)
+ # find_or_create_by(first_name: first_name, last_name: last_name)
+ # end
+ # end
+ # end
+ #
+ # person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson")
+ # person.first_name # => "David"
+ # person.last_name # => "Heinemeier Hansson"
+ #
+ # If you need to share the same extensions between many associations, you can use a named
+ # extension module.
+ #
+ # module FindOrCreateByNameExtension
+ # def find_or_create_by_name(name)
+ # first_name, last_name = name.split(" ", 2)
+ # find_or_create_by(first_name: first_name, last_name: last_name)
+ # end
+ # end
+ #
+ # class Account < ActiveRecord::Base
+ # has_many :people, -> { extending FindOrCreateByNameExtension }
+ # end
+ #
+ # class Company < ActiveRecord::Base
+ # has_many :people, -> { extending FindOrCreateByNameExtension }
+ # end
+ #
+ # Some extensions can only be made to work with knowledge of the association's internals.
+ # Extensions can access relevant state using the following methods (where +items+ is the
+ # name of the association):
+ #
+ # * <tt>record.association(:items).owner</tt> - Returns the object the association is part of.
+ # * <tt>record.association(:items).reflection</tt> - Returns the reflection object that describes the association.
+ # * <tt>record.association(:items).target</tt> - Returns the associated object for #belongs_to and #has_one, or
+ # the collection of associated objects for #has_many and #has_and_belongs_to_many.
+ #
+ # However, inside the actual extension code, you will not have access to the <tt>record</tt> as
+ # above. In this case, you can access <tt>proxy_association</tt>. For example,
+ # <tt>record.association(:items)</tt> and <tt>record.items.proxy_association</tt> will return
+ # the same object, allowing you to make calls like <tt>proxy_association.owner</tt> inside
+ # association extensions.
+ #
+ # == Association Join Models
+ #
+ # Has Many associations can be configured with the <tt>:through</tt> option to use an
+ # explicit join model to retrieve the data. This operates similarly to a
+ # #has_and_belongs_to_many association. The advantage is that you're able to add validations,
+ # callbacks, and extra attributes on the join model. Consider the following schema:
+ #
+ # class Author < ActiveRecord::Base
+ # has_many :authorships
+ # has_many :books, through: :authorships
+ # end
+ #
+ # class Authorship < ActiveRecord::Base
+ # belongs_to :author
+ # belongs_to :book
+ # end
+ #
+ # @author = Author.first
+ # @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to
+ # @author.books # selects all books by using the Authorship join model
+ #
+ # You can also go through a #has_many association on the join model:
+ #
+ # class Firm < ActiveRecord::Base
+ # has_many :clients
+ # has_many :invoices, through: :clients
+ # end
+ #
+ # class Client < ActiveRecord::Base
+ # belongs_to :firm
+ # has_many :invoices
+ # end
+ #
+ # class Invoice < ActiveRecord::Base
+ # belongs_to :client
+ # end
+ #
+ # @firm = Firm.first
+ # @firm.clients.flat_map { |c| c.invoices } # select all invoices for all clients of the firm
+ # @firm.invoices # selects all invoices by going through the Client join model
+ #
+ # Similarly you can go through a #has_one association on the join model:
+ #
+ # class Group < ActiveRecord::Base
+ # has_many :users
+ # has_many :avatars, through: :users
+ # end
+ #
+ # class User < ActiveRecord::Base
+ # belongs_to :group
+ # has_one :avatar
+ # end
+ #
+ # class Avatar < ActiveRecord::Base
+ # belongs_to :user
+ # end
+ #
+ # @group = Group.first
+ # @group.users.collect { |u| u.avatar }.compact # select all avatars for all users in the group
+ # @group.avatars # selects all avatars by going through the User join model.
+ #
+ # An important caveat with going through #has_one or #has_many associations on the
+ # join model is that these associations are *read-only*. For example, the following
+ # would not work following the previous example:
+ #
+ # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around
+ # @group.avatars.delete(@group.avatars.last) # so would this
+ #
+ # == Setting Inverses
+ #
+ # If you are using a #belongs_to on the join model, it is a good idea to set the
+ # <tt>:inverse_of</tt> option on the #belongs_to, which will mean that the following example
+ # works correctly (where <tt>tags</tt> is a #has_many <tt>:through</tt> association):
+ #
+ # @post = Post.first
+ # @tag = @post.tags.build name: "ruby"
+ # @tag.save
+ #
+ # The last line ought to save the through record (a <tt>Tagging</tt>). This will only work if the
+ # <tt>:inverse_of</tt> is set:
+ #
+ # class Tagging < ActiveRecord::Base
+ # belongs_to :post
+ # belongs_to :tag, inverse_of: :taggings
+ # end
+ #
+ # If you do not set the <tt>:inverse_of</tt> record, the association will
+ # do its best to match itself up with the correct inverse. Automatic
+ # inverse detection only works on #has_many, #has_one, and
+ # #belongs_to associations.
+ #
+ # Extra options on the associations, as defined in the
+ # <tt>AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS</tt> constant, will
+ # also prevent the association's inverse from being found automatically.
+ #
+ # The automatic guessing of the inverse association uses a heuristic based
+ # on the name of the class, so it may not work for all associations,
+ # especially the ones with non-standard names.
+ #
+ # You can turn off the automatic detection of inverse associations by setting
+ # the <tt>:inverse_of</tt> option to <tt>false</tt> like so:
+ #
+ # class Tagging < ActiveRecord::Base
+ # belongs_to :tag, inverse_of: false
+ # end
+ #
+ # == Nested \Associations
+ #
+ # You can actually specify *any* association with the <tt>:through</tt> option, including an
+ # association which has a <tt>:through</tt> option itself. For example:
+ #
+ # class Author < ActiveRecord::Base
+ # has_many :posts
+ # has_many :comments, through: :posts
+ # has_many :commenters, through: :comments
+ # end
+ #
+ # class Post < ActiveRecord::Base
+ # has_many :comments
+ # end
+ #
+ # class Comment < ActiveRecord::Base
+ # belongs_to :commenter
+ # end
+ #
+ # @author = Author.first
+ # @author.commenters # => People who commented on posts written by the author
+ #
+ # An equivalent way of setting up this association this would be:
+ #
+ # class Author < ActiveRecord::Base
+ # has_many :posts
+ # has_many :commenters, through: :posts
+ # end
+ #
+ # class Post < ActiveRecord::Base
+ # has_many :comments
+ # has_many :commenters, through: :comments
+ # end
+ #
+ # class Comment < ActiveRecord::Base
+ # belongs_to :commenter
+ # end
+ #
+ # When using a nested association, you will not be able to modify the association because there
+ # is not enough information to know what modification to make. For example, if you tried to
+ # add a <tt>Commenter</tt> in the example above, there would be no way to tell how to set up the
+ # intermediate <tt>Post</tt> and <tt>Comment</tt> objects.
+ #
+ # == Polymorphic \Associations
+ #
+ # Polymorphic associations on models are not restricted on what types of models they
+ # can be associated with. Rather, they specify an interface that a #has_many association
+ # must adhere to.
+ #
+ # class Asset < ActiveRecord::Base
+ # belongs_to :attachable, polymorphic: true
+ # end
+ #
+ # class Post < ActiveRecord::Base
+ # has_many :assets, as: :attachable # The :as option specifies the polymorphic interface to use.
+ # end
+ #
+ # @asset.attachable = @post
+ #
+ # This works by using a type column in addition to a foreign key to specify the associated
+ # record. In the Asset example, you'd need an +attachable_id+ integer column and an
+ # +attachable_type+ string column.
+ #
+ # Using polymorphic associations in combination with single table inheritance (STI) is
+ # a little tricky. In order for the associations to work as expected, ensure that you
+ # store the base model for the STI models in the type column of the polymorphic
+ # association. To continue with the asset example above, suppose there are guest posts
+ # and member posts that use the posts table for STI. In this case, there must be a +type+
+ # column in the posts table.
+ #
+ # Note: The <tt>attachable_type=</tt> method is being called when assigning an +attachable+.
+ # The +class_name+ of the +attachable+ is passed as a String.
+ #
+ # class Asset < ActiveRecord::Base
+ # belongs_to :attachable, polymorphic: true
+ #
+ # def attachable_type=(class_name)
+ # super(class_name.constantize.base_class.to_s)
+ # end
+ # end
+ #
+ # class Post < ActiveRecord::Base
+ # # because we store "Post" in attachable_type now dependent: :destroy will work
+ # has_many :assets, as: :attachable, dependent: :destroy
+ # end
+ #
+ # class GuestPost < Post
+ # end
+ #
+ # class MemberPost < Post
+ # end
+ #
+ # == Caching
+ #
+ # All of the methods are built on a simple caching principle that will keep the result
+ # of the last query around unless specifically instructed not to. The cache is even
+ # shared across methods to make it even cheaper to use the macro-added methods without
+ # worrying too much about performance at the first go.
+ #
+ # project.milestones # fetches milestones from the database
+ # project.milestones.size # uses the milestone cache
+ # project.milestones.empty? # uses the milestone cache
+ # project.milestones(true).size # fetches milestones from the database
+ # project.milestones # uses the milestone cache
+ #
+ # == Eager loading of associations
+ #
+ # Eager loading is a way to find objects of a certain class and a number of named associations.
+ # It is one of the easiest ways to prevent the dreaded N+1 problem in which fetching 100
+ # posts that each need to display their author triggers 101 database queries. Through the
+ # use of eager loading, the number of queries will be reduced from 101 to 2.
+ #
+ # class Post < ActiveRecord::Base
+ # belongs_to :author
+ # has_many :comments
+ # end
+ #
+ # Consider the following loop using the class above:
+ #
+ # Post.all.each do |post|
+ # puts "Post: " + post.title
+ # puts "Written by: " + post.author.name
+ # puts "Last comment on: " + post.comments.first.created_on
+ # end
+ #
+ # To iterate over these one hundred posts, we'll generate 201 database queries. Let's
+ # first just optimize it for retrieving the author:
+ #
+ # Post.includes(:author).each do |post|
+ #
+ # This references the name of the #belongs_to association that also used the <tt>:author</tt>
+ # symbol. After loading the posts, +find+ will collect the +author_id+ from each one and load
+ # all of the referenced authors with one query. Doing so will cut down the number of queries
+ # from 201 to 102.
+ #
+ # We can improve upon the situation further by referencing both associations in the finder with:
+ #
+ # Post.includes(:author, :comments).each do |post|
+ #
+ # This will load all comments with a single query. This reduces the total number of queries
+ # to 3. In general, the number of queries will be 1 plus the number of associations
+ # named (except if some of the associations are polymorphic #belongs_to - see below).
+ #
+ # To include a deep hierarchy of associations, use a hash:
+ #
+ # Post.includes(:author, { comments: { author: :gravatar } }).each do |post|
+ #
+ # The above code will load all the comments and all of their associated
+ # authors and gravatars. You can mix and match any combination of symbols,
+ # arrays, and hashes to retrieve the associations you want to load.
+ #
+ # All of this power shouldn't fool you into thinking that you can pull out huge amounts
+ # of data with no performance penalty just because you've reduced the number of queries.
+ # The database still needs to send all the data to Active Record and it still needs to
+ # be processed. So it's no catch-all for performance problems, but it's a great way to
+ # cut down on the number of queries in a situation as the one described above.
+ #
+ # Since only one table is loaded at a time, conditions or orders cannot reference tables
+ # other than the main one. If this is the case, Active Record falls back to the previously
+ # used <tt>LEFT OUTER JOIN</tt> based strategy. For example:
+ #
+ # Post.includes([:author, :comments]).where(['comments.approved = ?', true])
+ #
+ # This will result in a single SQL query with joins along the lines of:
+ # <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and
+ # <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Note that using conditions
+ # like this can have unintended consequences.
+ # In the above example, posts with no approved comments are not returned at all because
+ # the conditions apply to the SQL statement as a whole and not just to the association.
+ #
+ # You must disambiguate column references for this fallback to happen, for example
+ # <tt>order: "author.name DESC"</tt> will work but <tt>order: "name DESC"</tt> will not.
+ #
+ # If you want to load all posts (including posts with no approved comments), then write
+ # your own <tt>LEFT OUTER JOIN</tt> query using <tt>ON</tt>:
+ #
+ # Post.joins("LEFT OUTER JOIN comments ON comments.post_id = posts.id AND comments.approved = '1'")
+ #
+ # In this case, it is usually more natural to include an association which has conditions defined on it:
+ #
+ # class Post < ActiveRecord::Base
+ # has_many :approved_comments, -> { where(approved: true) }, class_name: 'Comment'
+ # end
+ #
+ # Post.includes(:approved_comments)
+ #
+ # This will load posts and eager load the +approved_comments+ association, which contains
+ # only those comments that have been approved.
+ #
+ # If you eager load an association with a specified <tt>:limit</tt> option, it will be ignored,
+ # returning all the associated objects:
+ #
+ # class Picture < ActiveRecord::Base
+ # has_many :most_recent_comments, -> { order('id DESC').limit(10) }, class_name: 'Comment'
+ # end
+ #
+ # Picture.includes(:most_recent_comments).first.most_recent_comments # => returns all associated comments.
+ #
+ # Eager loading is supported with polymorphic associations.
+ #
+ # class Address < ActiveRecord::Base
+ # belongs_to :addressable, polymorphic: true
+ # end
+ #
+ # A call that tries to eager load the addressable model
+ #
+ # Address.includes(:addressable)
+ #
+ # This will execute one query to load the addresses and load the addressables with one
+ # query per addressable type.
+ # For example, if all the addressables are either of class Person or Company, then a total
+ # of 3 queries will be executed. The list of addressable types to load is determined on
+ # the back of the addresses loaded. This is not supported if Active Record has to fallback
+ # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError.
+ # The reason is that the parent model's type is a column value so its corresponding table
+ # name cannot be put in the +FROM+/+JOIN+ clauses of that query.
+ #
+ # == Table Aliasing
+ #
+ # Active Record uses table aliasing in the case that a table is referenced multiple times
+ # in a join. If a table is referenced only once, the standard table name is used. The
+ # second time, the table is aliased as <tt>#{reflection_name}_#{parent_table_name}</tt>.
+ # Indexes are appended for any more successive uses of the table name.
+ #
+ # Post.joins(:comments)
+ # # => SELECT ... FROM posts INNER JOIN comments ON ...
+ # Post.joins(:special_comments) # STI
+ # # => SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment'
+ # Post.joins(:comments, :special_comments) # special_comments is the reflection name, posts is the parent table name
+ # # => SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts
+ #
+ # Acts as tree example:
+ #
+ # TreeMixin.joins(:children)
+ # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
+ # TreeMixin.joins(children: :parent)
+ # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
+ # INNER JOIN parents_mixins ...
+ # TreeMixin.joins(children: {parent: :children})
+ # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
+ # INNER JOIN parents_mixins ...
+ # INNER JOIN mixins childrens_mixins_2
+ #
+ # Has and Belongs to Many join tables use the same idea, but add a <tt>_join</tt> suffix:
+ #
+ # Post.joins(:categories)
+ # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
+ # Post.joins(categories: :posts)
+ # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
+ # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
+ # Post.joins(categories: {posts: :categories})
+ # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
+ # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
+ # INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2
+ #
+ # If you wish to specify your own custom joins using ActiveRecord::QueryMethods#joins method, those table
+ # names will take precedence over the eager associations:
+ #
+ # Post.joins(:comments).joins("inner join comments ...")
+ # # => SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ...
+ # Post.joins(:comments, :special_comments).joins("inner join comments ...")
+ # # => SELECT ... FROM posts INNER JOIN comments comments_posts ON ...
+ # INNER JOIN comments special_comments_posts ...
+ # INNER JOIN comments ...
+ #
+ # Table aliases are automatically truncated according to the maximum length of table identifiers
+ # according to the specific database.
+ #
+ # == Modules
+ #
+ # By default, associations will look for objects within the current module scope. Consider:
+ #
+ # module MyApplication
+ # module Business
+ # class Firm < ActiveRecord::Base
+ # has_many :clients
+ # end
+ #
+ # class Client < ActiveRecord::Base; end
+ # end
+ # end
+ #
+ # When <tt>Firm#clients</tt> is called, it will in turn call
+ # <tt>MyApplication::Business::Client.find_all_by_firm_id(firm.id)</tt>.
+ # If you want to associate with a class in another module scope, this can be done by
+ # specifying the complete class name.
+ #
+ # module MyApplication
+ # module Business
+ # class Firm < ActiveRecord::Base; end
+ # end
+ #
+ # module Billing
+ # class Account < ActiveRecord::Base
+ # belongs_to :firm, class_name: "MyApplication::Business::Firm"
+ # end
+ # end
+ # end
+ #
+ # == Bi-directional associations
+ #
+ # When you specify an association, there is usually an association on the associated model
+ # that specifies the same relationship in reverse. For example, with the following models:
+ #
+ # class Dungeon < ActiveRecord::Base
+ # has_many :traps
+ # has_one :evil_wizard
+ # end
+ #
+ # class Trap < ActiveRecord::Base
+ # belongs_to :dungeon
+ # end
+ #
+ # class EvilWizard < ActiveRecord::Base
+ # belongs_to :dungeon
+ # end
+ #
+ # The +traps+ association on +Dungeon+ and the +dungeon+ association on +Trap+ are
+ # the inverse of each other, and the inverse of the +dungeon+ association on +EvilWizard+
+ # is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default,
+ # Active Record can guess the inverse of the association based on the name
+ # of the class. The result is the following:
+ #
+ # d = Dungeon.first
+ # t = d.traps.first
+ # d.object_id == t.dungeon.object_id # => true
+ #
+ # The +Dungeon+ instances +d+ and <tt>t.dungeon</tt> in the above example refer to
+ # the same in-memory instance since the association matches the name of the class.
+ # The result would be the same if we added +:inverse_of+ to our model definitions:
+ #
+ # class Dungeon < ActiveRecord::Base
+ # has_many :traps, inverse_of: :dungeon
+ # has_one :evil_wizard, inverse_of: :dungeon
+ # end
+ #
+ # class Trap < ActiveRecord::Base
+ # belongs_to :dungeon, inverse_of: :traps
+ # end
+ #
+ # class EvilWizard < ActiveRecord::Base
+ # belongs_to :dungeon, inverse_of: :evil_wizard
+ # end
+ #
+ # There are limitations to <tt>:inverse_of</tt> support:
+ #
+ # * does not work with <tt>:through</tt> associations.
+ # * does not work with <tt>:polymorphic</tt> associations.
+ # * inverse associations for #belongs_to associations #has_many are ignored.
+ #
+ # For more information, see the documentation for the +:inverse_of+ option.
+ #
+ # == Deleting from associations
+ #
+ # === Dependent associations
+ #
+ # #has_many, #has_one, and #belongs_to associations support the <tt>:dependent</tt> option.
+ # This allows you to specify that associated records should be deleted when the owner is
+ # deleted.
+ #
+ # For example:
+ #
+ # class Author
+ # has_many :posts, dependent: :destroy
+ # end
+ # Author.find(1).destroy # => Will destroy all of the author's posts, too
+ #
+ # The <tt>:dependent</tt> option can have different values which specify how the deletion
+ # is done. For more information, see the documentation for this option on the different
+ # specific association types. When no option is given, the behavior is to do nothing
+ # with the associated records when destroying a record.
+ #
+ # Note that <tt>:dependent</tt> is implemented using Rails' callback
+ # system, which works by processing callbacks in order. Therefore, other
+ # callbacks declared either before or after the <tt>:dependent</tt> option
+ # can affect what it does.
+ #
+ # Note that <tt>:dependent</tt> option is ignored for #has_one <tt>:through</tt> associations.
+ #
+ # === Delete or destroy?
+ #
+ # #has_many and #has_and_belongs_to_many associations have the methods <tt>destroy</tt>,
+ # <tt>delete</tt>, <tt>destroy_all</tt> and <tt>delete_all</tt>.
+ #
+ # For #has_and_belongs_to_many, <tt>delete</tt> and <tt>destroy</tt> are the same: they
+ # cause the records in the join table to be removed.
+ #
+ # For #has_many, <tt>destroy</tt> and <tt>destroy_all</tt> will always call the <tt>destroy</tt> method of the
+ # record(s) being removed so that callbacks are run. However <tt>delete</tt> and <tt>delete_all</tt> will either
+ # do the deletion according to the strategy specified by the <tt>:dependent</tt> option, or
+ # if no <tt>:dependent</tt> option is given, then it will follow the default strategy.
+ # The default strategy is to do nothing (leave the foreign keys with the parent ids set), except for
+ # #has_many <tt>:through</tt>, where the default strategy is <tt>delete_all</tt> (delete
+ # the join records, without running their callbacks).
+ #
+ # There is also a <tt>clear</tt> method which is the same as <tt>delete_all</tt>, except that
+ # it returns the association rather than the records which have been deleted.
+ #
+ # === What gets deleted?
+ #
+ # There is a potential pitfall here: #has_and_belongs_to_many and #has_many <tt>:through</tt>
+ # associations have records in join tables, as well as the associated records. So when we
+ # call one of these deletion methods, what exactly should be deleted?
+ #
+ # The answer is that it is assumed that deletion on an association is about removing the
+ # <i>link</i> between the owner and the associated object(s), rather than necessarily the
+ # associated objects themselves. So with #has_and_belongs_to_many and #has_many
+ # <tt>:through</tt>, the join records will be deleted, but the associated records won't.
+ #
+ # This makes sense if you think about it: if you were to call <tt>post.tags.delete(Tag.find_by(name: 'food'))</tt>
+ # you would want the 'food' tag to be unlinked from the post, rather than for the tag itself
+ # to be removed from the database.
+ #
+ # However, there are examples where this strategy doesn't make sense. For example, suppose
+ # a person has many projects, and each project has many tasks. If we deleted one of a person's
+ # tasks, we would probably not want the project to be deleted. In this scenario, the delete method
+ # won't actually work: it can only be used if the association on the join model is a
+ # #belongs_to. In other situations you are expected to perform operations directly on
+ # either the associated records or the <tt>:through</tt> association.
+ #
+ # With a regular #has_many there is no distinction between the "associated records"
+ # and the "link", so there is only one choice for what gets deleted.
+ #
+ # With #has_and_belongs_to_many and #has_many <tt>:through</tt>, if you want to delete the
+ # associated records themselves, you can always do something along the lines of
+ # <tt>person.tasks.each(&:destroy)</tt>.
+ #
+ # == Type safety with ActiveRecord::AssociationTypeMismatch
+ #
+ # If you attempt to assign an object to an association that doesn't match the inferred
+ # or specified <tt>:class_name</tt>, you'll get an ActiveRecord::AssociationTypeMismatch.
+ #
+ # == Options
+ #
+ # All of the association macros can be specialized through options. This makes cases
+ # more complex than the simple and guessable ones possible.
module ClassMethods
# Specifies a one-to-many association. The following methods for retrieval and query of
# collections of associated objects will be added:
@@ -1296,6 +1316,12 @@ module ActiveRecord
# If using with the <tt>:through</tt> option, the association on the join model must be
# a #belongs_to, and the records which get deleted are the join records, rather than
# the associated records.
+ #
+ # If using <tt>dependent: :destroy</tt> on a scoped association, only the scoped objects are destroyed.
+ # For example, if a Post model defines
+ # <tt>has_many :comments, -> { where published: true }, dependent: :destroy</tt> and <tt>destroy</tt> is
+ # called on a post, only published comments are destroyed. This means that any unpublished comments in the
+ # database would still contain a foreign key pointing to the now deleted post.
# [:counter_cache]
# This option can be used to configure a custom named <tt>:counter_cache.</tt> You only need this option,
# when you customized the name of your <tt>:counter_cache</tt> on the #belongs_to association.
@@ -1786,12 +1812,7 @@ module ActiveRecord
# has_and_belongs_to_many :nations, class_name: "Country"
# has_and_belongs_to_many :categories, join_table: "prods_cats"
# has_and_belongs_to_many :categories, -> { readonly }
- def has_and_belongs_to_many(name, scope = nil, options = {}, &extension)
- if scope.is_a?(Hash)
- options = scope
- scope = nil
- end
-
+ def has_and_belongs_to_many(name, scope = nil, **options, &extension)
habtm_reflection = ActiveRecord::Reflection::HasAndBelongsToManyReflection.new(name, scope, options, self)
builder = Builder::HasAndBelongsToMany.new name, self, options
@@ -1809,12 +1830,12 @@ module ActiveRecord
include Module.new {
class_eval <<-RUBY, __FILE__, __LINE__ + 1
- def destroy_associations
- association(:#{middle_reflection.name}).delete_all(:delete_all)
- association(:#{name}).reset
- super
- end
- RUBY
+ def destroy_associations
+ association(:#{middle_reflection.name}).delete_all(:delete_all)
+ association(:#{name}).reset
+ super
+ end
+ RUBY
}
hm_options = {}
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 8328286805..84d0493a60 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -112,6 +112,15 @@ module ActiveRecord
record
end
+ # Remove the inverse association, if possible
+ def remove_inverse_instance(record)
+ if invertible_for?(record)
+ inverse = record.association(inverse_reflection_for(record).name)
+ inverse.target = nil
+ inverse.inversed = false
+ end
+ end
+
# Returns the class of the target. belongs_to polymorphic overrides this to look at the
# polymorphic_type field on the owner.
def klass
@@ -166,13 +175,21 @@ module ActiveRecord
def initialize_attributes(record, except_from_scope_attributes = nil) #:nodoc:
except_from_scope_attributes ||= {}
skip_assign = [reflection.foreign_key, reflection.type].compact
- assigned_keys = record.changed
+ assigned_keys = record.changed_attribute_names_to_save
assigned_keys += except_from_scope_attributes.keys.map(&:to_s)
attributes = create_scope.except(*(assigned_keys - skip_assign))
record.assign_attributes(attributes)
set_inverse_instance(record)
end
+ def create(attributes = {}, &block)
+ _create_record(attributes, &block)
+ end
+
+ def create!(attributes = {}, &block)
+ _create_record(attributes, true, &block)
+ end
+
private
def find_target?
@@ -246,7 +263,7 @@ module ActiveRecord
# so that when stale_state is different from the value stored on the last find_target,
# the target is stale.
#
- # This is only relevant to certain associations, which is why it returns nil by default.
+ # This is only relevant to certain associations, which is why it returns +nil+ by default.
def stale_state
end
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 12f8c1ccd4..c6d204d3c2 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -49,6 +49,8 @@ module ActiveRecord
binds
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 :value_transformation
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index 3121e70a04..a1609ab0fb 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -35,17 +35,17 @@ module ActiveRecord::Associations::Builder # :nodoc:
@_after_create_counter_called = false
elsif (@_after_replace_counter_called ||= false)
@_after_replace_counter_called = false
- elsif attribute_changed?(foreign_key) && !new_record?
+ elsif saved_change_to_attribute?(foreign_key) && !new_record?
if reflection.polymorphic?
- model = attribute(reflection.foreign_type).try(:constantize)
- model_was = attribute_was(reflection.foreign_type).try(:constantize)
+ model = attribute_in_database(reflection.foreign_type).try(:constantize)
+ model_was = attribute_before_last_save(reflection.foreign_type).try(:constantize)
else
model = reflection.klass
model_was = reflection.klass
end
- foreign_key_was = attribute_was foreign_key
- foreign_key = attribute foreign_key
+ foreign_key_was = attribute_before_last_save foreign_key
+ foreign_key = attribute_in_database foreign_key
if foreign_key && model.respond_to?(:increment_counter)
model.increment_counter(cache_column, foreign_key)
@@ -70,14 +70,16 @@ module ActiveRecord::Associations::Builder # :nodoc:
klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
end
- def self.touch_record(o, foreign_key, name, touch, touch_method) # :nodoc:
- old_foreign_id = o.changed_attributes[foreign_key]
+ def self.touch_record(o, changes, foreign_key, name, touch, touch_method) # :nodoc:
+ old_foreign_id = changes[foreign_key] && changes[foreign_key].first
if old_foreign_id
association = o.association(name)
reflection = association.reflection
if reflection.polymorphic?
- klass = o.public_send("#{reflection.foreign_type}_was").constantize
+ foreign_type = reflection.foreign_type
+ klass = changes[foreign_type] && changes[foreign_type].first || o.public_send(foreign_type)
+ klass = klass.constantize
else
klass = association.klass
end
@@ -107,13 +109,13 @@ module ActiveRecord::Associations::Builder # :nodoc:
n = reflection.name
touch = reflection.options[:touch]
- callback = lambda { |record|
- BelongsTo.touch_record(record, foreign_key, n, touch, belongs_to_touch_method)
- }
+ callback = lambda { |changes_method| lambda { |record|
+ BelongsTo.touch_record(record, record.send(changes_method), foreign_key, n, touch, belongs_to_touch_method)
+ }}
- model.after_save callback, if: :changed?
- model.after_touch callback
- model.after_destroy callback
+ model.after_save callback.(:saved_changes), if: :saved_changes?
+ model.after_touch callback.(:changes_to_save)
+ model.after_destroy callback.(:changes_to_save)
end
def self.add_destroy_callbacks(model, reflection)
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index 047292b2bd..6b71826431 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -28,7 +28,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
class_name = options.fetch(:class_name) {
name.to_s.camelize.singularize
}
- KnownClass.new lhs_class, class_name
+ KnownClass.new lhs_class, class_name.to_s
end
end
end
@@ -78,9 +78,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
private
- def self.suppress_composite_primary_key(pk)
- pk unless pk.is_a?(Array)
- end
+ def self.suppress_composite_primary_key(pk)
+ pk unless pk.is_a?(Array)
+ end
}
join_model.name = "HABTM_#{association_name.to_s.camelize}"
diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb
index bb96202a22..7732b63af6 100644
--- a/activerecord/lib/active_record/associations/builder/singular_association.rb
+++ b/activerecord/lib/active_record/associations/builder/singular_association.rb
@@ -8,7 +8,16 @@ module ActiveRecord::Associations::Builder # :nodoc:
def self.define_accessors(model, reflection)
super
- define_constructors(model.generated_association_methods, reflection.name) if reflection.constructable?
+ mixin = model.generated_association_methods
+ name = reflection.name
+
+ define_constructors(mixin, name) if reflection.constructable?
+
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def reload_#{name}
+ association(:#{name}).force_reload_reader
+ end
+ CODE
end
# Defines the (build|create)_association methods for belongs_to or has_one association
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index ab267f6897..9fa8b5d469 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -25,16 +25,8 @@ module ActiveRecord
# +load_target+ and the +loaded+ flag are your friends.
class CollectionAssociation < Association #:nodoc:
# Implements the reader method, e.g. foo.items for Foo.has_many :items
- def reader(force_reload = false)
- if force_reload
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing an argument to force an association to reload is now
- deprecated and will be removed in Rails 5.1. Please call `reload`
- on the result collection proxy instead.
- MSG
-
- klass.uncached { reload }
- elsif stale_target?
+ def reader
+ if stale_target?
reload
end
@@ -55,9 +47,7 @@ module ActiveRecord
# Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items
def ids_reader
if loaded?
- load_target.map do |record|
- record.send(reflection.association_primary_key)
- end
+ target.pluck(reflection.association_primary_key)
else
@association_ids ||= (
column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
@@ -68,13 +58,17 @@ module ActiveRecord
# Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
def ids_writer(ids)
- pk_type = reflection.primary_key_type
+ pk_type = reflection.association_primary_key_type
ids = Array(ids).reject(&:blank?)
ids.map! { |i| pk_type.cast(i) }
records = klass.where(reflection.association_primary_key => ids).index_by do |r|
r.send(reflection.association_primary_key)
- end.values_at(*ids)
- replace(records)
+ end.values_at(*ids).compact
+ if records.size != ids.size
+ klass.all.raise_record_not_found_exception!(ids, records.size, ids.size, reflection.association_primary_key)
+ else
+ replace(records)
+ end
end
def reset
@@ -82,14 +76,6 @@ module ActiveRecord
@target = []
end
- def select(*fields)
- if block_given?
- load_target.select.each { |e| yield e }
- else
- scope.select(*fields)
- end
- end
-
def find(*args)
if block_given?
load_target.find(*args) { |*block_args| yield(*block_args) }
@@ -111,50 +97,6 @@ module ActiveRecord
end
end
- def first(*args)
- first_nth_or_last(:first, *args)
- end
-
- def second(*args)
- first_nth_or_last(:second, *args)
- end
-
- def third(*args)
- first_nth_or_last(:third, *args)
- end
-
- def fourth(*args)
- first_nth_or_last(:fourth, *args)
- end
-
- def fifth(*args)
- first_nth_or_last(:fifth, *args)
- end
-
- def forty_two(*args)
- first_nth_or_last(:forty_two, *args)
- end
-
- def third_to_last(*args)
- first_nth_or_last(:third_to_last, *args)
- end
-
- def second_to_last(*args)
- first_nth_or_last(:second_to_last, *args)
- end
-
- def last(*args)
- first_nth_or_last(:last, *args)
- end
-
- def take(n = nil)
- if loaded?
- n ? target.take(n) : target.first
- else
- scope.take(n)
- end
- end
-
def build(attributes = {}, &block)
if attributes.is_a?(Array)
attributes.collect { |attr| build(attr, &block) }
@@ -165,14 +107,6 @@ module ActiveRecord
end
end
- def create(attributes = {}, &block)
- _create_record(attributes, &block)
- end
-
- def create!(attributes = {}, &block)
- _create_record(attributes, true, &block)
- end
-
# Add +records+ to this association. Returns +self+ so method calls may
# be chained. Since << flattens its argument list and inserts each record,
# +push+ and +concat+ behave identically.
@@ -243,31 +177,6 @@ module ActiveRecord
end
end
- # Returns the number of records. If no arguments are given, it counts all
- # columns using SQL. If one argument is given, it counts only the passed
- # column using SQL. If a block is given, it counts the number of records
- # yielding a true value.
- def count(column_name = nil)
- return super if block_given?
- relation = scope
- if association_scope.distinct_value
- # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
- column_name ||= reflection.klass.primary_key
- relation = relation.distinct
- end
-
- value = relation.count(column_name)
-
- limit = options[:limit]
- offset = options[:offset]
-
- if limit || offset
- [ [value - offset.to_i, 0].max, limit.to_i ].min
- else
- value
- end
- end
-
# Removes +records+ from this association calling +before_remove+ and
# +after_remove+ callbacks.
#
@@ -277,11 +186,8 @@ module ActiveRecord
# +delete_records+. They are in any case removed from the collection.
def delete(*records)
return if records.empty?
- _options = records.extract_options!
- dependent = _options[:dependent] || options[:dependent]
-
records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
- delete_or_destroy(records, dependent)
+ delete_or_destroy(records, options[:dependent])
end
# Deletes the +records+ and removes them from this association calling
@@ -307,14 +213,10 @@ module ActiveRecord
# +count_records+, which is a method descendants have to provide.
def size
if !find_target? || loaded?
- if association_scope.distinct_value
- target.uniq.size
- else
- target.size
- end
- elsif !loaded? && !association_scope.group_values.empty?
+ target.size
+ elsif !association_scope.group_values.empty?
load_target.size
- elsif !loaded? && !association_scope.distinct_value && target.is_a?(Array)
+ elsif !association_scope.distinct_value && target.is_a?(Array)
unsaved_records = target.select(&:new_record?)
unsaved_records.size + count_records
else
@@ -322,15 +224,6 @@ module ActiveRecord
end
end
- # Returns the size of the collection calling +size+ on the target.
- #
- # If the collection has been already loaded +length+ and +size+ are
- # equivalent. If not and you are going to need the records anyway this
- # method will take one less query. Otherwise +size+ is more efficient.
- def length
- load_target.size
- end
-
# Returns true if the collection is empty.
#
# If the collection has been loaded
@@ -347,36 +240,6 @@ module ActiveRecord
end
end
- # Returns true if the collections is not empty.
- # If block given, loads all records and checks for one or more matches.
- # Otherwise, equivalent to +!collection.empty?+.
- def any?
- if block_given?
- load_target.any? { |*block_args| yield(*block_args) }
- else
- !empty?
- end
- end
-
- # Returns true if the collection has more than 1 record.
- # If block given, loads all records and checks for two or more matches.
- # Otherwise, equivalent to +collection.size > 1+.
- def many?
- if block_given?
- load_target.many? { |*block_args| yield(*block_args) }
- else
- size > 1
- end
- end
-
- def distinct
- seen = {}
- load_target.find_all do |record|
- seen[record.id] = true unless seen.key?(record.id)
- end
- end
- alias uniq distinct
-
# Replace this collection with +other_array+. This will perform a diff
# and delete/add only records that have changed.
def replace(other_array)
@@ -426,15 +289,23 @@ module ActiveRecord
def replace_on_target(record, index, skip_callbacks)
callback(:before_add, record) unless skip_callbacks
- was_loaded = loaded?
- yield(record) if block_given?
+ begin
+ if index
+ record_was = target[index]
+ target[index] = record
+ else
+ target << record
+ end
- unless !was_loaded && loaded?
+ yield(record) if block_given?
+ rescue
if index
- @target[index] = record
+ target[index] = record_was
else
- @target << record
+ target.delete(record)
end
+
+ raise
end
callback(:after_add, record) unless skip_callbacks
@@ -453,6 +324,12 @@ module ActiveRecord
owner.new_record? && !foreign_key_present?
end
+ def find_from_target?
+ loaded? ||
+ owner.new_record? ||
+ target.any? { |record| record.new_record? || record.changed? }
+ end
+
private
def find_target
@@ -467,9 +344,9 @@ module ActiveRecord
end
binds = AssociationScope.get_bind_values(owner, reflection.chain)
- records = sc.execute(binds, klass, conn)
- records.each { |record| set_inverse_instance(record) }
- records
+ sc.execute(binds, klass, conn) do |record|
+ set_inverse_instance(record)
+ end
end
# We have some records loaded from the database (persisted) and some that are
@@ -489,7 +366,7 @@ module ActiveRecord
persisted.map! do |record|
if mem_record = memory.delete(record)
- ((record.attribute_names & mem_record.attribute_names) - mem_record.changes.keys).each do |name|
+ ((record.attribute_names & mem_record.attribute_names) - mem_record.changed_attribute_names_to_save).each do |name|
mem_record[name] = record[name]
end
@@ -549,8 +426,9 @@ module ActiveRecord
records.each { |record| callback(:after_remove, record) }
end
- # Delete the given records from the association, using one of the methods :destroy,
- # :delete_all or :nullify (or nil, in which case a default is used).
+ # Delete the given records from the association,
+ # using one of the methods +:destroy+, +:delete_all+
+ # or +:nullify+ (or +nil+, in which case a default is used).
def delete_records(records, method)
raise NotImplementedError
end
@@ -599,25 +477,6 @@ module ActiveRecord
owner.class.send(full_callback_name)
end
- # Should we deal with assoc.first or assoc.last by issuing an independent query to
- # the database, or by getting the target, and then taking the first/last item from that?
- #
- # If the args is just a non-empty options hash, go to the database.
- #
- # Otherwise, go to the database only if none of the following are true:
- # * target already loaded
- # * owner is new record
- # * target contains new or changed record(s)
- def fetch_first_nth_or_last_using_find?(args)
- if args.first.is_a?(Hash)
- true
- else
- !(loaded? ||
- owner.new_record? ||
- target.any? { |record| record.new_record? || record.changed? })
- end
- end
-
def include_in_memory?(record)
if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
assoc = owner.association(reflection.through_reflection.name)
@@ -644,14 +503,6 @@ module ActiveRecord
load_target.select { |r| ids.include?(r.id.to_s) }
end
end
-
- # Fetches the first/last using SQL if possible, otherwise from the target array.
- def first_nth_or_last(type, *args)
- args.shift if args.first.is_a?(Hash) && args.first.empty?
-
- collection = fetch_first_nth_or_last_using_find?(args) ? scope : load_target
- collection.send(type, *args)
- end
end
end
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 806a905323..0d84805b4d 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -28,7 +28,6 @@ module ActiveRecord
# is computed directly through SQL and does not trigger by itself the
# instantiation of the actual post records.
class CollectionProxy < Relation
- delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope)
delegate :exists?, :update_all, :arel, to: :scope
def initialize(klass, association) #:nodoc:
@@ -54,6 +53,12 @@ module ActiveRecord
@association.loaded?
end
+ ##
+ # :method: select
+ #
+ # :call-seq:
+ # select(*fields, &block)
+ #
# Works in two ways.
#
# *First:* Specify a subset of fields to be selected from the result set.
@@ -101,15 +106,6 @@ module ActiveRecord
# # #<Pet id: 2, name: "Spook", person_id: 1>,
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
# # ]
- #
- # person.pets.select(:name) { |pet| pet.name =~ /oo/ }
- # # => [
- # # #<Pet id: 2, name: "Spook">,
- # # #<Pet id: 3, name: "Choo-Choo">
- # # ]
- def select(*fields, &block)
- @association.select(*fields, &block)
- end
# Finds an object in the collection responding to the +id+. Uses the same
# rules as ActiveRecord::Base.find. Returns ActiveRecord::RecordNotFound
@@ -141,6 +137,12 @@ module ActiveRecord
@association.find(*args, &block)
end
+ ##
+ # :method: first
+ #
+ # :call-seq:
+ # first(limit = nil)
+ #
# Returns the first record, or the first +n+ records, from the collection.
# If the collection is empty, the first form returns +nil+, and the second
# form returns an empty array.
@@ -167,45 +169,63 @@ module ActiveRecord
# another_person_without.pets # => []
# another_person_without.pets.first # => nil
# another_person_without.pets.first(3) # => []
- def first(*args)
- @association.first(*args)
- end
+ ##
+ # :method: second
+ #
+ # :call-seq:
+ # second()
+ #
# Same as #first except returns only the second record.
- def second(*args)
- @association.second(*args)
- end
+ ##
+ # :method: third
+ #
+ # :call-seq:
+ # third()
+ #
# Same as #first except returns only the third record.
- def third(*args)
- @association.third(*args)
- end
+ ##
+ # :method: fourth
+ #
+ # :call-seq:
+ # fourth()
+ #
# Same as #first except returns only the fourth record.
- def fourth(*args)
- @association.fourth(*args)
- end
+ ##
+ # :method: fifth
+ #
+ # :call-seq:
+ # fifth()
+ #
# Same as #first except returns only the fifth record.
- def fifth(*args)
- @association.fifth(*args)
- end
+ ##
+ # :method: forty_two
+ #
+ # :call-seq:
+ # forty_two()
+ #
# Same as #first except returns only the forty second record.
# Also known as accessing "the reddit".
- def forty_two(*args)
- @association.forty_two(*args)
- end
+ ##
+ # :method: third_to_last
+ #
+ # :call-seq:
+ # third_to_last()
+ #
# Same as #first except returns only the third-to-last record.
- def third_to_last(*args)
- @association.third_to_last(*args)
- end
+ ##
+ # :method: second_to_last
+ #
+ # :call-seq:
+ # second_to_last()
+ #
# Same as #first except returns only the second-to-last record.
- def second_to_last(*args)
- @association.second_to_last(*args)
- end
# Returns the last record, or the last +n+ records, from the collection.
# If the collection is empty, the first form returns +nil+, and the second
@@ -233,8 +253,9 @@ module ActiveRecord
# another_person_without.pets # => []
# another_person_without.pets.last # => nil
# another_person_without.pets.last(3) # => []
- def last(*args)
- @association.last(*args)
+ def last(limit = nil)
+ load_target if find_from_target?
+ super
end
# Gives a record (or N records if a parameter is supplied) from the collection
@@ -262,8 +283,9 @@ module ActiveRecord
# another_person_without.pets # => []
# another_person_without.pets.take # => nil
# another_person_without.pets.take(2) # => []
- def take(n = nil)
- @association.take(n)
+ def take(limit = nil)
+ load_target if find_from_target?
+ super
end
# Returns a new object of the collection type that has been instantiated
@@ -696,6 +718,12 @@ module ActiveRecord
@association.destroy(*records)
end
+ ##
+ # :method: distinct
+ #
+ # :call-seq:
+ # distinct(value = true)
+ #
# Specifies whether the records should be unique or not.
#
# class Person < ActiveRecord::Base
@@ -710,11 +738,32 @@ module ActiveRecord
#
# person.pets.select(:name).distinct
# # => [#<Pet name: "Fancy-Fancy">]
- def distinct
- @association.distinct
+ #
+ # person.pets.select(:name).distinct.distinct(false)
+ # # => [
+ # # #<Pet name: "Fancy-Fancy">,
+ # # #<Pet name: "Fancy-Fancy">
+ # # ]
+
+ #--
+ def uniq
+ load_target.uniq
end
- alias uniq distinct
+ def calculate(operation, column_name)
+ null_scope? ? scope.calculate(operation, column_name) : super
+ end
+
+ def pluck(*column_names)
+ null_scope? ? scope.pluck(*column_names) : super
+ end
+
+ ##
+ # :method: count
+ #
+ # :call-seq:
+ # count(column_name = nil, &block)
+ #
# Count all records.
#
# class Person < ActiveRecord::Base
@@ -734,9 +783,6 @@ module ActiveRecord
# perform the count using Ruby.
#
# person.pets.count { |pet| pet.name.include?('-') } # => 2
- def count(column_name = nil, &block)
- @association.count(column_name, &block)
- end
# Returns the size of the collection. If the collection hasn't been loaded,
# it executes a <tt>SELECT COUNT(*)</tt> query. Else it calls <tt>collection.size</tt>.
@@ -766,6 +812,12 @@ module ActiveRecord
@association.size
end
+ ##
+ # :method: length
+ #
+ # :call-seq:
+ # length()
+ #
# Returns the size of the collection calling +size+ on the target.
# If the collection has been already loaded, +length+ and +size+ are
# equivalent. If not and you are going to need the records anyway this
@@ -786,9 +838,6 @@ module ActiveRecord
# # #<Pet id: 2, name: "Spook", person_id: 1>,
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
# # ]
- def length
- @association.length
- end
# Returns +true+ if the collection is empty. If the collection has been
# loaded it is equivalent
@@ -812,6 +861,12 @@ module ActiveRecord
@association.empty?
end
+ ##
+ # :method: any?
+ #
+ # :call-seq:
+ # any?()
+ #
# Returns +true+ if the collection is not empty.
#
# class Person < ActiveRecord::Base
@@ -841,10 +896,13 @@ module ActiveRecord
# pet.group == 'dogs'
# end
# # => true
- def any?(&block)
- @association.any?(&block)
- end
+ ##
+ # :method: many?
+ #
+ # :call-seq:
+ # many?()
+ #
# Returns true if the collection has more than one record.
# Equivalent to <tt>collection.size > 1</tt>.
#
@@ -879,9 +937,6 @@ module ActiveRecord
# pet.group == 'cats'
# end
# # => true
- def many?(&block)
- @association.many?(&block)
- end
# Returns +true+ if the given +record+ is present in the collection.
#
@@ -1073,6 +1128,24 @@ module ActiveRecord
private
+ def find_nth_with_limit(index, limit)
+ load_target if find_from_target?
+ super
+ end
+
+ def find_nth_from_last(index)
+ load_target if find_from_target?
+ super
+ end
+
+ def null_scope?
+ @association.null_scope?
+ end
+
+ def find_from_target?
+ @association.find_from_target?
+ end
+
def exec_queries
load_target
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 6720578b2c..c5a7d92a2b 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -16,14 +16,7 @@ module ActiveRecord
when :restrict_with_error
unless empty?
record = owner.class.human_attribute_name(reflection.name).downcase
- message = owner.errors.generate_message(:base, :'restrict_dependent_destroy.many', record: record, raise: true) rescue nil
- if message
- ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
- The error key `:'restrict_dependent_destroy.many'` has been deprecated and will be removed in Rails 5.1.
- Please use `:'restrict_dependent_destroy.has_many'` instead.
- MESSAGE
- end
- owner.errors.add(:base, message || :'restrict_dependent_destroy.has_many', record: record)
+ owner.errors.add(:base, :'restrict_dependent_destroy.has_many', record: record)
throw(:abort)
end
@@ -72,7 +65,7 @@ module ActiveRecord
# the loaded flag is set to true as well.
def count_records
count = if reflection.has_cached_counter?
- owner._read_attribute reflection.counter_cache_column
+ owner._read_attribute(reflection.counter_cache_column).to_i
else
scope.count
end
@@ -80,7 +73,7 @@ module ActiveRecord
# If there's nothing in the database and @target has no new records
# we are certain the current target is an empty array. This is a
# documented side-effect of the method that may avoid an extra SELECT.
- @target ||= [] and loaded! if count == 0
+ (@target ||= []) && loaded! if count == 0
[association_scope.limit_value, count].compact.min
end
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 d258eac0ed..c4a7fe4432 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -38,10 +38,12 @@ module ActiveRecord
def insert_record(record, validate = true, raise = false)
ensure_not_nested
- if raise
- record.save!(validate: validate)
- else
- return unless record.save(validate: validate)
+ if record.new_record? || record.has_changes_to_save?
+ if raise
+ record.save!(validate: validate)
+ else
+ return unless record.save(validate: validate)
+ end
end
save_through_record(record)
@@ -86,7 +88,10 @@ module ActiveRecord
end
def save_through_record(record)
- build_through_record(record).save!
+ association = build_through_record(record)
+ if association.changed?
+ association.save!
+ end
ensure
@through_records.delete(record.object_id)
end
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 5ea9577301..b624154def 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -12,14 +12,7 @@ module ActiveRecord
when :restrict_with_error
if load_target
record = owner.class.human_attribute_name(reflection.name).downcase
- message = owner.errors.generate_message(:base, :'restrict_dependent_destroy.one', record: record, raise: true) rescue nil
- if message
- ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
- The error key `:'restrict_dependent_destroy.one'` has been deprecated and will be removed in Rails 5.1.
- Please use `:'restrict_dependent_destroy.has_one'` instead.
- MESSAGE
- end
- owner.errors.add(:base, message || :'restrict_dependent_destroy.has_one', record: record)
+ owner.errors.add(:base, :'restrict_dependent_destroy.has_one', record: record)
throw(:abort)
end
@@ -35,7 +28,7 @@ module ActiveRecord
return target unless target || record
assigning_another_record = target != record
- if assigning_another_record || record.changed?
+ if assigning_another_record || record.has_changes_to_save?
save &&= owner.persisted?
transaction_if(save) do
@@ -86,8 +79,9 @@ module ActiveRecord
target.delete
when :destroy
target.destroy
- else
+ else
nullify_owner_attributes(target)
+ remove_inverse_instance(target)
if target.persisted? && owner.persisted? && !target.save
set_owner_attributes(target)
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index 3946d5baa4..4cd1e64c3d 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -7,12 +7,12 @@ module ActiveRecord
class Aliases # :nodoc:
def initialize(tables)
@tables = tables
- @alias_cache = tables.each_with_object({}) { |table,h|
- h[table.node] = table.columns.each_with_object({}) { |column,i|
+ @alias_cache = tables.each_with_object({}) { |table, h|
+ h[table.node] = table.columns.each_with_object({}) { |column, i|
i[column.name] = column.alias
}
}
- @name_and_alias_cache = tables.each_with_object({}) { |table,h|
+ @name_and_alias_cache = tables.each_with_object({}) { |table, h|
h[table.node] = table.columns.map { |column|
[column.name, column.alias]
}
@@ -62,7 +62,7 @@ module ActiveRecord
walk_tree assoc, hash
end
when Hash
- associations.each do |k,v|
+ associations.each do |k, v|
cache = hash[k] ||= {}
walk_tree v, cache
end
@@ -126,8 +126,8 @@ module ActiveRecord
end
def aliases
- Aliases.new join_root.each_with_index.map { |join_part,i|
- columns = join_part.column_names.each_with_index.map { |column_name,j|
+ Aliases.new join_root.each_with_index.map { |join_part, i|
+ columns = join_part.column_names.each_with_index.map { |column_name, j|
Aliases::Column.new column_name, "t#{i}_r#{j}"
}
Aliases::Table.new(join_part, columns)
@@ -143,7 +143,7 @@ module ActiveRecord
}
}
- model_cache = Hash.new { |h,klass| h[klass] = {} }
+ model_cache = Hash.new { |h, klass| h[klass] = {} }
parents = model_cache[join_root]
column_aliases = aliases.column_aliases join_root
@@ -223,13 +223,13 @@ module ActiveRecord
[left.children.find { |node2| node1.match? node2 }, node1]
}.partition(&:first)
- ojs = missing.flat_map { |_,n| make_outer_joins left, n }
- intersection.flat_map { |l,r| walk l, r }.concat ojs
+ ojs = missing.flat_map { |_, n| make_outer_joins left, n }
+ intersection.flat_map { |l, r| walk l, r }.concat ojs
end
def find_reflection(klass, name)
- klass._reflect_on_association(name) or
- raise ConfigurationError, "Can't join '#{ klass.name }' to association named '#{ name }'; perhaps you misspelled it?"
+ klass._reflect_on_association(name) ||
+ raise(ConfigurationError, "Can't join '#{klass.name}' to association named '#{name}'; perhaps you misspelled it?")
end
def build(associations, base_klass)
@@ -274,7 +274,11 @@ module ActiveRecord
construct(model, node, row, rs, seen, model_cache, aliases)
else
model = construct_model(ar_parent, node, row, model_cache, id, aliases)
- model.readonly!
+
+ if node.reflection.scope_for(node.base_klass).readonly_value
+ model.readonly!
+ end
+
seen[ar_parent.object_id][node.base_klass][id] = model
construct(model, node, row, rs, seen, model_cache, aliases)
end
@@ -282,17 +286,19 @@ module ActiveRecord
end
def construct_model(record, node, row, model_cache, id, aliases)
- model = model_cache[node][id] ||= node.instantiate(row,
- aliases.column_aliases(node))
other = record.association(node.reflection.name)
+ model = model_cache[node][id] ||=
+ node.instantiate(row, aliases.column_aliases(node)) do |m|
+ other.set_inverse_instance(m)
+ end
+
if node.reflection.collection?
other.target.push(model)
else
other.target = model
end
- other.set_inverse_instance(model)
model
end
end
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 551087f822..61cec5403a 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_part.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_part.rb
@@ -62,8 +62,8 @@ module ActiveRecord
hash
end
- def instantiate(row, aliases)
- base_klass.instantiate(extract_record(row, aliases))
+ def instantiate(row, aliases, &block)
+ base_klass.instantiate(extract_record(row, aliases), &block)
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index a81860e40f..9f77f38b35 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -106,7 +106,7 @@ module ActiveRecord
private
- # Loads all the given data into +records+ for the +association+.
+ # Loads all the given data into +records+ for the +association+.
def preloaders_on(association, records, scope)
case association
when Hash
@@ -132,18 +132,18 @@ module ActiveRecord
}
end
- # Loads all the given data into +records+ for a singular +association+.
- #
- # Functions by instantiating a preloader class such as Preloader::HasManyThrough and
- # call the +run+ method for each passed in class in the +records+ argument.
- #
- # Not all records have the same class, so group then preload group on the reflection
- # itself so that if various subclass share the same association then we do not split
- # them unnecessarily
- #
- # Additionally, polymorphic belongs_to associations can have multiple associated
- # classes, depending on the polymorphic_type field. So we group by the classes as
- # well.
+ # Loads all the given data into +records+ for a singular +association+.
+ #
+ # Functions by instantiating a preloader class such as Preloader::HasManyThrough and
+ # call the +run+ method for each passed in class in the +records+ argument.
+ #
+ # Not all records have the same class, so group then preload group on the reflection
+ # itself so that if various subclass share the same association then we do not split
+ # them unnecessarily
+ #
+ # Additionally, polymorphic belongs_to associations can have multiple associated
+ # classes, depending on the polymorphic_type field. So we group by the classes as
+ # well.
def preloaders_for_one(association, records, scope)
grouped_records(association, records).flat_map do |reflection, klasses|
klasses.map do |rhs_klass, rs|
@@ -187,10 +187,10 @@ module ActiveRecord
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.
+ # 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
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index a8afa48865..4072d19380 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -28,10 +28,6 @@ module ActiveRecord
end
def records_for(ids)
- query_scope(ids)
- end
-
- def query_scope(ids)
scope.where(association_key_name => ids)
end
@@ -62,7 +58,12 @@ module ActiveRecord
private
def associated_records_by_owner(preloader)
- records = load_records
+ records = load_records do |record|
+ owner = owners_by_key[convert_key(record[association_key_name])]
+ association = owner.association(reflection.name)
+ association.set_inverse_instance(record)
+ end
+
owners.each_with_object({}) do |owner, result|
result[owner] = records[convert_key(owner[owner_key_name])] || []
end
@@ -79,6 +80,15 @@ module ActiveRecord
@owner_keys
end
+ def owners_by_key
+ unless defined?(@owners_by_key)
+ @owners_by_key = owners.each_with_object({}) do |owner, h|
+ h[convert_key(owner[owner_key_name])] = owner
+ end
+ end
+ @owners_by_key
+ end
+
def key_conversion_required?
@key_conversion_required ||= association_key_type != owner_key_type
end
@@ -99,13 +109,13 @@ module ActiveRecord
@model.type_for_attribute(owner_key_name.to_s).type
end
- def load_records
+ def load_records(&block)
return {} if owner_keys.empty?
# Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
# 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)
+ slices = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
@preloaded_records = slices.flat_map do |slice|
- records_for(slice)
+ records_for(slice).load(&block)
end
@preloaded_records.group_by do |record|
convert_key(record[association_key_name])
@@ -113,7 +123,7 @@ module ActiveRecord
end
def reflection_scope
- @reflection_scope ||= reflection.scope ? klass.unscoped.instance_exec(nil, &reflection.scope) : klass.unscoped
+ @reflection_scope ||= reflection.scope_for(klass)
end
def build_scope
diff --git a/activerecord/lib/active_record/associations/preloader/collection_association.rb b/activerecord/lib/active_record/associations/preloader/collection_association.rb
index 24b8e01029..26690bf16d 100644
--- a/activerecord/lib/active_record/associations/preloader/collection_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/collection_association.rb
@@ -9,7 +9,6 @@ module ActiveRecord
association = owner.association(reflection.name)
association.loaded!
association.target.concat(records)
- records.each { |record| association.set_inverse_instance(record) }
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/singular_association.rb b/activerecord/lib/active_record/associations/preloader/singular_association.rb
index 0888d383a6..5c5828262e 100644
--- a/activerecord/lib/active_record/associations/preloader/singular_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/singular_association.rb
@@ -10,7 +10,6 @@ module ActiveRecord
association = owner.association(reflection.name)
association.target = record
- association.set_inverse_instance(record) if record
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb
index be9dfe7686..9d44a02021 100644
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
@@ -24,7 +24,7 @@ module ActiveRecord
reset_association owners, through_reflection.name
- middle_records = through_records.flat_map { |(_,rec)| rec }
+ middle_records = through_records.flat_map { |(_, rec)| rec }
preloaders = preloader.preload(middle_records,
source_reflection.name,
@@ -32,13 +32,13 @@ module ActiveRecord
@preloaded_records = preloaders.flat_map(&:preloaded_records)
- middle_to_pl = preloaders.each_with_object({}) do |pl,h|
+ middle_to_pl = preloaders.each_with_object({}) do |pl, h|
pl.owners.each { |middle|
h[middle] = pl
}
end
- through_records.each_with_object({}) do |(lhs,center), records_by_owner|
+ through_records.each_with_object({}) do |(lhs, center), records_by_owner|
pl_to_middle = center.group_by { |record| middle_to_pl[record] }
records_by_owner[lhs] = pl_to_middle.flat_map do |pl, middles|
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index 1fe9a23263..91580a28d0 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -2,16 +2,8 @@ module ActiveRecord
module Associations
class SingularAssociation < Association #:nodoc:
# Implements the reader method, e.g. foo.bar for Foo.has_one :bar
- def reader(force_reload = false)
- if force_reload && klass
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing an argument to force an association to reload is now
- deprecated and will be removed in Rails 5.1. Please call `reload`
- on the parent object instead.
- MSG
-
- klass.uncached { reload }
- elsif !loaded? || stale_target?
+ def reader
+ if !loaded? || stale_target?
reload
end
@@ -23,14 +15,6 @@ module ActiveRecord
replace(record)
end
- def create(attributes = {}, &block)
- _create_record(attributes, &block)
- end
-
- def create!(attributes = {}, &block)
- _create_record(attributes, true, &block)
- end
-
def build(attributes = {})
record = build_record(attributes)
yield(record) if block_given?
@@ -38,6 +22,13 @@ module ActiveRecord
record
end
+ # Implements the reload reader method, e.g. foo.reload_bar for
+ # Foo.has_one :bar
+ def force_reload_reader
+ klass.uncached { reload }
+ target
+ end
+
private
def create_scope
@@ -56,9 +47,11 @@ module ActiveRecord
end
binds = AssociationScope.get_bind_values(owner, reflection.chain)
- if record = sc.execute(binds, klass, conn).first
+ sc.execute(binds, klass, conn) do |record|
set_inverse_instance record
- end
+ end.first
+ rescue ::RangeError
+ nil
end
def replace(record)
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index f4129edc5a..6b87993ba3 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -4,7 +4,7 @@ module ActiveRecord
module ThroughAssociation #:nodoc:
delegate :source_reflection, :through_reflection, to: :reflection
- protected
+ private
# We merge in these scopes for two reasons:
#
@@ -21,8 +21,6 @@ module ActiveRecord
scope
end
- private
-
# Construct attributes for :through pointing to owner and associate. This is used by the
# methods which create and delete records on the association.
#
diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb
index 95b3adae5f..38281158d8 100644
--- a/activerecord/lib/active_record/attribute.rb
+++ b/activerecord/lib/active_record/attribute.rb
@@ -65,7 +65,7 @@ module ActiveRecord
def with_value_from_user(value)
type.assert_valid_value(value)
- self.class.from_user(name, value, type, self)
+ self.class.from_user(name, value, type, original_attribute || self)
end
def with_value_from_database(value)
@@ -128,11 +128,22 @@ module ActiveRecord
coder["value"] = value if defined?(@value)
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 :original_attribute
alias_method :assigned?, :original_attribute
+ def original_value_for_database
+ if assigned?
+ original_attribute.original_value_for_database
+ else
+ _original_value_for_database
+ end
+ end
+
+ private
def initialize_dup(other)
if defined?(@value) && @value.duplicable?
@value = @value.dup
@@ -143,14 +154,6 @@ module ActiveRecord
assigned? && type.changed?(original_value, value, value_before_type_cast)
end
- def original_value_for_database
- if assigned?
- original_attribute.original_value_for_database
- else
- _original_value_for_database
- end
- end
-
def _original_value_for_database
type.serialize(original_value)
end
@@ -187,7 +190,7 @@ module ActiveRecord
class Null < Attribute # :nodoc:
def initialize(name)
- super(name, nil, Type::Value.new)
+ super(name, nil, Type.default_value)
end
def type_cast(*)
diff --git a/activerecord/lib/active_record/attribute/user_provided_default.rb b/activerecord/lib/active_record/attribute/user_provided_default.rb
index a4e2c2ec85..57f8bbed76 100644
--- a/activerecord/lib/active_record/attribute/user_provided_default.rb
+++ b/activerecord/lib/active_record/attribute/user_provided_default.rb
@@ -20,6 +20,8 @@ module ActiveRecord
self.class.new(name, user_provided_value, type, original_attribute)
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 :user_provided_value
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index f3ce52fdfe..d0dfca0cac 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -12,7 +12,7 @@ module ActiveRecord
private
- def _assign_attributes(attributes) # :nodoc:
+ def _assign_attributes(attributes)
multi_parameter_attributes = {}
nested_parameter_attributes = {}
@@ -29,17 +29,17 @@ module ActiveRecord
assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
end
- # Assign any deferred nested attributes after the base attributes have been set.
+ # Assign any deferred nested attributes after the base attributes have been set.
def assign_nested_parameter_attributes(pairs)
pairs.each { |k, v| _assign_attribute(k, v) }
end
- # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
- # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
- # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
- # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
- # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and
- # f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
+ # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
+ # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
+ # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
+ # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
+ # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and
+ # f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
def assign_multiparameter_attributes(pairs)
execute_callstack_for_multiparameter_attributes(
extract_callstack_for_multiparameter_attributes(pairs)
diff --git a/activerecord/lib/active_record/attribute_decorators.rb b/activerecord/lib/active_record/attribute_decorators.rb
index 340dfe11cf..c39e9ce4c5 100644
--- a/activerecord/lib/active_record/attribute_decorators.rb
+++ b/activerecord/lib/active_record/attribute_decorators.rb
@@ -8,12 +8,34 @@ module ActiveRecord
end
module ClassMethods # :nodoc:
+ # This method is an internal API used to create class macros such as
+ # +serialize+, and features like time zone aware attributes.
+ #
+ # Used to wrap the type of an attribute in a new type.
+ # When the schema for a model is loaded, attributes with the same name as
+ # +column_name+ will have their type yielded to the given block. The
+ # return value of that block will be used instead.
+ #
+ # Subsequent calls where +column_name+ and +decorator_name+ are the same
+ # will override the previous decorator, not decorate twice. This can be
+ # used to create idempotent class macros like +serialize+
def decorate_attribute_type(column_name, decorator_name, &block)
matcher = ->(name, _) { name == column_name.to_s }
key = "_#{column_name}_#{decorator_name}"
decorate_matching_attribute_types(matcher, key, &block)
end
+ # This method is an internal API used to create higher level features like
+ # time zone aware attributes.
+ #
+ # When the schema for a model is loaded, +matcher+ will be called for each
+ # attribute with its name and type. If the matcher returns a truthy value,
+ # the type will then be yielded to the given block, and the return value
+ # of that block will replace the type.
+ #
+ # Subsequent calls to this method with the same value for +decorator_name+
+ # will replace the previous decorator, not decorate twice. This can be
+ # used to ensure that class macros are idempotent.
def decorate_matching_attribute_types(matcher, decorator_name, &block)
reload_schema_from_cache
decorator_name = decorator_name.to_s
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index ba26a11b39..1ed1deec55 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -416,8 +416,8 @@ module ActiveRecord
private
- # Returns a Hash of the Arel::Attributes and attribute values that have been
- # typecasted for use in an Arel insert/update method.
+ # Returns a Hash of the Arel::Attributes and attribute values that have been
+ # typecasted for use in an Arel insert/update method.
def arel_attributes_with_values(attribute_names)
attrs = {}
arel_table = self.class.arel_table
@@ -428,15 +428,15 @@ module ActiveRecord
attrs
end
- # Filters the primary keys and readonly attributes from the attribute names.
+ # Filters the primary keys and readonly attributes from the attribute names.
def attributes_for_update(attribute_names)
attribute_names.reject do |name|
readonly_attribute?(name)
end
end
- # Filters out the primary keys, from the attribute names, when the primary
- # key is to be generated (e.g. the id attribute has no value).
+ # Filters out the primary keys, from the attribute names, when the primary
+ # key is to be generated (e.g. the id attribute has no value).
def attributes_for_create(attribute_names)
attribute_names.reject do |name|
pk_attribute?(name) && id.nil?
diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
index 92f124078c..115eb1ef3f 100644
--- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
+++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
@@ -63,7 +63,7 @@ module ActiveRecord
private
- # Handle *_before_type_cast for method_missing.
+ # Handle *_before_type_cast for method_missing.
def attribute_before_type_cast(attribute_name)
read_attribute_before_type_cast(attribute_name)
end
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index c9638bf70b..b0e1391cb9 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
require "active_support/core_ext/module/attribute_accessors"
require "active_record/attribute_mutation_tracker"
@@ -15,6 +16,18 @@ module ActiveRecord
class_attribute :partial_writes, instance_writer: false
self.partial_writes = true
+
+ after_create { changes_internally_applied }
+ after_update { changes_internally_applied }
+
+ # Attribute methods for "changed in last call to save?"
+ attribute_method_affix(prefix: "saved_change_to_", suffix: "?")
+ attribute_method_prefix("saved_change_to_")
+ attribute_method_suffix("_before_last_save")
+
+ # Attribute methods for "will change if I call save?"
+ attribute_method_affix(prefix: "will_save_change_to_", suffix: "?")
+ attribute_method_suffix("_change_to_be_saved", "_in_database")
end
# Attempts to +save+ the record and clears changed attributes if successful.
@@ -35,8 +48,8 @@ module ActiveRecord
# <tt>reload</tt> the record and clears changed attributes.
def reload(*)
super.tap do
- @mutation_tracker = nil
@previous_mutation_tracker = nil
+ clear_mutation_trackers
@changed_attributes = HashWithIndifferentAccess.new
end
end
@@ -46,19 +59,26 @@ module ActiveRecord
@attributes = self.class._default_attributes.map do |attr|
attr.with_value_from_user(@attributes.fetch_value(attr.name))
end
- @mutation_tracker = nil
+ clear_mutation_trackers
+ end
+
+ def changes_internally_applied # :nodoc:
+ @mutations_before_last_save = mutation_tracker
+ forget_attribute_assignments
+ @mutations_from_database = AttributeMutationTracker.new(@attributes)
end
def changes_applied
@previous_mutation_tracker = mutation_tracker
@changed_attributes = HashWithIndifferentAccess.new
- store_original_attributes
+ clear_mutation_trackers
end
def clear_changes_information
@previous_mutation_tracker = nil
@changed_attributes = HashWithIndifferentAccess.new
- store_original_attributes
+ forget_attribute_assignments
+ clear_mutation_trackers
end
def raw_write_attribute(attr_name, *)
@@ -80,17 +100,27 @@ module ActiveRecord
if defined?(@cached_changed_attributes)
@cached_changed_attributes
else
+ emit_warning_if_needed("changed_attributes", "saved_changes.transform_values(&:first)")
super.reverse_merge(mutation_tracker.changed_values).freeze
end
end
def changes
cache_changed_attributes do
+ emit_warning_if_needed("changes", "saved_changes")
super
end
end
def previous_changes
+ unless previous_mutation_tracker.equal?(mutations_before_last_save)
+ ActiveSupport::Deprecation.warn(<<-EOW.strip_heredoc)
+ The behavior of `previous_changes` inside of after callbacks is
+ deprecated without replacement. In the next release of Rails,
+ this method inside of `after_save` will return the changes that
+ were just saved.
+ EOW
+ end
previous_mutation_tracker.changes
end
@@ -98,6 +128,114 @@ module ActiveRecord
mutation_tracker.changed_in_place?(attr_name)
end
+ # Did this attribute change when we last saved? This method can be invoked
+ # as `saved_change_to_name?` instead of `saved_change_to_attribute?("name")`.
+ # Behaves similarly to +attribute_changed?+. This method is useful in
+ # after callbacks to determine if the call to save changed a certain
+ # attribute.
+ #
+ # ==== Options
+ #
+ # +from+ When passed, this method will return false unless the original
+ # value is equal to the given option
+ #
+ # +to+ When passed, this method will return false unless the value was
+ # changed to the given value
+ def saved_change_to_attribute?(attr_name, **options)
+ mutations_before_last_save.changed?(attr_name, **options)
+ end
+
+ # Returns the change to an attribute during the last save. If the
+ # attribute was changed, the result will be an array containing the
+ # original value and the saved value.
+ #
+ # Behaves similarly to +attribute_change+. This method is useful in after
+ # callbacks, to see the change in an attribute that just occurred
+ #
+ # This method can be invoked as `saved_change_to_name` in instead of
+ # `saved_change_to_attribute("name")`
+ def saved_change_to_attribute(attr_name)
+ mutations_before_last_save.change_to_attribute(attr_name)
+ end
+
+ # Returns the original value of an attribute before the last save.
+ # Behaves similarly to +attribute_was+. This method is useful in after
+ # callbacks to get the original value of an attribute before the save that
+ # just occurred
+ def attribute_before_last_save(attr_name)
+ mutations_before_last_save.original_value(attr_name)
+ end
+
+ # Did the last call to `save` have any changes to change?
+ def saved_changes?
+ mutations_before_last_save.any_changes?
+ end
+
+ # Returns a hash containing all the changes that were just saved.
+ def saved_changes
+ mutations_before_last_save.changes
+ end
+
+ # Alias for `attribute_changed?`
+ def will_save_change_to_attribute?(attr_name, **options)
+ mutations_from_database.changed?(attr_name, **options)
+ end
+
+ # Alias for `attribute_change`
+ def attribute_change_to_be_saved(attr_name)
+ mutations_from_database.change_to_attribute(attr_name)
+ end
+
+ # Alias for `attribute_was`
+ def attribute_in_database(attr_name)
+ mutations_from_database.original_value(attr_name)
+ end
+
+ # Alias for `changed?`
+ def has_changes_to_save?
+ mutations_from_database.any_changes?
+ end
+
+ # Alias for `changes`
+ def changes_to_save
+ mutations_from_database.changes
+ end
+
+ # Alias for `changed`
+ def changed_attribute_names_to_save
+ changes_to_save.keys
+ end
+
+ # Alias for `changed_attributes`
+ def attributes_in_database
+ changes_to_save.transform_values(&:first)
+ end
+
+ def attribute_was(*)
+ emit_warning_if_needed("attribute_was", "attribute_before_last_save")
+ super
+ end
+
+ def attribute_change(*)
+ emit_warning_if_needed("attribute_change", "saved_change_to_attribute")
+ super
+ end
+
+ def attribute_changed?(*)
+ emit_warning_if_needed("attribute_changed?", "saved_change_to_attribute?")
+ super
+ end
+
+ def changed?(*)
+ emit_warning_if_needed("changed?", "saved_changes?")
+ super
+ end
+
+ def changed(*)
+ emit_warning_if_needed("changed", "saved_changes.keys")
+ super
+ end
+
private
def mutation_tracker
@@ -107,12 +245,37 @@ module ActiveRecord
@mutation_tracker ||= AttributeMutationTracker.new(@attributes)
end
+ def emit_warning_if_needed(method_name, new_method_name)
+ unless mutation_tracker.equal?(mutations_from_database)
+ ActiveSupport::Deprecation.warn(<<-EOW.squish)
+ The behavior of `#{method_name}` inside of after callbacks will
+ be changing in the next version of Rails. The new return value will reflect the
+ behavior of calling the method after `save` returned (e.g. the opposite of what
+ it returns now). To maintain the current behavior, use `#{new_method_name}`
+ instead.
+ EOW
+ end
+ end
+
+ def mutations_from_database
+ unless defined?(@mutations_from_database)
+ @mutations_from_database = nil
+ end
+ @mutations_from_database ||= mutation_tracker
+ end
+
def changes_include?(attr_name)
super || mutation_tracker.changed?(attr_name)
end
def clear_attribute_change(attr_name)
mutation_tracker.forget_change(attr_name)
+ mutations_from_database.forget_change(attr_name)
+ end
+
+ def attribute_will_change!(attr_name)
+ super
+ mutations_from_database.force_change(attr_name)
end
def _update_record(*)
@@ -124,18 +287,27 @@ module ActiveRecord
end
def keys_for_partial_write
- changed & self.class.column_names
+ changed_attribute_names_to_save & self.class.column_names
end
- def store_original_attributes
+ def forget_attribute_assignments
@attributes = @attributes.map(&:forgetting_assignment)
+ end
+
+ def clear_mutation_trackers
@mutation_tracker = nil
+ @mutations_from_database = nil
+ @mutations_before_last_save = nil
end
def previous_mutation_tracker
@previous_mutation_tracker ||= NullMutationTracker.instance
end
+ def mutations_before_last_save
+ @mutations_before_last_save ||= previous_mutation_tracker
+ end
+
def cache_changed_attributes
@cached_changed_attributes = changed_attributes
yield
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index 9e99ed8ac1..8fcac82a0d 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -45,7 +45,12 @@ module ActiveRecord
attribute_was(self.class.primary_key)
end
- protected
+ def id_in_database
+ sync_with_transaction_state
+ attribute_in_database(self.class.primary_key)
+ end
+
+ private
def attribute_method?(attr_name)
attr_name == "id" || super
@@ -60,7 +65,7 @@ module ActiveRecord
end
end
- ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was).to_set
+ 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)
@@ -130,10 +135,10 @@ module ActiveRecord
return pk unless pk.is_a?(Array)
warn <<-WARNING.strip_heredoc
- WARNING: Active Record does not support composite primary key.
+ WARNING: Active Record does not support composite primary key.
- #{table_name} has composite primary key. Composite primary key is ignored.
- WARNING
+ #{table_name} has composite primary key. Composite primary key is ignored.
+ WARNING
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 58f82cfd30..369a6e35aa 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -4,26 +4,26 @@ module ActiveRecord
extend ActiveSupport::Concern
module ClassMethods
- protected
+ private
- # We want to generate the methods via module_eval rather than
- # define_method, because define_method is slower on dispatch.
- # Evaluating many similar methods may use more memory as the instruction
- # sequences are duplicated and cached (in MRI). define_method may
- # be slower on dispatch, but if you're careful about the closure
- # created, then define_method will consume much less memory.
- #
- # But sometimes the database might return columns with
- # characters that are not allowed in normal method names (like
- # 'my_column(omg)'. So to work around this we first define with
- # the __temp__ identifier, and then use alias method to rename
- # it to what we want.
- #
- # We are also defining a constant to hold the frozen string of
- # the attribute name. Using a constant means that we do not have
- # to allocate an object on each call to the attribute method.
- # Making it frozen means that it doesn't get duped when used to
- # key the @attributes in read_attribute.
+ # We want to generate the methods via module_eval rather than
+ # define_method, because define_method is slower on dispatch.
+ # Evaluating many similar methods may use more memory as the instruction
+ # sequences are duplicated and cached (in MRI). define_method may
+ # be slower on dispatch, but if you're careful about the closure
+ # created, then define_method will consume much less memory.
+ #
+ # But sometimes the database might return columns with
+ # characters that are not allowed in normal method names (like
+ # 'my_column(omg)'. So to work around this we first define with
+ # the __temp__ identifier, and then use alias method to rename
+ # it to what we want.
+ #
+ # We are also defining a constant to hold the frozen string of
+ # the attribute name. Using a constant means that we do not have
+ # to allocate an object on each call to the attribute method.
+ # Making it frozen means that it doesn't get duped when used to
+ # key the @attributes in read_attribute.
def define_method_attribute(name)
safe_name = name.unpack("h*".freeze).first
temp_method = "__temp__#{safe_name}"
@@ -31,11 +31,11 @@ module ActiveRecord
ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
- def #{temp_method}
- name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
- _read_attribute(name) { |n| missing_attribute(n, caller) }
- end
- STR
+ def #{temp_method}
+ name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
+ _read_attribute(name) { |n| missing_attribute(n, caller) }
+ end
+ STR
generated_attribute_methods.module_eval do
alias_method name, temp_method
@@ -48,7 +48,12 @@ module ActiveRecord
# it has been typecast (for example, "2004-12-12" in a date column is cast
# to a date object, like Date.new(2004, 12, 12)).
def read_attribute(attr_name, &block)
- name = attr_name.to_s
+ name = if self.class.attribute_alias?(attr_name)
+ self.class.attribute_alias(attr_name).to_s
+ else
+ attr_name.to_s
+ end
+
name = self.class.primary_key if name == "id".freeze
_read_attribute(name, &block)
end
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index c70178cd2c..945192fe04 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -26,7 +26,7 @@ module ActiveRecord
# ==== Parameters
#
# * +attr_name+ - The field name that should be serialized.
- # * +class_name_or_coder+ - Optional, a coder object, which responds to `.load` / `.dump`
+ # * +class_name_or_coder+ - Optional, a coder object, which responds to +.load+ and +.dump+
# or a class name that the object type should be equal to.
#
# ==== Example
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 2c8e86fbb2..df1231ad47 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -39,7 +39,7 @@ module ActiveRecord
end
def set_time_zone_without_conversion(value)
- ::Time.zone.local_to_utc(value).in_time_zone if value
+ ::Time.zone.local_to_utc(value).try(:in_time_zone) if value
end
def map_avoiding_infinite_recursion(value)
@@ -63,13 +63,14 @@ module ActiveRecord
self.skip_time_zone_conversion_for_attributes = []
class_attribute :time_zone_aware_types, instance_writer: false
- self.time_zone_aware_types = [:datetime, :not_explicitly_configured]
+ self.time_zone_aware_types = [:datetime, :time]
end
module ClassMethods
private
def inherited(subclass)
+ super
# We need to apply this decorator here, rather than on module inclusion. The closure
# created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the
# sub class being decorated. As such, changes to `time_zone_aware_attributes`, or
@@ -80,35 +81,13 @@ module ActiveRecord
TimeZoneConverter.new(type)
end
end
- super
end
def create_time_zone_conversion_attribute?(name, cast_type)
enabled_for_column = time_zone_aware_attributes &&
!skip_time_zone_conversion_for_attributes.include?(name.to_sym)
- result = enabled_for_column &&
- time_zone_aware_types.include?(cast_type.type)
-
- if enabled_for_column &&
- !result &&
- cast_type.type == :time &&
- time_zone_aware_types.include?(:not_explicitly_configured)
- ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
- Time columns will become time zone aware in Rails 5.1. This
- still causes `String`s to be parsed as if they were in `Time.zone`,
- and `Time`s to be converted to `Time.zone`.
-
- To keep the old behavior, you must add the following to your initializer:
-
- config.active_record.time_zone_aware_types = [:datetime]
-
- To silence this deprecation warning, add the following:
-
- config.active_record.time_zone_aware_types = [:datetime, :time]
- MESSAGE
- end
- result
+ enabled_for_column && time_zone_aware_types.include?(cast_type.type)
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index 5822414129..fe0e01db28 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -8,20 +8,20 @@ module ActiveRecord
end
module ClassMethods
- protected
+ private
def define_method_attribute=(name)
safe_name = name.unpack("h*".freeze).first
ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
- def __temp__#{safe_name}=(value)
- name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
- write_attribute(name, value)
- end
- alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
- undef_method :__temp__#{safe_name}=
- STR
+ def __temp__#{safe_name}=(value)
+ name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
+ write_attribute(name, value)
+ end
+ alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
+ undef_method :__temp__#{safe_name}=
+ STR
end
end
@@ -29,7 +29,13 @@ module ActiveRecord
# specified +value+. Empty strings for Integer and Float columns are
# turned into +nil+.
def write_attribute(attr_name, value)
- write_attribute_with_type_cast(attr_name, value, true)
+ name = if self.class.attribute_alias?(attr_name)
+ self.class.attribute_alias(attr_name).to_s
+ else
+ attr_name.to_s
+ end
+
+ write_attribute_with_type_cast(name, value, true)
end
def raw_write_attribute(attr_name, value) # :nodoc:
@@ -37,7 +43,7 @@ module ActiveRecord
end
private
- # Handle *= for method_missing.
+ # Handle *= for method_missing.
def attribute=(attribute_name, value)
write_attribute(attribute_name, value)
end
diff --git a/activerecord/lib/active_record/attribute_mutation_tracker.rb b/activerecord/lib/active_record/attribute_mutation_tracker.rb
index c257aef52f..3417090830 100644
--- a/activerecord/lib/active_record/attribute_mutation_tracker.rb
+++ b/activerecord/lib/active_record/attribute_mutation_tracker.rb
@@ -1,7 +1,10 @@
module ActiveRecord
class AttributeMutationTracker # :nodoc:
+ OPTION_NOT_GIVEN = Object.new
+
def initialize(attributes)
@attributes = attributes
+ @forced_changes = Set.new
end
def changed_values
@@ -14,15 +17,29 @@ module ActiveRecord
def changes
attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
- if changed?(attr_name)
- result[attr_name] = [attributes[attr_name].original_value, attributes.fetch_value(attr_name)]
+ change = change_to_attribute(attr_name)
+ if change
+ result[attr_name] = change
end
end
end
- def changed?(attr_name)
+ def change_to_attribute(attr_name)
+ if changed?(attr_name)
+ [attributes[attr_name].original_value, attributes.fetch_value(attr_name)]
+ end
+ end
+
+ def any_changes?
+ attr_names.any? { |attr| changed?(attr) }
+ end
+
+ def changed?(attr_name, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN)
attr_name = attr_name.to_s
- attributes[attr_name].changed?
+ forced_changes.include?(attr_name) ||
+ attributes[attr_name].changed? &&
+ (OPTION_NOT_GIVEN == from || attributes[attr_name].original_value == from) &&
+ (OPTION_NOT_GIVEN == to || attributes[attr_name].value == to)
end
def changed_in_place?(attr_name)
@@ -32,11 +49,22 @@ module ActiveRecord
def forget_change(attr_name)
attr_name = attr_name.to_s
attributes[attr_name] = attributes[attr_name].forgetting_assignment
+ forced_changes.delete(attr_name)
+ end
+
+ def original_value(attr_name)
+ attributes[attr_name].original_value
+ end
+
+ def force_change(attr_name)
+ forced_changes << attr_name.to_s
end
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
+ # Workaround for Ruby 2.2 "private attribute?" warning.
protected
- attr_reader :attributes
+ attr_reader :attributes, :forced_changes
private
@@ -48,14 +76,21 @@ module ActiveRecord
class NullMutationTracker # :nodoc:
include Singleton
- def changed_values
+ def changed_values(*)
{}
end
- def changes
+ def changes(*)
{}
end
+ def change_to_attribute(attr_name)
+ end
+
+ def any_changes?(*)
+ false
+ end
+
def changed?(*)
false
end
@@ -66,5 +101,8 @@ module ActiveRecord
def forget_change(*)
end
+
+ def original_value(*)
+ end
end
end
diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb
index 5bde1f107c..66b278219a 100644
--- a/activerecord/lib/active_record/attribute_set.rb
+++ b/activerecord/lib/active_record/attribute_set.rb
@@ -98,6 +98,8 @@ module ActiveRecord
attributes == other.attributes
end
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
+ # Workaround for Ruby 2.2 "private attribute?" warning.
protected
attr_reader :attributes
diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb
index 661f996e1a..2f624d32af 100644
--- a/activerecord/lib/active_record/attribute_set/builder.rb
+++ b/activerecord/lib/active_record/attribute_set/builder.rb
@@ -90,6 +90,8 @@ module ActiveRecord
@materialized = true
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 :types, :values, :additional_types, :delegate_hash, :default
diff --git a/activerecord/lib/active_record/attribute_set/yaml_encoder.rb b/activerecord/lib/active_record/attribute_set/yaml_encoder.rb
index c86cfc4263..899de14792 100644
--- a/activerecord/lib/active_record/attribute_set/yaml_encoder.rb
+++ b/activerecord/lib/active_record/attribute_set/yaml_encoder.rb
@@ -31,6 +31,8 @@ module ActiveRecord
end
end
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
+ # Workaround for Ruby 2.2 "private attribute?" warning.
protected
attr_reader :default_types
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
index 4b92e5835f..75f5ba3a96 100644
--- a/activerecord/lib/active_record/attributes.rb
+++ b/activerecord/lib/active_record/attributes.rb
@@ -116,7 +116,7 @@ module ActiveRecord
# Users may also define their own custom types, as long as they respond
# to the methods defined on the value type. The method +deserialize+ or
# +cast+ will be called on your type object, with raw input from the
- # database or from your controllers. See ActiveRecord::Type::Value for the
+ # database or from your controllers. See ActiveModel::Type::Value for the
# expected API. It is recommended that your type objects inherit from an
# existing type, or from ActiveRecord::Type::Value
#
@@ -143,7 +143,7 @@ module ActiveRecord
# store_listing.price_in_cents # => 1000
#
# For more details on creating custom types, see the documentation for
- # ActiveRecord::Type::Value. For more details on registering your types
+ # ActiveModel::Type::Value. For more details on registering your types
# to be referenced by a symbol, see ActiveRecord::Type.register. You can
# also pass a type object directly, in place of a symbol.
#
@@ -190,8 +190,8 @@ module ActiveRecord
# The type of an attribute is given the opportunity to change how dirty
# tracking is performed. The methods +changed?+ and +changed_in_place?+
# will be called from ActiveModel::Dirty. See the documentation for those
- # methods in ActiveRecord::Type::Value for more details.
- def attribute(name, cast_type, **options)
+ # methods in ActiveModel::Type::Value for more details.
+ def attribute(name, cast_type = Type::Value.new, **options)
name = name.to_s
reload_schema_from_cache
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 6e6620aad5..9d0b501862 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -154,10 +154,10 @@ module ActiveRecord
# Loop prevention for validation of associations
unless @_already_called[name]
begin
- @_already_called[name]=true
+ @_already_called[name] = true
result = instance_eval(&block)
ensure
- @_already_called[name]=false
+ @_already_called[name] = false
end
end
@@ -267,7 +267,7 @@ module ActiveRecord
# Returns whether or not this record has been changed in any way (including whether
# any of its nested autosave associations are likewise changed)
def changed_for_autosave?
- new_record? || changed? || marked_for_destruction? || nested_records_changed_for_autosave?
+ new_record? || has_changes_to_save? || marked_for_destruction? || nested_records_changed_for_autosave?
end
private
@@ -325,30 +325,24 @@ module ActiveRecord
# Returns whether or not the association is valid and applies any errors to
# the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
# enabled records if they're marked_for_destruction? or destroyed.
- def association_valid?(reflection, record, index=nil)
+ def association_valid?(reflection, record, index = nil)
return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
validation_context = self.validation_context unless [:create, :update].include?(self.validation_context)
+
unless valid = record.valid?(validation_context)
if reflection.options[:autosave]
indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors)
record.errors.each do |attribute, message|
- if indexed_attribute
- attribute = "#{reflection.name}[#{index}].#{attribute}"
- else
- attribute = "#{reflection.name}.#{attribute}"
- end
+ attribute = normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
errors[attribute] << message
errors[attribute].uniq!
end
record.errors.details.each_key do |attribute|
- if indexed_attribute
- reflection_attribute = "#{reflection.name}[#{index}].#{attribute}"
- else
- reflection_attribute = "#{reflection.name}.#{attribute}"
- end
+ reflection_attribute =
+ normalize_reflection_attribute(indexed_attribute, reflection, index, attribute).to_sym
record.errors.details[attribute].each do |error|
errors.details[reflection_attribute] << error
@@ -362,6 +356,14 @@ module ActiveRecord
valid
end
+ def normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
+ if indexed_attribute
+ "#{reflection.name}[#{index}].#{attribute}"
+ else
+ "#{reflection.name}.#{attribute}"
+ end
+ end
+
# Is used as a before_save callback to check while saving a collection
# association whether or not the parent was a new record before saving.
def before_save_collection_association
@@ -381,6 +383,9 @@ module ActiveRecord
if association = association_instance_get(reflection.name)
autosave = reflection.options[:autosave]
+ # reconstruct the scope now that we know the owner's id
+ association.reset_scope if association.respond_to?(:reset_scope)
+
if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
if autosave
records_to_destroy = records.select(&:marked_for_destruction?)
@@ -406,9 +411,6 @@ module ActiveRecord
raise ActiveRecord::Rollback unless saved
end
end
-
- # reconstruct the scope now that we know the owner's id
- association.reset_scope if association.respond_to?(:reset_scope)
end
end
@@ -449,7 +451,7 @@ module ActiveRecord
def record_changed?(reflection, record, key)
record.new_record? ||
(record.has_attribute?(reflection.foreign_key) && record[reflection.foreign_key] != key) ||
- record.attribute_changed?(reflection.foreign_key)
+ record.will_save_change_to_attribute?(reflection.foreign_key)
end
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
@@ -457,7 +459,9 @@ module ActiveRecord
# In addition, it will destroy the association if it was marked for destruction.
def save_belongs_to_association(reflection)
association = association_instance_get(reflection.name)
- record = association && association.load_target
+ return unless association && association.loaded? && !association.stale_target?
+
+ record = association.load_target
if record && !record.destroyed?
autosave = reflection.options[:autosave]
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 01d7886406..ac1aa2df45 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -14,6 +14,7 @@ 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"
@@ -303,6 +304,7 @@ module ActiveRecord #:nodoc:
include AttributeDecorators
include Locking::Optimistic
include Locking::Pessimistic
+ include DefineCallbacks
include AttributeMethods
include Callbacks
include Timestamp
@@ -312,8 +314,8 @@ module ActiveRecord #:nodoc:
include NestedAttributes
include Aggregations
include Transactions
- include NoTouching
include TouchLater
+ include NoTouching
include Reflection
include Serialization
include Store
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index c616733aa4..be6720ddf3 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -265,17 +265,6 @@ module ActiveRecord
:before_destroy, :around_destroy, :after_destroy, :after_commit, :after_rollback
]
- module ClassMethods # :nodoc:
- include ActiveModel::Callbacks
- end
-
- included do
- include ActiveModel::Validations::Callbacks
-
- define_model_callbacks :initialize, :find, :touch, only: :after
- define_model_callbacks :save, :create, :update, :destroy
- end
-
def destroy #:nodoc:
@_destroy_callback_already_called ||= false
return if @_destroy_callback_already_called
@@ -294,15 +283,15 @@ module ActiveRecord
private
- def create_or_update(*) #:nodoc:
+ def create_or_update(*)
_run_save_callbacks { super }
end
- def _create_record #:nodoc:
+ def _create_record
_run_create_callbacks { super }
end
- def _update_record(*) #:nodoc:
+ def _update_record(*)
_run_update_callbacks { super }
end
end
diff --git a/activerecord/lib/active_record/coders/yaml_column.rb b/activerecord/lib/active_record/coders/yaml_column.rb
index 1c8c9fa272..3a04a10fc9 100644
--- a/activerecord/lib/active_record/coders/yaml_column.rb
+++ b/activerecord/lib/active_record/coders/yaml_column.rb
@@ -1,5 +1,4 @@
require "yaml"
-require "active_support/core_ext/regexp"
module ActiveRecord
module Coders # :nodoc:
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 535d79b525..5ec2fc073e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -69,7 +69,7 @@ module ActiveRecord
# threads, which can occur if a programmer forgets to close a
# connection at the end of a thread or a thread dies unexpectedly.
# Regardless of this setting, the Reaper will be invoked before every
- # blocking wait. (Default nil, which means don't schedule the Reaper).
+ # blocking wait. (Default +nil+, which means don't schedule the Reaper).
#
#--
# Synchronization policy:
@@ -116,7 +116,7 @@ module ActiveRecord
end
end
- # If +element+ is in the queue, remove and return it, or nil.
+ # If +element+ is in the queue, remove and return it, or +nil+.
def delete(element)
synchronize do
@queue.delete(element)
@@ -135,7 +135,7 @@ module ActiveRecord
# If +timeout+ is not given, remove and return the head the
# queue if the number of available elements is strictly
# greater than the number of threads currently waiting (that
- # is, don't jump ahead in line). Otherwise, return nil.
+ # is, don't jump ahead in line). Otherwise, return +nil+.
#
# If +timeout+ is given, block if there is no element
# available, waiting up to +timeout+ seconds for an element to
@@ -158,33 +158,33 @@ module ActiveRecord
@lock.synchronize(&block)
end
- # Test if the queue currently contains any elements.
+ # Test if the queue currently contains any elements.
def any?
!@queue.empty?
end
- # A thread can remove an element from the queue without
- # waiting if and only if the number of currently available
- # connections is strictly greater than the number of waiting
- # threads.
+ # A thread can remove an element from the queue without
+ # waiting if and only if the number of currently available
+ # connections is strictly greater than the number of waiting
+ # threads.
def can_remove_no_wait?
@queue.size > @num_waiting
end
- # Removes and returns the head of the queue if possible, or nil.
+ # Removes and returns the head of the queue if possible, or +nil+.
def remove
@queue.shift
end
- # Remove and return the head the queue if the number of
- # available elements is strictly greater than the number of
- # threads currently waiting. Otherwise, return nil.
+ # Remove and return the head the queue if the number of
+ # available elements is strictly greater than the number of
+ # threads currently waiting. Otherwise, return +nil+.
def no_wait_poll
remove if can_remove_no_wait?
end
- # Waits on the queue up to +timeout+ seconds, then removes and
- # returns the head of the queue.
+ # Waits on the queue up to +timeout+ seconds, then removes and
+ # returns the head of the queue.
def wait_poll(timeout)
@num_waiting += 1
@@ -282,7 +282,7 @@ module ActiveRecord
end
# Every +frequency+ seconds, the reaper will call +reap+ on +pool+.
- # A reaper instantiated with a nil frequency will never reap the
+ # A reaper instantiated with a +nil+ frequency will never reap the
# connection pool.
#
# Configure the frequency by setting "reaping_frequency" in your
@@ -307,6 +307,7 @@ module ActiveRecord
end
include MonitorMixin
+ include QueryCache::ConnectionPoolConfiguration
attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache
attr_reader :spec, :connections, :size, :reaper
@@ -349,8 +350,7 @@ module ActiveRecord
# currently in the process of independently establishing connections to the DB.
@now_connecting = 0
- # A boolean toggle that allows/disallows new connections.
- @new_cons_enabled = true
+ @threads_blocking_new_connections = 0
@available = ConnectionLeasingQueue.new self
end
@@ -445,8 +445,6 @@ module ActiveRecord
# connections in the pool within a timeout interval (default duration is
# <tt>spec.config[:checkout_timeout] * 2</tt> seconds).
def clear_reloadable_connections(raise_on_acquisition_timeout = true)
- num_new_conns_required = 0
-
with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
synchronize do
@connections.each do |conn|
@@ -457,24 +455,9 @@ module ActiveRecord
conn.disconnect! if conn.requires_reloading?
end
@connections.delete_if(&:requires_reloading?)
-
@available.clear
-
- if @connections.size < @size
- # because of the pruning done by this method, we might be running
- # low on connections, while threads stuck in queue are helpless
- # (not being able to establish new connections for themselves),
- # see also more detailed explanation in +remove+
- num_new_conns_required = num_waiting_in_queue - @connections.size
- end
-
- @connections.each do |conn|
- @available.add conn
- end
end
end
-
- bulk_make_new_connections(num_new_conns_required) if num_new_conns_required > 0
end
# Clears the cache which maps classes and re-connects connections that
@@ -581,9 +564,27 @@ module ActiveRecord
@available.num_waiting
end
+ # Return connection pool's usage statistic
+ # Example:
+ #
+ # ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
+ def stat
+ synchronize do
+ {
+ size: size,
+ connections: @connections.size,
+ busy: @connections.count { |c| c.in_use? && c.owner.alive? },
+ dead: @connections.count { |c| c.in_use? && !c.owner.alive? },
+ idle: @connections.count { |c| !c.in_use? },
+ waiting: num_waiting_in_queue,
+ checkout_timeout: checkout_timeout
+ }
+ end
+ end
+
private
- #--
- # this is unfortunately not concurrent
+ #--
+ # this is unfortunately not concurrent
def bulk_make_new_connections(num_new_conns_needed)
num_new_conns_needed.times do
# try_to_checkout_new_connection will not exceed pool's @size limit
@@ -594,19 +595,19 @@ module ActiveRecord
end
end
- #--
- # From the discussion on GitHub:
- # https://github.com/rails/rails/pull/14938#commitcomment-6601951
- # This hook-in method allows for easier monkey-patching fixes needed by
- # JRuby users that use Fibers.
+ #--
+ # From the discussion on GitHub:
+ # https://github.com/rails/rails/pull/14938#commitcomment-6601951
+ # This hook-in method allows for easier monkey-patching fixes needed by
+ # JRuby users that use Fibers.
def connection_cache_key(thread)
thread
end
- # Take control of all existing connections so a "group" action such as
- # reload/disconnect can be performed safely. It is no longer enough to
- # wrap it in +synchronize+ because some pool's actions are allowed
- # to be performed outside of the main +synchronize+ block.
+ # Take control of all existing connections so a "group" action such as
+ # reload/disconnect can be performed safely. It is no longer enough to
+ # wrap it in +synchronize+ because some pool's actions are allowed
+ # to be performed outside of the main +synchronize+ block.
def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true)
with_new_connections_blocked do
attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout)
@@ -617,7 +618,7 @@ module ActiveRecord
def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
collected_conns = synchronize do
# account for our own connections
- @connections.select {|conn| conn.owner == Thread.current}
+ @connections.select { |conn| conn.owner == Thread.current }
end
newly_checked_out = []
@@ -654,12 +655,12 @@ module ActiveRecord
if release_newly_checked_out && newly_checked_out
# releasing only those conns that were checked out in this method, conns
# checked outside this method (before it was called) are not for us to release
- newly_checked_out.each {|conn| checkin(conn)}
+ newly_checked_out.each { |conn| checkin(conn) }
end
end
- #--
- # Must be called in a synchronize block.
+ #--
+ # Must be called in a synchronize block.
def checkout_for_exclusive_access(checkout_timeout)
checkout(checkout_timeout)
rescue ConnectionTimeoutError
@@ -681,26 +682,45 @@ module ActiveRecord
end
def with_new_connections_blocked
- previous_value = nil
synchronize do
- previous_value, @new_cons_enabled = @new_cons_enabled, false
+ @threads_blocking_new_connections += 1
end
+
yield
ensure
- synchronize { @new_cons_enabled = previous_value }
+ num_new_conns_required = 0
+
+ synchronize do
+ @threads_blocking_new_connections -= 1
+
+ if @threads_blocking_new_connections.zero?
+ @available.clear
+
+ num_new_conns_required = num_waiting_in_queue
+
+ @connections.each do |conn|
+ next if conn.in_use?
+
+ @available.add conn
+ num_new_conns_required -= 1
+ end
+ end
+ end
+
+ bulk_make_new_connections(num_new_conns_required) if num_new_conns_required > 0
end
- # Acquire a connection by one of 1) immediately removing one
- # from the queue of available connections, 2) creating a new
- # connection if the pool is not at capacity, 3) waiting on the
- # queue for a connection to become available.
- #
- # Raises:
- # - ActiveRecord::ConnectionTimeoutError if a connection could not be acquired
- #
- #--
- # Implementation detail: the connection returned by +acquire_connection+
- # will already be "+connection.lease+ -ed" to the current thread.
+ # Acquire a connection by one of 1) immediately removing one
+ # from the queue of available connections, 2) creating a new
+ # connection if the pool is not at capacity, 3) waiting on the
+ # queue for a connection to become available.
+ #
+ # Raises:
+ # - ActiveRecord::ConnectionTimeoutError if a connection could not be acquired
+ #
+ #--
+ # Implementation detail: the connection returned by +acquire_connection+
+ # will already be "+connection.lease+ -ed" to the current thread.
def acquire_connection(checkout_timeout)
# NOTE: we rely on +@available.poll+ and +try_to_checkout_new_connection+ to
# +conn.lease+ the returned connection (and to do this in a +synchronized+
@@ -716,8 +736,8 @@ module ActiveRecord
end
end
- #--
- # if owner_thread param is omitted, this must be called in synchronize block
+ #--
+ # if owner_thread param is omitted, this must be called in synchronize block
def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
@thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn)
end
@@ -729,17 +749,17 @@ module ActiveRecord
end
end
- # If the pool is not at a +@size+ limit, establish new connection. Connecting
- # to the DB is done outside main synchronized section.
- #--
- # Implementation constraint: a newly established connection returned by this
- # method must be in the +.leased+ state.
+ # If the pool is not at a +@size+ limit, establish new connection. Connecting
+ # to the DB is done outside main synchronized section.
+ #--
+ # Implementation constraint: a newly established connection returned by this
+ # method must be in the +.leased+ state.
def try_to_checkout_new_connection
# first in synchronized section check if establishing new conns is allowed
# and increment @now_connecting, to prevent overstepping this pool's @size
# constraint
do_checkout = synchronize do
- if @new_cons_enabled && (@connections.size + @now_connecting) < @size
+ if @threads_blocking_new_connections.zero? && (@connections.size + @now_connecting) < @size
@now_connecting += 1
end
end
@@ -833,7 +853,7 @@ module ActiveRecord
class ConnectionHandler
def initialize
# These caches are keyed by spec.name (ConnectionSpecification#name).
- @owner_to_pool = Concurrent::Map.new(initial_capacity: 2) do |h,k|
+ @owner_to_pool = Concurrent::Map.new(initial_capacity: 2) do |h, k|
h[k] = Concurrent::Map.new(initial_capacity: 2)
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
index 95c72f1e20..407e019326 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
@@ -46,7 +46,7 @@ module ActiveRecord
end
# Returns the maximum number of elements in an IN (x,y,z) clause.
- # nil means no limit.
+ # +nil+ means no limit.
def in_clause_length
nil
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 452e78a40b..2c352819fb 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -10,9 +10,9 @@ module ActiveRecord
def to_sql(arel, binds = [])
if arel.respond_to?(:ast)
collected = visitor.accept(arel.ast, collector)
- collected.compile(binds, self)
+ collected.compile(binds, self).freeze
else
- arel
+ arel.dup.freeze
end
end
@@ -90,6 +90,7 @@ module ActiveRecord
# +binds+ as the bind substitutes. +name+ is logged along with
# the executed +sql+ statement.
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
+ sql, binds = sql_for_insert(sql, pk, nil, sequence_name, binds)
exec_query(sql, name, binds)
end
@@ -114,34 +115,27 @@ module ActiveRecord
# Executes an INSERT query and returns the new record's ID
#
- # +id_value+ will be returned unless the value is nil, in
+ # +id_value+ will be returned unless the value is +nil+, in
# which case the database will attempt to calculate the last inserted
# id and return that value.
#
# If the next id was calculated in advance (as in Oracle), it should be
# passed in as +id_value+.
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
- sql, binds, pk, sequence_name = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds)
- value = exec_insert(sql, name, binds, pk, sequence_name)
+ value = exec_insert(to_sql(arel, binds), name, binds, pk, sequence_name)
id_value || last_inserted_id(value)
end
alias create insert
- alias insert_sql insert
- deprecate insert_sql: :insert
# Executes the update statement and returns the number of rows affected.
def update(arel, name = nil, binds = [])
exec_update(to_sql(arel, binds), name, binds)
end
- alias update_sql update
- deprecate update_sql: :update
# Executes the delete statement and returns the number of rows affected.
def delete(arel, name = nil, binds = [])
exec_delete(to_sql(arel, binds), name, binds)
end
- alias delete_sql delete
- deprecate delete_sql: :delete
# Returns +true+ when the connection adapter supports prepared statement
# caching, otherwise returns +false+
@@ -245,7 +239,7 @@ module ActiveRecord
end
def reset_transaction #:nodoc:
- @transaction_manager = TransactionManager.new(self)
+ @transaction_manager = ConnectionAdapters::TransactionManager.new(self)
end
# Register a record with the current transaction so that its after_commit and after_rollback callbacks
@@ -334,17 +328,12 @@ module ActiveRecord
# Sanitizes the given LIMIT parameter in order to prevent SQL injection.
#
# The +limit+ may be anything that can evaluate to a string via #to_s. It
- # should look like an integer, or a comma-delimited list of integers, or
- # an Arel SQL literal.
+ # should look like an integer, or an Arel SQL literal.
#
# Returns Integer and Arel::Nodes::SqlLiteral limits as is.
- # Returns the sanitized limit parameter, either as an integer, or as a
- # string which contains a comma-delimited list of integers.
def sanitize_limit(limit)
if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)
limit
- elsif limit.to_s.include?(",")
- Arel.sql limit.to_s.split(",").map{ |i| Integer(i) }.join(",")
else
Integer(limit)
end
@@ -360,7 +349,7 @@ module ActiveRecord
end
alias join_to_delete join_to_update
- protected
+ private
# Returns a subquery for the given key using the join information.
def subquery_for(key, select)
@@ -379,7 +368,7 @@ module ActiveRecord
end
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
- [sql, binds, pk, sequence_name]
+ [sql, binds]
end
def last_inserted_id(result)
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 10c60080d5..7eab7de5d3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -4,6 +4,9 @@ module ActiveRecord
class << self
def included(base) #:nodoc:
dirties_query_cache base, :insert, :update, :delete, :rollback_to_savepoint, :rollback_db_transaction
+
+ base.set_callback :checkout, :after, :configure_query_cache!
+ base.set_callback :checkin, :after, :disable_query_cache!
end
def dirties_query_cache(base, *method_names)
@@ -18,11 +21,32 @@ module ActiveRecord
end
end
+ module ConnectionPoolConfiguration
+ def initialize(*)
+ super
+ @query_cache_enabled = Concurrent::Map.new { false }
+ end
+
+ def enable_query_cache!
+ @query_cache_enabled[connection_cache_key(Thread.current)] = true
+ connection.enable_query_cache! if active_connection?
+ end
+
+ def disable_query_cache!
+ @query_cache_enabled.delete connection_cache_key(Thread.current)
+ connection.disable_query_cache! if active_connection?
+ end
+
+ def query_cache_enabled
+ @query_cache_enabled[connection_cache_key(Thread.current)]
+ end
+ end
+
attr_reader :query_cache, :query_cache_enabled
def initialize(*)
super
- @query_cache = Hash.new { |h,sql| h[sql] = {} }
+ @query_cache = Hash.new { |h, sql| h[sql] = {} }
@query_cache_enabled = false
end
@@ -41,6 +65,7 @@ module ActiveRecord
def disable_query_cache!
@query_cache_enabled = false
+ clear_query_cache
end
# Disable the query cache within the block.
@@ -65,7 +90,7 @@ module ActiveRecord
if @query_cache_enabled && !locked?(arel)
arel, binds = binds_from_relation arel, binds
sql = to_sql(arel, binds)
- cache_sql(sql, binds) { super(sql, name, binds, preparable: preparable) }
+ cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable) }
else
super
end
@@ -73,11 +98,17 @@ module ActiveRecord
private
- def cache_sql(sql, binds)
+ def cache_sql(sql, name, binds)
result =
if @query_cache[sql].key?(binds)
- ActiveSupport::Notifications.instrument("sql.active_record",
- sql: sql, binds: binds, name: "CACHE", connection_id: object_id)
+ ActiveSupport::Notifications.instrument(
+ "sql.active_record",
+ sql: sql,
+ binds: binds,
+ name: name,
+ connection_id: object_id,
+ cached: true,
+ )
@query_cache[sql][binds]
else
@query_cache[sql][binds] = yield
@@ -85,11 +116,15 @@ module ActiveRecord
result.dup
end
- # If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such
- # queries should not be cached.
+ # If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such
+ # queries should not be cached.
def locked?(arel)
arel.respond_to?(:locked) && arel.locked
end
+
+ def configure_query_cache!
+ enable_query_cache! if pool.query_cache_enabled
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index bbd52b8a91..0c6bc16e6f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -5,20 +5,10 @@ module ActiveRecord
module Quoting
# Quotes the column value to help prevent
# {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection].
- def quote(value, column = nil)
+ def quote(value)
# records are quoted as their primary key
return value.quoted_id if value.respond_to?(:quoted_id)
- if column
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing a column to `quote` has been deprecated. It is only used
- for type casting, which should be handled elsewhere. See
- https://github.com/rails/arel/commit/6160bfbda1d1781c3b08a33ec4955f170e95be11
- for more information.
- MSG
- value = type_cast_from_column(column, value)
- end
-
_quote(value)
end
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 c10f45937e..9b324c090b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -71,7 +71,7 @@ module ActiveRecord
polymorphic: false,
index: true,
foreign_key: false,
- type: :integer,
+ type: :bigint,
**options
)
@name = name
@@ -100,6 +100,8 @@ module ActiveRecord
end
end
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
+ # Workaround for Ruby 2.2 "private attribute?" warning.
protected
attr_reader :name, :polymorphic, :index, :foreign_key, :type, :options
@@ -211,7 +213,7 @@ module ActiveRecord
def initialize(name, temporary = false, options = nil, as = nil, comment: nil)
@columns_hash = {}
- @indexes = {}
+ @indexes = []
@foreign_keys = []
@primary_keys = nil
@temporary = temporary
@@ -303,7 +305,7 @@ module ActiveRecord
# end
def column(name, type, options = {})
name = name.to_s
- type = type.to_sym
+ type = type.to_sym if type
options = options.dup
if @columns_hash[name] && @columns_hash[name].primary_key?
@@ -327,7 +329,7 @@ module ActiveRecord
#
# index(:account_id, name: 'index_projects_on_account_id')
def index(column_name, options = {})
- indexes[column_name] = options
+ indexes << [column_name, options]
end
def foreign_key(table_name, options = {}) # :nodoc:
@@ -341,9 +343,7 @@ module ActiveRecord
# <tt>:updated_at</tt> to the table. See {connection.add_timestamps}[rdoc-ref:SchemaStatements#add_timestamps]
#
# t.timestamps null: false
- def timestamps(*args)
- options = args.extract_options!
-
+ def timestamps(**options)
options[:null] = false if options[:null].nil?
column(:created_at, :datetime, options)
@@ -477,7 +477,7 @@ module ActiveRecord
# Checks to see if a column exists.
#
- # t.string(:name) unless t.column_exists?(:name, :string)
+ # t.string(:name) unless t.column_exists?(:name, :string)
#
# See {connection.column_exists?}[rdoc-ref:SchemaStatements#column_exists?]
def column_exists?(column_name, type = nil, options = {})
@@ -498,9 +498,9 @@ module ActiveRecord
# Checks to see if an index exists.
#
- # unless t.index_exists?(:branch_id)
- # t.index(:branch_id)
- # end
+ # unless t.index_exists?(:branch_id)
+ # t.index(:branch_id)
+ # end
#
# See {connection.index_exists?}[rdoc-ref:SchemaStatements#index_exists?]
def index_exists?(column_name, options = {})
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
index 8bb7362c2e..b912d24626 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
@@ -7,10 +7,7 @@ module ActiveRecord
# Adapter level by over-writing this code inside the database specific adapters
module ColumnDumper
def column_spec(column)
- spec = Hash[prepare_column_options(column).map { |k, v| [k, "#{k}: #{v}"] }]
- spec[:name] = column.name.inspect
- spec[:type] = schema_type(column).to_s
- spec
+ [schema_type(column), prepare_column_options(column)]
end
def column_spec_for_primary_key(column)
@@ -38,7 +35,7 @@ module ActiveRecord
end
default = schema_default(column) if column.has_default?
- spec[:default] = default unless default.nil?
+ spec[:default] = default unless default.nil?
spec[:null] = "false" unless column.null
@@ -53,13 +50,13 @@ module ActiveRecord
# Lists the valid migration options
def migration_keys
- [:name, :limit, :precision, :scale, :default, :null, :collation, :comment]
+ [:limit, :precision, :scale, :default, :null, :collation, :comment]
end
private
def default_primary_key?(column)
- schema_type(column) == :integer
+ schema_type(column) == :bigint
end
def schema_type(column)
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 d0aefcef68..e5c0e1690c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -43,7 +43,7 @@ module ActiveRecord
end
# Returns an array of table names defined in the database.
- def tables(name = nil)
+ def tables
raise NotImplementedError, "#tables is not implemented"
end
@@ -120,7 +120,7 @@ module ActiveRecord
checks = []
checks << lambda { |c| c.name == column_name }
checks << lambda { |c| c.type == type } if type
- (migration_keys - [:name]).each do |attr|
+ migration_keys.each do |attr|
checks << lambda { |c| c.send(attr) == options[attr] } if options.key?(attr)
end
@@ -278,16 +278,16 @@ module ActiveRecord
result = execute schema_creation.accept td
unless supports_indexes_in_create?
- td.indexes.each_pair do |column_name, index_options|
+ td.indexes.each do |column_name, index_options|
add_index(table_name, column_name, index_options)
end
end
if supports_comments? && !supports_comments_in_create?
- change_table_comment(table_name, comment) if comment
+ change_table_comment(table_name, comment) if comment.present?
td.columns.each do |column|
- change_column_comment(table_name, column.name, column.comment) if column.comment
+ change_column_comment(table_name, column.name, column.comment) if column.comment.present?
end
end
@@ -339,7 +339,7 @@ module ActiveRecord
column_options.reverse_merge!(null: false)
type = column_options.delete(:type) || :integer
- t1_column, t2_column = [table_1, table_2].map{ |t| t.to_s.singularize.foreign_key }
+ t1_column, t2_column = [table_1, table_2].map { |t| t.to_s.singularize.foreign_key }
create_table(join_table_name, options.merge!(id: false)) do |td|
td.send type, t1_column, column_options
@@ -511,7 +511,7 @@ module ActiveRecord
# Default is (38,0).
# * DB2: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..62].
# Default unknown.
- # * SqlServer?: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
+ # * SqlServer: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
# Default (38,0).
#
# == Examples
@@ -533,6 +533,10 @@ module ActiveRecord
# add_column(:measurements, :huge_integer, :decimal, precision: 30)
# # ALTER TABLE "measurements" ADD "huge_integer" decimal(30)
#
+ # # Defines a column that stores an array of a type.
+ # add_column(:users, :skills, :text, array: true)
+ # # ALTER TABLE "users" ADD "skills" text[]
+ #
# # Defines a column with a database-specific type.
# add_column(:shapes, :triangle, 'polygon')
# # ALTER TABLE "shapes" ADD "triangle" polygon
@@ -962,12 +966,12 @@ module ActiveRecord
def foreign_key_for(from_table, options_or_to_table = {}) # :nodoc:
return unless supports_foreign_keys?
- foreign_keys(from_table).detect {|fk| fk.defined_for? options_or_to_table }
+ foreign_keys(from_table).detect { |fk| fk.defined_for? options_or_to_table }
end
def foreign_key_for!(from_table, options_or_to_table = {}) # :nodoc:
- foreign_key_for(from_table, options_or_to_table) or \
- raise ArgumentError, "Table '#{from_table}' has no foreign key for #{options_or_to_table}"
+ foreign_key_for(from_table, options_or_to_table) || \
+ raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{options_or_to_table}")
end
def foreign_key_column_for(table_name) # :nodoc:
@@ -990,17 +994,15 @@ module ActiveRecord
end
def insert_versions_sql(versions) # :nodoc:
- sm_table = ActiveRecord::Migrator.schema_migrations_table_name
+ sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
- if supports_multi_insert?
+ if versions.is_a?(Array)
sql = "INSERT INTO #{sm_table} (version) VALUES\n"
- sql << versions.map {|v| "('#{v}')" }.join(",\n")
+ sql << versions.map { |v| "(#{quote(v)})" }.join(",\n")
sql << ";\n\n"
sql
else
- versions.map { |version|
- "INSERT INTO #{sm_table} (version) VALUES ('#{version}');"
- }.join "\n\n"
+ "INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});"
end
end
@@ -1024,26 +1026,33 @@ module ActiveRecord
sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
migrated = select_values("SELECT version FROM #{sm_table}").map(&:to_i)
- paths = migrations_paths.map {|p| "#{p}/[0-9]*_*.rb" }
+ paths = migrations_paths.map { |p| "#{p}/[0-9]*_*.rb" }
versions = Dir[*paths].map do |filename|
filename.split("/").last.split("_").first.to_i
end
unless migrated.include?(version)
- execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')"
+ execute "INSERT INTO #{sm_table} (version) VALUES (#{quote(version)})"
end
- inserting = (versions - migrated).select {|v| v < version}
+ inserting = (versions - migrated).select { |v| v < version }
if inserting.any?
- if (duplicate = inserting.detect {|v| inserting.count(v) > 1})
+ if (duplicate = inserting.detect { |v| inserting.count(v) > 1 })
raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict."
end
- execute insert_versions_sql(inserting)
+ if supports_multi_insert?
+ execute insert_versions_sql(inserting)
+ else
+ inserting.each do |v|
+ execute insert_versions_sql(v)
+ end
+ end
end
end
def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
- if native = native_database_types[type.to_sym]
+ type = type.to_sym if type
+ if native = native_database_types[type]
column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup
if type == :decimal # ignore limit, use precision and scale
@@ -1111,7 +1120,7 @@ module ActiveRecord
end
def add_index_options(table_name, column_name, comment: nil, **options) # :nodoc:
- if column_name.is_a?(String) && /\W/ === column_name
+ if column_name.is_a?(String) && /\W/.match?(column_name)
column_names = column_name
else
column_names = Array(column_name)
@@ -1160,41 +1169,41 @@ module ActiveRecord
raise NotImplementedError, "#{self.class} does not support changing column comments"
end
- protected
- def add_index_sort_order(option_strings, column_names, options = {})
- if options.is_a?(Hash) && order = options[:order]
+ private
+
+ def add_index_sort_order(quoted_columns, **options)
+ if order = options[:order]
case order
when Hash
- column_names.each {|name| option_strings[name] += " #{order[name].upcase}" if order.has_key?(name)}
+ order = order.symbolize_keys
+ quoted_columns.each { |name, column| column << " #{order[name].upcase}" if order[name].present? }
when String
- column_names.each {|name| option_strings[name] += " #{order.upcase}"}
+ quoted_columns.each { |name, column| column << " #{order.upcase}" if order.present? }
end
end
- return option_strings
+ quoted_columns
end
# Overridden by the MySQL adapter for supporting index lengths
- def quoted_columns_for_index(column_names, options = {})
- return [column_names] if column_names.is_a?(String)
-
- option_strings = Hash[column_names.map {|name| [name, ""]}]
-
- # add index sort order if supported
+ def add_options_for_index_columns(quoted_columns, **options)
if supports_index_sort_order?
- option_strings = add_index_sort_order(option_strings, column_names, options)
+ quoted_columns = add_index_sort_order(quoted_columns, options)
end
- column_names.map {|name| quote_column_name(name) + option_strings[name]}
+ quoted_columns
+ end
+
+ def quoted_columns_for_index(column_names, **options)
+ return [column_names] if column_names.is_a?(String)
+
+ quoted_columns = Hash[column_names.map { |name| [name.to_sym, quote_column_name(name).dup] }]
+ add_options_for_index_columns(quoted_columns, options).values
end
def index_name_for_remove(table_name, options = {})
return options[:name] if can_remove_index_by_name?(options)
- # if the adapter doesn't support the indexes call the best we can do
- # is return the default index name for the options provided
- return index_name(table_name, options) unless respond_to?(:indexes)
-
checks = []
if options.is_a?(Hash)
@@ -1244,7 +1253,6 @@ module ActiveRecord
end
end
- private
def create_table_definition(*args)
TableDefinition.new(*args)
end
@@ -1253,7 +1261,7 @@ module ActiveRecord
AlterTable.new create_table_definition(name)
end
- def index_name_options(column_names) # :nodoc:
+ def index_name_options(column_names)
if column_names.is_a?(String)
column_names = column_names.scan(/\w+/).join("_")
end
@@ -1261,7 +1269,7 @@ module ActiveRecord
{ column: column_names }
end
- def foreign_key_name(table_name, options) # :nodoc:
+ 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
@@ -1269,7 +1277,7 @@ module ActiveRecord
end
end
- def validate_index_length!(table_name, new_name, internal = false) # :nodoc:
+ def validate_index_length!(table_name, new_name, internal = false)
max_index_length = internal ? index_name_length : allowed_index_name_length
if new_name.length > max_index_length
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 0c7197a002..4046b3829d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -62,17 +62,17 @@ module ActiveRecord
# notably, the instance methods provided by SchemaStatements are very useful.
class AbstractAdapter
ADAPTER_NAME = "Abstract".freeze
+ include ActiveSupport::Callbacks
+ define_callbacks :checkout, :checkin
+
include Quoting, DatabaseStatements, SchemaStatements
include DatabaseLimits
include QueryCache
- include ActiveSupport::Callbacks
include ColumnDumper
include Savepoints
SIMPLE_INT = /\A\d+\z/
- define_callbacks :checkout, :checkin
-
attr_accessor :visitor, :pool
attr_reader :schema_cache, :owner, :logger
alias :in_use? :owner
@@ -106,7 +106,7 @@ module ActiveRecord
@pool = nil
@schema_cache = SchemaCache.new self
@quoted_column_names, @quoted_table_names = {}, {}
- @visitor = arel_visitor
+ @visitor = arel_visitor
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@prepared_statements = true
@@ -161,6 +161,14 @@ module ActiveRecord
SchemaCreation.new self
end
+ # Returns an array of +Column+ objects for the table specified by +table_name+.
+ def columns(table_name) # :nodoc:
+ table_name = table_name.to_s
+ column_definitions(table_name).map do |field|
+ new_column_from_field(table_name, field)
+ end
+ end
+
# this method must only be called while holding connection pool's mutex
def lease
if in_use?
@@ -491,9 +499,9 @@ module ActiveRecord
result
end
- protected
+ private
- def initialize_type_map(m) # :nodoc:
+ def initialize_type_map(m)
register_class_with_limit m, %r(boolean)i, Type::Boolean
register_class_with_limit m, %r(char)i, Type::String
register_class_with_limit m, %r(binary)i, Type::Binary
@@ -524,37 +532,37 @@ module ActiveRecord
end
end
- def reload_type_map # :nodoc:
+ def reload_type_map
type_map.clear
initialize_type_map(type_map)
end
- def register_class_with_limit(mapping, key, klass) # :nodoc:
+ def register_class_with_limit(mapping, key, klass)
mapping.register_type(key) do |*args|
limit = extract_limit(args.last)
klass.new(limit: limit)
end
end
- def register_class_with_precision(mapping, key, klass) # :nodoc:
+ def register_class_with_precision(mapping, key, klass)
mapping.register_type(key) do |*args|
precision = extract_precision(args.last)
klass.new(precision: precision)
end
end
- def extract_scale(sql_type) # :nodoc:
+ def extract_scale(sql_type)
case sql_type
when /\((\d+)\)/ then 0
when /\((\d+)(,(\d+))\)/ then $3.to_i
end
end
- def extract_precision(sql_type) # :nodoc:
+ def extract_precision(sql_type)
$1.to_i if sql_type =~ /\((\d+)(,\d+)?\)/
end
- def extract_limit(sql_type) # :nodoc:
+ def extract_limit(sql_type)
case sql_type
when /^bigint/i
8
@@ -575,7 +583,7 @@ module ActiveRecord
exception
end
- def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil)
+ def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil) # :doc:
@instrumenter.instrument(
"sql.active_record",
sql: sql,
@@ -590,14 +598,19 @@ module ActiveRecord
def translate_exception(exception, message)
# override in derived class
- ActiveRecord::StatementInvalid.new(message)
+ case exception
+ when RuntimeError
+ exception
+ else
+ ActiveRecord::StatementInvalid.new(message)
+ end
end
def without_prepared_statement?(binds)
!prepared_statements || binds.empty?
end
- def column_for(table_name, column_name) # :nodoc:
+ def column_for(table_name, column_name)
column_name = column_name.to_s
columns(table_name).detect { |c| c.name == column_name } ||
raise(ActiveRecordError, "No such column: #{table_name}.#{column_name}")
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 cc820036ca..01cd1e5446 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -9,7 +9,6 @@ require "active_record/connection_adapters/mysql/schema_dumper"
require "active_record/connection_adapters/mysql/type_metadata"
require "active_support/core_ext/string/strip"
-require "active_support/core_ext/regexp"
module ActiveRecord
module ConnectionAdapters
@@ -40,7 +39,7 @@ module ActiveRecord
self.emulate_booleans = true
NATIVE_DATABASE_TYPES = {
- primary_key: "int auto_increment PRIMARY KEY",
+ primary_key: "bigint auto_increment PRIMARY KEY",
string: { name: "varchar", limit: 255 },
text: { name: "text", limit: 65535 },
integer: { name: "int", limit: 4 },
@@ -68,8 +67,8 @@ module ActiveRecord
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
- if version < "5.0.0"
- raise "Your version of MySQL (#{full_version.match(/^\d+\.\d+\.\d+/)[0]}) is too old. Active Record supports MySQL >= 5.0."
+ if version < "5.1.10"
+ raise "Your version of MySQL (#{full_version.match(/^\d+\.\d+\.\d+/)[0]}) is too old. Active Record supports MySQL >= 5.1.10."
end
end
@@ -216,7 +215,11 @@ module ActiveRecord
# Executes the SQL statement in the context of this connection.
def execute(sql, name = nil)
- log(sql, name) { @connection.query(sql) }
+ log(sql, name) do
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
+ @connection.query(sql)
+ end
+ end
end
# Mysql2Adapter doesn't have to free a result after using it, but we use this method
@@ -307,45 +310,36 @@ module ActiveRecord
show_variable "collation_database"
end
- def tables(name = nil) # :nodoc:
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- #tables currently returns both tables and views.
- This behavior is deprecated and will be changed with Rails 5.1 to only return tables.
- Use #data_sources instead.
- MSG
+ def tables # :nodoc:
+ sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE'"
+ sql << " AND table_schema = #{quote(@config[:database])}"
- if name
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing arguments to #tables is deprecated without replacement.
- MSG
- end
+ select_values(sql, "SCHEMA")
+ end
- data_sources
+ def views # :nodoc:
+ select_values("SHOW FULL TABLES WHERE table_type = 'VIEW'", "SCHEMA")
end
- def data_sources
+ def data_sources # :nodoc:
sql = "SELECT table_name FROM information_schema.tables "
sql << "WHERE table_schema = #{quote(@config[:database])}"
select_values(sql, "SCHEMA")
end
- def truncate(table_name, name = nil)
- execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
- end
+ def table_exists?(table_name) # :nodoc:
+ return false unless table_name.present?
- def table_exists?(table_name)
- # Update lib/active_record/internal_metadata.rb when this gets removed
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- #table_exists? currently checks both tables and views.
- This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
- Use #data_source_exists? instead.
- MSG
+ schema, name = extract_schema_qualified_name(table_name)
- data_source_exists?(table_name)
+ sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE'"
+ sql << " AND table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
+
+ select_values(sql, "SCHEMA").any?
end
- def data_source_exists?(table_name)
+ def data_source_exists?(table_name) # :nodoc:
return false unless table_name.present?
schema, name = extract_schema_qualified_name(table_name)
@@ -356,10 +350,6 @@ module ActiveRecord
select_values(sql, "SCHEMA").any?
end
- def views # :nodoc:
- select_values("SHOW FULL TABLES WHERE table_type = 'VIEW'", "SCHEMA")
- end
-
def view_exists?(view_name) # :nodoc:
return false unless view_name.present?
@@ -371,6 +361,10 @@ module ActiveRecord
select_values(sql, "SCHEMA").any?
end
+ def truncate(table_name, name = nil)
+ 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:
indexes = []
@@ -384,29 +378,25 @@ module ActiveRecord
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)
+ 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 << row[:Sub_part]
+ indexes.last.lengths.merge!(row[:Column_name] => row[:Sub_part].to_i) if row[:Sub_part]
end
end
indexes
end
- # Returns an array of +Column+ objects for the table specified by +table_name+.
- def columns(table_name) # :nodoc:
- table_name = table_name.to_s
- column_definitions(table_name).map do |field|
- type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
- if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP"
- default, default_function = nil, field[:Default]
- else
- default, default_function = field[:Default], nil
- end
- new_column(field[:Field], default, type_metadata, field[:Null] == "YES", table_name, default_function, field[:Collation], comment: field[:Comment].presence)
+ def new_column_from_field(table_name, field) # :nodoc:
+ type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
+ if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP"
+ default, default_function = nil, field[:Default]
+ else
+ default, default_function = field[:Default], nil
end
+ new_column(field[:Field], default, type_metadata, field[:Null] == "YES", table_name, default_function, field[:Collation], comment: field[:Comment].presence)
end
def table_comment(table_name) # :nodoc:
@@ -509,7 +499,7 @@ module ActiveRecord
end
def add_sql_comment!(sql, comment) # :nodoc:
- sql << " COMMENT #{quote(comment)}" if comment
+ sql << " COMMENT #{quote(comment)}" if comment.present?
sql
end
@@ -570,22 +560,23 @@ module ActiveRecord
# Maps logical Rails types to MySQL-specific data types.
def type_to_sql(type, limit = nil, precision = nil, scale = nil, unsigned = nil)
- sql = case type.to_s
- when "integer"
- integer_to_sql(limit)
- when "text"
- text_to_sql(limit)
- when "blob"
- binary_to_sql(limit)
- when "binary"
- if (0..0xfff) === limit
- "varbinary(#{limit})"
- else
- binary_to_sql(limit)
- end
- else
- super(type, limit, precision, scale)
- end
+ sql = \
+ case type.to_s
+ when "integer"
+ integer_to_sql(limit)
+ when "text"
+ text_to_sql(limit)
+ when "blob"
+ binary_to_sql(limit)
+ when "binary"
+ if (0..0xfff) === limit
+ "varbinary(#{limit})"
+ else
+ binary_to_sql(limit)
+ end
+ else
+ super(type, limit, precision, scale)
+ end
sql << " unsigned" if unsigned && type != :primary_key
sql
@@ -649,9 +640,9 @@ module ActiveRecord
!native_database_types[type].nil?
end
- protected
+ private
- def initialize_type_map(m) # :nodoc:
+ def initialize_type_map(m)
super
register_class_with_limit m, %r(char)i, MysqlString
@@ -680,20 +671,20 @@ module ActiveRecord
m.register_type(%r(enum)i) do |sql_type|
limit = sql_type[/^enum\((.+)\)/i, 1]
- .split(",").map{|enum| enum.strip.length - 2}.max
+ .split(",").map { |enum| enum.strip.length - 2 }.max
MysqlString.new(limit: limit)
end
m.register_type(%r(^set)i) do |sql_type|
limit = sql_type[/^set\((.+)\)/i, 1]
- .split(",").map{|set| set.strip.length - 1}.sum - 1
+ .split(",").map { |set| set.strip.length - 1 }.sum - 1
MysqlString.new(limit: limit)
end
end
- def register_integer_type(mapping, key, options) # :nodoc:
+ def register_integer_type(mapping, key, options)
mapping.register_type(key) do |sql_type|
- if /\bunsigned\z/ === sql_type
+ if /\bunsigned\b/.match?(sql_type)
Type::UnsignedInteger.new(options)
else
Type::Integer.new(options)
@@ -702,7 +693,7 @@ module ActiveRecord
end
def extract_precision(sql_type)
- if /time/ === sql_type
+ if /time/.match?(sql_type)
super || 0
else
super
@@ -710,39 +701,38 @@ module ActiveRecord
end
def fetch_type_metadata(sql_type, extra = "")
- MySQL::TypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?)
+ MySQL::TypeMetadata.new(super(sql_type), extra: extra)
end
- def add_index_length(option_strings, column_names, options = {})
- if options.is_a?(Hash) && length = options[:length]
+ def add_index_length(quoted_columns, **options)
+ if length = options[:length]
case length
when Hash
- column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
+ length = length.symbolize_keys
+ quoted_columns.each { |name, column| column << "(#{length[name]})" if length[name].present? }
when Integer
- column_names.each {|name| option_strings[name] += "(#{length})"}
+ quoted_columns.each { |name, column| column << "(#{length})" }
end
end
- return option_strings
+ quoted_columns
end
- def quoted_columns_for_index(column_names, options = {})
- option_strings = Hash[column_names.map {|name| [name, ""]}]
-
- # add index length
- option_strings = add_index_length(option_strings, column_names, options)
-
- # add index sort order
- option_strings = add_index_sort_order(option_strings, column_names, options)
-
- column_names.map {|name| quote_column_name(name) + option_strings[name]}
+ def add_options_for_index_columns(quoted_columns, **options)
+ quoted_columns = add_index_length(quoted_columns, options)
+ super
end
- # See https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html
+ # See https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html
ER_DUP_ENTRY = 1062
+ ER_NOT_NULL_VIOLATION = 1048
+ ER_DO_NOT_HAVE_DEFAULT = 1364
ER_NO_REFERENCED_ROW_2 = 1452
ER_DATA_TOO_LONG = 1406
+ ER_OUT_OF_RANGE = 1264
ER_LOCK_DEADLOCK = 1213
+ ER_CANNOT_ADD_FOREIGN = 1215
+ ER_CANNOT_CREATE_TABLE = 1005
def translate_exception(exception, message)
case error_number(exception)
@@ -750,8 +740,20 @@ module ActiveRecord
RecordNotUnique.new(message)
when ER_NO_REFERENCED_ROW_2
InvalidForeignKey.new(message)
+ when ER_CANNOT_ADD_FOREIGN
+ mismatched_foreign_key(message)
+ when ER_CANNOT_CREATE_TABLE
+ if message.include?("errno: 150")
+ mismatched_foreign_key(message)
+ else
+ super
+ end
when ER_DATA_TOO_LONG
ValueTooLong.new(message)
+ when ER_OUT_OF_RANGE
+ RangeError.new(message)
+ when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
+ NotNullViolation.new(message)
when ER_LOCK_DEADLOCK
Deadlocked.new(message)
else
@@ -776,6 +778,10 @@ module ActiveRecord
options[:null] = column.null
end
+ unless options.key?(:comment)
+ options[:comment] = column.comment
+ end
+
td = create_table_definition(table_name)
cd = td.new_column_definition(column.name, type, options)
schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
@@ -800,7 +806,7 @@ module ActiveRecord
end
def remove_columns_sql(table_name, *column_names)
- column_names.map {|column_name| remove_column_sql(table_name, column_name) }
+ column_names.map { |column_name| remove_column_sql(table_name, column_name) }
end
def add_index_sql(table_name, column_name, options = {})
@@ -822,10 +828,8 @@ module ActiveRecord
[remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
end
- private
-
- # 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!
+ # 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]
@@ -893,7 +897,7 @@ module ActiveRecord
end.compact.join(", ")
# ...and send them all in one query
- @connection.query "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}"
+ execute "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}"
end
def column_definitions(table_name) # :nodoc:
@@ -917,6 +921,18 @@ module ActiveRecord
MySQL::TableDefinition.new(*args)
end
+ def mismatched_foreign_key(message)
+ parts = message.scan(/`(\w+)`[ $)]/).flatten
+ MismatchedForeignKey.new(
+ self,
+ message: message,
+ table: parts[0],
+ foreign_key: parts[1],
+ target_table: parts[2],
+ primary_key: parts[3],
+ )
+ end
+
def extract_schema_qualified_name(string) # :nodoc:
schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/)
schema, name = @config[:database], schema unless name
@@ -930,7 +946,7 @@ module ActiveRecord
when 3; "mediumint"
when nil, 4; "int"
when 5..8; "bigint"
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a decimal with scale 0 instead.")
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 1808173592..61cd7ae4cc 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -29,7 +29,7 @@ module ActiveRecord
end
def bigint?
- /\Abigint\b/ === sql_type
+ /\Abigint\b/.match?(sql_type)
end
# Returns the human name of the column name.
@@ -40,6 +40,28 @@ module ActiveRecord
Base.human_attribute_name(@name)
end
+ def init_with(coder)
+ @name = coder["name"]
+ @table_name = coder["table_name"]
+ @sql_type_metadata = coder["sql_type_metadata"]
+ @null = coder["null"]
+ @default = coder["default"]
+ @default_function = coder["default_function"]
+ @collation = coder["collation"]
+ @comment = coder["comment"]
+ end
+
+ def encode_with(coder)
+ coder["name"] = @name
+ coder["table_name"] = @table_name
+ coder["sql_type_metadata"] = @sql_type_metadata
+ coder["null"] = @null
+ coder["default"] = @default
+ coder["default_function"] = @default_function
+ coder["collation"] = @collation
+ coder["comment"] = @comment
+ end
+
def ==(other)
other.is_a?(Column) &&
attributes_for_hash == other.attributes_for_hash
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index be6b55e53c..dcf56997db 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -48,8 +48,8 @@ module ActiveRecord
# Converts the given URL to a full connection hash.
def to_hash
- config = raw_config.reject { |_,value| value.blank? }
- config.map { |key,value| config[key] = uri_parser.unescape(value) if value.is_a? String }
+ config = raw_config.reject { |_, value| value.blank? }
+ config.map { |key, value| config[key] = uri_parser.unescape(value) if value.is_a? String }
config
end
@@ -63,15 +63,15 @@ module ActiveRecord
@uri_parser ||= URI::Parser.new
end
- # Converts the query parameters of the URI into a hash.
- #
- # "localhost?pool=5&reaping_frequency=2"
- # # => { "pool" => "5", "reaping_frequency" => "2" }
- #
- # returns empty hash if no query present.
- #
- # "localhost"
- # # => {}
+ # Converts the query parameters of the URI into a hash.
+ #
+ # "localhost?pool=5&reaping_frequency=2"
+ # # => { "pool" => "5", "reaping_frequency" => "2" }
+ #
+ # returns empty hash if no query present.
+ #
+ # "localhost"
+ # # => {}
def query_hash
Hash[(@query || "").split("&").map { |pair| pair.split("=") }]
end
@@ -92,7 +92,7 @@ module ActiveRecord
end
end
- # Returns name of the database.
+ # Returns name of the database.
def database_from_path
if @adapter == "sqlite3"
# 'sqlite3:/foo' is absolute, because that makes sense. The
@@ -192,26 +192,26 @@ module ActiveRecord
private
- # Returns fully resolved connection, accepts hash, string or symbol.
- # Always returns a hash.
- #
- # == Examples
- #
- # Symbol representing current environment.
- #
- # Resolver.new("production" => {}).resolve_connection(:production)
- # # => {}
- #
- # One layer deep hash of connection values.
- #
- # Resolver.new({}).resolve_connection("adapter" => "sqlite3")
- # # => { "adapter" => "sqlite3" }
- #
- # Connection URL.
- #
- # Resolver.new({}).resolve_connection("postgresql://localhost/foo")
- # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
- #
+ # Returns fully resolved connection, accepts hash, string or symbol.
+ # Always returns a hash.
+ #
+ # == Examples
+ #
+ # Symbol representing current environment.
+ #
+ # Resolver.new("production" => {}).resolve_connection(:production)
+ # # => {}
+ #
+ # One layer deep hash of connection values.
+ #
+ # Resolver.new({}).resolve_connection("adapter" => "sqlite3")
+ # # => { "adapter" => "sqlite3" }
+ #
+ # Connection URL.
+ #
+ # Resolver.new({}).resolve_connection("postgresql://localhost/foo")
+ # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
+ #
def resolve_connection(spec)
case spec
when Symbol
@@ -223,13 +223,13 @@ module ActiveRecord
end
end
- # Takes the environment such as +:production+ or +:development+.
- # This requires that the @configurations was initialized with a key that
- # matches.
- #
- # Resolver.new("production" => {}).resolve_symbol_connection(:production)
- # # => {}
- #
+ # Takes the environment such as +:production+ or +:development+.
+ # This requires that the @configurations was initialized with a key that
+ # matches.
+ #
+ # Resolver.new("production" => {}).resolve_symbol_connection(:production)
+ # # => {}
+ #
def resolve_symbol_connection(spec)
if config = configurations[spec.to_s]
resolve_connection(config).merge("name" => spec.to_s)
@@ -238,10 +238,10 @@ module ActiveRecord
end
end
- # Accepts a hash. Expands the "url" key that contains a
- # URL database connection to a full connection
- # hash and merges with the rest of the hash.
- # Connection details inside of the "url" key win any merge conflicts
+ # Accepts a hash. Expands the "url" key that contains a
+ # URL database connection to a full connection
+ # hash and merges with the rest of the hash.
+ # Connection details inside of the "url" key win any merge conflicts
def resolve_hash_connection(spec)
if spec["url"] && spec["url"] !~ /^jdbc:/
connection_hash = resolve_url_connection(spec.delete("url"))
@@ -250,11 +250,11 @@ module ActiveRecord
spec
end
- # Takes a connection URL.
- #
- # Resolver.new({}).resolve_url_connection("postgresql://localhost/foo")
- # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
- #
+ # Takes a connection URL.
+ #
+ # Resolver.new({}).resolve_url_connection("postgresql://localhost/foo")
+ # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
+ #
def resolve_url_connection(url)
ConnectionUrlResolver.new(url).to_hash
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/column.rb b/activerecord/lib/active_record/connection_adapters/mysql/column.rb
index 5452e44c84..1499c1681f 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/column.rb
@@ -2,41 +2,19 @@ module ActiveRecord
module ConnectionAdapters
module MySQL
class Column < ConnectionAdapters::Column # :nodoc:
- delegate :strict, :extra, to: :sql_type_metadata, allow_nil: true
-
- def initialize(*)
- super
- extract_default
- end
-
- def has_default?
- return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
- super
- end
-
- def blob_or_text_column?
- /\A(?:tiny|medium|long)?blob\b/ === sql_type || type == :text
- end
+ delegate :extra, to: :sql_type_metadata, allow_nil: true
def unsigned?
- /\bunsigned\z/ === sql_type
+ /\bunsigned(?: zerofill)?\z/.match?(sql_type)
end
def case_sensitive?
- collation && collation !~ /_ci\z/
+ collation && !/_ci\z/.match?(collation)
end
def auto_increment?
extra == "auto_increment"
end
-
- private
-
- def extract_default
- if blob_or_text_column?
- @default = null || strict ? nil : ""
- end
- end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
index c8238eb266..78e7181266 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
@@ -24,11 +24,9 @@ module ActiveRecord
# Executes the SQL statement in the context of this connection.
def execute(sql, name = nil)
- if @connection
- # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
- # made since we established the connection
- @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
- end
+ # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
+ # made since we established the connection
+ @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
super
end
@@ -54,14 +52,12 @@ module ActiveRecord
end
alias :exec_update :exec_delete
- protected
+ private
def last_inserted_id(result)
@connection.last_id
end
- private
-
def select_result(sql, name = nil, binds = [])
if without_prepared_statement?(binds)
execute_and_free(sql, name) { |result| yield result }
@@ -71,11 +67,9 @@ module ActiveRecord
end
def exec_stmt_and_free(sql, name, binds, cache_stmt: false)
- if @connection
- # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
- # made since we established the connection
- @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
- end
+ # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
+ # made since we established the connection
+ @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
type_casted_binds = type_casted_binds(binds)
@@ -90,7 +84,9 @@ module ActiveRecord
end
begin
- result = stmt.execute(*type_casted_binds)
+ result = ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
+ stmt.execute(*type_casted_binds)
+ end
rescue Mysql2::Error => e
if cache_stmt
@statements.delete(sql)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb b/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb
index 0b7dea232f..9691060cd3 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb
@@ -39,7 +39,7 @@ module ActiveRecord
def compute_column_widths(result)
[].tap do |widths|
result.columns.each_with_index do |column, i|
- cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? "NULL" : r[i].to_s}
+ cells_in_column = [column] + result.rows.map { |r| r[i].nil? ? "NULL" : r[i].to_s }
widths << cells_in_column.map(&:length).max
end
end
@@ -47,7 +47,7 @@ module ActiveRecord
def build_separator(widths)
padding = 1
- "+" + widths.map {|w| "-" * (w + (padding*2))}.join("+") + "+"
+ "+" + widths.map { |w| "-" * (w + (padding * 2)) }.join("+") + "+"
end
def build_cells(items, widths)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
index ce773ed75b..0cf40de70f 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
@@ -3,7 +3,10 @@ module ActiveRecord
module MySQL
module ColumnMethods
def primary_key(name, type = :primary_key, **options)
- options[:auto_increment] = true if type == :bigint && !options.key?(:default)
+ if type == :primary_key && !options.key?(:default)
+ options[:auto_increment] = true
+ options[:limit] = 8
+ end
super
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 39221eeb0c..2065816501 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
@@ -3,11 +3,9 @@ module ActiveRecord
module MySQL
module ColumnDumper
def column_spec_for_primary_key(column)
- if column.bigint?
- spec = { id: :bigint.inspect }
- spec[:default] = schema_default(column) || "nil" unless column.auto_increment?
- else
- spec = super
+ spec = super
+ if column.type == :integer && !column.auto_increment?
+ spec[:default] = schema_default(column) || "nil"
end
spec[:unsigned] = "true" if column.unsigned?
spec
@@ -38,7 +36,7 @@ module ActiveRecord
end
def schema_precision(column)
- super unless /time/ === column.sql_type && column.precision == 0
+ super unless /time/.match?(column.sql_type) && column.precision == 0
end
def schema_collation(column)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb b/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb
index 1be5cb4740..24dcf852e1 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb
@@ -2,13 +2,12 @@ module ActiveRecord
module ConnectionAdapters
module MySQL
class TypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
- attr_reader :extra, :strict
+ attr_reader :extra
- def initialize(type_metadata, extra: "", strict: false)
+ def initialize(type_metadata, extra: "")
super(type_metadata)
@type_metadata = type_metadata
@extra = extra
- @strict = strict
end
def ==(other)
@@ -24,7 +23,7 @@ module ActiveRecord
protected
def attributes_for_hash
- [self.class, @type_metadata, extra, strict]
+ [self.class, @type_metadata, extra]
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 0130b4ef62..45e400b75b 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -14,12 +14,10 @@ module ActiveRecord
config[:username] = "root" if config[:username].nil?
config[:flags] ||= 0
- if Mysql2::Client.const_defined? :FOUND_ROWS
- if config[:flags].kind_of? Array
- config[:flags].push "FOUND_ROWS".freeze
- else
- config[:flags] |= Mysql2::Client::FOUND_ROWS
- end
+ if config[:flags].kind_of? Array
+ config[:flags].push "FOUND_ROWS".freeze
+ else
+ config[:flags] |= Mysql2::Client::FOUND_ROWS
end
client = Mysql2::Client.new(config)
@@ -90,7 +88,6 @@ module ActiveRecord
#++
def active?
- return false unless @connection
@connection.ping
end
@@ -105,10 +102,7 @@ module ActiveRecord
# Otherwise, this method does nothing.
def disconnect!
super
- unless @connection.nil?
- @connection.close
- @connection = nil
- end
+ @connection.close
end
private
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 87338986b9..9a2017b443 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -85,17 +85,21 @@ module ActiveRecord
# Queries the database and returns the results in an Array-like object
def query(sql, name = nil) #:nodoc:
log(sql, name) do
- result_as_array @connection.async_exec(sql)
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
+ result_as_array @connection.async_exec(sql)
+ end
end
end
- # Executes an SQL statement, returning a PGresult object on success
- # or raising a PGError exception otherwise.
- # Note: the PGresult object is manually memory managed; if you don't
- # need it specifically, you many want consider the exec_query wrapper.
+ # Executes an SQL statement, returning a PG::Result object on success
+ # or raising a PG::Error exception otherwise.
+ # Note: the PG::Result object is manually memory managed; if you don't
+ # need it specifically, you may want consider the <tt>exec_query</tt> wrapper.
def execute(sql, name = nil)
log(sql, name) do
- @connection.async_exec(sql)
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
+ @connection.async_exec(sql)
+ end
end
end
@@ -113,7 +117,7 @@ module ActiveRecord
end
def exec_delete(sql, name = nil, binds = [])
- execute_and_clear(sql, name, binds) {|result| result.cmd_tuples }
+ execute_and_clear(sql, name, binds) { |result| result.cmd_tuples }
end
alias :exec_update :exec_delete
@@ -124,26 +128,29 @@ module ActiveRecord
pk = primary_key(table_ref) if table_ref
end
- pk = suppress_composite_primary_key(pk)
-
- if pk && use_insert_returning?
+ if pk = suppress_composite_primary_key(pk)
sql = "#{sql} RETURNING #{quote_column_name(pk)}"
end
super
end
+ private :sql_for_insert
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
- val = exec_query(sql, name, binds)
- if !use_insert_returning? && pk
+ if use_insert_returning? || pk == false
+ super
+ else
+ result = exec_query(sql, name, binds)
unless sequence_name
table_ref = extract_table_ref_from_insert_sql(sql)
- sequence_name = default_sequence_name(table_ref, pk)
- return val unless sequence_name
+ if table_ref
+ pk = primary_key(table_ref) if pk.nil?
+ pk = suppress_composite_primary_key(pk)
+ sequence_name = default_sequence_name(table_ref, pk)
+ end
+ return result unless sequence_name
end
last_insert_id_result(sequence_name)
- else
- val
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb b/activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb
index f95a63968c..99f3a5bbdf 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb
@@ -28,7 +28,7 @@ module ActiveRecord
pp << header.center(width).rstrip
pp << "-" * width
- pp += lines.map {|line| " #{line}"}
+ pp += lines.map { |line| " #{line}" }
nrows = result.rows.length
rows_label = nrows == 1 ? "row" : "rows"
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index 8c318886cf..0e526f6201 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -12,7 +12,7 @@ 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/point"
-require "active_record/connection_adapters/postgresql/oid/rails_5_1_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"
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
index 1a66afb23a..e1a75f8e5e 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
@@ -5,8 +5,10 @@ module ActiveRecord
class Array < Type::Value # :nodoc:
include Type::Helpers::Mutable
+ Data = Struct.new(:encoder, :values) # :nodoc:
+
attr_reader :subtype, :delimiter
- delegate :type, :user_input_in_time_zone, :limit, to: :subtype
+ delegate :type, :user_input_in_time_zone, :limit, :precision, :scale, to: :subtype
def initialize(subtype, delimiter = ",")
@subtype = subtype
@@ -17,8 +19,11 @@ module ActiveRecord
end
def deserialize(value)
- if value.is_a?(::String)
+ case value
+ when ::String
type_cast_array(@pg_decoder.decode(value), :deserialize)
+ when Data
+ deserialize(value.values)
else
super
end
@@ -33,7 +38,8 @@ module ActiveRecord
def serialize(value)
if value.is_a?(::Array)
- @pg_encoder.encode(type_cast_array(value, :serialize))
+ casted_values = type_cast_array(value, :serialize)
+ Data.new(@pg_encoder, casted_values)
else
super
end
@@ -54,6 +60,10 @@ module ActiveRecord
value.map(&block)
end
+ def changed_in_place?(raw_old_value, new_value)
+ deserialize(raw_old_value) != new_value
+ end
+
private
def type_cast_array(value, method)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb
index a6b5a89ec0..0a505f46a7 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb
@@ -7,7 +7,7 @@ module ActiveRecord
:bit
end
- def cast(value)
+ def cast_value(value)
if ::String === value
case value
when /^0x/i
@@ -16,7 +16,7 @@ module ActiveRecord
value # Bit-string notation
end
else
- value
+ value.to_s
end
end
@@ -34,13 +34,15 @@ module ActiveRecord
end
def binary?
- /\A[01]*\Z/ === value
+ /\A[01]*\Z/.match?(value)
end
def hex?
- /\A[0-9A-F]*\Z/i === value
+ /\A[0-9A-F]*\Z/i.match?(value)
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 :value
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb
index 2d3e6a925d..49dd4fc73f 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb
@@ -12,8 +12,8 @@ module ActiveRecord
def deserialize(value)
if value.is_a?(::String)
::Hash[value.scan(HstorePair).map { |k, v|
- v = v.upcase == "NULL" ? nil : v.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
- k = k.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
+ v = v.upcase == "NULL" ? nil : v.gsub(/\A"(.*)"\Z/m, '\1').gsub(/\\(.)/, '\1')
+ k = k.gsub(/\A"(.*)"\Z/m, '\1').gsub(/\\(.)/, '\1')
[k, v]
}]
else
@@ -24,6 +24,8 @@ module ActiveRecord
def serialize(value)
if value.is_a?(::Hash)
value.map { |k, v| "#{escape_hstore(k)}=>#{escape_hstore(v)}" }.join(", ")
+ elsif value.respond_to?(:to_unsafe_h)
+ serialize(value.to_unsafe_h)
else
value
end
@@ -33,6 +35,14 @@ module ActiveRecord
ActiveRecord::Store::StringKeyedHashAccessor
end
+ # Will compare the Hash equivalents of +raw_old_value+ and +new_value+.
+ # By comparing hashes, this avoids an edge case where the order of
+ # the keys change between the two hashes, and they would not be marked
+ # as equal.
+ def changed_in_place?(raw_old_value, new_value)
+ deserialize(raw_old_value) != new_value
+ end
+
private
HstorePair = begin
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb
index 845ff5b6a9..775eecaf85 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb
@@ -1,10 +1,8 @@
module ActiveRecord
- Point = Struct.new(:x, :y)
-
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
- class Rails51Point < Type::Value # :nodoc:
+ class LegacyPoint < Type::Value # :nodoc:
include Type::Helpers::Mutable
def type
@@ -14,23 +12,20 @@ module ActiveRecord
def cast(value)
case value
when ::String
- return if value.blank?
-
if value[0] == "(" && value[-1] == ")"
value = value[1...-1]
end
- x, y = value.split(",")
- build_point(x, y)
+ cast(value.split(","))
when ::Array
- build_point(*value)
+ value.map { |v| Float(v) }
else
value
end
end
def serialize(value)
- if value.is_a?(ActiveRecord::Point)
- "(#{number_for_point(value.x)},#{number_for_point(value.y)})"
+ if value.is_a?(::Array)
+ "(#{number_for_point(value[0])},#{number_for_point(value[1])})"
else
super
end
@@ -41,10 +36,6 @@ module ActiveRecord
def number_for_point(number)
number.to_s.gsub(/\.0$/, "")
end
-
- def build_point(x, y)
- ActiveRecord::Point.new(Float(x), Float(y))
- end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb
index bb4db2564c..7c764e7287 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb
@@ -1,4 +1,6 @@
module ActiveRecord
+ Point = Struct.new(:x, :y)
+
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
@@ -12,20 +14,34 @@ module ActiveRecord
def cast(value)
case value
when ::String
+ return if value.blank?
+
if value[0] == "(" && value[-1] == ")"
value = value[1...-1]
end
- cast(value.split(","))
+ x, y = value.split(",")
+ build_point(x, y)
when ::Array
- value.map { |v| Float(v) }
+ build_point(*value)
else
value
end
end
def serialize(value)
- if value.is_a?(::Array)
- "(#{number_for_point(value[0])},#{number_for_point(value[1])})"
+ case value
+ when ActiveRecord::Point
+ "(#{number_for_point(value.x)},#{number_for_point(value.y)})"
+ when ::Array
+ serialize(build_point(*value))
+ else
+ super
+ end
+ end
+
+ def type_cast_for_schema(value)
+ if ActiveRecord::Point === value
+ [value.x, value.y]
else
super
end
@@ -36,6 +52,10 @@ module ActiveRecord
def number_for_point(number)
number.to_s.gsub(/\.0$/, "")
end
+
+ def build_point(x, y)
+ ActiveRecord::Point.new(Float(x), Float(y))
+ 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 b5031d890f..3783925954 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -92,6 +92,8 @@ module ActiveRecord
else
super
end
+ when OID::Array::Data
+ _quote(encode_array(value))
else
super
end
@@ -106,10 +108,37 @@ module ActiveRecord
{ value: value.to_s, format: 1 }
when OID::Xml::Data, OID::Bit::Data
value.to_s
+ when OID::Array::Data
+ encode_array(value)
else
super
end
end
+
+ def encode_array(array_data)
+ encoder = array_data.encoder
+ values = type_cast_array(array_data.values)
+
+ result = encoder.encode(values)
+ if encoding = determine_encoding_of_strings_in_array(values)
+ result.force_encoding(encoding)
+ end
+ result
+ end
+
+ def determine_encoding_of_strings_in_array(value)
+ case value
+ when ::Array then determine_encoding_of_strings_in_array(value.first)
+ when ::String then value.encoding
+ end
+ end
+
+ def type_cast_array(values)
+ case values
+ when ::Array then values.map { |item| type_cast_array(item) }
+ else _type_cast(values)
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
index a11dbe7dce..4afb4733eb 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
@@ -11,11 +11,22 @@ module ActiveRecord
# t.timestamps
# end
#
- # By default, this will use the +uuid_generate_v4()+ function from the
- # +uuid-ossp+ extension, which MUST be enabled on your database. To enable
- # the +uuid-ossp+ extension, you can use the +enable_extension+ method in your
- # migrations. To use a UUID primary key without +uuid-ossp+ enabled, you can
- # set the +:default+ option to +nil+:
+ # By default, this will use the +gen_random_uuid()+ function from the
+ # +pgcrypto+ extension. As that extension is only available in
+ # PostgreSQL 9.4+, for earlier versions an explicit default can be set
+ # to use +uuid_generate_v4()+ from the +uuid-ossp+ extension instead:
+ #
+ # create_table :stuffs, id: false do |t|
+ # t.primary_key :id, :uuid, default: "uuid_generate_v4()"
+ # t.uuid :foo_id
+ # t.timestamps
+ # end
+ #
+ # To enable the appropriate extension, which is a requirement, use
+ # the +enable_extension+ method in your migrations.
+ #
+ # To use a UUID primary key without any of the extensions, set the
+ # +:default+ option to +nil+:
#
# create_table :stuffs, id: false do |t|
# t.primary_key :id, :uuid, default: nil
@@ -23,15 +34,24 @@ module ActiveRecord
# t.timestamps
# end
#
- # You may also pass a different UUID generation function from +uuid-ossp+
- # or another library.
+ # You may also pass a custom stored procedure that returns a UUID or use a
+ # different UUID generation function from another library.
#
# Note that setting the UUID primary key default value to +nil+ will
# require you to assure that you always provide a UUID value before saving
# a record (as primary keys cannot be +nil+). This might be done via the
# +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
def primary_key(name, type = :primary_key, **options)
- options[:default] = options.fetch(:default, "uuid_generate_v4()") if type == :uuid
+ if type == :uuid
+ options[:default] = options.fetch(:default, "gen_random_uuid()")
+ elsif options.delete(:auto_increment) == true && %i(integer bigint).include?(type)
+ type = if type == :bigint || options[:limit] == 8
+ :bigserial
+ else
+ :serial
+ end
+ end
+
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
index c20baf655c..7808d37deb 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
@@ -25,7 +25,7 @@ module ActiveRecord
private
def default_primary_key?(column)
- schema_type(column) == :serial
+ schema_type(column) == :bigserial
end
def schema_type(column)
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 531d323a55..85836fc575 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -71,13 +71,7 @@ module ActiveRecord
end
# Returns the list of all tables in the schema search path.
- def tables(name = nil)
- if name
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing arguments to #tables is deprecated without replacement.
- MSG
- end
-
+ def tables
select_values("SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))", "SCHEMA")
end
@@ -86,7 +80,17 @@ module ActiveRecord
SELECT c.relname
FROM pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
- WHERE c.relkind IN ('r', 'v','m') -- (r)elation/table, (v)iew, (m)aterialized view
+ WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
+ AND n.nspname = ANY (current_schemas(false))
+ SQL
+ end
+
+ def views # :nodoc:
+ select_values(<<-SQL, "SCHEMA")
+ SELECT c.relname
+ FROM pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
+ WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view
AND n.nspname = ANY (current_schemas(false))
SQL
end
@@ -95,36 +99,28 @@ module ActiveRecord
# If the schema is not specified as part of +name+ then it will only find tables within
# the current schema search path (regardless of permissions to access tables in other schemas)
def table_exists?(name)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- #table_exists? currently checks both tables and views.
- This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
- Use #data_source_exists? instead.
- MSG
-
- data_source_exists?(name)
- end
-
- def data_source_exists?(name)
name = Utils.extract_schema_qualified_name(name.to_s)
return false unless name.identifier
- select_value(<<-SQL, "SCHEMA").to_i > 0
- SELECT COUNT(*)
- FROM pg_class c
- LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
- WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
- AND c.relname = '#{name.identifier}'
- AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
+ select_values(<<-SQL, "SCHEMA").any?
+ SELECT tablename
+ FROM pg_tables
+ WHERE tablename = #{quote(name.identifier)}
+ AND schemaname = #{name.schema ? quote(name.schema) : "ANY (current_schemas(false))"}
SQL
end
- def views # :nodoc:
- select_values(<<-SQL, "SCHEMA")
+ def data_source_exists?(name) # :nodoc:
+ name = Utils.extract_schema_qualified_name(name.to_s)
+ return false unless name.identifier
+
+ select_values(<<-SQL, "SCHEMA").any?
SELECT c.relname
FROM pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
- WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view
- AND n.nspname = ANY (current_schemas(false))
+ WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
+ AND c.relname = #{quote(name.identifier)}
+ AND n.nspname = #{name.schema ? quote(name.schema) : "ANY (current_schemas(false))"}
SQL
end
@@ -137,8 +133,8 @@ module ActiveRecord
FROM pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view
- AND c.relname = '#{name.identifier}'
- AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
+ AND c.relname = #{quote(name.identifier)}
+ AND n.nspname = #{name.schema ? quote(name.schema) : "ANY (current_schemas(false))"}
SQL
end
@@ -221,25 +217,29 @@ module ActiveRecord
end.compact
end
- # Returns the list of all column definitions for a table.
- def columns(table_name) # :nodoc:
- table_name = table_name.to_s
- column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod, collation, comment|
- oid = oid.to_i
- fmod = fmod.to_i
- type_metadata = fetch_type_metadata(column_name, type, oid, fmod)
- default_value = extract_value_from_default(default)
- default_function = extract_default_function(default_value, default)
- new_column(column_name, default_value, type_metadata, !notnull, table_name, default_function, collation, comment: comment.presence)
- end
- end
-
- def new_column(*args) # :nodoc:
- PostgreSQLColumn.new(*args)
+ def new_column_from_field(table_name, field) # :nondoc:
+ column_name, type, default, notnull, oid, fmod, collation, comment = field
+ oid = oid.to_i
+ fmod = fmod.to_i
+ type_metadata = fetch_type_metadata(column_name, type, oid, fmod)
+ default_value = extract_value_from_default(default)
+ default_function = extract_default_function(default_value, default)
+ PostgreSQLColumn.new(
+ column_name,
+ default_value,
+ type_metadata,
+ !notnull,
+ table_name,
+ default_function,
+ collation,
+ comment: comment.presence
+ )
end
def table_options(table_name) # :nodoc:
- { comment: table_comment(table_name) }
+ if comment = table_comment(table_name)
+ { comment: comment }
+ end
end
# Returns a comment stored in database for given table
@@ -360,7 +360,7 @@ module ActiveRecord
# Resets the sequence of a table's primary key to the maximum value.
def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
- unless pk and sequence
+ unless pk && sequence
default_pk, default_sequence = pk_and_sequence_for(table)
pk ||= default_pk
@@ -403,7 +403,7 @@ module ActiveRecord
AND dep.refobjid = '#{quote_table_name(table)}'::regclass
end_sql
- if result.nil? or result.empty?
+ if result.nil? || result.empty?
result = query(<<-end_sql, "SCHEMA")[0]
SELECT attr.attname, nsp.nspname,
CASE
@@ -439,7 +439,7 @@ module ActiveRecord
WITH pk_constraint AS (
SELECT conrelid, unnest(conkey) AS connum FROM pg_constraint
WHERE contype = 'p'
- AND conrelid = '#{quote_table_name(table_name)}'::regclass
+ AND conrelid = #{quote(quote_table_name(table_name))}::regclass
), cons AS (
SELECT conrelid, connum, row_number() OVER() AS rownum FROM pk_constraint
)
@@ -625,31 +625,32 @@ module ActiveRecord
# Maps logical Rails types to PostgreSQL-specific data types.
def type_to_sql(type, limit = nil, precision = nil, scale = nil, array = nil)
- sql = case type.to_s
- when "binary"
- # PostgreSQL doesn't support limits on binary (bytea) columns.
- # The hard limit is 1GB, because of a 32-bit size field, and TOAST.
- case limit
- when nil, 0..0x3fffffff; super(type)
- else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
- end
- when "text"
- # PostgreSQL doesn't support limits on text columns.
- # The hard limit is 1GB, according to section 8.3 in the manual.
- case limit
- when nil, 0..0x3fffffff; super(type)
- else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
- end
- when "integer"
- case limit
- when 1, 2; "smallint"
- when nil, 3, 4; "integer"
- when 5..8; "bigint"
- else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead.")
- end
- else
- super(type, limit, precision, scale)
- end
+ sql = \
+ case type.to_s
+ when "binary"
+ # PostgreSQL doesn't support limits on binary (bytea) columns.
+ # The hard limit is 1GB, because of a 32-bit size field, and TOAST.
+ case limit
+ when nil, 0..0x3fffffff; super(type)
+ else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
+ end
+ when "text"
+ # PostgreSQL doesn't support limits on text columns.
+ # The hard limit is 1GB, according to section 8.3 in the manual.
+ case limit
+ when nil, 0..0x3fffffff; super(type)
+ else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
+ end
+ when "integer"
+ case limit
+ when 1, 2; "smallint"
+ when nil, 3, 4; "integer"
+ when 5..8; "bigint"
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead.")
+ end
+ else
+ super(type, limit, precision, scale)
+ end
sql << "[]" if array && type != :primary_key
sql
@@ -658,7 +659,7 @@ module ActiveRecord
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
# requires that the ORDER BY include the distinct column.
def columns_for_distinct(columns, orders) #:nodoc:
- order_columns = orders.reject(&:blank?).map{ |s|
+ order_columns = orders.reject(&:blank?).map { |s|
# Convert Arel node to string
s = s.to_sql unless s.is_a?(String)
# Remove any ASC/DESC modifiers
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
index bcef8ac715..311988625f 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
@@ -8,7 +8,7 @@ module ActiveRecord
@type_metadata = type_metadata
@oid = oid
@fmod = fmod
- @array = /\[\]$/ === type_metadata.sql_type
+ @array = /\[\]$/.match?(type_metadata.sql_type)
end
def sql_type
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb b/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb
index 9a0b80d7d3..a3f9ce6d64 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb
@@ -35,6 +35,12 @@ module ActiveRecord
end
protected
+
+ def parts
+ @parts ||= [@schema, @identifier].compact
+ end
+
+ private
def unquote(part)
if part && part.start_with?('"')
part[1..-2]
@@ -42,10 +48,6 @@ module ActiveRecord
part
end
end
-
- def parts
- @parts ||= [@schema, @identifier].compact
- end
end
module Utils # :nodoc:
@@ -53,7 +55,7 @@ module ActiveRecord
# Returns an instance of <tt>ActiveRecord::ConnectionAdapters::PostgreSQL::Name</tt>
# extracted from +string+.
- # +schema+ is nil if not specified in +string+.
+ # +schema+ is +nil+ if not specified in +string+.
# +schema+ and +identifier+ exclude surrounding quotes (regardless of whether provided in +string+)
# +string+ supports the range of schema/table references understood by PostgreSQL, for example:
#
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index bd53123511..0ebd907cc0 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -70,7 +70,7 @@ module ActiveRecord
ADAPTER_NAME = "PostgreSQL".freeze
NATIVE_DATABASE_TYPES = {
- primary_key: "serial primary key",
+ primary_key: "bigserial primary key",
string: { name: "character varying" },
text: { name: "text" },
integer: { name: "integer" },
@@ -315,6 +315,10 @@ module ActiveRecord
postgresql_version >= 90300
end
+ def supports_pgcrypto_uuid?
+ postgresql_version >= 90400
+ 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")
@@ -400,10 +404,12 @@ module ActiveRecord
@connection.server_version
end
- protected
+ private
# See http://www.postgresql.org/docs/current/static/errcodes-appendix.html
VALUE_LIMIT_VIOLATION = "22001"
+ NUMERIC_VALUE_OUT_OF_RANGE = "22003"
+ NOT_NULL_VIOLATION = "23502"
FOREIGN_KEY_VIOLATION = "23503"
UNIQUE_VIOLATION = "23505"
SERIALIZATION_FAILURE = "40001"
@@ -419,6 +425,10 @@ module ActiveRecord
InvalidForeignKey.new(message)
when VALUE_LIMIT_VIOLATION
ValueTooLong.new(message)
+ when NUMERIC_VALUE_OUT_OF_RANGE
+ RangeError.new(message)
+ when NOT_NULL_VIOLATION
+ NotNullViolation.new(message)
when SERIALIZATION_FAILURE
SerializationFailure.new(message)
when DEADLOCK_DETECTED
@@ -428,22 +438,20 @@ module ActiveRecord
end
end
- private
-
- def get_oid_type(oid, fmod, column_name, sql_type = "") # :nodoc:
+ def get_oid_type(oid, fmod, column_name, sql_type = "")
if !type_map.key?(oid)
load_additional_types(type_map, [oid])
end
type_map.fetch(oid, fmod, sql_type) {
warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
- Type::Value.new.tap do |cast_type|
+ Type.default_value.tap do |cast_type|
type_map.register_type(oid, cast_type)
end
}
end
- def initialize_type_map(m) # :nodoc:
+ def initialize_type_map(m)
register_class_with_limit m, "int2", Type::Integer
register_class_with_limit m, "int4", Type::Integer
register_class_with_limit m, "int8", Type::Integer
@@ -511,7 +519,7 @@ module ActiveRecord
load_additional_types(m)
end
- def extract_limit(sql_type) # :nodoc:
+ def extract_limit(sql_type)
case sql_type
when /^bigint/i, /^int8/i
8
@@ -523,11 +531,11 @@ module ActiveRecord
end
# Extracts the value from a PostgreSQL column default definition.
- def extract_value_from_default(default) # :nodoc:
+ def extract_value_from_default(default)
case default
# Quoted types
when /\A[\(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m
- # The default 'now'::date is CURRENT_DATE
+ # The default 'now'::date is CURRENT_DATE
if $1 == "now".freeze && $2 == "date".freeze
nil
else
@@ -542,22 +550,22 @@ module ActiveRecord
# Object identifier types
when /\A-?\d+\z/
$1
- else
- # Anything else is blank, some user type, or some function
- # and we can't know the value of that, so return nil.
+ else
+ # Anything else is blank, some user type, or some function
+ # and we can't know the value of that, so return nil.
nil
end
end
- def extract_default_function(default_value, default) # :nodoc:
+ def extract_default_function(default_value, default)
default if has_default_function?(default_value, default)
end
- def has_default_function?(default_value, default) # :nodoc:
+ def has_default_function?(default_value, default)
!default_value && (%r{\w+\(.*\)|\(.*\)::\w+} === default)
end
- def load_additional_types(type_map, oids = nil) # :nodoc:
+ def load_additional_types(type_map, oids = nil)
initializer = OID::TypeMapInitializer.new(type_map)
if supports_ranges?
@@ -601,7 +609,11 @@ module ActiveRecord
def exec_no_cache(sql, name, binds)
type_casted_binds = type_casted_binds(binds)
- log(sql, name, binds, type_casted_binds) { @connection.async_exec(sql, type_casted_binds) }
+ log(sql, name, binds, type_casted_binds) do
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
+ @connection.async_exec(sql, type_casted_binds)
+ end
+ end
end
def exec_cache(sql, name, binds)
@@ -609,7 +621,9 @@ module ActiveRecord
type_casted_binds = type_casted_binds(binds)
log(sql, name, binds, type_casted_binds, stmt_key) do
- @connection.exec_prepared(stmt_key, type_casted_binds)
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
+ @connection.exec_prepared(stmt_key, type_casted_binds)
+ end
end
rescue ActiveRecord::StatementInvalid => e
raise unless is_cached_plan_failure?(e)
@@ -719,7 +733,7 @@ module ActiveRecord
end
# Returns the current ID of a table's sequence.
- def last_insert_id_result(sequence_name) # :nodoc:
+ def last_insert_id_result(sequence_name)
exec_query("SELECT currval('#{sequence_name}')", "SQL")
end
@@ -741,7 +755,7 @@ module ActiveRecord
# Query implementation notes:
# - format_type includes the column size constraint, e.g. varchar(50)
# - ::regclass is a function that gives the id for a table name
- def column_definitions(table_name) # :nodoc:
+ def column_definitions(table_name)
query(<<-end_sql, "SCHEMA")
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
@@ -750,18 +764,18 @@ module ActiveRecord
col_description(a.attrelid, a.attnum) AS comment
FROM pg_attribute a LEFT JOIN pg_attrdef d
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
- WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
+ WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass
AND a.attnum > 0 AND NOT a.attisdropped
ORDER BY a.attnum
end_sql
end
- def extract_table_ref_from_insert_sql(sql) # :nodoc:
+ def extract_table_ref_from_insert_sql(sql)
sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im]
$1.strip if $1
end
- def create_table_definition(*args) # :nodoc:
+ def create_table_definition(*args)
PostgreSQL::TableDefinition.new(*args)
end
@@ -771,10 +785,14 @@ module ActiveRecord
sql = <<-end_sql
SELECT exists(
SELECT * FROM pg_proc
+ WHERE proname = 'lower'
+ AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
+ ) OR exists(
+ SELECT * FROM pg_proc
INNER JOIN pg_cast
- ON casttarget::text::oidvector = proargtypes
+ ON ARRAY[casttarget]::oidvector = proargtypes
WHERE proname = 'lower'
- AND castsource = '#{column.sql_type}'::regtype::oid
+ AND castsource = #{quote column.sql_type}::regtype
)
end_sql
execute_and_clear(sql, "SCHEMA", []) do |result|
@@ -788,7 +806,6 @@ module ActiveRecord
map[Integer] = PG::TextEncoder::Integer.new
map[TrueClass] = PG::TextEncoder::Boolean.new
map[FalseClass] = PG::TextEncoder::Boolean.new
- map[Float] = PG::TextEncoder::Float.new
@connection.type_map_for_queries = map
end
@@ -838,8 +855,8 @@ module ActiveRecord
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::Rails51Point, adapter: :postgresql)
- ActiveRecord::Type.register(:legacy_point, OID::Point, adapter: :postgresql)
+ ActiveRecord::Type.register(:point, OID::Point, adapter: :postgresql)
+ ActiveRecord::Type.register(:legacy_point, OID::LegacyPoint, adapter: :postgresql)
ActiveRecord::Type.register(:uuid, OID::Uuid, adapter: :postgresql)
ActiveRecord::Type.register(:vector, OID::Vector, adapter: :postgresql)
ActiveRecord::Type.register(:xml, OID::Xml, adapter: :postgresql)
diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
index 8219f132c3..4d339b0a8c 100644
--- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
@@ -21,6 +21,22 @@ module ActiveRecord
@data_sources = @data_sources.dup
end
+ def encode_with(coder)
+ coder["columns"] = @columns
+ coder["columns_hash"] = @columns_hash
+ coder["primary_keys"] = @primary_keys
+ coder["data_sources"] = @data_sources
+ coder["version"] = ActiveRecord::Migrator.current_version
+ end
+
+ def init_with(coder)
+ @columns = coder["columns"]
+ @columns_hash = coder["columns_hash"]
+ @primary_keys = coder["primary_keys"]
+ @data_sources = coder["data_sources"]
+ @version = coder["version"]
+ end
+
def primary_keys(table_name)
@primary_keys[table_name] ||= data_source_exists?(table_name) ? connection.primary_key(table_name) : nil
end
@@ -32,8 +48,6 @@ module ActiveRecord
@data_sources[name] = connection.data_source_exists?(name)
end
- alias table_exists? data_source_exists?
- deprecate table_exists?: "use #data_source_exists? instead"
# Add internal cache for table with +table_name+.
def add(table_name)
@@ -47,8 +61,6 @@ module ActiveRecord
def data_sources(name)
@data_sources[name]
end
- alias tables data_sources
- deprecate tables: "use #data_sources instead"
# Get the columns for a table
def columns(table_name)
@@ -83,8 +95,6 @@ module ActiveRecord
@primary_keys.delete name
@data_sources.delete name
end
- alias clear_table_cache! clear_data_source_cache!
- deprecate clear_table_cache!: "use #clear_data_source_cache! instead"
def marshal_dump
# if we get current version during initialization, it happens stack over flow.
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb
new file mode 100644
index 0000000000..d0b38dff4c
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb
@@ -0,0 +1,23 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module SQLite3
+ module ColumnMethods
+ def primary_key(name, type = :primary_key, **options)
+ if options.delete(:auto_increment) == true && %i(integer bigint).include?(type)
+ type = :primary_key
+ end
+
+ super
+ end
+ end
+
+ class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
+ include ColumnMethods
+ end
+
+ class Table < ActiveRecord::ConnectionAdapters::Table
+ include ColumnMethods
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb
new file mode 100644
index 0000000000..c027fef83c
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb
@@ -0,0 +1,13 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module SQLite3
+ module ColumnDumper
+ private
+
+ def default_primary_key?(column)
+ schema_type(column) == :integer
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 57699badba..297e2997a9 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -3,6 +3,8 @@ 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"
gem "sqlite3", "~> 1.3.6"
require "sqlite3"
@@ -52,6 +54,7 @@ module ActiveRecord
ADAPTER_NAME = "SQLite".freeze
include SQLite3::Quoting
+ include SQLite3::ColumnDumper
NATIVE_DATABASE_TYPES = {
primary_key: "INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL",
@@ -75,6 +78,10 @@ module ActiveRecord
end
end
+ def update_table_definition(table_name, base) # :nodoc:
+ SQLite3::Table.new(table_name, base)
+ end
+
def schema_creation # :nodoc:
SQLite3::SchemaCreation.new self
end
@@ -191,30 +198,32 @@ module ActiveRecord
type_casted_binds = type_casted_binds(binds)
log(sql, name, binds, type_casted_binds) do
- # Don't cache statements if they are not prepared
- unless prepare
- stmt = @connection.prepare(sql)
- begin
- cols = stmt.columns
- unless without_prepared_statement?(binds)
- stmt.bind_params(type_casted_binds)
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
+ # Don't cache statements if they are not prepared
+ unless prepare
+ stmt = @connection.prepare(sql)
+ begin
+ cols = stmt.columns
+ unless without_prepared_statement?(binds)
+ stmt.bind_params(type_casted_binds)
+ end
+ records = stmt.to_a
+ ensure
+ stmt.close
end
+ else
+ cache = @statements[sql] ||= {
+ stmt: @connection.prepare(sql)
+ }
+ stmt = cache[:stmt]
+ cols = cache[:cols] ||= stmt.columns
+ stmt.reset!
+ stmt.bind_params(type_casted_binds)
records = stmt.to_a
- ensure
- stmt.close
end
- else
- cache = @statements[sql] ||= {
- stmt: @connection.prepare(sql)
- }
- stmt = cache[:stmt]
- cols = cache[:cols] ||= stmt.columns
- stmt.reset!
- stmt.bind_params(type_casted_binds)
- records = stmt.to_a
- end
- ActiveRecord::Result.new(cols, records)
+ ActiveRecord::Result.new(cols, records)
+ end
end
end
@@ -229,64 +238,55 @@ module ActiveRecord
end
def execute(sql, name = nil) #:nodoc:
- log(sql, name) { @connection.execute(sql) }
+ log(sql, name) do
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
+ @connection.execute(sql)
+ end
+ end
end
def begin_db_transaction #:nodoc:
- log("begin transaction",nil) { @connection.transaction }
+ log("begin transaction", nil) { @connection.transaction }
end
def commit_db_transaction #:nodoc:
- log("commit transaction",nil) { @connection.commit }
+ log("commit transaction", nil) { @connection.commit }
end
def exec_rollback_db_transaction #:nodoc:
- log("rollback transaction",nil) { @connection.rollback }
+ log("rollback transaction", nil) { @connection.rollback }
end
# SCHEMA STATEMENTS ========================================
- def tables(name = nil) # :nodoc:
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- #tables currently returns both tables and views.
- This behavior is deprecated and will be changed with Rails 5.1 to only return tables.
- Use #data_sources instead.
- MSG
-
- if name
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing arguments to #tables is deprecated without replacement.
- MSG
- end
-
- data_sources
+ def tables # :nodoc:
+ select_values("SELECT name FROM sqlite_master WHERE type = 'table' AND name <> 'sqlite_sequence'", "SCHEMA")
end
- def data_sources
+ def data_sources # :nodoc:
select_values("SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'", "SCHEMA")
end
- def table_exists?(table_name)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- #table_exists? currently checks both tables and views.
- This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
- Use #data_source_exists? instead.
- MSG
-
- data_source_exists?(table_name)
+ def views # :nodoc:
+ select_values("SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'", "SCHEMA")
end
- def data_source_exists?(table_name)
+ def table_exists?(table_name) # :nodoc:
return false unless table_name.present?
- sql = "SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'"
+ sql = "SELECT name FROM sqlite_master WHERE type = 'table' AND name <> 'sqlite_sequence'"
sql << " AND name = #{quote(table_name)}"
select_values(sql, "SCHEMA").any?
end
- def views # :nodoc:
- select_values("SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'", "SCHEMA")
+ def data_source_exists?(table_name) # :nodoc:
+ return false unless table_name.present?
+
+ sql = "SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'"
+ sql << " AND name = #{quote(table_name)}"
+
+ select_values(sql, "SCHEMA").any?
end
def view_exists?(view_name) # :nodoc:
@@ -298,24 +298,20 @@ module ActiveRecord
select_values(sql, "SCHEMA").any?
end
- # Returns an array of +Column+ objects for the table specified by +table_name+.
- def columns(table_name) # :nodoc:
- table_name = table_name.to_s
- table_structure(table_name).map do |field|
- case field["dflt_value"]
- when /^null$/i
- field["dflt_value"] = nil
- when /^'(.*)'$/m
- field["dflt_value"] = $1.gsub("''", "'")
- when /^"(.*)"$/m
- field["dflt_value"] = $1.gsub('""', '"')
- end
-
- collation = field["collation"]
- sql_type = field["type"]
- type_metadata = fetch_type_metadata(sql_type)
- new_column(field["name"], field["dflt_value"], type_metadata, field["notnull"].to_i == 0, table_name, nil, collation)
+ def new_column_from_field(table_name, field) # :nondoc:
+ case field["dflt_value"]
+ when /^null$/i
+ field["dflt_value"] = nil
+ when /^'(.*)'$/m
+ field["dflt_value"] = $1.gsub("''", "'")
+ when /^"(.*)"$/m
+ field["dflt_value"] = $1.gsub('""', '"')
end
+
+ collation = field["collation"]
+ sql_type = field["type"]
+ type_metadata = fetch_type_metadata(sql_type)
+ new_column(field["name"], field["dflt_value"], type_metadata, field["notnull"].to_i == 0, table_name, nil, collation)
end
# Returns an array of indexes for the given table.
@@ -410,7 +406,7 @@ module ActiveRecord
self.default = options[:default] if include_default
self.null = options[:null] if options.include?(:null)
self.precision = options[:precision] if options.include?(:precision)
- self.scale = options[:scale] if options.include?(:scale)
+ self.scale = options[:scale] if options.include?(:scale)
self.collation = options[:collation] if options.include?(:collation)
end
end
@@ -418,21 +414,22 @@ module ActiveRecord
def rename_column(table_name, column_name, new_column_name) #:nodoc:
column = column_for(table_name, column_name)
- alter_table(table_name, rename: {column.name => new_column_name.to_s})
+ alter_table(table_name, rename: { column.name => new_column_name.to_s })
rename_column_indexes(table_name, column.name, new_column_name)
end
- protected
+ private
def table_structure(table_name)
structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
table_structure_with_collation(table_name, structure)
end
+ alias column_definitions table_structure
- def alter_table(table_name, options = {}) #:nodoc:
+ def alter_table(table_name, options = {})
altered_table_name = "a#{table_name}"
- caller = lambda {|definition| yield definition if block_given?}
+ caller = lambda { |definition| yield definition if block_given? }
transaction do
move_table(table_name, altered_table_name,
@@ -441,12 +438,12 @@ module ActiveRecord
end
end
- def move_table(from, to, options = {}, &block) #:nodoc:
+ def move_table(from, to, options = {}, &block)
copy_table(from, to, options, &block)
drop_table(from)
end
- def copy_table(from, to, options = {}) #:nodoc:
+ def copy_table(from, to, options = {})
from_primary_key = primary_key(from)
options[:id] = false
create_table(to, options) do |definition|
@@ -472,7 +469,7 @@ module ActiveRecord
options[:rename] || {})
end
- def copy_table_indexes(from, to, rename = {}) #:nodoc:
+ def copy_table_indexes(from, to, rename = {})
indexes(from).each do |index|
name = index.name
if to == "a#{from}"
@@ -482,7 +479,7 @@ module ActiveRecord
end
to_column_names = columns(to).map(&:name)
- columns = index.columns.map {|c| rename[c] || c }.select do |column|
+ columns = index.columns.map { |c| rename[c] || c }.select do |column|
to_column_names.include?(column)
end
@@ -495,11 +492,11 @@ module ActiveRecord
end
end
- def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
- column_mappings = Hash[columns.map {|name| [name, name]}]
+ def copy_table_contents(from, to, columns, rename = {})
+ column_mappings = Hash[columns.map { |name| [name, name] }]
rename.each { |a| column_mappings[a.last] = a.first }
from_columns = columns(from).collect(&:name)
- columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
+ columns = columns.find_all { |col| from_columns.include?(column_mappings[col]) }
from_columns_to_copy = columns.map { |col| column_mappings[col] }
quoted_columns = columns.map { |col| quote_column_name(col) } * ","
quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ","
@@ -520,20 +517,23 @@ module ActiveRecord
# column *column_name* is not unique
when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/
RecordNotUnique.new(message)
+ when /.* may not be NULL/, /NOT NULL constraint failed: .*/
+ NotNullViolation.new(message)
else
super
end
end
- private
COLLATE_REGEX = /.*\"(\w+)\".*collate\s+\"(\w+)\".*/i.freeze
def table_structure_with_collation(table_name, basic_structure)
collation_hash = {}
- sql = "SELECT sql FROM
- (SELECT * FROM sqlite_master UNION ALL
- SELECT * FROM sqlite_temp_master)
- WHERE type='table' and name='#{ table_name }' \;"
+ sql = <<-SQL
+ SELECT sql FROM
+ (SELECT * FROM sqlite_master UNION ALL
+ SELECT * FROM sqlite_temp_master)
+ WHERE type = 'table' AND name = #{quote(table_name)}
+ SQL
# Result will have following sample string
# CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
@@ -564,6 +564,10 @@ module ActiveRecord
basic_structure.to_hash
end
end
+
+ def create_table_definition(*args)
+ SQLite3::TableDefinition.new(*args)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/statement_pool.rb b/activerecord/lib/active_record/connection_adapters/statement_pool.rb
index 273b1b0b5c..790db56185 100644
--- a/activerecord/lib/active_record/connection_adapters/statement_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/statement_pool.rb
@@ -6,7 +6,7 @@ module ActiveRecord
DEFAULT_STATEMENT_LIMIT = 1000
def initialize(statement_limit = nil)
- @cache = Hash.new { |h,pid| h[pid] = {} }
+ @cache = Hash.new { |h, pid| h[pid] = {} }
@statement_limit = statement_limit || DEFAULT_STATEMENT_LIMIT
end
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index b4cd6cdd38..2ede92feff 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -109,7 +109,7 @@ module ActiveRecord
end
def connection_pool
- connection_handler.retrieve_connection_pool(connection_specification_name) or raise ConnectionNotEstablished
+ connection_handler.retrieve_connection_pool(connection_specification_name) || raise(ConnectionNotEstablished)
end
def retrieve_connection
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index cb11cdefd9..5d0f82130d 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -171,30 +171,26 @@ module ActiveRecord
return super if block_given? ||
primary_key.nil? ||
scope_attributes? ||
- columns_hash.include?(inheritance_column) ||
- ids.first.kind_of?(Array)
-
- id = ids.first
- if ActiveRecord::Base === id
- id = id.id
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- You are passing an instance of ActiveRecord::Base to `find`.
- Please pass the id of the object by calling `.id`.
- MSG
- end
+ columns_hash.include?(inheritance_column)
+
+ id = ids.first
+
+ return super if id.kind_of?(Array) ||
+ id.is_a?(ActiveRecord::Base)
key = primary_key
statement = cached_find_by_statement(key) { |params|
where(key => params.bind).limit(1)
}
+
record = statement.execute([id], self, connection).first
unless record
raise RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}",
name, primary_key, id)
end
record
- rescue RangeError
+ rescue ::RangeError
raise RecordNotFound.new("Couldn't find #{name} with an out of range value for '#{primary_key}'",
name, primary_key)
end
@@ -223,13 +219,13 @@ module ActiveRecord
statement.execute(hash.values, self, connection).first
rescue TypeError
raise ActiveRecord::StatementInvalid
- rescue RangeError
+ rescue ::RangeError
nil
end
end
def find_by!(*args) # :nodoc:
- find_by(*args) or raise RecordNotFound.new("Couldn't find #{name}", name)
+ find_by(*args) || raise(RecordNotFound.new("Couldn't find #{name}", name))
end
def initialize_generated_modules # :nodoc:
@@ -239,7 +235,9 @@ module ActiveRecord
def generated_association_methods
@generated_association_methods ||= begin
mod = const_set(:GeneratedAssociationMethods, Module.new)
+ private_constant :GeneratedAssociationMethods
include mod
+
mod
end
end
@@ -299,14 +297,14 @@ module ActiveRecord
private
- def cached_find_by_statement(key, &block) # :nodoc:
+ def cached_find_by_statement(key, &block)
cache = @find_by_statement_cache[connection.prepared_statements]
cache[key] || cache.synchronize {
cache[key] ||= StatementCache.create(connection, &block)
}
end
- def relation # :nodoc:
+ def relation
relation = Relation.create(self, arel_table, predicate_builder)
if finder_needs_type_condition? && !ignore_default_scope?
@@ -316,7 +314,7 @@ module ActiveRecord
end
end
- def table_metadata # :nodoc:
+ def table_metadata
TableMetadata.new(self, arel_table)
end
end
@@ -330,8 +328,8 @@ module ActiveRecord
# # Instantiates a single new object
# User.new(first_name: 'Jamie')
def initialize(attributes = nil)
- @attributes = self.class._default_attributes.deep_dup
self.class.define_attribute_methods
+ @attributes = self.class._default_attributes.deep_dup
init_internals
initialize_internals_callback
@@ -366,6 +364,8 @@ module ActiveRecord
self.class.define_attribute_methods
+ yield self if block_given?
+
_run_find_callbacks
_run_initialize_callbacks
@@ -450,7 +450,7 @@ module ActiveRecord
# [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
def hash
if id
- [self.class, id].hash
+ self.class.hash ^ self.id.hash
else
super
end
@@ -498,7 +498,7 @@ module ActiveRecord
# We check defined?(@attributes) not to issue warnings if the object is
# allocated but not initialized.
inspection = if defined?(@attributes) && @attributes
- self.class.column_names.collect do |name|
+ self.class.attribute_names.collect do |name|
if has_attribute?(name)
"#{name}: #{attribute_for_inspect(name)}"
end
@@ -536,20 +536,20 @@ module ActiveRecord
# Returns a hash of the given methods with their names as keys and returned values as values.
def slice(*methods)
- Hash[methods.map! { |method| [method, public_send(method)] }].with_indifferent_access
+ Hash[methods.flatten.map! { |method| [method, public_send(method)] }].with_indifferent_access
end
private
- # Under Ruby 1.9, Array#flatten will call #to_ary (recursively) on each of the elements
- # of the array, and then rescues from the possible NoMethodError. If those elements are
- # ActiveRecord::Base's, then this triggers the various method_missing's that we have,
- # which significantly impacts upon performance.
- #
- # So we can avoid the method_missing hit by explicitly defining #to_ary as nil here.
- #
- # See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary.html
- def to_ary # :nodoc:
+ # +Array#flatten+ will call +#to_ary+ (recursively) on each of the elements of
+ # the array, and then rescues from the possible +NoMethodError+. If those elements are
+ # +ActiveRecord::Base+'s, then this triggers the various +method_missing+'s that we have,
+ # which significantly impacts upon performance.
+ #
+ # So we can avoid the +method_missing+ hit by explicitly defining +#to_ary+ as +nil+ here.
+ #
+ # See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary.html
+ def to_ary
nil
end
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index e2da512813..93b9371206 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -12,13 +12,21 @@ module ActiveRecord
#
# * +id+ - The id of the object you wish to reset a counter on.
# * +counters+ - One or more association counters to reset. Association name or counter name can be given.
+ # * <tt>:touch</tt> - Touch timestamp columns when updating.
+ # Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to
+ # touch that column or an array of symbols to touch just those ones.
#
# ==== Examples
#
- # # For Post with id #1 records reset the comments_count
+ # # For the Post with id #1, reset the comments_count
# Post.reset_counters(1, :comments)
- def reset_counters(id, *counters)
+ #
+ # # Like above, but also touch the +updated_at+ and/or +updated_on+
+ # # attributes.
+ # Post.reset_counters(1, :comments, touch: true)
+ def reset_counters(id, *counters, touch: nil)
object = find(id)
+
counters.each do |counter_association|
has_many_association = _reflect_on_association(counter_association)
unless has_many_association
@@ -37,10 +45,12 @@ module ActiveRecord
reflection = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
counter_name = reflection.counter_cache_column
- unscoped.where(primary_key => object.id).update_all(
- counter_name => object.send(counter_association).count(:all)
- )
+ updates = { counter_name.to_sym => object.send(counter_association).count(:all) }
+ updates.merge!(touch_updates(touch)) if touch
+
+ unscoped.where(primary_key => object.id).update_all(updates)
end
+
return true
end
@@ -55,6 +65,9 @@ module ActiveRecord
# * +id+ - The id of the object you wish to update a counter on or an array of ids.
# * +counters+ - A Hash containing the names of the fields
# to update as keys and the amount to update the field by as values.
+ # * <tt>:touch</tt> option - Touch timestamp columns when updating.
+ # Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to
+ # touch that column or an array of symbols to touch just those ones.
#
# ==== Examples
#
@@ -73,13 +86,28 @@ module ActiveRecord
# # UPDATE posts
# # SET comment_count = COALESCE(comment_count, 0) + 1
# # WHERE id IN (10, 15)
+ #
+ # # For the Posts with id of 10 and 15, increment the comment_count by 1
+ # # and update the updated_at value for each counter.
+ # Post.update_counters [10, 15], comment_count: 1, touch: true
+ # # Executes the following SQL:
+ # # UPDATE posts
+ # # SET comment_count = COALESCE(comment_count, 0) + 1,
+ # # `updated_at` = '2016-10-13T09:59:23-05:00'
+ # # WHERE id IN (10, 15)
def update_counters(id, counters)
+ touch = counters.delete(:touch)
+
updates = counters.map do |counter_name, value|
operator = value < 0 ? "-" : "+"
quoted_column = connection.quote_column_name(counter_name)
"#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
end
+ if touch
+ updates << sanitize_sql_for_assignment(touch_updates(touch))
+ end
+
unscoped.where(primary_key => id).update_all updates.join(", ")
end
@@ -94,13 +122,20 @@ module ActiveRecord
#
# * +counter_name+ - The name of the field that should be incremented.
# * +id+ - The id of the object that should be incremented or an array of ids.
+ # * <tt>:touch</tt> - Touch timestamp columns when updating.
+ # Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to
+ # touch that column or an array of symbols to touch just those ones.
#
# ==== Examples
#
# # Increment the posts_count column for the record with an id of 5
# DiscussionBoard.increment_counter(:posts_count, 5)
- def increment_counter(counter_name, id)
- update_counters(id, counter_name => 1)
+ #
+ # # Increment the posts_count column for the record with an id of 5
+ # # and update the updated_at value.
+ # DiscussionBoard.increment_counter(:posts_count, 5, touch: true)
+ def increment_counter(counter_name, id, touch: nil)
+ update_counters(id, counter_name => 1, touch: touch)
end
# Decrement a numeric field by one, via a direct SQL update.
@@ -112,14 +147,28 @@ module ActiveRecord
#
# * +counter_name+ - The name of the field that should be decremented.
# * +id+ - The id of the object that should be decremented or an array of ids.
+ # * <tt>:touch</tt> - Touch timestamp columns when updating.
+ # Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to
+ # touch that column or an array of symbols to touch just those ones.
#
# ==== Examples
#
# # Decrement the posts_count column for the record with an id of 5
# DiscussionBoard.decrement_counter(:posts_count, 5)
- def decrement_counter(counter_name, id)
- update_counters(id, counter_name => -1)
+ #
+ # # Decrement the posts_count column for the record with an id of 5
+ # # and update the updated_at value.
+ # DiscussionBoard.decrement_counter(:posts_count, 5, touch: true)
+ def decrement_counter(counter_name, id, touch: nil)
+ update_counters(id, counter_name => -1, touch: touch)
end
+
+ private
+ def touch_updates(touch)
+ touch = timestamp_attributes_for_update_in_model if touch == true
+ touch_time = current_time_from_proper_timezone
+ Array(touch).map { |column| [ column, touch_time ] }.to_h
+ end
end
private
diff --git a/activerecord/lib/active_record/define_callbacks.rb b/activerecord/lib/active_record/define_callbacks.rb
new file mode 100644
index 0000000000..7d955a24be
--- /dev/null
+++ b/activerecord/lib/active_record/define_callbacks.rb
@@ -0,0 +1,20 @@
+module ActiveRecord
+ # This module exists because `ActiveRecord::AttributeMethods::Dirty` needs to
+ # define callbacks, but continue to have its version of `save` be the super
+ # method of `ActiveRecord::Callbacks`. This will be removed when the removal
+ # of deprecated code removes this need.
+ module DefineCallbacks
+ extend ActiveSupport::Concern
+
+ module ClassMethods # :nodoc:
+ include ActiveModel::Callbacks
+ end
+
+ included do
+ include ActiveModel::Validations::Callbacks
+
+ define_model_callbacks :initialize, :find, :touch, only: :after
+ define_model_callbacks :save, :create, :update, :destroy
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb
index fd2fa7410a..08d42f3dd4 100644
--- a/activerecord/lib/active_record/dynamic_matchers.rb
+++ b/activerecord/lib/active_record/dynamic_matchers.rb
@@ -1,8 +1,7 @@
-require "active_support/core_ext/regexp"
module ActiveRecord
module DynamicMatchers #:nodoc:
- def respond_to?(name, include_private = false)
+ def respond_to_missing?(name, include_private = false)
if self == Base
super
else
@@ -63,10 +62,10 @@ module ActiveRecord
def define
model.class_eval <<-CODE, __FILE__, __LINE__ + 1
- def self.#{name}(#{signature})
- #{body}
- end
- CODE
+ def self.#{name}(#{signature})
+ #{body}
+ end
+ CODE
end
private
@@ -75,14 +74,14 @@ module ActiveRecord
"#{finder}(#{attributes_hash})"
end
- # The parameters in the signature may have reserved Ruby words, in order
- # to prevent errors, we start each param name with `_`.
+ # The parameters in the signature may have reserved Ruby words, in order
+ # to prevent errors, we start each param name with `_`.
def signature
attribute_names.map { |name| "_#{name}" }.join(", ")
end
- # Given that the parameters starts with `_`, the finder needs to use the
- # same parameter name.
+ # Given that the parameters starts with `_`, the finder needs to use the
+ # same parameter name.
def attributes_hash
"{" + attribute_names.map { |name| ":#{name} => _#{name}" }.join(",") + "}"
end
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 0a94ab58dd..0ab03b2ab3 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -140,6 +140,8 @@ module ActiveRecord
end
end
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
+ # Workaround for Ruby 2.2 "private attribute?" warning.
protected
attr_reader :name, :mapping, :subtype
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 8fbe43e3ec..18fac5af1b 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -43,7 +43,7 @@ module ActiveRecord
# Raised when connection to the database could not been established (for example when
# {ActiveRecord::Base.connection=}[rdoc-ref:ConnectionHandling#connection]
- # is given a nil object).
+ # is given a +nil+ object).
class ConnectionNotEstablished < ActiveRecordError
end
@@ -95,19 +95,9 @@ module ActiveRecord
#
# Wraps the underlying database error as +cause+.
class StatementInvalid < ActiveRecordError
- def initialize(message = nil, original_exception = nil)
- if original_exception
- ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \
- "Exceptions will automatically capture the original exception.", caller)
- end
-
+ def initialize(message = nil)
super(message || $!.try(:message))
end
-
- def original_exception
- ActiveSupport::Deprecation.warn("#original_exception is deprecated. Use #cause instead.", caller)
- cause
- end
end
# Defunct wrapper class kept for compatibility.
@@ -123,10 +113,46 @@ module ActiveRecord
class InvalidForeignKey < WrappedDatabaseException
end
+ # Raised when a foreign key constraint cannot be added because the column type does not match the referenced column type.
+ class MismatchedForeignKey < StatementInvalid
+ def initialize(adapter = nil, message: nil, table: nil, foreign_key: nil, target_table: nil, primary_key: nil)
+ @adapter = adapter
+ if table
+ msg = <<-EOM.strip_heredoc
+ Column `#{foreign_key}` on table `#{table}` has a type of `#{column_type(table, foreign_key)}`.
+ This does not match column `#{primary_key}` on `#{target_table}`, which has type `#{column_type(target_table, primary_key)}`.
+ To resolve this issue, change the type of the `#{foreign_key}` column on `#{table}` to be :integer. (For example `t.integer #{foreign_key}`).
+ EOM
+ else
+ msg = <<-EOM
+ There is a mismatch between the foreign key and primary key column types.
+ Verify that the foreign key column type and the primary key of the associated table match types.
+ EOM
+ end
+ if message
+ msg << "\nOriginal message: #{message}"
+ end
+ super(msg)
+ end
+
+ private
+ def column_type(table, column)
+ @adapter.columns(table).detect { |c| c.name == column }.sql_type
+ end
+ end
+
+ # Raised when a record cannot be inserted or updated because it would violate a not null constraint.
+ class NotNullViolation < StatementInvalid
+ end
+
# Raised when a record cannot be inserted or updated because a value too long for a column type.
class ValueTooLong < StatementInvalid
end
+ # Raised when values that executed are out of range.
+ class RangeError < StatementInvalid
+ end
+
# Raised when number of bind variables in statement given to +:condition+ key
# (for example, when using {ActiveRecord::Base.find}[rdoc-ref:FinderMethods#find] method)
# does not match number of expected values supplied.
diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb
index 980b8e1baa..8f7ae2c33c 100644
--- a/activerecord/lib/active_record/explain.rb
+++ b/activerecord/lib/active_record/explain.rb
@@ -1,4 +1,3 @@
-require "active_support/lazy_load_hooks"
require "active_record/explain_registry"
module ActiveRecord
diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb
index 706b57842f..abd8cfc8f2 100644
--- a/activerecord/lib/active_record/explain_subscriber.rb
+++ b/activerecord/lib/active_record/explain_subscriber.rb
@@ -18,10 +18,13 @@ module ActiveRecord
#
# On the other hand, we want to monitor the performance of our real database
# queries, not the performance of the access to the query cache.
- IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN CACHE)
+ IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN)
EXPLAINED_SQLS = /\A\s*(with|select|update|delete|insert)\b/i
def ignore_payload?(payload)
- payload[:exception] || IGNORED_PAYLOADS.include?(payload[:name]) || payload[:sql] !~ EXPLAINED_SQLS
+ payload[:exception] ||
+ payload[:cached] ||
+ IGNORED_PAYLOADS.include?(payload[:name]) ||
+ payload[:sql] !~ EXPLAINED_SQLS
end
ActiveSupport::Notifications.subscribe("sql.active_record", new)
diff --git a/activerecord/lib/active_record/fixture_set/file.rb b/activerecord/lib/active_record/fixture_set/file.rb
index 0888208709..6cf2e01179 100644
--- a/activerecord/lib/active_record/fixture_set/file.rb
+++ b/activerecord/lib/active_record/fixture_set/file.rb
@@ -38,7 +38,7 @@ module ActiveRecord
if row
row.last
else
- {'model_class': nil}
+ { 'model_class': nil }
end
end
end
@@ -66,10 +66,13 @@ module ActiveRecord
# Validate our unmarshalled data.
def validate(data)
unless Hash === data || YAML::Omap === data
- raise Fixture::FormatError, "fixture is not a hash"
+ raise Fixture::FormatError, "fixture is not a hash: #{@file}"
end
- raise Fixture::FormatError unless data.all? { |name, row| Hash === row }
+ invalid = data.reject { |_, row| Hash === row }
+ if invalid.any?
+ raise Fixture::FormatError, "fixture key is not a hash: #{@file}, keys: #{invalid.keys.inspect}"
+ end
data
end
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 40a9aa2783..de1b0d63bc 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -88,7 +88,7 @@ module ActiveRecord
# assert_equal "Ruby on Rails", @rubyonrails.name
# end
#
- # In order to use these methods to access fixtured data within your testcases, you must specify one of the
+ # In order to use these methods to access fixtured data within your test cases, you must specify one of the
# following in your ActiveSupport::TestCase-derived class:
#
# - to fully enable instantiated fixtures (enable alternate methods #1 and #2 above)
@@ -103,7 +103,7 @@ module ActiveRecord
#
# = Dynamic fixtures with ERB
#
- # Some times you don't care about the content of the fixtures as much as you care about the volume.
+ # Sometimes you don't care about the content of the fixtures as much as you care about the volume.
# In these cases, you can mix ERB in with your YAML fixtures to create a bunch of fixtures for load
# testing, like:
#
@@ -415,9 +415,9 @@ module ActiveRecord
# possibly in a folder with the same name.
#++
- MAX_ID = 2 ** 30 - 1
+ MAX_ID = 2**30 - 1
- @@all_cached_fixtures = Hash.new { |h,k| h[k] = {} }
+ @@all_cached_fixtures = Hash.new { |h, k| h[k] = {} }
def self.default_fixture_model_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc:
config.pluralize_table_names ?
@@ -536,16 +536,16 @@ module ActiveRecord
update_all_loaded_fixtures fixtures_map
connection.transaction(requires_new: true) do
- deleted_tables = Set.new
+ deleted_tables = Hash.new { |h, k| h[k] = Set.new }
fixture_sets.each do |fs|
conn = fs.model_class.respond_to?(:connection) ? fs.model_class.connection : connection
table_rows = fs.table_rows
table_rows.each_key do |table|
- unless deleted_tables.include? table
+ unless deleted_tables[conn].include? table
conn.delete "DELETE FROM #{conn.quote_table_name(table)}", "Fixture Delete"
end
- deleted_tables << table
+ deleted_tables[conn] << table
end
table_rows.each do |fixture_set_name, rows|
@@ -597,18 +597,18 @@ module ActiveRecord
@fixtures = read_fixture_files(path)
- @connection = connection
+ @connection = connection
- @table_name = ( model_class.respond_to?(:table_name) ?
+ @table_name = (model_class.respond_to?(:table_name) ?
model_class.table_name :
- self.class.default_fixture_table_name(name, config) )
+ self.class.default_fixture_table_name(name, config))
end
def [](x)
fixtures[x]
end
- def []=(k,v)
+ def []=(k, v)
fixtures[k] = v
end
@@ -629,7 +629,7 @@ module ActiveRecord
fixtures.delete("DEFAULTS")
# track any join tables we need to insert later
- rows = Hash.new { |h,table| h[table] = [] }
+ rows = Hash.new { |h, table| h[table] = [] }
rows[table_name] = fixtures.map do |label, fixture|
row = fixture.to_hash
@@ -862,29 +862,17 @@ module ActiveRecord
class_attribute :fixture_table_names
class_attribute :fixture_class_names
class_attribute :use_transactional_tests
- class_attribute :use_transactional_fixtures
class_attribute :use_instantiated_fixtures # true, false, or :no_instances
class_attribute :pre_loaded_fixtures
class_attribute :config
- singleton_class.deprecate "use_transactional_fixtures=" => "use use_transactional_tests= instead"
-
self.fixture_table_names = []
self.use_instantiated_fixtures = false
self.pre_loaded_fixtures = false
self.config = ActiveRecord::Base
self.fixture_class_names = {}
-
- silence_warnings do
- define_singleton_method :use_transactional_tests do
- if use_transactional_fixtures.nil?
- true
- else
- use_transactional_fixtures
- end
- end
- end
+ self.use_transactional_tests = true
end
module ClassMethods
@@ -902,7 +890,7 @@ module ActiveRecord
def fixtures(*fixture_set_names)
if fixture_set_names.first == :all
- fixture_set_names = Dir["#{fixture_path}/{**,*}/*.{yml}"]
+ fixture_set_names = Dir["#{fixture_path}/{**,*}/*.{yml}"].uniq
fixture_set_names.map! { |f| f[(fixture_path.to_s.size + 1)..-5] }
else
fixture_set_names = fixture_set_names.flatten.map(&:to_s)
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index 4adcd7e65c..fbdaeaae51 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -130,16 +130,26 @@ module ActiveRecord
store_full_sti_class ? name : name.demodulize
end
+ def inherited(subclass)
+ subclass.instance_variable_set(:@_type_candidates_cache, Concurrent::Map.new)
+ super
+ end
+
protected
- # Returns the class type of the record using the current module as a prefix. So descendants of
- # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
+ # Returns the class type of the record using the current module as a prefix. So descendants of
+ # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
def compute_type(type_name)
- if type_name.match(/^::/)
+ if type_name.start_with?("::".freeze)
# If the type is prefixed with a scope operator then we assume that
# the type_name is an absolute reference.
ActiveSupport::Dependencies.constantize(type_name)
else
+ type_candidate = @_type_candidates_cache[type_name]
+ if type_candidate && type_constant = ActiveSupport::Dependencies.safe_constantize(type_candidate)
+ return type_constant
+ end
+
# Build a list of candidates to search for
candidates = []
name.scan(/::|$/) { candidates.unshift "#{$`}::#{type_name}" }
@@ -147,7 +157,10 @@ module ActiveRecord
candidates.each do |candidate|
constant = ActiveSupport::Dependencies.safe_constantize(candidate)
- return constant if candidate == constant.to_s
+ if candidate == constant.to_s
+ @_type_candidates_cache[type_name] = candidate
+ return constant
+ end
end
raise NameError.new("uninitialized constant #{candidates.first}", candidates.first)
@@ -156,9 +169,9 @@ module ActiveRecord
private
- # Called by +instantiate+ to decide which class to use for a new
- # record instance. For single-table inheritance, we check the record
- # for a +type+ column and return the corresponding class.
+ # Called by +instantiate+ to decide which class to use for a new
+ # record instance. For single-table inheritance, we check the record
+ # for a +type+ column and return the corresponding class.
def discriminate_class_for_record(record)
if using_single_table_inheritance?(record)
find_sti_class(record[inheritance_column])
@@ -199,8 +212,8 @@ module ActiveRecord
sti_column.in(sti_names)
end
- # Detect the subclass from the inheritance column of attrs. If the inheritance column value
- # is not self or a valid subclass, raises ActiveRecord::SubclassNotFound
+ # Detect the subclass from the inheritance column of attrs. If the inheritance column value
+ # is not self or a valid subclass, raises ActiveRecord::SubclassNotFound
def subclass_from_attributes(attrs)
attrs = attrs.to_h if attrs.respond_to?(:permitted?)
if attrs.is_a?(Hash)
@@ -225,11 +238,11 @@ module ActiveRecord
ensure_proper_type
end
- # Sets the attribute used for single table inheritance to this class name if this is not the
- # ActiveRecord::Base descendant.
- # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to
- # do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself.
- # No such attribute would be set for objects of the Message class in that example.
+ # Sets the attribute used for single table inheritance to this class name if this is not the
+ # ActiveRecord::Base descendant.
+ # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to
+ # do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself.
+ # No such attribute would be set for objects of the Message class in that example.
def ensure_proper_type
klass = self.class
if klass.finder_needs_type_condition?
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
index e4c7a55541..8e71b60b29 100644
--- a/activerecord/lib/active_record/integration.rb
+++ b/activerecord/lib/active_record/integration.rb
@@ -15,9 +15,9 @@ module ActiveRecord
self.cache_timestamp_format = :usec
end
- # Returns a String, which Action Pack uses for constructing a URL to this
- # object. The default implementation returns this record's id as a String,
- # or nil if this record's unsaved.
+ # Returns a +String+, which Action Pack uses for constructing a URL to this
+ # object. The default implementation returns this record's id as a +String+,
+ # or +nil+ if this record's unsaved.
#
# For example, suppose that you have a User model, and that you have a
# <tt>resources :users</tt> route. Normally, +user_path+ will
@@ -53,18 +53,21 @@ module ActiveRecord
#
# Person.find(5).cache_key(:updated_at, :last_reviewed_at)
def cache_key(*timestamp_names)
- case
- when new_record?
+ if new_record?
"#{model_name.cache_key}/new"
- when timestamp_names.any?
- timestamp = max_updated_column_timestamp(timestamp_names)
- timestamp = timestamp.utc.to_s(cache_timestamp_format)
- "#{model_name.cache_key}/#{id}-#{timestamp}"
- when timestamp = max_updated_column_timestamp
- timestamp = timestamp.utc.to_s(cache_timestamp_format)
- "#{model_name.cache_key}/#{id}-#{timestamp}"
else
- "#{model_name.cache_key}/#{id}"
+ timestamp = if timestamp_names.any?
+ 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
diff --git a/activerecord/lib/active_record/internal_metadata.rb b/activerecord/lib/active_record/internal_metadata.rb
index 20d61dba67..25ee9d6bfe 100644
--- a/activerecord/lib/active_record/internal_metadata.rb
+++ b/activerecord/lib/active_record/internal_metadata.rb
@@ -23,7 +23,7 @@ module ActiveRecord
end
def table_exists?
- ActiveSupport::Deprecation.silence { connection.table_exists?(table_name) }
+ connection.table_exists?(table_name)
end
# Creates an internal metadata table with columns +key+ and +value+
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 1b6cda3861..2659c60f1f 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -47,6 +47,8 @@ module ActiveRecord
# self.locking_column = :lock_person
# end
#
+ # Please note that the optimistic locking will be ignored if you update the
+ # locking column's value.
module Optimistic
extend ActiveSupport::Concern
@@ -60,13 +62,14 @@ module ActiveRecord
end
private
+
def increment_lock
lock_col = self.class.locking_column
previous_lock_value = send(lock_col).to_i
send(lock_col + "=", previous_lock_value + 1)
end
- def _create_record(attribute_names = self.attribute_names, *) # :nodoc:
+ def _create_record(attribute_names = self.attribute_names, *)
if locking_enabled?
# We always want to persist the locking version, even if we don't detect
# a change from the default, since the database might have no default
@@ -75,23 +78,26 @@ module ActiveRecord
super
end
- def _update_record(attribute_names = self.attribute_names) #:nodoc:
+ def _update_record(attribute_names = self.attribute_names)
return super unless locking_enabled?
- return 0 if attribute_names.empty?
lock_col = self.class.locking_column
- previous_lock_value = send(lock_col).to_i
- increment_lock
- attribute_names += [lock_col]
- attribute_names.uniq!
+ return super if attribute_names.include?(lock_col)
+ return 0 if attribute_names.empty?
begin
+ previous_lock_value = read_attribute_before_type_cast(lock_col)
+
+ increment_lock
+
+ attribute_names.push(lock_col)
+
relation = self.class.unscoped
affected_rows = relation.where(
self.class.primary_key => id,
- lock_col => previous_lock_value,
+ lock_col => previous_lock_value
).update_all(
attributes_for_update(attribute_names).map do |name|
[name, _read_attribute(name)]
@@ -104,9 +110,9 @@ module ActiveRecord
affected_rows
- # If something went wrong, revert the version.
+ # If something went wrong, revert the locking_column value.
rescue Exception
- send(lock_col + "=", previous_lock_value)
+ send(lock_col + "=", previous_lock_value.to_i)
raise
end
end
@@ -168,10 +174,10 @@ module ActiveRecord
private
- # We need to apply this decorator here, rather than on module inclusion. The closure
- # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the
- # sub class being decorated. As such, changes to `lock_optimistically`, or
- # `locking_column` would not be picked up.
+ # We need to apply this decorator here, rather than on module inclusion. The closure
+ # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the
+ # sub class being decorated. As such, changes to `lock_optimistically`, or
+ # `locking_column` would not be picked up.
def inherited(subclass)
subclass.class_eval do
is_lock_column = ->(name, _) { lock_optimistically && name == locking_column }
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index 20910fbb15..4b8d8d9105 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -15,36 +15,22 @@ module ActiveRecord
rt
end
- def initialize
- super
- @odd = false
- end
-
- def render_bind(attr, type_casted_value)
- value = if attr.type.binary? && attr.value
- "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
- else
- type_casted_value
- end
-
- [attr.name, value]
- end
-
def sql(event)
- return unless logger.debug?
-
self.class.runtime += event.duration
+ return unless logger.debug?
payload = event.payload
return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
+ name = "CACHE #{name}" if payload[:cached]
sql = payload[:sql]
binds = nil
unless (payload[:binds] || []).empty?
- binds = " " + payload[:binds].zip(payload[:type_casted_binds]).map { |attr, value|
+ casted_params = type_casted_binds(payload[:binds], payload[:type_casted_binds])
+ binds = " " + payload[:binds].zip(casted_params).map { |attr, value|
render_bind(attr, value)
}.inspect
end
@@ -57,6 +43,20 @@ module ActiveRecord
private
+ def type_casted_binds(binds, casted_binds)
+ casted_binds || binds.map { |attr| type_cast attr.value_for_database }
+ end
+
+ def render_bind(attr, type_casted_value)
+ value = if attr.type.binary? && attr.value
+ "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
+ else
+ type_casted_value
+ end
+
+ [attr.name, value]
+ end
+
def colorize_payload_name(name, payload_name)
if payload_name.blank? || payload_name == "SQL" # SQL vs Model Load/Exists
color(name, MAGENTA, true)
@@ -89,6 +89,10 @@ module ActiveRecord
def logger
ActiveRecord::Base.logger
end
+
+ def type_cast(value)
+ ActiveRecord::Base.connection.type_cast(value)
+ end
end
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 9206547acf..ed0c81b639 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -1,6 +1,6 @@
require "set"
+require "zlib"
require "active_support/core_ext/module/attribute_accessors"
-require "active_support/core_ext/regexp"
module ActiveRecord
class MigrationError < ActiveRecordError#:nodoc:
@@ -277,8 +277,10 @@ module ActiveRecord
#
# * <tt>change_column(table_name, column_name, type, options)</tt>: Changes
# the column to a different type using the same parameters as add_column.
- # * <tt>change_column_default(table_name, column_name, default)</tt>: Sets a
- # default value for +column_name+ defined by +default+ on +table_name+.
+ # * <tt>change_column_default(table_name, column_name, default_or_changes)</tt>:
+ # Sets a default value for +column_name+ defined by +default_or_changes+ on
+ # +table_name+. Passing a hash containing <tt>:from</tt> and <tt>:to</tt>
+ # as +default_or_changes+ will make this change reversible in the migration.
# * <tt>change_column_null(table_name, column_name, null, default = nil)</tt>:
# Sets or removes a +NOT NULL+ constraint on +column_name+. The +null+ flag
# indicates whether the value can be +NULL+. See
@@ -520,7 +522,10 @@ module ActiveRecord
def self.inherited(subclass) # :nodoc:
super
if subclass.superclass == Migration
- subclass.include Compatibility::Legacy
+ raise StandardError, "Directly inheriting from ActiveRecord::Migration is not supported. " \
+ "Please specify the Rails release the migration was written for:\n" \
+ "\n" \
+ " class #{self.class.name} < ActiveRecord::Migration[4.2]"
end
end
@@ -725,7 +730,7 @@ module ActiveRecord
# end
def reversible
helper = ReversibleBlockHelper.new(reverting?)
- execute_block{ yield helper }
+ execute_block { yield helper }
end
# Runs the given migration classes.
@@ -767,7 +772,7 @@ module ActiveRecord
when :down then announce "reverting"
end
- time = nil
+ time = nil
ActiveRecord::Base.connection_pool.with_connection do |conn|
time = Benchmark.measure do
exec_migration(conn, direction)
@@ -795,7 +800,7 @@ module ActiveRecord
@connection = nil
end
- def write(text="")
+ def write(text = "")
puts(text) if verbose
end
@@ -805,7 +810,7 @@ module ActiveRecord
write "== %s %s" % [text, "=" * length]
end
- def say(message, subitem=false)
+ def say(message, subitem = false)
write "#{subitem ? " ->" : "--"} #{message}"
end
@@ -989,11 +994,11 @@ module ActiveRecord
end
end
- def rollback(migrations_paths, steps=1)
+ def rollback(migrations_paths, steps = 1)
move(:down, migrations_paths, steps)
end
- def forward(migrations_paths, steps=1)
+ def forward(migrations_paths, steps = 1)
move(:up, migrations_paths, steps)
end
@@ -1024,12 +1029,10 @@ module ActiveRecord
end
def get_all_versions(connection = Base.connection)
- ActiveSupport::Deprecation.silence do
- if connection.table_exists?(schema_migrations_table_name)
- SchemaMigration.all.map { |x| x.version.to_i }.sort
- else
- []
- end
+ if connection.table_exists?(schema_migrations_table_name)
+ SchemaMigration.all.map { |x| x.version.to_i }.sort
+ else
+ []
end
end
@@ -1163,7 +1166,7 @@ module ActiveRecord
private
- # Used for running a specific migration.
+ # Used for running a specific migration.
def run_without_lock
migration = migrations.detect { |m| m.version == @target_version }
raise UnknownMigrationVersionError.new(@target_version) if migration.nil?
@@ -1172,7 +1175,7 @@ module ActiveRecord
record_environment
end
- # Used for running multiple migrations up to or down to a certain value.
+ # Used for running multiple migrations up to or down to a certain value.
def migrate_without_lock
if invalid_target?
raise UnknownMigrationVersionError.new(@target_version)
@@ -1185,7 +1188,7 @@ module ActiveRecord
record_environment
end
- # Stores the current environment in the database.
+ # Stores the current environment in the database.
def record_environment
return if down?
ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment
@@ -1195,7 +1198,7 @@ module ActiveRecord
migrated.include?(migration.version.to_i)
end
- # Return true if a valid version is not provided.
+ # Return true if a valid version is not provided.
def invalid_target?
!target && @target_version && @target_version > 0
end
@@ -1230,10 +1233,10 @@ module ActiveRecord
end
def validate(migrations)
- name ,= migrations.group_by(&:name).find { |_,v| v.length > 1 }
+ name , = migrations.group_by(&:name).find { |_, v| v.length > 1 }
raise DuplicateMigrationNameError.new(name) if name
- version ,= migrations.group_by(&:version).find { |_,v| v.length > 1 }
+ version , = migrations.group_by(&:version).find { |_, v| v.length > 1 }
raise DuplicateMigrationVersionError.new(version) if version
end
@@ -1272,7 +1275,7 @@ module ActiveRecord
@direction == :down
end
- # Wrap the migration in a transaction only if supported by the adapter.
+ # Wrap the migration in a transaction only if supported by the adapter.
def ddl_transaction(migration)
if use_transaction?(migration)
Base.transaction { yield }
diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb
index 6b2c5d8da5..03103bba98 100644
--- a/activerecord/lib/active_record/migration/command_recorder.rb
+++ b/activerecord/lib/active_record/migration/command_recorder.rb
@@ -92,7 +92,7 @@ module ActiveRecord
send(method, args, &block)
end
- def respond_to?(*args) # :nodoc:
+ def respond_to_missing?(*args) # :nodoc:
super || delegate.respond_to?(*args)
end
@@ -125,10 +125,10 @@ module ActiveRecord
}.each do |cmd, inv|
[[inv, cmd], [cmd, inv]].uniq.each do |method, inverse|
class_eval <<-EOV, __FILE__, __LINE__ + 1
- def invert_#{method}(args, &block) # def invert_create_table(args, &block)
- [:#{inverse}, args, block] # [:drop_table, args, block]
- end # end
- EOV
+ def invert_#{method}(args, &block) # def invert_create_table(args, &block)
+ [:#{inverse}, args, block] # [:drop_table, args, block]
+ end # end
+ EOV
end
end
end
@@ -225,7 +225,7 @@ module ActiveRecord
[:add_foreign_key, reversed_args]
end
- # Forwards any missing method call to the \target.
+ # Forwards any missing method call to the \target.
def method_missing(method, *args, &block)
if @delegate.respond_to?(method)
@delegate.send(method, *args, &block)
diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb
index a79251f908..2904634eb7 100644
--- a/activerecord/lib/active_record/migration/compatibility.rb
+++ b/activerecord/lib/active_record/migration/compatibility.rb
@@ -13,7 +13,27 @@ module ActiveRecord
V5_1 = Current
- module FourTwoShared
+ class V5_0 < V5_1
+ def create_table(table_name, options = {})
+ if adapter_name == "PostgreSQL"
+ if options[:id] == :uuid && !options[:default]
+ options[:default] = "uuid_generate_v4()"
+ end
+ end
+
+ # Since 5.1 Postgres adapter uses bigserial type for primary
+ # keys by default and MySQL uses bigint. This compat layer makes old migrations utilize
+ # serial/int type instead -- the way it used to work before 5.1.
+ if options[:id].blank?
+ options[:id] = :integer
+ options[:auto_increment] = true
+ end
+
+ super
+ end
+ end
+
+ class V4_2 < V5_0
module TableDefinition
def references(*, **options)
options[:index] ||= false
@@ -21,7 +41,7 @@ module ActiveRecord
end
alias :belongs_to :references
- def timestamps(*, **options)
+ def timestamps(**options)
options[:null] = true if options[:null].nil?
super
end
@@ -59,7 +79,7 @@ module ActiveRecord
end
alias :add_belongs_to :add_reference
- def add_timestamps(*, **options)
+ def add_timestamps(_, **options)
options[:null] = true if options[:null].nil?
super
end
@@ -101,29 +121,6 @@ module ActiveRecord
index_name
end
end
-
- class V5_0 < V5_1
- end
-
- class V4_2 < V5_0
- # 4.2 is defined as a module because it needs to be shared with
- # Legacy. When the time comes, V5_0 should be defined straight
- # in its class.
- include FourTwoShared
- end
-
- module Legacy
- include FourTwoShared
-
- def migrate(*)
- ActiveSupport::Deprecation.warn \
- "Directly inheriting from ActiveRecord::Migration is deprecated. " \
- "Please specify the Rails release the migration was written for:\n" \
- "\n" \
- " class #{self.class.name} < ActiveRecord::Migration[4.2]"
- super
- end
- end
end
end
end
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 480734669d..2a28c6bf6d 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -2,71 +2,150 @@ module ActiveRecord
module ModelSchema
extend ActiveSupport::Concern
+ ##
+ # :singleton-method: primary_key_prefix_type
+ # :call-seq: primary_key_prefix_type
+ #
+ # The prefix type that will be prepended to every primary key column name.
+ # The options are +:table_name+ and +:table_name_with_underscore+. If the first is specified,
+ # the Product class will look for "productid" instead of "id" as the primary column. If the
+ # latter is specified, the Product class will look for "product_id" instead of "id". Remember
+ # that this is a global setting for all Active Records.
+
+ ##
+ # :singleton-method: primary_key_prefix_type=
+ # :call-seq: primary_key_prefix_type=(prefix_type)
+ #
+ # Sets the prefix type that will be prepended to every primary key column name.
+ # The options are +:table_name+ and +:table_name_with_underscore+. If the first is specified,
+ # the Product class will look for "productid" instead of "id" as the primary column. If the
+ # latter is specified, the Product class will look for "product_id" instead of "id". Remember
+ # that this is a global setting for all Active Records.
+
+ ##
+ # :singleton-method: table_name_prefix
+ # :call-seq: table_name_prefix
+ #
+ # The prefix string to prepend to every table name.
+
+ ##
+ # :singleton-method: table_name_prefix=
+ # :call-seq: table_name_prefix=(prefix)
+ #
+ # Sets the prefix string to prepend to every table name. So if set to "basecamp_", all table
+ # names will be named like "basecamp_projects", "basecamp_people", etc. This is a convenient
+ # way of creating a namespace for tables in a shared database. By default, the prefix is the
+ # empty string.
+ #
+ # If you are organising your models within modules you can add a prefix to the models within
+ # a namespace by defining a singleton method in the parent module called table_name_prefix which
+ # returns your chosen prefix.
+
+ ##
+ # :singleton-method: table_name_suffix
+ # :call-seq: table_name_suffix
+ #
+ # The suffix string to append to every table name.
+
+ ##
+ # :singleton-method: table_name_suffix=
+ # :call-seq: table_name_suffix=(suffix)
+ #
+ # Works like +table_name_prefix=+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
+ # "people_basecamp"). By default, the suffix is the empty string.
+ #
+ # If you are organising your models within modules, you can add a suffix to the models within
+ # a namespace by defining a singleton method in the parent module called table_name_suffix which
+ # returns your chosen suffix.
+
+ ##
+ # :singleton-method: schema_migrations_table_name
+ # :call-seq: schema_migrations_table_name
+ #
+ # The name of the schema migrations table. By default, the value is <tt>"schema_migrations"</tt>.
+
+ ##
+ # :singleton-method: schema_migrations_table_name=
+ # :call-seq: schema_migrations_table_name=(table_name)
+ #
+ # Sets the name of the schema migrations table.
+
+ ##
+ # :singleton-method: internal_metadata_table_name
+ # :call-seq: internal_metadata_table_name
+ #
+ # The name of the internal metadata table. By default, the value is <tt>"ar_internal_metadata"</tt>.
+
+ ##
+ # :singleton-method: internal_metadata_table_name=
+ # :call-seq: internal_metadata_table_name=(table_name)
+ #
+ # Sets the name of the internal metadata table.
+
+ ##
+ # :singleton-method: protected_environments
+ # :call-seq: protected_environments
+ #
+ # The array of names of environments where destructive actions should be prohibited. By default,
+ # the value is <tt>["production"]</tt>.
+
+ ##
+ # :singleton-method: protected_environments=
+ # :call-seq: protected_environments=(environments)
+ #
+ # Sets an array of names of environments where destructive actions should be prohibited.
+
+ ##
+ # :singleton-method: pluralize_table_names
+ # :call-seq: pluralize_table_names
+ #
+ # Indicates whether table names should be the pluralized versions of the corresponding class names.
+ # If true, the default table name for a Product class will be "products". If false, it would just be "product".
+ # See table_name for the full rules on table/class naming. This is true, by default.
+
+ ##
+ # :singleton-method: pluralize_table_names=
+ # :call-seq: pluralize_table_names=(value)
+ #
+ # Set whether table names should be the pluralized versions of the corresponding class names.
+ # If true, the default table name for a Product class will be "products". If false, it would just be "product".
+ # See table_name for the full rules on table/class naming. This is true, by default.
+
+ ##
+ # :singleton-method: ignored_columns
+ # :call-seq: ignored_columns
+ #
+ # The list of columns names the model should ignore. Ignored columns won't have attribute
+ # accessors defined, and won't be referenced in SQL queries.
+
+ ##
+ # :singleton-method: ignored_columns=
+ # :call-seq: ignored_columns=(columns)
+ #
+ # Sets the columns names the model should ignore. Ignored columns won't have attribute
+ # accessors defined, and won't be referenced in SQL queries.
+
included do
- ##
- # :singleton-method:
- # Accessor for the prefix type that will be prepended to every primary key column name.
- # The options are :table_name and :table_name_with_underscore. If the first is specified,
- # the Product class will look for "productid" instead of "id" as the primary column. If the
- # latter is specified, the Product class will look for "product_id" instead of "id". Remember
- # that this is a global setting for all Active Records.
mattr_accessor :primary_key_prefix_type, instance_writer: false
- ##
- # :singleton-method:
- # Accessor for the name of the prefix string to prepend to every table name. So if set
- # to "basecamp_", all table names will be named like "basecamp_projects", "basecamp_people",
- # etc. This is a convenient way of creating a namespace for tables in a shared database.
- # By default, the prefix is the empty string.
- #
- # If you are organising your models within modules you can add a prefix to the models within
- # a namespace by defining a singleton method in the parent module called table_name_prefix which
- # returns your chosen prefix.
class_attribute :table_name_prefix, instance_writer: false
self.table_name_prefix = ""
- ##
- # :singleton-method:
- # Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
- # "people_basecamp"). By default, the suffix is the empty string.
- #
- # If you are organising your models within modules, you can add a suffix to the models within
- # a namespace by defining a singleton method in the parent module called table_name_suffix which
- # returns your chosen suffix.
class_attribute :table_name_suffix, instance_writer: false
self.table_name_suffix = ""
- ##
- # :singleton-method:
- # Accessor for the name of the schema migrations table. By default, the value is "schema_migrations"
class_attribute :schema_migrations_table_name, instance_accessor: false
self.schema_migrations_table_name = "schema_migrations"
- ##
- # :singleton-method:
- # Accessor for the name of the internal metadata table. By default, the value is "ar_internal_metadata"
class_attribute :internal_metadata_table_name, instance_accessor: false
self.internal_metadata_table_name = "ar_internal_metadata"
- ##
- # :singleton-method:
- # Accessor for an array of names of environments where destructive actions should be prohibited. By default,
- # the value is ["production"]
class_attribute :protected_environments, instance_accessor: false
self.protected_environments = ["production"]
- ##
- # :singleton-method:
- # Indicates whether table names should be the pluralized versions of the corresponding class names.
- # If true, the default table name for a Product class will be +products+. If false, it would just be +product+.
- # See table_name for the full rules on table/class naming. This is true, by default.
class_attribute :pluralize_table_names, instance_writer: false
self.pluralize_table_names = true
- ##
- # :singleton-method:
- # Accessor for the list of columns names the model should ignore. Ignored columns won't have attribute
- # accessors defined, and won't be referenced in SQL queries.
class_attribute :ignored_columns, instance_accessor: false
self.ignored_columns = [].freeze
@@ -173,11 +252,11 @@ module ActiveRecord
end
def full_table_name_prefix #:nodoc:
- (parents.detect{ |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
+ (parents.detect { |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
end
def full_table_name_suffix #:nodoc:
- (parents.detect {|p| p.respond_to?(:table_name_suffix) } || self).table_name_suffix
+ (parents.detect { |p| p.respond_to?(:table_name_suffix) } || self).table_name_suffix
end
# Defines the name of the table column which will store the class name on single-table
@@ -213,7 +292,7 @@ module ActiveRecord
end
# Sets the name of the sequence to use when generating ids to the given
- # value, or (if the value is nil or false) to the value returned by the
+ # value, or (if the value is +nil+ or +false+) to the value returned by the
# given block. This is required for Oracle and is useful for any
# database which relies on sequences for primary key generation.
#
@@ -268,7 +347,7 @@ module ActiveRecord
def attribute_types # :nodoc:
load_schema
- @attribute_types ||= Hash.new(Type::Value.new)
+ @attribute_types ||= Hash.new(Type.default_value)
end
def yaml_encoder # :nodoc:
@@ -370,7 +449,6 @@ module ActiveRecord
def load_schema!
@columns_hash = connection.schema_cache.columns_hash(table_name).except(*ignored_columns)
@columns_hash.each do |name, column|
- warn_if_deprecated_type(column)
define_attribute(
name,
connection.lookup_cast_type_from_column(column),
@@ -398,13 +476,13 @@ module ActiveRecord
end
end
- # Guesses the table name, but does not decorate it with prefix and suffix information.
+ # Guesses the table name, but does not decorate it with prefix and suffix information.
def undecorated_table_name(class_name = base_class.name)
table_name = class_name.to_s.demodulize.underscore
pluralize_table_names ? table_name.pluralize : table_name
end
- # Computes and returns a table name according to default conventions.
+ # Computes and returns a table name according to default conventions.
def compute_table_name
base = base_class
if self == base
@@ -421,28 +499,6 @@ module ActiveRecord
base.table_name
end
end
-
- def warn_if_deprecated_type(column)
- return if attributes_to_define_after_schema_loads.key?(column.name)
- if column.respond_to?(:oid) && column.sql_type.start_with?("point")
- if column.array?
- array_arguments = ", array: true"
- else
- array_arguments = ""
- end
- ActiveSupport::Deprecation.warn(<<-WARNING.strip_heredoc)
- The behavior of the `:point` type will be changing in Rails 5.1 to
- return a `Point` object, rather than an `Array`. If you'd like to
- keep the old behavior, you can add this line to #{name}:
-
- attribute :#{column.name}, :legacy_point#{array_arguments}
-
- If you'd like the new behavior today, you can add this line:
-
- attribute :#{column.name}, :point#{array_arguments}
- WARNING
- end
- end
end
end
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 4873db7270..e983026961 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -341,26 +341,26 @@ module ActiveRecord
private
- # Generates a writer method for this association. Serves as a point for
- # accessing the objects in the association. For example, this method
- # could generate the following:
- #
- # def pirate_attributes=(attributes)
- # assign_nested_attributes_for_one_to_one_association(:pirate, attributes)
- # end
- #
- # This redirects the attempts to write objects in an association through
- # the helper methods defined below. Makes it seem like the nested
- # associations are just regular associations.
+ # Generates a writer method for this association. Serves as a point for
+ # accessing the objects in the association. For example, this method
+ # could generate the following:
+ #
+ # def pirate_attributes=(attributes)
+ # assign_nested_attributes_for_one_to_one_association(:pirate, attributes)
+ # end
+ #
+ # This redirects the attempts to write objects in an association through
+ # the helper methods defined below. Makes it seem like the nested
+ # associations are just regular associations.
def generate_association_writer(association_name, type)
generated_association_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1
- if method_defined?(:#{association_name}_attributes=)
- remove_method(:#{association_name}_attributes=)
- end
- def #{association_name}_attributes=(attributes)
- assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
- end
- eoruby
+ if method_defined?(:#{association_name}_attributes=)
+ remove_method(:#{association_name}_attributes=)
+ end
+ def #{association_name}_attributes=(attributes)
+ assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
+ end
+ eoruby
end
end
@@ -375,23 +375,23 @@ module ActiveRecord
private
- # Attribute hash keys that should not be assigned as normal attributes.
- # These hash keys are nested attributes implementation details.
+ # Attribute hash keys that should not be assigned as normal attributes.
+ # These hash keys are nested attributes implementation details.
UNASSIGNABLE_KEYS = %w( id _destroy )
- # Assigns the given attributes to the association.
- #
- # If an associated record does not yet exist, one will be instantiated. If
- # an associated record already exists, the method's behavior depends on
- # the value of the update_only option. If update_only is +false+ and the
- # given attributes include an <tt>:id</tt> that matches the existing record's
- # id, then the existing record will be modified. If no <tt>:id</tt> is provided
- # it will be replaced with a new record. If update_only is +true+ the existing
- # record will be modified regardless of whether an <tt>:id</tt> is provided.
- #
- # If the given attributes include a matching <tt>:id</tt> attribute, or
- # update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
- # then the existing record will be marked for destruction.
+ # Assigns the given attributes to the association.
+ #
+ # If an associated record does not yet exist, one will be instantiated. If
+ # an associated record already exists, the method's behavior depends on
+ # the value of the update_only option. If update_only is +false+ and the
+ # given attributes include an <tt>:id</tt> that matches the existing record's
+ # id, then the existing record will be modified. If no <tt>:id</tt> is provided
+ # it will be replaced with a new record. If update_only is +true+ the existing
+ # record will be modified regardless of whether an <tt>:id</tt> is provided.
+ #
+ # If the given attributes include a matching <tt>:id</tt> attribute, or
+ # update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
+ # then the existing record will be marked for destruction.
def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
options = self.nested_attributes_options[association_name]
if attributes.respond_to?(:permitted?)
@@ -424,33 +424,33 @@ module ActiveRecord
end
end
- # Assigns the given attributes to the collection association.
- #
- # Hashes with an <tt>:id</tt> value matching an existing associated record
- # will update that record. Hashes without an <tt>:id</tt> value will build
- # a new record for the association. Hashes with a matching <tt>:id</tt>
- # value and a <tt>:_destroy</tt> key set to a truthy value will mark the
- # matched record for destruction.
- #
- # For example:
- #
- # assign_nested_attributes_for_collection_association(:people, {
- # '1' => { id: '1', name: 'Peter' },
- # '2' => { name: 'John' },
- # '3' => { id: '2', _destroy: true }
- # })
- #
- # Will update the name of the Person with ID 1, build a new associated
- # person with the name 'John', and mark the associated Person with ID 2
- # for destruction.
- #
- # Also accepts an Array of attribute hashes:
- #
- # assign_nested_attributes_for_collection_association(:people, [
- # { id: '1', name: 'Peter' },
- # { name: 'John' },
- # { id: '2', _destroy: true }
- # ])
+ # Assigns the given attributes to the collection association.
+ #
+ # Hashes with an <tt>:id</tt> value matching an existing associated record
+ # will update that record. Hashes without an <tt>:id</tt> value will build
+ # a new record for the association. Hashes with a matching <tt>:id</tt>
+ # value and a <tt>:_destroy</tt> key set to a truthy value will mark the
+ # matched record for destruction.
+ #
+ # For example:
+ #
+ # assign_nested_attributes_for_collection_association(:people, {
+ # '1' => { id: '1', name: 'Peter' },
+ # '2' => { name: 'John' },
+ # '3' => { id: '2', _destroy: true }
+ # })
+ #
+ # Will update the name of the Person with ID 1, build a new associated
+ # person with the name 'John', and mark the associated Person with ID 2
+ # for destruction.
+ #
+ # Also accepts an Array of attribute hashes:
+ #
+ # assign_nested_attributes_for_collection_association(:people, [
+ # { id: '1', name: 'Peter' },
+ # { name: 'John' },
+ # { id: '2', _destroy: true }
+ # ])
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
options = self.nested_attributes_options[association_name]
if attributes_collection.respond_to?(:permitted?)
@@ -477,7 +477,7 @@ module ActiveRecord
existing_records = if association.loaded?
association.target
else
- attribute_ids = attributes_collection.map {|a| a["id"] || a[:id] }.compact
+ attribute_ids = attributes_collection.map { |a| a["id"] || a[:id] }.compact
attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids)
end
@@ -511,22 +511,23 @@ module ActiveRecord
end
end
- # Takes in a limit and checks if the attributes_collection has too many
- # records. It accepts limit in the form of symbol, proc, or
- # number-like object (anything that can be compared with an integer).
- #
- # Raises TooManyRecords error if the attributes_collection is
- # larger than the limit.
+ # Takes in a limit and checks if the attributes_collection has too many
+ # records. It accepts limit in the form of symbol, proc, or
+ # number-like object (anything that can be compared with an integer).
+ #
+ # Raises TooManyRecords error if the attributes_collection is
+ # larger than the limit.
def check_record_limit!(limit, attributes_collection)
if limit
- limit = case limit
- when Symbol
- send(limit)
- when Proc
- limit.call
- else
- limit
- end
+ limit = \
+ case limit
+ when Symbol
+ send(limit)
+ when Proc
+ limit.call
+ else
+ limit
+ end
if limit && attributes_collection.size > limit
raise TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead."
@@ -534,30 +535,30 @@ module ActiveRecord
end
end
- # Updates a record with the +attributes+ or marks it for destruction if
- # +allow_destroy+ is +true+ and has_destroy_flag? returns +true+.
+ # Updates a record with the +attributes+ or marks it for destruction if
+ # +allow_destroy+ is +true+ and has_destroy_flag? returns +true+.
def assign_to_or_mark_for_destruction(record, attributes, allow_destroy)
record.assign_attributes(attributes.except(*UNASSIGNABLE_KEYS))
record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy
end
- # Determines if a hash contains a truthy _destroy key.
+ # Determines if a hash contains a truthy _destroy key.
def has_destroy_flag?(hash)
Type::Boolean.new.cast(hash["_destroy"])
end
- # Determines if a new record should be rejected by checking
- # has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
- # association and evaluates to +true+.
+ # Determines if a new record should be rejected by checking
+ # has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
+ # association and evaluates to +true+.
def reject_new_record?(association_name, attributes)
will_be_destroyed?(association_name, attributes) || call_reject_if(association_name, attributes)
end
- # Determines if a record with the particular +attributes+ should be
- # rejected by calling the reject_if Symbol or Proc (if defined).
- # The reject_if option is defined by +accepts_nested_attributes_for+.
- #
- # Returns false if there is a +destroy_flag+ on the attributes.
+ # Determines if a record with the particular +attributes+ should be
+ # rejected by calling the reject_if Symbol or Proc (if defined).
+ # The reject_if option is defined by +accepts_nested_attributes_for+.
+ #
+ # Returns false if there is a +destroy_flag+ on the attributes.
def call_reject_if(association_name, attributes)
return false if will_be_destroyed?(association_name, attributes)
@@ -569,7 +570,7 @@ module ActiveRecord
end
end
- # Only take into account the destroy flag if <tt>:allow_destroy</tt> is true
+ # Only take into account the destroy flag if <tt>:allow_destroy</tt> is true
def will_be_destroyed?(association_name, attributes)
allow_destroy?(association_name) && has_destroy_flag?(attributes)
end
diff --git a/activerecord/lib/active_record/no_touching.rb b/activerecord/lib/active_record/no_touching.rb
index edb5066fa0..4059020e25 100644
--- a/activerecord/lib/active_record/no_touching.rb
+++ b/activerecord/lib/active_record/no_touching.rb
@@ -45,6 +45,10 @@ module ActiveRecord
NoTouching.applied_to?(self.class)
end
+ def touch_later(*) # :nodoc:
+ super unless no_touching?
+ end
+
def touch(*) # :nodoc:
super unless no_touching?
end
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
index 254550c378..2bb7ed6d5e 100644
--- a/activerecord/lib/active_record/null_relation.rb
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -41,12 +41,11 @@ module ActiveRecord
end
def calculate(operation, _column_name)
- if [:count, :sum].include? operation
+ case operation
+ when :count, :sum
group_values.any? ? Hash.new : 0
- elsif [:average, :minimum, :maximum].include?(operation) && group_values.any?
- Hash.new
- else
- nil
+ when :average, :minimum, :maximum
+ group_values.any? ? Hash.new : nil
end
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index dc4af4f390..63d8a201f0 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -63,10 +63,10 @@ module ActiveRecord
#
# See <tt>ActiveRecord::Inheritance#discriminate_class_for_record</tt> to see
# how this "single-table" inheritance mapping is implemented.
- def instantiate(attributes, column_types = {})
+ def instantiate(attributes, column_types = {}, &block)
klass = discriminate_class_for_record(attributes)
attributes = klass.attributes_builder.build_from_database(attributes, column_types)
- klass.allocate.init_with("attributes" => attributes, "new_record" => false)
+ klass.allocate.init_with("attributes" => attributes, "new_record" => false, &block)
end
private
@@ -107,7 +107,7 @@ module ActiveRecord
#
# By default, save always runs validations. If any of them fail the action
# is cancelled and #save returns +false+, and the record won't be saved. However, if you supply
- # validate: false, validations are bypassed altogether. See
+ # <tt>validate: false</tt>, validations are bypassed altogether. See
# ActiveRecord::Validations for more information.
#
# By default, #save also sets the +updated_at+/+updated_on+ attributes to
@@ -134,7 +134,7 @@ module ActiveRecord
#
# By default, #save! always runs validations. If any of them fail
# ActiveRecord::RecordInvalid gets raised, and the record won't be saved. However, if you supply
- # validate: false, validations are bypassed altogether. See
+ # <tt>validate: false</tt>, validations are bypassed altogether. See
# ActiveRecord::Validations for more information.
#
# By default, #save! also sets the +updated_at+/+updated_on+ attributes to
@@ -178,10 +178,14 @@ module ActiveRecord
# and #destroy returns +false+.
# See ActiveRecord::Callbacks for further details.
def destroy
- raise ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly?
+ _raise_readonly_record_error if readonly?
destroy_associations
self.class.connection.add_transaction_record(self)
- destroy_row if persisted?
+ @_trigger_destroy_callback = if persisted?
+ destroy_row > 0
+ else
+ true
+ end
@destroyed = true
freeze
end
@@ -252,7 +256,12 @@ module ActiveRecord
name = name.to_s
verify_readonly_attribute(name)
public_send("#{name}=", value)
- save(validate: false) if changed?
+
+ if has_changes_to_save?
+ save(validate: false)
+ else
+ true
+ end
end
# Updates the attributes of the model from the passed-in hash and saves the
@@ -335,7 +344,7 @@ module ActiveRecord
# record could be saved.
def increment!(attribute, by = 1)
increment(attribute, by)
- change = public_send(attribute) - (attribute_was(attribute.to_s) || 0)
+ change = public_send(attribute) - (attribute_in_database(attribute.to_s) || 0)
self.class.update_counters(id, attribute => change)
clear_attribute_change(attribute) # eww
self
@@ -383,8 +392,8 @@ module ActiveRecord
# Reloads the record from the database.
#
- # This method finds record by its primary key (which could be assigned manually) and
- # modifies the receiver in-place:
+ # This method finds the record by its primary key (which could be assigned
+ # manually) and modifies the receiver in-place:
#
# account = Account.new
# # => #<Account id: nil, email: nil>
@@ -498,7 +507,6 @@ module ActiveRecord
changes[column] = write_attribute(column, time)
end
- clear_attribute_changes(changes.keys)
primary_key = self.class.primary_key
scope = self.class.unscoped.where(primary_key => _read_attribute(primary_key))
@@ -508,12 +516,14 @@ module ActiveRecord
changes[locking_column] = increment_lock
end
+ clear_attribute_changes(changes.keys)
result = scope.update_all(changes) == 1
if !result && locking_enabled?
raise ActiveRecord::StaleObjectError.new(self, "touch")
end
+ @_trigger_update_callback = result
result
else
true
@@ -535,7 +545,7 @@ module ActiveRecord
end
def create_or_update(*args)
- raise ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly?
+ _raise_readonly_record_error if readonly?
result = new_record? ? _create_record : _update_record(*args)
result != false
end
@@ -545,10 +555,13 @@ module ActiveRecord
def _update_record(attribute_names = self.attribute_names)
attributes_values = arel_attributes_with_values_for_update(attribute_names)
if attributes_values.empty?
- 0
+ rows_affected = 0
+ @_trigger_update_callback = true
else
- self.class.unscoped._update_record attributes_values, id, id_was
+ rows_affected = self.class.unscoped._update_record attributes_values, id, id_in_database
+ @_trigger_update_callback = rows_affected > 0
end
+ rows_affected
end
# Creates a record with values matching those of the instance attributes
@@ -577,5 +590,9 @@ module ActiveRecord
def belongs_to_touch_method
:touch
end
+
+ def _raise_readonly_record_error
+ raise ReadOnlyRecord, "#{self.class} is marked as readonly"
+ end
end
end
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index 387dd8e9bd..ec246e97bc 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -24,26 +24,24 @@ module ActiveRecord
end
def self.run
- connection = ActiveRecord::Base.connection
- enabled = connection.query_cache_enabled
- connection.enable_query_cache!
+ caching_pool = ActiveRecord::Base.connection_pool
+ caching_was_enabled = caching_pool.query_cache_enabled
- enabled
+ caching_pool.enable_query_cache!
+
+ [caching_pool, caching_was_enabled]
end
- def self.complete(enabled)
- ActiveRecord::Base.connection.clear_query_cache
- ActiveRecord::Base.connection.disable_query_cache! unless enabled
+ def self.complete((caching_pool, caching_was_enabled))
+ caching_pool.disable_query_cache! unless caching_was_enabled
+
+ ActiveRecord::Base.connection_handler.connection_pool_list.each do |pool|
+ pool.release_connection if pool.active_connection? && !pool.connection.transaction_open?
+ end
end
def self.install_executor_hooks(executor = ActiveSupport::Executor)
executor.register_hook(self)
-
- executor.to_complete do
- unless ActiveRecord::Base.connected? && ActiveRecord::Base.connection.transaction_open?
- ActiveRecord::Base.clear_active_connections!
- end
- end
end
end
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 7b4b8c60f8..36689f6559 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -35,7 +35,7 @@ module ActiveRecord
#
# Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
# Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }]
- def find_by_sql(sql, binds = [], preparable: nil)
+ def find_by_sql(sql, binds = [], preparable: nil, &block)
result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable)
column_types = result_set.column_types.dup
columns_hash.each_key { |k| column_types.delete k }
@@ -47,7 +47,7 @@ module ActiveRecord
}
message_bus.instrument("instantiation.active_record", payload) do
- result_set.map { |record| instantiate(record, column_types) }
+ result_set.map { |record| instantiate(record, column_types, &block) }
end
end
@@ -62,8 +62,7 @@ module ActiveRecord
#
# * +sql+ - An SQL statement which should return a count query from the database, see the example above.
def count_by_sql(sql)
- sql = sanitize_conditions(sql)
- connection.select_value(sql, "#{name} Count").to_i
+ connection.select_value(sanitize_sql(sql), "#{name} Count").to_i
end
end
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 989d23bc37..2701c5bca9 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -82,15 +82,15 @@ module ActiveRecord
if config.active_record.delete(:use_schema_cache_dump)
config.after_initialize do |app|
ActiveSupport.on_load(:active_record) do
- filename = File.join(app.config.paths["db"].first, "schema_cache.dump")
+ filename = File.join(app.config.paths["db"].first, "schema_cache.yml")
if File.file?(filename)
- cache = Marshal.load File.binread filename
+ cache = YAML.load(File.read(filename))
if cache.version == ActiveRecord::Migrator.current_version
self.connection.schema_cache = cache
self.connection_pool.schema_cache = cache.dup
else
- warn "Ignoring db/schema_cache.dump because it has expired. The current schema version is #{ActiveRecord::Migrator.current_version}, but the one in the cache is #{cache.version}."
+ warn "Ignoring db/schema_cache.yml because it has expired. The current schema version is #{ActiveRecord::Migrator.current_version}, but the one in the cache is #{cache.version}."
end
end
end
@@ -108,7 +108,7 @@ module ActiveRecord
initializer "active_record.set_configs" do |app|
ActiveSupport.on_load(:active_record) do
- app.config.active_record.each do |k,v|
+ app.config.active_record.each do |k, v|
send "#{k}=", v
end
end
diff --git a/activerecord/lib/active_record/railties/controller_runtime.rb b/activerecord/lib/active_record/railties/controller_runtime.rb
index adb3c6c4e6..8658188623 100644
--- a/activerecord/lib/active_record/railties/controller_runtime.rb
+++ b/activerecord/lib/active_record/railties/controller_runtime.rb
@@ -6,10 +6,14 @@ module ActiveRecord
module ControllerRuntime #:nodoc:
extend ActiveSupport::Concern
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
+ # Workaround for Ruby 2.2 "private attribute?" warning.
protected
attr_internal :db_runtime
+ private
+
def process_action(action, *args)
# We also need to reset the runtime before each action
# because of queries in middleware or in cases we are streaming
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 1d52f77622..cf1c0fb423 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -213,7 +213,7 @@ db_namespace = namespace :db do
ENV["FIXTURES"].split(",")
else
# The use of String#[] here is to support namespaced fixtures.
- Dir["#{fixtures_dir}/**/*.yml"].map {|f| f[(fixtures_dir.size + 1)..-5] }
+ Dir["#{fixtures_dir}/**/*.yml"].map { |f| f[(fixtures_dir.size + 1)..-5] }
end
ActiveRecord::FixtureSet.create_fixtures(fixtures_dir, fixture_files)
@@ -265,19 +265,16 @@ db_namespace = namespace :db do
end
namespace :cache do
- desc "Creates a db/schema_cache.dump file."
+ desc "Creates a db/schema_cache.yml file."
task dump: [:environment, :load_config] do
- con = ActiveRecord::Base.connection
- filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump")
-
- con.schema_cache.clear!
- con.data_sources.each { |table| con.schema_cache.add(table) }
- open(filename, "wb") { |f| f.write(Marshal.dump(con.schema_cache)) }
+ conn = ActiveRecord::Base.connection
+ filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.yml")
+ ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(conn, filename)
end
- desc "Clears a db/schema_cache.dump file."
+ desc "Clears a db/schema_cache.yml file."
task clear: [:environment, :load_config] do
- filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump")
+ filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.yml")
rm_f filename, verbose: false
end
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 3553ff4da3..f3e81ee1e2 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -14,18 +14,19 @@ module ActiveRecord
end
def self.create(macro, name, scope, options, ar)
- klass = case macro
- when :composed_of
- AggregateReflection
- when :has_many
- HasManyReflection
- when :has_one
- HasOneReflection
- when :belongs_to
- BelongsToReflection
- else
- raise "Unsupported Macro: #{macro}"
- end
+ klass = \
+ case macro
+ when :composed_of
+ AggregateReflection
+ when :has_many
+ HasManyReflection
+ when :has_one
+ HasOneReflection
+ when :belongs_to
+ BelongsToReflection
+ else
+ raise "Unsupported Macro: #{macro}"
+ end
reflection = klass.new(name, scope, options, ar)
options[:through] ? ThroughReflection.new(reflection) : reflection
@@ -135,8 +136,8 @@ module ActiveRecord
# BelongsToReflection
# HasAndBelongsToManyReflection
# ThroughReflection
- # PolymorphicReflection
- # RuntimeReflection
+ # PolymorphicReflection
+ # RuntimeReflection
class AbstractReflection # :nodoc:
def through_reflection?
false
@@ -281,7 +282,6 @@ module ActiveRecord
end
def autosave=(autosave)
- @automatic_inverse_of = false
@options[:autosave] = autosave
parent_reflection = self.parent_reflection
if parent_reflection
@@ -311,6 +311,10 @@ module ActiveRecord
active_record == other_aggregation.active_record
end
+ def scope_for(klass)
+ scope ? klass.unscoped.instance_exec(nil, &scope) : klass.unscoped
+ end
+
private
def derive_class_name
name.to_s.camelize
@@ -393,6 +397,10 @@ module ActiveRecord
options[:primary_key] || primary_key(klass || self.klass)
end
+ def association_primary_key_type
+ klass.type_for_attribute(association_primary_key)
+ end
+
def active_record_primary_key
@active_record_primary_key ||= options[:primary_key] || primary_key(active_record)
end
@@ -532,14 +540,10 @@ module ActiveRecord
# Attempts to find the inverse association name automatically.
# If it cannot find a suitable inverse association name, it returns
- # nil.
+ # +nil+.
def inverse_name
options.fetch(:inverse_of) do
- if @automatic_inverse_of == false
- nil
- else
- @automatic_inverse_of ||= automatic_inverse_of
- end
+ @automatic_inverse_of ||= automatic_inverse_of
end
end
@@ -703,7 +707,7 @@ module ActiveRecord
def initialize(delegate_reflection)
@delegate_reflection = delegate_reflection
- @klass = delegate_reflection.options[:anonymous_class]
+ @klass = delegate_reflection.options[:anonymous_class]
@source_reflection_name = delegate_reflection.options[:source]
end
@@ -846,6 +850,10 @@ module ActiveRecord
actual_source_reflection.options[:primary_key] || primary_key(klass || self.klass)
end
+ def association_primary_key_type
+ klass.type_for_attribute(association_primary_key)
+ end
+
# Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
#
# class Post < ActiveRecord::Base
@@ -870,15 +878,13 @@ module ActiveRecord
}
if names.length > 1
- example_options = options.dup
- example_options[:source] = source_reflection_names.first
- ActiveSupport::Deprecation.warn \
- "Ambiguous source reflection for through association. Please " \
- "specify a :source directive on your declaration like:\n" \
- "\n" \
- " class #{active_record.name} < ActiveRecord::Base\n" \
- " #{macro} :#{name}, #{example_options}\n" \
- " end"
+ raise AmbiguousSourceReflectionForThroughAssociation.new(
+ active_record.name,
+ macro,
+ name,
+ options,
+ source_reflection_names
+ )
end
@source_reflection_name = names.first
@@ -979,7 +985,7 @@ module ActiveRecord
delegate(*delegate_methods, to: :delegate_reflection)
end
- class PolymorphicReflection < ThroughReflection # :nodoc:
+ class PolymorphicReflection < AbstractReflection # :nodoc:
def initialize(reflection, previous_reflection)
@reflection = reflection
@previous_reflection = previous_reflection
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 3983065d7a..ccd75ec5d2 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -29,9 +29,7 @@ module ActiveRecord
end
def initialize_copy(other)
- # This method is a hot spot, so for now, use Hash[] to dup the hash.
- # https://bugs.ruby-lang.org/issues/7166
- @values = Hash[@values]
+ @values = @values.dup
reset
end
@@ -245,7 +243,6 @@ module ActiveRecord
# Please see further details in the
# {Active Record Query Interface guide}[http://guides.rubyonrails.org/active_record_querying.html#running-explain].
def explain
- #TODO: Fix for binds.
exec_explain(collecting_queries_for_explain { exec_queries })
end
@@ -365,6 +362,9 @@ module ActiveRecord
#
# # Update all books that match conditions, but limit it to 5 ordered by date
# Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(author: 'David')
+ #
+ # # Update all invoices and set the number column to its id value.
+ # Invoice.update_all('number = id')
def update_all(updates)
raise ArgumentError, "Empty list of attributes to change" if updates.blank?
@@ -373,7 +373,7 @@ module ActiveRecord
stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates))
stmt.table(table)
- if joins_values.any?
+ if has_join_values?
@klass.connection.join_to_update(stmt, arel, arel_attribute(primary_key))
else
stmt.key = arel_attribute(primary_key)
@@ -446,16 +446,8 @@ module ActiveRecord
# ==== Examples
#
# Person.where(age: 0..18).destroy_all
- def destroy_all(conditions = nil)
- if conditions
- ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
- Passing conditions to destroy_all is deprecated and will be removed in Rails 5.1.
- To achieve the same use where(conditions).destroy_all.
- MESSAGE
- where(conditions).destroy_all
- else
- records.each(&:destroy).tap { reset }
- end
+ def destroy_all
+ records.each(&:destroy).tap { reset }
end
# Destroy an object (or multiple objects) that has the given id. The object is instantiated first,
@@ -503,41 +495,28 @@ module ActiveRecord
#
# Post.limit(100).delete_all
# # => ActiveRecord::ActiveRecordError: delete_all doesn't support limit
- def delete_all(conditions = nil)
- invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select { |method|
- if MULTI_VALUE_METHODS.include?(method)
- send("#{method}_values").any?
- elsif SINGLE_VALUE_METHODS.include?(method)
- send("#{method}_value")
- elsif CLAUSE_METHODS.include?(method)
- send("#{method}_clause").any?
- end
- }
+ def delete_all
+ invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
+ value = get_value(method)
+ SINGLE_VALUE_METHODS.include?(method) ? value : value.any?
+ end
if invalid_methods.any?
raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
end
- if conditions
- ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
- Passing conditions to delete_all is deprecated and will be removed in Rails 5.1.
- To achieve the same use where(conditions).delete_all.
- MESSAGE
- where(conditions).delete_all
- else
- stmt = Arel::DeleteManager.new
- stmt.from(table)
+ stmt = Arel::DeleteManager.new
+ stmt.from(table)
- if joins_values.any?
- @klass.connection.join_to_delete(stmt, arel, arel_attribute(primary_key))
- else
- stmt.wheres = arel.constraints
- end
+ if has_join_values?
+ @klass.connection.join_to_delete(stmt, arel, arel_attribute(primary_key))
+ else
+ stmt.wheres = arel.constraints
+ end
- affected = @klass.connection.delete(stmt, "SQL", bound_attributes)
+ affected = @klass.connection.delete(stmt, "SQL", bound_attributes)
- reset
- affected
- end
+ reset
+ affected
end
# Deletes the row with a primary key matching the +id+ argument, using a
@@ -569,8 +548,8 @@ module ActiveRecord
# return value is the relation itself, not the records.
#
# Post.where(published: true).load # => #<ActiveRecord::Relation>
- def load
- exec_queries unless loaded?
+ def load(&block)
+ exec_queries(&block) unless loaded?
self
end
@@ -635,15 +614,6 @@ module ActiveRecord
includes_values & joins_values
end
- # {#uniq}[rdoc-ref:QueryMethods#uniq] and
- # {#uniq!}[rdoc-ref:QueryMethods#uniq!] are silently deprecated.
- # #uniq_value delegates to #distinct_value to maintain backwards compatibility.
- # Use #distinct_value instead.
- def uniq_value
- distinct_value
- end
- deprecate uniq_value: :distinct_value
-
# Compares two relations for equality.
def ==(other)
case other
@@ -666,7 +636,7 @@ module ActiveRecord
end
def values
- Hash[@values]
+ @values.dup
end
def inspect
@@ -685,13 +655,18 @@ module ActiveRecord
private
- def exec_queries
- @records = eager_loading? ? find_with_associations.freeze : @klass.find_by_sql(arel, bound_attributes).freeze
+ def has_join_values?
+ joins_values.any? || left_outer_joins_values.any?
+ end
+
+ def exec_queries(&block)
+ @records = eager_loading? ? find_with_associations.freeze : @klass.find_by_sql(arel, bound_attributes, &block).freeze
preload = preload_values
- preload += includes_values unless eager_loading?
- preloader = build_preloader
+ preload += includes_values unless eager_loading?
+ preloader = nil
preload.each do |associations|
+ preloader ||= build_preloader
preloader.preload @records, associations
end
diff --git a/activerecord/lib/active_record/relation/batches/batch_enumerator.rb b/activerecord/lib/active_record/relation/batches/batch_enumerator.rb
index 333b3a63cf..3555779ec2 100644
--- a/activerecord/lib/active_record/relation/batches/batch_enumerator.rb
+++ b/activerecord/lib/active_record/relation/batches/batch_enumerator.rb
@@ -7,7 +7,7 @@ module ActiveRecord
@of = of
@relation = relation
@start = start
- @finish = finish
+ @finish = finish
end
# Looping through a collection of records from the database (using the
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index ecf3700aab..827688a663 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -112,12 +112,11 @@ module ActiveRecord
# ...
# end
def calculate(operation, column_name)
- if column_name.is_a?(Symbol) && attribute_alias?(column_name)
- column_name = attribute_alias(column_name)
- end
-
if has_include?(column_name)
- construct_relation_for_association_calculations.calculate(operation, column_name)
+ relation = construct_relation_for_association_calculations
+ relation = relation.distinct if operation.to_s.downcase == "count"
+
+ relation.calculate(operation, column_name)
else
perform_calculation(operation, column_name)
end
@@ -160,7 +159,7 @@ module ActiveRecord
#
def pluck(*column_names)
if loaded? && (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
- return @records.pluck(*column_names)
+ return records.pluck(*column_names)
end
if has_include?(column_names.first)
@@ -198,11 +197,6 @@ module ActiveRecord
if operation == "count"
column_name ||= select_for_count
-
- unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
- distinct = true
- end
-
column_name = primary_key if column_name == :all && distinct
distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i
end
@@ -217,8 +211,8 @@ module ActiveRecord
def aggregate_column(column_name)
return column_name if Arel::Expressions === column_name
- if @klass.column_names.include?(column_name.to_s)
- Arel::Attribute.new(@klass.unscoped.table, column_name)
+ if @klass.has_attribute?(column_name.to_s) || @klass.attribute_alias?(column_name.to_s)
+ @klass.arel_attribute(column_name)
else
Arel.sql(column_name == :all ? "*" : column_name.to_s)
end
@@ -229,17 +223,17 @@ module ActiveRecord
end
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
- # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
- relation = unscope(:order)
-
column_alias = column_name
- if operation == "count" && (relation.limit_value || relation.offset_value)
+ if operation == "count" && (limit_value || offset_value)
# Shortcut when limit is zero.
- return 0 if relation.limit_value == 0
+ return 0 if limit_value == 0
- query_builder = build_count_subquery(relation, column_name, distinct)
+ query_builder = build_count_subquery(spawn, column_name, distinct)
else
+ # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
+ relation = unscope(:order)
+
column = aggregate_column(column_name)
select_value = operation_over_aggregate_column(column, operation, distinct)
@@ -254,11 +248,11 @@ module ActiveRecord
result = @klass.connection.select_all(query_builder, nil, bound_attributes)
row = result.first
value = row && row.values.first
- column = result.column_types.fetch(column_alias) do
+ type = result.column_types.fetch(column_alias) do
type_for(column_name)
end
- type_cast_calculated_value(value, column, operation)
+ type_cast_calculated_value(value, type, operation)
end
def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
@@ -312,28 +306,26 @@ module ActiveRecord
Hash[calculated_data.map do |row|
key = group_columns.map { |aliaz, col_name|
- column = type_for(col_name) do
- calculated_data.column_types.fetch(aliaz) do
- Type::Value.new
- end
+ type = type_for(col_name) do
+ calculated_data.column_types.fetch(aliaz, Type.default_value)
end
- type_cast_calculated_value(row[aliaz], column)
+ type_cast_calculated_value(row[aliaz], type)
}
key = key.first if key.size == 1
key = key_records[key] if associated
- column_type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) }
- [key, type_cast_calculated_value(row[aggregate_alias], column_type, operation)]
+ type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) }
+ [key, type_cast_calculated_value(row[aggregate_alias], type, operation)]
end]
end
- # Converts the given keys to the value that the database adapter returns as
- # a usable column name:
- #
- # column_alias_for("users.id") # => "users_id"
- # column_alias_for("sum(id)") # => "sum_id"
- # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
- # column_alias_for("count(*)") # => "count_all"
+ # Converts the given keys to the value that the database adapter returns as
+ # a usable column name:
+ #
+ # column_alias_for("users.id") # => "users_id"
+ # column_alias_for("sum(id)") # => "sum_id"
+ # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
+ # column_alias_for("count(*)") # => "count_all"
def column_alias_for(keys)
if keys.respond_to? :name
keys = "#{keys.relation.name}.#{keys.name}"
@@ -358,7 +350,7 @@ module ActiveRecord
when "count" then value.to_i
when "sum" then type.deserialize(value || 0)
when "average" then value.respond_to?(:to_d) ? value.to_d : value
- else type.deserialize(value)
+ else type.deserialize(value)
end
end
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index e1c36982dd..3c1dea8c6c 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -1,6 +1,3 @@
-require "active_support/concern"
-require "active_support/core_ext/regexp"
-
module ActiveRecord
module Delegation # :nodoc:
module DelegateCache # :nodoc:
@@ -18,7 +15,10 @@ module ActiveRecord
delegate = Class.new(klass) {
include ClassSpecificRelation
}
- const_set klass.name.gsub("::".freeze, "_".freeze), delegate
+ mangled_name = klass.name.gsub("::".freeze, "_".freeze)
+ const_set mangled_name, delegate
+ private_constant mangled_name
+
cache[klass] = delegate
end
end
@@ -81,7 +81,7 @@ module ActiveRecord
end
end
- protected
+ private
def method_missing(method, *args, &block)
if @klass.respond_to?(method)
@@ -108,7 +108,7 @@ module ActiveRecord
end
end
- def respond_to?(method, include_private = false)
+ def respond_to_missing?(method, include_private = false)
super || @klass.respond_to?(method, include_private) ||
arel.respond_to?(method, include_private)
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 7bf84c2f15..6663bdb244 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -76,7 +76,7 @@ module ActiveRecord
# Post.find_by "published_at < ?", 2.weeks.ago
def find_by(arg, *args)
where(arg, *args).take
- rescue RangeError
+ rescue ::RangeError
nil
end
@@ -84,7 +84,7 @@ module ActiveRecord
# an ActiveRecord::RecordNotFound error.
def find_by!(arg, *args)
where(arg, *args).take!
- rescue RangeError
+ rescue ::RangeError
raise RecordNotFound.new("Couldn't find #{@klass.name} with an out of range value",
@klass.name)
end
@@ -97,13 +97,13 @@ module ActiveRecord
# Person.take(5) # returns 5 objects fetched by SELECT * FROM people LIMIT 5
# Person.where(["name LIKE '%?'", name]).take
def take(limit = nil)
- limit ? limit(limit).to_a : find_take
+ limit ? find_take_with_limit(limit) : find_take
end
# Same as #take but raises ActiveRecord::RecordNotFound if no record
# is found. Note that #take! accepts no arguments.
def take!
- take or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
+ take || raise_record_not_found_exception!
end
# Find the first record (or first N records if a parameter is supplied).
@@ -117,7 +117,7 @@ module ActiveRecord
#
def first(limit = nil)
if limit
- find_nth_with_limit_and_offset(0, limit, offset: offset_index)
+ find_nth_with_limit(0, limit)
else
find_nth 0
end
@@ -126,7 +126,7 @@ module ActiveRecord
# Same as #first but raises ActiveRecord::RecordNotFound if no record
# is found. Note that #first! accepts no arguments.
def first!
- find_nth! 0
+ first || raise_record_not_found_exception!
end
# Find the last record (or last N records if a parameter is supplied).
@@ -152,20 +152,12 @@ module ActiveRecord
result = result.reverse_order!
limit ? result.reverse : result.first
- rescue ActiveRecord::IrreversibleOrderError
- ActiveSupport::Deprecation.warn(<<-WARNING.squish)
- Finding a last element by loading the relation when SQL ORDER
- can not be reversed is deprecated.
- Rails 5.1 will raise ActiveRecord::IrreversibleOrderError in this case.
- Please call `to_a.last` if you still want to load the relation.
- WARNING
- find_last(limit)
end
# Same as #last but raises ActiveRecord::RecordNotFound if no record
# is found. Note that #last! accepts no arguments.
def last!
- last or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
+ last || raise_record_not_found_exception!
end
# Find the second record.
@@ -181,7 +173,7 @@ module ActiveRecord
# Same as #second but raises ActiveRecord::RecordNotFound if no record
# is found.
def second!
- find_nth! 1
+ second || raise_record_not_found_exception!
end
# Find the third record.
@@ -197,7 +189,7 @@ module ActiveRecord
# Same as #third but raises ActiveRecord::RecordNotFound if no record
# is found.
def third!
- find_nth! 2
+ third || raise_record_not_found_exception!
end
# Find the fourth record.
@@ -213,7 +205,7 @@ module ActiveRecord
# Same as #fourth but raises ActiveRecord::RecordNotFound if no record
# is found.
def fourth!
- find_nth! 3
+ fourth || raise_record_not_found_exception!
end
# Find the fifth record.
@@ -229,7 +221,7 @@ module ActiveRecord
# Same as #fifth but raises ActiveRecord::RecordNotFound if no record
# is found.
def fifth!
- find_nth! 4
+ fifth || raise_record_not_found_exception!
end
# Find the forty-second record. Also known as accessing "the reddit".
@@ -245,7 +237,7 @@ module ActiveRecord
# Same as #forty_two but raises ActiveRecord::RecordNotFound if no record
# is found.
def forty_two!
- find_nth! 41
+ forty_two || raise_record_not_found_exception!
end
# Find the third-to-last record.
@@ -261,7 +253,7 @@ module ActiveRecord
# Same as #third_to_last but raises ActiveRecord::RecordNotFound if no record
# is found.
def third_to_last!
- find_nth_from_last 3 or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
+ third_to_last || raise_record_not_found_exception!
end
# Find the second-to-last record.
@@ -277,7 +269,7 @@ module ActiveRecord
# Same as #second_to_last but raises ActiveRecord::RecordNotFound if no record
# is found.
def second_to_last!
- find_nth_from_last 2 or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
+ second_to_last || raise_record_not_found_exception!
end
# Returns true if a record exists in the table that matches the +id+ or
@@ -309,8 +301,7 @@ module ActiveRecord
# Person.exists?
def exists?(conditions = :none)
if Base === conditions
- conditions = conditions.id
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ raise ArgumentError, <<-MSG.squish
You are passing an instance of ActiveRecord::Base to `exists?`.
Please pass the id of the object by calling `.id`.
MSG
@@ -321,7 +312,7 @@ module ActiveRecord
relation = apply_join_dependency(self, construct_join_dependency(eager_loading: false))
return false if ActiveRecord::NullRelation === relation
- relation = relation.except(:select, :order).select(ONE_AS_ONE).limit(1)
+ relation = relation.except(:select, :distinct).select(ONE_AS_ONE).limit(1)
case conditions
when Array, Hash
@@ -333,7 +324,7 @@ module ActiveRecord
end
connection.select_value(relation, "#{name} Exists", relation.bound_attributes) ? true : false
- rescue RangeError
+ rescue ::RangeError
false
end
@@ -345,18 +336,24 @@ module ActiveRecord
# of results obtained should be provided in the +result_size+ argument and
# the expected number of results should be provided in the +expected_size+
# argument.
- def raise_record_not_found_exception!(ids, result_size, expected_size) #:nodoc:
+ def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil, key = primary_key) # :nodoc:
conditions = arel.where_sql(@klass.arel_engine)
conditions = " [#{conditions}]" if conditions
-
- if Array(ids).size == 1
- error = "Couldn't find #{@klass.name} with '#{primary_key}'=#{ids}#{conditions}"
+ name = @klass.name
+
+ if ids.nil?
+ error = "Couldn't find #{name}"
+ 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 #{@klass.name.pluralize} with '#{primary_key}': "
+ error = "Couldn't find all #{name.pluralize} with '#{key}': "
error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})"
- end
- raise RecordNotFound, error
+ raise RecordNotFound.new(error, name, primary_key, ids)
+ end
end
private
@@ -426,167 +423,147 @@ module ActiveRecord
arel = relation.arel
id_rows = @klass.connection.select_all(arel, "SQL", relation.bound_attributes)
- id_rows.map {|row| row[primary_key]}
+ id_rows.map { |row| row[primary_key] }
end
def using_limitable_reflections?(reflections)
reflections.none?(&:collection?)
end
- protected
+ private
- def find_with_ids(*ids)
- raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
+ def find_with_ids(*ids)
+ raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
- expects_array = ids.first.kind_of?(Array)
- return ids.first if expects_array && ids.first.empty?
+ expects_array = ids.first.kind_of?(Array)
+ return ids.first if expects_array && ids.first.empty?
- ids = ids.flatten.compact.uniq
+ ids = ids.flatten.compact.uniq
- case ids.size
- when 0
- raise RecordNotFound, "Couldn't find #{@klass.name} without an ID"
- when 1
- result = find_one(ids.first)
- expects_array ? [ result ] : result
- else
- find_some(ids)
+ case ids.size
+ when 0
+ raise RecordNotFound, "Couldn't find #{@klass.name} without an ID"
+ when 1
+ result = find_one(ids.first)
+ expects_array ? [ result ] : result
+ else
+ find_some(ids)
+ end
+ rescue ::RangeError
+ raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID"
end
- rescue RangeError
- raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID"
- end
- def find_one(id)
- if ActiveRecord::Base === id
- id = id.id
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- You are passing an instance of ActiveRecord::Base to `find`.
- Please pass the id of the object by calling `.id`.
- MSG
- end
+ def find_one(id)
+ if ActiveRecord::Base === id
+ raise ArgumentError, <<-MSG.squish
+ You are passing an instance of ActiveRecord::Base to `find`.
+ Please pass the id of the object by calling `.id`.
+ MSG
+ end
- relation = where(primary_key => id)
- record = relation.take
+ relation = where(primary_key => id)
+ record = relation.take
- raise_record_not_found_exception!(id, 0, 1) unless record
+ raise_record_not_found_exception!(id, 0, 1) unless record
- record
- end
+ record
+ end
- def find_some(ids)
- return find_some_ordered(ids) unless order_values.present?
+ def find_some(ids)
+ return find_some_ordered(ids) unless order_values.present?
- result = where(primary_key => ids).to_a
+ result = where(primary_key => ids).to_a
- expected_size =
- if limit_value && ids.size > limit_value
- limit_value
- else
- ids.size
+ expected_size =
+ if limit_value && ids.size > limit_value
+ limit_value
+ else
+ ids.size
+ end
+
+ # 11 ids with limit 3, offset 9 should give 2 results.
+ if offset_value && (ids.size - offset_value < expected_size)
+ expected_size = ids.size - offset_value
end
- # 11 ids with limit 3, offset 9 should give 2 results.
- if offset_value && (ids.size - offset_value < expected_size)
- expected_size = ids.size - offset_value
+ if result.size == expected_size
+ result
+ else
+ raise_record_not_found_exception!(ids, result.size, expected_size)
+ end
end
- if result.size == expected_size
- result
- else
- raise_record_not_found_exception!(ids, result.size, expected_size)
- end
- end
+ def find_some_ordered(ids)
+ ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
- def find_some_ordered(ids)
- ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
+ result = except(:limit, :offset).where(primary_key => ids).records
- result = except(:limit, :offset).where(primary_key => ids).records
+ if result.size == ids.size
+ pk_type = @klass.type_for_attribute(primary_key)
- if result.size == ids.size
- pk_type = @klass.type_for_attribute(primary_key)
-
- records_by_id = result.index_by(&:id)
- ids.map { |id| records_by_id.fetch(pk_type.cast(id)) }
- else
- raise_record_not_found_exception!(ids, result.size, ids.size)
+ records_by_id = result.index_by(&:id)
+ ids.map { |id| records_by_id.fetch(pk_type.cast(id)) }
+ else
+ raise_record_not_found_exception!(ids, result.size, ids.size)
+ end
end
- end
- def find_take
- if loaded?
- records.first
- else
- @take ||= limit(1).records.first
+ def find_take
+ if loaded?
+ records.first
+ else
+ @take ||= limit(1).records.first
+ end
end
- end
- def find_nth(index, offset = nil)
- # TODO: once the offset argument is removed we rely on offset_index
- # within find_nth_with_limit, rather than pass it in via
- # find_nth_with_limit_and_offset
- if offset
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing an offset argument to find_nth is deprecated,
- please use Relation#offset instead.
- MSG
- end
- if loaded?
- records[index]
- else
- offset ||= offset_index
- @offsets[offset + index] ||= find_nth_with_limit_and_offset(index, 1, offset: offset).first
+ def find_take_with_limit(limit)
+ if loaded?
+ records.take(limit)
+ else
+ limit(limit).to_a
+ end
end
- end
-
- def find_nth!(index)
- find_nth(index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
- end
- def find_nth_with_limit(index, limit)
- # TODO: once the offset argument is removed from find_nth,
- # find_nth_with_limit_and_offset can be merged into this method.
- relation = if order_values.empty? && primary_key
- order(arel_attribute(primary_key).asc)
- else
- self
+ def find_nth(index)
+ @offsets[offset_index + index] ||= find_nth_with_limit(index, 1).first
end
- relation = relation.offset(index) unless index.zero?
- relation.limit(limit).to_a
- end
-
- def find_nth_from_last(index)
- if loaded?
- records[-index]
- else
- relation = if order_values.empty? && primary_key
- order(arel_attribute(primary_key).asc)
+ def find_nth_with_limit(index, limit)
+ if loaded?
+ records[index, limit] || []
else
- self
+ relation = if order_values.empty? && primary_key
+ order(arel_attribute(primary_key).asc)
+ else
+ self
+ end
+
+ relation = relation.offset(offset_index + index) unless index.zero?
+ relation.limit(limit).to_a
end
-
- relation.to_a[-index]
- # TODO: can be made more performant on large result sets by
- # for instance, last(index)[-index] (which would require
- # refactoring the last(n) finder method to make test suite pass),
- # or by using a combination of reverse_order, limit, and offset,
- # e.g., reverse_order.offset(index-1).first
end
- end
-
- private
- def find_nth_with_limit_and_offset(index, limit, offset:) # :nodoc:
- if loaded?
- records[index, limit]
- else
- index += offset
- find_nth_with_limit(index, limit)
+ def find_nth_from_last(index)
+ if loaded?
+ records[-index]
+ else
+ relation = if order_values.empty? && primary_key
+ order(arel_attribute(primary_key).asc)
+ else
+ self
+ end
+
+ relation.to_a[-index]
+ # TODO: can be made more performant on large result sets by
+ # for instance, last(index)[-index] (which would require
+ # refactoring the last(n) finder method to make test suite pass),
+ # or by using a combination of reverse_order, limit, and offset,
+ # e.g., reverse_order.offset(index-1).first
+ end
end
- end
- def find_last(limit)
- limit ? records.last(limit) : records.last
- end
+ def find_last(limit)
+ limit ? records.last(limit) : records.last
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index df43b5c64b..5dac00724a 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -151,15 +151,11 @@ module ActiveRecord
end
end
- CLAUSE_METHOD_NAMES = CLAUSE_METHODS.map do |name|
- ["#{name}_clause", "#{name}_clause="]
- end
-
def merge_clauses
- CLAUSE_METHOD_NAMES.each do |(reader, writer)|
- clause = relation.send(reader)
- other_clause = other.send(reader)
- relation.send(writer, clause.merge(other_clause))
+ CLAUSE_METHODS.each do |method|
+ clause = relation.get_value(method)
+ other_clause = other.get_value(method)
+ relation.set_value(method, clause.merge(other_clause))
end
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 505a78cb78..18ae10a652 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -4,7 +4,6 @@ module ActiveRecord
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/class_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"
@@ -16,7 +15,6 @@ module ActiveRecord
@handlers = []
register_handler(BasicObject, BasicObjectHandler.new)
- register_handler(Class, ClassHandler.new(self))
register_handler(Base, BaseHandler.new(self))
register_handler(Range, RangeHandler.new)
register_handler(RangeHandler::RangeWithBinds, RangeHandler.new)
@@ -66,6 +64,8 @@ module ActiveRecord
handler_for(value).call(attribute, value)
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 :table
@@ -74,10 +74,10 @@ module ActiveRecord
return ["1=0"] if attributes.empty?
attributes.flat_map do |key, value|
- if value.is_a?(Hash)
+ if value.is_a?(Hash) && !table.has_column?(key)
associated_predicate_builder(key).expand_from_hash(value)
else
- expand(key, value)
+ build(table.arel_attribute(key), value)
end
end
end
@@ -87,14 +87,15 @@ module ActiveRecord
binds = []
attributes.each do |column_name, value|
- case value
- when Hash
+ case
+ when value.is_a?(Hash) && !table.has_column?(column_name)
attrs, bvs = associated_predicate_builder(column_name).create_binds_for_hash(value)
result[column_name] = attrs
binds += bvs
- when Relation
+ next
+ when value.is_a?(Relation)
binds += value.bound_attributes
- when Range
+ 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?
@@ -113,6 +114,15 @@ module ActiveRecord
binds << build_bind_param(column_name, value)
end
end
+
+ # Find the foreign key when using queries such as:
+ # Post.where(author: author)
+ #
+ # For polymorphic relationships, find the foreign key and type:
+ # PriceEstimate.where(estimate_of: treasure)
+ if table.associated_with?(column_name)
+ result[column_name] = AssociationQueryHandler.value_for(table, column_name, value)
+ end
end
[result, binds]
@@ -120,16 +130,6 @@ module ActiveRecord
private
- def expand(column, value)
- # Find the foreign key when using queries such as:
- # Post.where(author: author)
- #
- # For polymorphic relationships, find the foreign key and type:
- # PriceEstimate.where(estimate_of: treasure)
- value = AssociationQueryHandler.value_for(table, column, value) if table.associated_with?(column)
- build(table.arel_attribute(column), value)
- end
-
def associated_predicate_builder(association_name)
self.class.new(table.associated_table(association_name))
end
@@ -155,9 +155,13 @@ module ActiveRecord
end
def can_be_bound?(column_name, value)
- !value.nil? &&
- handler_for(value).is_a?(BasicObjectHandler) &&
- !table.associated_with?(column_name)
+ return if table.associated_with?(column_name)
+ case value
+ when Array, Range
+ table.type(column_name).respond_to?(:subtype)
+ else
+ !value.nil? && handler_for(value).is_a?(BasicObjectHandler)
+ end
end
def build_bind_param(column_name, 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 6400caba06..88b6c37d43 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
@@ -29,6 +29,8 @@ module ActiveRecord
array_predicates.inject { |composite, predicate| composite.or(predicate) }
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
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
index 7e20cb2c63..29860ec677 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
@@ -28,6 +28,8 @@ module ActiveRecord
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
@@ -68,9 +70,6 @@ module ActiveRecord
case value
when Relation
value.klass.base_class
- when Array
- val = value.compact.first
- val.class.base_class if val.is_a?(Base)
when Base
value.class.base_class
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb
index 65c5159704..3bb1037885 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb
@@ -9,6 +9,8 @@ module ActiveRecord
predicate_builder.build(attribute, value.id)
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
diff --git a/activerecord/lib/active_record/relation/predicate_builder/class_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/class_handler.rb
deleted file mode 100644
index 73ad864765..0000000000
--- a/activerecord/lib/active_record/relation/predicate_builder/class_handler.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-module ActiveRecord
- class PredicateBuilder
- class ClassHandler # :nodoc:
- def initialize(predicate_builder)
- @predicate_builder = predicate_builder
- end
-
- def call(attribute, value)
- print_deprecation_warning
- predicate_builder.build(attribute, value.name)
- end
-
- protected
-
- attr_reader :predicate_builder
-
- private
-
- def print_deprecation_warning
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing a class as a value in an Active Record query is deprecated and
- will be removed. Pass a string instead.
- MSG
- 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
index 0c7f92b3d0..335124c952 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb
@@ -21,6 +21,8 @@ module ActiveRecord
end
end
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
+ # Workaround for Ruby 2.2 "private attribute?" warning.
protected
attr_reader :predicate_builder
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 1a59b3a146..78c046b07f 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -4,7 +4,6 @@ require "active_record/relation/where_clause"
require "active_record/relation/where_clause_factory"
require "active_model/forbidden_attributes_protection"
require "active_support/core_ext/string/filters"
-require "active_support/core_ext/regexp"
module ActiveRecord
module QueryMethods
@@ -56,62 +55,39 @@ module ActiveRecord
end
FROZEN_EMPTY_ARRAY = [].freeze
- Relation::MULTI_VALUE_METHODS.each do |name|
- class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}_values
- @values[:#{name}] || FROZEN_EMPTY_ARRAY
- end
+ FROZEN_EMPTY_HASH = {}.freeze
- def #{name}_values=(values)
- assert_mutability!
- @values[:#{name}] = values
+ Relation::VALUE_METHODS.each do |name|
+ method_name = \
+ case name
+ when *Relation::MULTI_VALUE_METHODS then "#{name}_values"
+ when *Relation::SINGLE_VALUE_METHODS then "#{name}_value"
+ when *Relation::CLAUSE_METHODS then "#{name}_clause"
end
- CODE
- end
-
- (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |name|
class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}_value # def readonly_value
- @values[:#{name}] # @values[:readonly]
+ def #{method_name} # def includes_values
+ get_value(#{name.inspect}) # get_value(:includes)
end # end
- CODE
- end
- Relation::SINGLE_VALUE_METHODS.each do |name|
- class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}_value=(value) # def readonly_value=(value)
- assert_mutability! # assert_mutability!
- @values[:#{name}] = value # @values[:readonly] = value
+ def #{method_name}=(value) # def includes_values=(value)
+ set_value(#{name.inspect}, value) # set_value(:includes, value)
end # end
CODE
end
- Relation::CLAUSE_METHODS.each do |name|
- class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}_clause # def where_clause
- @values[:#{name}] || new_#{name}_clause # @values[:where] || new_where_clause
- end # end
- #
- def #{name}_clause=(value) # def where_clause=(value)
- assert_mutability! # assert_mutability!
- @values[:#{name}] = value # @values[:where] = value
- end # end
- CODE
- end
-
def bound_attributes
- if limit_value && !string_containing_comma?(limit_value)
+ if limit_value
limit_bind = Attribute.with_cast_value(
"LIMIT".freeze,
connection.sanitize_limit(limit_value),
- Type::Value.new,
+ Type.default_value,
)
end
if offset_value
offset_bind = Attribute.with_cast_value(
"OFFSET".freeze,
offset_value.to_i,
- Type::Value.new,
+ Type.default_value,
)
end
connection.combine_bind_parameters(
@@ -124,11 +100,6 @@ module ActiveRecord
)
end
- FROZEN_EMPTY_HASH = {}.freeze
- def create_with_value # :nodoc:
- @values[:create_with] || FROZEN_EMPTY_HASH
- end
-
alias extensions extending_values
# Specify relationships to be included in the result set. For
@@ -270,7 +241,14 @@ module ActiveRecord
# Model.select(:field).first.other_field
# # => ActiveModel::MissingAttributeError: missing attribute: other_field
def select(*fields)
- return super if block_given?
+ if block_given?
+ if fields.any?
+ raise ArgumentError, "`select' with block doesn't take arguments."
+ end
+
+ return super()
+ end
+
raise ArgumentError, "Call this with at least one field" if fields.empty?
spawn._select!(*fields)
end
@@ -418,7 +396,10 @@ module ActiveRecord
args.each do |scope|
case scope
when Symbol
- symbol_unscoping(scope)
+ if !VALID_UNSCOPING_VALUES.include?(scope)
+ raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
+ end
+ set_value(scope, nil)
when Hash
scope.each do |key, target_value|
if key != :where
@@ -496,7 +477,6 @@ module ActiveRecord
self.left_outer_joins_values += args
self
end
- alias :left_joins! :left_outer_joins!
# Returns a new relation, which is the result of filtering the current relation
# according to the conditions in the arguments.
@@ -708,13 +688,6 @@ module ActiveRecord
end
def limit!(value) # :nodoc:
- if string_containing_comma?(value)
- # Remove `string_containing_comma?` when removing this deprecation
- ActiveSupport::Deprecation.warn(<<-WARNING.squish)
- Passing a string to limit in the form "1,2" is deprecated and will be
- removed in Rails 5.1. Please call `offset` explicitly instead.
- WARNING
- end
self.limit_value = value
self
end
@@ -781,7 +754,7 @@ module ActiveRecord
# end
#
def none
- where("1=0").extending!(NullRelation)
+ spawn.none!
end
def none! # :nodoc:
@@ -866,16 +839,12 @@ module ActiveRecord
def distinct(value = true)
spawn.distinct!(value)
end
- alias uniq distinct
- deprecate uniq: :distinct
# Like #distinct, but modifies relation in place.
def distinct!(value = true) # :nodoc:
self.distinct_value = value
self
end
- alias uniq! distinct!
- deprecate uniq!: :distinct!
# Used to extend a scope with additional methods, either through
# a module or through a block provided.
@@ -950,6 +919,17 @@ module ActiveRecord
@arel ||= build_arel
end
+ # Returns a relation value with a given name
+ def get_value(name) # :nodoc:
+ @values[name] || default_value_for(name)
+ end
+
+ # Sets the relation value with the given name
+ def set_value(name, value) # :nodoc:
+ assert_mutability!
+ @values[name] = value
+ end
+
private
def assert_mutability!
@@ -965,13 +945,7 @@ module ActiveRecord
arel.where(where_clause.ast) unless where_clause.empty?
arel.having(having_clause.ast) unless having_clause.empty?
- if limit_value
- if string_containing_comma?(limit_value)
- arel.take(connection.sanitize_limit(limit_value))
- else
- arel.take(Arel::Nodes::BindParam.new)
- end
- end
+ arel.take(Arel::Nodes::BindParam.new) if limit_value
arel.skip(Arel::Nodes::BindParam.new) if offset_value
arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
@@ -986,29 +960,6 @@ module ActiveRecord
arel
end
- def symbol_unscoping(scope)
- if !VALID_UNSCOPING_VALUES.include?(scope)
- raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
- end
-
- clause_method = Relation::CLAUSE_METHODS.include?(scope)
- multi_val_method = Relation::MULTI_VALUE_METHODS.include?(scope)
- if clause_method
- unscope_code = "#{scope}_clause="
- else
- unscope_code = "#{scope}_value#{'s' if multi_val_method}="
- end
-
- case scope
- when :order
- result = []
- else
- result = [] if multi_val_method
- end
-
- send(unscope_code, result)
- end
-
def build_from
opts = from_clause.value
name = from_clause.name
@@ -1137,9 +1088,9 @@ module ActiveRecord
def does_not_support_reverse?(order)
# Uses SQL function with multiple arguments.
- /\([^()]*,[^()]*\)/.match?(order) ||
- # Uses "nulls first" like construction.
- /nulls (first|last)\Z/i.match?(order)
+ (order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) ||
+ # Uses "nulls first" like construction.
+ /nulls (first|last)\Z/i.match?(order)
end
def build_order(arel)
@@ -1188,50 +1139,57 @@ module ActiveRecord
end.flatten!
end
- # Checks to make sure that the arguments are not blank. Note that if some
- # blank-like object were initially passed into the query method, then this
- # method will not raise an error.
- #
- # Example:
- #
- # Post.references() # raises an error
- # Post.references([]) # does not raise an error
- #
- # This particular method should be called with a method_name and the args
- # passed into that method as an input. For example:
- #
- # def references(*args)
- # check_if_method_has_arguments!("references", args)
- # ...
- # end
+ # Checks to make sure that the arguments are not blank. Note that if some
+ # blank-like object were initially passed into the query method, then this
+ # method will not raise an error.
+ #
+ # Example:
+ #
+ # Post.references() # raises an error
+ # Post.references([]) # does not raise an error
+ #
+ # This particular method should be called with a method_name and the args
+ # passed into that method as an input. For example:
+ #
+ # def references(*args)
+ # check_if_method_has_arguments!("references", args)
+ # ...
+ # end
def check_if_method_has_arguments!(method_name, args)
if args.blank?
raise ArgumentError, "The method .#{method_name}() must contain arguments."
end
end
+ STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having]
def structurally_incompatible_values_for_or(other)
- Relation::SINGLE_VALUE_METHODS.reject { |m| send("#{m}_value") == other.send("#{m}_value") } +
- (Relation::MULTI_VALUE_METHODS - [:extending]).reject { |m| send("#{m}_values") == other.send("#{m}_values") } +
- (Relation::CLAUSE_METHODS - [:having, :where]).reject { |m| send("#{m}_clause") == other.send("#{m}_clause") }
- end
-
- def new_where_clause
- Relation::WhereClause.empty
+ STRUCTURAL_OR_METHODS.reject do |method|
+ get_value(method) == other.get_value(method)
+ end
end
- alias new_having_clause new_where_clause
def where_clause_factory
@where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder)
end
alias having_clause_factory where_clause_factory
- def new_from_clause
- Relation::FromClause.empty
- end
-
- def string_containing_comma?(value)
- ::String === value && value.include?(",")
+ def default_value_for(name)
+ case name
+ when :create_with
+ FROZEN_EMPTY_HASH
+ when :readonly
+ false
+ when :where, :having
+ Relation::WhereClause.empty
+ when :from
+ Relation::FromClause.empty
+ when *Relation::MULTI_VALUE_METHODS
+ FROZEN_EMPTY_ARRAY
+ when *Relation::SINGLE_VALUE_METHODS
+ nil
+ else
+ raise ArgumentError, "unknown relation value #{name.inspect}"
+ end
end
end
end
diff --git a/activerecord/lib/active_record/relation/record_fetch_warning.rb b/activerecord/lib/active_record/relation/record_fetch_warning.rb
index dbd08811fa..31544c730e 100644
--- a/activerecord/lib/active_record/relation/record_fetch_warning.rb
+++ b/activerecord/lib/active_record/relation/record_fetch_warning.rb
@@ -2,15 +2,15 @@ module ActiveRecord
class Relation
module RecordFetchWarning
# When this module is prepended to ActiveRecord::Relation and
- # `config.active_record.warn_on_records_fetched_greater_than` is
+ # +config.active_record.warn_on_records_fetched_greater_than+ is
# set to an integer, if the number of records a query returns is
- # greater than the value of `warn_on_records_fetched_greater_than`,
+ # greater than the value of +warn_on_records_fetched_greater_than+,
# a warning is logged. This allows for the detection of queries that
# return a large number of records, which could cause memory bloat.
#
# In most cases, fetching large number of records can be performed
# efficiently using the ActiveRecord::Batches methods.
- # See active_record/lib/relation/batches.rb for more information.
+ # See ActiveRecord::Batches for more information.
def exec_queries
QueryRegistry.reset
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index 190e339ea8..ada89b5ec3 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -66,7 +66,7 @@ module ActiveRecord
private
- def relation_with(values) # :nodoc:
+ def relation_with(values)
result = Relation.create(klass, table, predicate_builder, values)
result.extend(*extending_values) if extending_values.any?
result
diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb
index 402f8acfd1..ef0d059d1c 100644
--- a/activerecord/lib/active_record/relation/where_clause.rb
+++ b/activerecord/lib/active_record/relation/where_clause.rb
@@ -84,6 +84,8 @@ module ActiveRecord
@empty ||= new([], [])
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 :predicates
diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb
index dc00149130..737bc278bd 100644
--- a/activerecord/lib/active_record/relation/where_clause_factory.rb
+++ b/activerecord/lib/active_record/relation/where_clause_factory.rb
@@ -7,8 +7,6 @@ module ActiveRecord
end
def build(opts, other)
- binds = []
-
case opts
when String, Array
parts = [klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
@@ -26,9 +24,11 @@ module ActiveRecord
raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
end
- WhereClause.new(parts, binds)
+ WhereClause.new(parts, binds || [])
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 :klass, :predicate_builder
diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb
index f5383f4c14..9ed70a9c2b 100644
--- a/activerecord/lib/active_record/result.rb
+++ b/activerecord/lib/active_record/result.rb
@@ -32,8 +32,6 @@ module ActiveRecord
class Result
include Enumerable
- IDENTITY_TYPE = Type::Value.new # :nodoc:
-
attr_reader :columns, :rows, :column_types
def initialize(columns, rows, column_types = {})
@@ -105,7 +103,7 @@ module ActiveRecord
def column_type(name, type_overrides = {})
type_overrides.fetch(name) do
- column_types.fetch(name, IDENTITY_TYPE)
+ column_types.fetch(name, Type.default_value)
end
end
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 6617008344..647834b12e 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -1,34 +1,26 @@
-require "active_support/core_ext/regexp"
module ActiveRecord
module Sanitization
extend ActiveSupport::Concern
module ClassMethods
- # Used to sanitize objects before they're used in an SQL SELECT statement.
- # Delegates to {connection.quote}[rdoc-ref:ConnectionAdapters::Quoting#quote].
- def sanitize(object) # :nodoc:
- connection.quote(object)
- end
- alias_method :quote_value, :sanitize
-
- protected
-
- # Accepts an array or string of SQL conditions and sanitizes
- # them into a valid SQL fragment for a WHERE clause.
- #
- # sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4])
- # # => "name='foo''bar' and group_id=4"
- #
- # sanitize_sql_for_conditions(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
- # # => "name='foo''bar' and group_id='4'"
- #
- # sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4])
- # # => "name='foo''bar' and group_id='4'"
- #
- # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'")
- # # => "name='foo''bar' and group_id='4'"
- def sanitize_sql_for_conditions(condition)
+ private
+
+ # Accepts an array or string of SQL conditions and sanitizes
+ # them into a valid SQL fragment for a WHERE clause.
+ #
+ # sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4])
+ # # => "name='foo''bar' and group_id=4"
+ #
+ # sanitize_sql_for_conditions(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
+ # # => "name='foo''bar' and group_id='4'"
+ #
+ # sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4])
+ # # => "name='foo''bar' and group_id='4'"
+ #
+ # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'")
+ # # => "name='foo''bar' and group_id='4'"
+ def sanitize_sql_for_conditions(condition) # :doc:
return nil if condition.blank?
case condition
@@ -36,24 +28,25 @@ module ActiveRecord
else condition
end
end
- alias_method :sanitize_sql, :sanitize_sql_for_conditions
- alias_method :sanitize_conditions, :sanitize_sql
-
- # Accepts an array, hash, or string of SQL conditions and sanitizes
- # them into a valid SQL fragment for a SET clause.
- #
- # sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4])
- # # => "name=NULL and group_id=4"
- #
- # sanitize_sql_for_assignment(["name=:name and group_id=:group_id", name: nil, group_id: 4])
- # # => "name=NULL and group_id=4"
- #
- # Post.send(:sanitize_sql_for_assignment, { name: nil, group_id: 4 })
- # # => "`posts`.`name` = NULL, `posts`.`group_id` = 4"
- #
- # sanitize_sql_for_assignment("name=NULL and group_id='4'")
- # # => "name=NULL and group_id='4'"
- def sanitize_sql_for_assignment(assignments, default_table_name = self.table_name)
+ alias :sanitize_sql :sanitize_sql_for_conditions
+ alias :sanitize_conditions :sanitize_sql
+ deprecate sanitize_conditions: :sanitize_sql
+
+ # Accepts an array, hash, or string of SQL conditions and sanitizes
+ # them into a valid SQL fragment for a SET clause.
+ #
+ # sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4])
+ # # => "name=NULL and group_id=4"
+ #
+ # sanitize_sql_for_assignment(["name=:name and group_id=:group_id", name: nil, group_id: 4])
+ # # => "name=NULL and group_id=4"
+ #
+ # Post.send(:sanitize_sql_for_assignment, { name: nil, group_id: 4 })
+ # # => "`posts`.`name` = NULL, `posts`.`group_id` = 4"
+ #
+ # sanitize_sql_for_assignment("name=NULL and group_id='4'")
+ # # => "name=NULL and group_id='4'"
+ def sanitize_sql_for_assignment(assignments, default_table_name = self.table_name) # :doc:
case assignments
when Array; sanitize_sql_array(assignments)
when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name)
@@ -61,15 +54,15 @@ module ActiveRecord
end
end
- # Accepts an array, or string of SQL conditions and sanitizes
- # them into a valid SQL fragment for an ORDER clause.
- #
- # sanitize_sql_for_order(["field(id, ?)", [1,3,2]])
- # # => "field(id, 1,3,2)"
- #
- # sanitize_sql_for_order("id ASC")
- # # => "id ASC"
- def sanitize_sql_for_order(condition)
+ # Accepts an array, or string of SQL conditions and sanitizes
+ # them into a valid SQL fragment for an ORDER clause.
+ #
+ # sanitize_sql_for_order(["field(id, ?)", [1,3,2]])
+ # # => "field(id, 1,3,2)"
+ #
+ # sanitize_sql_for_order("id ASC")
+ # # => "id ASC"
+ def sanitize_sql_for_order(condition) # :doc:
if condition.is_a?(Array) && condition.first.to_s.include?("?")
sanitize_sql_array(condition)
else
@@ -77,22 +70,22 @@ module ActiveRecord
end
end
- # Accepts a hash of SQL conditions and replaces those attributes
- # that correspond to a {#composed_of}[rdoc-ref:Aggregations::ClassMethods#composed_of]
- # relationship with their expanded aggregate attribute values.
- #
- # Given:
- #
- # class Person < ActiveRecord::Base
- # composed_of :address, class_name: "Address",
- # mapping: [%w(address_street street), %w(address_city city)]
- # end
- #
- # Then:
- #
- # { address: Address.new("813 abc st.", "chicago") }
- # # => { address_street: "813 abc st.", address_city: "chicago" }
- def expand_hash_conditions_for_aggregates(attrs)
+ # Accepts a hash of SQL conditions and replaces those attributes
+ # that correspond to a {#composed_of}[rdoc-ref:Aggregations::ClassMethods#composed_of]
+ # relationship with their expanded aggregate attribute values.
+ #
+ # Given:
+ #
+ # class Person < ActiveRecord::Base
+ # composed_of :address, class_name: "Address",
+ # mapping: [%w(address_street street), %w(address_city city)]
+ # end
+ #
+ # Then:
+ #
+ # { address: Address.new("813 abc st.", "chicago") }
+ # # => { address_street: "813 abc st.", address_city: "chicago" }
+ def expand_hash_conditions_for_aggregates(attrs) # :doc:
expanded_attrs = {}
attrs.each do |attr, value|
if aggregation = reflect_on_aggregation(attr.to_sym)
@@ -111,11 +104,11 @@ module ActiveRecord
expanded_attrs
end
- # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
- #
- # sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts")
- # # => "`posts`.`status` = NULL, `posts`.`group_id` = 1"
- def sanitize_sql_hash_for_assignment(attrs, table)
+ # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
+ #
+ # sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts")
+ # # => "`posts`.`status` = NULL, `posts`.`group_id` = 1"
+ def sanitize_sql_hash_for_assignment(attrs, table) # :doc:
c = connection
attrs.map do |attr, value|
value = type_for_attribute(attr.to_s).serialize(value)
@@ -123,37 +116,37 @@ module ActiveRecord
end.join(", ")
end
- # Sanitizes a +string+ so that it is safe to use within an SQL
- # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%".
- #
- # sanitize_sql_like("100%")
- # # => "100\\%"
- #
- # sanitize_sql_like("snake_cased_string")
- # # => "snake\\_cased\\_string"
- #
- # sanitize_sql_like("100%", "!")
- # # => "100!%"
- #
- # sanitize_sql_like("snake_cased_string", "!")
- # # => "snake!_cased!_string"
- def sanitize_sql_like(string, escape_character = "\\")
+ # Sanitizes a +string+ so that it is safe to use within an SQL
+ # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%".
+ #
+ # sanitize_sql_like("100%")
+ # # => "100\\%"
+ #
+ # sanitize_sql_like("snake_cased_string")
+ # # => "snake\\_cased\\_string"
+ #
+ # sanitize_sql_like("100%", "!")
+ # # => "100!%"
+ #
+ # sanitize_sql_like("snake_cased_string", "!")
+ # # => "snake!_cased!_string"
+ def sanitize_sql_like(string, escape_character = "\\") # :doc:
pattern = Regexp.union(escape_character, "%", "_")
string.gsub(pattern) { |x| [escape_character, x].join }
end
- # Accepts an array of conditions. The array has each value
- # sanitized and interpolated into the SQL statement.
- #
- # sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
- # # => "name='foo''bar' and group_id=4"
- #
- # sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
- # # => "name='foo''bar' and group_id=4"
- #
- # sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
- # # => "name='foo''bar' and group_id='4'"
- def sanitize_sql_array(ary)
+ # Accepts an array of conditions. The array has each value
+ # sanitized and interpolated into the SQL statement.
+ #
+ # sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
+ # # => "name='foo''bar' and group_id=4"
+ #
+ # sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
+ # # => "name='foo''bar' and group_id=4"
+ #
+ # sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
+ # # => "name='foo''bar' and group_id='4'"
+ def sanitize_sql_array(ary) # :doc:
statement, *values = ary
if values.first.is_a?(Hash) && /:\w+/.match?(statement)
replace_named_bind_variables(statement, values.first)
@@ -166,7 +159,7 @@ module ActiveRecord
end
end
- def replace_bind_variables(statement, values) # :nodoc:
+ def replace_bind_variables(statement, values)
raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size)
bound = values.dup
c = connection
@@ -175,7 +168,7 @@ module ActiveRecord
end
end
- def replace_bind_variable(value, c = connection) # :nodoc:
+ def replace_bind_variable(value, c = connection)
if ActiveRecord::Relation === value
value.to_sql
else
@@ -183,7 +176,7 @@ module ActiveRecord
end
end
- def replace_named_bind_variables(statement, bind_vars) # :nodoc:
+ def replace_named_bind_variables(statement, bind_vars)
statement.gsub(/(:?):([a-zA-Z]\w*)/) do |match|
if $1 == ":" # skip postgresql casts
match # return the whole match
@@ -195,7 +188,7 @@ module ActiveRecord
end
end
- def quote_bound_value(value, c = connection) # :nodoc:
+ def quote_bound_value(value, c = connection)
if value.respond_to?(:map) && !value.acts_like?(:string)
if value.respond_to?(:empty?) && value.empty?
c.quote(nil)
@@ -207,7 +200,7 @@ module ActiveRecord
end
end
- def raise_if_bind_arity_mismatch(statement, expected, provided) # :nodoc:
+ def raise_if_bind_arity_mismatch(statement, expected, provided)
unless expected == provided
raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
end
@@ -216,7 +209,7 @@ module ActiveRecord
# TODO: Deprecate this
def quoted_id # :nodoc:
- self.class.quote_value(@attributes[self.class.primary_key].value_for_database)
+ self.class.connection.quote(@attributes[self.class.primary_key].value_for_database)
end
end
end
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index 784a02d2c3..7a2bc9c8af 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -40,7 +40,7 @@ module ActiveRecord
# ActiveRecord::Schema.define(version: 20380119000001) do
# ...
# end
- def self.define(info={}, &block)
+ def self.define(info = {}, &block)
new.define(info, &block)
end
@@ -61,7 +61,7 @@ module ActiveRecord
#
# ActiveRecord::Schema.new.migrations_paths
# # => ["db/migrate"] # Rails migration path by default.
- def migrations_paths # :nodoc:
+ def migrations_paths
ActiveRecord::Migrator.migrations_paths
end
end
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 5105088b2f..12289511b7 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -17,7 +17,7 @@ module ActiveRecord
@@ignore_tables = []
class << self
- def dump(connection=ActiveRecord::Base.connection, stream=STDOUT, config = ActiveRecord::Base)
+ def dump(connection = ActiveRecord::Base.connection, stream = STDOUT, config = ActiveRecord::Base)
new(connection, generate_options(config)).dump(stream)
stream
end
@@ -115,9 +115,7 @@ HEADER
pkcol = columns.detect { |c| c.name == pk }
pkcolspec = @connection.column_spec_for_primary_key(pkcol)
if pkcolspec.present?
- pkcolspec.each do |key, value|
- tbl.print ", #{key}: #{value}"
- end
+ tbl.print ", #{format_colspec(pkcolspec)}"
end
when Array
tbl.print ", primary_key: #{pk.inspect}"
@@ -128,45 +126,18 @@ HEADER
table_options = @connection.table_options(table)
if table_options.present?
- table_options.each do |key, value|
- tbl.print ", #{key}: #{value.inspect}" if value.present?
- end
+ tbl.print ", #{format_options(table_options)}"
end
tbl.puts " do |t|"
# then dump all non-primary key columns
- column_specs = columns.map do |column|
+ columns.each do |column|
raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
next if column.name == pk
- @connection.column_spec(column)
- end.compact
-
- # find all migration keys used in this table
- keys = @connection.migration_keys
-
- # figure out the lengths for each column based on above keys
- lengths = keys.map { |key|
- column_specs.map { |spec|
- spec[key] ? spec[key].length + 2 : 0
- }.max
- }
-
- # the string we're going to sprintf our values against, with standardized column widths
- format_string = lengths.map{ |len| "%-#{len}s" }
-
- # find the max length for the 'type' column, which is special
- type_length = column_specs.map{ |column| column[:type].length }.max
-
- # add column type definition to our format string
- format_string.unshift " t.%-#{type_length}s "
-
- format_string *= ""
-
- column_specs.each do |colspec|
- values = keys.zip(lengths).map{ |key, len| colspec.key?(key) ? colspec[key] + ", " : " " * len }
- values.unshift colspec[:type]
- tbl.print((format_string % values).gsub(/,\s*$/, ""))
+ type, colspec = @connection.column_spec(column)
+ tbl.print " t.#{type} #{column.name.inspect}"
+ tbl.print ", #{format_colspec(colspec)}" if colspec.present?
tbl.puts
end
@@ -191,7 +162,7 @@ HEADER
if (indexes = @connection.indexes(table)).any?
add_index_statements = indexes.map do |index|
table_name = remove_prefix_and_suffix(index.table).inspect
- " add_index #{([table_name]+index_parts(index)).join(', ')}"
+ " add_index #{([table_name] + index_parts(index)).join(', ')}"
end
stream.puts add_index_statements.sort.join("\n")
@@ -214,12 +185,8 @@ HEADER
"name: #{index.name.inspect}",
]
index_parts << "unique: true" if index.unique
-
- index_lengths = (index.lengths || []).compact
- index_parts << "length: #{Hash[index.columns.zip(index.lengths)].inspect}" if index_lengths.any?
-
- index_orders = index.orders || {}
- index_parts << "order: #{index.orders.inspect}" if index_orders.any?
+ index_parts << "length: { #{format_options(index.lengths)} }" if index.lengths.present?
+ index_parts << "order: { #{format_options(index.orders)} }" if index.orders.present?
index_parts << "where: #{index.where.inspect}" if index.where
index_parts << "using: #{index.using.inspect}" if index.using
index_parts << "type: #{index.type.inspect}" if index.type
@@ -257,6 +224,14 @@ HEADER
end
end
+ def format_colspec(colspec)
+ colspec.map { |key, value| "#{key}: #{value}" }.join(", ")
+ end
+
+ def format_options(options)
+ options.map { |key, value| "#{key}: #{value.inspect}" }.join(", ")
+ end
+
def remove_prefix_and_suffix(table)
table.gsub(/^(#{@options[:table_name_prefix]})(.+)(#{@options[:table_name_suffix]})$/, "\\2")
end
diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb
index 99b23e5593..5efbcff96a 100644
--- a/activerecord/lib/active_record/schema_migration.rb
+++ b/activerecord/lib/active_record/schema_migration.rb
@@ -17,7 +17,7 @@ module ActiveRecord
end
def table_exists?
- ActiveSupport::Deprecation.silence { connection.table_exists?(table_name) }
+ connection.table_exists?(table_name)
end
def create_table
diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb
index d1bd1cd89a..7c00e7e4ed 100644
--- a/activerecord/lib/active_record/scoping.rb
+++ b/activerecord/lib/active_record/scoping.rb
@@ -33,7 +33,7 @@ module ActiveRecord
def populate_with_current_scope_attributes # :nodoc:
return unless self.class.scope_attributes?
- self.class.scope_attributes.each do |att,value|
+ self.class.scope_attributes.each do |att, value|
send("#{att}=", value) if respond_to?("#{att}=")
end
end
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index 7409706851..2daa48859a 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -44,50 +44,50 @@ module ActiveRecord
self.current_scope = nil
end
- protected
-
- # Use this macro in your model to set a default scope for all operations on
- # the model.
- #
- # class Article < ActiveRecord::Base
- # default_scope { where(published: true) }
- # end
- #
- # Article.all # => SELECT * FROM articles WHERE published = true
- #
- # The #default_scope is also applied while creating/building a record.
- # It is not applied while updating a record.
- #
- # Article.new.published # => true
- # Article.create.published # => true
- #
- # (You can also pass any object which responds to +call+ to the
- # +default_scope+ macro, and it will be called when building the
- # default scope.)
- #
- # If you use multiple #default_scope declarations in your model then
- # they will be merged together:
- #
- # class Article < ActiveRecord::Base
- # default_scope { where(published: true) }
- # default_scope { where(rating: 'G') }
- # end
- #
- # Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
- #
- # This is also the case with inheritance and module includes where the
- # parent or module defines a #default_scope and the child or including
- # class defines a second one.
- #
- # If you need to do more complex things with a default scope, you can
- # alternatively define it as a class method:
- #
- # class Article < ActiveRecord::Base
- # def self.default_scope
- # # Should return a scope, you can call 'super' here etc.
- # end
- # end
- def default_scope(scope = nil)
+ private
+
+ # Use this macro in your model to set a default scope for all operations on
+ # the model.
+ #
+ # class Article < ActiveRecord::Base
+ # default_scope { where(published: true) }
+ # end
+ #
+ # Article.all # => SELECT * FROM articles WHERE published = true
+ #
+ # The #default_scope is also applied while creating/building a record.
+ # It is not applied while updating a record.
+ #
+ # Article.new.published # => true
+ # Article.create.published # => true
+ #
+ # (You can also pass any object which responds to +call+ to the
+ # +default_scope+ macro, and it will be called when building the
+ # default scope.)
+ #
+ # If you use multiple #default_scope declarations in your model then
+ # they will be merged together:
+ #
+ # class Article < ActiveRecord::Base
+ # default_scope { where(published: true) }
+ # default_scope { where(rating: 'G') }
+ # end
+ #
+ # Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
+ #
+ # This is also the case with inheritance and module includes where the
+ # parent or module defines a #default_scope and the child or including
+ # class defines a second one.
+ #
+ # If you need to do more complex things with a default scope, you can
+ # alternatively define it as a class method:
+ #
+ # class Article < ActiveRecord::Base
+ # def self.default_scope
+ # # Should return a scope, you can call 'super' here etc.
+ # end
+ # end
+ def default_scope(scope = nil) # :doc:
scope = Proc.new if block_given?
if scope.is_a?(Relation) || !scope.respond_to?(:call)
@@ -101,7 +101,7 @@ module ActiveRecord
self.default_scopes += [scope]
end
- def build_default_scope(base_rel = nil) # :nodoc:
+ def build_default_scope(base_rel = nil)
return if abstract_class?
if default_scope_override.nil?
@@ -122,18 +122,18 @@ module ActiveRecord
end
end
- def ignore_default_scope? # :nodoc:
+ def ignore_default_scope?
ScopeRegistry.value_for(:ignore_default_scope, base_class)
end
- def ignore_default_scope=(ignore) # :nodoc:
+ def ignore_default_scope=(ignore)
ScopeRegistry.set_value_for(:ignore_default_scope, base_class, ignore)
end
- # The ignore_default_scope flag is used to prevent an infinite recursion
- # situation where a default scope references a scope which has a default
- # scope which references a scope...
- def evaluate_default_scope # :nodoc:
+ # The ignore_default_scope flag is used to prevent an infinite recursion
+ # situation where a default scope references a scope which has a default
+ # scope which references a scope...
+ def evaluate_default_scope
return if ignore_default_scope?
begin
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index 46ee3540bd..27cdf8cb7e 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -42,7 +42,7 @@ module ActiveRecord
# Adds a class method for retrieving and querying objects.
# The method is intended to return an ActiveRecord::Relation
# object, which is composable with other scopes.
- # If it returns nil or false, an
+ # If it returns +nil+ or +false+, an
# {all}[rdoc-ref:Scoping::Named::ClassMethods#all] scope is returned instead.
#
# A \scope represents a narrowing of a database query, such as
@@ -171,14 +171,14 @@ module ActiveRecord
end
end
- protected
+ private
- def valid_scope_name?(name)
- if respond_to?(name, true)
- logger.warn "Creating scope :#{name}. " \
- "Overwriting existing method #{self.name}.#{name}."
+ def valid_scope_name?(name)
+ if respond_to?(name, true) && logger
+ logger.warn "Creating scope :#{name}. " \
+ "Overwriting existing method #{self.name}.#{name}."
+ end
end
- end
end
end
end
diff --git a/activerecord/lib/active_record/secure_token.rb b/activerecord/lib/active_record/secure_token.rb
index 2c412a45ed..7606961e2e 100644
--- a/activerecord/lib/active_record/secure_token.rb
+++ b/activerecord/lib/active_record/secure_token.rb
@@ -27,7 +27,7 @@ module ActiveRecord
# Load securerandom only when has_secure_token is used.
require "active_support/core_ext/securerandom"
define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token }
- before_create { self.send("#{attribute}=", self.class.generate_unique_secure_token) unless self.send("#{attribute}?")}
+ before_create { self.send("#{attribute}=", self.class.generate_unique_secure_token) unless self.send("#{attribute}?") }
end
def generate_unique_secure_token
diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb
index fd67032235..1877489e55 100644
--- a/activerecord/lib/active_record/statement_cache.rb
+++ b/activerecord/lib/active_record/statement_cache.rb
@@ -7,12 +7,12 @@ module ActiveRecord
# end
#
# The cached statement is executed by using the
- # [connection.execute]{rdoc-ref:ConnectionAdapters::DatabaseStatements#execute} method:
+ # {connection.execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute] method:
#
# cache.execute([], Book, Book.connection)
#
# The relation returned by the block is cached, and for each
- # [execute]{rdoc-ref:ConnectionAdapters::DatabaseStatements#execute}
+ # {execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute]
# call the cached relation gets duped. Database is queried when +to_a+ is called on the relation.
#
# If you want to cache the statement without the values you can use the +bind+ method of the
@@ -41,7 +41,7 @@ module ActiveRecord
class PartialQuery < Query # :nodoc:
def initialize(values)
@values = values
- @indexes = values.each_with_index.find_all { |thing,i|
+ @indexes = values.each_with_index.find_all { |thing, i|
Arel::Nodes::BindParam === thing
}.map(&:last)
end
@@ -68,7 +68,7 @@ module ActiveRecord
class BindMap # :nodoc:
def initialize(bound_attributes)
- @indexes = []
+ @indexes = []
@bound_attributes = bound_attributes
bound_attributes.each_with_index do |attr, i|
@@ -80,7 +80,7 @@ module ActiveRecord
def bind(values)
bas = @bound_attributes.dup
- @indexes.each_with_index { |offset,i| bas[offset] = bas[offset].with_cast_value(values[i]) }
+ @indexes.each_with_index { |offset, i| bas[offset] = bas[offset].with_cast_value(values[i]) }
bas
end
end
@@ -99,12 +99,12 @@ module ActiveRecord
@bind_map = bind_map
end
- def execute(params, klass, connection)
+ def execute(params, klass, connection, &block)
bind_values = bind_map.bind params
sql = query_builder.sql_for bind_values, connection
- klass.find_by_sql(sql, bind_values, preparable: true)
+ klass.find_by_sql(sql, bind_values, preparable: true, &block)
end
alias :call :execute
end
diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb
index 066573192e..d4be20d999 100644
--- a/activerecord/lib/active_record/store.rb
+++ b/activerecord/lib/active_record/store.rb
@@ -121,18 +121,17 @@ module ActiveRecord
end
end
- protected
- def read_store_attribute(store_attribute, key)
+ private
+ def read_store_attribute(store_attribute, key) # :doc:
accessor = store_accessor_for(store_attribute)
accessor.read(self, store_attribute, key)
end
- def write_store_attribute(store_attribute, key, value)
+ def write_store_attribute(store_attribute, key, value) # :doc:
accessor = store_accessor_for(store_attribute)
accessor.write(self, store_attribute, key, value)
end
- private
def store_accessor_for(store_attribute)
type_for_attribute(store_attribute.to_s).accessor
end
diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb
index e8d6a144f9..b618e5cfcd 100644
--- a/activerecord/lib/active_record/table_metadata.rb
+++ b/activerecord/lib/active_record/table_metadata.rb
@@ -10,9 +10,7 @@ module ActiveRecord
end
def resolve_column_aliases(hash)
- # This method is a hot spot, so for now, use Hash[] to dup the hash.
- # https://bugs.ruby-lang.org/issues/7166
- new_hash = Hash[hash]
+ new_hash = hash.dup
hash.each do |key, _|
if (key.is_a?(Symbol)) && klass.attribute_alias?(key)
new_hash[klass.attribute_alias(key)] = new_hash.delete(key)
@@ -33,10 +31,14 @@ module ActiveRecord
if klass
klass.type_for_attribute(column_name.to_s)
else
- Type::Value.new
+ Type.default_value
end
end
+ def has_column?(column_name)
+ klass && klass.columns_hash.key?(column_name.to_s)
+ end
+
def associated_with?(association_name)
klass && klass._reflect_on_association(association_name)
end
@@ -62,6 +64,8 @@ module ActiveRecord
association && association.polymorphic?
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 :klass, :arel_table, :association
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index a19913f2a8..1423e6008f 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -35,12 +35,22 @@ module ActiveRecord
#
# DatabaseTasks.create_current('production')
module DatabaseTasks
+ ##
+ # :singleton-method:
+ # Extra flags passed to database CLI tool (mysqldump/pg_dump) when calling db:structure:dump
+ mattr_accessor :structure_dump_flags, instance_accessor: false
+
+ ##
+ # :singleton-method:
+ # Extra flags passed to database CLI tool when calling db:structure:load
+ mattr_accessor :structure_load_flags, instance_accessor: false
+
extend self
attr_writer :current_config, :db_dir, :migrations_paths, :fixtures_path, :root, :env, :seed_loader
attr_accessor :database_configuration
- LOCAL_HOSTS = ["127.0.0.1", "localhost"]
+ LOCAL_HOSTS = ["127.0.0.1", "localhost"]
def check_protected_environments!
unless ENV["DISABLE_DATABASE_ENVIRONMENT_CHECK"]
@@ -204,13 +214,13 @@ module ActiveRecord
def structure_dump(*arguments)
configuration = arguments.first
filename = arguments.delete_at 1
- class_for_adapter(configuration["adapter"]).new(*arguments).structure_dump(filename)
+ class_for_adapter(configuration["adapter"]).new(*arguments).structure_dump(filename, structure_dump_flags)
end
def structure_load(*arguments)
configuration = arguments.first
filename = arguments.delete_at 1
- class_for_adapter(configuration["adapter"]).new(*arguments).structure_load(filename)
+ class_for_adapter(configuration["adapter"]).new(*arguments).structure_load(filename, structure_load_flags)
end
def load_schema(configuration, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc:
@@ -231,14 +241,6 @@ module ActiveRecord
ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment
end
- def load_schema_for(*args)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- This method was renamed to `#load_schema` and will be removed in the future.
- Use `#load_schema` instead.
- MSG
- load_schema(*args)
- end
-
def schema_file(format = ActiveRecord::Base.schema_format)
case format
when :ruby
@@ -273,6 +275,16 @@ module ActiveRecord
end
end
+ # Dumps the schema cache in YAML format for the connection into the file
+ #
+ # ==== Examples:
+ # ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(ActiveRecord::Base.connection, "tmp/schema_dump.yaml")
+ def dump_schema_cache(conn, filename)
+ conn.schema_cache.clear!
+ conn.data_sources.each { |table| conn.schema_cache.add(table) }
+ open(filename, "wb") { |f| f.write(YAML.dump(conn.schema_cache)) }
+ end
+
private
def class_for_adapter(adapter)
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index c8b89f1fdf..920830b9cf 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -14,7 +14,7 @@ module ActiveRecord
connection.create_database configuration["database"], creation_options
establish_connection configuration
rescue ActiveRecord::StatementInvalid => error
- if /database exists/ === error.message
+ if error.message.include?("database exists")
raise DatabaseAlreadyExists
else
raise
@@ -53,21 +53,23 @@ module ActiveRecord
connection.collation
end
- def structure_dump(filename)
+ def structure_dump(filename, extra_flags)
args = prepare_command_options
args.concat(["--result-file", "#{filename}"])
args.concat(["--no-data"])
args.concat(["--routines"])
args.concat(["--skip-comments"])
+ args.concat(Array(extra_flags)) if extra_flags
args.concat(["#{configuration['database']}"])
run_cmd("mysqldump", args, "dumping")
end
- def structure_load(filename)
+ def structure_load(filename, extra_flags)
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
run_cmd("mysql", args, "loading")
end
@@ -105,7 +107,7 @@ module ActiveRecord
GRANT ALL PRIVILEGES ON #{configuration['database']}.*
TO '#{configuration['username']}'@'localhost'
IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION;
- SQL
+ SQL
end
def root_configuration_without_database
diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
index 6eac9af236..5155ced0e2 100644
--- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
@@ -17,7 +17,7 @@ module ActiveRecord
configuration.merge("encoding" => encoding)
establish_connection configuration
rescue ActiveRecord::StatementInvalid => error
- if /database .* already exists/ === error.message
+ if /database .* already exists/.match?(error.message)
raise DatabaseAlreadyExists
else
raise
@@ -43,19 +43,21 @@ module ActiveRecord
create true
end
- def structure_dump(filename)
+ def structure_dump(filename, extra_flags)
set_psql_env
- search_path = case ActiveRecord::Base.dump_schemas
- when :schema_search_path
- configuration["schema_search_path"]
- when :all
- nil
- when String
- ActiveRecord::Base.dump_schemas
- end
+ search_path = \
+ case ActiveRecord::Base.dump_schemas
+ when :schema_search_path
+ configuration["schema_search_path"]
+ when :all
+ nil
+ when String
+ ActiveRecord::Base.dump_schemas
+ end
args = ["-s", "-x", "-O", "-f", filename]
+ args.concat(Array(extra_flags)) if extra_flags
unless search_path.blank?
args += search_path.split(",").map do |part|
"--schema=#{part.strip}"
@@ -66,10 +68,12 @@ module ActiveRecord
File.open(filename, "a") { |f| f << "SET search_path TO #{connection.schema_search_path};\n\n" }
end
- def structure_load(filename)
+ def structure_load(filename, extra_flags)
set_psql_env
- args = [ "-v", ON_ERROR_STOP_1, "-q", "-f", filename, configuration["database"] ]
- run_cmd("psql", args, "loading" )
+ args = ["-v", ON_ERROR_STOP_1, "-q", "-f", filename]
+ args.concat(Array(extra_flags)) if extra_flags
+ args << configuration["database"]
+ run_cmd("psql", args, "loading")
end
private
diff --git a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
index 31f1b7efd4..1f756c2979 100644
--- a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
@@ -21,7 +21,7 @@ module ActiveRecord
FileUtils.rm(file)
rescue Errno::ENOENT => error
- raise NoDatabaseError.new(error.message, error)
+ raise NoDatabaseError.new(error.message)
end
def purge
@@ -35,14 +35,16 @@ module ActiveRecord
connection.encoding
end
- def structure_dump(filename)
+ def structure_dump(filename, extra_flags)
dbfile = configuration["database"]
- `sqlite3 #{dbfile} .schema > #{filename}`
+ flags = extra_flags.join(" ") if extra_flags
+ `sqlite3 #{flags} #{dbfile} .schema > #{filename}`
end
- def structure_load(filename)
+ def structure_load(filename, extra_flags)
dbfile = configuration["database"]
- `sqlite3 #{dbfile} < "#{filename}"`
+ flags = extra_flags.join(" ") if extra_flags
+ `sqlite3 #{flags} #{dbfile} < "#{filename}"`
end
private
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index 6641ab5df1..09d8d1cdd4 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
module ActiveRecord
# = Active Record \Timestamp
#
@@ -51,15 +52,41 @@ module ActiveRecord
clear_timestamp_attributes
end
+ class_methods do
+ private
+ def timestamp_attributes_for_create_in_model
+ timestamp_attributes_for_create.select { |c| column_names.include?(c) }
+ end
+
+ def timestamp_attributes_for_update_in_model
+ timestamp_attributes_for_update.select { |c| column_names.include?(c) }
+ end
+
+ def all_timestamp_attributes_in_model
+ timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model
+ end
+
+ def timestamp_attributes_for_create
+ ["created_at", "created_on"]
+ end
+
+ def timestamp_attributes_for_update
+ ["updated_at", "updated_on"]
+ end
+
+ def current_time_from_proper_timezone
+ default_timezone == :utc ? Time.now.utc : Time.now
+ end
+ end
+
private
def _create_record
if record_timestamps
current_time = current_time_from_proper_timezone
- all_timestamp_attributes.each do |column|
- column = column.to_s
- if has_attribute?(column) && !attribute_present?(column)
+ all_timestamp_attributes_in_model.each do |column|
+ if !attribute_present?(column)
write_attribute(column, current_time)
end
end
@@ -73,8 +100,7 @@ module ActiveRecord
current_time = current_time_from_proper_timezone
timestamp_attributes_for_update_in_model.each do |column|
- column = column.to_s
- next if attribute_changed?(column)
+ next if will_save_change_to_attribute?(column)
write_attribute(column, current_time)
end
end
@@ -82,34 +108,26 @@ module ActiveRecord
end
def should_record_timestamps?
- record_timestamps && (!partial_writes? || changed?)
+ record_timestamps && (!partial_writes? || has_changes_to_save?)
end
def timestamp_attributes_for_create_in_model
- timestamp_attributes_for_create.select { |c| self.class.column_names.include?(c.to_s) }
+ self.class.send(:timestamp_attributes_for_create_in_model)
end
def timestamp_attributes_for_update_in_model
- timestamp_attributes_for_update.select { |c| self.class.column_names.include?(c.to_s) }
+ self.class.send(:timestamp_attributes_for_update_in_model)
end
def all_timestamp_attributes_in_model
- timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model
- end
-
- def timestamp_attributes_for_update
- [:updated_at, :updated_on]
- end
-
- def timestamp_attributes_for_create
- [:created_at, :created_on]
+ self.class.send(:all_timestamp_attributes_in_model)
end
- def all_timestamp_attributes
- timestamp_attributes_for_create + timestamp_attributes_for_update
+ def current_time_from_proper_timezone
+ self.class.send(:current_time_from_proper_timezone)
end
- def max_updated_column_timestamp(timestamp_names = timestamp_attributes_for_update)
+ def max_updated_column_timestamp(timestamp_names = self.class.send(:timestamp_attributes_for_update))
timestamp_names
.map { |attr| self[attr] }
.compact
@@ -117,10 +135,6 @@ module ActiveRecord
.max
end
- def current_time_from_proper_timezone
- self.class.default_timezone == :utc ? Time.now.utc : Time.now
- end
-
# Clear attributes and changed_attributes
def clear_timestamp_attributes
all_timestamp_attributes_in_model.each do |attribute_name|
diff --git a/activerecord/lib/active_record/touch_later.rb b/activerecord/lib/active_record/touch_later.rb
index c337a7532f..cacde9c881 100644
--- a/activerecord/lib/active_record/touch_later.rb
+++ b/activerecord/lib/active_record/touch_later.rb
@@ -25,7 +25,7 @@ module ActiveRecord
# touch the parents as we are not calling the after_save callbacks
self.class.reflect_on_all_associations(:belongs_to).each do |r|
if touch = r.options[:touch]
- ActiveRecord::Associations::Builder::BelongsTo.touch_record(self, r.foreign_key, r.name, touch, :touch_later)
+ ActiveRecord::Associations::Builder::BelongsTo.touch_record(self, changes_to_save, r.foreign_key, r.name, touch, :touch_later)
end
end
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index b19ae5c46e..56b75540e3 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -274,16 +274,6 @@ module ActiveRecord
set_callback(:rollback_without_transaction_enrollment, :after, *args, &block)
end
- def raise_in_transactional_callbacks
- ActiveSupport::Deprecation.warn("ActiveRecord::Base.raise_in_transactional_callbacks is deprecated and will be removed without replacement.")
- true
- end
-
- def raise_in_transactional_callbacks=(value)
- ActiveSupport::Deprecation.warn("ActiveRecord::Base.raise_in_transactional_callbacks= is deprecated, has no effect and will be removed without replacement.")
- value
- end
-
private
def set_options_for_callbacks!(args, enforced_options = {})
@@ -407,10 +397,10 @@ module ActiveRecord
end
end
- protected
+ private
- # Save the new record state and id of a record so it can be restored later if a transaction fails.
- def remember_transaction_record_state #:nodoc:
+ # Save the new record state and id of a record so it can be restored later if a transaction fails.
+ def remember_transaction_record_state
@_start_transaction_state[:id] = id
@_start_transaction_state.reverse_merge!(
new_record: @new_record,
@@ -420,19 +410,19 @@ module ActiveRecord
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
end
- # Clear the new record state and id of a record.
- def clear_transaction_record_state #:nodoc:
+ # Clear the new record state and id of a record.
+ def clear_transaction_record_state
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
force_clear_transaction_record_state if @_start_transaction_state[:level] < 1
end
- # Force to clear the transaction record state.
- def force_clear_transaction_record_state #:nodoc:
+ # Force to clear the transaction record state.
+ def force_clear_transaction_record_state
@_start_transaction_state.clear
end
- # Restore the new record state and id of a record that was previously saved by a call to save_record_state.
- def restore_transaction_record_state(force = false) #:nodoc:
+ # Restore the new record state and id of a record that was previously saved by a call to save_record_state.
+ def restore_transaction_record_state(force = false)
unless @_start_transaction_state.empty?
transaction_level = (@_start_transaction_state[:level] || 0) - 1
if transaction_level < 1 || force
@@ -449,52 +439,51 @@ module ActiveRecord
end
end
- # Determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed.
- def transaction_record_state(state) #:nodoc:
+ # Determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed.
+ def transaction_record_state(state)
@_start_transaction_state[state]
end
- # Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks.
- def transaction_include_any_action?(actions) #:nodoc:
+ # Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks.
+ def transaction_include_any_action?(actions)
actions.any? do |action|
case action
when :create
transaction_record_state(:new_record)
when :destroy
- destroyed?
+ defined?(@_trigger_destroy_callback) && @_trigger_destroy_callback
when :update
- !(transaction_record_state(:new_record) || destroyed?)
+ !(transaction_record_state(:new_record) || destroyed?) &&
+ (defined?(@_trigger_update_callback) && @_trigger_update_callback)
end
end
end
- private
-
- def set_transaction_state(state) # :nodoc:
+ def set_transaction_state(state)
@transaction_state = state
end
- def has_transactional_callbacks? # :nodoc:
+ def has_transactional_callbacks?
!_rollback_callbacks.empty? || !_commit_callbacks.empty? || !_before_commit_callbacks.empty?
end
- # Updates the attributes on this particular Active Record object so that
- # if it's associated with a transaction, then the state of the Active Record
- # object will be updated to reflect the current state of the transaction.
- #
- # The +@transaction_state+ variable stores the states of the associated
- # transaction. This relies on the fact that a transaction can only be in
- # one rollback or commit (otherwise a list of states would be required).
- # Each Active Record object inside of a transaction carries that transaction's
- # TransactionState.
- #
- # This method checks to see if the ActiveRecord object's state reflects
- # the TransactionState, and rolls back or commits the Active Record object
- # as appropriate.
- #
- # Since Active Record objects can be inside multiple transactions, this
- # method recursively goes through the parent of the TransactionState and
- # checks if the Active Record object reflects the state of the object.
+ # Updates the attributes on this particular Active Record object so that
+ # if it's associated with a transaction, then the state of the Active Record
+ # object will be updated to reflect the current state of the transaction.
+ #
+ # The +@transaction_state+ variable stores the states of the associated
+ # transaction. This relies on the fact that a transaction can only be in
+ # one rollback or commit (otherwise a list of states would be required).
+ # Each Active Record object inside of a transaction carries that transaction's
+ # TransactionState.
+ #
+ # This method checks to see if the ActiveRecord object's state reflects
+ # the TransactionState, and rolls back or commits the Active Record object
+ # as appropriate.
+ #
+ # Since Active Record objects can be inside multiple transactions, this
+ # method recursively goes through the parent of the TransactionState and
+ # checks if the Active Record object reflects the state of the object.
def sync_with_transaction_state
update_attributes_from_transaction_state(@transaction_state)
end
diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb
index 1b2fc1b034..4f632660a8 100644
--- a/activerecord/lib/active_record/type.rb
+++ b/activerecord/lib/active_record/type.rb
@@ -5,7 +5,10 @@ require "active_record/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 "active_record/type/serialized"
require "active_record/type/adapter_specific_registry"
@@ -37,6 +40,10 @@ module ActiveRecord
registry.lookup(*args, adapter: adapter, **kwargs)
end
+ def default_value # :nodoc:
+ @default_value ||= Value.new
+ end
+
private
def current_adapter_name
@@ -49,12 +56,9 @@ module ActiveRecord
Binary = ActiveModel::Type::Binary
Boolean = ActiveModel::Type::Boolean
Decimal = ActiveModel::Type::Decimal
- DecimalWithoutScale = ActiveModel::Type::DecimalWithoutScale
Float = ActiveModel::Type::Float
Integer = ActiveModel::Type::Integer
String = ActiveModel::Type::String
- Text = ActiveModel::Type::Text
- UnsignedInteger = ActiveModel::Type::UnsignedInteger
Value = ActiveModel::Type::Value
register(:big_integer, Type::BigInteger, override: false)
diff --git a/activerecord/lib/active_record/type/adapter_specific_registry.rb b/activerecord/lib/active_record/type/adapter_specific_registry.rb
index d0f9581576..7cc866f7a7 100644
--- a/activerecord/lib/active_record/type/adapter_specific_registry.rb
+++ b/activerecord/lib/active_record/type/adapter_specific_registry.rb
@@ -50,6 +50,8 @@ module ActiveRecord
priority <=> other.priority
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 :name, :block, :adapter, :override
@@ -110,6 +112,8 @@ module ActiveRecord
super | 4
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 :options, :klass
diff --git a/activerecord/lib/active_record/type/decimal_without_scale.rb b/activerecord/lib/active_record/type/decimal_without_scale.rb
new file mode 100644
index 0000000000..7ce33e9cd3
--- /dev/null
+++ b/activerecord/lib/active_record/type/decimal_without_scale.rb
@@ -0,0 +1,9 @@
+module ActiveRecord
+ module Type
+ class DecimalWithoutScale < ActiveModel::Type::BigInteger # :nodoc:
+ def type
+ :decimal
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/internal/abstract_json.rb b/activerecord/lib/active_record/type/internal/abstract_json.rb
index 513c938088..e19c5a14da 100644
--- a/activerecord/lib/active_record/type/internal/abstract_json.rb
+++ b/activerecord/lib/active_record/type/internal/abstract_json.rb
@@ -17,7 +17,11 @@ module ActiveRecord
end
def serialize(value)
- ::ActiveSupport::JSON.encode(value)
+ if value.nil?
+ nil
+ else
+ ::ActiveSupport::JSON.encode(value)
+ end
end
def accessor
diff --git a/activerecord/lib/active_record/type/text.rb b/activerecord/lib/active_record/type/text.rb
new file mode 100644
index 0000000000..cb1949700a
--- /dev/null
+++ b/activerecord/lib/active_record/type/text.rb
@@ -0,0 +1,9 @@
+module ActiveRecord
+ module Type
+ class Text < ActiveModel::Type::String # :nodoc:
+ def type
+ :text
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/type_map.rb b/activerecord/lib/active_record/type/type_map.rb
index 9618ff8787..7bce82a1ff 100644
--- a/activerecord/lib/active_record/type/type_map.rb
+++ b/activerecord/lib/active_record/type/type_map.rb
@@ -11,7 +11,7 @@ module ActiveRecord
end
def lookup(lookup_key, *args)
- fetch(lookup_key, *args) { default_value }
+ fetch(lookup_key, *args) { Type.default_value }
end
def fetch(lookup_key, *args, &block)
@@ -55,10 +55,6 @@ module ActiveRecord
yield lookup_key, *args
end
end
-
- def default_value
- @default_value ||= ActiveModel::Type::Value.new
- end
end
end
end
diff --git a/activemodel/lib/active_model/type/unsigned_integer.rb b/activerecord/lib/active_record/type/unsigned_integer.rb
index 288fa23efe..9ae0109f9f 100644
--- a/activemodel/lib/active_model/type/unsigned_integer.rb
+++ b/activerecord/lib/active_record/type/unsigned_integer.rb
@@ -1,6 +1,6 @@
-module ActiveModel
+module ActiveRecord
module Type
- class UnsignedInteger < Integer # :nodoc:
+ class UnsignedInteger < ActiveModel::Type::Integer # :nodoc:
private
def max_value
diff --git a/activerecord/lib/active_record/type_caster/connection.rb b/activerecord/lib/active_record/type_caster/connection.rb
index 6c54792e26..9f7bbe8843 100644
--- a/activerecord/lib/active_record/type_caster/connection.rb
+++ b/activerecord/lib/active_record/type_caster/connection.rb
@@ -12,6 +12,8 @@ module ActiveRecord
connection.type_cast_from_column(column, value)
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 :table_name
diff --git a/activerecord/lib/active_record/type_caster/map.rb b/activerecord/lib/active_record/type_caster/map.rb
index 52529a6b42..9f79723125 100644
--- a/activerecord/lib/active_record/type_caster/map.rb
+++ b/activerecord/lib/active_record/type_caster/map.rb
@@ -11,6 +11,8 @@ module ActiveRecord
type.serialize(value)
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 :types
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index ecaf04e39e..9633f226f0 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -40,13 +40,13 @@ module ActiveRecord
# The validation process on save can be skipped by passing <tt>validate: false</tt>.
# The regular {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] method is replaced
# with this when the validations module is mixed in, which it is by default.
- def save(options={})
+ def save(options = {})
perform_validations(options) ? super : false
end
# Attempts to save the record just like {ActiveRecord::Base#save}[rdoc-ref:Base#save] but
# will raise an ActiveRecord::RecordInvalid exception instead of returning +false+ if the record is not valid.
- def save!(options={})
+ def save!(options = {})
perform_validations(options) ? super : raise_validation_error
end
@@ -68,7 +68,7 @@ module ActiveRecord
alias_method :validate, :valid?
- protected
+ private
def default_validation_context
new_record? ? :create : :update
@@ -78,7 +78,7 @@ module ActiveRecord
raise(RecordInvalid.new(self))
end
- def perform_validations(options={}) # :nodoc:
+ def perform_validations(options = {})
options[:validate] == false || valid?(options[:context])
end
end
diff --git a/activerecord/lib/active_record/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb
index b14db85167..c695965d7b 100644
--- a/activerecord/lib/active_record/validations/associated.rb
+++ b/activerecord/lib/active_record/validations/associated.rb
@@ -37,7 +37,7 @@ module ActiveRecord
#
# * <tt>:message</tt> - A custom error message (default is: "is invalid").
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
- # Runs in all validation contexts by default (nil). You can pass a symbol
+ # Runs in all validation contexts by default +nil+. You can pass a symbol
# or an array of symbols. (e.g. <tt>on: :create</tt> or
# <tt>on: :custom_validation_context</tt> or
# <tt>on: [:create, :custom_validation_context]</tt>)
diff --git a/activerecord/lib/active_record/validations/presence.rb b/activerecord/lib/active_record/validations/presence.rb
index ad82ea66c4..ca5eda2f84 100644
--- a/activerecord/lib/active_record/validations/presence.rb
+++ b/activerecord/lib/active_record/validations/presence.rb
@@ -44,7 +44,7 @@ module ActiveRecord
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "can't be blank").
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
- # Runs in all validation contexts by default (nil). You can pass a symbol
+ # Runs in all validation contexts by default +nil+. You can pass a symbol
# or an array of symbols. (e.g. <tt>on: :create</tt> or
# <tt>on: :custom_validation_context</tt> or
# <tt>on: [:create, :custom_validation_context]</tt>)
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 8c4930a81d..9e8edfbfaf 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -17,7 +17,7 @@ module ActiveRecord
relation = build_relation(finder_class, attribute, value)
if record.persisted?
if finder_class.primary_key
- relation = relation.where.not(finder_class.primary_key => record.id_was || record.id)
+ relation = relation.where.not(finder_class.primary_key => record.id_in_database || record.id)
else
raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.")
end
@@ -33,13 +33,13 @@ module ActiveRecord
end
end
- protected
+ private
# The check for an existing value should be run from a class that
# isn't abstract. This means working down from the current class
# (self), to the first non-abstract class. Since classes don't know
# their subclasses, we have to build the hierarchy between self and
# the record's class.
- def find_finder_class_for(record) #:nodoc:
+ def find_finder_class_for(record)
class_hierarchy = [record.class]
while class_hierarchy.first != @klass
@@ -49,7 +49,7 @@ module ActiveRecord
class_hierarchy.detect { |klass| !klass.abstract_class? }
end
- def build_relation(klass, attribute, value) # :nodoc:
+ def build_relation(klass, attribute, value)
if reflection = klass._reflect_on_association(attribute)
attribute = reflection.foreign_key
value = value.attributes[reflection.klass.primary_key] unless value.nil?
@@ -85,11 +85,10 @@ module ActiveRecord
def scope_relation(record, relation)
Array(options[:scope]).each do |scope_item|
- if reflection = record.class._reflect_on_association(scope_item)
- scope_value = record.send(reflection.foreign_key)
- scope_item = reflection.foreign_key
+ scope_value = if record.class._reflect_on_association(scope_item)
+ record.association(scope_item).reader
else
- scope_value = record._read_attribute(scope_item)
+ record._read_attribute(scope_item)
end
relation = relation.where(scope_item => scope_value)
end
diff --git a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
index 76182a3c24..8511531af7 100644
--- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
@@ -1,5 +1,4 @@
require "rails/generators/active_record"
-require "active_support/core_ext/regexp"
module ActiveRecord
module Generators # :nodoc:
@@ -14,12 +13,16 @@ module ActiveRecord
migration_template @migration_template, "db/migrate/#{file_name}.rb"
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 :migration_action, :join_tables
- # Sets the default migration template that is being used for the generation of the migration.
- # Depending on command line arguments, the migration template and the table name instance
- # variables are set up.
+ private
+
+ # Sets the default migration template that is being used for the generation of the migration.
+ # Depending on command line arguments, the migration template and the table name instance
+ # variables are set up.
def set_local_assigns!
@migration_template = "migration.rb"
case file_name
@@ -41,7 +44,7 @@ module ActiveRecord
def set_index_names
attributes.each_with_index do |attr, i|
- attr.index_name = [attr, attributes[i - 1]].map{ |a| index_name_for(a) }
+ attr.index_name = [attr, attributes[i - 1]].map { |a| index_name_for(a) }
end
end
@@ -53,7 +56,6 @@ module ActiveRecord
end.to_sym
end
- private
def attributes_with_index
attributes.select { |a| !a.reference? && a.has_index? }
end
diff --git a/activerecord/lib/rails/generators/active_record/model/model_generator.rb b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
index f1ddc61688..61a8d3c100 100644
--- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
@@ -33,7 +33,7 @@ module ActiveRecord
hook_for :test_framework
- protected
+ private
def attributes_with_index
attributes.select { |a| !a.reference? && a.has_index? }
diff --git a/activerecord/test/active_record/connection_adapters/fake_adapter.rb b/activerecord/test/active_record/connection_adapters/fake_adapter.rb
index 43c817e057..b0d8050721 100644
--- a/activerecord/test/active_record/connection_adapters/fake_adapter.rb
+++ b/activerecord/test/active_record/connection_adapters/fake_adapter.rb
@@ -9,7 +9,7 @@ module ActiveRecord
class FakeAdapter < AbstractAdapter
attr_accessor :data_sources, :primary_keys
- @columns = Hash.new { |h,k| h[k] = [] }
+ @columns = Hash.new { |h, k| h[k] = [] }
class << self
attr_reader :columns
end
diff --git a/activerecord/test/assets/schema_dump_5_1.yml b/activerecord/test/assets/schema_dump_5_1.yml
new file mode 100644
index 0000000000..f37977daf2
--- /dev/null
+++ b/activerecord/test/assets/schema_dump_5_1.yml
@@ -0,0 +1,345 @@
+--- !ruby/object:ActiveRecord::ConnectionAdapters::SchemaCache
+columns:
+ posts:
+ - &1 !ruby/object:ActiveRecord::ConnectionAdapters::Column
+ name: id
+ table_name: posts
+ sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
+ sql_type: INTEGER
+ type: :integer
+ limit:
+ precision:
+ scale:
+ 'null': false
+ default:
+ default_function:
+ collation:
+ comment:
+ - &2 !ruby/object:ActiveRecord::ConnectionAdapters::Column
+ name: author_id
+ table_name: posts
+ sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
+ sql_type: integer
+ type: :integer
+ limit:
+ precision:
+ scale:
+ 'null': true
+ default:
+ default_function:
+ collation:
+ comment:
+ - &3 !ruby/object:ActiveRecord::ConnectionAdapters::Column
+ name: title
+ table_name: posts
+ sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
+ sql_type: varchar
+ type: :string
+ limit:
+ precision:
+ scale:
+ 'null': false
+ default:
+ default_function:
+ collation:
+ comment:
+ - &4 !ruby/object:ActiveRecord::ConnectionAdapters::Column
+ name: body
+ table_name: posts
+ sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
+ sql_type: text
+ type: :text
+ limit:
+ precision:
+ scale:
+ 'null': false
+ default:
+ default_function:
+ collation:
+ comment:
+ - &5 !ruby/object:ActiveRecord::ConnectionAdapters::Column
+ name: type
+ table_name: posts
+ sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
+ sql_type: varchar
+ type: :string
+ limit:
+ precision:
+ scale:
+ 'null': true
+ default:
+ default_function:
+ collation:
+ comment:
+ - &6 !ruby/object:ActiveRecord::ConnectionAdapters::Column
+ name: comments_count
+ table_name: posts
+ sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
+ sql_type: integer
+ type: :integer
+ limit:
+ precision:
+ scale:
+ 'null': true
+ default: '0'
+ default_function:
+ collation:
+ comment:
+ - &7 !ruby/object:ActiveRecord::ConnectionAdapters::Column
+ name: taggings_with_delete_all_count
+ table_name: posts
+ sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
+ sql_type: integer
+ type: :integer
+ limit:
+ precision:
+ scale:
+ 'null': true
+ default: '0'
+ default_function:
+ collation:
+ comment:
+ - &8 !ruby/object:ActiveRecord::ConnectionAdapters::Column
+ name: taggings_with_destroy_count
+ table_name: posts
+ sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
+ sql_type: integer
+ type: :integer
+ limit:
+ precision:
+ scale:
+ 'null': true
+ default: '0'
+ default_function:
+ collation:
+ comment:
+ - &9 !ruby/object:ActiveRecord::ConnectionAdapters::Column
+ name: tags_count
+ table_name: posts
+ sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
+ sql_type: integer
+ type: :integer
+ limit:
+ precision:
+ scale:
+ 'null': true
+ default: '0'
+ default_function:
+ collation:
+ comment:
+ - &10 !ruby/object:ActiveRecord::ConnectionAdapters::Column
+ name: tags_with_destroy_count
+ table_name: posts
+ sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
+ sql_type: integer
+ type: :integer
+ limit:
+ precision:
+ scale:
+ 'null': true
+ default: '0'
+ default_function:
+ collation:
+ comment:
+ - &11 !ruby/object:ActiveRecord::ConnectionAdapters::Column
+ name: tags_with_nullify_count
+ table_name: posts
+ sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
+ sql_type: integer
+ type: :integer
+ limit:
+ precision:
+ scale:
+ 'null': true
+ default: '0'
+ default_function:
+ collation:
+ comment:
+columns_hash:
+ posts:
+ id: *1
+ author_id: *2
+ title: *3
+ body: *4
+ type: *5
+ comments_count: *6
+ taggings_with_delete_all_count: *7
+ taggings_with_destroy_count: *8
+ tags_count: *9
+ tags_with_destroy_count: *10
+ tags_with_nullify_count: *11
+primary_keys:
+ posts: id
+data_sources:
+ ar_internal_metadata: true
+ table_with_autoincrement: true
+ accounts: true
+ admin_accounts: true
+ admin_users: true
+ aircraft: true
+ articles: true
+ articles_magazines: true
+ articles_tags: true
+ audit_logs: true
+ authors: true
+ author_addresses: true
+ author_favorites: true
+ auto_id_tests: true
+ binaries: true
+ birds: true
+ books: true
+ booleans: true
+ bulbs: true
+ CamelCase: true
+ cars: true
+ carriers: true
+ categories: true
+ categories_posts: true
+ categorizations: true
+ citations: true
+ clubs: true
+ collections: true
+ colnametests: true
+ columns: true
+ comments: true
+ companies: true
+ content: true
+ content_positions: true
+ vegetables: true
+ computers: true
+ computers_developers: true
+ contracts: true
+ customers: true
+ customer_carriers: true
+ dashboards: true
+ developers: true
+ developers_projects: true
+ dog_lovers: true
+ dogs: true
+ doubloons: true
+ edges: true
+ engines: true
+ entrants: true
+ essays: true
+ events: true
+ eyes: true
+ funny_jokes: true
+ cold_jokes: true
+ friendships: true
+ goofy_string_id: true
+ having: true
+ guids: true
+ guitars: true
+ inept_wizards: true
+ integer_limits: true
+ invoices: true
+ iris: true
+ items: true
+ jobs: true
+ jobs_pool: true
+ keyboards: true
+ legacy_things: true
+ lessons: true
+ lessons_students: true
+ students: true
+ lint_models: true
+ line_items: true
+ lions: true
+ lock_without_defaults: true
+ lock_without_defaults_cust: true
+ magazines: true
+ mateys: true
+ members: true
+ member_details: true
+ member_friends: true
+ memberships: true
+ member_types: true
+ mentors: true
+ minivans: true
+ minimalistics: true
+ mixed_case_monkeys: true
+ mixins: true
+ movies: true
+ notifications: true
+ numeric_data: true
+ orders: true
+ organizations: true
+ owners: true
+ paint_colors: true
+ paint_textures: true
+ parrots: true
+ parrots_pirates: true
+ parrots_treasures: true
+ people: true
+ peoples_treasures: true
+ personal_legacy_things: true
+ pets: true
+ pets_treasures: true
+ pirates: true
+ posts: true
+ serialized_posts: true
+ images: true
+ price_estimates: true
+ products: true
+ product_types: true
+ projects: true
+ randomly_named_table1: true
+ randomly_named_table2: true
+ randomly_named_table3: true
+ ratings: true
+ readers: true
+ references: true
+ shape_expressions: true
+ ships: true
+ ship_parts: true
+ prisoners: true
+ shop_accounts: true
+ speedometers: true
+ sponsors: true
+ string_key_objects: true
+ subscribers: true
+ subscriptions: true
+ tags: true
+ taggings: true
+ tasks: true
+ topics: true
+ toys: true
+ traffic_lights: true
+ treasures: true
+ tuning_pegs: true
+ tyres: true
+ variants: true
+ vertices: true
+ warehouse-things: true
+ circles: true
+ squares: true
+ triangles: true
+ non_poly_ones: true
+ non_poly_twos: true
+ men: true
+ faces: true
+ interests: true
+ zines: true
+ wheels: true
+ countries: true
+ treaties: true
+ countries_treaties: true
+ liquid: true
+ molecules: true
+ electrons: true
+ weirds: true
+ nodes: true
+ trees: true
+ hotels: true
+ departments: true
+ cake_designers: true
+ drink_designers: true
+ chefs: true
+ recipes: true
+ records: true
+ overloaded_types: true
+ users: true
+ test_with_keyword_column_name: true
+ fk_test_has_pk: true
+ fk_test_has_fk: true
+version: 0
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index b164c12c89..9828e682ef 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -32,57 +32,49 @@ module ActiveRecord
def test_tables
tables = nil
- ActiveSupport::Deprecation.silence { tables = @connection.tables }
- assert tables.include?("accounts")
- assert tables.include?("authors")
- assert tables.include?("tasks")
- assert tables.include?("topics")
+ tables = @connection.tables
+ assert_includes tables, "accounts"
+ assert_includes tables, "authors"
+ assert_includes tables, "tasks"
+ assert_includes tables, "topics"
end
def test_table_exists?
- ActiveSupport::Deprecation.silence do
- assert @connection.table_exists?("accounts")
- assert !@connection.table_exists?("nonexistingtable")
- assert !@connection.table_exists?(nil)
- end
- end
-
- def test_table_exists_checking_both_tables_and_views_is_deprecated
- assert_deprecated { @connection.table_exists?("accounts") }
+ assert @connection.table_exists?("accounts")
+ assert @connection.table_exists?(:accounts)
+ assert_not @connection.table_exists?("nonexistingtable")
+ assert_not @connection.table_exists?("'")
+ assert_not @connection.table_exists?(nil)
end
def test_data_sources
data_sources = @connection.data_sources
- assert data_sources.include?("accounts")
- assert data_sources.include?("authors")
- assert data_sources.include?("tasks")
- assert data_sources.include?("topics")
+ assert_includes data_sources, "accounts"
+ assert_includes data_sources, "authors"
+ assert_includes data_sources, "tasks"
+ assert_includes data_sources, "topics"
end
def test_data_source_exists?
assert @connection.data_source_exists?("accounts")
assert @connection.data_source_exists?(:accounts)
assert_not @connection.data_source_exists?("nonexistingtable")
+ assert_not @connection.data_source_exists?("'")
assert_not @connection.data_source_exists?(nil)
end
def test_indexes
idx_name = "accounts_idx"
- if @connection.respond_to?(:indexes)
- indexes = @connection.indexes("accounts")
- assert indexes.empty?
-
- @connection.add_index :accounts, :firm_id, name: idx_name
- indexes = @connection.indexes("accounts")
- assert_equal "accounts", indexes.first.table
- assert_equal idx_name, indexes.first.name
- assert !indexes.first.unique
- assert_equal ["firm_id"], indexes.first.columns
- else
- warn "#{@connection.class} does not respond to #indexes"
- end
+ indexes = @connection.indexes("accounts")
+ assert indexes.empty?
+ @connection.add_index :accounts, :firm_id, name: idx_name
+ indexes = @connection.indexes("accounts")
+ assert_equal "accounts", indexes.first.table
+ assert_equal idx_name, indexes.first.name
+ assert !indexes.first.unique
+ assert_equal ["firm_id"], indexes.first.columns
ensure
@connection.remove_index(:accounts, name: idx_name) rescue nil
end
@@ -184,6 +176,14 @@ module ActiveRecord
assert_not_nil error.cause
end
+ def test_not_null_violations_are_translated_to_specific_exception
+ error = assert_raises(ActiveRecord::NotNullViolation) do
+ Post.create
+ end
+
+ assert_not_nil error.cause
+ end
+
unless current_adapter?(:SQLite3Adapter)
def test_foreign_key_violations_are_translated_to_specific_exception
error = assert_raises(ActiveRecord::InvalidForeignKey) do
@@ -220,6 +220,14 @@ module ActiveRecord
assert_not_nil error.cause
end
+
+ def test_numeric_value_out_of_ranges_are_translated_to_specific_exception
+ error = assert_raises(ActiveRecord::RangeError) do
+ Book.connection.create("INSERT INTO books(author_id) VALUES (2147483648)")
+ end
+
+ assert_not_nil error.cause
+ end
end
def test_disable_referential_integrity
@@ -248,8 +256,8 @@ module ActiveRecord
author = Author.create!(name: "john")
Post.create!(author: author, title: "foo", body: "bar")
query = author.posts.where(title: "foo").select(:title)
- assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bound_attributes))
- assert_equal({"title" => "foo"}, @connection.select_one(query))
+ assert_equal({ "title" => "foo" }, @connection.select_one(query.arel, nil, query.bound_attributes))
+ assert_equal({ "title" => "foo" }, @connection.select_one(query))
assert @connection.select_all(query).is_a?(ActiveRecord::Result)
assert_equal "foo", @connection.select_value(query)
assert_equal ["foo"], @connection.select_values(query)
@@ -258,8 +266,8 @@ module ActiveRecord
def test_select_methods_passing_a_relation
Post.create!(title: "foo", body: "bar")
query = Post.where(title: "foo").select(:title)
- assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bound_attributes))
- assert_equal({"title" => "foo"}, @connection.select_one(query))
+ assert_equal({ "title" => "foo" }, @connection.select_one(query.arel, nil, query.bound_attributes))
+ assert_equal({ "title" => "foo" }, @connection.select_one(query))
assert @connection.select_all(query).is_a?(ActiveRecord::Result)
assert_equal "foo", @connection.select_value(query)
assert_equal ["foo"], @connection.select_values(query)
@@ -271,25 +279,15 @@ module ActiveRecord
unless current_adapter?(:PostgreSQLAdapter)
def test_log_invalid_encoding
- error = assert_raise ActiveRecord::StatementInvalid do
+ error = assert_raises RuntimeError do
@connection.send :log, "SELECT 'ы' FROM DUAL" do
raise "ы".force_encoding(Encoding::ASCII_8BIT)
end
end
- assert_not_nil error.cause
- end
- end
-
- if current_adapter?(:Mysql2Adapter, :SQLite3Adapter)
- def test_tables_returning_both_tables_and_views_is_deprecated
- assert_deprecated { @connection.tables }
+ assert_not_nil error.message
end
end
-
- def test_passing_arguments_to_tables_is_deprecated
- assert_deprecated { @connection.tables(:books) }
- end
end
class AdapterTestWithoutTransaction < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
index e7e37cac36..2a528b2cb1 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -28,12 +28,15 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15)) "
assert_equal expected, add_index(:people, [:last_name, :first_name], length: 15)
+ assert_equal expected, add_index(:people, ["last_name", "first_name"], length: 15)
expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`) "
- assert_equal expected, add_index(:people, [:last_name, :first_name], length: {last_name: 15})
+ assert_equal expected, add_index(:people, [:last_name, :first_name], length: { last_name: 15 })
+ assert_equal expected, add_index(:people, ["last_name", "first_name"], length: { last_name: 15 })
expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10)) "
- assert_equal expected, add_index(:people, [:last_name, :first_name], length: {last_name: 15, first_name: 10})
+ assert_equal expected, add_index(:people, [:last_name, :first_name], length: { last_name: 15, first_name: 10 })
+ assert_equal expected, add_index(:people, ["last_name", :first_name], length: { last_name: 15, "first_name" => 10 })
%w(SPATIAL FULLTEXT UNIQUE).each do |type|
expected = "CREATE #{type} INDEX `index_people_on_last_name` ON `people` (`last_name`) "
diff --git a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
index 4d81877a9a..50ba9ab831 100644
--- a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
@@ -30,7 +30,7 @@ class Mysql2CaseSensitivityTest < ActiveRecord::Mysql2TestCase
CollationTest.create!(string_cs_column: "A")
invalid = CollationTest.new(string_cs_column: "a")
queries = assert_sql { invalid.save }
- cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/)}
+ cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) }
assert_match(/lower/i, cs_uniqueness_query)
end
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index a3f83e73a6..c1de2218e2 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -63,6 +63,27 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
assert @connection.active?
end
+ def test_execute_after_disconnect
+ @connection.disconnect!
+
+ assert_raise(ActiveRecord::StatementInvalid) do
+ @connection.execute("SELECT 1")
+ end
+ end
+
+ def test_quote_after_disconnect
+ @connection.disconnect!
+
+ assert_raise(Mysql2::Error) do
+ @connection.quote("string")
+ end
+ end
+
+ def test_active_after_disconnect
+ @connection.disconnect!
+ assert_equal false, @connection.active?
+ end
+
def test_mysql_connection_collation_is_configured
assert_equal "utf8_unicode_ci", @connection.show_variable("collation_connection")
assert_equal "utf8_general_ci", ARUnit2Model.connection.show_variable("collation_connection")
@@ -101,7 +122,7 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
def test_passing_arbitary_flags_to_adapter
run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.merge(flags: Mysql2::Client::COMPRESS))
- assert_equal (Mysql2::Client::COMPRESS | Mysql2::Client::FOUND_ROWS), ActiveRecord::Base.connection.raw_connection.query_options[:flags]
+ assert_equal (Mysql2::Client::COMPRESS | Mysql2::Client::FOUND_ROWS), ActiveRecord::Base.connection.raw_connection.query_options[:flags]
end
end
@@ -114,7 +135,7 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
def test_mysql_set_session_variable
run_without_connection do |orig_connection|
- ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: {default_week_format: 3}))
+ ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { default_week_format: 3 }))
session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT"
assert_equal 3, session_mode.rows.first.first.to_i
end
@@ -122,7 +143,7 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
def test_mysql_set_session_variable_to_default
run_without_connection do |orig_connection|
- ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: {default_week_format: :default}))
+ ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { default_week_format: :default }))
global_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.DEFAULT_WEEK_FORMAT"
session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT"
assert_equal global_mode.rows, session_mode.rows
@@ -165,7 +186,7 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
"expected release_advisory_lock to return false when there was no lock to release"
end
- protected
+ private
def test_lock_free(lock_name)
@connection.select_value("SELECT IS_FREE_LOCK(#{@connection.quote(lock_name)})") == 1
diff --git a/activerecord/test/cases/adapters/mysql2/enum_test.rb b/activerecord/test/cases/adapters/mysql2/enum_test.rb
index 9f51223d58..7ad3e3ca2d 100644
--- a/activerecord/test/cases/adapters/mysql2/enum_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/enum_test.rb
@@ -9,11 +9,6 @@ class Mysql2EnumTest < ActiveRecord::Mysql2TestCase
assert_equal 8, column.limit
end
- def test_should_not_be_blob_or_text_column
- column = EnumTest.columns_hash["enum_column"]
- assert_not column.blob_or_text_column?
- end
-
def test_should_not_be_unsigned
column = EnumTest.columns_hash["enum_column"]
assert_not column.unsigned?
diff --git a/activerecord/test/cases/adapters/mysql2/json_test.rb b/activerecord/test/cases/adapters/mysql2/json_test.rb
index 2f7dc3f7ef..6954006003 100644
--- a/activerecord/test/cases/adapters/mysql2/json_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/json_test.rb
@@ -51,11 +51,11 @@ if ActiveRecord::Base.connection.supports_json?
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 = 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)
+ assert_equal({ "string" => "foo", "symbol" => "bar" }, x.reload.payload)
end
def test_type_cast_json
@@ -63,12 +63,12 @@ if ActiveRecord::Base.connection.supports_json?
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({ "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"})))
+ 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
@@ -81,31 +81,47 @@ if ActiveRecord::Base.connection.supports_json?
def test_select
@connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')"
x = JsonDataType.first
- assert_equal({"k" => "v"}, x.payload)
+ 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)
+ 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_equal(nil, x.payload)
+ 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)
+ 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"]
+ x.payload = ["v1", { "k2" => "v2" }, "v3"]
assert x.save!
end
diff --git a/activerecord/test/cases/adapters/mysql2/legacy_migration_test.rb b/activerecord/test/cases/adapters/mysql2/legacy_migration_test.rb
new file mode 100644
index 0000000000..5d3125c2be
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/legacy_migration_test.rb
@@ -0,0 +1,60 @@
+require "cases/helper"
+
+class MysqlLegacyMigrationTest < ActiveRecord::Mysql2TestCase
+ self.use_transactional_tests = false
+
+ class GenerateTableWithoutBigint < ActiveRecord::Migration[5.0]
+ def change
+ create_table :legacy_integer_pk do |table|
+ table.string :foo
+ end
+
+ create_table :override_pk, id: :bigint do |table|
+ table.string :bar
+ end
+ end
+ end
+
+ def setup
+ super
+ @connection = ActiveRecord::Base.connection
+
+ @migration_verbose_old = ActiveRecord::Migration.verbose
+ ActiveRecord::Migration.verbose = false
+
+ migrations = [GenerateTableWithoutBigint.new(nil, 1)]
+
+ ActiveRecord::Migrator.new(:up, migrations).migrate
+ end
+
+ def teardown
+ ActiveRecord::Migration.verbose = @migration_verbose_old
+ @connection.drop_table("legacy_integer_pk")
+ @connection.drop_table("override_pk")
+ ActiveRecord::SchemaMigration.delete_all rescue nil
+ super
+ end
+
+ def test_create_table_uses_integer_as_pkey_by_default
+ col = column(:legacy_integer_pk, :id)
+ assert_equal "int(11)", sql_type_for(col)
+ assert col.auto_increment?
+ end
+
+ def test_create_tables_respects_pk_column_type_override
+ col = column(:override_pk, :id)
+ assert_equal "bigint(20)", sql_type_for(col)
+ end
+
+ private
+
+ def column(table_name, column_name)
+ ActiveRecord::Base.connection
+ .columns(table_name.to_s)
+ .detect { |c| c.name == column_name.to_s }
+ end
+
+ def sql_type_for(col)
+ col && col.sql_type
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
index 69336eb906..aab3dcb724 100644
--- a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
@@ -65,6 +65,19 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
@conn.columns_for_distinct("posts.id", [order])
end
+ def test_errors_for_bigint_fks_on_integer_pk_table
+ # table old_cars has primary key of integer
+
+ error = assert_raises(ActiveRecord::MismatchedForeignKey) do
+ @conn.add_reference :engines, :old_car
+ @conn.add_foreign_key :engines, :old_cars
+ end
+
+ assert_match "Column `old_car_id` on table `engines` has a type of `bigint(20)`", error.message
+ assert_not_nil error.cause
+ @conn.exec_query("ALTER TABLE engines DROP COLUMN old_car_id")
+ end
+
private
def with_example_table(definition = "id int auto_increment primary key, number int, data varchar(255)", &block)
diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
index 7736113a19..2c778b1150 100644
--- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
@@ -30,11 +30,11 @@ class Mysql2ReservedWordTest < ActiveRecord::Mysql2TestCase
# 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"
+ 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
@@ -130,20 +130,20 @@ class Mysql2ReservedWordTest < ActiveRecord::Mysql2TestCase
#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
+ # 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
+ # 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)
+ # 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
diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb
index 3c08a4ed1f..1fad5585de 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb
@@ -76,11 +76,11 @@ module ActiveRecord
table = "key_tests"
indexes = @connection.indexes(table).sort_by(&:name)
- assert_equal 3,indexes.size
+ assert_equal 3, indexes.size
- index_a = indexes.select{|i| i.name == index_a_name}[0]
- index_b = indexes.select{|i| i.name == index_b_name}[0]
- index_c = indexes.select{|i| i.name == index_c_name}[0]
+ index_a = indexes.select { |i| i.name == index_a_name }[0]
+ index_b = indexes.select { |i| i.name == index_b_name }[0]
+ index_c = indexes.select { |i| i.name == index_c_name }[0]
assert_equal :btree, index_a.using
assert_nil index_a.type
assert_equal :btree, index_b.using
@@ -121,6 +121,6 @@ class Mysql2AnsiQuotesTest < ActiveRecord::Mysql2TestCase
def test_foreign_keys_method_with_ansi_quotes
fks = @connection.foreign_keys("lessons_students")
assert_equal([["lessons_students", "students", :cascade]],
- fks.map {|fk| [fk.from_table, fk.to_table, fk.on_delete] })
+ fks.map { |fk| [fk.from_table, fk.to_table, fk.on_delete] })
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/transaction_test.rb b/activerecord/test/cases/adapters/mysql2/transaction_test.rb
index edd5353ee3..16101e38cb 100644
--- a/activerecord/test/cases/adapters/mysql2/transaction_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/transaction_test.rb
@@ -10,6 +10,8 @@ module ActiveRecord
end
setup do
+ @abort, Thread.abort_on_exception = Thread.abort_on_exception, false
+
@connection = ActiveRecord::Base.connection
@connection.clear_cache!
@@ -25,30 +27,34 @@ module ActiveRecord
teardown do
@connection.drop_table "samples", if_exists: true
+
+ Thread.abort_on_exception = @abort
end
test "raises Deadlocked when a deadlock is encountered" do
assert_raises(ActiveRecord::Deadlocked) do
+ barrier = Concurrent::CyclicBarrier.new(2)
+
s1 = Sample.create value: 1
s2 = Sample.create value: 2
thread = Thread.new do
Sample.transaction do
s1.lock!
- sleep 1
+ barrier.wait
s2.update_attributes value: 1
end
end
- sleep 0.5
-
- Sample.transaction do
- s2.lock!
- sleep 1
- s1.update_attributes value: 2
+ begin
+ Sample.transaction do
+ s2.lock!
+ barrier.wait
+ s1.update_attributes value: 2
+ end
+ ensure
+ thread.join
end
-
- thread.join
end
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
index 3df11ce11b..a0823be143 100644
--- a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
@@ -15,6 +15,7 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase
t.bigint :unsigned_bigint, unsigned: true
t.float :unsigned_float, unsigned: true
t.decimal :unsigned_decimal, unsigned: true, precision: 10, scale: 2
+ t.column :unsigned_zerofill, "int unsigned zerofill"
end
end
@@ -34,10 +35,10 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase
assert_raise(ActiveModel::RangeError) do
UnsignedType.create(unsigned_bigint: -10)
end
- assert_raise(ActiveRecord::StatementInvalid) do
+ assert_raise(ActiveRecord::RangeError) do
UnsignedType.create(unsigned_float: -10.0)
end
- assert_raise(ActiveRecord::StatementInvalid) do
+ assert_raise(ActiveRecord::RangeError) do
UnsignedType.create(unsigned_decimal: -10.0)
end
end
@@ -50,7 +51,7 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase
t.unsigned_decimal :unsigned_decimal_t, precision: 10, scale: 2
end
- @connection.columns("unsigned_types").select { |c| /^unsigned_/ === c.name }.each do |column|
+ @connection.columns("unsigned_types").select { |c| /^unsigned_/.match?(c.name) }.each do |column|
assert column.unsigned?
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
index d3c65f3d94..b787de8453 100644
--- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
@@ -39,6 +39,10 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase
expected = %(CREATE INDEX CONCURRENTLY "index_people_on_last_name" ON "people" ("last_name"))
assert_equal expected, add_index(:people, :last_name, algorithm: :concurrently)
+ expected = %(CREATE INDEX "index_people_on_last_name_and_first_name" ON "people" ("last_name" DESC, "first_name" ASC))
+ assert_equal expected, add_index(:people, [:last_name, :first_name], order: { last_name: :desc, first_name: :asc })
+ assert_equal expected, add_index(:people, ["last_name", :first_name], order: { last_name: :desc, "first_name" => :asc })
+
%w(gin gist hash btree).each do |type|
expected = %(CREATE INDEX "index_people_on_last_name" ON "people" USING #{type} ("last_name"))
assert_equal expected, add_index(:people, :last_name, using: type)
diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb
index e496960f21..c78c6178ff 100644
--- a/activerecord/test/cases/adapters/postgresql/array_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/array_test.rb
@@ -16,10 +16,12 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
@connection.transaction do
@connection.create_table("pg_arrays") do |t|
- t.string "tags", array: true
+ t.string "tags", array: true, limit: 255
t.integer "ratings", array: true
t.datetime :datetimes, array: true
t.hstore :hstores, array: true
+ t.decimal :decimals, array: true, default: [], precision: 10, scale: 2
+ t.timestamp :timestamps, array: true, default: [], precision: 6
end
end
PgArray.reset_column_information
@@ -34,7 +36,7 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
def test_column
assert_equal :string, @column.type
- assert_equal "character varying", @column.sql_type
+ assert_equal "character varying(255)", @column.sql_type
assert @column.array?
assert_not @type.binary?
@@ -110,22 +112,23 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
def test_schema_dump_with_shorthand
output = dump_table_schema "pg_arrays"
- assert_match %r[t\.string\s+"tags",\s+array: true], output
+ assert_match %r[t\.string\s+"tags",\s+limit: 255,\s+array: true], output
assert_match %r[t\.integer\s+"ratings",\s+array: true], output
+ assert_match %r[t\.decimal\s+"decimals",\s+precision: 10,\s+scale: 2,\s+default: \[\],\s+array: true], output
end
def test_select_with_strings
@connection.execute "insert into pg_arrays (tags) VALUES ('{1,2,3}')"
x = PgArray.first
- assert_equal(["1","2","3"], x.tags)
+ assert_equal(["1", "2", "3"], x.tags)
end
def test_rewrite_with_strings
@connection.execute "insert into pg_arrays (tags) VALUES ('{1,2,3}')"
x = PgArray.first
- x.tags = ["1","2","3","4"]
+ x.tags = ["1", "2", "3", "4"]
x.save!
- assert_equal ["1","2","3","4"], x.reload.tags
+ assert_equal ["1", "2", "3", "4"], x.reload.tags
end
def test_select_with_integers
@@ -163,28 +166,28 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
end
def test_strings_with_quotes
- assert_cycle(:tags, ["this has",'some "s that need to be escaped"'])
+ assert_cycle(:tags, ["this has", 'some "s that need to be escaped"'])
end
def test_strings_with_commas
- assert_cycle(:tags, ["this,has","many,values"])
+ assert_cycle(:tags, ["this,has", "many,values"])
end
def test_strings_with_array_delimiters
- assert_cycle(:tags, ["{","}"])
+ assert_cycle(:tags, ["{", "}"])
end
def test_strings_with_null_strings
- assert_cycle(:tags, ["NULL","NULL"])
+ assert_cycle(:tags, ["NULL", "NULL"])
end
def test_contains_nils
- assert_cycle(:tags, ["1",nil,nil])
+ assert_cycle(:tags, ["1", nil, nil])
end
def test_insert_fixture
tag_values = ["val1", "val2", "val3_with_'_multiple_quote_'_chars"]
- @connection.insert_fixture({"tags" => tag_values}, "pg_arrays" )
+ @connection.insert_fixture({ "tags" => tag_values }, "pg_arrays")
assert_equal(PgArray.last.tags, tag_values)
end
@@ -211,7 +214,7 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
x = PgArray.create!(tags: tags)
x.reload
- assert_equal x.tags_before_type_cast, PgArray.type_for_attribute("tags").serialize(tags)
+ refute x.changed?
end
def test_quoting_non_standard_delimiters
@@ -219,9 +222,10 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
oid = ActiveRecord::ConnectionAdapters::PostgreSQL::OID
comma_delim = oid::Array.new(ActiveRecord::Type::String.new, ",")
semicolon_delim = oid::Array.new(ActiveRecord::Type::String.new, ";")
+ conn = PgArray.connection
- assert_equal %({"hello,",world;}), comma_delim.serialize(strings)
- assert_equal %({hello,;"world;"}), semicolon_delim.serialize(strings)
+ assert_equal %({"hello,",world;}), conn.type_cast(comma_delim.serialize(strings))
+ assert_equal %({hello,;"world;"}), conn.type_cast(semicolon_delim.serialize(strings))
end
def test_mutate_array
@@ -290,6 +294,12 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
assert_equal record.tags, record.reload.tags
end
+ def test_where_by_attribute_with_array
+ tags = ["black", "blue"]
+ record = PgArray.create!(tags: tags)
+ assert_equal record, PgArray.where(tags: tags).take
+ end
+
def test_uniqueness_validation
klass = Class.new(PgArray) do
validates_uniqueness_of :tags
@@ -305,6 +315,21 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
assert_equal ["has already been taken"], e2.errors[:tags], "Should have uniqueness message for tags"
end
+ def test_encoding_arrays_of_utf8_strings
+ arrays_of_utf8_strings = %w(nový ファイル)
+ assert_equal arrays_of_utf8_strings, @type.deserialize(@type.serialize(arrays_of_utf8_strings))
+ assert_equal [arrays_of_utf8_strings], @type.deserialize(@type.serialize([arrays_of_utf8_strings]))
+ end
+
+ def test_precision_is_respected_on_timestamp_columns
+ time = Time.now.change(usec: 123)
+ record = PgArray.create!(timestamps: [time])
+
+ assert_equal 123, record.timestamps.first.usec
+ record.reload
+ assert_equal 123, record.timestamps.first.usec
+ end
+
private
def assert_cycle(field, array)
# test creation
diff --git a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
index f646e59848..7712e809a2 100644
--- a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
@@ -65,10 +65,11 @@ class PostgresqlBitStringTest < ActiveRecord::PostgreSQLTestCase
end
def test_roundtrip
- PostgresqlBitString.create! a_bit: "00001010", a_bit_varying: "0101"
- record = PostgresqlBitString.first
+ record = PostgresqlBitString.create!(a_bit: "00001010", a_bit_varying: "0101")
assert_equal "00001010", record.a_bit
assert_equal "0101", record.a_bit_varying
+ assert_nil record.another_bit
+ assert_nil record.another_bit_varying
record.a_bit = "11111111"
record.a_bit_varying = "0xF"
diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
index dc0df8715a..5c207116c4 100644
--- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
@@ -52,7 +52,7 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase
end
def test_type_case_nil
- assert_equal(nil, @type.deserialize(nil))
+ assert_nil(@type.deserialize(nil))
end
def test_read_value
@@ -66,7 +66,7 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase
def test_read_nil_value
@connection.execute "insert into bytea_data_type (payload) VALUES (null)"
record = ByteaDataType.first
- assert_equal(nil, record.payload)
+ assert_nil(record.payload)
record.delete
end
@@ -106,8 +106,8 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase
def test_write_nil
record = ByteaDataType.create(payload: nil)
assert_not record.new_record?
- assert_equal(nil, record.payload)
- assert_equal(nil, ByteaDataType.where(id: record.id).first.payload)
+ assert_nil(record.payload)
+ assert_nil(ByteaDataType.where(id: record.id).first.payload)
end
class Serializer
diff --git a/activerecord/test/cases/adapters/postgresql/case_insensitive_test.rb b/activerecord/test/cases/adapters/postgresql/case_insensitive_test.rb
new file mode 100644
index 0000000000..03b44feab6
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/case_insensitive_test.rb
@@ -0,0 +1,26 @@
+require "cases/helper"
+
+class PostgresqlCaseInsensitiveTest < ActiveRecord::PostgreSQLTestCase
+ class Default < ActiveRecord::Base; end
+
+ def test_case_insensitiveness
+ connection = ActiveRecord::Base.connection
+ table = Default.arel_table
+
+ column = Default.columns_hash["char1"]
+ comparison = connection.case_insensitive_comparison table, :char1, column, nil
+ assert_match(/lower/i, comparison.to_sql)
+
+ column = Default.columns_hash["char2"]
+ comparison = connection.case_insensitive_comparison table, :char2, column, nil
+ assert_match(/lower/i, comparison.to_sql)
+
+ column = Default.columns_hash["char3"]
+ comparison = connection.case_insensitive_comparison table, :char3, column, nil
+ assert_match(/lower/i, comparison.to_sql)
+
+ column = Default.columns_hash["multiline_default"]
+ comparison = connection.case_insensitive_comparison table, :multiline_default, column, nil
+ assert_match(/lower/i, comparison.to_sql)
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index d01f601031..e916d15f7f 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -90,7 +90,7 @@ module ActiveRecord
end
def test_tables_logs_name
- ActiveSupport::Deprecation.silence { @connection.tables("hello") }
+ @connection.tables
assert_equal "SCHEMA", @subscriber.logged[0][1]
end
@@ -100,7 +100,7 @@ module ActiveRecord
end
def test_table_exists_logs_name
- ActiveSupport::Deprecation.silence { @connection.table_exists?("items") }
+ @connection.table_exists?("items")
assert_equal "SCHEMA", @subscriber.logged[0][1]
end
@@ -184,7 +184,7 @@ module ActiveRecord
def test_set_session_variable_true
run_without_connection do |orig_connection|
- ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: {debug_print_plan: true}))
+ ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { debug_print_plan: true }))
set_true = ActiveRecord::Base.connection.exec_query "SHOW DEBUG_PRINT_PLAN"
assert_equal set_true.rows, [["on"]]
end
@@ -192,7 +192,7 @@ module ActiveRecord
def test_set_session_variable_false
run_without_connection do |orig_connection|
- ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: {debug_print_plan: false}))
+ ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { debug_print_plan: false }))
set_false = ActiveRecord::Base.connection.exec_query "SHOW DEBUG_PRINT_PLAN"
assert_equal set_false.rows, [["off"]]
end
@@ -201,14 +201,14 @@ module ActiveRecord
def test_set_session_variable_nil
run_without_connection do |orig_connection|
# This should be a no-op that does not raise an error
- ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: {debug_print_plan: nil}))
+ ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { debug_print_plan: nil }))
end
end
def test_set_session_variable_default
run_without_connection do |orig_connection|
# This should execute a query that does not raise an error
- ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: {debug_print_plan: :default}))
+ ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { debug_print_plan: :default }))
end
end
@@ -224,14 +224,14 @@ module ActiveRecord
got_lock = @connection.get_advisory_lock(lock_id)
assert got_lock, "get_advisory_lock should have returned true but it didn't"
- advisory_lock = @connection.query(list_advisory_locks).find {|l| l[1] == lock_id}
+ advisory_lock = @connection.query(list_advisory_locks).find { |l| l[1] == lock_id }
assert advisory_lock,
"expected to find an advisory lock with lock_id #{lock_id} but there wasn't one"
released_lock = @connection.release_advisory_lock(lock_id)
assert released_lock, "expected release_advisory_lock to return true but it didn't"
- advisory_locks = @connection.query(list_advisory_locks).select {|l| l[1] == lock_id}
+ advisory_locks = @connection.query(list_advisory_locks).select { |l| l[1] == lock_id }
assert_empty advisory_locks,
"expected to have released advisory lock with lock_id #{lock_id} but it was still held"
end
@@ -245,7 +245,7 @@ module ActiveRecord
end
end
- protected
+ private
def with_warning_suppression
log_level = @connection.client_min_messages
diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
index 27a7467326..c1f3a4ae2c 100644
--- a/activerecord/test/cases/adapters/postgresql/geometric_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
@@ -27,22 +27,10 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase
t.point :legacy_y, default: [12.2, 13.3]
t.point :legacy_z, default: "(14.4,15.5)"
end
- @connection.create_table("deprecated_points") do |t|
- t.point :x
- end
end
teardown do
@connection.drop_table "postgresql_points", if_exists: true
- @connection.drop_table "deprecated_points", if_exists: true
- end
-
- class DeprecatedPoint < ActiveRecord::Base; end
-
- def test_deprecated_legacy_type
- assert_deprecated do
- DeprecatedPoint.new
- end
end
def test_column
@@ -108,7 +96,7 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase
assert_nothing_raised { PostgresqlPoint.new(x: "") }
p = PostgresqlPoint.new(x: "")
- assert_equal nil, p.x
+ assert_nil p.x
end
def test_array_of_points_round_trip
diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
index 817dfbf705..f9cce10fb8 100644
--- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
@@ -10,6 +10,12 @@ if ActiveRecord::Base.connection.supports_extensions?
store_accessor :settings, :language, :timezone
end
+ class FakeParameters
+ def to_unsafe_h
+ { "hi" => "hi" }
+ end
+ end
+
def setup
@connection = ActiveRecord::Base.connection
@@ -38,7 +44,7 @@ if ActiveRecord::Base.connection.supports_extensions?
def test_hstore_included_in_extensions
assert @connection.respond_to?(:extensions), "connection should have a list of extensions"
- assert @connection.extensions.include?("hstore"), "extension list should include hstore"
+ assert_includes @connection.extensions, "hstore", "extension list should include hstore"
end
def test_disable_enable_hstore
@@ -64,8 +70,8 @@ if ActiveRecord::Base.connection.supports_extensions?
@connection.add_column "hstores", "permissions", :hstore, default: '"users"=>"read", "articles"=>"write"'
Hstore.reset_column_information
- assert_equal({"users"=>"read", "articles"=>"write"}, Hstore.column_defaults["permissions"])
- assert_equal({"users"=>"read", "articles"=>"write"}, Hstore.new.permissions)
+ assert_equal({ "users" => "read", "articles" => "write" }, Hstore.column_defaults["permissions"])
+ assert_equal({ "users" => "read", "articles" => "write" }, Hstore.new.permissions)
ensure
Hstore.reset_column_information
end
@@ -103,18 +109,18 @@ if ActiveRecord::Base.connection.supports_extensions?
end
def test_cast_value_on_write
- x = Hstore.new tags: {"bool" => true, "number" => 5}
- assert_equal({"bool" => true, "number" => 5}, x.tags_before_type_cast)
- assert_equal({"bool" => "true", "number" => "5"}, x.tags)
+ x = Hstore.new tags: { "bool" => true, "number" => 5 }
+ assert_equal({ "bool" => true, "number" => 5 }, x.tags_before_type_cast)
+ assert_equal({ "bool" => "true", "number" => "5" }, x.tags)
x.save
- assert_equal({"bool" => "true", "number" => "5"}, x.reload.tags)
+ assert_equal({ "bool" => "true", "number" => "5" }, x.reload.tags)
end
def test_type_cast_hstore
- assert_equal({"1" => "2"}, @type.deserialize("\"1\"=>\"2\""))
+ assert_equal({ "1" => "2" }, @type.deserialize("\"1\"=>\"2\""))
assert_equal({}, @type.deserialize(""))
- assert_equal({"key"=>nil}, @type.deserialize("key => NULL"))
- assert_equal({"c"=>"}",'"a"'=>'b "a b'}, @type.deserialize(%q(c=>"}", "\"a\""=>"b \"a b")))
+ 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_with_store_accessors
@@ -165,48 +171,67 @@ if ActiveRecord::Base.connection.supports_extensions?
assert_not hstore.changed?
end
+ def test_dirty_from_user_equal
+ settings = { "alongkey" => "anything", "key" => "value" }
+ hstore = Hstore.create!(settings: settings)
+
+ hstore.settings = { "key" => "value", "alongkey" => "anything" }
+ assert_equal settings, hstore.settings
+ refute hstore.changed?
+ end
+
+ def test_hstore_dirty_from_database_equal
+ settings = { "alongkey" => "anything", "key" => "value" }
+ hstore = Hstore.create!(settings: settings)
+ hstore.reload
+
+ assert_equal settings, hstore.settings
+ hstore.settings = settings
+ refute hstore.changed?
+ end
+
def test_gen1
- assert_equal('" "=>""', @type.serialize(" "=>""))
+ assert_equal('" "=>""', @type.serialize(" " => ""))
end
def test_gen2
- assert_equal('","=>""', @type.serialize(","=>""))
+ assert_equal('","=>""', @type.serialize("," => ""))
end
def test_gen3
- assert_equal('"="=>""', @type.serialize("="=>""))
+ assert_equal('"="=>""', @type.serialize("=" => ""))
end
def test_gen4
- assert_equal('">"=>""', @type.serialize(">"=>""))
+ assert_equal('">"=>""', @type.serialize(">" => ""))
end
def test_parse1
- assert_equal({"a"=>nil,"b"=>nil,"c"=>"NuLl","null"=>"c"}, @type.deserialize('a=>null,b=>NuLl,c=>"NuLl",null=>c'))
+ assert_equal({ "a" => nil, "b" => nil, "c" => "NuLl", "null" => "c" }, @type.deserialize('a=>null,b=>NuLl,c=>"NuLl",null=>c'))
end
def test_parse2
- assert_equal({" " => " "}, @type.deserialize("\\ =>\\ "))
+ assert_equal({ " " => " " }, @type.deserialize("\\ =>\\ "))
end
def test_parse3
- assert_equal({"=" => ">"}, @type.deserialize("==>>"))
+ assert_equal({ "=" => ">" }, @type.deserialize("==>>"))
end
def test_parse4
- assert_equal({"=a"=>"q=w"}, @type.deserialize('\=a=>q=w'))
+ assert_equal({ "=a" => "q=w" }, @type.deserialize('\=a=>q=w'))
end
def test_parse5
- assert_equal({"=a"=>"q=w"}, @type.deserialize('"=a"=>q\=w'))
+ assert_equal({ "=a" => "q=w" }, @type.deserialize('"=a"=>q\=w'))
end
def test_parse6
- assert_equal({"\"a"=>"q>w"}, @type.deserialize('"\"a"=>q>w'))
+ assert_equal({ "\"a" => "q>w" }, @type.deserialize('"\"a"=>q>w'))
end
def test_parse7
- assert_equal({"\"a"=>"q\"w"}, @type.deserialize('\"a=>q"w'))
+ assert_equal({ "\"a" => "q\"w" }, @type.deserialize('\"a=>q"w'))
end
def test_rewrite
@@ -219,19 +244,19 @@ if ActiveRecord::Base.connection.supports_extensions?
def test_select
@connection.execute "insert into hstores (tags) VALUES ('1=>2')"
x = Hstore.first
- assert_equal({"1" => "2"}, x.tags)
+ assert_equal({ "1" => "2" }, x.tags)
end
def test_array_cycle
- assert_array_cycle([{"AA" => "BB", "CC" => "DD"}, {"AA" => nil}])
+ assert_array_cycle([{ "AA" => "BB", "CC" => "DD" }, { "AA" => nil }])
end
def test_array_strings_with_quotes
- assert_array_cycle([{"this has" => 'some "s that need to be escaped"'}])
+ assert_array_cycle([{ "this has" => 'some "s that need to be escaped"' }])
end
def test_array_strings_with_commas
- assert_array_cycle([{"this,has" => "many,values"}])
+ assert_array_cycle([{ "this,has" => "many,values" }])
end
def test_array_strings_with_array_delimiters
@@ -239,17 +264,17 @@ if ActiveRecord::Base.connection.supports_extensions?
end
def test_array_strings_with_null_strings
- assert_array_cycle([{"NULL" => "NULL"}])
+ assert_array_cycle([{ "NULL" => "NULL" }])
end
def test_contains_nils
- assert_array_cycle([{"NULL" => nil}])
+ assert_array_cycle([{ "NULL" => nil }])
end
def test_select_multikey
@connection.execute "insert into hstores (tags) VALUES ('1=>2,2=>3')"
x = Hstore.first
- assert_equal({"1" => "2", "2" => "3"}, x.tags)
+ assert_equal({ "1" => "2", "2" => "3" }, x.tags)
end
def test_create
@@ -303,17 +328,17 @@ if ActiveRecord::Base.connection.supports_extensions?
HstoreWithSerialize.create! tags: TagCollection.new("one" => "two")
record = HstoreWithSerialize.first
assert_instance_of TagCollection, record.tags
- assert_equal({"one" => "two"}, record.tags.to_hash)
+ assert_equal({ "one" => "two" }, record.tags.to_hash)
record.tags = TagCollection.new("three" => "four")
record.save!
- assert_equal({"three" => "four"}, HstoreWithSerialize.first.tags.to_hash)
+ assert_equal({ "three" => "four" }, HstoreWithSerialize.first.tags.to_hash)
end
def test_clone_hstore_with_serialized_attributes
HstoreWithSerialize.create! tags: TagCollection.new("one" => "two")
record = HstoreWithSerialize.first
dupe = record.dup
- assert_equal({"one" => "two"}, dupe.tags.to_hash)
+ assert_equal({ "one" => "two" }, dupe.tags.to_hash)
end
def test_schema_dump_with_shorthand
@@ -321,6 +346,10 @@ if ActiveRecord::Base.connection.supports_extensions?
assert_match %r[t\.hstore "tags",\s+default: {}], output
end
+ def test_supports_to_unsafe_h_values
+ assert_equal("\"hi\"=>\"hi\"", @type.serialize(FakeParameters.new))
+ end
+
private
def assert_array_cycle(array)
# test creation
diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb
index d0e206ae80..93558ac4d2 100644
--- a/activerecord/test/cases/adapters/postgresql/json_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/json_test.rb
@@ -38,11 +38,11 @@ module PostgresqlJSONSharedTestCases
end
def test_default
- @connection.add_column "json_data_type", "permissions", column_type, default: {"users": "read", "posts": ["read", "write"]}
+ @connection.add_column "json_data_type", "permissions", column_type, default: { "users": "read", "posts": ["read", "write"] }
JsonDataType.reset_column_information
- assert_equal({"users"=>"read", "posts"=>["read", "write"]}, JsonDataType.column_defaults["permissions"])
- assert_equal({"users"=>"read", "posts"=>["read", "write"]}, JsonDataType.new.permissions)
+ 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
@@ -68,11 +68,11 @@ module PostgresqlJSONSharedTestCases
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 = 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)
+ assert_equal({ "string" => "foo", "symbol" => "bar" }, x.reload.payload)
end
def test_type_cast_json
@@ -80,12 +80,12 @@ module PostgresqlJSONSharedTestCases
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({ "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"})))
+ 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
@@ -98,31 +98,47 @@ module PostgresqlJSONSharedTestCases
def test_select
@connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')"
x = JsonDataType.first
- assert_equal({"k" => "v"}, x.payload)
+ 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)
+ 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_equal(nil, x.payload)
+ 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)
+ 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"]
+ x.payload = ["v1", { "k2" => "v2" }, "v3"]
assert x.save!
end
diff --git a/activerecord/test/cases/adapters/postgresql/legacy_migration_test.rb b/activerecord/test/cases/adapters/postgresql/legacy_migration_test.rb
new file mode 100644
index 0000000000..082fe95053
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/legacy_migration_test.rb
@@ -0,0 +1,54 @@
+require "cases/helper"
+
+class PostgresqlLegacyMigrationTest < ActiveRecord::PostgreSQLTestCase
+ class GenerateTableWithoutBigserial < ActiveRecord::Migration[5.0]
+ def change
+ create_table :legacy_integer_pk do |table|
+ table.string :foo
+ end
+
+ create_table :override_pk, id: :bigint do |table|
+ table.string :bar
+ end
+ end
+ end
+
+ def setup
+ super
+
+ @migration_verbose_old = ActiveRecord::Migration.verbose
+ ActiveRecord::Migration.verbose = false
+
+ migrations = [GenerateTableWithoutBigserial.new(nil, 1)]
+ ActiveRecord::Migrator.new(:up, migrations).migrate
+ end
+
+ def teardown
+ ActiveRecord::Migration.verbose = @migration_verbose_old
+
+ super
+ end
+
+ def test_create_table_uses_serial_as_pkey_by_default
+ col = column(:legacy_integer_pk, :id)
+ assert_equal "integer", sql_type_for(col)
+ assert col.serial?
+ end
+
+ def test_create_tables_respects_pk_column_type_override
+ col = column(:override_pk, :id)
+ assert_equal "bigint", sql_type_for(col)
+ end
+
+ private
+
+ def column(table_name, column_name)
+ ActiveRecord::Base.connection.
+ columns(table_name.to_s).
+ detect { |c| c.name == column_name.to_s }
+ end
+
+ def sql_type_for(col)
+ col && col.sql_type
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 4b8d06be4b..e6af93a53e 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -88,12 +88,6 @@ module ActiveRecord
assert_equal expect.to_i, result.rows.first.first
end
- def test_sql_for_insert_with_returning_disabled
- connection = connection_without_insert_returning
- sql, binds = connection.sql_for_insert("sql", nil, nil, nil, "binds")
- assert_equal ["sql", "binds"], [sql, binds]
- end
-
def test_serial_sequence
assert_equal "public.accounts_id_seq",
@connection.serial_sequence("accounts", "id")
diff --git a/activerecord/test/cases/adapters/postgresql/prepared_statements_disabled_test.rb b/activerecord/test/cases/adapters/postgresql/prepared_statements_disabled_test.rb
new file mode 100644
index 0000000000..8c62690866
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/prepared_statements_disabled_test.rb
@@ -0,0 +1,25 @@
+require "cases/helper"
+require "models/computer"
+require "models/developer"
+
+class PreparedStatementsDisabledTest < ActiveRecord::PostgreSQLTestCase
+ fixtures :developers
+
+ def setup
+ @conn = ActiveRecord::Base.establish_connection :arunit_without_prepared_statements
+ end
+
+ def teardown
+ @conn.release_connection
+ ActiveRecord::Base.establish_connection :arunit
+ end
+
+ def test_select_query_works_even_when_prepared_statements_are_disabled
+ assert_not Developer.connection.prepared_statements
+
+ david = developers(:david)
+
+ assert_equal david, Developer.where(name: "David").last # With Binds
+ assert_operator Developer.count, :>, 0 # Without Binds
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/prepared_statements_test.rb b/activerecord/test/cases/adapters/postgresql/prepared_statements_test.rb
deleted file mode 100644
index b898929f8a..0000000000
--- a/activerecord/test/cases/adapters/postgresql/prepared_statements_test.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-require "cases/helper"
-require "models/developer"
-
-class PreparedStatementsTest < ActiveRecord::PostgreSQLTestCase
- fixtures :developers
-
- def setup
- @default_prepared_statements = Developer.connection_config[:prepared_statements]
- Developer.connection_config[:prepared_statements] = false
- end
-
- def teardown
- Developer.connection_config[:prepared_statements] = @default_prepared_statements
- end
-
- def nothing_raised_with_falsy_prepared_statements
- assert_nothing_raised do
- Developer.where(id: 1)
- end
- end
-end
diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
index 865a3a5098..141baffa5b 100644
--- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
@@ -18,12 +18,12 @@ module ActiveRecord
end
def test_quote_float_nan
- nan = 0.0/0
+ nan = 0.0 / 0
assert_equal "'NaN'", @conn.quote(nan)
end
def test_quote_float_infinity
- infinity = 1.0/0
+ infinity = 1.0 / 0
assert_equal "'Infinity'", @conn.quote(infinity)
end
@@ -36,7 +36,7 @@ module ActiveRecord
def test_quote_bit_string
value = "'); SELECT * FROM users; /*\n01\n*/--"
type = OID::Bit.new
- assert_equal nil, @conn.quote(type.serialize(value))
+ assert_nil @conn.quote(type.serialize(value))
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb
index 3f819f7bd5..f411884dfd 100644
--- a/activerecord/test/cases/adapters/postgresql/range_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/range_test.rb
@@ -282,6 +282,12 @@ _SQL
assert_raises(ArgumentError) { PostgresqlRange.create!(tstz_range: "(''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'']") }
end
+ def test_where_by_attribute_with_range
+ range = 1..100
+ record = PostgresqlRange.create!(int4_range: range)
+ assert_equal record, PostgresqlRange.where(int4_range: range).take
+ end
+
def test_update_all_with_ranges
PostgresqlRange.create!
diff --git a/activerecord/test/cases/adapters/postgresql/rename_table_test.rb b/activerecord/test/cases/adapters/postgresql/rename_table_test.rb
index 461353d239..e9e7f717ac 100644
--- a/activerecord/test/cases/adapters/postgresql/rename_table_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/rename_table_test.rb
@@ -26,9 +26,9 @@ class PostgresqlRenameTableTest < ActiveRecord::PostgreSQLTestCase
def num_indices_named(name)
@connection.execute(<<-SQL).values.length
- SELECT 1 FROM "pg_index"
- JOIN "pg_class" ON "pg_index"."indexrelid" = "pg_class"."oid"
- WHERE "pg_class"."relname" = '#{name}'
- SQL
+ SELECT 1 FROM "pg_index"
+ JOIN "pg_class" ON "pg_index"."indexrelid" = "pg_class"."oid"
+ WHERE "pg_class"."relname" = '#{name}'
+ SQL
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 22f935f24e..f6a07da85f 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
@@ -100,17 +100,17 @@ class SchemaAuthorizationTest < ActiveRecord::PostgreSQLTestCase
end
def test_tables_in_current_schemas
- assert !@connection.tables.include?(TABLE_NAME)
+ assert_not_includes @connection.tables, TABLE_NAME
USERS.each do |u|
set_session_auth u
- assert @connection.tables.include?(TABLE_NAME)
+ assert_includes @connection.tables, TABLE_NAME
set_session_auth
end
end
private
def set_session_auth(auth = nil)
- @connection.session_auth = auth || "default"
+ @connection.session_auth = auth || "default"
end
def bind_param(value)
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index d821cbafdf..237e9ff6a5 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -130,7 +130,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
ensure
@connection.drop_schema "test_schema3"
end
- assert !@connection.schema_names.include?("test_schema3")
+ assert_not_includes @connection.schema_names, "test_schema3"
end
def test_drop_schema_if_exists
@@ -332,7 +332,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
@connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)"
with_schema_search_path SCHEMA_NAME do
- assert_nothing_raised { @connection.remove_index "things", name: "things_Index"}
+ assert_nothing_raised { @connection.remove_index "things", name: "things_Index" }
end
end
@@ -383,7 +383,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
pk, seq = @connection.pk_and_sequence_for(given)
assert_equal "id", pk, "primary key should be found when table referenced as #{given}"
assert_equal pg_name.new(SCHEMA_NAME, "#{PK_TABLE_NAME}_id_seq"), seq, "sequence name should be found when table referenced as #{given}" if given == %("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}")
- assert_equal pg_name.new(SCHEMA_NAME, UNMATCHED_SEQUENCE_NAME), seq, "sequence name should be found when table referenced as #{given}" if given == %("#{SCHEMA_NAME}"."#{UNMATCHED_PK_TABLE_NAME}")
+ assert_equal pg_name.new(SCHEMA_NAME, UNMATCHED_SEQUENCE_NAME), seq, "sequence name should be found when table referenced as #{given}" if given == %("#{SCHEMA_NAME}"."#{UNMATCHED_PK_TABLE_NAME}")
end
end
@@ -393,7 +393,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
SCHEMA_NAME => SCHEMA_NAME,
%(#{SCHEMA2_NAME},#{SCHEMA_NAME},public) => SCHEMA2_NAME,
%(public,#{SCHEMA2_NAME},#{SCHEMA_NAME}) => "public"
- }.each do |given,expect|
+ }.each do |given, expect|
with_schema_search_path(given) { assert_equal expect, @connection.current_schema }
end
end
@@ -418,7 +418,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
SCHEMA_NAME => true,
SCHEMA2_NAME => true,
"darkside" => false
- }.each do |given,expect|
+ }.each do |given, expect|
assert_equal expect, @connection.schema_exists?(given)
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb
index e7c1d97d16..962450aada 100644
--- a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb
@@ -21,7 +21,7 @@ class PostgresqlTimestampTest < ActiveRecord::PostgreSQLTestCase
@connection.reconnect!
timestamp = PostgresqlTimestampWithZone.find(1)
- assert_equal Time.utc(2010,1,1, 11,0,0), timestamp.time
+ assert_equal Time.utc(2010, 1, 1, 11, 0, 0), timestamp.time
assert_instance_of Time, timestamp.time
end
ensure
@@ -35,7 +35,7 @@ class PostgresqlTimestampTest < ActiveRecord::PostgreSQLTestCase
@connection.execute("SET time zone 'America/Jamaica'", "SCHEMA")
timestamp = PostgresqlTimestampWithZone.find(1)
- assert_equal Time.utc(2010,1,1, 11,0,0), timestamp.time
+ assert_equal Time.utc(2010, 1, 1, 11, 0, 0), timestamp.time
assert_instance_of Time, timestamp.time
end
ensure
diff --git a/activerecord/test/cases/adapters/postgresql/transaction_test.rb b/activerecord/test/cases/adapters/postgresql/transaction_test.rb
index d992e22305..9b42d0383d 100644
--- a/activerecord/test/cases/adapters/postgresql/transaction_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/transaction_test.rb
@@ -1,5 +1,6 @@
require "cases/helper"
require "support/connection_helper"
+require "concurrent/atomic/cyclic_barrier"
module ActiveRecord
class PostgresqlTransactionTest < ActiveRecord::PostgreSQLTestCase
@@ -10,6 +11,8 @@ module ActiveRecord
end
setup do
+ @abort, Thread.abort_on_exception = Thread.abort_on_exception, false
+
@connection = ActiveRecord::Base.connection
@connection.transaction do
@@ -24,35 +27,34 @@ module ActiveRecord
teardown do
@connection.drop_table "samples", if_exists: true
+
+ Thread.abort_on_exception = @abort
end
test "raises SerializationFailure when a serialization failure occurs" do
- with_warning_suppression do
- assert_raises(ActiveRecord::SerializationFailure) do
- thread = Thread.new do
- Sample.transaction isolation: :serializable do
- Sample.delete_all
+ assert_raises(ActiveRecord::SerializationFailure) do
+ before = Concurrent::CyclicBarrier.new(2)
+ after = Concurrent::CyclicBarrier.new(2)
- 10.times do |i|
- sleep 0.1
-
- Sample.create value: i
- end
+ thread = Thread.new do
+ with_warning_suppression do
+ Sample.transaction isolation: :serializable do
+ before.wait
+ Sample.create value: Sample.sum(:value)
+ after.wait
end
end
+ end
- sleep 0.1
-
- Sample.transaction isolation: :serializable do
- Sample.delete_all
-
- 10.times do |i|
- sleep 0.1
-
- Sample.create value: i
+ begin
+ with_warning_suppression do
+ Sample.transaction isolation: :serializable do
+ before.wait
+ Sample.create value: Sample.sum(:value)
+ after.wait
end
end
-
+ ensure
thread.join
end
end
@@ -61,37 +63,40 @@ module ActiveRecord
test "raises Deadlocked when a deadlock is encountered" do
with_warning_suppression do
assert_raises(ActiveRecord::Deadlocked) do
+ barrier = Concurrent::CyclicBarrier.new(2)
+
s1 = Sample.create value: 1
s2 = Sample.create value: 2
thread = Thread.new do
Sample.transaction do
s1.lock!
- sleep 1
+ barrier.wait
s2.update_attributes value: 1
end
end
- sleep 0.5
-
- Sample.transaction do
- s2.lock!
- sleep 1
- s1.update_attributes value: 2
+ begin
+ Sample.transaction do
+ s2.lock!
+ barrier.wait
+ s1.update_attributes value: 2
+ end
+ ensure
+ thread.join
end
-
- thread.join
end
end
end
- protected
+ private
def with_warning_suppression
- log_level = @connection.client_min_messages
- @connection.client_min_messages = "error"
+ log_level = ActiveRecord::Base.connection.client_min_messages
+ ActiveRecord::Base.connection.client_min_messages = "error"
yield
- @connection.client_min_messages = log_level
+ ensure
+ ActiveRecord::Base.connection.client_min_messages = log_level
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
index bd45a9daa0..784d77a8d1 100644
--- a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
@@ -19,7 +19,7 @@ class PostgresqlTypeLookupTest < ActiveRecord::PostgreSQLTestCase
big_array = [123456789123456789]
assert_raises(ActiveModel::RangeError) { int_array.serialize(big_array) }
- assert_equal "{123456789123456789}", bigint_array.serialize(big_array)
+ assert_equal "{123456789123456789}", @connection.type_cast(bigint_array.serialize(big_array))
end
test "range types correctly respect registration of subtypes" do
diff --git a/activerecord/test/cases/adapters/postgresql/utils_test.rb b/activerecord/test/cases/adapters/postgresql/utils_test.rb
index 70f72dd929..9f9e3bda2f 100644
--- a/activerecord/test/cases/adapters/postgresql/utils_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/utils_test.rb
@@ -7,13 +7,13 @@ class PostgreSQLUtilsTest < ActiveRecord::PostgreSQLTestCase
def test_extract_schema_qualified_name
{
- %(table_name) => [nil,"table_name"],
- %("table.name") => [nil,"table.name"],
+ %(table_name) => [nil, "table_name"],
+ %("table.name") => [nil, "table.name"],
%(schema.table_name) => %w{schema table_name},
%("schema".table_name) => %w{schema table_name},
%(schema."table_name") => %w{schema table_name},
%("schema"."table_name") => %w{schema table_name},
- %("even spaces".table) => ["even spaces","table"],
+ %("even spaces".table) => ["even spaces", "table"],
%(schema."table.name") => ["schema", "table.name"]
}.each do |given, expect|
assert_equal Name.new(*expect), extract_schema_qualified_name(given)
@@ -54,9 +54,9 @@ class PostgreSQLNameTest < ActiveRecord::PostgreSQLTestCase
end
test "can be used as hash key" do
- hash = {Name.new("schema", "article_seq") => "success"}
+ hash = { Name.new("schema", "article_seq") => "success" }
assert_equal "success", hash[Name.new("schema", "article_seq")]
- assert_equal nil, hash[Name.new("schema", "articles")]
- assert_equal nil, hash[Name.new("public", "article_seq")]
+ assert_nil hash[Name.new("schema", "articles")]
+ assert_nil hash[Name.new("public", "article_seq")]
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index 321b74c1f5..f34d50e25c 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -9,6 +9,10 @@ module PostgresqlUUIDHelper
def drop_table(name)
connection.drop_table name, if_exists: true
end
+
+ def uuid_function
+ connection.supports_pgcrypto_uuid? ? "gen_random_uuid()" : "uuid_generate_v4()"
+ end
end
class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase
@@ -21,6 +25,7 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase
setup do
enable_extension!("uuid-ossp", connection)
+ enable_extension!("pgcrypto", connection) if connection.supports_pgcrypto_uuid?
connection.create_table "uuid_data_type" do |t|
t.uuid "guid"
@@ -31,14 +36,22 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase
drop_table "uuid_data_type"
end
+ if 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
+ column = UUIDType.columns_hash["thingy"]
+ assert_equal "gen_random_uuid()", column.default_function
+ end
+ end
+
def test_change_column_default
- @connection.add_column :uuid_data_type, :thingy, :uuid, null: false, default: "uuid_generate_v1()"
+ connection.add_column :uuid_data_type, :thingy, :uuid, null: false, default: "uuid_generate_v1()"
UUIDType.reset_column_information
column = UUIDType.columns_hash["thingy"]
assert_equal "uuid_generate_v1()", column.default_function
- @connection.change_column :uuid_data_type, :thingy, :uuid, null: false, default: "uuid_generate_v4()"
-
+ connection.change_column :uuid_data_type, :thingy, :uuid, null: false, default: "uuid_generate_v4()"
UUIDType.reset_column_information
column = UUIDType.columns_hash["thingy"]
assert_equal "uuid_generate_v4()", column.default_function
@@ -58,12 +71,12 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase
def test_treat_blank_uuid_as_nil
UUIDType.create! guid: ""
- assert_equal(nil, UUIDType.last.guid)
+ assert_nil(UUIDType.last.guid)
end
def test_treat_invalid_uuid_as_nil
uuid = UUIDType.create! guid: "foobar"
- assert_equal(nil, uuid.guid)
+ assert_nil(uuid.guid)
end
def test_invalid_uuid_dont_modify_before_type_cast
@@ -155,7 +168,7 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase
# to test dumping tables which columns have defaults with custom functions
connection.execute <<-SQL
CREATE OR REPLACE FUNCTION my_uuid_generator() RETURNS uuid
- AS $$ SELECT * FROM uuid_generate_v4() $$
+ AS $$ SELECT * FROM #{uuid_function} $$
LANGUAGE SQL VOLATILE;
SQL
@@ -164,11 +177,16 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase
t.string "name"
t.uuid "other_uuid_2", default: "my_uuid_generator()"
end
+
+ connection.create_table("pg_uuids_3", id: :uuid) do |t|
+ t.string "name"
+ end
end
teardown do
drop_table "pg_uuids"
drop_table "pg_uuids_2"
+ drop_table "pg_uuids_3"
connection.execute "DROP FUNCTION IF EXISTS my_uuid_generator();"
end
@@ -192,19 +210,49 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase
def test_pk_and_sequence_for_uuid_primary_key
pk, seq = connection.pk_and_sequence_for("pg_uuids")
assert_equal "id", pk
- assert_equal nil, seq
+ assert_nil seq
end
def test_schema_dumper_for_uuid_primary_key
schema = dump_table_schema "pg_uuids"
assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: -> { "uuid_generate_v1\(\)" }/, schema)
- assert_match(/t\.uuid "other_uuid", default: -> { "uuid_generate_v4\(\)" }/, schema)
+ assert_match(/t\.uuid "other_uuid", default: -> { "uuid_generate_v4\(\)" }/, schema)
end
def test_schema_dumper_for_uuid_primary_key_with_custom_default
schema = dump_table_schema "pg_uuids_2"
assert_match(/\bcreate_table "pg_uuids_2", id: :uuid, default: -> { "my_uuid_generator\(\)" }/, schema)
- assert_match(/t\.uuid "other_uuid_2", default: -> { "my_uuid_generator\(\)" }/, schema)
+ assert_match(/t\.uuid "other_uuid_2", default: -> { "my_uuid_generator\(\)" }/, schema)
+ end
+
+ def test_schema_dumper_for_uuid_primary_key_default
+ schema = dump_table_schema "pg_uuids_3"
+ if connection.supports_pgcrypto_uuid?
+ assert_match(/\bcreate_table "pg_uuids_3", id: :uuid, default: -> { "gen_random_uuid\(\)" }/, schema)
+ else
+ assert_match(/\bcreate_table "pg_uuids_3", id: :uuid, default: -> { "uuid_generate_v4\(\)" }/, schema)
+ end
+ end
+
+ if ActiveRecord::Base.connection.supports_pgcrypto_uuid?
+ def test_schema_dumper_for_uuid_primary_key_default_in_legacy_migration
+ @verbose_was = ActiveRecord::Migration.verbose
+ ActiveRecord::Migration.verbose = false
+
+ migration = Class.new(ActiveRecord::Migration[4.2]) do
+ def version; 101 end
+ def migrate(x)
+ create_table("pg_uuids_4", id: :uuid)
+ end
+ end.new
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+
+ schema = dump_table_schema "pg_uuids_4"
+ assert_match(/\bcreate_table "pg_uuids_4", id: :uuid, default: -> { "uuid_generate_v4\(\)" }/, schema)
+ ensure
+ drop_table "pg_uuids_4"
+ ActiveRecord::Migration.verbose = @verbose_was
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
index 70e2f0fec1..91967c1e33 100644
--- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
@@ -11,7 +11,7 @@ class CopyTableTest < ActiveRecord::SQLite3TestCase
end
def test_copy_table(from = "customers", to = "customers2", options = {})
- assert_nothing_raised {copy_table(from, to, options)}
+ assert_nothing_raised { copy_table(from, to, options) }
assert_equal row_count(from), row_count(to)
if block_given?
@@ -25,7 +25,7 @@ class CopyTableTest < ActiveRecord::SQLite3TestCase
def test_copy_table_renaming_column
test_copy_table("customers", "customers2",
- rename: {"name" => "person_name"}) do |from, to, options|
+ rename: { "name" => "person_name" }) do |from, to, options|
expected = column_values(from, "name")
assert_equal expected, column_values(to, "person_name")
assert expected.any?, "No values in table: #{expected.inspect}"
@@ -55,8 +55,8 @@ class CopyTableTest < ActiveRecord::SQLite3TestCase
def test_copy_table_with_id_col_that_is_not_primary_key
test_copy_table("goofy_string_id", "goofy_string_id2") do
- original_id = @connection.columns("goofy_string_id").detect{|col| col.name == "id" }
- copied_id = @connection.columns("goofy_string_id2").detect{|col| col.name == "id" }
+ original_id = @connection.columns("goofy_string_id").detect { |col| col.name == "id" }
+ copied_id = @connection.columns("goofy_string_id2").detect { |col| col.name == "id" }
assert_equal original_id.type, copied_id.type
assert_equal original_id.sql_type, copied_id.sql_type
assert_equal original_id.limit, copied_id.limit
@@ -75,17 +75,17 @@ class CopyTableTest < ActiveRecord::SQLite3TestCase
test_copy_table "binaries", "binaries2"
end
-protected
+private
def copy_table(from, to, options = {})
- @connection.copy_table(from, to, {temporary: true}.merge(options))
+ @connection.copy_table(from, to, { temporary: true }.merge(options))
end
def column_names(table)
- @connection.table_structure(table).map {|column| column["name"]}
+ @connection.table_structure(table).map { |column| column["name"] }
end
def column_values(table, column)
- @connection.select_all("SELECT #{column} FROM #{table} ORDER BY id").map {|row| row[column]}
+ @connection.select_all("SELECT #{column} FROM #{table} ORDER BY id").map { |row| row[column] }
end
def table_indexes_without_name(table)
diff --git a/activerecord/test/cases/adapters/sqlite3/legacy_migration_test.rb b/activerecord/test/cases/adapters/sqlite3/legacy_migration_test.rb
new file mode 100644
index 0000000000..fcca8d66b5
--- /dev/null
+++ b/activerecord/test/cases/adapters/sqlite3/legacy_migration_test.rb
@@ -0,0 +1,59 @@
+require "cases/helper"
+
+class SqliteLegacyMigrationTest < ActiveRecord::SQLite3TestCase
+ self.use_transactional_tests = false
+
+ class GenerateTableWithoutBigint < ActiveRecord::Migration[5.0]
+ def change
+ create_table :legacy_integer_pk do |table|
+ table.string :foo
+ end
+
+ create_table :override_pk, id: :bigint do |table|
+ table.string :bar
+ end
+ end
+ end
+
+ def setup
+ super
+ @connection = ActiveRecord::Base.connection
+
+ @migration_verbose_old = ActiveRecord::Migration.verbose
+ ActiveRecord::Migration.verbose = false
+
+ migrations = [GenerateTableWithoutBigint.new(nil, 1)]
+
+ ActiveRecord::Migrator.new(:up, migrations).migrate
+ end
+
+ def teardown
+ ActiveRecord::Migration.verbose = @migration_verbose_old
+ @connection.drop_table("legacy_integer_pk")
+ @connection.drop_table("override_pk")
+ ActiveRecord::SchemaMigration.delete_all rescue nil
+ super
+ end
+
+ def test_create_table_uses_integer_as_pkey_by_default
+ col = column(:legacy_integer_pk, :id)
+ assert_equal "INTEGER", sql_type_for(col)
+ assert primary_key?(:legacy_integer_pk, "id"), "id is not primary key"
+ end
+
+ private
+
+ def column(table_name, column_name)
+ ActiveRecord::Base.connection
+ .columns(table_name.to_s)
+ .detect { |c| c.name == column_name.to_s }
+ end
+
+ def sql_type_for(col)
+ col && col.sql_type
+ end
+
+ def primary_key?(table_name, column)
+ ActiveRecord::Base.connection.execute("PRAGMA table_info(#{table_name})").find { |col| col["name"] == column }["pk"] == 1
+ end
+end
diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
index 80a37e83ff..9750840051 100644
--- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
@@ -37,7 +37,7 @@ class SQLite3QuotingTest < ActiveRecord::SQLite3TestCase
end
def test_type_cast_nil
- assert_equal nil, @conn.type_cast(nil)
+ assert_nil @conn.type_cast(nil)
end
def test_type_cast_true
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 4800d3d7d5..a6109348cc 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -190,7 +190,7 @@ module ActiveRecord
end
def test_type_cast_should_not_mutate_encoding
- name = "hello".force_encoding(Encoding::ASCII_8BIT)
+ name = "hello".force_encoding(Encoding::ASCII_8BIT)
Owner.create(name: name)
assert_equal Encoding::ASCII_8BIT, name.encoding
ensure
@@ -267,9 +267,9 @@ module ActiveRecord
def test_tables
with_example_table do
- ActiveSupport::Deprecation.silence { assert_equal %w{ ex }, @conn.tables }
+ assert_equal %w{ ex }, @conn.tables
with_example_table "id integer PRIMARY KEY AUTOINCREMENT, number integer", "people" do
- ActiveSupport::Deprecation.silence { assert_equal %w{ ex people }.sort, @conn.tables.sort }
+ assert_equal %w{ ex people }.sort, @conn.tables.sort
end
end
end
@@ -277,12 +277,10 @@ module ActiveRecord
def test_tables_logs_name
sql = <<-SQL
SELECT name FROM sqlite_master
- WHERE type IN ('table','view') AND name <> 'sqlite_sequence'
+ WHERE type = 'table' AND name <> 'sqlite_sequence'
SQL
assert_logged [[sql.squish, "SCHEMA", []]] do
- ActiveSupport::Deprecation.silence do
- @conn.tables("hello")
- end
+ @conn.tables
end
end
@@ -298,12 +296,10 @@ module ActiveRecord
with_example_table do
sql = <<-SQL
SELECT name FROM sqlite_master
- WHERE type IN ('table','view') AND name <> 'sqlite_sequence' AND name = 'ex'
+ WHERE type = 'table' AND name <> 'sqlite_sequence' AND name = 'ex'
SQL
assert_logged [[sql.squish, "SCHEMA", []]] do
- ActiveSupport::Deprecation.silence do
- assert @conn.table_exists?("ex")
- end
+ assert @conn.table_exists?("ex")
end
end
end
@@ -405,7 +401,7 @@ module ActiveRecord
configurations["arunit"]["database"])
statement = ::SQLite3::Statement.new(db,
"CREATE TABLE statement_test (number integer not null)")
- statement.stub(:step, ->{ raise ::SQLite3::BusyException.new("busy") }) do
+ statement.stub(:step, -> { raise ::SQLite3::BusyException.new("busy") }) do
assert_called(statement, :columns, returns: []) do
assert_called(statement, :close) do
::SQLite3::Statement.stub(:new, statement) do
@@ -431,9 +427,9 @@ module ActiveRecord
def with_example_table(definition = nil, table_name = "ex", &block)
definition ||= <<-SQL
- id integer PRIMARY KEY AUTOINCREMENT,
- number integer
- SQL
+ id integer PRIMARY KEY AUTOINCREMENT,
+ number integer
+ SQL
super(@conn, table_name, definition, &block)
end
end
diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb
index a4919c913d..397ac599b9 100644
--- a/activerecord/test/cases/ar_schema_test.rb
+++ b/activerecord/test/cases/ar_schema_test.rb
@@ -17,6 +17,7 @@ if ActiveRecord::Base.connection.supports_migrations?
@connection.drop_table :nep_fruits rescue nil
@connection.drop_table :nep_schema_migrations rescue nil
@connection.drop_table :has_timestamps rescue nil
+ @connection.drop_table :multiple_indexes rescue nil
ActiveRecord::SchemaMigration.delete_all rescue nil
ActiveRecord::Migration.verbose = @original_verbose
end
@@ -53,7 +54,7 @@ if ActiveRecord::Base.connection.supports_migrations?
def test_schema_define_w_table_name_prefix
table_name = ActiveRecord::SchemaMigration.table_name
old_table_name_prefix = ActiveRecord::Base.table_name_prefix
- ActiveRecord::Base.table_name_prefix = "nep_"
+ ActiveRecord::Base.table_name_prefix = "nep_"
ActiveRecord::SchemaMigration.table_name = "nep_#{table_name}"
ActiveRecord::Schema.define(version: 7) do
create_table :fruits do |t|
@@ -65,7 +66,7 @@ if ActiveRecord::Base.connection.supports_migrations?
end
assert_equal 7, ActiveRecord::Migrator::current_version
ensure
- ActiveRecord::Base.table_name_prefix = old_table_name_prefix
+ ActiveRecord::Base.table_name_prefix = old_table_name_prefix
ActiveRecord::SchemaMigration.table_name = table_name
end
@@ -93,6 +94,21 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_equal "20131219224947", ActiveRecord::SchemaMigration.normalize_migration_number("20131219224947")
end
+ def test_schema_load_with_multiple_indexes_for_column_of_different_names
+ ActiveRecord::Schema.define do
+ create_table :multiple_indexes do |t|
+ t.string "foo"
+ t.index ["foo"], name: "multiple_indexes_foo_1"
+ t.index ["foo"], name: "multiple_indexes_foo_2"
+ end
+ end
+
+ indexes = @connection.indexes("multiple_indexes")
+
+ assert_equal 2, indexes.length
+ assert_equal ["multiple_indexes_foo_1", "multiple_indexes_foo_2"], indexes.collect(&:name).sort
+ end
+
def test_timestamps_without_null_set_null_to_false_on_create_table
ActiveRecord::Schema.define do
create_table :has_timestamps do |t|
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 2413bf0c3a..5875a1871f 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -94,7 +94,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
account = model.new
assert_not account.valid?
- assert_equal [{error: :blank}], account.errors.details[:company]
+ assert_equal [{ error: :blank }], account.errors.details[:company]
ensure
ActiveRecord::Base.belongs_to_required_by_default = original_value
end
@@ -111,7 +111,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
account = model.new
assert_not account.valid?
- assert_equal [{error: :blank}], account.errors.details[:company]
+ assert_equal [{ error: :blank }], account.errors.details[:company]
ensure
ActiveRecord::Base.belongs_to_required_by_default = original_value
end
@@ -203,14 +203,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_eager_loading_with_primary_key
Firm.create("name" => "Apple")
Client.create("name" => "Citibank", :firm_name => "Apple")
- citibank_result = Client.all.merge!(where: {name: "Citibank"}, includes: :firm_with_primary_key).first
+ citibank_result = Client.all.merge!(where: { name: "Citibank" }, includes: :firm_with_primary_key).first
assert citibank_result.association(:firm_with_primary_key).loaded?
end
def test_eager_loading_with_primary_key_as_symbol
Firm.create("name" => "Apple")
Client.create("name" => "Citibank", :firm_name => "Apple")
- citibank_result = Client.all.merge!(where: {name: "Citibank"}, includes: :firm_with_primary_key_symbols).first
+ citibank_result = Client.all.merge!(where: { name: "Citibank" }, includes: :firm_with_primary_key_symbols).first
assert citibank_result.association(:firm_with_primary_key_symbols).loaded?
end
@@ -285,12 +285,22 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
end
def test_failing_create!
- client = Client.create!(name: "Jimmy")
+ client = Client.create!(name: "Jimmy")
assert_raise(ActiveRecord::RecordInvalid) { client.create_account! }
assert_not_nil client.account
assert client.account.new_record?
end
+ def test_reloading_the_belonging_object
+ odegy_account = accounts(:odegy_account)
+
+ assert_equal "Odegy", odegy_account.firm.name
+ Company.where(id: odegy_account.firm_id).update_all(name: "ODEGY")
+ assert_equal "Odegy", odegy_account.firm.name
+
+ assert_equal "ODEGY", odegy_account.reload_firm.name
+ end
+
def test_natural_assignment_to_nil
client = Client.find(3)
client.firm = nil
@@ -346,7 +356,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_with_select
assert_equal 1, Company.find(2).firm_with_select.attributes.size
- assert_equal 1, Company.all.merge!(includes: :firm_with_select ).find(2).firm_with_select.attributes.size
+ assert_equal 1, Company.all.merge!(includes: :firm_with_select).find(2).firm_with_select.attributes.size
end
def test_belongs_to_without_counter_cache_option
@@ -626,6 +636,12 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_queries(0) { tagging.super_tag }
end
+ def test_dont_find_target_when_saving_foreign_key_after_stale_association_loaded
+ client = Client.create!(name: "Test client", firm_with_basic_id: Firm.find(1))
+ client.firm_id = Firm.create!(name: "Test firm").id
+ assert_queries(1) { client.save! }
+ end
+
def test_field_name_same_as_foreign_key
computer = Computer.find(1)
assert_not_nil computer.developer, ":foreign key == attribute didn't lock up" # '
@@ -1004,21 +1020,21 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_build_with_block
client = Client.create(name: "Client Company")
- firm = client.build_firm{ |f| f.name = "Agency Company" }
+ firm = client.build_firm { |f| f.name = "Agency Company" }
assert_equal "Agency Company", firm.name
end
def test_create_with_block
client = Client.create(name: "Client Company")
- firm = client.create_firm{ |f| f.name = "Agency Company" }
+ firm = client.create_firm { |f| f.name = "Agency Company" }
assert_equal "Agency Company", firm.name
end
def test_create_bang_with_block
client = Client.create(name: "Client Company")
- firm = client.create_firm!{ |f| f.name = "Agency Company" }
+ firm = client.create_firm! { |f| f.name = "Agency Company" }
assert_equal "Agency Company", firm.name
end
@@ -1041,7 +1057,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
comment.parent = nil
comment.save!
- assert_equal nil, comment.reload.parent
+ assert_nil comment.reload.parent
assert_equal 0, comments(:greetings).reload.children_count
end
@@ -1056,6 +1072,20 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal 1, parent.reload.children_count
end
+ def test_belongs_to_with_out_of_range_value_assigning
+ model = Class.new(Comment) do
+ def self.name; "Temp"; end
+ validates :post, presence: true
+ end
+
+ comment = model.new
+ comment.post_id = 9223372036854775808 # out of range in the bigint
+
+ assert_nil comment.post
+ assert_not comment.valid?
+ assert_equal [{ error: :blank }], comment.errors.details[:post]
+ end
+
def test_polymorphic_with_custom_primary_key
toy = Toy.create!
sponsor = Sponsor.create!(sponsorable: toy)
@@ -1101,12 +1131,6 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
Column.create! record: record
assert_equal 1, Column.count
end
-
- def test_association_force_reload_with_only_true_is_deprecated
- client = Client.find(3)
-
- assert_deprecated { client.firm(true) }
- end
end
class BelongsToWithForeignKeyTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb
index 74876c8cf0..5fd2411f6f 100644
--- a/activerecord/test/cases/associations/callbacks_test.rb
+++ b/activerecord/test/cases/associations/callbacks_test.rb
@@ -109,7 +109,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
def self.name; Project.name; end
has_and_belongs_to_many :developers_with_callbacks,
class_name: "Developer",
- before_add: lambda { |o,r|
+ before_add: lambda { |o, r|
dev = r
new_dev = r.new_record?
}
@@ -159,7 +159,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
activerecord.reload
assert activerecord.developers_with_callbacks.size == 2
end
- activerecord.developers_with_callbacks.flat_map {|d| ["before_removing#{d.id}","after_removing#{d.id}"]}.sort
+ activerecord.developers_with_callbacks.flat_map { |d| ["before_removing#{d.id}", "after_removing#{d.id}"] }.sort
assert activerecord.developers_with_callbacks.clear
assert_predicate activerecord.developers_log, :empty?
end
@@ -176,14 +176,14 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
end
def test_dont_add_if_before_callback_raises_exception
- assert !@david.unchangeable_posts.include?(@authorless)
+ assert_not_includes @david.unchangeable_posts, @authorless
begin
@david.unchangeable_posts << @authorless
rescue Exception
end
assert @david.post_log.empty?
- assert !@david.unchangeable_posts.include?(@authorless)
+ assert_not_includes @david.unchangeable_posts, @authorless
@david.reload
- assert !@david.unchangeable_posts.include?(@authorless)
+ assert_not_includes @david.unchangeable_posts, @authorless
end
end
diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
index d79246a6c7..ddb5c7a4aa 100644
--- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
+++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -16,28 +16,28 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
:categorizations, :people, :categories, :edges, :vertices
def test_eager_association_loading_with_cascaded_two_levels
- authors = Author.all.merge!(includes: {posts: :comments}, order: "authors.id").to_a
+ authors = Author.all.merge!(includes: { posts: :comments }, order: "authors.id").to_a
assert_equal 3, authors.size
assert_equal 5, authors[0].posts.size
assert_equal 3, authors[1].posts.size
- assert_equal 10, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
+ assert_equal 10, authors[0].posts.collect { |post| post.comments.size }.inject(0) { |sum, i| sum + i }
end
def test_eager_association_loading_with_cascaded_two_levels_and_one_level
- authors = Author.all.merge!(includes: [{posts: :comments}, :categorizations], order: "authors.id").to_a
+ authors = Author.all.merge!(includes: [{ posts: :comments }, :categorizations], order: "authors.id").to_a
assert_equal 3, authors.size
assert_equal 5, authors[0].posts.size
assert_equal 3, authors[1].posts.size
- assert_equal 10, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
+ assert_equal 10, authors[0].posts.collect { |post| post.comments.size }.inject(0) { |sum, i| sum + i }
assert_equal 1, authors[0].categorizations.size
assert_equal 2, authors[1].categorizations.size
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
+ 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
+ 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
@@ -50,7 +50,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_cascaded_eager_association_loading_with_join_for_count
- categories = Category.joins(:categorizations).includes([{posts: :comments}, :authors])
+ categories = Category.joins(:categorizations).includes([{ posts: :comments }, :authors])
assert_equal 4, categories.count
assert_equal 4, categories.to_a.count
@@ -82,29 +82,29 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_with_cascaded_two_levels_with_two_has_many_associations
- authors = Author.all.merge!(includes: {posts: [:comments, :categorizations]}, order: "authors.id").to_a
+ authors = Author.all.merge!(includes: { posts: [:comments, :categorizations] }, order: "authors.id").to_a
assert_equal 3, authors.size
assert_equal 5, authors[0].posts.size
assert_equal 3, authors[1].posts.size
- assert_equal 10, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
+ assert_equal 10, authors[0].posts.collect { |post| post.comments.size }.inject(0) { |sum, i| sum + i }
end
def test_eager_association_loading_with_cascaded_two_levels_and_self_table_reference
- authors = Author.all.merge!(includes: {posts: [:comments, :author]}, order: "authors.id").to_a
+ authors = Author.all.merge!(includes: { posts: [:comments, :author] }, order: "authors.id").to_a
assert_equal 3, authors.size
assert_equal 5, authors[0].posts.size
assert_equal authors(:david).name, authors[0].name
- assert_equal [authors(:david).name], authors[0].posts.collect{|post| post.author.name}.uniq
+ assert_equal [authors(:david).name], authors[0].posts.collect { |post| post.author.name }.uniq
end
def test_eager_association_loading_with_cascaded_two_levels_with_condition
- authors = Author.all.merge!(includes: {posts: :comments}, where: "authors.id=1", order: "authors.id").to_a
+ authors = Author.all.merge!(includes: { posts: :comments }, where: "authors.id=1", order: "authors.id").to_a
assert_equal 1, authors.size
assert_equal 5, authors[0].posts.size
end
def test_eager_association_loading_with_cascaded_three_levels_by_ping_pong
- firms = Firm.all.merge!(includes: {account: {firm: :account}}, order: "companies.id").to_a
+ firms = Firm.all.merge!(includes: { account: { firm: :account } }, order: "companies.id").to_a
assert_equal 2, firms.size
assert_equal firms.first.account, firms.first.account.firm.account
assert_equal companies(:first_firm).account, assert_no_queries { firms.first.account.firm.account }
@@ -134,8 +134,8 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
def test_eager_association_loading_with_belongs_to_sti
replies = Reply.all.merge!(includes: :topic, order: "topics.id").to_a
- assert replies.include?(topics(:second))
- assert !replies.include?(topics(:first))
+ assert_includes replies, topics(:second)
+ assert_not_includes replies, topics(:first)
assert_equal topics(:first), assert_no_queries { replies.first.topic }
end
@@ -158,7 +158,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_where_first_level_returns_nil
- authors = Author.all.merge!(includes: {post_about_thinking: :comments}, order: "authors.id DESC").to_a
+ authors = Author.all.merge!(includes: { post_about_thinking: :comments }, order: "authors.id DESC").to_a
assert_equal [authors(:bob), authors(:mary), authors(:david)], authors
assert_no_queries do
authors[2].post_about_thinking.comments.first
@@ -166,12 +166,12 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_with_recursive_cascading_four_levels_has_many_through
- source = Vertex.all.merge!(includes: {sinks: {sinks: {sinks: :sinks}}}, order: "vertices.id").first
+ source = Vertex.all.merge!(includes: { sinks: { sinks: { sinks: :sinks } } }, order: "vertices.id").first
assert_equal vertices(:vertex_4), assert_no_queries { source.sinks.first.sinks.first.sinks.first }
end
def test_eager_association_loading_with_recursive_cascading_four_levels_has_and_belongs_to_many
- sink = Vertex.all.merge!(includes: {sources: {sources: {sources: :sources}}}, order: "vertices.id DESC").first
+ sink = Vertex.all.merge!(includes: { sources: { sources: { sources: :sources } } }, order: "vertices.id DESC").first
assert_equal vertices(:vertex_1), assert_no_queries { sink.sources.first.sources.first.sources.first.sources.first }
end
@@ -183,6 +183,6 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
assert_equal 1, authors[1].comments.size
assert_equal 5, authors[0].posts.size
assert_equal 3, authors[1].posts.size
- assert_equal 3, authors[0].posts.collect { |post| post.categorizations.size }.inject(0) { |sum, i| sum+i }
+ assert_equal 3, authors[0].posts.collect { |post| post.categorizations.size }.inject(0) { |sum, i| sum + i }
end
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 aa82b9dd2a..4f0fe3236e 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
@@ -15,8 +15,8 @@ class EagerLoadIncludeFullStiClassNamesTest < ActiveRecord::TestCase
end
def generate_test_objects
- post = Namespaced::Post.create( title: "Great stuff", body: "This is not", author_id: 1 )
- Tagging.create( taggable: post )
+ post = Namespaced::Post.create(title: "Great stuff", body: "This is not", author_id: 1)
+ Tagging.create(taggable: post)
end
def test_class_names
diff --git a/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
index aebece1b91..e9f551b6b2 100644
--- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb
+++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
@@ -12,7 +12,7 @@ module Remembered
included do
after_create :remember
- protected
+ private
def remember; self.class.remembered << self; end
end
@@ -39,7 +39,7 @@ class Triangle < ActiveRecord::Base
has_many :shape_expressions, as: :shape
include Remembered
end
-class PaintColor < ActiveRecord::Base
+class PaintColor < ActiveRecord::Base
has_many :shape_expressions, as: :paint
belongs_to :non_poly, foreign_key: "non_poly_one_id", class_name: "NonPolyOne"
include Remembered
@@ -117,8 +117,8 @@ class EagerLoadNestedIncludeWithMissingDataTest < ActiveRecord::TestCase
def test_missing_data_in_a_nested_include_should_not_cause_errors_when_constructing_objects
assert_nothing_raised do
# @davey_mcdave doesn't have any author_favorites
- includes = {posts: :comments, categorizations: :category, author_favorites: :favorite_author }
- Author.all.merge!(includes: includes, where: {authors: {name: @davey_mcdave.name}}, order: "categories.name").to_a
+ includes = { posts: :comments, categorizations: :category, author_favorites: :favorite_author }
+ Author.all.merge!(includes: includes, where: { authors: { name: @davey_mcdave.name } }, order: "categories.name").to_a
end
end
end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index c7c945e33f..ff1bf8acd4 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -42,11 +42,11 @@ class EagerAssociationTest < ActiveRecord::TestCase
posts = Post.all.merge!(includes: :comments).to_a
post = posts.find { |p| p.id == 1 }
assert_equal 2, post.comments.size
- assert post.comments.include?(comments(:greetings))
+ assert_includes post.comments, comments(:greetings)
post = Post.all.merge!(includes: :comments, where: "posts.title = 'Welcome to the weblog'").first
assert_equal 2, post.comments.size
- assert post.comments.include?(comments(:greetings))
+ assert_includes post.comments, comments(:greetings)
posts = Post.all.merge!(includes: :last_comment).to_a
post = posts.find { |p| p.id == 1 }
@@ -103,13 +103,13 @@ class EagerAssociationTest < ActiveRecord::TestCase
posts = Post.all.merge!(includes: [ :comments, :author, :categories ], order: "posts.id").to_a
assert_equal 2, posts.first.comments.size
assert_equal 2, posts.first.categories.size
- assert posts.first.comments.include?(comments(:greetings))
+ assert_includes posts.first.comments, comments(:greetings)
end
def test_duplicate_middle_objects
comments = Comment.all.merge!(where: "post_id = 1", includes: [post: :author]).to_a
assert_no_queries do
- comments.each {|comment| comment.post.author.name}
+ comments.each { |comment| comment.post.author.name }
end
end
@@ -175,7 +175,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
popular_post.readers.create!(person: people(:david))
readers = Reader.all.merge!(where: ["post_id = ?", popular_post.id],
- includes: {post: :comments}).to_a
+ includes: { post: :comments }).to_a
readers.each do |reader|
assert_equal [comment], reader.post.comments
end
@@ -188,7 +188,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
comment = car_post.comments.create!(body: "hmm")
categories = Category.all.merge!(where: { "posts.id" => car_post.id },
- includes: {posts: :comments}).to_a
+ includes: { posts: :comments }).to_a
categories.each do |category|
assert_equal [comment], category.posts[0].comments
end
@@ -206,7 +206,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_finding_with_includes_on_has_many_association_with_same_include_includes_only_once
author_id = authors(:david).id
- author = assert_queries(3) { Author.all.merge!(includes: {posts_with_comments: :comments}).find(author_id) } # find the author, then find the posts, then find the comments
+ author = assert_queries(3) { Author.all.merge!(includes: { posts_with_comments: :comments }).find(author_id) } # find the author, then find the posts, then find the comments
author.posts_with_comments.each do |post_with_comments|
assert_equal post_with_comments.comments.length, post_with_comments.comments.count
assert_nil post_with_comments.comments.to_a.uniq!
@@ -217,7 +217,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
author = authors(:david)
post = author.post_about_thinking_with_last_comment
last_comment = post.last_comment
- author = assert_queries(3) { Author.all.merge!(includes: {post_about_thinking_with_last_comment: :last_comment}).find(author.id)} # find the author, then find the posts, then find the comments
+ author = assert_queries(3) { Author.all.merge!(includes: { post_about_thinking_with_last_comment: :last_comment }).find(author.id) } # find the author, then find the posts, then find the comments
assert_no_queries do
assert_equal post, author.post_about_thinking_with_last_comment
assert_equal last_comment, author.post_about_thinking_with_last_comment.last_comment
@@ -228,7 +228,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
post = posts(:welcome)
author = post.author
author_address = author.author_address
- post = assert_queries(3) { Post.all.merge!(includes: {author_with_address: :author_address}).find(post.id) } # find the post, then find the author, then find the address
+ post = assert_queries(3) { Post.all.merge!(includes: { author_with_address: :author_address }).find(post.id) } # find the post, then find the author, then find the address
assert_no_queries do
assert_equal author, post.author_with_address
assert_equal author_address, post.author_with_address.author_address
@@ -238,10 +238,10 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_finding_with_includes_on_null_belongs_to_association_with_same_include_includes_only_once
post = posts(:welcome)
post.update!(author: nil)
- post = assert_queries(1) { Post.all.merge!(includes: {author_with_address: :author_address}).find(post.id) }
+ post = assert_queries(1) { Post.all.merge!(includes: { author_with_address: :author_address }).find(post.id) }
# find the post, then find the author which is null so no query for the author or address
assert_no_queries do
- assert_equal nil, post.author_with_address
+ assert_nil post.author_with_address
end
end
@@ -250,7 +250,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
sponsor.update!(sponsorable: nil)
sponsor = assert_queries(1) { Sponsor.all.merge!(includes: :sponsorable).find(sponsor.id) }
assert_no_queries do
- assert_equal nil, sponsor.sponsorable
+ assert_nil sponsor.sponsorable
end
end
@@ -261,7 +261,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_nothing_raised { Sponsor.all.merge!(includes: :sponsorable).find(sponsor.id) }
end
assert_no_queries do
- assert_equal nil, sponsor.sponsorable
+ assert_nil sponsor.sponsorable
end
end
@@ -284,7 +284,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
# Regression test for 21c75e5
def test_nested_loading_does_not_raise_exception_when_association_does_not_exist
assert_nothing_raised do
- Post.all.merge!(includes: {author: :author_addresss}).find(posts(:authorless).id)
+ Post.all.merge!(includes: { author: :author_addresss }).find(posts(:authorless).id)
end
end
@@ -292,33 +292,33 @@ class EagerAssociationTest < ActiveRecord::TestCase
post_id = Comment.where(author_id: nil).where.not(post_id: nil).first.post_id
assert_nothing_raised do
- Post.preload(comments: [{author: :essays}]).find(post_id)
+ Post.preload(comments: [{ author: :essays }]).find(post_id)
end
end
def test_nested_loading_through_has_one_association
- aa = AuthorAddress.all.merge!(includes: {author: :posts}).find(author_addresses(:david_address).id)
+ aa = AuthorAddress.all.merge!(includes: { author: :posts }).find(author_addresses(:david_address).id)
assert_equal aa.author.posts.count, aa.author.posts.length
end
def test_nested_loading_through_has_one_association_with_order
- aa = AuthorAddress.all.merge!(includes: {author: :posts}, order: "author_addresses.id").find(author_addresses(:david_address).id)
+ aa = AuthorAddress.all.merge!(includes: { author: :posts }, order: "author_addresses.id").find(author_addresses(:david_address).id)
assert_equal aa.author.posts.count, aa.author.posts.length
end
def test_nested_loading_through_has_one_association_with_order_on_association
- aa = AuthorAddress.all.merge!(includes: {author: :posts}, order: "authors.id").find(author_addresses(:david_address).id)
+ aa = AuthorAddress.all.merge!(includes: { author: :posts }, order: "authors.id").find(author_addresses(:david_address).id)
assert_equal aa.author.posts.count, aa.author.posts.length
end
def test_nested_loading_through_has_one_association_with_order_on_nested_association
- aa = AuthorAddress.all.merge!(includes: {author: :posts}, order: "posts.id").find(author_addresses(:david_address).id)
+ aa = AuthorAddress.all.merge!(includes: { author: :posts }, order: "posts.id").find(author_addresses(:david_address).id)
assert_equal aa.author.posts.count, aa.author.posts.length
end
def test_nested_loading_through_has_one_association_with_conditions
aa = AuthorAddress.references(:author_addresses).merge(
- includes: {author: :posts},
+ includes: { author: :posts },
where: "author_addresses.id > 0"
).find author_addresses(:david_address).id
assert_equal aa.author.posts.count, aa.author.posts.length
@@ -326,7 +326,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_nested_loading_through_has_one_association_with_conditions_on_association
aa = AuthorAddress.references(:authors).merge(
- includes: {author: :posts},
+ includes: { author: :posts },
where: "authors.id > 0"
).find author_addresses(:david_address).id
assert_equal aa.author.posts.count, aa.author.posts.length
@@ -334,7 +334,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_nested_loading_through_has_one_association_with_conditions_on_nested_association
aa = AuthorAddress.references(:posts).merge(
- includes: {author: :posts},
+ includes: { author: :posts },
where: "posts.id > 0"
).find author_addresses(:david_address).id
assert_equal aa.author.posts.count, aa.author.posts.length
@@ -349,38 +349,38 @@ class EagerAssociationTest < ActiveRecord::TestCase
comments = Comment.all.merge!(includes: :post).to_a
assert_equal 11, comments.length
titles = comments.map { |c| c.post.title }
- assert titles.include?(posts(:welcome).title)
- assert titles.include?(posts(:sti_post_and_comments).title)
+ assert_includes titles, posts(:welcome).title
+ assert_includes titles, posts(:sti_post_and_comments).title
end
def test_eager_association_loading_with_belongs_to_and_limit
comments = Comment.all.merge!(includes: :post, limit: 5, order: "comments.id").to_a
assert_equal 5, comments.length
- assert_equal [1,2,3,5,6], comments.collect(&:id)
+ assert_equal [1, 2, 3, 5, 6], comments.collect(&:id)
end
def test_eager_association_loading_with_belongs_to_and_limit_and_conditions
comments = Comment.all.merge!(includes: :post, where: "post_id = 4", limit: 3, order: "comments.id").to_a
assert_equal 3, comments.length
- assert_equal [5,6,7], comments.collect(&:id)
+ assert_equal [5, 6, 7], comments.collect(&:id)
end
def test_eager_association_loading_with_belongs_to_and_limit_and_offset
comments = Comment.all.merge!(includes: :post, limit: 3, offset: 2, order: "comments.id").to_a
assert_equal 3, comments.length
- assert_equal [3,5,6], comments.collect(&:id)
+ assert_equal [3, 5, 6], comments.collect(&:id)
end
def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions
comments = Comment.all.merge!(includes: :post, where: "post_id = 4", limit: 3, offset: 1, order: "comments.id").to_a
assert_equal 3, comments.length
- assert_equal [6,7,8], comments.collect(&:id)
+ assert_equal [6, 7, 8], comments.collect(&:id)
end
def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions_array
- comments = Comment.all.merge!(includes: :post, where: ["post_id = ?",4], limit: 3, offset: 1, order: "comments.id").to_a
+ comments = Comment.all.merge!(includes: :post, where: ["post_id = ?", 4], limit: 3, offset: 1, order: "comments.id").to_a
assert_equal 3, comments.length
- assert_equal [6,7,8], comments.collect(&:id)
+ assert_equal [6, 7, 8], comments.collect(&:id)
end
def test_eager_association_loading_with_belongs_to_and_conditions_string_with_unquoted_table_name
@@ -392,17 +392,17 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_association_loading_with_belongs_to_and_conditions_hash
comments = []
assert_nothing_raised do
- comments = Comment.all.merge!(includes: :post, where: {posts: {id: 4}}, limit: 3, order: "comments.id").to_a
+ comments = Comment.all.merge!(includes: :post, where: { posts: { id: 4 } }, limit: 3, order: "comments.id").to_a
end
assert_equal 3, comments.length
- assert_equal [5,6,7], comments.collect(&:id)
+ assert_equal [5, 6, 7], comments.collect(&:id)
assert_no_queries do
comments.first.post
end
end
def test_eager_association_loading_with_belongs_to_and_conditions_string_with_quoted_table_name
- quoted_posts_id= Comment.connection.quote_table_name("posts") + "." + Comment.connection.quote_column_name("id")
+ quoted_posts_id = Comment.connection.quote_table_name("posts") + "." + Comment.connection.quote_column_name("id")
assert_nothing_raised do
Comment.includes(:post).references(:posts).where("#{quoted_posts_id} = ?", 4)
end
@@ -415,7 +415,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_association_loading_with_belongs_to_and_order_string_with_quoted_table_name
- quoted_posts_id= Comment.connection.quote_table_name("posts") + "." + Comment.connection.quote_column_name("id")
+ quoted_posts_id = Comment.connection.quote_table_name("posts") + "." + Comment.connection.quote_column_name("id")
assert_nothing_raised do
Comment.includes(:post).references(:posts).order(quoted_posts_id)
end
@@ -441,30 +441,30 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_load_belongs_to_quotes_table_and_column_names
job = Job.includes(:ideal_reference).find jobs(:unicyclist).id
references(:michael_unicyclist)
- assert_no_queries{ assert_equal references(:michael_unicyclist), job.ideal_reference}
+ assert_no_queries { assert_equal references(:michael_unicyclist), job.ideal_reference }
end
def test_eager_load_has_one_quotes_table_and_column_names
michael = Person.all.merge!(includes: :favourite_reference).find(people(:michael).id)
references(:michael_unicyclist)
- assert_no_queries{ assert_equal references(:michael_unicyclist), michael.favourite_reference}
+ assert_no_queries { assert_equal references(:michael_unicyclist), michael.favourite_reference }
end
def test_eager_load_has_many_quotes_table_and_column_names
michael = Person.all.merge!(includes: :references).find(people(:michael).id)
- references(:michael_magician,:michael_unicyclist)
- assert_no_queries{ assert_equal references(:michael_magician,:michael_unicyclist), michael.references.sort_by(&:id) }
+ references(:michael_magician, :michael_unicyclist)
+ assert_no_queries { assert_equal references(:michael_magician, :michael_unicyclist), michael.references.sort_by(&:id) }
end
def test_eager_load_has_many_through_quotes_table_and_column_names
michael = Person.all.merge!(includes: :jobs).find(people(:michael).id)
jobs(:magician, :unicyclist)
- assert_no_queries{ assert_equal jobs(:unicyclist, :magician), michael.jobs.sort_by(&:id) }
+ assert_no_queries { assert_equal jobs(:unicyclist, :magician), michael.jobs.sort_by(&:id) }
end
def test_eager_load_has_many_with_string_keys
subscriptions = subscriptions(:webster_awdr, :webster_rfr)
- subscriber =Subscriber.all.merge!(includes: :subscriptions).find(subscribers(:second).id)
+ subscriber = Subscriber.all.merge!(includes: :subscriptions).find(subscribers(:second).id)
assert_equal subscriptions, subscriber.subscriptions.sort_by(&:id)
end
@@ -563,13 +563,13 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_with_has_many_and_limit_and_conditions
posts = Post.all.merge!(includes: [ :author, :comments ], limit: 2, where: "posts.body = 'hello'", order: "posts.id").to_a
assert_equal 2, posts.size
- assert_equal [4,5], posts.collect(&:id)
+ assert_equal [4, 5], posts.collect(&:id)
end
def test_eager_with_has_many_and_limit_and_conditions_array
posts = Post.all.merge!(includes: [ :author, :comments ], limit: 2, where: [ "posts.body = ?", "hello" ], order: "posts.id").to_a
assert_equal 2, posts.size
- assert_equal [4,5], posts.collect(&:id)
+ assert_equal [4, 5], posts.collect(&:id)
end
def test_eager_with_has_many_and_limit_and_conditions_array_on_the_eagers
@@ -630,8 +630,8 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal 2, posts[0].categories.size
assert_equal 1, posts[1].categories.size
assert_equal 0, posts[2].categories.size
- assert posts[0].categories.include?(categories(:technology))
- assert posts[1].categories.include?(categories(:general))
+ assert_includes posts[0].categories, categories(:technology)
+ assert_includes posts[1].categories, categories(:general)
end
# Since the preloader for habtm gets raw row hashes from the database and then
@@ -695,8 +695,8 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal 2, posts[0].categories.size
assert_equal 1, posts[1].categories.size
assert_equal 0, posts[2].categories.size
- assert posts[0].categories.include?(categories(:technology))
- assert posts[1].categories.include?(categories(:general))
+ assert_includes posts[0].categories, categories(:technology)
+ assert_includes posts[1].categories, categories(:general)
end
def test_eager_with_inheritance
@@ -740,7 +740,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_with_invalid_association_reference
assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
- Post.all.merge!(includes: :monkeys ).find(6)
+ Post.all.merge!(includes: :monkeys).find(6)
}
assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
Post.all.merge!(includes: [ :monkeys ]).find(6)
@@ -844,7 +844,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
- def find_all_ordered(className, include=nil)
+ def find_all_ordered(className, include = nil)
className.all.merge!(order: "#{className.table_name}.#{className.primary_key}", includes: include).to_a
end
@@ -895,16 +895,16 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_polymorphic_type_condition
post = Post.all.merge!(includes: :taggings).find(posts(:thinking).id)
- assert post.taggings.include?(taggings(:thinking_general))
+ assert_includes post.taggings, taggings(:thinking_general)
post = SpecialPost.all.merge!(includes: :taggings).find(posts(:thinking).id)
- assert post.taggings.include?(taggings(:thinking_general))
+ assert_includes post.taggings, taggings(:thinking_general)
end
def test_eager_with_multiple_associations_with_same_table_has_many_and_habtm
# Eager includes of has many and habtm associations aren't necessarily sorted in the same way
def assert_equal_after_sort(item1, item2, item3 = nil)
- assert_equal(item1.sort{|a,b| a.id <=> b.id}, item2.sort{|a,b| a.id <=> b.id})
- assert_equal(item3.sort{|a,b| a.id <=> b.id}, item2.sort{|a,b| a.id <=> b.id}) if item3
+ assert_equal(item1.sort { |a, b| a.id <=> b.id }, item2.sort { |a, b| a.id <=> b.id })
+ assert_equal(item3.sort { |a, b| a.id <=> b.id }, item2.sort { |a, b| a.id <=> b.id }) if item3
end
# Test regular association, association with conditions, association with
# STI, and association with conditions assured not to be true
@@ -959,12 +959,12 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_preconfigured_includes_with_belongs_to
author = posts(:welcome).author_with_posts
- assert_no_queries {assert_equal 5, author.posts.size}
+ assert_no_queries { assert_equal 5, author.posts.size }
end
def test_preconfigured_includes_with_has_one
comment = posts(:sti_comments).very_special_comment_with_post
- assert_no_queries {assert_equal posts(:sti_comments), comment.post}
+ assert_no_queries { assert_equal posts(:sti_comments), comment.post }
end
def test_eager_association_with_scope_with_joins
@@ -1066,7 +1066,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
Post.all.merge!(joins: :comments, includes: :author, order: "comments.id DESC").to_a
end
assert_equal posts(:eager_other), posts[1]
- assert_equal authors(:mary), assert_no_queries { posts[1].author}
+ assert_equal authors(:mary), assert_no_queries { posts[1].author }
end
def test_eager_loading_with_conditions_on_joined_table_preloads
@@ -1074,21 +1074,21 @@ class EagerAssociationTest < ActiveRecord::TestCase
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}
+ 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}
+ 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
+ Post.all.merge!(includes: :author, joins: { taggings: :tag }, where: "tags.name = 'General'", order: "posts.id").to_a
end
assert_equal posts(:welcome, :thinking), posts
posts = assert_queries(2) do
- Post.all.merge!(includes: :author, joins: {taggings: {tag: :taggings}}, where: "taggings_tags.super_tag_id=2", order: "posts.id").to_a
+ Post.all.merge!(includes: :author, joins: { taggings: { tag: :taggings } }, where: "taggings_tags.super_tag_id=2", order: "posts.id").to_a
end
assert_equal posts(:welcome, :thinking), posts
end
@@ -1110,13 +1110,13 @@ class EagerAssociationTest < ActiveRecord::TestCase
Post.all.merge!(select: "distinct posts.*", includes: :author, joins: "INNER JOIN comments on comments.post_id = posts.id", 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}
+ assert_equal authors(:david), assert_no_queries { posts[0].author }
posts = assert_queries(2) do
Post.all.merge!(select: "distinct posts.*", includes: :author, joins: ["INNER JOIN comments on comments.post_id = posts.id"], 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}
+ assert_equal authors(:david), assert_no_queries { posts[0].author }
end
def test_eager_loading_with_select_on_joined_table_preloads
@@ -1124,7 +1124,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
Post.all.merge!(select: "posts.*, authors.name as author_name", includes: :comments, joins: :author, order: "posts.id").to_a
end
assert_equal "David", posts[0].author_name
- assert_equal posts(:welcome).comments, assert_no_queries { posts[0].comments}
+ assert_equal posts(:welcome).comments, assert_no_queries { posts[0].comments }
end
def test_eager_loading_with_conditions_on_join_model_preloads
@@ -1139,7 +1139,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
people = Person.males.merge(includes: :primary_contact).to_a
assert_not_equal people.length, 0
people.each do |person|
- assert_no_queries {assert_not_nil person.primary_contact}
+ assert_no_queries { assert_not_nil person.primary_contact }
assert_equal Person.find(person.id).primary_contact, person.primary_contact
end
end
@@ -1163,7 +1163,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
expected = Firm.find(1).clients_using_primary_key.sort_by(&:name)
# Oracle adapter truncates alias to 30 characters
if current_adapter?(:OracleAdapter)
- firm = Firm.all.merge!(includes: :clients_using_primary_key, order: "clients_using_primary_keys_companies"[0,30]+".name").find(1)
+ firm = Firm.all.merge!(includes: :clients_using_primary_key, order: "clients_using_primary_keys_companies"[0, 30] + ".name").find(1)
else
firm = Firm.all.merge!(includes: :clients_using_primary_key, order: "clients_using_primary_keys_companies.name").find(1)
end
@@ -1182,7 +1182,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_include_has_one_using_primary_key
expected = accounts(:signals37)
- firm = Firm.all.merge!(includes: :account_using_primary_key, order: "accounts.id").to_a.detect {|f| f.id == 1}
+ firm = Firm.all.merge!(includes: :account_using_primary_key, order: "accounts.id").to_a.detect { |f| f.id == 1 }
assert_no_queries do
assert_equal expected, firm.account_using_primary_key
end
@@ -1253,16 +1253,16 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_join_eager_with_empty_order_should_generate_valid_sql
assert_nothing_raised do
- Post.includes(:comments).order("").where(comments: {body: "Thank you for the welcome"}).first
+ Post.includes(:comments).order("").where(comments: { body: "Thank you for the welcome" }).first
end
end
def test_deep_including_through_habtm
# warm up habtm cache
- posts = Post.all.merge!(includes: {categories: :categorizations}, order: "posts.id").to_a
+ posts = Post.all.merge!(includes: { categories: :categorizations }, order: "posts.id").to_a
posts[0].categories[0].categorizations.length
- posts = Post.all.merge!(includes: {categories: :categorizations}, order: "posts.id").to_a
+ posts = Post.all.merge!(includes: { categories: :categorizations }, order: "posts.id").to_a
assert_no_queries { assert_equal 2, posts[0].categories[0].categorizations.length }
assert_no_queries { assert_equal 1, posts[0].categories[1].categorizations.length }
assert_no_queries { assert_equal 2, posts[1].categories[0].categorizations.length }
@@ -1360,7 +1360,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
test "including associations with where.not adds implicit references" do
author = assert_queries(2) {
- Author.includes(:posts).where.not(posts: { title: "Welcome to the weblog"} ).last
+ Author.includes(:posts).where.not(posts: { title: "Welcome to the weblog" }).last
}
assert_no_queries {
@@ -1424,6 +1424,24 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert david.readonly_comments.first.readonly?
end
+ test "eager-loading non-readonly association" do
+ # has_one
+ firm = Firm.where(id: "1").eager_load(:account).first!
+ assert_not firm.account.readonly?
+
+ # has_and_belongs_to_many
+ project = Project.where(id: "2").eager_load(:developers).first!
+ assert_not project.developers.first.readonly?
+
+ # has_many :through
+ david = Author.where(id: "1").eager_load(:comments).first!
+ assert_not david.comments.first.readonly?
+
+ # belongs_to
+ post = Post.where(id: "1").eager_load(:author).first!
+ assert_not post.author.readonly?
+ end
+
test "eager-loading readonly association" do
# has-one
firm = Firm.where(id: "1").eager_load(:readonly_account).first!
@@ -1438,8 +1456,8 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert david.readonly_comments.first.readonly?
# belongs_to
- post = Post.where(id: "1").eager_load(:author).first!
- assert post.author.readonly?
+ post = Post.where(id: "1").eager_load(:readonly_author).first!
+ assert post.readonly_author.readonly?
end
test "preloading a polymorphic association with references to the associated table" do
diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb
index 8f9fc10ba5..974a3080d4 100644
--- a/activerecord/test/cases/associations/extension_test.rb
+++ b/activerecord/test/cases/associations/extension_test.rb
@@ -45,7 +45,7 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase
# Marshaling an association shouldn't make it unusable by wiping its reflection.
assert_not_nil david.association(:projects).reflection
- david_too = Marshal.load(marshalled)
+ david_too = Marshal.load(marshalled)
assert_equal projects(:action_controller), david_too.projects.find_most_recent
end
@@ -76,6 +76,6 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase
private
def extend!(model)
- ActiveRecord::Associations::Builder::HasMany.define_extensions(model, :association_name) { }
+ ActiveRecord::Associations::Builder::HasMany.define_extensions(model, :association_name) {}
end
end
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index ec6ae39835..54fb61d6a5 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -86,6 +86,10 @@ class DeveloperWithSymbolClassName < Developer
has_and_belongs_to_many :projects, class_name: :ProjectWithSymbolsForKeys
end
+class DeveloperWithConstantClassName < Developer
+ has_and_belongs_to_many :projects, class_name: ProjectWithSymbolsForKeys
+end
+
class DeveloperWithExtendOption < Developer
module NamedExtension
def category
@@ -169,7 +173,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
active_record = Project.find(1)
assert !active_record.developers.empty?
assert_equal 3, active_record.developers.size
- assert active_record.developers.include?(david)
+ assert_includes active_record.developers, david
end
def test_adding_single
@@ -249,8 +253,8 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert !p.persisted?
assert aredridel.save
assert aredridel.persisted?
- assert_equal no_of_devels+1, Developer.count
- assert_equal no_of_projects+1, Project.count
+ assert_equal no_of_devels + 1, Developer.count
+ assert_equal no_of_projects + 1, Project.count
assert_equal 2, aredridel.projects.size
assert_equal 2, aredridel.projects.reload.size
end
@@ -258,7 +262,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_habtm_saving_multiple_relationships
new_project = Project.new("name" => "Grimetime")
amount_of_developers = 4
- developers = (0...amount_of_developers).collect {|i| Developer.create(name: "JME #{i}") }.reverse
+ developers = (0...amount_of_developers).collect { |i| Developer.create(name: "JME #{i}") }.reverse
new_project.developer_ids = [developers[0].id, developers[1].id]
new_project.developers_with_callback_ids = [developers[2].id, developers[3].id]
@@ -379,7 +383,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
dev.projects << projects(:active_record)
assert_equal 3, dev.projects.size
- assert_equal 1, dev.projects.distinct.size
+ assert_equal 1, dev.projects.uniq.size
end
def test_distinct_before_the_fact
@@ -544,7 +548,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_no_queries(ignore_none: false) do
assert project.developers.loaded?
- assert project.developers.include?(developer)
+ assert_includes project.developers, developer
end
end
@@ -555,7 +559,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
project.reload
assert ! project.developers.loaded?
assert_queries(1) do
- assert project.developers.include?(developer)
+ assert_includes project.developers, developer
end
assert ! project.developers.loaded?
end
@@ -588,7 +592,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
def test_dynamic_find_all_should_respect_readonly_access
- projects(:active_record).readonly_developers.each { |d| assert_raise(ActiveRecord::ReadOnlyRecord) { d.save! } if d.valid?}
+ projects(:active_record).readonly_developers.each { |d| assert_raise(ActiveRecord::ReadOnlyRecord) { d.save! } if d.valid? }
projects(:active_record).readonly_developers.each(&:readonly?)
end
@@ -600,8 +604,8 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
project.save!
project.reload
- assert project.developers.include?(jamis)
- assert project.developers.include?(david)
+ assert_includes project.developers, jamis
+ assert_includes project.developers, david
end
def test_find_in_association_with_options
@@ -628,7 +632,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
david.projects = [projects(:action_controller), Project.new("name" => "ActionWebSearch")]
david.save
assert_equal 2, david.projects.length
- assert !david.projects.include?(projects(:active_record))
+ assert_not_includes david.projects, projects(:active_record)
end
def test_replace_on_new_object
@@ -646,9 +650,9 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
developer.special_projects << special_project
developer.reload
- assert developer.projects.include?(special_project)
- assert developer.special_projects.include?(special_project)
- assert !developer.special_projects.include?(other_project)
+ assert_includes developer.projects, special_project
+ assert_includes developer.special_projects, special_project
+ assert_not_includes developer.special_projects, other_project
end
def test_symbol_join_table
@@ -700,7 +704,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal(
3,
Developer.references(:developers_projects_join).merge(
- includes: {projects: :developers},
+ includes: { projects: :developers },
where: "projects_developers_projects_join.joined_on IS NOT NULL"
).to_a.size
)
@@ -720,7 +724,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal(
3,
Developer.references(:developers_projects_join).merge(
- includes: {projects: :developers}, where: "projects_developers_projects_join.joined_on IS NOT NULL",
+ includes: { projects: :developers }, where: "projects_developers_projects_join.joined_on IS NOT NULL",
group: group.join(",")
).to_a.size
)
@@ -854,7 +858,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_include_method_in_has_and_belongs_to_many_association_should_return_true_for_instance_added_with_build
project = Project.new
developer = project.developers.build
- assert project.developers.include?(developer)
+ assert_includes project.developers, developer
end
def test_destruction_does_not_error_without_primary_key
@@ -939,14 +943,16 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_with_symbol_class_name
assert_nothing_raised do
- DeveloperWithSymbolClassName.new
+ developer = DeveloperWithSymbolClassName.new
+ developer.projects
end
end
- def test_association_force_reload_with_only_true_is_deprecated
- developer = Developer.find(1)
-
- assert_deprecated { developer.projects(true) }
+ def test_with_constant_class_name
+ assert_nothing_raised do
+ developer = DeveloperWithConstantClassName.new
+ developer.projects
+ end
end
def test_alternate_database
@@ -1000,4 +1006,17 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
user = User.create!
assert_nothing_raised { user.jobs_pool.clear }
end
+
+ def test_has_and_belongs_to_many_while_partial_writes_false
+ begin
+ original_partial_writes = ActiveRecord::Base.partial_writes
+ ActiveRecord::Base.partial_writes = false
+ developer = Developer.new(name: "Mehmet Emin İNAÇ")
+ developer.projects << Project.new(name: "Bounty")
+
+ assert developer.save
+ ensure
+ ActiveRecord::Base.partial_writes = original_partial_writes
+ end
+ 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 09692fc3a0..cbecfa84ff 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -84,7 +84,7 @@ class HasManyAssociationsTestPrimaryKeys < ActiveRecord::TestCase
david = people(:david)
assert_equal ["A Modest Proposal"], david.essays.map(&:name)
- david.essays = [Essay.create!(name: "Remote Work" )]
+ david.essays = [Essay.create!(name: "Remote Work")]
assert_equal ["Remote Work"], david.essays.map(&:name)
end
@@ -187,7 +187,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
ship.parts.clear
part.reload
- assert_equal nil, part.ship
+ assert_nil part.ship
assert !part.updated_at_changed?
end
@@ -305,7 +305,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
test "building the association with an array" do
speedometer = Speedometer.new(speedometer_id: "a")
- data = [{name: "first"}, {name: "second"}]
+ data = [{ name: "first" }, { name: "second" }]
speedometer.minivans.build(data)
assert_equal 2, speedometer.minivans.size
@@ -380,47 +380,78 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_no_queries do
bulbs.first()
- bulbs.first({})
end
assert_no_queries do
bulbs.second()
- bulbs.second({})
end
assert_no_queries do
bulbs.third()
- bulbs.third({})
end
assert_no_queries do
bulbs.fourth()
- bulbs.fourth({})
end
assert_no_queries do
bulbs.fifth()
- bulbs.fifth({})
end
assert_no_queries do
bulbs.forty_two()
- bulbs.forty_two({})
end
assert_no_queries do
bulbs.third_to_last()
- bulbs.third_to_last({})
end
assert_no_queries do
bulbs.second_to_last()
- bulbs.second_to_last({})
end
assert_no_queries do
bulbs.last()
- bulbs.last({})
+ end
+ end
+
+ def test_finder_method_with_dirty_target
+ company = companies(:first_firm)
+ new_clients = []
+ assert_no_queries(ignore_none: false) do
+ new_clients << company.clients_of_firm.build(name: "Another Client")
+ new_clients << company.clients_of_firm.build(name: "Another Client II")
+ new_clients << company.clients_of_firm.build(name: "Another Client III")
+ end
+
+ assert_not company.clients_of_firm.loaded?
+ assert_queries(1) do
+ assert_same new_clients[0], company.clients_of_firm.third
+ assert_same new_clients[1], company.clients_of_firm.fourth
+ assert_same new_clients[2], company.clients_of_firm.fifth
+ assert_same new_clients[0], company.clients_of_firm.third_to_last
+ assert_same new_clients[1], company.clients_of_firm.second_to_last
+ assert_same new_clients[2], company.clients_of_firm.last
+ end
+ end
+
+ def test_finder_bang_method_with_dirty_target
+ company = companies(:first_firm)
+ new_clients = []
+ assert_no_queries(ignore_none: false) do
+ new_clients << company.clients_of_firm.build(name: "Another Client")
+ new_clients << company.clients_of_firm.build(name: "Another Client II")
+ new_clients << company.clients_of_firm.build(name: "Another Client III")
+ end
+
+ assert_not company.clients_of_firm.loaded?
+ assert_queries(1) do
+ assert_same new_clients[0], company.clients_of_firm.third!
+ assert_same new_clients[1], company.clients_of_firm.fourth!
+ assert_same new_clients[2], company.clients_of_firm.fifth!
+ assert_same new_clients[0], company.clients_of_firm.third_to_last!
+ assert_same new_clients[1], company.clients_of_firm.second_to_last!
+ assert_same new_clients[2], company.clients_of_firm.last!
end
end
@@ -487,7 +518,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_finding_array_compatibility
- assert_equal 3, Firm.order(:id).find{|f| f.id > 0}.clients.length
+ assert_equal 3, Firm.order(:id).find { |f| f.id > 0 }.clients.length
end
def test_find_many_with_merged_options
@@ -497,7 +528,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_find_should_append_to_association_order
- ordered_clients = companies(:first_firm).clients_sorted_desc.order("companies.id")
+ ordered_clients = companies(:first_firm).clients_sorted_desc.order("companies.id")
assert_equal ["id DESC", "companies.id"], ordered_clients.order_values
end
@@ -525,14 +556,18 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_taking_with_a_number
# taking from unloaded Relation
bob = Author.find(authors(:bob).id)
+ new_post = bob.posts.build
+ assert_not bob.posts.loaded?
assert_equal [posts(:misc_by_bob)], bob.posts.take(1)
- bob = Author.find(authors(:bob).id)
assert_equal [posts(:misc_by_bob), posts(:other_by_bob)], bob.posts.take(2)
+ assert_equal [posts(:misc_by_bob), posts(:other_by_bob), new_post], bob.posts.take(3)
# taking from loaded Relation
- bob.posts.to_a
- assert_equal [posts(:misc_by_bob)], authors(:bob).posts.take(1)
- assert_equal [posts(:misc_by_bob), posts(:other_by_bob)], authors(:bob).posts.take(2)
+ bob.posts.load
+ assert bob.posts.loaded?
+ assert_equal [posts(:misc_by_bob)], bob.posts.take(1)
+ assert_equal [posts(:misc_by_bob), posts(:other_by_bob)], bob.posts.take(2)
+ assert_equal [posts(:misc_by_bob), posts(:other_by_bob), new_post], bob.posts.take(3)
end
def test_taking_with_inverse_of
@@ -618,6 +653,18 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find(2, 99) }
end
+ def test_find_one_message_on_primary_key
+ firm = Firm.all.merge!(order: "id").first
+
+ e = assert_raises(ActiveRecord::RecordNotFound) do
+ firm.clients.find(0)
+ end
+ assert_equal 0, e.id
+ assert_equal "id", e.primary_key
+ assert_equal "Client", e.model
+ assert_match (/\ACouldn't find Client with 'id'=0/), e.message
+ end
+
def test_find_ids_and_inverse_of
force_signal37_to_load_all_clients_of_firm
@@ -644,7 +691,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert ! firm.clients.loaded?
assert_queries(4) do
- firm.clients.find_each(batch_size: 1) {|c| assert_equal firm.id, c.firm_id }
+ firm.clients.find_each(batch_size: 1) { |c| assert_equal firm.id, c.firm_id }
end
assert ! firm.clients.loaded?
@@ -670,7 +717,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_queries(2) do
firm.clients.find_in_batches(batch_size: 2) do |clients|
- clients.each {|c| assert_equal firm.id, c.firm_id }
+ clients.each { |c| assert_equal firm.id, c.firm_id }
end
end
@@ -701,7 +748,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
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
+ Developer.all.merge!(joins: :audit_logs, where: { "audit_logs.message" => nil, :name => "Smith" }).to_a
end
end
@@ -823,7 +870,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
rescue Client::RaisedOnSave
end
- assert !companies(:first_firm).clients_of_firm.reload.include?(good)
+ assert_not_includes companies(:first_firm).clients_of_firm.reload, good
end
def test_transactions_when_adding_to_new_record
@@ -865,6 +912,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
company.clients_of_firm.build("name" => "Another Client")
company.clients_of_firm.build("name" => "Yet Another Client")
assert_equal 4, company.clients_of_firm.size
+ assert_equal 4, company.clients_of_firm.uniq.size
end
def test_collection_not_empty_after_building
@@ -888,7 +936,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_build_many
company = companies(:first_firm)
- new_clients = assert_no_queries(ignore_none: false) { company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) }
+ new_clients = assert_no_queries(ignore_none: false) { company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) }
assert_equal 2, new_clients.size
end
@@ -914,7 +962,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_build_via_block
company = companies(:first_firm)
- new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build {|client| client.name = "Another Client" } }
+ new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build { |client| client.name = "Another Client" } }
assert !company.clients_of_firm.loaded?
assert_equal "Another Client", new_client.name
@@ -925,7 +973,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_build_many_via_block
company = companies(:first_firm)
new_clients = assert_no_queries(ignore_none: false) do
- company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) do |client|
+ company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) do |client|
client.name = "changed"
end
end
@@ -936,7 +984,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_create_without_loading_association
- first_firm = companies(:first_firm)
+ first_firm = companies(:first_firm)
Firm.column_names
Client.column_names
@@ -962,7 +1010,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_create_many
- companies(:first_firm).clients_of_firm.create([{"name" => "Another Client"}, {"name" => "Another Client II"}])
+ companies(:first_firm).clients_of_firm.create([{ "name" => "Another Client" }, { "name" => "Another Client II" }])
assert_equal 4, companies(:first_firm).clients_of_firm.reload.size
end
@@ -1527,26 +1575,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert firm.companies.exists?(name: "child")
end
- def test_restrict_with_error_is_deprecated_using_key_many
- I18n.backend = I18n::Backend::Simple.new
- I18n.backend.store_translations :en, activerecord: { errors: { messages: { restrict_dependent_destroy: { many: "message for deprecated key" } } } }
-
- firm = RestrictedWithErrorFirm.create!(name: "restrict")
- firm.companies.create(name: "child")
-
- assert !firm.companies.empty?
-
- assert_deprecated { firm.destroy }
-
- assert !firm.errors.empty?
-
- assert_equal "message for deprecated key", firm.errors[:base].first
- assert RestrictedWithErrorFirm.exists?(name: "restrict")
- assert firm.companies.exists?(name: "child")
- ensure
- I18n.backend.reload!
- end
-
def test_restrict_with_error
firm = RestrictedWithErrorFirm.create!(name: "restrict")
firm.companies.create(name: "child")
@@ -1564,7 +1592,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_restrict_with_error_with_locale
I18n.backend = I18n::Backend::Simple.new
- I18n.backend.store_translations "en", activerecord: {attributes: {restricted_with_error_firm: {companies: "client companies"}}}
+ I18n.backend.store_translations "en", activerecord: { attributes: { restricted_with_error_firm: { companies: "client companies" } } }
firm = RestrictedWithErrorFirm.create!(name: "restrict")
firm.companies.create(name: "child")
@@ -1688,6 +1716,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert !company.clients.loaded?
end
+ def test_counter_cache_on_unloaded_association
+ car = Car.create(name: "My AppliCar")
+ assert_equal car.engines.size, 0
+ end
+
def test_get_ids_ignores_include_option
assert_equal [readers(:michael_welcome).id], posts(:welcome).readers_with_person_ids
end
@@ -1742,7 +1775,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
lambda { authors(:mary).comments = [comments(:greetings), comments(:more_greetings)] },
lambda { authors(:mary).comments << Comment.create!(body: "Yay", post_id: 424242) },
lambda { authors(:mary).comments.delete(authors(:mary).comments.first) },
- ].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) }
+ ].each { |block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) }
end
def test_dynamic_find_should_respect_association_order_for_through
@@ -2244,7 +2277,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
test "does not duplicate associations when used with natural primary keys" do
speedometer = Speedometer.create!(id: "4")
- speedometer.minivans.create!(minivan_id: "a-van-red" ,name: "a van", color: "red")
+ speedometer.minivans.create!(minivan_id: "a-van-red" , name: "a van", color: "red")
assert_equal 1, speedometer.minivans.to_a.size, "Only one association should be present:\n#{speedometer.minivans.to_a}"
assert_equal 1, speedometer.reload.minivans.to_a.size
@@ -2414,15 +2447,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
test "double insertion of new object to association when same association used in the after create callback of a new object" do
- car = Car.create!
- car.bulbs << TrickyBulb.new
- assert_equal 1, car.bulbs.size
- end
-
- def test_association_force_reload_with_only_true_is_deprecated
- company = Company.find(1)
-
- assert_deprecated { company.clients_of_firm(true) }
+ 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
class AuthorWithErrorDestroyingAssociation < ActiveRecord::Base
@@ -2463,9 +2493,34 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
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 }
+
+ car = Car.create!(name: "Car")
+ bulb = car.bulbs.create!
+
+ assert_equal [bulb], car.bulbs
+ end
+ end
+
private
def force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.load_target
end
+
+ def reset_callbacks(kind, klass)
+ old_callbacks = {}
+ old_callbacks[klass] = klass.send("_#{kind}_callbacks").dup
+ klass.subclasses.each do |subclass|
+ old_callbacks[subclass] = subclass.send("_#{kind}_callbacks").dup
+ end
+ yield
+ ensure
+ klass.send("_#{kind}_callbacks=", old_callbacks[klass])
+ klass.subclasses.each do |subclass|
+ subclass.send("_#{kind}_callbacks=", old_callbacks[subclass])
+ end
+ end
end
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 90004eb70f..47c6480a8e 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -75,7 +75,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
posts = person_prime.includes(:posts).first.posts
assert_operator posts.length, :>, 1
- posts.each_cons(2) do |left,right|
+ posts.each_cons(2) do |left, right|
assert_operator left.id, :>, right.id
end
end
@@ -175,7 +175,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
person = Person.new
post = Post.new
person.posts << post
- assert person.posts.include?(post)
+ assert_includes person.posts, post
end
def test_associate_existing
@@ -187,10 +187,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
assert_queries(1) do
- assert post.people.include?(person)
+ assert_includes post.people, person
end
- assert post.reload.people.reload.include?(person)
+ assert_includes post.reload.people.reload, person
end
def test_delete_all_for_with_dependent_option_destroy
@@ -266,7 +266,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
post.people.delete(person)
end
- assert !post.people.reload.include?(person)
+ assert_not_includes post.people.reload, person
end
def test_associating_new
@@ -284,10 +284,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
assert_queries(1) do
- assert posts(:thinking).people.include?(new_person)
+ assert_includes posts(:thinking).people, new_person
end
- assert posts(:thinking).reload.people.reload.include?(new_person)
+ assert_includes posts(:thinking).reload.people.reload, new_person
end
def test_associate_new_by_building
@@ -300,8 +300,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
# Should only need to load the association once
assert_queries(1) do
- assert posts(:thinking).people.collect(&:first_name).include?("Bob")
- assert posts(:thinking).people.collect(&:first_name).include?("Ted")
+ assert_includes posts(:thinking).people.collect(&:first_name), "Bob"
+ assert_includes posts(:thinking).people.collect(&:first_name), "Ted"
end
# 2 queries for each new record (1 to save the record itself, 1 for the join model)
@@ -312,8 +312,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
posts(:thinking).save
end
- assert posts(:thinking).reload.people.reload.collect(&:first_name).include?("Bob")
- assert posts(:thinking).reload.people.reload.collect(&:first_name).include?("Ted")
+ assert_includes posts(:thinking).reload.people.reload.collect(&:first_name), "Bob"
+ assert_includes posts(:thinking).reload.people.reload.collect(&:first_name), "Ted"
end
def test_build_then_save_with_has_many_inverse
@@ -322,7 +322,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
person.save
post.reload
- assert post.people.include?(person)
+ assert_includes post.people, person
end
def test_build_then_save_with_has_one_inverse
@@ -331,7 +331,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
person.save
post.reload
- assert post.single_people.include?(person)
+ assert_includes post.single_people, person
end
def test_both_parent_ids_set_when_saving_new
@@ -348,7 +348,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
def test_delete_association
- assert_queries(2){posts(:welcome);people(:michael); }
+ assert_queries(2) { posts(:welcome);people(:michael); }
assert_queries(1) do
posts(:welcome).people.delete(people(:michael))
@@ -402,7 +402,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
end
- assert_equal nil, reference.reload.job_id
+ assert_nil reference.reload.job_id
ensure
Reference.make_comments = false
end
@@ -423,7 +423,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
# Check that the destroy callback on Reference did not run
- assert_equal nil, person.reload.comments
+ assert_nil person.reload.comments
ensure
Reference.make_comments = false
end
@@ -485,7 +485,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
references.each do |reference|
- assert_equal nil, reference.reload.job_id
+ assert_nil reference.reload.job_id
end
end
@@ -541,7 +541,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
def test_replace_association
- assert_queries(4){posts(:welcome);people(:david);people(:michael); posts(:welcome).people.reload}
+ assert_queries(4) { posts(:welcome);people(:david);people(:michael); posts(:welcome).people.reload }
# 1 query to delete the existing reader (michael)
# 1 query to associate the new reader (david)
@@ -549,13 +549,13 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
posts(:welcome).people = [people(:david)]
end
- assert_queries(0){
- assert posts(:welcome).people.include?(people(:david))
- assert !posts(:welcome).people.include?(people(:michael))
+ assert_queries(0) {
+ assert_includes posts(:welcome).people, people(:david)
+ assert_not_includes posts(:welcome).people, people(:michael)
}
- assert posts(:welcome).reload.people.reload.include?(people(:david))
- assert !posts(:welcome).reload.people.reload.include?(people(:michael))
+ assert_includes posts(:welcome).reload.people.reload, people(:david)
+ assert_not_includes posts(:welcome).reload.people.reload, people(:michael)
end
def test_replace_order_is_preserved
@@ -591,10 +591,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
# *Now* we actually need the collection so it's loaded
assert_queries(1) do
- assert posts(:thinking).people.collect(&:first_name).include?("Jeb")
+ assert_includes posts(:thinking).people.collect(&:first_name), "Jeb"
end
- assert posts(:thinking).reload.people.reload.collect(&:first_name).include?("Jeb")
+ assert_includes posts(:thinking).reload.people.reload.collect(&:first_name), "Jeb"
end
def test_through_record_is_built_when_created_with_where
@@ -658,7 +658,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_push_with_invalid_join_record
repair_validations(Contract) do
- Contract.validate {|r| r.errors[:base] << "Invalid Contract" }
+ Contract.validate { |r| r.errors[:base] << "Invalid Contract" }
firm = companies(:first_firm)
lifo = Developer.new(name: "lifo")
@@ -702,7 +702,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
[:added, :after, "Bob"],
[:added, :before, "Lary"],
[:added, :after, "Lary"]
- ],log.last(6)
+ ], log.last(6)
post.people_with_callbacks.build(first_name: "Ted")
assert_equal [
@@ -716,7 +716,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
[:added, :after, "Sam"]
], log.last(2)
- post.people_with_callbacks = [people(:michael),people(:david), Person.new(first_name: "Julian"), Person.create!(first_name: "Roger")]
+ post.people_with_callbacks = [people(:michael), people(:david), Person.new(first_name: "Julian"), Person.create!(first_name: "Roger")]
assert_equal((%w(Ted Bob Sam Lary) * 2).sort, log[-12..-5].collect(&:last).sort)
assert_equal [
[:added, :before, "Julian"],
@@ -812,7 +812,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
lambda { authors(:david).very_special_comments = [VerySpecialComment.create!(body: "Gorp!", post_id: 1011), VerySpecialComment.create!(body: "Eep!", post_id: 1012)] },
lambda { authors(:david).very_special_comments << VerySpecialComment.create!(body: "Hoohah!", post_id: 1013) },
lambda { authors(:david).very_special_comments.delete(authors(:david).very_special_comments.first) },
- ].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) }
+ ].each { |block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) }
end
def test_has_many_association_through_a_has_many_association_to_self
@@ -832,14 +832,14 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
category = author.named_categories.build(name: "Primary")
author.save
assert Categorization.exists?(author_id: author.id, named_category_name: category.name)
- assert author.named_categories.reload.include?(category)
+ assert_includes author.named_categories.reload, category
end
def test_collection_create_with_nonstandard_primary_key_on_belongs_to
author = authors(:mary)
category = author.named_categories.create(name: "Primary")
assert Categorization.exists?(author_id: author.id, named_category_name: category.name)
- assert author.named_categories.reload.include?(category)
+ assert_includes author.named_categories.reload, category
end
def test_collection_exists
@@ -883,10 +883,32 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
+ def test_collection_singular_ids_setter_with_changed_primary_key
+ company = companies(:first_firm)
+ client = companies(:first_client)
+ company.clients_using_primary_key_ids = [client.name]
+ assert_equal [client], company.clients_using_primary_key
+ end
+
def test_collection_singular_ids_setter_raises_exception_when_invalid_ids_set
company = companies(:rails_core)
- ids = [Developer.first.id, -9999]
- assert_raises(ActiveRecord::AssociationTypeMismatch) {company.developer_ids= ids}
+ ids = [Developer.first.id, -9999]
+ e = assert_raises(ActiveRecord::RecordNotFound) { company.developer_ids = ids }
+ assert_match(/Couldn't find all Developers with 'id'/, e.message)
+ end
+
+ def test_collection_singular_ids_setter_raises_exception_when_invalid_ids_set_with_changed_primary_key
+ company = companies(:first_firm)
+ ids = [Client.first.name, "unknown client"]
+ e = assert_raises(ActiveRecord::RecordNotFound) { company.clients_using_primary_key_ids = ids }
+ assert_match(/Couldn't find all Clients with 'name'/, e.message)
+ end
+
+ def test_collection_singular_ids_through_setter_raises_exception_when_invalid_ids_set
+ author = authors(:david)
+ ids = [categories(:general).name, "Unknown"]
+ e = assert_raises(ActiveRecord::RecordNotFound) { author.essay_category_ids = ids }
+ assert_equal "Couldn't find all Categories with 'name': (General, Unknown) (found 1 results, but was looking for 2)", e.message
end
def test_build_a_model_from_hm_through_association_with_where_clause
@@ -908,14 +930,14 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
person = Person.new
reference = person.references.build
job = reference.build_job
- assert person.jobs.include?(job)
+ assert_includes person.jobs, job
end
def test_include_method_in_association_through_should_return_true_for_instance_added_with_nested_builds
author = Author.new
post = author.posts.build
comment = post.comments.build
- assert author.comments.include?(comment)
+ assert_includes author.comments, comment
end
def test_through_association_readonly_should_be_false
@@ -1022,7 +1044,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
post = posts(:welcome)
address = author_addresses(:david_address)
- assert post.author_addresses.include?(address)
+ assert_includes post.author_addresses, address
post.author_addresses.delete(address)
assert post[:author_count].nil?
end
@@ -1107,7 +1129,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_has_many_through_obeys_order_on_through_association
owner = owners(:blackbeard)
- assert owner.toys.to_sql.include?("pets.name desc")
+ assert_includes owner.toys.to_sql, "pets.name desc"
assert_equal ["parrot", "bulbul"], owner.toys.map { |r| r.pet.name }
end
@@ -1182,12 +1204,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_nil Club.new.special_favourites.distinct_value
end
- def test_association_force_reload_with_only_true_is_deprecated
- post = Post.find(1)
-
- assert_deprecated { post.people(true) }
- end
-
def test_has_many_through_do_not_cache_association_reader_if_the_though_method_has_default_scopes
member = Member.create!
club = Club.create!
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index ad0d47920d..ed22a9802f 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -186,25 +186,6 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert firm.account.present?
end
- def test_restrict_with_error_is_deprecated_using_key_one
- I18n.backend = I18n::Backend::Simple.new
- I18n.backend.store_translations :en, activerecord: { errors: { messages: { restrict_dependent_destroy: { one: "message for deprecated key" } } } }
-
- firm = RestrictedWithErrorFirm.create!(name: "restrict")
- firm.create_account(credit_limit: 10)
-
- assert_not_nil firm.account
-
- assert_deprecated { firm.destroy }
-
- assert !firm.errors.empty?
- assert_equal "message for deprecated key", firm.errors[:base].first
- assert RestrictedWithErrorFirm.exists?(name: "restrict")
- assert firm.account.present?
- ensure
- I18n.backend.reload!
- end
-
def test_restrict_with_error
firm = RestrictedWithErrorFirm.create!(name: "restrict")
firm.create_account(credit_limit: 10)
@@ -221,7 +202,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
def test_restrict_with_error_with_locale
I18n.backend = I18n::Backend::Simple.new
- I18n.backend.store_translations "en", activerecord: {attributes: {restricted_with_error_firm: {account: "firm account"}}}
+ I18n.backend.store_translations "en", activerecord: { attributes: { restricted_with_error_firm: { account: "firm account" } } }
firm = RestrictedWithErrorFirm.create!(name: "restrict")
firm.create_account(credit_limit: 10)
@@ -326,6 +307,16 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_reload_association
+ odegy = companies(:odegy)
+
+ assert_equal 53, odegy.account.credit_limit
+ Account.where(id: odegy.account.id).update_all(credit_limit: 80)
+ assert_equal 53, odegy.account.credit_limit
+
+ assert_equal 80, odegy.reload_account.credit_limit
+ end
+
def test_build
firm = Firm.new("name" => "GlobalMegaCorp")
firm.save
@@ -539,21 +530,21 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
def test_build_with_block
car = Car.create(name: "honda")
- bulb = car.build_bulb{ |b| b.color = "Red" }
+ bulb = car.build_bulb { |b| b.color = "Red" }
assert_equal "RED!", bulb.color
end
def test_create_with_block
car = Car.create(name: "honda")
- bulb = car.create_bulb{ |b| b.color = "Red" }
+ bulb = car.create_bulb { |b| b.color = "Red" }
assert_equal "RED!", bulb.color
end
def test_create_bang_with_block
car = Car.create(name: "honda")
- bulb = car.create_bulb!{ |b| b.color = "Red" }
+ bulb = car.create_bulb! { |b| b.color = "Red" }
assert_equal "RED!", bulb.color
end
@@ -601,7 +592,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
new_ship = Ship.create(name: "new name")
assert_queries(2) do
- # One query for updating name and second query for updating pirate_id
+ # One query to nullify the old ship, one query to update the new ship
pirate.ship = new_ship
end
@@ -654,12 +645,6 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
end
end
- def test_association_force_reload_with_only_true_is_deprecated
- firm = Firm.find(1)
-
- assert_deprecated { firm.account(true) }
- end
-
class SpecialBook < ActiveRecord::Base
self.table_name = "books"
belongs_to :author, class_name: "SpecialAuthor"
@@ -675,6 +660,6 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
book = SpecialBook.create!(status: "published")
author.book = book
- refute_equal 0, SpecialAuthor.joins(:book).where(books: { status: "published" } ).count
+ refute_equal 0, SpecialAuthor.joins(:book).where(books: { status: "published" }).count
end
end
diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb
index 3a7f48fbb7..432c3526a5 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -82,7 +82,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
def test_set_record_to_nil_should_delete_association
@member.club = nil
@member.reload
- assert_equal nil, @member.current_membership
+ assert_nil @member.current_membership
assert_nil @member.club
end
@@ -95,7 +95,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
Member.all.merge!(includes: :club, where: ["name = ?", "Groucho Marx"]).to_a
end
assert_equal 1, members.size
- assert_not_nil assert_no_queries {members[0].club}
+ assert_not_nil assert_no_queries { members[0].club }
end
def test_has_one_through_eager_loading_through_polymorphic
@@ -103,19 +103,19 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
Member.all.merge!(includes: :sponsor_club, where: ["name = ?", "Groucho Marx"]).to_a
end
assert_equal 1, members.size
- assert_not_nil assert_no_queries {members[0].sponsor_club}
+ assert_not_nil assert_no_queries { members[0].sponsor_club }
end
def test_has_one_through_with_conditions_eager_loading
# conditions on the through table
assert_equal clubs(:moustache_club), Member.all.merge!(includes: :favourite_club).find(@member.id).favourite_club
memberships(:membership_of_favourite_club).update_columns(favourite: false)
- assert_equal nil, Member.all.merge!(includes: :favourite_club).find(@member.id).reload.favourite_club
+ assert_nil Member.all.merge!(includes: :favourite_club).find(@member.id).reload.favourite_club
# conditions on the source table
assert_equal clubs(:moustache_club), Member.all.merge!(includes: :hairy_club).find(@member.id).hairy_club
clubs(:moustache_club).update_columns(name: "Association of Clean-Shaven Persons")
- assert_equal nil, Member.all.merge!(includes: :hairy_club).find(@member.id).reload.hairy_club
+ assert_nil Member.all.merge!(includes: :hairy_club).find(@member.id).reload.hairy_club
end
def test_has_one_through_polymorphic_with_source_type
@@ -123,9 +123,9 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
end
def test_eager_has_one_through_polymorphic_with_source_type
- clubs = Club.all.merge!(includes: :sponsored_member, where: ["name = ?","Moustache and Eyebrow Fancier Club"]).to_a
+ clubs = Club.all.merge!(includes: :sponsored_member, where: ["name = ?", "Moustache and Eyebrow Fancier Club"]).to_a
# Only the eyebrow fanciers club has a sponsored_member
- assert_not_nil assert_no_queries {clubs[0].sponsored_member}
+ assert_not_nil assert_no_queries { clubs[0].sponsored_member }
end
def test_has_one_through_nonpreload_eagerloading
@@ -133,7 +133,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
Member.all.merge!(includes: :club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name").to_a #force fallback
end
assert_equal 1, members.size
- assert_not_nil assert_no_queries {members[0].club}
+ assert_not_nil assert_no_queries { members[0].club }
end
def test_has_one_through_nonpreload_eager_loading_through_polymorphic
@@ -141,7 +141,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
Member.all.merge!(includes: :sponsor_club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name").to_a #force fallback
end
assert_equal 1, members.size
- assert_not_nil assert_no_queries {members[0].sponsor_club}
+ assert_not_nil assert_no_queries { members[0].sponsor_club }
end
def test_has_one_through_nonpreload_eager_loading_through_polymorphic_with_more_than_one_through_record
@@ -182,7 +182,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
@member.organization = @organization
end
assert_equal @organization, @member.organization
- assert @organization.members.include?(@member)
+ assert_includes @organization.members, @member
assert_equal "Extra", @member.member_detail.extra_data
end
@@ -197,16 +197,16 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
end
assert_equal @organization, @member.organization
assert_equal "Extra", @member.member_detail.extra_data
- assert @organization.members.include?(@member)
- assert !@new_organization.members.include?(@member)
+ assert_includes @organization.members, @member
+ assert_not_includes @new_organization.members, @member
assert_no_difference "MemberDetail.count" do
@member.organization = @new_organization
end
assert_equal @new_organization, @member.organization
assert_equal "Extra", @member.member_detail.extra_data
- assert !@organization.members.include?(@member)
- assert @new_organization.members.include?(@member)
+ assert_not_includes @organization.members, @member
+ assert_includes @new_organization.members, @member
end
def test_preloading_has_one_through_on_belongs_to
diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb
index be5c8854be..7414869c8f 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -20,7 +20,7 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
def test_construct_finder_sql_does_not_table_name_collide_on_duplicate_associations
assert_nothing_raised do
- sql = Person.joins(agents: {agents: :agents}).joins(agents: {agents: {primary_contact: :agents}}).to_sql
+ sql = Person.joins(agents: { agents: :agents }).joins(agents: { agents: { primary_contact: :agents } }).to_sql
assert_match(/agents_people_4/i, sql)
end
end
@@ -47,7 +47,7 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
end
def test_join_conditions_allow_nil_associations
- authors = Author.includes(:essays).where(essays: {id: nil})
+ authors = Author.includes(:essays).where(essays: { id: nil })
assert_equal 2, authors.count
end
@@ -60,13 +60,13 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
def test_find_with_implicit_inner_joins_honors_readonly_with_select
authors = Author.joins(:posts).select("authors.*").to_a
assert !authors.empty?, "expected authors to be non-empty"
- assert authors.all? {|a| !a.readonly? }, "expected no authors to be readonly"
+ assert authors.all? { |a| !a.readonly? }, "expected no authors to be readonly"
end
def test_find_with_implicit_inner_joins_honors_readonly_false
authors = Author.joins(:posts).readonly(false).to_a
assert !authors.empty?, "expected authors to be non-empty"
- assert authors.all? {|a| !a.readonly? }, "expected no authors to be readonly"
+ assert authors.all? { |a| !a.readonly? }, "expected no authors to be readonly"
end
def test_find_with_implicit_inner_joins_does_not_set_associations
@@ -76,17 +76,17 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
end
def test_count_honors_implicit_inner_joins
- real_count = Author.all.to_a.sum{|a| a.posts.count }
+ real_count = Author.all.to_a.sum { |a| a.posts.count }
assert_equal real_count, Author.joins(:posts).count, "plain inner join count should match the number of referenced posts records"
end
def test_calculate_honors_implicit_inner_joins
- real_count = Author.all.to_a.sum{|a| a.posts.count }
+ real_count = Author.all.to_a.sum { |a| a.posts.count }
assert_equal real_count, Author.joins(:posts).calculate(:count, "authors.id"), "plain inner join count should match the number of referenced posts records"
end
def test_calculate_honors_implicit_inner_joins_and_distinct_and_conditions
- real_count = Author.all.to_a.select {|a| a.posts.any? {|p| p.title.start_with?("Welcome")} }.length
+ real_count = Author.all.to_a.select { |a| a.posts.any? { |p| p.title.start_with?("Welcome") } }.length
authors_with_welcoming_post_titles = Author.all.merge!(joins: :posts, where: "posts.title like 'Welcome%'").distinct.calculate(:count, "authors.id")
assert_equal real_count, authors_with_welcoming_post_titles, "inner join and conditions should have only returned authors posting titles starting with 'Welcome'"
end
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index 4534cbe664..6fe6ee6783 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -227,7 +227,7 @@ class InverseHasOneTests < ActiveRecord::TestCase
end
def test_parent_instance_should_be_shared_with_eager_loaded_child_on_find
- m = Man.all.merge!(where: {name: "Gordon"}, includes: :face).first
+ m = Man.all.merge!(where: { name: "Gordon" }, includes: :face).first
f = m.face
assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
m.name = "Bongo"
@@ -235,7 +235,7 @@ class InverseHasOneTests < ActiveRecord::TestCase
f.man.name = "Mungo"
assert_equal m.name, f.man.name, "Name of man should be the same after changes to child-owned instance"
- m = Man.all.merge!(where: {name: "Gordon"}, includes: :face, order: "faces.id").first
+ m = Man.all.merge!(where: { name: "Gordon" }, includes: :face, order: "faces.id").first
f = m.face
assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
m.name = "Bongo"
@@ -310,7 +310,7 @@ class InverseHasManyTests < ActiveRecord::TestCase
end
def test_parent_instance_should_be_shared_with_eager_loaded_children
- m = Man.all.merge!(where: {name: "Gordon"}, includes: :interests).first
+ m = Man.all.merge!(where: { name: "Gordon" }, includes: :interests).first
is = m.interests
is.each do |i|
assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
@@ -320,7 +320,7 @@ class InverseHasManyTests < ActiveRecord::TestCase
assert_equal m.name, i.man.name, "Name of man should be the same after changes to child-owned instance"
end
- m = Man.all.merge!(where: {name: "Gordon"}, includes: :interests, order: "interests.id").first
+ m = Man.all.merge!(where: { name: "Gordon" }, includes: :interests, order: "interests.id").first
is = m.interests
is.each do |i|
assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
@@ -333,7 +333,7 @@ class InverseHasManyTests < ActiveRecord::TestCase
def test_parent_instance_should_be_shared_with_newly_block_style_built_child
m = Man.first
- i = m.interests.build {|ii| ii.topic = "Industrial Revolution Re-enactment"}
+ i = m.interests.build { |ii| ii.topic = "Industrial Revolution Re-enactment" }
assert_not_nil i.topic, "Child attributes supplied to build via blocks should be populated"
assert_not_nil i.man
assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
@@ -356,7 +356,7 @@ class InverseHasManyTests < ActiveRecord::TestCase
def test_parent_instance_should_be_shared_with_newly_block_style_created_child
m = Man.first
- i = m.interests.create {|ii| ii.topic = "Industrial Revolution Re-enactment"}
+ i = m.interests.create { |ii| ii.topic = "Industrial Revolution Re-enactment" }
assert_not_nil i.topic, "Child attributes supplied to create via blocks should be populated"
assert_not_nil i.man
assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
@@ -494,6 +494,33 @@ class InverseHasManyTests < ActiveRecord::TestCase
assert !man.persisted?
end
+
+ def test_inverse_instance_should_be_set_before_find_callbacks_are_run
+ reset_callbacks(Interest, :find) do
+ Interest.after_find { raise unless association(:man).loaded? && man.present? }
+
+ assert Man.first.interests.reload.any?
+ assert Man.includes(:interests).first.interests.any?
+ assert Man.joins(:interests).includes(:interests).first.interests.any?
+ end
+ end
+
+ def test_inverse_instance_should_be_set_before_initialize_callbacks_are_run
+ reset_callbacks(Interest, :initialize) do
+ Interest.after_initialize { raise unless association(:man).loaded? && man.present? }
+
+ assert Man.first.interests.reload.any?
+ assert Man.includes(:interests).first.interests.any?
+ assert Man.joins(:interests).includes(:interests).first.interests.any?
+ end
+ end
+
+ def reset_callbacks(target, type)
+ old_callbacks = target.send(:get_callbacks, type).deep_dup
+ yield
+ ensure
+ target.send(:set_callbacks, type, old_callbacks) if old_callbacks
+ end
end
class InverseBelongsToTests < ActiveRecord::TestCase
@@ -510,7 +537,7 @@ class InverseBelongsToTests < ActiveRecord::TestCase
end
def test_eager_loaded_child_instance_should_be_shared_with_parent_on_find
- f = Face.all.merge!(includes: :man, where: {description: "trusting"}).first
+ f = Face.all.merge!(includes: :man, where: { description: "trusting" }).first
m = f.man
assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance"
f.description = "gormless"
@@ -518,7 +545,7 @@ class InverseBelongsToTests < ActiveRecord::TestCase
m.face.description = "pleasing"
assert_equal f.description, m.face.description, "Description of face should be the same after changes to parent-owned instance"
- f = Face.all.merge!(includes: :man, order: "men.id", where: {description: "trusting"}).first
+ f = Face.all.merge!(includes: :man, order: "men.id", where: { description: "trusting" }).first
m = f.man
assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance"
f.description = "gormless"
@@ -553,7 +580,7 @@ class InverseBelongsToTests < ActiveRecord::TestCase
i = interests(:trainspotting)
m = i.man
assert_not_nil m.interests
- iz = m.interests.detect { |_iz| _iz.id == i.id}
+ iz = m.interests.detect { |_iz| _iz.id == i.id }
assert_not_nil iz
assert_equal i.topic, iz.topic, "Interest topics should be the same before changes to child"
i.topic = "Eating cheese with a spoon"
@@ -583,7 +610,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
fixtures :men, :faces, :interests
def test_child_instance_should_be_shared_with_parent_on_find
- f = Face.all.merge!(where: {description: "confused"}).first
+ f = Face.all.merge!(where: { description: "confused" }).first
m = f.polymorphic_man
assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same before changes to child instance"
f.description = "gormless"
@@ -593,7 +620,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
end
def test_eager_loaded_child_instance_should_be_shared_with_parent_on_find
- f = Face.all.merge!(where: {description: "confused"}, includes: :man).first
+ f = Face.all.merge!(where: { description: "confused" }, includes: :man).first
m = f.polymorphic_man
assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same before changes to child instance"
f.description = "gormless"
@@ -601,7 +628,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
m.polymorphic_face.description = "pleasing"
assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same after changes to parent-owned instance"
- f = Face.all.merge!(where: {description: "confused"}, includes: :man, order: "men.id").first
+ f = Face.all.merge!(where: { description: "confused" }, includes: :man, order: "men.id").first
m = f.polymorphic_man
assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same before changes to child instance"
f.description = "gormless"
@@ -654,7 +681,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
i = interests(:llama_wrangling)
m = i.polymorphic_man
assert_not_nil m.polymorphic_interests
- iz = m.polymorphic_interests.detect { |_iz| _iz.id == i.id}
+ iz = m.polymorphic_interests.detect { |_iz| _iz.id == i.id }
assert_not_nil iz
assert_equal i.topic, iz.topic, "Interest topics should be the same before changes to child"
i.topic = "Eating cheese with a spoon"
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index cf08cc9537..a4345f3857 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -24,15 +24,15 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
:edges
def test_has_many
- assert authors(:david).categories.include?(categories(:general))
+ assert_includes authors(:david).categories, categories(:general)
end
def test_has_many_inherited
- assert authors(:mary).categories.include?(categories(:sti_test))
+ assert_includes authors(:mary).categories, categories(:sti_test)
end
def test_inherited_has_many
- assert categories(:sti_test).authors.include?(authors(:mary))
+ assert_includes categories(:sti_test).authors, authors(:mary)
end
def test_has_many_distinct_through_join_model
@@ -155,21 +155,21 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
old_count = posts(:welcome).taggings.count
tagging = posts(:welcome).taggings.create(tag: tags(:misc))
assert_equal "Post", tagging.taggable_type
- assert_equal old_count+1, posts(:welcome).taggings.count
+ assert_equal old_count + 1, posts(:welcome).taggings.count
end
def test_create_bang_polymorphic_with_has_many_scope
old_count = posts(:welcome).taggings.count
tagging = posts(:welcome).taggings.create!(tag: tags(:misc))
assert_equal "Post", tagging.taggable_type
- assert_equal old_count+1, posts(:welcome).taggings.count
+ assert_equal old_count + 1, posts(:welcome).taggings.count
end
def test_create_polymorphic_has_one_with_scope
old_count = Tagging.count
tagging = posts(:welcome).create_tagging(tag: tags(:misc))
assert_equal "Post", tagging.taggable_type
- assert_equal old_count+1, Tagging.count
+ assert_equal old_count + 1, Tagging.count
end
def test_delete_polymorphic_has_many_with_delete_all
@@ -179,7 +179,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
old_count = Tagging.count
post.destroy
- assert_equal old_count-1, Tagging.count
+ assert_equal old_count - 1, Tagging.count
assert_equal 0, posts(:welcome).taggings.count
end
@@ -190,7 +190,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
old_count = Tagging.count
post.destroy
- assert_equal old_count-1, Tagging.count
+ assert_equal old_count - 1, Tagging.count
assert_equal 0, posts(:welcome).taggings.count
end
@@ -212,7 +212,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
old_count = Tagging.count
post.destroy
- assert_equal old_count-1, Tagging.count
+ assert_equal old_count - 1, Tagging.count
posts(:welcome).association(:tagging).reload
assert_nil posts(:welcome).tagging
end
@@ -237,7 +237,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
category = categories(:sti_test)
ernie = category.authors_with_select.create(name: "Ernie")
assert_nothing_raised do
- assert_equal ernie, category.authors_with_select.detect {|a| a.name == "Ernie"}
+ assert_equal ernie, category.authors_with_select.detect { |a| a.name == "Ernie" }
end
end
@@ -402,7 +402,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_has_many_through_polymorphic_has_one
- assert_equal Tagging.find(1,2).sort_by(&:id), authors(:david).taggings_2
+ assert_equal Tagging.find(1, 2).sort_by(&:id), authors(:david).taggings_2
end
def test_has_many_through_polymorphic_has_many
@@ -413,7 +413,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
author = Author.includes(:taggings).find authors(:david).id
expected_taggings = taggings(:welcome_general, :thinking_general)
assert_no_queries do
- assert_equal expected_taggings, author.taggings.distinct.sort_by(&:id)
+ assert_equal expected_taggings, author.taggings.uniq.sort_by(&:id)
end
end
@@ -421,7 +421,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
author = Author.all.merge!(where: ["name = ?", "David"], includes: :comments, order: "comments.id").first
SpecialComment.new; VerySpecialComment.new
assert_no_queries do
- assert_equal [1,2,3,5,6,7,8,9,10,12], author.comments.collect(&:id)
+ assert_equal [1, 2, 3, 5, 6, 7, 8, 9, 10, 12], author.comments.collect(&:id)
end
end
@@ -467,10 +467,10 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
saved_post.tags << new_tag
assert new_tag.persisted? #consistent with habtm!
assert saved_post.persisted?
- assert saved_post.tags.include?(new_tag)
+ assert_includes saved_post.tags, new_tag
assert new_tag.persisted?
- assert saved_post.reload.tags.reload.include?(new_tag)
+ assert_includes saved_post.reload.tags.reload, new_tag
new_post = Post.new(title: "Association replacement works!", body: "You best believe it.")
saved_tag = tags(:general)
@@ -478,11 +478,11 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
new_post.tags << saved_tag
assert !new_post.persisted?
assert saved_tag.persisted?
- assert new_post.tags.include?(saved_tag)
+ assert_includes new_post.tags, saved_tag
new_post.save!
assert new_post.persisted?
- assert new_post.reload.tags.reload.include?(saved_tag)
+ assert_includes new_post.reload.tags.reload, saved_tag
assert !posts(:thinking).tags.build.persisted?
assert !posts(:thinking).tags.new.persisted?
@@ -493,25 +493,25 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
push = Tag.create!(name: "pushme")
post_thinking = posts(:thinking)
assert_nothing_raised { post_thinking.tags << push }
- assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag },
+ assert_nil(wrong = post_thinking.tags.detect { |t| t.class != Tag },
message = "Expected a Tag in tags collection, got #{wrong.class}.")
- assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging },
+ assert_nil(wrong = post_thinking.taggings.detect { |t| t.class != Tagging },
message = "Expected a Tagging in taggings collection, got #{wrong.class}.")
assert_equal(count + 1, post_thinking.reload.tags.size)
assert_equal(count + 1, post_thinking.tags.reload.size)
assert_kind_of Tag, post_thinking.tags.create!(name: "foo")
- assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag },
+ assert_nil(wrong = post_thinking.tags.detect { |t| t.class != Tag },
message = "Expected a Tag in tags collection, got #{wrong.class}.")
- assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging },
+ assert_nil(wrong = post_thinking.taggings.detect { |t| t.class != Tagging },
message = "Expected a Tagging in taggings collection, got #{wrong.class}.")
assert_equal(count + 2, post_thinking.reload.tags.size)
assert_equal(count + 2, post_thinking.tags.reload.size)
assert_nothing_raised { post_thinking.tags.concat(Tag.create!(name: "abc"), Tag.create!(name: "def")) }
- assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag },
+ assert_nil(wrong = post_thinking.tags.detect { |t| t.class != Tag },
message = "Expected a Tag in tags collection, got #{wrong.class}.")
- assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging },
+ assert_nil(wrong = post_thinking.taggings.detect { |t| t.class != Tagging },
message = "Expected a Tagging in taggings collection, got #{wrong.class}.")
assert_equal(count + 4, post_thinking.reload.tags.size)
assert_equal(count + 4, post_thinking.tags.reload.size)
@@ -642,20 +642,20 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_polymorphic_has_many
expected = taggings(:welcome_general)
p = Post.all.merge!(includes: :taggings).find(posts(:welcome).id)
- assert_no_queries {assert p.taggings.include?(expected)}
- assert posts(:welcome).taggings.include?(taggings(:welcome_general))
+ assert_no_queries { assert_includes p.taggings, expected }
+ assert_includes posts(:welcome).taggings, taggings(:welcome_general)
end
def test_polymorphic_has_one
expected = posts(:welcome)
tagging = Tagging.all.merge!(includes: :taggable).find(taggings(:welcome_general).id)
- assert_no_queries { assert_equal expected, tagging.taggable}
+ assert_no_queries { assert_equal expected, tagging.taggable }
end
def test_polymorphic_belongs_to
- p = Post.all.merge!(includes: {taggings: :taggable}).find(posts(:welcome).id)
- assert_no_queries {assert_equal posts(:welcome), p.taggings.first.taggable}
+ p = Post.all.merge!(includes: { taggings: :taggable }).find(posts(:welcome).id)
+ assert_no_queries { assert_equal posts(:welcome), p.taggings.first.taggable }
end
def test_preload_polymorphic_has_many_through
@@ -675,8 +675,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
taggables = taggings.map(&:taggable)
- assert taggables.include?(items(:dvd))
- assert taggables.include?(posts(:welcome))
+ assert_includes taggables, items(:dvd)
+ assert_includes taggables, posts(:welcome)
end
def test_preload_nil_polymorphic_belongs_to
@@ -709,7 +709,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert_no_queries do
assert david.categories.loaded?
- assert david.categories.include?(category)
+ assert_includes david.categories, category
end
end
@@ -720,7 +720,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
david.reload
assert ! david.categories.loaded?
assert_queries(1) do
- assert david.categories.include?(category)
+ assert_includes david.categories, category
end
assert ! david.categories.loaded?
end
@@ -748,17 +748,17 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_proper_error_message_for_eager_load_and_includes_association_errors
includes_error = assert_raises(ActiveRecord::ConfigurationError) {
- Post.includes(:nonexistent_relation).where(nonexistent_relation: {name: "Rochester"}).find(1)
+ Post.includes(:nonexistent_relation).where(nonexistent_relation: { name: "Rochester" }).find(1)
}
assert_equal("Can't join 'Post' to association named 'nonexistent_relation'; perhaps you misspelled it?", includes_error.message)
eager_load_error = assert_raises(ActiveRecord::ConfigurationError) {
- Post.eager_load(:nonexistent_relation).where(nonexistent_relation: {name: "Rochester"}).find(1)
+ Post.eager_load(:nonexistent_relation).where(nonexistent_relation: { name: "Rochester" }).find(1)
}
assert_equal("Can't join 'Post' to association named 'nonexistent_relation'; perhaps you misspelled it?", eager_load_error.message)
includes_and_eager_load_error = assert_raises(ActiveRecord::ConfigurationError) {
- Post.eager_load(:nonexistent_relation).includes(:nonexistent_relation).where(nonexistent_relation: {name: "Rochester"}).find(1)
+ Post.eager_load(:nonexistent_relation).includes(:nonexistent_relation).where(nonexistent_relation: { name: "Rochester" }).find(1)
}
assert_equal("Can't join 'Post' to association named 'nonexistent_relation'; perhaps you misspelled it?", includes_and_eager_load_error.message)
end
diff --git a/activerecord/test/cases/associations/left_outer_join_association_test.rb b/activerecord/test/cases/associations/left_outer_join_association_test.rb
index 003240bb26..42dbbad1c8 100644
--- a/activerecord/test/cases/associations/left_outer_join_association_test.rb
+++ b/activerecord/test/cases/associations/left_outer_join_association_test.rb
@@ -5,7 +5,6 @@ require "models/author"
require "models/essay"
require "models/categorization"
require "models/person"
-require "active_support/core_ext/regexp"
class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
fixtures :authors, :essays, :posts, :comments, :categorizations, :people
@@ -18,35 +17,44 @@ class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
def test_construct_finder_sql_does_not_table_name_collide_on_duplicate_associations
assert_nothing_raised do
queries = capture_sql do
- Person.left_outer_joins(agents: {agents: :agents})
- .left_outer_joins(agents: {agents: {primary_contact: :agents}}).to_a
+ Person.left_outer_joins(agents: { agents: :agents })
+ .left_outer_joins(agents: { agents: { primary_contact: :agents } }).to_a
end
assert queries.any? { |sql| /agents_people_4/i.match?(sql) }
end
end
- def test_construct_finder_sql_executes_a_left_outer_join
- assert_not_equal Author.count, Author.joins(:posts).count
- assert_equal Author.count, Author.left_outer_joins(:posts).count
+ def test_left_outer_joins_count_is_same_as_size_of_loaded_results
+ assert_equal 17, Post.left_outer_joins(:comments).to_a.size
+ assert_equal 17, Post.left_outer_joins(:comments).count
end
- def test_left_outer_join_by_left_joins
- assert_not_equal Author.count, Author.joins(:posts).count
- assert_equal Author.count, Author.left_joins(:posts).count
+ def test_left_joins_aliases_left_outer_joins
+ assert_equal Post.left_outer_joins(:comments).to_sql, Post.left_joins(:comments).to_sql
+ end
+
+ def test_left_outer_joins_return_has_value_for_every_comment
+ all_post_ids = Post.pluck(:id)
+ assert_equal all_post_ids, all_post_ids & Post.left_outer_joins(:comments).pluck(:id)
+ end
+
+ def test_left_outer_joins_actually_does_a_left_outer_join
+ queries = capture_sql { Author.left_outer_joins(:posts).to_a }
+ assert queries.any? { |sql| /LEFT OUTER JOIN/i.match?(sql) }
end
def test_construct_finder_sql_ignores_empty_left_outer_joins_hash
- queries = capture_sql { Author.left_outer_joins({}) }
+ queries = capture_sql { Author.left_outer_joins({}).to_a }
assert queries.none? { |sql| /LEFT OUTER JOIN/i.match?(sql) }
end
def test_construct_finder_sql_ignores_empty_left_outer_joins_array
- queries = capture_sql { Author.left_outer_joins([]) }
+ queries = capture_sql { Author.left_outer_joins([]).to_a }
assert queries.none? { |sql| /LEFT OUTER JOIN/i.match?(sql) }
end
def test_left_outer_joins_forbids_to_use_string_as_argument
- assert_raise(ArgumentError){ Author.left_outer_joins('LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"').to_a }
+ assert_raise(ArgumentError) { Author.left_outer_joins('LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"').to_a }
end
def test_join_conditions_added_to_join_clause
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 45aefde3e1..a223b4338f 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -57,8 +57,8 @@ class AssociationsTest < ActiveRecord::TestCase
end
def test_include_with_order_works
- assert_nothing_raised {Account.all.merge!(order: "id", includes: :firm).first}
- assert_nothing_raised {Account.all.merge!(order: :id, includes: :firm).first}
+ assert_nothing_raised { Account.all.merge!(order: "id", includes: :firm).first }
+ assert_nothing_raised { Account.all.merge!(order: :id, includes: :firm).first }
end
def test_bad_collection_keys
@@ -88,10 +88,10 @@ class AssociationsTest < ActiveRecord::TestCase
assert firm.clients.empty?, "New firm should have cached no client objects"
assert_equal 0, firm.clients.size, "New firm should have cached 0 clients count"
- ActiveSupport::Deprecation.silence do
- assert !firm.clients(true).empty?, "New firm should have reloaded client objects"
- assert_equal 1, firm.clients(true).size, "New firm should have reloaded clients count"
- end
+ firm.clients.reload
+
+ assert !firm.clients.empty?, "New firm should have reloaded client objects"
+ assert_equal 1, firm.clients.size, "New firm should have reloaded clients count"
end
def test_using_limitable_reflections_helper
@@ -104,19 +104,6 @@ class AssociationsTest < ActiveRecord::TestCase
assert !using_limitable_reflections.call(mixed_reflections), "No collection associations (has many style) should pass"
end
- def test_force_reload_is_uncached
- firm = Firm.create!("name" => "A New Firm, Inc")
- Client.create!("name" => "TheClient.com", :firm => firm)
-
- ActiveSupport::Deprecation.silence do
- ActiveRecord::Base.cache do
- firm.clients.each {}
- assert_queries(0) { assert_not_nil firm.clients.each {} }
- assert_queries(1) { assert_not_nil firm.clients(true).each {} }
- end
- end
- end
-
def test_association_with_references
firm = companies(:first_firm)
assert_includes firm.association_with_references.references_values, "foo"
@@ -131,7 +118,7 @@ class AssociationProxyTest < ActiveRecord::TestCase
david.posts << (post = Post.new(title: "New on Edge", body: "More cool stuff!"))
assert !david.posts.loaded?
- assert david.posts.include?(post)
+ assert_includes david.posts, post
end
def test_push_has_many_through_does_not_load_target
@@ -139,7 +126,7 @@ class AssociationProxyTest < ActiveRecord::TestCase
david.categories << categories(:technology)
assert !david.categories.loaded?
- assert david.categories.include?(categories(:technology))
+ assert_includes david.categories, categories(:technology)
end
def test_push_followed_by_save_does_not_load_target
@@ -149,7 +136,7 @@ class AssociationProxyTest < ActiveRecord::TestCase
assert !david.posts.loaded?
david.save
assert !david.posts.loaded?
- assert david.posts.include?(post)
+ assert_includes david.posts, post
end
def test_push_does_not_lose_additions_to_new_record
@@ -199,13 +186,13 @@ class AssociationProxyTest < ActiveRecord::TestCase
end
def test_create_via_association_with_block
- post = authors(:david).posts.create(title: "New on Edge") {|p| p.body = "More cool stuff!"}
+ post = authors(:david).posts.create(title: "New on Edge") { |p| p.body = "More cool stuff!" }
assert_equal post.title, "New on Edge"
assert_equal post.body, "More cool stuff!"
end
def test_create_with_bang_via_association_with_block
- post = authors(:david).posts.create!(title: "New on Edge") {|p| p.body = "More cool stuff!"}
+ post = authors(:david).posts.create!(title: "New on Edge") { |p| p.body = "More cool stuff!" }
assert_equal post.title, "New on Edge"
assert_equal post.body, "More cool stuff!"
end
@@ -256,6 +243,13 @@ class AssociationProxyTest < ActiveRecord::TestCase
assert_no_queries { david.posts.first! }
end
+ def test_pluck_uses_loaded_target
+ david = authors(:david)
+ assert_equal david.posts.pluck(:title), david.posts.load.pluck(:title)
+ assert david.posts.loaded?
+ assert_no_queries { david.posts.pluck(:title) }
+ end
+
def test_reset_unloads_target
david = authors(:david)
david.posts.reload
diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb
index 2d2ecb4079..978dd93fa4 100644
--- a/activerecord/test/cases/attribute_methods/read_test.rb
+++ b/activerecord/test/cases/attribute_methods/read_test.rb
@@ -1,5 +1,4 @@
require "cases/helper"
-require "thread"
module ActiveRecord
module AttributeMethods
@@ -15,6 +14,7 @@ module ActiveRecord
def self.decorate_matching_attribute_types(*); end
def self.initialize_generated_modules; end
+ include ActiveRecord::DefineCallbacks
include ActiveRecord::AttributeMethods
def self.attribute_names
@@ -40,13 +40,13 @@ module ActiveRecord
instance = @klass.new
@klass.attribute_names.each do |name|
- assert !instance.methods.map(&:to_s).include?(name)
+ assert_not_includes instance.methods.map(&:to_s), name
end
@klass.define_attribute_methods
@klass.attribute_names.each do |name|
- assert instance.methods.map(&:to_s).include?(name), "#{name} is not defined"
+ assert_includes instance.methods.map(&:to_s), name, "#{name} is not defined"
end
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 7a2041937d..3dc0c0ce53 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -92,7 +92,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
test "attribute keys on a new instance" do
t = Topic.new
- assert_equal nil, t.title, "The topics table has a title column, so it should be nil"
+ assert_nil t.title, "The topics table has a title column, so it should be nil"
assert_raise(NoMethodError) { t.title2 }
end
@@ -156,7 +156,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
keyboard = Keyboard.create
keyboard.key_number = "10"
assert_equal "10", keyboard.id_before_type_cast
- assert_equal nil, keyboard.read_attribute_before_type_cast("id")
+ assert_nil keyboard.read_attribute_before_type_cast("id")
assert_equal "10", keyboard.read_attribute_before_type_cast("key_number")
assert_equal "10", keyboard.read_attribute_before_type_cast(:key_number)
end
@@ -213,7 +213,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
record.written_on = "345643456"
assert_equal "345643456", record.written_on_before_type_cast
- assert_equal nil, record.written_on
+ assert_nil record.written_on
record.written_on = "2009-10-11 12:13:14"
assert_equal "2009-10-11 12:13:14", record.written_on_before_type_cast
@@ -294,7 +294,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
topic = Topic.new(new_topic)
assert_equal new_topic[:title], topic.title
- topic.attributes= new_topic_values
+ topic.attributes = new_topic_values
assert_equal new_topic_values[:title], topic.title
end
@@ -319,6 +319,13 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal "Still another topic: part 4", topic.title
end
+ test "write_attribute can write aliased attributes as well" do
+ topic = Topic.new(title: "Don't change the topic")
+ topic.write_attribute :heading, "New topic"
+
+ assert_equal "New topic", topic.title
+ end
+
test "read_attribute" do
topic = Topic.new
topic.title = "Don't change the topic"
@@ -329,6 +336,16 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal "Don't change the topic", topic[:title]
end
+ test "read_attribute can read aliased attributes as well" do
+ topic = Topic.new(title: "Don't change the topic")
+
+ assert_equal "Don't change the topic", topic.read_attribute("heading")
+ assert_equal "Don't change the topic", topic["heading"]
+
+ assert_equal "Don't change the topic", topic.read_attribute(:heading)
+ assert_equal "Don't change the topic", topic[:heading]
+ end
+
test "read_attribute raises ActiveModel::MissingAttributeError when the attribute does not exist" do
computer = Computer.select("id").first
assert_raises(ActiveModel::MissingAttributeError) { computer[:developer] }
@@ -609,7 +626,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
utc_time = Time.utc(2008, 1, 1)
cst_time = utc_time.in_time_zone("Central Time (US & Canada)")
in_time_zone "Pacific Time (US & Canada)" do
- record = @target.new
+ record = @target.new
record.written_on = cst_time
assert_equal utc_time, record.written_on
assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone
@@ -633,7 +650,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
(-11..13).each do |timezone_offset|
time_string = utc_time.in_time_zone(timezone_offset).to_s
in_time_zone "Pacific Time (US & Canada)" do
- record = @target.new
+ record = @target.new
record.written_on = time_string
assert_equal Time.zone.parse(time_string), record.written_on
assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone
@@ -654,7 +671,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
test "setting a time zone-aware attribute to a blank string returns nil" do
in_time_zone "Pacific Time (US & Canada)" do
- record = @target.new
+ record = @target.new
record.written_on = " "
assert_nil record.written_on
assert_nil record[:written_on]
@@ -665,7 +682,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
time_string = "Tue Jan 01 00:00:00 2008"
(-11..13).each do |timezone_offset|
in_time_zone timezone_offset do
- record = @target.new
+ record = @target.new
record.written_on = time_string
assert_equal Time.zone.parse(time_string), record.written_on
assert_equal ActiveSupport::TimeZone[timezone_offset], record.written_on.time_zone
@@ -677,7 +694,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
test "setting a time zone-aware datetime in the current time zone" do
utc_time = Time.utc(2008, 1, 1)
in_time_zone "Pacific Time (US & Canada)" do
- record = @target.new
+ record = @target.new
record.written_on = utc_time.in_time_zone
assert_equal utc_time, record.written_on
assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone
@@ -737,7 +754,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
test "time zone-aware attributes do not recurse infinitely on invalid values" do
in_time_zone "Pacific Time (US & Canada)" do
record = @target.new(bonus_time: [])
- assert_equal nil, record.bonus_time
+ assert_nil record.bonus_time
end
end
@@ -755,7 +772,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
topic = @target.new(title: "The pros and cons of programming naked.")
assert !topic.respond_to?(:title)
exception = assert_raise(NoMethodError) { topic.title }
- assert exception.message.include?("private method")
+ assert_includes exception.message, "private method"
assert_equal "I'm private", topic.send(:title)
end
@@ -765,7 +782,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
topic = @target.new
assert !topic.respond_to?(:title=)
exception = assert_raise(NoMethodError) { topic.title = "Pants" }
- assert exception.message.include?("private method")
+ assert_includes exception.message, "private method"
topic.send(:title=, "Very large pants")
end
@@ -775,7 +792,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
topic = @target.new(title: "Isaac Newton's pants")
assert !topic.respond_to?(:title?)
exception = assert_raise(NoMethodError) { topic.title? }
- assert exception.message.include?("private method")
+ assert_includes exception.message, "private method"
assert topic.send(:title?)
end
@@ -1003,10 +1020,10 @@ class AttributeMethodsTest < ActiveRecord::TestCase
def privatize(method_signature)
@target.class_eval(<<-private_method, __FILE__, __LINE__ + 1)
- private
- def #{method_signature}
- "I'm private"
- end
- private_method
+ private
+ def #{method_signature}
+ "I'm private"
+ end
+ private_method
end
end
diff --git a/activerecord/test/cases/attribute_set_test.rb b/activerecord/test/cases/attribute_set_test.rb
index 059b5b2401..bd4b200735 100644
--- a/activerecord/test/cases/attribute_set_test.rb
+++ b/activerecord/test/cases/attribute_set_test.rb
@@ -25,7 +25,7 @@ module ActiveRecord
attributes = builder.build_from_database(foo: "3.3")
assert_equal "3.3", attributes[:foo].value_before_type_cast
- assert_equal nil, attributes[:bar].value_before_type_cast
+ assert_nil attributes[:bar].value_before_type_cast
assert_equal :bar, attributes[:bar].name
end
diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb
index 7ec1f6f18c..3705a6be89 100644
--- a/activerecord/test/cases/attributes_test.rb
+++ b/activerecord/test/cases/attributes_test.rb
@@ -112,7 +112,7 @@ module ActiveRecord
assert_equal 7, klass.attribute_types.length
assert_equal 7, klass.column_defaults.length
- assert klass.attribute_types.include?("wibble")
+ assert_includes klass.attribute_types, "wibble"
end
test "the given default value is cast from user" do
@@ -249,5 +249,19 @@ module ActiveRecord
model.foo = "lol"
refute model.changed?
end
+
+ test "attributes not backed by database columns appear in inspect" do
+ inspection = OverloadedType.new.inspect
+
+ assert_includes inspection, "non_existent_decimal"
+ end
+
+ test "attributes do not require a type" do
+ klass = Class.new(OverloadedType) do
+ attribute :no_type
+ end
+ assert_equal 1, klass.new(no_type: 1).no_type
+ assert_equal "foo", klass.new(no_type: "foo").no_type
+ end
end
end
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index ae5127cc56..6d31b7a091 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -1,5 +1,6 @@
require "cases/helper"
require "models/bird"
+require "models/post"
require "models/comment"
require "models/company"
require "models/customer"
@@ -11,7 +12,6 @@ require "models/order"
require "models/parrot"
require "models/person"
require "models/pirate"
-require "models/post"
require "models/reader"
require "models/ship"
require "models/ship_part"
@@ -36,11 +36,11 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
private
- def should_be_cool
- unless self.first_name == "cool"
- errors.add :first_name, "not cool"
+ def should_be_cool
+ unless self.first_name == "cool"
+ errors.add :first_name, "not cool"
+ end
end
- end
}
reference = Class.new(ActiveRecord::Base) {
self.table_name = "references"
@@ -196,21 +196,21 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas
end
def test_callbacks_firing_order_on_create
- eye = Eye.create(iris_attributes: {color: "honey"})
+ eye = Eye.create(iris_attributes: { color: "honey" })
assert_equal [true, false], eye.after_create_callbacks_stack
end
def test_callbacks_firing_order_on_update
- eye = Eye.create(iris_attributes: {color: "honey"})
- eye.update(iris_attributes: {color: "green"})
+ eye = Eye.create(iris_attributes: { color: "honey" })
+ eye.update(iris_attributes: { color: "green" })
assert_equal [true, false], eye.after_update_callbacks_stack
end
def test_callbacks_firing_order_on_save
- eye = Eye.create(iris_attributes: {color: "honey"})
+ eye = Eye.create(iris_attributes: { color: "honey" })
assert_equal [false, false], eye.after_save_callbacks_stack
- eye.update(iris_attributes: {color: "blue"})
+ eye.update(iris_attributes: { color: "blue" })
assert_equal [false, false, false, false], eye.after_save_callbacks_stack
end
end
@@ -443,7 +443,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib
assert_not invalid_electron.valid?
assert valid_electron.valid?
assert_not molecule.valid?
- assert_equal [{error: :blank}], molecule.errors.details["electrons.name"]
+ assert_equal [{ error: :blank }], molecule.errors.details[:"electrons.name"]
end
def test_errors_details_should_be_indexed_when_passed_as_array
@@ -457,8 +457,8 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib
assert_not tuning_peg_invalid.valid?
assert tuning_peg_valid.valid?
assert_not guitar.valid?
- assert_equal [{error: :not_a_number, value: nil}] , guitar.errors.details["tuning_pegs[1].pitch"]
- assert_equal [], guitar.errors.details["tuning_pegs.pitch"]
+ assert_equal [{ error: :not_a_number, value: nil }], guitar.errors.details[:"tuning_pegs[1].pitch"]
+ assert_equal [], guitar.errors.details[:"tuning_pegs.pitch"]
end
def test_errors_details_should_be_indexed_when_global_flag_is_set
@@ -474,8 +474,8 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib
assert_not invalid_electron.valid?
assert valid_electron.valid?
assert_not molecule.valid?
- assert_equal [{error: :blank}], molecule.errors.details["electrons[1].name"]
- assert_equal [], molecule.errors.details["electrons.name"]
+ assert_equal [{ error: :blank }], molecule.errors.details[:"electrons[1].name"]
+ assert_equal [], molecule.errors.details[:"electrons.name"]
ensure
ActiveRecord::Base.index_nested_attribute_errors = old_attribute_config
end
@@ -585,7 +585,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
firm.save
firm.reload
assert_equal 2, firm.clients.length
- assert firm.clients.include?(companies(:second_client))
+ assert_includes firm.clients, companies(:second_client)
end
def test_assign_ids_for_through_a_belongs_to
@@ -594,7 +594,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
post.save
post.reload
assert_equal 2, post.people.length
- assert post.people.include?(people(:david))
+ assert_includes post.people, people(:david)
end
def test_build_before_save
@@ -610,7 +610,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
def test_build_many_before_save
company = companies(:first_firm)
- assert_no_queries(ignore_none: false) { company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) }
+ assert_no_queries(ignore_none: false) { company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) }
company.name += "-changed"
assert_queries(3) { assert company.save }
@@ -619,7 +619,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
def test_build_via_block_before_save
company = companies(:first_firm)
- new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build {|client| client.name = "Another Client" } }
+ new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build { |client| client.name = "Another Client" } }
assert !company.clients_of_firm.loaded?
company.name += "-changed"
@@ -631,7 +631,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
def test_build_many_via_block_before_save
company = companies(:first_firm)
assert_no_queries(ignore_none: false) do
- company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) do |client|
+ company.clients_of_firm.build([{ "name" => "Another Client" }, { "name" => "Another Client II" }]) do |client|
client.name = "changed"
end
end
@@ -647,7 +647,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
assert firm.save
firm.reload
assert_equal 2, firm.clients.length
- assert firm.clients.include?(Client.find_by_name("New Client"))
+ assert_includes firm.clients, Client.find_by_name("New Client")
end
end
@@ -792,6 +792,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
end
@ship.pirate.catchphrase = "Changed Catchphrase"
+ @ship.name_will_change!
assert_raise(RuntimeError) { assert !@pirate.save }
assert_not_nil @pirate.reload.ship
@@ -1130,7 +1131,7 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
assert_queries(0) { @ship.save! }
@parrot = @pirate.parrots.create(name: "some_name")
- @parrot.name="changed_name"
+ @parrot.name = "changed_name"
assert_queries(1) { @ship.save! }
assert_queries(0) { @ship.save! }
end
@@ -1390,6 +1391,14 @@ module AutosaveAssociationOnACollectionAssociationTests
assert_equal "Squawky", parrot.reload.name
end
+ def test_should_not_update_children_when_parent_creation_with_no_reason
+ parrot = Parrot.create!(name: "Polly")
+ assert_equal 0, parrot.updated_count
+
+ Pirate.create!(parrot_ids: [parrot.id], catchphrase: "Arrrr")
+ assert_equal 0, parrot.reload.updated_count
+ end
+
def test_should_automatically_validate_the_associated_models
@pirate.send(@association_name).each { |child| child.name = "" }
@@ -1407,9 +1416,9 @@ module AutosaveAssociationOnACollectionAssociationTests
end
def test_should_default_invalid_error_from_i18n
- I18n.backend.store_translations(:en, activerecord: {errors: { models:
+ I18n.backend.store_translations(:en, activerecord: { errors: { models:
{ @associated_model_name.to_s.to_sym => { blank: "cannot be blank" } }
- }})
+ } })
@pirate.send(@association_name).build(name: "")
@@ -1698,3 +1707,27 @@ class TestAutosaveAssociationWithTouch < ActiveRecord::TestCase
assert_nothing_raised { invoice.line_items.create(amount: 10) }
end
end
+
+class TestAutosaveAssociationOnAHasManyAssociationWithInverse < ActiveRecord::TestCase
+ class Post < ActiveRecord::Base
+ has_many :comments, inverse_of: :post
+ end
+
+ class Comment < ActiveRecord::Base
+ belongs_to :post, inverse_of: :comments
+
+ attr_accessor :post_comments_count
+ after_save do
+ self.post_comments_count = post.comments.count
+ end
+ end
+
+ def test_after_save_callback_with_autosave
+ post = Post.new(title: "Test", body: "...")
+ comment = post.comments.build(body: "...")
+ post.save!
+
+ assert_equal 1, post.comments.count
+ assert_equal 1, comment.post_comments_count
+ end
+end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index cd896e5948..a611cc208c 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -4,6 +4,7 @@ require "models/author"
require "models/topic"
require "models/reply"
require "models/category"
+require "models/categorization"
require "models/company"
require "models/customer"
require "models/developer"
@@ -33,8 +34,6 @@ class SecondAbstractClass < FirstAbstractClass
self.abstract_class = true
end
class Photo < SecondAbstractClass; end
-class Category < ActiveRecord::Base; end
-class Categorization < ActiveRecord::Base; end
class Smarts < ActiveRecord::Base; end
class CreditCard < ActiveRecord::Base
class PinNumber < ActiveRecord::Base
@@ -45,8 +44,6 @@ class CreditCard < ActiveRecord::Base
class Brand < Category; end
end
class MasterCreditCard < ActiveRecord::Base; end
-class Post < ActiveRecord::Base; end
-class Computer < ActiveRecord::Base; end
class NonExistentTable < ActiveRecord::Base; end
class TestOracleDefault < ActiveRecord::Base; end
@@ -56,12 +53,6 @@ end
class Weird < ActiveRecord::Base; end
-class Boolean < ActiveRecord::Base
- def has_fun
- super
- end
-end
-
class LintTest < ActiveRecord::TestCase
include ActiveModel::Lint::Tests
@@ -107,12 +98,11 @@ class BasicsTest < ActiveRecord::TestCase
assert_nil Edge.primary_key
end
- unless current_adapter?(:PostgreSQLAdapter, :OracleAdapter, :SQLServerAdapter, :FbAdapter)
- def test_limit_with_comma
- assert_deprecated do
- assert Topic.limit("1,2").to_a
- end
- end
+ def test_many_mutations
+ car = Car.new name: "<3<3<3"
+ car.engines_count = 0
+ 20_000.times { car.engines_count += 1 }
+ assert car.save
end
def test_limit_without_comma
@@ -137,10 +127,8 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_limit_should_sanitize_sql_injection_for_limit_with_commas
- assert_deprecated do
- assert_raises(ArgumentError) do
- Topic.limit("1, 7 procedure help()").to_a
- end
+ assert_raises(ArgumentError) do
+ Topic.limit("1, 7 procedure help()").to_a
end
end
@@ -300,7 +288,7 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_create_after_initialize_with_block
- cb = CustomBulb.create {|c| c.name = "Dude" }
+ cb = CustomBulb.create { |c| c.name = "Dude" }
assert_equal("Dude", cb.name)
assert_equal(true, cb.frickinawesome)
end
@@ -487,12 +475,12 @@ class BasicsTest < ActiveRecord::TestCase
def test_utc_as_time_zone_and_new
with_timezone_config default: :utc do
- attributes = { "bonus_time(1i)"=>"2000",
- "bonus_time(2i)"=>"1",
- "bonus_time(3i)"=>"1",
- "bonus_time(4i)"=>"10",
- "bonus_time(5i)"=>"35",
- "bonus_time(6i)"=>"50" }
+ attributes = { "bonus_time(1i)" => "2000",
+ "bonus_time(2i)" => "1",
+ "bonus_time(3i)" => "1",
+ "bonus_time(4i)" => "10",
+ "bonus_time(5i)" => "35",
+ "bonus_time(6i)" => "50" }
topic = Topic.new(attributes)
assert_equal Time.utc(2000, 1, 1, 10, 35, 50), topic.bonus_time
end
@@ -615,7 +603,7 @@ class BasicsTest < ActiveRecord::TestCase
Topic.find(topic.id).destroy
end
- assert_equal nil, Topic.find_by_id(topic.id)
+ assert_nil Topic.find_by_id(topic.id)
end
def test_comparison_with_different_objects
@@ -657,7 +645,7 @@ class BasicsTest < ActiveRecord::TestCase
Weird.reset_column_information
- assert_equal ["EUC-JP"], Weird.columns.map {|c| c.name.encoding.name }.uniq
+ assert_equal ["EUC-JP"], Weird.columns.map { |c| c.name.encoding.name }.uniq
ensure
silence_warnings { Encoding.default_internal = old_default_internal }
Weird.reset_column_information
@@ -891,7 +879,7 @@ class BasicsTest < ActiveRecord::TestCase
# fixed dates / times
assert_equal Date.new(2004, 1, 1), default.fixed_date
- assert_equal Time.local(2004, 1,1,0,0,0,0), default.fixed_time
+ assert_equal Time.local(2004, 1, 1, 0, 0, 0, 0), default.fixed_time
# char types
assert_equal "Y", default.char1
@@ -1102,7 +1090,7 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_set_table_name_with_inheritance
- k = Class.new( ActiveRecord::Base )
+ k = Class.new(ActiveRecord::Base)
def k.name; "Foo"; end
def k.table_name; super + "ks"; end
assert_equal "foosks", k.table_name
@@ -1153,7 +1141,7 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_find_last
- last = Developer.last
+ last = Developer.last
assert_equal last, Developer.all.merge!(order: "id desc").first
end
@@ -1172,17 +1160,17 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_find_ordered_last
- last = Developer.all.merge!(order: "developers.salary ASC").last
+ last = Developer.all.merge!(order: "developers.salary ASC").last
assert_equal last, Developer.all.merge!(order: "developers.salary ASC").to_a.last
end
def test_find_reverse_ordered_last
- last = Developer.all.merge!(order: "developers.salary DESC").last
+ last = Developer.all.merge!(order: "developers.salary DESC").last
assert_equal last, Developer.all.merge!(order: "developers.salary DESC").to_a.last
end
def test_find_multiple_ordered_last
- last = Developer.all.merge!(order: "developers.name, developers.salary DESC").last
+ last = Developer.all.merge!(order: "developers.name, developers.salary DESC").last
assert_equal last, Developer.all.merge!(order: "developers.name, developers.salary DESC").to_a.last
end
@@ -1197,7 +1185,7 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_find_symbol_ordered_last
- last = Developer.all.merge!(order: :salary).last
+ last = Developer.all.merge!(order: :salary).last
assert_equal last, Developer.all.merge!(order: :salary).to_a.last
end
@@ -1271,13 +1259,13 @@ class BasicsTest < ActiveRecord::TestCase
ActiveSupport::Dependencies.remove_unloadable_constants!
assert_nil ActiveRecord::Scoping::ScopeRegistry.value_for(:current_scope, klass)
ensure
- Object.class_eval{ remove_const :UnloadablePost } if defined?(UnloadablePost)
+ Object.class_eval { remove_const :UnloadablePost } if defined?(UnloadablePost)
end
def test_marshal_round_trip
expected = posts(:welcome)
marshalled = Marshal.dump(expected)
- actual = Marshal.load(marshalled)
+ actual = Marshal.load(marshalled)
assert_equal expected.attributes, actual.attributes
end
@@ -1372,12 +1360,6 @@ class BasicsTest < ActiveRecord::TestCase
end
end
- def test_uniq_delegates_to_scoped
- assert_deprecated do
- assert_equal Bird.all.distinct, Bird.uniq
- end
- end
-
def test_distinct_delegates_to_scoped
assert_equal Bird.all.distinct, Bird.distinct
end
@@ -1421,6 +1403,16 @@ class BasicsTest < ActiveRecord::TestCase
assert_nil hash["firm_name"]
end
+ def test_slice_accepts_array_argument
+ attrs = {
+ title: "slice",
+ author_name: "@Cohen-Carlisle",
+ content: "accept arrays so I don't have to splat"
+ }.with_indifferent_access
+ topic = Topic.new(attrs)
+ assert_equal attrs, topic.slice(attrs.keys)
+ end
+
def test_default_values_are_deeply_dupped
company = Company.new
company.description << "foo"
@@ -1512,7 +1504,7 @@ class BasicsTest < ActiveRecord::TestCase
test "ignored columns are not present in columns_hash" do
cache_columns = Developer.connection.schema_cache.columns_hash(Developer.table_name)
assert_includes cache_columns.keys, "first_name"
- refute_includes Developer.columns_hash.keys, "first_name"
+ assert_not_includes Developer.columns_hash.keys, "first_name"
end
test "ignored columns have no attribute methods" do
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index 077e6d0bc0..f7e21faf0f 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -21,7 +21,7 @@ class EachTest < ActiveRecord::TestCase
def test_each_should_not_return_query_chain_and_execute_only_one_query
assert_queries(1) do
- result = Post.find_each(batch_size: 100000){ }
+ result = Post.find_each(batch_size: 100000) {}
assert_nil result
end
end
@@ -135,11 +135,11 @@ class EachTest < ActiveRecord::TestCase
def test_find_in_batches_shouldnt_execute_query_unless_needed
assert_queries(2) do
- Post.find_in_batches(batch_size: @total) {|batch| assert_kind_of Array, batch }
+ Post.find_in_batches(batch_size: @total) { |batch| assert_kind_of Array, batch }
end
assert_queries(1) do
- Post.find_in_batches(batch_size: @total + 1) {|batch| assert_kind_of Array, batch }
+ Post.find_in_batches(batch_size: @total + 1) { |batch| assert_kind_of Array, batch }
end
end
@@ -156,7 +156,7 @@ class EachTest < ActiveRecord::TestCase
def test_find_in_batches_should_not_use_records_after_yielding_them_in_case_original_array_is_modified
not_a_post = "not a post"
def not_a_post.id; end
- not_a_post.stub(:id, ->{ raise StandardError.new("not_a_post had #id called on it") }) do
+ not_a_post.stub(:id, -> { raise StandardError.new("not_a_post had #id called on it") }) do
assert_nothing_raised do
Post.find_in_batches(batch_size: 1) do |batch|
assert_kind_of Array, batch
@@ -182,7 +182,7 @@ class EachTest < ActiveRecord::TestCase
def test_find_in_batches_should_error_on_ignore_the_order
assert_raise(ArgumentError) do
- PostWithDefaultScope.find_in_batches(error_on_ignore: true){}
+ PostWithDefaultScope.find_in_batches(error_on_ignore: true) {}
end
end
@@ -191,7 +191,7 @@ class EachTest < ActiveRecord::TestCase
prev = ActiveRecord::Base.error_on_ignored_order
ActiveRecord::Base.error_on_ignored_order = true
assert_nothing_raised do
- PostWithDefaultScope.find_in_batches(error_on_ignore: false){}
+ PostWithDefaultScope.find_in_batches(error_on_ignore: false) {}
end
ensure
# Set back to default
@@ -203,7 +203,7 @@ class EachTest < ActiveRecord::TestCase
prev = ActiveRecord::Base.error_on_ignored_order
ActiveRecord::Base.error_on_ignored_order = true
assert_raise(ArgumentError) do
- PostWithDefaultScope.find_in_batches(){}
+ PostWithDefaultScope.find_in_batches() {}
end
ensure
# Set back to default
@@ -212,7 +212,7 @@ class EachTest < ActiveRecord::TestCase
def test_find_in_batches_should_not_error_by_default
assert_nothing_raised do
- PostWithDefaultScope.find_in_batches(){}
+ PostWithDefaultScope.find_in_batches() {}
end
end
@@ -227,7 +227,7 @@ class EachTest < ActiveRecord::TestCase
def test_find_in_batches_should_not_modify_passed_options
assert_nothing_raised do
- Post.find_in_batches({ batch_size: 42, start: 1 }.freeze){}
+ Post.find_in_batches({ batch_size: 42, start: 1 }.freeze) {}
end
end
@@ -445,7 +445,7 @@ class EachTest < ActiveRecord::TestCase
def test_in_batches_should_not_modify_passed_options
assert_nothing_raised do
- Post.in_batches({ of: 42, start: 1 }.freeze){}
+ Post.in_batches({ of: 42, start: 1 }.freeze) {}
end
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 13f9a47980..ffd5c1395d 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -92,21 +92,21 @@ class CalculationsTest < ActiveRecord::TestCase
def test_should_group_by_field
c = Account.group(:firm_id).sum(:credit_limit)
- [1,6,2].each do |firm_id|
- assert c.keys.include?(firm_id), "Group #{c.inspect} does not contain firm_id #{firm_id}"
+ [1, 6, 2].each do |firm_id|
+ assert_includes c.keys, firm_id, "Group #{c.inspect} does not contain firm_id #{firm_id}"
end
end
def test_should_group_by_arel_attribute
c = Account.group(Account.arel_table[:firm_id]).sum(:credit_limit)
- [1,6,2].each do |firm_id|
- assert c.keys.include?(firm_id), "Group #{c.inspect} does not contain firm_id #{firm_id}"
+ [1, 6, 2].each do |firm_id|
+ assert_includes c.keys, firm_id, "Group #{c.inspect} does not contain firm_id #{firm_id}"
end
end
def test_should_group_by_multiple_fields
c = Account.group("firm_id", :credit_limit).count(:all)
- [ [nil, 50], [1, 50], [6, 50], [6, 55], [9, 53], [2, 60] ].each { |firm_and_limit| assert c.keys.include?(firm_and_limit) }
+ [ [nil, 50], [1, 50], [6, 50], [6, 55], [9, 53], [2, 60] ].each { |firm_and_limit| assert_includes c.keys, firm_and_limit }
end
def test_should_group_by_multiple_fields_having_functions
@@ -381,7 +381,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_perform_joined_include_when_referencing_included_tables
- joined_count = Account.includes(:firm).where(companies: {name: "37signals"}).count
+ joined_count = Account.includes(:firm).where(companies: { name: "37signals" }).count
assert_equal 1, joined_count
end
@@ -421,10 +421,6 @@ class CalculationsTest < ActiveRecord::TestCase
def test_count_with_distinct
assert_equal 4, Account.select(:credit_limit).distinct.count
-
- assert_deprecated do
- assert_equal 4, Account.select(:credit_limit).uniq.count
- end
end
def test_count_with_aliased_attribute
@@ -453,7 +449,7 @@ class CalculationsTest < ActiveRecord::TestCase
def test_should_count_field_in_joined_table_with_group_by
c = Account.group("accounts.firm_id").joins(:firm).count("companies.id")
- [1,6,2,9].each { |firm_id| assert c.keys.include?(firm_id) }
+ [1, 6, 2, 9].each { |firm_id| assert_includes c.keys, firm_id }
end
def test_should_count_field_of_root_table_with_conflicting_group_by_column
@@ -572,7 +568,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_pluck
- assert_equal [1,2,3,4,5], Topic.order(:id).pluck(:id)
+ assert_equal [1, 2, 3, 4, 5], Topic.order(:id).pluck(:id)
end
def test_pluck_without_column_names
@@ -604,11 +600,11 @@ class CalculationsTest < ActiveRecord::TestCase
def test_pluck_with_serialization
t = Topic.create!(content: { foo: :bar })
- assert_equal [{foo: :bar}], Topic.where(id: t.id).pluck(:content)
+ assert_equal [{ foo: :bar }], Topic.where(id: t.id).pluck(:content)
end
def test_pluck_with_qualified_column_name
- assert_equal [1,2,3,4,5], Topic.order(:id).pluck("topics.id")
+ assert_equal [1, 2, 3, 4, 5], Topic.order(:id).pluck("topics.id")
end
def test_pluck_auto_table_name_prefix
@@ -688,7 +684,7 @@ class CalculationsTest < ActiveRecord::TestCase
def test_pluck_replaces_select_clause
taks_relation = Topic.select(:approved, :id).order(:id)
- assert_equal [1,2,3,4,5], taks_relation.pluck(:id)
+ assert_equal [1, 2, 3, 4, 5], taks_relation.pluck(:id)
assert_equal [false, true, true, true, true], taks_relation.pluck(:approved)
end
diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb
index e2352bd9f9..11ec6fb2c5 100644
--- a/activerecord/test/cases/callbacks_test.rb
+++ b/activerecord/test/cases/callbacks_test.rb
@@ -57,7 +57,7 @@ end
class ParentDeveloper < ActiveRecord::Base
self.table_name = "developers"
attr_accessor :after_save_called
- before_validation {|record| record.after_save_called = true}
+ before_validation { |record| record.after_save_called = true }
end
class ChildDeveloper < ParentDeveloper
@@ -95,16 +95,16 @@ class OnCallbacksDeveloper < ActiveRecord::Base
self.table_name = "developers"
before_validation { history << :before_validation }
- before_validation(on: :create){ history << :before_validation_on_create }
- before_validation(on: :update){ history << :before_validation_on_update }
+ before_validation(on: :create) { history << :before_validation_on_create }
+ before_validation(on: :update) { history << :before_validation_on_update }
validate do
history << :validate
end
after_validation { history << :after_validation }
- after_validation(on: :create){ history << :after_validation_on_create }
- after_validation(on: :update){ history << :after_validation_on_update }
+ after_validation(on: :create) { history << :after_validation_on_create }
+ after_validation(on: :update) { history << :after_validation_on_update }
def history
@history ||= []
@@ -143,7 +143,7 @@ class CallbackCancellationDeveloper < ActiveRecord::Base
attr_reader :after_save_called, :after_create_called, :after_update_called, :after_destroy_called
attr_accessor :cancel_before_save, :cancel_before_create, :cancel_before_update, :cancel_before_destroy
- before_save {defined?(@cancel_before_save) ? !@cancel_before_save : false}
+ before_save { defined?(@cancel_before_save) ? !@cancel_before_save : false }
before_create { !@cancel_before_create }
before_update { !@cancel_before_update }
before_destroy { !@cancel_before_destroy }
@@ -449,7 +449,7 @@ class CallbacksTest < ActiveRecord::TestCase
assert david.valid?
assert !david.save
exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! }
- assert_equal exc.record, david
+ assert_equal david, exc.record
assert_equal "Failed to save the record", exc.message
end
@@ -493,7 +493,7 @@ class CallbacksTest < ActiveRecord::TestCase
assert_deprecated do
assert !david.destroy
exc = assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! }
- assert_equal exc.record, david
+ assert_equal david, exc.record
assert_equal "Failed to destroy the record", exc.message
end
assert_not_nil ImmutableDeveloper.find_by_id(1)
@@ -527,7 +527,7 @@ class CallbacksTest < ActiveRecord::TestCase
assert david.valid?
assert !david.save
exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! }
- assert_equal exc.record, david
+ assert_equal david, exc.record
david = DeveloperWithCanceledCallbacks.find(1)
david.salary = 10_000_000
@@ -554,7 +554,7 @@ class CallbacksTest < ActiveRecord::TestCase
david = DeveloperWithCanceledCallbacks.find(1)
assert !david.destroy
exc = assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! }
- assert_equal exc.record, david
+ assert_equal david, exc.record
assert_not_nil ImmutableDeveloper.find_by_id(1)
someone = CallbackHaltedDeveloper.find(1)
diff --git a/activerecord/test/cases/collection_cache_key_test.rb b/activerecord/test/cases/collection_cache_key_test.rb
index a2874438c1..381a78a8e2 100644
--- a/activerecord/test/cases/collection_cache_key_test.rb
+++ b/activerecord/test/cases/collection_cache_key_test.rb
@@ -28,20 +28,20 @@ module ActiveRecord
end
test "it triggers at most one query" do
- developers = Developer.where(name: "David")
+ developers = Developer.where(name: "David")
assert_queries(1) { developers.cache_key }
assert_queries(0) { developers.cache_key }
end
test "it doesn't trigger any query if the relation is already loaded" do
- developers = Developer.where(name: "David").load
+ developers = Developer.where(name: "David").load
assert_queries(0) { developers.cache_key }
end
test "relation cache_key changes when the sql query changes" do
developers = Developer.where(name: "David")
- other_relation = Developer.where(name: "David").where("1 = 1")
+ other_relation = Developer.where(name: "David").where("1 = 1")
assert_not_equal developers.cache_key, other_relation.cache_key
end
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index 31e618664f..a65bb89052 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -6,7 +6,7 @@ module ActiveRecord
def setup
@adapter = AbstractAdapter.new(nil)
def @adapter.native_database_types
- {string: "varchar"}
+ { string: "varchar" }
end
@viz = @adapter.schema_creation
end
@@ -60,14 +60,13 @@ module ActiveRecord
end
def test_should_not_set_default_for_blob_and_text_data_types
- text_type = MySQL::TypeMetadata.new(
- SqlTypeMetadata.new(type: :text))
+ text_type = MySQL::TypeMetadata.new(SqlTypeMetadata.new(type: :text))
text_column = MySQL::Column.new("title", nil, text_type)
- assert_equal nil, text_column.default
+ assert_nil text_column.default
not_null_text_column = MySQL::Column.new("title", nil, text_type, false)
- assert_equal "", not_null_text_column.default
+ assert_nil not_null_text_column.default
end
def test_has_default_should_return_false_for_blob_and_text_data_types
diff --git a/activerecord/test/cases/comment_test.rb b/activerecord/test/cases/comment_test.rb
index 262ad319be..a625299e8d 100644
--- a/activerecord/test/cases/comment_test.rb
+++ b/activerecord/test/cases/comment_test.rb
@@ -2,7 +2,6 @@ require "cases/helper"
require "support/schema_dumping_helper"
if ActiveRecord::Base.connection.supports_comments?
-
class CommentTest < ActiveRecord::TestCase
include SchemaDumpingHelper
@@ -102,6 +101,7 @@ if ActiveRecord::Base.connection.supports_comments?
# Do all the stuff from other tests
@connection.add_column :commenteds, :rating, :integer, comment: "I am running out of imagination"
@connection.change_column :commenteds, :content, :string, comment: "Whoa, content describes itself!"
+ @connection.change_column :commenteds, :content, :string
@connection.change_column :commenteds, :obvious, :string, comment: nil
@connection.add_index :commenteds, :obvious, name: "idx_obvious", comment: "We need to see obvious comments"
@@ -135,5 +135,4 @@ if ActiveRecord::Base.connection.supports_comments?
assert_no_match %r[t\.string\s+"absent_comment", comment:\n], output
end
end
-
end
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index d9bcc0c130..2c33bf22ab 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -6,11 +6,11 @@ module ActiveRecord
def setup
@handler = ConnectionHandler.new
@spec_name = "primary"
- @pool = @handler.establish_connection(ActiveRecord::Base.configurations["arunit"])
+ @pool = @handler.establish_connection(ActiveRecord::Base.configurations["arunit"])
end
def test_establish_connection_uses_spec_name
- config = {"readonly" => {"adapter" => "sqlite3"}}
+ config = { "readonly" => { "adapter" => "sqlite3" } }
resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(config)
spec = resolver.spec(:readonly)
@handler.establish_connection(spec.to_hash)
diff --git a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
index b1ba6f11f2..8faa67255d 100644
--- a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
+++ b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
@@ -27,7 +27,7 @@ module ActiveRecord
ENV["DATABASE_URL"] = "postgres://localhost/foo"
config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
actual = resolve_spec(:default_env, config)
- expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost", "name"=>"default_env" }
+ expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost", "name" => "default_env" }
assert_equal expected, actual
end
@@ -37,7 +37,7 @@ module ActiveRecord
config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
actual = resolve_spec(:foo, config)
- expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost","name"=>"foo" }
+ expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost", "name" => "foo" }
assert_equal expected, actual
end
@@ -47,7 +47,7 @@ module ActiveRecord
config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
actual = resolve_spec(:foo, config)
- expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost","name"=>"foo" }
+ expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost", "name" => "foo" }
assert_equal expected, actual
end
@@ -55,13 +55,13 @@ module ActiveRecord
ENV["DATABASE_URL"] = "postgres://localhost/foo"
config = { "production" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } }
actual = resolve_spec(:production, config)
- expected = { "adapter"=>"not_postgres", "database"=>"not_foo", "host"=>"localhost", "name"=>"production" }
+ expected = { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost", "name" => "production" }
assert_equal expected, actual
end
def test_resolver_with_database_uri_and_unknown_symbol_key
ENV["DATABASE_URL"] = "postgres://localhost/foo"
- config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
+ config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
assert_raises AdapterNotSpecified do
resolve_spec(:production, config)
end
@@ -71,7 +71,7 @@ module ActiveRecord
ENV["DATABASE_URL"] = "not-postgres://not-localhost/not_foo"
config = { "production" => { "adapter" => "also_not_postgres", "database" => "also_not_foo" } }
actual = resolve_spec("postgres://localhost/foo", config)
- expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
+ expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" }
assert_equal expected, actual
end
@@ -85,7 +85,7 @@ module ActiveRecord
ENV["DATABASE_URL"] = "postgres://localhost/foo"
config = { "not_default_env" => { "adapter" => "not_postgres", "database" => "not_foo" } }
actual = resolve_config(config)
- expect_prod = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
+ expect_prod = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" }
assert_equal expect_prod, actual["default_env"]
end
@@ -93,7 +93,7 @@ module ActiveRecord
ENV["DATABASE_URL"] = "ibm-db://localhost/foo"
config = { "default_env" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } }
actual = resolve_spec(:default_env, config)
- expected = { "adapter"=>"ibm_db", "database"=>"foo", "host"=>"localhost", "name"=>"default_env" }
+ expected = { "adapter" => "ibm_db", "database" => "foo", "host" => "localhost", "name" => "default_env" }
assert_equal expected, actual
end
@@ -142,13 +142,13 @@ module ActiveRecord
"database" => "foo",
"host" => "localhost" }
assert_equal expected, actual["default_env"]
- assert_equal nil, actual["production"]
- assert_equal nil, actual["development"]
- assert_equal nil, actual["test"]
- assert_equal nil, actual[:default_env]
- assert_equal nil, actual[:production]
- assert_equal nil, actual[:development]
- assert_equal nil, actual[:test]
+ assert_nil actual["production"]
+ assert_nil actual["development"]
+ assert_nil actual["test"]
+ assert_nil actual[:default_env]
+ assert_nil actual[:production]
+ assert_nil actual[:development]
+ assert_nil actual[:test]
end
def test_blank_with_database_url_with_rails_env
@@ -162,15 +162,15 @@ module ActiveRecord
"host" => "localhost" }
assert_equal expected, actual["not_production"]
- assert_equal nil, actual["production"]
- assert_equal nil, actual["default_env"]
- assert_equal nil, actual["development"]
- assert_equal nil, actual["test"]
- assert_equal nil, actual[:default_env]
- assert_equal nil, actual[:not_production]
- assert_equal nil, actual[:production]
- assert_equal nil, actual[:development]
- assert_equal nil, actual[:test]
+ assert_nil actual["production"]
+ assert_nil actual["default_env"]
+ assert_nil actual["development"]
+ assert_nil actual["test"]
+ assert_nil actual[:default_env]
+ assert_nil actual[:not_production]
+ assert_nil actual[:production]
+ assert_nil actual[:development]
+ assert_nil actual[:test]
end
def test_blank_with_database_url_with_rack_env
@@ -184,15 +184,15 @@ module ActiveRecord
"host" => "localhost" }
assert_equal expected, actual["not_production"]
- assert_equal nil, actual["production"]
- assert_equal nil, actual["default_env"]
- assert_equal nil, actual["development"]
- assert_equal nil, actual["test"]
- assert_equal nil, actual[:default_env]
- assert_equal nil, actual[:not_production]
- assert_equal nil, actual[:production]
- assert_equal nil, actual[:development]
- assert_equal nil, actual[:test]
+ assert_nil actual["production"]
+ assert_nil actual["default_env"]
+ assert_nil actual["development"]
+ assert_nil actual["test"]
+ assert_nil actual[:default_env]
+ assert_nil actual[:not_production]
+ assert_nil actual[:production]
+ assert_nil actual[:development]
+ assert_nil actual[:test]
end
def test_database_url_with_ipv6_host_and_port
@@ -213,7 +213,7 @@ module ActiveRecord
config = { "default_env" => { "url" => "postgres://localhost/foo" } }
actual = resolve_config(config)
expected = { "default_env" =>
- { "adapter" => "postgresql",
+ { "adapter" => "postgresql",
"database" => "foo",
"host" => "localhost"
}
@@ -224,7 +224,7 @@ module ActiveRecord
def test_merge_no_conflicts_with_database_url
ENV["DATABASE_URL"] = "postgres://localhost/foo"
- config = {"default_env" => { "pool" => "5" } }
+ config = { "default_env" => { "pool" => "5" } }
actual = resolve_config(config)
expected = { "default_env" =>
{ "adapter" => "postgresql",
@@ -239,7 +239,7 @@ module ActiveRecord
def test_merge_conflicts_with_database_url
ENV["DATABASE_URL"] = "postgres://localhost/foo"
- config = {"default_env" => { "adapter" => "NOT-POSTGRES", "database" => "NOT-FOO", "pool" => "5" } }
+ config = { "default_env" => { "adapter" => "NOT-POSTGRES", "database" => "NOT-FOO", "pool" => "5" } }
actual = resolve_config(config)
expected = { "default_env" =>
{ "adapter" => "postgresql",
diff --git a/activerecord/test/cases/connection_adapters/quoting_test.rb b/activerecord/test/cases/connection_adapters/quoting_test.rb
deleted file mode 100644
index 59dcb96ebc..0000000000
--- a/activerecord/test/cases/connection_adapters/quoting_test.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-require "cases/helper"
-
-module ActiveRecord
- module ConnectionAdapters
- module Quoting
- class QuotingTest < ActiveRecord::TestCase
- def test_quoting_classes
- assert_equal "'Object'", AbstractAdapter.new(nil).quote(Object)
- end
- end
- end
- end
-end
diff --git a/activerecord/test/cases/connection_adapters/schema_cache_test.rb b/activerecord/test/cases/connection_adapters/schema_cache_test.rb
index d4459603af..106323ccc9 100644
--- a/activerecord/test/cases/connection_adapters/schema_cache_test.rb
+++ b/activerecord/test/cases/connection_adapters/schema_cache_test.rb
@@ -12,6 +12,33 @@ module ActiveRecord
assert_equal "id", @cache.primary_keys("posts")
end
+ def test_yaml_dump_and_load
+ @cache.columns("posts")
+ @cache.columns_hash("posts")
+ @cache.data_sources("posts")
+ @cache.primary_keys("posts")
+
+ new_cache = YAML.load(YAML.dump(@cache))
+ assert_no_queries do
+ assert_equal 11, new_cache.columns("posts").size
+ assert_equal 11, new_cache.columns_hash("posts").size
+ assert new_cache.data_sources("posts")
+ assert_equal "id", new_cache.primary_keys("posts")
+ end
+ end
+
+ def test_yaml_loads_5_1_dump
+ body = File.open(schema_dump_path).read
+ cache = YAML.load(body)
+
+ assert_no_queries do
+ assert_equal 11, cache.columns("posts").size
+ assert_equal 11, cache.columns_hash("posts").size
+ assert cache.data_sources("posts")
+ assert_equal "id", cache.primary_keys("posts")
+ end
+ end
+
def test_primary_key_for_non_existent_table
assert_nil @cache.primary_keys("omgponies")
end
@@ -45,17 +72,28 @@ module ActiveRecord
@cache = Marshal.load(Marshal.dump(@cache))
- assert_equal 11, @cache.columns("posts").size
- assert_equal 11, @cache.columns_hash("posts").size
- assert @cache.data_sources("posts")
- assert_equal "id", @cache.primary_keys("posts")
+ assert_no_queries do
+ assert_equal 11, @cache.columns("posts").size
+ assert_equal 11, @cache.columns_hash("posts").size
+ assert @cache.data_sources("posts")
+ assert_equal "id", @cache.primary_keys("posts")
+ end
end
- def test_table_methods_deprecation
- assert_deprecated { assert @cache.table_exists?("posts") }
- assert_deprecated { assert @cache.tables("posts") }
- assert_deprecated { @cache.clear_table_cache!("posts") }
+ def test_data_source_exist
+ assert @cache.data_source_exists?("posts")
+ assert_not @cache.data_source_exists?("foo")
end
+
+ def test_clear_data_source_cache
+ @cache.clear_data_source_cache!("posts")
+ end
+
+ private
+
+ def schema_dump_path
+ "test/assets/schema_dump_5_1.yml"
+ end
end
end
end
diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb
index 88620f9f09..d1e946d401 100644
--- a/activerecord/test/cases/connection_management_test.rb
+++ b/activerecord/test/cases/connection_management_test.rb
@@ -104,7 +104,7 @@ module ActiveRecord
def middleware(app)
lambda do |env|
a, b, c = executor.wrap { app.call(env) }
- [a, b, Rack::BodyProxy.new(c) { }]
+ [a, b, Rack::BodyProxy.new(c) {}]
end
end
end
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index d7ff9d6880..42600e53fd 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -184,14 +184,14 @@ module ActiveRecord
def test_checkout_behaviour
pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec
- connection = pool.connection
- assert_not_nil connection
+ main_connection = pool.connection
+ assert_not_nil main_connection
threads = []
4.times do |i|
threads << Thread.new(i) do
- connection = pool.connection
- assert_not_nil connection
- connection.close
+ thread_connection = pool.connection
+ assert_not_nil thread_connection
+ thread_connection.close
end
end
@@ -455,49 +455,63 @@ module ActiveRecord
with_single_connection_pool do |pool|
[:disconnect, :disconnect!, :clear_reloadable_connections, :clear_reloadable_connections!].each do |group_action_method|
conn = pool.connection # drain the only available connection
- second_thread_done = Concurrent::CountDownLatch.new
-
- # create a first_thread and let it get into the FIFO queue first
- first_thread = Thread.new do
- pool.with_connection { second_thread_done.wait }
- end
-
- # wait for first_thread to get in queue
- Thread.pass until pool.num_waiting_in_queue == 1
-
- # create a different, later thread, that will attempt to do a "group action",
- # but because of the group action semantics it should be able to preempt the
- # first_thread when a connection is made available
- second_thread = Thread.new do
- pool.send(group_action_method)
- second_thread_done.count_down
- end
+ second_thread_done = Concurrent::Event.new
- # wait for second_thread to get in queue
- Thread.pass until pool.num_waiting_in_queue == 2
-
- # return the only available connection
- pool.checkin(conn)
-
- # if the second_thread is not able to preempt the first_thread,
- # they will temporarily (until either of them timeouts with ConnectionTimeoutError)
- # deadlock and a join(2) timeout will be reached
- failed = true unless second_thread.join(2)
-
- #--- post test clean up start
- second_thread_done.count_down if failed
-
- # after `pool.disconnect()` the first thread will be left stuck in queue, no need to wait for
- # it to timeout with ConnectionTimeoutError
- if (group_action_method == :disconnect || group_action_method == :disconnect!) && pool.num_waiting_in_queue > 0
- pool.with_connection {} # create a new connection in case there are threads still stuck in a queue
+ begin
+ # create a first_thread and let it get into the FIFO queue first
+ first_thread = Thread.new do
+ pool.with_connection { second_thread_done.wait }
+ end
+
+ # wait for first_thread to get in queue
+ Thread.pass until pool.num_waiting_in_queue == 1
+
+ # create a different, later thread, that will attempt to do a "group action",
+ # but because of the group action semantics it should be able to preempt the
+ # first_thread when a connection is made available
+ second_thread = Thread.new do
+ pool.send(group_action_method)
+ second_thread_done.set
+ end
+
+ # wait for second_thread to get in queue
+ Thread.pass until pool.num_waiting_in_queue == 2
+
+ # return the only available connection
+ pool.checkin(conn)
+
+ # if the second_thread is not able to preempt the first_thread,
+ # they will temporarily (until either of them timeouts with ConnectionTimeoutError)
+ # deadlock and a join(2) timeout will be reached
+ assert second_thread.join(2), "#{group_action_method} is not able to preempt other waiting threads"
+
+ ensure
+ # post test clean up
+ failed = !second_thread_done.set?
+
+ 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")
+ second_thread.join(10) || raise("second_thread got stuck")
end
-
- first_thread.join
- second_thread.join
- #--- post test clean up end
-
- flunk "#{group_action_method} is not able to preempt other waiting threads" if failed
end
end
end
@@ -526,6 +540,26 @@ module ActiveRecord
end
end
+ def test_connection_pool_stat
+ with_single_connection_pool do |pool|
+ pool.with_connection do |connection|
+ stats = pool.stat
+ assert_equal({ size: 1, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }, stats)
+ end
+
+ stats = pool.stat
+ assert_equal({ size: 1, connections: 1, busy: 0, dead: 0, idle: 1, waiting: 0, checkout_timeout: 5 }, stats)
+
+ Thread.new do
+ pool.checkout
+ Thread.current.kill
+ end.join
+
+ stats = pool.stat
+ assert_equal({ size: 1, connections: 1, busy: 0, dead: 1, idle: 0, waiting: 0, checkout_timeout: 5 }, stats)
+ end
+ end
+
private
def with_single_connection_pool
one_conn_spec = ActiveRecord::Base.connection_pool.spec.dup
diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb
index 4bfec47124..13b5bae13c 100644
--- a/activerecord/test/cases/connection_specification/resolver_test.rb
+++ b/activerecord/test/cases/connection_specification/resolver_test.rb
@@ -4,11 +4,11 @@ module ActiveRecord
module ConnectionAdapters
class ConnectionSpecification
class ResolverTest < ActiveRecord::TestCase
- def resolve(spec, config={})
+ def resolve(spec, config = {})
Resolver.new(config).resolve(spec)
end
- def spec(spec, config={})
+ def spec(spec, config = {})
Resolver.new(config).spec(spec)
end
@@ -29,27 +29,27 @@ module ActiveRecord
"adapter" => "abstract",
"host" => "foo",
"encoding" => "utf8",
- "name" => "production"}, spec)
+ "name" => "production" }, spec)
end
def test_url_sub_key
- spec = resolve :production, "production" => {"url" => "abstract://foo?encoding=utf8"}
+ spec = resolve :production, "production" => { "url" => "abstract://foo?encoding=utf8" }
assert_equal({
"adapter" => "abstract",
"host" => "foo",
"encoding" => "utf8",
- "name" => "production"}, spec)
+ "name" => "production" }, spec)
end
def test_url_sub_key_merges_correctly
- hash = {"url" => "abstract://foo?encoding=utf8&", "adapter" => "sqlite3", "host" => "bar", "pool" => "3"}
+ hash = { "url" => "abstract://foo?encoding=utf8&", "adapter" => "sqlite3", "host" => "bar", "pool" => "3" }
spec = resolve :production, "production" => hash
assert_equal({
"adapter" => "abstract",
"host" => "foo",
"encoding" => "utf8",
"pool" => "3",
- "name" => "production"}, spec)
+ "name" => "production" }, spec)
end
def test_url_host_no_db
@@ -112,16 +112,16 @@ module ActiveRecord
end
def test_url_sub_key_for_sqlite3
- spec = resolve :production, "production" => {"url" => "sqlite3:foo?encoding=utf8"}
+ spec = resolve :production, "production" => { "url" => "sqlite3:foo?encoding=utf8" }
assert_equal({
"adapter" => "sqlite3",
"database" => "foo",
"encoding" => "utf8",
- "name" => "production"}, spec)
+ "name" => "production" }, spec)
end
def test_spec_name_on_key_lookup
- spec = spec(:readonly, "readonly" => {"adapter" => "sqlite3"})
+ spec = spec(:readonly, "readonly" => { "adapter" => "sqlite3" })
assert_equal "readonly", spec.name
end
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index 84f2c3a465..c7d0ba32b4 100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
@@ -211,4 +211,146 @@ class CounterCacheTest < ActiveRecord::TestCase
aircraft.wheels.first.destroy
end
end
+
+ test "update counters doesn't touch timestamps by default" do
+ @topic.update_column :updated_at, 5.minutes.ago
+ previously_updated_at = @topic.updated_at
+
+ Topic.update_counters(@topic.id, replies_count: -1)
+
+ assert_equal previously_updated_at, @topic.updated_at
+ end
+
+ test "update counters with touch: true" do
+ assert_touching @topic, :updated_at do
+ Topic.update_counters(@topic.id, replies_count: -1, touch: true)
+ end
+ end
+
+ test "update counters of multiple records with touch: true" do
+ t1, t2 = topics(:first, :second)
+
+ assert_touching t1, :updated_at do
+ assert_difference ["t1.reload.replies_count", "t2.reload.replies_count"], 2 do
+ Topic.update_counters([t1.id, t2.id], replies_count: 2, touch: true)
+ end
+ end
+ end
+
+ test "update multiple counters with touch: true" do
+ assert_touching @topic, :updated_at do
+ Topic.update_counters(@topic.id, replies_count: 2, unique_replies_count: 2, touch: true)
+ end
+ end
+
+ test "reset counters with touch: true" do
+ assert_touching @topic, :updated_at do
+ Topic.reset_counters(@topic.id, :replies, touch: true)
+ end
+ end
+
+ test "reset multiple counters with touch: true" do
+ assert_touching @topic, :updated_at do
+ Topic.update_counters(@topic.id, replies_count: 1, unique_replies_count: 1)
+ Topic.reset_counters(@topic.id, :replies, :unique_replies, touch: true)
+ end
+ end
+
+ test "increment counters with touch: true" do
+ assert_touching @topic, :updated_at do
+ Topic.increment_counter(:replies_count, @topic.id, touch: true)
+ end
+ end
+
+ test "decrement counters with touch: true" do
+ assert_touching @topic, :updated_at do
+ Topic.decrement_counter(:replies_count, @topic.id, touch: true)
+ end
+ end
+
+ test "update counters with touch: :written_on" do
+ assert_touching @topic, :written_on do
+ Topic.update_counters(@topic.id, replies_count: -1, touch: :written_on)
+ end
+ end
+
+ test "update multiple counters with touch: :written_on" do
+ assert_touching @topic, :written_on do
+ Topic.update_counters(@topic.id, replies_count: 2, unique_replies_count: 2, touch: :written_on)
+ end
+ end
+
+ test "reset counters with touch: :written_on" do
+ assert_touching @topic, :written_on do
+ Topic.reset_counters(@topic.id, :replies, touch: :written_on)
+ end
+ end
+
+ test "reset multiple counters with touch: :written_on" do
+ assert_touching @topic, :written_on do
+ Topic.update_counters(@topic.id, replies_count: 1, unique_replies_count: 1)
+ Topic.reset_counters(@topic.id, :replies, :unique_replies, touch: :written_on)
+ end
+ end
+
+ test "increment counters with touch: :written_on" do
+ assert_touching @topic, :written_on do
+ Topic.increment_counter(:replies_count, @topic.id, touch: :written_on)
+ end
+ end
+
+ test "decrement counters with touch: :written_on" do
+ assert_touching @topic, :written_on do
+ Topic.decrement_counter(:replies_count, @topic.id, touch: :written_on)
+ end
+ end
+
+ test "update counters with touch: %i( updated_at written_on )" do
+ assert_touching @topic, :updated_at, :written_on do
+ Topic.update_counters(@topic.id, replies_count: -1, touch: %i( updated_at written_on ))
+ end
+ end
+
+ test "update multiple counters with touch: %i( updated_at written_on )" do
+ assert_touching @topic, :updated_at, :written_on do
+ Topic.update_counters(@topic.id, replies_count: 2, unique_replies_count: 2, touch: %i( updated_at written_on ))
+ end
+ end
+
+ test "reset counters with touch: %i( updated_at written_on )" do
+ assert_touching @topic, :updated_at, :written_on do
+ Topic.reset_counters(@topic.id, :replies, touch: %i( updated_at written_on ))
+ end
+ end
+
+ test "reset multiple counters with touch: %i( updated_at written_on )" do
+ assert_touching @topic, :updated_at, :written_on do
+ Topic.update_counters(@topic.id, replies_count: 1, unique_replies_count: 1)
+ Topic.reset_counters(@topic.id, :replies, :unique_replies, touch: %i( updated_at written_on ))
+ end
+ end
+
+ test "increment counters with touch: %i( updated_at written_on )" do
+ assert_touching @topic, :updated_at, :written_on do
+ Topic.increment_counter(:replies_count, @topic.id, touch: %i( updated_at written_on ))
+ end
+ end
+
+ test "decrement counters with touch: %i( updated_at written_on )" do
+ assert_touching @topic, :updated_at, :written_on do
+ Topic.decrement_counter(:replies_count, @topic.id, touch: %i( updated_at written_on ))
+ end
+ end
+
+ private
+ def assert_touching(record, *attributes)
+ record.update_columns attributes.map { |attr| [ attr, 5.minutes.ago ] }.to_h
+ touch_times = attributes.map { |attr| [ attr, record.public_send(attr) ] }.to_h
+
+ yield
+
+ touch_times.each do |attr, previous_touch_time|
+ assert_operator previous_touch_time, :<, record.reload.public_send(attr)
+ end
+ end
end
diff --git a/activerecord/test/cases/database_statements_test.rb b/activerecord/test/cases/database_statements_test.rb
index ac9912d4d1..66035865be 100644
--- a/activerecord/test/cases/database_statements_test.rb
+++ b/activerecord/test/cases/database_statements_test.rb
@@ -5,6 +5,13 @@ class DatabaseStatementsTest < ActiveRecord::TestCase
@connection = ActiveRecord::Base.connection
end
+ unless current_adapter?(:OracleAdapter)
+ def test_exec_insert
+ result = @connection.exec_insert("INSERT INTO accounts (firm_id,credit_limit) VALUES (42,5000)", nil, [])
+ assert_not_nil @connection.send(:last_inserted_id, result)
+ end
+ end
+
def test_insert_should_return_the_inserted_id
assert_not_nil return_the_inserted_id(method: :insert)
end
@@ -13,12 +20,6 @@ class DatabaseStatementsTest < ActiveRecord::TestCase
assert_not_nil return_the_inserted_id(method: :create)
end
- def test_insert_update_delete_sql_is_deprecated
- assert_deprecated { @connection.insert_sql("INSERT INTO accounts (firm_id,credit_limit) VALUES (42,5000)") }
- assert_deprecated { @connection.update_sql("UPDATE accounts SET credit_limit = 6000 WHERE firm_id = 42") }
- assert_deprecated { @connection.delete_sql("DELETE FROM accounts WHERE firm_id = 42") }
- end
-
private
def return_the_inserted_id(method:)
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index f94815d34c..6532efcf22 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -127,92 +127,66 @@ if current_adapter?(:Mysql2Adapter)
ActiveRecord::Base.establish_connection connection
end
- # MySQL cannot have defaults on text/blob columns. It reports the
- # default value as null.
+ # Strict mode controls how MySQL handles invalid or missing values
+ # in data-change statements such as INSERT or UPDATE. A value can be
+ # invalid for several reasons. For example, it might have the wrong
+ # data type for the column, or it might be out of range. A value is
+ # missing when a new row to be inserted does not contain a value for
+ # a non-NULL column that has no explicit DEFAULT clause in its definition.
+ # (For a NULL column, NULL is inserted if the value is missing.)
#
- # Despite this, in non-strict mode, MySQL will use an empty string
- # as the default value of the field, if no other value is
- # specified.
+ # If strict mode is not in effect, MySQL inserts adjusted values for
+ # invalid or missing values and produces warnings. In strict mode,
+ # you can produce this behavior by using INSERT IGNORE or UPDATE IGNORE.
#
- # Therefore, in non-strict mode, we want column.default to report
- # an empty string as its default, to be consistent with that.
- #
- # In strict mode, column.default should be nil.
- def test_mysql_text_not_null_defaults_non_strict
+ # https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sql-mode-strict
+ def test_mysql_not_null_defaults_non_strict
using_strict(false) do
- with_text_blob_not_null_table do |klass|
+ with_mysql_not_null_table do |klass|
record = klass.new
- assert_equal "", record.non_null_blob
- assert_equal "", record.non_null_text
-
- assert_nil record.null_blob
- assert_nil record.null_text
+ assert_nil record.non_null_integer
+ assert_nil record.non_null_string
+ assert_nil record.non_null_text
+ assert_nil record.non_null_blob
record.save!
record.reload
+ assert_equal 0, record.non_null_integer
+ assert_equal "", record.non_null_string
assert_equal "", record.non_null_text
assert_equal "", record.non_null_blob
-
- assert_nil record.null_text
- assert_nil record.null_blob
end
end
end
- def test_mysql_text_not_null_defaults_strict
+ def test_mysql_not_null_defaults_strict
using_strict(true) do
- with_text_blob_not_null_table do |klass|
+ with_mysql_not_null_table do |klass|
record = klass.new
- assert_nil record.non_null_blob
+ assert_nil record.non_null_integer
+ assert_nil record.non_null_string
assert_nil record.non_null_text
- assert_nil record.null_blob
- assert_nil record.null_text
+ assert_nil record.non_null_blob
- assert_raises(ActiveRecord::StatementInvalid) { klass.create }
+ assert_raises(ActiveRecord::NotNullViolation) { klass.create }
end
end
end
- def with_text_blob_not_null_table
+ def with_mysql_not_null_table
klass = Class.new(ActiveRecord::Base)
- klass.table_name = "test_mysql_text_not_null_defaults"
+ klass.table_name = "test_mysql_not_null_defaults"
klass.connection.create_table klass.table_name do |t|
- t.column :non_null_text, :text, null: false
- t.column :non_null_blob, :blob, null: false
- t.column :null_text, :text, null: true
- t.column :null_blob, :blob, null: true
+ t.integer :non_null_integer, null: false
+ t.string :non_null_string, null: false
+ t.text :non_null_text, null: false
+ t.blob :non_null_blob, null: false
end
yield klass
ensure
klass.connection.drop_table(klass.table_name) rescue nil
end
-
- # MySQL uses an implicit default 0 rather than NULL unless in strict mode.
- # We use an implicit NULL so schema.rb is compatible with other databases.
- def test_mysql_integer_not_null_defaults
- klass = Class.new(ActiveRecord::Base)
- klass.table_name = "test_integer_not_null_default_zero"
- klass.connection.create_table klass.table_name do |t|
- t.column :zero, :integer, null: false, default: 0
- t.column :omit, :integer, null: false
- end
-
- assert_equal "0", klass.columns_hash["zero"].default
- assert !klass.columns_hash["zero"].null
- assert_equal nil, klass.columns_hash["omit"].default
- assert !klass.columns_hash["omit"].null
-
- assert_raise(ActiveRecord::StatementInvalid) { klass.create! }
-
- assert_nothing_raised do
- instance = klass.create!(omit: 1)
- assert_equal 0, instance.zero
- assert_equal 1, instance.omit
- end
- ensure
- klass.connection.drop_table(klass.table_name) rescue nil
- end
end
end
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index 32df15eef3..0e58e65a07 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -280,7 +280,7 @@ class DirtyTest < ActiveRecord::TestCase
assert pirate.changed?
assert_nil pirate.catchphrase_was
assert_equal %w(catchphrase), pirate.changed
- assert_equal({"catchphrase" => [nil, "arrr"]}, pirate.changes)
+ assert_equal({ "catchphrase" => [nil, "arrr"] }, pirate.changes)
pirate.save
assert !pirate.changed?
@@ -314,7 +314,7 @@ class DirtyTest < ActiveRecord::TestCase
assert !topic.approved_changed?
# Coming from web form.
- params = {topic: {approved: 1}}
+ params = { topic: { approved: 1 } }
# In the controller.
topic.attributes = params[:topic]
assert topic.approved?
@@ -341,7 +341,7 @@ class DirtyTest < ActiveRecord::TestCase
def test_partial_update_with_optimistic_locking
person = Person.new(first_name: "foo")
- old_lock_version = 1
+ old_lock_version = person.lock_version
with_partial_writes Person, false do
assert_queries(2) { 2.times { person.save! } }
@@ -423,7 +423,7 @@ class DirtyTest < ActiveRecord::TestCase
def test_save_should_store_serialized_attributes_even_with_partial_writes
with_partial_writes(Topic) do
- topic = Topic.create!(content: {a: "a"})
+ topic = Topic.create!(content: { a: "a" })
assert_not topic.changed?
@@ -444,7 +444,7 @@ class DirtyTest < ActiveRecord::TestCase
def test_save_always_should_update_timestamps_when_serialized_attributes_are_present
with_partial_writes(Topic) do
- topic = Topic.create!(content: {a: "a"})
+ topic = Topic.create!(content: { a: "a" })
topic.save!
updated_at = topic.updated_at
@@ -460,7 +460,7 @@ class DirtyTest < ActiveRecord::TestCase
def test_save_should_not_save_serialized_attribute_with_partial_writes_if_not_present
with_partial_writes(Topic) do
- Topic.create!(author_name: "Bill", content: {a: "a"})
+ Topic.create!(author_name: "Bill", content: { a: "a" })
topic = Topic.select("id, author_name").first
topic.update_columns author_name: "John"
topic = Topic.first
@@ -495,8 +495,8 @@ class DirtyTest < ActiveRecord::TestCase
assert_equal 4, pirate.previous_changes.size
assert_equal [nil, "arrr"], pirate.previous_changes["catchphrase"]
assert_equal [nil, pirate.id], pirate.previous_changes["id"]
- assert pirate.previous_changes.include?("updated_on")
- assert pirate.previous_changes.include?("created_on")
+ assert_includes pirate.previous_changes, "updated_on"
+ assert_includes pirate.previous_changes, "created_on"
assert !pirate.previous_changes.key?("parrot_id")
pirate.catchphrase = "Yar!!"
@@ -631,7 +631,7 @@ class DirtyTest < ActiveRecord::TestCase
assert_equal("arrrr", pirate.catchphrase_was)
assert pirate.catchphrase_changed?(from: "arrrr")
assert_not pirate.catchphrase_changed?(from: "anything else")
- assert pirate.changed_attributes.include?(:catchphrase)
+ assert_includes pirate.changed_attributes, :catchphrase
pirate.save!
pirate.reload
@@ -726,6 +726,89 @@ class DirtyTest < ActiveRecord::TestCase
assert person.changed?
end
+ test "saved_change_to_attribute? returns whether a change occurred in the last save" do
+ person = Person.create!(first_name: "Sean")
+
+ assert person.saved_change_to_first_name?
+ refute person.saved_change_to_gender?
+ assert person.saved_change_to_first_name?(from: nil, to: "Sean")
+ assert person.saved_change_to_first_name?(from: nil)
+ assert person.saved_change_to_first_name?(to: "Sean")
+ refute person.saved_change_to_first_name?(from: "Jim", to: "Sean")
+ refute person.saved_change_to_first_name?(from: "Jim")
+ refute person.saved_change_to_first_name?(to: "Jim")
+ end
+
+ test "saved_change_to_attribute returns the change that occurred in the last save" do
+ person = Person.create!(first_name: "Sean", gender: "M")
+
+ assert_equal [nil, "Sean"], person.saved_change_to_first_name
+ assert_equal [nil, "M"], person.saved_change_to_gender
+
+ person.update(first_name: "Jim")
+
+ assert_equal ["Sean", "Jim"], person.saved_change_to_first_name
+ assert_nil person.saved_change_to_gender
+ end
+
+ test "attribute_before_last_save returns the original value before saving" do
+ person = Person.create!(first_name: "Sean", gender: "M")
+
+ assert_nil person.first_name_before_last_save
+ assert_nil person.gender_before_last_save
+
+ person.first_name = "Jim"
+
+ assert_nil person.first_name_before_last_save
+ assert_nil person.gender_before_last_save
+
+ person.save
+
+ assert_equal "Sean", person.first_name_before_last_save
+ assert_equal "M", person.gender_before_last_save
+ end
+
+ test "saved_changes? returns whether the last call to save changed anything" do
+ person = Person.create!(first_name: "Sean")
+
+ assert person.saved_changes?
+
+ person.save
+
+ refute person.saved_changes?
+ end
+
+ test "saved_changes returns a hash of all the changes that occurred" do
+ person = Person.create!(first_name: "Sean", gender: "M")
+
+ assert_equal [nil, "Sean"], person.saved_changes[:first_name]
+ assert_equal [nil, "M"], person.saved_changes[:gender]
+ assert_equal %w(id first_name gender created_at updated_at).sort, person.saved_changes.keys.sort
+
+ travel(1.second) do
+ person.update(first_name: "Jim")
+ end
+
+ assert_equal ["Sean", "Jim"], person.saved_changes[:first_name]
+ assert_equal %w(first_name lock_version updated_at).sort, person.saved_changes.keys.sort
+ end
+
+ test "changed? in after callbacks returns true but is deprecated" do
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "people"
+
+ after_save do
+ ActiveSupport::Deprecation.silence do
+ raise "changed? should be true" unless changed?
+ end
+ raise "has_changes_to_save? should be false" if has_changes_to_save?
+ end
+ end
+
+ person = klass.create!(first_name: "Sean")
+ refute person.changed?
+ end
+
private
def with_partial_writes(klass, on = true)
old = klass.partial_writes?
diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb
index c2009843f0..b7641fcf32 100644
--- a/activerecord/test/cases/enum_test.rb
+++ b/activerecord/test/cases/enum_test.rb
@@ -18,6 +18,7 @@ class EnumTest < ActiveRecord::TestCase
assert @book.author_visibility_visible?
assert @book.illustrator_visibility_visible?
assert @book.with_medium_font_size?
+ assert @book.medium_to_read?
end
test "query state with strings" do
@@ -26,6 +27,7 @@ class EnumTest < ActiveRecord::TestCase
assert_equal "english", @book.language
assert_equal "visible", @book.author_visibility
assert_equal "visible", @book.illustrator_visibility
+ assert_equal "medium", @book.difficulty
end
test "find via scope" do
@@ -34,6 +36,7 @@ class EnumTest < ActiveRecord::TestCase
assert_equal @book, Book.in_english.first
assert_equal @book, Book.author_visibility_visible.first
assert_equal @book, Book.illustrator_visibility_visible.first
+ assert_equal @book, Book.medium_to_read.first
end
test "find via where with values" do
@@ -422,6 +425,43 @@ class EnumTest < ActiveRecord::TestCase
assert_not @book.in_french?
end
+ test "query state by predicate with custom suffix" do
+ assert @book.medium_to_read?
+ assert_not @book.easy_to_read?
+ assert_not @book.hard_to_read?
+ end
+
+ test "enum methods with custom suffix defined" do
+ assert @book.class.respond_to?(:easy_to_read)
+ assert @book.class.respond_to?(:medium_to_read)
+ assert @book.class.respond_to?(:hard_to_read)
+
+ assert @book.respond_to?(:easy_to_read?)
+ assert @book.respond_to?(:medium_to_read?)
+ assert @book.respond_to?(:hard_to_read?)
+
+ assert @book.respond_to?(:easy_to_read!)
+ assert @book.respond_to?(:medium_to_read!)
+ assert @book.respond_to?(:hard_to_read!)
+ end
+
+ test "update enum attributes with custom suffix" do
+ @book.medium_to_read!
+ assert_not @book.easy_to_read?
+ assert @book.medium_to_read?
+ assert_not @book.hard_to_read?
+
+ @book.easy_to_read!
+ assert @book.easy_to_read?
+ assert_not @book.medium_to_read?
+ assert_not @book.hard_to_read?
+
+ @book.hard_to_read!
+ assert_not @book.easy_to_read?
+ assert_not @book.medium_to_read?
+ assert @book.hard_to_read?
+ end
+
test "uses default status when no status is provided in fixtures" do
book = books(:tlg)
assert book.proposed?, "expected fixture to default to proposed status"
diff --git a/activerecord/test/cases/errors_test.rb b/activerecord/test/cases/errors_test.rb
index 0711a372f2..73feb831d0 100644
--- a/activerecord/test/cases/errors_test.rb
+++ b/activerecord/test/cases/errors_test.rb
@@ -5,7 +5,7 @@ class ErrorsTest < ActiveRecord::TestCase
base = ActiveRecord::ActiveRecordError
error_klasses = ObjectSpace.each_object(Class).select { |klass| klass < base }
- error_klasses.each do |error_klass|
+ (error_klasses - [ActiveRecord::AmbiguousSourceReflectionForThroughAssociation]).each do |error_klass|
begin
error_klass.new.inspect
rescue ArgumentError
diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb
index 43f1d401dd..86fe90ae51 100644
--- a/activerecord/test/cases/explain_test.rb
+++ b/activerecord/test/cases/explain_test.rb
@@ -40,7 +40,7 @@ if ActiveRecord::Base.connection.supports_explain?
queries = sqls.zip(binds)
stub_explain_for_query_plans do
- expected = sqls.map {|sql| "EXPLAIN for: #{sql}\nquery plan #{sql}"}.join("\n")
+ expected = sqls.map { |sql| "EXPLAIN for: #{sql}\nquery plan #{sql}" }.join("\n")
assert_equal expected, base.exec_explain(queries)
end
end
@@ -75,7 +75,7 @@ if ActiveRecord::Base.connection.supports_explain?
def stub_explain_for_query_plans(query_plans = ["query plan foo", "query plan bar"])
explain_called = 0
- connection.stub(:explain, proc{ explain_called += 1; query_plans[explain_called - 1] }) do
+ connection.stub(:explain, proc { explain_called += 1; query_plans[explain_called - 1] }) do
yield
end
end
diff --git a/activerecord/test/cases/finder_respond_to_test.rb b/activerecord/test/cases/finder_respond_to_test.rb
index a7af8ac0ea..3eaa993d45 100644
--- a/activerecord/test/cases/finder_respond_to_test.rb
+++ b/activerecord/test/cases/finder_respond_to_test.rb
@@ -10,7 +10,7 @@ class FinderRespondToTest < ActiveRecord::TestCase
end
def test_should_preserve_normal_respond_to_behaviour_and_respond_to_newly_added_method
- class << Topic; self; end.send(:define_method, :method_added_for_finder_respond_to_test) { }
+ class << Topic; self; end.send(:define_method, :method_added_for_finder_respond_to_test) {}
assert_respond_to Topic, :method_added_for_finder_respond_to_test
ensure
class << Topic; self; end.send(:remove_method, :method_added_for_finder_respond_to_test)
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index bf0ce18bb8..ca22fe969c 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -22,13 +22,13 @@ class FinderTest < ActiveRecord::TestCase
fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :author_addresses, :customers, :categories, :categorizations, :cars
def test_find_by_id_with_hash
- assert_raises(ActiveRecord::StatementInvalid) do
+ assert_nothing_raised do
Post.find_by_id(limit: 1)
end
end
def test_find_by_title_and_id_with_hash
- assert_raises(ActiveRecord::StatementInvalid) do
+ assert_nothing_raised do
Post.find_by_title_and_id("foo", limit: 1)
end
end
@@ -49,22 +49,22 @@ class FinderTest < ActiveRecord::TestCase
end
def test_find_with_ids_returning_ordered
- records = Topic.find([4,2,5])
+ records = Topic.find([4, 2, 5])
assert_equal "The Fourth Topic of the day", records[0].title
assert_equal "The Second Topic of the day", records[1].title
assert_equal "The Fifth Topic of the day", records[2].title
- records = Topic.find(4,2,5)
+ records = Topic.find(4, 2, 5)
assert_equal "The Fourth Topic of the day", records[0].title
assert_equal "The Second Topic of the day", records[1].title
assert_equal "The Fifth Topic of the day", records[2].title
- records = Topic.find(["4","2","5"])
+ records = Topic.find(["4", "2", "5"])
assert_equal "The Fourth Topic of the day", records[0].title
assert_equal "The Second Topic of the day", records[1].title
assert_equal "The Fifth Topic of the day", records[2].title
- records = Topic.find("4","2","5")
+ records = Topic.find("4", "2", "5")
assert_equal "The Fourth Topic of the day", records[0].title
assert_equal "The Second Topic of the day", records[1].title
assert_equal "The Fifth Topic of the day", records[2].title
@@ -72,12 +72,12 @@ class FinderTest < ActiveRecord::TestCase
def test_find_with_ids_and_order_clause
# The order clause takes precedence over the informed ids
- records = Topic.order(:author_name).find([5,3,1])
+ records = Topic.order(:author_name).find([5, 3, 1])
assert_equal "The Third Topic of the day", records[0].title
assert_equal "The First Topic", records[1].title
assert_equal "The Fifth Topic of the day", records[2].title
- records = Topic.order(:id).find([5,3,1])
+ records = Topic.order(:id).find([5, 3, 1])
assert_equal "The First Topic", records[0].title
assert_equal "The Third Topic of the day", records[1].title
assert_equal "The Fifth Topic of the day", records[2].title
@@ -85,14 +85,14 @@ class FinderTest < ActiveRecord::TestCase
def test_find_with_ids_with_limit_and_order_clause
# The order clause takes precedence over the informed ids
- records = Topic.limit(2).order(:id).find([5,3,1])
+ records = Topic.limit(2).order(:id).find([5, 3, 1])
assert_equal 2, records.size
assert_equal "The First Topic", records[0].title
assert_equal "The Third Topic of the day", records[1].title
end
def test_find_with_ids_and_limit
- records = Topic.limit(3).find([3,2,5,1,4])
+ records = Topic.limit(3).find([3, 2, 5, 1, 4])
assert_equal 3, records.size
assert_equal "The Third Topic of the day", records[0].title
assert_equal "The Second Topic of the day", records[1].title
@@ -102,7 +102,7 @@ class FinderTest < ActiveRecord::TestCase
def test_find_with_ids_where_and_limit
# Please note that Topic 1 is the only not approved so
# if it were among the first 3 it would raise an ActiveRecord::RecordNotFound
- records = Topic.where(approved: true).limit(3).find([3,2,5,1,4])
+ records = Topic.where(approved: true).limit(3).find([3, 2, 5, 1, 4])
assert_equal 3, records.size
assert_equal "The Third Topic of the day", records[0].title
assert_equal "The Second Topic of the day", records[1].title
@@ -110,7 +110,7 @@ class FinderTest < ActiveRecord::TestCase
end
def test_find_with_ids_and_offset
- records = Topic.offset(2).find([3,2,5,1,4])
+ records = Topic.offset(2).find([3, 2, 5, 1, 4])
assert_equal 3, records.size
assert_equal "The Fifth Topic of the day", records[0].title
assert_equal "The First Topic", records[1].title
@@ -118,7 +118,7 @@ class FinderTest < ActiveRecord::TestCase
end
def test_find_passing_active_record_object_is_deprecated
- assert_deprecated do
+ assert_raises(ArgumentError) do
Topic.find(Topic.last)
end
end
@@ -127,7 +127,7 @@ class FinderTest < ActiveRecord::TestCase
gc_disabled = GC.disable
Post.where("author_id" => nil) # warm up
x = Symbol.all_symbols.count
- Post.where("title" => {"xxxqqqq" => "bar"})
+ Post.where("title" => { "xxxqqqq" => "bar" })
assert_equal x, Symbol.all_symbols.count
ensure
GC.enable if gc_disabled == false
@@ -136,7 +136,7 @@ class FinderTest < ActiveRecord::TestCase
# find should handle strings that come from URLs
# (example: Category.find(params[:id]))
def test_find_with_string
- assert_equal(Topic.find(1).title,Topic.find("1").title)
+ assert_equal(Topic.find(1).title, Topic.find("1").title)
end
def test_exists
@@ -151,7 +151,7 @@ class FinderTest < ActiveRecord::TestCase
assert_equal false, Topic.exists?(45)
assert_equal false, Topic.exists?(Topic.new.id)
- assert_raise(NoMethodError) { Topic.exists?([1,2]) }
+ assert_raise(NoMethodError) { Topic.exists?([1, 2]) }
end
def test_exists_with_polymorphic_relation
@@ -167,15 +167,15 @@ class FinderTest < ActiveRecord::TestCase
assert_equal false, relation.exists?(false)
end
- def test_exists_passing_active_record_object_is_deprecated
- assert_deprecated do
+ def test_exists_passing_active_record_object_is_not_permited
+ assert_raises(ArgumentError) do
Topic.exists?(Topic.new)
end
end
def test_exists_returns_false_when_parameter_has_invalid_type
assert_equal false, Topic.exists?("foo")
- assert_equal false, Topic.exists?(("9"*53).to_i) # number that's bigger than int
+ assert_equal false, Topic.exists?(("9" * 53).to_i) # number that's bigger than int
end
def test_exists_does_not_select_columns_without_alias
@@ -258,8 +258,8 @@ class FinderTest < ActiveRecord::TestCase
end
def test_find_by_ids_with_limit_and_offset
- assert_equal 2, Entrant.limit(2).find([1,3,2]).size
- entrants = Entrant.limit(3).offset(2).find([1,3,2])
+ assert_equal 2, Entrant.limit(2).find([1, 3, 2]).size
+ entrants = Entrant.limit(3).offset(2).find([1, 3, 2])
assert_equal 1, entrants.size
assert_equal "Ruby Guru", entrants.first.name
@@ -488,12 +488,12 @@ class FinderTest < ActiveRecord::TestCase
assert_equal topics(:fourth), Topic.offset(1).second_to_last
assert_equal topics(:fourth), Topic.offset(2).second_to_last
assert_equal topics(:fourth), Topic.offset(3).second_to_last
- assert_equal nil, Topic.offset(4).second_to_last
- assert_equal nil, Topic.offset(5).second_to_last
+ assert_nil Topic.offset(4).second_to_last
+ assert_nil Topic.offset(5).second_to_last
#test with limit
- # assert_equal nil, Topic.limit(1).second # TODO: currently failing
- assert_equal nil, Topic.limit(1).second_to_last
+ # assert_nil Topic.limit(1).second # TODO: currently failing
+ assert_nil Topic.limit(1).second_to_last
end
def test_second_to_last_have_primary_key_order_by_default
@@ -516,15 +516,15 @@ class FinderTest < ActiveRecord::TestCase
# test with offset
assert_equal topics(:third), Topic.offset(1).third_to_last
assert_equal topics(:third), Topic.offset(2).third_to_last
- assert_equal nil, Topic.offset(3).third_to_last
- assert_equal nil, Topic.offset(4).third_to_last
- assert_equal nil, Topic.offset(5).third_to_last
+ assert_nil Topic.offset(3).third_to_last
+ assert_nil Topic.offset(4).third_to_last
+ assert_nil Topic.offset(5).third_to_last
# test with limit
- # assert_equal nil, Topic.limit(1).third # TODO: currently failing
- assert_equal nil, Topic.limit(1).third_to_last
- # assert_equal nil, Topic.limit(2).third # TODO: currently failing
- assert_equal nil, Topic.limit(2).third_to_last
+ # assert_nil Topic.limit(1).third # TODO: currently failing
+ assert_nil Topic.limit(1).third_to_last
+ # assert_nil Topic.limit(2).third # TODO: currently failing
+ assert_nil Topic.limit(2).third_to_last
end
def test_third_to_last_have_primary_key_order_by_default
@@ -584,7 +584,7 @@ class FinderTest < ActiveRecord::TestCase
end
def test_last_on_loaded_relation_should_not_use_sql
- relation = Topic.limit(10).load
+ relation = Topic.limit(10).load
assert_no_queries do
relation.last
relation.last(2)
@@ -592,7 +592,7 @@ class FinderTest < ActiveRecord::TestCase
end
def test_last_with_irreversible_order
- assert_deprecated do
+ assert_raises(ActiveRecord::IrreversibleOrderError) do
Topic.order("coalesce(author_name, title)").last
end
end
@@ -627,8 +627,8 @@ class FinderTest < ActiveRecord::TestCase
def test_find_only_some_columns
topic = Topic.select("author_name").find(1)
- assert_raise(ActiveModel::MissingAttributeError) {topic.title}
- assert_raise(ActiveModel::MissingAttributeError) {topic.title?}
+ assert_raise(ActiveModel::MissingAttributeError) { topic.title }
+ assert_raise(ActiveModel::MissingAttributeError) { topic.title? }
assert_nil topic.read_attribute("title")
assert_equal "David", topic.author_name
assert !topic.attribute_present?("title")
@@ -693,27 +693,27 @@ class FinderTest < ActiveRecord::TestCase
end
def test_find_on_hash_conditions_with_range
- assert_equal [1,2], Topic.where(id: 1..2).to_a.map(&:id).sort
+ assert_equal [1, 2], Topic.where(id: 1..2).to_a.map(&:id).sort
assert_raise(ActiveRecord::RecordNotFound) { Topic.where(id: 2..3).find(1) }
end
def test_find_on_hash_conditions_with_end_exclusive_range
- assert_equal [1,2,3], Topic.where(id: 1..3).to_a.map(&:id).sort
- assert_equal [1,2], Topic.where(id: 1...3).to_a.map(&:id).sort
+ assert_equal [1, 2, 3], Topic.where(id: 1..3).to_a.map(&:id).sort
+ assert_equal [1, 2], Topic.where(id: 1...3).to_a.map(&:id).sort
assert_raise(ActiveRecord::RecordNotFound) { Topic.where(id: 2...3).find(3) }
end
def test_find_on_hash_conditions_with_multiple_ranges
- assert_equal [1,2,3], Comment.where(id: 1..3, post_id: 1..2).to_a.map(&:id).sort
+ assert_equal [1, 2, 3], Comment.where(id: 1..3, post_id: 1..2).to_a.map(&:id).sort
assert_equal [1], Comment.where(id: 1..1, post_id: 1..10).to_a.map(&:id).sort
end
def test_find_on_hash_conditions_with_array_of_integers_and_ranges
- assert_equal [1,2,3,5,6,7,8,9], Comment.where(id: [1..2, 3, 5, 6..8, 9]).to_a.map(&:id).sort
+ assert_equal [1, 2, 3, 5, 6, 7, 8, 9], Comment.where(id: [1..2, 3, 5, 6..8, 9]).to_a.map(&:id).sort
end
def test_find_on_hash_conditions_with_array_of_ranges
- assert_equal [1,2,6,7,8], Comment.where(id: [1..2, 6..8]).to_a.map(&:id).sort
+ assert_equal [1, 2, 6, 7, 8], Comment.where(id: [1..2, 6..8]).to_a.map(&:id).sort
end
def test_find_on_multiple_hash_conditions
@@ -864,7 +864,7 @@ class FinderTest < ActiveRecord::TestCase
def test_named_bind_variables_with_quotes
Company.create("name" => "37signals' go'es agains")
- assert Company.where(["name = :name", {name: "37signals' go'es agains"}]).first
+ assert Company.where(["name = :name", { name: "37signals' go'es agains" }]).first
end
def test_named_bind_variables
@@ -874,11 +874,6 @@ class FinderTest < ActiveRecord::TestCase
assert_kind_of Time, Topic.where(["id = :id", { id: 1 }]).first.written_on
end
- def test_string_sanitation
- assert_not_equal "'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1")
- assert_equal "'something; select table'", ActiveRecord::Base.sanitize("something; select table")
- end
-
def test_count_by_sql
assert_equal(0, Entrant.count_by_sql("SELECT COUNT(*) FROM entrants WHERE id > 3"))
assert_equal(1, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 2]))
@@ -988,7 +983,6 @@ class FinderTest < ActiveRecord::TestCase
assert_equal devs[2], Developer.offset(2).first
assert_equal devs[-3], Developer.offset(2).last
- assert_equal devs[-3], Developer.offset(2).last
assert_equal devs[-3], Developer.offset(2).order("id DESC").first
end
@@ -1013,8 +1007,8 @@ class FinderTest < ActiveRecord::TestCase
where("project_id=1").to_a
assert_equal 3, developers_on_project_one.length
developer_names = developers_on_project_one.map(&:name)
- assert developer_names.include?("David")
- assert developer_names.include?("Jamis")
+ assert_includes developer_names, "David"
+ assert_includes developer_names, "Jamis"
end
def test_joins_dont_clobber_id
@@ -1034,7 +1028,7 @@ class FinderTest < ActiveRecord::TestCase
def test_find_by_id_with_conditions_with_or
assert_nothing_raised do
- Post.where("posts.id <= 3 OR posts.#{QUOTED_TYPE} = 'Post'").find([1,2,3])
+ Post.where("posts.id <= 3 OR posts.#{QUOTED_TYPE} = 'Post'").find([1, 2, 3])
end
end
@@ -1066,8 +1060,8 @@ class FinderTest < ActiveRecord::TestCase
end
def test_select_values
- assert_equal ["1","2","3","4","5","6","7","8","9", "10", "11"], Company.connection.select_values("SELECT id FROM companies ORDER BY id").map!(&:to_s)
- assert_equal ["37signals","Summit","Microsoft", "Flamboyant Software", "Ex Nihilo", "RailsCore", "Leetsoft", "Jadedpixel", "Odegy", "Ex Nihilo Part Deux", "Apex"], Company.connection.select_values("SELECT name FROM companies ORDER BY id")
+ assert_equal ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"], Company.connection.select_values("SELECT id FROM companies ORDER BY id").map!(&:to_s)
+ assert_equal ["37signals", "Summit", "Microsoft", "Flamboyant Software", "Ex Nihilo", "RailsCore", "Leetsoft", "Jadedpixel", "Odegy", "Ex Nihilo Part Deux", "Apex"], Company.connection.select_values("SELECT name FROM companies ORDER BY id")
end
def test_select_rows
@@ -1075,9 +1069,9 @@ class FinderTest < ActiveRecord::TestCase
[["1", "1", nil, "37signals"],
["2", "1", "2", "Summit"],
["3", "1", "1", "Microsoft"]],
- Company.connection.select_rows("SELECT id, firm_id, client_of, name FROM companies WHERE id IN (1,2,3) ORDER BY id").map! {|i| i.map! {|j| j.to_s unless j.nil?}})
+ Company.connection.select_rows("SELECT id, firm_id, client_of, name FROM companies WHERE id IN (1,2,3) ORDER BY id").map! { |i| i.map! { |j| j.to_s unless j.nil? } })
assert_equal [["1", "37signals"], ["2", "Summit"], ["3", "Microsoft"]],
- Company.connection.select_rows("SELECT id, name FROM companies WHERE id IN (1,2,3) ORDER BY id").map! {|i| i.map! {|j| j.to_s unless j.nil?}}
+ Company.connection.select_rows("SELECT id, name FROM companies WHERE id IN (1,2,3) ORDER BY id").map! { |i| i.map! { |j| j.to_s unless j.nil? } }
end
def test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct
@@ -1097,7 +1091,7 @@ class FinderTest < ActiveRecord::TestCase
order("client_of DESC").
map(&:client_of)
- assert client_of.include?(nil)
+ assert_includes client_of, nil
assert_equal [2, 1].sort, client_of.compact.sort
end
@@ -1119,6 +1113,16 @@ class FinderTest < ActiveRecord::TestCase
assert_equal [0, 1, 1], posts.map(&:author_id).sort
end
+ def test_find_one_message_on_primary_key
+ e = assert_raises(ActiveRecord::RecordNotFound) do
+ Car.find(0)
+ end
+ assert_equal 0, e.id
+ assert_equal "id", e.primary_key
+ assert_equal "Car", e.model
+ assert_equal "Couldn't find Car with 'id'=0", e.message
+ end
+
def test_find_one_message_with_custom_primary_key
table_with_custom_primary_key do |model|
model.primary_key = :name
@@ -1162,12 +1166,12 @@ class FinderTest < ActiveRecord::TestCase
end
test "find_by returns nil if the record is missing" do
- assert_equal nil, Post.find_by("1 = 0")
+ assert_nil Post.find_by("1 = 0")
end
test "find_by with associations" do
assert_equal authors(:david), Post.find_by(author: authors(:david)).author
- assert_equal authors(:mary) , Post.find_by(author: authors(:mary) ).author
+ assert_equal authors(:mary) , Post.find_by(author: authors(:mary)).author
end
test "find_by doesn't have implicit ordering" do
@@ -1216,7 +1220,7 @@ class FinderTest < ActiveRecord::TestCase
assert_equal tyre2, zyke.tyres.custom_find_by(id: tyre2.id)
end
- protected
+ private
def table_with_custom_primary_key
yield(Class.new(Toy) do
def self.name
diff --git a/activerecord/test/cases/fixture_set/file_test.rb b/activerecord/test/cases/fixture_set/file_test.rb
index e69c9f303a..533edcc2e0 100644
--- a/activerecord/test/cases/fixture_set/file_test.rb
+++ b/activerecord/test/cases/fixture_set/file_test.rb
@@ -31,7 +31,7 @@ module ActiveRecord
def test_values
File.open(::File.join(FIXTURES_ROOT, "accounts.yml")) do |fh|
- assert_equal [1,2,3,4,5,6].sort, fh.to_a.map(&:last).map { |x|
+ assert_equal [1, 2, 3, 4, 5, 6].sort, fh.to_a.map(&:last).map { |x|
x["id"]
}.sort
end
@@ -77,7 +77,7 @@ module ActiveRecord
yaml = "one:\n name: <%= fixture_helper %>\n"
tmp_yaml ["curious", "yml"], yaml do |t|
golden =
- [["one", {"name" => "Fixture helper"}]]
+ [["one", { "name" => "Fixture helper" }]]
assert_equal golden, File.open(t.path) { |fh| fh.to_a }
end
ActiveRecord::FixtureSet.context_class.class_eval do
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 24a9894f19..dd48053823 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -12,9 +12,11 @@ require "models/company"
require "models/computer"
require "models/course"
require "models/developer"
+require "models/dog"
require "models/doubloon"
require "models/joke"
require "models/matey"
+require "models/other_dog"
require "models/parrot"
require "models/pirate"
require "models/post"
@@ -192,28 +194,38 @@ class FixturesTest < ActiveRecord::TestCase
end
def test_empty_yaml_fixture
- assert_not_nil ActiveRecord::FixtureSet.new( Account.connection, "accounts", Account, FIXTURES_ROOT + "/naked/yml/accounts")
+ assert_not_nil ActiveRecord::FixtureSet.new(Account.connection, "accounts", Account, FIXTURES_ROOT + "/naked/yml/accounts")
end
def test_empty_yaml_fixture_with_a_comment_in_it
- assert_not_nil ActiveRecord::FixtureSet.new( Account.connection, "companies", Company, FIXTURES_ROOT + "/naked/yml/companies")
+ assert_not_nil ActiveRecord::FixtureSet.new(Account.connection, "companies", Company, FIXTURES_ROOT + "/naked/yml/companies")
end
def test_nonexistent_fixture_file
nonexistent_fixture_path = FIXTURES_ROOT + "/imnothere"
#sanity check to make sure that this file never exists
- assert Dir[nonexistent_fixture_path+"*"].empty?
+ assert Dir[nonexistent_fixture_path + "*"].empty?
assert_raise(Errno::ENOENT) do
- ActiveRecord::FixtureSet.new( Account.connection, "companies", Company, nonexistent_fixture_path)
+ ActiveRecord::FixtureSet.new(Account.connection, "companies", Company, nonexistent_fixture_path)
end
end
def test_dirty_dirty_yaml_file
- assert_raise(ActiveRecord::Fixture::FormatError) do
- ActiveRecord::FixtureSet.new( Account.connection, "courses", Course, FIXTURES_ROOT + "/naked/yml/courses")
+ fixture_path = FIXTURES_ROOT + "/naked/yml/courses"
+ error = assert_raise(ActiveRecord::Fixture::FormatError) do
+ ActiveRecord::FixtureSet.new(Account.connection, "courses", Course, fixture_path)
end
+ assert_equal "fixture is not a hash: #{fixture_path}.yml", error.to_s
+ end
+
+ def test_yaml_file_with_one_invalid_fixture
+ fixture_path = FIXTURES_ROOT + "/naked/yml/courses_with_invalid_key"
+ error = assert_raise(ActiveRecord::Fixture::FormatError) do
+ ActiveRecord::FixtureSet.new(Account.connection, "courses", Course, fixture_path)
+ end
+ assert_equal "fixture key is not a hash: #{fixture_path}.yml, keys: [\"two\"]", error.to_s
end
def test_yaml_file_with_invalid_column
@@ -728,7 +740,7 @@ class LoadAllFixturesTest < ActiveRecord::TestCase
self.class.fixtures :all
if File.symlink? FIXTURES_ROOT + "/all/admin"
- assert_equal %w(admin/accounts admin/users developers people tasks), fixture_table_names.sort
+ assert_equal %w(admin/accounts admin/users developers namespaced/accounts people tasks), fixture_table_names.sort
end
ensure
ActiveRecord::FixtureSet.reset_cache
@@ -741,7 +753,7 @@ class LoadAllFixturesWithPathnameTest < ActiveRecord::TestCase
self.class.fixtures :all
if File.symlink? FIXTURES_ROOT + "/all/admin"
- assert_equal %w(admin/accounts admin/users developers people tasks), fixture_table_names.sort
+ assert_equal %w(admin/accounts admin/users developers namespaced/accounts people tasks), fixture_table_names.sort
end
ensure
ActiveRecord::FixtureSet.reset_cache
@@ -923,7 +935,7 @@ class FoxyFixturesTest < ActiveRecord::TestCase
end
def test_namespaced_models
- assert admin_accounts(:signals37).users.include?(admin_users(:david))
+ assert_includes admin_accounts(:signals37).users, admin_users(:david)
assert_equal 2, admin_accounts(:signals37).users.size
end
@@ -948,7 +960,7 @@ end
class CustomNameForFixtureOrModelTest < ActiveRecord::TestCase
ActiveRecord::FixtureSet.reset_cache
- set_fixture_class :randomly_named_a9 =>
+ set_fixture_class :randomly_named_a9 =>
ClassNameThatDoesNotFollowCONVENTIONS,
:'admin/randomly_named_a9' =>
Admin::ClassNameThatDoesNotFollowCONVENTIONS1,
@@ -1011,3 +1023,16 @@ class FixtureClassNamesTest < ActiveRecord::TestCase
assert_nil fixture_class_names["unregistered_identifier"]
end
end
+
+class SameNameDifferentDatabaseFixturesTest < ActiveRecord::TestCase
+ fixtures :dogs, :other_dogs
+
+ test "fixtures are properly loaded" do
+ # Force loading the fixtures again to reproduce issue
+ ActiveRecord::FixtureSet.reset_cache
+ create_fixtures("dogs", "other_dogs")
+
+ assert_kind_of Dog, dogs(:sophie)
+ assert_kind_of OtherDog, other_dogs(:lassie)
+ end
+end
diff --git a/activerecord/test/cases/forbidden_attributes_protection_test.rb b/activerecord/test/cases/forbidden_attributes_protection_test.rb
index b17daafe01..ffa3f63e0d 100644
--- a/activerecord/test/cases/forbidden_attributes_protection_test.rb
+++ b/activerecord/test/cases/forbidden_attributes_protection_test.rb
@@ -140,11 +140,11 @@ class ForbiddenAttributesProtectionTest < ActiveRecord::TestCase
def test_where_not_works_with_permitted_params
params = ProtectedParams.new(first_name: "Guille").permit!
Person.create!(params)
- assert_empty Person.where.not(params).select {|p| p.first_name == "Guille" }
+ assert_empty Person.where.not(params).select { |p| p.first_name == "Guille" }
end
def test_strong_params_style_objects_work_with_singular_associations
- params = ProtectedParams.new( name: "Stern", ship_attributes: ProtectedParams.new(name: "The Black Rock").permit!).permit!
+ params = ProtectedParams.new(name: "Stern", ship_attributes: ProtectedParams.new(name: "The Black Rock").permit!).permit!
part = ShipPart.new(params)
assert_equal "Stern", part.name
@@ -155,7 +155,7 @@ class ForbiddenAttributesProtectionTest < ActiveRecord::TestCase
params = ProtectedParams.new(
trinkets_attributes: ProtectedParams.new(
"0" => ProtectedParams.new(name: "Necklace").permit!,
- "1" => ProtectedParams.new(name: "Spoon").permit! ) ).permit!
+ "1" => ProtectedParams.new(name: "Spoon").permit!)).permit!
part = ShipPart.new(params)
assert_equal "Necklace", part.trinkets[0].name
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index ebd4437a87..1ddcbf0e4f 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -1,7 +1,5 @@
require "config"
-require "active_support/testing/autorun"
-require "active_support/testing/method_call_assertions"
require "stringio"
require "active_record"
@@ -29,9 +27,6 @@ ARTest.connect
# Quote "type" if it's a reserved word for the current connection.
QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name("type")
-# FIXME: Remove this when the deprecation cycle on TZ aware types by default ends.
-ActiveRecord::Base.time_zone_aware_types << :time
-
def current_adapter?(*types)
types.any? do |type|
ActiveRecord::ConnectionAdapters.const_defined?(type) &&
@@ -134,21 +129,6 @@ def disable_extension!(extension, connection)
connection.reconnect!
end
-require "cases/validations_repair_helper"
-class ActiveSupport::TestCase
- include ActiveRecord::TestFixtures
- include ActiveRecord::ValidationsRepairHelper
- include ActiveSupport::Testing::MethodCallAssertions
-
- self.fixture_path = FIXTURES_ROOT
- self.use_instantiated_fixtures = false
- self.use_transactional_tests = true
-
- def create_fixtures(*fixture_set_names, &block)
- ActiveRecord::FixtureSet.create_fixtures(ActiveSupport::TestCase.fixture_path, fixture_set_names, fixture_class_names, &block)
- end
-end
-
def load_schema
# silence verbose schema loading
original_stdout = $stdout
diff --git a/activerecord/test/cases/hot_compatibility_test.rb b/activerecord/test/cases/hot_compatibility_test.rb
index e0946544ef..e107ff2362 100644
--- a/activerecord/test/cases/hot_compatibility_test.rb
+++ b/activerecord/test/cases/hot_compatibility_test.rb
@@ -119,11 +119,11 @@ class HotCompatibilityTest < ActiveRecord::TestCase
.instance_variable_get(:@cache)[Process.pid]
end
- # Rails will automatically clear the prepared statements on the connection
- # that runs the migration, so we use two connections to simulate what would
- # actually happen on a production system; we'd have one connection running the
- # migration from the rake task ("ddl_connection" here), and we'd have another
- # connection in a web worker.
+ # Rails will automatically clear the prepared statements on the connection
+ # that runs the migration, so we use two connections to simulate what would
+ # actually happen on a production system; we'd have one connection running the
+ # migration from the rake task ("ddl_connection" here), and we'd have another
+ # connection in a web worker.
def with_two_connections
run_without_connection do |original_connection|
ActiveRecord::Base.establish_connection(original_connection.merge(pool_size: 2))
diff --git a/activerecord/test/cases/i18n_test.rb b/activerecord/test/cases/i18n_test.rb
index ab3a1132e8..7f03c5b23d 100644
--- a/activerecord/test/cases/i18n_test.rb
+++ b/activerecord/test/cases/i18n_test.rb
@@ -8,37 +8,37 @@ class ActiveRecordI18nTests < ActiveRecord::TestCase
end
def test_translated_model_attributes
- I18n.backend.store_translations "en", activerecord: {attributes: {topic: {title: "topic title attribute"} } }
+ I18n.backend.store_translations "en", activerecord: { attributes: { topic: { title: "topic title attribute" } } }
assert_equal "topic title attribute", Topic.human_attribute_name("title")
end
def test_translated_model_attributes_with_symbols
- I18n.backend.store_translations "en", activerecord: {attributes: {topic: {title: "topic title attribute"} } }
+ I18n.backend.store_translations "en", activerecord: { attributes: { topic: { title: "topic title attribute" } } }
assert_equal "topic title attribute", Topic.human_attribute_name(:title)
end
def test_translated_model_attributes_with_sti
- I18n.backend.store_translations "en", activerecord: {attributes: {reply: {title: "reply title attribute"} } }
+ I18n.backend.store_translations "en", activerecord: { attributes: { reply: { title: "reply title attribute" } } }
assert_equal "reply title attribute", Reply.human_attribute_name("title")
end
def test_translated_model_attributes_with_sti_fallback
- I18n.backend.store_translations "en", activerecord: {attributes: {topic: {title: "topic title attribute"} } }
+ I18n.backend.store_translations "en", activerecord: { attributes: { topic: { title: "topic title attribute" } } }
assert_equal "topic title attribute", Reply.human_attribute_name("title")
end
def test_translated_model_names
- I18n.backend.store_translations "en", activerecord: {models: {topic: "topic model"} }
+ I18n.backend.store_translations "en", activerecord: { models: { topic: "topic model" } }
assert_equal "topic model", Topic.model_name.human
end
def test_translated_model_names_with_sti
- I18n.backend.store_translations "en", activerecord: {models: {reply: "reply model"} }
+ I18n.backend.store_translations "en", activerecord: { models: { reply: "reply model" } }
assert_equal "reply model", Reply.model_name.human
end
def test_translated_model_names_with_sti_fallback
- I18n.backend.store_translations "en", activerecord: {models: {topic: "topic model"} }
+ I18n.backend.store_translations "en", activerecord: { models: { topic: "topic model" } }
assert_equal "topic model", Reply.model_name.human
end
end
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 9090184622..e570e9ac1d 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -58,21 +58,21 @@ class InheritanceTest < ActiveRecord::TestCase
end
def test_compute_type_success
- assert_equal Author, ActiveRecord::Base.send(:compute_type, "Author")
+ assert_equal Author, Company.send(:compute_type, "Author")
end
def test_compute_type_nonexistent_constant
e = assert_raises NameError do
- ActiveRecord::Base.send :compute_type, "NonexistentModel"
+ Company.send :compute_type, "NonexistentModel"
end
- assert_equal "uninitialized constant ActiveRecord::Base::NonexistentModel", e.message
- assert_equal "ActiveRecord::Base::NonexistentModel", e.name
+ assert_equal "uninitialized constant Company::NonexistentModel", e.message
+ assert_equal "Company::NonexistentModel", e.name
end
def test_compute_type_no_method_error
- ActiveSupport::Dependencies.stub(:safe_constantize, proc{ raise NoMethodError }) do
+ ActiveSupport::Dependencies.stub(:safe_constantize, proc { raise NoMethodError }) do
assert_raises NoMethodError do
- ActiveRecord::Base.send :compute_type, "InvalidModel"
+ Company.send :compute_type, "InvalidModel"
end
end
end
@@ -87,19 +87,19 @@ class InheritanceTest < ActiveRecord::TestCase
error = e
end
- ActiveSupport::Dependencies.stub(:safe_constantize, proc{ raise e }) do
+ ActiveSupport::Dependencies.stub(:safe_constantize, proc { raise e }) do
exception = assert_raises NameError do
- ActiveRecord::Base.send :compute_type, "InvalidModel"
+ Company.send :compute_type, "InvalidModel"
end
assert_equal error.message, exception.message
end
end
def test_compute_type_argument_error
- ActiveSupport::Dependencies.stub(:safe_constantize, proc{ raise ArgumentError }) do
+ ActiveSupport::Dependencies.stub(:safe_constantize, proc { raise ArgumentError }) do
assert_raises ArgumentError do
- ActiveRecord::Base.send :compute_type, "InvalidModel"
+ Company.send :compute_type, "InvalidModel"
end
end
end
diff --git a/activerecord/test/cases/integration_test.rb b/activerecord/test/cases/integration_test.rb
index 766917b196..d7aa091623 100644
--- a/activerecord/test/cases/integration_test.rb
+++ b/activerecord/test/cases/integration_test.rb
@@ -15,7 +15,7 @@ class IntegrationTest < ActiveRecord::TestCase
def test_to_param_returns_nil_if_not_persisted
client = Client.new
- assert_equal nil, client.to_param
+ assert_nil client.to_param
end
def test_to_param_returns_id_if_not_persisted_but_id_is_set
@@ -89,7 +89,7 @@ class IntegrationTest < ActiveRecord::TestCase
def test_to_param_class_method_uses_default_if_not_persisted
firm = Firm.new(name: "Fancy Shirts")
- assert_equal nil, firm.to_param
+ assert_nil firm.to_param
end
def test_to_param_with_no_arguments
@@ -172,4 +172,10 @@ class IntegrationTest < ActiveRecord::TestCase
owner = owners(:blackbeard)
assert_equal "owners/#{owner.id}-#{owner.happy_at.utc.to_s(:usec)}", owner.cache_key(:updated_at, :happy_at)
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)
+ end
end
diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb
index 81e8b0000a..cc3951e2ba 100644
--- a/activerecord/test/cases/invertible_migration_test.rb
+++ b/activerecord/test/cases/invertible_migration_test.rb
@@ -165,10 +165,8 @@ module ActiveRecord
teardown do
%w[horses new_horses].each do |table|
- ActiveSupport::Deprecation.silence do
- if ActiveRecord::Base.connection.table_exists?(table)
- ActiveRecord::Base.connection.drop_table(table)
- end
+ if ActiveRecord::Base.connection.table_exists?(table)
+ ActiveRecord::Base.connection.drop_table(table)
end
end
ActiveRecord::Migration.verbose = @verbose_was
@@ -199,14 +197,14 @@ module ActiveRecord
def test_migrate_up
migration = InvertibleMigration.new
migration.migrate(:up)
- ActiveSupport::Deprecation.silence { assert migration.connection.table_exists?("horses"), "horses should exist" }
+ assert migration.connection.table_exists?("horses"), "horses should exist"
end
def test_migrate_down
migration = InvertibleMigration.new
migration.migrate :up
migration.migrate :down
- ActiveSupport::Deprecation.silence { assert !migration.connection.table_exists?("horses") }
+ assert !migration.connection.table_exists?("horses")
end
def test_migrate_revert
@@ -214,36 +212,30 @@ module ActiveRecord
revert = InvertibleRevertMigration.new
migration.migrate :up
revert.migrate :up
- ActiveSupport::Deprecation.silence { assert !migration.connection.table_exists?("horses") }
+ assert !migration.connection.table_exists?("horses")
revert.migrate :down
- ActiveSupport::Deprecation.silence { assert migration.connection.table_exists?("horses") }
+ assert migration.connection.table_exists?("horses")
migration.migrate :down
- ActiveSupport::Deprecation.silence { assert !migration.connection.table_exists?("horses") }
+ assert !migration.connection.table_exists?("horses")
end
def test_migrate_revert_by_part
InvertibleMigration.new.migrate :up
received = []
migration = InvertibleByPartsMigration.new
- migration.test = ->(dir){
- ActiveSupport::Deprecation.silence do
- assert migration.connection.table_exists?("horses")
- assert migration.connection.table_exists?("new_horses")
- end
+ migration.test = ->(dir) {
+ assert migration.connection.table_exists?("horses")
+ assert migration.connection.table_exists?("new_horses")
received << dir
}
migration.migrate :up
assert_equal [:both, :up], received
- ActiveSupport::Deprecation.silence do
- assert !migration.connection.table_exists?("horses")
- assert migration.connection.table_exists?("new_horses")
- end
+ assert !migration.connection.table_exists?("horses")
+ assert migration.connection.table_exists?("new_horses")
migration.migrate :down
assert_equal [:both, :up, :both, :down], received
- ActiveSupport::Deprecation.silence do
- assert migration.connection.table_exists?("horses")
- assert !migration.connection.table_exists?("new_horses")
- end
+ assert migration.connection.table_exists?("horses")
+ assert !migration.connection.table_exists?("new_horses")
end
def test_migrate_revert_whole_migration
@@ -252,20 +244,20 @@ module ActiveRecord
revert = RevertWholeMigration.new(klass)
migration.migrate :up
revert.migrate :up
- ActiveSupport::Deprecation.silence { assert !migration.connection.table_exists?("horses") }
+ assert !migration.connection.table_exists?("horses")
revert.migrate :down
- ActiveSupport::Deprecation.silence { assert migration.connection.table_exists?("horses") }
+ assert migration.connection.table_exists?("horses")
migration.migrate :down
- ActiveSupport::Deprecation.silence { assert !migration.connection.table_exists?("horses") }
+ assert !migration.connection.table_exists?("horses")
end
end
def test_migrate_nested_revert_whole_migration
revert = NestedRevertWholeMigration.new(InvertibleRevertMigration)
revert.migrate :down
- ActiveSupport::Deprecation.silence { assert revert.connection.table_exists?("horses") }
+ assert revert.connection.table_exists?("horses")
revert.migrate :up
- ActiveSupport::Deprecation.silence { assert !revert.connection.table_exists?("horses") }
+ assert !revert.connection.table_exists?("horses")
end
def test_migrate_revert_change_column_default
@@ -305,7 +297,7 @@ module ActiveRecord
end
def test_revert_order
- block = Proc.new{|t| t.string :name }
+ block = Proc.new { |t| t.string :name }
recorder = ActiveRecord::Migration::CommandRecorder.new(ActiveRecord::Base.connection)
recorder.instance_eval do
create_table("apples", &block)
@@ -330,24 +322,24 @@ module ActiveRecord
def test_legacy_up
LegacyMigration.migrate :up
- ActiveSupport::Deprecation.silence { assert ActiveRecord::Base.connection.table_exists?("horses"), "horses should exist" }
+ assert ActiveRecord::Base.connection.table_exists?("horses"), "horses should exist"
end
def test_legacy_down
LegacyMigration.migrate :up
LegacyMigration.migrate :down
- ActiveSupport::Deprecation.silence { assert !ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist" }
+ assert !ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist"
end
def test_up
LegacyMigration.up
- ActiveSupport::Deprecation.silence { assert ActiveRecord::Base.connection.table_exists?("horses"), "horses should exist" }
+ assert ActiveRecord::Base.connection.table_exists?("horses"), "horses should exist"
end
def test_down
LegacyMigration.up
LegacyMigration.down
- ActiveSupport::Deprecation.silence { assert !ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist" }
+ assert !ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist"
end
def test_migrate_down_with_table_name_prefix
@@ -356,7 +348,7 @@ module ActiveRecord
migration = InvertibleMigration.new
migration.migrate(:up)
assert_nothing_raised { migration.migrate(:down) }
- ActiveSupport::Deprecation.silence { assert !ActiveRecord::Base.connection.table_exists?("p_horses_s"), "p_horses_s should not exist" }
+ assert !ActiveRecord::Base.connection.table_exists?("p_horses_s"), "p_horses_s should not exist"
ensure
ActiveRecord::Base.table_name_prefix = ActiveRecord::Base.table_name_suffix = ""
end
diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb
index 814d3b63b2..155e858822 100644
--- a/activerecord/test/cases/json_serialization_test.rb
+++ b/activerecord/test/cases/json_serialization_test.rb
@@ -51,7 +51,7 @@ class JsonSerializationTest < ActiveRecord::TestCase
assert_match %r{^\{"contact":\{}, json
assert_match %r{"name":"Konata Izumi"}, json
assert_match %r{"age":16}, json
- assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
+ assert_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})
assert_match %r{"awesome":true}, json
assert_match %r{"preferences":\{"shows":"anime"\}}, json
end
@@ -62,7 +62,7 @@ class JsonSerializationTest < ActiveRecord::TestCase
assert_match %r{"name":"Konata Izumi"}, json
assert_match %r{"age":16}, json
- assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
+ assert_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})
assert_match %r{"awesome":true}, json
assert_match %r{"preferences":\{"shows":"anime"\}}, json
end
@@ -73,7 +73,7 @@ class JsonSerializationTest < ActiveRecord::TestCase
assert_match %r{"name":"Konata Izumi"}, json
assert_match %r{"age":16}, json
assert_no_match %r{"awesome":true}, json
- assert !json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
+ assert_not_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})
assert_no_match %r{"preferences":\{"shows":"anime"\}}, json
end
@@ -83,7 +83,7 @@ class JsonSerializationTest < ActiveRecord::TestCase
assert_no_match %r{"name":"Konata Izumi"}, json
assert_no_match %r{"age":16}, json
assert_match %r{"awesome":true}, json
- assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
+ assert_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})
assert_match %r{"preferences":\{"shows":"anime"\}}, json
end
@@ -102,7 +102,7 @@ class JsonSerializationTest < ActiveRecord::TestCase
end
def test_uses_serializable_hash_with_only_option
- def @contact.serializable_hash(options=nil)
+ def @contact.serializable_hash(options = nil)
super(only: %w(name))
end
@@ -113,7 +113,7 @@ class JsonSerializationTest < ActiveRecord::TestCase
end
def test_uses_serializable_hash_with_except_option
- def @contact.serializable_hash(options=nil)
+ def @contact.serializable_hash(options = nil)
super(except: %w(age))
end
@@ -137,7 +137,7 @@ class JsonSerializationTest < ActiveRecord::TestCase
@contact = ContactSti.new(@contact.attributes)
assert_equal "ContactSti", @contact.type
- def @contact.serializable_hash(options={})
+ def @contact.serializable_hash(options = {})
super({ except: %w(age) }.merge!(options))
end
@@ -243,7 +243,7 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase
assert !@david.posts.first.respond_to?(:favorite_quote)
assert_match %r{"favorite_quote":"Constraints are liberating"}, json
- assert_equal %r{"favorite_quote":}.match(json).size, 1
+ assert_equal 1, %r{"favorite_quote":}.match(json).size
end
def test_should_allow_only_option_for_list_of_authors
@@ -275,7 +275,7 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase
['"name":"David"', '"posts":[', '{"id":1}', '{"id":2}', '{"id":4}',
'{"id":5}', '{"id":6}', '"name":"Mary"', '"posts":[', '{"id":7}', '{"id":9}'].each do |fragment|
- assert json.include?(fragment), json
+ assert_includes json, fragment, json
end
end
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 5c55584ff7..95fb670dac 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -161,14 +161,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal(error.record.object_id, p2.object_id)
end
- def test_lock_new_with_nil
- p1 = Person.new(first_name: "anika")
- p1.save!
- p1.lock_version = nil # simulate bad fixture or column with no default
- p1.save!
- assert_equal 1, p1.lock_version
- end
-
def test_lock_new_when_explicitly_passing_nil
p1 = Person.new(first_name: "anika", lock_version: nil)
p1.save!
@@ -181,6 +173,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase
p1.touch
assert_equal 1, p1.lock_version
+ assert_not p1.changed?, "Changes should have been cleared"
end
def test_touch_stale_object
@@ -221,22 +214,149 @@ class OptimisticLockingTest < ActiveRecord::TestCase
def test_lock_without_default_sets_version_to_zero
t1 = LockWithoutDefault.new
+
+ assert_equal 0, t1.lock_version
+ assert_nil t1.lock_version_before_type_cast
+
+ t1.save!
+ t1.reload
+
assert_equal 0, t1.lock_version
+ assert_equal 0, t1.lock_version_before_type_cast
+ 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
+
+ assert_equal 0, t1.lock_version
+ assert_nil t1.lock_version_before_type_cast
+ assert_equal 0, t2.lock_version
+ assert_nil t2.lock_version_before_type_cast
+
+ t1.title = "new title1"
+ t2.title = "new title2"
+
+ assert_nothing_raised { t1.save! }
+ assert_equal 1, t1.lock_version
+ assert_equal "new title1", t1.title
+
+ assert_raise(ActiveRecord::StaleObjectError) { t2.save! }
+ assert_equal 0, t2.lock_version
+ assert_equal "new title2", t2.title
+ end
+
+ def test_lock_without_default_should_update_with_lock_col
+ t1 = LockWithoutDefault.create(title: "title1", lock_version: 6)
+
+ assert_equal 6, t1.lock_version
+
+ t1.update(lock_version: 0)
+ t1.reload
- t1.save
- t1 = LockWithoutDefault.find(t1.id)
assert_equal 0, t1.lock_version
end
+ def test_lock_without_default_queries_count
+ t1 = LockWithoutDefault.create(title: "title1")
+
+ assert_equal "title1", t1.title
+ assert_equal 0, t1.lock_version
+
+ assert_queries(1) { t1.update(title: "title2") }
+
+ t1.reload
+ assert_equal "title2", t1.title
+ assert_equal 1, t1.lock_version
+
+ assert_queries(1) { t1.update(title: "title3", lock_version: 6) }
+
+ t1.reload
+ assert_equal "title3", t1.title
+ assert_equal 6, t1.lock_version
+
+ t2 = LockWithoutDefault.new(title: "title1")
+
+ assert_queries(1) { t2.save! }
+
+ t2.reload
+ assert_equal "title1", t2.title
+ assert_equal 0, t2.lock_version
+ end
+
def test_lock_with_custom_column_without_default_sets_version_to_zero
t1 = LockWithCustomColumnWithoutDefault.new
+
assert_equal 0, t1.custom_lock_version
assert_nil t1.custom_lock_version_before_type_cast
t1.save!
t1.reload
+
+ assert_equal 0, t1.custom_lock_version
+ assert_equal 0, t1.custom_lock_version_before_type_cast
+ end
+
+ def test_lock_with_custom_column_without_default_should_work_with_null_in_the_database
+ ActiveRecord::Base.connection.execute("INSERT INTO lock_without_defaults_cust(title) VALUES('title1')")
+
+ t1 = LockWithCustomColumnWithoutDefault.last
+ t2 = LockWithCustomColumnWithoutDefault.last
+
assert_equal 0, t1.custom_lock_version
- assert [0, "0"].include?(t1.custom_lock_version_before_type_cast)
+ assert_nil t1.custom_lock_version_before_type_cast
+ assert_equal 0, t2.custom_lock_version
+ assert_nil t2.custom_lock_version_before_type_cast
+
+ t1.title = "new title1"
+ t2.title = "new title2"
+
+ assert_nothing_raised { t1.save! }
+ assert_equal 1, t1.custom_lock_version
+ assert_equal "new title1", t1.title
+
+ assert_raise(ActiveRecord::StaleObjectError) { t2.save! }
+ assert_equal 0, t2.custom_lock_version
+ assert_equal "new title2", t2.title
+ end
+
+ def test_lock_with_custom_column_without_default_should_update_with_lock_col
+ t1 = LockWithCustomColumnWithoutDefault.create(title: "title1", custom_lock_version: 6)
+
+ assert_equal 6, t1.custom_lock_version
+
+ t1.update(custom_lock_version: 0)
+ t1.reload
+
+ assert_equal 0, t1.custom_lock_version
+ end
+
+ def test_lock_with_custom_column_without_default_queries_count
+ t1 = LockWithCustomColumnWithoutDefault.create(title: "title1")
+
+ assert_equal "title1", t1.title
+ assert_equal 0, t1.custom_lock_version
+
+ assert_queries(1) { t1.update(title: "title2") }
+
+ t1.reload
+ assert_equal "title2", t1.title
+ assert_equal 1, t1.custom_lock_version
+
+ assert_queries(1) { t1.update(title: "title3", custom_lock_version: 6) }
+
+ t1.reload
+ assert_equal "title3", t1.title
+ assert_equal 6, t1.custom_lock_version
+
+ t2 = LockWithCustomColumnWithoutDefault.new(title: "title1")
+
+ assert_queries(1) { t2.save! }
+
+ t2.reload
+ assert_equal "title1", t2.title
+ assert_equal 0, t2.custom_lock_version
end
def test_readonly_attributes
@@ -350,7 +470,7 @@ class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase
private
- def add_counter_column_to(model, col="test_count")
+ def add_counter_column_to(model, col = "test_count")
model.connection.add_column model.table_name, col, :integer, null: false, default: 0
model.reset_column_information
end
@@ -459,7 +579,8 @@ unless in_memory_db?
assert first.end > second.end
end
- protected
+ private
+
def duel(zzz = 5)
t0, t1, t2, t3 = nil, nil, nil, nil
diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
index 0191c9d03b..48cfe89882 100644
--- a/activerecord/test/cases/migration/change_schema_test.rb
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -43,7 +43,7 @@ module ActiveRecord
t.column :foo, :string, null: false
end
- assert_raises(ActiveRecord::StatementInvalid) do
+ assert_raises(ActiveRecord::NotNullViolation) do
connection.execute "insert into testings (foo) values (NULL)"
end
end
@@ -200,8 +200,8 @@ module ActiveRecord
end
created_columns = connection.columns(table_name)
- created_at_column = created_columns.detect {|c| c.name == "created_at" }
- updated_at_column = created_columns.detect {|c| c.name == "updated_at" }
+ created_at_column = created_columns.detect { |c| c.name == "created_at" }
+ updated_at_column = created_columns.detect { |c| c.name == "updated_at" }
assert !created_at_column.null
assert !updated_at_column.null
@@ -213,8 +213,8 @@ module ActiveRecord
end
created_columns = connection.columns(table_name)
- created_at_column = created_columns.detect {|c| c.name == "created_at" }
- updated_at_column = created_columns.detect {|c| c.name == "updated_at" }
+ created_at_column = created_columns.detect { |c| c.name == "created_at" }
+ updated_at_column = created_columns.detect { |c| c.name == "updated_at" }
assert created_at_column.null
assert updated_at_column.null
@@ -233,7 +233,7 @@ module ActiveRecord
end
connection.add_column :testings, :bar, :string, null: false
- assert_raise(ActiveRecord::StatementInvalid) do
+ assert_raise(ActiveRecord::NotNullViolation) do
connection.execute "insert into testings (foo, bar) values ('hello', NULL)"
end
end
@@ -244,12 +244,16 @@ module ActiveRecord
t.column :foo, :string
end
- con = connection
- connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}) values (1, 'hello')"
- assert_nothing_raised {connection.add_column :testings, :bar, :string, null: false, default: "default" }
+ quoted_id = connection.quote_column_name("id")
+ quoted_foo = connection.quote_column_name("foo")
+ quoted_bar = connection.quote_column_name("bar")
+ connection.execute("insert into testings (#{quoted_id}, #{quoted_foo}) values (1, 'hello')")
+ assert_nothing_raised do
+ connection.add_column :testings, :bar, :string, null: false, default: "default"
+ end
- assert_raises(ActiveRecord::StatementInvalid) do
- connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) values (2, 'hello', NULL)"
+ assert_raises(ActiveRecord::NotNullViolation) do
+ connection.execute("insert into testings (#{quoted_id}, #{quoted_foo}, #{quoted_bar}) values (2, 'hello', NULL)")
end
end
@@ -298,9 +302,9 @@ module ActiveRecord
assert_equal false, person_klass.columns_hash["wealth"].null
# Oracle needs primary key value from sequence
if current_adapter?(:OracleAdapter)
- assert_nothing_raised {person_klass.connection.execute("insert into testings (id, title) values (testings_seq.nextval, 'tester')")}
+ assert_nothing_raised { person_klass.connection.execute("insert into testings (id, title) values (testings_seq.nextval, 'tester')") }
else
- assert_nothing_raised {person_klass.connection.execute("insert into testings (title) values ('tester')")}
+ assert_nothing_raised { person_klass.connection.execute("insert into testings (title) values ('tester')") }
end
# change column default to see that column doesn't lose its not null definition
@@ -346,9 +350,9 @@ module ActiveRecord
end
notnull_migration.new.suppress_messages do
notnull_migration.migrate(:up)
- assert_equal false, connection.columns(:testings).find{ |c| c.name == "foo"}.null
+ assert_equal false, connection.columns(:testings).find { |c| c.name == "foo" }.null
notnull_migration.migrate(:down)
- assert connection.columns(:testings).find{ |c| c.name == "foo"}.null
+ assert connection.columns(:testings).find { |c| c.name == "foo" }.null
end
end
end
@@ -405,9 +409,9 @@ module ActiveRecord
def test_drop_table_if_exists
connection.create_table(:testings)
- ActiveSupport::Deprecation.silence { assert connection.table_exists?(:testings) }
+ assert connection.table_exists?(:testings)
connection.drop_table(:testings, if_exists: true)
- ActiveSupport::Deprecation.silence { assert_not connection.table_exists?(:testings) }
+ assert_not connection.table_exists?(:testings)
end
def test_drop_table_if_exists_nothing_raised
diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb
index 87da9c90fe..8a4242cf1d 100644
--- a/activerecord/test/cases/migration/change_table_test.rb
+++ b/activerecord/test/cases/migration/change_table_test.rb
@@ -101,7 +101,12 @@ module ActiveRecord
def test_primary_key_creates_primary_key_column
with_change_table do |t|
- @connection.expect :add_column, nil, [:delete_me, :id, :primary_key, primary_key: true, first: true]
+ if current_adapter?(:Mysql2Adapter)
+ @connection.expect :add_column, nil, [:delete_me, :id, :primary_key, { first: true, auto_increment: true, limit: 8, primary_key: true }]
+ else
+ @connection.expect :add_column, nil, [:delete_me, :id, :primary_key, primary_key: true, first: true]
+ end
+
t.primary_key :id, first: true
end
end
@@ -157,7 +162,7 @@ module ActiveRecord
def test_column_creates_column_with_options
with_change_table do |t|
- @connection.expect :add_column, nil, [:delete_me, :bar, :integer, {null: false}]
+ @connection.expect :add_column, nil, [:delete_me, :bar, :integer, { null: false }]
t.column :bar, :integer, null: false
end
end
@@ -171,7 +176,7 @@ module ActiveRecord
def test_index_creates_index_with_options
with_change_table do |t|
- @connection.expect :add_index, nil, [:delete_me, :bar, {unique: true}]
+ @connection.expect :add_index, nil, [:delete_me, :bar, { unique: true }]
t.index :bar, unique: true
end
end
@@ -185,7 +190,7 @@ module ActiveRecord
def test_index_exists_with_options
with_change_table do |t|
- @connection.expect :index_exists?, nil, [:delete_me, :bar, {unique: true}]
+ @connection.expect :index_exists?, nil, [:delete_me, :bar, { unique: true }]
t.index_exists?(:bar, unique: true)
end
end
@@ -206,7 +211,7 @@ module ActiveRecord
def test_change_changes_column_with_options
with_change_table do |t|
- @connection.expect :change_column, nil, [:delete_me, :bar, :string, {null: true}]
+ @connection.expect :change_column, nil, [:delete_me, :bar, :string, { null: true }]
t.change :bar, :string, null: true
end
end
@@ -234,7 +239,7 @@ module ActiveRecord
def test_remove_index_removes_index_with_options
with_change_table do |t|
- @connection.expect :remove_index, nil, [:delete_me, {unique: true}]
+ @connection.expect :remove_index, nil, [:delete_me, { unique: true }]
t.remove_index unique: true
end
end
diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb
index ab8229dbae..48df931543 100644
--- a/activerecord/test/cases/migration/column_attributes_test.rb
+++ b/activerecord/test/cases/migration/column_attributes_test.rb
@@ -63,8 +63,6 @@ module ActiveRecord
# Do a manual insertion
if current_adapter?(:OracleAdapter)
connection.execute "insert into test_models (id, wealth) values (people_seq.nextval, 12345678901234567890.0123456789)"
- elsif current_adapter?(:PostgreSQLAdapter)
- connection.execute "insert into test_models (wealth) values (12345678901234567890.0123456789)"
else
connection.execute "insert into test_models (wealth) values (12345678901234567890.0123456789)"
end
@@ -74,9 +72,7 @@ module ActiveRecord
assert_kind_of BigDecimal, row.wealth
# If this assert fails, that means the SELECT is broken!
- unless current_adapter?(:SQLite3Adapter)
- assert_equal correct_value, row.wealth
- end
+ assert_equal correct_value, row.wealth
# Reset to old state
TestModel.delete_all
@@ -167,7 +163,7 @@ module ActiveRecord
assert_raise(ActiveRecordError) { add_column :test_models, :integer_too_big, :integer, limit: 10 }
unless current_adapter?(:PostgreSQLAdapter)
- assert_raise(ActiveRecordError) { add_column :test_models, :text_too_big, :integer, limit: 0xfffffffff }
+ assert_raise(ActiveRecordError) { add_column :test_models, :text_too_big, :text, limit: 0xfffffffff }
end
end
end
diff --git a/activerecord/test/cases/migration/columns_test.rb b/activerecord/test/cases/migration/columns_test.rb
index 70d40ed87a..55c06da411 100644
--- a/activerecord/test/cases/migration/columns_test.rb
+++ b/activerecord/test/cases/migration/columns_test.rb
@@ -32,7 +32,7 @@ module ActiveRecord
rename_column :test_models, :first_name, :nick_name
TestModel.reset_column_information
- assert TestModel.column_names.include?("nick_name")
+ assert_includes TestModel.column_names, "nick_name"
assert_equal ["foo"], TestModel.all.map(&:nick_name)
end
@@ -45,7 +45,7 @@ module ActiveRecord
rename_column "test_models", "first_name", "nick_name"
TestModel.reset_column_information
- assert TestModel.column_names.include?("nick_name")
+ assert_includes TestModel.column_names, "nick_name"
assert_equal ["foo"], TestModel.all.map(&:nick_name)
end
@@ -57,7 +57,7 @@ module ActiveRecord
rename_column "test_models", "salary", "annual_salary"
- assert TestModel.column_names.include?("annual_salary")
+ assert_includes TestModel.column_names, "annual_salary"
default_after = connection.columns("test_models").find { |c| c.name == "annual_salary" }.default
assert_equal "70000", default_after
end
@@ -88,7 +88,7 @@ module ActiveRecord
add_column "test_models", "first_name", :string
rename_column "test_models", "first_name", "group"
- assert TestModel.column_names.include?("group")
+ assert_includes TestModel.column_names, "group"
end
def test_rename_column_with_an_index
@@ -192,8 +192,8 @@ module ActiveRecord
new_columns = connection.columns(TestModel.table_name)
- assert_not new_columns.find { |c| c.name == "age" and c.type == :integer }
- assert new_columns.find { |c| c.name == "age" and c.type == :string }
+ assert_not new_columns.find { |c| c.name == "age" && c.type == :integer }
+ assert new_columns.find { |c| c.name == "age" && c.type == :string }
old_columns = connection.columns(TestModel.table_name)
assert old_columns.find { |c|
@@ -206,11 +206,11 @@ module ActiveRecord
assert_not new_columns.find { |c|
default = connection.lookup_cast_type_from_column(c).deserialize(c.default)
- c.name == "approved" and c.type == :boolean and default == true
+ c.name == "approved" && c.type == :boolean && default == true
}
assert new_columns.find { |c|
default = connection.lookup_cast_type_from_column(c).deserialize(c.default)
- c.name == "approved" and c.type == :boolean and default == false
+ c.name == "approved" && c.type == :boolean && default == false
}
change_column :test_models, :approved, :boolean, default: true
end
diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb
index 39f3fd99c7..802a969cb7 100644
--- a/activerecord/test/cases/migration/command_recorder_test.rb
+++ b/activerecord/test/cases/migration/command_recorder_test.rb
@@ -44,7 +44,7 @@ module ActiveRecord
def test_irreversible_commands_raise_exception
assert_raises(ActiveRecord::IrreversibleMigration) do
- @recorder.revert{ @recorder.execute "some sql" }
+ @recorder.revert { @recorder.execute "some sql" }
end
end
@@ -58,12 +58,12 @@ module ActiveRecord
@recorder.record :create_table, [:hello]
@recorder.record :create_table, [:world]
end
- tables = @recorder.commands.map{|_cmd, args, _block| args}
+ tables = @recorder.commands.map { |_cmd, args, _block| args }
assert_equal [[:world], [:hello]], tables
end
def test_revert_order
- block = Proc.new{|t| t.string :name }
+ block = Proc.new { |t| t.string :name }
@recorder.instance_eval do
create_table("apples", &block)
revert do
@@ -115,13 +115,13 @@ module ActiveRecord
end
def test_invert_create_table_with_options_and_block
- block = Proc.new{}
+ block = Proc.new {}
drop_table = @recorder.inverse_of :create_table, [:people_reminders, id: false], &block
assert_equal [:drop_table, [:people_reminders, id: false], block], drop_table
end
def test_invert_drop_table
- block = Proc.new{}
+ block = Proc.new {}
create_table = @recorder.inverse_of :drop_table, [:people_reminders, id: false], &block
assert_equal [:create_table, [:people_reminders, id: false], block], create_table
end
@@ -143,7 +143,7 @@ module ActiveRecord
end
def test_invert_drop_join_table
- block = Proc.new{}
+ block = Proc.new {}
create_join_table = @recorder.inverse_of :drop_join_table, [:musics, :artists, table_name: :catalog], &block
assert_equal [:create_join_table, [:musics, :artists, table_name: :catalog], block], create_join_table
end
@@ -203,17 +203,17 @@ module ActiveRecord
def test_invert_add_index
remove = @recorder.inverse_of :add_index, [:table, [:one, :two]]
- assert_equal [:remove_index, [:table, {column: [:one, :two]}]], remove
+ assert_equal [:remove_index, [:table, { column: [:one, :two] }]], remove
end
def test_invert_add_index_with_name
remove = @recorder.inverse_of :add_index, [:table, [:one, :two], name: "new_index"]
- assert_equal [:remove_index, [:table, {name: "new_index"}]], remove
+ 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
+ assert_equal [:remove_index, [:table, { column: [:one, :two] }]], remove
end
def test_invert_remove_index
@@ -222,17 +222,17 @@ module ActiveRecord
end
def test_invert_remove_index_with_column
- add = @recorder.inverse_of :remove_index, [:table, {column: [:one, :two], options: true}]
+ add = @recorder.inverse_of :remove_index, [:table, { column: [:one, :two], options: true }]
assert_equal [:add_index, [:table, [:one, :two], options: true]], add
end
def test_invert_remove_index_with_name
- add = @recorder.inverse_of :remove_index, [:table, {column: [:one, :two], name: "new_index"}]
+ add = @recorder.inverse_of :remove_index, [:table, { column: [:one, :two], name: "new_index" }]
assert_equal [:add_index, [:table, [:one, :two], name: "new_index"]], add
end
def test_invert_remove_index_with_no_special_options
- add = @recorder.inverse_of :remove_index, [:table, {column: [:one, :two]}]
+ add = @recorder.inverse_of :remove_index, [:table, { column: [:one, :two] }]
assert_equal [:add_index, [:table, [:one, :two], {}]], add
end
@@ -254,7 +254,7 @@ module ActiveRecord
def test_invert_remove_timestamps
add = @recorder.inverse_of :remove_timestamps, [:table, { null: true }]
- assert_equal [:add_timestamps, [:table, {null: true }], nil], add
+ assert_equal [:add_timestamps, [:table, { null: true }], nil], add
end
def test_invert_add_reference
diff --git a/activerecord/test/cases/migration/compatibility_test.rb b/activerecord/test/cases/migration/compatibility_test.rb
index 0a4b604601..e5a7412bc3 100644
--- a/activerecord/test/cases/migration/compatibility_test.rb
+++ b/activerecord/test/cases/migration/compatibility_test.rb
@@ -55,7 +55,7 @@ module ActiveRecord
end
def test_references_does_not_add_index_by_default
- migration = Class.new(ActiveRecord::Migration) {
+ migration = Class.new(ActiveRecord::Migration[4.2]) {
def migrate(x)
create_table :more_testings do |t|
t.references :foo
@@ -73,7 +73,7 @@ module ActiveRecord
end
def test_timestamps_have_null_constraints_if_not_present_in_migration_of_create_table
- migration = Class.new(ActiveRecord::Migration) {
+ migration = Class.new(ActiveRecord::Migration[4.2]) {
def migrate(x)
create_table :more_testings do |t|
t.timestamps
@@ -90,7 +90,7 @@ module ActiveRecord
end
def test_timestamps_have_null_constraints_if_not_present_in_migration_for_adding_timestamps_to_existing_table
- migration = Class.new(ActiveRecord::Migration) {
+ migration = Class.new(ActiveRecord::Migration[4.2]) {
def migrate(x)
add_timestamps :testings
end
@@ -102,15 +102,9 @@ module ActiveRecord
assert connection.columns(:testings).find { |c| c.name == "updated_at" }.null
end
- def test_legacy_migrations_get_deprecation_warning_when_run
- migration = Class.new(ActiveRecord::Migration) {
- def up
- add_column :testings, :baz, :string
- end
- }
-
- assert_deprecated do
- migration.migrate :up
+ def test_legacy_migrations_raises_exception_when_inherited
+ assert_raises(StandardError) do
+ Class.new(ActiveRecord::Migration)
end
end
end
diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb
index 372ec40a04..26b1bb4419 100644
--- a/activerecord/test/cases/migration/create_join_table_test.rb
+++ b/activerecord/test/cases/migration/create_join_table_test.rb
@@ -12,9 +12,7 @@ module ActiveRecord
teardown do
%w(artists_musics musics_videos catalog).each do |table_name|
- ActiveSupport::Deprecation.silence do
- connection.drop_table table_name if connection.table_exists?(table_name)
- end
+ connection.drop_table table_name if connection.table_exists?(table_name)
end
end
@@ -61,7 +59,7 @@ module ActiveRecord
end
def test_create_join_table_with_column_options
- connection.create_join_table :artists, :musics, column_options: {null: true}
+ connection.create_join_table :artists, :musics, column_options: { null: true }
assert_equal [true, true], connection.columns(:artists_musics).map(&:null)
end
@@ -84,51 +82,51 @@ module ActiveRecord
connection.create_join_table :artists, :musics
connection.drop_join_table :artists, :musics
- ActiveSupport::Deprecation.silence { assert !connection.table_exists?("artists_musics") }
+ assert !connection.table_exists?("artists_musics")
end
def test_drop_join_table_with_strings
connection.create_join_table :artists, :musics
connection.drop_join_table "artists", "musics"
- ActiveSupport::Deprecation.silence { assert !connection.table_exists?("artists_musics") }
+ assert !connection.table_exists?("artists_musics")
end
def test_drop_join_table_with_the_proper_order
connection.create_join_table :videos, :musics
connection.drop_join_table :videos, :musics
- ActiveSupport::Deprecation.silence { assert !connection.table_exists?("musics_videos") }
+ assert !connection.table_exists?("musics_videos")
end
def test_drop_join_table_with_the_table_name
connection.create_join_table :artists, :musics, table_name: :catalog
connection.drop_join_table :artists, :musics, table_name: :catalog
- ActiveSupport::Deprecation.silence { assert !connection.table_exists?("catalog") }
+ assert !connection.table_exists?("catalog")
end
def test_drop_join_table_with_the_table_name_as_string
connection.create_join_table :artists, :musics, table_name: "catalog"
connection.drop_join_table :artists, :musics, table_name: "catalog"
- ActiveSupport::Deprecation.silence { assert !connection.table_exists?("catalog") }
+ assert !connection.table_exists?("catalog")
end
def test_drop_join_table_with_column_options
- connection.create_join_table :artists, :musics, column_options: {null: true}
- connection.drop_join_table :artists, :musics, column_options: {null: true}
+ connection.create_join_table :artists, :musics, column_options: { null: true }
+ connection.drop_join_table :artists, :musics, column_options: { null: true }
- ActiveSupport::Deprecation.silence { assert !connection.table_exists?("artists_musics") }
+ assert !connection.table_exists?("artists_musics")
end
def test_create_and_drop_join_table_with_common_prefix
with_table_cleanup do
connection.create_join_table "audio_artists", "audio_musics"
- ActiveSupport::Deprecation.silence { assert connection.table_exists?("audio_artists_musics") }
+ assert connection.table_exists?("audio_artists_musics")
connection.drop_join_table "audio_artists", "audio_musics"
- ActiveSupport::Deprecation.silence { assert !connection.table_exists?("audio_artists_musics"), "Should have dropped join table, but didn't" }
+ assert !connection.table_exists?("audio_artists_musics"), "Should have dropped join table, but didn't"
end
end
diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb
index cab2069754..9be6667aa1 100644
--- a/activerecord/test/cases/migration/foreign_key_test.rb
+++ b/activerecord/test/cases/migration/foreign_key_test.rb
@@ -76,7 +76,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
end
def test_add_foreign_key_with_non_standard_primary_key
- with_example_table @connection, "space_shuttles", "pk integer PRIMARY KEY" do
+ with_example_table @connection, "space_shuttles", "pk BIGINT PRIMARY KEY" do
@connection.add_foreign_key(:astronauts, :space_shuttles,
column: "rocket_id", primary_key: "pk", name: "custom_pk")
@@ -101,7 +101,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
fk = foreign_keys.first
if current_adapter?(:Mysql2Adapter)
# ON DELETE RESTRICT is the default on MySQL
- assert_equal nil, fk.on_delete
+ assert_nil fk.on_delete
else
assert_equal :restrict, fk.on_delete
end
@@ -229,7 +229,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
create_table("cities") { |t| }
create_table("houses") do |t|
- t.column :city_id, :integer
+ t.column :city_id, :bigint
end
add_foreign_key :houses, :cities, column: "city_id"
@@ -261,7 +261,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
create_table(:schools)
create_table(:classes) do |t|
- t.column :school_id, :integer
+ t.column :school_id, :bigint
end
add_foreign_key :classes, :schools
end
diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb
index 85e09986ea..0f975026b8 100644
--- a/activerecord/test/cases/migration/index_test.rb
+++ b/activerecord/test/cases/migration/index_test.rb
@@ -143,7 +143,7 @@ module ActiveRecord
end
def test_add_index_attribute_length_limit
- connection.add_index :testings, [:foo, :bar], length: {foo: 10, bar: nil}
+ connection.add_index :testings, [:foo, :bar], length: { foo: 10, bar: nil }
assert connection.index_exists?(:testings, [:foo, :bar])
end
@@ -169,13 +169,13 @@ module ActiveRecord
connection.add_index("testings", ["last_name"], length: 10)
connection.remove_index("testings", "last_name")
- connection.add_index("testings", ["last_name"], length: {last_name: 10})
+ connection.add_index("testings", ["last_name"], length: { last_name: 10 })
connection.remove_index("testings", ["last_name"])
connection.add_index("testings", ["last_name", "first_name"], length: 10)
connection.remove_index("testings", ["last_name", "first_name"])
- connection.add_index("testings", ["last_name", "first_name"], length: {last_name: 10, first_name: 20})
+ connection.add_index("testings", ["last_name", "first_name"], length: { last_name: 10, first_name: 20 })
connection.remove_index("testings", ["last_name", "first_name"])
connection.add_index("testings", ["key"], name: "key_idx", unique: true)
@@ -186,11 +186,11 @@ module ActiveRecord
# Selected adapters support index sort order
if current_adapter?(:SQLite3Adapter, :Mysql2Adapter, :PostgreSQLAdapter)
- connection.add_index("testings", ["last_name"], order: {last_name: :desc})
+ connection.add_index("testings", ["last_name"], order: { last_name: :desc })
connection.remove_index("testings", ["last_name"])
- connection.add_index("testings", ["last_name", "first_name"], order: {last_name: :desc})
+ connection.add_index("testings", ["last_name", "first_name"], order: { last_name: :desc })
connection.remove_index("testings", ["last_name", "first_name"])
- connection.add_index("testings", ["last_name", "first_name"], order: {last_name: :desc, first_name: :asc})
+ connection.add_index("testings", ["last_name", "first_name"], order: { last_name: :desc, first_name: :asc })
connection.remove_index("testings", ["last_name", "first_name"])
connection.add_index("testings", ["last_name", "first_name"], order: :desc)
connection.remove_index("testings", ["last_name", "first_name"])
diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb
index 8436435170..4957ab8b3d 100644
--- a/activerecord/test/cases/migration/references_foreign_key_test.rb
+++ b/activerecord/test/cases/migration/references_foreign_key_test.rb
@@ -42,7 +42,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
test "options hash can be passed" do
@connection.change_table :testing_parents do |t|
- t.integer :other_id
+ t.bigint :other_id
t.index :other_id, unique: true
end
@connection.create_table :testings do |t|
@@ -59,7 +59,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
end
fks = @connection.foreign_keys("testings")
assert_equal([["testings", "testing_parents", "parent_id"]],
- fks.map {|fk| [fk.from_table, fk.to_table, fk.column] })
+ fks.map { |fk| [fk.from_table, fk.to_table, fk.column] })
end
test "foreign keys cannot be added to polymorphic relations when creating the table" do
@@ -92,7 +92,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
test "foreign keys accept options when changing the table" do
@connection.change_table :testing_parents do |t|
- t.integer :other_id
+ t.bigint :other_id
t.index :other_id, unique: true
end
@connection.create_table :testings
@@ -177,8 +177,8 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
test "multiple foreign keys can be added to the same table" do
@connection.create_table :testings do |t|
- t.integer :col_1
- t.integer :col_2
+ t.bigint :col_1
+ t.bigint :col_2
t.foreign_key :testing_parents, column: :col_1
t.foreign_key :testing_parents, column: :col_2
@@ -186,7 +186,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
fks = @connection.foreign_keys("testings")
- fk_definitions = fks.map {|fk| [fk.from_table, fk.to_table, fk.column] }
+ fk_definitions = fks.map { |fk| [fk.from_table, fk.to_table, fk.column] }
assert_equal([["testings", "testing_parents", "col_1"],
["testings", "testing_parents", "col_2"]], fk_definitions)
end
diff --git a/activerecord/test/cases/migration/references_index_test.rb b/activerecord/test/cases/migration/references_index_test.rb
index 0d0b0a193b..2866cabab6 100644
--- a/activerecord/test/cases/migration/references_index_test.rb
+++ b/activerecord/test/cases/migration/references_index_test.rb
@@ -41,8 +41,8 @@ module ActiveRecord
def test_creates_index_with_options
connection.create_table table_name do |t|
- t.references :foo, index: {name: :index_testings_on_yo_momma}
- t.references :bar, index: {unique: true}
+ t.references :foo, index: { name: :index_testings_on_yo_momma }
+ t.references :bar, index: { unique: true }
end
assert connection.index_exists?(table_name, :foo_id, name: :index_testings_on_yo_momma)
diff --git a/activerecord/test/cases/migration/references_statements_test.rb b/activerecord/test/cases/migration/references_statements_test.rb
index 8fbe60f24e..df15d7cb45 100644
--- a/activerecord/test/cases/migration/references_statements_test.rb
+++ b/activerecord/test/cases/migration/references_statements_test.rb
@@ -57,7 +57,7 @@ module ActiveRecord
def test_creates_named_unique_index
add_reference table_name, :tag, index: { name: "index_taggings_on_tag_id", unique: true }
- assert index_exists?(table_name, :tag_id, name: "index_taggings_on_tag_id", unique: true )
+ assert index_exists?(table_name, :tag_id, name: "index_taggings_on_tag_id", unique: true)
end
def test_creates_reference_id_with_specified_type
diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb
index 5474c9ce71..19588d28a2 100644
--- a/activerecord/test/cases/migration/rename_table_test.rb
+++ b/activerecord/test/cases/migration/rename_table_test.rb
@@ -15,7 +15,7 @@ module ActiveRecord
end
def teardown
- ActiveSupport::Deprecation.silence { rename_table :octopi, :test_models if connection.table_exists? :octopi }
+ rename_table :octopi, :test_models if connection.table_exists? :octopi
super
end
@@ -57,7 +57,7 @@ module ActiveRecord
assert_equal "http://www.foreverflying.com/octopus-black7.jpg", connection.select_value("SELECT url FROM octopi WHERE id=1")
index = connection.indexes(:octopi).first
- assert index.columns.include?("url")
+ assert_includes index.columns, "url"
assert_equal "index_octopi_on_url", index.name
end
@@ -82,7 +82,7 @@ module ActiveRecord
def test_renaming_table_doesnt_attempt_to_rename_non_existent_sequences
connection.create_table :cats, id: :uuid
assert_nothing_raised { rename_table :cats, :felines }
- ActiveSupport::Deprecation.silence { assert connection.table_exists? :felines }
+ assert connection.table_exists? :felines
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 5d96569d38..082cfd3242 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -388,7 +388,7 @@ class MigrationTest < ActiveRecord::TestCase
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
+ new_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
refute_equal current_env, new_env
@@ -413,7 +413,7 @@ class MigrationTest < ActiveRecord::TestCase
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
+ new_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
refute_equal current_env, new_env
@@ -429,14 +429,14 @@ class MigrationTest < ActiveRecord::TestCase
def test_internal_metadata_stores_environment_when_other_data_exists
ActiveRecord::InternalMetadata.delete_all
- ActiveRecord::InternalMetadata[:foo] = "bar"
+ ActiveRecord::InternalMetadata[:foo] = "bar"
current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
migrations_path = MIGRATIONS_ROOT + "/valid"
old_path = ActiveRecord::Migrator.migrations_paths
ActiveRecord::Migrator.migrations_paths = migrations_path
- current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
+ current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
ActiveRecord::Migrator.up(migrations_path)
assert_equal current_env, ActiveRecord::InternalMetadata[:environment]
assert_equal "bar", ActiveRecord::InternalMetadata[:foo]
@@ -551,6 +551,23 @@ class MigrationTest < ActiveRecord::TestCase
end
end
+ if current_adapter?(:SQLite3Adapter)
+ def test_allows_sqlite3_rollback_on_invalid_column_type
+ Person.connection.create_table :something, force: true do |t|
+ t.column :number, :integer
+ t.column :name, :string
+ t.column :foo, :bar
+ end
+ assert Person.connection.column_exists?(:something, :foo)
+ assert_nothing_raised { Person.connection.remove_column :something, :foo, :bar }
+ assert !Person.connection.column_exists?(:something, :foo)
+ assert Person.connection.column_exists?(:something, :name)
+ assert Person.connection.column_exists?(:something, :number)
+ ensure
+ Person.connection.drop_table :something, if_exists: true
+ end
+ end
+
if current_adapter? :OracleAdapter
def test_create_table_with_custom_sequence_name
# table name is 29 chars, the standard sequence name will
@@ -688,7 +705,7 @@ class MigrationTest < ActiveRecord::TestCase
end
end
- protected
+ private
# This is needed to isolate class_attribute assignments like `table_name_prefix`
# for each test case.
def new_isolated_reminder_class
@@ -759,7 +776,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
class BulkAlterTableMigrationsTest < ActiveRecord::TestCase
def setup
@connection = Person.connection
- @connection.create_table(:delete_me, force: true) {|t| }
+ @connection.create_table(:delete_me, force: true) { |t| }
Person.reset_column_information
Person.reset_sequence_name
end
@@ -780,7 +797,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
end
assert_equal 8, columns.size
- [:name, :qualification, :experience].each {|s| assert_equal :string, column(s).type }
+ [:name, :qualification, :experience].each { |s| assert_equal :string, column(s).type }
assert_equal "0", column(:age).default
end
@@ -789,7 +806,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
t.string :qualification, :experience
end
- [:qualification, :experience].each {|c| assert column(c) }
+ [:qualification, :experience].each { |c| assert column(c) }
assert_queries(1) do
with_bulk_change_table do |t|
@@ -798,7 +815,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
end
end
- [:qualification, :experience].each {|c| assert ! column(c) }
+ [:qualification, :experience].each { |c| assert ! column(c) }
assert column(:qualification_experience)
end
@@ -882,7 +899,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
end
def column(name)
- columns.detect {|c| c.name == name.to_s }
+ columns.detect { |c| c.name == name.to_s }
end
def columns
@@ -890,7 +907,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
end
def index(name)
- indexes.detect {|i| i.name == name.to_s }
+ indexes.detect { |i| i.name == name.to_s }
end
def indexes
diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb
index 1ba18bc9c2..bb9835394b 100644
--- a/activerecord/test/cases/migrator_test.rb
+++ b/activerecord/test/cases/migrator_test.rb
@@ -11,7 +11,7 @@ class MigratorTest < ActiveRecord::TestCase
def initialize(name = self.class.name, version = nil)
super
- @went_up = false
+ @went_up = false
@went_down = false
end
@@ -313,9 +313,9 @@ class MigratorTest < ActiveRecord::TestCase
_, migrator = migrator_class(3)
ActiveRecord::Base.connection.drop_table "schema_migrations", if_exists: true
- ActiveSupport::Deprecation.silence { assert_not ActiveRecord::Base.connection.table_exists?("schema_migrations") }
+ assert_not ActiveRecord::Base.connection.table_exists?("schema_migrations")
migrator.migrate("valid", 1)
- ActiveSupport::Deprecation.silence { assert ActiveRecord::Base.connection.table_exists?("schema_migrations") }
+ assert ActiveRecord::Base.connection.table_exists?("schema_migrations")
end
def test_migrator_forward
@@ -344,10 +344,10 @@ class MigratorTest < ActiveRecord::TestCase
_, migrator = migrator_class(3)
migrator.migrate("valid")
- assert_equal([1,2,3], ActiveRecord::Migrator.get_all_versions)
+ assert_equal([1, 2, 3], ActiveRecord::Migrator.get_all_versions)
migrator.rollback("valid")
- assert_equal([1,2], ActiveRecord::Migrator.get_all_versions)
+ assert_equal([1, 2], ActiveRecord::Migrator.get_all_versions)
migrator.rollback("valid")
assert_equal([1], ActiveRecord::Migrator.get_all_versions)
@@ -368,7 +368,7 @@ class MigratorTest < ActiveRecord::TestCase
def sensors(count)
calls = []
migrations = count.times.map { |i|
- m(nil, i + 1) { |c,migration|
+ m(nil, i + 1) { |c, migration|
calls << [c, migration.version]
}
}
diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb
index 1b7720e4f8..f8a7bab35f 100644
--- a/activerecord/test/cases/modules_test.rb
+++ b/activerecord/test/cases/modules_test.rb
@@ -73,7 +73,7 @@ class ModulesTest < ActiveRecord::TestCase
clients = []
assert_nothing_raised do
- clients << MyApplication::Business::Client.references(:accounts).merge!(includes: {firm: :account}, where: "accounts.id IS NOT NULL").find(3)
+ clients << MyApplication::Business::Client.references(:accounts).merge!(includes: { firm: :account }, where: "accounts.id IS NOT NULL").find(3)
clients << MyApplication::Business::Client.includes(firm: :account).find(3)
end
diff --git a/activerecord/test/cases/multiparameter_attributes_test.rb b/activerecord/test/cases/multiparameter_attributes_test.rb
index b2f76398df..ceb5724377 100644
--- a/activerecord/test/cases/multiparameter_attributes_test.rb
+++ b/activerecord/test/cases/multiparameter_attributes_test.rb
@@ -260,6 +260,13 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
topic.attributes = attributes
assert_equal Time.zone.local(2000, 1, 1, 16, 24, 0), topic.bonus_time
assert_not topic.bonus_time.utc?
+
+ attributes = {
+ "written_on(1i)" => "2000", "written_on(2i)" => "", "written_on(3i)" => "",
+ "written_on(4i)" => "", "written_on(5i)" => ""
+ }
+ topic.attributes = attributes
+ assert_nil topic.written_on
end
ensure
Topic.reset_column_information
@@ -280,14 +287,14 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
unless current_adapter? :OracleAdapter
def test_multiparameter_attributes_setting_time_attribute
- topic = Topic.new( "bonus_time(4i)"=> "01", "bonus_time(5i)" => "05" )
+ topic = Topic.new("bonus_time(4i)" => "01", "bonus_time(5i)" => "05")
assert_equal 1, topic.bonus_time.hour
assert_equal 5, topic.bonus_time.min
end
end
def test_multiparameter_attributes_setting_date_attribute
- topic = Topic.new( "written_on(1i)" => "1952", "written_on(2i)" => "3", "written_on(3i)" => "11" )
+ topic = Topic.new("written_on(1i)" => "1952", "written_on(2i)" => "3", "written_on(3i)" => "11")
assert_equal 1952, topic.written_on.year
assert_equal 3, topic.written_on.month
assert_equal 11, topic.written_on.day
@@ -308,8 +315,8 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
end
def test_multiparameter_attributes_setting_time_but_not_date_on_date_field
- assert_raise( ActiveRecord::MultiparameterAssignmentErrors ) do
- Topic.new( "written_on(4i)" => "13", "written_on(5i)" => "55" )
+ assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ Topic.new("written_on(4i)" => "13", "written_on(5i)" => "55")
end
end
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 788277faea..b87419d203 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -31,7 +31,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
def test_should_not_build_a_new_record_using_reject_all_even_if_destroy_is_given
pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?")
- pirate.birds_with_reject_all_blank_attributes = [{name: "", color: "", _destroy: "0"}]
+ pirate.birds_with_reject_all_blank_attributes = [{ name: "", color: "", _destroy: "0" }]
pirate.save!
assert pirate.birds_with_reject_all_blank.empty?
@@ -39,7 +39,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
def test_should_not_build_a_new_record_if_reject_all_blank_returns_false
pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?")
- pirate.birds_with_reject_all_blank_attributes = [{name: "", color: ""}]
+ pirate.birds_with_reject_all_blank_attributes = [{ name: "", color: "" }]
pirate.save!
assert pirate.birds_with_reject_all_blank.empty?
@@ -47,7 +47,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
def test_should_build_a_new_record_if_reject_all_blank_does_not_return_false
pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?")
- pirate.birds_with_reject_all_blank_attributes = [{name: "Tweetie", color: ""}]
+ pirate.birds_with_reject_all_blank_attributes = [{ name: "Tweetie", color: "" }]
pirate.save!
assert_equal 1, pirate.birds_with_reject_all_blank.count
@@ -108,7 +108,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
end
def test_reject_if_with_indifferent_keys
- Pirate.accepts_nested_attributes_for :ship, reject_if: proc {|attributes| attributes[:name].blank? }
+ Pirate.accepts_nested_attributes_for :ship, reject_if: proc { |attributes| attributes[:name].blank? }
pirate = Pirate.new(catchphrase: "Stop wastin' me time")
pirate.ship_attributes = { name: "Hello Pearl" }
@@ -116,7 +116,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
end
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.accepts_nested_attributes_for :ship, reject_if: proc { |attributes| true }
pirate = Pirate.new(catchphrase: "Stop wastin' me time")
ship = pirate.create_ship(name: "s1")
pirate.update(ship_attributes: { name: "s2", id: ship.id })
@@ -138,7 +138,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
end
def test_reject_if_with_a_proc_which_returns_true_always_for_has_many
- Man.accepts_nested_attributes_for :interests, reject_if: proc {|attributes| true }
+ Man.accepts_nested_attributes_for :interests, reject_if: proc { |attributes| true }
man = Man.create(name: "John")
interest = man.interests.create(topic: "photography")
man.update(interests_attributes: { topic: "gardening", id: interest.id })
@@ -146,7 +146,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
end
def test_destroy_works_independent_of_reject_if
- Man.accepts_nested_attributes_for :interests, reject_if: proc {|attributes| true }, allow_destroy: true
+ Man.accepts_nested_attributes_for :interests, reject_if: proc { |attributes| true }, allow_destroy: true
man = Man.create(name: "Jon")
interest = man.interests.create(topic: "the ladies")
man.update(interests_attributes: { _destroy: "1", id: interest.id })
@@ -170,13 +170,13 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
Man.accepts_nested_attributes_for(:interests)
man = Man.create(name: "John")
interest = man.interests.create(topic: "photography")
- man.update(interests_attributes: {topic: "gardening", id: interest.id})
+ man.update(interests_attributes: { topic: "gardening", id: interest.id })
assert_equal "gardening", interest.reload.topic
end
def test_reject_if_with_blank_nested_attributes_id
# When using a select list to choose an existing 'ship' id, with include_blank: true
- Pirate.accepts_nested_attributes_for :ship, reject_if: proc {|attributes| attributes[:id].blank? }
+ Pirate.accepts_nested_attributes_for :ship, reject_if: proc { |attributes| attributes[:id].blank? }
pirate = Pirate.new(catchphrase: "Stop wastin' me time")
pirate.ship_attributes = { id: "" }
@@ -188,7 +188,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
man = Man.create(name: "John")
interest = man.interests.create topic: "gardening"
man = Man.find man.id
- man.interests_attributes = [{id: interest.id, topic: "gardening"}]
+ man.interests_attributes = [{ id: interest.id, topic: "gardening" }]
assert_equal man.interests.first.topic, man.interests[0].topic
end
@@ -225,7 +225,7 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
def test_should_raise_argument_error_if_trying_to_build_polymorphic_belongs_to
exception = assert_raise ArgumentError do
- Treasure.new(name: "pearl", looter_attributes: {catchphrase: "Arrr"})
+ Treasure.new(name: "pearl", looter_attributes: { catchphrase: "Arrr" })
end
assert_equal "Cannot build association `looter'. Are you trying to build a polymorphic one-to-one association?", exception.message
end
@@ -700,7 +700,7 @@ module NestedAttributesOnACollectionAssociationTests
def test_should_automatically_build_new_associated_models_for_each_entry_in_a_hash_where_the_id_is_missing
@pirate.send(@association_name).destroy_all
@pirate.reload.attributes = {
- association_getter => { "foo" => { name: "Grace OMalley" }, "bar" => { name: "Privateers Greed" }}
+ association_getter => { "foo" => { name: "Grace OMalley" }, "bar" => { name: "Privateers Greed" } }
}
assert !@pirate.send(@association_name).first.persisted?
@@ -757,7 +757,7 @@ module NestedAttributesOnACollectionAssociationTests
def test_should_work_with_update_as_well
@pirate.update(catchphrase: "Arr",
- association_getter => { "foo" => { id: @child_1.id, name: "Grace OMalley" }})
+ association_getter => { "foo" => { id: @child_1.id, name: "Grace OMalley" } })
assert_equal "Grace OMalley", @child_1.reload.name
end
@@ -813,7 +813,7 @@ module NestedAttributesOnACollectionAssociationTests
assert_difference "Man.count" do
assert_difference "Interest.count", 2 do
man = Man.create!(name: "John",
- interests_attributes: [{topic: "Cars"}, {topic: "Sports"}])
+ interests_attributes: [{ topic: "Cars" }, { topic: "Sports" }])
assert_equal 2, man.interests.count
end
end
@@ -911,7 +911,7 @@ module NestedAttributesLimitTests
assert_raises(ActiveRecord::NestedAttributes::TooManyRecords) do
@pirate.attributes = { parrots_attributes: { "foo" => { name: "Lovely Day" },
"bar" => { name: "Blown Away" },
- "car" => { name: "The Happening" }} }
+ "car" => { name: "The Happening" } } }
end
end
end
@@ -971,10 +971,10 @@ class TestNestedAttributesWithNonStandardPrimaryKeys < ActiveRecord::TestCase
def test_attr_accessor_of_child_should_be_value_provided_during_update
@owner = owners(:ashley)
@pet1 = pets(:chew)
- attributes = {pets_attributes: { "1"=> { id: @pet1.id,
+ attributes = { pets_attributes: { "1" => { id: @pet1.id,
name: "Foo2",
current_user: "John",
- _destroy: true }}}
+ _destroy: true } } }
@owner.update(attributes)
assert_equal "John", Pet.after_destroy_output
end
@@ -997,18 +997,18 @@ class TestHasOneAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveRe
end
test "when great-grandchild changed via attributes, saving parent should save great-grandchild" do
- @pirate.attributes = {ship_attributes: {id: @ship.id, parts_attributes: [{id: @part.id, trinkets_attributes: [{id: @trinket.id, name: "changed"}]}]}}
+ @pirate.attributes = { ship_attributes: { id: @ship.id, parts_attributes: [{ id: @part.id, trinkets_attributes: [{ id: @trinket.id, name: "changed" }] }] } }
@pirate.save
assert_equal "changed", @trinket.reload.name
end
test "when great-grandchild marked_for_destruction via attributes, saving parent should destroy great-grandchild" do
- @pirate.attributes = {ship_attributes: {id: @ship.id, parts_attributes: [{id: @part.id, trinkets_attributes: [{id: @trinket.id, _destroy: true}]}]}}
+ @pirate.attributes = { ship_attributes: { id: @ship.id, parts_attributes: [{ id: @part.id, trinkets_attributes: [{ id: @trinket.id, _destroy: true }] }] } }
assert_difference("@part.trinkets.count", -1) { @pirate.save }
end
test "when great-grandchild added via attributes, saving parent should create great-grandchild" do
- @pirate.attributes = {ship_attributes: {id: @ship.id, parts_attributes: [{id: @part.id, trinkets_attributes: [{name: "created"}]}]}}
+ @pirate.attributes = { ship_attributes: { id: @ship.id, parts_attributes: [{ id: @part.id, trinkets_attributes: [{ name: "created" }] }] } }
assert_difference("@part.trinkets.count", 1) { @pirate.save }
end
@@ -1030,13 +1030,13 @@ class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveR
end
test "if association is not loaded and association record is saved and then in memory record attributes should be saved" do
- @ship.parts_attributes=[{id: @part.id,name: "Deck"}]
+ @ship.parts_attributes = [{ id: @part.id, name: "Deck" }]
assert_equal 1, @ship.association(:parts).target.size
assert_equal "Deck", @ship.parts[0].name
end
test "if association is not loaded and child doesn't change and I am saving a grandchild then in memory record should be used" do
- @ship.parts_attributes=[{id: @part.id,trinkets_attributes: [{id: @trinket.id, name: "Ruby"}]}]
+ @ship.parts_attributes = [{ id: @part.id, trinkets_attributes: [{ id: @trinket.id, name: "Ruby" }] }]
assert_equal 1, @ship.association(:parts).target.size
assert_equal "Mast", @ship.parts[0].name
assert_no_difference("@ship.parts[0].association(:trinkets).target.size") do
@@ -1054,18 +1054,18 @@ class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveR
end
test "when grandchild changed via attributes, saving parent should save grandchild" do
- @ship.attributes = {parts_attributes: [{id: @part.id, trinkets_attributes: [{id: @trinket.id, name: "changed"}]}]}
+ @ship.attributes = { parts_attributes: [{ id: @part.id, trinkets_attributes: [{ id: @trinket.id, name: "changed" }] }] }
@ship.save
assert_equal "changed", @trinket.reload.name
end
test "when grandchild marked_for_destruction via attributes, saving parent should destroy grandchild" do
- @ship.attributes = {parts_attributes: [{id: @part.id, trinkets_attributes: [{id: @trinket.id, _destroy: true}]}]}
+ @ship.attributes = { parts_attributes: [{ id: @part.id, trinkets_attributes: [{ id: @trinket.id, _destroy: true }] }] }
assert_difference("@part.trinkets.count", -1) { @ship.save }
end
test "when grandchild added via attributes, saving parent should create grandchild" do
- @ship.attributes = {parts_attributes: [{id: @part.id, trinkets_attributes: [{name: "created"}]}]}
+ @ship.attributes = { parts_attributes: [{ id: @part.id, trinkets_attributes: [{ name: "created" }] }] }
assert_difference("@part.trinkets.count", 1) { @ship.save }
end
diff --git a/activerecord/test/cases/nested_attributes_with_callbacks_test.rb b/activerecord/test/cases/nested_attributes_with_callbacks_test.rb
index d606a6c12d..350a966d40 100644
--- a/activerecord/test/cases/nested_attributes_with_callbacks_test.rb
+++ b/activerecord/test/cases/nested_attributes_with_callbacks_test.rb
@@ -5,13 +5,13 @@ require "models/bird"
class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase
Pirate.has_many(:birds_with_add_load,
class_name: "Bird",
- before_add: proc { |p,b|
+ before_add: proc { |p, b|
@@add_callback_called << b
p.birds_with_add_load.to_a
})
Pirate.has_many(:birds_with_add,
class_name: "Bird",
- before_add: proc { |p,b| @@add_callback_called << b })
+ before_add: proc { |p, b| @@add_callback_called << b })
Pirate.accepts_nested_attributes_for(:birds_with_add_load,
:birds_with_add,
@@ -21,7 +21,7 @@ class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase
@@add_callback_called = []
@pirate = Pirate.new.tap do |pirate|
pirate.catchphrase = "Don't call me!"
- pirate.birds_attributes = [{name: "Bird1"},{name: "Bird2"}]
+ pirate.birds_attributes = [{ name: "Bird1" }, { name: "Bird2" }]
pirate.save!
end
@birds = @pirate.birds.to_a
@@ -37,7 +37,7 @@ class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase
def existing_birds_attributes
@birds.map do |bird|
- bird.attributes.slice("id","name")
+ bird.attributes.slice("id", "name")
end
end
@@ -46,17 +46,17 @@ class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase
end
def new_bird_attributes
- [{"name" => "New Bird"}]
+ [{ "name" => "New Bird" }]
end
def destroy_bird_attributes
- [{"id" => bird_to_destroy.id.to_s, "_destroy" => true}]
+ [{ "id" => bird_to_destroy.id.to_s, "_destroy" => true }]
end
def update_new_and_destroy_bird_attributes
- [{"id" => @birds[0].id.to_s, "name" => "New Name"},
- {"name" => "New Bird"},
- {"id" => bird_to_destroy.id.to_s, "_destroy" => true}]
+ [{ "id" => @birds[0].id.to_s, "name" => "New Name" },
+ { "name" => "New Bird" },
+ { "id" => bird_to_destroy.id.to_s, "_destroy" => true }]
end
# Characterizing when :before_add callback is called
@@ -136,9 +136,9 @@ class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase
def assert_assignment_affects_records_in_target(association_name)
association = @pirate.send(association_name)
- assert association.detect {|b| b == bird_to_update }.name_changed?,
+ assert association.detect { |b| b == bird_to_update }.name_changed?,
"Update record not updated"
- assert association.detect {|b| b == bird_to_destroy }.marked_for_destruction?,
+ assert association.detect { |b| b == bird_to_destroy }.marked_for_destruction?,
"Destroy record not marked for destruction"
end
end
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index e293770725..3f1da82cb4 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -83,13 +83,21 @@ class PersistenceTest < ActiveRecord::TestCase
end
def test_delete_all_with_joins_and_where_part_is_hash
- where_args = {toys: {name: "Bone"}}
+ where_args = { toys: { name: "Bone" } }
count = Pet.joins(:toys).where(where_args).count
assert_equal count, 1
assert_equal count, Pet.joins(:toys).where(where_args).delete_all
end
+ def test_delete_all_with_left_joins
+ where_args = { toys: { name: "Bone" } }
+ count = Pet.left_joins(:toys).where(where_args).count
+
+ assert_equal count, 1
+ assert_equal count, Pet.left_joins(:toys).where(where_args).delete_all
+ end
+
def test_delete_all_with_joins_and_where_part_is_not_hash
where_args = ["toys.name = ?", "Bone"]
count = Pet.joins(:toys).where(where_args).count
@@ -391,14 +399,14 @@ class PersistenceTest < ActiveRecord::TestCase
end
topic = klass.create(title: "Another New Topic")
assert_queries(0) do
- topic.update_attribute(:title, "Another New Topic")
+ assert topic.update_attribute(:title, "Another New Topic")
end
end
def test_update_does_not_run_sql_if_record_has_not_changed
topic = Topic.create(title: "Another New Topic")
- assert_queries(0) { topic.update(title: "Another New Topic") }
- assert_queries(0) { topic.update_attributes(title: "Another New Topic") }
+ assert_queries(0) { assert topic.update(title: "Another New Topic") }
+ assert_queries(0) { assert topic.update_attributes(title: "Another New Topic") }
end
def test_delete
@@ -453,6 +461,20 @@ class PersistenceTest < ActiveRecord::TestCase
assert_nil Topic.find(2).last_read
end
+ def test_update_all_with_joins
+ where_args = { toys: { name: "Bone" } }
+ count = Pet.left_joins(:toys).where(where_args).count
+
+ assert_equal count, Pet.joins(:toys).where(where_args).update_all(name: "Bob")
+ end
+
+ def test_update_all_with_left_joins
+ where_args = { toys: { name: "Bone" } }
+ count = Pet.left_joins(:toys).where(where_args).count
+
+ assert_equal count, Pet.left_joins(:toys).where(where_args).update_all(name: "Bob")
+ end
+
def test_update_all_with_non_standard_table_name
assert_equal 1, WarehouseThing.where(id: 1).update_all(["value = ?", 0])
assert_equal 0, WarehouseThing.find(1).value
@@ -967,7 +989,7 @@ class PersistenceTest < ActiveRecord::TestCase
self.table_name = :widgets
end
- instance = widget.create!(
+ instance = widget.create!(
name: "Bob",
created_at: 1.day.ago,
updated_at: 1.day.ago)
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 31d612abd1..1d72899102 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -120,7 +120,7 @@ class PrimaryKeysTest < ActiveRecord::TestCase
assert_nothing_raised { MixedCaseMonkey.find(1) }
end
def test_find_with_multiple_ids_should_quote_pkey
- assert_nothing_raised { MixedCaseMonkey.find([1,2]) }
+ assert_nothing_raised { MixedCaseMonkey.find([1, 2]) }
end
def test_instance_update_should_quote_pkey
assert_nothing_raised { MixedCaseMonkey.find(1).save }
@@ -129,12 +129,6 @@ class PrimaryKeysTest < ActiveRecord::TestCase
assert_nothing_raised { MixedCaseMonkey.find(1).destroy }
end
- def test_supports_primary_key
- assert_nothing_raised do
- ActiveRecord::Base.connection.supports_primary_key?
- end
- end
-
if ActiveRecord::Base.connection.supports_primary_key?
def test_primary_key_returns_value_if_it_exists
klass = Class.new(ActiveRecord::Base) do
@@ -154,7 +148,7 @@ class PrimaryKeysTest < ActiveRecord::TestCase
end
def test_quoted_primary_key_after_set_primary_key
- k = Class.new( ActiveRecord::Base )
+ k = Class.new(ActiveRecord::Base)
assert_equal k.connection.quote_column_name("id"), k.quoted_primary_key
k.primary_key = "foo"
assert_equal k.connection.quote_column_name("foo"), k.quoted_primary_key
@@ -216,6 +210,43 @@ class PrimaryKeyWithNoConnectionTest < ActiveRecord::TestCase
end
end
+class PrimaryKeyWithAutoIncrementTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ class AutoIncrement < ActiveRecord::Base
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def teardown
+ @connection.drop_table(:auto_increments, if_exists: true)
+ end
+
+ def test_primary_key_with_auto_increment
+ @connection.create_table(:auto_increments, id: :integer, auto_increment: true, force: true)
+ assert_auto_incremented
+ end
+
+ def test_primary_key_with_auto_increment_and_bigint
+ @connection.create_table(:auto_increments, id: :bigint, auto_increment: true, force: true)
+ assert_auto_incremented
+ end
+
+ private
+ def assert_auto_incremented
+ record1 = AutoIncrement.create!
+ assert_not_nil record1.id
+
+ record1.destroy
+
+ record2 = AutoIncrement.create!
+ assert_not_nil record2.id
+ assert_operator record2.id, :>, record1.id
+ end
+end
+
class PrimaryKeyAnyTypeTest < ActiveRecord::TestCase
include SchemaDumpingHelper
@@ -289,85 +320,69 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase
end
if current_adapter?(:Mysql2Adapter)
- class PrimaryKeyBigintNilDefaultTest < ActiveRecord::TestCase
+ class PrimaryKeyIntegerNilDefaultTest < ActiveRecord::TestCase
include SchemaDumpingHelper
self.use_transactional_tests = false
def setup
@connection = ActiveRecord::Base.connection
- @connection.create_table(:bigint_defaults, id: :bigint, default: nil, force: true)
+ @connection.create_table(:int_defaults, id: :integer, default: nil, force: true)
end
def teardown
- @connection.drop_table :bigint_defaults, if_exists: true
+ @connection.drop_table :int_defaults, if_exists: true
end
- test "primary key with bigint allows default override via nil" do
- column = @connection.columns(:bigint_defaults).find { |c| c.name == "id" }
- assert column.bigint?
+ test "primary key with integer allows default override via nil" do
+ column = @connection.columns(:int_defaults).find { |c| c.name == "id" }
+ assert_equal :integer, column.type
assert_not column.auto_increment?
end
- test "schema dump primary key with bigint default nil" do
- schema = dump_table_schema "bigint_defaults"
- assert_match %r{create_table "bigint_defaults", id: :bigint, default: nil}, schema
+ test "schema dump primary key with int default nil" do
+ schema = dump_table_schema "int_defaults"
+ assert_match %r{create_table "int_defaults", id: :integer, default: nil}, schema
end
end
end
-if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter)
- class PrimaryKeyBigSerialTest < ActiveRecord::TestCase
- include SchemaDumpingHelper
+class PrimaryKeyIntegerTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
- self.use_transactional_tests = false
+ self.use_transactional_tests = false
- class Widget < ActiveRecord::Base
- end
+ class Widget < ActiveRecord::Base
+ end
- setup do
- @connection = ActiveRecord::Base.connection
- if current_adapter?(:PostgreSQLAdapter)
- @connection.create_table(:widgets, id: :bigserial, force: true)
- else
- @connection.create_table(:widgets, id: :bigint, force: true)
- end
- end
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table(:widgets, force: true)
+ end
- teardown do
- @connection.drop_table :widgets, if_exists: true
- Widget.reset_column_information
- end
+ teardown do
+ @connection.drop_table :widgets, if_exists: true
+ Widget.reset_column_information
+ end
- test "primary key column type with bigserial" do
- column_type = Widget.type_for_attribute(Widget.primary_key)
- assert_equal :integer, column_type.type
- assert_equal 8, column_type.limit
+ if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter)
+ test "schema dump primary key with bigserial" do
+ schema = dump_table_schema "widgets"
+ assert_match %r{create_table "widgets", force: :cascade}, schema
end
+ end
- test "primary key with bigserial are automatically numbered" do
- widget = Widget.create!
- assert_not_nil widget.id
- end
+ test "primary key column type" do
+ column_type = Widget.type_for_attribute(Widget.primary_key)
+ assert_equal :integer, column_type.type
- test "schema dump primary key with bigserial" do
- schema = dump_table_schema "widgets"
- if current_adapter?(:PostgreSQLAdapter)
- assert_match %r{create_table "widgets", id: :bigserial, force: :cascade}, schema
- else
- assert_match %r{create_table "widgets", id: :bigint, force: :cascade}, schema
- end
+ if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter)
+ assert_equal 8, column_type.limit
end
if current_adapter?(:Mysql2Adapter)
- test "primary key column type with options" do
- @connection.create_table(:widgets, id: :primary_key, limit: 8, unsigned: true, force: true)
- column = @connection.columns(:widgets).find { |c| c.name == "id" }
- assert column.auto_increment?
- assert_equal :integer, column.type
- assert_equal 8, column.limit
- assert column.unsigned?
- end
+ column = @connection.columns(:widgets).find { |c| c.name == "id" }
+ assert column.auto_increment?
end
end
end
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index d99e8a1dc6..4a49bfe9b1 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -10,13 +10,34 @@ class QueryCacheTest < ActiveRecord::TestCase
fixtures :tasks, :topics, :categories, :posts, :categories_posts
- teardown do
+ class ShouldNotHaveExceptionsLogger < ActiveRecord::LogSubscriber
+ attr_reader :logger
+
+ def initialize
+ super
+ @logger = ::Logger.new File::NULL
+ @exception = false
+ end
+
+ def exception?
+ @exception
+ end
+
+ def sql(event)
+ super
+ rescue
+ @exception = true
+ end
+ end
+
+ def teardown
Task.connection.clear_query_cache
ActiveRecord::Base.connection.disable_query_cache!
+ super
end
def test_exceptional_middleware_clears_and_disables_cache_on_error
- assert !ActiveRecord::Base.connection.query_cache_enabled, "cache off"
+ assert_cache :off
mw = middleware { |env|
Task.find 1
@@ -26,19 +47,66 @@ class QueryCacheTest < ActiveRecord::TestCase
}
assert_raises(RuntimeError) { mw.call({}) }
- assert_equal 0, ActiveRecord::Base.connection.query_cache.length
- assert !ActiveRecord::Base.connection.query_cache_enabled, "cache off"
+ assert_cache :off
end
- def test_exceptional_middleware_leaves_enabled_cache_alone
- ActiveRecord::Base.connection.enable_query_cache!
+ def test_query_cache_across_threads
+ ActiveRecord::Base.connection_pool.connections.each do |conn|
+ assert_cache :off, conn
+ end
- mw = middleware { |env|
- raise "lol borked"
- }
- assert_raises(RuntimeError) { mw.call({}) }
+ assert !ActiveRecord::Base.connection.nil?
+ assert_cache :off
+
+ middleware {
+ assert_cache :clean
+
+ Task.find 1
+ assert_cache :dirty
+
+ thread_1_connection = ActiveRecord::Base.connection
+ ActiveRecord::Base.clear_active_connections!
+ assert_cache :off, thread_1_connection
- assert ActiveRecord::Base.connection.query_cache_enabled, "cache on"
+ started = Concurrent::Event.new
+ checked = Concurrent::Event.new
+
+ thread_2_connection = nil
+ thread = Thread.new {
+ thread_2_connection = ActiveRecord::Base.connection
+
+ assert_equal thread_2_connection, thread_1_connection
+ assert_cache :off
+
+ middleware {
+ assert_cache :clean
+
+ Task.find 1
+ assert_cache :dirty
+
+ started.set
+ checked.wait
+
+ ActiveRecord::Base.clear_active_connections!
+ }.call({})
+ }
+
+ started.wait
+
+ thread_1_connection = ActiveRecord::Base.connection
+ assert_not_equal thread_1_connection, thread_2_connection
+ assert_cache :dirty, thread_2_connection
+ checked.set
+ thread.join
+
+ assert_cache :off, thread_2_connection
+ }.call({})
+
+ ActiveRecord::Base.connection_pool.connections.each do |conn|
+ assert_cache :off, conn
+ end
+ ensure
+ ActiveRecord::Base.clear_all_connections!
end
def test_middleware_delegates
@@ -62,10 +130,10 @@ class QueryCacheTest < ActiveRecord::TestCase
end
def test_cache_enabled_during_call
- assert !ActiveRecord::Base.connection.query_cache_enabled, "cache off"
+ assert_cache :off
mw = middleware { |env|
- assert ActiveRecord::Base.connection.query_cache_enabled, "cache on"
+ assert_cache :clean
[200, {}, nil]
}
mw.call({})
@@ -121,6 +189,33 @@ class QueryCacheTest < ActiveRecord::TestCase
end
end
+ def test_cache_does_not_raise_exceptions
+ logger = ShouldNotHaveExceptionsLogger.new
+ subscriber = ActiveSupport::Notifications.subscribe "sql.active_record", logger
+
+ ActiveRecord::Base.cache do
+ assert_queries(1) { Task.find(1); Task.find(1) }
+ end
+
+ assert_not_predicate logger, :exception?
+ ensure
+ ActiveSupport::Notifications.unsubscribe subscriber
+ end
+
+ def test_query_cache_does_not_allow_sql_key_mutation
+ subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |_, _, _, _, payload|
+ payload[:sql].downcase!
+ end
+
+ assert_raises RuntimeError do
+ ActiveRecord::Base.cache do
+ assert_queries(1) { Task.find(1); Task.find(1) }
+ end
+ end
+ ensure
+ ActiveSupport::Notifications.unsubscribe subscriber
+ end
+
def test_cache_is_flat
Task.cache do
assert_queries(1) { Topic.find(1); Topic.find(1); }
@@ -138,7 +233,7 @@ class QueryCacheTest < ActiveRecord::TestCase
assert_kind_of Numeric, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
elsif current_adapter?(:SQLite3Adapter, :Mysql2Adapter, :PostgreSQLAdapter)
# Future versions of the sqlite3 adapter will return numeric
- assert_instance_of Fixnum, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
+ assert_instance_of 0.class, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
else
assert_instance_of String, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
end
@@ -212,12 +307,82 @@ class QueryCacheTest < ActiveRecord::TestCase
end
end
+ def test_query_cached_even_when_types_are_reset
+ Task.cache do
+ # Warm the cache
+ Task.find(1)
+
+ Task.connection.type_map.clear
+
+ # Preload the type cache again (so we don't have those queries issued during our assertions)
+ Task.connection.send(:initialize_type_map, Task.connection.type_map)
+
+ # Clear places where type information is cached
+ Task.reset_column_information
+ Task.initialize_find_by_cache
+
+ assert_queries(0) do
+ Task.find(1)
+ end
+ end
+ end
+
+ def test_query_cache_does_not_establish_connection_if_unconnected
+ ActiveRecord::Base.clear_active_connections!
+ refute ActiveRecord::Base.connection_handler.active_connections? # sanity check
+
+ middleware {
+ refute ActiveRecord::Base.connection_handler.active_connections?, "QueryCache forced ActiveRecord::Base to establish a connection in setup"
+ }.call({})
+
+ refute ActiveRecord::Base.connection_handler.active_connections?, "QueryCache forced ActiveRecord::Base to establish a connection in cleanup"
+ end
+
+ def test_query_cache_is_enabled_on_connections_established_after_middleware_runs
+ ActiveRecord::Base.clear_active_connections!
+ refute ActiveRecord::Base.connection_handler.active_connections? # sanity check
+
+ middleware {
+ assert ActiveRecord::Base.connection.query_cache_enabled, "QueryCache did not get lazily enabled"
+ }.call({})
+ end
+
+ def test_query_caching_is_local_to_the_current_thread
+ ActiveRecord::Base.clear_active_connections!
+
+ middleware {
+ assert ActiveRecord::Base.connection_pool.query_cache_enabled
+ assert ActiveRecord::Base.connection.query_cache_enabled
+
+ Thread.new {
+ refute ActiveRecord::Base.connection_pool.query_cache_enabled
+ refute ActiveRecord::Base.connection.query_cache_enabled
+ }.join
+ }.call({})
+ end
+
private
def middleware(&app)
executor = Class.new(ActiveSupport::Executor)
ActiveRecord::QueryCache.install_executor_hooks executor
lambda { |env| executor.wrap { app.call(env) } }
end
+
+ def assert_cache(state, connection = ActiveRecord::Base.connection)
+ case state
+ when :off
+ assert !connection.query_cache_enabled, "cache should be off"
+ assert connection.query_cache.empty?, "cache should be empty"
+ when :clean
+ assert connection.query_cache_enabled, "cache should be on"
+ assert connection.query_cache.empty?, "cache should be empty"
+ when :dirty
+ assert connection.query_cache_enabled, "cache should be on"
+ assert !connection.query_cache.empty?, "cache should be dirty"
+ else
+ raise "unknown state"
+ end
+ end
end
class QueryCacheExpiryTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb
index 1c276aa765..5ff5e3c735 100644
--- a/activerecord/test/cases/quoting_test.rb
+++ b/activerecord/test/cases/quoting_test.rb
@@ -82,67 +82,67 @@ module ActiveRecord
end
def test_quote_with_quoted_id
- assert_equal 1, @quoter.quote(Struct.new(:quoted_id).new(1), nil)
+ assert_equal 1, @quoter.quote(Struct.new(:quoted_id).new(1))
end
def test_quote_nil
- assert_equal "NULL", @quoter.quote(nil, nil)
+ assert_equal "NULL", @quoter.quote(nil)
end
def test_quote_true
- assert_equal @quoter.quoted_true, @quoter.quote(true, nil)
+ assert_equal @quoter.quoted_true, @quoter.quote(true)
end
def test_quote_false
- assert_equal @quoter.quoted_false, @quoter.quote(false, nil)
+ assert_equal @quoter.quoted_false, @quoter.quote(false)
end
def test_quote_float
float = 1.2
- assert_equal float.to_s, @quoter.quote(float, nil)
+ assert_equal float.to_s, @quoter.quote(float)
end
def test_quote_integer
integer = 1
- assert_equal integer.to_s, @quoter.quote(integer, nil)
+ assert_equal integer.to_s, @quoter.quote(integer)
end
def test_quote_bignum
bignum = 1 << 100
- assert_equal bignum.to_s, @quoter.quote(bignum, nil)
+ assert_equal bignum.to_s, @quoter.quote(bignum)
end
def test_quote_bigdecimal
bigdec = BigDecimal.new((1 << 100).to_s)
- assert_equal bigdec.to_s("F"), @quoter.quote(bigdec, nil)
+ assert_equal bigdec.to_s("F"), @quoter.quote(bigdec)
end
def test_dates_and_times
@quoter.extend(Module.new { def quoted_date(value) "lol" end })
- assert_equal "'lol'", @quoter.quote(Date.today, nil)
- assert_equal "'lol'", @quoter.quote(Time.now, nil)
- assert_equal "'lol'", @quoter.quote(DateTime.now, nil)
+ assert_equal "'lol'", @quoter.quote(Date.today)
+ assert_equal "'lol'", @quoter.quote(Time.now)
+ assert_equal "'lol'", @quoter.quote(DateTime.now)
+ end
+
+ def test_quoting_classes
+ assert_equal "'Object'", @quoter.quote(Object)
end
def test_crazy_object
crazy = Object.new
e = assert_raises(TypeError) do
- @quoter.quote(crazy, nil)
+ @quoter.quote(crazy)
end
assert_equal "can't quote Object", e.message
end
def test_quote_string_no_column
- assert_equal "'lo\\\\l'", @quoter.quote('lo\l', nil)
+ assert_equal "'lo\\\\l'", @quoter.quote('lo\l')
end
def test_quote_as_mb_chars_no_column
string = ActiveSupport::Multibyte::Chars.new('lo\l')
- assert_equal "'lo\\\\l'", @quoter.quote(string, nil)
- end
-
- def test_string_with_crazy_column
- assert_equal "'lo\\\\l'", @quoter.quote('lo\l')
+ assert_equal "'lo\\\\l'", @quoter.quote(string)
end
def test_quote_duration
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index d71b30af69..0ef51272b9 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -86,8 +86,8 @@ class ReflectionTest < ActiveRecord::TestCase
column = @first.column_for_attribute("attribute_that_doesnt_exist")
assert_instance_of ActiveRecord::ConnectionAdapters::NullColumn, column
assert_equal "attribute_that_doesnt_exist", column.name
- assert_equal nil, column.sql_type
- assert_equal nil, column.type
+ assert_nil column.sql_type
+ assert_nil column.type
end
def test_non_existent_types_are_identity_types
@@ -100,7 +100,13 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_reflection_klass_for_nested_class_name
- reflection = ActiveRecord::Reflection.create(:has_many, nil, nil, { class_name: "MyApplication::Business::Company" }, ActiveRecord::Base)
+ reflection = ActiveRecord::Reflection.create(
+ :has_many,
+ nil,
+ nil,
+ { class_name: "MyApplication::Business::Company" },
+ Customer
+ )
assert_nothing_raised do
assert_equal MyApplication::Business::Company, reflection.klass
end
@@ -124,12 +130,12 @@ class ReflectionTest < ActiveRecord::TestCase
)
reflection_for_gps_location = AggregateReflection.new(
- :gps_location, nil, { }, Customer
+ :gps_location, nil, {}, Customer
)
- assert Customer.reflect_on_all_aggregations.include?(reflection_for_gps_location)
- assert Customer.reflect_on_all_aggregations.include?(reflection_for_balance)
- assert Customer.reflect_on_all_aggregations.include?(reflection_for_address)
+ assert_includes Customer.reflect_on_all_aggregations, reflection_for_gps_location
+ assert_includes Customer.reflect_on_all_aggregations, reflection_for_balance
+ assert_includes Customer.reflect_on_all_aggregations, reflection_for_address
assert_equal reflection_for_address, Customer.reflect_on_aggregation(:address)
diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb
index bc3b1de697..278dac8171 100644
--- a/activerecord/test/cases/relation/merging_test.rb
+++ b/activerecord/test/cases/relation/merging_test.rb
@@ -86,9 +86,9 @@ class RelationMergingTest < ActiveRecord::TestCase
merged = left.merge(right)
assert_equal expected, merged.bound_attributes
- assert !merged.to_sql.include?("omg")
- assert merged.to_sql.include?("wtf")
- assert merged.to_sql.include?("bbq")
+ assert_not_includes merged.to_sql, "omg"
+ assert_includes merged.to_sql, "wtf"
+ assert_includes merged.to_sql, "bbq"
end
def test_merging_reorders_bind_params
diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb
index ca5d187819..2cbbc775ce 100644
--- a/activerecord/test/cases/relation/mutation_test.rb
+++ b/activerecord/test/cases/relation/mutation_test.rb
@@ -71,7 +71,7 @@ module ActiveRecord
test "#references!" do
assert relation.references!(:foo).equal?(relation)
- assert relation.references_values.include?("foo")
+ assert_includes relation.references_values, "foo"
end
test "extending!" do
@@ -90,7 +90,7 @@ module ActiveRecord
assert_equal [], relation.extending_values
end
- (Relation::SINGLE_VALUE_METHODS - [:lock, :reordering, :reverse_order, :create_with, :uniq]).each do |method|
+ (Relation::SINGLE_VALUE_METHODS - [:lock, :reordering, :reverse_order, :create_with]).each do |method|
test "##{method}!" do
assert relation.public_send("#{method}!", :foo).equal?(relation)
assert_equal :foo, relation.public_send("#{method}_value")
@@ -140,7 +140,7 @@ module ActiveRecord
test "create_with!" do
assert relation.create_with!(foo: "bar").equal?(relation)
- assert_equal({foo: "bar"}, relation.create_with_value)
+ assert_equal({ foo: "bar" }, relation.create_with_value)
end
test "test_merge!" do
@@ -161,22 +161,6 @@ module ActiveRecord
test "distinct!" do
relation.distinct! :foo
assert_equal :foo, relation.distinct_value
-
- assert_deprecated do
- assert_equal :foo, relation.uniq_value # deprecated access
- end
- end
-
- test "uniq! was replaced by distinct!" do
- assert_deprecated(/use distinct! instead/) do
- relation.uniq! :foo
- end
-
- assert_deprecated(/use distinct_value instead/) do
- assert_equal :foo, relation.uniq_value # deprecated access
- end
-
- assert_equal :foo, relation.distinct_value
end
end
end
diff --git a/activerecord/test/cases/relation/or_test.rb b/activerecord/test/cases/relation/or_test.rb
index 4efc742d5a..abb7ca72dd 100644
--- a/activerecord/test/cases/relation/or_test.rb
+++ b/activerecord/test/cases/relation/or_test.rb
@@ -61,8 +61,8 @@ module ActiveRecord
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] }
- assert_equal expected, groups.having("COUNT(*) > 1").or(groups.having("body like 'Such%'")).to_a.map {|o| [o.body, o.c] }
+ expected = groups.having("COUNT(*) > 1 OR body like 'Such%'").to_a.map { |o| [o.body, o.c] }
+ assert_equal expected, groups.having("COUNT(*) > 1").or(groups.having("body like 'Such%'")).to_a.map { |o| [o.body, o.c] }
end
def test_or_with_named_scope
@@ -79,7 +79,7 @@ module ActiveRecord
expected = Post.where("id = 1 or id = 2").to_a
p = Post.where("id = 1")
p.load
- assert_equal p.loaded?, true
+ assert_equal true, p.loaded?
assert_equal expected, p.or(Post.where("id = 2")).to_a
end
diff --git a/activerecord/test/cases/relation/where_chain_test.rb b/activerecord/test/cases/relation/where_chain_test.rb
index 3d93cb7bce..a96d1ae5b5 100644
--- a/activerecord/test/cases/relation/where_chain_test.rb
+++ b/activerecord/test/cases/relation/where_chain_test.rb
@@ -26,7 +26,7 @@ module ActiveRecord
def test_association_not_eq
expected = Arel::Nodes::Grouping.new(Comment.arel_table[@name].not_eq(Arel::Nodes::BindParam.new))
- relation = Post.joins(:comments).where.not(comments: {title: "hello"})
+ 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_test.rb b/activerecord/test/cases/relation/where_test.rb
index ad9008ea4d..ed8ffddcf5 100644
--- a/activerecord/test/cases/relation/where_test.rb
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -4,10 +4,10 @@ require "models/binary"
require "models/cake_designer"
require "models/car"
require "models/chef"
+require "models/post"
require "models/comment"
require "models/edge"
require "models/essay"
-require "models/post"
require "models/price_estimate"
require "models/topic"
require "models/treasure"
@@ -64,12 +64,12 @@ module ActiveRecord
end
def test_belongs_to_array_value_where
- assert_equal Post.where(author_id: [1,2]).to_sql, Post.where(author: [1,2]).to_sql
+ assert_equal Post.where(author_id: [1, 2]).to_sql, Post.where(author: [1, 2]).to_sql
end
def test_belongs_to_nested_relation_where
- expected = Post.where(author_id: Author.where(id: [1,2])).to_sql
- actual = Post.where(author: Author.where(id: [1,2])).to_sql
+ expected = Post.where(author_id: Author.where(id: [1, 2])).to_sql
+ actual = Post.where(author: Author.where(id: [1, 2])).to_sql
assert_equal expected, actual
end
@@ -87,7 +87,7 @@ module ActiveRecord
def test_belongs_to_nested_where_with_relation
author = authors(:david)
- expected = Author.where(id: author ).joins(:posts)
+ expected = Author.where(id: author).joins(:posts)
actual = Author.where(posts: { author_id: Author.where(id: author.id) }).joins(:posts)
assert_equal expected.to_a, actual.to_a
@@ -127,8 +127,8 @@ module ActiveRecord
end
def test_polymorphic_nested_relation_where
- expected = PriceEstimate.where(estimate_of_type: "Treasure", estimate_of_id: Treasure.where(id: [1,2]))
- actual = PriceEstimate.where(estimate_of: Treasure.where(id: [1,2]))
+ expected = PriceEstimate.where(estimate_of_type: "Treasure", estimate_of_id: Treasure.where(id: [1, 2]))
+ actual = PriceEstimate.where(estimate_of: Treasure.where(id: [1, 2]))
assert_equal expected.to_sql, actual.to_sql
end
@@ -196,7 +196,7 @@ module ActiveRecord
end
def test_where_error
- assert_raises(ActiveRecord::StatementInvalid) do
+ assert_nothing_raised do
Post.where(id: { "posts.author_id" => 10 }).first
end
end
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 0802afac5a..d5af0cc9a5 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -40,9 +40,10 @@ module ActiveRecord
def test_initialize_single_values
relation = Relation.new(FakeKlass, :b, nil)
- (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |method|
+ (Relation::SINGLE_VALUE_METHODS - [:create_with, :readonly]).each do |method|
assert_nil relation.send("#{method}_value"), method.to_s
end
+ assert_equal false, relation.readonly_value
value = relation.create_with_value
assert_equal({}, value)
assert_predicate value, :frozen?
@@ -70,7 +71,7 @@ module ActiveRecord
def test_has_values
relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
relation.where! relation.table[:id].eq(10)
- assert_equal({id: 10}, relation.where_values_hash)
+ assert_equal({ id: 10 }, relation.where_values_hash)
end
def test_values_wrong_table
@@ -108,8 +109,8 @@ module ActiveRecord
def test_create_with_value_with_wheres
relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
relation.where! relation.table[:id].eq(10)
- relation.create_with_value = {hello: "world"}
- assert_equal({hello: "world", id: 10}, relation.scope_for_create)
+ relation.create_with_value = { hello: "world" }
+ assert_equal({ hello: "world", id: 10 }, relation.scope_for_create)
end
# FIXME: is this really wanted or expected behavior?
@@ -120,7 +121,7 @@ module ActiveRecord
relation.where! relation.table[:id].eq(10)
assert_equal({}, relation.scope_for_create)
- relation.create_with_value = {hello: "world"}
+ relation.create_with_value = { hello: "world" }
assert_equal({}, relation.scope_for_create)
end
@@ -156,9 +157,9 @@ module ActiveRecord
test "merging a hash into a relation" do
relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
- relation = relation.merge where: {name: :lol}, readonly: true
+ relation = relation.merge where: { name: :lol }, readonly: true
- assert_equal({"name"=>:lol}, relation.where_clause.to_h)
+ assert_equal({ "name" => :lol }, relation.where_clause.to_h)
assert_equal true, relation.readonly_value
end
@@ -223,7 +224,7 @@ 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 3, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count.length
+ assert_equal({ 2 => 1, 4 => 3, 5 => 1 }, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count)
end
def test_relation_merging_with_joins_as_join_dependency_pick_proper_parent
@@ -273,7 +274,7 @@ module ActiveRecord
join_string = "LEFT OUTER JOIN #{Rating.quoted_table_name} ON #{SpecialComment.quoted_table_name}.id = #{Rating.quoted_table_name}.comment_id"
special_comments_with_ratings = SpecialComment.joins join_string
posts_with_special_comments_with_ratings = Post.group("posts.id").joins(:special_comments).merge(special_comments_with_ratings)
- assert_equal 3, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count.length
+ assert_equal({ 2 => 1, 4 => 3, 5 => 1 }, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count)
end
class EnsureRoundTripTypeCasting < ActiveRecord::Type::Value
@@ -309,10 +310,10 @@ module ActiveRecord
def skip_if_sqlite3_version_includes_quoting_bug
if sqlite3_version_includes_quoting_bug?
skip <<-ERROR.squish
- You are using an outdated version of SQLite3 which has a bug in
- quoted column names. Please update SQLite3 and rebuild the sqlite3
- ruby gem
- ERROR
+ You are using an outdated version of SQLite3 which has a bug in
+ quoted column names. Please update SQLite3 and rebuild the sqlite3
+ ruby gem
+ ERROR
end
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 8652ef1081..9519fec0c4 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -184,14 +184,14 @@ class RelationTest < ActiveRecord::TestCase
def test_select_with_subquery_in_from_does_not_use_original_table_name
relation = Comment.group(:type).select("COUNT(post_id) AS post_count, type")
- subquery = Comment.from(relation).select("type","post_count")
- assert_equal(relation.map(&:post_count).sort,subquery.map(&:post_count).sort)
+ subquery = Comment.from(relation).select("type", "post_count")
+ assert_equal(relation.map(&:post_count).sort, subquery.map(&:post_count).sort)
end
def test_group_with_subquery_in_from_does_not_use_original_table_name
relation = Comment.group(:type).select("COUNT(post_id) AS post_count,type")
subquery = Comment.from(relation).group("type").average("post_count")
- assert_equal(relation.map(&:post_count).sort,subquery.values.sort)
+ assert_equal(relation.map(&:post_count).sort, subquery.values.sort)
end
def test_finding_with_conditions
@@ -240,6 +240,15 @@ class RelationTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::IrreversibleOrderError) do
Topic.order("concat(author_name, title)").reverse_order
end
+ assert_raises(ActiveRecord::IrreversibleOrderError) do
+ Topic.order("concat(lower(author_name), title)").reverse_order
+ end
+ assert_raises(ActiveRecord::IrreversibleOrderError) do
+ Topic.order("concat(author_name, lower(title))").reverse_order
+ end
+ assert_raises(ActiveRecord::IrreversibleOrderError) do
+ Topic.order("concat(lower(author_name), title, length(title)").reverse_order
+ end
end
def test_reverse_order_with_nulls_first_or_last
@@ -282,7 +291,7 @@ class RelationTest < ActiveRecord::TestCase
assert_includes Topic.order(id: "DESC").to_sql, "DESC"
assert_includes Topic.order(id: "desc").to_sql, "DESC"
assert_includes Topic.order(id: :DESC).to_sql, "DESC"
- assert_includes Topic.order(id: :desc).to_sql,"DESC"
+ assert_includes Topic.order(id: :desc).to_sql, "DESC"
end
def test_raising_exception_on_invalid_hash_params
@@ -356,7 +365,7 @@ class RelationTest < ActiveRecord::TestCase
end
def test_finding_with_sanitized_order
- query = Tag.order(["field(id, ?)", [1,3,2]]).to_sql
+ query = Tag.order(["field(id, ?)", [1, 3, 2]]).to_sql
assert_match(/field\(id, 1,3,2\)/, query)
query = Tag.order(["field(id, ?)", []]).to_sql
@@ -384,7 +393,7 @@ class RelationTest < ActiveRecord::TestCase
end
def test_select_with_block
- even_ids = Developer.all.select {|d| d.id % 2 == 0 }.map(&:id)
+ even_ids = Developer.all.select { |d| d.id % 2 == 0 }.map(&:id)
assert_equal [2, 4, 6, 8, 10], even_ids.sort
end
@@ -433,7 +442,7 @@ class RelationTest < ActiveRecord::TestCase
assert_no_queries(ignore_none: false) do
assert_equal 0, Developer.none.count
assert_equal 0, Developer.none.calculate(:count, nil)
- assert_equal nil, Developer.none.calculate(:average, "salary")
+ assert_nil Developer.none.calculate(:average, "salary")
end
end
@@ -449,55 +458,55 @@ class RelationTest < ActiveRecord::TestCase
def test_null_relation_sum
ac = Aircraft.new
assert_equal Hash.new, ac.engines.group(:id).sum(:id)
- assert_equal 0, ac.engines.count
+ assert_equal 0, ac.engines.count
ac.save
assert_equal Hash.new, ac.engines.group(:id).sum(:id)
- assert_equal 0, ac.engines.count
+ assert_equal 0, ac.engines.count
end
def test_null_relation_count
ac = Aircraft.new
assert_equal Hash.new, ac.engines.group(:id).count
- assert_equal 0, ac.engines.count
+ assert_equal 0, ac.engines.count
ac.save
assert_equal Hash.new, ac.engines.group(:id).count
- assert_equal 0, ac.engines.count
+ assert_equal 0, ac.engines.count
end
def test_null_relation_size
ac = Aircraft.new
assert_equal Hash.new, ac.engines.group(:id).size
- assert_equal 0, ac.engines.size
+ assert_equal 0, ac.engines.size
ac.save
assert_equal Hash.new, ac.engines.group(:id).size
- assert_equal 0, ac.engines.size
+ assert_equal 0, ac.engines.size
end
def test_null_relation_average
ac = Aircraft.new
assert_equal Hash.new, ac.engines.group(:car_id).average(:id)
- assert_equal nil, ac.engines.average(:id)
+ assert_nil ac.engines.average(:id)
ac.save
assert_equal Hash.new, ac.engines.group(:car_id).average(:id)
- assert_equal nil, ac.engines.average(:id)
+ assert_nil ac.engines.average(:id)
end
def test_null_relation_minimum
ac = Aircraft.new
assert_equal Hash.new, ac.engines.group(:car_id).minimum(:id)
- assert_equal nil, ac.engines.minimum(:id)
+ assert_nil ac.engines.minimum(:id)
ac.save
assert_equal Hash.new, ac.engines.group(:car_id).minimum(:id)
- assert_equal nil, ac.engines.minimum(:id)
+ assert_nil ac.engines.minimum(:id)
end
def test_null_relation_maximum
ac = Aircraft.new
assert_equal Hash.new, ac.engines.group(:car_id).maximum(:id)
- assert_equal nil, ac.engines.maximum(:id)
+ assert_nil ac.engines.maximum(:id)
ac.save
assert_equal Hash.new, ac.engines.group(:car_id).maximum(:id)
- assert_equal nil, ac.engines.maximum(:id)
+ assert_nil ac.engines.maximum(:id)
end
def test_null_relation_in_where_condition
@@ -521,12 +530,12 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 3, developers_on_project_one.length
developer_names = developers_on_project_one.map(&:name)
- assert developer_names.include?("David")
- assert developer_names.include?("Jamis")
+ assert_includes developer_names, "David"
+ assert_includes developer_names, "Jamis"
end
def test_find_on_hash_conditions
- assert_equal Topic.all.merge!(where: {approved: false}).to_a, Topic.where(approved: false).to_a
+ assert_equal Topic.all.merge!(where: { approved: false }).to_a, Topic.where(approved: false).to_a
end
def test_joins_with_string_array
@@ -739,11 +748,11 @@ class RelationTest < ActiveRecord::TestCase
posts = Post.preload(:comments)
post = posts.find { |p| p.id == 1 }
assert_equal 2, post.comments.size
- assert post.comments.include?(comments(:greetings))
+ assert_includes post.comments, comments(:greetings)
post = Post.where("posts.title = 'Welcome to the weblog'").preload(:comments).first
assert_equal 2, post.comments.size
- assert post.comments.include?(comments(:greetings))
+ assert_includes post.comments, comments(:greetings)
posts = Post.preload(:last_comment)
post = posts.find { |p| p.id == 1 }
@@ -776,7 +785,6 @@ class RelationTest < ActiveRecord::TestCase
expected_taggings = taggings(:welcome_general, :thinking_general)
assert_no_queries do
- assert_equal expected_taggings, author.taggings.distinct.sort_by(&:id)
assert_equal expected_taggings, author.taggings.uniq.sort_by(&:id)
end
@@ -832,15 +840,6 @@ class RelationTest < ActiveRecord::TestCase
assert_equal author, authors.first
end
- class Mary < Author; end
-
- def test_find_by_classname
- Author.create!(name: Mary.name)
- assert_deprecated do
- assert_equal 1, Author.where(name: Mary).size
- end
- end
-
def test_find_by_id_with_list_of_ar
author = Author.first
authors = Author.find_by_id([author])
@@ -970,7 +969,7 @@ class RelationTest < ActiveRecord::TestCase
assert ! davids.exists?(42)
assert ! davids.exists?(davids.new.id)
- fake = Author.where(name: "fake author")
+ fake = Author.where(name: "fake author")
assert ! fake.exists?
assert ! fake.exists?(authors(:david).id)
end
@@ -1005,12 +1004,6 @@ class RelationTest < ActiveRecord::TestCase
assert davids.loaded?
end
- def test_destroy_all_with_conditions_is_deprecated
- assert_deprecated do
- assert_difference("Author.count", -1) { Author.destroy_all(name: "David") }
- end
- end
-
def test_delete_all
davids = Author.where(name: "David")
@@ -1018,12 +1011,6 @@ class RelationTest < ActiveRecord::TestCase
assert ! davids.loaded?
end
- def test_delete_all_with_conditions_is_deprecated
- assert_deprecated do
- assert_difference("Author.count", -1) { Author.delete_all(name: "David") }
- end
- end
-
def test_delete_all_loaded
davids = Author.where(name: "David")
@@ -1225,8 +1212,8 @@ class RelationTest < ActiveRecord::TestCase
assert posts.any? # Uses COUNT()
assert ! posts.where(id: nil).any?
- assert posts.any? {|p| p.id > 0 }
- assert ! posts.any? {|p| p.id <= 0 }
+ assert posts.any? { |p| p.id > 0 }
+ assert ! posts.any? { |p| p.id <= 0 }
end
assert posts.loaded?
@@ -1237,8 +1224,8 @@ class RelationTest < ActiveRecord::TestCase
assert_queries(2) do
assert posts.many? # Uses COUNT()
- assert posts.many? {|p| p.id > 0 }
- assert ! posts.many? {|p| p.id < 2 }
+ assert posts.many? { |p| p.id > 0 }
+ assert ! posts.many? { |p| p.id < 2 }
end
assert posts.loaded?
@@ -1260,8 +1247,8 @@ class RelationTest < ActiveRecord::TestCase
assert ! posts.loaded?
assert_queries(1) do
- assert posts.none? {|p| p.id < 0 }
- assert ! posts.none? {|p| p.id == 1 }
+ assert posts.none? { |p| p.id < 0 }
+ assert ! posts.none? { |p| p.id == 1 }
end
assert posts.loaded?
@@ -1276,8 +1263,8 @@ class RelationTest < ActiveRecord::TestCase
assert ! posts.loaded?
assert_queries(1) do
- assert ! posts.one? {|p| p.id < 3 }
- assert posts.one? {|p| p.id == 1 }
+ assert ! posts.one? { |p| p.id < 3 }
+ assert posts.one? { |p| p.id == 1 }
end
assert posts.loaded?
@@ -1363,11 +1350,11 @@ class RelationTest < ActiveRecord::TestCase
end
def test_first_or_create_with_array
- several_green_birds = Bird.where(color: "green").first_or_create([{name: "parrot"}, {name: "parakeet"}])
+ several_green_birds = Bird.where(color: "green").first_or_create([{ name: "parrot" }, { name: "parakeet" }])
assert_kind_of Array, several_green_birds
several_green_birds.each { |bird| assert bird.persisted? }
- same_parrot = Bird.where(color: "green").first_or_create([{name: "hummingbird"}, {name: "macaw"}])
+ same_parrot = Bird.where(color: "green").first_or_create([{ name: "hummingbird" }, { name: "macaw" }])
assert_kind_of Bird, same_parrot
assert_equal several_green_birds.first, same_parrot
end
@@ -1411,17 +1398,17 @@ class RelationTest < ActiveRecord::TestCase
end
def test_first_or_create_with_valid_array
- several_green_birds = Bird.where(color: "green").first_or_create!([{name: "parrot"}, {name: "parakeet"}])
+ several_green_birds = Bird.where(color: "green").first_or_create!([{ name: "parrot" }, { name: "parakeet" }])
assert_kind_of Array, several_green_birds
several_green_birds.each { |bird| assert bird.persisted? }
- same_parrot = Bird.where(color: "green").first_or_create!([{name: "hummingbird"}, {name: "macaw"}])
+ same_parrot = Bird.where(color: "green").first_or_create!([{ name: "hummingbird" }, { name: "macaw" }])
assert_kind_of Bird, same_parrot
assert_equal several_green_birds.first, same_parrot
end
def test_first_or_create_with_invalid_array
- assert_raises(ActiveRecord::RecordInvalid) { Bird.where(color: "green").first_or_create!([ {name: "parrot"}, {pirate_id: 1} ]) }
+ assert_raises(ActiveRecord::RecordInvalid) { Bird.where(color: "green").first_or_create!([ { name: "parrot" }, { pirate_id: 1 } ]) }
end
def test_first_or_initialize
@@ -1513,7 +1500,7 @@ class RelationTest < ActiveRecord::TestCase
assert_equal Post.where(author_id: 1).to_a, author_posts.to_a
all_posts = relation.only(:limit)
- assert_equal Post.limit(1).to_a.first, all_posts.first
+ assert_equal Post.limit(1).to_a, all_posts.to_a
end
def test_anonymous_extension
@@ -1555,8 +1542,8 @@ class RelationTest < ActiveRecord::TestCase
end
def test_unscoped_block_style
- assert_equal "honda", CoolCar.unscoped { CoolCar.order_using_new_style.limit(1).first.name}
- assert_equal "honda", FastCar.unscoped { FastCar.order_using_new_style.limit(1).first.name}
+ assert_equal "honda", CoolCar.unscoped { CoolCar.order_using_new_style.limit(1).first.name }
+ assert_equal "honda", FastCar.unscoped { FastCar.order_using_new_style.limit(1).first.name }
end
def test_intersection_with_array
@@ -1646,17 +1633,11 @@ class RelationTest < ActiveRecord::TestCase
assert_equal ["Foo", "Foo"], query.map(&:name)
assert_sql(/DISTINCT/) do
assert_equal ["Foo"], query.distinct.map(&:name)
- assert_deprecated { assert_equal ["Foo"], query.uniq.map(&:name) }
end
assert_sql(/DISTINCT/) do
assert_equal ["Foo"], query.distinct(true).map(&:name)
- assert_deprecated { assert_equal ["Foo"], query.uniq(true).map(&:name) }
end
assert_equal ["Foo", "Foo"], query.distinct(true).distinct(false).map(&:name)
-
- assert_deprecated do
- assert_equal ["Foo", "Foo"], query.uniq(true).uniq(false).map(&:name)
- end
end
def test_doesnt_add_having_values_if_options_are_blank
@@ -1806,7 +1787,7 @@ class RelationTest < ActiveRecord::TestCase
end
test "find_by returns nil if the record is missing" do
- assert_equal nil, Post.all.find_by("1 = 0")
+ assert_nil Post.all.find_by("1 = 0")
end
test "find_by doesn't have implicit ordering" do
@@ -1955,7 +1936,7 @@ class RelationTest < ActiveRecord::TestCase
end
def test_unscope_removes_binds
- left = Post.where(id: Arel::Nodes::BindParam.new)
+ left = Post.where(id: Arel::Nodes::BindParam.new)
column = Post.columns_hash["id"]
left.bind_values += [[column, 20]]
@@ -1964,8 +1945,8 @@ class RelationTest < ActiveRecord::TestCase
end
def test_merging_removes_rhs_bind_parameters
- left = Post.where(id: 20)
- right = Post.where(id: [1,2,3,4])
+ left = Post.where(id: 20)
+ right = Post.where(id: [1, 2, 3, 4])
merged = left.merge(right)
assert_equal [], merged.bind_values
diff --git a/activerecord/test/cases/result_test.rb b/activerecord/test/cases/result_test.rb
index 8bbd4b3bc5..949086fda0 100644
--- a/activerecord/test/cases/result_test.rb
+++ b/activerecord/test/cases/result_test.rb
@@ -16,20 +16,20 @@ module ActiveRecord
test "to_hash returns row_hashes" do
assert_equal [
- {"col_1" => "row 1 col 1", "col_2" => "row 1 col 2"},
- {"col_1" => "row 2 col 1", "col_2" => "row 2 col 2"},
- {"col_1" => "row 3 col 1", "col_2" => "row 3 col 2"},
+ { "col_1" => "row 1 col 1", "col_2" => "row 1 col 2" },
+ { "col_1" => "row 2 col 1", "col_2" => "row 2 col 2" },
+ { "col_1" => "row 3 col 1", "col_2" => "row 3 col 2" },
], result.to_hash
end
test "first returns first row as a hash" do
assert_equal(
- {"col_1" => "row 1 col 1", "col_2" => "row 1 col 2"}, result.first)
+ { "col_1" => "row 1 col 1", "col_2" => "row 1 col 2" }, result.first)
end
test "last returns last row as a hash" do
assert_equal(
- {"col_1" => "row 3 col 1", "col_2" => "row 3 col 2"}, result.last)
+ { "col_1" => "row 3 col 1", "col_2" => "row 3 col 2" }, result.last)
end
test "each with block returns row hashes" do
diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb
index 464bb12ccb..23bcb0af1e 100644
--- a/activerecord/test/cases/sanitize_test.rb
+++ b/activerecord/test/cases/sanitize_test.rb
@@ -12,8 +12,8 @@ class SanitizeTest < ActiveRecord::TestCase
assert_equal "name='#{quoted_bambi}'", Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi"])
assert_equal "name='#{quoted_bambi}'", Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi".mb_chars])
quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote_string("Bambi\nand\nThumper")
- assert_equal "name='#{quoted_bambi_and_thumper}'",Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi\nand\nThumper"])
- assert_equal "name='#{quoted_bambi_and_thumper}'",Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi\nand\nThumper".mb_chars])
+ assert_equal "name='#{quoted_bambi_and_thumper}'", Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi\nand\nThumper"])
+ assert_equal "name='#{quoted_bambi_and_thumper}'", Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi\nand\nThumper".mb_chars])
end
def test_sanitize_sql_array_handles_bind_variables
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 2ad3da94e4..bea78d2a95 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -51,6 +51,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
output = standard_dump
assert_match %r{create_table "accounts"}, output
assert_match %r{create_table "authors"}, output
+ assert_no_match %r{(?<=, ) do \|t\|}, output
assert_no_match %r{create_table "schema_migrations"}, output
assert_no_match %r{create_table "ar_internal_metadata"}, output
end
@@ -70,38 +71,35 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{create_table "CamelCase"}, output
end
- def assert_line_up(lines, pattern, required = false)
+ def assert_no_line_up(lines, pattern)
return assert(true) if lines.empty?
matches = lines.map { |line| line.match(pattern) }
- assert matches.all? if required
matches.compact!
return assert(true) if matches.empty?
- assert_equal 1, matches.map{ |match| match.offset(0).first }.uniq.length
+ line_matches = lines.map { |line| [line, line.match(pattern)] }.select { |line, match| match }
+ assert line_matches.all? { |line, match|
+ start = match.offset(0).first
+ line[start - 2..start - 1] == ", "
+ }
end
def column_definition_lines(output = standard_dump)
- output.scan(/^( *)create_table.*?\n(.*?)^\1end/m).map{ |m| m.last.split(/\n/) }
+ output.scan(/^( *)create_table.*?\n(.*?)^\1end/m).map { |m| m.last.split(/\n/) }
end
- def test_types_line_up
+ def test_types_no_line_up
column_definition_lines.each do |column_set|
next if column_set.empty?
- lengths = column_set.map do |column|
- if match = column.match(/\bt\.\w+\s+(?="\w+?")/)
- match[0].length
- end
- end.compact
-
- assert_equal 1, lengths.uniq.length
+ assert column_set.all? { |column| !column.match(/\bt\.\w+\s{2,}/) }
end
end
- def test_arguments_line_up
+ def test_arguments_no_line_up
column_definition_lines.each do |column_set|
- assert_line_up(column_set, /default: /)
- assert_line_up(column_set, /limit: /)
- assert_line_up(column_set, /null: /)
+ assert_no_line_up(column_set, /default: /)
+ assert_no_line_up(column_set, /limit: /)
+ assert_no_line_up(column_set, /null: /)
end
end
@@ -150,12 +148,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{c_int_4.*limit: 4}, output
end
- if current_adapter?(:SQLite3Adapter)
- assert_match %r{c_int_5.*limit: 5}, output
- assert_match %r{c_int_6.*limit: 6}, output
- assert_match %r{c_int_7.*limit: 7}, output
- assert_match %r{c_int_8.*limit: 8}, output
- elsif current_adapter?(:OracleAdapter)
+ if current_adapter?(:SQLite3Adapter, :OracleAdapter)
assert_match %r{c_int_5.*limit: 5}, output
assert_match %r{c_int_6.*limit: 6}, output
assert_match %r{c_int_7.*limit: 7}, output
@@ -186,8 +179,10 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_schema_dumps_index_columns_in_right_order
index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_index/).first.strip
- if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
- assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", using: :btree', index_definition
+ if current_adapter?(:PostgreSQLAdapter)
+ assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", order: { rating: :desc }, using: :btree', index_definition
+ elsif current_adapter?(:Mysql2Adapter)
+ assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", length: { type: 10 }, using: :btree', index_definition
else
assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index"', index_definition
end
@@ -346,7 +341,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
create_table("dogs") do |t|
t.column :name, :string
- t.column :owner_id, :integer
+ t.column :owner_id, :bigint
t.index [:name]
t.foreign_key :dog_owners, column: "owner_id" if supports_foreign_keys?
end
@@ -426,6 +421,13 @@ class SchemaDumperDefaultsTest < ActiveRecord::TestCase
t.datetime :datetime_with_default, default: "2014-06-05 07:17:04"
t.time :time_with_default, default: "07:17:04"
end
+
+ if current_adapter?(:PostgreSQLAdapter)
+ @connection.create_table :infinity_defaults, force: true do |t|
+ t.float :float_with_inf_default, default: Float::INFINITY
+ t.float :float_with_nan_default, default: Float::NAN
+ end
+ end
end
teardown do
@@ -441,4 +443,11 @@ class SchemaDumperDefaultsTest < ActiveRecord::TestCase
assert_match %r{t\.datetime\s+"datetime_with_default",\s+default: '2014-06-05 07:17:04'}, output
assert_match %r{t\.time\s+"time_with_default",\s+default: '2000-01-01 07:17:04'}, output
end
+
+ def test_schema_dump_with_float_column_infinity_default
+ skip unless current_adapter?(:PostgreSQLAdapter)
+ output = dump_table_schema("infinity_defaults")
+ assert_match %r{t\.float\s+"float_with_inf_default",\s+default: ::Float::INFINITY}, output
+ assert_match %r{t\.float\s+"float_with_nan_default",\s+default: ::Float::NAN}, output
+ end
end
diff --git a/activerecord/test/cases/schema_loading_test.rb b/activerecord/test/cases/schema_loading_test.rb
index 3d92a5e104..362370ac61 100644
--- a/activerecord/test/cases/schema_loading_test.rb
+++ b/activerecord/test/cases/schema_loading_test.rb
@@ -8,7 +8,7 @@ module SchemaLoadCounter
def load_schema!
self.load_schema_calls ||= 0
- self.load_schema_calls +=1
+ self.load_schema_calls += 1
super
end
end
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index effecfd294..3a04f4bf7d 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -5,7 +5,7 @@ require "models/developer"
require "models/computer"
require "models/vehicle"
require "models/cat"
-require "active_support/core_ext/regexp"
+require "concurrent/atomic/cyclic_barrier"
class DefaultScopingTest < ActiveRecord::TestCase
fixtures :developers, :posts, :comments
@@ -51,7 +51,7 @@ class DefaultScopingTest < ActiveRecord::TestCase
def test_default_scope_with_conditions_string
assert_equal Developer.where(name: "David").map(&:id).sort, DeveloperCalledDavid.all.map(&:id).sort
- assert_equal nil, DeveloperCalledDavid.create!.name
+ assert_nil DeveloperCalledDavid.create!.name
end
def test_default_scope_with_conditions_hash
@@ -63,7 +63,7 @@ class DefaultScopingTest < ActiveRecord::TestCase
def test_default_scoping_with_threads
2.times do
Thread.new {
- assert DeveloperOrderedBySalary.all.to_sql.include?("salary DESC")
+ assert_includes DeveloperOrderedBySalary.all.to_sql, "salary DESC"
DeveloperOrderedBySalary.connection.close
}.join
end
@@ -132,7 +132,7 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal expected, received
expected_2 = Developer.order("salary DESC").collect(&:name)
- received_2 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope({where: :name}, :select).collect(&:name)
+ received_2 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope({ where: :name }, :select).collect(&:name)
assert_equal expected_2, received_2
expected_3 = Developer.order("salary DESC").collect(&:name)
@@ -315,7 +315,7 @@ class DefaultScopingTest < ActiveRecord::TestCase
end
def test_create_attribute_overwrites_default_values
- assert_equal nil, PoorDeveloperCalledJamis.create!(salary: nil).salary
+ assert_nil PoorDeveloperCalledJamis.create!(salary: nil).salary
assert_equal 50000, PoorDeveloperCalledJamis.create!(name: "David").salary
end
@@ -368,7 +368,7 @@ class DefaultScopingTest < ActiveRecord::TestCase
def test_unscoped_with_named_scope_should_not_have_default_scope
assert_equal [DeveloperCalledJamis.find(developers(:poor_jamis).id)], DeveloperCalledJamis.poor
- assert DeveloperCalledJamis.unscoped.poor.include?(developers(:david).becomes(DeveloperCalledJamis))
+ assert_includes DeveloperCalledJamis.unscoped.poor, developers(:david).becomes(DeveloperCalledJamis)
assert_equal 11, DeveloperCalledJamis.unscoped.length
assert_equal 1, DeveloperCalledJamis.poor.length
@@ -438,12 +438,17 @@ class DefaultScopingTest < ActiveRecord::TestCase
threads = []
assert_not_equal 1, ThreadsafeDeveloper.unscoped.count
+ barrier_1 = Concurrent::CyclicBarrier.new(2)
+ barrier_2 = Concurrent::CyclicBarrier.new(2)
+
threads << Thread.new do
- Thread.current[:long_default_scope] = true
+ Thread.current[:default_scope_delay] = -> { barrier_1.wait; barrier_2.wait }
assert_equal 1, ThreadsafeDeveloper.all.to_a.count
ThreadsafeDeveloper.connection.close
end
threads << Thread.new do
+ Thread.current[:default_scope_delay] = -> { barrier_2.wait }
+ barrier_1.wait
assert_equal 1, ThreadsafeDeveloper.all.to_a.count
ThreadsafeDeveloper.connection.close
end
diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb
index 4e799d6dc0..995ff4dfc5 100644
--- a/activerecord/test/cases/scoping/named_scoping_test.rb
+++ b/activerecord/test/cases/scoping/named_scoping_test.rb
@@ -33,8 +33,8 @@ class NamedScopingTest < ActiveRecord::TestCase
all_posts.to_a
new_post = Topic.create!
- assert !all_posts.include?(new_post)
- assert all_posts.reload.include?(new_post)
+ assert_not_includes all_posts, new_post
+ assert_includes all_posts.reload, new_post
end
def test_delegates_finds_and_calculations_to_the_base_class
@@ -69,9 +69,9 @@ class NamedScopingTest < ActiveRecord::TestCase
end
def test_scopes_with_options_limit_finds_to_those_matching_the_criteria_specified
- assert !Topic.all.merge!(where: {approved: true}).to_a.empty?
+ assert !Topic.all.merge!(where: { approved: true }).to_a.empty?
- assert_equal Topic.all.merge!(where: {approved: true}).to_a, Topic.approved
+ assert_equal Topic.all.merge!(where: { approved: true }).to_a, Topic.approved
assert_equal Topic.where(approved: true).count, Topic.approved.count
end
@@ -82,7 +82,7 @@ class NamedScopingTest < ActiveRecord::TestCase
end
def test_scopes_are_composable
- assert_equal((approved = Topic.all.merge!(where: {approved: true}).to_a), Topic.approved)
+ assert_equal((approved = Topic.all.merge!(where: { approved: true }).to_a), Topic.approved)
assert_equal((replied = Topic.all.merge!(where: "replies_count > 0").to_a), Topic.replied)
assert !(approved == replied)
assert !(approved & replied).empty?
@@ -119,8 +119,8 @@ class NamedScopingTest < ActiveRecord::TestCase
end
def test_scope_with_STI
- assert_equal 3,Post.containing_the_letter_a.count
- assert_equal 1,SpecialPost.containing_the_letter_a.count
+ assert_equal 3, Post.containing_the_letter_a.count
+ assert_equal 1, SpecialPost.containing_the_letter_a.count
end
def test_has_many_through_associations_have_access_to_scopes
@@ -327,12 +327,12 @@ class NamedScopingTest < ActiveRecord::TestCase
conflicts.each do |name|
e = assert_raises(ArgumentError, "scope `#{name}` should not be allowed") do
- klass.class_eval { scope name, ->{ where(approved: true) } }
+ klass.class_eval { scope name, -> { where(approved: true) } }
end
assert_match(/You tried to define a scope named \"#{name}\" on the model/, e.message)
e = assert_raises(ArgumentError, "scope `#{name}` should not be allowed") do
- subklass.class_eval { scope name, ->{ where(approved: true) } }
+ subklass.class_eval { scope name, -> { where(approved: true) } }
end
assert_match(/You tried to define a scope named \"#{name}\" on the model/, e.message)
end
@@ -340,12 +340,12 @@ class NamedScopingTest < ActiveRecord::TestCase
non_conflicts.each do |name|
assert_nothing_raised do
silence_warnings do
- klass.class_eval { scope name, ->{ where(approved: true) } }
+ klass.class_eval { scope name, -> { where(approved: true) } }
end
end
assert_nothing_raised do
- subklass.class_eval { scope name, ->{ where(approved: true) } }
+ subklass.class_eval { scope name, -> { where(approved: true) } }
end
end
end
@@ -431,12 +431,12 @@ class NamedScopingTest < ActiveRecord::TestCase
assert_equal 4, Topic.approved.count
assert_queries(5) do
- Topic.approved.find_each(batch_size: 1) {|t| assert t.approved? }
+ Topic.approved.find_each(batch_size: 1) { |t| assert t.approved? }
end
assert_queries(3) do
Topic.approved.find_in_batches(batch_size: 2) do |group|
- group.each {|t| assert t.approved? }
+ group.each { |t| assert t.approved? }
end
end
end
@@ -462,7 +462,7 @@ class NamedScopingTest < ActiveRecord::TestCase
[:public_method, :protected_method, :private_method].each do |reserved_method|
assert Topic.respond_to?(reserved_method, true)
ActiveRecord::Base.logger.expects(:warn)
- silence_warnings { Topic.scope(reserved_method, -> { }) }
+ silence_warnings { Topic.scope(reserved_method, -> {}) }
end
end
diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb
index a46123f451..a1ae57fdbb 100644
--- a/activerecord/test/cases/scoping/relation_scoping_test.rb
+++ b/activerecord/test/cases/scoping/relation_scoping_test.rb
@@ -28,7 +28,7 @@ class RelationScopingTest < ActiveRecord::TestCase
def test_scope_breaks_caching_on_collections
author = authors :david
ids = author.reload.special_posts_with_default_scope.map(&:id)
- assert_equal [1,5,6], ids.sort
+ assert_equal [1, 5, 6], ids.sort
scoped_posts = SpecialPostWithDefaultScope.unscoped do
author = authors :david
author.reload.special_posts_with_default_scope.to_a
@@ -133,8 +133,8 @@ class RelationScopingTest < ActiveRecord::TestCase
scoped_developers = Developer.includes(:projects).scoping do
Developer.where("projects.id" => 2).to_a
end
- assert scoped_developers.include?(developers(:david))
- assert !scoped_developers.include?(developers(:jamis))
+ assert_includes scoped_developers, developers(:david)
+ assert_not_includes scoped_developers, developers(:jamis)
assert_equal 1, scoped_developers.size
end
@@ -143,8 +143,8 @@ class RelationScopingTest < ActiveRecord::TestCase
Developer.where("developers_projects.project_id = 2").to_a
end
- assert scoped_developers.include?(developers(:david))
- assert !scoped_developers.include?(developers(:jamis))
+ assert_includes scoped_developers, developers(:david)
+ assert_not_includes scoped_developers, developers(:jamis)
assert_equal 1, scoped_developers.size
assert_equal developers(:david).attributes, scoped_developers.first.attributes
end
@@ -155,7 +155,7 @@ class RelationScopingTest < ActiveRecord::TestCase
end
assert_equal 1, new_comment.post_id
- assert Post.find(1).comments.include?(new_comment)
+ assert_includes Post.find(1).comments, new_comment
end
def test_scoped_create_with_create_with
@@ -164,7 +164,7 @@ class RelationScopingTest < ActiveRecord::TestCase
end
assert_equal 1, new_comment.post_id
- assert Post.find(1).comments.include?(new_comment)
+ assert_includes Post.find(1).comments, new_comment
end
def test_scoped_create_with_create_with_has_higher_priority
@@ -173,7 +173,7 @@ class RelationScopingTest < ActiveRecord::TestCase
end
assert_equal 1, new_comment.post_id
- assert Post.find(1).comments.include?(new_comment)
+ assert_includes Post.find(1).comments, new_comment
end
def test_ensure_that_method_scoping_is_correctly_restored
@@ -277,7 +277,7 @@ class NestedRelationScopingTest < ActiveRecord::TestCase
assert_equal "David", Developer.first.name
Developer.unscoped.where("name = 'Maiha'") do
- assert_equal nil, Developer.first
+ assert_nil Developer.first
end
# ensure that scoping is restored
diff --git a/activerecord/test/cases/secure_token_test.rb b/activerecord/test/cases/secure_token_test.rb
index eda0229c26..7b9cbee40a 100644
--- a/activerecord/test/cases/secure_token_test.rb
+++ b/activerecord/test/cases/secure_token_test.rb
@@ -27,6 +27,6 @@ class SecureTokenTest < ActiveRecord::TestCase
@user.token = "custom-secure-token"
@user.save
- assert_equal @user.token, "custom-secure-token"
+ assert_equal "custom-secure-token", @user.token
end
end
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index ceb0d5441b..a469da0a5b 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -107,7 +107,7 @@ class SerializedAttributeTest < ActiveRecord::TestCase
end
def test_serialized_time_attribute
- myobj = Time.local(2008,1,1,1,0)
+ myobj = Time.local(2008, 1, 1, 1, 0)
topic = Topic.create("content" => myobj).reload
assert_equal(myobj, topic.content)
end
@@ -157,6 +157,13 @@ class SerializedAttributeTest < ActiveRecord::TestCase
assert_equal(settings, Topic.find(topic.id).content)
end
+ def test_where_by_serialized_attribute_with_hash
+ settings = { "color" => "green" }
+ Topic.serialize(:content, Hash)
+ topic = Topic.create!(content: settings)
+ assert_equal topic, Topic.where(content: settings).take
+ end
+
def test_serialized_default_class
Topic.serialize(:content, Hash)
topic = Topic.new
@@ -178,14 +185,14 @@ class SerializedAttributeTest < ActiveRecord::TestCase
topic = Topic.new(content: true)
assert topic.save
topic = topic.reload
- assert_equal topic.content, true
+ assert_equal true, topic.content
end
def test_serialized_boolean_value_false
topic = Topic.new(content: false)
assert topic.save
topic = topic.reload
- assert_equal topic.content, false
+ assert_equal false, topic.content
end
def test_serialize_with_coder
@@ -204,7 +211,7 @@ class SerializedAttributeTest < ActiveRecord::TestCase
topic.save!
topic.reload
assert_kind_of some_class, topic.content
- assert_equal topic.content, some_class.new("my value")
+ assert_equal some_class.new("my value"), topic.content
end
def test_serialize_attribute_via_select_method_when_time_zone_available
diff --git a/activerecord/test/cases/suppressor_test.rb b/activerecord/test/cases/suppressor_test.rb
index 64f39d8574..a7d16b7cdb 100644
--- a/activerecord/test/cases/suppressor_test.rb
+++ b/activerecord/test/cases/suppressor_test.rb
@@ -64,7 +64,7 @@ class SuppressorTest < ActiveRecord::TestCase
def test_suppresses_when_nested_multiple_times
assert_no_difference -> { Notification.count } do
Notification.suppress do
- Notification.suppress { }
+ Notification.suppress {}
Notification.create
Notification.create!
Notification.new.save
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index 0519c70ac7..f7c53b5801 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -24,13 +24,13 @@ module ActiveRecord
sqlite3: :sqlite_tasks
}
- class DatabaseTasksUtilsTask< ActiveRecord::TestCase
+ class DatabaseTasksUtilsTask < ActiveRecord::TestCase
def test_raises_an_error_when_called_with_protected_environment
ActiveRecord::Migrator.stubs(:current_version).returns(1)
protected_environments = ActiveRecord::Base.protected_environments.dup
current_env = ActiveRecord::Migrator.current_environment
- assert !protected_environments.include?(current_env)
+ assert_not_includes protected_environments, current_env
# Assert no error
ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
@@ -61,15 +61,15 @@ module ActiveRecord
instance = klazz.new
klazz.stubs(:new).returns instance
- instance.expects(:structure_dump).with("awesome-file.sql")
+ instance.expects(:structure_dump).with("awesome-file.sql", nil)
ActiveRecord::Tasks::DatabaseTasks.register_task(/foo/, klazz)
- ActiveRecord::Tasks::DatabaseTasks.structure_dump({"adapter" => :foo}, "awesome-file.sql")
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => :foo }, "awesome-file.sql")
end
def test_unregistered_task
assert_raise(ActiveRecord::Tasks::DatabaseNotSupported) do
- ActiveRecord::Tasks::DatabaseTasks.structure_dump({"adapter" => :bar}, "awesome-file.sql")
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => :bar }, "awesome-file.sql")
end
end
end
@@ -85,9 +85,19 @@ module ActiveRecord
end
end
+ class DatabaseTasksDumpSchemaCacheTest < ActiveRecord::TestCase
+ def test_dump_schema_cache
+ path = "/tmp/my_schema_cache.yml"
+ ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(ActiveRecord::Base.connection, path)
+ assert File.file?(path)
+ ensure
+ FileUtils.rm_rf(path)
+ end
+ end
+
class DatabaseTasksCreateAllTest < ActiveRecord::TestCase
def setup
- @configurations = {"development" => {"database" => "my-db"}}
+ @configurations = { "development" => { "database" => "my-db" } }
ActiveRecord::Base.stubs(:configurations).returns(@configurations)
end
@@ -145,9 +155,9 @@ module ActiveRecord
class DatabaseTasksCreateCurrentTest < ActiveRecord::TestCase
def setup
@configurations = {
- "development" => {"database" => "dev-db"},
- "test" => {"database" => "test-db"},
- "production" => {"database" => "prod-db"}
+ "development" => { "database" => "dev-db" },
+ "test" => { "database" => "test-db" },
+ "production" => { "database" => "prod-db" }
}
ActiveRecord::Base.stubs(:configurations).returns(@configurations)
@@ -213,7 +223,7 @@ module ActiveRecord
class DatabaseTasksDropAllTest < ActiveRecord::TestCase
def setup
- @configurations = {development: {"database" => "my-db"}}
+ @configurations = { development: { "database" => "my-db" } }
ActiveRecord::Base.stubs(:configurations).returns(@configurations)
end
@@ -271,9 +281,9 @@ module ActiveRecord
class DatabaseTasksDropCurrentTest < ActiveRecord::TestCase
def setup
@configurations = {
- "development" => {"database" => "dev-db"},
- "test" => {"database" => "test-db"},
- "production" => {"database" => "prod-db"}
+ "development" => { "database" => "dev-db" },
+ "test" => { "database" => "test-db" },
+ "production" => { "database" => "prod-db" }
}
ActiveRecord::Base.stubs(:configurations).returns(@configurations)
@@ -358,9 +368,9 @@ module ActiveRecord
class DatabaseTasksPurgeCurrentTest < ActiveRecord::TestCase
def test_purges_current_environment_database
configurations = {
- "development" => {"database" => "dev-db"},
- "test" => {"database" => "test-db"},
- "production" => {"database" => "prod-db"}
+ "development" => { "database" => "dev-db" },
+ "test" => { "database" => "test-db" },
+ "production" => { "database" => "prod-db" }
}
ActiveRecord::Base.stubs(:configurations).returns(configurations)
@@ -374,7 +384,7 @@ module ActiveRecord
class DatabaseTasksPurgeAllTest < ActiveRecord::TestCase
def test_purge_all_local_configurations
- configurations = {development: {"database" => "my-db"}}
+ configurations = { development: { "database" => "my-db" } }
ActiveRecord::Base.stubs(:configurations).returns(configurations)
ActiveRecord::Tasks::DatabaseTasks.expects(:purge).
@@ -411,8 +421,8 @@ module ActiveRecord
ADAPTERS_TASKS.each do |k, v|
define_method("test_#{k}_structure_dump") do
- eval("@#{v}").expects(:structure_dump).with("awesome-file.sql")
- ActiveRecord::Tasks::DatabaseTasks.structure_dump({"adapter" => k}, "awesome-file.sql")
+ eval("@#{v}").expects(:structure_dump).with("awesome-file.sql", nil)
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => k }, "awesome-file.sql")
end
end
end
@@ -422,8 +432,8 @@ module ActiveRecord
ADAPTERS_TASKS.each do |k, v|
define_method("test_#{k}_structure_load") do
- eval("@#{v}").expects(:structure_load).with("awesome-file.sql")
- ActiveRecord::Tasks::DatabaseTasks.structure_load({"adapter" => k}, "awesome-file.sql")
+ eval("@#{v}").expects(:structure_load).with("awesome-file.sql", nil)
+ ActiveRecord::Tasks::DatabaseTasks.structure_load({ "adapter" => k }, "awesome-file.sql")
end
end
end
@@ -443,7 +453,7 @@ module ActiveRecord
end
class DatabaseTasksCheckSchemaFileSpecifiedFormatsTest < ActiveRecord::TestCase
- {ruby: "schema.rb", sql: "structure.sql"}.each_pair do |fmt, filename|
+ { ruby: "schema.rb", sql: "structure.sql" }.each_pair do |fmt, filename|
define_method("test_check_schema_file_for_#{fmt}_format") do
ActiveRecord::Tasks::DatabaseTasks.stubs(:db_dir).returns("/tmp")
assert_equal "/tmp/#{filename}", ActiveRecord::Tasks::DatabaseTasks.schema_file(fmt)
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index dbe935808e..f30e0958c3 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -59,7 +59,7 @@ if current_adapter?(:Mysql2Adapter)
def test_when_database_created_successfully_outputs_info_to_stdout
ActiveRecord::Tasks::DatabaseTasks.create @configuration
- assert_equal $stdout.string, "Created database 'my-app-db'\n"
+ assert_equal "Created database 'my-app-db'\n", $stdout.string
end
def test_create_when_database_exists_outputs_info_to_stderr
@@ -69,7 +69,7 @@ if current_adapter?(:Mysql2Adapter)
ActiveRecord::Tasks::DatabaseTasks.create @configuration
- assert_equal $stderr.string, "Database 'my-app-db' already exists\n"
+ assert_equal "Database 'my-app-db' already exists\n", $stderr.string
end
end
@@ -205,7 +205,7 @@ if current_adapter?(:Mysql2Adapter)
def test_when_database_dropped_successfully_outputs_info_to_stdout
ActiveRecord::Tasks::DatabaseTasks.drop @configuration
- assert_equal $stdout.string, "Dropped database 'my-app-db'\n"
+ assert_equal "Dropped database 'my-app-db'\n", $stdout.string
end
end
@@ -294,6 +294,17 @@ if current_adapter?(:Mysql2Adapter)
ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
end
+ 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"]
+
+ assert_called_with(Kernel, :system, expected_command, returns: true) do
+ with_structure_dump_flags(["--noop"]) do
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
+ end
+ end
+ end
+
def test_warn_when_external_structure_dump_command_execution_fails
filename = "awesome-file.sql"
Kernel.expects(:system)
@@ -323,6 +334,15 @@ if current_adapter?(:Mysql2Adapter)
@configuration.merge("sslca" => "ca.crt"),
filename)
end
+
+ private
+ def with_structure_dump_flags(flags)
+ old = ActiveRecord::Tasks::DatabaseTasks.structure_dump_flags
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump_flags = flags
+ yield
+ ensure
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump_flags = old
+ end
end
class MySQLStructureLoadTest < ActiveRecord::TestCase
@@ -335,11 +355,23 @@ if current_adapter?(:Mysql2Adapter)
def test_structure_load
filename = "awesome-file.sql"
- Kernel.expects(:system).with("mysql", "--execute", %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}, "--database", "test-db")
- .returns(true)
+ expected_command = ["mysql", "--execute", %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}, "--database", "test-db", "--noop"]
- ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
+ assert_called_with(Kernel, :system, expected_command, returns: true) do
+ with_structure_load_flags(["--noop"]) do
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
+ end
+ end
end
+
+ private
+ def with_structure_load_flags(flags)
+ old = ActiveRecord::Tasks::DatabaseTasks.structure_load_flags
+ ActiveRecord::Tasks::DatabaseTasks.structure_load_flags = flags
+ yield
+ ensure
+ ActiveRecord::Tasks::DatabaseTasks.structure_load_flags = old
+ end
end
end
end
diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb
index b8c8ec88f0..a23100c32a 100644
--- a/activerecord/test/cases/tasks/postgresql_rake_test.rb
+++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb
@@ -74,7 +74,7 @@ if current_adapter?(:PostgreSQLAdapter)
def test_when_database_created_successfully_outputs_info_to_stdout
ActiveRecord::Tasks::DatabaseTasks.create @configuration
- assert_equal $stdout.string, "Created database 'my-app-db'\n"
+ assert_equal "Created database 'my-app-db'\n", $stdout.string
end
def test_create_when_database_exists_outputs_info_to_stderr
@@ -84,7 +84,7 @@ if current_adapter?(:PostgreSQLAdapter)
ActiveRecord::Tasks::DatabaseTasks.create @configuration
- assert_equal $stderr.string, "Database 'my-app-db' already exists\n"
+ assert_equal "Database 'my-app-db' already exists\n", $stderr.string
end
end
@@ -126,7 +126,7 @@ if current_adapter?(:PostgreSQLAdapter)
def test_when_database_dropped_successfully_outputs_info_to_stdout
ActiveRecord::Tasks::DatabaseTasks.drop @configuration
- assert_equal $stdout.string, "Dropped database 'my-app-db'\n"
+ assert_equal "Dropped database 'my-app-db'\n", $stdout.string
end
end
@@ -236,6 +236,16 @@ if current_adapter?(:PostgreSQLAdapter)
ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
end
+ def test_structure_dump_with_extra_flags
+ expected_command = ["pg_dump", "-s", "-x", "-O", "-f", @filename, "--noop", "my-app-db"]
+
+ assert_called_with(Kernel, :system, expected_command, returns: true) do
+ with_structure_dump_flags(["--noop"]) do
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
+ end
+ end
+ end
+
def test_structure_dump_with_schema_search_path
@configuration["schema_search_path"] = "foo,bar"
@@ -263,7 +273,6 @@ if current_adapter?(:PostgreSQLAdapter)
end
private
-
def with_dump_schemas(value, &block)
old_dump_schemas = ActiveRecord::Base.dump_schemas
ActiveRecord::Base.dump_schemas = value
@@ -271,6 +280,14 @@ if current_adapter?(:PostgreSQLAdapter)
ensure
ActiveRecord::Base.dump_schemas = old_dump_schemas
end
+
+ def with_structure_dump_flags(flags)
+ old = ActiveRecord::Tasks::DatabaseTasks.structure_dump_flags
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump_flags = flags
+ yield
+ ensure
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump_flags = old
+ end
end
class PostgreSQLStructureLoadTest < ActiveRecord::TestCase
@@ -293,12 +310,32 @@ if current_adapter?(:PostgreSQLAdapter)
ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
end
+ def test_structure_load_with_extra_flags
+ filename = "awesome-file.sql"
+ expected_command = ["psql", "-v", "ON_ERROR_STOP=1", "-q", "-f", filename, "--noop", @configuration["database"]]
+
+ assert_called_with(Kernel, :system, expected_command, returns: true) do
+ with_structure_load_flags(["--noop"]) do
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
+ end
+ end
+ end
+
def test_structure_load_accepts_path_with_spaces
filename = "awesome file.sql"
Kernel.expects(:system).with("psql", "-v", "ON_ERROR_STOP=1", "-q", "-f", filename, @configuration["database"]).returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
end
+
+ private
+ def with_structure_load_flags(flags)
+ old = ActiveRecord::Tasks::DatabaseTasks.structure_load_flags
+ ActiveRecord::Tasks::DatabaseTasks.structure_load_flags = flags
+ yield
+ ensure
+ ActiveRecord::Tasks::DatabaseTasks.structure_load_flags = old
+ end
end
end
end
diff --git a/activerecord/test/cases/tasks/sqlite_rake_test.rb b/activerecord/test/cases/tasks/sqlite_rake_test.rb
index 141048bfe7..0d917f3f6c 100644
--- a/activerecord/test/cases/tasks/sqlite_rake_test.rb
+++ b/activerecord/test/cases/tasks/sqlite_rake_test.rb
@@ -34,7 +34,7 @@ if current_adapter?(:SQLite3Adapter)
def test_when_db_created_successfully_outputs_info_to_stdout
ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root"
- assert_equal $stdout.string, "Created database '#{@database}'\n"
+ assert_equal "Created database '#{@database}'\n", $stdout.string
end
def test_db_create_when_file_exists
@@ -42,7 +42,7 @@ if current_adapter?(:SQLite3Adapter)
ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root"
- assert_equal $stderr.string, "Database '#{@database}' already exists\n"
+ assert_equal "Database '#{@database}' already exists\n", $stderr.string
end
def test_db_create_with_file_does_nothing
@@ -128,7 +128,7 @@ if current_adapter?(:SQLite3Adapter)
def test_when_db_dropped_successfully_outputs_info_to_stdout
ActiveRecord::Tasks::DatabaseTasks.drop @configuration, "/rails/root"
- assert_equal $stdout.string, "Dropped database '#{@database}'\n"
+ assert_equal "Dropped database '#{@database}'\n", $stdout.string
end
end
diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb
index b4f5226f2b..31b11c19f7 100644
--- a/activerecord/test/cases/test_case.rb
+++ b/activerecord/test/cases/test_case.rb
@@ -1,13 +1,28 @@
require "active_support/test_case"
+require "active_support/testing/autorun"
+require "active_support/testing/method_call_assertions"
require "active_support/testing/stream"
-require "active_support/core_ext/regexp"
+require "active_record/fixtures"
+
+require "cases/validations_repair_helper"
module ActiveRecord
# = Active Record Test Case
#
# Defines some test assertions to test against SQL queries.
class TestCase < ActiveSupport::TestCase #:nodoc:
+ include ActiveSupport::Testing::MethodCallAssertions
include ActiveSupport::Testing::Stream
+ include ActiveRecord::TestFixtures
+ include ActiveRecord::ValidationsRepairHelper
+
+ self.fixture_path = FIXTURES_ROOT
+ self.use_instantiated_fixtures = false
+ self.use_transactional_tests = true
+
+ def create_fixtures(*fixture_set_names, &block)
+ ActiveRecord::FixtureSet.create_fixtures(ActiveRecord::TestCase.fixture_path, fixture_set_names, fixture_class_names, &block)
+ end
def teardown
SQLCounter.clear_log
@@ -24,7 +39,7 @@ module ActiveRecord
ensure
failed_patterns = []
patterns_to_match.each do |pattern|
- failed_patterns << pattern unless SQLCounter.log_all.any?{ |sql| pattern === sql }
+ failed_patterns << pattern unless SQLCounter.log_all.any? { |sql| pattern === sql }
end
assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map(&:inspect).join(', ')} not found.#{SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{SQLCounter.log.join("\n")}"}"
end
@@ -48,11 +63,11 @@ module ActiveRecord
assert_queries(0, options, &block)
end
- def assert_column(model, column_name, msg=nil)
+ def assert_column(model, column_name, msg = nil)
assert has_column?(model, column_name), msg
end
- def assert_no_column(model, column_name, msg=nil)
+ def assert_no_column(model, column_name, msg = nil)
assert_not has_column?(model, column_name), msg
end
@@ -109,12 +124,9 @@ module ActiveRecord
end
def call(name, start, finish, message_id, values)
- sql = values[:sql]
-
- # FIXME: this seems bad. we should probably have a better way to indicate
- # the query was cached
- return if "CACHE" == values[:name]
+ return if values[:cached]
+ sql = values[:sql]
self.class.log_all << sql
self.class.log << sql unless ignore.match?(sql)
end
diff --git a/activerecord/test/cases/test_fixtures_test.rb b/activerecord/test/cases/test_fixtures_test.rb
index 14a5faa85e..58d3bea3a2 100644
--- a/activerecord/test/cases/test_fixtures_test.rb
+++ b/activerecord/test/cases/test_fixtures_test.rb
@@ -3,28 +3,10 @@ require "cases/helper"
class TestFixturesTest < ActiveRecord::TestCase
setup do
@klass = Class.new
- @klass.send(:include, ActiveRecord::TestFixtures)
- end
-
- def test_deprecated_use_transactional_fixtures=
- assert_deprecated "use use_transactional_tests= instead" do
- @klass.use_transactional_fixtures = true
- end
- end
-
- def test_use_transactional_tests_prefers_use_transactional_fixtures
- ActiveSupport::Deprecation.silence do
- @klass.use_transactional_fixtures = false
- end
-
- assert_equal false, @klass.use_transactional_tests
+ @klass.include(ActiveRecord::TestFixtures)
end
def test_use_transactional_tests_defaults_to_true
- ActiveSupport::Deprecation.silence do
- @klass.use_transactional_fixtures = nil
- end
-
assert_equal true, @klass.use_transactional_tests
end
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 519d9db601..7766a74612 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -430,39 +430,24 @@ class TimestampTest < ActiveRecord::TestCase
assert_not_equal person.born_at, nil
end
- def test_timestamp_attributes_for_create
- toy = Toy.first
- assert_equal [:created_at, :created_on], toy.send(:timestamp_attributes_for_create)
- end
-
- def test_timestamp_attributes_for_update
- toy = Toy.first
- assert_equal [:updated_at, :updated_on], toy.send(:timestamp_attributes_for_update)
- end
-
- def test_all_timestamp_attributes
- toy = Toy.first
- assert_equal [:created_at, :created_on, :updated_at, :updated_on], toy.send(:all_timestamp_attributes)
- end
-
def test_timestamp_attributes_for_create_in_model
toy = Toy.first
- assert_equal [:created_at], toy.send(:timestamp_attributes_for_create_in_model)
+ assert_equal ["created_at"], toy.send(:timestamp_attributes_for_create_in_model)
end
def test_timestamp_attributes_for_update_in_model
toy = Toy.first
- assert_equal [:updated_at], toy.send(:timestamp_attributes_for_update_in_model)
+ assert_equal ["updated_at"], toy.send(:timestamp_attributes_for_update_in_model)
end
def test_all_timestamp_attributes_in_model
toy = Toy.first
- assert_equal [:created_at, :updated_at], toy.send(:all_timestamp_attributes_in_model)
+ assert_equal ["created_at", "updated_at"], toy.send(:all_timestamp_attributes_in_model)
end
def test_index_is_created_for_both_timestamps
ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
- t.timestamps(:foos, null: true, index: true)
+ t.timestamps null: true, index: true
end
indexes = ActiveRecord::Base.connection.indexes("foos")
diff --git a/activerecord/test/cases/touch_later_test.rb b/activerecord/test/cases/touch_later_test.rb
index 697447a4f3..d1e8c649d9 100644
--- a/activerecord/test/cases/touch_later_test.rb
+++ b/activerecord/test/cases/touch_later_test.rb
@@ -24,6 +24,15 @@ class TouchLaterTest < ActiveRecord::TestCase
assert_not invoice.changed?
end
+ def test_touch_later_respects_no_touching_policy
+ time = Time.now.utc - 25.days
+ topic = Topic.create!(updated_at: time, created_at: time)
+ Topic.no_touching do
+ topic.touch_later
+ end
+ assert_equal time.to_i, topic.updated_at.to_i
+ end
+
def test_touch_later_update_the_attributes
time = Time.now.utc - 25.days
topic = Topic.create!(updated_at: time, created_at: time)
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index 6a7d48871c..391bbe8877 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -68,17 +68,17 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
def do_before_commit(on)
blocks = @before_commit[on] if defined?(@before_commit)
- blocks.each{|b| b.call(self)} if blocks
+ blocks.each { |b| b.call(self) } if blocks
end
def do_after_commit(on)
blocks = @after_commit[on] if defined?(@after_commit)
- blocks.each{|b| b.call(self)} if blocks
+ blocks.each { |b| b.call(self) } if blocks
end
def do_after_rollback(on)
blocks = @after_rollback[on] if defined?(@after_rollback)
- blocks.each{|b| b.call(self)} if blocks
+ blocks.each { |b| b.call(self) } if blocks
end
end
@@ -101,8 +101,8 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
end
def test_call_after_commit_after_transaction_commits
- @first.after_commit_block{|r| r.history << :after_commit}
- @first.after_rollback_block{|r| r.history << :after_rollback}
+ @first.after_commit_block { |r| r.history << :after_commit }
+ @first.after_rollback_block { |r| r.history << :after_rollback }
@first.save!
assert_equal [:after_commit], @first.history
@@ -155,7 +155,7 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
end
def test_only_call_after_commit_on_top_level_transactions
- @first.after_commit_block{|r| r.history << :after_commit}
+ @first.after_commit_block { |r| r.history << :after_commit }
assert @first.history.empty?
@first.transaction do
@@ -168,8 +168,8 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
end
def test_call_after_rollback_after_transaction_rollsback
- @first.after_commit_block{|r| r.history << :after_commit}
- @first.after_rollback_block{|r| r.history << :after_rollback}
+ @first.after_commit_block { |r| r.history << :after_commit }
+ @first.after_rollback_block { |r| r.history << :after_rollback }
Topic.transaction do
@first.save!
@@ -243,16 +243,16 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
end
def test_only_call_after_rollback_on_records_rolled_back_to_a_savepoint
- def @first.rollbacks(i=0); @rollbacks ||= 0; @rollbacks += i if i; end
- def @first.commits(i=0); @commits ||= 0; @commits += i if i; end
- @first.after_rollback_block{|r| r.rollbacks(1)}
- @first.after_commit_block{|r| r.commits(1)}
+ def @first.rollbacks(i = 0); @rollbacks ||= 0; @rollbacks += i if i; end
+ def @first.commits(i = 0); @commits ||= 0; @commits += i if i; end
+ @first.after_rollback_block { |r| r.rollbacks(1) }
+ @first.after_commit_block { |r| r.commits(1) }
second = TopicWithCallbacks.find(3)
- def second.rollbacks(i=0); @rollbacks ||= 0; @rollbacks += i if i; end
- def second.commits(i=0); @commits ||= 0; @commits += i if i; end
- second.after_rollback_block{|r| r.rollbacks(1)}
- second.after_commit_block{|r| r.commits(1)}
+ def second.rollbacks(i = 0); @rollbacks ||= 0; @rollbacks += i if i; end
+ def second.commits(i = 0); @commits ||= 0; @commits += i if i; end
+ second.after_rollback_block { |r| r.rollbacks(1) }
+ second.after_commit_block { |r| r.commits(1) }
Topic.transaction do
@first.save!
@@ -269,11 +269,11 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
end
def test_only_call_after_rollback_on_records_rolled_back_to_a_savepoint_when_release_savepoint_fails
- def @first.rollbacks(i=0); @rollbacks ||= 0; @rollbacks += i if i; end
- def @first.commits(i=0); @commits ||= 0; @commits += i if i; end
+ def @first.rollbacks(i = 0); @rollbacks ||= 0; @rollbacks += i if i; end
+ def @first.commits(i = 0); @commits ||= 0; @commits += i if i; end
- @first.after_rollback_block{|r| r.rollbacks(1)}
- @first.after_commit_block{|r| r.commits(1)}
+ @first.after_rollback_block { |r| r.rollbacks(1) }
+ @first.after_commit_block { |r| r.commits(1) }
Topic.transaction do
@first.save
@@ -292,7 +292,7 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
end
def test_after_commit_callback_should_not_swallow_errors
- @first.after_commit_block{ fail "boom" }
+ @first.after_commit_block { fail "boom" }
assert_raises(RuntimeError) do
Topic.transaction do
@first.save!
@@ -303,8 +303,8 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
def test_after_commit_callback_when_raise_should_not_restore_state
first = TopicWithCallbacks.new
second = TopicWithCallbacks.new
- first.after_commit_block{ fail "boom" }
- second.after_commit_block{ fail "boom" }
+ first.after_commit_block { fail "boom" }
+ second.after_commit_block { fail "boom" }
begin
Topic.transaction do
@@ -322,7 +322,7 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
def test_after_rollback_callback_should_not_swallow_errors_when_set_to_raise
error_class = Class.new(StandardError)
- @first.after_rollback_block{ raise error_class }
+ @first.after_rollback_block { raise error_class }
assert_raises(error_class) do
Topic.transaction do
@first.save!
@@ -336,8 +336,8 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
first = TopicWithCallbacks.new
second = TopicWithCallbacks.new
- first.after_rollback_block{ raise error_class }
- second.after_rollback_block{ raise error_class }
+ first.after_rollback_block { raise error_class }
+ second.after_rollback_block { raise error_class }
begin
Topic.transaction do
@@ -449,6 +449,51 @@ class CallbacksOnMultipleActionsTest < ActiveRecord::TestCase
end
end
+class CallbacksOnDestroyUpdateActionRaceTest < ActiveRecord::TestCase
+ class TopicWithHistory < ActiveRecord::Base
+ self.table_name = :topics
+
+ def self.clear_history
+ @@history = []
+ end
+
+ def self.history
+ @@history ||= []
+ end
+ end
+
+ class TopicWithCallbacksOnDestroy < TopicWithHistory
+ after_commit(on: :destroy) { |record| record.class.history << :destroy }
+ end
+
+ class TopicWithCallbacksOnUpdate < TopicWithHistory
+ after_commit(on: :update) { |record| record.class.history << :update }
+ end
+
+ def test_trigger_once_on_multiple_deletions
+ TopicWithCallbacksOnDestroy.clear_history
+ topic = TopicWithCallbacksOnDestroy.new
+ topic.save
+ topic_clone = TopicWithCallbacksOnDestroy.find(topic.id)
+ topic.destroy
+ topic_clone.destroy
+
+ assert_equal [:destroy], TopicWithCallbacksOnDestroy.history
+ end
+
+ def test_trigger_on_update_where_row_was_deleted
+ TopicWithCallbacksOnUpdate.clear_history
+ topic = TopicWithCallbacksOnUpdate.new
+ topic.save
+ topic_clone = TopicWithCallbacksOnUpdate.find(topic.id)
+ topic.destroy
+ topic_clone.author_name = "Test Author"
+ topic_clone.save
+
+ assert_equal [], TopicWithCallbacksOnUpdate.history
+ end
+end
+
class TransactionEnrollmentCallbacksTest < ActiveRecord::TestCase
class TopicWithoutTransactionalEnrollmentCallbacks < ActiveRecord::Base
self.table_name = :topics
diff --git a/activerecord/test/cases/transaction_isolation_test.rb b/activerecord/test/cases/transaction_isolation_test.rb
index f177f0644a..58abfadaf4 100644
--- a/activerecord/test/cases/transaction_isolation_test.rb
+++ b/activerecord/test/cases/transaction_isolation_test.rb
@@ -9,7 +9,7 @@ unless ActiveRecord::Base.connection.supports_transaction_isolation?
test "setting the isolation level raises an error" do
assert_raises(ActiveRecord::TransactionIsolationError) do
- Tag.transaction(isolation: :serializable) { }
+ Tag.transaction(isolation: :serializable) {}
end
end
end
@@ -90,7 +90,7 @@ if ActiveRecord::Base.connection.supports_transaction_isolation?
test "setting isolation when joining a transaction raises an error" do
Tag.transaction do
assert_raises(ActiveRecord::TransactionIsolationError) do
- Tag.transaction(isolation: :serializable) { }
+ Tag.transaction(isolation: :serializable) {}
end
end
end
@@ -98,7 +98,7 @@ if ActiveRecord::Base.connection.supports_transaction_isolation?
test "setting isolation when starting a nested transaction raises error" do
Tag.transaction do
assert_raises(ActiveRecord::TransactionIsolationError) do
- Tag.transaction(requires_new: true, isolation: :serializable) { }
+ Tag.transaction(requires_new: true, isolation: :serializable) {}
end
end
end
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index c8963ada63..9b1cca8583 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -98,7 +98,7 @@ class TransactionTest < ActiveRecord::TestCase
end
Topic.transaction do
- @first.approved = true
+ @first.approved = true
@first.save!
end
@@ -160,7 +160,7 @@ class TransactionTest < ActiveRecord::TestCase
assert !@first.approved
Topic.transaction do
- @first.approved = true
+ @first.approved = true
@first.save!
end
assert !Topic.find(@first.id).approved?, "Should not commit the approved flag"
@@ -443,16 +443,16 @@ class TransactionTest < ActiveRecord::TestCase
def test_using_named_savepoints
Topic.transaction do
- @first.approved = true
+ @first.approved = true
@first.save!
Topic.connection.create_savepoint("first")
- @first.approved = false
+ @first.approved = false
@first.save!
Topic.connection.rollback_to_savepoint("first")
assert @first.reload.approved?
- @first.approved = false
+ @first.approved = false
@first.save!
Topic.connection.release_savepoint("first")
assert_not @first.reload.approved?
@@ -493,7 +493,7 @@ class TransactionTest < ActiveRecord::TestCase
def test_rollback_when_commit_raises
assert_called(Topic.connection, :begin_db_transaction) do
- Topic.connection.stub(:commit_db_transaction, ->{ raise("OH NOES") }) do
+ Topic.connection.stub(:commit_db_transaction, -> { raise("OH NOES") }) do
assert_called(Topic.connection, :rollback_db_transaction) do
e = assert_raise RuntimeError do
@@ -511,9 +511,10 @@ class TransactionTest < ActiveRecord::TestCase
topic = Topic.new(title: "test")
topic.freeze
e = assert_raise(RuntimeError) { topic.save }
- assert_match(/frozen/i, e.message) # Not good enough, but we can't do much
- # about it since there is no specific error
- # for frozen objects.
+ # Not good enough, but we can't do much
+ # about it since there is no specific error
+ # for frozen objects.
+ assert_match(/frozen/i, e.message)
assert !topic.persisted?, "not persisted"
assert_nil topic.id
assert topic.frozen?, "not frozen"
@@ -630,11 +631,11 @@ class TransactionTest < ActiveRecord::TestCase
assert_nothing_raised do
Topic.reset_column_information
Topic.connection.add_column("topics", "stuff", :string)
- assert Topic.column_names.include?("stuff")
+ assert_includes Topic.column_names, "stuff"
Topic.reset_column_information
Topic.connection.remove_column("topics", "stuff")
- assert !Topic.column_names.include?("stuff")
+ assert_not_includes Topic.column_names, "stuff"
end
if Topic.connection.supports_ddl_transactions?
@@ -692,7 +693,7 @@ class TransactionTest < ActiveRecord::TestCase
klass = Class.new(ActiveRecord::Base) do
self.table_name = "transaction_without_primary_keys"
- after_commit { } # necessary to trigger the has_transactional_callbacks branch
+ after_commit {} # necessary to trigger the has_transactional_callbacks branch
end
assert_no_difference(-> { klass.count }) do
diff --git a/activerecord/test/cases/type/date_time_test.rb b/activerecord/test/cases/type/date_time_test.rb
index bc4900e1c2..6848619ece 100644
--- a/activerecord/test/cases/type/date_time_test.rb
+++ b/activerecord/test/cases/type/date_time_test.rb
@@ -3,7 +3,7 @@ require "models/task"
module ActiveRecord
module Type
- class IntegerTest < ActiveRecord::TestCase
+ class DateTimeTest < ActiveRecord::TestCase
def test_datetime_seconds_precision_applied_to_timestamp
skip "This test is invalid if subsecond precision isn't supported" unless subsecond_precision_supported?
p = Task.create!(starting: ::Time.now)
diff --git a/activemodel/test/cases/type/unsigned_integer_test.rb b/activerecord/test/cases/type/unsigned_integer_test.rb
index 026cb08a06..1cd4dbc2c5 100644
--- a/activemodel/test/cases/type/unsigned_integer_test.rb
+++ b/activerecord/test/cases/type/unsigned_integer_test.rb
@@ -1,9 +1,8 @@
require "cases/helper"
-require "active_model/type"
-module ActiveModel
+module ActiveRecord
module Type
- class UnsignedIntegerTest < ActiveModel::TestCase
+ class UnsignedIntegerTest < ActiveRecord::TestCase
test "unsigned int max value is in range" do
assert_equal(4294967295, UnsignedInteger.new.serialize(4294967295))
end
diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb
index 66d6ecb928..f5ceb27d97 100644
--- a/activerecord/test/cases/validations/association_validation_test.rb
+++ b/activerecord/test/cases/validations/association_validation_test.rb
@@ -26,7 +26,7 @@ class AssociationValidationTest < ActiveRecord::TestCase
def test_validates_associated_one
Reply.validates :topic, associated: true
- Topic.validates_presence_of( :content )
+ Topic.validates_presence_of(:content)
r = Reply.new("title" => "A reply", "content" => "with content!")
r.topic = Topic.create("title" => "uhohuhoh")
assert !r.valid?
diff --git a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
index d73d287073..a57065ba75 100644
--- a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
@@ -47,7 +47,7 @@ class I18nGenerateMessageValidationTest < ActiveRecord::TestCase
test "RecordInvalid exception translation falls back to the :errors namespace" do
reset_i18n_load_path do
- I18n.backend.store_translations "en", errors: {messages: {record_invalid: "fallback message"}}
+ I18n.backend.store_translations "en", errors: { messages: { record_invalid: "fallback message" } }
topic = Topic.new
topic.errors.add(:title, :blank)
assert_equal "fallback message", ActiveRecord::RecordInvalid.new(topic).message
@@ -56,28 +56,28 @@ class I18nGenerateMessageValidationTest < ActiveRecord::TestCase
test "translation for 'taken' can be overridden" do
reset_i18n_load_path do
- I18n.backend.store_translations "en", errors: {attributes: {title: {taken: "Custom taken message" }}}
+ I18n.backend.store_translations "en", errors: { attributes: { title: { taken: "Custom taken message" } } }
assert_equal "Custom taken message", @topic.errors.generate_message(:title, :taken, value: "title")
end
end
test "translation for 'taken' can be overridden in activerecord scope" do
reset_i18n_load_path do
- I18n.backend.store_translations "en", activerecord: {errors: {messages: {taken: "Custom taken message" }}}
+ I18n.backend.store_translations "en", activerecord: { errors: { messages: { taken: "Custom taken message" } } }
assert_equal "Custom taken message", @topic.errors.generate_message(:title, :taken, value: "title")
end
end
test "translation for 'taken' can be overridden in activerecord model scope" do
reset_i18n_load_path do
- I18n.backend.store_translations "en", activerecord: {errors: {models: {topic: {taken: "Custom taken message" }}}}
+ I18n.backend.store_translations "en", activerecord: { errors: { models: { topic: { taken: "Custom taken message" } } } }
assert_equal "Custom taken message", @topic.errors.generate_message(:title, :taken, value: "title")
end
end
test "translation for 'taken' can be overridden in activerecord attributes scope" do
reset_i18n_load_path do
- I18n.backend.store_translations "en", activerecord: {errors: {models: {topic: {attributes: {title: {taken: "Custom taken message" }}}}}}
+ I18n.backend.store_translations "en", activerecord: { errors: { models: { topic: { attributes: { title: { taken: "Custom taken message" } } } } } }
assert_equal "Custom taken message", @topic.errors.generate_message(:title, :taken, value: "title")
end
end
diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb
index cbb5362f3f..fd88a3ea67 100644
--- a/activerecord/test/cases/validations/i18n_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_validation_test.rb
@@ -12,7 +12,7 @@ class I18nValidationTest < ActiveRecord::TestCase
@old_load_path, @old_backend = I18n.load_path.dup, I18n.backend
I18n.load_path.clear
I18n.backend = I18n::Backend::Simple.new
- I18n.backend.store_translations("en", errors: {messages: {custom: nil}})
+ I18n.backend.store_translations("en", errors: { messages: { custom: nil } })
end
teardown do
@@ -36,13 +36,13 @@ class I18nValidationTest < ActiveRecord::TestCase
# are used to generate tests to keep things DRY
#
COMMON_CASES = [
- # [ case, validation_options, generate_message_options]
+ # [ case, validation_options, generate_message_options]
[ "given no options", {}, {}],
- [ "given custom message", {message: "custom"}, {message: "custom"}],
- [ "given if condition", {if: lambda { true }}, {}],
- [ "given unless condition", {unless: lambda { false }}, {}],
- [ "given option that is not reserved", {format: "jpg"}, {format: "jpg" }],
- [ "given on condition", {on: [:create, :update] }, {}]
+ [ "given custom message", { message: "custom" }, { message: "custom" }],
+ [ "given if condition", { if: lambda { true } }, {}],
+ [ "given unless condition", { unless: lambda { false } }, {}],
+ [ "given option that is not reserved", { format: "jpg" }, { format: "jpg" }],
+ [ "given on condition", { on: [:create, :update] }, {}]
]
COMMON_CASES.each do |name, validation_options, generate_message_options|
@@ -65,8 +65,8 @@ class I18nValidationTest < ActiveRecord::TestCase
end
def test_validates_associated_finds_custom_model_key_translation
- I18n.backend.store_translations "en", activerecord: {errors: {models: {topic: {attributes: {replies: {invalid: "custom message"}}}}}}
- I18n.backend.store_translations "en", activerecord: {errors: {messages: {invalid: "global message"}}}
+ I18n.backend.store_translations "en", activerecord: { errors: { models: { topic: { attributes: { replies: { invalid: "custom message" } } } } } }
+ I18n.backend.store_translations "en", activerecord: { errors: { messages: { invalid: "global message" } } }
Topic.validates_associated :replies
replied_topic.valid?
@@ -74,7 +74,7 @@ class I18nValidationTest < ActiveRecord::TestCase
end
def test_validates_associated_finds_global_default_translation
- I18n.backend.store_translations "en", activerecord: {errors: {messages: {invalid: "global message"}}}
+ I18n.backend.store_translations "en", activerecord: { errors: { messages: { invalid: "global message" } } }
Topic.validates_associated :replies
replied_topic.valid?
diff --git a/activerecord/test/cases/validations/length_validation_test.rb b/activerecord/test/cases/validations/length_validation_test.rb
index 4fcb3e2eb6..ba45c6dcc1 100644
--- a/activerecord/test/cases/validations/length_validation_test.rb
+++ b/activerecord/test/cases/validations/length_validation_test.rb
@@ -54,7 +54,7 @@ class LengthValidationTest < ActiveRecord::TestCase
assert owner.save
pet_count = Pet.count
- assert_not owner.update_attributes pets_attributes: [ {_destroy: 1, id: pet.id} ]
+ assert_not owner.update_attributes pets_attributes: [ { _destroy: 1, id: pet.id } ]
assert_not owner.valid?
assert owner.errors[:pets].any?
assert_equal pet_count, Pet.count
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 44b4e28777..6d22638592 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -6,6 +6,9 @@ require "models/guid"
require "models/event"
require "models/dashboard"
require "models/uuid_item"
+require "models/author"
+require "models/person"
+require "models/essay"
class Wizard < ActiveRecord::Base
self.abstract_class = true
@@ -163,6 +166,19 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert !r2.valid?, "Saving r2 first time"
end
+ def test_validate_uniqueness_with_polymorphic_object_scope
+ Essay.validates_uniqueness_of(:name, scope: :writer)
+
+ a = Author.create(name: "Sergey")
+ p = Person.create(first_name: "Sergey")
+
+ e1 = a.essays.create(name: "Essay")
+ assert e1.valid?, "Saving e1"
+
+ e2 = p.essays.create(name: "Essay")
+ assert e2.valid?, "Saving e2"
+ end
+
def test_validate_uniqueness_with_composed_attribute_scope
r1 = ReplyWithTitleObject.create "title" => "r1", "content" => "hello world"
assert r1.valid?, "Saving r1"
diff --git a/activerecord/test/cases/view_test.rb b/activerecord/test/cases/view_test.rb
index 5f85c5ff8b..d055968a56 100644
--- a/activerecord/test/cases/view_test.rb
+++ b/activerecord/test/cases/view_test.rb
@@ -11,20 +11,21 @@ module ViewBehavior
end
class Ebook < ActiveRecord::Base
+ self.table_name = "ebooks'"
self.primary_key = "id"
end
def setup
super
@connection = ActiveRecord::Base.connection
- create_view "ebooks", <<-SQL
+ create_view "ebooks'", <<-SQL
SELECT id, name, status FROM books WHERE format = 'ebook'
SQL
end
def teardown
super
- drop_view "ebooks"
+ drop_view "ebooks'"
end
def test_reading
@@ -44,8 +45,7 @@ module ViewBehavior
def test_table_exists
view_name = Ebook.table_name
- # TODO: switch this assertion around once we changed #tables to not return views.
- ActiveSupport::Deprecation.silence { assert @connection.table_exists?(view_name), "'#{view_name}' table should exist" }
+ assert_not @connection.table_exists?(view_name), "'#{view_name}' table should not exist"
end
def test_views_ara_valid_data_sources
@@ -60,21 +60,26 @@ module ViewBehavior
end
def test_attributes
- assert_equal({"id" => 2, "name" => "Ruby for Rails", "status" => 0},
+ assert_equal({ "id" => 2, "name" => "Ruby for Rails", "status" => 0 },
Ebook.first.attributes)
end
def test_does_not_assume_id_column_as_primary_key
model = Class.new(ActiveRecord::Base) do
- self.table_name = "ebooks"
+ self.table_name = "ebooks'"
end
assert_nil model.primary_key
end
def test_does_not_dump_view_as_table
- schema = dump_table_schema "ebooks"
- assert_no_match %r{create_table "ebooks"}, schema
+ schema = dump_table_schema "ebooks'"
+ assert_no_match %r{create_table "ebooks'"}, schema
end
+
+ private
+ def quote_table_name(name)
+ @connection.quote_table_name(name)
+ end
end
if ActiveRecord::Base.connection.supports_views?
@@ -83,11 +88,11 @@ if ActiveRecord::Base.connection.supports_views?
private
def create_view(name, query)
- @connection.execute "CREATE VIEW #{name} AS #{query}"
+ @connection.execute "CREATE VIEW #{quote_table_name(name)} AS #{query}"
end
def drop_view(name)
- @connection.execute "DROP VIEW #{name}" if @connection.view_exists? name
+ @connection.execute "DROP VIEW #{quote_table_name(name)}" if @connection.view_exists? name
end
end
@@ -100,9 +105,9 @@ if ActiveRecord::Base.connection.supports_views?
setup do
@connection = ActiveRecord::Base.connection
@connection.execute <<-SQL
- CREATE VIEW paperbacks
- AS SELECT name, status FROM books WHERE format = 'paperback'
- SQL
+ CREATE VIEW paperbacks
+ AS SELECT name, status FROM books WHERE format = 'paperback'
+ SQL
end
teardown do
@@ -125,8 +130,7 @@ if ActiveRecord::Base.connection.supports_views?
def test_table_exists
view_name = Paperback.table_name
- # TODO: switch this assertion around once we changed #tables to not return views.
- ActiveSupport::Deprecation.silence { assert @connection.table_exists?(view_name), "'#{view_name}' table should exist" }
+ assert_not @connection.table_exists?(view_name), "'#{view_name}' table should not exist"
end
def test_column_definitions
@@ -135,7 +139,7 @@ if ActiveRecord::Base.connection.supports_views?
end
def test_attributes
- assert_equal({"name" => "Agile Web Development with Rails", "status" => 2},
+ assert_equal({ "name" => "Agile Web Development with Rails", "status" => 2 },
Paperback.first.attributes)
end
@@ -149,7 +153,7 @@ if ActiveRecord::Base.connection.supports_views?
end
end
-# sqlite dose not support CREATE, INSERT, and DELETE for VIEW
+ # sqlite dose not support CREATE, INSERT, and DELETE for VIEW
if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
class UpdateableViewTest < ActiveRecord::TestCase
self.use_transactional_tests = false
@@ -162,9 +166,9 @@ if ActiveRecord::Base.connection.supports_views?
setup do
@connection = ActiveRecord::Base.connection
@connection.execute <<-SQL
- CREATE VIEW printed_books
- AS SELECT id, name, status, format FROM books WHERE format = 'paperback'
- SQL
+ CREATE VIEW printed_books
+ AS SELECT id, name, status, format FROM books WHERE format = 'paperback'
+ SQL
end
teardown do
@@ -196,8 +200,8 @@ if ActiveRecord::Base.connection.supports_views?
end
end
end
- end # end fo `if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)`
-end # end fo `if ActiveRecord::Base.connection.supports_views?`
+ end # end of `if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)`
+end # end of `if ActiveRecord::Base.connection.supports_views?`
if ActiveRecord::Base.connection.respond_to?(:supports_materialized_views?) &&
ActiveRecord::Base.connection.supports_materialized_views?
@@ -206,11 +210,11 @@ if ActiveRecord::Base.connection.respond_to?(:supports_materialized_views?) &&
private
def create_view(name, query)
- @connection.execute "CREATE MATERIALIZED VIEW #{name} AS #{query}"
+ @connection.execute "CREATE MATERIALIZED VIEW #{quote_table_name(name)} AS #{query}"
end
def drop_view(name)
- @connection.execute "DROP MATERIALIZED VIEW #{name}" if @connection.view_exists? name
+ @connection.execute "DROP MATERIALIZED VIEW #{quote_table_name(name)}" if @connection.view_exists? name
end
end
end
diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb
index ade6e6095b..1571b31329 100644
--- a/activerecord/test/cases/yaml_serialization_test.rb
+++ b/activerecord/test/cases/yaml_serialization_test.rb
@@ -22,8 +22,8 @@ class YamlSerializationTest < ActiveRecord::TestCase
end
def test_roundtrip_serialized_column
- topic = Topic.new(content: {omg: :lol})
- assert_equal({omg: :lol}, YAML.load(YAML.dump(topic)).content)
+ topic = Topic.new(content: { omg: :lol })
+ assert_equal({ omg: :lol }, YAML.load(YAML.dump(topic)).content)
end
def test_psych_roundtrip
@@ -95,7 +95,7 @@ class YamlSerializationTest < ActiveRecord::TestCase
topic = YAML.load(yaml_fixture("rails_4_1"))
assert topic.new_record?
- assert_equal nil, topic.id
+ assert_nil topic.id
assert_equal "The First Topic", topic.title
assert_equal({ omg: :lol }, topic.content)
end
diff --git a/activerecord/test/config.example.yml b/activerecord/test/config.example.yml
index 58e2d45748..4bcb2aeea6 100644
--- a/activerecord/test/config.example.yml
+++ b/activerecord/test/config.example.yml
@@ -77,6 +77,9 @@ connections:
postgresql:
arunit:
min_messages: warning
+ arunit_without_prepared_statements:
+ min_messages: warning
+ prepared_statements: false
arunit2:
min_messages: warning
diff --git a/activerecord/test/fixtures/all/namespaced/accounts.yml b/activerecord/test/fixtures/all/namespaced/accounts.yml
new file mode 100644
index 0000000000..9e341a15af
--- /dev/null
+++ b/activerecord/test/fixtures/all/namespaced/accounts.yml
@@ -0,0 +1,2 @@
+signals37:
+ name: 37signals
diff --git a/activerecord/test/fixtures/books.yml b/activerecord/test/fixtures/books.yml
index a304fba399..b3625ee72e 100644
--- a/activerecord/test/fixtures/books.yml
+++ b/activerecord/test/fixtures/books.yml
@@ -9,6 +9,7 @@ awdr:
author_visibility: :visible
illustrator_visibility: :visible
font_size: :medium
+ difficulty: :medium
rfr:
author_id: 1
diff --git a/activerecord/test/fixtures/naked/yml/courses_with_invalid_key.yml b/activerecord/test/fixtures/naked/yml/courses_with_invalid_key.yml
new file mode 100644
index 0000000000..6f9da79b45
--- /dev/null
+++ b/activerecord/test/fixtures/naked/yml/courses_with_invalid_key.yml
@@ -0,0 +1,3 @@
+one:
+ id: 1
+two: ['not a hash']
diff --git a/activerecord/test/fixtures/other_dogs.yml b/activerecord/test/fixtures/other_dogs.yml
new file mode 100644
index 0000000000..b576861929
--- /dev/null
+++ b/activerecord/test/fixtures/other_dogs.yml
@@ -0,0 +1,2 @@
+lassie:
+ id: 1
diff --git a/activerecord/test/models/admin/user.rb b/activerecord/test/models/admin/user.rb
index 2e703f6219..a76e4b6795 100644
--- a/activerecord/test/models/admin/user.rb
+++ b/activerecord/test/models/admin/user.rb
@@ -22,11 +22,11 @@ class Admin::User < ActiveRecord::Base
store :json_data_empty, accessors: [ :is_a_good_guy ], coder: Coder.new
def phone_number
- read_store_attribute(:settings, :phone_number).gsub(/(\d{3})(\d{3})(\d{4})/,'(\1) \2-\3')
+ read_store_attribute(:settings, :phone_number).gsub(/(\d{3})(\d{3})(\d{4})/, '(\1) \2-\3')
end
def phone_number=(value)
- write_store_attribute(:settings, :phone_number, value && value.gsub(/[^\d]/,""))
+ write_store_attribute(:settings, :phone_number, value && value.gsub(/[^\d]/, ""))
end
def color
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index 8633504d54..fab613afd1 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -67,13 +67,13 @@ class Author < ActiveRecord::Base
before_remove: :log_before_removing,
after_remove: :log_after_removing
has_many :posts_with_proc_callbacks, class_name: "Post",
- before_add: Proc.new {|o, r| o.post_log << "before_adding#{r.id || '<new>'}"},
- after_add: Proc.new {|o, r| o.post_log << "after_adding#{r.id || '<new>'}"},
- before_remove: Proc.new {|o, r| o.post_log << "before_removing#{r.id}"},
- after_remove: Proc.new {|o, r| o.post_log << "after_removing#{r.id}"}
+ before_add: Proc.new { |o, r| o.post_log << "before_adding#{r.id || '<new>'}" },
+ after_add: Proc.new { |o, r| o.post_log << "after_adding#{r.id || '<new>'}" },
+ before_remove: Proc.new { |o, r| o.post_log << "before_removing#{r.id}" },
+ after_remove: Proc.new { |o, r| o.post_log << "after_removing#{r.id}" }
has_many :posts_with_multiple_callbacks, class_name: "Post",
- before_add: [:log_before_adding, Proc.new {|o, r| o.post_log << "before_adding_proc#{r.id || '<new>'}"}],
- after_add: [:log_after_adding, Proc.new {|o, r| o.post_log << "after_adding_proc#{r.id || '<new>'}"}]
+ before_add: [:log_before_adding, Proc.new { |o, r| o.post_log << "before_adding_proc#{r.id || '<new>'}" }],
+ after_add: [:log_after_adding, Proc.new { |o, r| o.post_log << "after_adding_proc#{r.id || '<new>'}" }]
has_many :unchangeable_posts, class_name: "Post", before_add: :raise_exception, after_add: :log_after_adding
has_many :categorizations
diff --git a/activerecord/test/models/book.rb b/activerecord/test/models/book.rb
index edd0281127..17bf3fbcb4 100644
--- a/activerecord/test/models/book.rb
+++ b/activerecord/test/models/book.rb
@@ -8,12 +8,13 @@ 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 }
enum nullable_status: [:single, :married]
enum language: [:english, :spanish, :french], _prefix: :in
enum author_visibility: [:visible, :invisible], _prefix: true
enum illustrator_visibility: [:visible, :invisible], _prefix: true
enum font_size: [:small, :medium, :large], _prefix: :with, _suffix: true
+ enum difficulty: [:easy, :medium, :hard], _suffix: :to_read
enum cover: { hard: "hard", soft: "soft" }
def published!
diff --git a/activerecord/test/models/boolean.rb b/activerecord/test/models/boolean.rb
index 7bae22e5f9..0da228aac2 100644
--- a/activerecord/test/models/boolean.rb
+++ b/activerecord/test/models/boolean.rb
@@ -1,2 +1,5 @@
class Boolean < ActiveRecord::Base
+ def has_fun
+ super
+ end
end
diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb
index 3196207ac9..113d21cb84 100644
--- a/activerecord/test/models/bulb.rb
+++ b/activerecord/test/models/bulb.rb
@@ -50,9 +50,3 @@ class FailedBulb < Bulb
throw(:abort)
end
end
-
-class TrickyBulb < Bulb
- after_create do |record|
- record.car.bulbs.to_a
- end
-end
diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb
index 7315a1af8e..a4b81d56e0 100644
--- a/activerecord/test/models/comment.rb
+++ b/activerecord/test/models/comment.rb
@@ -1,5 +1,5 @@
class Comment < ActiveRecord::Base
- scope :limit_by, lambda {|l| limit(l) }
+ scope :limit_by, lambda { |l| limit(l) }
scope :containing_the_letter_e, -> { where("comments.body LIKE '%e%'") }
scope :not_again, -> { where("comments.body NOT LIKE '%again%'") }
scope :for_first_post, -> { where(post_id: 1) }
@@ -55,6 +55,6 @@ class CommentThatAutomaticallyAltersPostBody < Comment
end
class CommentWithDefaultScopeReferencesAssociation < Comment
- default_scope ->{ includes(:developer).order("developers.name").references(:developer) }
+ default_scope -> { includes(:developer).order("developers.name").references(:developer) }
belongs_to :developer
end
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index 025630087c..4561b3132b 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -151,7 +151,7 @@ class Client < Company
# is calling client.destroy, deleting from the database, or setting
# foreign keys to NULL.
def self.destroyed_client_ids
- @destroyed_client_ids ||= Hash.new { |h,k| h[k] = [] }
+ @destroyed_client_ids ||= Hash.new { |h, k| h[k] = [] }
end
before_destroy do |client|
@@ -199,7 +199,7 @@ class Account < ActiveRecord::Base
alias_attribute :available_credit, :credit_limit
def self.destroyed_account_ids
- @destroyed_account_ids ||= Hash.new { |h,k| h[k] = [] }
+ @destroyed_account_ids ||= Hash.new { |h, k| h[k] = [] }
end
# Test private kernel method through collection proxy using has_many.
diff --git a/activerecord/test/models/company_in_module.rb b/activerecord/test/models/company_in_module.rb
index 682f99e365..0782c1eff4 100644
--- a/activerecord/test/models/company_in_module.rb
+++ b/activerecord/test/models/company_in_module.rb
@@ -88,7 +88,7 @@ module MyApplication
validate :check_empty_credit_limit
- protected
+ private
def check_empty_credit_limit
errors.add("credit_card", :blank) if credit_card.blank?
diff --git a/activerecord/test/models/customer.rb b/activerecord/test/models/customer.rb
index 71afd0866e..60af7c2247 100644
--- a/activerecord/test/models/customer.rb
+++ b/activerecord/test/models/customer.rb
@@ -5,7 +5,7 @@ class Customer < ActiveRecord::Base
composed_of :balance, class_name: "Money", mapping: %w(balance amount), converter: Proc.new(&:to_money)
composed_of :gps_location, allow_nil: true
composed_of :non_blank_gps_location, class_name: "GpsLocation", allow_nil: true, mapping: %w(gps_location gps_location),
- converter: lambda { |gps| self.gps_conversion_was_run = true; gps.blank? ? nil : GpsLocation.new(gps)}
+ converter: lambda { |gps| self.gps_conversion_was_run = true; gps.blank? ? nil : GpsLocation.new(gps) }
composed_of :fullname, mapping: %w(name to_s), constructor: Proc.new { |name| Fullname.parse(name) }, converter: :parse
composed_of :fullname_no_converter, mapping: %w(name to_s), class_name: "Fullname"
end
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index 5ca1d37f6d..ea4f719517 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -251,7 +251,7 @@ class ThreadsafeDeveloper < ActiveRecord::Base
self.table_name = "developers"
def self.default_scope
- sleep 0.05 if Thread.current[:long_default_scope]
+ Thread.current[:default_scope_delay].call
limit(1)
end
end
diff --git a/activerecord/test/models/eye.rb b/activerecord/test/models/eye.rb
index ab3b3eacf3..f53c34e4b1 100644
--- a/activerecord/test/models/eye.rb
+++ b/activerecord/test/models/eye.rb
@@ -22,12 +22,12 @@ class Eye < ActiveRecord::Base
alias trace_after_create2 trace_after_create
def trace_after_update
- (@after_update_callbacks_stack ||= []) << iris.changed?
+ (@after_update_callbacks_stack ||= []) << iris.has_changes_to_save?
end
alias trace_after_update2 trace_after_update
def trace_after_save
- (@after_save_callbacks_stack ||= []) << iris.changed?
+ (@after_save_callbacks_stack ||= []) << iris.has_changes_to_save?
end
alias trace_after_save2 trace_after_save
end
diff --git a/activerecord/test/models/invoice.rb b/activerecord/test/models/invoice.rb
index 44ee717187..4be5a00193 100644
--- a/activerecord/test/models/invoice.rb
+++ b/activerecord/test/models/invoice.rb
@@ -1,4 +1,4 @@
class Invoice < ActiveRecord::Base
has_many :line_items, autosave: true
- before_save {|record| record.balance = record.line_items.map(&:amount).sum }
+ before_save { |record| record.balance = record.line_items.map(&:amount).sum }
end
diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb
index 93dfc8253f..36f2461b84 100644
--- a/activerecord/test/models/member.rb
+++ b/activerecord/test/models/member.rb
@@ -5,7 +5,7 @@ class Member < ActiveRecord::Base
has_one :club, through: :current_membership
has_one :selected_club, through: :selected_membership, source: :club
has_one :favourite_club, -> { where "memberships.favourite = ?", true }, through: :membership, source: :club
- has_one :hairy_club, -> { where clubs: {name: "Moustache and Eyebrow Fancier Club"} }, through: :membership, source: :club
+ has_one :hairy_club, -> { where clubs: { name: "Moustache and Eyebrow Fancier Club" } }, through: :membership, source: :club
has_one :sponsor, as: :sponsorable
has_one :sponsor_club, through: :sponsor
has_one :member_detail, inverse_of: false
diff --git a/activerecord/test/models/other_dog.rb b/activerecord/test/models/other_dog.rb
new file mode 100644
index 0000000000..418caf34be
--- /dev/null
+++ b/activerecord/test/models/other_dog.rb
@@ -0,0 +1,5 @@
+require_dependency "models/arunit2_model"
+
+class OtherDog < ARUnit2Model
+ self.table_name = "dogs"
+end
diff --git a/activerecord/test/models/parrot.rb b/activerecord/test/models/parrot.rb
index 5b693664d4..1e5f9285a8 100644
--- a/activerecord/test/models/parrot.rb
+++ b/activerecord/test/models/parrot.rb
@@ -13,6 +13,11 @@ class Parrot < ActiveRecord::Base
def cancel_save_callback_method
throw(:abort)
end
+
+ before_update :increment_updated_count
+ def increment_updated_count
+ self.updated_count += 1
+ end
end
class LiveParrot < Parrot
diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb
index 80a941874b..c532ab426e 100644
--- a/activerecord/test/models/pirate.rb
+++ b/activerecord/test/models/pirate.rb
@@ -9,10 +9,10 @@ class Pirate < ActiveRecord::Base
before_remove: :log_before_remove,
after_remove: :log_after_remove
has_and_belongs_to_many :parrots_with_proc_callbacks, class_name: "Parrot",
- before_add: proc {|p,pa| p.ship_log << "before_adding_proc_parrot_#{pa.id || '<new>'}"},
- after_add: proc {|p,pa| p.ship_log << "after_adding_proc_parrot_#{pa.id || '<new>'}"},
- before_remove: proc {|p,pa| p.ship_log << "before_removing_proc_parrot_#{pa.id}"},
- after_remove: proc {|p,pa| p.ship_log << "after_removing_proc_parrot_#{pa.id}"}
+ before_add: proc { |p, pa| p.ship_log << "before_adding_proc_parrot_#{pa.id || '<new>'}" },
+ after_add: proc { |p, pa| p.ship_log << "after_adding_proc_parrot_#{pa.id || '<new>'}" },
+ before_remove: proc { |p, pa| p.ship_log << "before_removing_proc_parrot_#{pa.id}" },
+ after_remove: proc { |p, pa| p.ship_log << "after_removing_proc_parrot_#{pa.id}" }
has_and_belongs_to_many :autosaved_parrots, class_name: "Parrot", autosave: true
has_many :treasures, as: :looter
@@ -28,10 +28,10 @@ class Pirate < ActiveRecord::Base
before_remove: :log_before_remove,
after_remove: :log_after_remove
has_many :birds_with_proc_callbacks, class_name: "Bird",
- before_add: proc {|p,b| p.ship_log << "before_adding_proc_bird_#{b.id || '<new>'}"},
- after_add: proc {|p,b| p.ship_log << "after_adding_proc_bird_#{b.id || '<new>'}"},
- before_remove: proc {|p,b| p.ship_log << "before_removing_proc_bird_#{b.id}"},
- after_remove: proc {|p,b| p.ship_log << "after_removing_proc_bird_#{b.id}"}
+ before_add: proc { |p, b| p.ship_log << "before_adding_proc_bird_#{b.id || '<new>'}" },
+ after_add: proc { |p, b| p.ship_log << "after_adding_proc_bird_#{b.id || '<new>'}" },
+ before_remove: proc { |p, b| p.ship_log << "before_removing_proc_bird_#{b.id}" },
+ after_remove: proc { |p, b| p.ship_log << "after_removing_proc_bird_#{b.id}" }
has_many :birds_with_reject_all_blank, class_name: "Bird"
has_one :foo_bulb, -> { where name: "foo" }, foreign_key: :car_id, class_name: "Bulb"
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 8bfd79d907..e74aedb814 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -21,9 +21,10 @@ class Post < ActiveRecord::Base
scope :titled_with_an_apostrophe, -> { where("title LIKE '%''%'") }
scope :ranked_by_comments, -> { order("comments_count DESC") }
- scope :limit_by, lambda {|l| limit(l) }
+ scope :limit_by, lambda { |l| limit(l) }
belongs_to :author
+ belongs_to :readonly_author, -> { readonly }, class_name: "Author", foreign_key: :author_id
belongs_to :author_with_posts, -> { includes(:posts) }, class_name: "Author", foreign_key: :author_id
belongs_to :author_with_address, -> { includes(:author_address) }, class_name: "Author", foreign_key: :author_id
@@ -34,8 +35,8 @@ class Post < ActiveRecord::Base
has_one :first_comment, -> { order("id ASC") }, class_name: "Comment"
has_one :last_comment, -> { order("id desc") }, class_name: "Comment"
- scope :with_special_comments, -> { joins(:comments).where(comments: {type: "SpecialComment"}) }
- scope :with_very_special_comments, -> { joins(:comments).where(comments: {type: "VerySpecialComment"}) }
+ scope :with_special_comments, -> { joins(:comments).where(comments: { type: "SpecialComment" }) }
+ scope :with_very_special_comments, -> { joins(:comments).where(comments: { type: "VerySpecialComment" }) }
scope :with_post, ->(post_id) { joins(:comments).where(comments: { post_id: post_id }) }
scope :with_comments, -> { preload(:comments) }
@@ -46,7 +47,7 @@ class Post < ActiveRecord::Base
scope :typographically_interesting, -> { containing_the_letter_a.or(titled_with_an_apostrophe) }
- has_many :comments do
+ has_many :comments do
def find_most_recent
order("id DESC").first
end
@@ -141,10 +142,10 @@ class Post < ActiveRecord::Base
has_many :people, through: :readers
has_many :single_people, through: :readers
has_many :people_with_callbacks, source: :person, through: :readers,
- before_add: lambda {|owner, reader| log(:added, :before, reader.first_name) },
- after_add: lambda {|owner, reader| log(:added, :after, reader.first_name) },
- before_remove: lambda {|owner, reader| log(:removed, :before, reader.first_name) },
- after_remove: lambda {|owner, reader| log(:removed, :after, reader.first_name) }
+ before_add: lambda { |owner, reader| log(:added, :before, reader.first_name) },
+ after_add: lambda { |owner, reader| log(:added, :after, reader.first_name) },
+ before_remove: lambda { |owner, reader| log(:removed, :before, reader.first_name) },
+ after_remove: lambda { |owner, reader| log(:removed, :after, reader.first_name) }
has_many :skimmers, -> { where skimmer: true }, class_name: "Reader"
has_many :impatient_people, through: :skimmers, source: :person
@@ -167,7 +168,7 @@ class Post < ActiveRecord::Base
@log = []
end
- def self.log(message=nil, side=nil, new_record=nil)
+ def self.log(message = nil, side = nil, new_record = nil)
return @log if message.nil?
@log << [message, side, new_record]
end
@@ -230,7 +231,7 @@ end
class SpecialPostWithDefaultScope < ActiveRecord::Base
self.inheritance_column = :disabled
self.table_name = "posts"
- default_scope { where(id: [1, 5,6]) }
+ default_scope { where(id: [1, 5, 6]) }
end
class PostThatLoadsCommentsInAnAfterSaveHook < ActiveRecord::Base
diff --git a/activerecord/test/models/project.rb b/activerecord/test/models/project.rb
index 4d6b269731..5009f8f54b 100644
--- a/activerecord/test/models/project.rb
+++ b/activerecord/test/models/project.rb
@@ -7,10 +7,10 @@ class Project < ActiveRecord::Base
has_and_belongs_to_many :developers_named_david, -> { where("name = 'David'").distinct }, class_name: "Developer"
has_and_belongs_to_many :developers_named_david_with_hash_conditions, -> { where(name: "David").distinct }, class_name: "Developer"
has_and_belongs_to_many :salaried_developers, -> { where "salary > 0" }, class_name: "Developer"
- has_and_belongs_to_many :developers_with_callbacks, class_name: "Developer", before_add: Proc.new {|o, r| o.developers_log << "before_adding#{r.id || '<new>'}"},
- after_add: Proc.new {|o, r| o.developers_log << "after_adding#{r.id || '<new>'}"},
- before_remove: Proc.new {|o, r| o.developers_log << "before_removing#{r.id}"},
- after_remove: Proc.new {|o, r| o.developers_log << "after_removing#{r.id}"}
+ has_and_belongs_to_many :developers_with_callbacks, class_name: "Developer", before_add: Proc.new { |o, r| o.developers_log << "before_adding#{r.id || '<new>'}" },
+ after_add: Proc.new { |o, r| o.developers_log << "after_adding#{r.id || '<new>'}" },
+ before_remove: Proc.new { |o, r| o.developers_log << "before_removing#{r.id}" },
+ after_remove: Proc.new { |o, r| o.developers_log << "after_removing#{r.id}" }
has_and_belongs_to_many :well_payed_salary_groups, -> { group("developers.salary").having("SUM(salary) > 10000").select("SUM(salary) as salary") }, class_name: "Developer"
belongs_to :firm
has_one :lead_developer, through: :firm, inverse_of: :contracted_projects
diff --git a/activerecord/test/models/sponsor.rb b/activerecord/test/models/sponsor.rb
index 175933ccf1..3f142b25fe 100644
--- a/activerecord/test/models/sponsor.rb
+++ b/activerecord/test/models/sponsor.rb
@@ -2,6 +2,6 @@ class Sponsor < ActiveRecord::Base
belongs_to :sponsor_club, class_name: "Club", foreign_key: "club_id"
belongs_to :sponsorable, polymorphic: true
belongs_to :thing, polymorphic: true, foreign_type: :sponsorable_type, foreign_key: :sponsorable_id
- belongs_to :sponsorable_with_conditions, -> { where name: "Ernie"}, polymorphic: true,
+ belongs_to :sponsorable_with_conditions, -> { where name: "Ernie" }, polymorphic: true,
foreign_type: "sponsorable_type", foreign_key: "sponsorable_id"
end
diff --git a/activerecord/test/models/subject.rb b/activerecord/test/models/subject.rb
index 29e290825e..504f68a296 100644
--- a/activerecord/test/models/subject.rb
+++ b/activerecord/test/models/subject.rb
@@ -5,7 +5,7 @@ class Subject < ActiveRecord::Base
# as otherwise synonym test was failing
after_initialize :set_email_address
- protected
+ private
def set_email_address
unless persisted?
self.author_email_address = "test@test.com"
diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb
index db04735d01..0420e2d15c 100644
--- a/activerecord/test/models/topic.rb
+++ b/activerecord/test/models/topic.rb
@@ -73,7 +73,7 @@ class Topic < ActiveRecord::Base
write_attribute(:approved, val)
end
- protected
+ private
def default_written_on
self.written_on = Time.now unless attribute_present?("written_on")
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index f00b858ea6..15ba2d67ab 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -1,6 +1,7 @@
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|
t.string :name
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 558c21b8d2..ba6f5de894 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -54,7 +54,7 @@ ActiveRecord::Schema.define do
create_table :authors, force: true do |t|
t.string :name, null: false
- t.integer :author_address_id
+ t.bigint :author_address_id
t.integer :author_address_extra_id
t.string :organization_id
t.string :owned_essay_id
@@ -98,6 +98,7 @@ ActiveRecord::Schema.define do
t.column :author_visibility, :integer, default: 0
t.column :illustrator_visibility, :integer, default: 0
t.column :font_size, :integer, default: 0
+ t.column :difficulty, :integer, default: 0
t.column :cover, :string, default: "hard"
end
@@ -125,6 +126,9 @@ ActiveRecord::Schema.define do
t.timestamps null: false
end
+ create_table :old_cars, id: :integer, force: true do |t|
+ end
+
create_table :carriers, force: true
create_table :categories, force: true do |t|
@@ -196,7 +200,7 @@ ActiveRecord::Schema.define do
t.integer :rating, default: 1
t.integer :account_id
t.string :description, default: ""
- t.index [:firm_id, :type, :rating], name: "company_index"
+ t.index [:firm_id, :type, :rating], name: "company_index", length: { type: 10 }, order: { rating: :desc }
t.index [:firm_id, :type], name: "company_partial_index", where: "rating > 10"
t.index :name, name: "company_name_index", using: :btree
t.index "lower(name)", name: "company_expression_index" if supports_expression_index?
@@ -302,7 +306,7 @@ ActiveRecord::Schema.define do
end
create_table :engines, force: true do |t|
- t.integer :car_id
+ t.bigint :car_id
end
create_table :entrants, force: true do |t|
@@ -435,10 +439,12 @@ ActiveRecord::Schema.define do
end
create_table :lock_without_defaults, force: true do |t|
+ t.column :title, :string
t.column :lock_version, :integer
end
create_table :lock_without_defaults_cust, force: true do |t|
+ t.column :title, :string
t.column :custom_lock_version, :integer
end
@@ -568,6 +574,7 @@ ActiveRecord::Schema.define do
t.column :color, :string
t.column :parrot_sti_class, :string
t.column :killer_id, :integer
+ t.column :updated_count, :integer, default: 0
if subsecond_precision_supported?
t.column :created_at, :datetime, precision: 0
t.column :created_on, :datetime, precision: 0
@@ -899,10 +906,9 @@ ActiveRecord::Schema.define do
end
[:circles, :squares, :triangles, :non_poly_ones, :non_poly_twos].each do |t|
- create_table(t, force: true) { }
+ create_table(t, force: true) {}
end
- # NOTE - the following 4 tables are used by models that have :inverse_of options on the associations
create_table :men, force: true do |t|
t.string :name
end
@@ -926,14 +932,14 @@ ActiveRecord::Schema.define do
t.integer :zine_id
end
- create_table :wheels, force: true do |t|
- t.references :wheelable, polymorphic: true
- end
-
create_table :zines, force: true do |t|
t.string :title
end
+ create_table :wheels, force: true do |t|
+ t.references :wheelable, polymorphic: true
+ end
+
create_table :countries, force: true, id: false, primary_key: "country_id" do |t|
t.string :country_id
t.string :name
@@ -1002,7 +1008,7 @@ ActiveRecord::Schema.define do
if supports_foreign_keys?
# fk_test_has_fk should be before fk_test_has_pk
create_table :fk_test_has_fk, force: true do |t|
- t.integer :fk_id, null: false
+ t.bigint :fk_id, null: false
end
create_table :fk_test_has_pk, force: true, primary_key: "pk_id" do |t|
@@ -1045,3 +1051,5 @@ Professor.connection.create_table :courses_professors, id: false, force: true do
t.references :course
t.references :professor
end
+
+OtherDog.connection.create_table :dogs, force: true
diff --git a/activerecord/test/support/config.rb b/activerecord/test/support/config.rb
index d0717f7b34..aaff408b41 100644
--- a/activerecord/test/support/config.rb
+++ b/activerecord/test/support/config.rb
@@ -1,5 +1,5 @@
require "yaml"
-require "erubis"
+require "erb"
require "fileutils"
require "pathname"
@@ -20,13 +20,14 @@ module ARTest
FileUtils.cp TEST_ROOT + "/config.example.yml", config_file
end
- erb = Erubis::Eruby.new(config_file.read)
+ erb = ERB.new(config_file.read)
expand_config(YAML.parse(erb.result(binding)).transform)
end
def expand_config(config)
config["connections"].each do |adapter, connection|
- dbs = [["arunit", "activerecord_unittest"], ["arunit2", "activerecord_unittest2"]]
+ dbs = [["arunit", "activerecord_unittest"], ["arunit2", "activerecord_unittest2"],
+ ["arunit_without_prepared_statements", "activerecord_unittest"]]
dbs.each do |name, dbname|
unless connection[name].is_a?(Hash)
connection[name] = { "database" => connection[name] }
diff --git a/activerecord/test/support/connection.rb b/activerecord/test/support/connection.rb
index c9260398e2..bc5af36a28 100644
--- a/activerecord/test/support/connection.rb
+++ b/activerecord/test/support/connection.rb
@@ -2,6 +2,7 @@ require "active_support/logger"
require "models/college"
require "models/course"
require "models/professor"
+require "models/other_dog"
module ARTest
def self.connection_name
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 163fbdbca6..873a39dbf6 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,146 @@
+* Change return value of `Rational#duplicable?`, `ComplexClass#duplicable?`
+ to false.
+
+ *utilum*
+
+* Change return value of `NilClass#duplicable?`, `FalseClass#duplicable?`,
+ `TrueClass#duplicable?`, `Symbol#duplicable?` and `Numeric#duplicable?`
+ to true with Ruby 2.4+. These classes can dup with Ruby 2.4+.
+
+ *Yuji Yaginuma*
+
+* Remove deprecated class `ActiveSupport::Concurrency::Latch`
+
+ *Andrew White*
+
+* Remove deprecated separator argument from `parameterize`
+
+ *Andrew White*
+
+* Remove deprecated method `Numeric#to_formatted_s`
+
+ *Andrew White*
+
+* Remove deprecated method `alias_method_chain`
+
+ *Andrew White*
+
+* Remove deprecated constant `MissingSourceFile`
+
+ *Andrew White*
+
+* Remove deprecated methods `Module.qualified_const_defined?`,
+ `Module.qualified_const_get` and `Module.qualified_const_set`
+
+ *Andrew White*
+
+* Remove deprecated `:prefix` option from `number_to_human_size`
+
+ *Andrew White*
+
+* Remove deprecated method `ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default`
+
+ *Andrew White*
+
+* Remove deprecated file `active_support/core_ext/time/marshal.rb`
+
+ *Andrew White*
+
+* Remove deprecated file `active_support/core_ext/struct.rb`
+
+ *Andrew White*
+
+* Remove deprecated file `active_support/core_ext/module/method_transplanting.rb`
+
+ *Andrew White*
+
+* Remove deprecated method `Module.local_constants`
+
+ *Andrew White*
+
+* Remove deprecated file `active_support/core_ext/kernel/debugger.rb`
+
+ *Andrew White*
+
+* Remove deprecated method `ActiveSupport::Cache::Store#namespaced_key`
+
+ *Andrew White*
+
+* Remove deprecated method `ActiveSupport::Cache::Strategy::LocalCache::LocalStore#set_cache_value`
+
+ *Andrew White*
+
+* Remove deprecated method `ActiveSupport::Cache::MemCacheStore#escape_key`
+
+ *Andrew White*
+
+* Remove deprecated method `ActiveSupport::Cache::FileStore#key_file_path`
+
+ *Andrew White*
+
+* Ensure duration parsing is consistent across DST changes
+
+ Previously `ActiveSupport::Duration.parse` used `Time.current` and
+ `Time#advance` to calculate the number of seconds in the duration
+ from an arbitrary collection of parts. However as `advance` tries to
+ be consistent across DST boundaries this meant that either the
+ duration was shorter or longer depending on the time of year.
+
+ This was fixed by using an absolute reference point in UTC which
+ isn't subject to DST transitions. An arbitrary date of Jan 1st, 2000
+ was chosen for no other reason that it seemed appropriate.
+
+ Additionally, duration parsing should now be marginally faster as we
+ are no longer creating instances of `ActiveSupport::TimeWithZone`
+ every time we parse a duration string.
+
+ Fixes #26941.
+
+ *Andrew White*
+
+* Use `Hash#compact` and `Hash#compact!` from Ruby 2.4. Old Ruby versions
+ will continue to get these methods from Active Support as before.
+
+ *Prathamesh Sonpatki*
+
+* Fix `ActiveSupport::TimeZone#strptime`.
+ Support for timestamps in format of seconds (%s) and milliseconds (%Q).
+
+ Fixes #26840.
+
+ *Lev Denisov*
+
+* Fix `DateAndTime::Calculations#copy_time_to`. Copy `nsec` instead of `usec`.
+
+ Jumping forward or backward between weeks now preserves nanosecond digits.
+
+ *Josua Schmid*
+
+* Fix `ActiveSupport::TimeWithZone#in` across DST boundaries.
+
+ Previously calls to `in` were being sent to the non-DST aware
+ method `Time#since` via `method_missing`. It is now aliased to
+ the DST aware `ActiveSupport::TimeWithZone#+` which handles
+ transitions across DST boundaries, e.g:
+
+ Time.zone = "US/Eastern"
+
+ t = Time.zone.local(2016,11,6,1)
+ # => Sun, 06 Nov 2016 01:00:00 EDT -05:00
+
+ t.in(1.hour)
+ # => Sun, 06 Nov 2016 01:00:00 EST -05:00
+
+ Fixes #26580.
+
+ *Thomas Balthazar*
+
+* Remove unused parameter `options = nil` for `#clear` of
+ `ActiveSupport::Cache::Strategy::LocalCache::LocalStore` and
+ `ActiveSupport::Cache::Strategy::LocalCache`.
+
+ *Yosuke Kabuto*
+
* Fix `thread_mattr_accessor` subclass no longer overwrites parent.
Assigning a value to a subclass using `thread_mattr_accessor` no
@@ -82,12 +225,6 @@
*Genadi Samokovarov*
-* Add `:fallback_string` option to `Array#to_sentence`. If an empty array
- calls the function and a fallback string option is set then it returns the
- fallback string other than an empty string.
-
- *Mohamed Osama*
-
* Fix `ActiveSupport::TimeZone#strptime`. Now raises `ArgumentError` when the
given time doesn't match the format. The error is the same as the one given
by Ruby's `Date.strptime`. Previously it raised
diff --git a/activesupport/MIT-LICENSE b/activesupport/MIT-LICENSE
index 40235833ba..6b3cead1a7 100644
--- a/activesupport/MIT-LICENSE
+++ b/activesupport/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2005-2016 David Heinemeier Hansson
+Copyright (c) 2005-2017 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activesupport/Rakefile b/activesupport/Rakefile
index 54d7a5c6f3..2e1c50cc3d 100644
--- a/activesupport/Rakefile
+++ b/activesupport/Rakefile
@@ -16,6 +16,6 @@ namespace :test do
task :isolated do
Dir.glob("test/**/*_test.rb").all? do |file|
sh(Gem.ruby, "-w", "-Ilib:test", file)
- end or raise "Failures"
+ end || raise("Failures")
end
end
diff --git a/activesupport/bin/generate_tables b/activesupport/bin/generate_tables
index 06dcff4e87..5d912f375c 100755
--- a/activesupport/bin/generate_tables
+++ b/activesupport/bin/generate_tables
@@ -51,9 +51,9 @@ module ActiveSupport
codepoint.code = $1.hex
codepoint.combining_class = Integer($4)
codepoint.decomp_type = $7
- codepoint.decomp_mapping = ($8=="") ? nil : $8.split.collect(&:hex)
- codepoint.uppercase_mapping = ($16=="") ? 0 : $16.hex
- codepoint.lowercase_mapping = ($17=="") ? 0 : $17.hex
+ codepoint.decomp_mapping = ($8 == "") ? nil : $8.split.collect(&:hex)
+ codepoint.uppercase_mapping = ($16 == "") ? 0 : $16.hex
+ codepoint.lowercase_mapping = ($17 == "") ? 0 : $17.hex
@ucd.codepoints[codepoint.code] = codepoint
end
@@ -84,7 +84,7 @@ module ActiveSupport
def create_composition_map
@ucd.codepoints.each do |_, cp|
- if !cp.nil? and cp.combining_class == 0 and cp.decomp_type.nil? and !cp.decomp_mapping.nil? and cp.decomp_mapping.length == 2 and @ucd.codepoints[cp.decomp_mapping[0]].combining_class == 0 and !@ucd.composition_exclusion.include?(cp.code)
+ if !cp.nil? && cp.combining_class == 0 && cp.decomp_type.nil? && !cp.decomp_mapping.nil? && cp.decomp_mapping.length == 2 && @ucd.codepoints[cp.decomp_mapping[0]].combining_class == 0 && !@ucd.composition_exclusion.include?(cp.code)
@ucd.composition_map[cp.decomp_mapping[0]] ||= {}
@ucd.composition_map[cp.decomp_mapping[0]][cp.decomp_mapping[1]] = cp.code
end
@@ -92,7 +92,7 @@ module ActiveSupport
end
def normalize_boundary_map
- @ucd.boundary.each do |k,v|
+ @ucd.boundary.each do |k, v|
if [:lf, :cr].include? k
@ucd.boundary[k] = v[0]
end
@@ -101,7 +101,7 @@ module ActiveSupport
def parse
SOURCES.each do |type, url|
- filename = File.join(Dir.tmpdir, "#{url.split('/').last}")
+ filename = File.join(Dir.tmpdir, "#{url.split('/').last}")
unless File.exist?(filename)
$stderr.puts "Downloading #{url.split('/').last}"
File.open(filename, "wb") do |target|
diff --git a/activesupport/bin/test b/activesupport/bin/test
index 84a05bba08..a7beb14b27 100755
--- a/activesupport/bin/test
+++ b/activesupport/bin/test
@@ -2,5 +2,3 @@
COMPONENT_ROOT = File.expand_path("..", __dir__)
require File.expand_path("../tools/test", COMPONENT_ROOT)
-
-exit Minitest.run(ARGV)
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 2fc42e1975..267fa755c6 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2005-2016 David Heinemeier Hansson
+# Copyright (c) 2005-2017 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb
index e161ec4cca..169a58ecd1 100644
--- a/activesupport/lib/active_support/backtrace_cleaner.rb
+++ b/activesupport/lib/active_support/backtrace_cleaner.rb
@@ -14,7 +14,7 @@ module ActiveSupport
#
# bc = BacktraceCleaner.new
# bc.add_filter { |line| line.gsub(Rails.root.to_s, '') } # strip the Rails.root prefix
- # bc.add_silencer { |line| line =~ /mongrel|rubygems/ } # skip any lines from mongrel or rubygems
+ # bc.add_silencer { |line| line =~ /puma|rubygems/ } # skip any lines from puma or rubygems
# bc.clean(exception.backtrace) # perform the cleanup
#
# To reconfigure an existing BacktraceCleaner (like the default one in Rails)
@@ -59,8 +59,8 @@ module ActiveSupport
# Adds a silencer from the block provided. If the silencer returns +true+
# for a given line, it will be excluded from the clean backtrace.
#
- # # Will reject all lines that include the word "mongrel", like "/gems/mongrel/server.rb" or "/app/my_mongrel_server/rb"
- # backtrace_cleaner.add_silencer { |line| line =~ /mongrel/ }
+ # # Will reject all lines that include the word "puma", like "/gems/puma/server.rb" or "/app/my_puma_server/rb"
+ # backtrace_cleaner.add_silencer { |line| line =~ /puma/ }
def add_silencer(&block)
@silencers << block
end
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index a760915bfd..d1bbd2f405 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -1,8 +1,6 @@
-require "benchmark"
require "zlib"
require "active_support/core_ext/array/extract_options"
require "active_support/core_ext/array/wrap"
-require "active_support/core_ext/benchmark"
require "active_support/core_ext/module/attribute_accessors"
require "active_support/core_ext/numeric/bytes"
require "active_support/core_ext/numeric/time"
@@ -200,15 +198,15 @@ module ActiveSupport
# You may also specify additional options via the +options+ argument.
# Setting <tt>force: true</tt> forces a cache "miss," meaning we treat
# the cache value as missing even if it's present. Passing a block is
- # required when `force` is true so this always results in a cache write.
+ # required when +force+ is true so this always results in a cache write.
#
# cache.write('today', 'Monday')
# cache.fetch('today', force: true) { 'Tuesday' } # => 'Tuesday'
# cache.fetch('today', force: true) # => ArgumentError
#
- # The `:force` option is useful when you're calling some other method to
+ # The +:force+ option is useful when you're calling some other method to
# ask whether you should force a cache write. Otherwise, it's clearer to
- # just call `Cache#write`.
+ # just call <tt>Cache#write</tt>.
#
# Setting <tt>:compress</tt> will store a large cache entry set by the call
# in a compressed format.
@@ -361,6 +359,9 @@ module ActiveSupport
# 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,
# and the result will be written to the cache and returned.
+ # Therefore, you need to pass a block that returns the data to be written
+ # to the cache. If you do not want to write the cache when the cache is
+ # not found, use #read_multi.
#
# Options are passed to the underlying cache implementation.
#
@@ -374,6 +375,8 @@ module ActiveSupport
# # "unknown_key" => "Fallback value for key: unknown_key" }
#
def fetch_multi(*names)
+ raise ArgumentError, "Missing block: `Cache#fetch_multi` requires a block." unless block_given?
+
options = names.extract_options!
options = merged_options(options)
results = read_multi(*names, options)
@@ -464,16 +467,16 @@ module ActiveSupport
# The options hash is passed to the underlying cache implementation.
#
# All implementations may not support this method.
- def clear(options = nil)
+ def clear
raise NotImplementedError.new("#{self.class.name} does not support clear")
end
- protected
+ private
# Adds the namespace defined in the options to a pattern designed to
# match keys. Implementations that support delete_matched should call
# this method to translate a pattern that matches names into one that
# matches namespaced keys.
- def key_matcher(pattern, options)
+ def key_matcher(pattern, options) # :doc:
prefix = options[:namespace].is_a?(Proc) ? options[:namespace].call : options[:namespace]
if prefix
source = pattern.source
@@ -490,25 +493,24 @@ module ActiveSupport
# Reads an entry from the cache implementation. Subclasses must implement
# this method.
- def read_entry(key, options) # :nodoc:
+ def read_entry(key, options)
raise NotImplementedError.new
end
# Writes an entry to the cache implementation. Subclasses must implement
# this method.
- def write_entry(key, entry, options) # :nodoc:
+ def write_entry(key, entry, options)
raise NotImplementedError.new
end
# Deletes an entry from the cache implementation. Subclasses must
# implement this method.
- def delete_entry(key, options) # :nodoc:
+ def delete_entry(key, options)
raise NotImplementedError.new
end
- private
# Merges the default options with ones specific to a method call.
- def merged_options(call_options) # :nodoc:
+ def merged_options(call_options)
if call_options
options.merge(call_options)
else
@@ -519,18 +521,18 @@ module ActiveSupport
# Expands key to be a consistent string value. Invokes +cache_key+ if
# object responds to +cache_key+. Otherwise, +to_param+ method will be
# called. If the key is a Hash, then keys will be sorted alphabetically.
- def expanded_key(key) # :nodoc:
+ def expanded_key(key)
return key.cache_key.to_s if key.respond_to?(:cache_key)
case key
when Array
if key.size > 1
- key = key.collect{|element| expanded_key(element)}
+ key = key.collect { |element| expanded_key(element) }
else
key = key.first
end
when Hash
- key = key.sort_by { |k,_| k.to_s }.collect{|k,v| "#{k}=#{v}"}
+ key = key.sort_by { |k, _| k.to_s }.collect { |k, v| "#{k}=#{v}" }
end
key.to_param
@@ -546,20 +548,12 @@ module ActiveSupport
key
end
- def namespaced_key(*args)
- ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
- `namespaced_key` is deprecated and will be removed from Rails 5.1.
- Please use `normalize_key` which will return a fully resolved key.
- MESSAGE
- normalize_key(*args)
- end
-
def instrument(operation, key, options = nil)
log { "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" }
payload = { key: key }
payload.merge!(options) if options.is_a?(Hash)
- ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload){ yield(payload) }
+ ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) { yield(payload) }
end
def log
@@ -584,7 +578,7 @@ module ActiveSupport
end
def get_entry_value(entry, name, options)
- instrument(:fetch_hit, name, options) { }
+ instrument(:fetch_hit, name, options) {}
entry.value
end
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index 1132425526..d5c8585816 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -27,9 +27,9 @@ 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(options = nil)
+ def clear
root_dirs = exclude_from(cache_path, EXCLUDED_DIRS + GITKEEP_FILES)
- FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)})
+ FileUtils.rm_r(root_dirs.collect { |f| File.join(cache_path, f) })
rescue Errno::ENOENT
end
@@ -66,7 +66,7 @@ module ActiveSupport
end
end
- protected
+ private
def read_entry(key, options)
if File.exist?(key)
@@ -80,7 +80,7 @@ module ActiveSupport
def write_entry(key, entry, options)
return false if options[:unless_exist] && File.exist?(key)
ensure_cache_path(File.dirname(key))
- File.atomic_write(key, cache_path) {|f| Marshal.dump(entry, f)}
+ File.atomic_write(key, cache_path) { |f| Marshal.dump(entry, f) }
true
end
@@ -98,9 +98,8 @@ module ActiveSupport
end
end
- private
# Lock a file for a block so only one process can modify it at a time.
- def lock_file(file_name, &block) # :nodoc:
+ def lock_file(file_name, &block)
if File.exist?(file_name)
File.open(file_name, "r+") do |f|
begin
@@ -138,14 +137,6 @@ module ActiveSupport
File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, *fname_paths)
end
- def key_file_path(key)
- ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
- `key_file_path` is deprecated and will be removed from Rails 5.1.
- Please use `normalize_key` which will return a fully resolved key or nothing.
- MESSAGE
- key
- end
-
# Translate a file path into a key.
def file_path_key(path)
fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index e51c6b2f10..e09cee3335 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -26,7 +26,7 @@ module ActiveSupport
class MemCacheStore < Store
# Provide support for raw values in the local cache strategy.
module LocalCacheWithRaw # :nodoc:
- protected
+ private
def read_entry(key, options)
entry = super
if options[:raw] && local_cache && entry
@@ -35,7 +35,7 @@ module ActiveSupport
entry
end
- def write_entry(key, entry, options) # :nodoc:
+ def write_entry(key, entry, options)
if options[:raw] && local_cache
raw_entry = Entry.new(entry.value.to_s)
raw_entry.expires_at = entry.expires_at
@@ -85,7 +85,7 @@ module ActiveSupport
@data = addresses.first
else
mem_cache_options = options.dup
- UNIVERSAL_OPTIONS.each{|name| mem_cache_options.delete(name)}
+ UNIVERSAL_OPTIONS.each { |name| mem_cache_options.delete(name) }
@data = self.class.build_mem_cache(*(addresses + [mem_cache_options]))
end
end
@@ -96,8 +96,8 @@ module ActiveSupport
options = names.extract_options!
options = merged_options(options)
- keys_to_names = Hash[names.map{|name| [normalize_key(name, options), name]}]
- raw_values = @data.get_multi(keys_to_names.keys, raw: true)
+ 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)
@@ -110,7 +110,7 @@ module ActiveSupport
# operator and can only be used on values written with the :raw option.
# Calling it on a value not stored with :raw will initialize that value
# to zero.
- def increment(name, amount = 1, options = nil) # :nodoc:
+ def increment(name, amount = 1, options = nil)
options = merged_options(options)
instrument(:increment, name, amount: amount) do
rescue_error_with nil do
@@ -123,7 +123,7 @@ module ActiveSupport
# operator and can only be used on values written with the :raw option.
# Calling it on a value not stored with :raw will initialize that value
# to zero.
- def decrement(name, amount = 1, options = nil) # :nodoc:
+ def decrement(name, amount = 1, options = nil)
options = merged_options(options)
instrument(:decrement, name, amount: amount) do
rescue_error_with nil do
@@ -143,14 +143,14 @@ module ActiveSupport
@data.stats
end
- protected
+ private
# Read an entry from the cache.
- def read_entry(key, options) # :nodoc:
+ def read_entry(key, options)
rescue_error_with(nil) { deserialize_entry(@data.get(key, options)) }
end
# Write an entry to the cache.
- def write_entry(key, entry, options) # :nodoc:
+ def write_entry(key, entry, options)
method = options && options[:unless_exist] ? :add : :set
value = options[:raw] ? entry.value.to_s : entry
expires_in = options[:expires_in].to_i
@@ -164,31 +164,21 @@ module ActiveSupport
end
# Delete an entry from the cache.
- def delete_entry(key, options) # :nodoc:
+ def delete_entry(key, options)
rescue_error_with(false) { @data.delete(key) }
end
- private
-
# Memcache keys are binaries. So we need to force their encoding to binary
# before applying the regular expression to ensure we are escaping all
# characters properly.
def normalize_key(key, options)
key = super.dup
key = key.force_encoding(Encoding::ASCII_8BIT)
- key = key.gsub(ESCAPE_KEY_CHARS){ |match| "%#{match.getbyte(0).to_s(16).upcase}" }
+ key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
key = "#{key[0, 213]}:md5:#{Digest::MD5.hexdigest(key)}" if key.size > 250
key
end
- def escape_key(key)
- ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
- `escape_key` is deprecated and will be removed from Rails 5.1.
- Please use `normalize_key` which will return a fully resolved key or nothing.
- MESSAGE
- key
- end
-
def deserialize_entry(raw_value)
if raw_value
entry = Marshal.load(raw_value) rescue raw_value
diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb
index d7dc574f5b..fea072d91c 100644
--- a/activesupport/lib/active_support/cache/memory_store.rb
+++ b/activesupport/lib/active_support/cache/memory_store.rb
@@ -4,7 +4,7 @@ module ActiveSupport
module Cache
# A cache store implementation which stores everything into memory in the
# same process. If you're running multiple Ruby on Rails server processes
- # (which is the case if you're using mongrel_cluster or Phusion Passenger),
+ # (which is the case if you're using Phusion Passenger or puma clustered mode),
# then this means that Rails server process instances won't be able
# to share cache data with each other and this may not be the most
# appropriate cache in that scenario.
@@ -40,7 +40,7 @@ module ActiveSupport
def cleanup(options = nil)
options = merged_options(options)
instrument(:cleanup, size: @data.size) do
- keys = synchronize{ @data.keys }
+ keys = synchronize { @data.keys }
keys.each do |key|
entry = @data[key]
delete_entry(key, options) if entry && entry.expired?
@@ -57,7 +57,7 @@ module ActiveSupport
start_time = Time.now
cleanup
instrument(:prune, target_size, from: @cache_size) do
- keys = synchronize{ @key_access.keys.sort{|a,b| @key_access[a].to_f <=> @key_access[b].to_f} }
+ keys = synchronize { @key_access.keys.sort { |a, b| @key_access[a].to_f <=> @key_access[b].to_f } }
keys.each do |key|
delete_entry(key, options)
return if @cache_size <= target_size || (max_time && Time.now - start_time > max_time)
@@ -104,15 +104,15 @@ module ActiveSupport
@monitor.synchronize(&block)
end
- protected
+ private
PER_ENTRY_OVERHEAD = 240
- def cached_size(key, entry) # :nodoc:
+ def cached_size(key, entry)
key.to_s.bytesize + entry.size + PER_ENTRY_OVERHEAD
end
- def read_entry(key, options) # :nodoc:
+ def read_entry(key, options)
entry = @data[key]
synchronize do
if entry
@@ -124,7 +124,7 @@ module ActiveSupport
entry
end
- def write_entry(key, entry, options) # :nodoc:
+ def write_entry(key, entry, options)
entry.dup_value!
synchronize do
old_entry = @data[key]
@@ -141,7 +141,7 @@ module ActiveSupport
end
end
- def delete_entry(key, options) # :nodoc:
+ def delete_entry(key, options)
synchronize do
@key_access.delete(key)
entry = @data.delete(key)
@@ -150,8 +150,6 @@ module ActiveSupport
end
end
- private
-
def modify_value(name, amount, options)
synchronize do
options = merged_options(options)
diff --git a/activesupport/lib/active_support/cache/null_store.rb b/activesupport/lib/active_support/cache/null_store.rb
index 0564ce5312..550659fc56 100644
--- a/activesupport/lib/active_support/cache/null_store.rb
+++ b/activesupport/lib/active_support/cache/null_store.rb
@@ -25,15 +25,15 @@ module ActiveSupport
def delete_matched(matcher, options = nil)
end
- protected
- def read_entry(key, options) # :nodoc:
+ private
+ def read_entry(key, options)
end
- def write_entry(key, entry, options) # :nodoc:
+ def write_entry(key, entry, options)
true
end
- def delete_entry(key, options) # :nodoc:
+ def delete_entry(key, options)
false
end
end
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb
index 8ed8ab36ab..672eb2bb80 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb
@@ -44,7 +44,7 @@ module ActiveSupport
yield
end
- def clear(options = nil)
+ def clear
@data.clear
end
@@ -70,6 +70,7 @@ module ActiveSupport
def with_local_cache
use_temporary_local_cache(LocalStore.new) { yield }
end
+
# Middleware class can be inserted as a Rack handler to be local cache for the
# duration of request.
def middleware
@@ -78,9 +79,9 @@ module ActiveSupport
local_cache_key)
end
- def clear(options = nil) # :nodoc:
+ def clear # :nodoc:
return super unless cache = local_cache
- cache.clear(options)
+ cache.clear
super
end
@@ -92,20 +93,20 @@ module ActiveSupport
def increment(name, amount = 1, options = nil) # :nodoc:
return super unless local_cache
- value = bypass_local_cache{super}
+ value = bypass_local_cache { super }
write_cache_value(name, value, options)
value
end
def decrement(name, amount = 1, options = nil) # :nodoc:
return super unless local_cache
- value = bypass_local_cache{super}
+ value = bypass_local_cache { super }
write_cache_value(name, value, options)
value
end
- protected
- def read_entry(key, options) # :nodoc:
+ private
+ def read_entry(key, options)
if cache = local_cache
cache.fetch_entry(key) { super }
else
@@ -113,25 +114,17 @@ module ActiveSupport
end
end
- def write_entry(key, entry, options) # :nodoc:
+ def write_entry(key, entry, options)
local_cache.write_entry(key, entry, options) if local_cache
super
end
- def delete_entry(key, options) # :nodoc:
+ def delete_entry(key, options)
local_cache.delete_entry(key, options) if local_cache
super
end
- def set_cache_value(value, name, amount, options) # :nodoc:
- ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
- `set_cache_value` is deprecated and will be removed from Rails 5.1.
- Please use `write_cache_value` instead.
- MESSAGE
- write_cache_value name, value, options
- end
-
- def write_cache_value(name, value, options) # :nodoc:
+ def write_cache_value(name, value, options)
name = normalize_key(name, options)
cache = local_cache
cache.mute do
@@ -143,8 +136,6 @@ module ActiveSupport
end
end
- private
-
def local_cache_key
@local_cache_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, "_").to_sym
end
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb b/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb
index af51f66dda..174cb72b1e 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb
@@ -12,9 +12,9 @@ module ActiveSupport
attr_reader :name, :local_cache_key
def initialize(name, local_cache_key)
- @name = name
+ @name = name
@local_cache_key = local_cache_key
- @app = nil
+ @app = nil
end
def new(app)
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index d6687ae937..e6c79f2a38 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -63,6 +63,8 @@ module ActiveSupport
included do
extend ActiveSupport::DescendantsTracker
+ class_attribute :__callbacks, instance_writer: false
+ self.__callbacks ||= {}
end
CALLBACK_FILTER_TYPES = [:before, :after, :around]
@@ -86,25 +88,67 @@ module ActiveSupport
# run_callbacks :save do
# save
# end
- def run_callbacks(kind, &block)
- send "_run_#{kind}_callbacks", &block
- end
-
- private
+ #
+ #--
+ #
+ # As this method is used in many places, and often wraps large portions of
+ # user code, it has an additional design goal of minimizing its impact on
+ # the visible call stack. An exception from inside a :before or :after
+ # callback can be as noisy as it likes -- but when control has passed
+ # smoothly through and into the supplied block, we want as little evidence
+ # as possible that we were here.
+ def run_callbacks(kind)
+ callbacks = __callbacks[kind.to_sym]
+
+ if callbacks.empty?
+ yield if block_given?
+ else
+ env = Filters::Environment.new(self, false, nil)
+ next_sequence = callbacks.compile
+
+ invoke_sequence = Proc.new do
+ skipped = nil
+ while true
+ current = next_sequence
+ current.invoke_before(env)
+ if current.final?
+ env.value = !env.halted && (!block_given? || yield)
+ elsif current.skip?(env)
+ (skipped ||= []) << current
+ next_sequence = next_sequence.nested
+ next
+ else
+ next_sequence = next_sequence.nested
+ begin
+ target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
+ target.send(method, *arguments, &block)
+ ensure
+ next_sequence = current
+ end
+ end
+ current.invoke_after(env)
+ skipped.pop.invoke_after(env) while skipped && skipped.first
+ break env.value
+ end
+ end
- def __run_callbacks__(callbacks, &block)
- if callbacks.empty?
- yield if block_given?
+ # Common case: no 'around' callbacks defined
+ if next_sequence.final?
+ next_sequence.invoke_before(env)
+ env.value = !env.halted && (!block_given? || yield)
+ next_sequence.invoke_after(env)
+ env.value
else
- runner = callbacks.compile
- e = Filters::Environment.new(self, false, nil, block)
- runner.call(e).value
+ invoke_sequence.call
end
end
+ end
- # A hook invoked every time a before callback is halted.
- # This can be overridden in ActiveSupport::Callbacks implementors in order
- # to provide better debugging/logging.
+ private
+
+ # A hook invoked every time a before callback is halted.
+ # This can be overridden in ActiveSupport::Callbacks implementors in order
+ # to provide better debugging/logging.
def halted_callback_hook(filter)
end
@@ -118,16 +162,7 @@ module ActiveSupport
end
module Filters
- Environment = Struct.new(:target, :halted, :value, :run_block)
-
- class End
- def call(env)
- block = env.run_block
- env.value = !env.halted && (!block || block.call)
- env
- end
- end
- ENDING = End.new
+ Environment = Struct.new(:target, :halted, :value)
class Before
def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter)
@@ -246,60 +281,15 @@ module ActiveSupport
end
private_class_method :simple
end
-
- class Around
- def self.build(callback_sequence, user_callback, user_conditions, chain_config)
- if user_conditions.any?
- halting_and_conditional(callback_sequence, user_callback, user_conditions)
- else
- halting(callback_sequence, user_callback)
- end
- end
-
- def self.halting_and_conditional(callback_sequence, user_callback, user_conditions)
- callback_sequence.around do |env, &run|
- target = env.target
- value = env.value
- halted = env.halted
-
- if !halted && user_conditions.all? { |c| c.call(target, value) }
- user_callback.call(target, value) {
- run.call.value
- }
- env
- else
- run.call
- end
- end
- end
- private_class_method :halting_and_conditional
-
- def self.halting(callback_sequence, user_callback)
- callback_sequence.around do |env, &run|
- target = env.target
- value = env.value
-
- if env.halted
- run.call
- else
- user_callback.call(target, value) {
- run.call.value
- }
- env
- end
- end
- end
- private_class_method :halting
- end
end
class Callback #:nodoc:#
def self.build(chain, filter, kind, options)
if filter.is_a?(String)
ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing string to define callback is deprecated and will be removed
- in Rails 5.1 without replacement.
- MSG
+ Passing string to define callback is deprecated and will be removed
+ in Rails 5.1 without replacement.
+ MSG
end
new chain.name, filter, kind, options, chain.config
@@ -309,7 +299,7 @@ module ActiveSupport
attr_reader :chain_config
def initialize(name, filter, kind, options, chain_config)
- @chain_config = chain_config
+ @chain_config = chain_config
@name = name
@kind = kind
@filter = filter
@@ -349,23 +339,96 @@ module ActiveSupport
# Wraps code with filter
def apply(callback_sequence)
user_conditions = conditions_lambdas
- user_callback = make_lambda @filter
+ user_callback = CallTemplate.build(@filter, self)
case kind
when :before
- Filters::Before.build(callback_sequence, user_callback, user_conditions, chain_config, @filter)
+ Filters::Before.build(callback_sequence, user_callback.make_lambda, user_conditions, chain_config, @filter)
when :after
- Filters::After.build(callback_sequence, user_callback, user_conditions, chain_config)
+ Filters::After.build(callback_sequence, user_callback.make_lambda, user_conditions, chain_config)
when :around
- Filters::Around.build(callback_sequence, user_callback, user_conditions, chain_config)
+ callback_sequence.around(user_callback, user_conditions)
end
end
+ def current_scopes
+ Array(chain_config[:scope]).map { |s| public_send(s) }
+ end
+
private
+ def compute_identifier(filter)
+ case filter
+ when String, ::Proc
+ filter.object_id
+ else
+ filter
+ end
+ end
+
+ def conditions_lambdas
+ @if.map { |c| CallTemplate.build(c, self).make_lambda } +
+ @unless.map { |c| CallTemplate.build(c, self).inverted_lambda }
+ end
+ end
+
+ # A future invocation of user-supplied code (either as a callback,
+ # or a condition filter).
+ class CallTemplate # :nodoc:
+ def initialize(target, method, arguments, block)
+ @override_target = target
+ @method_name = method
+ @arguments = arguments
+ @override_block = block
+ end
- def invert_lambda(l)
- lambda { |*args, &blk| !l.call(*args, &blk) }
+ # Return the parts needed to make this call, with the given
+ # input values.
+ #
+ # Returns an array of the form:
+ #
+ # [target, block, method, *arguments]
+ #
+ # This array can be used as such:
+ #
+ # target.send(method, *arguments, &block)
+ #
+ # The actual invocation is left up to the caller to minimize
+ # call stack pollution.
+ def expand(target, value, block)
+ result = @arguments.map { |arg|
+ case arg
+ when :value; value
+ when :target; target
+ when :block; block || raise(ArgumentError)
+ end
+ }
+
+ result.unshift @method_name
+ result.unshift @override_block || block
+ result.unshift @override_target || target
+
+ # target, block, method, *arguments = result
+ # target.send(method, *arguments, &block)
+ result
+ end
+
+ # Return a lambda that will make this call when given the input
+ # values.
+ def make_lambda
+ lambda do |target, value, &block|
+ target, block, method, *arguments = expand(target, value, block)
+ target.send(method, *arguments, &block)
end
+ end
+
+ # Return a lambda that will make this call when given the input
+ # values, but then return the boolean inverse of that result.
+ def inverted_lambda
+ lambda do |target, value, &block|
+ target, block, method, *arguments = expand(target, value, block)
+ ! target.send(method, *arguments, &block)
+ end
+ end
# Filters support:
#
@@ -374,60 +437,45 @@ module ActiveSupport
# Procs:: A proc to call with the object.
# Objects:: An object with a <tt>before_foo</tt> method on it to call.
#
- # All of these objects are converted into a lambda and handled
+ # All of these objects are converted into a CallTemplate and handled
# the same after this point.
- def make_lambda(filter)
- case filter
- when Symbol
- lambda { |target, _, &blk| target.send filter, &blk }
- when String
- l = eval "lambda { |value| #{filter} }"
- lambda { |target, value| target.instance_exec(value, &l) }
- when Conditionals::Value then filter
- when ::Proc
- if filter.arity > 1
- return lambda { |target, _, &block|
- raise ArgumentError unless block
- target.instance_exec(target, block, &filter)
- }
- end
-
- if filter.arity <= 0
- lambda { |target, _| target.instance_exec(&filter) }
- else
- lambda { |target, _| target.instance_exec(target, &filter) }
- end
+ def self.build(filter, callback)
+ case filter
+ when Symbol
+ new(nil, filter, [], nil)
+ when String
+ new(nil, :instance_exec, [:value], compile_lambda(filter))
+ when Conditionals::Value
+ new(filter, :call, [:target, :value], nil)
+ when ::Proc
+ if filter.arity > 1
+ new(nil, :instance_exec, [:target, :block], filter)
+ elsif filter.arity > 0
+ new(nil, :instance_exec, [:target], filter)
else
- scopes = Array(chain_config[:scope])
- method_to_call = scopes.map{ |s| public_send(s) }.join("_")
-
- lambda { |target, _, &blk|
- filter.public_send method_to_call, target, &blk
- }
+ new(nil, :instance_exec, [], filter)
end
- end
+ else
+ method_to_call = callback.current_scopes.join("_")
- def compute_identifier(filter)
- case filter
- when String, ::Proc
- filter.object_id
- else
- filter
- end
+ new(filter, method_to_call, [:target], nil)
end
+ end
- def conditions_lambdas
- @if.map { |c| make_lambda c } +
- @unless.map { |c| invert_lambda make_lambda c }
- end
+ def self.compile_lambda(filter)
+ eval("lambda { |value| #{filter} }")
+ end
end
- # Execute before and after filters in a sequence instead of
- # chaining them with nested lambda calls, see:
- # https://github.com/rails/rails/issues/18011
- class CallbackSequence
- def initialize(&call)
- @call = call
+ # Execute before and after filters in a sequence instead of
+ # chaining them with nested lambda calls, see:
+ # https://github.com/rails/rails/issues/18011
+ class CallbackSequence # :nodoc:
+ def initialize(nested = nil, call_template = nil, user_conditions = nil)
+ @nested = nested
+ @call_template = call_template
+ @user_conditions = user_conditions
+
@before = []
@after = []
end
@@ -442,23 +490,36 @@ module ActiveSupport
self
end
- def around(&around)
- CallbackSequence.new do |arg|
- around.call(arg) {
- call(arg)
- }
- end
+ def around(call_template, user_conditions)
+ CallbackSequence.new(self, call_template, user_conditions)
+ end
+
+ def skip?(arg)
+ arg.halted || !@user_conditions.all? { |c| c.call(arg.target, arg.value) }
end
- def call(arg)
+ def nested
+ @nested
+ end
+
+ def final?
+ !@call_template
+ end
+
+ def expand_call_template(arg, block)
+ @call_template.expand(arg.target, arg.value, block)
+ end
+
+ def invoke_before(arg)
@before.each { |b| b.call(arg) }
- value = @call.call(arg)
+ end
+
+ def invoke_after(arg)
@after.each { |a| a.call(arg) }
- value
end
end
- # An Array with a compile method.
+ # An Array with a compile method.
class CallbackChain #:nodoc:#
include Enumerable
@@ -503,7 +564,7 @@ module ActiveSupport
def compile
@callbacks || @mutex.synchronize do
- final_sequence = CallbackSequence.new { |env| Filters::ENDING.call(env) }
+ final_sequence = CallbackSequence.new
@callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback|
callback.apply callback_sequence
end
@@ -635,7 +696,7 @@ module ActiveSupport
__update_callbacks(name) do |target, chain|
filters.each do |filter|
- callback = chain.find {|c| c.matches?(type, filter) }
+ callback = chain.find { |c| c.matches?(type, filter) }
if !callback && options[:raise]
raise ArgumentError, "#{type.to_s.capitalize} #{name} callback #{filter.inspect} has not been defined"
@@ -738,34 +799,47 @@ module ActiveSupport
#
# ===== Notes
#
- # +names+ passed to `define_callbacks` must not end with
- # `!`, `?` or `=`.
+ # +names+ passed to +define_callbacks+ must not end with
+ # <tt>!</tt>, <tt>?</tt> or <tt>=</tt>.
#
- # Calling `define_callbacks` multiple times with the same +names+ will
- # overwrite previous callbacks registered with `set_callback`.
+ # Calling +define_callbacks+ multiple times with the same +names+ will
+ # overwrite previous callbacks registered with +set_callback+.
def define_callbacks(*names)
options = names.extract_options!
names.each do |name|
- class_attribute "_#{name}_callbacks", instance_writer: false
+ name = name.to_sym
+
set_callbacks name, CallbackChain.new(name, options)
module_eval <<-RUBY, __FILE__, __LINE__ + 1
- def _run_#{name}_callbacks(&block)
- __run_callbacks__(_#{name}_callbacks, &block)
- end
- RUBY
+ def _run_#{name}_callbacks(&block)
+ run_callbacks #{name.inspect}, &block
+ end
+
+ def self._#{name}_callbacks
+ get_callbacks(#{name.inspect})
+ end
+
+ def self._#{name}_callbacks=(value)
+ set_callbacks(#{name.inspect}, value)
+ end
+
+ def _#{name}_callbacks
+ __callbacks[#{name.inspect}]
+ end
+ RUBY
end
end
protected
def get_callbacks(name) # :nodoc:
- send "_#{name}_callbacks"
+ __callbacks[name.to_sym]
end
def set_callbacks(name, callbacks) # :nodoc:
- send "_#{name}_callbacks=", callbacks
+ self.__callbacks = __callbacks.merge(name.to_sym => callbacks)
end
def deprecated_false_terminator # :nodoc:
@@ -787,9 +861,9 @@ module ActiveSupport
def display_deprecation_warning_for_false_terminator
ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Returning `false` in Active Record and Active Model callbacks will not implicitly halt a callback chain in Rails 5.1.
- To explicitly halt the callback chain, please use `throw :abort` instead.
- MSG
+ Returning `false` in Active Record and Active Model callbacks will not implicitly halt a callback chain in Rails 5.1.
+ To explicitly halt the callback chain, please use `throw :abort` instead.
+ MSG
end
end
end
diff --git a/activesupport/lib/active_support/concurrency/latch.rb b/activesupport/lib/active_support/concurrency/latch.rb
deleted file mode 100644
index 53e09b685c..0000000000
--- a/activesupport/lib/active_support/concurrency/latch.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-require "concurrent/atomic/count_down_latch"
-
-module ActiveSupport
- module Concurrency
- class Latch
- def initialize(count = 1)
- if count == 1
- ActiveSupport::Deprecation.warn("ActiveSupport::Concurrency::Latch is deprecated. Please use Concurrent::Event instead.")
- else
- ActiveSupport::Deprecation.warn("ActiveSupport::Concurrency::Latch is deprecated. Please use Concurrent::CountDownLatch instead.")
- end
-
- @inner = Concurrent::CountDownLatch.new(count)
- end
-
- def release
- @inner.count_down
- end
-
- def await
- @inner.wait(nil)
- end
- end
- end
-end
diff --git a/activesupport/lib/active_support/concurrency/share_lock.rb b/activesupport/lib/active_support/concurrency/share_lock.rb
index 8969ebb080..4318523b07 100644
--- a/activesupport/lib/active_support/concurrency/share_lock.rb
+++ b/activesupport/lib/active_support/concurrency/share_lock.rb
@@ -199,7 +199,7 @@ module ActiveSupport
private
- # Must be called within synchronize
+ # Must be called within synchronize
def busy_for_exclusive?(purpose)
busy_for_sharing?(purpose) ||
@sharing.size > (@sharing[Thread.current] > 0 ? 1 : 0)
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index 1d4c83ae52..cac15e1100 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -40,12 +40,6 @@ class Array
# ['one', 'two', 'three'].to_sentence(words_connector: ' or ', last_word_connector: ' or at least ')
# # => "one or two or at least three"
#
- # [].to_sentence(fallback_string: 'none')
- # # => "none"
- #
- # ['one', 'two'].to_sentence(fallback_string: 'none')
- # # => "one and two"
- #
# Using <tt>:locale</tt> option:
#
# # Given this locale dictionary:
@@ -63,7 +57,7 @@ class Array
# ['uno', 'dos', 'tres'].to_sentence(locale: :es)
# # => "uno o dos o al menos tres"
def to_sentence(options = {})
- options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale, :fallback_string)
+ options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
default_connectors = {
words_connector: ", ",
@@ -78,7 +72,7 @@ class Array
case length
when 0
- "#{options[:fallback_string] || ''}"
+ ""
when 1
"#{self[0]}"
when 2
diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb
index 9b4a9a9992..ba422f9071 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute.rb
@@ -20,7 +20,7 @@ class Class
# Base.setting # => true
#
# In the above case as long as Subclass does not assign a value to setting
- # by performing <tt>Subclass.setting = _something_ </tt>, <tt>Subclass.setting</tt>
+ # by performing <tt>Subclass.setting = _something_</tt>, <tt>Subclass.setting</tt>
# would read value assigned to parent class. Once Subclass assigns a value then
# the value assigned by Subclass would be returned.
#
diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb
index 8a21c71a42..62397d9508 100644
--- a/activesupport/lib/active_support/core_ext/class/subclasses.rb
+++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb
@@ -6,15 +6,29 @@ class Class
# Test if this Ruby supports each_object against singleton_class
ObjectSpace.each_object(Numeric.singleton_class) {}
- def descendants # :nodoc:
+ # Returns an array with all classes that are < than its receiver.
+ #
+ # class C; end
+ # C.descendants # => []
+ #
+ # class B < C; end
+ # C.descendants # => [B]
+ #
+ # class A < B; end
+ # C.descendants # => [B, A]
+ #
+ # class D < C; end
+ # C.descendants # => [B, A, D]
+ def descendants
descendants = []
ObjectSpace.each_object(singleton_class) do |k|
+ next if k.singleton_class?
descendants.unshift k unless k == self
end
descendants
end
rescue StandardError # JRuby 9.0.4.0 and earlier
- def descendants # :nodoc:
+ def descendants
descendants = []
ObjectSpace.each_object(Class) do |k|
descendants.unshift k if k < self
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 792076a449..f2ba7fdda5 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
@@ -300,7 +300,7 @@ module DateAndTime
end
# Returns a Range representing the whole week of the current date/time.
- # Week starts on start_day, default is <tt>Date.week_start</tt> or <tt>config.week_start</tt> when set.
+ # Week starts on start_day, default is <tt>Date.beginning_of_week</tt> or <tt>config.beginning_of_week</tt> when set.
def all_week(start_day = Date.beginning_of_week)
beginning_of_week(start_day)..end_of_week(start_day)
end
@@ -334,7 +334,7 @@ module DateAndTime
end
def copy_time_to(other)
- other.change(hour: hour, min: min, sec: sec, usec: try(:usec))
+ other.change(hour: hour, min: min, sec: sec, nsec: try(:nsec))
end
end
end
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 3f4e236ab7..db95ae0db5 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
@@ -12,7 +12,11 @@ module DateAndTime
mattr_accessor(:preserve_timezone, instance_writer: false) { false }
def to_time
- preserve_timezone ? getlocal(utc_offset) : getlocal
+ if preserve_timezone
+ @_to_time_with_instance_offset ||= getlocal(utc_offset)
+ else
+ @_to_time_with_system_offset ||= getlocal
+ end
end
end
end
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 44ae96dbe8..d9b3743858 100644
--- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
@@ -64,7 +64,7 @@ class DateTime
# # => Sun, 01 Jan 2012 00:00:00 +0300
# DateTime.civil_from_format :local, 2012, 12, 17
# # => Mon, 17 Dec 2012 00:00:00 +0000
- def self.civil_from_format(utc_or_local, year, month=1, day=1, hour=0, min=0, sec=0)
+ def self.civil_from_format(utc_or_local, year, month = 1, day = 1, hour = 0, min = 0, sec = 0)
if utc_or_local.to_sym == :local
offset = ::Time.local(year, month, day).utc_offset.to_r / 86400
else
diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb
index 8ebe758078..ae08fd90c1 100644
--- a/activesupport/lib/active_support/core_ext/enumerable.rb
+++ b/activesupport/lib/active_support/core_ext/enumerable.rb
@@ -115,9 +115,14 @@ end
# and fall back to the compatible implementation, but that's much slower than
# just calling the compat method in the first place.
if Array.instance_methods(false).include?(:sum) && !(%w[a].sum rescue false)
- class Array
- alias :orig_sum :sum
+ # Using Refinements here in order not to expose our internal method
+ using Module.new {
+ refine Array do
+ alias :orig_sum :sum
+ end
+ }
+ class Array
def sum(init = nil, &block) #:nodoc:
if init.is_a?(Numeric) || first.is_a?(Numeric)
init ||= 0
diff --git a/activesupport/lib/active_support/core_ext/hash/compact.rb b/activesupport/lib/active_support/core_ext/hash/compact.rb
index 78b3387c3b..e357284be0 100644
--- a/activesupport/lib/active_support/core_ext/hash/compact.rb
+++ b/activesupport/lib/active_support/core_ext/hash/compact.rb
@@ -1,23 +1,27 @@
class Hash
- # Returns a hash with non +nil+ values.
- #
- # hash = { a: true, b: false, c: nil }
- # hash.compact # => { a: true, b: false }
- # hash # => { a: true, b: false, c: nil }
- # { c: nil }.compact # => {}
- # { c: true }.compact # => { c: true }
- def compact
- select { |_, value| !value.nil? }
+ unless Hash.instance_methods(false).include?(:compact)
+ # Returns a hash with non +nil+ values.
+ #
+ # hash = { a: true, b: false, c: nil }
+ # hash.compact # => { a: true, b: false }
+ # hash # => { a: true, b: false, c: nil }
+ # { c: nil }.compact # => {}
+ # { c: true }.compact # => { c: true }
+ def compact
+ select { |_, value| !value.nil? }
+ end
end
- # Replaces current hash with non +nil+ values.
- # Returns nil if no changes were made, otherwise returns the hash.
- #
- # hash = { a: true, b: false, c: nil }
- # hash.compact! # => { a: true, b: false }
- # hash # => { a: true, b: false }
- # { c: true }.compact! # => nil
- def compact!
- reject! { |_, value| value.nil? }
+ unless Hash.instance_methods(false).include?(:compact!)
+ # Replaces current hash with non +nil+ values.
+ # Returns +nil+ if no changes were made, otherwise returns the hash.
+ #
+ # hash = { a: true, b: false, c: nil }
+ # hash.compact! # => { a: true, b: false }
+ # hash # => { a: true, b: false }
+ # { c: true }.compact! # => nil
+ def compact!
+ reject! { |_, value| value.nil? }
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 3cd96ccb9a..2a58a7f1ca 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -160,7 +160,7 @@ module ActiveSupport
def normalize_keys(params)
case params
when Hash
- Hash[params.map { |k,v| [k.to_s.tr("-", "_"), normalize_keys(v)] } ]
+ Hash[params.map { |k, v| [k.to_s.tr("-", "_"), normalize_keys(v)] } ]
when Array
params.map { |v| normalize_keys(v) }
else
@@ -187,7 +187,7 @@ module ActiveSupport
end
if become_array?(value)
- _, entries = Array.wrap(value.detect { |k,v| not v.is_a?(String) })
+ _, entries = Array.wrap(value.detect { |k, v| not v.is_a?(String) })
if entries.nil? || value["__content__"].try(:empty?)
[]
else
@@ -206,7 +206,7 @@ module ActiveSupport
elsif become_empty_string?(value)
""
elsif become_hash?(value)
- xml_value = Hash[value.map { |k,v| [k, deep_to_h(v)] }]
+ xml_value = Hash[value.map { |k, v| [k, deep_to_h(v)] }]
# Turn { files: { file: #<StringIO> } } into { files: #<StringIO> } so it is compatible with
# how multipart uploaded files from HTML appear
diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb
index 1bfa18aeee..b7089357a8 100644
--- a/activesupport/lib/active_support/core_ext/hash/keys.rb
+++ b/activesupport/lib/active_support/core_ext/hash/keys.rb
@@ -52,14 +52,14 @@ class Hash
# hash.symbolize_keys
# # => {:name=>"Rob", :age=>"28"}
def symbolize_keys
- transform_keys{ |key| key.to_sym rescue key }
+ transform_keys { |key| key.to_sym rescue key }
end
alias_method :to_options, :symbolize_keys
# Destructively converts all keys to symbols, as long as they respond
# to +to_sym+. Same as +symbolize_keys+, but modifies +self+.
def symbolize_keys!
- transform_keys!{ |key| key.to_sym rescue key }
+ transform_keys! { |key| key.to_sym rescue key }
end
alias_method :to_options!, :symbolize_keys!
@@ -128,14 +128,14 @@ class Hash
# hash.deep_symbolize_keys
# # => {:person=>{:name=>"Rob", :age=>"28"}}
def deep_symbolize_keys
- deep_transform_keys{ |key| key.to_sym rescue key }
+ deep_transform_keys { |key| key.to_sym rescue key }
end
# Destructively converts all keys to symbols, as long as they respond
# to +to_sym+. This includes the keys from the root hash and from all
# nested hashes and arrays.
def deep_symbolize_keys!
- deep_transform_keys!{ |key| key.to_sym rescue key }
+ deep_transform_keys! { |key| key.to_sym rescue key }
end
private
@@ -147,7 +147,7 @@ class Hash
result[yield(key)] = _deep_transform_keys_in_object(value, &block)
end
when Array
- object.map {|e| _deep_transform_keys_in_object(e, &block) }
+ object.map { |e| _deep_transform_keys_in_object(e, &block) }
else
object
end
@@ -162,7 +162,7 @@ class Hash
end
object
when Array
- object.map! {|e| _deep_transform_keys_in_object!(e, &block)}
+ object.map! { |e| _deep_transform_keys_in_object!(e, &block) }
else
object
end
diff --git a/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb b/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb
index fbb482435d..efb9d1b8a0 100644
--- a/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb
+++ b/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb
@@ -16,7 +16,7 @@ class Hash
# Destructive +reverse_merge+.
def reverse_merge!(other_hash)
# right wins if there is no left
- merge!( other_hash ){|key,left,right| left }
+ merge!(other_hash) { |key, left, right| left }
end
alias_method :reverse_update, :reverse_merge!
end
diff --git a/activesupport/lib/active_support/core_ext/hash/transform_values.rb b/activesupport/lib/active_support/core_ext/hash/transform_values.rb
index 7d507ac998..2f693bff0c 100644
--- a/activesupport/lib/active_support/core_ext/hash/transform_values.rb
+++ b/activesupport/lib/active_support/core_ext/hash/transform_values.rb
@@ -16,7 +16,7 @@ class Hash
result[key] = yield(value)
end
result
- end
+ end unless method_defined? :transform_values
# Destructively converts all values using the +block+ operations.
# Same as +transform_values+ but modifies +self+.
@@ -25,5 +25,6 @@ class Hash
each do |key, value|
self[key] = yield(value)
end
- end
+ end unless method_defined? :transform_values!
+ # TODO: Remove this file when supporting only Ruby 2.4+.
end
diff --git a/activesupport/lib/active_support/core_ext/kernel/debugger.rb b/activesupport/lib/active_support/core_ext/kernel/debugger.rb
deleted file mode 100644
index dccfa3e9bb..0000000000
--- a/activesupport/lib/active_support/core_ext/kernel/debugger.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-require "active_support/deprecation"
-
-ActiveSupport::Deprecation.warn("This file is deprecated and will be removed in Rails 5.1 with no replacement.")
diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
index d0197af95f..c02618d5f3 100644
--- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
@@ -1,7 +1,7 @@
module Kernel
module_function
- # Sets $VERBOSE to nil for the duration of the block and back to its original
+ # Sets $VERBOSE to +nil+ for the duration of the block and back to its original
# value afterwards.
#
# silence_warnings do
diff --git a/activesupport/lib/active_support/core_ext/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb
index 4cb6ffea5e..3cf7ea0a97 100644
--- a/activesupport/lib/active_support/core_ext/load_error.rb
+++ b/activesupport/lib/active_support/core_ext/load_error.rb
@@ -1,3 +1,4 @@
+require "active_support/deprecation"
require "active_support/deprecation/proxy_wrappers"
class LoadError
@@ -26,5 +27,3 @@ class LoadError
location.sub(/\.rb$/, "".freeze) == path.sub(/\.rb$/, "".freeze)
end
end
-
-MissingSourceFile = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("MissingSourceFile", "LoadError")
diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb
index 57feea69a5..2930255557 100644
--- a/activesupport/lib/active_support/core_ext/module.rb
+++ b/activesupport/lib/active_support/core_ext/module.rb
@@ -9,4 +9,3 @@ 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 "active_support/core_ext/module/qualified_const"
diff --git a/activesupport/lib/active_support/core_ext/module/aliasing.rb b/activesupport/lib/active_support/core_ext/module/aliasing.rb
index 4a04bdd446..c48bd3354a 100644
--- a/activesupport/lib/active_support/core_ext/module/aliasing.rb
+++ b/activesupport/lib/active_support/core_ext/module/aliasing.rb
@@ -1,52 +1,4 @@
class Module
- # NOTE: This method is deprecated. Please use <tt>Module#prepend</tt> that
- # comes with Ruby 2.0 or newer instead.
- #
- # Encapsulates the common pattern of:
- #
- # alias_method :foo_without_feature, :foo
- # alias_method :foo, :foo_with_feature
- #
- # With this, you simply do:
- #
- # alias_method_chain :foo, :feature
- #
- # And both aliases are set up for you.
- #
- # Query and bang methods (foo?, foo!) keep the same punctuation:
- #
- # alias_method_chain :foo?, :feature
- #
- # is equivalent to
- #
- # alias_method :foo_without_feature?, :foo?
- # alias_method :foo?, :foo_with_feature?
- #
- # so you can safely chain foo, foo?, foo! and/or foo= with the same feature.
- def alias_method_chain(target, feature)
- ActiveSupport::Deprecation.warn("alias_method_chain is deprecated. Please, use Module#prepend instead. From module, you can access the original method using super.")
-
- # Strip out punctuation on predicates, bang or writer methods since
- # e.g. target?_without_feature is not a valid method name.
- aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ""), $1
- yield(aliased_target, punctuation) if block_given?
-
- with_method = "#{aliased_target}_with_#{feature}#{punctuation}"
- without_method = "#{aliased_target}_without_#{feature}#{punctuation}"
-
- alias_method without_method, target
- alias_method target, with_method
-
- case
- when public_method_defined?(without_method)
- public target
- when protected_method_defined?(without_method)
- protected target
- when private_method_defined?(without_method)
- private target
- end
- end
-
# Allows you to make aliases for attributes, which includes
# getter, setter, and a predicate.
#
diff --git a/activesupport/lib/active_support/core_ext/module/attr_internal.rb b/activesupport/lib/active_support/core_ext/module/attr_internal.rb
index 48421a8850..5081d5f7a3 100644
--- a/activesupport/lib/active_support/core_ext/module/attr_internal.rb
+++ b/activesupport/lib/active_support/core_ext/module/attr_internal.rb
@@ -1,12 +1,12 @@
class Module
# Declares an attribute reader backed by an internally-named instance variable.
def attr_internal_reader(*attrs)
- attrs.each {|attr_name| attr_internal_define(attr_name, :reader)}
+ attrs.each { |attr_name| attr_internal_define(attr_name, :reader) }
end
# Declares an attribute writer backed by an internally-named instance variable.
def attr_internal_writer(*attrs)
- attrs.each {|attr_name| attr_internal_define(attr_name, :writer)}
+ attrs.each { |attr_name| attr_internal_define(attr_name, :writer) }
end
# Declares an attribute reader and writer backed by an internally-named instance
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
index 4c16b26138..1e82b4acc2 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
@@ -34,7 +34,7 @@ class Module
# end
#
# Current.new.user # => NoMethodError
- def thread_mattr_reader(*syms)
+ def thread_mattr_reader(*syms) # :nodoc:
options = syms.extract_options!
syms.each do |sym|
@@ -77,7 +77,7 @@ class Module
# end
#
# Current.new.user = "DHH" # => NoMethodError
- def thread_mattr_writer(*syms)
+ def thread_mattr_writer(*syms) # :nodoc:
options = syms.extract_options!
syms.each do |sym|
raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym)
@@ -140,9 +140,9 @@ class Module
#
# Current.new.user = "DHH" # => NoMethodError
# Current.new.user # => NoMethodError
- def thread_mattr_accessor(*syms, &blk)
- thread_mattr_reader(*syms, &blk)
- thread_mattr_writer(*syms, &blk)
+ def thread_mattr_accessor(*syms)
+ thread_mattr_reader(*syms)
+ thread_mattr_writer(*syms)
end
alias :thread_cattr_accessor :thread_mattr_accessor
end
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index f01ff23bcf..19f692e943 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -6,11 +6,12 @@ class Module
# option is not used.
class DelegationError < NoMethodError; end
+ RUBY_RESERVED_KEYWORDS = %w(alias and BEGIN begin break case class def defined? do
+ else elsif END end ensure false for if in module next nil not or redo rescue retry
+ return self super then true undef unless until when while yield)
+ DELEGATION_RESERVED_KEYWORDS = %w(_ arg args block)
DELEGATION_RESERVED_METHOD_NAMES = Set.new(
- %w(_ arg args alias and BEGIN begin block break case class def defined? do
- else elsif END end ensure false for if in module next nil not or redo
- rescue retry return self super then true undef unless until when while
- yield)
+ RUBY_RESERVED_KEYWORDS + DELEGATION_RESERVED_KEYWORDS
).freeze
# Provides a +delegate+ class method to easily expose contained objects'
@@ -256,7 +257,7 @@ class Module
# end
# end
#
- # The target can be anything callable withing the object. E.g. instance
+ # The target can be anything callable within the object. E.g. instance
# variables, methods, constants ant the likes.
def delegate_missing_to(target)
target = target.to_s
diff --git a/activesupport/lib/active_support/core_ext/module/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb
index 4f854a718b..0665aa88bc 100644
--- a/activesupport/lib/active_support/core_ext/module/introspection.rb
+++ b/activesupport/lib/active_support/core_ext/module/introspection.rb
@@ -55,12 +55,4 @@ class Module
parents << Object unless parents.include? Object
parents
end
-
- def local_constants #:nodoc:
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Module#local_constants is deprecated and will be removed in Rails 5.1.
- Use Module#constants(false) instead.
- MSG
- constants(false)
- end
end
diff --git a/activesupport/lib/active_support/core_ext/module/method_transplanting.rb b/activesupport/lib/active_support/core_ext/module/method_transplanting.rb
deleted file mode 100644
index dccfa3e9bb..0000000000
--- a/activesupport/lib/active_support/core_ext/module/method_transplanting.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-require "active_support/deprecation"
-
-ActiveSupport::Deprecation.warn("This file is deprecated and will be removed in Rails 5.1 with no replacement.")
diff --git a/activesupport/lib/active_support/core_ext/module/qualified_const.rb b/activesupport/lib/active_support/core_ext/module/qualified_const.rb
deleted file mode 100644
index 62f0687ae9..0000000000
--- a/activesupport/lib/active_support/core_ext/module/qualified_const.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-require "active_support/core_ext/string/inflections"
-
-#--
-# Allows code reuse in the methods below without polluting Module.
-#++
-
-module ActiveSupport
- module QualifiedConstUtils
- def self.raise_if_absolute(path)
- raise NameError.new("wrong constant name #$&") if path =~ /\A::[^:]+/
- end
-
- def self.names(path)
- path.split("::")
- end
- end
-end
-
-##
-# Extends the API for constants to be able to deal with qualified names. Arguments
-# are assumed to be relative to the receiver.
-#
-#--
-# Qualified names are required to be relative because we are extending existing
-# methods that expect constant names, ie, relative paths of length 1. For example,
-# Object.const_get('::String') raises NameError and so does qualified_const_get.
-#++
-class Module
- def qualified_const_defined?(path, search_parents=true)
- ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
- Module#qualified_const_defined? is deprecated in favour of the builtin
- Module#const_defined? and will be removed in Rails 5.1.
- MESSAGE
-
- ActiveSupport::QualifiedConstUtils.raise_if_absolute(path)
-
- ActiveSupport::QualifiedConstUtils.names(path).inject(self) do |mod, name|
- return unless mod.const_defined?(name, search_parents)
- mod.const_get(name)
- end
- return true
- end
-
- def qualified_const_get(path)
- ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
- Module#qualified_const_get is deprecated in favour of the builtin
- Module#const_get and will be removed in Rails 5.1.
- MESSAGE
-
- ActiveSupport::QualifiedConstUtils.raise_if_absolute(path)
-
- ActiveSupport::QualifiedConstUtils.names(path).inject(self) do |mod, name|
- mod.const_get(name)
- end
- end
-
- def qualified_const_set(path, value)
- ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
- Module#qualified_const_set is deprecated in favour of the builtin
- Module#const_set and will be removed in Rails 5.1.
- MESSAGE
-
- ActiveSupport::QualifiedConstUtils.raise_if_absolute(path)
-
- const_name = path.demodulize
- mod_name = path.deconstantize
- mod = mod_name.empty? ? self : const_get(mod_name)
- mod.const_set(const_name, value)
- end
-end
diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
index 5ac312790d..946f8ddeab 100644
--- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
@@ -126,15 +126,10 @@ module ActiveSupport::NumericWithFormat
end
end
end
-
- def to_formatted_s(*args)
- to_s(*args)
- end
- deprecate to_formatted_s: :to_s
end
# Ruby 2.4+ unifies Fixnum & Bignum into Integer.
-if Integer == Fixnum
+if 0.class == Integer
Integer.prepend ActiveSupport::NumericWithFormat
else
Fixnum.prepend ActiveSupport::NumericWithFormat
diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb
index aa2282cb7e..9485e74816 100644
--- a/activesupport/lib/active_support/core_ext/object/duplicable.rb
+++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb
@@ -1,7 +1,7 @@
#--
-# Most objects are cloneable, but not all. For example you can't dup +nil+:
+# Most objects are cloneable, but not all. For example you can't dup methods:
#
-# nil.dup # => TypeError: can't dup NilClass
+# method(:puts).dup # => TypeError: allocator undefined for Method
#
# Classes may signal their instances are not duplicable removing +dup+/+clone+
# or raising exceptions from them. So, to dup an arbitrary object you normally
@@ -19,7 +19,7 @@
class Object
# Can you safely dup this object?
#
- # False for +nil+, +false+, +true+, symbol, number, method objects;
+ # False for method objects;
# true otherwise.
def duplicable?
true
@@ -27,52 +27,78 @@ class Object
end
class NilClass
- # +nil+ is not duplicable:
- #
- # nil.duplicable? # => false
- # nil.dup # => TypeError: can't dup NilClass
- def duplicable?
- false
+ begin
+ nil.dup
+ rescue TypeError
+
+ # +nil+ is not duplicable:
+ #
+ # nil.duplicable? # => false
+ # nil.dup # => TypeError: can't dup NilClass
+ def duplicable?
+ false
+ end
end
end
class FalseClass
- # +false+ is not duplicable:
- #
- # false.duplicable? # => false
- # false.dup # => TypeError: can't dup FalseClass
- def duplicable?
- false
+ begin
+ false.dup
+ rescue TypeError
+
+ # +false+ is not duplicable:
+ #
+ # false.duplicable? # => false
+ # false.dup # => TypeError: can't dup FalseClass
+ def duplicable?
+ false
+ end
end
end
class TrueClass
- # +true+ is not duplicable:
- #
- # true.duplicable? # => false
- # true.dup # => TypeError: can't dup TrueClass
- def duplicable?
- false
+ begin
+ true.dup
+ rescue TypeError
+
+ # +true+ is not duplicable:
+ #
+ # true.duplicable? # => false
+ # true.dup # => TypeError: can't dup TrueClass
+ def duplicable?
+ false
+ end
end
end
class Symbol
- # Symbols are not duplicable:
- #
- # :my_symbol.duplicable? # => false
- # :my_symbol.dup # => TypeError: can't dup Symbol
- def duplicable?
- false
+ begin
+ :symbol.dup # Ruby 2.4.x.
+ 'symbol_from_string'.to_sym.dup # Some symbols can't `dup` in Ruby 2.4.0.
+ rescue TypeError
+
+ # Symbols are not duplicable:
+ #
+ # :my_symbol.duplicable? # => false
+ # :my_symbol.dup # => TypeError: can't dup Symbol
+ def duplicable?
+ false
+ end
end
end
class Numeric
- # Numbers are not duplicable:
- #
- # 3.duplicable? # => false
- # 3.dup # => TypeError: can't dup Integer
- def duplicable?
- false
+ begin
+ 1.dup
+ rescue TypeError
+
+ # Numbers are not duplicable:
+ #
+ # 3.duplicable? # => false
+ # 3.dup # => TypeError: can't dup Integer
+ def duplicable?
+ false
+ end
end
end
@@ -96,3 +122,23 @@ class Method
false
end
end
+
+class Complex
+ # Complexes are not duplicable:
+ #
+ # Complex(1).duplicable? # => false
+ # Complex(1).dup # => TypeError: can't copy Complex
+ def duplicable?
+ false
+ end
+end
+
+class Rational
+ # Rationals are not duplicable:
+ #
+ # Rational(1).duplicable? # => false
+ # Rational(1).dup # => TypeError: can't copy Rational
+ def duplicable?
+ false
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/regexp.rb b/activesupport/lib/active_support/core_ext/regexp.rb
index 062d568228..d77d01bf42 100644
--- a/activesupport/lib/active_support/core_ext/regexp.rb
+++ b/activesupport/lib/active_support/core_ext/regexp.rb
@@ -3,7 +3,7 @@ class Regexp #:nodoc:
options & MULTILINE == MULTILINE
end
- def match?(string, pos=0)
+ def match?(string, pos = 0)
!!match(string, pos)
end unless //.respond_to?(:match?)
end
diff --git a/activesupport/lib/active_support/core_ext/securerandom.rb b/activesupport/lib/active_support/core_ext/securerandom.rb
index 92392d1a92..a57685bea1 100644
--- a/activesupport/lib/active_support/core_ext/securerandom.rb
+++ b/activesupport/lib/active_support/core_ext/securerandom.rb
@@ -1,12 +1,12 @@
require "securerandom"
module SecureRandom
- BASE58_ALPHABET = ("0".."9").to_a + ("A".."Z").to_a + ("a".."z").to_a - ["0", "O", "I", "l"]
+ BASE58_ALPHABET = ("0".."9").to_a + ("A".."Z").to_a + ("a".."z").to_a - ["0", "O", "I", "l"]
# SecureRandom.base58 generates a random base58 string.
#
# The argument _n_ specifies the length, of the random string to be generated.
#
- # If _n_ is not specified or is nil, 16 is assumed. It may be larger in the future.
+ # If _n_ is not specified or is +nil+, 16 is assumed. It may be larger in the future.
#
# The result may contain alphanumeric characters except 0, O, I and l
#
diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb
index caa48e34c5..6133826f37 100644
--- a/activesupport/lib/active_support/core_ext/string/access.rb
+++ b/activesupport/lib/active_support/core_ext/string/access.rb
@@ -3,7 +3,7 @@ class String
# position. The first character of the string is at position 0, the next at
# position 1, and so on. If a range is supplied, a substring containing
# characters at offsets given by the range is returned. In both cases, if an
- # offset is negative, it is counted from the end of the string. Returns nil
+ # offset is negative, it is counted from the end of the string. Returns +nil+
# if the initial offset falls outside the string. Returns an empty string if
# the beginning of the range is greater than the end of the string.
#
@@ -17,7 +17,7 @@ class String
#
# If a Regexp is given, the matching portion of the string is returned.
# If a String is given, that given string is returned if it occurs in
- # the string. In both cases, nil is returned if there is no match.
+ # the string. In both cases, +nil+ is returned if there is no match.
#
# str = "hello"
# str.at(/lo/) # => "lo"
diff --git a/activesupport/lib/active_support/core_ext/string/indent.rb b/activesupport/lib/active_support/core_ext/string/indent.rb
index 4cc332aa23..d7b58301d3 100644
--- a/activesupport/lib/active_support/core_ext/string/indent.rb
+++ b/activesupport/lib/active_support/core_ext/string/indent.rb
@@ -2,7 +2,7 @@ class String
# Same as +indent+, except it indents the receiver in-place.
#
# Returns the indented string, or +nil+ if there was nothing to indent.
- def indent!(amount, indent_string=nil, indent_empty_lines=false)
+ def indent!(amount, indent_string = nil, indent_empty_lines = false)
indent_string = indent_string || self[/^[ \t]/] || " "
re = indent_empty_lines ? /^/ : /^(?!$)/
gsub!(re, indent_string * amount)
@@ -37,7 +37,7 @@ class String
# "foo\n\nbar".indent(2) # => " foo\n\n bar"
# "foo\n\nbar".indent(2, nil, true) # => " foo\n \n bar"
#
- def indent(amount, indent_string=nil, indent_empty_lines=false)
- dup.tap {|_| _.indent!(amount, indent_string, indent_empty_lines)}
+ def indent(amount, indent_string = nil, indent_empty_lines = false)
+ dup.tap { |_| _.indent!(amount, indent_string, indent_empty_lines) }
end
end
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index 7e12700c8c..4eabce79e2 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -67,7 +67,7 @@ class String
end
# +safe_constantize+ tries to find a declared constant with the name specified
- # in the string. It returns nil when the name is not in CamelCase
+ # in the string. It returns +nil+ when the name is not in CamelCase
# or is not initialized. See ActiveSupport::Inflector.safe_constantize
#
# 'Module'.safe_constantize # => Module
@@ -178,11 +178,7 @@ class String
#
# <%= link_to(@person.name, person_path) %>
# # => <a href="/person/1-Donald-E-Knuth">Donald E. Knuth</a>
- def parameterize(sep = :unused, separator: "-", preserve_case: false)
- unless sep == :unused
- ActiveSupport::Deprecation.warn("Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '#{sep}'` instead.")
- separator = sep
- end
+ def parameterize(separator: "-", preserve_case: false)
ActiveSupport::Inflector.parameterize(self, separator: separator, preserve_case: preserve_case)
end
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 2b7e556ced..227c34b032 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -202,7 +202,7 @@ module ActiveSupport #:nodoc:
def %(args)
case args
when Hash
- escaped_args = Hash[args.map { |k,arg| [k, html_escape_interpolated_argument(arg)] }]
+ escaped_args = Hash[args.map { |k, arg| [k, html_escape_interpolated_argument(arg)] }]
else
escaped_args = Array(args).map { |arg| html_escape_interpolated_argument(arg) }
end
diff --git a/activesupport/lib/active_support/core_ext/struct.rb b/activesupport/lib/active_support/core_ext/struct.rb
deleted file mode 100644
index dccfa3e9bb..0000000000
--- a/activesupport/lib/active_support/core_ext/struct.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-require "active_support/deprecation"
-
-ActiveSupport::Deprecation.warn("This file is deprecated and will be removed in Rails 5.1 with no replacement.")
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 2cbfa14772..cbdcb86d6d 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -104,7 +104,7 @@ class Time
raise ArgumentError, "Can't change both :nsec and :usec at the same time: #{options.inspect}" if options[:usec]
new_usec = Rational(new_nsec, 1000)
else
- new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000))
+ new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000))
end
if utc?
diff --git a/activesupport/lib/active_support/core_ext/time/marshal.rb b/activesupport/lib/active_support/core_ext/time/marshal.rb
deleted file mode 100644
index d095d76ebb..0000000000
--- a/activesupport/lib/active_support/core_ext/time/marshal.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-require "active_support/deprecation"
-
-ActiveSupport::Deprecation.warn("This is deprecated and will be removed in Rails 5.1 with no replacement.")
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index e585e89563..e125b657f2 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -6,7 +6,6 @@ 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/module/qualified_const"
require "active_support/core_ext/object/blank"
require "active_support/core_ext/kernel/reporting"
require "active_support/core_ext/load_error"
@@ -108,7 +107,7 @@ module ActiveSupport #:nodoc:
def initialize
@watching = []
- @stack = Hash.new { |h,k| h[k] = [] }
+ @stack = Hash.new { |h, k| h[k] = [] }
end
def each(&block)
@@ -243,7 +242,7 @@ module ActiveSupport #:nodoc:
# resolution deterministic for constants with the same relative name in
# different namespaces whose evaluation would depend on load order
# otherwise.
- def require_dependency(file_name, message = "No such file to load -- %s")
+ def require_dependency(file_name, message = "No such file to load -- %s.rb")
file_name = file_name.to_path if file_name.respond_to?(:to_path)
unless file_name.is_a?(String)
raise ArgumentError, "the file name must either be a String or implement #to_path -- you passed #{file_name.inspect}"
@@ -371,7 +370,7 @@ module ActiveSupport #:nodoc:
load_args = ["#{file_name}.rb"]
load_args << const_path unless const_path.nil?
- if !warnings_on_first_load or history.include?(expanded)
+ if !warnings_on_first_load || history.include?(expanded)
result = load_file(*load_args)
else
enable_warnings { result = load_file(*load_args) }
@@ -547,7 +546,7 @@ module ActiveSupport #:nodoc:
end
name_error = NameError.new("uninitialized constant #{qualified_name}", const_name)
- name_error.set_backtrace(caller.reject {|l| l.starts_with? __FILE__ })
+ name_error.set_backtrace(caller.reject { |l| l.starts_with? __FILE__ })
raise name_error
end
diff --git a/activesupport/lib/active_support/deprecation/instance_delegator.rb b/activesupport/lib/active_support/deprecation/instance_delegator.rb
index 8efa6aabdc..6d390f3b37 100644
--- a/activesupport/lib/active_support/deprecation/instance_delegator.rb
+++ b/activesupport/lib/active_support/deprecation/instance_delegator.rb
@@ -6,6 +6,7 @@ module ActiveSupport
module InstanceDelegator # :nodoc:
def self.included(base)
base.extend(ClassMethods)
+ base.singleton_class.prepend(OverrideDelegators)
base.public_class_method :new
end
@@ -19,6 +20,18 @@ module ActiveSupport
singleton_class.delegate(method_name, to: :instance)
end
end
+
+ module OverrideDelegators # :nodoc:
+ def warn(message = nil, callstack = nil)
+ callstack ||= caller_locations(2)
+ super
+ end
+
+ def deprecation_warning(deprecated_method_name, message = nil, caller_backtrace = nil)
+ caller_backtrace ||= caller_locations(2)
+ super
+ end
+ end
end
end
end
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 6c6922749d..c9e8c8fdc4 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -7,22 +7,30 @@ module ActiveSupport
#
# 1.month.ago # equivalent to Time.now.advance(months: -1)
class Duration
+ EPOCH = ::Time.utc(2000)
+
attr_accessor :value, :parts
autoload :ISO8601Parser, "active_support/duration/iso8601_parser"
autoload :ISO8601Serializer, "active_support/duration/iso8601_serializer"
def initialize(value, parts) #:nodoc:
- @value, @parts = value, parts
+ @value, @parts = value, parts.to_h
+ @parts.default = 0
end
# Adds another Duration or a Numeric to this Duration. Numeric values
# are treated as seconds.
def +(other)
if Duration === other
- Duration.new(value + other.value, @parts + other.parts)
+ parts = @parts.dup
+ other.parts.each do |(key, value)|
+ parts[key] += value
+ end
+ Duration.new(value + other.value, parts)
else
- Duration.new(value + other, @parts + [[:seconds, other]])
+ seconds = @parts[:seconds] + other
+ Duration.new(value + other, @parts.merge(seconds: seconds))
end
end
@@ -33,7 +41,7 @@ module ActiveSupport
end
def -@ #:nodoc:
- Duration.new(-value, parts.map { |type,number| [type, -number] })
+ Duration.new(-value, parts.map { |type, number| [type, -number] })
end
def is_a?(klass) #:nodoc:
@@ -119,9 +127,9 @@ module ActiveSupport
def inspect #:nodoc:
parts.
- reduce(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }.
- sort_by {|unit, _ | [:years, :months, :weeks, :days, :hours, :minutes, :seconds].index(unit)}.
- map {|unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}"}.
+ reduce(::Hash.new(0)) { |h, (l, r)| h[l] += r; h }.
+ sort_by { |unit, _ | [:years, :months, :weeks, :days, :hours, :minutes, :seconds].index(unit) }.
+ map { |unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}" }.
to_sentence(locale: ::I18n.default_locale)
end
@@ -129,7 +137,7 @@ module ActiveSupport
to_i
end
- def respond_to_missing?(method, include_private=false) #:nodoc:
+ def respond_to_missing?(method, include_private = false) #:nodoc:
@value.respond_to?(method, include_private)
end
@@ -140,8 +148,7 @@ module ActiveSupport
# If invalid string is provided, it will raise +ActiveSupport::Duration::ISO8601Parser::ParsingError+.
def self.parse(iso8601duration)
parts = ISO8601Parser.new(iso8601duration).parse!
- time = ::Time.current
- new(time.advance(parts) - time, parts)
+ new(EPOCH.advance(parts) - EPOCH, parts)
end
# Build ISO 8601 Duration string for this duration.
@@ -152,10 +159,10 @@ module ActiveSupport
delegate :<=>, to: :value
- protected
+ private
- def sum(sign, time = ::Time.current) #:nodoc:
- parts.inject(time) do |t,(type,number)|
+ def sum(sign, time = ::Time.current)
+ parts.inject(time) do |t, (type, number)|
if t.acts_like?(:time) || t.acts_like?(:date)
if type == :seconds
t.since(sign * number)
@@ -172,9 +179,7 @@ module ActiveSupport
end
end
- private
-
- def method_missing(method, *args, &block) #:nodoc:
+ def method_missing(method, *args, &block)
value.send(method, *args, &block)
end
end
diff --git a/activesupport/lib/active_support/duration/iso8601_parser.rb b/activesupport/lib/active_support/duration/iso8601_parser.rb
index df56f13b8d..e96cb8e883 100644
--- a/activesupport/lib/active_support/duration/iso8601_parser.rb
+++ b/activesupport/lib/active_support/duration/iso8601_parser.rb
@@ -84,7 +84,7 @@ module ActiveSupport
scanner.eos?
end
- # Parses number which can be a float with either comma or period.
+ # Parses number which can be a float with either comma or period.
def number
PERIOD_OR_COMMA.match?(scanner[1]) ? scanner[1].tr(COMMA, PERIOD).to_f : scanner[1].to_i
end
@@ -97,7 +97,7 @@ module ActiveSupport
raise ParsingError, "Invalid ISO 8601 duration: #{scanner.string.inspect} #{reason}".strip
end
- # Checks for various semantic errors as stated in ISO 8601 standard.
+ # Checks for various semantic errors as stated in ISO 8601 standard.
def validate!
raise_parsing_error("is empty duration") if parts.empty?
diff --git a/activesupport/lib/active_support/duration/iso8601_serializer.rb b/activesupport/lib/active_support/duration/iso8601_serializer.rb
index 542630b950..51d53e2f8d 100644
--- a/activesupport/lib/active_support/duration/iso8601_serializer.rb
+++ b/activesupport/lib/active_support/duration/iso8601_serializer.rb
@@ -26,7 +26,7 @@ module ActiveSupport
if parts.key?(:seconds)
time << "#{sprintf(@precision ? "%0.0#{@precision}f" : '%g', parts[:seconds])}S"
end
- output << "T#{time}" if time.present?
+ output << "T#{time}" unless time.empty?
"#{sign}#{output}"
end
@@ -37,7 +37,7 @@ module ActiveSupport
# Zero parts are removed as not significant.
# If all parts are negative it will negate all of them and return minus as a sign.
def normalize
- parts = @duration.parts.each_with_object(Hash.new(0)) do |(k,v),p|
+ parts = @duration.parts.each_with_object(Hash.new(0)) do |(k, v), p|
p[k] += v unless v.zero?
end
# If all parts are negative - let's make a negative duration
diff --git a/activesupport/lib/active_support/execution_wrapper.rb b/activesupport/lib/active_support/execution_wrapper.rb
index 4c8b03c9df..3384d12d5b 100644
--- a/activesupport/lib/active_support/execution_wrapper.rb
+++ b/activesupport/lib/active_support/execution_wrapper.rb
@@ -19,6 +19,23 @@ module ActiveSupport
set_callback(:complete, *args, &block)
end
+ class RunHook < Struct.new(:hook) # :nodoc:
+ def before(target)
+ hook_state = target.send(:hook_state)
+ hook_state[hook] = hook.run
+ end
+ end
+
+ class CompleteHook < Struct.new(:hook) # :nodoc:
+ def before(target)
+ hook_state = target.send(:hook_state)
+ if hook_state.key?(hook)
+ hook.complete hook_state[hook]
+ end
+ end
+ alias after before
+ end
+
# Register an object to be invoked during both the +run+ and
# +complete+ steps.
#
@@ -29,19 +46,11 @@ module ActiveSupport
# invoked in that situation.)
def self.register_hook(hook, outer: false)
if outer
- run_args = [prepend: true]
- complete_args = [:after]
+ to_run RunHook.new(hook), prepend: true
+ to_complete :after, CompleteHook.new(hook)
else
- run_args = complete_args = []
- end
-
- to_run(*run_args) do
- hook_state[hook] = hook.run
- end
- to_complete(*complete_args) do
- if hook_state.key?(hook)
- hook.complete hook_state[hook]
- end
+ to_run RunHook.new(hook)
+ to_complete CompleteHook.new(hook)
end
end
diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb
index 0b7d67e37a..2dbbfadac1 100644
--- a/activesupport/lib/active_support/file_update_checker.rb
+++ b/activesupport/lib/active_support/file_update_checker.rb
@@ -105,13 +105,13 @@ module ActiveSupport
@updated_at || max_mtime(paths) || Time.at(0)
end
- # This method returns the maximum mtime of the files in +paths+, or +nil+
- # if the array is empty.
- #
- # Files with a mtime in the future are ignored. Such abnormal situation
- # can happen for example if the user changes the clock by hand. It is
- # healthy to consider this edge case because with mtimes in the future
- # reloading is not triggered.
+ # This method returns the maximum mtime of the files in +paths+, or +nil+
+ # if the array is empty.
+ #
+ # Files with a mtime in the future are ignored. Such abnormal situation
+ # can happen for example if the user changes the clock by hand. It is
+ # healthy to consider this edge case because with mtimes in the future
+ # reloading is not triggered.
def max_mtime(paths)
time_now = Time.now
max_mtime = nil
@@ -145,7 +145,7 @@ module ActiveSupport
end
def escape(key)
- key.gsub(",",'\,')
+ key.gsub(",", '\,')
end
def compile_ext(array)
diff --git a/activesupport/lib/active_support/gzip.rb b/activesupport/lib/active_support/gzip.rb
index 4aee8dddba..84eef6a623 100644
--- a/activesupport/lib/active_support/gzip.rb
+++ b/activesupport/lib/active_support/gzip.rb
@@ -25,7 +25,7 @@ module ActiveSupport
end
# Compresses a string using gzip.
- def self.compress(source, level=Zlib::DEFAULT_COMPRESSION, strategy=Zlib::DEFAULT_STRATEGY)
+ def self.compress(source, level = Zlib::DEFAULT_COMPRESSION, strategy = Zlib::DEFAULT_STRATEGY)
output = Stream.new
gz = Zlib::GzipWriter.new(output, level, strategy)
gz.write(source)
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 74a603c05d..d3f7b46e77 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -40,6 +40,12 @@ module ActiveSupport
# rgb = { black: '#000000', white: '#FFFFFF' }.with_indifferent_access
#
# which may be handy.
+ #
+ # To access this class outside of Rails, require the core extension with:
+ #
+ # require "active_support/core_ext/hash/indifferent_access"
+ #
+ # which will, in turn, require this file.
class HashWithIndifferentAccess < Hash
# Returns +true+ so that <tt>Array#extract_options!</tt> finds members of
# this class.
@@ -78,15 +84,6 @@ module ActiveSupport
end
end
- def self.new_from_hash_copying_default(hash)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- `ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default`
- has been deprecated, and will be removed in Rails 5.1. The behavior of
- this method is now identical to the behavior of `.new`.
- MSG
- new(hash)
- end
-
def self.[](*args)
new.merge!(Hash[*args])
end
@@ -231,7 +228,7 @@ module ActiveSupport
# Same semantics as +reverse_merge+ but modifies the receiver in-place.
def reverse_merge!(other_hash)
- replace(reverse_merge( other_hash ))
+ replace(reverse_merge(other_hash))
end
# Replaces the contents of this hash with other_hash.
@@ -267,6 +264,11 @@ module ActiveSupport
dup.tap { |hash| hash.reject!(*args, &block) }
end
+ def transform_values(*args, &block)
+ return to_enum(:transform_values) unless block_given?
+ dup.tap { |hash| hash.transform_values!(*args, &block) }
+ end
+
# Convert to a regular hash with string keys.
def to_hash
_new_hash = Hash.new
@@ -278,12 +280,12 @@ module ActiveSupport
_new_hash
end
- protected
- def convert_key(key)
+ private
+ def convert_key(key) # :doc:
key.kind_of?(Symbol) ? key.to_s : key
end
- def convert_value(value, options = {})
+ def convert_value(value, options = {}) # :doc:
if value.is_a? Hash
if options[:for] == :to_hash
value.to_hash
@@ -300,7 +302,7 @@ module ActiveSupport
end
end
- def set_defaults(target)
+ def set_defaults(target) # :doc:
if default_proc
target.default_proc = default_proc.dup
else
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index 4749147ef4..b94368df14 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -21,8 +21,6 @@ module I18n
I18n::Railtie.initialize_i18n(app)
end
- protected
-
@i18n_inited = false
# Setup i18n configuration.
@@ -83,14 +81,15 @@ module I18n
def self.init_fallbacks(fallbacks)
include_fallbacks_module
- args = case fallbacks
- when ActiveSupport::OrderedOptions
- [*(fallbacks[:defaults] || []) << fallbacks[:map]].compact
- when Hash, Array
- Array.wrap(fallbacks)
- else # TrueClass
- []
- end
+ args = \
+ case fallbacks
+ when ActiveSupport::OrderedOptions
+ [*(fallbacks[:defaults] || []) << fallbacks[:map]].compact
+ when Hash, Array
+ Array.wrap(fallbacks)
+ else # TrueClass
+ []
+ end
I18n.fallbacks = I18n::Locale::Fallbacks.new(*args)
end
diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb
index 7b1cbba7fa..c47a2e34e1 100644
--- a/activesupport/lib/active_support/inflector/inflections.rb
+++ b/activesupport/lib/active_support/inflector/inflections.rb
@@ -1,5 +1,6 @@
require "concurrent/map"
require "active_support/core_ext/array/prepend_and_append"
+require "active_support/core_ext/regexp"
require "active_support/i18n"
module ActiveSupport
@@ -43,13 +44,14 @@ module ActiveSupport
end
def add(words)
- concat(words.flatten.map(&:downcase))
- @regex_array += map {|word| to_regex(word) }
+ words = words.flatten.map(&:downcase)
+ concat(words)
+ @regex_array += words.map { |word| to_regex(word) }
self
end
def uncountable?(str)
- @regex_array.any? { |regex| regex === str }
+ @regex_array.any? { |regex| regex.match? str }
end
private
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 79cede2a0c..8fea96a82a 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -207,7 +207,7 @@ module ActiveSupport
def demodulize(path)
path = path.to_s
if i = path.rindex("::")
- path[(i+2)..-1]
+ path[(i + 2)..-1]
else
path
end
@@ -274,7 +274,7 @@ module ActiveSupport
# Go down the ancestors to check if it is owned directly. The check
# stops when we reach Object or the end of ancestors tree.
- constant = constant.ancestors.inject do |const, ancestor|
+ constant = constant.ancestors.inject(constant) do |const, ancestor|
break const if ancestor == Object
break ancestor if ancestor.const_defined?(name, false)
const
@@ -356,27 +356,27 @@ module ActiveSupport
private
- # Mounts a regular expression, returned as a string to ease interpolation,
- # that will match part by part the given constant.
- #
- # const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?"
- # const_regexp("::") # => "::"
- def const_regexp(camel_cased_word) #:nodoc:
+ # Mounts a regular expression, returned as a string to ease interpolation,
+ # that will match part by part the given constant.
+ #
+ # const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?"
+ # const_regexp("::") # => "::"
+ def const_regexp(camel_cased_word)
parts = camel_cased_word.split("::".freeze)
return Regexp.escape(camel_cased_word) if parts.blank?
- last = parts.pop
+ last = parts.pop
parts.reverse.inject(last) do |acc, part|
part.empty? ? acc : "#{part}(::#{acc})?"
end
end
- # Applies inflection rules for +singularize+ and +pluralize+.
- #
- # apply_inflections('post', inflections.plurals) # => "posts"
- # apply_inflections('posts', inflections.singulars) # => "post"
+ # Applies inflection rules for +singularize+ and +pluralize+.
+ #
+ # apply_inflections('post', inflections.plurals) # => "posts"
+ # apply_inflections('posts', inflections.singulars) # => "post"
def apply_inflections(word, rules)
result = word.to_s.dup
diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb
index 85fa83c803..3e78986e8e 100644
--- a/activesupport/lib/active_support/inflector/transliterate.rb
+++ b/activesupport/lib/active_support/inflector/transliterate.rb
@@ -78,11 +78,7 @@ module ActiveSupport
# parameterize("Donald E. Knuth", preserve_case: true) # => "Donald-E-Knuth"
# parameterize("^trés|Jolie-- ", preserve_case: true) # => "tres-Jolie"
#
- def parameterize(string, sep = :unused, separator: "-", preserve_case: false)
- unless sep == :unused
- ActiveSupport::Deprecation.warn("Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '#{sep}'` instead.")
- separator = sep
- end
+ def parameterize(string, separator: "-", preserve_case: false)
# Replace accented chars with their ASCII equivalents.
parameterized_string = transliterate(string)
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index cee731417f..defaf3f395 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -68,7 +68,8 @@ module ActiveSupport
:ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, :EscapedString
# Convert an object into a "JSON-ready" representation composed of
- # primitives like Hash, Array, String, Numeric, and true/false/nil.
+ # primitives like Hash, Array, String, Numeric,
+ # and +true+/+false+/+nil+.
# Recursively calls #as_json to the object to recursively build a
# fully JSON-ready object.
#
@@ -84,7 +85,7 @@ module ActiveSupport
when String
EscapedString.new(value)
when Numeric, NilClass, TrueClass, FalseClass
- value
+ value.as_json
when Hash
Hash[value.map { |k, v| [jsonify(k), jsonify(v)] }]
when Array
diff --git a/activesupport/lib/active_support/key_generator.rb b/activesupport/lib/active_support/key_generator.rb
index 156cfc32f8..23ab804eb1 100644
--- a/activesupport/lib/active_support/key_generator.rb
+++ b/activesupport/lib/active_support/key_generator.rb
@@ -17,7 +17,7 @@ module ActiveSupport
# Returns a derived key suitable for use. The default key_size is chosen
# to be compatible with the default settings of ActiveSupport::MessageVerifier.
# i.e. OpenSSL::Digest::SHA1#block_length
- def generate_key(salt, key_size=64)
+ def generate_key(salt, key_size = 64)
OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size)
end
end
@@ -51,8 +51,8 @@ module ActiveSupport
private
- # To prevent users from using something insecure like "Password" we make sure that the
- # secret they've provided is at least 30 characters in length.
+ # To prevent users from using something insecure like "Password" we make sure that the
+ # secret they've provided is at least 30 characters in length.
def ensure_secret_secure(secret)
if secret.blank?
raise ArgumentError, "A secret is required to generate an integrity hash " \
diff --git a/activesupport/lib/active_support/lazy_load_hooks.rb b/activesupport/lib/active_support/lazy_load_hooks.rb
index b84c7253a0..720ed47331 100644
--- a/activesupport/lib/active_support/lazy_load_hooks.rb
+++ b/activesupport/lib/active_support/lazy_load_hooks.rb
@@ -15,16 +15,16 @@ module ActiveSupport
# end
# end
#
- # When the entirety of +activerecord/lib/active_record/base.rb+ has been
+ # When the entirety of +ActiveRecord::Base+ has been
# evaluated then +run_load_hooks+ is invoked. The very last line of
- # +activerecord/lib/active_record/base.rb+ is:
+ # +ActiveRecord::Base+ is:
#
# ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)
module LazyLoadHooks
def self.extended(base) # :nodoc:
base.class_eval do
- @load_hooks = Hash.new { |h,k| h[k] = [] }
- @loaded = Hash.new { |h,k| h[k] = [] }
+ @load_hooks = Hash.new { |h, k| h[k] = [] }
+ @loaded = Hash.new { |h, k| h[k] = [] }
end
end
diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb
index e5812d75d0..e2c4f33565 100644
--- a/activesupport/lib/active_support/log_subscriber.rb
+++ b/activesupport/lib/active_support/log_subscriber.rb
@@ -85,7 +85,7 @@ module ActiveSupport
logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
end
- protected
+ private
%w(info debug warn error fatal unknown).each do |level|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
@@ -99,7 +99,7 @@ module ActiveSupport
# option is set to +true+, it also adds bold to the string. This is based
# on the Highline implementation and will automatically append CLEAR to the
# end of the returned String.
- def color(text, color, bold=false)
+ def color(text, color, bold = false) # :doc:
return text unless colorize_logging
color = self.class.const_get(color.upcase) if color.is_a?(Symbol)
bold = bold ? BOLD : ""
diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb
index a7e30632f6..953ee77c2a 100644
--- a/activesupport/lib/active_support/log_subscriber/test_helper.rb
+++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb
@@ -58,7 +58,7 @@ module ActiveSupport
def initialize(level = DEBUG)
@flush_count = 0
@level = level
- @logged = Hash.new { |h,k| h[k] = [] }
+ @logged = Hash.new { |h, k| h[k] = [] }
end
def method_missing(level, message = nil)
diff --git a/activesupport/lib/active_support/logger.rb b/activesupport/lib/active_support/logger.rb
index 3ba6461b57..ea09d7d2df 100644
--- a/activesupport/lib/active_support/logger.rb
+++ b/activesupport/lib/active_support/logger.rb
@@ -59,14 +59,14 @@ module ActiveSupport
define_method(:silence) do |level = Logger::ERROR, &block|
if logger.respond_to?(:silence)
logger.silence(level) do
- if respond_to?(:silence)
+ if defined?(super)
super(level, &block)
else
block.call(self)
end
end
else
- if respond_to?(:silence)
+ if defined?(super)
super(level, &block)
else
block.call(self)
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index c198e4e6f1..7b33dc3481 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -19,6 +19,8 @@ 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"
+
module NullSerializer #:nodoc:
def self.load(value)
value
@@ -77,6 +79,11 @@ module ActiveSupport
_decrypt(verifier.verify(value))
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)
+ OpenSSL::Cipher.new(cipher).key_len
+ end
+
private
def _encrypt(value)
@@ -98,7 +105,7 @@ module ActiveSupport
def _decrypt(encrypted_message)
cipher = new_cipher
- encrypted_data, iv, auth_tag = encrypted_message.split("--".freeze).map {|v| ::Base64.strict_decode64(v)}
+ encrypted_data, iv, auth_tag = encrypted_message.split("--".freeze).map { |v| ::Base64.strict_decode64(v) }
# Currently the OpenSSL bindings do not raise an error if auth_tag is
# truncated, which would allow an attacker to easily forge it. See
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index c690b1b50b..65d6259a06 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -87,7 +87,7 @@ module ActiveSupport #:nodoc:
end
# Works like <tt>String#slice!</tt>, but returns an instance of
- # Chars, or nil if the string was not modified. The string will not be
+ # Chars, or +nil+ if the string was not modified. The string will not be
# modified if the range given is out of bounds
#
# string = 'Welcome'
@@ -151,7 +151,7 @@ module ActiveSupport #:nodoc:
# "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró"
# "日本語".mb_chars.titleize # => "日本語"
def titleize
- chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1)})
+ chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1) })
end
alias_method :titlecase, :titleize
@@ -210,9 +210,9 @@ module ActiveSupport #:nodoc:
end
end
- protected
+ private
- def translate_offset(byte_offset) #:nodoc:
+ def translate_offset(byte_offset)
return nil if byte_offset.nil?
return 0 if @wrapped_string == ""
@@ -224,7 +224,7 @@ module ActiveSupport #:nodoc:
end
end
- def chars(string) #:nodoc:
+ def chars(string)
self.class.new(string)
end
end
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index c194804c08..05cfb249c3 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -30,36 +30,6 @@ module ActiveSupport
HANGUL_NCOUNT = HANGUL_VCOUNT * HANGUL_TCOUNT
HANGUL_SCOUNT = 11172
HANGUL_SLAST = HANGUL_SBASE + HANGUL_SCOUNT
- HANGUL_JAMO_FIRST = 0x1100
- HANGUL_JAMO_LAST = 0x11FF
-
- # All the unicode whitespace
- WHITESPACE = [
- (0x0009..0x000D).to_a, # White_Space # Cc [5] <control-0009>..<control-000D>
- 0x0020, # White_Space # Zs SPACE
- 0x0085, # White_Space # Cc <control-0085>
- 0x00A0, # White_Space # Zs NO-BREAK SPACE
- 0x1680, # White_Space # Zs OGHAM SPACE MARK
- (0x2000..0x200A).to_a, # White_Space # Zs [11] EN QUAD..HAIR SPACE
- 0x2028, # White_Space # Zl LINE SEPARATOR
- 0x2029, # White_Space # Zp PARAGRAPH SEPARATOR
- 0x202F, # White_Space # Zs NARROW NO-BREAK SPACE
- 0x205F, # White_Space # Zs MEDIUM MATHEMATICAL SPACE
- 0x3000, # White_Space # Zs IDEOGRAPHIC SPACE
- ].flatten.freeze
-
- # BOM (byte order mark) can also be seen as whitespace, it's a
- # non-rendering character used to distinguish between little and big
- # endian. This is not an issue in utf-8, so it must be ignored.
- LEADERS_AND_TRAILERS = WHITESPACE + [65279] # ZERO-WIDTH NO-BREAK SPACE aka BOM
-
- # Returns a regular expression pattern that matches the passed Unicode
- # codepoints.
- def self.codepoints_to_pattern(array_of_codepoints) #:nodoc:
- array_of_codepoints.collect{ |e| [e].pack "U*".freeze }.join("|".freeze)
- end
- TRAILERS_PAT = /(#{codepoints_to_pattern(LEADERS_AND_TRAILERS)})+\Z/u
- LEADERS_PAT = /\A(#{codepoints_to_pattern(LEADERS_AND_TRAILERS)})+/u
# Detect whether the codepoint is in a certain character class. Returns
# +true+ when it's in the specified character class and +false+ otherwise.
@@ -82,32 +52,32 @@ module ActiveSupport
pos = 0
marker = 0
eoc = codepoints.length
- while(pos < eoc)
+ while (pos < eoc)
pos += 1
- previous = codepoints[pos-1]
+ previous = codepoints[pos - 1]
current = codepoints[pos]
should_break =
# GB3. CR X LF
- if previous == database.boundary[:cr] and current == database.boundary[:lf]
+ if previous == database.boundary[:cr] && current == database.boundary[:lf]
false
# GB4. (Control|CR|LF) ÷
- elsif previous and in_char_class?(previous, [:control,:cr,:lf])
+ elsif previous && in_char_class?(previous, [:control, :cr, :lf])
true
# GB5. ÷ (Control|CR|LF)
- elsif in_char_class?(current, [:control,:cr,:lf])
+ elsif in_char_class?(current, [:control, :cr, :lf])
true
# GB6. L X (L|V|LV|LVT)
- elsif database.boundary[:l] === previous and in_char_class?(current, [:l,:v,:lv,:lvt])
+ elsif database.boundary[:l] === previous && in_char_class?(current, [:l, :v, :lv, :lvt])
false
# GB7. (LV|V) X (V|T)
- elsif in_char_class?(previous, [:lv,:v]) and in_char_class?(current, [:v,:t])
+ elsif in_char_class?(previous, [:lv, :v]) && in_char_class?(current, [:v, :t])
false
# GB8. (LVT|T) X (T)
- elsif in_char_class?(previous, [:lvt,:t]) and database.boundary[:t] === current
+ elsif in_char_class?(previous, [:lvt, :t]) && database.boundary[:t] === current
false
# GB8a. Regional_Indicator X Regional_Indicator
- elsif database.boundary[:regional_indicator] === previous and database.boundary[:regional_indicator] === current
+ elsif database.boundary[:regional_indicator] === previous && database.boundary[:regional_indicator] === current
false
# GB9. X Extend
elsif database.boundary[:extend] === current
@@ -124,7 +94,7 @@ module ActiveSupport
end
if should_break
- unpacked << codepoints[marker..pos-1]
+ unpacked << codepoints[marker..pos - 1]
marker = pos
end
end
@@ -140,12 +110,12 @@ module ActiveSupport
# Re-order codepoints so the string becomes canonical.
def reorder_characters(codepoints)
- length = codepoints.length- 1
+ length = codepoints.length - 1
pos = 0
while pos < length do
- cp1, cp2 = database.codepoints[codepoints[pos]], database.codepoints[codepoints[pos+1]]
+ cp1, cp2 = database.codepoints[codepoints[pos]], database.codepoints[codepoints[pos + 1]]
if (cp1.combining_class > cp2.combining_class) && (cp2.combining_class > 0)
- codepoints[pos..pos+1] = cp2.code, cp1.code
+ codepoints[pos..pos + 1] = cp2.code, cp1.code
pos += (pos > 0 ? -1 : 1)
else
pos += 1
@@ -158,7 +128,7 @@ module ActiveSupport
def decompose(type, codepoints)
codepoints.inject([]) do |decomposed, cp|
# if it's a hangul syllable starter character
- if HANGUL_SBASE <= cp and cp < HANGUL_SLAST
+ if HANGUL_SBASE <= cp && cp < HANGUL_SLAST
sindex = cp - HANGUL_SBASE
ncp = [] # new codepoints
ncp << HANGUL_LBASE + sindex / HANGUL_NCOUNT
@@ -167,7 +137,7 @@ module ActiveSupport
ncp << (HANGUL_TBASE + tindex) unless tindex == 0
decomposed.concat ncp
# if the codepoint is decomposable in with the current decomposition type
- elsif (ncp = database.codepoints[cp].decomp_mapping) and (!database.codepoints[cp].decomp_type || type == :compatibility)
+ elsif (ncp = database.codepoints[cp].decomp_mapping) && (!database.codepoints[cp].decomp_type || type == :compatibility)
decomposed.concat decompose(type, ncp.dup)
else
decomposed << cp
@@ -186,11 +156,11 @@ module ActiveSupport
pos += 1
lindex = starter_char - HANGUL_LBASE
# -- Hangul
- if 0 <= lindex and lindex < HANGUL_LCOUNT
- vindex = codepoints[starter_pos+1] - HANGUL_VBASE rescue vindex = -1
- if 0 <= vindex and vindex < HANGUL_VCOUNT
- tindex = codepoints[starter_pos+2] - HANGUL_TBASE rescue tindex = -1
- if 0 <= tindex and tindex < HANGUL_TCOUNT
+ if 0 <= lindex && lindex < HANGUL_LCOUNT
+ vindex = codepoints[starter_pos + 1] - HANGUL_VBASE rescue vindex = -1
+ if 0 <= vindex && vindex < HANGUL_VCOUNT
+ tindex = codepoints[starter_pos + 2] - HANGUL_TBASE rescue tindex = -1
+ if 0 <= tindex && tindex < HANGUL_TCOUNT
j = starter_pos + 2
eoa -= 2
else
@@ -281,7 +251,7 @@ module ActiveSupport
# * <tt>form</tt> - The form you want to normalize in. Should be one of
# the following: <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>.
# Default is ActiveSupport::Multibyte::Unicode.default_normalization_form.
- def normalize(string, form=nil)
+ def normalize(string, form = nil)
form ||= @default_normalization_form
# See http://www.unicode.org/reports/tr15, Table 1
codepoints = string.codepoints.to_a
@@ -388,11 +358,11 @@ module ActiveSupport
private
- def apply_mapping(string, mapping) #:nodoc:
+ def apply_mapping(string, mapping)
database.codepoints
string.each_codepoint.map do |codepoint|
cp = database.codepoints[codepoint]
- if cp and (ncp = cp.send(mapping)) and ncp > 0
+ if cp && (ncp = cp.send(mapping)) && ncp > 0
ncp
else
codepoint
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index bae5f067ae..2df819e554 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -13,7 +13,7 @@ module ActiveSupport
# To instrument an event you just need to do:
#
# ActiveSupport::Notifications.instrument('render', extra: :information) do
- # render text: 'Foo'
+ # render plain: 'Foo'
# end
#
# That first executes the block and then notifies all subscribers once done.
@@ -48,7 +48,7 @@ module ActiveSupport
# The block is saved and will be called whenever someone instruments "render":
#
# ActiveSupport::Notifications.instrument('render', extra: :information) do
- # render text: 'Foo'
+ # render plain: 'Foo'
# end
#
# event = events.first
diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb
index 055ff6e211..9da115f552 100644
--- a/activesupport/lib/active_support/notifications/fanout.rb
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -68,7 +68,7 @@ module ActiveSupport
module Subscribers # :nodoc:
def self.new(pattern, listener)
- if listener.respond_to?(:start) and listener.respond_to?(:finish)
+ if listener.respond_to?(:start) && listener.respond_to?(:finish)
subscriber = Evented.new pattern, listener
else
subscriber = Timed.new pattern, listener
diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb
index 23262d5398..e11e2e0689 100644
--- a/activesupport/lib/active_support/notifications/instrumenter.rb
+++ b/activesupport/lib/active_support/notifications/instrumenter.rb
@@ -14,7 +14,7 @@ module ActiveSupport
# Instrument the given block by measuring the time taken to execute it
# and publish it. Notice that events get sent even if an error occurs
# in the passed-in block.
- def instrument(name, payload={})
+ def instrument(name, payload = {})
# some of the listeners might have state
listeners_state = start name, payload
begin
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index 7a49bbb960..6000ea44be 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -109,7 +109,7 @@ module ActiveSupport
# * <tt>:locale</tt> - Sets the locale to be used for formatting
# (defaults to current locale).
# * <tt>:precision</tt> - Sets the precision of the number
- # (defaults to 3). Keeps the number's precision if nil.
+ # (defaults to 3). Keeps the number's precision if +nil+.
# * <tt>:significant</tt> - If +true+, precision will be the number
# of significant_digits. If +false+, the number of fractional
# digits (defaults to +false+).
@@ -183,7 +183,7 @@ module ActiveSupport
# * <tt>:locale</tt> - Sets the locale to be used for formatting
# (defaults to current locale).
# * <tt>:precision</tt> - Sets the precision of the number
- # (defaults to 3). Keeps the number's precision if nil.
+ # (defaults to 3). Keeps the number's precision if +nil+.
# * <tt>:significant</tt> - If +true+, precision will be the number
# of significant_digits. If +false+, the number of fractional
# digits (defaults to +false+).
diff --git a/activesupport/lib/active_support/number_helper/number_converter.rb b/activesupport/lib/active_support/number_helper/number_converter.rb
index c485a6af63..ce363287cf 100644
--- a/activesupport/lib/active_support/number_helper/number_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_converter.rb
@@ -139,17 +139,17 @@ module ActiveSupport
@options ||= format_options.merge(opts)
end
- def format_options #:nodoc:
+ def format_options
default_format_options.merge!(i18n_format_options)
end
- def default_format_options #:nodoc:
+ def default_format_options
options = DEFAULTS[:format].dup
options.merge!(DEFAULTS[namespace][:format]) if namespace
options
end
- def i18n_format_options #:nodoc:
+ def i18n_format_options
locale = opts[:locale]
options = I18n.translate(:'number.format', locale: locale, default: {}).dup
@@ -160,7 +160,7 @@ module ActiveSupport
options
end
- def translate_number_value_with_default(key, i18n_options = {}) #:nodoc:
+ def translate_number_value_with_default(key, i18n_options = {})
I18n.translate(key, { default: default_value(key), scope: :number }.merge!(i18n_options))
end
@@ -172,7 +172,7 @@ module ActiveSupport
key.split(".").reduce(DEFAULTS) { |defaults, k| defaults[k.to_sym] }
end
- def valid_float? #:nodoc:
+ def valid_float?
Float(number)
rescue ArgumentError, TypeError
false
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 695ee1dad3..56185ddf4b 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
@@ -18,7 +18,7 @@ module ActiveSupport
units = opts[:units]
exponent = calculate_exponent(units)
- @number = number / (10 ** exponent)
+ @number = number / (10**exponent)
until (rounded_number = NumberToRoundedConverter.convert(number, options)) != NumberToRoundedConverter.convert(1000, options)
@number = number / 1000.0
diff --git a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
index 78cb33aa62..f263dbe9f8 100644
--- a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
@@ -7,10 +7,6 @@ module ActiveSupport
self.validate_float = true
def convert
- if opts.key?(:prefix)
- ActiveSupport::Deprecation.warn("The :prefix option of `number_to_human_size` is deprecated and will be removed in Rails 5.1 with no replacement.")
- end
-
@number = Float(number)
# for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
@@ -21,7 +17,7 @@ module ActiveSupport
if smaller_than_base?
number_to_format = number.to_i.to_s
else
- human_size = number / (base ** exponent)
+ human_size = number / (base**exponent)
number_to_format = NumberToRoundedConverter.convert(human_size, options)
end
conversion_format.gsub("%n".freeze, number_to_format).gsub("%u".freeze, unit)
@@ -54,7 +50,7 @@ module ActiveSupport
end
def base
- opts[:prefix] == :si ? 1000 : 1024
+ 1024
end
end
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 c2612f9a9b..1de9f50f34 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])
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 cfcb0045fb..1f013990ea 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
@@ -52,7 +52,7 @@ module ActiveSupport
[1, 0]
else
digits = digit_count(number)
- multiplier = 10 ** (digits - precision)
+ multiplier = 10**(digits - precision)
rounded_number = calculate_rounded_number(multiplier)
digits = digit_count(rounded_number) # After rounding, the number of digits may have changed
[digits, rounded_number]
diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb
index 2d986fe4e0..3aa0a14f04 100644
--- a/activesupport/lib/active_support/ordered_hash.rb
+++ b/activesupport/lib/active_support/ordered_hash.rb
@@ -1,7 +1,7 @@
require "yaml"
YAML.add_builtin_type("omap") do |type, val|
- ActiveSupport::OrderedHash[val.map{ |v| v.to_a.first }]
+ ActiveSupport::OrderedHash[val.map { |v| v.to_a.first }]
end
module ActiveSupport
@@ -25,7 +25,7 @@ module ActiveSupport
end
def encode_with(coder)
- coder.represent_seq "!omap", map { |k,v| { k => v } }
+ coder.represent_seq "!omap", map { |k, v| { k => v } }
end
def select(*args, &block)
diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb
index 14ed9049be..04d6dfaf9c 100644
--- a/activesupport/lib/active_support/ordered_options.rb
+++ b/activesupport/lib/active_support/ordered_options.rb
@@ -68,9 +68,9 @@ module ActiveSupport
def initialize(parent = nil)
if parent.kind_of?(OrderedOptions)
# use the faster _get when dealing with OrderedOptions
- super() { |h,k| parent._get(k) }
+ super() { |h, k| parent._get(k) }
elsif parent
- super() { |h,k| parent[k] }
+ super() { |h, k| parent[k] }
else
super()
end
diff --git a/activesupport/lib/active_support/per_thread_registry.rb b/activesupport/lib/active_support/per_thread_registry.rb
index 9e6d8d4fd8..02431704d3 100644
--- a/activesupport/lib/active_support/per_thread_registry.rb
+++ b/activesupport/lib/active_support/per_thread_registry.rb
@@ -45,8 +45,8 @@ module ActiveSupport
Thread.current[@per_thread_registry_key] ||= new
end
- protected
- def method_missing(name, *args, &block) # :nodoc:
+ private
+ 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.
diff --git a/activesupport/lib/active_support/rails.rb b/activesupport/lib/active_support/rails.rb
index 57380061f7..f6b018f0d3 100644
--- a/activesupport/lib/active_support/rails.rb
+++ b/activesupport/lib/active_support/rails.rb
@@ -25,3 +25,9 @@ require "active_support/core_ext/module/delegation"
# Defines ActiveSupport::Deprecation.
require "active_support/deprecation"
+
+# Defines Regexp#match?.
+#
+# This should be removed when Rails needs Ruby 2.4 or later, and the require
+# added where other Regexp extensions are being used (easy to grep).
+require "active_support/core_ext/regexp"
diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb
index dc3f27a16d..ee6592fb5a 100644
--- a/activesupport/lib/active_support/rescuable.rb
+++ b/activesupport/lib/active_support/rescuable.rb
@@ -36,7 +36,7 @@ module ActiveSupport
# render xml: exception, status: 500
# end
#
- # protected
+ # private
# def deny_access
# ...
# end
@@ -74,7 +74,7 @@ module ActiveSupport
#
# If no handler matches the exception, check for a handler matching the
# (optional) exception.cause. If no handler matches the exception or its
- # cause, this returns nil so you can deal with unhandled exceptions.
+ # cause, this returns +nil+, so you can deal with unhandled exceptions.
# Be sure to re-raise unhandled exceptions if this is what you expect.
#
# begin
@@ -83,11 +83,13 @@ module ActiveSupport
# rescue_with_handler(exception) || raise
# end
#
- # Returns the exception if it was handled and nil if it was not.
+ # Returns the exception if it was handled and +nil+ if it was not.
def rescue_with_handler(exception, object: self)
if handler = handler_for_rescue(exception, object: object)
handler.call exception
exception
+ elsif exception
+ rescue_with_handler(exception.cause, object: object)
end
end
@@ -121,7 +123,7 @@ module ActiveSupport
end
end
- handler || find_rescue_handler(exception.cause)
+ handler
end
end
diff --git a/activesupport/lib/active_support/subscriber.rb b/activesupport/lib/active_support/subscriber.rb
index 0f09f1eb63..2924139755 100644
--- a/activesupport/lib/active_support/subscriber.rb
+++ b/activesupport/lib/active_support/subscriber.rb
@@ -1,4 +1,5 @@
require "active_support/per_thread_registry"
+require "active_support/notifications"
module ActiveSupport
# ActiveSupport::Subscriber is an object set to consume
@@ -24,7 +25,7 @@ module ActiveSupport
class Subscriber
class << self
# Attach the subscriber to a namespace.
- def attach_to(namespace, subscriber=new, notifier=ActiveSupport::Notifications)
+ def attach_to(namespace, subscriber = new, notifier = ActiveSupport::Notifications)
@namespace = namespace
@subscriber = subscriber
@notifier = notifier
@@ -51,11 +52,15 @@ module ActiveSupport
@@subscribers ||= []
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 :subscriber, :notifier, :namespace
- def add_event_subscriber(event)
+ private
+
+ def add_event_subscriber(event) # :doc:
return if %w{ start finish }.include?(event.to_s)
pattern = "#{event}.#{namespace}"
diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb
index 6836378943..ad134c49b6 100644
--- a/activesupport/lib/active_support/tagged_logging.rb
+++ b/activesupport/lib/active_support/tagged_logging.rb
@@ -48,13 +48,12 @@ module ActiveSupport
Thread.current[thread_key] ||= []
end
- private
- def tags_text
- tags = current_tags
- if tags.any?
- tags.collect { |tag| "[#{tag}] " }.join
- end
+ def tags_text
+ tags = current_tags
+ if tags.any?
+ tags.collect { |tag| "[#{tag}] " }.join
end
+ end
end
def self.new(logger)
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index a8e98b704a..3de4ccc1da 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -66,15 +66,6 @@ module ActiveSupport
alias :assert_not_respond_to :refute_respond_to
alias :assert_not_same :refute_same
- # Assertion that the block should not raise an exception.
- #
- # Passes if evaluated code in the yielded block raises no exception.
- #
- # assert_nothing_raised do
- # perform_service(param: 'no_exception')
- # end
- def assert_nothing_raised
- yield
- end
+ ActiveSupport.run_load_hooks(:active_support_test_case, self)
end
end
diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb
index 7770aa8006..28cf2953bf 100644
--- a/activesupport/lib/active_support/testing/assertions.rb
+++ b/activesupport/lib/active_support/testing/assertions.rb
@@ -19,6 +19,17 @@ module ActiveSupport
assert !object, message
end
+ # Assertion that the block should not raise an exception.
+ #
+ # Passes if evaluated code in the yielded block raises no exception.
+ #
+ # assert_nothing_raised do
+ # perform_service(param: 'no_exception')
+ # end
+ def assert_nothing_raised
+ yield
+ end
+
# Test numeric difference between the return value of an expression as a
# result of what is evaluated in the yielded block.
#
@@ -136,7 +147,7 @@ module ActiveSupport
retval = yield
unless from == UNTRACKED
- error = "#{expression.inspect} isn't #{from}"
+ error = "#{expression.inspect} isn't #{from.inspect}"
error = "#{message}.\n#{error}" if message
assert from === before, error
end
@@ -148,7 +159,7 @@ module ActiveSupport
error = "#{message}.\n#{error}" if message
assert_not_equal before, after, error
else
- message = "#{expression.inspect} didn't change to #{to}"
+ error = "#{expression.inspect} didn't change to #{to}"
error = "#{message}.\n#{error}" if message
assert to === after, error
end
diff --git a/activesupport/lib/active_support/testing/autorun.rb b/activesupport/lib/active_support/testing/autorun.rb
index 898ef209da..3108e3e549 100644
--- a/activesupport/lib/active_support/testing/autorun.rb
+++ b/activesupport/lib/active_support/testing/autorun.rb
@@ -2,11 +2,8 @@ gem "minitest"
require "minitest"
-if Minitest.respond_to?(:run_with_rails_extension)
- unless Minitest.run_with_rails_extension
- Minitest.run_with_autorun = true
- Minitest.autorun
- end
-else
- Minitest.autorun
+if Minitest.respond_to?(:run_via) && !Minitest.run_via[:rails]
+ Minitest.run_via[:ruby] = true
end
+
+Minitest.autorun
diff --git a/activesupport/lib/active_support/testing/declarative.rb b/activesupport/lib/active_support/testing/declarative.rb
index 0bf3643a56..53ab3ebf78 100644
--- a/activesupport/lib/active_support/testing/declarative.rb
+++ b/activesupport/lib/active_support/testing/declarative.rb
@@ -9,7 +9,7 @@ module ActiveSupport
# ...
# end
def test(name, &block)
- test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym
+ test_name = "test_#{name.gsub(/\s+/, '_')}".to_sym
defined = method_defined? test_name
raise "#{test_name} is already defined in #{self}" if defined
if block_given?
diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb
index af9ee93600..14edd13a8f 100644
--- a/activesupport/lib/active_support/testing/isolation.rb
+++ b/activesupport/lib/active_support/testing/isolation.rb
@@ -78,13 +78,15 @@ module ActiveSupport
"ISOLATION_OUTPUT" => tmpfile.path
}
- load_paths = $-I.map {|p| "-I\"#{File.expand_path(p)}\"" }.join(" ")
- orig_args = ORIG_ARGV.join(" ")
test_opts = "-n#{self.class.name}##{self.name}"
- command = "#{Gem.ruby} #{load_paths} #{$0} '#{orig_args}' #{test_opts}"
- # IO.popen lets us pass env in a cross-platform way
- child = IO.popen(env, command)
+ load_path_args = []
+ $-I.each do |p|
+ load_path_args << "-I"
+ load_path_args << File.expand_path(p)
+ end
+
+ child = IO.popen([env, Gem.ruby, *load_path_args, $0, *ORIG_ARGV, test_opts])
begin
Process.wait(child.pid)
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 9cc82e9187..889f71c4f3 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -133,7 +133,7 @@ module ActiveSupport
period.zone_identifier.to_s
end
- # Returns a string of the object's date, time, zone and offset from UTC.
+ # Returns a string of the object's date, time, zone, and offset from UTC.
#
# Time.zone.now.inspect # => "Thu, 04 Dec 2014 11:00:25 EST -05:00"
def inspect
@@ -279,6 +279,7 @@ module ActiveSupport
end
end
alias_method :since, :+
+ alias_method :in, :+
# Returns a new TimeWithZone object that represents the difference between
# the current object's time and the +other+ time.
@@ -406,7 +407,7 @@ module ActiveSupport
# Time.zone.now.to_datetime # => Tue, 18 Aug 2015 02:32:20 +0000
# Time.current.in_time_zone('Hawaii').to_datetime # => Mon, 17 Aug 2015 16:32:20 -1000
def to_datetime
- utc.to_datetime.new_offset(Rational(utc_offset, 86_400))
+ @to_datetime ||= utc.to_datetime.new_offset(Rational(utc_offset, 86_400))
end
# So that +self+ <tt>acts_like?(:time)</tt>.
@@ -476,11 +477,13 @@ module ActiveSupport
end
def transfer_time_values_to_utc_constructor(time)
+ # avoid creating another Time object if possible
+ return time if time.instance_of?(::Time) && time.utc?
::Time.utc(time.year, time.month, time.day, time.hour, time.min, time.sec + time.subsec)
end
def duration_of_variable_length?(obj)
- ActiveSupport::Duration === obj && obj.parts.any? {|p| [:years, :months, :weeks, :days].include?(p[0]) }
+ ActiveSupport::Duration === obj && obj.parts.any? { |p| [:years, :months, :weeks, :days].include?(p[0]) }
end
def wrap_with_time_zone(time)
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index b5a74ddc52..09cb9cbbe1 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -260,7 +260,7 @@ module ActiveSupport
private
def zones_map
@zones_map ||= begin
- MAPPING.each_key {|place| self[place]} # load all the zones
+ MAPPING.each_key { |place| self[place] } # load all the zones
@lazy_zones_map
end
end
@@ -295,7 +295,7 @@ module ActiveSupport
# zone = ActiveSupport::TimeZone['Central Time (US & Canada)']
# zone.formatted_offset # => "-06:00"
# zone.formatted_offset(false) # => "-0600"
- def formatted_offset(colon=true, alternate_utc_string = nil)
+ def formatted_offset(colon = true, alternate_utc_string = nil)
utc_offset == 0 && alternate_utc_string || self.class.seconds_to_utc_offset(utc_offset, colon)
end
@@ -355,7 +355,7 @@ module ActiveSupport
# components are supplied, then the day of the month defaults to 1:
#
# Time.zone.parse('Mar 2000') # => Wed, 01 Mar 2000 00:00:00 HST -10:00
- def parse(str, now=now())
+ def parse(str, now = now())
parts_to_time(Date._parse(str, false), now)
end
@@ -379,7 +379,7 @@ module ActiveSupport
# components are supplied, then the day of the month defaults to 1:
#
# Time.zone.strptime('Mar 2000', '%b %Y') # => Wed, 01 Mar 2000 00:00:00 HST -10:00
- def strptime(str, format, now=now())
+ def strptime(str, format, now = now())
parts_to_time(DateTime._strptime(str, format), now)
end
@@ -416,7 +416,7 @@ module ActiveSupport
# Adjust the given time to the simultaneous time in UTC. Returns a
# Time.utc() instance.
- def local_to_utc(time, dst=true)
+ def local_to_utc(time, dst = true)
tzinfo.local_to_utc(time, dst)
end
@@ -428,7 +428,7 @@ module ActiveSupport
# Available so that TimeZone instances respond like TZInfo::Timezone
# instances.
- def period_for_local(time, dst=true)
+ def period_for_local(time, dst = true)
tzinfo.period_for_local(time, dst)
end
@@ -441,7 +441,7 @@ module ActiveSupport
end
def encode_with(coder) #:nodoc:
- coder.tag ="!ruby/object:#{self.class}"
+ coder.tag = "!ruby/object:#{self.class}"
coder.map = { "name" => tzinfo.name }
end
@@ -450,17 +450,21 @@ module ActiveSupport
raise ArgumentError, "invalid date" if parts.nil?
return if parts.empty?
- time = Time.new(
- parts.fetch(:year, now.year),
- parts.fetch(:mon, now.month),
- parts.fetch(:mday, parts[:year] || parts[:mon] ? 1 : now.day),
- parts.fetch(:hour, 0),
- parts.fetch(:min, 0),
- parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0),
- parts.fetch(:offset, 0)
- )
-
- if parts[:offset]
+ if parts[:seconds]
+ time = Time.at(parts[:seconds])
+ else
+ time = Time.new(
+ parts.fetch(:year, now.year),
+ parts.fetch(:mon, now.month),
+ parts.fetch(:mday, parts[:year] || parts[:mon] ? 1 : now.day),
+ parts.fetch(:hour, 0),
+ parts.fetch(:min, 0),
+ parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0),
+ parts.fetch(:offset, 0)
+ )
+ end
+
+ if parts[:offset] || parts[:seconds]
TimeWithZone.new(time.utc, self)
else
TimeWithZone.new(nil, self, time)
diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb
index 12a7c61240..782fb41288 100644
--- a/activesupport/lib/active_support/xml_mini.rb
+++ b/activesupport/lib/active_support/xml_mini.rb
@@ -48,8 +48,8 @@ module ActiveSupport
}
# No need to map these on Ruby 2.4+
- TYPE_NAMES["Fixnum"] = "integer" unless Fixnum == Integer
- TYPE_NAMES["Bignum"] = "integer" unless Bignum == Integer
+ TYPE_NAMES["Fixnum"] = "integer" unless 0.class == Integer
+ TYPE_NAMES["Bignum"] = "integer" unless 0.class == Integer
end
FORMATTING = {
@@ -68,7 +68,17 @@ module ActiveSupport
"datetime" => Proc.new { |time| Time.xmlschema(time).utc rescue ::DateTime.parse(time).utc },
"integer" => Proc.new { |integer| integer.to_i },
"float" => Proc.new { |float| float.to_f },
- "decimal" => Proc.new { |number| BigDecimal(number) },
+ "decimal" => Proc.new do |number|
+ if String === number
+ begin
+ BigDecimal(number)
+ rescue ArgumentError
+ BigDecimal("0")
+ end
+ else
+ BigDecimal(number)
+ end
+ end,
"boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.to_s.strip) },
"string" => Proc.new { |string| string.to_s },
"yaml" => Proc.new { |yaml| YAML::load(yaml) rescue yaml },
@@ -126,7 +136,7 @@ module ActiveSupport
key = rename_key(key.to_s, options)
- attributes = options[:skip_types] || type_name.nil? ? { } : { type: type_name }
+ attributes = options[:skip_types] || type_name.nil? ? {} : { type: type_name }
attributes[:nil] = true if value.nil?
encoding = options[:encoding] || DEFAULT_ENCODINGS[type_name]
@@ -149,16 +159,16 @@ module ActiveSupport
key
end
- protected
+ private
def _dasherize(key)
# $2 must be a non-greedy regex for this to work
- left, middle, right = /\A(_*)(.*?)(_*)\Z/.match(key.strip)[1,3]
+ left, middle, right = /\A(_*)(.*?)(_*)\Z/.match(key.strip)[1, 3]
"#{left}#{middle.tr('_ ', '--')}#{right}"
end
- # TODO: Add support for other encodings
- def _parse_binary(bin, entity) #:nodoc:
+ # TODO: Add support for other encodings
+ def _parse_binary(bin, entity)
case entity["encoding"]
when "base64"
::Base64.decode64(bin)
@@ -175,8 +185,6 @@ module ActiveSupport
f
end
- private
-
def current_thread_backend
Thread.current[:xml_mini_backend]
end
diff --git a/activesupport/lib/active_support/xml_mini/jdom.rb b/activesupport/lib/active_support/xml_mini/jdom.rb
index 90f6cc464f..a7939b3185 100644
--- a/activesupport/lib/active_support/xml_mini/jdom.rb
+++ b/activesupport/lib/active_support/xml_mini/jdom.rb
@@ -46,18 +46,18 @@ module ActiveSupport
xml_string_reader = StringReader.new(data)
xml_input_source = InputSource.new(xml_string_reader)
doc = @dbf.new_document_builder.parse(xml_input_source)
- merge_element!({CONTENT_KEY => ""}, doc.document_element, XmlMini.depth)
+ merge_element!({ CONTENT_KEY => "" }, doc.document_element, XmlMini.depth)
end
end
private
- # Convert an XML element and merge into the hash
- #
- # hash::
- # Hash to merge the converted element into.
- # element::
- # XML element to merge into hash
+ # Convert an XML element and merge into the hash
+ #
+ # hash::
+ # Hash to merge the converted element into.
+ # element::
+ # XML element to merge into hash
def merge_element!(hash, element, depth)
raise "Document too deep!" if depth == 0
delete_empty(hash)
@@ -68,10 +68,10 @@ module ActiveSupport
hash.delete(CONTENT_KEY) if hash[CONTENT_KEY] == ""
end
- # Actually converts an XML document element into a data structure.
- #
- # element::
- # The document element to be collapsed.
+ # Actually converts an XML document element into a data structure.
+ #
+ # element::
+ # The document element to be collapsed.
def collapse(element, depth)
hash = get_attributes(element)
@@ -88,12 +88,12 @@ module ActiveSupport
end
end
- # Merge all the texts of an element into the hash
- #
- # hash::
- # Hash to add the converted element to.
- # element::
- # XML element whose texts are to me merged into the hash
+ # Merge all the texts of an element into the hash
+ #
+ # hash::
+ # Hash to add the converted element to.
+ # element::
+ # XML element whose texts are to me merged into the hash
def merge_texts!(hash, element)
delete_empty(hash)
text_children = texts(element)
@@ -105,17 +105,17 @@ module ActiveSupport
end
end
- # Adds a new key/value pair to an existing Hash. If the key to be added
- # already exists and the existing value associated with key is not
- # an Array, it will be wrapped in an Array. Then the new value is
- # appended to that Array.
- #
- # hash::
- # Hash to add key/value pair to.
- # key::
- # Key to be added.
- # value::
- # Value to be associated with key.
+ # Adds a new key/value pair to an existing Hash. If the key to be added
+ # already exists and the existing value associated with key is not
+ # an Array, it will be wrapped in an Array. Then the new value is
+ # appended to that Array.
+ #
+ # hash::
+ # Hash to add key/value pair to.
+ # key::
+ # Key to be added.
+ # value::
+ # Value to be associated with key.
def merge!(hash, key, value)
if hash.has_key?(key)
if hash[key].instance_of?(Array)
@@ -131,25 +131,25 @@ module ActiveSupport
hash
end
- # Converts the attributes array of an XML element into a hash.
- # Returns an empty Hash if node has no attributes.
- #
- # element::
- # XML element to extract attributes from.
+ # Converts the attributes array of an XML element into a hash.
+ # Returns an empty Hash if node has no attributes.
+ #
+ # element::
+ # XML element to extract attributes from.
def get_attributes(element)
attribute_hash = {}
attributes = element.attributes
(0...attributes.length).each do |i|
attribute_hash[CONTENT_KEY] ||= ""
- attribute_hash[attributes.item(i).name] = attributes.item(i).value
+ attribute_hash[attributes.item(i).name] = attributes.item(i).value
end
attribute_hash
end
- # Determines if a document element has text content
- #
- # element::
- # XML element to be checked.
+ # Determines if a document element has text content
+ #
+ # element::
+ # XML element to be checked.
def texts(element)
texts = []
child_nodes = element.child_nodes
@@ -162,10 +162,10 @@ module ActiveSupport
texts
end
- # Determines if a document element has text content
- #
- # element::
- # XML element to be checked.
+ # Determines if a document element has text content
+ #
+ # element::
+ # XML element to be checked.
def empty_content?(element)
text = ""
child_nodes = element.child_nodes
diff --git a/activesupport/lib/active_support/xml_mini/libxml.rb b/activesupport/lib/active_support/xml_mini/libxml.rb
index 8a4aa03963..44b0bdb7dc 100644
--- a/activesupport/lib/active_support/xml_mini/libxml.rb
+++ b/activesupport/lib/active_support/xml_mini/libxml.rb
@@ -40,7 +40,7 @@ module LibXML #:nodoc:
#
# hash::
# Hash to merge the converted element into.
- def to_hash(hash={})
+ def to_hash(hash = {})
node_hash = {}
# Insert node hash into parent hash correctly.
diff --git a/activesupport/lib/active_support/xml_mini/nokogiri.rb b/activesupport/lib/active_support/xml_mini/nokogiri.rb
index b92fa7ea7c..4c2be3f3ec 100644
--- a/activesupport/lib/active_support/xml_mini/nokogiri.rb
+++ b/activesupport/lib/active_support/xml_mini/nokogiri.rb
@@ -44,7 +44,7 @@ module ActiveSupport
#
# hash::
# Hash to merge the converted element into.
- def to_hash(hash={})
+ def to_hash(hash = {})
node_hash = {}
# Insert node hash into parent hash correctly.
diff --git a/activesupport/lib/active_support/xml_mini/rexml.rb b/activesupport/lib/active_support/xml_mini/rexml.rb
index 28da82aeb1..03fa910fa5 100644
--- a/activesupport/lib/active_support/xml_mini/rexml.rb
+++ b/activesupport/lib/active_support/xml_mini/rexml.rb
@@ -55,7 +55,7 @@ module ActiveSupport
hash = get_attributes(element)
if element.has_elements?
- element.each_element {|child| merge_element!(hash, child, depth - 1) }
+ element.each_element { |child| merge_element!(hash, child, depth - 1) }
merge_texts!(hash, element) unless empty_content?(element)
hash
else
@@ -113,7 +113,7 @@ module ActiveSupport
# XML element to extract attributes from.
def get_attributes(element)
attributes = {}
- element.attributes.each { |n,v| attributes[n] = v }
+ element.attributes.each { |n, v| attributes[n] = v }
attributes
end
diff --git a/activesupport/test/autoload_test.rb b/activesupport/test/autoload_test.rb
index c4d6a69212..6c8aa3e055 100644
--- a/activesupport/test/autoload_test.rb
+++ b/activesupport/test/autoload_test.rb
@@ -31,7 +31,7 @@ class TestAutoloadModule < ActiveSupport::TestCase
end
end
- assert !$LOADED_FEATURES.include?(@some_class_path)
+ assert_not_includes $LOADED_FEATURES, @some_class_path
assert_nothing_raised { ::Fixtures::Autoload::SomeClass }
end
@@ -40,7 +40,7 @@ class TestAutoloadModule < ActiveSupport::TestCase
autoload :SomeClass
end
- assert !$LOADED_FEATURES.include?(@some_class_path)
+ assert_not_includes $LOADED_FEATURES, @some_class_path
assert_nothing_raised { ::Fixtures::Autoload::SomeClass }
end
@@ -51,9 +51,9 @@ class TestAutoloadModule < ActiveSupport::TestCase
end
end
- assert !$LOADED_FEATURES.include?(@some_class_path)
+ assert_not_includes $LOADED_FEATURES, @some_class_path
::Fixtures::Autoload.eager_load!
- assert $LOADED_FEATURES.include?(@some_class_path)
+ assert_includes $LOADED_FEATURES, @some_class_path
assert_nothing_raised { ::Fixtures::Autoload::SomeClass }
end
@@ -64,7 +64,7 @@ class TestAutoloadModule < ActiveSupport::TestCase
end
end
- assert !$LOADED_FEATURES.include?(@another_class_path)
+ assert_not_includes $LOADED_FEATURES, @another_class_path
assert_nothing_raised { ::Fixtures::AnotherClass }
end
@@ -75,7 +75,7 @@ class TestAutoloadModule < ActiveSupport::TestCase
end
end
- assert !$LOADED_FEATURES.include?(@another_class_path)
+ assert_not_includes $LOADED_FEATURES, @another_class_path
assert_nothing_raised { ::Fixtures::AnotherClass }
end
end
diff --git a/activesupport/test/autoloading_fixtures/prepend.rb b/activesupport/test/autoloading_fixtures/prepend.rb
new file mode 100644
index 0000000000..3134d1df2b
--- /dev/null
+++ b/activesupport/test/autoloading_fixtures/prepend.rb
@@ -0,0 +1,8 @@
+class SubClassConflict
+end
+
+class Prepend
+ module PrependedModule
+ end
+ prepend PrependedModule
+end
diff --git a/activesupport/test/autoloading_fixtures/prepend/sub_class_conflict.rb b/activesupport/test/autoloading_fixtures/prepend/sub_class_conflict.rb
new file mode 100644
index 0000000000..090dda3043
--- /dev/null
+++ b/activesupport/test/autoloading_fixtures/prepend/sub_class_conflict.rb
@@ -0,0 +1,2 @@
+class Prepend::SubClassConflict
+end
diff --git a/activesupport/test/benchmarkable_test.rb b/activesupport/test/benchmarkable_test.rb
index 6b9eb5aa66..210b9cb9fd 100644
--- a/activesupport/test/benchmarkable_test.rb
+++ b/activesupport/test/benchmarkable_test.rb
@@ -57,13 +57,13 @@ class BenchmarkableTest < ActiveSupport::TestCase
def test_within_level
logger.level = ActiveSupport::Logger::DEBUG
- benchmark("included_debug_run", level: :debug) { }
+ benchmark("included_debug_run", level: :debug) {}
assert_last_logged "included_debug_run"
end
def test_outside_level
logger.level = ActiveSupport::Logger::ERROR
- benchmark("skipped_debug_run", level: :debug) { }
+ benchmark("skipped_debug_run", level: :debug) {}
assert_no_match(/skipped_debug_run/, buffer.last)
ensure
logger.level = ActiveSupport::Logger::DEBUG
diff --git a/activesupport/test/broadcast_logger_test.rb b/activesupport/test/broadcast_logger_test.rb
index 4b74f1313e..184d0ebddd 100644
--- a/activesupport/test/broadcast_logger_test.rb
+++ b/activesupport/test/broadcast_logger_test.rb
@@ -69,6 +69,20 @@ module ActiveSupport
assert_equal ::Logger::FATAL, log2.local_level
end
+ test "#silence does not break custom loggers" do
+ new_logger = FakeLogger.new
+ custom_logger = CustomLogger.new
+ custom_logger.extend(Logger.broadcast(new_logger))
+
+ custom_logger.silence do
+ custom_logger.error "from error"
+ custom_logger.unknown "from unknown"
+ end
+
+ assert_equal [[::Logger::ERROR, "from error", nil], [::Logger::UNKNOWN, "from unknown", nil]], custom_logger.adds
+ assert_equal [[::Logger::ERROR, "from error", nil], [::Logger::UNKNOWN, "from unknown", nil]], new_logger.adds
+ end
+
test "#silence silences all loggers below the default level of ERROR" do
logger.silence do
logger.debug "test"
@@ -98,9 +112,7 @@ module ActiveSupport
assert_equal [[::Logger::FATAL, "seen", nil]], log2.adds
end
- class FakeLogger
- include LoggerSilence
-
+ class CustomLogger
attr_reader :adds, :closed, :chevrons
attr_accessor :level, :progname, :formatter, :local_level
@@ -142,7 +154,7 @@ module ActiveSupport
@chevrons << x
end
- def add(message_level, message=nil, progname=nil, &block)
+ def add(message_level, message = nil, progname = nil, &block)
@adds << [message_level, message, progname] if message_level >= local_level
end
@@ -150,5 +162,9 @@ module ActiveSupport
@closed = true
end
end
+
+ class FakeLogger < CustomLogger
+ include LoggerSilence
+ end
end
end
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index 072a2530ba..c543122d91 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -19,7 +19,7 @@ module ActiveSupport
})
_, _, body = middleware.call({})
assert LocalCacheRegistry.cache_for(key), "should still have a cache"
- body.each { }
+ body.each {}
assert LocalCacheRegistry.cache_for(key), "should still have a cache"
body.close
assert_nil LocalCacheRegistry.cache_for(key)
@@ -135,7 +135,7 @@ class CacheKeyTest < ActiveSupport::TestCase
kv.each { |key, value| old_values[key], ENV[key] = ENV[key], value }
yield
ensure
- old_values.each { |key, value| ENV[key] = value}
+ old_values.each { |key, value| ENV[key] = value }
end
end
@@ -212,7 +212,7 @@ class CacheStoreNamespaceTest < ActiveSupport::TestCase
def test_proc_namespace
test_val = "tester"
- proc = lambda{test_val}
+ proc = lambda { test_val }
cache = ActiveSupport::Cache.lookup_store(:memory_store, namespace: proc)
cache.write("foo", "bar")
assert_equal "bar", cache.read("foo")
@@ -306,7 +306,7 @@ module CacheStoreBehavior
def test_should_read_and_write_hash
assert @cache.write("foo", a: "b")
- assert_equal({a: "b"}, @cache.read("foo"))
+ assert_equal({ a: "b" }, @cache.read("foo"))
end
def test_should_read_and_write_integer
@@ -316,7 +316,7 @@ module CacheStoreBehavior
def test_should_read_and_write_nil
assert @cache.write("foo", nil)
- assert_equal nil, @cache.read("foo")
+ assert_nil @cache.read("foo")
end
def test_should_read_and_write_false
@@ -328,7 +328,7 @@ module CacheStoreBehavior
@cache.write("foo", "bar")
@cache.write("fu", "baz")
@cache.write("fud", "biz")
- assert_equal({"foo" => "bar", "fu" => "baz"}, @cache.read_multi("foo", "fu"))
+ assert_equal({ "foo" => "bar", "fu" => "baz" }, @cache.read_multi("foo", "fu"))
end
def test_read_multi_with_expires
@@ -337,7 +337,7 @@ module CacheStoreBehavior
@cache.write("fu", "baz")
@cache.write("fud", "biz")
Time.stub(:now, time + 11) do
- assert_equal({"fu" => "baz"}, @cache.read_multi("foo", "fu"))
+ assert_equal({ "fu" => "baz" }, @cache.read_multi("foo", "fu"))
end
end
@@ -363,6 +363,12 @@ module CacheStoreBehavior
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")
@@ -402,7 +408,7 @@ module CacheStoreBehavior
end
def test_hash_as_cache_key
- @cache.write({foo: 1, fu: 2}, "bar")
+ @cache.write({ foo: 1, fu: 2 }, "bar")
assert_equal "bar", @cache.read("foo=1/fu=2")
end
@@ -458,7 +464,7 @@ module CacheStoreBehavior
Time.stub(:now, Time.at(time)) do
result = @cache.fetch("foo") do
- assert_equal nil, @cache.read("foo")
+ assert_nil @cache.read("foo")
"baz"
end
assert_equal "baz", result
@@ -470,7 +476,7 @@ module CacheStoreBehavior
@cache.write("foo", "bar", expires_in: 60)
Time.stub(:now, time + 71) do
result = @cache.fetch("foo", race_condition_ttl: 10) do
- assert_equal nil, @cache.read("foo")
+ assert_nil @cache.read("foo")
"baz"
end
assert_equal "baz", result
@@ -520,12 +526,12 @@ module CacheStoreBehavior
def test_really_long_keys
key = ""
- 900.times{key << "x"}
+ 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_equal({ key => "bar" }, @cache.read_multi(key))
assert @cache.delete(key)
end
@@ -560,12 +566,6 @@ module CacheStoreBehavior
ensure
ActiveSupport::Notifications.unsubscribe "cache_read.active_support"
end
-
- def test_can_call_deprecated_namesaced_key
- assert_deprecated "`namespaced_key` is deprecated" do
- @cache.send(:namespaced_key, 111, {})
- end
- end
end
# https://rails.lighthouseapp.com/projects/8994/tickets/6225-memcachestore-cant-deal-with-umlauts-and-special-characters
@@ -675,9 +675,9 @@ module LocalCacheBehavior
def test_local_cache_of_read_nil
@cache.with_local_cache do
- assert_equal nil, @cache.read("foo")
+ assert_nil @cache.read("foo")
@cache.send(:bypass_local_cache) { @cache.write "foo", "bar" }
- assert_equal nil, @cache.read("foo")
+ assert_nil @cache.read("foo")
end
end
@@ -741,15 +741,6 @@ module LocalCacheBehavior
app = @cache.middleware.new(app)
app.call({})
end
-
- def test_can_call_deprecated_set_cache_value
- @cache.with_local_cache do
- assert_deprecated "`set_cache_value` is deprecated" do
- @cache.send(:set_cache_value, 1, "foo", :ignored, {})
- end
- assert_equal 1, @cache.read("foo")
- end
- end
end
module AutoloadingCacheBehavior
@@ -831,14 +822,9 @@ class FileStoreTest < ActiveSupport::TestCase
@cache.clear
end
- def test_long_keys
- @cache.write("a"*10000, 1)
- assert_equal 1, @cache.read("a"*10000)
- end
-
def test_long_uri_encoded_keys
- @cache.write("%"*870, 1)
- assert_equal 1, @cache.read("%"*870)
+ @cache.write("%" * 870, 1)
+ assert_equal 1, @cache.read("%" * 870)
end
def test_key_transformation
@@ -858,7 +844,7 @@ class FileStoreTest < ActiveSupport::TestCase
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}"
+ assert File.basename(tmpname + ".lock").length <= 255, "Temp filename too long: #{File.basename(tmpname + '.lock').length}"
end
end
@@ -867,7 +853,7 @@ class FileStoreTest < ActiveSupport::TestCase
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 path.split("/").all? { |dir_name| dir_name.size <= ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE }
assert_equal "B", File.basename(path)
end
@@ -888,7 +874,7 @@ class FileStoreTest < ActiveSupport::TestCase
end
assert File.exist?(cache_dir), "Parent of top level cache dir was deleted!"
assert File.exist?(sub_cache_dir), "Top level cache dir was deleted!"
- assert Dir.entries(sub_cache_dir).reject {|f| ActiveSupport::Cache::FileStore::EXCLUDED_DIRS.include?(f)}.empty?
+ assert Dir.entries(sub_cache_dir).reject { |f| ActiveSupport::Cache::FileStore::EXCLUDED_DIRS.include?(f) }.empty?
end
def test_log_exception_when_cache_read_fails
@@ -917,12 +903,6 @@ class FileStoreTest < ActiveSupport::TestCase
@cache.write(1, nil)
assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true)
end
-
- def test_can_call_deprecated_key_file_path
- assert_deprecated "`key_file_path` is deprecated" do
- assert_equal 111, @cache.send(:key_file_path, 111)
- end
- end
end
class MemoryStoreTest < ActiveSupport::TestCase
@@ -1003,7 +983,7 @@ class MemoryStoreTest < ActiveSupport::TestCase
end
def test_pruning_is_capped_at_a_max_time
- def @cache.delete_entry (*args)
+ def @cache.delete_entry(*args)
sleep(0.01)
super
end
@@ -1097,12 +1077,6 @@ class MemCacheStoreTest < ActiveSupport::TestCase
value << "bingo"
assert_not_equal value, @cache.read("foo")
end
-
- def test_can_call_deprecated_escape_key
- assert_deprecated "`escape_key` is deprecated" do
- assert_equal 111, @cache.send(:escape_key, 111)
- end
- end
end
class NullStoreTest < ActiveSupport::TestCase
diff --git a/activesupport/test/callback_inheritance_test.rb b/activesupport/test/callback_inheritance_test.rb
index d1c217177d..9e2f7527e0 100644
--- a/activesupport/test/callback_inheritance_test.rb
+++ b/activesupport/test/callback_inheritance_test.rb
@@ -9,8 +9,8 @@ class GrandParent
end
define_callbacks :dispatch
- set_callback :dispatch, :before, :before1, :before2, if: proc {|c| c.action_name == "index" || c.action_name == "update" }
- set_callback :dispatch, :after, :after1, :after2, if: proc {|c| c.action_name == "update" || c.action_name == "delete" }
+ set_callback :dispatch, :before, :before1, :before2, if: proc { |c| c.action_name == "index" || c.action_name == "update" }
+ set_callback :dispatch, :after, :after1, :after2, if: proc { |c| c.action_name == "update" || c.action_name == "delete" }
def before1
@log << "before1"
@@ -37,12 +37,12 @@ class GrandParent
end
class Parent < GrandParent
- skip_callback :dispatch, :before, :before2, unless: proc {|c| c.action_name == "update" }
- skip_callback :dispatch, :after, :after2, unless: proc {|c| c.action_name == "delete" }
+ skip_callback :dispatch, :before, :before2, unless: proc { |c| c.action_name == "update" }
+ skip_callback :dispatch, :after, :after2, unless: proc { |c| c.action_name == "delete" }
end
class Child < GrandParent
- skip_callback :dispatch, :before, :before2, unless: proc {|c| c.action_name == "update" }, if: :state_open?
+ skip_callback :dispatch, :before, :before2, unless: proc { |c| c.action_name == "update" }, if: :state_open?
def state_open?
@state == :open
diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb
index 913b7c8cfc..28caa30bf1 100644
--- a/activesupport/test/callbacks_test.rb
+++ b/activesupport/test/callbacks_test.rb
@@ -56,6 +56,8 @@ module CallbacksTest
end
class Person < Record
+ attr_accessor :save_fails
+
[:before_save, :after_save].each do |callback_method|
callback_method_sym = callback_method.to_sym
send(callback_method, callback_symbol(callback_method_sym))
@@ -67,7 +69,9 @@ module CallbacksTest
end
def save
- run_callbacks :save
+ run_callbacks :save do
+ raise "inside save" if save_fails
+ end
end
end
@@ -89,7 +93,7 @@ module CallbacksTest
define_callbacks :dispatch
- set_callback :dispatch, :before, :log, unless: proc {|c| c.action_name == :index || c.action_name == :show }
+ set_callback :dispatch, :before, :log, unless: proc { |c| c.action_name == :index || c.action_name == :show }
set_callback :dispatch, :after, :log2
attr_reader :action_name, :logger
@@ -114,7 +118,7 @@ module CallbacksTest
end
class Child < ParentController
- skip_callback :dispatch, :before, :log, if: proc {|c| c.action_name == :update}
+ skip_callback :dispatch, :before, :log, if: proc { |c| c.action_name == :update }
skip_callback :dispatch, :after, :log2
end
@@ -125,10 +129,10 @@ module CallbacksTest
super
end
- before_save Proc.new {|r| r.history << [:before_save, :starts_true, :if] }, if: :starts_true
- before_save Proc.new {|r| r.history << [:before_save, :starts_false, :if] }, if: :starts_false
- before_save Proc.new {|r| r.history << [:before_save, :starts_true, :unless] }, unless: :starts_true
- before_save Proc.new {|r| r.history << [:before_save, :starts_false, :unless] }, unless: :starts_false
+ before_save Proc.new { |r| r.history << [:before_save, :starts_true, :if] }, if: :starts_true
+ before_save Proc.new { |r| r.history << [:before_save, :starts_false, :if] }, if: :starts_false
+ before_save Proc.new { |r| r.history << [:before_save, :starts_true, :unless] }, unless: :starts_true
+ before_save Proc.new { |r| r.history << [:before_save, :starts_false, :unless] }, unless: :starts_false
def starts_true
if @@starts_true
@@ -220,14 +224,56 @@ module CallbacksTest
define_callbacks :save
end
- class AroundPerson < MySuper
+ class MySlate < MySuper
attr_reader :history
+ attr_accessor :save_fails
+
+ def initialize
+ @history = []
+ end
+
+ def save
+ run_callbacks :save do
+ raise "inside save" if save_fails
+ @history << "running"
+ end
+ end
+
+ def no; false; end
+ def yes; true; end
+
+ def method_missing(sym, *)
+ case sym
+ when /^log_(.*)/
+ @history << $1
+ nil
+ when /^wrap_(.*)/
+ @history << "wrap_#$1"
+ yield
+ @history << "unwrap_#$1"
+ nil
+ when /^double_(.*)/
+ @history << "first_#$1"
+ yield
+ @history << "second_#$1"
+ yield
+ @history << "third_#$1"
+ else
+ super
+ end
+ end
+ def respond_to_missing?(sym)
+ sym =~ /^(log|wrap)_/ || super
+ end
+ end
+
+ class AroundPerson < MySlate
set_callback :save, :before, :nope, if: :no
set_callback :save, :before, :nope, unless: :yes
set_callback :save, :after, :tweedle
ActiveSupport::Deprecation.silence { set_callback :save, :before, "tweedle_dee" }
- set_callback :save, :before, proc {|m| m.history << "yup" }
+ set_callback :save, :before, proc { |m| m.history << "yup" }
set_callback :save, :before, :nope, if: proc { false }
set_callback :save, :before, :nope, unless: proc { true }
set_callback :save, :before, :yup, if: proc { true }
@@ -237,9 +283,6 @@ module CallbacksTest
set_callback :save, :around, :w0tno, if: :no
set_callback :save, :around, :tweedle_deedle
- def no; false; end
- def yes; true; end
-
def nope
@history << "boom"
end
@@ -278,16 +321,6 @@ module CallbacksTest
yield
@history << "tweedle deedle post"
end
-
- def initialize
- @history = []
- end
-
- def save
- run_callbacks :save do
- @history << "running"
- end
- end
end
class AroundPersonResult < MySuper
@@ -402,6 +435,97 @@ module CallbacksTest
end
end
+ class DoubleYieldTest < ActiveSupport::TestCase
+ class DoubleYieldModel < MySlate
+ set_callback :save, :around, :wrap_outer
+ set_callback :save, :around, :double_trouble
+ set_callback :save, :around, :wrap_inner
+ end
+
+ def test_double_save
+ double = DoubleYieldModel.new
+ double.save
+ assert_equal [
+ "wrap_outer",
+ "first_trouble",
+ "wrap_inner",
+ "running",
+ "unwrap_inner",
+ "second_trouble",
+ "wrap_inner",
+ "running",
+ "unwrap_inner",
+ "third_trouble",
+ "unwrap_outer",
+ ], double.history
+ end
+ end
+
+ class CallStackTest < ActiveSupport::TestCase
+ def test_tidy_call_stack
+ around = AroundPerson.new
+ around.save_fails = true
+
+ exception = (around.save rescue $!)
+
+ # Make sure we have the exception we're expecting
+ assert_equal "inside save", exception.message
+
+ call_stack = exception.backtrace_locations
+ call_stack.pop caller_locations(0).size
+
+ # Yes, this looks like an implementation test, but it's the least
+ # obtuse way of asserting that there aren't a load of entries in
+ # the call stack for each callback.
+ #
+ # If you've renamed a method, or squeezed more lines out, go ahead
+ # and update this assertion. But if you're here because a
+ # refactoring added new lines, please reconsider.
+
+ # As shown here, our current budget is one line for run_callbacks
+ # itself, plus N+1 lines where N is the number of :around
+ # callbacks that have been invoked, if there are any (plus
+ # whatever the callbacks do themselves, of course).
+
+ assert_equal [
+ "block in save",
+ "block in run_callbacks",
+ "tweedle_deedle",
+ "block in run_callbacks",
+ "w0tyes",
+ "block in run_callbacks",
+ "tweedle_dum",
+ "block in run_callbacks",
+ ("call" if RUBY_VERSION < "2.3"),
+ "run_callbacks",
+ "save"
+ ].compact, call_stack.map(&:label)
+ end
+
+ def test_short_call_stack
+ person = Person.new
+ person.save_fails = true
+
+ exception = (person.save rescue $!)
+
+ # Make sure we have the exception we're expecting
+ assert_equal "inside save", exception.message
+
+ call_stack = exception.backtrace_locations
+ call_stack.pop caller_locations(0).size
+
+ # This budget much simpler: with no :around callbacks invoked,
+ # there should be just one line. run_callbacks yields directly
+ # back to its caller.
+
+ assert_equal [
+ "block in save",
+ "run_callbacks",
+ "save"
+ ], call_stack.map(&:label)
+ end
+ end
+
class AroundCallbackResultTest < ActiveSupport::TestCase
def test_save_around
around = AroundPersonResult.new
@@ -763,7 +887,7 @@ module CallbacksTest
def test_returning_false_does_not_halt_callback_if_config_variable_is_not_set
obj = CallbackFalseTerminator.new
obj.save
- assert_equal nil, obj.halted
+ assert_nil obj.halted
assert obj.saved
end
end
@@ -776,7 +900,7 @@ module CallbacksTest
def test_returning_false_does_not_halt_callback_if_config_variable_is_true
obj = CallbackFalseTerminator.new
obj.save
- assert_equal nil, obj.halted
+ assert_nil obj.halted
assert obj.saved
end
end
@@ -789,7 +913,7 @@ module CallbacksTest
def test_returning_false_does_not_halt_callback_if_config_variable_is_false
obj = CallbackFalseTerminator.new
obj.save
- assert_equal nil, obj.halted
+ assert_nil obj.halted
assert obj.saved
end
end
@@ -879,7 +1003,7 @@ module CallbacksTest
def test_proc_arity_2
assert_raises(ArgumentError) do
- klass = build_class(->(x,y) { })
+ klass = build_class(->(x, y) {})
klass.new.run
end
end
@@ -916,7 +1040,7 @@ module CallbacksTest
set_callback :foo, :before, :foo, if: callback
def run; run_callbacks :foo; end
private
- def foo; end
+ def foo; end
}
object = klass.new
object.run
@@ -958,7 +1082,7 @@ module CallbacksTest
def test_proc_arity2
assert_raises(ArgumentError) do
- object = build_class(->(a,b) { }).new
+ object = build_class(->(a, b) {}).new
object.run
end
end
@@ -1080,7 +1204,7 @@ module CallbacksTest
def test_skip_string # raises error
calls = []
- klass = ActiveSupport::Deprecation.silence { build_class("bar") }
+ klass = ActiveSupport::Deprecation.silence { build_class("bar") }
klass.class_eval { define_method(:bar) { calls << klass } }
assert_raises(ArgumentError) { klass.skip "bar" }
klass.new.run
diff --git a/activesupport/test/class_cache_test.rb b/activesupport/test/class_cache_test.rb
index c618fea81a..004b4dc9ce 100644
--- a/activesupport/test/class_cache_test.rb
+++ b/activesupport/test/class_cache_test.rb
@@ -61,7 +61,7 @@ module ActiveSupport
def test_safe_get_constantizes_doesnt_fail_on_invalid_names
assert @cache.empty?
- assert_equal nil, @cache.safe_get("OmgTotallyInvalidConstantName")
+ assert_nil @cache.safe_get("OmgTotallyInvalidConstantName")
end
def test_new_rejects_strings
diff --git a/activesupport/test/concern_test.rb b/activesupport/test/concern_test.rb
index 4af9ce8163..7a5a5414a7 100644
--- a/activesupport/test/concern_test.rb
+++ b/activesupport/test/concern_test.rb
@@ -66,7 +66,7 @@ class ConcernTest < ActiveSupport::TestCase
def test_module_is_included_normally
@klass.include(Baz)
assert_equal "baz", @klass.new.baz
- assert @klass.included_modules.include?(ConcernTest::Baz)
+ assert_includes @klass.included_modules, ConcernTest::Baz
end
def test_class_methods_are_extended
@@ -76,7 +76,7 @@ class ConcernTest < ActiveSupport::TestCase
end
def test_class_methods_are_extended_only_on_expected_objects
- ::Object.__send__(:include, Qux)
+ ::Object.include(Qux)
Object.extend(Qux::ClassMethods)
# module needs to be created after Qux is included in Object or bug won't
# be triggered
@@ -105,7 +105,7 @@ class ConcernTest < ActiveSupport::TestCase
assert_equal "bar", @klass.new.bar
assert_equal "bar+baz", @klass.new.baz
assert_equal "bar's baz + baz", @klass.baz
- assert @klass.included_modules.include?(ConcernTest::Bar)
+ assert_includes @klass.included_modules, ConcernTest::Bar
end
def test_dependencies_with_multiple_modules
diff --git a/activesupport/test/configurable_test.rb b/activesupport/test/configurable_test.rb
index c67ea8b222..3cd6d2d4d0 100644
--- a/activesupport/test/configurable_test.rb
+++ b/activesupport/test/configurable_test.rb
@@ -121,11 +121,11 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase
def assert_method_defined(object, method)
methods = object.public_methods.map(&:to_s)
- assert methods.include?(method.to_s), "Expected #{methods.inspect} to include #{method.to_s.inspect}"
+ assert_includes methods, method.to_s, "Expected #{methods.inspect} to include #{method.to_s.inspect}"
end
def assert_method_not_defined(object, method)
methods = object.public_methods.map(&:to_s)
- assert !methods.include?(method.to_s), "Expected #{methods.inspect} to not include #{method.to_s.inspect}"
+ assert_not_includes methods, method.to_s, "Expected #{methods.inspect} to not include #{method.to_s.inspect}"
end
end
diff --git a/activesupport/test/constantize_test_cases.rb b/activesupport/test/constantize_test_cases.rb
index af2db8c991..32b720bcbb 100644
--- a/activesupport/test/constantize_test_cases.rb
+++ b/activesupport/test/constantize_test_cases.rb
@@ -73,6 +73,11 @@ module ConstantizeTestCases
yield("RaisesNoMethodError")
end
end
+
+ with_autoloading_fixtures do
+ yield("Prepend::SubClassConflict")
+ assert_equal "constant", defined?(Prepend::SubClassConflict)
+ end
end
def run_safe_constantize_tests_on
diff --git a/activesupport/test/core_ext/array/conversions_test.rb b/activesupport/test/core_ext/array/conversions_test.rb
index ccbdab19f0..29e661a99b 100644
--- a/activesupport/test/core_ext/array/conversions_test.rb
+++ b/activesupport/test/core_ext/array/conversions_test.rb
@@ -25,11 +25,6 @@ class ToSentenceTest < ActiveSupport::TestCase
assert_equal "one, two and three", ["one", "two", "three"].to_sentence(last_word_connector: " and ")
end
- def test_to_sentence_with_fallback_string
- assert_equal "none", [].to_sentence(fallback_string: "none")
- assert_equal "one, two, and three", ["one", "two", "three"].to_sentence(fallback_string: "none")
- end
-
def test_two_elements
assert_equal "one and two", ["one", "two"].to_sentence
assert_equal "one two", ["one", "two"].to_sentence(two_words_connector: " ")
@@ -63,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, :fallback_string"
+ assert_equal exception.message, "Unknown key: :passing. Valid keys are: :words_connector, :two_words_connector, :last_word_connector, :locale"
end
def test_always_returns_string
@@ -97,28 +92,28 @@ class ToXmlTest < ActiveSupport::TestCase
].to_xml(skip_instruct: true, indent: 0)
assert_equal '<objects type="array"><object>', xml.first(30)
- assert xml.include?(%(<age type="integer">26</age>)), xml
- assert xml.include?(%(<age-in-millis type="integer">820497600000</age-in-millis>)), xml
- assert xml.include?(%(<name>David</name>)), xml
- assert xml.include?(%(<age type="integer">31</age>)), xml
- assert xml.include?(%(<age-in-millis type="decimal">1.0</age-in-millis>)), xml
- assert xml.include?(%(<name>Jason</name>)), xml
+ assert_includes xml, %(<age type="integer">26</age>), xml
+ assert_includes xml, %(<age-in-millis type="integer">820497600000</age-in-millis>), xml
+ assert_includes xml, %(<name>David</name>), xml
+ assert_includes xml, %(<age type="integer">31</age>), xml
+ assert_includes xml, %(<age-in-millis type="decimal">1.0</age-in-millis>), xml
+ assert_includes xml, %(<name>Jason</name>), xml
end
def test_to_xml_with_non_hash_elements
xml = %w[1 2 3].to_xml(skip_instruct: true, indent: 0)
assert_equal '<strings type="array"><string', xml.first(29)
- assert xml.include?(%(<string>2</string>)), xml
+ assert_includes xml, %(<string>2</string>), xml
end
def test_to_xml_with_non_hash_different_type_elements
xml = [1, 2.0, "3"].to_xml(skip_instruct: true, indent: 0)
assert_equal '<objects type="array"><object', xml.first(29)
- assert xml.include?(%(<object type="integer">1</object>)), xml
- assert xml.include?(%(<object type="float">2.0</object>)), xml
- assert xml.include?(%(object>3</object>)), xml
+ assert_includes xml, %(<object type="integer">1</object>), xml
+ assert_includes xml, %(<object type="float">2.0</object>), xml
+ assert_includes xml, %(object>3</object>), xml
end
def test_to_xml_with_dedicated_name
@@ -135,10 +130,10 @@ class ToXmlTest < ActiveSupport::TestCase
].to_xml(skip_instruct: true, skip_types: true, indent: 0)
assert_equal "<objects><object>", xml.first(17)
- assert xml.include?(%(<street-address>Paulina</street-address>))
- assert xml.include?(%(<name>David</name>))
- assert xml.include?(%(<street-address>Evergreen</street-address>))
- assert xml.include?(%(<name>Jason</name>))
+ assert_includes xml, %(<street-address>Paulina</street-address>)
+ assert_includes xml, %(<name>David</name>)
+ assert_includes xml, %(<street-address>Evergreen</street-address>)
+ assert_includes xml, %(<name>Jason</name>)
end
def test_to_xml_with_indent_set
@@ -147,10 +142,10 @@ class ToXmlTest < ActiveSupport::TestCase
].to_xml(skip_instruct: true, skip_types: true, indent: 4)
assert_equal "<objects>\n <object>", xml.first(22)
- assert xml.include?(%(\n <street-address>Paulina</street-address>))
- assert xml.include?(%(\n <name>David</name>))
- assert xml.include?(%(\n <street-address>Evergreen</street-address>))
- assert xml.include?(%(\n <name>Jason</name>))
+ assert_includes xml, %(\n <street-address>Paulina</street-address>)
+ assert_includes xml, %(\n <name>David</name>)
+ assert_includes xml, %(\n <street-address>Evergreen</street-address>)
+ assert_includes xml, %(\n <name>Jason</name>)
end
def test_to_xml_with_dasherize_false
@@ -159,8 +154,8 @@ class ToXmlTest < ActiveSupport::TestCase
].to_xml(skip_instruct: true, skip_types: true, indent: 0, dasherize: false)
assert_equal "<objects><object>", xml.first(17)
- assert xml.include?(%(<street_address>Paulina</street_address>))
- assert xml.include?(%(<street_address>Evergreen</street_address>))
+ assert_includes xml, %(<street_address>Paulina</street_address>)
+ assert_includes xml, %(<street_address>Evergreen</street_address>)
end
def test_to_xml_with_dasherize_true
@@ -169,8 +164,8 @@ class ToXmlTest < ActiveSupport::TestCase
].to_xml(skip_instruct: true, skip_types: true, indent: 0, dasherize: true)
assert_equal "<objects><object>", xml.first(17)
- assert xml.include?(%(<street-address>Paulina</street-address>))
- assert xml.include?(%(<street-address>Evergreen</street-address>))
+ assert_includes xml, %(<street-address>Paulina</street-address>)
+ assert_includes xml, %(<street-address>Evergreen</street-address>)
end
def test_to_xml_with_instruct
@@ -191,7 +186,7 @@ class ToXmlTest < ActiveSupport::TestCase
builder.count 2
end
- assert xml.include?(%(<count>2</count>)), xml
+ assert_includes xml, %(<count>2</count>), xml
end
def test_to_xml_with_empty
diff --git a/activesupport/test/core_ext/array/grouping_test.rb b/activesupport/test/core_ext/array/grouping_test.rb
index 86c9bae131..4c6aadba8c 100644
--- a/activesupport/test/core_ext/array/grouping_test.rb
+++ b/activesupport/test/core_ext/array/grouping_test.rb
@@ -4,11 +4,11 @@ require "active_support/core_ext/array"
class GroupingTest < ActiveSupport::TestCase
def setup
# In Ruby < 2.4, test we avoid Integer#/ (redefined by mathn)
- Fixnum.send :private, :/ unless Fixnum == Integer
+ Fixnum.send :private, :/ unless 0.class == Integer
end
def teardown
- Fixnum.send :public, :/ unless Fixnum == Integer
+ Fixnum.send :public, :/ unless 0.class == Integer
end
def test_in_groups_of_with_perfect_fit
@@ -114,7 +114,7 @@ class SplitTest < ActiveSupport::TestCase
def test_split_with_block
a = (1..10).to_a
assert_equal [[1, 2], [4, 5], [7, 8], [10]], a.split { |i| i % 3 == 0 }
- assert_equal [1, 2, 3, 4, 5, 6, 7, 8, 9 ,10], a
+ assert_equal [1, 2, 3, 4, 5, 6, 7, 8, 9 , 10], a
end
def test_split_with_edge_values
diff --git a/activesupport/test/core_ext/class_test.rb b/activesupport/test/core_ext/class_test.rb
index a9c44907cc..a7905196ae 100644
--- a/activesupport/test/core_ext/class_test.rb
+++ b/activesupport/test/core_ext/class_test.rb
@@ -25,4 +25,14 @@ class ClassTest < ActiveSupport::TestCase
assert_equal [Baz], Bar.subclasses
assert_equal [], Baz.subclasses
end
+
+ def test_descendants_excludes_singleton_classes
+ klass = Parent.new.singleton_class
+ refute Parent.descendants.include?(klass), "descendants should not include singleton classes"
+ end
+
+ def test_subclasses_excludes_singleton_classes
+ klass = Parent.new.singleton_class
+ refute Parent.subclasses.include?(klass), "subclasses should not include singleton classes"
+ end
end
diff --git a/activesupport/test/core_ext/date_and_time_behavior.rb b/activesupport/test/core_ext/date_and_time_behavior.rb
index bf83ac602f..6c77e8f313 100644
--- a/activesupport/test/core_ext/date_and_time_behavior.rb
+++ b/activesupport/test/core_ext/date_and_time_behavior.rb
@@ -2,106 +2,106 @@ require "abstract_unit"
module DateAndTimeBehavior
def test_yesterday
- assert_equal date_time_init(2005,2,21,10,10,10), date_time_init(2005,2,22,10,10,10).yesterday
- assert_equal date_time_init(2005,2,28,10,10,10), date_time_init(2005,3,2,10,10,10).yesterday.yesterday
+ assert_equal date_time_init(2005, 2, 21, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).yesterday
+ assert_equal date_time_init(2005, 2, 28, 10, 10, 10), date_time_init(2005, 3, 2, 10, 10, 10).yesterday.yesterday
end
def test_prev_day
- assert_equal date_time_init(2005,2,21,10,10,10), date_time_init(2005,2,22,10,10,10).prev_day
- assert_equal date_time_init(2005,2,28,10,10,10), date_time_init(2005,3,2,10,10,10).prev_day.prev_day
+ assert_equal date_time_init(2005, 2, 21, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).prev_day
+ assert_equal date_time_init(2005, 2, 28, 10, 10, 10), date_time_init(2005, 3, 2, 10, 10, 10).prev_day.prev_day
end
def test_tomorrow
- assert_equal date_time_init(2005,2,23,10,10,10), date_time_init(2005,2,22,10,10,10).tomorrow
- assert_equal date_time_init(2005,3,2,10,10,10), date_time_init(2005,2,28,10,10,10).tomorrow.tomorrow
+ assert_equal date_time_init(2005, 2, 23, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).tomorrow
+ assert_equal date_time_init(2005, 3, 2, 10, 10, 10), date_time_init(2005, 2, 28, 10, 10, 10).tomorrow.tomorrow
end
def test_next_day
- assert_equal date_time_init(2005,2,23,10,10,10), date_time_init(2005,2,22,10,10,10).next_day
- assert_equal date_time_init(2005,3,2,10,10,10), date_time_init(2005,2,28,10,10,10).next_day.next_day
+ assert_equal date_time_init(2005, 2, 23, 10, 10, 10), date_time_init(2005, 2, 22, 10, 10, 10).next_day
+ assert_equal date_time_init(2005, 3, 2, 10, 10, 10), date_time_init(2005, 2, 28, 10, 10, 10).next_day.next_day
end
def test_days_ago
- assert_equal date_time_init(2005,6,4,10,10,10), date_time_init(2005,6,5,10,10,10).days_ago(1)
- assert_equal date_time_init(2005,5,31,10,10,10), date_time_init(2005,6,5,10,10,10).days_ago(5)
+ assert_equal date_time_init(2005, 6, 4, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).days_ago(1)
+ assert_equal date_time_init(2005, 5, 31, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).days_ago(5)
end
def test_days_since
- assert_equal date_time_init(2005,6,6,10,10,10), date_time_init(2005,6,5,10,10,10).days_since(1)
- assert_equal date_time_init(2005,1,1,10,10,10), date_time_init(2004,12,31,10,10,10).days_since(1)
+ assert_equal date_time_init(2005, 6, 6, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).days_since(1)
+ assert_equal date_time_init(2005, 1, 1, 10, 10, 10), date_time_init(2004, 12, 31, 10, 10, 10).days_since(1)
end
def test_weeks_ago
- assert_equal date_time_init(2005,5,29,10,10,10), date_time_init(2005,6,5,10,10,10).weeks_ago(1)
- assert_equal date_time_init(2005,5,1,10,10,10), date_time_init(2005,6,5,10,10,10).weeks_ago(5)
- assert_equal date_time_init(2005,4,24,10,10,10), date_time_init(2005,6,5,10,10,10).weeks_ago(6)
- assert_equal date_time_init(2005,2,27,10,10,10), date_time_init(2005,6,5,10,10,10).weeks_ago(14)
- assert_equal date_time_init(2004,12,25,10,10,10), date_time_init(2005,1,1,10,10,10).weeks_ago(1)
+ assert_equal date_time_init(2005, 5, 29, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).weeks_ago(1)
+ assert_equal date_time_init(2005, 5, 1, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).weeks_ago(5)
+ assert_equal date_time_init(2005, 4, 24, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).weeks_ago(6)
+ assert_equal date_time_init(2005, 2, 27, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).weeks_ago(14)
+ assert_equal date_time_init(2004, 12, 25, 10, 10, 10), date_time_init(2005, 1, 1, 10, 10, 10).weeks_ago(1)
end
def test_weeks_since
- assert_equal date_time_init(2005,7,14,10,10,10), date_time_init(2005,7,7,10,10,10).weeks_since(1)
- assert_equal date_time_init(2005,7,14,10,10,10), date_time_init(2005,7,7,10,10,10).weeks_since(1)
- assert_equal date_time_init(2005,7,4,10,10,10), date_time_init(2005,6,27,10,10,10).weeks_since(1)
- assert_equal date_time_init(2005,1,4,10,10,10), date_time_init(2004,12,28,10,10,10).weeks_since(1)
+ assert_equal date_time_init(2005, 7, 14, 10, 10, 10), date_time_init(2005, 7, 7, 10, 10, 10).weeks_since(1)
+ assert_equal date_time_init(2005, 7, 14, 10, 10, 10), date_time_init(2005, 7, 7, 10, 10, 10).weeks_since(1)
+ assert_equal date_time_init(2005, 7, 4, 10, 10, 10), date_time_init(2005, 6, 27, 10, 10, 10).weeks_since(1)
+ assert_equal date_time_init(2005, 1, 4, 10, 10, 10), date_time_init(2004, 12, 28, 10, 10, 10).weeks_since(1)
end
def test_months_ago
- assert_equal date_time_init(2005,5,5,10,10,10), date_time_init(2005,6,5,10,10,10).months_ago(1)
- assert_equal date_time_init(2004,11,5,10,10,10), date_time_init(2005,6,5,10,10,10).months_ago(7)
- assert_equal date_time_init(2004,12,5,10,10,10), date_time_init(2005,6,5,10,10,10).months_ago(6)
- assert_equal date_time_init(2004,6,5,10,10,10), date_time_init(2005,6,5,10,10,10).months_ago(12)
- assert_equal date_time_init(2003,6,5,10,10,10), date_time_init(2005,6,5,10,10,10).months_ago(24)
+ assert_equal date_time_init(2005, 5, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).months_ago(1)
+ assert_equal date_time_init(2004, 11, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).months_ago(7)
+ assert_equal date_time_init(2004, 12, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).months_ago(6)
+ assert_equal date_time_init(2004, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).months_ago(12)
+ assert_equal date_time_init(2003, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).months_ago(24)
end
def test_months_since
- assert_equal date_time_init(2005,7,5,10,10,10), date_time_init(2005,6,5,10,10,10).months_since(1)
- assert_equal date_time_init(2006,1,5,10,10,10), date_time_init(2005,12,5,10,10,10).months_since(1)
- assert_equal date_time_init(2005,12,5,10,10,10), date_time_init(2005,6,5,10,10,10).months_since(6)
- assert_equal date_time_init(2006,6,5,10,10,10), date_time_init(2005,12,5,10,10,10).months_since(6)
- assert_equal date_time_init(2006,1,5,10,10,10), date_time_init(2005,6,5,10,10,10).months_since(7)
- assert_equal date_time_init(2006,6,5,10,10,10), date_time_init(2005,6,5,10,10,10).months_since(12)
- assert_equal date_time_init(2007,6,5,10,10,10), date_time_init(2005,6,5,10,10,10).months_since(24)
- assert_equal date_time_init(2005,4,30,10,10,10), date_time_init(2005,3,31,10,10,10).months_since(1)
- assert_equal date_time_init(2005,2,28,10,10,10), date_time_init(2005,1,29,10,10,10).months_since(1)
- assert_equal date_time_init(2005,2,28,10,10,10), date_time_init(2005,1,30,10,10,10).months_since(1)
- assert_equal date_time_init(2005,2,28,10,10,10), date_time_init(2005,1,31,10,10,10).months_since(1)
+ assert_equal date_time_init(2005, 7, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).months_since(1)
+ assert_equal date_time_init(2006, 1, 5, 10, 10, 10), date_time_init(2005, 12, 5, 10, 10, 10).months_since(1)
+ assert_equal date_time_init(2005, 12, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).months_since(6)
+ assert_equal date_time_init(2006, 6, 5, 10, 10, 10), date_time_init(2005, 12, 5, 10, 10, 10).months_since(6)
+ assert_equal date_time_init(2006, 1, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).months_since(7)
+ assert_equal date_time_init(2006, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).months_since(12)
+ assert_equal date_time_init(2007, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).months_since(24)
+ assert_equal date_time_init(2005, 4, 30, 10, 10, 10), date_time_init(2005, 3, 31, 10, 10, 10).months_since(1)
+ assert_equal date_time_init(2005, 2, 28, 10, 10, 10), date_time_init(2005, 1, 29, 10, 10, 10).months_since(1)
+ assert_equal date_time_init(2005, 2, 28, 10, 10, 10), date_time_init(2005, 1, 30, 10, 10, 10).months_since(1)
+ assert_equal date_time_init(2005, 2, 28, 10, 10, 10), date_time_init(2005, 1, 31, 10, 10, 10).months_since(1)
end
def test_years_ago
- assert_equal date_time_init(2004,6,5,10,10,10), date_time_init(2005,6,5,10,10,10).years_ago(1)
- assert_equal date_time_init(1998,6,5,10,10,10), date_time_init(2005,6,5,10,10,10).years_ago(7)
- assert_equal date_time_init(2003,2,28,10,10,10), date_time_init(2004,2,29,10,10,10).years_ago(1) # 1 year ago from leap day
+ assert_equal date_time_init(2004, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).years_ago(1)
+ assert_equal date_time_init(1998, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).years_ago(7)
+ assert_equal date_time_init(2003, 2, 28, 10, 10, 10), date_time_init(2004, 2, 29, 10, 10, 10).years_ago(1) # 1 year ago from leap day
end
def test_years_since
- assert_equal date_time_init(2006,6,5,10,10,10), date_time_init(2005,6,5,10,10,10).years_since(1)
- assert_equal date_time_init(2012,6,5,10,10,10), date_time_init(2005,6,5,10,10,10).years_since(7)
- assert_equal date_time_init(2005,2,28,10,10,10), date_time_init(2004,2,29,10,10,10).years_since(1) # 1 year since leap day
- assert_equal date_time_init(2182,6,5,10,10,10), date_time_init(2005,6,5,10,10,10).years_since(177)
+ assert_equal date_time_init(2006, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).years_since(1)
+ assert_equal date_time_init(2012, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).years_since(7)
+ assert_equal date_time_init(2005, 2, 28, 10, 10, 10), date_time_init(2004, 2, 29, 10, 10, 10).years_since(1) # 1 year since leap day
+ assert_equal date_time_init(2182, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).years_since(177)
end
def test_beginning_of_month
- assert_equal date_time_init(2005,2,1,0,0,0), date_time_init(2005,2,22,10,10,10).beginning_of_month
+ assert_equal date_time_init(2005, 2, 1, 0, 0, 0), date_time_init(2005, 2, 22, 10, 10, 10).beginning_of_month
end
def test_beginning_of_quarter
- assert_equal date_time_init(2005,1,1,0,0,0), date_time_init(2005,2,15,10,10,10).beginning_of_quarter
- assert_equal date_time_init(2005,1,1,0,0,0), date_time_init(2005,1,1,0,0,0).beginning_of_quarter
- assert_equal date_time_init(2005,10,1,0,0,0), date_time_init(2005,12,31,10,10,10).beginning_of_quarter
- assert_equal date_time_init(2005,4,1,0,0,0), date_time_init(2005,6,30,23,59,59).beginning_of_quarter
+ assert_equal date_time_init(2005, 1, 1, 0, 0, 0), date_time_init(2005, 2, 15, 10, 10, 10).beginning_of_quarter
+ assert_equal date_time_init(2005, 1, 1, 0, 0, 0), date_time_init(2005, 1, 1, 0, 0, 0).beginning_of_quarter
+ assert_equal date_time_init(2005, 10, 1, 0, 0, 0), date_time_init(2005, 12, 31, 10, 10, 10).beginning_of_quarter
+ assert_equal date_time_init(2005, 4, 1, 0, 0, 0), date_time_init(2005, 6, 30, 23, 59, 59).beginning_of_quarter
end
def test_end_of_quarter
- assert_equal date_time_init(2007,3,31,23,59,59,Rational(999999999, 1000)), date_time_init(2007,2,15,10,10,10).end_of_quarter
- assert_equal date_time_init(2007,3,31,23,59,59,Rational(999999999, 1000)), date_time_init(2007,3,31,0,0,0).end_of_quarter
- assert_equal date_time_init(2007,12,31,23,59,59,Rational(999999999, 1000)), date_time_init(2007,12,21,10,10,10).end_of_quarter
- assert_equal date_time_init(2007,6,30,23,59,59,Rational(999999999, 1000)), date_time_init(2007,4,1,0,0,0).end_of_quarter
- assert_equal date_time_init(2008,6,30,23,59,59,Rational(999999999, 1000)), date_time_init(2008,5,31,0,0,0).end_of_quarter
+ assert_equal date_time_init(2007, 3, 31, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 2, 15, 10, 10, 10).end_of_quarter
+ assert_equal date_time_init(2007, 3, 31, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 3, 31, 0, 0, 0).end_of_quarter
+ assert_equal date_time_init(2007, 12, 31, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 12, 21, 10, 10, 10).end_of_quarter
+ assert_equal date_time_init(2007, 6, 30, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 4, 1, 0, 0, 0).end_of_quarter
+ assert_equal date_time_init(2008, 6, 30, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2008, 5, 31, 0, 0, 0).end_of_quarter
end
def test_beginning_of_year
- assert_equal date_time_init(2005,1,1,0,0,0), date_time_init(2005,2,22,10,10,10).beginning_of_year
+ assert_equal date_time_init(2005, 1, 1, 0, 0, 0), date_time_init(2005, 2, 22, 10, 10, 10).beginning_of_year
end
def test_next_week
@@ -110,10 +110,10 @@ module DateAndTimeBehavior
# | 22/2 | | | | | # | | | | 4/3 | | # friday in next week `next_week(:friday)`
# 23/10 | | | | | | # 30/10 | | | | | | # monday in next week `next_week`
# 23/10 | | | | | | # | | 1/11 | | | | # wednesday in next week `next_week(:wednesday)`
- assert_equal date_time_init(2005,2,28,0,0,0), date_time_init(2005,2,22,15,15,10).next_week
- assert_equal date_time_init(2005,3,4,0,0,0), date_time_init(2005,2,22,15,15,10).next_week(:friday)
- assert_equal date_time_init(2006,10,30,0,0,0), date_time_init(2006,10,23,0,0,0).next_week
- assert_equal date_time_init(2006,11,1,0,0,0), date_time_init(2006,10,23,0,0,0).next_week(:wednesday)
+ assert_equal date_time_init(2005, 2, 28, 0, 0, 0), date_time_init(2005, 2, 22, 15, 15, 10).next_week
+ assert_equal date_time_init(2005, 3, 4, 0, 0, 0), date_time_init(2005, 2, 22, 15, 15, 10).next_week(:friday)
+ assert_equal date_time_init(2006, 10, 30, 0, 0, 0), date_time_init(2006, 10, 23, 0, 0, 0).next_week
+ assert_equal date_time_init(2006, 11, 1, 0, 0, 0), date_time_init(2006, 10, 23, 0, 0, 0).next_week(:wednesday)
end
def test_next_week_with_default_beginning_of_week_set
@@ -126,45 +126,47 @@ module DateAndTimeBehavior
end
def test_next_week_at_same_time
- assert_equal date_time_init(2005,2,28,15,15,10), date_time_init(2005,2,22,15,15,10).next_week(:monday, same_time: true)
- assert_equal date_time_init(2005,3,4,15,15,10), date_time_init(2005,2,22,15,15,10).next_week(:friday, same_time: true)
- assert_equal date_time_init(2006,10,30,0,0,0), date_time_init(2006,10,23,0,0,0).next_week(:monday, same_time: true)
- assert_equal date_time_init(2006,11,1,0,0,0), date_time_init(2006,10,23,0,0,0).next_week(:wednesday, same_time: true)
+ assert_equal date_time_init(2005, 2, 28, 15, 15, 10), date_time_init(2005, 2, 22, 15, 15, 10).next_week(:monday, same_time: true)
+ assert_equal date_time_init(2005, 2, 28, 15, 15, 10, 999999), date_time_init(2005, 2, 22, 15, 15, 10, 999999).next_week(:monday, same_time: true)
+ assert_equal date_time_init(2005, 2, 28, 15, 15, 10, Rational(999999999, 1000)), date_time_init(2005, 2, 22, 15, 15, 10, Rational(999999999, 1000)).next_week(:monday, same_time: true)
+ assert_equal date_time_init(2005, 3, 4, 15, 15, 10), date_time_init(2005, 2, 22, 15, 15, 10).next_week(:friday, same_time: true)
+ assert_equal date_time_init(2006, 10, 30, 0, 0, 0), date_time_init(2006, 10, 23, 0, 0, 0).next_week(:monday, same_time: true)
+ assert_equal date_time_init(2006, 11, 1, 0, 0, 0), date_time_init(2006, 10, 23, 0, 0, 0).next_week(:wednesday, same_time: true)
end
def test_next_weekday_on_wednesday
- assert_equal date_time_init(2015,1,8,0,0,0), date_time_init(2015,1,7,0,0,0).next_weekday
- assert_equal date_time_init(2015,1,8,15,15,10), date_time_init(2015,1,7,15,15,10).next_weekday
+ assert_equal date_time_init(2015, 1, 8, 0, 0, 0), date_time_init(2015, 1, 7, 0, 0, 0).next_weekday
+ assert_equal date_time_init(2015, 1, 8, 15, 15, 10), date_time_init(2015, 1, 7, 15, 15, 10).next_weekday
end
def test_next_weekday_on_friday
- assert_equal date_time_init(2015,1,5,0,0,0), date_time_init(2015,1,2,0,0,0).next_weekday
- assert_equal date_time_init(2015,1,5,15,15,10), date_time_init(2015,1,2,15,15,10).next_weekday
+ assert_equal date_time_init(2015, 1, 5, 0, 0, 0), date_time_init(2015, 1, 2, 0, 0, 0).next_weekday
+ assert_equal date_time_init(2015, 1, 5, 15, 15, 10), date_time_init(2015, 1, 2, 15, 15, 10).next_weekday
end
def test_next_weekday_on_saturday
- assert_equal date_time_init(2015,1,5,0,0,0), date_time_init(2015,1,3,0,0,0).next_weekday
- assert_equal date_time_init(2015,1,5,15,15,10), date_time_init(2015,1,3,15,15,10).next_weekday
+ assert_equal date_time_init(2015, 1, 5, 0, 0, 0), date_time_init(2015, 1, 3, 0, 0, 0).next_weekday
+ assert_equal date_time_init(2015, 1, 5, 15, 15, 10), date_time_init(2015, 1, 3, 15, 15, 10).next_weekday
end
def test_next_month_on_31st
- assert_equal date_time_init(2005,9,30,15,15,10), date_time_init(2005,8,31,15,15,10).next_month
+ assert_equal date_time_init(2005, 9, 30, 15, 15, 10), date_time_init(2005, 8, 31, 15, 15, 10).next_month
end
def test_next_quarter_on_31st
- assert_equal date_time_init(2005,11,30,15,15,10), date_time_init(2005,8,31,15,15,10).next_quarter
+ assert_equal date_time_init(2005, 11, 30, 15, 15, 10), date_time_init(2005, 8, 31, 15, 15, 10).next_quarter
end
def test_next_year
- assert_equal date_time_init(2006,6,5,10,10,10), date_time_init(2005,6,5,10,10,10).next_year
+ assert_equal date_time_init(2006, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).next_year
end
def test_prev_week
- assert_equal date_time_init(2005,2,21,0,0,0), date_time_init(2005,3,1,15,15,10).prev_week
- assert_equal date_time_init(2005,2,22,0,0,0), date_time_init(2005,3,1,15,15,10).prev_week(:tuesday)
- assert_equal date_time_init(2005,2,25,0,0,0), date_time_init(2005,3,1,15,15,10).prev_week(:friday)
- assert_equal date_time_init(2006,10,30,0,0,0), date_time_init(2006,11,6,0,0,0).prev_week
- assert_equal date_time_init(2006,11,15,0,0,0), date_time_init(2006,11,23,0,0,0).prev_week(:wednesday)
+ assert_equal date_time_init(2005, 2, 21, 0, 0, 0), date_time_init(2005, 3, 1, 15, 15, 10).prev_week
+ assert_equal date_time_init(2005, 2, 22, 0, 0, 0), date_time_init(2005, 3, 1, 15, 15, 10).prev_week(:tuesday)
+ assert_equal date_time_init(2005, 2, 25, 0, 0, 0), date_time_init(2005, 3, 1, 15, 15, 10).prev_week(:friday)
+ assert_equal date_time_init(2006, 10, 30, 0, 0, 0), date_time_init(2006, 11, 6, 0, 0, 0).prev_week
+ assert_equal date_time_init(2006, 11, 15, 0, 0, 0), date_time_init(2006, 11, 23, 0, 0, 0).prev_week(:wednesday)
end
def test_prev_week_with_default_beginning_of_week
@@ -177,138 +179,138 @@ module DateAndTimeBehavior
end
def test_prev_week_at_same_time
- assert_equal date_time_init(2005,2,21,15,15,10), date_time_init(2005,3,1,15,15,10).prev_week(:monday, same_time: true)
- assert_equal date_time_init(2005,2,22,15,15,10), date_time_init(2005,3,1,15,15,10).prev_week(:tuesday, same_time: true)
- assert_equal date_time_init(2005,2,25,15,15,10), date_time_init(2005,3,1,15,15,10).prev_week(:friday, same_time: true)
- assert_equal date_time_init(2006,10,30,0,0,0), date_time_init(2006,11,6,0,0,0).prev_week(:monday, same_time: true)
- assert_equal date_time_init(2006,11,15,0,0,0), date_time_init(2006,11,23,0,0,0).prev_week(:wednesday, same_time: true)
+ assert_equal date_time_init(2005, 2, 21, 15, 15, 10), date_time_init(2005, 3, 1, 15, 15, 10).prev_week(:monday, same_time: true)
+ assert_equal date_time_init(2005, 2, 22, 15, 15, 10), date_time_init(2005, 3, 1, 15, 15, 10).prev_week(:tuesday, same_time: true)
+ assert_equal date_time_init(2005, 2, 25, 15, 15, 10), date_time_init(2005, 3, 1, 15, 15, 10).prev_week(:friday, same_time: true)
+ assert_equal date_time_init(2006, 10, 30, 0, 0, 0), date_time_init(2006, 11, 6, 0, 0, 0).prev_week(:monday, same_time: true)
+ assert_equal date_time_init(2006, 11, 15, 0, 0, 0), date_time_init(2006, 11, 23, 0, 0, 0).prev_week(:wednesday, same_time: true)
end
def test_prev_weekday_on_wednesday
- assert_equal date_time_init(2015,1,6,0,0,0), date_time_init(2015,1,7,0,0,0).prev_weekday
- assert_equal date_time_init(2015,1,6,15,15,10), date_time_init(2015,1,7,15,15,10).prev_weekday
+ assert_equal date_time_init(2015, 1, 6, 0, 0, 0), date_time_init(2015, 1, 7, 0, 0, 0).prev_weekday
+ assert_equal date_time_init(2015, 1, 6, 15, 15, 10), date_time_init(2015, 1, 7, 15, 15, 10).prev_weekday
end
def test_prev_weekday_on_monday
- assert_equal date_time_init(2015,1,2,0,0,0), date_time_init(2015,1,5,0,0,0).prev_weekday
- assert_equal date_time_init(2015,1,2,15,15,10), date_time_init(2015,1,5,15,15,10).prev_weekday
+ assert_equal date_time_init(2015, 1, 2, 0, 0, 0), date_time_init(2015, 1, 5, 0, 0, 0).prev_weekday
+ assert_equal date_time_init(2015, 1, 2, 15, 15, 10), date_time_init(2015, 1, 5, 15, 15, 10).prev_weekday
end
def test_prev_weekday_on_sunday
- assert_equal date_time_init(2015,1,2,0,0,0), date_time_init(2015,1,4,0,0,0).prev_weekday
- assert_equal date_time_init(2015,1,2,15,15,10), date_time_init(2015,1,4,15,15,10).prev_weekday
+ assert_equal date_time_init(2015, 1, 2, 0, 0, 0), date_time_init(2015, 1, 4, 0, 0, 0).prev_weekday
+ assert_equal date_time_init(2015, 1, 2, 15, 15, 10), date_time_init(2015, 1, 4, 15, 15, 10).prev_weekday
end
def test_prev_month_on_31st
- assert_equal date_time_init(2004,2,29,10,10,10), date_time_init(2004,3,31,10,10,10).prev_month
+ assert_equal date_time_init(2004, 2, 29, 10, 10, 10), date_time_init(2004, 3, 31, 10, 10, 10).prev_month
end
def test_prev_quarter_on_31st
- assert_equal date_time_init(2004,2,29,10,10,10), date_time_init(2004,5,31,10,10,10).prev_quarter
+ assert_equal date_time_init(2004, 2, 29, 10, 10, 10), date_time_init(2004, 5, 31, 10, 10, 10).prev_quarter
end
def test_prev_year
- assert_equal date_time_init(2004,6,5,10,10,10), date_time_init(2005,6,5,10,10,10).prev_year
+ assert_equal date_time_init(2004, 6, 5, 10, 10, 10), date_time_init(2005, 6, 5, 10, 10, 10).prev_year
end
def test_days_to_week_start
- assert_equal 0, date_time_init(2011,11,01,0,0,0).days_to_week_start(:tuesday)
- assert_equal 1, date_time_init(2011,11,02,0,0,0).days_to_week_start(:tuesday)
- assert_equal 2, date_time_init(2011,11,03,0,0,0).days_to_week_start(:tuesday)
- assert_equal 3, date_time_init(2011,11,04,0,0,0).days_to_week_start(:tuesday)
- assert_equal 4, date_time_init(2011,11,05,0,0,0).days_to_week_start(:tuesday)
- assert_equal 5, date_time_init(2011,11,06,0,0,0).days_to_week_start(:tuesday)
- assert_equal 6, date_time_init(2011,11,07,0,0,0).days_to_week_start(:tuesday)
-
- assert_equal 3, date_time_init(2011,11,03,0,0,0).days_to_week_start(:monday)
- assert_equal 3, date_time_init(2011,11,04,0,0,0).days_to_week_start(:tuesday)
- assert_equal 3, date_time_init(2011,11,05,0,0,0).days_to_week_start(:wednesday)
- assert_equal 3, date_time_init(2011,11,06,0,0,0).days_to_week_start(:thursday)
- assert_equal 3, date_time_init(2011,11,07,0,0,0).days_to_week_start(:friday)
- assert_equal 3, date_time_init(2011,11,8,0,0,0).days_to_week_start(:saturday)
- assert_equal 3, date_time_init(2011,11,9,0,0,0).days_to_week_start(:sunday)
+ assert_equal 0, date_time_init(2011, 11, 01, 0, 0, 0).days_to_week_start(:tuesday)
+ assert_equal 1, date_time_init(2011, 11, 02, 0, 0, 0).days_to_week_start(:tuesday)
+ assert_equal 2, date_time_init(2011, 11, 03, 0, 0, 0).days_to_week_start(:tuesday)
+ assert_equal 3, date_time_init(2011, 11, 04, 0, 0, 0).days_to_week_start(:tuesday)
+ assert_equal 4, date_time_init(2011, 11, 05, 0, 0, 0).days_to_week_start(:tuesday)
+ assert_equal 5, date_time_init(2011, 11, 06, 0, 0, 0).days_to_week_start(:tuesday)
+ assert_equal 6, date_time_init(2011, 11, 07, 0, 0, 0).days_to_week_start(:tuesday)
+
+ assert_equal 3, date_time_init(2011, 11, 03, 0, 0, 0).days_to_week_start(:monday)
+ assert_equal 3, date_time_init(2011, 11, 04, 0, 0, 0).days_to_week_start(:tuesday)
+ assert_equal 3, date_time_init(2011, 11, 05, 0, 0, 0).days_to_week_start(:wednesday)
+ assert_equal 3, date_time_init(2011, 11, 06, 0, 0, 0).days_to_week_start(:thursday)
+ assert_equal 3, date_time_init(2011, 11, 07, 0, 0, 0).days_to_week_start(:friday)
+ assert_equal 3, date_time_init(2011, 11, 8, 0, 0, 0).days_to_week_start(:saturday)
+ assert_equal 3, date_time_init(2011, 11, 9, 0, 0, 0).days_to_week_start(:sunday)
end
def test_days_to_week_start_with_default_set
with_bw_default(:friday) do
- assert_equal 6, Time.local(2012,03,8,0,0,0).days_to_week_start
- assert_equal 5, Time.local(2012,03,7,0,0,0).days_to_week_start
- assert_equal 4, Time.local(2012,03,6,0,0,0).days_to_week_start
- assert_equal 3, Time.local(2012,03,5,0,0,0).days_to_week_start
- assert_equal 2, Time.local(2012,03,4,0,0,0).days_to_week_start
- assert_equal 1, Time.local(2012,03,3,0,0,0).days_to_week_start
- assert_equal 0, Time.local(2012,03,2,0,0,0).days_to_week_start
+ assert_equal 6, Time.local(2012, 03, 8, 0, 0, 0).days_to_week_start
+ assert_equal 5, Time.local(2012, 03, 7, 0, 0, 0).days_to_week_start
+ assert_equal 4, Time.local(2012, 03, 6, 0, 0, 0).days_to_week_start
+ assert_equal 3, Time.local(2012, 03, 5, 0, 0, 0).days_to_week_start
+ assert_equal 2, Time.local(2012, 03, 4, 0, 0, 0).days_to_week_start
+ assert_equal 1, Time.local(2012, 03, 3, 0, 0, 0).days_to_week_start
+ assert_equal 0, Time.local(2012, 03, 2, 0, 0, 0).days_to_week_start
end
end
def test_beginning_of_week
- assert_equal date_time_init(2005,1,31,0,0,0), date_time_init(2005,2,4,10,10,10).beginning_of_week
- assert_equal date_time_init(2005,11,28,0,0,0), date_time_init(2005,11,28,0,0,0).beginning_of_week #monday
- assert_equal date_time_init(2005,11,28,0,0,0), date_time_init(2005,11,29,0,0,0).beginning_of_week #tuesday
- assert_equal date_time_init(2005,11,28,0,0,0), date_time_init(2005,11,30,0,0,0).beginning_of_week #wednesday
- assert_equal date_time_init(2005,11,28,0,0,0), date_time_init(2005,12,01,0,0,0).beginning_of_week #thursday
- assert_equal date_time_init(2005,11,28,0,0,0), date_time_init(2005,12,02,0,0,0).beginning_of_week #friday
- assert_equal date_time_init(2005,11,28,0,0,0), date_time_init(2005,12,03,0,0,0).beginning_of_week #saturday
- assert_equal date_time_init(2005,11,28,0,0,0), date_time_init(2005,12,04,0,0,0).beginning_of_week #sunday
+ assert_equal date_time_init(2005, 1, 31, 0, 0, 0), date_time_init(2005, 2, 4, 10, 10, 10).beginning_of_week
+ assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 11, 28, 0, 0, 0).beginning_of_week #monday
+ assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 11, 29, 0, 0, 0).beginning_of_week #tuesday
+ assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 11, 30, 0, 0, 0).beginning_of_week #wednesday
+ assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 01, 0, 0, 0).beginning_of_week #thursday
+ assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 02, 0, 0, 0).beginning_of_week #friday
+ assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 03, 0, 0, 0).beginning_of_week #saturday
+ assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 04, 0, 0, 0).beginning_of_week #sunday
end
def test_end_of_week
- assert_equal date_time_init(2008,1,6,23,59,59,Rational(999999999, 1000)), date_time_init(2007,12,31,10,10,10).end_of_week
- assert_equal date_time_init(2007,9,2,23,59,59,Rational(999999999, 1000)), date_time_init(2007,8,27,0,0,0).end_of_week #monday
- assert_equal date_time_init(2007,9,2,23,59,59,Rational(999999999, 1000)), date_time_init(2007,8,28,0,0,0).end_of_week #tuesday
- assert_equal date_time_init(2007,9,2,23,59,59,Rational(999999999, 1000)), date_time_init(2007,8,29,0,0,0).end_of_week #wednesday
- assert_equal date_time_init(2007,9,2,23,59,59,Rational(999999999, 1000)), date_time_init(2007,8,30,0,0,0).end_of_week #thursday
- assert_equal date_time_init(2007,9,2,23,59,59,Rational(999999999, 1000)), date_time_init(2007,8,31,0,0,0).end_of_week #friday
- assert_equal date_time_init(2007,9,2,23,59,59,Rational(999999999, 1000)), date_time_init(2007,9,01,0,0,0).end_of_week #saturday
- assert_equal date_time_init(2007,9,2,23,59,59,Rational(999999999, 1000)), date_time_init(2007,9,02,0,0,0).end_of_week #sunday
+ assert_equal date_time_init(2008, 1, 6, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 12, 31, 10, 10, 10).end_of_week
+ assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 27, 0, 0, 0).end_of_week #monday
+ assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 28, 0, 0, 0).end_of_week #tuesday
+ assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 29, 0, 0, 0).end_of_week #wednesday
+ assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 30, 0, 0, 0).end_of_week #thursday
+ assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 31, 0, 0, 0).end_of_week #friday
+ assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 9, 01, 0, 0, 0).end_of_week #saturday
+ assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 9, 02, 0, 0, 0).end_of_week #sunday
end
def test_end_of_month
- assert_equal date_time_init(2005,3,31,23,59,59,Rational(999999999, 1000)), date_time_init(2005,3,20,10,10,10).end_of_month
- assert_equal date_time_init(2005,2,28,23,59,59,Rational(999999999, 1000)), date_time_init(2005,2,20,10,10,10).end_of_month
- assert_equal date_time_init(2005,4,30,23,59,59,Rational(999999999, 1000)), date_time_init(2005,4,20,10,10,10).end_of_month
+ assert_equal date_time_init(2005, 3, 31, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2005, 3, 20, 10, 10, 10).end_of_month
+ assert_equal date_time_init(2005, 2, 28, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2005, 2, 20, 10, 10, 10).end_of_month
+ assert_equal date_time_init(2005, 4, 30, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2005, 4, 20, 10, 10, 10).end_of_month
end
def test_end_of_year
- assert_equal date_time_init(2007,12,31,23,59,59,Rational(999999999, 1000)), date_time_init(2007,2,22,10,10,10).end_of_year
- assert_equal date_time_init(2007,12,31,23,59,59,Rational(999999999, 1000)), date_time_init(2007,12,31,10,10,10).end_of_year
+ assert_equal date_time_init(2007, 12, 31, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 2, 22, 10, 10, 10).end_of_year
+ assert_equal date_time_init(2007, 12, 31, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 12, 31, 10, 10, 10).end_of_year
end
def test_monday_with_default_beginning_of_week_set
with_bw_default(:saturday) do
- assert_equal date_time_init(2012,9,17,0,0,0), date_time_init(2012,9,18,0,0,0).monday
+ assert_equal date_time_init(2012, 9, 17, 0, 0, 0), date_time_init(2012, 9, 18, 0, 0, 0).monday
end
end
def test_sunday_with_default_beginning_of_week_set
with_bw_default(:wednesday) do
- assert_equal date_time_init(2012,9,23,23,59,59, Rational(999999999, 1000)), date_time_init(2012,9,19,0,0,0).sunday
+ assert_equal date_time_init(2012, 9, 23, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2012, 9, 19, 0, 0, 0).sunday
end
end
def test_on_weekend_on_saturday
- assert date_time_init(2015,1,3,0,0,0).on_weekend?
- assert date_time_init(2015,1,3,15,15,10).on_weekend?
+ assert date_time_init(2015, 1, 3, 0, 0, 0).on_weekend?
+ assert date_time_init(2015, 1, 3, 15, 15, 10).on_weekend?
end
def test_on_weekend_on_sunday
- assert date_time_init(2015,1,4,0,0,0).on_weekend?
- assert date_time_init(2015,1,4,15,15,10).on_weekend?
+ assert date_time_init(2015, 1, 4, 0, 0, 0).on_weekend?
+ assert date_time_init(2015, 1, 4, 15, 15, 10).on_weekend?
end
def test_on_weekend_on_monday
- assert_not date_time_init(2015,1,5,0,0,0).on_weekend?
- assert_not date_time_init(2015,1,5,15,15,10).on_weekend?
+ assert_not date_time_init(2015, 1, 5, 0, 0, 0).on_weekend?
+ assert_not date_time_init(2015, 1, 5, 15, 15, 10).on_weekend?
end
def test_on_weekday_on_sunday
- assert_not date_time_init(2015,1,4,0,0,0).on_weekday?
- assert_not date_time_init(2015,1,4,15,15,10).on_weekday?
+ assert_not date_time_init(2015, 1, 4, 0, 0, 0).on_weekday?
+ assert_not date_time_init(2015, 1, 4, 15, 15, 10).on_weekday?
end
def test_on_weekday_on_monday
- assert date_time_init(2015,1,5,0,0,0).on_weekday?
- assert date_time_init(2015,1,5,15,15,10).on_weekday?
+ assert date_time_init(2015, 1, 5, 0, 0, 0).on_weekday?
+ assert date_time_init(2015, 1, 5, 15, 15, 10).on_weekday?
end
def with_bw_default(bw = :monday)
diff --git a/activesupport/test/core_ext/date_and_time_compatibility_test.rb b/activesupport/test/core_ext/date_and_time_compatibility_test.rb
index 180b3e12aa..4c90460032 100644
--- a/activesupport/test/core_ext/date_and_time_compatibility_test.rb
+++ b/activesupport/test/core_ext/date_and_time_compatibility_test.rb
@@ -40,7 +40,7 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_datetime_to_time_preserves_timezone
with_preserve_timezone(true) do
with_env_tz "US/Eastern" do
- time = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1,24)).to_time
+ time = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24)).to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
@@ -52,7 +52,7 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_datetime_to_time_does_not_preserve_time_zone
with_preserve_timezone(false) do
with_env_tz "US/Eastern" do
- time = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1,24)).to_time
+ time = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24)).to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb
index 0655197335..5a90210bb8 100644
--- a/activesupport/test/core_ext/date_ext_test.rb
+++ b/activesupport/test/core_ext/date_ext_test.rb
@@ -4,19 +4,19 @@ require "core_ext/date_and_time_behavior"
require "time_zone_test_helpers"
class DateExtCalculationsTest < ActiveSupport::TestCase
- def date_time_init(year,month,day,*args)
- Date.new(year,month,day)
+ def date_time_init(year, month, day, *args)
+ Date.new(year, month, day)
end
include DateAndTimeBehavior
include TimeZoneTestHelpers
def test_yesterday_in_calendar_reform
- assert_equal Date.new(1582,10,4), Date.new(1582,10,15).yesterday
+ assert_equal Date.new(1582, 10, 4), Date.new(1582, 10, 15).yesterday
end
def test_tomorrow_in_calendar_reform
- assert_equal Date.new(1582,10,15), Date.new(1582,10,4).tomorrow
+ assert_equal Date.new(1582, 10, 15), Date.new(1582, 10, 4).tomorrow
end
def test_to_s
@@ -83,70 +83,70 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
def test_change
assert_equal Date.new(2005, 2, 21), Date.new(2005, 2, 11).change(day: 21)
assert_equal Date.new(2007, 5, 11), Date.new(2005, 2, 11).change(year: 2007, month: 5)
- assert_equal Date.new(2006,2,22), Date.new(2005,2,22).change(year: 2006)
- assert_equal Date.new(2005,6,22), Date.new(2005,2,22).change(month: 6)
+ assert_equal Date.new(2006, 2, 22), Date.new(2005, 2, 22).change(year: 2006)
+ assert_equal Date.new(2005, 6, 22), Date.new(2005, 2, 22).change(month: 6)
end
def test_sunday
- assert_equal Date.new(2008,3,2), Date.new(2008,3,02).sunday
- assert_equal Date.new(2008,3,2), Date.new(2008,2,29).sunday
+ assert_equal Date.new(2008, 3, 2), Date.new(2008, 3, 02).sunday
+ assert_equal Date.new(2008, 3, 2), Date.new(2008, 2, 29).sunday
end
def test_beginning_of_week_in_calendar_reform
- assert_equal Date.new(1582,10,1), Date.new(1582,10,15).beginning_of_week #friday
+ assert_equal Date.new(1582, 10, 1), Date.new(1582, 10, 15).beginning_of_week #friday
end
def test_end_of_week_in_calendar_reform
- assert_equal Date.new(1582,10,17), Date.new(1582,10,4).end_of_week #thursday
+ assert_equal Date.new(1582, 10, 17), Date.new(1582, 10, 4).end_of_week #thursday
end
def test_end_of_year
- assert_equal Date.new(2008,12,31).to_s, Date.new(2008,2,22).end_of_year.to_s
+ assert_equal Date.new(2008, 12, 31).to_s, Date.new(2008, 2, 22).end_of_year.to_s
end
def test_end_of_month
- assert_equal Date.new(2005,3,31), Date.new(2005,3,20).end_of_month
- assert_equal Date.new(2005,2,28), Date.new(2005,2,20).end_of_month
- assert_equal Date.new(2005,4,30), Date.new(2005,4,20).end_of_month
+ assert_equal Date.new(2005, 3, 31), Date.new(2005, 3, 20).end_of_month
+ assert_equal Date.new(2005, 2, 28), Date.new(2005, 2, 20).end_of_month
+ assert_equal Date.new(2005, 4, 30), Date.new(2005, 4, 20).end_of_month
end
def test_prev_year_in_leap_years
- assert_equal Date.new(1999,2,28), Date.new(2000,2,29).prev_year
+ assert_equal Date.new(1999, 2, 28), Date.new(2000, 2, 29).prev_year
end
def test_prev_year_in_calendar_reform
- assert_equal Date.new(1582,10,4), Date.new(1583,10,14).prev_year
+ assert_equal Date.new(1582, 10, 4), Date.new(1583, 10, 14).prev_year
end
def test_last_year
- assert_equal Date.new(2004,6,5), Date.new(2005,6,5).last_year
+ assert_equal Date.new(2004, 6, 5), Date.new(2005, 6, 5).last_year
end
def test_last_year_in_leap_years
- assert_equal Date.new(1999,2,28), Date.new(2000,2,29).last_year
+ assert_equal Date.new(1999, 2, 28), Date.new(2000, 2, 29).last_year
end
def test_last_year_in_calendar_reform
- assert_equal Date.new(1582,10,4), Date.new(1583,10,14).last_year
+ assert_equal Date.new(1582, 10, 4), Date.new(1583, 10, 14).last_year
end
def test_next_year_in_leap_years
- assert_equal Date.new(2001,2,28), Date.new(2000,2,29).next_year
+ assert_equal Date.new(2001, 2, 28), Date.new(2000, 2, 29).next_year
end
def test_next_year_in_calendar_reform
- assert_equal Date.new(1582,10,4), Date.new(1581,10,10).next_year
+ assert_equal Date.new(1582, 10, 4), Date.new(1581, 10, 10).next_year
end
def test_advance
- assert_equal Date.new(2006,2,28), Date.new(2005,2,28).advance(years: 1)
- assert_equal Date.new(2005,6,28), Date.new(2005,2,28).advance(months: 4)
- assert_equal Date.new(2005,3,21), Date.new(2005,2,28).advance(weeks: 3)
- assert_equal Date.new(2005,3,5), Date.new(2005,2,28).advance(days: 5)
- assert_equal Date.new(2012,9,28), Date.new(2005,2,28).advance(years: 7, months: 7)
- assert_equal Date.new(2013,10,3), Date.new(2005,2,28).advance(years: 7, months: 19, days: 5)
- assert_equal Date.new(2013,10,17), Date.new(2005,2,28).advance(years: 7, months: 19, weeks: 2, days: 5)
- assert_equal Date.new(2005,2,28), Date.new(2004,2,29).advance(years: 1) #leap day plus one year
+ assert_equal Date.new(2006, 2, 28), Date.new(2005, 2, 28).advance(years: 1)
+ assert_equal Date.new(2005, 6, 28), Date.new(2005, 2, 28).advance(months: 4)
+ assert_equal Date.new(2005, 3, 21), Date.new(2005, 2, 28).advance(weeks: 3)
+ assert_equal Date.new(2005, 3, 5), Date.new(2005, 2, 28).advance(days: 5)
+ assert_equal Date.new(2012, 9, 28), Date.new(2005, 2, 28).advance(years: 7, months: 7)
+ assert_equal Date.new(2013, 10, 3), Date.new(2005, 2, 28).advance(years: 7, months: 19, days: 5)
+ assert_equal Date.new(2013, 10, 17), Date.new(2005, 2, 28).advance(years: 7, months: 19, weeks: 2, days: 5)
+ assert_equal Date.new(2005, 2, 28), Date.new(2004, 2, 29).advance(years: 1) #leap day plus one year
end
def test_advance_does_first_years_and_then_days
@@ -160,27 +160,27 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
end
def test_advance_in_calendar_reform
- assert_equal Date.new(1582,10,15), Date.new(1582,10,4).advance(days: 1)
- assert_equal Date.new(1582,10,4), Date.new(1582,10,15).advance(days: -1)
+ assert_equal Date.new(1582, 10, 15), Date.new(1582, 10, 4).advance(days: 1)
+ assert_equal Date.new(1582, 10, 4), Date.new(1582, 10, 15).advance(days: -1)
5.upto(14) do |day|
- assert_equal Date.new(1582,10,4), Date.new(1582,9,day).advance(months: 1)
- assert_equal Date.new(1582,10,4), Date.new(1582,11,day).advance(months: -1)
- assert_equal Date.new(1582,10,4), Date.new(1581,10,day).advance(years: 1)
- assert_equal Date.new(1582,10,4), Date.new(1583,10,day).advance(years: -1)
+ assert_equal Date.new(1582, 10, 4), Date.new(1582, 9, day).advance(months: 1)
+ assert_equal Date.new(1582, 10, 4), Date.new(1582, 11, day).advance(months: -1)
+ assert_equal Date.new(1582, 10, 4), Date.new(1581, 10, day).advance(years: 1)
+ assert_equal Date.new(1582, 10, 4), Date.new(1583, 10, day).advance(years: -1)
end
end
def test_last_week
- assert_equal Date.new(2005,5,9), Date.new(2005,5,17).last_week
- assert_equal Date.new(2006,12,25), Date.new(2007,1,7).last_week
- assert_equal Date.new(2010,2,12), Date.new(2010,2,19).last_week(:friday)
- assert_equal Date.new(2010,2,13), Date.new(2010,2,19).last_week(:saturday)
- assert_equal Date.new(2010,2,27), Date.new(2010,3,4).last_week(:saturday)
+ assert_equal Date.new(2005, 5, 9), Date.new(2005, 5, 17).last_week
+ assert_equal Date.new(2006, 12, 25), Date.new(2007, 1, 7).last_week
+ assert_equal Date.new(2010, 2, 12), Date.new(2010, 2, 19).last_week(:friday)
+ assert_equal Date.new(2010, 2, 13), Date.new(2010, 2, 19).last_week(:saturday)
+ assert_equal Date.new(2010, 2, 27), Date.new(2010, 3, 4).last_week(:saturday)
end
def test_next_week_in_calendar_reform
- assert_equal Date.new(1582,10,15), Date.new(1582,9,30).next_week(:friday)
- assert_equal Date.new(1582,10,18), Date.new(1582,10,4).next_week
+ assert_equal Date.new(1582, 10, 15), Date.new(1582, 9, 30).next_week(:friday)
+ assert_equal Date.new(1582, 10, 18), Date.new(1582, 10, 4).next_week
end
def test_last_month_on_31st
@@ -236,97 +236,97 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
end
def test_since
- assert_equal Time.local(2005,2,21,0,0,45), Date.new(2005,2,21).since(45)
+ assert_equal Time.local(2005, 2, 21, 0, 0, 45), Date.new(2005, 2, 21).since(45)
end
def test_since_when_zone_is_set
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
with_env_tz "UTC" do
with_tz_default zone do
- assert_equal zone.local(2005,2,21,0,0,45), Date.new(2005,2,21).since(45)
- assert_equal zone, Date.new(2005,2,21).since(45).time_zone
+ assert_equal zone.local(2005, 2, 21, 0, 0, 45), Date.new(2005, 2, 21).since(45)
+ assert_equal zone, Date.new(2005, 2, 21).since(45).time_zone
end
end
end
def test_ago
- assert_equal Time.local(2005,2,20,23,59,15), Date.new(2005,2,21).ago(45)
+ assert_equal Time.local(2005, 2, 20, 23, 59, 15), Date.new(2005, 2, 21).ago(45)
end
def test_ago_when_zone_is_set
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
with_env_tz "UTC" do
with_tz_default zone do
- assert_equal zone.local(2005,2,20,23,59,15), Date.new(2005,2,21).ago(45)
- assert_equal zone, Date.new(2005,2,21).ago(45).time_zone
+ assert_equal zone.local(2005, 2, 20, 23, 59, 15), Date.new(2005, 2, 21).ago(45)
+ assert_equal zone, Date.new(2005, 2, 21).ago(45).time_zone
end
end
end
def test_beginning_of_day
- assert_equal Time.local(2005,2,21,0,0,0), Date.new(2005,2,21).beginning_of_day
+ assert_equal Time.local(2005, 2, 21, 0, 0, 0), Date.new(2005, 2, 21).beginning_of_day
end
def test_middle_of_day
- assert_equal Time.local(2005,2,21,12,0,0), Date.new(2005,2,21).middle_of_day
+ assert_equal Time.local(2005, 2, 21, 12, 0, 0), Date.new(2005, 2, 21).middle_of_day
end
def test_beginning_of_day_when_zone_is_set
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
with_env_tz "UTC" do
with_tz_default zone do
- assert_equal zone.local(2005,2,21,0,0,0), Date.new(2005,2,21).beginning_of_day
- assert_equal zone, Date.new(2005,2,21).beginning_of_day.time_zone
+ assert_equal zone.local(2005, 2, 21, 0, 0, 0), Date.new(2005, 2, 21).beginning_of_day
+ assert_equal zone, Date.new(2005, 2, 21).beginning_of_day.time_zone
end
end
end
def test_end_of_day
- assert_equal Time.local(2005,2,21,23,59,59,Rational(999999999, 1000)), Date.new(2005,2,21).end_of_day
+ assert_equal Time.local(2005, 2, 21, 23, 59, 59, Rational(999999999, 1000)), Date.new(2005, 2, 21).end_of_day
end
def test_end_of_day_when_zone_is_set
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
with_env_tz "UTC" do
with_tz_default zone do
- assert_equal zone.local(2005,2,21,23,59,59,Rational(999999999, 1000)), Date.new(2005,2,21).end_of_day
- assert_equal zone, Date.new(2005,2,21).end_of_day.time_zone
+ assert_equal zone.local(2005, 2, 21, 23, 59, 59, Rational(999999999, 1000)), Date.new(2005, 2, 21).end_of_day
+ assert_equal zone, Date.new(2005, 2, 21).end_of_day.time_zone
end
end
end
def test_all_day
- beginning_of_day = Time.local(2011,6,7,0,0,0)
- end_of_day = Time.local(2011,6,7,23,59,59,Rational(999999999, 1000))
- assert_equal beginning_of_day..end_of_day, Date.new(2011,6,7).all_day
+ beginning_of_day = Time.local(2011, 6, 7, 0, 0, 0)
+ end_of_day = Time.local(2011, 6, 7, 23, 59, 59, Rational(999999999, 1000))
+ assert_equal beginning_of_day..end_of_day, Date.new(2011, 6, 7).all_day
end
def test_all_day_when_zone_is_set
zone = ActiveSupport::TimeZone["Hawaii"]
with_env_tz "UTC" do
with_tz_default zone do
- beginning_of_day = zone.local(2011,6,7,0,0,0)
- end_of_day = zone.local(2011,6,7,23,59,59,Rational(999999999, 1000))
- assert_equal beginning_of_day..end_of_day, Date.new(2011,6,7).all_day
+ beginning_of_day = zone.local(2011, 6, 7, 0, 0, 0)
+ end_of_day = zone.local(2011, 6, 7, 23, 59, 59, Rational(999999999, 1000))
+ assert_equal beginning_of_day..end_of_day, Date.new(2011, 6, 7).all_day
end
end
end
def test_all_week
- assert_equal Date.new(2011,6,6)..Date.new(2011,6,12), Date.new(2011,6,7).all_week
- assert_equal Date.new(2011,6,5)..Date.new(2011,6,11), Date.new(2011,6,7).all_week(:sunday)
+ assert_equal Date.new(2011, 6, 6)..Date.new(2011, 6, 12), Date.new(2011, 6, 7).all_week
+ assert_equal Date.new(2011, 6, 5)..Date.new(2011, 6, 11), Date.new(2011, 6, 7).all_week(:sunday)
end
def test_all_month
- assert_equal Date.new(2011,6,1)..Date.new(2011,6,30), Date.new(2011,6,7).all_month
+ assert_equal Date.new(2011, 6, 1)..Date.new(2011, 6, 30), Date.new(2011, 6, 7).all_month
end
def test_all_quarter
- assert_equal Date.new(2011,4,1)..Date.new(2011,6,30), Date.new(2011,6,7).all_quarter
+ assert_equal Date.new(2011, 4, 1)..Date.new(2011, 6, 30), Date.new(2011, 6, 7).all_quarter
end
def test_all_year
- assert_equal Date.new(2011,1,1)..Date.new(2011,12,31), Date.new(2011,6,7).all_year
+ assert_equal Date.new(2011, 1, 1)..Date.new(2011, 12, 31), Date.new(2011, 6, 7).all_year
end
def test_xmlschema
@@ -353,16 +353,16 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
def test_past
Date.stub(:current, Date.new(2000, 1, 1)) do
assert_equal true, Date.new(1999, 12, 31).past?
- assert_equal false, Date.new(2000,1,1).past?
- assert_equal false, Date.new(2000,1,2).past?
+ assert_equal false, Date.new(2000, 1, 1).past?
+ assert_equal false, Date.new(2000, 1, 2).past?
end
end
def test_future
Date.stub(:current, Date.new(2000, 1, 1)) do
assert_equal false, Date.new(1999, 12, 31).future?
- assert_equal false, Date.new(2000,1,1).future?
- assert_equal true, Date.new(2000,1,2).future?
+ assert_equal false, Date.new(2000, 1, 1).future?
+ assert_equal true, Date.new(2000, 1, 2).future?
end
end
@@ -385,7 +385,7 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
def test_date_advance_should_not_change_passed_options_hash
options = { years: 3, months: 11, days: 2 }
- Date.new(2005,2,28).advance(options)
+ Date.new(2005, 2, 28).advance(options)
assert_equal({ years: 3, months: 11, days: 2 }, options)
end
end
diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index f2a50f4693..e9be181749 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -4,8 +4,8 @@ require "core_ext/date_and_time_behavior"
require "time_zone_test_helpers"
class DateTimeExtCalculationsTest < ActiveSupport::TestCase
- def date_time_init(year,month,day,hour,minute,second,*args)
- DateTime.civil(year,month,day,hour,minute,second)
+ def date_time_init(year, month, day, hour, minute, second, *args)
+ DateTime.civil(year, month, day, hour, minute, second)
end
include DateAndTimeBehavior
@@ -45,7 +45,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_instance_of Time, DateTime.new(2016, 3, 11, 15, 11, 12, 0).localtime
assert_equal Time.local(2016, 3, 11, 10, 11, 12), DateTime.new(2016, 3, 11, 15, 11, 12, 0).localtime
assert_equal Time.local(2016, 3, 21, 11, 11, 12), DateTime.new(2016, 3, 21, 15, 11, 12, 0).localtime
- assert_equal Time.local(2016, 4, 1, 11, 11, 12), DateTime.new(2016, 4, 1, 16, 11, 12, Rational(1,24)).localtime
+ assert_equal Time.local(2016, 4, 1, 11, 11, 12), DateTime.new(2016, 4, 1, 16, 11, 12, Rational(1, 24)).localtime
end
end
@@ -54,7 +54,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_instance_of Time, DateTime.new(2016, 3, 11, 15, 11, 12, 0).getlocal
assert_equal Time.local(2016, 3, 11, 10, 11, 12), DateTime.new(2016, 3, 11, 15, 11, 12, 0).getlocal
assert_equal Time.local(2016, 3, 21, 11, 11, 12), DateTime.new(2016, 3, 21, 15, 11, 12, 0).getlocal
- assert_equal Time.local(2016, 4, 1, 11, 11, 12), DateTime.new(2016, 4, 1, 16, 11, 12, Rational(1,24)).getlocal
+ assert_equal Time.local(2016, 4, 1, 11, 11, 12), DateTime.new(2016, 4, 1, 16, 11, 12, Rational(1, 24)).getlocal
end
end
@@ -90,108 +90,108 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_seconds_since_midnight
- assert_equal 1,DateTime.civil(2005,1,1,0,0,1).seconds_since_midnight
- assert_equal 60,DateTime.civil(2005,1,1,0,1,0).seconds_since_midnight
- assert_equal 3660,DateTime.civil(2005,1,1,1,1,0).seconds_since_midnight
- assert_equal 86399,DateTime.civil(2005,1,1,23,59,59).seconds_since_midnight
+ assert_equal 1, DateTime.civil(2005, 1, 1, 0, 0, 1).seconds_since_midnight
+ assert_equal 60, DateTime.civil(2005, 1, 1, 0, 1, 0).seconds_since_midnight
+ assert_equal 3660, DateTime.civil(2005, 1, 1, 1, 1, 0).seconds_since_midnight
+ assert_equal 86399, DateTime.civil(2005, 1, 1, 23, 59, 59).seconds_since_midnight
end
def test_seconds_until_end_of_day
- assert_equal 0, DateTime.civil(2005,1,1,23,59,59).seconds_until_end_of_day
- assert_equal 1, DateTime.civil(2005,1,1,23,59,58).seconds_until_end_of_day
- assert_equal 60, DateTime.civil(2005,1,1,23,58,59).seconds_until_end_of_day
- assert_equal 3660, DateTime.civil(2005,1,1,22,58,59).seconds_until_end_of_day
- assert_equal 86399, DateTime.civil(2005,1,1,0,0,0).seconds_until_end_of_day
+ assert_equal 0, DateTime.civil(2005, 1, 1, 23, 59, 59).seconds_until_end_of_day
+ assert_equal 1, DateTime.civil(2005, 1, 1, 23, 59, 58).seconds_until_end_of_day
+ assert_equal 60, DateTime.civil(2005, 1, 1, 23, 58, 59).seconds_until_end_of_day
+ assert_equal 3660, DateTime.civil(2005, 1, 1, 22, 58, 59).seconds_until_end_of_day
+ assert_equal 86399, DateTime.civil(2005, 1, 1, 0, 0, 0).seconds_until_end_of_day
end
def test_beginning_of_day
- assert_equal DateTime.civil(2005,2,4,0,0,0), DateTime.civil(2005,2,4,10,10,10).beginning_of_day
+ assert_equal DateTime.civil(2005, 2, 4, 0, 0, 0), DateTime.civil(2005, 2, 4, 10, 10, 10).beginning_of_day
end
def test_middle_of_day
- assert_equal DateTime.civil(2005,2,4,12,0,0), DateTime.civil(2005,2,4,10,10,10).middle_of_day
+ assert_equal DateTime.civil(2005, 2, 4, 12, 0, 0), DateTime.civil(2005, 2, 4, 10, 10, 10).middle_of_day
end
def test_end_of_day
- assert_equal DateTime.civil(2005,2,4,23,59,59), DateTime.civil(2005,2,4,10,10,10).end_of_day
+ assert_equal DateTime.civil(2005, 2, 4, 23, 59, 59), DateTime.civil(2005, 2, 4, 10, 10, 10).end_of_day
end
def test_beginning_of_hour
- assert_equal DateTime.civil(2005,2,4,19,0,0), DateTime.civil(2005,2,4,19,30,10).beginning_of_hour
+ assert_equal DateTime.civil(2005, 2, 4, 19, 0, 0), DateTime.civil(2005, 2, 4, 19, 30, 10).beginning_of_hour
end
def test_end_of_hour
- assert_equal DateTime.civil(2005,2,4,19,59,59), DateTime.civil(2005,2,4,19,30,10).end_of_hour
+ assert_equal DateTime.civil(2005, 2, 4, 19, 59, 59), DateTime.civil(2005, 2, 4, 19, 30, 10).end_of_hour
end
def test_beginning_of_minute
- assert_equal DateTime.civil(2005,2,4,19,30,0), DateTime.civil(2005,2,4,19,30,10).beginning_of_minute
+ assert_equal DateTime.civil(2005, 2, 4, 19, 30, 0), DateTime.civil(2005, 2, 4, 19, 30, 10).beginning_of_minute
end
def test_end_of_minute
- assert_equal DateTime.civil(2005,2,4,19,30,59), DateTime.civil(2005,2,4,19,30,10).end_of_minute
+ assert_equal DateTime.civil(2005, 2, 4, 19, 30, 59), DateTime.civil(2005, 2, 4, 19, 30, 10).end_of_minute
end
def test_end_of_month
- assert_equal DateTime.civil(2005,3,31,23,59,59), DateTime.civil(2005,3,20,10,10,10).end_of_month
- assert_equal DateTime.civil(2005,2,28,23,59,59), DateTime.civil(2005,2,20,10,10,10).end_of_month
- assert_equal DateTime.civil(2005,4,30,23,59,59), DateTime.civil(2005,4,20,10,10,10).end_of_month
+ assert_equal DateTime.civil(2005, 3, 31, 23, 59, 59), DateTime.civil(2005, 3, 20, 10, 10, 10).end_of_month
+ assert_equal DateTime.civil(2005, 2, 28, 23, 59, 59), DateTime.civil(2005, 2, 20, 10, 10, 10).end_of_month
+ assert_equal DateTime.civil(2005, 4, 30, 23, 59, 59), DateTime.civil(2005, 4, 20, 10, 10, 10).end_of_month
end
def test_last_year
- assert_equal DateTime.civil(2004,6,5,10), DateTime.civil(2005,6,5,10,0,0).last_year
+ assert_equal DateTime.civil(2004, 6, 5, 10), DateTime.civil(2005, 6, 5, 10, 0, 0).last_year
end
def test_ago
- assert_equal DateTime.civil(2005,2,22,10,10,9), DateTime.civil(2005,2,22,10,10,10).ago(1)
- assert_equal DateTime.civil(2005,2,22,9,10,10), DateTime.civil(2005,2,22,10,10,10).ago(3600)
- assert_equal DateTime.civil(2005,2,20,10,10,10), DateTime.civil(2005,2,22,10,10,10).ago(86400*2)
- assert_equal DateTime.civil(2005,2,20,9,9,45), DateTime.civil(2005,2,22,10,10,10).ago(86400*2 + 3600 + 25)
+ assert_equal DateTime.civil(2005, 2, 22, 10, 10, 9), DateTime.civil(2005, 2, 22, 10, 10, 10).ago(1)
+ assert_equal DateTime.civil(2005, 2, 22, 9, 10, 10), DateTime.civil(2005, 2, 22, 10, 10, 10).ago(3600)
+ assert_equal DateTime.civil(2005, 2, 20, 10, 10, 10), DateTime.civil(2005, 2, 22, 10, 10, 10).ago(86400 * 2)
+ assert_equal DateTime.civil(2005, 2, 20, 9, 9, 45), DateTime.civil(2005, 2, 22, 10, 10, 10).ago(86400 * 2 + 3600 + 25)
end
def test_since
- assert_equal DateTime.civil(2005,2,22,10,10,11), DateTime.civil(2005,2,22,10,10,10).since(1)
- assert_equal DateTime.civil(2005,2,22,11,10,10), DateTime.civil(2005,2,22,10,10,10).since(3600)
- assert_equal DateTime.civil(2005,2,24,10,10,10), DateTime.civil(2005,2,22,10,10,10).since(86400*2)
- assert_equal DateTime.civil(2005,2,24,11,10,35), DateTime.civil(2005,2,22,10,10,10).since(86400*2 + 3600 + 25)
- assert_equal DateTime.civil(2005,2,22,10,10,11), DateTime.civil(2005,2,22,10,10,10).since(1.333)
- assert_equal DateTime.civil(2005,2,22,10,10,12), DateTime.civil(2005,2,22,10,10,10).since(1.667)
+ assert_equal DateTime.civil(2005, 2, 22, 10, 10, 11), DateTime.civil(2005, 2, 22, 10, 10, 10).since(1)
+ assert_equal DateTime.civil(2005, 2, 22, 11, 10, 10), DateTime.civil(2005, 2, 22, 10, 10, 10).since(3600)
+ assert_equal DateTime.civil(2005, 2, 24, 10, 10, 10), DateTime.civil(2005, 2, 22, 10, 10, 10).since(86400 * 2)
+ assert_equal DateTime.civil(2005, 2, 24, 11, 10, 35), DateTime.civil(2005, 2, 22, 10, 10, 10).since(86400 * 2 + 3600 + 25)
+ assert_equal DateTime.civil(2005, 2, 22, 10, 10, 11), DateTime.civil(2005, 2, 22, 10, 10, 10).since(1.333)
+ assert_equal DateTime.civil(2005, 2, 22, 10, 10, 12), DateTime.civil(2005, 2, 22, 10, 10, 10).since(1.667)
end
def test_change
- assert_equal DateTime.civil(2006,2,22,15,15,10), DateTime.civil(2005,2,22,15,15,10).change(year: 2006)
- assert_equal DateTime.civil(2005,6,22,15,15,10), DateTime.civil(2005,2,22,15,15,10).change(month: 6)
- assert_equal DateTime.civil(2012,9,22,15,15,10), DateTime.civil(2005,2,22,15,15,10).change(year: 2012, month: 9)
- assert_equal DateTime.civil(2005,2,22,16), DateTime.civil(2005,2,22,15,15,10).change(hour: 16)
- 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)
+ assert_equal DateTime.civil(2006, 2, 22, 15, 15, 10), DateTime.civil(2005, 2, 22, 15, 15, 10).change(year: 2006)
+ assert_equal DateTime.civil(2005, 6, 22, 15, 15, 10), DateTime.civil(2005, 2, 22, 15, 15, 10).change(month: 6)
+ assert_equal DateTime.civil(2012, 9, 22, 15, 15, 10), DateTime.civil(2005, 2, 22, 15, 15, 10).change(year: 2012, month: 9)
+ assert_equal DateTime.civil(2005, 2, 22, 16), DateTime.civil(2005, 2, 22, 15, 15, 10).change(hour: 16)
+ 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 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, 2, 1, 15, 15, 10.7), DateTime.civil(2005, 2, 22, 15, 15, 10.7).change(day: 1)
end
def test_advance
- assert_equal DateTime.civil(2006,2,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(years: 1)
- assert_equal DateTime.civil(2005,6,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(months: 4)
- assert_equal DateTime.civil(2005,3,21,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(weeks: 3)
- assert_equal DateTime.civil(2005,3,5,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(days: 5)
- assert_equal DateTime.civil(2012,9,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(years: 7, months: 7)
- assert_equal DateTime.civil(2013,10,3,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(years: 7, months: 19, days: 5)
- assert_equal DateTime.civil(2013,10,17,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(years: 7, months: 19, weeks: 2, days: 5)
- assert_equal DateTime.civil(2001,12,27,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(years: -3, months: -2, days: -1)
- assert_equal DateTime.civil(2005,2,28,15,15,10), DateTime.civil(2004,2,29,15,15,10).advance(years: 1) #leap day plus one year
- assert_equal DateTime.civil(2005,2,28,20,15,10), DateTime.civil(2005,2,28,15,15,10).advance(hours: 5)
- assert_equal DateTime.civil(2005,2,28,15,22,10), DateTime.civil(2005,2,28,15,15,10).advance(minutes: 7)
- assert_equal DateTime.civil(2005,2,28,15,15,19), DateTime.civil(2005,2,28,15,15,10).advance(seconds: 9)
- assert_equal DateTime.civil(2005,2,28,20,22,19), DateTime.civil(2005,2,28,15,15,10).advance(hours: 5, minutes: 7, seconds: 9)
- assert_equal DateTime.civil(2005,2,28,10,8,1), DateTime.civil(2005,2,28,15,15,10).advance(hours: -5, minutes: -7, seconds: -9)
- assert_equal DateTime.civil(2013,10,17,20,22,19), DateTime.civil(2005,2,28,15,15,10).advance(years: 7, months: 19, weeks: 2, days: 5, hours: 5, minutes: 7, seconds: 9)
+ assert_equal DateTime.civil(2006, 2, 28, 15, 15, 10), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(years: 1)
+ assert_equal DateTime.civil(2005, 6, 28, 15, 15, 10), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(months: 4)
+ assert_equal DateTime.civil(2005, 3, 21, 15, 15, 10), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(weeks: 3)
+ assert_equal DateTime.civil(2005, 3, 5, 15, 15, 10), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(days: 5)
+ assert_equal DateTime.civil(2012, 9, 28, 15, 15, 10), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(years: 7, months: 7)
+ assert_equal DateTime.civil(2013, 10, 3, 15, 15, 10), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(years: 7, months: 19, days: 5)
+ assert_equal DateTime.civil(2013, 10, 17, 15, 15, 10), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(years: 7, months: 19, weeks: 2, days: 5)
+ assert_equal DateTime.civil(2001, 12, 27, 15, 15, 10), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(years: -3, months: -2, days: -1)
+ assert_equal DateTime.civil(2005, 2, 28, 15, 15, 10), DateTime.civil(2004, 2, 29, 15, 15, 10).advance(years: 1) #leap day plus one year
+ assert_equal DateTime.civil(2005, 2, 28, 20, 15, 10), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(hours: 5)
+ assert_equal DateTime.civil(2005, 2, 28, 15, 22, 10), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(minutes: 7)
+ assert_equal DateTime.civil(2005, 2, 28, 15, 15, 19), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(seconds: 9)
+ assert_equal DateTime.civil(2005, 2, 28, 20, 22, 19), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(hours: 5, minutes: 7, seconds: 9)
+ assert_equal DateTime.civil(2005, 2, 28, 10, 8, 1), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(hours: -5, minutes: -7, seconds: -9)
+ assert_equal DateTime.civil(2013, 10, 17, 20, 22, 19), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(years: 7, months: 19, weeks: 2, days: 5, hours: 5, minutes: 7, seconds: 9)
end
def test_advance_partial_days
- assert_equal DateTime.civil(2012,9,29,13,15,10), DateTime.civil(2012,9,28,1,15,10).advance(days: 1.5)
- assert_equal DateTime.civil(2012,9,28,13,15,10), DateTime.civil(2012,9,28,1,15,10).advance(days: 0.5)
- assert_equal DateTime.civil(2012,10,29,13,15,10), DateTime.civil(2012,9,28,1,15,10).advance(days: 1.5, months: 1)
+ assert_equal DateTime.civil(2012, 9, 29, 13, 15, 10), DateTime.civil(2012, 9, 28, 1, 15, 10).advance(days: 1.5)
+ assert_equal DateTime.civil(2012, 9, 28, 13, 15, 10), DateTime.civil(2012, 9, 28, 1, 15, 10).advance(days: 0.5)
+ assert_equal DateTime.civil(2012, 10, 29, 13, 15, 10), DateTime.civil(2012, 9, 28, 1, 15, 10).advance(days: 1.5, months: 1)
end
def test_advanced_processes_first_the_date_deltas_and_then_the_time_deltas
@@ -203,11 +203,11 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_last_week
- assert_equal DateTime.civil(2005,2,21), DateTime.civil(2005,3,1,15,15,10).last_week
- assert_equal DateTime.civil(2005,2,22), DateTime.civil(2005,3,1,15,15,10).last_week(:tuesday)
- assert_equal DateTime.civil(2005,2,25), DateTime.civil(2005,3,1,15,15,10).last_week(:friday)
- assert_equal DateTime.civil(2006,10,30), DateTime.civil(2006,11,6,0,0,0).last_week
- assert_equal DateTime.civil(2006,11,15), DateTime.civil(2006,11,23,0,0,0).last_week(:wednesday)
+ assert_equal DateTime.civil(2005, 2, 21), DateTime.civil(2005, 3, 1, 15, 15, 10).last_week
+ assert_equal DateTime.civil(2005, 2, 22), DateTime.civil(2005, 3, 1, 15, 15, 10).last_week(:tuesday)
+ assert_equal DateTime.civil(2005, 2, 25), DateTime.civil(2005, 3, 1, 15, 15, 10).last_week(:friday)
+ assert_equal DateTime.civil(2006, 10, 30), DateTime.civil(2006, 11, 6, 0, 0, 0).last_week
+ assert_equal DateTime.civil(2006, 11, 15), DateTime.civil(2006, 11, 23, 0, 0, 0).last_week(:wednesday)
end
def test_date_time_should_have_correct_last_week_for_leap_year
@@ -233,51 +233,51 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
def test_today_with_offset
Date.stub(:current, Date.new(2000, 1, 1)) do
- assert_equal false, DateTime.civil(1999,12,31,23,59,59, Rational(-18000, 86400)).today?
- assert_equal true, DateTime.civil(2000,1,1,0,0,0, Rational(-18000, 86400)).today?
- assert_equal true, DateTime.civil(2000,1,1,23,59,59, Rational(-18000, 86400)).today?
- assert_equal false, DateTime.civil(2000,1,2,0,0,0, Rational(-18000, 86400)).today?
+ assert_equal false, DateTime.civil(1999, 12, 31, 23, 59, 59, Rational(-18000, 86400)).today?
+ assert_equal true, DateTime.civil(2000, 1, 1, 0, 0, 0, Rational(-18000, 86400)).today?
+ assert_equal true, DateTime.civil(2000, 1, 1, 23, 59, 59, Rational(-18000, 86400)).today?
+ assert_equal false, DateTime.civil(2000, 1, 2, 0, 0, 0, Rational(-18000, 86400)).today?
end
end
def test_today_without_offset
Date.stub(:current, Date.new(2000, 1, 1)) do
- assert_equal false, DateTime.civil(1999,12,31,23,59,59).today?
- assert_equal true, DateTime.civil(2000,1,1,0).today?
- assert_equal true, DateTime.civil(2000,1,1,23,59,59).today?
- assert_equal false, DateTime.civil(2000,1,2,0).today?
+ assert_equal false, DateTime.civil(1999, 12, 31, 23, 59, 59).today?
+ assert_equal true, DateTime.civil(2000, 1, 1, 0).today?
+ assert_equal true, DateTime.civil(2000, 1, 1, 23, 59, 59).today?
+ assert_equal false, DateTime.civil(2000, 1, 2, 0).today?
end
end
def test_past_with_offset
- DateTime.stub(:current, DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400))) do
- assert_equal true, DateTime.civil(2005,2,10,15,30,44, Rational(-18000, 86400)).past?
- assert_equal false, DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400)).past?
- assert_equal false, DateTime.civil(2005,2,10,15,30,46, Rational(-18000, 86400)).past?
+ DateTime.stub(:current, DateTime.civil(2005, 2, 10, 15, 30, 45, Rational(-18000, 86400))) do
+ assert_equal true, DateTime.civil(2005, 2, 10, 15, 30, 44, Rational(-18000, 86400)).past?
+ assert_equal false, DateTime.civil(2005, 2, 10, 15, 30, 45, Rational(-18000, 86400)).past?
+ assert_equal false, DateTime.civil(2005, 2, 10, 15, 30, 46, Rational(-18000, 86400)).past?
end
end
def test_past_without_offset
- DateTime.stub(:current, DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400))) do
- assert_equal true, DateTime.civil(2005,2,10,20,30,44).past?
- assert_equal false, DateTime.civil(2005,2,10,20,30,45).past?
- assert_equal false, DateTime.civil(2005,2,10,20,30,46).past?
+ DateTime.stub(:current, DateTime.civil(2005, 2, 10, 15, 30, 45, Rational(-18000, 86400))) do
+ assert_equal true, DateTime.civil(2005, 2, 10, 20, 30, 44).past?
+ assert_equal false, DateTime.civil(2005, 2, 10, 20, 30, 45).past?
+ assert_equal false, DateTime.civil(2005, 2, 10, 20, 30, 46).past?
end
end
def test_future_with_offset
- DateTime.stub(:current, DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400))) do
- assert_equal false, DateTime.civil(2005,2,10,15,30,44, Rational(-18000, 86400)).future?
- assert_equal false, DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400)).future?
- assert_equal true, DateTime.civil(2005,2,10,15,30,46, Rational(-18000, 86400)).future?
+ DateTime.stub(:current, DateTime.civil(2005, 2, 10, 15, 30, 45, Rational(-18000, 86400))) do
+ assert_equal false, DateTime.civil(2005, 2, 10, 15, 30, 44, Rational(-18000, 86400)).future?
+ assert_equal false, DateTime.civil(2005, 2, 10, 15, 30, 45, Rational(-18000, 86400)).future?
+ assert_equal true, DateTime.civil(2005, 2, 10, 15, 30, 46, Rational(-18000, 86400)).future?
end
end
def test_future_without_offset
- DateTime.stub(:current, DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400))) do
- assert_equal false, DateTime.civil(2005,2,10,20,30,44).future?
- assert_equal false, DateTime.civil(2005,2,10,20,30,45).future?
- assert_equal true, DateTime.civil(2005,2,10,20,30,46).future?
+ DateTime.stub(:current, DateTime.civil(2005, 2, 10, 15, 30, 45, Rational(-18000, 86400))) do
+ assert_equal false, DateTime.civil(2005, 2, 10, 20, 30, 44).future?
+ assert_equal false, DateTime.civil(2005, 2, 10, 20, 30, 45).future?
+ assert_equal true, DateTime.civil(2005, 2, 10, 20, 30, 46).future?
end
end
@@ -329,8 +329,8 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal 0, DateTime.civil(2005, 2, 21, 10, 11, 12).utc_offset
assert_equal 0, DateTime.civil(2005, 2, 21, 10, 11, 12, 0).utc_offset
assert_equal 21600, DateTime.civil(2005, 2, 21, 10, 11, 12, 0.25).utc_offset
- assert_equal( -21600, DateTime.civil(2005, 2, 21, 10, 11, 12, -0.25).utc_offset )
- assert_equal( -18000, DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-5, 24)).utc_offset )
+ assert_equal(-21600, DateTime.civil(2005, 2, 21, 10, 11, 12, -0.25).utc_offset)
+ assert_equal(-18000, DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-5, 24)).utc_offset)
end
def test_utc
@@ -367,16 +367,16 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_compare_with_time_with_zone
- assert_equal 1, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(1999, 12, 31, 23, 59, 59), ActiveSupport::TimeZone["UTC"] )
- assert_equal 0, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone["UTC"] )
- assert_equal(-1, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone["UTC"] ))
+ assert_equal 1, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new(Time.utc(1999, 12, 31, 23, 59, 59), ActiveSupport::TimeZone["UTC"])
+ assert_equal 0, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone["UTC"])
+ assert_equal(-1, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone["UTC"]))
end
def test_compare_with_string
assert_equal 1, DateTime.civil(2000) <=> Time.utc(1999, 12, 31, 23, 59, 59).to_s
assert_equal 0, DateTime.civil(2000) <=> Time.utc(2000, 1, 1, 0, 0, 0).to_s
- assert_equal( -1, DateTime.civil(2000) <=> Time.utc(2000, 1, 1, 0, 0, 1).to_s)
- assert_equal nil, DateTime.civil(2000) <=> "Invalid as Time"
+ assert_equal(-1, DateTime.civil(2000) <=> Time.utc(2000, 1, 1, 0, 0, 1).to_s)
+ assert_nil DateTime.civil(2000) <=> "Invalid as Time"
end
def test_compare_with_integer
@@ -399,27 +399,27 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
def test_to_f
assert_equal 946684800.0, DateTime.civil(2000).to_f
- assert_equal 946684800.0, DateTime.civil(1999,12,31,19,0,0,Rational(-5,24)).to_f
- assert_equal 946684800.5, DateTime.civil(1999,12,31,19,0,0.5,Rational(-5,24)).to_f
+ assert_equal 946684800.0, DateTime.civil(1999, 12, 31, 19, 0, 0, Rational(-5, 24)).to_f
+ assert_equal 946684800.5, DateTime.civil(1999, 12, 31, 19, 0, 0.5, Rational(-5, 24)).to_f
end
def test_to_i
assert_equal 946684800, DateTime.civil(2000).to_i
- assert_equal 946684800, DateTime.civil(1999,12,31,19,0,0,Rational(-5,24)).to_i
+ assert_equal 946684800, DateTime.civil(1999, 12, 31, 19, 0, 0, Rational(-5, 24)).to_i
end
def test_usec
assert_equal 0, DateTime.civil(2000).usec
- assert_equal 500000, DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)).usec
+ assert_equal 500000, DateTime.civil(2000, 1, 1, 0, 0, Rational(1, 2)).usec
end
def test_nsec
assert_equal 0, DateTime.civil(2000).nsec
- assert_equal 500000000, DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)).nsec
+ assert_equal 500000000, DateTime.civil(2000, 1, 1, 0, 0, Rational(1, 2)).nsec
end
def test_subsec
assert_equal 0, DateTime.civil(2000).subsec
- assert_equal Rational(1,2), DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)).subsec
+ assert_equal Rational(1, 2), DateTime.civil(2000, 1, 1, 0, 0, Rational(1, 2)).subsec
end
end
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index 64b5bca151..fc0dd41d0e 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -21,7 +21,7 @@ class DurationTest < ActiveSupport::TestCase
end
def test_instance_of
- assert 1.minute.instance_of?(Fixnum)
+ assert 1.minute.instance_of?(1.class)
assert 2.days.instance_of?(ActiveSupport::Duration)
assert !3.second.instance_of?(Numeric)
end
@@ -75,7 +75,7 @@ class DurationTest < ActiveSupport::TestCase
current_locale = I18n.default_locale
I18n.default_locale = :de
I18n.backend.store_translations(:de, support: { array: { last_word_connector: " und " } })
- assert_equal "10 years, 1 month und 1 day", (10.years + 1.month + 1.day).inspect
+ assert_equal "10 years, 1 month und 1 day", (10.years + 1.month + 1.day).inspect
ensure
I18n.default_locale = current_locale
end
@@ -89,7 +89,7 @@ class DurationTest < ActiveSupport::TestCase
end
def test_time_plus_duration_returns_same_time_datatype
- twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Moscow"] , Time.utc(2016,4,28,00,45))
+ twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Moscow"] , Time.utc(2016, 4, 28, 00, 45))
now = Time.now.utc
%w( second minute hour day week month year ).each do |unit|
assert_equal((now + 1.send(unit)).class, Time, "Time + 1.#{unit} must be Time")
@@ -153,10 +153,10 @@ class DurationTest < ActiveSupport::TestCase
Time.stub(:now, Time.local(2000)) do
# since
assert_not_instance_of ActiveSupport::TimeWithZone, 5.seconds.since
- assert_equal Time.local(2000,1,1,0,0,5), 5.seconds.since
+ assert_equal Time.local(2000, 1, 1, 0, 0, 5), 5.seconds.since
# ago
assert_not_instance_of ActiveSupport::TimeWithZone, 5.seconds.ago
- assert_equal Time.local(1999,12,31,23,59,55), 5.seconds.ago
+ assert_equal Time.local(1999, 12, 31, 23, 59, 55), 5.seconds.ago
end
end
end
@@ -167,11 +167,11 @@ class DurationTest < ActiveSupport::TestCase
Time.stub(:now, Time.local(2000)) do
# since
assert_instance_of ActiveSupport::TimeWithZone, 5.seconds.since
- assert_equal Time.utc(2000,1,1,0,0,5), 5.seconds.since.time
+ assert_equal Time.utc(2000, 1, 1, 0, 0, 5), 5.seconds.since.time
assert_equal "Eastern Time (US & Canada)", 5.seconds.since.time_zone.name
# ago
assert_instance_of ActiveSupport::TimeWithZone, 5.seconds.ago
- assert_equal Time.utc(1999,12,31,23,59,55), 5.seconds.ago.time
+ assert_equal Time.utc(1999, 12, 31, 23, 59, 55), 5.seconds.ago.time
assert_equal "Eastern Time (US & Canada)", 5.seconds.ago.time_zone.name
end
end
@@ -181,22 +181,22 @@ class DurationTest < ActiveSupport::TestCase
def test_adding_hours_across_dst_boundary
with_env_tz "CET" do
- assert_equal Time.local(2009,3,29,0,0,0) + 24.hours, Time.local(2009,3,30,1,0,0)
+ assert_equal Time.local(2009, 3, 29, 0, 0, 0) + 24.hours, Time.local(2009, 3, 30, 1, 0, 0)
end
end
def test_adding_day_across_dst_boundary
with_env_tz "CET" do
- assert_equal Time.local(2009,3,29,0,0,0) + 1.day, Time.local(2009,3,30,0,0,0)
+ assert_equal Time.local(2009, 3, 29, 0, 0, 0) + 1.day, Time.local(2009, 3, 30, 0, 0, 0)
end
end
def test_delegation_with_block_works
counter = 0
assert_nothing_raised do
- 1.minute.times {counter += 1}
+ 1.minute.times { counter += 1 }
end
- assert_equal counter, 60
+ assert_equal 60, counter
end
def test_as_json
@@ -208,8 +208,12 @@ class DurationTest < ActiveSupport::TestCase
end
def test_case_when
- cased = case 1.day when 1.day then "ok" end
- assert_equal cased, "ok"
+ cased = \
+ case 1.day
+ when 1.day
+ "ok"
+ end
+ assert_equal "ok", cased
end
def test_respond_to
@@ -288,11 +292,11 @@ class DurationTest < ActiveSupport::TestCase
def test_iso8601_output_precision
expectations = [
- [nil, "P1Y1MT5.55S", 1.year + 1.month + (5.55).seconds ],
- [0, "P1Y1MT6S", 1.year + 1.month + (5.55).seconds ],
- [1, "P1Y1MT5.5S", 1.year + 1.month + (5.55).seconds ],
- [2, "P1Y1MT5.55S", 1.year + 1.month + (5.55).seconds ],
- [3, "P1Y1MT5.550S", 1.year + 1.month + (5.55).seconds ],
+ [nil, "P1Y1MT8.55S", 1.year + 1.month + (8.55).seconds ],
+ [0, "P1Y1MT9S", 1.year + 1.month + (8.55).seconds ],
+ [1, "P1Y1MT8.6S", 1.year + 1.month + (8.55).seconds ],
+ [2, "P1Y1MT8.55S", 1.year + 1.month + (8.55).seconds ],
+ [3, "P1Y1MT8.550S", 1.year + 1.month + (8.55).seconds ],
[nil, "PT1S", 1.second ],
[2, "PT1.00S", 1.second ],
[nil, "PT1.4S", (1.4).seconds ],
@@ -315,7 +319,49 @@ class DurationTest < ActiveSupport::TestCase
time = Time.current
patterns.each do |pattern|
duration = ActiveSupport::Duration.parse(pattern)
- assert_equal time+duration, time+ActiveSupport::Duration.parse(duration.iso8601), pattern.inspect
+ assert_equal time + duration, time + ActiveSupport::Duration.parse(duration.iso8601), pattern.inspect
+ end
+ end
+
+ def test_iso8601_parsing_across_spring_dst_boundary
+ with_env_tz eastern_time_zone do
+ with_tz_default "Eastern Time (US & Canada)" do
+ travel_to Time.utc(2016, 3, 11) do
+ assert_equal 604800, ActiveSupport::Duration.parse("P7D").to_i
+ assert_equal 604800, ActiveSupport::Duration.parse("P1W").to_i
+ end
+ end
end
end
+
+ def test_iso8601_parsing_across_autumn_dst_boundary
+ with_env_tz eastern_time_zone do
+ with_tz_default "Eastern Time (US & Canada)" do
+ travel_to Time.utc(2016, 11, 4) do
+ assert_equal 604800, ActiveSupport::Duration.parse("P7D").to_i
+ assert_equal 604800, ActiveSupport::Duration.parse("P1W").to_i
+ end
+ end
+ end
+ end
+
+ def test_adding_durations_do_not_hold_prior_states
+ time = Time.parse("Nov 29, 2016")
+ # If the implementation adds and subtracts 3 months, the
+ # resulting date would have been in February so the day will
+ # change to the 29th.
+ d1 = 3.months - 3.months
+ d2 = 2.months - 2.months
+
+ assert_equal time + d1, time + d2
+ end
+
+ private
+ def eastern_time_zone
+ if Gem.win_platform?
+ "EST5EDT"
+ else
+ "America/New_York"
+ end
+ end
end
diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb
index 3d63a5fbb6..4f1ab993b8 100644
--- a/activesupport/test/core_ext/enumerable_test.rb
+++ b/activesupport/test/core_ext/enumerable_test.rb
@@ -22,7 +22,7 @@ class EnumerableTests < ActiveSupport::TestCase
end
end
- def assert_typed_equal(e, v, cls, msg=nil)
+ def assert_typed_equal(e, v, cls, msg = nil)
assert_kind_of(cls, v, msg)
assert_equal(e, v, msg)
end
@@ -70,12 +70,12 @@ class EnumerableTests < ActiveSupport::TestCase
sum = GenericEnumerable.new([1.quo(2), 1.quo(3)]).sum
assert_typed_equal(5.quo(6), sum, Rational)
- sum = GenericEnumerable.new([2.0, 3.0*Complex::I]).sum
+ sum = GenericEnumerable.new([2.0, 3.0 * Complex::I]).sum
assert_typed_equal(Complex(2.0, 3.0), sum, Complex)
assert_typed_equal(2.0, sum.real, Float)
assert_typed_equal(3.0, sum.imag, Float)
- sum = GenericEnumerable.new([1, 2]).sum(10) {|v| v * 2 }
+ sum = GenericEnumerable.new([1, 2]).sum(10) { |v| v * 2 }
assert_typed_equal(16, sum, Integer)
end
@@ -157,12 +157,12 @@ class EnumerableTests < ActiveSupport::TestCase
sum = [1.quo(2), 1.quo(3)].sum
assert_typed_equal(5.quo(6), sum, Rational)
- sum = [2.0, 3.0*Complex::I].sum
+ sum = [2.0, 3.0 * Complex::I].sum
assert_typed_equal(Complex(2.0, 3.0), sum, Complex)
assert_typed_equal(2.0, sum.real, Float)
assert_typed_equal(3.0, sum.imag, Float)
- sum = [1, 2].sum(10) {|v| v * 2 }
+ sum = [1, 2].sum(10) { |v| v * 2 }
assert_typed_equal(16, sum, Integer)
end
@@ -172,7 +172,7 @@ class EnumerableTests < ActiveSupport::TestCase
payments.index_by(&:price))
assert_equal Enumerator, payments.index_by.class
if Enumerator.method_defined? :size
- assert_equal nil, payments.index_by.size
+ assert_nil payments.index_by.size
assert_equal 42, (1..42).index_by.size
end
assert_equal({ 5 => Payment.new(5), 15 => Payment.new(15), 10 => Payment.new(10) },
@@ -180,21 +180,21 @@ class EnumerableTests < ActiveSupport::TestCase
end
def test_many
- assert_equal false, GenericEnumerable.new([] ).many?
- assert_equal false, GenericEnumerable.new([ 1 ] ).many?
- assert_equal true, GenericEnumerable.new([ 1, 2 ] ).many?
-
- assert_equal false, GenericEnumerable.new([] ).many? {|x| x > 1 }
- assert_equal false, GenericEnumerable.new([ 2 ] ).many? {|x| x > 1 }
- assert_equal false, GenericEnumerable.new([ 1, 2 ] ).many? {|x| x > 1 }
- assert_equal true, GenericEnumerable.new([ 1, 2, 2 ]).many? {|x| x > 1 }
+ assert_equal false, GenericEnumerable.new([]).many?
+ assert_equal false, GenericEnumerable.new([ 1 ]).many?
+ assert_equal true, GenericEnumerable.new([ 1, 2 ]).many?
+
+ assert_equal false, GenericEnumerable.new([]).many? { |x| x > 1 }
+ assert_equal false, GenericEnumerable.new([ 2 ]).many? { |x| x > 1 }
+ assert_equal false, GenericEnumerable.new([ 1, 2 ]).many? { |x| x > 1 }
+ assert_equal true, GenericEnumerable.new([ 1, 2, 2 ]).many? { |x| x > 1 }
end
def test_many_iterates_only_on_what_is_needed
- infinity = 1.0/0.0
+ infinity = 1.0 / 0.0
very_long_enum = 0..infinity
assert_equal true, very_long_enum.many?
- assert_equal true, very_long_enum.many?{|x| x > 100}
+ assert_equal true, very_long_enum.many? { |x| x > 100 }
end
def test_exclude?
@@ -206,7 +206,7 @@ class EnumerableTests < ActiveSupport::TestCase
assert_equal [1, 2, 4], GenericEnumerable.new((1..5).to_a).without(3, 5)
assert_equal [1, 2, 4], (1..5).to_a.without(3, 5)
assert_equal [1, 2, 4], (1..5).to_set.without(3, 5)
- assert_equal({foo: 1, baz: 3}, {foo: 1, bar: 2, baz: 3}.without(:bar))
+ assert_equal({ foo: 1, baz: 3 }, { foo: 1, bar: 2, baz: 3 }.without(:bar))
end
def test_pluck
diff --git a/activesupport/test/core_ext/hash/transform_keys_test.rb b/activesupport/test/core_ext/hash/transform_keys_test.rb
index ea34a47b3d..7a11d827f8 100644
--- a/activesupport/test/core_ext/hash/transform_keys_test.rb
+++ b/activesupport/test/core_ext/hash/transform_keys_test.rb
@@ -48,7 +48,7 @@ class TransformKeysTest < ActiveSupport::TestCase
class HashDescendant < ::Hash
def initialize(elements = nil)
super(elements)
- (elements || {}).each_pair{ |key, value| self[key] = value }
+ (elements || {}).each_pair { |key, value| self[key] = value }
end
end
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 6a65f999fd..bea0a84b53 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -41,9 +41,9 @@ class HashExtTest < ActiveSupport::TestCase
@mixed = { :a => 1, "b" => 2 }
@nested_mixed = { "a" => { b: { "c" => 3 } } }
@integers = { 0 => 1, 1 => 2 }
- @nested_integers = { 0 => { 1 => { 2 => 3} } }
+ @nested_integers = { 0 => { 1 => { 2 => 3 } } }
@illegal_symbols = { [] => 3 }
- @nested_illegal_symbols = { [] => { [] => 3} }
+ @nested_illegal_symbols = { [] => { [] => 3 } }
@upcase_strings = { "A" => 1, "B" => 2 }
@nested_upcase_strings = { "A" => { "B" => { "C" => 3 } } }
@string_array_of_hashes = { "a" => [ { "b" => 2 }, { "c" => 3 }, 4 ] }
@@ -75,59 +75,59 @@ class HashExtTest < ActiveSupport::TestCase
end
def test_transform_keys
- assert_equal @upcase_strings, @strings.transform_keys{ |key| key.to_s.upcase }
- assert_equal @upcase_strings, @symbols.transform_keys{ |key| key.to_s.upcase }
- assert_equal @upcase_strings, @mixed.transform_keys{ |key| key.to_s.upcase }
+ assert_equal @upcase_strings, @strings.transform_keys { |key| key.to_s.upcase }
+ assert_equal @upcase_strings, @symbols.transform_keys { |key| key.to_s.upcase }
+ assert_equal @upcase_strings, @mixed.transform_keys { |key| key.to_s.upcase }
end
def test_transform_keys_not_mutates
transformed_hash = @mixed.dup
- transformed_hash.transform_keys{ |key| key.to_s.upcase }
+ transformed_hash.transform_keys { |key| key.to_s.upcase }
assert_equal @mixed, transformed_hash
end
def test_deep_transform_keys
- assert_equal @nested_upcase_strings, @nested_symbols.deep_transform_keys{ |key| key.to_s.upcase }
- assert_equal @nested_upcase_strings, @nested_strings.deep_transform_keys{ |key| key.to_s.upcase }
- assert_equal @nested_upcase_strings, @nested_mixed.deep_transform_keys{ |key| key.to_s.upcase }
- assert_equal @upcase_array_of_hashes, @string_array_of_hashes.deep_transform_keys{ |key| key.to_s.upcase }
- assert_equal @upcase_array_of_hashes, @symbol_array_of_hashes.deep_transform_keys{ |key| key.to_s.upcase }
- assert_equal @upcase_array_of_hashes, @mixed_array_of_hashes.deep_transform_keys{ |key| key.to_s.upcase }
+ assert_equal @nested_upcase_strings, @nested_symbols.deep_transform_keys { |key| key.to_s.upcase }
+ assert_equal @nested_upcase_strings, @nested_strings.deep_transform_keys { |key| key.to_s.upcase }
+ assert_equal @nested_upcase_strings, @nested_mixed.deep_transform_keys { |key| key.to_s.upcase }
+ assert_equal @upcase_array_of_hashes, @string_array_of_hashes.deep_transform_keys { |key| key.to_s.upcase }
+ assert_equal @upcase_array_of_hashes, @symbol_array_of_hashes.deep_transform_keys { |key| key.to_s.upcase }
+ assert_equal @upcase_array_of_hashes, @mixed_array_of_hashes.deep_transform_keys { |key| key.to_s.upcase }
end
def test_deep_transform_keys_not_mutates
transformed_hash = @nested_mixed.deep_dup
- transformed_hash.deep_transform_keys{ |key| key.to_s.upcase }
+ transformed_hash.deep_transform_keys { |key| key.to_s.upcase }
assert_equal @nested_mixed, transformed_hash
end
def test_transform_keys!
- assert_equal @upcase_strings, @symbols.dup.transform_keys!{ |key| key.to_s.upcase }
- assert_equal @upcase_strings, @strings.dup.transform_keys!{ |key| key.to_s.upcase }
- assert_equal @upcase_strings, @mixed.dup.transform_keys!{ |key| key.to_s.upcase }
+ assert_equal @upcase_strings, @symbols.dup.transform_keys! { |key| key.to_s.upcase }
+ assert_equal @upcase_strings, @strings.dup.transform_keys! { |key| key.to_s.upcase }
+ assert_equal @upcase_strings, @mixed.dup.transform_keys! { |key| key.to_s.upcase }
end
def test_transform_keys_with_bang_mutates
transformed_hash = @mixed.dup
- transformed_hash.transform_keys!{ |key| key.to_s.upcase }
+ transformed_hash.transform_keys! { |key| key.to_s.upcase }
assert_equal @upcase_strings, transformed_hash
- assert_equal @mixed, :a => 1, "b" => 2
+ assert_equal({ :a => 1, "b" => 2 }, @mixed)
end
def test_deep_transform_keys!
- assert_equal @nested_upcase_strings, @nested_symbols.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
- assert_equal @nested_upcase_strings, @nested_strings.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
- assert_equal @nested_upcase_strings, @nested_mixed.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
- assert_equal @upcase_array_of_hashes, @string_array_of_hashes.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
- assert_equal @upcase_array_of_hashes, @symbol_array_of_hashes.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
- assert_equal @upcase_array_of_hashes, @mixed_array_of_hashes.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
+ assert_equal @nested_upcase_strings, @nested_symbols.deep_dup.deep_transform_keys! { |key| key.to_s.upcase }
+ assert_equal @nested_upcase_strings, @nested_strings.deep_dup.deep_transform_keys! { |key| key.to_s.upcase }
+ assert_equal @nested_upcase_strings, @nested_mixed.deep_dup.deep_transform_keys! { |key| key.to_s.upcase }
+ assert_equal @upcase_array_of_hashes, @string_array_of_hashes.deep_dup.deep_transform_keys! { |key| key.to_s.upcase }
+ assert_equal @upcase_array_of_hashes, @symbol_array_of_hashes.deep_dup.deep_transform_keys! { |key| key.to_s.upcase }
+ assert_equal @upcase_array_of_hashes, @mixed_array_of_hashes.deep_dup.deep_transform_keys! { |key| key.to_s.upcase }
end
def test_deep_transform_keys_with_bang_mutates
transformed_hash = @nested_mixed.deep_dup
- transformed_hash.deep_transform_keys!{ |key| key.to_s.upcase }
+ transformed_hash.deep_transform_keys! { |key| key.to_s.upcase }
assert_equal @nested_upcase_strings, transformed_hash
- assert_equal @nested_mixed, "a" => { b: { "c" => 3 } }
+ assert_equal({ "a" => { b: { "c" => 3 } } }, @nested_mixed)
end
def test_symbolize_keys
@@ -167,7 +167,7 @@ class HashExtTest < ActiveSupport::TestCase
transformed_hash = @mixed.dup
transformed_hash.deep_symbolize_keys!
assert_equal @symbols, transformed_hash
- assert_equal @mixed, :a => 1, "b" => 2
+ assert_equal({ :a => 1, "b" => 2 }, @mixed)
end
def test_deep_symbolize_keys!
@@ -183,7 +183,7 @@ class HashExtTest < ActiveSupport::TestCase
transformed_hash = @nested_mixed.deep_dup
transformed_hash.deep_symbolize_keys!
assert_equal @nested_symbols, transformed_hash
- assert_equal @nested_mixed, "a" => { b: { "c" => 3 } }
+ assert_equal({ "a" => { b: { "c" => 3 } } }, @nested_mixed)
end
def test_symbolize_keys_preserves_keys_that_cant_be_symbolized
@@ -243,7 +243,7 @@ class HashExtTest < ActiveSupport::TestCase
transformed_hash = @mixed.dup
transformed_hash.stringify_keys!
assert_equal @strings, transformed_hash
- assert_equal @mixed, :a => 1, "b" => 2
+ assert_equal({ :a => 1, "b" => 2 }, @mixed)
end
def test_deep_stringify_keys!
@@ -259,7 +259,7 @@ class HashExtTest < ActiveSupport::TestCase
transformed_hash = @nested_mixed.deep_dup
transformed_hash.deep_stringify_keys!
assert_equal @nested_strings, transformed_hash
- assert_equal @nested_mixed, "a" => { b: { "c" => 3 } }
+ assert_equal({ "a" => { b: { "c" => 3 } } }, @nested_mixed)
end
def test_symbolize_keys_for_hash_with_indifferent_access
@@ -390,8 +390,8 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal 1, hash[:a]
assert_equal true, hash[:b]
assert_equal false, hash[:c]
- assert_equal nil, hash[:d]
- assert_equal nil, hash[:e]
+ assert_nil hash[:d]
+ assert_nil hash[:e]
end
def test_indifferent_reading_with_nonnil_default
@@ -404,7 +404,7 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal 1, hash[:a]
assert_equal true, hash[:b]
assert_equal false, hash[:c]
- assert_equal nil, hash[:d]
+ assert_nil hash[:d]
assert_equal 1, hash[:e]
end
@@ -414,11 +414,11 @@ class HashExtTest < ActiveSupport::TestCase
hash["b"] = 2
hash[3] = 3
- assert_equal hash["a"], 1
- assert_equal hash["b"], 2
- assert_equal hash[:a], 1
- assert_equal hash[:b], 2
- assert_equal hash[3], 3
+ assert_equal 1, hash["a"]
+ assert_equal 2, hash["b"]
+ assert_equal 1, hash[:a]
+ assert_equal 2, hash[:b]
+ assert_equal 3, hash[3]
end
def test_indifferent_update
@@ -430,16 +430,16 @@ class HashExtTest < ActiveSupport::TestCase
updated_with_symbols = hash.update(@symbols)
updated_with_mixed = hash.update(@mixed)
- assert_equal updated_with_strings[:a], 1
- assert_equal updated_with_strings["a"], 1
- assert_equal updated_with_strings["b"], 2
+ assert_equal 1, updated_with_strings[:a]
+ assert_equal 1, updated_with_strings["a"]
+ assert_equal 2, updated_with_strings["b"]
- assert_equal updated_with_symbols[:a], 1
- assert_equal updated_with_symbols["b"], 2
- assert_equal updated_with_symbols[:b], 2
+ assert_equal 1, updated_with_symbols[:a]
+ assert_equal 2, updated_with_symbols["b"]
+ assert_equal 2, updated_with_symbols[:b]
- assert_equal updated_with_mixed[:a], 1
- assert_equal updated_with_mixed["b"], 2
+ assert_equal 1, updated_with_mixed[:a]
+ assert_equal 2, updated_with_mixed["b"]
assert [updated_with_strings, updated_with_symbols, updated_with_mixed].all? { |h| h.keys.size == 2 }
end
@@ -447,7 +447,7 @@ class HashExtTest < ActiveSupport::TestCase
def test_update_with_to_hash_conversion
hash = HashWithIndifferentAccess.new
hash.update HashByConversion.new(a: 1)
- assert_equal hash["a"], 1
+ assert_equal 1, hash["a"]
end
def test_indifferent_merging
@@ -472,7 +472,7 @@ class HashExtTest < ActiveSupport::TestCase
def test_merge_with_to_hash_conversion
hash = HashWithIndifferentAccess.new
merged = hash.merge HashByConversion.new(a: 1)
- assert_equal merged["a"], 1
+ assert_equal 1, merged["a"]
end
def test_indifferent_replace
@@ -534,17 +534,17 @@ class HashExtTest < ActiveSupport::TestCase
end
def test_indifferent_deleting
- get_hash = proc{ { a: "foo" }.with_indifferent_access }
+ get_hash = proc { { a: "foo" }.with_indifferent_access }
hash = get_hash.call
- assert_equal hash.delete(:a), "foo"
- assert_equal hash.delete(:a), nil
+ assert_equal "foo", hash.delete(:a)
+ assert_nil hash.delete(:a)
hash = get_hash.call
- assert_equal hash.delete("a"), "foo"
- assert_equal hash.delete("a"), nil
+ assert_equal "foo", hash.delete("a")
+ assert_nil hash.delete("a")
end
def test_indifferent_select
- hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select {|k,v| v == 1}
+ hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select { |k, v| v == 1 }
assert_equal({ "a" => 1 }, hash)
assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
@@ -556,21 +556,21 @@ class HashExtTest < ActiveSupport::TestCase
end
def test_indifferent_select_returns_a_hash_when_unchanged
- hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select {|k,v| true}
+ hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select { |k, v| true }
assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
end
def test_indifferent_select_bang
indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
- indifferent_strings.select! {|k,v| v == 1}
+ indifferent_strings.select! { |k, v| v == 1 }
assert_equal({ "a" => 1 }, indifferent_strings)
assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
end
def test_indifferent_reject
- hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject {|k,v| v != 1}
+ hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject { |k, v| v != 1 }
assert_equal({ "a" => 1 }, hash)
assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
@@ -583,7 +583,7 @@ class HashExtTest < ActiveSupport::TestCase
def test_indifferent_reject_bang
indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
- indifferent_strings.reject! {|k,v| v != 1}
+ indifferent_strings.reject! { |k, v| v != 1 }
assert_equal({ "a" => 1 }, indifferent_strings)
assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
@@ -608,21 +608,21 @@ class HashExtTest < ActiveSupport::TestCase
end
def test_lookup_returns_the_same_object_that_is_stored_in_hash_indifferent_access
- hash = HashWithIndifferentAccess.new {|h, k| h[k] = []}
+ hash = HashWithIndifferentAccess.new { |h, k| h[k] = [] }
hash[:a] << 1
assert_equal [1], hash[:a]
end
def test_with_indifferent_access_has_no_side_effects_on_existing_hash
- hash = {content: [{:foo => :bar, "bar" => "baz"}]}
+ hash = { content: [{ :foo => :bar, "bar" => "baz" }] }
hash.with_indifferent_access
assert_equal [:foo, "bar"], hash[:content].first.keys
end
def test_indifferent_hash_with_array_of_hashes
- hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] }}.with_indifferent_access
+ hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] } }.with_indifferent_access
assert_equal "1", hash[:urls][:url].first[:address]
hash = hash.to_hash
@@ -634,14 +634,14 @@ class HashExtTest < ActiveSupport::TestCase
def test_should_preserve_array_subclass_when_value_is_array
array = SubclassingArray.new
array << { "address" => "1" }
- hash = { "urls" => { "url" => array }}.with_indifferent_access
+ hash = { "urls" => { "url" => array } }.with_indifferent_access
assert_equal SubclassingArray, hash[:urls][:url].class
end
def test_should_preserve_array_class_when_hash_value_is_frozen_array
array = SubclassingArray.new
array << { "address" => "1" }
- hash = { "urls" => { "url" => array.freeze }}.with_indifferent_access
+ hash = { "urls" => { "url" => array.freeze } }.with_indifferent_access
assert_equal SubclassingArray, hash[:urls][:url].class
end
@@ -683,11 +683,11 @@ class HashExtTest < ActiveSupport::TestCase
end
def test_indifferent_sub_hashes
- h = {"user" => {"id" => 5}}.with_indifferent_access
- ["user", :user].each {|user| [:id, "id"].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}}
+ h = { "user" => { "id" => 5 } }.with_indifferent_access
+ ["user", :user].each { |user| [:id, "id"].each { |id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5" } }
- h = {user: {id: 5}}.with_indifferent_access
- ["user", :user].each {|user| [:id, "id"].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}}
+ h = { user: { id: 5 } }.with_indifferent_access
+ ["user", :user].each { |user| [:id, "id"].each { |id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5" } }
end
def test_indifferent_duplication
@@ -703,7 +703,7 @@ class HashExtTest < ActiveSupport::TestCase
def test_nested_dig_indifferent_access
skip if RUBY_VERSION < "2.3.0"
- data = {"this" => {"views" => 1234}}.with_indifferent_access
+ data = { "this" => { "views" => 1234 } }.with_indifferent_access
assert_equal 1234, data.dig(:this, :views)
end
@@ -740,9 +740,9 @@ class HashExtTest < ActiveSupport::TestCase
end
def test_assorted_keys_not_stringified
- original = {Object.new => 2, 1 => 2, [] => true}
+ original = { Object.new => 2, 1 => 2, [] => true }
indiff = original.with_indifferent_access
- assert(!indiff.keys.any? {|k| k.kind_of? String}, "A key was converted to a string!")
+ assert(!indiff.keys.any? { |k| k.kind_of? String }, "A key was converted to a string!")
end
def test_deep_merge
@@ -759,9 +759,9 @@ class HashExtTest < ActiveSupport::TestCase
hash_1 = { a: "a", b: "b", c: { c1: "c1", c2: "c2", c3: { d1: "d1" } } }
hash_2 = { a: 1, c: { c1: 2, c3: { d2: "d2" } } }
expected = { a: [:a, "a", 1], b: "b", c: { c1: [:c1, "c1", 2], c2: "c2", c3: { d1: "d1", d2: "d2" } } }
- assert_equal(expected, hash_1.deep_merge(hash_2) { |k,o,n| [k, o, n] })
+ assert_equal(expected, hash_1.deep_merge(hash_2) { |k, o, n| [k, o, n] })
- hash_1.deep_merge!(hash_2) { |k,o,n| [k, o, n] }
+ hash_1.deep_merge!(hash_2) { |k, o, n| [k, o, n] }
assert_equal expected, hash_1
end
@@ -891,7 +891,7 @@ class HashExtTest < ActiveSupport::TestCase
end
def test_indifferent_slice_access_with_symbols
- original = {"login" => "bender", "password" => "shiny", "stuff" => "foo"}
+ original = { "login" => "bender", "password" => "shiny", "stuff" => "foo" }
original = original.with_indifferent_access
slice = original.slice(:login, :password)
@@ -919,28 +919,28 @@ class HashExtTest < ActiveSupport::TestCase
end
def test_extract
- original = {a: 1, b: 2, c: 3, d: 4}
- expected = {a: 1, b: 2}
- remaining = {c: 3, d: 4}
+ original = { a: 1, b: 2, c: 3, d: 4 }
+ expected = { a: 1, b: 2 }
+ remaining = { c: 3, d: 4 }
assert_equal expected, original.extract!(:a, :b, :x)
assert_equal remaining, original
end
def test_extract_nils
- original = {a: nil, b: nil}
- expected = {a: nil}
+ original = { a: nil, b: nil }
+ expected = { a: nil }
extracted = original.extract!(:a, :x)
assert_equal expected, extracted
- assert_equal nil, extracted[:a]
- assert_equal nil, extracted[:x]
+ assert_nil extracted[:a]
+ assert_nil extracted[:x]
end
def test_indifferent_extract
- original = {:a => 1, "b" => 2, :c => 3, "d" => 4}.with_indifferent_access
- expected = {a: 1, b: 2}.with_indifferent_access
- remaining = {c: 3, d: 4}.with_indifferent_access
+ original = { :a => 1, "b" => 2, :c => 3, "d" => 4 }.with_indifferent_access
+ expected = { a: 1, b: 2 }.with_indifferent_access
+ remaining = { c: 3, d: 4 }.with_indifferent_access
[["a", "b"], [:a, :b]].each do |keys|
copy = original.dup
@@ -1017,7 +1017,7 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal({}, h)
h = @symbols.dup
- assert_equal(nil, h.compact!)
+ assert_nil(h.compact!)
assert_equal(@symbols, h)
end
@@ -1051,13 +1051,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_nothing_raised { hash.to_hash }
end
- def test_new_from_hash_copying_default_should_not_raise_when_default_proc_does
- hash = Hash.new
- hash.default_proc = proc { |h, k| raise "walrus" }
-
- assert_deprecated { HashWithIndifferentAccess.new_from_hash_copying_default(hash) }
- end
-
def test_new_with_to_hash_conversion_copies_default
normal_hash = Hash.new(3)
normal_hash[:a] = 1
@@ -1099,15 +1092,15 @@ class HashExtToParamTests < ActiveSupport::TestCase
assert_equal "", {}.to_param
assert_equal "hello=world", { hello: "world" }.to_param
assert_equal "hello=10", { "hello" => 10 }.to_param
- assert_equal "hello=world&say_bye=true", {:hello => "world", "say_bye" => true}.to_param
+ assert_equal "hello=world&say_bye=true", { :hello => "world", "say_bye" => true }.to_param
end
def test_number_hash
- assert_equal "10=20&30=40&50=60", {10 => 20, 30 => 40, 50 => 60}.to_param
+ assert_equal "10=20&30=40&50=60", { 10 => 20, 30 => 40, 50 => 60 }.to_param
end
def test_to_param_hash
- assert_equal "custom-1=param-1&custom2-1=param2-1", {ToParam.new("custom") => ToParam.new("param"), ToParam.new("custom2") => ToParam.new("param2")}.to_param
+ assert_equal "custom-1=param-1&custom2-1=param2-1", { ToParam.new("custom") => ToParam.new("param"), ToParam.new("custom2") => ToParam.new("param2") }.to_param
end
def test_to_param_hash_escapes_its_keys_and_values
@@ -1127,63 +1120,63 @@ class HashToXmlTest < ActiveSupport::TestCase
def test_one_level
xml = { name: "David", street: "Paulina" }.to_xml(@xml_options)
assert_equal "<person>", xml.first(8)
- assert xml.include?(%(<street>Paulina</street>))
- assert xml.include?(%(<name>David</name>))
+ assert_includes xml, %(<street>Paulina</street>)
+ assert_includes xml, %(<name>David</name>)
end
def test_one_level_dasherize_false
xml = { name: "David", street_name: "Paulina" }.to_xml(@xml_options.merge(dasherize: false))
assert_equal "<person>", xml.first(8)
- assert xml.include?(%(<street_name>Paulina</street_name>))
- assert xml.include?(%(<name>David</name>))
+ assert_includes xml, %(<street_name>Paulina</street_name>)
+ assert_includes xml, %(<name>David</name>)
end
def test_one_level_dasherize_true
xml = { name: "David", street_name: "Paulina" }.to_xml(@xml_options.merge(dasherize: true))
assert_equal "<person>", xml.first(8)
- assert xml.include?(%(<street-name>Paulina</street-name>))
- assert xml.include?(%(<name>David</name>))
+ assert_includes xml, %(<street-name>Paulina</street-name>)
+ assert_includes xml, %(<name>David</name>)
end
def test_one_level_camelize_true
xml = { name: "David", street_name: "Paulina" }.to_xml(@xml_options.merge(camelize: true))
assert_equal "<Person>", xml.first(8)
- assert xml.include?(%(<StreetName>Paulina</StreetName>))
- assert xml.include?(%(<Name>David</Name>))
+ assert_includes xml, %(<StreetName>Paulina</StreetName>)
+ assert_includes xml, %(<Name>David</Name>)
end
def test_one_level_camelize_lower
xml = { name: "David", street_name: "Paulina" }.to_xml(@xml_options.merge(camelize: :lower))
assert_equal "<person>", xml.first(8)
- assert xml.include?(%(<streetName>Paulina</streetName>))
- assert xml.include?(%(<name>David</name>))
+ assert_includes xml, %(<streetName>Paulina</streetName>)
+ assert_includes xml, %(<name>David</name>)
end
def test_one_level_with_types
xml = { name: "David", street: "Paulina", age: 26, age_in_millis: 820497600000, moved_on: Date.new(2005, 11, 15), resident: :yes }.to_xml(@xml_options)
assert_equal "<person>", xml.first(8)
- assert xml.include?(%(<street>Paulina</street>))
- assert xml.include?(%(<name>David</name>))
- assert xml.include?(%(<age type="integer">26</age>))
- assert xml.include?(%(<age-in-millis type="integer">820497600000</age-in-millis>))
- assert xml.include?(%(<moved-on type="date">2005-11-15</moved-on>))
- assert xml.include?(%(<resident type="symbol">yes</resident>))
+ assert_includes xml, %(<street>Paulina</street>)
+ assert_includes xml, %(<name>David</name>)
+ assert_includes xml, %(<age type="integer">26</age>)
+ assert_includes xml, %(<age-in-millis type="integer">820497600000</age-in-millis>)
+ assert_includes xml, %(<moved-on type="date">2005-11-15</moved-on>)
+ assert_includes xml, %(<resident type="symbol">yes</resident>)
end
def test_one_level_with_nils
xml = { name: "David", street: "Paulina", age: nil }.to_xml(@xml_options)
assert_equal "<person>", xml.first(8)
- assert xml.include?(%(<street>Paulina</street>))
- assert xml.include?(%(<name>David</name>))
- assert xml.include?(%(<age nil="true"/>))
+ assert_includes xml, %(<street>Paulina</street>)
+ assert_includes xml, %(<name>David</name>)
+ assert_includes xml, %(<age nil="true"/>)
end
def test_one_level_with_skipping_types
xml = { name: "David", street: "Paulina", age: nil }.to_xml(@xml_options.merge(skip_types: true))
assert_equal "<person>", xml.first(8)
- assert xml.include?(%(<street>Paulina</street>))
- assert xml.include?(%(<name>David</name>))
- assert xml.include?(%(<age nil="true"/>))
+ assert_includes xml, %(<street>Paulina</street>)
+ assert_includes xml, %(<name>David</name>)
+ assert_includes xml, %(<age nil="true"/>)
end
def test_one_level_with_yielding
@@ -1192,43 +1185,43 @@ class HashToXmlTest < ActiveSupport::TestCase
end
assert_equal "<person>", xml.first(8)
- assert xml.include?(%(<street>Paulina</street>))
- assert xml.include?(%(<name>David</name>))
- assert xml.include?(%(<creator>Rails</creator>))
+ assert_includes xml, %(<street>Paulina</street>)
+ assert_includes xml, %(<name>David</name>)
+ assert_includes xml, %(<creator>Rails</creator>)
end
def test_two_levels
xml = { name: "David", address: { street: "Paulina" } }.to_xml(@xml_options)
assert_equal "<person>", xml.first(8)
- assert xml.include?(%(<address><street>Paulina</street></address>))
- assert xml.include?(%(<name>David</name>))
+ assert_includes xml, %(<address><street>Paulina</street></address>)
+ assert_includes xml, %(<name>David</name>)
end
def test_two_levels_with_second_level_overriding_to_xml
xml = { name: "David", address: { street: "Paulina" }, child: IWriteMyOwnXML.new }.to_xml(@xml_options)
assert_equal "<person>", xml.first(8)
- assert xml.include?(%(<address><street>Paulina</street></address>))
- assert xml.include?(%(<level_one><second_level>content</second_level></level_one>))
+ assert_includes xml, %(<address><street>Paulina</street></address>)
+ assert_includes xml, %(<level_one><second_level>content</second_level></level_one>)
end
def test_two_levels_with_array
xml = { name: "David", addresses: [{ street: "Paulina" }, { street: "Evergreen" }] }.to_xml(@xml_options)
assert_equal "<person>", xml.first(8)
- assert xml.include?(%(<addresses type="array"><address>))
- assert xml.include?(%(<address><street>Paulina</street></address>))
- assert xml.include?(%(<address><street>Evergreen</street></address>))
- assert xml.include?(%(<name>David</name>))
+ assert_includes xml, %(<addresses type="array"><address>)
+ assert_includes xml, %(<address><street>Paulina</street></address>)
+ assert_includes xml, %(<address><street>Evergreen</street></address>)
+ assert_includes xml, %(<name>David</name>)
end
def test_three_levels_with_array
xml = { name: "David", addresses: [{ streets: [ { name: "Paulina" }, { name: "Paulina" } ] } ] }.to_xml(@xml_options)
- assert xml.include?(%(<addresses type="array"><address><streets type="array"><street><name>))
+ assert_includes xml, %(<addresses type="array"><address><streets type="array"><street><name>)
end
def test_timezoned_attributes
xml = {
- created_at: Time.utc(1999,2,2),
- local_created_at: Time.utc(1999,2,2).in_time_zone("Eastern Time (US & Canada)")
+ created_at: Time.utc(1999, 2, 2),
+ local_created_at: Time.utc(1999, 2, 2).in_time_zone("Eastern Time (US & Canada)")
}.to_xml(@xml_options)
assert_match %r{<created-at type=\"dateTime\">1999-02-02T00:00:00Z</created-at>}, xml
assert_match %r{<local-created-at type=\"dateTime\">1999-02-01T19:00:00-05:00</local-created-at>}, xml
@@ -1437,7 +1430,7 @@ class HashToXmlTest < ActiveSupport::TestCase
<posts type="array"></posts>
</blog>
XML
- expected_blog_hash = {"blog" => {"posts" => []}}
+ expected_blog_hash = { "blog" => { "posts" => [] } }
assert_equal expected_blog_hash, Hash.from_xml(blog_xml)
end
@@ -1448,7 +1441,7 @@ class HashToXmlTest < ActiveSupport::TestCase
</posts>
</blog>
XML
- expected_blog_hash = {"blog" => {"posts" => []}}
+ expected_blog_hash = { "blog" => { "posts" => [] } }
assert_equal expected_blog_hash, Hash.from_xml(blog_xml)
end
@@ -1460,7 +1453,7 @@ class HashToXmlTest < ActiveSupport::TestCase
</posts>
</blog>
XML
- expected_blog_hash = {"blog" => {"posts" => ["a post"]}}
+ expected_blog_hash = { "blog" => { "posts" => ["a post"] } }
assert_equal expected_blog_hash, Hash.from_xml(blog_xml)
end
@@ -1473,7 +1466,7 @@ class HashToXmlTest < ActiveSupport::TestCase
</posts>
</blog>
XML
- expected_blog_hash = {"blog" => {"posts" => ["a post", "another post"]}}
+ expected_blog_hash = { "blog" => { "posts" => ["a post", "another post"] } }
assert_equal expected_blog_hash, Hash.from_xml(blog_xml)
end
@@ -1537,7 +1530,7 @@ class HashToXmlTest < ActiveSupport::TestCase
weight: 0.5,
chunky: true,
price: BigDecimal("12.50"),
- expires_at: Time.utc(2007,12,25,12,34,56),
+ expires_at: Time.utc(2007, 12, 25, 12, 34, 56),
notes: "",
illustration: "babe.png",
caption: "That'll do, pig."
@@ -1557,7 +1550,7 @@ class HashToXmlTest < ActiveSupport::TestCase
expected_product_hash = {
weight: 0.5,
- image: {"type" => "ProductImage", "filename" => "image.gif" },
+ image: { "type" => "ProductImage", "filename" => "image.gif" },
}.stringify_keys
assert_equal expected_product_hash, Hash.from_xml(product_xml)["product"]
@@ -1580,29 +1573,29 @@ class HashToXmlTest < ActiveSupport::TestCase
end
def test_from_xml_array_one
- expected = { "numbers" => { "type" => "Array", "value" => "1" }}
+ expected = { "numbers" => { "type" => "Array", "value" => "1" } }
assert_equal expected, Hash.from_xml('<numbers type="Array"><value>1</value></numbers>')
end
def test_from_xml_array_many
- expected = { "numbers" => { "type" => "Array", "value" => [ "1", "2" ] }}
+ expected = { "numbers" => { "type" => "Array", "value" => [ "1", "2" ] } }
assert_equal expected, Hash.from_xml('<numbers type="Array"><value>1</value><value>2</value></numbers>')
end
def test_from_trusted_xml_allows_symbol_and_yaml_types
- expected = { "product" => { "name" => :value }}
+ expected = { "product" => { "name" => :value } }
assert_equal expected, Hash.from_trusted_xml('<product><name type="symbol">value</name></product>')
assert_equal expected, Hash.from_trusted_xml('<product><name type="yaml">:value</name></product>')
end
def test_should_use_default_proc_for_unknown_key
- hash_wia = HashWithIndifferentAccess.new { 1 + 2 }
+ hash_wia = HashWithIndifferentAccess.new { 1 + 2 }
assert_equal 3, hash_wia[:new_key]
end
def test_should_return_nil_if_no_key_is_supplied
- hash_wia = HashWithIndifferentAccess.new { 1 + 2 }
- assert_equal nil, hash_wia.default
+ hash_wia = HashWithIndifferentAccess.new { 1 + 2 }
+ assert_nil hash_wia.default
end
def test_should_use_default_value_for_unknown_key
@@ -1734,10 +1727,10 @@ class HashToXmlTest < ActiveSupport::TestCase
end
def test_to_xml_dups_options
- options = {skip_instruct: true}
+ options = { skip_instruct: true }
{}.to_xml(options)
# :builder, etc, shouldn't be added to options
- assert_equal({skip_instruct: true}, options)
+ assert_equal({ skip_instruct: true }, options)
end
def test_expansion_count_is_limited
diff --git a/activesupport/test/core_ext/load_error_test.rb b/activesupport/test/core_ext/load_error_test.rb
index b50a35b2cb..44ff6bb051 100644
--- a/activesupport/test/core_ext/load_error_test.rb
+++ b/activesupport/test/core_ext/load_error_test.rb
@@ -1,14 +1,6 @@
require "abstract_unit"
require "active_support/core_ext/load_error"
-class TestMissingSourceFile < ActiveSupport::TestCase
- def test_it_is_deprecated
- assert_deprecated do
- MissingSourceFile.new
- end
- end
-end
-
class TestLoadError < ActiveSupport::TestCase
def test_with_require
assert_raise(LoadError) { require 'no_this_file_don\'t_exist' }
diff --git a/activesupport/test/core_ext/marshal_test.rb b/activesupport/test/core_ext/marshal_test.rb
index 275002b8a1..a899f98705 100644
--- a/activesupport/test/core_ext/marshal_test.rb
+++ b/activesupport/test/core_ext/marshal_test.rb
@@ -12,7 +12,7 @@ class MarshalTest < ActiveSupport::TestCase
end
test "that Marshal#load still works" do
- sanity_data = ["test", [1, 2, 3], {a: [1, 2, 3]}, ActiveSupport::TestCase]
+ sanity_data = ["test", [1, 2, 3], { a: [1, 2, 3] }, ActiveSupport::TestCase]
sanity_data.each do |obj|
dumped = Marshal.dump(obj)
assert_equal Marshal.method(:load).super_method.call(dumped), Marshal.load(dumped)
diff --git a/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb b/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb
index b816fa50e3..af240bc38d 100644
--- a/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb
+++ b/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb
@@ -121,11 +121,11 @@ class ModuleAttributeAccessorPerThreadTest < ActiveSupport::TestCase
def test_should_not_affect_superclass_if_subclass_set_value
@class.foo = "super"
- assert_equal @class.foo, "super"
+ assert_equal "super", @class.foo
assert_nil @subclass.foo
@subclass.foo = "sub"
- assert_equal @class.foo, "super"
- assert_equal @subclass.foo, "sub"
+ assert_equal "super", @class.foo
+ assert_equal "sub", @subclass.foo
end
end
diff --git a/activesupport/test/core_ext/module/attribute_accessor_test.rb b/activesupport/test/core_ext/module/attribute_accessor_test.rb
index fea6806b9b..464a000d59 100644
--- a/activesupport/test/core_ext/module/attribute_accessor_test.rb
+++ b/activesupport/test/core_ext/module/attribute_accessor_test.rb
@@ -93,7 +93,7 @@ class ModuleAttributeAccessorTest < ActiveSupport::TestCase
def test_should_not_invoke_default_value_block_multiple_times
count = 0
- @module.cattr_accessor(:defcount){ count += 1 }
+ @module.cattr_accessor(:defcount) { count += 1 }
assert_equal 1, count
end
end
diff --git a/activesupport/test/core_ext/module/concerning_test.rb b/activesupport/test/core_ext/module/concerning_test.rb
index 45178e477d..098036828a 100644
--- a/activesupport/test/core_ext/module/concerning_test.rb
+++ b/activesupport/test/core_ext/module/concerning_test.rb
@@ -3,8 +3,8 @@ require "active_support/core_ext/module/concerning"
class ModuleConcerningTest < ActiveSupport::TestCase
def test_concerning_declares_a_concern_and_includes_it_immediately
- klass = Class.new { concerning(:Foo) { } }
- assert klass.ancestors.include?(klass::Foo), klass.ancestors.inspect
+ klass = Class.new { concerning(:Foo) {} }
+ assert_includes klass.ancestors, klass::Foo, klass.ancestors.inspect
end
end
@@ -21,10 +21,10 @@ class ModuleConcernTest < ActiveSupport::TestCase
assert klass.const_defined?(:Baz, false)
assert !ModuleConcernTest.const_defined?(:Baz)
assert_kind_of ActiveSupport::Concern, klass::Baz
- assert !klass.ancestors.include?(klass::Baz), klass.ancestors.inspect
+ assert_not_includes klass.ancestors, klass::Baz, klass.ancestors.inspect
# Public method visibility by default
- assert klass::Baz.public_instance_methods.map(&:to_s).include?("should_be_public")
+ assert_includes klass::Baz.public_instance_methods.map(&:to_s), "should_be_public"
# Calls included hook
assert_equal 1, Class.new { include klass::Baz }.instance_variable_get("@foo")
diff --git a/activesupport/test/core_ext/module/qualified_const_test.rb b/activesupport/test/core_ext/module/qualified_const_test.rb
deleted file mode 100644
index dcfac4ced1..0000000000
--- a/activesupport/test/core_ext/module/qualified_const_test.rb
+++ /dev/null
@@ -1,118 +0,0 @@
-require "abstract_unit"
-require "active_support/core_ext/module/qualified_const"
-
-module QualifiedConstTestMod
- X = false
-
- module M
- X = 1
-
- class C
- X = 2
- end
- end
-
- module N
- include M
- end
-end
-
-class QualifiedConstTest < ActiveSupport::TestCase
- test "Object.qualified_const_defined?" do
- assert_deprecated do
- assert Object.qualified_const_defined?("QualifiedConstTestMod")
- assert !Object.qualified_const_defined?("NonExistingQualifiedConstTestMod")
-
- assert Object.qualified_const_defined?("QualifiedConstTestMod::X")
- assert !Object.qualified_const_defined?("QualifiedConstTestMod::Y")
-
- assert Object.qualified_const_defined?("QualifiedConstTestMod::M::X")
- assert !Object.qualified_const_defined?("QualifiedConstTestMod::M::Y")
-
- if Module.method(:const_defined?).arity == 1
- assert !Object.qualified_const_defined?("QualifiedConstTestMod::N::X")
- else
- assert Object.qualified_const_defined?("QualifiedConstTestMod::N::X")
- assert !Object.qualified_const_defined?("QualifiedConstTestMod::N::X", false)
- assert Object.qualified_const_defined?("QualifiedConstTestMod::N::X", true)
- end
- end
- end
-
- test "mod.qualified_const_defined?" do
- assert_deprecated do
- assert QualifiedConstTestMod.qualified_const_defined?("M")
- assert !QualifiedConstTestMod.qualified_const_defined?("NonExistingM")
-
- assert QualifiedConstTestMod.qualified_const_defined?("M::X")
- assert !QualifiedConstTestMod.qualified_const_defined?("M::Y")
-
- assert QualifiedConstTestMod.qualified_const_defined?("M::C::X")
- assert !QualifiedConstTestMod.qualified_const_defined?("M::C::Y")
-
- if Module.method(:const_defined?).arity == 1
- assert !QualifiedConstTestMod.qualified_const_defined?("QualifiedConstTestMod::N::X")
- else
- assert QualifiedConstTestMod.qualified_const_defined?("N::X")
- assert !QualifiedConstTestMod.qualified_const_defined?("N::X", false)
- assert QualifiedConstTestMod.qualified_const_defined?("N::X", true)
- end
- end
- end
-
- test "qualified_const_get" do
- assert_deprecated do
- assert_equal false, Object.qualified_const_get("QualifiedConstTestMod::X")
- assert_equal false, QualifiedConstTestMod.qualified_const_get("X")
- assert_equal 1, QualifiedConstTestMod.qualified_const_get("M::X")
- assert_equal 1, QualifiedConstTestMod.qualified_const_get("N::X")
- assert_equal 2, QualifiedConstTestMod.qualified_const_get("M::C::X")
-
- assert_raise(NameError) { QualifiedConstTestMod.qualified_const_get("M::C::Y")}
- end
- end
-
- test "qualified_const_set" do
- assert_deprecated do
- begin
- m = Module.new
- assert_equal m, Object.qualified_const_set("QualifiedConstTestMod2", m)
- assert_equal m, ::QualifiedConstTestMod2
-
- # We are going to assign to existing constants on purpose, so silence warnings.
- silence_warnings do
- assert_equal true, QualifiedConstTestMod.qualified_const_set("QualifiedConstTestMod::X", true)
- assert_equal true, QualifiedConstTestMod::X
-
- assert_equal 10, QualifiedConstTestMod::M.qualified_const_set("X", 10)
- assert_equal 10, QualifiedConstTestMod::M::X
- end
- ensure
- silence_warnings do
- QualifiedConstTestMod.qualified_const_set("QualifiedConstTestMod::X", false)
- QualifiedConstTestMod::M.qualified_const_set("X", 1)
- end
- end
- end
- end
-
- test "reject absolute paths" do
- assert_deprecated do
- assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_defined?("::X")}
- assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_defined?("::X::Y")}
-
- assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_get("::X")}
- assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_get("::X::Y")}
-
- assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_set("::X", nil)}
- assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_set("::X::Y", nil)}
- end
- end
-
- private
-
- def assert_raise_with_message(expected_exception, expected_message, &block)
- exception = assert_raise(expected_exception, &block)
- assert_equal expected_message, exception.message
- end
-end
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index 104082b777..a4515d1956 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -48,12 +48,12 @@ class Someone < Struct.new(:name, :place)
end
end
-Invoice = Struct.new(:client) do
+Invoice = Struct.new(:client) do
delegate :street, :city, :name, to: :client, prefix: true
delegate :street, :city, :name, to: :client, prefix: :customer
end
-Project = Struct.new(:description, :person) do
+Project = Struct.new(:description, :person) do
delegate :name, to: :person, allow_nil: true
delegate :to_f, to: :description, allow_nil: true
end
@@ -117,7 +117,7 @@ class ParameterSet
delegate :[], :[]=, to: :@params
def initialize
- @params = {foo: "bar"}
+ @params = { foo: "bar" }
end
end
@@ -210,21 +210,21 @@ class ModuleTest < ActiveSupport::TestCase
def test_delegation_prefix
invoice = Invoice.new(@david)
- assert_equal invoice.client_name, "David"
- assert_equal invoice.client_street, "Paulina"
- assert_equal invoice.client_city, "Chicago"
+ assert_equal "David", invoice.client_name
+ assert_equal "Paulina", invoice.client_street
+ assert_equal "Chicago", invoice.client_city
end
def test_delegation_custom_prefix
invoice = Invoice.new(@david)
- assert_equal invoice.customer_name, "David"
- assert_equal invoice.customer_street, "Paulina"
- assert_equal invoice.customer_city, "Chicago"
+ assert_equal "David", invoice.customer_name
+ assert_equal "Paulina", invoice.customer_street
+ assert_equal "Chicago", invoice.customer_city
end
def test_delegation_prefix_with_nil_or_false
- assert_equal Developer.new(@david).name, "David"
- assert_equal Tester.new(@david).name, "David"
+ assert_equal "David", Developer.new(@david).name
+ assert_equal "David", Tester.new(@david).name
end
def test_delegation_prefix_with_instance_variable
@@ -240,7 +240,7 @@ class ModuleTest < ActiveSupport::TestCase
def test_delegation_with_allow_nil
rails = Project.new("Rails", Someone.new("David"))
- assert_equal rails.name, "David"
+ assert_equal "David", rails.name
end
def test_delegation_with_allow_nil_and_nil_value
@@ -312,7 +312,7 @@ class ModuleTest < ActiveSupport::TestCase
rescue NoMethodError => e
file_and_line = "#{__FILE__}:#{Someone::FAILED_DELEGATE_LINE}"
# We can't simply check the first line of the backtrace, because JRuby reports the call to __send__ in the backtrace.
- assert e.backtrace.any?{|a| a.include?(file_and_line)},
+ assert e.backtrace.any? { |a| a.include?(file_and_line) },
"[#{e.backtrace.inspect}] did not include [#{file_and_line}]"
end
@@ -322,7 +322,7 @@ class ModuleTest < ActiveSupport::TestCase
rescue NoMethodError => e
file_and_line = "#{__FILE__}:#{Someone::FAILED_DELEGATE_LINE_2}"
# We can't simply check the first line of the backtrace, because JRuby reports the call to __send__ in the backtrace.
- assert e.backtrace.any?{|a| a.include?(file_and_line)},
+ assert e.backtrace.any? { |a| a.include?(file_and_line) },
"[#{e.backtrace.inspect}] did not include [#{file_and_line}]"
end
@@ -386,231 +386,6 @@ class ModuleTest < ActiveSupport::TestCase
assert_equal [Yz, Object], Yz::Zy.parents
end
- def test_local_constants
- ActiveSupport::Deprecation.silence do
- assert_equal %w(Constant1 Constant3), Ab.local_constants.sort.map(&:to_s)
- end
- end
-
- def test_local_constants_is_deprecated
- assert_deprecated { Ab.local_constants.sort.map(&:to_s) }
- end
-end
-
-module BarMethodAliaser
- def self.included(foo_class)
- foo_class.class_eval do
- include BarMethods
- alias_method_chain :bar, :baz
- end
- end
-end
-
-module BarMethods
- def bar_with_baz
- bar_without_baz << "_with_baz"
- end
-
- def quux_with_baz!
- quux_without_baz! << "_with_baz"
- end
-
- def quux_with_baz?
- false
- end
-
- def quux_with_baz=(v)
- send(:quux_without_baz=, v) << "_with_baz"
- end
-
- def duck_with_orange
- duck_without_orange << "_with_orange"
- end
-end
-
-class MethodAliasingTest < ActiveSupport::TestCase
- def setup
- Object.const_set :FooClassWithBarMethod, Class.new { def bar() "bar" end }
- @instance = FooClassWithBarMethod.new
- end
-
- def teardown
- Object.instance_eval { remove_const :FooClassWithBarMethod }
- end
-
- def test_alias_method_chain_deprecated
- assert_deprecated(/alias_method_chain/) do
- Module.new do
- def base
- end
-
- def base_with_deprecated
- end
-
- alias_method_chain :base, :deprecated
- end
- end
- end
-
- def test_alias_method_chain
- assert_deprecated(/alias_method_chain/) do
- assert @instance.respond_to?(:bar)
- feature_aliases = [:bar_with_baz, :bar_without_baz]
-
- feature_aliases.each do |method|
- assert !@instance.respond_to?(method)
- end
-
- assert_equal "bar", @instance.bar
-
- FooClassWithBarMethod.class_eval { include BarMethodAliaser }
-
- feature_aliases.each do |method|
- assert_respond_to @instance, method
- end
-
- assert_equal "bar_with_baz", @instance.bar
- assert_equal "bar", @instance.bar_without_baz
- end
- end
-
- def test_alias_method_chain_with_punctuation_method
- assert_deprecated(/alias_method_chain/) do
- FooClassWithBarMethod.class_eval do
- def quux!; "quux" end
- end
-
- assert !@instance.respond_to?(:quux_with_baz!)
- FooClassWithBarMethod.class_eval do
- include BarMethodAliaser
- alias_method_chain :quux!, :baz
- end
- assert_respond_to @instance, :quux_with_baz!
-
- assert_equal "quux_with_baz", @instance.quux!
- assert_equal "quux", @instance.quux_without_baz!
- end
- end
-
- def test_alias_method_chain_with_same_names_between_predicates_and_bang_methods
- assert_deprecated(/alias_method_chain/) do
- FooClassWithBarMethod.class_eval do
- def quux!; "quux!" end
- def quux?; true end
- def quux=(v); "quux=" end
- end
-
- assert !@instance.respond_to?(:quux_with_baz!)
- assert !@instance.respond_to?(:quux_with_baz?)
- assert !@instance.respond_to?(:quux_with_baz=)
-
- FooClassWithBarMethod.class_eval { include BarMethodAliaser }
- assert_respond_to @instance, :quux_with_baz!
- assert_respond_to @instance, :quux_with_baz?
- assert_respond_to @instance, :quux_with_baz=
-
- FooClassWithBarMethod.alias_method_chain :quux!, :baz
- assert_equal "quux!_with_baz", @instance.quux!
- assert_equal "quux!", @instance.quux_without_baz!
-
- FooClassWithBarMethod.alias_method_chain :quux?, :baz
- assert_equal false, @instance.quux?
- assert_equal true, @instance.quux_without_baz?
-
- FooClassWithBarMethod.alias_method_chain :quux=, :baz
- assert_equal "quux=_with_baz", @instance.send(:quux=, 1234)
- assert_equal "quux=", @instance.send(:quux_without_baz=, 1234)
- end
- end
-
- def test_alias_method_chain_with_feature_punctuation
- assert_deprecated(/alias_method_chain/) do
- FooClassWithBarMethod.class_eval do
- def quux; "quux" end
- def quux?; "quux?" end
- include BarMethodAliaser
- alias_method_chain :quux, :baz!
- end
-
- assert_nothing_raised do
- assert_equal "quux_with_baz", @instance.quux_with_baz!
- end
-
- assert_raise(NameError) do
- FooClassWithBarMethod.alias_method_chain :quux?, :baz!
- end
- end
- end
-
- def test_alias_method_chain_yields_target_and_punctuation
- assert_deprecated(/alias_method_chain/) do
- args = nil
-
- FooClassWithBarMethod.class_eval do
- def quux?; end
- include BarMethods
-
- FooClassWithBarMethod.alias_method_chain :quux?, :baz do |target, punctuation|
- args = [target, punctuation]
- end
- end
-
- assert_not_nil args
- assert_equal "quux", args[0]
- assert_equal "?", args[1]
- end
- end
-
- def test_alias_method_chain_preserves_private_method_status
- assert_deprecated(/alias_method_chain/) do
- FooClassWithBarMethod.class_eval do
- def duck; "duck" end
- include BarMethodAliaser
- private :duck
- alias_method_chain :duck, :orange
- end
-
- assert_raise NoMethodError do
- @instance.duck
- end
-
- assert_equal "duck_with_orange", @instance.instance_eval { duck }
- assert FooClassWithBarMethod.private_method_defined?(:duck)
- end
- end
-
- def test_alias_method_chain_preserves_protected_method_status
- assert_deprecated(/alias_method_chain/) do
- FooClassWithBarMethod.class_eval do
- def duck; "duck" end
- include BarMethodAliaser
- protected :duck
- alias_method_chain :duck, :orange
- end
-
- assert_raise NoMethodError do
- @instance.duck
- end
-
- assert_equal "duck_with_orange", @instance.instance_eval { duck }
- assert FooClassWithBarMethod.protected_method_defined?(:duck)
- end
- end
-
- def test_alias_method_chain_preserves_public_method_status
- assert_deprecated(/alias_method_chain/) do
- FooClassWithBarMethod.class_eval do
- def duck; "duck" end
- include BarMethodAliaser
- public :duck
- alias_method_chain :duck, :orange
- end
-
- assert_equal "duck_with_orange", @instance.duck
- assert FooClassWithBarMethod.public_method_defined?(:duck)
- end
- end
-
def test_delegate_with_case
event = Event.new(Tester.new)
assert_equal 1, event.foo
diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb
index b99217a1b3..5361b7b708 100644
--- a/activesupport/test/core_ext/numeric_ext_test.rb
+++ b/activesupport/test/core_ext/numeric_ext_test.rb
@@ -5,8 +5,8 @@ require "active_support/core_ext/integer"
class NumericExtTimeAndDateTimeTest < ActiveSupport::TestCase
def setup
- @now = Time.local(2005,2,10,15,30,45)
- @dtnow = DateTime.civil(2005,2,10,15,30,45)
+ @now = Time.local(2005, 2, 10, 15, 30, 45)
+ @dtnow = DateTime.civil(2005, 2, 10, 15, 30, 45)
@seconds = {
1.minute => 60,
10.minutes => 600,
@@ -68,8 +68,8 @@ class NumericExtTimeAndDateTimeTest < ActiveSupport::TestCase
end
def test_add_one_year_to_leap_day
- assert_equal Time.utc(2005,2,28,15,15,10), Time.utc(2004,2,29,15,15,10) + 1.year
- assert_equal DateTime.civil(2005,2,28,15,15,10), DateTime.civil(2004,2,29,15,15,10) + 1.year
+ assert_equal Time.utc(2005, 2, 28, 15, 15, 10), Time.utc(2004, 2, 29, 15, 15, 10) + 1.year
+ assert_equal DateTime.civil(2005, 2, 28, 15, 15, 10), DateTime.civil(2004, 2, 29, 15, 15, 10) + 1.year
end
end
@@ -83,7 +83,7 @@ class NumericExtDateTest < ActiveSupport::TestCase
assert_equal @today >> 1, @today + 1.month
assert_equal @today.to_time.since(1), @today + 1.second
assert_equal @today.to_time.since(60), @today + 1.minute
- assert_equal @today.to_time.since(60*60), @today + 1.hour
+ assert_equal @today.to_time.since(60 * 60), @today + 1.hour
end
def test_chaining_duration_operations
@@ -92,7 +92,7 @@ class NumericExtDateTest < ActiveSupport::TestCase
end
def test_add_one_year_to_leap_day
- assert_equal Date.new(2005,2,28), Date.new(2004,2,29) + 1.year
+ assert_equal Date.new(2005, 2, 28), Date.new(2004, 2, 29) + 1.year
end
end
@@ -102,12 +102,12 @@ class NumericExtSizeTest < ActiveSupport::TestCase
assert_equal 1024.kilobytes, 1.megabyte
assert_equal 3584.0.kilobytes, 3.5.megabytes
assert_equal 3584.0.megabytes, 3.5.gigabytes
- assert_equal 1.kilobyte ** 4, 1.terabyte
+ assert_equal 1.kilobyte**4, 1.terabyte
assert_equal 1024.kilobytes + 2.megabytes, 3.megabytes
assert_equal 2.gigabytes / 4, 512.megabytes
assert_equal 256.megabytes * 20 + 5.gigabytes, 10.gigabytes
- assert_equal 1.kilobyte ** 5, 1.petabyte
- assert_equal 1.kilobyte ** 6, 1.exabyte
+ assert_equal 1.kilobyte**5, 1.petabyte
+ assert_equal 1.kilobyte**6, 1.exabyte
end
def test_units_as_bytes_independently
@@ -228,37 +228,37 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
def test_to_s__rounded__with_significant_digits
assert_equal "124000", 123987.to_s(:rounded, precision: 3, significant: true)
- assert_equal "120000000", 123987876.to_s(:rounded, precision: 2, significant: true )
- assert_equal "9775", 9775.to_s(:rounded, precision: 4, significant: true )
- assert_equal "5.4", 5.3923.to_s(:rounded, precision: 2, significant: true )
- assert_equal "5", 5.3923.to_s(:rounded, precision: 1, significant: true )
- assert_equal "1", 1.232.to_s(:rounded, precision: 1, significant: true )
- assert_equal "7", 7.to_s(:rounded, precision: 1, significant: true )
- assert_equal "1", 1.to_s(:rounded, precision: 1, significant: true )
- assert_equal "53", 52.7923.to_s(:rounded, precision: 2, significant: true )
- assert_equal "9775.00", 9775.to_s(:rounded, precision: 6, significant: true )
- assert_equal "5.392900", 5.3929.to_s(:rounded, precision: 7, significant: true )
- assert_equal "0.0", 0.to_s(:rounded, precision: 2, significant: true )
- assert_equal "0", 0.to_s(:rounded, precision: 1, significant: true )
- assert_equal "0.0001", 0.0001.to_s(:rounded, precision: 1, significant: true )
- assert_equal "0.000100", 0.0001.to_s(:rounded, precision: 3, significant: true )
- assert_equal "0.0001", 0.0001111.to_s(:rounded, precision: 1, significant: true )
+ assert_equal "120000000", 123987876.to_s(:rounded, precision: 2, significant: true)
+ assert_equal "9775", 9775.to_s(:rounded, precision: 4, significant: true)
+ assert_equal "5.4", 5.3923.to_s(:rounded, precision: 2, significant: true)
+ assert_equal "5", 5.3923.to_s(:rounded, precision: 1, significant: true)
+ assert_equal "1", 1.232.to_s(:rounded, precision: 1, significant: true)
+ assert_equal "7", 7.to_s(:rounded, precision: 1, significant: true)
+ assert_equal "1", 1.to_s(:rounded, precision: 1, significant: true)
+ assert_equal "53", 52.7923.to_s(:rounded, precision: 2, significant: true)
+ assert_equal "9775.00", 9775.to_s(:rounded, precision: 6, significant: true)
+ assert_equal "5.392900", 5.3929.to_s(:rounded, precision: 7, significant: true)
+ assert_equal "0.0", 0.to_s(:rounded, precision: 2, significant: true)
+ assert_equal "0", 0.to_s(:rounded, precision: 1, significant: true)
+ assert_equal "0.0001", 0.0001.to_s(:rounded, precision: 1, significant: true)
+ assert_equal "0.000100", 0.0001.to_s(:rounded, precision: 3, significant: true)
+ assert_equal "0.0001", 0.0001111.to_s(:rounded, precision: 1, significant: true)
assert_equal "10.0", 9.995.to_s(:rounded, precision: 3, significant: true)
assert_equal "9.99", 9.994.to_s(:rounded, precision: 3, significant: true)
assert_equal "11.0", 10.995.to_s(:rounded, precision: 3, significant: true)
end
def test_to_s__rounded__with_strip_insignificant_zeros
- assert_equal "9775.43", 9775.43.to_s(:rounded, precision: 4, strip_insignificant_zeros: true )
- assert_equal "9775.2", 9775.2.to_s(:rounded, precision: 6, significant: true, strip_insignificant_zeros: true )
- assert_equal "0", 0.to_s(:rounded, precision: 6, significant: true, strip_insignificant_zeros: true )
+ assert_equal "9775.43", 9775.43.to_s(:rounded, precision: 4, strip_insignificant_zeros: true)
+ assert_equal "9775.2", 9775.2.to_s(:rounded, precision: 6, significant: true, strip_insignificant_zeros: true)
+ assert_equal "0", 0.to_s(:rounded, precision: 6, significant: true, strip_insignificant_zeros: true)
end
def test_to_s__rounded__with_significant_true_and_zero_precision
# Zero precision with significant is a mistake (would always return zero),
# so we treat it as if significant was false (increases backwards compatibility for number_to_human_size)
assert_equal "124", 123.987.to_s(:rounded, precision: 0, significant: true)
- assert_equal "12", 12.to_s(:rounded, precision: 0, significant: true )
+ assert_equal "12", 12.to_s(:rounded, precision: 0, significant: true)
end
def test_to_s__human_size
@@ -287,21 +287,6 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
assert_equal "10 Bytes", 10.to_s(:human_size)
end
- def test_to_s__human_size_with_si_prefix
- assert_deprecated do
- assert_equal "3 Bytes", 3.14159265.to_s(:human_size, prefix: :si)
- assert_equal "123 Bytes", 123.0.to_s(:human_size, prefix: :si)
- assert_equal "123 Bytes", 123.to_s(:human_size, prefix: :si)
- assert_equal "1.23 KB", 1234.to_s(:human_size, prefix: :si)
- assert_equal "12.3 KB", 12345.to_s(:human_size, prefix: :si)
- assert_equal "1.23 MB", 1234567.to_s(:human_size, prefix: :si)
- assert_equal "1.23 GB", 1234567890.to_s(:human_size, prefix: :si)
- assert_equal "1.23 TB", 1234567890123.to_s(:human_size, prefix: :si)
- assert_equal "1.23 PB", 1234567890123456.to_s(:human_size, prefix: :si)
- assert_equal "1.23 EB", 1234567890123456789.to_s(:human_size, prefix: :si)
- end
- end
-
def test_to_s__human_size_with_options_hash
assert_equal "1.2 MB", 1234567.to_s(:human_size, precision: 2)
assert_equal "3 Bytes", 3.14159265.to_s(:human_size, precision: 4)
@@ -348,13 +333,13 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
def test_number_to_human_with_custom_units
#Only integers
- volume = {unit: "ml", thousand: "lt", million: "m3"}
+ volume = { unit: "ml", thousand: "lt", million: "m3" }
assert_equal "123 lt", 123456.to_s(:human, units: volume)
assert_equal "12 ml", 12.to_s(:human, units: volume)
assert_equal "1.23 m3", 1234567.to_s(:human, units: volume)
#Including fractionals
- distance = {mili: "mm", centi: "cm", deci: "dm", unit: "m", ten: "dam", hundred: "hm", thousand: "km"}
+ distance = { mili: "mm", centi: "cm", deci: "dm", unit: "m", ten: "dam", hundred: "hm", thousand: "km" }
assert_equal "1.23 mm", 0.00123.to_s(:human, units: distance)
assert_equal "1.23 cm", 0.0123.to_s(:human, units: distance)
assert_equal "1.23 dm", 0.123.to_s(:human, units: distance)
@@ -367,20 +352,20 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
assert_equal "12.3 km", 12300.to_s(:human, units: distance)
#The quantifiers don't need to be a continuous sequence
- gangster = {hundred: "hundred bucks", million: "thousand quids"}
+ gangster = { hundred: "hundred bucks", million: "thousand quids" }
assert_equal "1 hundred bucks", 100.to_s(:human, units: gangster)
assert_equal "25 hundred bucks", 2500.to_s(:human, units: gangster)
assert_equal "25 thousand quids", 25000000.to_s(:human, units: gangster)
assert_equal "12300 thousand quids", 12345000000.to_s(:human, units: gangster)
#Spaces are stripped from the resulting string
- assert_equal "4", 4.to_s(:human, units: {unit: "", ten: "tens "})
- assert_equal "4.5 tens", 45.to_s(:human, units: {unit: "", ten: " tens "})
+ assert_equal "4", 4.to_s(:human, units: { unit: "", ten: "tens " })
+ assert_equal "4.5 tens", 45.to_s(:human, units: { unit: "", ten: " tens " })
end
def test_number_to_human_with_custom_format
assert_equal "123 times Thousand", 123456.to_s(:human, format: "%n times %u")
- volume = {unit: "ml", thousand: "lt", million: "m3"}
+ volume = { unit: "ml", thousand: "lt", million: "m3" }
assert_equal "123.lt", 123456.to_s(:human, units: volume, format: "%n.%u")
end
@@ -391,12 +376,6 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
assert_equal "1 Million", BigDecimal("1000010").to_s(:human)
end
- def test_to_formatted_s_is_deprecated
- assert_deprecated do
- 5551234.to_formatted_s(:phone)
- end
- end
-
def test_to_s_with_invalid_formatter
assert_equal "123", 123.to_s(:invalid)
assert_equal "2.5", 2.5.to_s(:invalid)
@@ -423,7 +402,6 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
# TODO: Remove positive and negative tests when we drop support to ruby < 2.3
b = 2**64
- b *= b until Bignum === b
T_ZERO = b.coerce(0).first
T_ONE = b.coerce(1).first
@@ -453,8 +431,8 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
end.new
assert_not_predicate(a, :positive?)
- assert_predicate(1/2r, :positive?)
- assert_not_predicate(-1/2r, :positive?)
+ assert_predicate(1 / 2r, :positive?)
+ assert_not_predicate(-1 / 2r, :positive?)
assert_predicate(T_ONE, :positive?)
assert_not_predicate(T_MONE, :positive?)
@@ -491,8 +469,8 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
end.new
assert_not_predicate(a, :negative?)
- assert_predicate(-1/2r, :negative?)
- assert_not_predicate(1/2r, :negative?)
+ assert_predicate(-1 / 2r, :negative?)
+ assert_not_predicate(1 / 2r, :negative?)
assert_not_predicate(T_ONE, :negative?)
assert_predicate(T_MONE, :negative?)
diff --git a/activesupport/test/core_ext/object/blank_test.rb b/activesupport/test/core_ext/object/blank_test.rb
index ab0676524e..1bedc76320 100644
--- a/activesupport/test/core_ext/object/blank_test.rb
+++ b/activesupport/test/core_ext/object/blank_test.rb
@@ -28,7 +28,7 @@ class BlankTest < ActiveSupport::TestCase
end
def test_presence
- BLANK.each { |v| assert_equal nil, v.presence, "#{v.inspect}.presence should return nil" }
+ BLANK.each { |v| assert_nil v.presence, "#{v.inspect}.presence should return nil" }
NOT.each { |v| assert_equal v, v.presence, "#{v.inspect}.presence should return self" }
end
end
diff --git a/activesupport/test/core_ext/object/deep_dup_test.rb b/activesupport/test/core_ext/object/deep_dup_test.rb
index e335ec1b40..f247ee16de 100644
--- a/activesupport/test/core_ext/object/deep_dup_test.rb
+++ b/activesupport/test/core_ext/object/deep_dup_test.rb
@@ -6,7 +6,7 @@ class DeepDupTest < ActiveSupport::TestCase
array = [1, [2, 3]]
dup = array.deep_dup
dup[1][2] = 4
- assert_equal nil, array[1][2]
+ assert_nil array[1][2]
assert_equal 4, dup[1][2]
end
@@ -14,7 +14,7 @@ class DeepDupTest < ActiveSupport::TestCase
hash = { a: { b: "b" } }
dup = hash.deep_dup
dup[:a][:c] = "c"
- assert_equal nil, hash[:a][:c]
+ assert_nil hash[:a][:c]
assert_equal "c", dup[:a][:c]
end
@@ -22,7 +22,7 @@ class DeepDupTest < ActiveSupport::TestCase
array = [1, { a: 2, b: 3 } ]
dup = array.deep_dup
dup[1][:c] = 4
- assert_equal nil, array[1][:c]
+ assert_nil array[1][:c]
assert_equal 4, dup[1][:c]
end
@@ -30,7 +30,7 @@ class DeepDupTest < ActiveSupport::TestCase
hash = { a: [1, 2] }
dup = hash.deep_dup
dup[:a][2] = "c"
- assert_equal nil, hash[:a][2]
+ assert_nil hash[:a][2]
assert_equal "c", dup[:a][2]
end
diff --git a/activesupport/test/core_ext/object/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb
index 2cbfefe235..fb140a5b76 100644
--- a/activesupport/test/core_ext/object/duplicable_test.rb
+++ b/activesupport/test/core_ext/object/duplicable_test.rb
@@ -4,9 +4,13 @@ require "active_support/core_ext/object/duplicable"
require "active_support/core_ext/numeric/time"
class DuplicableTest < ActiveSupport::TestCase
- RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, method(:puts)]
- ALLOW_DUP = ["1", Object.new, /foo/, [], {}, Time.now, Class.new, Module.new]
- ALLOW_DUP << BigDecimal.new("4.56")
+ if RUBY_VERSION >= "2.4.0"
+ RAISE_DUP = [method(:puts), Complex(1), Rational(1), 'symbol_from_string'.to_sym]
+ ALLOW_DUP = ["1", Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal.new("4.56"), nil, false, true, 1, 2.3]
+ else
+ RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, method(:puts), Complex(1), Rational(1)]
+ ALLOW_DUP = ["1", Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal.new("4.56")]
+ end
def test_duplicable
rubinius_skip "* Method#dup is allowed at the moment on Rubinius\n" \
diff --git a/activesupport/test/core_ext/object/inclusion_test.rb b/activesupport/test/core_ext/object/inclusion_test.rb
index be211ec7dc..955686d6aa 100644
--- a/activesupport/test/core_ext/object/inclusion_test.rb
+++ b/activesupport/test/core_ext/object/inclusion_test.rb
@@ -3,8 +3,8 @@ require "active_support/core_ext/object/inclusion"
class InTest < ActiveSupport::TestCase
def test_in_array
- assert 1.in?([1,2])
- assert !3.in?([1,2])
+ assert 1.in?([1, 2])
+ assert !3.in?([1, 2])
end
def test_in_hash
@@ -25,7 +25,7 @@ class InTest < ActiveSupport::TestCase
end
def test_in_set
- s = Set.new([1,2])
+ s = Set.new([1, 2])
assert 1.in?(s)
assert !3.in?(s)
end
diff --git a/activesupport/test/core_ext/object/instance_variables_test.rb b/activesupport/test/core_ext/object/instance_variables_test.rb
index 510352595a..5bdb2fbc35 100644
--- a/activesupport/test/core_ext/object/instance_variables_test.rb
+++ b/activesupport/test/core_ext/object/instance_variables_test.rb
@@ -13,7 +13,7 @@ class ObjectInstanceVariableTest < ActiveSupport::TestCase
end
def test_instance_values
- assert_equal({"bar" => "bar", "baz" => "baz"}, @source.instance_values)
+ assert_equal({ "bar" => "bar", "baz" => "baz" }, @source.instance_values)
end
def test_instance_exec_passes_arguments_to_block
diff --git a/activesupport/test/core_ext/object/to_query_test.rb b/activesupport/test/core_ext/object/to_query_test.rb
index 2eff475274..298c8bf373 100644
--- a/activesupport/test/core_ext/object/to_query_test.rb
+++ b/activesupport/test/core_ext/object/to_query_test.rb
@@ -33,17 +33,17 @@ class ToQueryTest < ActiveSupport::TestCase
def test_multiple_nested
assert_query_equal "account%5Bperson%5D%5Bid%5D=20&person%5Bid%5D=10",
- Hash[:account, {person: {id: 20}}, :person, {id: 10}]
+ Hash[:account, { person: { id: 20 } }, :person, { id: 10 }]
end
def test_array_values
assert_query_equal "person%5Bid%5D%5B%5D=10&person%5Bid%5D%5B%5D=20",
- person: {id: [10, 20]}
+ person: { id: [10, 20] }
end
def test_array_values_are_not_sorted
assert_query_equal "person%5Bid%5D%5B%5D=20&person%5Bid%5D%5B%5D=10",
- person: {id: [20, 10]}
+ person: { id: [20, 10] }
end
def test_empty_array
@@ -56,7 +56,7 @@ class ToQueryTest < ActiveSupport::TestCase
assert_query_equal "a=1&b%5Bc%5D=3",
a: 1, b: { c: 3, d: {} }
assert_query_equal "",
- a: {b: {c: {}}}
+ a: { b: { c: {} } }
assert_query_equal "b%5Bc%5D=false&b%5Be%5D=&b%5Bf%5D=&p=12",
p: 12, b: { c: false, e: nil, f: "" }
assert_query_equal "b%5Bc%5D=3&b%5Bf%5D=",
diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb
index 25e6693e4d..d166c7309c 100644
--- a/activesupport/test/core_ext/range_ext_test.rb
+++ b/activesupport/test/core_ext/range_ext_test.rb
@@ -66,15 +66,15 @@ class RangeTest < ActiveSupport::TestCase
end
def test_exclusive_end_should_not_include_identical_with_inclusive_end
- assert !(1...10).include?(1..10)
+ assert_not_includes (1...10), 1..10
end
def test_should_not_include_overlapping_first
- assert !(2..8).include?(1..3)
+ assert_not_includes (2..8), 1..3
end
def test_should_not_include_overlapping_last
- assert !(2..8).include?(5..9)
+ assert_not_includes (2..8), 5..9
end
def test_should_include_identical_exclusive_with_floats
@@ -99,21 +99,21 @@ class RangeTest < ActiveSupport::TestCase
end
def test_each_on_time_with_zone
- twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"] , Time.utc(2006,11,28,10,30))
+ twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"] , Time.utc(2006, 11, 28, 10, 30))
assert_raises TypeError do
((twz - 1.hour)..twz).each {}
end
end
def test_step_on_time_with_zone
- twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"] , Time.utc(2006,11,28,10,30))
+ twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"] , Time.utc(2006, 11, 28, 10, 30))
assert_raises TypeError do
((twz - 1.hour)..twz).step(1) {}
end
end
def test_include_on_time_with_zone
- twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"] , Time.utc(2006,11,28,10,30))
+ twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"] , Time.utc(2006, 11, 28, 10, 30))
assert_raises TypeError do
((twz - 1.hour)..twz).include?(twz)
end
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 4b40503d22..00685cd952 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -152,53 +152,37 @@ class StringInflectionsTest < ActiveSupport::TestCase
def test_string_parameterized_normal
StringToParameterized.each do |normal, slugged|
- assert_equal(normal.parameterize, slugged)
+ assert_equal(slugged, normal.parameterize)
end
end
def test_string_parameterized_normal_preserve_case
StringToParameterizedPreserveCase.each do |normal, slugged|
- assert_equal(normal.parameterize(preserve_case: true), slugged)
+ assert_equal(slugged, normal.parameterize(preserve_case: true))
end
end
def test_string_parameterized_no_separator
StringToParameterizeWithNoSeparator.each do |normal, slugged|
- assert_equal(normal.parameterize(separator: ""), slugged)
- end
- end
-
- def test_string_parameterized_no_separator_deprecated
- StringToParameterizeWithNoSeparator.each do |normal, slugged|
- assert_deprecated(/Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: ''` instead./i) do
- assert_equal(normal.parameterize(""), slugged)
- end
+ assert_equal(slugged, normal.parameterize(separator: ""))
end
end
def test_string_parameterized_no_separator_preserve_case
StringToParameterizePreserveCaseWithNoSeparator.each do |normal, slugged|
- assert_equal(normal.parameterize(separator: "", preserve_case: true), slugged)
+ assert_equal(slugged, normal.parameterize(separator: "", preserve_case: true))
end
end
def test_string_parameterized_underscore
StringToParameterizeWithUnderscore.each do |normal, slugged|
- assert_equal(normal.parameterize(separator: "_"), slugged)
- end
- end
-
- def test_string_parameterized_underscore_deprecated
- StringToParameterizeWithUnderscore.each do |normal, slugged|
- assert_deprecated(/Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '_'` instead./i) do
- assert_equal(normal.parameterize("_"), slugged)
- end
+ assert_equal(slugged, normal.parameterize(separator: "_"))
end
end
def test_string_parameterized_underscore_preserve_case
StringToParameterizePreserceCaseWithUnderscore.each do |normal, slugged|
- assert_equal(normal.parameterize(separator: "_", preserve_case: true), slugged)
+ assert_equal(slugged, normal.parameterize(separator: "_", preserve_case: true))
end
end
@@ -299,7 +283,7 @@ class StringInflectionsTest < ActiveSupport::TestCase
def test_truncate_words_with_complex_string
Timeout.timeout(10) do
complex_string = "aa aa aaa aa aaa aaa aaa aa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaaa aaaaa aaaaa aaaaaa aa aa aa aaa aa aaa aa aa aa aa a aaa aaa \n a aaa <<s"
- assert_equal complex_string.truncate_words(80), complex_string
+ assert_equal complex_string, complex_string.truncate_words(80)
end
rescue Timeout::Error
assert false
@@ -355,7 +339,7 @@ class StringAccessTest < ActiveSupport::TestCase
test "#at with Regex, returns the matching portion of the string" do
assert_equal "lo", "hello".at(/lo/)
- assert_equal nil, "hello".at(/nonexisting/)
+ assert_nil "hello".at(/nonexisting/)
end
test "#from with positive Integer, returns substring from the given position to the end" do
@@ -726,14 +710,14 @@ class OutputSafetyTest < ActiveSupport::TestCase
test "Prepending safe onto unsafe yields unsafe" do
@string.prepend "other".html_safe
assert !@string.html_safe?
- assert_equal @string, "otherhello"
+ assert_equal "otherhello", @string
end
test "Prepending unsafe onto safe yields escaped safe" do
other = "other".html_safe
other.prepend "<foo>"
assert other.html_safe?
- assert_equal other, "&lt;foo&gt;other"
+ assert_equal "&lt;foo&gt;other", other
end
test "Concatting safe onto unsafe yields unsafe" do
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index a6c9a7d5a8..a399e36dc9 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -4,32 +4,32 @@ require "core_ext/date_and_time_behavior"
require "time_zone_test_helpers"
class TimeExtCalculationsTest < ActiveSupport::TestCase
- def date_time_init(year,month,day,hour,minute,second,usec=0)
- Time.local(year,month,day,hour,minute,second,usec)
+ def date_time_init(year, month, day, hour, minute, second, usec = 0)
+ Time.local(year, month, day, hour, minute, second, usec)
end
include DateAndTimeBehavior
include TimeZoneTestHelpers
def test_seconds_since_midnight
- assert_equal 1,Time.local(2005,1,1,0,0,1).seconds_since_midnight
- assert_equal 60,Time.local(2005,1,1,0,1,0).seconds_since_midnight
- assert_equal 3660,Time.local(2005,1,1,1,1,0).seconds_since_midnight
- assert_equal 86399,Time.local(2005,1,1,23,59,59).seconds_since_midnight
- assert_equal 60.00001,Time.local(2005,1,1,0,1,0,10).seconds_since_midnight
+ assert_equal 1, Time.local(2005, 1, 1, 0, 0, 1).seconds_since_midnight
+ assert_equal 60, Time.local(2005, 1, 1, 0, 1, 0).seconds_since_midnight
+ assert_equal 3660, Time.local(2005, 1, 1, 1, 1, 0).seconds_since_midnight
+ assert_equal 86399, Time.local(2005, 1, 1, 23, 59, 59).seconds_since_midnight
+ assert_equal 60.00001, Time.local(2005, 1, 1, 0, 1, 0, 10).seconds_since_midnight
end
def test_seconds_since_midnight_at_daylight_savings_time_start
with_env_tz "US/Eastern" do
# dt: US: 2005 April 3rd 2:00am ST => April 3rd 3:00am DT
- assert_equal 2*3600-1, Time.local(2005,4,3,1,59,59).seconds_since_midnight, "just before DST start"
- assert_equal 2*3600+1, Time.local(2005,4,3,3, 0, 1).seconds_since_midnight, "just after DST start"
+ assert_equal 2 * 3600 - 1, Time.local(2005, 4, 3, 1, 59, 59).seconds_since_midnight, "just before DST start"
+ assert_equal 2 * 3600 + 1, Time.local(2005, 4, 3, 3, 0, 1).seconds_since_midnight, "just after DST start"
end
with_env_tz "NZ" do
# dt: New Zealand: 2006 October 1st 2:00am ST => October 1st 3:00am DT
- assert_equal 2*3600-1, Time.local(2006,10,1,1,59,59).seconds_since_midnight, "just before DST start"
- assert_equal 2*3600+1, Time.local(2006,10,1,3, 0, 1).seconds_since_midnight, "just after DST start"
+ assert_equal 2 * 3600 - 1, Time.local(2006, 10, 1, 1, 59, 59).seconds_since_midnight, "just before DST start"
+ assert_equal 2 * 3600 + 1, Time.local(2006, 10, 1, 3, 0, 1).seconds_since_midnight, "just after DST start"
end
end
@@ -37,47 +37,47 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
with_env_tz "US/Eastern" do
# st: US: 2005 October 30th 2:00am DT => October 30th 1:00am ST
# avoid setting a time between 1:00 and 2:00 since that requires specifying whether DST is active
- assert_equal 1*3600-1, Time.local(2005,10,30,0,59,59).seconds_since_midnight, "just before DST end"
- assert_equal 3*3600+1, Time.local(2005,10,30,2, 0, 1).seconds_since_midnight, "just after DST end"
+ assert_equal 1 * 3600 - 1, Time.local(2005, 10, 30, 0, 59, 59).seconds_since_midnight, "just before DST end"
+ assert_equal 3 * 3600 + 1, Time.local(2005, 10, 30, 2, 0, 1).seconds_since_midnight, "just after DST end"
# now set a time between 1:00 and 2:00 by specifying whether DST is active
# uses: Time.local( sec, min, hour, day, month, year, wday, yday, isdst, tz )
- assert_equal 1*3600+30*60, Time.local(0,30,1,30,10,2005,0,0,true,ENV["TZ"]).seconds_since_midnight, "before DST end"
- assert_equal 2*3600+30*60, Time.local(0,30,1,30,10,2005,0,0,false,ENV["TZ"]).seconds_since_midnight, "after DST end"
+ assert_equal 1 * 3600 + 30 * 60, Time.local(0, 30, 1, 30, 10, 2005, 0, 0, true, ENV["TZ"]).seconds_since_midnight, "before DST end"
+ assert_equal 2 * 3600 + 30 * 60, Time.local(0, 30, 1, 30, 10, 2005, 0, 0, false, ENV["TZ"]).seconds_since_midnight, "after DST end"
end
with_env_tz "NZ" do
# st: New Zealand: 2006 March 19th 3:00am DT => March 19th 2:00am ST
# avoid setting a time between 2:00 and 3:00 since that requires specifying whether DST is active
- assert_equal 2*3600-1, Time.local(2006,3,19,1,59,59).seconds_since_midnight, "just before DST end"
- assert_equal 4*3600+1, Time.local(2006,3,19,3, 0, 1).seconds_since_midnight, "just after DST end"
+ assert_equal 2 * 3600 - 1, Time.local(2006, 3, 19, 1, 59, 59).seconds_since_midnight, "just before DST end"
+ assert_equal 4 * 3600 + 1, Time.local(2006, 3, 19, 3, 0, 1).seconds_since_midnight, "just after DST end"
# now set a time between 2:00 and 3:00 by specifying whether DST is active
# uses: Time.local( sec, min, hour, day, month, year, wday, yday, isdst, tz )
- assert_equal 2*3600+30*60, Time.local(0,30,2,19,3,2006,0,0,true, ENV["TZ"]).seconds_since_midnight, "before DST end"
- assert_equal 3*3600+30*60, Time.local(0,30,2,19,3,2006,0,0,false,ENV["TZ"]).seconds_since_midnight, "after DST end"
+ assert_equal 2 * 3600 + 30 * 60, Time.local(0, 30, 2, 19, 3, 2006, 0, 0, true, ENV["TZ"]).seconds_since_midnight, "before DST end"
+ assert_equal 3 * 3600 + 30 * 60, Time.local(0, 30, 2, 19, 3, 2006, 0, 0, false, ENV["TZ"]).seconds_since_midnight, "after DST end"
end
end
def test_seconds_until_end_of_day
- assert_equal 0, Time.local(2005,1,1,23,59,59).seconds_until_end_of_day
- assert_equal 1, Time.local(2005,1,1,23,59,58).seconds_until_end_of_day
- assert_equal 60, Time.local(2005,1,1,23,58,59).seconds_until_end_of_day
- assert_equal 3660, Time.local(2005,1,1,22,58,59).seconds_until_end_of_day
- assert_equal 86399, Time.local(2005,1,1,0,0,0).seconds_until_end_of_day
+ assert_equal 0, Time.local(2005, 1, 1, 23, 59, 59).seconds_until_end_of_day
+ assert_equal 1, Time.local(2005, 1, 1, 23, 59, 58).seconds_until_end_of_day
+ assert_equal 60, Time.local(2005, 1, 1, 23, 58, 59).seconds_until_end_of_day
+ assert_equal 3660, Time.local(2005, 1, 1, 22, 58, 59).seconds_until_end_of_day
+ assert_equal 86399, Time.local(2005, 1, 1, 0, 0, 0).seconds_until_end_of_day
end
def test_seconds_until_end_of_day_at_daylight_savings_time_start
with_env_tz "US/Eastern" do
# dt: US: 2005 April 3rd 2:00am ST => April 3rd 3:00am DT
- assert_equal 21*3600, Time.local(2005,4,3,1,59,59).seconds_until_end_of_day, "just before DST start"
- assert_equal 21*3600-2, Time.local(2005,4,3,3,0,1).seconds_until_end_of_day, "just after DST start"
+ assert_equal 21 * 3600, Time.local(2005, 4, 3, 1, 59, 59).seconds_until_end_of_day, "just before DST start"
+ assert_equal 21 * 3600 - 2, Time.local(2005, 4, 3, 3, 0, 1).seconds_until_end_of_day, "just after DST start"
end
with_env_tz "NZ" do
# dt: New Zealand: 2006 October 1st 2:00am ST => October 1st 3:00am DT
- assert_equal 21*3600, Time.local(2006,10,1,1,59,59).seconds_until_end_of_day, "just before DST start"
- assert_equal 21*3600-2, Time.local(2006,10,1,3,0,1).seconds_until_end_of_day, "just after DST start"
+ assert_equal 21 * 3600, Time.local(2006, 10, 1, 1, 59, 59).seconds_until_end_of_day, "just before DST start"
+ assert_equal 21 * 3600 - 2, Time.local(2006, 10, 1, 3, 0, 1).seconds_until_end_of_day, "just after DST start"
end
end
@@ -85,83 +85,83 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
with_env_tz "US/Eastern" do
# st: US: 2005 October 30th 2:00am DT => October 30th 1:00am ST
# avoid setting a time between 1:00 and 2:00 since that requires specifying whether DST is active
- assert_equal 24*3600, Time.local(2005,10,30,0,59,59).seconds_until_end_of_day, "just before DST end"
- assert_equal 22*3600-2, Time.local(2005,10,30,2,0,1).seconds_until_end_of_day, "just after DST end"
+ assert_equal 24 * 3600, Time.local(2005, 10, 30, 0, 59, 59).seconds_until_end_of_day, "just before DST end"
+ assert_equal 22 * 3600 - 2, Time.local(2005, 10, 30, 2, 0, 1).seconds_until_end_of_day, "just after DST end"
# now set a time between 1:00 and 2:00 by specifying whether DST is active
# uses: Time.local( sec, min, hour, day, month, year, wday, yday, isdst, tz )
- assert_equal 24*3600-30*60-1, Time.local(0,30,1,30,10,2005,0,0,true,ENV["TZ"]).seconds_until_end_of_day, "before DST end"
- assert_equal 23*3600-30*60-1, Time.local(0,30,1,30,10,2005,0,0,false,ENV["TZ"]).seconds_until_end_of_day, "after DST end"
+ assert_equal 24 * 3600 - 30 * 60 - 1, Time.local(0, 30, 1, 30, 10, 2005, 0, 0, true, ENV["TZ"]).seconds_until_end_of_day, "before DST end"
+ assert_equal 23 * 3600 - 30 * 60 - 1, Time.local(0, 30, 1, 30, 10, 2005, 0, 0, false, ENV["TZ"]).seconds_until_end_of_day, "after DST end"
end
with_env_tz "NZ" do
# st: New Zealand: 2006 March 19th 3:00am DT => March 19th 2:00am ST
# avoid setting a time between 2:00 and 3:00 since that requires specifying whether DST is active
- assert_equal 23*3600, Time.local(2006,3,19,1,59,59).seconds_until_end_of_day, "just before DST end"
- assert_equal 21*3600-2, Time.local(2006,3,19,3,0,1).seconds_until_end_of_day, "just after DST end"
+ assert_equal 23 * 3600, Time.local(2006, 3, 19, 1, 59, 59).seconds_until_end_of_day, "just before DST end"
+ assert_equal 21 * 3600 - 2, Time.local(2006, 3, 19, 3, 0, 1).seconds_until_end_of_day, "just after DST end"
# now set a time between 2:00 and 3:00 by specifying whether DST is active
# uses: Time.local( sec, min, hour, day, month, year, wday, yday, isdst, tz )
- assert_equal 23*3600-30*60-1, Time.local(0,30,2,19,3,2006,0,0,true, ENV["TZ"]).seconds_until_end_of_day, "before DST end"
- assert_equal 22*3600-30*60-1, Time.local(0,30,2,19,3,2006,0,0,false,ENV["TZ"]).seconds_until_end_of_day, "after DST end"
+ assert_equal 23 * 3600 - 30 * 60 - 1, Time.local(0, 30, 2, 19, 3, 2006, 0, 0, true, ENV["TZ"]).seconds_until_end_of_day, "before DST end"
+ assert_equal 22 * 3600 - 30 * 60 - 1, Time.local(0, 30, 2, 19, 3, 2006, 0, 0, false, ENV["TZ"]).seconds_until_end_of_day, "after DST end"
end
end
def test_sec_fraction
- time = Time.utc(2016, 4, 23, 0, 0, Rational(1,10000000000))
- assert_equal Rational(1,10000000000), time.sec_fraction
+ time = Time.utc(2016, 4, 23, 0, 0, Rational(1, 10000000000))
+ assert_equal Rational(1, 10000000000), time.sec_fraction
time = Time.utc(2016, 4, 23, 0, 0, 0.0000000001)
assert_equal 0.0000000001.to_r, time.sec_fraction
- time = Time.utc(2016, 4, 23, 0, 0, 0, Rational(1,10000))
- assert_equal Rational(1,10000000000), time.sec_fraction
+ time = Time.utc(2016, 4, 23, 0, 0, 0, Rational(1, 10000))
+ assert_equal Rational(1, 10000000000), time.sec_fraction
time = Time.utc(2016, 4, 23, 0, 0, 0, 0.0001)
assert_equal 0.0001.to_r / 1000000, time.sec_fraction
end
def test_beginning_of_day
- assert_equal Time.local(2005,2,4,0,0,0), Time.local(2005,2,4,10,10,10).beginning_of_day
+ assert_equal Time.local(2005, 2, 4, 0, 0, 0), Time.local(2005, 2, 4, 10, 10, 10).beginning_of_day
with_env_tz "US/Eastern" do
- assert_equal Time.local(2006,4,2,0,0,0), Time.local(2006,4,2,10,10,10).beginning_of_day, "start DST"
- assert_equal Time.local(2006,10,29,0,0,0), Time.local(2006,10,29,10,10,10).beginning_of_day, "ends DST"
+ assert_equal Time.local(2006, 4, 2, 0, 0, 0), Time.local(2006, 4, 2, 10, 10, 10).beginning_of_day, "start DST"
+ assert_equal Time.local(2006, 10, 29, 0, 0, 0), Time.local(2006, 10, 29, 10, 10, 10).beginning_of_day, "ends DST"
end
with_env_tz "NZ" do
- assert_equal Time.local(2006,3,19,0,0,0), Time.local(2006,3,19,10,10,10).beginning_of_day, "ends DST"
- assert_equal Time.local(2006,10,1,0,0,0), Time.local(2006,10,1,10,10,10).beginning_of_day, "start DST"
+ assert_equal Time.local(2006, 3, 19, 0, 0, 0), Time.local(2006, 3, 19, 10, 10, 10).beginning_of_day, "ends DST"
+ assert_equal Time.local(2006, 10, 1, 0, 0, 0), Time.local(2006, 10, 1, 10, 10, 10).beginning_of_day, "start DST"
end
end
def test_middle_of_day
- assert_equal Time.local(2005,2,4,12,0,0), Time.local(2005,2,4,10,10,10).middle_of_day
+ assert_equal Time.local(2005, 2, 4, 12, 0, 0), Time.local(2005, 2, 4, 10, 10, 10).middle_of_day
with_env_tz "US/Eastern" do
- assert_equal Time.local(2006,4,2,12,0,0), Time.local(2006,4,2,10,10,10).middle_of_day, "start DST"
- assert_equal Time.local(2006,10,29,12,0,0), Time.local(2006,10,29,10,10,10).middle_of_day, "ends DST"
+ assert_equal Time.local(2006, 4, 2, 12, 0, 0), Time.local(2006, 4, 2, 10, 10, 10).middle_of_day, "start DST"
+ assert_equal Time.local(2006, 10, 29, 12, 0, 0), Time.local(2006, 10, 29, 10, 10, 10).middle_of_day, "ends DST"
end
with_env_tz "NZ" do
- assert_equal Time.local(2006,3,19,12,0,0), Time.local(2006,3,19,10,10,10).middle_of_day, "ends DST"
- assert_equal Time.local(2006,10,1,12,0,0), Time.local(2006,10,1,10,10,10).middle_of_day, "start DST"
+ assert_equal Time.local(2006, 3, 19, 12, 0, 0), Time.local(2006, 3, 19, 10, 10, 10).middle_of_day, "ends DST"
+ assert_equal Time.local(2006, 10, 1, 12, 0, 0), Time.local(2006, 10, 1, 10, 10, 10).middle_of_day, "start DST"
end
end
def test_beginning_of_hour
- assert_equal Time.local(2005,2,4,19,0,0), Time.local(2005,2,4,19,30,10).beginning_of_hour
+ assert_equal Time.local(2005, 2, 4, 19, 0, 0), Time.local(2005, 2, 4, 19, 30, 10).beginning_of_hour
end
def test_beginning_of_minute
- assert_equal Time.local(2005,2,4,19,30,0), Time.local(2005,2,4,19,30,10).beginning_of_minute
+ assert_equal Time.local(2005, 2, 4, 19, 30, 0), Time.local(2005, 2, 4, 19, 30, 10).beginning_of_minute
end
def test_end_of_day
- assert_equal Time.local(2007,8,12,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,12,10,10,10).end_of_day
+ assert_equal Time.local(2007, 8, 12, 23, 59, 59, Rational(999999999, 1000)), Time.local(2007, 8, 12, 10, 10, 10).end_of_day
with_env_tz "US/Eastern" do
- assert_equal Time.local(2007,4,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,4,2,10,10,10).end_of_day, "start DST"
- assert_equal Time.local(2007,10,29,23,59,59,Rational(999999999, 1000)), Time.local(2007,10,29,10,10,10).end_of_day, "ends DST"
+ assert_equal Time.local(2007, 4, 2, 23, 59, 59, Rational(999999999, 1000)), Time.local(2007, 4, 2, 10, 10, 10).end_of_day, "start DST"
+ assert_equal Time.local(2007, 10, 29, 23, 59, 59, Rational(999999999, 1000)), Time.local(2007, 10, 29, 10, 10, 10).end_of_day, "ends DST"
end
with_env_tz "NZ" do
- assert_equal Time.local(2006,3,19,23,59,59,Rational(999999999, 1000)), Time.local(2006,3,19,10,10,10).end_of_day, "ends DST"
- assert_equal Time.local(2006,10,1,23,59,59,Rational(999999999, 1000)), Time.local(2006,10,1,10,10,10).end_of_day, "start DST"
+ assert_equal Time.local(2006, 3, 19, 23, 59, 59, Rational(999999999, 1000)), Time.local(2006, 3, 19, 10, 10, 10).end_of_day, "ends DST"
+ assert_equal Time.local(2006, 10, 1, 23, 59, 59, Rational(999999999, 1000)), Time.local(2006, 10, 1, 10, 10, 10).end_of_day, "start DST"
end
with_env_tz "Asia/Yekaterinburg" do
assert_equal Time.local(2015, 2, 8, 23, 59, 59, Rational(999999999, 1000)), Time.new(2015, 2, 8, 8, 0, 0, "+05:00").end_of_day
@@ -169,334 +169,334 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_end_of_hour
- assert_equal Time.local(2005,2,4,19,59,59,Rational(999999999, 1000)), Time.local(2005,2,4,19,30,10).end_of_hour
+ assert_equal Time.local(2005, 2, 4, 19, 59, 59, Rational(999999999, 1000)), Time.local(2005, 2, 4, 19, 30, 10).end_of_hour
end
def test_end_of_minute
- assert_equal Time.local(2005,2,4,19,30,59,Rational(999999999, 1000)), Time.local(2005,2,4,19,30,10).end_of_minute
+ assert_equal Time.local(2005, 2, 4, 19, 30, 59, Rational(999999999, 1000)), Time.local(2005, 2, 4, 19, 30, 10).end_of_minute
end
def test_last_year
- assert_equal Time.local(2004,6,5,10), Time.local(2005,6,5,10,0,0).last_year
+ assert_equal Time.local(2004, 6, 5, 10), Time.local(2005, 6, 5, 10, 0, 0).last_year
end
def test_ago
- assert_equal Time.local(2005,2,22,10,10,9), Time.local(2005,2,22,10,10,10).ago(1)
- assert_equal Time.local(2005,2,22,9,10,10), Time.local(2005,2,22,10,10,10).ago(3600)
- assert_equal Time.local(2005,2,20,10,10,10), Time.local(2005,2,22,10,10,10).ago(86400*2)
- assert_equal Time.local(2005,2,20,9,9,45), Time.local(2005,2,22,10,10,10).ago(86400*2 + 3600 + 25)
+ assert_equal Time.local(2005, 2, 22, 10, 10, 9), Time.local(2005, 2, 22, 10, 10, 10).ago(1)
+ assert_equal Time.local(2005, 2, 22, 9, 10, 10), Time.local(2005, 2, 22, 10, 10, 10).ago(3600)
+ assert_equal Time.local(2005, 2, 20, 10, 10, 10), Time.local(2005, 2, 22, 10, 10, 10).ago(86400 * 2)
+ assert_equal Time.local(2005, 2, 20, 9, 9, 45), Time.local(2005, 2, 22, 10, 10, 10).ago(86400 * 2 + 3600 + 25)
end
def test_daylight_savings_time_crossings_backward_start
with_env_tz "US/Eastern" do
# dt: US: 2005 April 3rd 4:18am
- assert_equal Time.local(2005,4,2,3,18,0), Time.local(2005,4,3,4,18,0).ago(24.hours), "dt-24.hours=>st"
- assert_equal Time.local(2005,4,2,3,18,0), Time.local(2005,4,3,4,18,0).ago(86400), "dt-86400=>st"
- assert_equal Time.local(2005,4,2,3,18,0), Time.local(2005,4,3,4,18,0).ago(86400.seconds), "dt-86400.seconds=>st"
+ assert_equal Time.local(2005, 4, 2, 3, 18, 0), Time.local(2005, 4, 3, 4, 18, 0).ago(24.hours), "dt-24.hours=>st"
+ assert_equal Time.local(2005, 4, 2, 3, 18, 0), Time.local(2005, 4, 3, 4, 18, 0).ago(86400), "dt-86400=>st"
+ assert_equal Time.local(2005, 4, 2, 3, 18, 0), Time.local(2005, 4, 3, 4, 18, 0).ago(86400.seconds), "dt-86400.seconds=>st"
- assert_equal Time.local(2005,4,1,4,18,0), Time.local(2005,4,2,4,18,0).ago(24.hours), "st-24.hours=>st"
- assert_equal Time.local(2005,4,1,4,18,0), Time.local(2005,4,2,4,18,0).ago(86400), "st-86400=>st"
- assert_equal Time.local(2005,4,1,4,18,0), Time.local(2005,4,2,4,18,0).ago(86400.seconds), "st-86400.seconds=>st"
+ assert_equal Time.local(2005, 4, 1, 4, 18, 0), Time.local(2005, 4, 2, 4, 18, 0).ago(24.hours), "st-24.hours=>st"
+ assert_equal Time.local(2005, 4, 1, 4, 18, 0), Time.local(2005, 4, 2, 4, 18, 0).ago(86400), "st-86400=>st"
+ assert_equal Time.local(2005, 4, 1, 4, 18, 0), Time.local(2005, 4, 2, 4, 18, 0).ago(86400.seconds), "st-86400.seconds=>st"
end
with_env_tz "NZ" do
# dt: New Zealand: 2006 October 1st 4:18am
- assert_equal Time.local(2006,9,30,3,18,0), Time.local(2006,10,1,4,18,0).ago(24.hours), "dt-24.hours=>st"
- assert_equal Time.local(2006,9,30,3,18,0), Time.local(2006,10,1,4,18,0).ago(86400), "dt-86400=>st"
- assert_equal Time.local(2006,9,30,3,18,0), Time.local(2006,10,1,4,18,0).ago(86400.seconds), "dt-86400.seconds=>st"
+ assert_equal Time.local(2006, 9, 30, 3, 18, 0), Time.local(2006, 10, 1, 4, 18, 0).ago(24.hours), "dt-24.hours=>st"
+ assert_equal Time.local(2006, 9, 30, 3, 18, 0), Time.local(2006, 10, 1, 4, 18, 0).ago(86400), "dt-86400=>st"
+ assert_equal Time.local(2006, 9, 30, 3, 18, 0), Time.local(2006, 10, 1, 4, 18, 0).ago(86400.seconds), "dt-86400.seconds=>st"
- assert_equal Time.local(2006,9,29,4,18,0), Time.local(2006,9,30,4,18,0).ago(24.hours), "st-24.hours=>st"
- assert_equal Time.local(2006,9,29,4,18,0), Time.local(2006,9,30,4,18,0).ago(86400), "st-86400=>st"
- assert_equal Time.local(2006,9,29,4,18,0), Time.local(2006,9,30,4,18,0).ago(86400.seconds), "st-86400.seconds=>st"
+ assert_equal Time.local(2006, 9, 29, 4, 18, 0), Time.local(2006, 9, 30, 4, 18, 0).ago(24.hours), "st-24.hours=>st"
+ assert_equal Time.local(2006, 9, 29, 4, 18, 0), Time.local(2006, 9, 30, 4, 18, 0).ago(86400), "st-86400=>st"
+ assert_equal Time.local(2006, 9, 29, 4, 18, 0), Time.local(2006, 9, 30, 4, 18, 0).ago(86400.seconds), "st-86400.seconds=>st"
end
end
def test_daylight_savings_time_crossings_backward_end
with_env_tz "US/Eastern" do
# st: US: 2005 October 30th 4:03am
- assert_equal Time.local(2005,10,29,5,3), Time.local(2005,10,30,4,3,0).ago(24.hours), "st-24.hours=>dt"
- assert_equal Time.local(2005,10,29,5,3), Time.local(2005,10,30,4,3,0).ago(86400), "st-86400=>dt"
- assert_equal Time.local(2005,10,29,5,3), Time.local(2005,10,30,4,3,0).ago(86400.seconds), "st-86400.seconds=>dt"
+ assert_equal Time.local(2005, 10, 29, 5, 3), Time.local(2005, 10, 30, 4, 3, 0).ago(24.hours), "st-24.hours=>dt"
+ assert_equal Time.local(2005, 10, 29, 5, 3), Time.local(2005, 10, 30, 4, 3, 0).ago(86400), "st-86400=>dt"
+ assert_equal Time.local(2005, 10, 29, 5, 3), Time.local(2005, 10, 30, 4, 3, 0).ago(86400.seconds), "st-86400.seconds=>dt"
- assert_equal Time.local(2005,10,28,4,3), Time.local(2005,10,29,4,3,0).ago(24.hours), "dt-24.hours=>dt"
- assert_equal Time.local(2005,10,28,4,3), Time.local(2005,10,29,4,3,0).ago(86400), "dt-86400=>dt"
- assert_equal Time.local(2005,10,28,4,3), Time.local(2005,10,29,4,3,0).ago(86400.seconds), "dt-86400.seconds=>dt"
+ assert_equal Time.local(2005, 10, 28, 4, 3), Time.local(2005, 10, 29, 4, 3, 0).ago(24.hours), "dt-24.hours=>dt"
+ assert_equal Time.local(2005, 10, 28, 4, 3), Time.local(2005, 10, 29, 4, 3, 0).ago(86400), "dt-86400=>dt"
+ assert_equal Time.local(2005, 10, 28, 4, 3), Time.local(2005, 10, 29, 4, 3, 0).ago(86400.seconds), "dt-86400.seconds=>dt"
end
with_env_tz "NZ" do
# st: New Zealand: 2006 March 19th 4:03am
- assert_equal Time.local(2006,3,18,5,3), Time.local(2006,3,19,4,3,0).ago(24.hours), "st-24.hours=>dt"
- assert_equal Time.local(2006,3,18,5,3), Time.local(2006,3,19,4,3,0).ago(86400), "st-86400=>dt"
- assert_equal Time.local(2006,3,18,5,3), Time.local(2006,3,19,4,3,0).ago(86400.seconds), "st-86400.seconds=>dt"
+ assert_equal Time.local(2006, 3, 18, 5, 3), Time.local(2006, 3, 19, 4, 3, 0).ago(24.hours), "st-24.hours=>dt"
+ assert_equal Time.local(2006, 3, 18, 5, 3), Time.local(2006, 3, 19, 4, 3, 0).ago(86400), "st-86400=>dt"
+ assert_equal Time.local(2006, 3, 18, 5, 3), Time.local(2006, 3, 19, 4, 3, 0).ago(86400.seconds), "st-86400.seconds=>dt"
- assert_equal Time.local(2006,3,17,4,3), Time.local(2006,3,18,4,3,0).ago(24.hours), "dt-24.hours=>dt"
- assert_equal Time.local(2006,3,17,4,3), Time.local(2006,3,18,4,3,0).ago(86400), "dt-86400=>dt"
- assert_equal Time.local(2006,3,17,4,3), Time.local(2006,3,18,4,3,0).ago(86400.seconds), "dt-86400.seconds=>dt"
+ assert_equal Time.local(2006, 3, 17, 4, 3), Time.local(2006, 3, 18, 4, 3, 0).ago(24.hours), "dt-24.hours=>dt"
+ assert_equal Time.local(2006, 3, 17, 4, 3), Time.local(2006, 3, 18, 4, 3, 0).ago(86400), "dt-86400=>dt"
+ assert_equal Time.local(2006, 3, 17, 4, 3), Time.local(2006, 3, 18, 4, 3, 0).ago(86400.seconds), "dt-86400.seconds=>dt"
end
end
def test_daylight_savings_time_crossings_backward_start_1day
with_env_tz "US/Eastern" do
# dt: US: 2005 April 3rd 4:18am
- assert_equal Time.local(2005,4,2,4,18,0), Time.local(2005,4,3,4,18,0).ago(1.day), "dt-1.day=>st"
- assert_equal Time.local(2005,4,1,4,18,0), Time.local(2005,4,2,4,18,0).ago(1.day), "st-1.day=>st"
+ assert_equal Time.local(2005, 4, 2, 4, 18, 0), Time.local(2005, 4, 3, 4, 18, 0).ago(1.day), "dt-1.day=>st"
+ assert_equal Time.local(2005, 4, 1, 4, 18, 0), Time.local(2005, 4, 2, 4, 18, 0).ago(1.day), "st-1.day=>st"
end
with_env_tz "NZ" do
# dt: New Zealand: 2006 October 1st 4:18am
- assert_equal Time.local(2006,9,30,4,18,0), Time.local(2006,10,1,4,18,0).ago(1.day), "dt-1.day=>st"
- assert_equal Time.local(2006,9,29,4,18,0), Time.local(2006,9,30,4,18,0).ago(1.day), "st-1.day=>st"
+ assert_equal Time.local(2006, 9, 30, 4, 18, 0), Time.local(2006, 10, 1, 4, 18, 0).ago(1.day), "dt-1.day=>st"
+ assert_equal Time.local(2006, 9, 29, 4, 18, 0), Time.local(2006, 9, 30, 4, 18, 0).ago(1.day), "st-1.day=>st"
end
end
def test_daylight_savings_time_crossings_backward_end_1day
with_env_tz "US/Eastern" do
# st: US: 2005 October 30th 4:03am
- assert_equal Time.local(2005,10,29,4,3), Time.local(2005,10,30,4,3,0).ago(1.day), "st-1.day=>dt"
- assert_equal Time.local(2005,10,28,4,3), Time.local(2005,10,29,4,3,0).ago(1.day), "dt-1.day=>dt"
+ assert_equal Time.local(2005, 10, 29, 4, 3), Time.local(2005, 10, 30, 4, 3, 0).ago(1.day), "st-1.day=>dt"
+ assert_equal Time.local(2005, 10, 28, 4, 3), Time.local(2005, 10, 29, 4, 3, 0).ago(1.day), "dt-1.day=>dt"
end
with_env_tz "NZ" do
# st: New Zealand: 2006 March 19th 4:03am
- assert_equal Time.local(2006,3,18,4,3), Time.local(2006,3,19,4,3,0).ago(1.day), "st-1.day=>dt"
- assert_equal Time.local(2006,3,17,4,3), Time.local(2006,3,18,4,3,0).ago(1.day), "dt-1.day=>dt"
+ assert_equal Time.local(2006, 3, 18, 4, 3), Time.local(2006, 3, 19, 4, 3, 0).ago(1.day), "st-1.day=>dt"
+ assert_equal Time.local(2006, 3, 17, 4, 3), Time.local(2006, 3, 18, 4, 3, 0).ago(1.day), "dt-1.day=>dt"
end
end
def test_since
- assert_equal Time.local(2005,2,22,10,10,11), Time.local(2005,2,22,10,10,10).since(1)
- assert_equal Time.local(2005,2,22,11,10,10), Time.local(2005,2,22,10,10,10).since(3600)
- assert_equal Time.local(2005,2,24,10,10,10), Time.local(2005,2,22,10,10,10).since(86400*2)
- assert_equal Time.local(2005,2,24,11,10,35), Time.local(2005,2,22,10,10,10).since(86400*2 + 3600 + 25)
+ assert_equal Time.local(2005, 2, 22, 10, 10, 11), Time.local(2005, 2, 22, 10, 10, 10).since(1)
+ assert_equal Time.local(2005, 2, 22, 11, 10, 10), Time.local(2005, 2, 22, 10, 10, 10).since(3600)
+ assert_equal Time.local(2005, 2, 24, 10, 10, 10), Time.local(2005, 2, 22, 10, 10, 10).since(86400 * 2)
+ assert_equal Time.local(2005, 2, 24, 11, 10, 35), Time.local(2005, 2, 22, 10, 10, 10).since(86400 * 2 + 3600 + 25)
# when out of range of Time, returns a DateTime
- assert_equal DateTime.civil(2038,1,20,11,59,59), Time.utc(2038,1,18,11,59,59).since(86400*2)
+ assert_equal DateTime.civil(2038, 1, 20, 11, 59, 59), Time.utc(2038, 1, 18, 11, 59, 59).since(86400 * 2)
end
def test_daylight_savings_time_crossings_forward_start
with_env_tz "US/Eastern" do
# st: US: 2005 April 2nd 7:27pm
- assert_equal Time.local(2005,4,3,20,27,0), Time.local(2005,4,2,19,27,0).since(24.hours), "st+24.hours=>dt"
- assert_equal Time.local(2005,4,3,20,27,0), Time.local(2005,4,2,19,27,0).since(86400), "st+86400=>dt"
- assert_equal Time.local(2005,4,3,20,27,0), Time.local(2005,4,2,19,27,0).since(86400.seconds), "st+86400.seconds=>dt"
+ assert_equal Time.local(2005, 4, 3, 20, 27, 0), Time.local(2005, 4, 2, 19, 27, 0).since(24.hours), "st+24.hours=>dt"
+ assert_equal Time.local(2005, 4, 3, 20, 27, 0), Time.local(2005, 4, 2, 19, 27, 0).since(86400), "st+86400=>dt"
+ assert_equal Time.local(2005, 4, 3, 20, 27, 0), Time.local(2005, 4, 2, 19, 27, 0).since(86400.seconds), "st+86400.seconds=>dt"
- assert_equal Time.local(2005,4,4,19,27,0), Time.local(2005,4,3,19,27,0).since(24.hours), "dt+24.hours=>dt"
- assert_equal Time.local(2005,4,4,19,27,0), Time.local(2005,4,3,19,27,0).since(86400), "dt+86400=>dt"
- assert_equal Time.local(2005,4,4,19,27,0), Time.local(2005,4,3,19,27,0).since(86400.seconds), "dt+86400.seconds=>dt"
+ assert_equal Time.local(2005, 4, 4, 19, 27, 0), Time.local(2005, 4, 3, 19, 27, 0).since(24.hours), "dt+24.hours=>dt"
+ assert_equal Time.local(2005, 4, 4, 19, 27, 0), Time.local(2005, 4, 3, 19, 27, 0).since(86400), "dt+86400=>dt"
+ assert_equal Time.local(2005, 4, 4, 19, 27, 0), Time.local(2005, 4, 3, 19, 27, 0).since(86400.seconds), "dt+86400.seconds=>dt"
end
with_env_tz "NZ" do
# st: New Zealand: 2006 September 30th 7:27pm
- assert_equal Time.local(2006,10,1,20,27,0), Time.local(2006,9,30,19,27,0).since(24.hours), "st+24.hours=>dt"
- assert_equal Time.local(2006,10,1,20,27,0), Time.local(2006,9,30,19,27,0).since(86400), "st+86400=>dt"
- assert_equal Time.local(2006,10,1,20,27,0), Time.local(2006,9,30,19,27,0).since(86400.seconds), "st+86400.seconds=>dt"
+ assert_equal Time.local(2006, 10, 1, 20, 27, 0), Time.local(2006, 9, 30, 19, 27, 0).since(24.hours), "st+24.hours=>dt"
+ assert_equal Time.local(2006, 10, 1, 20, 27, 0), Time.local(2006, 9, 30, 19, 27, 0).since(86400), "st+86400=>dt"
+ assert_equal Time.local(2006, 10, 1, 20, 27, 0), Time.local(2006, 9, 30, 19, 27, 0).since(86400.seconds), "st+86400.seconds=>dt"
- assert_equal Time.local(2006,10,2,19,27,0), Time.local(2006,10,1,19,27,0).since(24.hours), "dt+24.hours=>dt"
- assert_equal Time.local(2006,10,2,19,27,0), Time.local(2006,10,1,19,27,0).since(86400), "dt+86400=>dt"
- assert_equal Time.local(2006,10,2,19,27,0), Time.local(2006,10,1,19,27,0).since(86400.seconds), "dt+86400.seconds=>dt"
+ assert_equal Time.local(2006, 10, 2, 19, 27, 0), Time.local(2006, 10, 1, 19, 27, 0).since(24.hours), "dt+24.hours=>dt"
+ assert_equal Time.local(2006, 10, 2, 19, 27, 0), Time.local(2006, 10, 1, 19, 27, 0).since(86400), "dt+86400=>dt"
+ assert_equal Time.local(2006, 10, 2, 19, 27, 0), Time.local(2006, 10, 1, 19, 27, 0).since(86400.seconds), "dt+86400.seconds=>dt"
end
end
def test_daylight_savings_time_crossings_forward_start_1day
with_env_tz "US/Eastern" do
# st: US: 2005 April 2nd 7:27pm
- assert_equal Time.local(2005,4,3,19,27,0), Time.local(2005,4,2,19,27,0).since(1.day), "st+1.day=>dt"
- assert_equal Time.local(2005,4,4,19,27,0), Time.local(2005,4,3,19,27,0).since(1.day), "dt+1.day=>dt"
+ assert_equal Time.local(2005, 4, 3, 19, 27, 0), Time.local(2005, 4, 2, 19, 27, 0).since(1.day), "st+1.day=>dt"
+ assert_equal Time.local(2005, 4, 4, 19, 27, 0), Time.local(2005, 4, 3, 19, 27, 0).since(1.day), "dt+1.day=>dt"
end
with_env_tz "NZ" do
# st: New Zealand: 2006 September 30th 7:27pm
- assert_equal Time.local(2006,10,1,19,27,0), Time.local(2006,9,30,19,27,0).since(1.day), "st+1.day=>dt"
- assert_equal Time.local(2006,10,2,19,27,0), Time.local(2006,10,1,19,27,0).since(1.day), "dt+1.day=>dt"
+ assert_equal Time.local(2006, 10, 1, 19, 27, 0), Time.local(2006, 9, 30, 19, 27, 0).since(1.day), "st+1.day=>dt"
+ assert_equal Time.local(2006, 10, 2, 19, 27, 0), Time.local(2006, 10, 1, 19, 27, 0).since(1.day), "dt+1.day=>dt"
end
end
def test_daylight_savings_time_crossings_forward_start_tomorrow
with_env_tz "US/Eastern" do
# st: US: 2005 April 2nd 7:27pm
- assert_equal Time.local(2005,4,3,19,27,0), Time.local(2005,4,2,19,27,0).tomorrow, "st+1.day=>dt"
- assert_equal Time.local(2005,4,4,19,27,0), Time.local(2005,4,3,19,27,0).tomorrow, "dt+1.day=>dt"
+ assert_equal Time.local(2005, 4, 3, 19, 27, 0), Time.local(2005, 4, 2, 19, 27, 0).tomorrow, "st+1.day=>dt"
+ assert_equal Time.local(2005, 4, 4, 19, 27, 0), Time.local(2005, 4, 3, 19, 27, 0).tomorrow, "dt+1.day=>dt"
end
with_env_tz "NZ" do
# st: New Zealand: 2006 September 30th 7:27pm
- assert_equal Time.local(2006,10,1,19,27,0), Time.local(2006,9,30,19,27,0).tomorrow, "st+1.day=>dt"
- assert_equal Time.local(2006,10,2,19,27,0), Time.local(2006,10,1,19,27,0).tomorrow, "dt+1.day=>dt"
+ assert_equal Time.local(2006, 10, 1, 19, 27, 0), Time.local(2006, 9, 30, 19, 27, 0).tomorrow, "st+1.day=>dt"
+ assert_equal Time.local(2006, 10, 2, 19, 27, 0), Time.local(2006, 10, 1, 19, 27, 0).tomorrow, "dt+1.day=>dt"
end
end
def test_daylight_savings_time_crossings_backward_start_yesterday
with_env_tz "US/Eastern" do
# st: US: 2005 April 2nd 7:27pm
- assert_equal Time.local(2005,4,2,19,27,0), Time.local(2005,4,3,19,27,0).yesterday, "dt-1.day=>st"
- assert_equal Time.local(2005,4,3,19,27,0), Time.local(2005,4,4,19,27,0).yesterday, "dt-1.day=>dt"
+ assert_equal Time.local(2005, 4, 2, 19, 27, 0), Time.local(2005, 4, 3, 19, 27, 0).yesterday, "dt-1.day=>st"
+ assert_equal Time.local(2005, 4, 3, 19, 27, 0), Time.local(2005, 4, 4, 19, 27, 0).yesterday, "dt-1.day=>dt"
end
with_env_tz "NZ" do
# st: New Zealand: 2006 September 30th 7:27pm
- assert_equal Time.local(2006,9,30,19,27,0), Time.local(2006,10,1,19,27,0).yesterday, "dt-1.day=>st"
- assert_equal Time.local(2006,10,1,19,27,0), Time.local(2006,10,2,19,27,0).yesterday, "dt-1.day=>dt"
+ assert_equal Time.local(2006, 9, 30, 19, 27, 0), Time.local(2006, 10, 1, 19, 27, 0).yesterday, "dt-1.day=>st"
+ assert_equal Time.local(2006, 10, 1, 19, 27, 0), Time.local(2006, 10, 2, 19, 27, 0).yesterday, "dt-1.day=>dt"
end
end
def test_daylight_savings_time_crossings_forward_end
with_env_tz "US/Eastern" do
# dt: US: 2005 October 30th 12:45am
- assert_equal Time.local(2005,10,30,23,45,0), Time.local(2005,10,30,0,45,0).since(24.hours), "dt+24.hours=>st"
- assert_equal Time.local(2005,10,30,23,45,0), Time.local(2005,10,30,0,45,0).since(86400), "dt+86400=>st"
- assert_equal Time.local(2005,10,30,23,45,0), Time.local(2005,10,30,0,45,0).since(86400.seconds), "dt+86400.seconds=>st"
+ assert_equal Time.local(2005, 10, 30, 23, 45, 0), Time.local(2005, 10, 30, 0, 45, 0).since(24.hours), "dt+24.hours=>st"
+ assert_equal Time.local(2005, 10, 30, 23, 45, 0), Time.local(2005, 10, 30, 0, 45, 0).since(86400), "dt+86400=>st"
+ assert_equal Time.local(2005, 10, 30, 23, 45, 0), Time.local(2005, 10, 30, 0, 45, 0).since(86400.seconds), "dt+86400.seconds=>st"
- assert_equal Time.local(2005,11, 1,0,45,0), Time.local(2005,10,31,0,45,0).since(24.hours), "st+24.hours=>st"
- assert_equal Time.local(2005,11, 1,0,45,0), Time.local(2005,10,31,0,45,0).since(86400), "st+86400=>st"
- assert_equal Time.local(2005,11, 1,0,45,0), Time.local(2005,10,31,0,45,0).since(86400.seconds), "st+86400.seconds=>st"
+ assert_equal Time.local(2005, 11, 1, 0, 45, 0), Time.local(2005, 10, 31, 0, 45, 0).since(24.hours), "st+24.hours=>st"
+ assert_equal Time.local(2005, 11, 1, 0, 45, 0), Time.local(2005, 10, 31, 0, 45, 0).since(86400), "st+86400=>st"
+ assert_equal Time.local(2005, 11, 1, 0, 45, 0), Time.local(2005, 10, 31, 0, 45, 0).since(86400.seconds), "st+86400.seconds=>st"
end
with_env_tz "NZ" do
# dt: New Zealand: 2006 March 19th 1:45am
- assert_equal Time.local(2006,3,20,0,45,0), Time.local(2006,3,19,1,45,0).since(24.hours), "dt+24.hours=>st"
- assert_equal Time.local(2006,3,20,0,45,0), Time.local(2006,3,19,1,45,0).since(86400), "dt+86400=>st"
- assert_equal Time.local(2006,3,20,0,45,0), Time.local(2006,3,19,1,45,0).since(86400.seconds), "dt+86400.seconds=>st"
+ assert_equal Time.local(2006, 3, 20, 0, 45, 0), Time.local(2006, 3, 19, 1, 45, 0).since(24.hours), "dt+24.hours=>st"
+ assert_equal Time.local(2006, 3, 20, 0, 45, 0), Time.local(2006, 3, 19, 1, 45, 0).since(86400), "dt+86400=>st"
+ assert_equal Time.local(2006, 3, 20, 0, 45, 0), Time.local(2006, 3, 19, 1, 45, 0).since(86400.seconds), "dt+86400.seconds=>st"
- assert_equal Time.local(2006,3,21,1,45,0), Time.local(2006,3,20,1,45,0).since(24.hours), "st+24.hours=>st"
- assert_equal Time.local(2006,3,21,1,45,0), Time.local(2006,3,20,1,45,0).since(86400), "st+86400=>st"
- assert_equal Time.local(2006,3,21,1,45,0), Time.local(2006,3,20,1,45,0).since(86400.seconds), "st+86400.seconds=>st"
+ assert_equal Time.local(2006, 3, 21, 1, 45, 0), Time.local(2006, 3, 20, 1, 45, 0).since(24.hours), "st+24.hours=>st"
+ assert_equal Time.local(2006, 3, 21, 1, 45, 0), Time.local(2006, 3, 20, 1, 45, 0).since(86400), "st+86400=>st"
+ assert_equal Time.local(2006, 3, 21, 1, 45, 0), Time.local(2006, 3, 20, 1, 45, 0).since(86400.seconds), "st+86400.seconds=>st"
end
end
def test_daylight_savings_time_crossings_forward_end_1day
with_env_tz "US/Eastern" do
# dt: US: 2005 October 30th 12:45am
- assert_equal Time.local(2005,10,31,0,45,0), Time.local(2005,10,30,0,45,0).since(1.day), "dt+1.day=>st"
- assert_equal Time.local(2005,11, 1,0,45,0), Time.local(2005,10,31,0,45,0).since(1.day), "st+1.day=>st"
+ assert_equal Time.local(2005, 10, 31, 0, 45, 0), Time.local(2005, 10, 30, 0, 45, 0).since(1.day), "dt+1.day=>st"
+ assert_equal Time.local(2005, 11, 1, 0, 45, 0), Time.local(2005, 10, 31, 0, 45, 0).since(1.day), "st+1.day=>st"
end
with_env_tz "NZ" do
# dt: New Zealand: 2006 March 19th 1:45am
- assert_equal Time.local(2006,3,20,1,45,0), Time.local(2006,3,19,1,45,0).since(1.day), "dt+1.day=>st"
- assert_equal Time.local(2006,3,21,1,45,0), Time.local(2006,3,20,1,45,0).since(1.day), "st+1.day=>st"
+ assert_equal Time.local(2006, 3, 20, 1, 45, 0), Time.local(2006, 3, 19, 1, 45, 0).since(1.day), "dt+1.day=>st"
+ assert_equal Time.local(2006, 3, 21, 1, 45, 0), Time.local(2006, 3, 20, 1, 45, 0).since(1.day), "st+1.day=>st"
end
end
def test_daylight_savings_time_crossings_forward_end_tomorrow
with_env_tz "US/Eastern" do
# dt: US: 2005 October 30th 12:45am
- assert_equal Time.local(2005,10,31,0,45,0), Time.local(2005,10,30,0,45,0).tomorrow, "dt+1.day=>st"
- assert_equal Time.local(2005,11, 1,0,45,0), Time.local(2005,10,31,0,45,0).tomorrow, "st+1.day=>st"
+ assert_equal Time.local(2005, 10, 31, 0, 45, 0), Time.local(2005, 10, 30, 0, 45, 0).tomorrow, "dt+1.day=>st"
+ assert_equal Time.local(2005, 11, 1, 0, 45, 0), Time.local(2005, 10, 31, 0, 45, 0).tomorrow, "st+1.day=>st"
end
with_env_tz "NZ" do
# dt: New Zealand: 2006 March 19th 1:45am
- assert_equal Time.local(2006,3,20,1,45,0), Time.local(2006,3,19,1,45,0).tomorrow, "dt+1.day=>st"
- assert_equal Time.local(2006,3,21,1,45,0), Time.local(2006,3,20,1,45,0).tomorrow, "st+1.day=>st"
+ assert_equal Time.local(2006, 3, 20, 1, 45, 0), Time.local(2006, 3, 19, 1, 45, 0).tomorrow, "dt+1.day=>st"
+ assert_equal Time.local(2006, 3, 21, 1, 45, 0), Time.local(2006, 3, 20, 1, 45, 0).tomorrow, "st+1.day=>st"
end
end
def test_daylight_savings_time_crossings_backward_end_yesterday
with_env_tz "US/Eastern" do
# dt: US: 2005 October 30th 12:45am
- assert_equal Time.local(2005,10,30,0,45,0), Time.local(2005,10,31,0,45,0).yesterday, "st-1.day=>dt"
- assert_equal Time.local(2005,10, 31,0,45,0), Time.local(2005,11,1,0,45,0).yesterday, "st-1.day=>st"
+ assert_equal Time.local(2005, 10, 30, 0, 45, 0), Time.local(2005, 10, 31, 0, 45, 0).yesterday, "st-1.day=>dt"
+ assert_equal Time.local(2005, 10, 31, 0, 45, 0), Time.local(2005, 11, 1, 0, 45, 0).yesterday, "st-1.day=>st"
end
with_env_tz "NZ" do
# dt: New Zealand: 2006 March 19th 1:45am
- assert_equal Time.local(2006,3,19,1,45,0), Time.local(2006,3,20,1,45,0).yesterday, "st-1.day=>dt"
- assert_equal Time.local(2006,3,20,1,45,0), Time.local(2006,3,21,1,45,0).yesterday, "st-1.day=>st"
+ assert_equal Time.local(2006, 3, 19, 1, 45, 0), Time.local(2006, 3, 20, 1, 45, 0).yesterday, "st-1.day=>dt"
+ assert_equal Time.local(2006, 3, 20, 1, 45, 0), Time.local(2006, 3, 21, 1, 45, 0).yesterday, "st-1.day=>st"
end
end
def test_change
- assert_equal Time.local(2006,2,22,15,15,10), Time.local(2005,2,22,15,15,10).change(year: 2006)
- assert_equal Time.local(2005,6,22,15,15,10), Time.local(2005,2,22,15,15,10).change(month: 6)
- assert_equal Time.local(2012,9,22,15,15,10), Time.local(2005,2,22,15,15,10).change(year: 2012, month: 9)
- assert_equal Time.local(2005,2,22,16), Time.local(2005,2,22,15,15,10).change(hour: 16)
- assert_equal Time.local(2005,2,22,16,45), Time.local(2005,2,22,15,15,10).change(hour: 16, min: 45)
- assert_equal Time.local(2005,2,22,15,45), Time.local(2005,2,22,15,15,10).change(min: 45)
-
- assert_equal Time.local(2005,1,2, 5, 0, 0, 0), Time.local(2005,1,2,11,22,33,44).change(hour: 5)
- assert_equal Time.local(2005,1,2,11, 6, 0, 0), Time.local(2005,1,2,11,22,33,44).change(min: 6)
- assert_equal Time.local(2005,1,2,11,22, 7, 0), Time.local(2005,1,2,11,22,33,44).change(sec: 7)
- assert_equal Time.local(2005,1,2,11,22,33, 8), Time.local(2005,1,2,11,22,33,44).change(usec: 8)
- assert_equal Time.local(2005,1,2,11,22,33, 8), Time.local(2005,1,2,11,22,33,2).change(nsec: 8000)
- assert_raise(ArgumentError) { Time.local(2005,1,2,11,22,33, 8).change(usec: 1, nsec: 1) }
+ assert_equal Time.local(2006, 2, 22, 15, 15, 10), Time.local(2005, 2, 22, 15, 15, 10).change(year: 2006)
+ assert_equal Time.local(2005, 6, 22, 15, 15, 10), Time.local(2005, 2, 22, 15, 15, 10).change(month: 6)
+ assert_equal Time.local(2012, 9, 22, 15, 15, 10), Time.local(2005, 2, 22, 15, 15, 10).change(year: 2012, month: 9)
+ assert_equal Time.local(2005, 2, 22, 16), Time.local(2005, 2, 22, 15, 15, 10).change(hour: 16)
+ assert_equal Time.local(2005, 2, 22, 16, 45), Time.local(2005, 2, 22, 15, 15, 10).change(hour: 16, min: 45)
+ assert_equal Time.local(2005, 2, 22, 15, 45), Time.local(2005, 2, 22, 15, 15, 10).change(min: 45)
+
+ assert_equal Time.local(2005, 1, 2, 5, 0, 0, 0), Time.local(2005, 1, 2, 11, 22, 33, 44).change(hour: 5)
+ assert_equal Time.local(2005, 1, 2, 11, 6, 0, 0), Time.local(2005, 1, 2, 11, 22, 33, 44).change(min: 6)
+ assert_equal Time.local(2005, 1, 2, 11, 22, 7, 0), Time.local(2005, 1, 2, 11, 22, 33, 44).change(sec: 7)
+ assert_equal Time.local(2005, 1, 2, 11, 22, 33, 8), Time.local(2005, 1, 2, 11, 22, 33, 44).change(usec: 8)
+ assert_equal Time.local(2005, 1, 2, 11, 22, 33, 8), Time.local(2005, 1, 2, 11, 22, 33, 2).change(nsec: 8000)
+ assert_raise(ArgumentError) { Time.local(2005, 1, 2, 11, 22, 33, 8).change(usec: 1, nsec: 1) }
assert_nothing_raised { Time.new(2015, 5, 9, 10, 00, 00, "+03:00").change(nsec: 999999999) }
end
def test_utc_change
- assert_equal Time.utc(2006,2,22,15,15,10), Time.utc(2005,2,22,15,15,10).change(year: 2006)
- assert_equal Time.utc(2005,6,22,15,15,10), Time.utc(2005,2,22,15,15,10).change(month: 6)
- assert_equal Time.utc(2012,9,22,15,15,10), Time.utc(2005,2,22,15,15,10).change(year: 2012, month: 9)
- assert_equal Time.utc(2005,2,22,16), Time.utc(2005,2,22,15,15,10).change(hour: 16)
- assert_equal Time.utc(2005,2,22,16,45), Time.utc(2005,2,22,15,15,10).change(hour: 16, min: 45)
- assert_equal Time.utc(2005,2,22,15,45), Time.utc(2005,2,22,15,15,10).change(min: 45)
- assert_equal Time.utc(2005,1,2,11,22,33,8), Time.utc(2005,1,2,11,22,33,2).change(nsec: 8000)
+ assert_equal Time.utc(2006, 2, 22, 15, 15, 10), Time.utc(2005, 2, 22, 15, 15, 10).change(year: 2006)
+ assert_equal Time.utc(2005, 6, 22, 15, 15, 10), Time.utc(2005, 2, 22, 15, 15, 10).change(month: 6)
+ assert_equal Time.utc(2012, 9, 22, 15, 15, 10), Time.utc(2005, 2, 22, 15, 15, 10).change(year: 2012, month: 9)
+ assert_equal Time.utc(2005, 2, 22, 16), Time.utc(2005, 2, 22, 15, 15, 10).change(hour: 16)
+ assert_equal Time.utc(2005, 2, 22, 16, 45), Time.utc(2005, 2, 22, 15, 15, 10).change(hour: 16, min: 45)
+ assert_equal Time.utc(2005, 2, 22, 15, 45), Time.utc(2005, 2, 22, 15, 15, 10).change(min: 45)
+ assert_equal Time.utc(2005, 1, 2, 11, 22, 33, 8), Time.utc(2005, 1, 2, 11, 22, 33, 2).change(nsec: 8000)
end
def test_offset_change
- assert_equal Time.new(2006,2,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(year: 2006)
- assert_equal Time.new(2005,6,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(month: 6)
- assert_equal Time.new(2012,9,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(year: 2012, month: 9)
- assert_equal Time.new(2005,2,22,16,0,0,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(hour: 16)
- assert_equal Time.new(2005,2,22,16,45,0,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(hour: 16, min: 45)
- assert_equal Time.new(2005,2,22,15,45,0,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(min: 45)
- assert_equal Time.new(2005,2,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,0,"-08:00").change(sec: 10)
- assert_equal 10, Time.new(2005,2,22,15,15,0,"-08:00").change(usec: 10).usec
- assert_equal 10, Time.new(2005,2,22,15,15,0,"-08:00").change(nsec: 10).nsec
+ assert_equal Time.new(2006, 2, 22, 15, 15, 10, "-08:00"), Time.new(2005, 2, 22, 15, 15, 10, "-08:00").change(year: 2006)
+ assert_equal Time.new(2005, 6, 22, 15, 15, 10, "-08:00"), Time.new(2005, 2, 22, 15, 15, 10, "-08:00").change(month: 6)
+ assert_equal Time.new(2012, 9, 22, 15, 15, 10, "-08:00"), Time.new(2005, 2, 22, 15, 15, 10, "-08:00").change(year: 2012, month: 9)
+ assert_equal Time.new(2005, 2, 22, 16, 0, 0, "-08:00"), Time.new(2005, 2, 22, 15, 15, 10, "-08:00").change(hour: 16)
+ assert_equal Time.new(2005, 2, 22, 16, 45, 0, "-08:00"), Time.new(2005, 2, 22, 15, 15, 10, "-08:00").change(hour: 16, min: 45)
+ assert_equal Time.new(2005, 2, 22, 15, 45, 0, "-08:00"), Time.new(2005, 2, 22, 15, 15, 10, "-08:00").change(min: 45)
+ assert_equal Time.new(2005, 2, 22, 15, 15, 10, "-08:00"), Time.new(2005, 2, 22, 15, 15, 0, "-08:00").change(sec: 10)
+ assert_equal 10, Time.new(2005, 2, 22, 15, 15, 0, "-08:00").change(usec: 10).usec
+ assert_equal 10, Time.new(2005, 2, 22, 15, 15, 0, "-08:00").change(nsec: 10).nsec
assert_raise(ArgumentError) { Time.new(2005, 2, 22, 15, 15, 45, "-08:00").change(usec: 1000000) }
assert_raise(ArgumentError) { Time.new(2005, 2, 22, 15, 15, 45, "-08:00").change(nsec: 1000000000) }
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)
- assert_equal Time.local(2005,3,21,15,15,10), Time.local(2005,2,28,15,15,10).advance(weeks: 3)
- assert_equal Time.local(2005,3,25,3,15,10), Time.local(2005,2,28,15,15,10).advance(weeks: 3.5)
- assert_in_delta Time.local(2005,3,26,12,51,10), Time.local(2005,2,28,15,15,10).advance(weeks: 3.7), 1
- assert_equal Time.local(2005,3,5,15,15,10), Time.local(2005,2,28,15,15,10).advance(days: 5)
- assert_equal Time.local(2005,3,6,3,15,10), Time.local(2005,2,28,15,15,10).advance(days: 5.5)
- assert_in_delta Time.local(2005,3,6,8,3,10), Time.local(2005,2,28,15,15,10).advance(days: 5.7), 1
- assert_equal Time.local(2012,9,28,15,15,10), Time.local(2005,2,28,15,15,10).advance(years: 7, months: 7)
- assert_equal Time.local(2013,10,3,15,15,10), Time.local(2005,2,28,15,15,10).advance(years: 7, months: 19, days: 5)
- assert_equal Time.local(2013,10,17,15,15,10), Time.local(2005,2,28,15,15,10).advance(years: 7, months: 19, weeks: 2, days: 5)
- assert_equal Time.local(2001,12,27,15,15,10), Time.local(2005,2,28,15,15,10).advance(years: -3, months: -2, days: -1)
- assert_equal Time.local(2005,2,28,15,15,10), Time.local(2004,2,29,15,15,10).advance(years: 1) #leap day plus one year
- assert_equal Time.local(2005,2,28,20,15,10), Time.local(2005,2,28,15,15,10).advance(hours: 5)
- assert_equal Time.local(2005,2,28,15,22,10), Time.local(2005,2,28,15,15,10).advance(minutes: 7)
- assert_equal Time.local(2005,2,28,15,15,19), Time.local(2005,2,28,15,15,10).advance(seconds: 9)
- assert_equal Time.local(2005,2,28,20,22,19), Time.local(2005,2,28,15,15,10).advance(hours: 5, minutes: 7, seconds: 9)
- assert_equal Time.local(2005,2,28,10,8,1), Time.local(2005,2,28,15,15,10).advance(hours: -5, minutes: -7, seconds: -9)
- assert_equal Time.local(2013,10,17,20,22,19), Time.local(2005,2,28,15,15,10).advance(years: 7, months: 19, weeks: 2, days: 5, hours: 5, minutes: 7, seconds: 9)
+ 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)
+ assert_equal Time.local(2005, 3, 21, 15, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(weeks: 3)
+ assert_equal Time.local(2005, 3, 25, 3, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(weeks: 3.5)
+ assert_in_delta Time.local(2005, 3, 26, 12, 51, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(weeks: 3.7), 1
+ assert_equal Time.local(2005, 3, 5, 15, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(days: 5)
+ assert_equal Time.local(2005, 3, 6, 3, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(days: 5.5)
+ assert_in_delta Time.local(2005, 3, 6, 8, 3, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(days: 5.7), 1
+ assert_equal Time.local(2012, 9, 28, 15, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(years: 7, months: 7)
+ assert_equal Time.local(2013, 10, 3, 15, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(years: 7, months: 19, days: 5)
+ assert_equal Time.local(2013, 10, 17, 15, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(years: 7, months: 19, weeks: 2, days: 5)
+ assert_equal Time.local(2001, 12, 27, 15, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(years: -3, months: -2, days: -1)
+ assert_equal Time.local(2005, 2, 28, 15, 15, 10), Time.local(2004, 2, 29, 15, 15, 10).advance(years: 1) #leap day plus one year
+ assert_equal Time.local(2005, 2, 28, 20, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(hours: 5)
+ assert_equal Time.local(2005, 2, 28, 15, 22, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(minutes: 7)
+ assert_equal Time.local(2005, 2, 28, 15, 15, 19), Time.local(2005, 2, 28, 15, 15, 10).advance(seconds: 9)
+ assert_equal Time.local(2005, 2, 28, 20, 22, 19), Time.local(2005, 2, 28, 15, 15, 10).advance(hours: 5, minutes: 7, seconds: 9)
+ assert_equal Time.local(2005, 2, 28, 10, 8, 1), Time.local(2005, 2, 28, 15, 15, 10).advance(hours: -5, minutes: -7, seconds: -9)
+ assert_equal Time.local(2013, 10, 17, 20, 22, 19), Time.local(2005, 2, 28, 15, 15, 10).advance(years: 7, months: 19, weeks: 2, days: 5, hours: 5, minutes: 7, seconds: 9)
end
def test_utc_advance
- assert_equal Time.utc(2006,2,22,15,15,10), Time.utc(2005,2,22,15,15,10).advance(years: 1)
- assert_equal Time.utc(2005,6,22,15,15,10), Time.utc(2005,2,22,15,15,10).advance(months: 4)
- assert_equal Time.utc(2005,3,21,15,15,10), Time.utc(2005,2,28,15,15,10).advance(weeks: 3)
- assert_equal Time.utc(2005,3,25,3,15,10), Time.utc(2005,2,28,15,15,10).advance(weeks: 3.5)
- assert_in_delta Time.utc(2005,3,26,12,51,10), Time.utc(2005,2,28,15,15,10).advance(weeks: 3.7), 1
- assert_equal Time.utc(2005,3,5,15,15,10), Time.utc(2005,2,28,15,15,10).advance(days: 5)
- assert_equal Time.utc(2005,3,6,3,15,10), Time.utc(2005,2,28,15,15,10).advance(days: 5.5)
- assert_in_delta Time.utc(2005,3,6,8,3,10), Time.utc(2005,2,28,15,15,10).advance(days: 5.7), 1
- assert_equal Time.utc(2012,9,22,15,15,10), Time.utc(2005,2,22,15,15,10).advance(years: 7, months: 7)
- assert_equal Time.utc(2013,10,3,15,15,10), Time.utc(2005,2,22,15,15,10).advance(years: 7, months: 19, days: 11)
- assert_equal Time.utc(2013,10,17,15,15,10), Time.utc(2005,2,28,15,15,10).advance(years: 7, months: 19, weeks: 2, days: 5)
- assert_equal Time.utc(2001,12,27,15,15,10), Time.utc(2005,2,28,15,15,10).advance(years: -3, months: -2, days: -1)
- assert_equal Time.utc(2005,2,28,15,15,10), Time.utc(2004,2,29,15,15,10).advance(years: 1) #leap day plus one year
- assert_equal Time.utc(2005,2,28,20,15,10), Time.utc(2005,2,28,15,15,10).advance(hours: 5)
- assert_equal Time.utc(2005,2,28,15,22,10), Time.utc(2005,2,28,15,15,10).advance(minutes: 7)
- assert_equal Time.utc(2005,2,28,15,15,19), Time.utc(2005,2,28,15,15,10).advance(seconds: 9)
- assert_equal Time.utc(2005,2,28,20,22,19), Time.utc(2005,2,28,15,15,10).advance(hours: 5, minutes: 7, seconds: 9)
- assert_equal Time.utc(2005,2,28,10,8,1), Time.utc(2005,2,28,15,15,10).advance(hours: -5, minutes: -7, seconds: -9)
- assert_equal Time.utc(2013,10,17,20,22,19), Time.utc(2005,2,28,15,15,10).advance(years: 7, months: 19, weeks: 2, days: 5, hours: 5, minutes: 7, seconds: 9)
+ assert_equal Time.utc(2006, 2, 22, 15, 15, 10), Time.utc(2005, 2, 22, 15, 15, 10).advance(years: 1)
+ assert_equal Time.utc(2005, 6, 22, 15, 15, 10), Time.utc(2005, 2, 22, 15, 15, 10).advance(months: 4)
+ assert_equal Time.utc(2005, 3, 21, 15, 15, 10), Time.utc(2005, 2, 28, 15, 15, 10).advance(weeks: 3)
+ assert_equal Time.utc(2005, 3, 25, 3, 15, 10), Time.utc(2005, 2, 28, 15, 15, 10).advance(weeks: 3.5)
+ assert_in_delta Time.utc(2005, 3, 26, 12, 51, 10), Time.utc(2005, 2, 28, 15, 15, 10).advance(weeks: 3.7), 1
+ assert_equal Time.utc(2005, 3, 5, 15, 15, 10), Time.utc(2005, 2, 28, 15, 15, 10).advance(days: 5)
+ assert_equal Time.utc(2005, 3, 6, 3, 15, 10), Time.utc(2005, 2, 28, 15, 15, 10).advance(days: 5.5)
+ assert_in_delta Time.utc(2005, 3, 6, 8, 3, 10), Time.utc(2005, 2, 28, 15, 15, 10).advance(days: 5.7), 1
+ assert_equal Time.utc(2012, 9, 22, 15, 15, 10), Time.utc(2005, 2, 22, 15, 15, 10).advance(years: 7, months: 7)
+ assert_equal Time.utc(2013, 10, 3, 15, 15, 10), Time.utc(2005, 2, 22, 15, 15, 10).advance(years: 7, months: 19, days: 11)
+ assert_equal Time.utc(2013, 10, 17, 15, 15, 10), Time.utc(2005, 2, 28, 15, 15, 10).advance(years: 7, months: 19, weeks: 2, days: 5)
+ assert_equal Time.utc(2001, 12, 27, 15, 15, 10), Time.utc(2005, 2, 28, 15, 15, 10).advance(years: -3, months: -2, days: -1)
+ assert_equal Time.utc(2005, 2, 28, 15, 15, 10), Time.utc(2004, 2, 29, 15, 15, 10).advance(years: 1) #leap day plus one year
+ assert_equal Time.utc(2005, 2, 28, 20, 15, 10), Time.utc(2005, 2, 28, 15, 15, 10).advance(hours: 5)
+ assert_equal Time.utc(2005, 2, 28, 15, 22, 10), Time.utc(2005, 2, 28, 15, 15, 10).advance(minutes: 7)
+ assert_equal Time.utc(2005, 2, 28, 15, 15, 19), Time.utc(2005, 2, 28, 15, 15, 10).advance(seconds: 9)
+ assert_equal Time.utc(2005, 2, 28, 20, 22, 19), Time.utc(2005, 2, 28, 15, 15, 10).advance(hours: 5, minutes: 7, seconds: 9)
+ assert_equal Time.utc(2005, 2, 28, 10, 8, 1), Time.utc(2005, 2, 28, 15, 15, 10).advance(hours: -5, minutes: -7, seconds: -9)
+ assert_equal Time.utc(2013, 10, 17, 20, 22, 19), Time.utc(2005, 2, 28, 15, 15, 10).advance(years: 7, months: 19, weeks: 2, days: 5, hours: 5, minutes: 7, seconds: 9)
end
def test_offset_advance
- assert_equal Time.new(2006,2,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").advance(years: 1)
- assert_equal Time.new(2005,6,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").advance(months: 4)
- assert_equal Time.new(2005,3,21,15,15,10,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(weeks: 3)
- assert_equal Time.new(2005,3,25,3,15,10,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(weeks: 3.5)
- assert_in_delta Time.new(2005,3,26,12,51,10,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(weeks: 3.7), 1
- assert_equal Time.new(2005,3,5,15,15,10,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(days: 5)
- assert_equal Time.new(2005,3,6,3,15,10,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(days: 5.5)
- assert_in_delta Time.new(2005,3,6,8,3,10,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(days: 5.7), 1
- assert_equal Time.new(2012,9,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").advance(years: 7, months: 7)
- assert_equal Time.new(2013,10,3,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").advance(years: 7, months: 19, days: 11)
- assert_equal Time.new(2013,10,17,15,15,10,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(years: 7, months: 19, weeks: 2, days: 5)
- assert_equal Time.new(2001,12,27,15,15,10,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(years: -3, months: -2, days: -1)
- assert_equal Time.new(2005,2,28,15,15,10,"-08:00"), Time.new(2004,2,29,15,15,10,"-08:00").advance(years: 1) #leap day plus one year
- assert_equal Time.new(2005,2,28,20,15,10,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(hours: 5)
- assert_equal Time.new(2005,2,28,15,22,10,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(minutes: 7)
- assert_equal Time.new(2005,2,28,15,15,19,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(seconds: 9)
- assert_equal Time.new(2005,2,28,20,22,19,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(hours: 5, minutes: 7, seconds: 9)
- assert_equal Time.new(2005,2,28,10,8,1,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(hours: -5, minutes: -7, seconds: -9)
- assert_equal Time.new(2013,10,17,20,22,19,"-08:00"), Time.new(2005,2,28,15,15,10,"-08:00").advance(years: 7, months: 19, weeks: 2, days: 5, hours: 5, minutes: 7, seconds: 9)
+ assert_equal Time.new(2006, 2, 22, 15, 15, 10, "-08:00"), Time.new(2005, 2, 22, 15, 15, 10, "-08:00").advance(years: 1)
+ assert_equal Time.new(2005, 6, 22, 15, 15, 10, "-08:00"), Time.new(2005, 2, 22, 15, 15, 10, "-08:00").advance(months: 4)
+ assert_equal Time.new(2005, 3, 21, 15, 15, 10, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(weeks: 3)
+ assert_equal Time.new(2005, 3, 25, 3, 15, 10, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(weeks: 3.5)
+ assert_in_delta Time.new(2005, 3, 26, 12, 51, 10, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(weeks: 3.7), 1
+ assert_equal Time.new(2005, 3, 5, 15, 15, 10, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(days: 5)
+ assert_equal Time.new(2005, 3, 6, 3, 15, 10, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(days: 5.5)
+ assert_in_delta Time.new(2005, 3, 6, 8, 3, 10, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(days: 5.7), 1
+ assert_equal Time.new(2012, 9, 22, 15, 15, 10, "-08:00"), Time.new(2005, 2, 22, 15, 15, 10, "-08:00").advance(years: 7, months: 7)
+ assert_equal Time.new(2013, 10, 3, 15, 15, 10, "-08:00"), Time.new(2005, 2, 22, 15, 15, 10, "-08:00").advance(years: 7, months: 19, days: 11)
+ assert_equal Time.new(2013, 10, 17, 15, 15, 10, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(years: 7, months: 19, weeks: 2, days: 5)
+ assert_equal Time.new(2001, 12, 27, 15, 15, 10, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(years: -3, months: -2, days: -1)
+ assert_equal Time.new(2005, 2, 28, 15, 15, 10, "-08:00"), Time.new(2004, 2, 29, 15, 15, 10, "-08:00").advance(years: 1) #leap day plus one year
+ assert_equal Time.new(2005, 2, 28, 20, 15, 10, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(hours: 5)
+ assert_equal Time.new(2005, 2, 28, 15, 22, 10, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(minutes: 7)
+ assert_equal Time.new(2005, 2, 28, 15, 15, 19, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(seconds: 9)
+ assert_equal Time.new(2005, 2, 28, 20, 22, 19, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(hours: 5, minutes: 7, seconds: 9)
+ assert_equal Time.new(2005, 2, 28, 10, 8, 1, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(hours: -5, minutes: -7, seconds: -9)
+ assert_equal Time.new(2013, 10, 17, 20, 22, 19, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(years: 7, months: 19, weeks: 2, days: 5, hours: 5, minutes: 7, seconds: 9)
end
def test_advance_with_nsec
@@ -505,37 +505,37 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_advance_gregorian_proleptic
- assert_equal Time.local(1582,10,14,15,15,10), Time.local(1582,10,15,15,15,10).advance(days: -1)
- assert_equal Time.local(1582,10,15,15,15,10), Time.local(1582,10,14,15,15,10).advance(days: 1)
- assert_equal Time.local(1582,10,5,15,15,10), Time.local(1582,10,4,15,15,10).advance(days: 1)
- assert_equal Time.local(1582,10,4,15,15,10), Time.local(1582,10,5,15,15,10).advance(days: -1)
+ assert_equal Time.local(1582, 10, 14, 15, 15, 10), Time.local(1582, 10, 15, 15, 15, 10).advance(days: -1)
+ assert_equal Time.local(1582, 10, 15, 15, 15, 10), Time.local(1582, 10, 14, 15, 15, 10).advance(days: 1)
+ assert_equal Time.local(1582, 10, 5, 15, 15, 10), Time.local(1582, 10, 4, 15, 15, 10).advance(days: 1)
+ assert_equal Time.local(1582, 10, 4, 15, 15, 10), Time.local(1582, 10, 5, 15, 15, 10).advance(days: -1)
end
def test_last_week
with_env_tz "US/Eastern" do
- assert_equal Time.local(2005,2,21), Time.local(2005,3,1,15,15,10).last_week
- assert_equal Time.local(2005,2,22), Time.local(2005,3,1,15,15,10).last_week(:tuesday)
- assert_equal Time.local(2005,2,25), Time.local(2005,3,1,15,15,10).last_week(:friday)
- assert_equal Time.local(2006,10,30), Time.local(2006,11,6,0,0,0).last_week
- assert_equal Time.local(2006,11,15), Time.local(2006,11,23,0,0,0).last_week(:wednesday)
+ assert_equal Time.local(2005, 2, 21), Time.local(2005, 3, 1, 15, 15, 10).last_week
+ assert_equal Time.local(2005, 2, 22), Time.local(2005, 3, 1, 15, 15, 10).last_week(:tuesday)
+ assert_equal Time.local(2005, 2, 25), Time.local(2005, 3, 1, 15, 15, 10).last_week(:friday)
+ assert_equal Time.local(2006, 10, 30), Time.local(2006, 11, 6, 0, 0, 0).last_week
+ assert_equal Time.local(2006, 11, 15), Time.local(2006, 11, 23, 0, 0, 0).last_week(:wednesday)
end
end
def test_next_week_near_daylight_start
with_env_tz "US/Eastern" do
- assert_equal Time.local(2006,4,3), Time.local(2006,4,2,23,1,0).next_week, "just crossed standard => daylight"
+ assert_equal Time.local(2006, 4, 3), Time.local(2006, 4, 2, 23, 1, 0).next_week, "just crossed standard => daylight"
end
with_env_tz "NZ" do
- assert_equal Time.local(2006,10,2), Time.local(2006,10,1,23,1,0).next_week, "just crossed standard => daylight"
+ assert_equal Time.local(2006, 10, 2), Time.local(2006, 10, 1, 23, 1, 0).next_week, "just crossed standard => daylight"
end
end
def test_next_week_near_daylight_end
with_env_tz "US/Eastern" do
- assert_equal Time.local(2006,10,30), Time.local(2006,10,29,23,1,0).next_week, "just crossed daylight => standard"
+ assert_equal Time.local(2006, 10, 30), Time.local(2006, 10, 29, 23, 1, 0).next_week, "just crossed daylight => standard"
end
with_env_tz "NZ" do
- assert_equal Time.local(2006,3,20), Time.local(2006,3,19,23,1,0).next_week, "just crossed daylight => standard"
+ assert_equal Time.local(2006, 3, 20), Time.local(2006, 3, 19, 23, 1, 0).next_week, "just crossed daylight => standard"
end
end
@@ -660,72 +660,72 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
def test_today_with_time_local
Date.stub(:current, Date.new(2000, 1, 1)) do
- assert_equal false, Time.local(1999,12,31,23,59,59).today?
- assert_equal true, Time.local(2000,1,1,0).today?
- assert_equal true, Time.local(2000,1,1,23,59,59).today?
- assert_equal false, Time.local(2000,1,2,0).today?
+ assert_equal false, Time.local(1999, 12, 31, 23, 59, 59).today?
+ assert_equal true, Time.local(2000, 1, 1, 0).today?
+ assert_equal true, Time.local(2000, 1, 1, 23, 59, 59).today?
+ assert_equal false, Time.local(2000, 1, 2, 0).today?
end
end
def test_today_with_time_utc
Date.stub(:current, Date.new(2000, 1, 1)) do
- assert_equal false, Time.utc(1999,12,31,23,59,59).today?
- assert_equal true, Time.utc(2000,1,1,0).today?
- assert_equal true, Time.utc(2000,1,1,23,59,59).today?
- assert_equal false, Time.utc(2000,1,2,0).today?
+ assert_equal false, Time.utc(1999, 12, 31, 23, 59, 59).today?
+ assert_equal true, Time.utc(2000, 1, 1, 0).today?
+ assert_equal true, Time.utc(2000, 1, 1, 23, 59, 59).today?
+ assert_equal false, Time.utc(2000, 1, 2, 0).today?
end
end
def test_past_with_time_current_as_time_local
with_env_tz "US/Eastern" do
- Time.stub(:current, Time.local(2005,2,10,15,30,45)) do
- assert_equal true, Time.local(2005,2,10,15,30,44).past?
- assert_equal false, Time.local(2005,2,10,15,30,45).past?
- assert_equal false, Time.local(2005,2,10,15,30,46).past?
- assert_equal true, Time.utc(2005,2,10,20,30,44).past?
- assert_equal false, Time.utc(2005,2,10,20,30,45).past?
- assert_equal false, Time.utc(2005,2,10,20,30,46).past?
+ Time.stub(:current, Time.local(2005, 2, 10, 15, 30, 45)) do
+ assert_equal true, Time.local(2005, 2, 10, 15, 30, 44).past?
+ assert_equal false, Time.local(2005, 2, 10, 15, 30, 45).past?
+ assert_equal false, Time.local(2005, 2, 10, 15, 30, 46).past?
+ assert_equal true, Time.utc(2005, 2, 10, 20, 30, 44).past?
+ assert_equal false, Time.utc(2005, 2, 10, 20, 30, 45).past?
+ assert_equal false, Time.utc(2005, 2, 10, 20, 30, 46).past?
end
end
end
def test_past_with_time_current_as_time_with_zone
with_env_tz "US/Eastern" do
- twz = Time.utc(2005,2,10,15,30,45).in_time_zone("Central Time (US & Canada)")
+ twz = Time.utc(2005, 2, 10, 15, 30, 45).in_time_zone("Central Time (US & Canada)")
Time.stub(:current, twz) do
- assert_equal true, Time.local(2005,2,10,10,30,44).past?
- assert_equal false, Time.local(2005,2,10,10,30,45).past?
- assert_equal false, Time.local(2005,2,10,10,30,46).past?
- assert_equal true, Time.utc(2005,2,10,15,30,44).past?
- assert_equal false, Time.utc(2005,2,10,15,30,45).past?
- assert_equal false, Time.utc(2005,2,10,15,30,46).past?
+ assert_equal true, Time.local(2005, 2, 10, 10, 30, 44).past?
+ assert_equal false, Time.local(2005, 2, 10, 10, 30, 45).past?
+ assert_equal false, Time.local(2005, 2, 10, 10, 30, 46).past?
+ assert_equal true, Time.utc(2005, 2, 10, 15, 30, 44).past?
+ assert_equal false, Time.utc(2005, 2, 10, 15, 30, 45).past?
+ assert_equal false, Time.utc(2005, 2, 10, 15, 30, 46).past?
end
end
end
def test_future_with_time_current_as_time_local
with_env_tz "US/Eastern" do
- Time.stub(:current, Time.local(2005,2,10,15,30,45)) do
- assert_equal false, Time.local(2005,2,10,15,30,44).future?
- assert_equal false, Time.local(2005,2,10,15,30,45).future?
- assert_equal true, Time.local(2005,2,10,15,30,46).future?
- assert_equal false, Time.utc(2005,2,10,20,30,44).future?
- assert_equal false, Time.utc(2005,2,10,20,30,45).future?
- assert_equal true, Time.utc(2005,2,10,20,30,46).future?
+ Time.stub(:current, Time.local(2005, 2, 10, 15, 30, 45)) do
+ assert_equal false, Time.local(2005, 2, 10, 15, 30, 44).future?
+ assert_equal false, Time.local(2005, 2, 10, 15, 30, 45).future?
+ assert_equal true, Time.local(2005, 2, 10, 15, 30, 46).future?
+ assert_equal false, Time.utc(2005, 2, 10, 20, 30, 44).future?
+ assert_equal false, Time.utc(2005, 2, 10, 20, 30, 45).future?
+ assert_equal true, Time.utc(2005, 2, 10, 20, 30, 46).future?
end
end
end
def test_future_with_time_current_as_time_with_zone
with_env_tz "US/Eastern" do
- twz = Time.utc(2005,2,10,15,30,45).in_time_zone("Central Time (US & Canada)")
+ twz = Time.utc(2005, 2, 10, 15, 30, 45).in_time_zone("Central Time (US & Canada)")
Time.stub(:current, twz) do
- assert_equal false, Time.local(2005,2,10,10,30,44).future?
- assert_equal false, Time.local(2005,2,10,10,30,45).future?
- assert_equal true, Time.local(2005,2,10,10,30,46).future?
- assert_equal false, Time.utc(2005,2,10,15,30,44).future?
- assert_equal false, Time.utc(2005,2,10,15,30,45).future?
- assert_equal true, Time.utc(2005,2,10,15,30,46).future?
+ assert_equal false, Time.local(2005, 2, 10, 10, 30, 44).future?
+ assert_equal false, Time.local(2005, 2, 10, 10, 30, 45).future?
+ assert_equal true, Time.local(2005, 2, 10, 10, 30, 46).future?
+ assert_equal false, Time.utc(2005, 2, 10, 15, 30, 44).future?
+ assert_equal false, Time.utc(2005, 2, 10, 15, 30, 45).future?
+ assert_equal true, Time.utc(2005, 2, 10, 15, 30, 46).future?
end
end
end
@@ -762,16 +762,16 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_compare_with_time_with_zone
- assert_equal 1, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(1999, 12, 31, 23, 59, 59), ActiveSupport::TimeZone["UTC"] )
- assert_equal 0, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone["UTC"] )
- assert_equal(-1, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone["UTC"] ))
+ assert_equal 1, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new(Time.utc(1999, 12, 31, 23, 59, 59), ActiveSupport::TimeZone["UTC"])
+ assert_equal 0, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone["UTC"])
+ assert_equal(-1, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone["UTC"]))
end
def test_compare_with_string
assert_equal 1, Time.utc(2000) <=> Time.utc(1999, 12, 31, 23, 59, 59, 999).to_s
assert_equal 0, Time.utc(2000) <=> Time.utc(2000, 1, 1, 0, 0, 0).to_s
- assert_equal( -1, Time.utc(2000) <=> Time.utc(2000, 1, 1, 0, 0, 1, 0).to_s)
- assert_equal nil, Time.utc(2000) <=> "Invalid as Time"
+ assert_equal(-1, Time.utc(2000) <=> Time.utc(2000, 1, 1, 0, 0, 1, 0).to_s)
+ assert_nil Time.utc(2000) <=> "Invalid as Time"
end
def test_at_with_datetime
@@ -851,13 +851,13 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_eql?
- assert_equal true, Time.utc(2000).eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["UTC"]) )
- assert_equal true, Time.utc(2000).eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Hawaii"]) )
- assert_equal false,Time.utc(2000, 1, 1, 0, 0, 1).eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["UTC"]) )
+ assert_equal true, Time.utc(2000).eql?(ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["UTC"]))
+ assert_equal true, Time.utc(2000).eql?(ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Hawaii"]))
+ assert_equal false, Time.utc(2000, 1, 1, 0, 0, 1).eql?(ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["UTC"]))
end
def test_minus_with_time_with_zone
- assert_equal 86_400.0, Time.utc(2000, 1, 2) - ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), ActiveSupport::TimeZone["UTC"] )
+ assert_equal 86_400.0, Time.utc(2000, 1, 2) - ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1), ActiveSupport::TimeZone["UTC"])
end
def test_minus_with_datetime
@@ -883,32 +883,32 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_all_day
- assert_equal Time.local(2011,6,7,0,0,0)..Time.local(2011,6,7,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_day
+ assert_equal Time.local(2011, 6, 7, 0, 0, 0)..Time.local(2011, 6, 7, 23, 59, 59, Rational(999999999, 1000)), Time.local(2011, 6, 7, 10, 10, 10).all_day
end
def test_all_day_with_timezone
- beginning_of_day = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], Time.local(2011,6,7,0,0,0))
- end_of_day = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], Time.local(2011,6,7,23,59,59,Rational(999999999, 1000)))
+ beginning_of_day = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], Time.local(2011, 6, 7, 0, 0, 0))
+ end_of_day = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], Time.local(2011, 6, 7, 23, 59, 59, Rational(999999999, 1000)))
- assert_equal beginning_of_day, ActiveSupport::TimeWithZone.new(Time.local(2011,6,7,10,10,10), ActiveSupport::TimeZone["Hawaii"]).all_day.begin
- assert_equal end_of_day, ActiveSupport::TimeWithZone.new(Time.local(2011,6,7,10,10,10), ActiveSupport::TimeZone["Hawaii"]).all_day.end
+ assert_equal beginning_of_day, ActiveSupport::TimeWithZone.new(Time.local(2011, 6, 7, 10, 10, 10), ActiveSupport::TimeZone["Hawaii"]).all_day.begin
+ assert_equal end_of_day, ActiveSupport::TimeWithZone.new(Time.local(2011, 6, 7, 10, 10, 10), ActiveSupport::TimeZone["Hawaii"]).all_day.end
end
def test_all_week
- assert_equal Time.local(2011,6,6,0,0,0)..Time.local(2011,6,12,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_week
- assert_equal Time.local(2011,6,5,0,0,0)..Time.local(2011,6,11,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_week(:sunday)
+ assert_equal Time.local(2011, 6, 6, 0, 0, 0)..Time.local(2011, 6, 12, 23, 59, 59, Rational(999999999, 1000)), Time.local(2011, 6, 7, 10, 10, 10).all_week
+ assert_equal Time.local(2011, 6, 5, 0, 0, 0)..Time.local(2011, 6, 11, 23, 59, 59, Rational(999999999, 1000)), Time.local(2011, 6, 7, 10, 10, 10).all_week(:sunday)
end
def test_all_month
- assert_equal Time.local(2011,6,1,0,0,0)..Time.local(2011,6,30,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_month
+ assert_equal Time.local(2011, 6, 1, 0, 0, 0)..Time.local(2011, 6, 30, 23, 59, 59, Rational(999999999, 1000)), Time.local(2011, 6, 7, 10, 10, 10).all_month
end
def test_all_quarter
- assert_equal Time.local(2011,4,1,0,0,0)..Time.local(2011,6,30,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_quarter
+ assert_equal Time.local(2011, 4, 1, 0, 0, 0)..Time.local(2011, 6, 30, 23, 59, 59, Rational(999999999, 1000)), Time.local(2011, 6, 7, 10, 10, 10).all_quarter
end
def test_all_year
- assert_equal Time.local(2011,1,1,0,0,0)..Time.local(2011,12,31,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_year
+ assert_equal Time.local(2011, 1, 1, 0, 0, 0)..Time.local(2011, 12, 31, 23, 59, 59, Rational(999999999, 1000)), Time.local(2011, 6, 7, 10, 10, 10).all_year
end
end
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 773d2c0fb7..629666947f 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -90,7 +90,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_nsec
- local = Time.local(2011,6,7,23,59,59,Rational(999999999, 1000))
+ local = Time.local(2011, 6, 7, 23, 59, 59, Rational(999999999, 1000))
with_zone = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], local)
assert_equal local.nsec, with_zone.nsec
@@ -215,69 +215,69 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_compare_with_time_with_zone
- assert_equal 1, @twz <=> ActiveSupport::TimeWithZone.new( Time.utc(1999, 12, 31, 23, 59, 59), ActiveSupport::TimeZone["UTC"] )
- assert_equal 0, @twz <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone["UTC"] )
- assert_equal(-1, @twz <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone["UTC"] ))
+ assert_equal 1, @twz <=> ActiveSupport::TimeWithZone.new(Time.utc(1999, 12, 31, 23, 59, 59), ActiveSupport::TimeZone["UTC"])
+ assert_equal 0, @twz <=> ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone["UTC"])
+ assert_equal(-1, @twz <=> ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone["UTC"]))
end
def test_between?
- assert @twz.between?(Time.utc(1999,12,31,23,59,59), Time.utc(2000,1,1,0,0,1))
- assert_equal false, @twz.between?(Time.utc(2000,1,1,0,0,1), Time.utc(2000,1,1,0,0,2))
+ assert @twz.between?(Time.utc(1999, 12, 31, 23, 59, 59), Time.utc(2000, 1, 1, 0, 0, 1))
+ assert_equal false, @twz.between?(Time.utc(2000, 1, 1, 0, 0, 1), Time.utc(2000, 1, 1, 0, 0, 2))
end
def test_today
Date.stub(:current, Date.new(2000, 1, 1)) do
- assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(1999,12,31,23,59,59) ).today?
- assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(2000,1,1,0) ).today?
- assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(2000,1,1,23,59,59) ).today?
- assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(2000,1,2,0) ).today?
+ assert_equal false, ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(1999, 12, 31, 23, 59, 59)).today?
+ assert_equal true, ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2000, 1, 1, 0)).today?
+ assert_equal true, ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2000, 1, 1, 23, 59, 59)).today?
+ assert_equal false, ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2000, 1, 2, 0)).today?
end
end
def test_past_with_time_current_as_time_local
with_env_tz "US/Eastern" do
- Time.stub(:current, Time.local(2005,2,10,15,30,45)) do
- assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).past?
- assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).past?
- assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).past?
+ Time.stub(:current, Time.local(2005, 2, 10, 15, 30, 45)) do
+ assert_equal true, ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.local(2005, 2, 10, 15, 30, 44)).past?
+ assert_equal false, ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.local(2005, 2, 10, 15, 30, 45)).past?
+ assert_equal false, ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.local(2005, 2, 10, 15, 30, 46)).past?
end
end
end
def test_past_with_time_current_as_time_with_zone
- twz = ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45) )
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.local(2005, 2, 10, 15, 30, 45))
Time.stub(:current, twz) do
- assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).past?
- assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).past?
- assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).past?
+ assert_equal true, ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.local(2005, 2, 10, 15, 30, 44)).past?
+ assert_equal false, ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.local(2005, 2, 10, 15, 30, 45)).past?
+ assert_equal false, ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.local(2005, 2, 10, 15, 30, 46)).past?
end
end
def test_future_with_time_current_as_time_local
with_env_tz "US/Eastern" do
- Time.stub(:current, Time.local(2005,2,10,15,30,45)) do
- assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).future?
- assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).future?
- assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).future?
+ Time.stub(:current, Time.local(2005, 2, 10, 15, 30, 45)) do
+ assert_equal false, ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.local(2005, 2, 10, 15, 30, 44)).future?
+ assert_equal false, ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.local(2005, 2, 10, 15, 30, 45)).future?
+ assert_equal true, ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.local(2005, 2, 10, 15, 30, 46)).future?
end
end
end
def test_future_with_time_current_as_time_with_zone
- twz = ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45) )
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.local(2005, 2, 10, 15, 30, 45))
Time.stub(:current, twz) do
- assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).future?
- assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).future?
- assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).future?
+ assert_equal false, ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.local(2005, 2, 10, 15, 30, 44)).future?
+ assert_equal false, ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.local(2005, 2, 10, 15, 30, 45)).future?
+ assert_equal true, ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.local(2005, 2, 10, 15, 30, 46)).future?
end
end
def test_eql?
assert_equal true, @twz.eql?(@twz.dup)
assert_equal true, @twz.eql?(Time.utc(2000))
- assert_equal true, @twz.eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Hawaii"]) )
- assert_equal false, @twz.eql?( Time.utc(2000, 1, 1, 0, 0, 1) )
- assert_equal false, @twz.eql?( DateTime.civil(1999, 12, 31, 23, 59, 59) )
+ assert_equal true, @twz.eql?(ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Hawaii"]))
+ assert_equal false, @twz.eql?(Time.utc(2000, 1, 1, 0, 0, 1))
+ assert_equal false, @twz.eql?(DateTime.civil(1999, 12, 31, 23, 59, 59))
other_twz = ActiveSupport::TimeWithZone.new(DateTime.now.utc, @time_zone)
assert_equal true, other_twz.eql?(other_twz.dup)
@@ -289,117 +289,117 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_plus_with_integer
- assert_equal Time.utc(1999, 12, 31, 19, 0 ,5), (@twz + 5).time
+ assert_equal Time.utc(1999, 12, 31, 19, 0 , 5), (@twz + 5).time
end
def test_plus_with_integer_when_self_wraps_datetime
datetime = DateTime.civil(2000, 1, 1, 0)
twz = ActiveSupport::TimeWithZone.new(datetime, @time_zone)
- assert_equal DateTime.civil(1999, 12, 31, 19, 0 ,5), (twz + 5).time
+ assert_equal DateTime.civil(1999, 12, 31, 19, 0 , 5), (twz + 5).time
end
def test_plus_when_crossing_time_class_limit
twz = ActiveSupport::TimeWithZone.new(Time.utc(2038, 1, 19), @time_zone)
- assert_equal [0, 0, 19, 19, 1, 2038], (twz + 86_400).to_a[0,6]
+ assert_equal [0, 0, 19, 19, 1, 2038], (twz + 86_400).to_a[0, 6]
end
def test_plus_with_duration
- assert_equal Time.utc(2000, 1, 5, 19, 0 ,0), (@twz + 5.days).time
+ assert_equal Time.utc(2000, 1, 5, 19, 0 , 0), (@twz + 5.days).time
end
def test_minus_with_integer
- assert_equal Time.utc(1999, 12, 31, 18, 59 ,55), (@twz - 5).time
+ assert_equal Time.utc(1999, 12, 31, 18, 59 , 55), (@twz - 5).time
end
def test_minus_with_integer_when_self_wraps_datetime
datetime = DateTime.civil(2000, 1, 1, 0)
twz = ActiveSupport::TimeWithZone.new(datetime, @time_zone)
- assert_equal DateTime.civil(1999, 12, 31, 18, 59 ,55), (twz - 5).time
+ assert_equal DateTime.civil(1999, 12, 31, 18, 59 , 55), (twz - 5).time
end
def test_minus_with_duration
- assert_equal Time.utc(1999, 12, 26, 19, 0 ,0), (@twz - 5.days).time
+ assert_equal Time.utc(1999, 12, 26, 19, 0 , 0), (@twz - 5.days).time
end
def test_minus_with_time
- assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone["UTC"] ) - Time.utc(2000, 1, 1)
- assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone["Hawaii"] ) - Time.utc(2000, 1, 1)
+ assert_equal 86_400.0, ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 2), ActiveSupport::TimeZone["UTC"]) - Time.utc(2000, 1, 1)
+ assert_equal 86_400.0, ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 2), ActiveSupport::TimeZone["Hawaii"]) - Time.utc(2000, 1, 1)
end
def test_minus_with_time_precision
- assert_equal 86_399.999999998, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone["UTC"] ) - Time.utc(2000, 1, 2, 0, 0, 0, Rational(1, 1000))
- assert_equal 86_399.999999998, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone["Hawaii"] ) - Time.utc(2000, 1, 2, 0, 0, 0, Rational(1, 1000))
+ assert_equal 86_399.999999998, ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 2, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone["UTC"]) - Time.utc(2000, 1, 2, 0, 0, 0, Rational(1, 1000))
+ assert_equal 86_399.999999998, ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 2, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone["Hawaii"]) - Time.utc(2000, 1, 2, 0, 0, 0, Rational(1, 1000))
end
def test_minus_with_time_with_zone
- twz1 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), ActiveSupport::TimeZone["UTC"] )
- twz2 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone["UTC"] )
- assert_equal 86_400.0, twz2 - twz1
+ twz1 = ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1), ActiveSupport::TimeZone["UTC"])
+ twz2 = ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 2), ActiveSupport::TimeZone["UTC"])
+ assert_equal 86_400.0, twz2 - twz1
end
def test_minus_with_time_with_zone_precision
- twz1 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 0, Rational(1, 1000)), ActiveSupport::TimeZone["UTC"] )
- twz2 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone["UTC"] )
+ twz1 = ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0, Rational(1, 1000)), ActiveSupport::TimeZone["UTC"])
+ twz2 = ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone["UTC"])
assert_equal 86_399.999999998, twz2 - twz1
end
def test_minus_with_datetime
- assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone["UTC"] ) - DateTime.civil(2000, 1, 1)
+ assert_equal 86_400.0, ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 2), ActiveSupport::TimeZone["UTC"]) - DateTime.civil(2000, 1, 1)
end
def test_minus_with_datetime_precision
- assert_equal 86_399.999999999, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone["UTC"] ) - DateTime.civil(2000, 1, 1)
+ assert_equal 86_399.999999999, ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone["UTC"]) - DateTime.civil(2000, 1, 1)
end
def test_minus_with_wrapped_datetime
- assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( DateTime.civil(2000, 1, 2), ActiveSupport::TimeZone["UTC"] ) - Time.utc(2000, 1, 1)
- assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( DateTime.civil(2000, 1, 2), ActiveSupport::TimeZone["UTC"] ) - DateTime.civil(2000, 1, 1)
+ assert_equal 86_400.0, ActiveSupport::TimeWithZone.new(DateTime.civil(2000, 1, 2), ActiveSupport::TimeZone["UTC"]) - Time.utc(2000, 1, 1)
+ assert_equal 86_400.0, ActiveSupport::TimeWithZone.new(DateTime.civil(2000, 1, 2), ActiveSupport::TimeZone["UTC"]) - DateTime.civil(2000, 1, 1)
end
def test_plus_and_minus_enforce_spring_dst_rules
- utc = Time.utc(2006,4,2,6,59,59) # == Apr 2 2006 01:59:59 EST; i.e., 1 second before daylight savings start
+ utc = Time.utc(2006, 4, 2, 6, 59, 59) # == Apr 2 2006 01:59:59 EST; i.e., 1 second before daylight savings start
twz = ActiveSupport::TimeWithZone.new(utc, @time_zone)
- assert_equal Time.utc(2006,4,2,1,59,59), twz.time
+ assert_equal Time.utc(2006, 4, 2, 1, 59, 59), twz.time
assert_equal false, twz.dst?
assert_equal "EST", twz.zone
twz = twz + 1
- assert_equal Time.utc(2006,4,2,3), twz.time # adding 1 sec springs forward to 3:00AM EDT
+ assert_equal Time.utc(2006, 4, 2, 3), twz.time # adding 1 sec springs forward to 3:00AM EDT
assert_equal true, twz.dst?
assert_equal "EDT", twz.zone
twz = twz - 1 # subtracting 1 second takes goes back to 1:59:59AM EST
- assert_equal Time.utc(2006,4,2,1,59,59), twz.time
+ assert_equal Time.utc(2006, 4, 2, 1, 59, 59), twz.time
assert_equal false, twz.dst?
assert_equal "EST", twz.zone
end
def test_plus_and_minus_enforce_fall_dst_rules
- utc = Time.utc(2006,10,29,5,59,59) # == Oct 29 2006 01:59:59 EST; i.e., 1 second before daylight savings end
+ utc = Time.utc(2006, 10, 29, 5, 59, 59) # == Oct 29 2006 01:59:59 EST; i.e., 1 second before daylight savings end
twz = ActiveSupport::TimeWithZone.new(utc, @time_zone)
- assert_equal Time.utc(2006,10,29,1,59,59), twz.time
+ assert_equal Time.utc(2006, 10, 29, 1, 59, 59), twz.time
assert_equal true, twz.dst?
assert_equal "EDT", twz.zone
twz = twz + 1
- assert_equal Time.utc(2006,10,29,1), twz.time # adding 1 sec falls back from 1:59:59 EDT to 1:00AM EST
+ assert_equal Time.utc(2006, 10, 29, 1), twz.time # adding 1 sec falls back from 1:59:59 EDT to 1:00AM EST
assert_equal false, twz.dst?
assert_equal "EST", twz.zone
twz = twz - 1
- assert_equal Time.utc(2006,10,29,1,59,59), twz.time # subtracting 1 sec goes back to 1:59:59AM EDT
+ assert_equal Time.utc(2006, 10, 29, 1, 59, 59), twz.time # subtracting 1 sec goes back to 1:59:59AM EDT
assert_equal true, twz.dst?
assert_equal "EDT", twz.zone
end
def test_to_a
- assert_equal [45, 30, 5, 1, 2, 2000, 2, 32, false, "HST"], ActiveSupport::TimeWithZone.new( Time.utc(2000, 2, 1, 15, 30, 45), ActiveSupport::TimeZone["Hawaii"] ).to_a
+ assert_equal [45, 30, 5, 1, 2, 2000, 2, 32, false, "HST"], ActiveSupport::TimeWithZone.new(Time.utc(2000, 2, 1, 15, 30, 45), ActiveSupport::TimeZone["Hawaii"]).to_a
end
def test_to_f
- result = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), ActiveSupport::TimeZone["Hawaii"] ).to_f
+ result = ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1), ActiveSupport::TimeZone["Hawaii"]).to_f
assert_equal 946684800.0, result
assert_kind_of Float, result
end
def test_to_i
- result = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), ActiveSupport::TimeZone["Hawaii"] ).to_i
+ result = ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1), ActiveSupport::TimeZone["Hawaii"]).to_i
assert_equal 946684800, result
assert_kind_of Integer, result
end
@@ -431,13 +431,13 @@ class TimeWithZoneTest < ActiveSupport::TestCase
def test_to_date
# 1 sec before midnight Jan 1 EST
- assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 4, 59, 59), ActiveSupport::TimeZone["Eastern Time (US & Canada)"] ).to_date
+ assert_equal Date.new(1999, 12, 31), ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 4, 59, 59), ActiveSupport::TimeZone["Eastern Time (US & Canada)"]).to_date
# midnight Jan 1 EST
- assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 5, 0, 0), ActiveSupport::TimeZone["Eastern Time (US & Canada)"] ).to_date
+ assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 5, 0, 0), ActiveSupport::TimeZone["Eastern Time (US & Canada)"]).to_date
# 1 sec before midnight Jan 2 EST
- assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2, 4, 59, 59), ActiveSupport::TimeZone["Eastern Time (US & Canada)"] ).to_date
+ assert_equal Date.new(2000, 1, 1), ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 2, 4, 59, 59), ActiveSupport::TimeZone["Eastern Time (US & Canada)"]).to_date
# midnight Jan 2 EST
- assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2, 5, 0, 0), ActiveSupport::TimeZone["Eastern Time (US & Canada)"] ).to_date
+ assert_equal Date.new(2000, 1, 2), ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 2, 5, 0, 0), ActiveSupport::TimeZone["Eastern Time (US & Canada)"]).to_date
end
def test_to_datetime
@@ -467,7 +467,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase
def test_method_missing_with_time_return_value
assert_instance_of ActiveSupport::TimeWithZone, @twz.months_since(1)
- assert_equal Time.utc(2000, 1, 31, 19, 0 ,0), @twz.months_since(1).time
+ assert_equal Time.utc(2000, 1, 31, 19, 0 , 0), @twz.months_since(1).time
end
def test_marshal_dump_and_load
@@ -513,7 +513,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_date_part_value_methods
- twz = ActiveSupport::TimeWithZone.new(Time.utc(1999,12,31,19,18,17,500), @time_zone)
+ twz = ActiveSupport::TimeWithZone.new(Time.utc(1999, 12, 31, 19, 18, 17, 500), @time_zone)
assert_not_called(twz, :method_missing) do
assert_equal 1999, twz.year
assert_equal 12, twz.month
@@ -533,12 +533,12 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_usec_returns_sec_fraction_when_datetime_is_wrapped
- twz = ActiveSupport::TimeWithZone.new(DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)), @time_zone)
+ twz = ActiveSupport::TimeWithZone.new(DateTime.civil(2000, 1, 1, 0, 0, Rational(1, 2)), @time_zone)
assert_equal 500000, twz.usec
end
def test_nsec_returns_sec_fraction_when_datetime_is_wrapped
- twz = ActiveSupport::TimeWithZone.new(DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)), @time_zone)
+ twz = ActiveSupport::TimeWithZone.new(DateTime.civil(2000, 1, 1, 0, 0, Rational(1, 2)), @time_zone)
assert_equal 500000000, twz.nsec
end
@@ -554,15 +554,15 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_instance_created_with_local_time_enforces_spring_dst_rules
- twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,2,2)) # first second of DST
- assert_equal Time.utc(2006,4,2,3), twz.time # springs forward to 3AM
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 4, 2, 2)) # first second of DST
+ assert_equal Time.utc(2006, 4, 2, 3), twz.time # springs forward to 3AM
assert_equal true, twz.dst?
assert_equal "EDT", twz.zone
end
def test_instance_created_with_local_time_enforces_fall_dst_rules
- twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,29,1)) # 1AM can be either DST or non-DST; we'll pick DST
- assert_equal Time.utc(2006,10,29,1), twz.time
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 10, 29, 1)) # 1AM can be either DST or non-DST; we'll pick DST
+ assert_equal Time.utc(2006, 10, 29, 1), twz.time
assert_equal true, twz.dst?
assert_equal "EDT", twz.zone
end
@@ -575,11 +575,11 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_utc_to_local_conversion_with_far_future_datetime
- assert_equal [0,0,19,31,12,2049], ActiveSupport::TimeWithZone.new(DateTime.civil(2050), @time_zone).to_a[0,6]
+ assert_equal [0, 0, 19, 31, 12, 2049], ActiveSupport::TimeWithZone.new(DateTime.civil(2050), @time_zone).to_a[0, 6]
end
def test_local_to_utc_conversion_with_far_future_datetime
- assert_equal DateTime.civil(2050).to_f, ActiveSupport::TimeWithZone.new(nil, @time_zone, DateTime.civil(2049,12,31,19)).to_f
+ assert_equal DateTime.civil(2050).to_f, ActiveSupport::TimeWithZone.new(nil, @time_zone, DateTime.civil(2049, 12, 31, 19)).to_f
end
def test_change
@@ -675,6 +675,10 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal "Fri, 31 Dec 1999 19:00:01 EST -05:00", @twz.since(1).inspect
end
+ def test_in
+ assert_equal "Fri, 31 Dec 1999 19:00:01 EST -05:00", @twz.in(1).inspect
+ end
+
def test_ago
assert_equal "Fri, 31 Dec 1999 18:59:59 EST -05:00", @twz.ago(1).inspect
end
@@ -684,59 +688,67 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_advance_1_year_from_leap_day
- twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2004,2,29))
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2004, 2, 29))
assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.advance(years: 1).inspect
assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.years_since(1).inspect
assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.since(1.year).inspect
+ assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.in(1.year).inspect
assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", (twz + 1.year).inspect
end
def test_advance_1_month_from_last_day_of_january
- twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2005,1,31))
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2005, 1, 31))
assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.advance(months: 1).inspect
assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.months_since(1).inspect
assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.since(1.month).inspect
+ assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.in(1.month).inspect
assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", (twz + 1.month).inspect
end
def test_advance_1_month_from_last_day_of_january_during_leap_year
- twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2000,1,31))
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2000, 1, 31))
assert_equal "Tue, 29 Feb 2000 00:00:00 EST -05:00", twz.advance(months: 1).inspect
assert_equal "Tue, 29 Feb 2000 00:00:00 EST -05:00", twz.months_since(1).inspect
assert_equal "Tue, 29 Feb 2000 00:00:00 EST -05:00", twz.since(1.month).inspect
+ assert_equal "Tue, 29 Feb 2000 00:00:00 EST -05:00", twz.in(1.month).inspect
assert_equal "Tue, 29 Feb 2000 00:00:00 EST -05:00", (twz + 1.month).inspect
end
def test_advance_1_month_into_spring_dst_gap
- twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,3,2,2))
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 3, 2, 2))
assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.advance(months: 1).inspect
assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.months_since(1).inspect
assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.since(1.month).inspect
+ assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.in(1.month).inspect
assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", (twz + 1.month).inspect
end
def test_advance_1_second_into_spring_dst_gap
- twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,2,1,59,59))
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 4, 2, 1, 59, 59))
assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.advance(seconds: 1).inspect
assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", (twz + 1).inspect
+ assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", (twz + 1.second).inspect
assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.since(1).inspect
assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.since(1.second).inspect
- assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", (twz + 1.second).inspect
+ assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.in(1).inspect
+ assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.in(1.second).inspect
end
def test_advance_1_day_across_spring_dst_transition
- twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,1,10,30))
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 4, 1, 10, 30))
# In 2006, spring DST transition occurred Apr 2 at 2AM; this day was only 23 hours long
# When we advance 1 day, we want to end up at the same time on the next day
assert_equal "Sun, 02 Apr 2006 10:30:00 EDT -04:00", twz.advance(days: 1).inspect
assert_equal "Sun, 02 Apr 2006 10:30:00 EDT -04:00", twz.since(1.days).inspect
+ assert_equal "Sun, 02 Apr 2006 10:30:00 EDT -04:00", twz.in(1.days).inspect
assert_equal "Sun, 02 Apr 2006 10:30:00 EDT -04:00", (twz + 1.days).inspect
assert_equal "Sun, 02 Apr 2006 10:30:01 EDT -04:00", twz.since(1.days + 1.second).inspect
+ assert_equal "Sun, 02 Apr 2006 10:30:01 EDT -04:00", twz.in(1.days + 1.second).inspect
assert_equal "Sun, 02 Apr 2006 10:30:01 EDT -04:00", (twz + 1.days + 1.second).inspect
end
def test_advance_1_day_across_spring_dst_transition_backwards
- twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,2,10,30))
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 4, 2, 10, 30))
# In 2006, spring DST transition occurred Apr 2 at 2AM; this day was only 23 hours long
# When we advance back 1 day, we want to end up at the same time on the previous day
assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.advance(days: -1).inspect
@@ -746,24 +758,28 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_advance_1_day_expressed_as_number_of_seconds_minutes_or_hours_across_spring_dst_transition
- twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,1,10,30))
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 4, 1, 10, 30))
# In 2006, spring DST transition occurred Apr 2 at 2AM; this day was only 23 hours long
# When we advance a specific number of hours, minutes or seconds, we want to advance exactly that amount
assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", (twz + 86400).inspect
assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", (twz + 86400.seconds).inspect
assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.since(86400).inspect
assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.since(86400.seconds).inspect
+ assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.in(86400).inspect
+ assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.in(86400.seconds).inspect
assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.advance(seconds: 86400).inspect
assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", (twz + 1440.minutes).inspect
assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.since(1440.minutes).inspect
+ assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.in(1440.minutes).inspect
assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.advance(minutes: 1440).inspect
assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", (twz + 24.hours).inspect
assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.since(24.hours).inspect
+ assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.in(24.hours).inspect
assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.advance(hours: 24).inspect
end
def test_advance_1_day_expressed_as_number_of_seconds_minutes_or_hours_across_spring_dst_transition_backwards
- twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,2,11,30))
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 4, 2, 11, 30))
# In 2006, spring DST transition occurred Apr 2 at 2AM; this day was only 23 hours long
# When we advance a specific number of hours, minutes or seconds, we want to advance exactly that amount
assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", (twz - 86400).inspect
@@ -780,18 +796,20 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_advance_1_day_across_fall_dst_transition
- twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,28,10,30))
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 10, 28, 10, 30))
# In 2006, fall DST transition occurred Oct 29 at 2AM; this day was 25 hours long
# When we advance 1 day, we want to end up at the same time on the next day
assert_equal "Sun, 29 Oct 2006 10:30:00 EST -05:00", twz.advance(days: 1).inspect
assert_equal "Sun, 29 Oct 2006 10:30:00 EST -05:00", twz.since(1.days).inspect
+ assert_equal "Sun, 29 Oct 2006 10:30:00 EST -05:00", twz.in(1.days).inspect
assert_equal "Sun, 29 Oct 2006 10:30:00 EST -05:00", (twz + 1.days).inspect
assert_equal "Sun, 29 Oct 2006 10:30:01 EST -05:00", twz.since(1.days + 1.second).inspect
+ assert_equal "Sun, 29 Oct 2006 10:30:01 EST -05:00", twz.in(1.days + 1.second).inspect
assert_equal "Sun, 29 Oct 2006 10:30:01 EST -05:00", (twz + 1.days + 1.second).inspect
end
def test_advance_1_day_across_fall_dst_transition_backwards
- twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,29,10,30))
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 10, 29, 10, 30))
# In 2006, fall DST transition occurred Oct 29 at 2AM; this day was 25 hours long
# When we advance backwards 1 day, we want to end up at the same time on the previous day
assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.advance(days: -1).inspect
@@ -801,24 +819,28 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_advance_1_day_expressed_as_number_of_seconds_minutes_or_hours_across_fall_dst_transition
- twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,28,10,30))
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 10, 28, 10, 30))
# In 2006, fall DST transition occurred Oct 29 at 2AM; this day was 25 hours long
# When we advance a specific number of hours, minutes or seconds, we want to advance exactly that amount
assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", (twz + 86400).inspect
assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", (twz + 86400.seconds).inspect
assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.since(86400).inspect
assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.since(86400.seconds).inspect
+ assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.in(86400).inspect
+ assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.in(86400.seconds).inspect
assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.advance(seconds: 86400).inspect
assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", (twz + 1440.minutes).inspect
assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.since(1440.minutes).inspect
+ assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.in(1440.minutes).inspect
assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.advance(minutes: 1440).inspect
assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", (twz + 24.hours).inspect
assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.since(24.hours).inspect
+ assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.in(24.hours).inspect
assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.advance(hours: 24).inspect
end
def test_advance_1_day_expressed_as_number_of_seconds_minutes_or_hours_across_fall_dst_transition_backwards
- twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,29,9,30))
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 10, 29, 9, 30))
# In 2006, fall DST transition occurred Oct 29 at 2AM; this day was 25 hours long
# When we advance a specific number of hours, minutes or seconds, we want to advance exactly that amount
assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", (twz - 86400).inspect
@@ -835,15 +857,16 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_advance_1_week_across_spring_dst_transition
- twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,1,10,30))
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 4, 1, 10, 30))
assert_equal "Sat, 08 Apr 2006 10:30:00 EDT -04:00", twz.advance(weeks: 1).inspect
assert_equal "Sat, 08 Apr 2006 10:30:00 EDT -04:00", twz.weeks_since(1).inspect
assert_equal "Sat, 08 Apr 2006 10:30:00 EDT -04:00", twz.since(1.week).inspect
+ assert_equal "Sat, 08 Apr 2006 10:30:00 EDT -04:00", twz.in(1.week).inspect
assert_equal "Sat, 08 Apr 2006 10:30:00 EDT -04:00", (twz + 1.week).inspect
end
def test_advance_1_week_across_spring_dst_transition_backwards
- twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,8,10,30))
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 4, 8, 10, 30))
assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.advance(weeks: -1).inspect
assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.weeks_ago(1).inspect
assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.ago(1.week).inspect
@@ -851,15 +874,16 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_advance_1_week_across_fall_dst_transition
- twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,28,10,30))
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 10, 28, 10, 30))
assert_equal "Sat, 04 Nov 2006 10:30:00 EST -05:00", twz.advance(weeks: 1).inspect
assert_equal "Sat, 04 Nov 2006 10:30:00 EST -05:00", twz.weeks_since(1).inspect
assert_equal "Sat, 04 Nov 2006 10:30:00 EST -05:00", twz.since(1.week).inspect
+ assert_equal "Sat, 04 Nov 2006 10:30:00 EST -05:00", twz.in(1.week).inspect
assert_equal "Sat, 04 Nov 2006 10:30:00 EST -05:00", (twz + 1.week).inspect
end
def test_advance_1_week_across_fall_dst_transition_backwards
- twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,11,4,10,30))
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 11, 4, 10, 30))
assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.advance(weeks: -1).inspect
assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.weeks_ago(1).inspect
assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.ago(1.week).inspect
@@ -867,15 +891,16 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_advance_1_month_across_spring_dst_transition
- twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,1,10,30))
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 4, 1, 10, 30))
assert_equal "Mon, 01 May 2006 10:30:00 EDT -04:00", twz.advance(months: 1).inspect
assert_equal "Mon, 01 May 2006 10:30:00 EDT -04:00", twz.months_since(1).inspect
assert_equal "Mon, 01 May 2006 10:30:00 EDT -04:00", twz.since(1.month).inspect
+ assert_equal "Mon, 01 May 2006 10:30:00 EDT -04:00", twz.in(1.month).inspect
assert_equal "Mon, 01 May 2006 10:30:00 EDT -04:00", (twz + 1.month).inspect
end
def test_advance_1_month_across_spring_dst_transition_backwards
- twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,5,1,10,30))
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 5, 1, 10, 30))
assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.advance(months: -1).inspect
assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.months_ago(1).inspect
assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.ago(1.month).inspect
@@ -883,15 +908,16 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_advance_1_month_across_fall_dst_transition
- twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,28,10,30))
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 10, 28, 10, 30))
assert_equal "Tue, 28 Nov 2006 10:30:00 EST -05:00", twz.advance(months: 1).inspect
assert_equal "Tue, 28 Nov 2006 10:30:00 EST -05:00", twz.months_since(1).inspect
assert_equal "Tue, 28 Nov 2006 10:30:00 EST -05:00", twz.since(1.month).inspect
+ assert_equal "Tue, 28 Nov 2006 10:30:00 EST -05:00", twz.in(1.month).inspect
assert_equal "Tue, 28 Nov 2006 10:30:00 EST -05:00", (twz + 1.month).inspect
end
def test_advance_1_month_across_fall_dst_transition_backwards
- twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,11,28,10,30))
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 11, 28, 10, 30))
assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.advance(months: -1).inspect
assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.months_ago(1).inspect
assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.ago(1.month).inspect
@@ -899,9 +925,11 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_advance_1_year
- twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2008,2,15,10,30))
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2008, 2, 15, 10, 30))
assert_equal "Sun, 15 Feb 2009 10:30:00 EST -05:00", twz.advance(years: 1).inspect
assert_equal "Sun, 15 Feb 2009 10:30:00 EST -05:00", twz.years_since(1).inspect
+ assert_equal "Sun, 15 Feb 2009 10:30:00 EST -05:00", twz.since(1.year).inspect
+ assert_equal "Sun, 15 Feb 2009 10:30:00 EST -05:00", twz.in(1.year).inspect
assert_equal "Sun, 15 Feb 2009 10:30:00 EST -05:00", (twz + 1.year).inspect
assert_equal "Thu, 15 Feb 2007 10:30:00 EST -05:00", twz.advance(years: -1).inspect
assert_equal "Thu, 15 Feb 2007 10:30:00 EST -05:00", twz.years_ago(1).inspect
@@ -909,9 +937,11 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_advance_1_year_during_dst
- twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2008,7,15,10,30))
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2008, 7, 15, 10, 30))
assert_equal "Wed, 15 Jul 2009 10:30:00 EDT -04:00", twz.advance(years: 1).inspect
assert_equal "Wed, 15 Jul 2009 10:30:00 EDT -04:00", twz.years_since(1).inspect
+ assert_equal "Wed, 15 Jul 2009 10:30:00 EDT -04:00", twz.since(1.year).inspect
+ assert_equal "Wed, 15 Jul 2009 10:30:00 EDT -04:00", twz.in(1.year).inspect
assert_equal "Wed, 15 Jul 2009 10:30:00 EDT -04:00", (twz + 1.year).inspect
assert_equal "Sun, 15 Jul 2007 10:30:00 EDT -04:00", twz.advance(years: -1).inspect
assert_equal "Sun, 15 Jul 2007 10:30:00 EDT -04:00", twz.years_ago(1).inspect
@@ -1014,7 +1044,7 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < ActiveSupport::TestCase
def test_use_zone_raises_on_invalid_timezone
Time.zone = "Alaska"
assert_raise ArgumentError do
- Time.use_zone("No such timezone exists") { }
+ Time.use_zone("No such timezone exists") {}
end
assert_equal ActiveSupport::TimeZone["Alaska"], Time.zone
end
@@ -1027,7 +1057,7 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < ActiveSupport::TestCase
Time.zone = -9.hours
assert_equal ActiveSupport::TimeZone["Alaska"], Time.zone
Time.zone = nil
- assert_equal nil, Time.zone
+ assert_nil Time.zone
end
def test_time_zone_getter_and_setter_with_zone_default_set
@@ -1072,9 +1102,9 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < ActiveSupport::TestCase
end
def test_time_zone_setter_with_invalid_zone
- assert_raise(ArgumentError){ Time.zone = "No such timezone exists" }
- assert_raise(ArgumentError){ Time.zone = -15.hours }
- assert_raise(ArgumentError){ Time.zone = Object.new }
+ assert_raise(ArgumentError) { Time.zone = "No such timezone exists" }
+ assert_raise(ArgumentError) { Time.zone = -15.hours }
+ assert_raise(ArgumentError) { Time.zone = Object.new }
end
def test_find_zone_without_bang_returns_nil_if_time_zone_can_not_be_found
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 1ed6337fca..e772d15d53 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -114,25 +114,25 @@ class DependenciesTest < ActiveSupport::TestCase
assert_equal 1, $check_warnings_load_count
assert_equal true, $checked_verbose, "On first load warnings should be enabled."
- assert ActiveSupport::Dependencies.loaded.include?(expanded)
+ assert_includes ActiveSupport::Dependencies.loaded, expanded
ActiveSupport::Dependencies.clear
assert_not ActiveSupport::Dependencies.loaded.include?(expanded)
- assert ActiveSupport::Dependencies.history.include?(expanded)
+ assert_includes ActiveSupport::Dependencies.history, expanded
silence_warnings { require_dependency filename }
assert_equal 2, $check_warnings_load_count
- assert_equal nil, $checked_verbose, "After first load warnings should be left alone."
+ assert_nil $checked_verbose, "After first load warnings should be left alone."
- assert ActiveSupport::Dependencies.loaded.include?(expanded)
+ assert_includes ActiveSupport::Dependencies.loaded, expanded
ActiveSupport::Dependencies.clear
assert_not ActiveSupport::Dependencies.loaded.include?(expanded)
- assert ActiveSupport::Dependencies.history.include?(expanded)
+ assert_includes ActiveSupport::Dependencies.history, expanded
enable_warnings { require_dependency filename }
assert_equal 3, $check_warnings_load_count
assert_equal true, $checked_verbose, "After first load warnings should be left alone."
- assert ActiveSupport::Dependencies.loaded.include?(expanded)
+ assert_includes ActiveSupport::Dependencies.loaded, expanded
ActiveSupport::Dependencies.warnings_on_first_load = old_warnings
end
end
@@ -526,8 +526,8 @@ class DependenciesTest < ActiveSupport::TestCase
def test_file_search
with_loading "dependencies" do
root = ActiveSupport::Dependencies.autoload_paths.first
- assert_equal nil, ActiveSupport::Dependencies.search_for_file("service_three")
- assert_equal nil, ActiveSupport::Dependencies.search_for_file("service_three.rb")
+ assert_nil ActiveSupport::Dependencies.search_for_file("service_three")
+ assert_nil ActiveSupport::Dependencies.search_for_file("service_three.rb")
assert_equal root + "/service_one.rb", ActiveSupport::Dependencies.search_for_file("service_one")
assert_equal root + "/service_one.rb", ActiveSupport::Dependencies.search_for_file("service_one.rb")
end
@@ -659,7 +659,7 @@ class DependenciesTest < ActiveSupport::TestCase
def test_autoload_once_pathnames_do_not_add_to_autoloaded_constants
with_autoloading_fixtures do
- pathnames = ActiveSupport::Dependencies.autoload_paths.collect{|p| Pathname.new(p)}
+ pathnames = ActiveSupport::Dependencies.autoload_paths.collect { |p| Pathname.new(p) }
ActiveSupport::Dependencies.autoload_paths = pathnames
ActiveSupport::Dependencies.autoload_once_paths = pathnames
@@ -756,15 +756,15 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_new_contants_in_without_constants
- assert_equal [], (ActiveSupport::Dependencies.new_constants_in(Object) { })
- assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? }
+ assert_equal [], (ActiveSupport::Dependencies.new_constants_in(Object) {})
+ assert ActiveSupport::Dependencies.constant_watch_stack.all? { |k, v| v.empty? }
end
def test_new_constants_in_with_a_single_constant
assert_equal ["Hello"], ActiveSupport::Dependencies.new_constants_in(Object) {
Object.const_set :Hello, 10
}.map(&:to_s)
- assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? }
+ assert ActiveSupport::Dependencies.constant_watch_stack.all? { |k, v| v.empty? }
ensure
remove_constants(:Hello)
end
@@ -781,7 +781,7 @@ class DependenciesTest < ActiveSupport::TestCase
end
assert_equal ["OuterAfter", "OuterBefore"], outer.sort.map(&:to_s)
- assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? }
+ assert ActiveSupport::Dependencies.constant_watch_stack.all? { |k, v| v.empty? }
ensure
remove_constants(:OuterBefore, :Inner, :OuterAfter)
end
@@ -800,7 +800,7 @@ class DependenciesTest < ActiveSupport::TestCase
M.const_set :OuterAfter, 30
end
assert_equal ["M::OuterAfter", "M::OuterBefore"], outer.sort
- assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? }
+ assert ActiveSupport::Dependencies.constant_watch_stack.all? { |k, v| v.empty? }
ensure
remove_constants(:M)
end
@@ -818,7 +818,7 @@ class DependenciesTest < ActiveSupport::TestCase
M.const_set :OuterAfter, 30
end
assert_equal ["M::OuterAfter", "M::OuterBefore"], outer.sort
- assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? }
+ assert ActiveSupport::Dependencies.constant_watch_stack.all? { |k, v| v.empty? }
ensure
remove_constants(:M)
end
@@ -1059,13 +1059,13 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_load_and_require_stay_private
- assert Object.private_methods.include?(:load)
- assert Object.private_methods.include?(:require)
+ assert_includes Object.private_methods, :load
+ assert_includes Object.private_methods, :require
ActiveSupport::Dependencies.unhook!
- assert Object.private_methods.include?(:load)
- assert Object.private_methods.include?(:require)
+ assert_includes Object.private_methods, :load
+ assert_includes Object.private_methods, :require
ensure
ActiveSupport::Dependencies.hook!
end
diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb
index 6669a70a00..5be93f3a1a 100644
--- a/activesupport/test/deprecation_test.rb
+++ b/activesupport/test/deprecation_test.rb
@@ -16,7 +16,7 @@ class Deprecatee
def not() 2 end
def none() 1 end
def one(a) a end
- def multi(a,b,c) [a,b,c] end
+ def multi(a, b, c) [a, b, c] end
deprecate :none, :one, :multi
def a; end
@@ -73,7 +73,7 @@ class DeprecationTest < ActiveSupport::TestCase
end
assert_deprecated(/multi is deprecated/) do
- assert_equal [1,2,3], @dtc.multi(1,2,3)
+ assert_equal [1, 2, 3], @dtc.multi(1, 2, 3)
end
end
@@ -279,7 +279,7 @@ class DeprecationTest < ActiveSupport::TestCase
def test_deprecated_constant_with_deprecator_given
deprecator = deprecator_with_messages
klass = Class.new
- klass.const_set(:OLD, ActiveSupport::Deprecation::DeprecatedConstantProxy.new("klass::OLD", "Object", deprecator) )
+ klass.const_set(:OLD, ActiveSupport::Deprecation::DeprecatedConstantProxy.new("klass::OLD", "Object", deprecator))
assert_difference("deprecator.messages.size") do
klass::OLD.to_s
end
@@ -363,7 +363,7 @@ class DeprecationTest < ActiveSupport::TestCase
def deprecator_with_messages
klass = Class.new(ActiveSupport::Deprecation)
deprecator = klass.new
- deprecator.behavior = Proc.new{|message, callstack| deprecator.messages << message}
+ deprecator.behavior = Proc.new { |message, callstack| deprecator.messages << message }
def deprecator.messages
@messages ||= []
end
diff --git a/activesupport/test/descendants_tracker_test_cases.rb b/activesupport/test/descendants_tracker_test_cases.rb
index 09c5ce1f07..cf349d53ee 100644
--- a/activesupport/test/descendants_tracker_test_cases.rb
+++ b/activesupport/test/descendants_tracker_test_cases.rb
@@ -40,7 +40,7 @@ module DescendantsTrackerTestCases
end
end
- protected
+ private
def assert_equal_sets(expected, actual)
assert_equal Set.new(expected), Set.new(actual)
diff --git a/activesupport/test/evented_file_update_checker_test.rb b/activesupport/test/evented_file_update_checker_test.rb
index aaa9aaa397..77d8dcb0f8 100644
--- a/activesupport/test/evented_file_update_checker_test.rb
+++ b/activesupport/test/evented_file_update_checker_test.rb
@@ -36,9 +36,11 @@ class EventedFileUpdateCheckerTest < ActiveSupport::TestCase
end
test "notifies forked processes" do
+ jruby_skip "Forking not available on JRuby"
+
FileUtils.touch(tmpfiles)
- checker = new_checker(tmpfiles) { }
+ checker = new_checker(tmpfiles) {}
assert !checker.updated?
# Pipes used for flow controll across fork.
diff --git a/activesupport/test/executor_test.rb b/activesupport/test/executor_test.rb
index 11ef4e5a50..7fefc066b3 100644
--- a/activesupport/test/executor_test.rb
+++ b/activesupport/test/executor_test.rb
@@ -21,7 +21,7 @@ class ExecutorTest < ActiveSupport::TestCase
executor.to_run { @foo = true }
executor.to_complete { result = @foo }
- executor.wrap { }
+ executor.wrap {}
assert result
end
@@ -83,7 +83,7 @@ class ExecutorTest < ActiveSupport::TestCase
executor.register_hook(hook)
- executor.wrap { }
+ executor.wrap {}
assert_equal :some_state, supplied_state
end
@@ -103,9 +103,9 @@ class ExecutorTest < ActiveSupport::TestCase
executor.register_hook(hook)
- executor.wrap { }
+ executor.wrap {}
- assert_equal nil, supplied_state
+ assert_nil supplied_state
end
def test_exception_skips_uninvoked_hook
@@ -127,7 +127,7 @@ class ExecutorTest < ActiveSupport::TestCase
executor.register_hook(hook)
assert_raises(DummyError) do
- executor.wrap { }
+ executor.wrap {}
end
assert_equal :none, supplied_state
@@ -152,12 +152,69 @@ class ExecutorTest < ActiveSupport::TestCase
end
assert_raises(DummyError) do
- executor.wrap { }
+ executor.wrap {}
end
assert_equal :some_state, supplied_state
end
+ def test_hook_insertion_order
+ invoked = []
+ supplied_state = []
+
+ hook_class = Class.new do
+ attr_accessor :letter
+
+ define_method(:initialize) do |letter|
+ self.letter = letter
+ end
+
+ define_method(:run) do
+ invoked << :"run_#{letter}"
+ :"state_#{letter}"
+ end
+
+ define_method(:complete) do |state|
+ invoked << :"complete_#{letter}"
+ supplied_state << state
+ end
+ end
+
+ executor.register_hook(hook_class.new(:a))
+ executor.register_hook(hook_class.new(:b))
+ executor.register_hook(hook_class.new(:c), outer: true)
+ executor.register_hook(hook_class.new(:d))
+
+ executor.wrap {}
+
+ assert_equal [:run_c, :run_a, :run_b, :run_d, :complete_a, :complete_b, :complete_d, :complete_c], invoked
+ assert_equal [:state_a, :state_b, :state_d, :state_c], supplied_state
+ end
+
+ def test_class_serial_is_unaffected
+ skip if !defined?(RubyVM)
+
+ hook = Class.new do
+ define_method(:run) do
+ nil
+ end
+
+ define_method(:complete) do |state|
+ nil
+ end
+ end.new
+
+ executor.register_hook(hook)
+
+ before = RubyVM.stat(:class_serial)
+ executor.wrap {}
+ executor.wrap {}
+ executor.wrap {}
+ after = RubyVM.stat(:class_serial)
+
+ assert_equal before, after
+ end
+
def test_separate_classes_can_wrap
other_executor = Class.new(ActiveSupport::Executor)
diff --git a/activesupport/test/file_update_checker_shared_tests.rb b/activesupport/test/file_update_checker_shared_tests.rb
index cd6a58e840..48cd387196 100644
--- a/activesupport/test/file_update_checker_shared_tests.rb
+++ b/activesupport/test/file_update_checker_shared_tests.rb
@@ -49,6 +49,7 @@ module FileUpdateCheckerSharedTests
checker = new_checker(tmpfiles) { i += 1 }
touch(tmpfiles)
+ wait
assert checker.execute_if_updated
assert_equal 1, i
@@ -62,6 +63,7 @@ module FileUpdateCheckerSharedTests
checker = new_checker(tmpfiles) { i += 1 }
touch(tmpfiles)
+ wait
assert checker.execute_if_updated
assert_equal 1, i
@@ -75,6 +77,7 @@ module FileUpdateCheckerSharedTests
checker = new_checker(tmpfiles) { i += 1 }
rm_f(tmpfiles)
+ wait
assert checker.execute_if_updated
assert_equal 1, i
@@ -87,6 +90,7 @@ module FileUpdateCheckerSharedTests
assert !checker.updated?
touch(tmpfiles)
+ wait
assert checker.updated?
end
@@ -100,6 +104,7 @@ module FileUpdateCheckerSharedTests
assert !checker.updated?
touch(tmpfiles)
+ wait
assert checker.updated?
end
@@ -113,6 +118,7 @@ module FileUpdateCheckerSharedTests
assert !checker.updated?
rm_f(tmpfiles)
+ wait
assert checker.updated?
end
@@ -129,6 +135,7 @@ module FileUpdateCheckerSharedTests
checker = new_checker(tmpfiles) { i += 1 }
touch(tmpfiles[1..-1])
+ wait
assert checker.execute_if_updated
assert_equal 1, i
@@ -145,6 +152,7 @@ module FileUpdateCheckerSharedTests
checker = new_checker(tmpfiles) { i += 1 }
touch(tmpfiles[1..-1])
+ wait
assert checker.execute_if_updated
assert_equal 1, i
@@ -157,6 +165,7 @@ module FileUpdateCheckerSharedTests
assert !checker.updated?
touch(tmpfiles)
+ wait
assert checker.updated?
checker.execute
@@ -169,6 +178,7 @@ module FileUpdateCheckerSharedTests
checker = new_checker([], tmpdir => :rb) { i += 1 }
touch(tmpfile("foo.rb"))
+ wait
assert checker.execute_if_updated
assert_equal 1, i
@@ -180,11 +190,13 @@ module FileUpdateCheckerSharedTests
checker = new_checker([], tmpdir => [:rb, :txt]) { i += 1 }
touch(tmpfile("foo.rb"))
+ wait
assert checker.execute_if_updated
assert_equal 1, i
touch(tmpfile("foo.txt"))
+ wait
assert checker.execute_if_updated
assert_equal 2, i
@@ -196,6 +208,7 @@ module FileUpdateCheckerSharedTests
checker = new_checker([], tmpdir => :txt) { i += 1 }
touch(tmpfile("foo.rb"))
+ wait
assert !checker.execute_if_updated
assert_equal 0, i
@@ -208,6 +221,7 @@ module FileUpdateCheckerSharedTests
checker = new_checker([non_existing]) { i += 1 }
touch(non_existing)
+ wait
assert checker.execute_if_updated
assert_equal 1, i
@@ -226,6 +240,7 @@ module FileUpdateCheckerSharedTests
assert_equal 0, i
touch(File.join(subdir, "nested.rb"))
+ wait
assert checker.execute_if_updated
assert_equal 1, i
@@ -240,17 +255,20 @@ module FileUpdateCheckerSharedTests
checker = new_checker([], tmpdir => :rb, subdir => :txt) { i += 1 }
touch(tmpfile("new.txt"))
+ wait
assert !checker.execute_if_updated
assert_equal 0, i
# subdir does not look for Ruby files, but its parent tmpdir does.
touch(File.join(subdir, "nested.rb"))
+ wait
assert checker.execute_if_updated
assert_equal 1, i
touch(File.join(subdir, "nested.txt"))
+ wait
assert checker.execute_if_updated
assert_equal 2, i
diff --git a/activesupport/test/gzip_test.rb b/activesupport/test/gzip_test.rb
index d88408b55c..f51d3cdf65 100644
--- a/activesupport/test/gzip_test.rb
+++ b/activesupport/test/gzip_test.rb
@@ -20,7 +20,7 @@ class GzipTest < ActiveSupport::TestCase
end
def test_compress_should_return_gzipped_string_by_compression_level
- source_string = "Hello World"*100
+ source_string = "Hello World" * 100
gzipped_by_speed = ActiveSupport::Gzip.compress(source_string, Zlib::BEST_SPEED)
assert_equal 1, Zlib::GzipReader.new(StringIO.new(gzipped_by_speed)).level
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 39e8c611e5..8d39303f9b 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -31,6 +31,32 @@ class InflectorTest < ActiveSupport::TestCase
assert_equal "", ActiveSupport::Inflector.pluralize("")
end
+ test "uncountability of ascii word" do
+ word = "HTTP"
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.uncountable word
+ end
+
+ assert_equal word, ActiveSupport::Inflector.pluralize(word)
+ assert_equal word, ActiveSupport::Inflector.singularize(word)
+ assert_equal ActiveSupport::Inflector.pluralize(word), ActiveSupport::Inflector.singularize(word)
+
+ ActiveSupport::Inflector.inflections.uncountables.pop
+ end
+
+ test "uncountability of non-ascii word" do
+ word = "猫"
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.uncountable word
+ end
+
+ assert_equal word, ActiveSupport::Inflector.pluralize(word)
+ assert_equal word, ActiveSupport::Inflector.singularize(word)
+ assert_equal ActiveSupport::Inflector.pluralize(word), ActiveSupport::Inflector.singularize(word)
+
+ ActiveSupport::Inflector.inflections.uncountables.pop
+ end
+
ActiveSupport::Inflector.inflections.uncountable.each do |word|
define_method "test_uncountability_of_#{word}" do
assert_equal word, ActiveSupport::Inflector.singularize(word)
@@ -245,12 +271,12 @@ class InflectorTest < ActiveSupport::TestCase
end
end
-# FIXME: get following tests to pass on jruby, currently skipped
-#
-# Currently this fails because ActiveSupport::Multibyte::Unicode#tidy_bytes
-# required a specific Encoding::Converter(UTF-8 to UTF8-MAC) which unavailable on JRuby
-# causing our tests to error out.
-# related bug http://jira.codehaus.org/browse/JRUBY-7194
+ # FIXME: get following tests to pass on jruby, currently skipped
+ #
+ # Currently this fails because ActiveSupport::Multibyte::Unicode#tidy_bytes
+ # required a specific Encoding::Converter(UTF-8 to UTF8-MAC) which unavailable on JRuby
+ # causing our tests to error out.
+ # related bug http://jira.codehaus.org/browse/JRUBY-7194
def test_parameterize
jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable"
StringToParameterized.each do |some_string, parameterized_string|
@@ -272,15 +298,6 @@ class InflectorTest < ActiveSupport::TestCase
end
end
- def test_parameterize_with_custom_separator_deprecated
- jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable"
- StringToParameterizeWithUnderscore.each do |some_string, parameterized_string|
- assert_deprecated(/Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '_'` instead./i) do
- assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string, "_"))
- end
- end
- end
-
def test_parameterize_with_multi_character_separator
jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable"
StringToParameterized.each do |some_string, parameterized_string|
@@ -288,15 +305,6 @@ class InflectorTest < ActiveSupport::TestCase
end
end
- def test_parameterize_with_multi_character_separator_deprecated
- jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable"
- StringToParameterized.each do |some_string, parameterized_string|
- assert_deprecated(/Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '__sep__'` instead./i) do
- assert_equal(parameterized_string.gsub("-", "__sep__"), ActiveSupport::Inflector.parameterize(some_string, "__sep__"))
- end
- end
- end
-
def test_classify
ClassNameToTableName.each do |class_name, table_name|
assert_equal(class_name, ActiveSupport::Inflector.classify(table_name))
@@ -525,12 +533,4 @@ class InflectorTest < ActiveSupport::TestCase
end
end
end
-
- def test_inflections_with_uncountable_words
- ActiveSupport::Inflector.inflections do |inflect|
- inflect.uncountable "HTTP"
- end
-
- assert_equal "HTTP", ActiveSupport::Inflector.pluralize("HTTP")
- end
end
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index a8283301b3..b660987d92 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -237,7 +237,7 @@ module InflectorTestCases
"Ops\331" => "opsu",
"Ærøskøbing" => "aeroskobing",
"Aßlar" => "asslar",
- "Japanese: 日本語" => "japanese"
+ "Japanese: 日本語" => "japanese"
}
UnderscoreToHuman = {
diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb
index de314921ec..6d1d8f1b95 100644
--- a/activesupport/test/json/decoding_test.rb
+++ b/activesupport/test/json/decoding_test.rb
@@ -13,57 +13,57 @@ class TestJSONDecoding < ActiveSupport::TestCase
end
TESTS = {
- %q({"returnTo":{"\/categories":"\/"}}) => {"returnTo" => {"/categories" => "/"}},
- %q({"return\\"To\\":":{"\/categories":"\/"}}) => {"return\"To\":" => {"/categories" => "/"}},
- %q({"returnTo":{"\/categories":1}}) => {"returnTo" => {"/categories" => 1}},
- %({"returnTo":[1,"a"]}) => {"returnTo" => [1, "a"]},
- %({"returnTo":[1,"\\"a\\",", "b"]}) => {"returnTo" => [1, "\"a\",", "b"]},
- %({"a": "'", "b": "5,000"}) => {"a" => "'", "b" => "5,000"},
- %({"a": "a's, b's and c's", "b": "5,000"}) => {"a" => "a's, b's and c's", "b" => "5,000"},
+ %q({"returnTo":{"\/categories":"\/"}}) => { "returnTo" => { "/categories" => "/" } },
+ %q({"return\\"To\\":":{"\/categories":"\/"}}) => { "return\"To\":" => { "/categories" => "/" } },
+ %q({"returnTo":{"\/categories":1}}) => { "returnTo" => { "/categories" => 1 } },
+ %({"returnTo":[1,"a"]}) => { "returnTo" => [1, "a"] },
+ %({"returnTo":[1,"\\"a\\",", "b"]}) => { "returnTo" => [1, "\"a\",", "b"] },
+ %({"a": "'", "b": "5,000"}) => { "a" => "'", "b" => "5,000" },
+ %({"a": "a's, b's and c's", "b": "5,000"}) => { "a" => "a's, b's and c's", "b" => "5,000" },
# multibyte
- %({"matzue": "松江", "asakusa": "浅草"}) => {"matzue" => "松江", "asakusa" => "浅草"},
- %({"a": "2007-01-01"}) => {"a" => Date.new(2007, 1, 1)},
- %({"a": "2007-01-01 01:12:34 Z"}) => {"a" => Time.utc(2007, 1, 1, 1, 12, 34)},
+ %({"matzue": "松江", "asakusa": "浅草"}) => { "matzue" => "松江", "asakusa" => "浅草" },
+ %({"a": "2007-01-01"}) => { "a" => Date.new(2007, 1, 1) },
+ %({"a": "2007-01-01 01:12:34 Z"}) => { "a" => Time.utc(2007, 1, 1, 1, 12, 34) },
%(["2007-01-01 01:12:34 Z"]) => [Time.utc(2007, 1, 1, 1, 12, 34)],
%(["2007-01-01 01:12:34 Z", "2007-01-01 01:12:35 Z"]) => [Time.utc(2007, 1, 1, 1, 12, 34), Time.utc(2007, 1, 1, 1, 12, 35)],
# no time zone
- %({"a": "2007-01-01 01:12:34"}) => {"a" => Time.new(2007, 1, 1, 1, 12, 34, "-05:00")},
+ %({"a": "2007-01-01 01:12:34"}) => { "a" => Time.new(2007, 1, 1, 1, 12, 34, "-05:00") },
# invalid date
- %({"a": "1089-10-40"}) => {"a" => "1089-10-40"},
+ %({"a": "1089-10-40"}) => { "a" => "1089-10-40" },
# xmlschema date notation
- %({"a": "2009-08-10T19:01:02"}) => {"a" => Time.new(2009, 8, 10, 19, 1, 2, "-04:00")},
- %({"a": "2009-08-10T19:01:02Z"}) => {"a" => Time.utc(2009, 8, 10, 19, 1, 2)},
- %({"a": "2009-08-10T19:01:02+02:00"}) => {"a" => Time.utc(2009, 8, 10, 17, 1, 2)},
- %({"a": "2009-08-10T19:01:02-05:00"}) => {"a" => Time.utc(2009, 8, 11, 00, 1, 2)},
+ %({"a": "2009-08-10T19:01:02"}) => { "a" => Time.new(2009, 8, 10, 19, 1, 2, "-04:00") },
+ %({"a": "2009-08-10T19:01:02Z"}) => { "a" => Time.utc(2009, 8, 10, 19, 1, 2) },
+ %({"a": "2009-08-10T19:01:02+02:00"}) => { "a" => Time.utc(2009, 8, 10, 17, 1, 2) },
+ %({"a": "2009-08-10T19:01:02-05:00"}) => { "a" => Time.utc(2009, 8, 11, 00, 1, 2) },
# needs to be *exact*
- %({"a": " 2007-01-01 01:12:34 Z "}) => {"a" => " 2007-01-01 01:12:34 Z "},
- %({"a": "2007-01-01 : it's your birthday"}) => {"a" => "2007-01-01 : it's your birthday"},
+ %({"a": " 2007-01-01 01:12:34 Z "}) => { "a" => " 2007-01-01 01:12:34 Z " },
+ %({"a": "2007-01-01 : it's your birthday"}) => { "a" => "2007-01-01 : it's your birthday" },
%([]) => [],
%({}) => {},
- %({"a":1}) => {"a" => 1},
- %({"a": ""}) => {"a" => ""},
- %({"a":"\\""}) => {"a" => "\""},
- %({"a": null}) => {"a" => nil},
- %({"a": true}) => {"a" => true},
- %({"a": false}) => {"a" => false},
- '{"bad":"\\\\","trailing":""}' => {"bad" => "\\", "trailing" => ""},
- %q({"a": "http:\/\/test.host\/posts\/1"}) => {"a" => "http://test.host/posts/1"},
- %q({"a": "\u003cunicode\u0020escape\u003e"}) => {"a" => "<unicode escape>"},
- '{"a": "\\\\u0020skip double backslashes"}' => {"a" => "\\u0020skip double backslashes"},
- %q({"a": "\u003cbr /\u003e"}) => {"a" => "<br />"},
- %q({"b":["\u003ci\u003e","\u003cb\u003e","\u003cu\u003e"]}) => {"b" => ["<i>","<b>","<u>"]},
+ %({"a":1}) => { "a" => 1 },
+ %({"a": ""}) => { "a" => "" },
+ %({"a":"\\""}) => { "a" => "\"" },
+ %({"a": null}) => { "a" => nil },
+ %({"a": true}) => { "a" => true },
+ %({"a": false}) => { "a" => false },
+ '{"bad":"\\\\","trailing":""}' => { "bad" => "\\", "trailing" => "" },
+ %q({"a": "http:\/\/test.host\/posts\/1"}) => { "a" => "http://test.host/posts/1" },
+ %q({"a": "\u003cunicode\u0020escape\u003e"}) => { "a" => "<unicode escape>" },
+ '{"a": "\\\\u0020skip double backslashes"}' => { "a" => "\\u0020skip double backslashes" },
+ %q({"a": "\u003cbr /\u003e"}) => { "a" => "<br />" },
+ %q({"b":["\u003ci\u003e","\u003cb\u003e","\u003cu\u003e"]}) => { "b" => ["<i>", "<b>", "<u>"] },
# test combination of dates and escaped or unicode encoded data in arrays
%q([{"d":"1970-01-01", "s":"\u0020escape"},{"d":"1970-01-01", "s":"\u0020escape"}]) =>
- [{"d" => Date.new(1970, 1, 1), "s" => " escape"},{"d" => Date.new(1970, 1, 1), "s" => " escape"}],
+ [{ "d" => Date.new(1970, 1, 1), "s" => " escape" }, { "d" => Date.new(1970, 1, 1), "s" => " escape" }],
%q([{"d":"1970-01-01","s":"http:\/\/example.com"},{"d":"1970-01-01","s":"http:\/\/example.com"}]) =>
- [{"d" => Date.new(1970, 1, 1), "s" => "http://example.com"},
- {"d" => Date.new(1970, 1, 1), "s" => "http://example.com"}],
+ [{ "d" => Date.new(1970, 1, 1), "s" => "http://example.com" },
+ { "d" => Date.new(1970, 1, 1), "s" => "http://example.com" }],
# tests escaping of "\n" char with Yaml backend
- %q({"a":"\n"}) => {"a"=>"\n"},
- %q({"a":"\u000a"}) => {"a"=>"\n"},
- %q({"a":"Line1\u000aLine2"}) => {"a"=>"Line1\nLine2"},
+ %q({"a":"\n"}) => { "a" => "\n" },
+ %q({"a":"\u000a"}) => { "a" => "\n" },
+ %q({"a":"Line1\u000aLine2"}) => { "a" => "Line1\nLine2" },
# prevent json unmarshalling
- '{"json_class":"TestJSONDecoding::Foo"}' => {"json_class"=>"TestJSONDecoding::Foo"},
+ '{"json_class":"TestJSONDecoding::Foo"}' => { "json_class" => "TestJSONDecoding::Foo" },
# json "fragments" - these are invalid JSON, but ActionPack relies on this
'"a string"' => "a string",
"1.1" => 1.1,
@@ -89,7 +89,7 @@ class TestJSONDecoding < ActiveSupport::TestCase
test "json decodes time json with time parsing disabled" do
with_parse_json_times(false) do
- expected = {"a" => "2007-01-01 01:12:34 Z"}
+ expected = { "a" => "2007-01-01 01:12:34 Z" }
assert_equal expected, ActiveSupport::JSON.decode(%({"a": "2007-01-01 01:12:34 Z"}))
end
end
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index 9c95bf35b8..6d8f7cfbd0 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -49,7 +49,7 @@ class TestJSONEncoding < ActiveSupport::TestCase
def test_hash_encoding
assert_equal %({\"a\":\"b\"}), ActiveSupport::JSON.encode(a: :b)
assert_equal %({\"a\":1}), ActiveSupport::JSON.encode("a" => 1)
- assert_equal %({\"a\":[1,2]}), ActiveSupport::JSON.encode("a" => [1,2])
+ assert_equal %({\"a\":[1,2]}), ActiveSupport::JSON.encode("a" => [1, 2])
assert_equal %({"1":2}), ActiveSupport::JSON.encode(1 => 2)
assert_equal %({\"a\":\"b\",\"c\":\"d\"}), sorted_json(ActiveSupport::JSON.encode(a: :b, c: :d))
@@ -93,22 +93,22 @@ class TestJSONEncoding < ActiveSupport::TestCase
end
def test_hash_key_identifiers_are_always_quoted
- values = {0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B"}
+ values = { 0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B" }
assert_equal %w( "$" "A" "A0" "A0B" "_" "a" "0" "1" ).sort, object_keys(ActiveSupport::JSON.encode(values))
end
def test_hash_should_allow_key_filtering_with_only
- assert_equal %({"a":1}), ActiveSupport::JSON.encode({"a" => 1, :b => 2, :c => 3}, only: "a")
+ assert_equal %({"a":1}), ActiveSupport::JSON.encode({ "a" => 1, :b => 2, :c => 3 }, only: "a")
end
def test_hash_should_allow_key_filtering_with_except
- assert_equal %({"b":2}), ActiveSupport::JSON.encode({"foo" => "bar", :b => 2, :c => 3}, except: ["foo", :c])
+ assert_equal %({"b":2}), ActiveSupport::JSON.encode({ "foo" => "bar", :b => 2, :c => 3 }, except: ["foo", :c])
end
def test_time_to_json_includes_local_offset
with_standard_json_time_format(true) do
with_env_tz "US/Eastern" do
- assert_equal %("2005-02-01T15:15:10.000-05:00"), ActiveSupport::JSON.encode(Time.local(2005,2,1,15,15,10))
+ assert_equal %("2005-02-01T15:15:10.000-05:00"), ActiveSupport::JSON.encode(Time.local(2005, 2, 1, 15, 15, 10))
end
end
end
@@ -135,7 +135,7 @@ class TestJSONEncoding < ActiveSupport::TestCase
h = JSONTest::Hashlike.new
json = h.to_json only: [:foo]
- assert_equal({"foo"=>"hello"}, JSON.parse(json))
+ assert_equal({ "foo" => "hello" }, JSON.parse(json))
end
def test_object_to_json_with_options
@@ -144,7 +144,7 @@ class TestJSONEncoding < ActiveSupport::TestCase
obj.instance_variable_set :@bar, "world"
json = obj.to_json only: ["foo"]
- assert_equal({"foo"=>"hello"}, JSON.parse(json))
+ assert_equal({ "foo" => "hello" }, JSON.parse(json))
end
def test_struct_to_json_with_options
@@ -153,7 +153,7 @@ class TestJSONEncoding < ActiveSupport::TestCase
struct.bar = "world"
json = struct.to_json only: [:foo]
- assert_equal({"foo"=>"hello"}, JSON.parse(json))
+ assert_equal({ "foo" => "hello" }, JSON.parse(json))
end
def test_hash_should_pass_encoding_options_to_children_in_as_json
@@ -166,7 +166,7 @@ class TestJSONEncoding < ActiveSupport::TestCase
}
json = person.as_json only: [:address, :city]
- assert_equal({ "address" => { "city" => "London" }}, json)
+ assert_equal({ "address" => { "city" => "London" } }, json)
end
def test_hash_should_pass_encoding_options_to_children_in_to_json
@@ -184,13 +184,13 @@ class TestJSONEncoding < ActiveSupport::TestCase
def test_array_should_pass_encoding_options_to_children_in_as_json
people = [
- { name: "John", address: { city: "London", country: "UK" }},
- { name: "Jean", address: { city: "Paris" , country: "France" }}
+ { name: "John", address: { city: "London", country: "UK" } },
+ { name: "Jean", address: { city: "Paris" , country: "France" } }
]
json = people.as_json only: [:address, :city]
expected = [
- { "address" => { "city" => "London" }},
- { "address" => { "city" => "Paris" }}
+ { "address" => { "city" => "London" } },
+ { "address" => { "city" => "Paris" } }
]
assert_equal(expected, json)
@@ -198,8 +198,8 @@ class TestJSONEncoding < ActiveSupport::TestCase
def test_array_should_pass_encoding_options_to_children_in_to_json
people = [
- { name: "John", address: { city: "London", country: "UK" }},
- { name: "Jean", address: { city: "Paris" , country: "France" }}
+ { name: "John", address: { city: "London", country: "UK" } },
+ { name: "Jean", address: { city: "Paris" , country: "France" } }
]
json = people.to_json only: [:address, :city]
@@ -210,8 +210,8 @@ class TestJSONEncoding < ActiveSupport::TestCase
include Enumerable
def initialize()
@people = [
- { name: "John", address: { city: "London", country: "UK" }},
- { name: "Jean", address: { city: "Paris" , country: "France" }}
+ { name: "John", address: { city: "London", country: "UK" } },
+ { name: "Jean", address: { city: "Paris" , country: "France" } }
]
end
def each(*, &blk)
@@ -225,8 +225,8 @@ class TestJSONEncoding < ActiveSupport::TestCase
def test_enumerable_should_generate_json_with_as_json
json = People.new.as_json only: [:address, :city]
expected = [
- { "address" => { "city" => "London" }},
- { "address" => { "city" => "Paris" }}
+ { "address" => { "city" => "London" } },
+ { "address" => { "city" => "Paris" } }
]
assert_equal(expected, json)
@@ -240,8 +240,8 @@ class TestJSONEncoding < ActiveSupport::TestCase
def test_enumerable_should_pass_encoding_options_to_children_in_as_json
json = People.new.each.as_json only: [:address, :city]
expected = [
- { "address" => { "city" => "London" }},
- { "address" => { "city" => "Paris" }}
+ { "address" => { "city" => "London" } },
+ { "address" => { "city" => "Paris" } }
]
assert_equal(expected, json)
@@ -256,7 +256,7 @@ class TestJSONEncoding < ActiveSupport::TestCase
class CustomWithOptions
attr_accessor :foo, :bar
- def as_json(options={})
+ def as_json(options = {})
options[:only] = %w(foo bar)
super(options)
end
@@ -267,9 +267,9 @@ class TestJSONEncoding < ActiveSupport::TestCase
f.foo = "hello"
f.bar = "world"
- hash = {"foo" => f, "other_hash" => {"foo" => "other_foo", "test" => "other_test"}}
- assert_equal({"foo"=>{"foo"=>"hello","bar"=>"world"},
- "other_hash" => {"foo"=>"other_foo","test"=>"other_test"}}, ActiveSupport::JSON.decode(hash.to_json))
+ hash = { "foo" => f, "other_hash" => { "foo" => "other_foo", "test" => "other_test" } }
+ assert_equal({ "foo" => { "foo" => "hello", "bar" => "world" },
+ "other_hash" => { "foo" => "other_foo", "test" => "other_test" } }, ActiveSupport::JSON.decode(hash.to_json))
end
def test_array_to_json_should_not_keep_options_around
@@ -277,9 +277,9 @@ class TestJSONEncoding < ActiveSupport::TestCase
f.foo = "hello"
f.bar = "world"
- array = [f, {"foo" => "other_foo", "test" => "other_test"}]
- assert_equal([{"foo"=>"hello","bar"=>"world"},
- {"foo"=>"other_foo","test"=>"other_test"}], ActiveSupport::JSON.decode(array.to_json))
+ array = [f, { "foo" => "other_foo", "test" => "other_test" }]
+ assert_equal([{ "foo" => "hello", "bar" => "world" },
+ { "foo" => "other_foo", "test" => "other_test" }], ActiveSupport::JSON.decode(array.to_json))
end
class OptionsTest
@@ -290,7 +290,7 @@ class TestJSONEncoding < ActiveSupport::TestCase
def test_hash_as_json_without_options
json = { foo: OptionsTest.new }.as_json
- assert_equal({"foo" => :default}, json)
+ assert_equal({ "foo" => :default }, json)
end
def test_array_as_json_without_options
@@ -316,20 +316,20 @@ class TestJSONEncoding < ActiveSupport::TestCase
json_custom = custom.to_json
end
- assert_equal({"name" => "David",
+ assert_equal({ "name" => "David",
"sub" => {
"name" => "David",
- "date" => "2010-01-01" }}, ActiveSupport::JSON.decode(json_custom))
+ "date" => "2010-01-01" } }, ActiveSupport::JSON.decode(json_custom))
- assert_equal({"name" => "David", "email" => "sample@example.com"},
+ assert_equal({ "name" => "David", "email" => "sample@example.com" },
ActiveSupport::JSON.decode(json_strings))
- assert_equal({"name" => "David", "date" => "2010-01-01"},
+ assert_equal({ "name" => "David", "date" => "2010-01-01" },
ActiveSupport::JSON.decode(json_string_and_date))
end
def test_nil_true_and_false_represented_as_themselves
- assert_equal nil, nil.as_json
+ assert_nil nil.as_json
assert_equal true, true.as_json
assert_equal false, false.as_json
end
@@ -341,7 +341,7 @@ class TestJSONEncoding < ActiveSupport::TestCase
super
end
- def as_json(options={})
+ def as_json(options = {})
@as_json_called = true
super
end
@@ -432,7 +432,27 @@ EXPECTED
assert_equal '"foo"', ActiveSupport::JSON.encode(exception)
end
- protected
+ class InfiniteNumber
+ def as_json(options = nil)
+ { "number" => Float::INFINITY }
+ end
+ end
+
+ def test_to_json_works_when_as_json_returns_infinite_number
+ assert_equal '{"number":null}', InfiniteNumber.new.to_json
+ end
+
+ class NaNNumber
+ def as_json(options = nil)
+ { "number" => Float::NAN }
+ end
+ end
+
+ def test_to_json_works_when_as_json_returns_NaN_number
+ assert_equal '{"number":null}', NaNNumber.new.to_json
+ end
+
+ private
def object_keys(json_object)
json_object[1..-2].scan(/([^{}:,\s]+):/).flatten.sort
diff --git a/activesupport/test/json/encoding_test_cases.rb b/activesupport/test/json/encoding_test_cases.rb
index f8c44f314f..8eac246937 100644
--- a/activesupport/test/json/encoding_test_cases.rb
+++ b/activesupport/test/json/encoding_test_cases.rb
@@ -36,10 +36,10 @@ module JSONTest
NilTests = [[ nil, %(null) ]]
NumericTests = [[ 1, %(1) ],
[ 2.5, %(2.5) ],
- [ 0.0/0.0, %(null) ],
- [ 1.0/0.0, %(null) ],
- [ -1.0/0.0, %(null) ],
- [ BigDecimal("0.0")/BigDecimal("0.0"), %(null) ],
+ [ 0.0 / 0.0, %(null) ],
+ [ 1.0 / 0.0, %(null) ],
+ [ -1.0 / 0.0, %(null) ],
+ [ BigDecimal("0.0") / BigDecimal("0.0"), %(null) ],
[ BigDecimal("2.5"), %("#{BigDecimal('2.5')}") ]]
StringTests = [[ "this is the <string>", %("this is the \\u003cstring\\u003e")],
@@ -51,8 +51,8 @@ module JSONTest
ArrayTests = [[ ["a", "b", "c"], %([\"a\",\"b\",\"c\"]) ],
[ [1, "a", :b, nil, false], %([1,\"a\",\"b\",null,false]) ]]
- HashTests = [[ {foo: "bar"}, %({\"foo\":\"bar\"}) ],
- [ {1 => 1, 2 => "a", 3 => :b, 4 => nil, 5 => false}, %({\"1\":1,\"2\":\"a\",\"3\":\"b\",\"4\":null,\"5\":false}) ]]
+ HashTests = [[ { foo: "bar" }, %({\"foo\":\"bar\"}) ],
+ [ { 1 => 1, 2 => "a", 3 => :b, 4 => nil, 5 => false }, %({\"1\":1,\"2\":\"a\",\"3\":\"b\",\"4\":null,\"5\":false}) ]]
RangeTests = [[ 1..2, %("1..2")],
[ 1...2, %("1...2")],
@@ -80,13 +80,13 @@ module JSONTest
PathnameTests = [[ Pathname.new("lib/index.rb"), %("lib/index.rb") ]]
- DateTests = [[ Date.new(2005,2,1), %("2005/02/01") ]]
- TimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
- DateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
+ DateTests = [[ Date.new(2005, 2, 1), %("2005/02/01") ]]
+ TimeTests = [[ Time.utc(2005, 2, 1, 15, 15, 10), %("2005/02/01 15:15:10 +0000") ]]
+ DateTimeTests = [[ DateTime.civil(2005, 2, 1, 15, 15, 10), %("2005/02/01 15:15:10 +0000") ]]
- StandardDateTests = [[ Date.new(2005,2,1), %("2005-02-01") ]]
- StandardTimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000Z") ]]
- StandardDateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000+00:00") ]]
+ StandardDateTests = [[ Date.new(2005, 2, 1), %("2005-02-01") ]]
+ StandardTimeTests = [[ Time.utc(2005, 2, 1, 15, 15, 10), %("2005-02-01T15:15:10.000Z") ]]
+ StandardDateTimeTests = [[ DateTime.civil(2005, 2, 1, 15, 15, 10), %("2005-02-01T15:15:10.000+00:00") ]]
StandardStringTests = [[ "this is the <string>", %("this is the <string>")]]
end
end
diff --git a/activesupport/test/logger_test.rb b/activesupport/test/logger_test.rb
index 74e94bb9d7..3f04783401 100644
--- a/activesupport/test/logger_test.rb
+++ b/activesupport/test/logger_test.rb
@@ -67,7 +67,7 @@ class LoggerTest < ActiveSupport::TestCase
def test_should_log_debugging_message_when_debugging
@logger.level = Logger::DEBUG
@logger.add(Logger::DEBUG, @message)
- assert @output.string.include?(@message)
+ assert_includes @output.string, @message
end
def test_should_not_log_debug_messages_when_log_level_is_info
@@ -78,32 +78,32 @@ class LoggerTest < ActiveSupport::TestCase
def test_should_add_message_passed_as_block_when_using_add
@logger.level = Logger::INFO
- @logger.add(Logger::INFO) {@message}
- assert @output.string.include?(@message)
+ @logger.add(Logger::INFO) { @message }
+ assert_includes @output.string, @message
end
def test_should_add_message_passed_as_block_when_using_shortcut
@logger.level = Logger::INFO
- @logger.info {@message}
- assert @output.string.include?(@message)
+ @logger.info { @message }
+ assert_includes @output.string, @message
end
def test_should_convert_message_to_string
@logger.level = Logger::INFO
@logger.info @integer_message
- assert @output.string.include?(@integer_message.to_s)
+ assert_includes @output.string, @integer_message.to_s
end
def test_should_convert_message_to_string_when_passed_in_block
@logger.level = Logger::INFO
- @logger.info {@integer_message}
- assert @output.string.include?(@integer_message.to_s)
+ @logger.info { @integer_message }
+ assert_includes @output.string, @integer_message.to_s
end
def test_should_not_evaluate_block_if_message_wont_be_logged
@logger.level = Logger::INFO
evaluated = false
- @logger.add(Logger::DEBUG) {evaluated = true}
+ @logger.add(Logger::DEBUG) { evaluated = true }
assert evaluated == false
end
@@ -125,10 +125,10 @@ class LoggerTest < ActiveSupport::TestCase
@logger.level = Logger::INFO
@logger.info(UNICODE_STRING)
@logger.info(BYTE_STRING)
- assert @output.string.include?(UNICODE_STRING)
+ assert_includes @output.string, UNICODE_STRING
byte_string = @output.string.dup
byte_string.force_encoding("ASCII-8BIT")
- assert byte_string.include?(BYTE_STRING)
+ assert_includes byte_string, BYTE_STRING
end
def test_silencing_everything_but_errors
@@ -138,7 +138,7 @@ class LoggerTest < ActiveSupport::TestCase
end
assert_not @output.string.include?("NOT THERE")
- assert @output.string.include?("THIS IS HERE")
+ assert_includes @output.string, "THIS IS HERE"
end
def test_logger_silencing_works_for_broadcast
@@ -154,12 +154,12 @@ class LoggerTest < ActiveSupport::TestCase
@logger.error "CORRECT ERROR"
end
- assert @output.string.include?("CORRECT DEBUG")
- assert @output.string.include?("CORRECT ERROR")
+ assert_includes @output.string, "CORRECT DEBUG"
+ assert_includes @output.string, "CORRECT ERROR"
assert_not @output.string.include?("FAILURE")
- assert another_output.string.include?("CORRECT DEBUG")
- assert another_output.string.include?("CORRECT ERROR")
+ assert_includes another_output.string, "CORRECT DEBUG"
+ assert_includes another_output.string, "CORRECT ERROR"
assert_not another_output.string.include?("FAILURE")
end
@@ -176,13 +176,13 @@ class LoggerTest < ActiveSupport::TestCase
@logger.error "CORRECT ERROR"
end
- assert @output.string.include?("CORRECT DEBUG")
- assert @output.string.include?("CORRECT ERROR")
+ assert_includes @output.string, "CORRECT DEBUG"
+ assert_includes @output.string, "CORRECT ERROR"
assert_not @output.string.include?("FAILURE")
- assert another_output.string.include?("CORRECT DEBUG")
- assert another_output.string.include?("CORRECT ERROR")
- assert another_output.string.include?("FAILURE")
+ assert_includes another_output.string, "CORRECT DEBUG"
+ assert_includes another_output.string, "CORRECT ERROR"
+ assert_includes another_output.string, "FAILURE"
# We can't silence plain ruby Logger cause with thread safety
# but at least we don't break it
end
diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb
index fb0b1e377d..56a436f751 100644
--- a/activesupport/test/message_encryptor_test.rb
+++ b/activesupport/test/message_encryptor_test.rb
@@ -48,6 +48,16 @@ class MessageEncryptorTest < ActiveSupport::TestCase
assert_equal @data, @encryptor.decrypt_and_verify(message)
end
+ def test_backwards_compat_for_64_bytes_key
+ # 64 bit key
+ secret = ["3942b1bf81e622559ed509e3ff274a780784fe9e75b065866bd270438c74da822219de3156473cc27df1fd590e4baf68c95eeb537b6e4d4c5a10f41635b5597e"].pack("H*")
+ # Encryptor with 32 bit key, 64 bit secret for verifier
+ encryptor = ActiveSupport::MessageEncryptor.new(secret[0..31], secret)
+ # Message generated with 64 bit key
+ message = "eHdGeExnZEwvMSt3U3dKaFl1WFo0TjVvYzA0eGpjbm5WSkt5MXlsNzhpZ0ZnbWhBWFlQZTRwaXE1bVJCS2oxMDZhYVp2dVN3V0lNZUlWQ3c2eVhQbnhnVjFmeVVubmhRKzF3WnZyWHVNMDg9LS1HSisyakJVSFlPb05ISzRMaXRzcFdBPT0=--831a1d54a3cda8a0658dc668a03dedcbce13b5ca"
+ assert_equal "data", encryptor.decrypt_and_verify(message)[:some]
+ end
+
def test_alternative_serialization_method
prev = ActiveSupport.use_standard_json_time_format
ActiveSupport.use_standard_json_time_format = true
@@ -61,7 +71,7 @@ class MessageEncryptorTest < ActiveSupport::TestCase
def test_message_obeys_strict_encoding
bad_encoding_characters = "\n!@#"
- message, iv = @encryptor.encrypt_and_sign("This is a very \n\nhumble string"+bad_encoding_characters)
+ message, iv = @encryptor.encrypt_and_sign("This is a very \n\nhumble string" + bad_encoding_characters)
assert_not_decrypted("#{::Base64.encode64 message.to_s}--#{::Base64.encode64 iv.to_s}")
assert_not_verified("#{::Base64.encode64 message.to_s}--#{::Base64.encode64 iv.to_s}")
diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb
index d56a46b250..d6109c761d 100644
--- a/activesupport/test/message_verifier_test.rb
+++ b/activesupport/test/message_verifier_test.rb
@@ -80,6 +80,6 @@ class MessageVerifierTest < ActiveSupport::TestCase
exception = assert_raise(ArgumentError) do
ActiveSupport::MessageVerifier.new(nil)
end
- assert_equal exception.message, "Secret should not be nil."
+ assert_equal "Secret should not be nil.", exception.message
end
end
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index 9ca9c7c803..68ce42bf72 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -208,16 +208,16 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
end
def test_insert_throws_index_error
- assert_raise(IndexError) { @chars.insert(-12, "わ")}
+ assert_raise(IndexError) { @chars.insert(-12, "わ") }
assert_raise(IndexError) { @chars.insert(12, "わ") }
end
def test_should_know_if_one_includes_the_other
- assert @chars.include?("")
- assert @chars.include?("ち")
- assert @chars.include?("わ")
- assert !@chars.include?("こちわ")
- assert !@chars.include?("a")
+ assert_includes @chars, ""
+ assert_includes @chars, "ち"
+ assert_includes @chars, "わ"
+ assert_not_includes @chars, "こちわ"
+ assert_not_includes @chars, "a"
end
def test_include_raises_when_nil_is_passed
@@ -231,7 +231,7 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
assert_equal 0, @chars.index("こに")
assert_equal 2, @chars.index("ち")
assert_equal 2, @chars.index("ち", -2)
- assert_equal nil, @chars.index("ち", -1)
+ assert_nil @chars.index("ち", -1)
assert_equal 3, @chars.index("わ")
assert_equal 5, "ééxééx".mb_chars.index("x", 4)
end
@@ -390,11 +390,11 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
end
def test_slice_should_take_character_offsets
- assert_equal nil, "".mb_chars.slice(0)
+ assert_nil "".mb_chars.slice(0)
assert_equal "こ", @chars.slice(0)
assert_equal "わ", @chars.slice(3)
- assert_equal nil, "".mb_chars.slice(-1..1)
- assert_equal nil, "".mb_chars.slice(-1, 1)
+ assert_nil "".mb_chars.slice(-1..1)
+ assert_nil "".mb_chars.slice(-1, 1)
assert_equal "", "".mb_chars.slice(0..10)
assert_equal "にちわ", @chars.slice(1..3)
assert_equal "にちわ", @chars.slice(1, 3)
@@ -403,10 +403,10 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
assert_equal "", @chars.slice(4..10)
assert_equal "に", @chars.slice(/に/u)
assert_equal "にち", @chars.slice(/に./u)
- assert_equal nil, @chars.slice(/unknown/u)
+ assert_nil @chars.slice(/unknown/u)
assert_equal "にち", @chars.slice(/(にち)/u, 1)
- assert_equal nil, @chars.slice(/(にち)/u, 2)
- assert_equal nil, @chars.slice(7..6)
+ assert_nil @chars.slice(/(にち)/u, 2)
+ assert_nil @chars.slice(7..6)
end
def test_slice_bang_returns_sliced_out_substring
@@ -414,12 +414,12 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
end
def test_slice_bang_returns_nil_on_out_of_bound_arguments
- assert_equal nil, @chars.mb_chars.slice!(9..10)
+ assert_nil @chars.mb_chars.slice!(9..10)
end
def test_slice_bang_removes_the_slice_from_the_receiver
chars = "úüù".mb_chars
- chars.slice!(0,2)
+ chars.slice!(0, 2)
assert_equal "ù", chars
end
@@ -479,7 +479,7 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
assert_equal "Hello", chars.method(:capitalize!).call # Defined on Chars
assert_equal "Hello", chars
assert_equal "jello", "hello".mb_chars.method(:gsub).call(/h/, "j") # Defined on String
- assert_raise(NameError){ "".mb_chars.method(:undefined_method) } # Not defined
+ assert_raise(NameError) { "".mb_chars.method(:undefined_method) } # Not defined
end
def test_acts_like_string
@@ -512,7 +512,7 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase
{ "аБвг аБвг" => "Абвг абвг",
"аБвг АБВГ" => "Абвг абвг",
"АБВГ АБВГ" => "Абвг абвг",
- "" => "" }.each do |f,t|
+ "" => "" }.each do |f, t|
assert_equal t, chars(f).capitalize
end
end
@@ -600,10 +600,10 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase
].pack("U*")
assert_equal_codepoints "", chars("").normalize
- assert_equal_codepoints [44,105,106,328,323].pack("U*"), chars(comp_str).normalize(:kc).to_s
- assert_equal_codepoints [44,307,328,323].pack("U*"), chars(comp_str).normalize(:c).to_s
- assert_equal_codepoints [44,307,110,780,78,769].pack("U*"), chars(comp_str).normalize(:d).to_s
- assert_equal_codepoints [44,105,106,110,780,78,769].pack("U*"), chars(comp_str).normalize(:kd).to_s
+ assert_equal_codepoints [44, 105, 106, 328, 323].pack("U*"), chars(comp_str).normalize(:kc).to_s
+ assert_equal_codepoints [44, 307, 328, 323].pack("U*"), chars(comp_str).normalize(:c).to_s
+ assert_equal_codepoints [44, 307, 110, 780, 78, 769].pack("U*"), chars(comp_str).normalize(:d).to_s
+ assert_equal_codepoints [44, 105, 106, 110, 780, 78, 769].pack("U*"), chars(comp_str).normalize(:kd).to_s
end
def test_should_compute_grapheme_length
diff --git a/activesupport/test/multibyte_conformance_test.rb b/activesupport/test/multibyte_conformance_test.rb
index a98e542783..ef1a26135f 100644
--- a/activesupport/test/multibyte_conformance_test.rb
+++ b/activesupport/test/multibyte_conformance_test.rb
@@ -82,7 +82,7 @@ class MultibyteConformanceTest < ActiveSupport::TestCase
end
end
- protected
+ private
def each_line_of_norm_tests(&block)
File.open(File.join(CACHE_DIR, UNIDATA_FILE), "r") do | f |
until f.eof?
@@ -94,7 +94,7 @@ class MultibyteConformanceTest < ActiveSupport::TestCase
next unless cols.length == 5
# codepoints are in hex in the test suite, pack wants them as integers
- cols.map!{|c| c.split.map{|codepoint| codepoint.to_i(16)}.pack("U*") }
+ cols.map! { |c| c.split.map { |codepoint| codepoint.to_i(16) }.pack("U*") }
cols << comment
yield(*cols)
@@ -103,6 +103,6 @@ class MultibyteConformanceTest < ActiveSupport::TestCase
end
def inspect_codepoints(str)
- str.to_s.unpack("U*").map{|cp| cp.to_s(16) }.join(" ")
+ str.to_s.unpack("U*").map { |cp| cp.to_s(16) }.join(" ")
end
end
diff --git a/activesupport/test/multibyte_grapheme_break_conformance_test.rb b/activesupport/test/multibyte_grapheme_break_conformance_test.rb
index bb7668acd3..b3328987ae 100644
--- a/activesupport/test/multibyte_grapheme_break_conformance_test.rb
+++ b/activesupport/test/multibyte_grapheme_break_conformance_test.rb
@@ -28,24 +28,24 @@ class MultibyteGraphemeBreakConformanceTest < ActiveSupport::TestCase
end
end
- protected
+ private
def each_line_of_break_tests(&block)
lines = 0
max_test_lines = 0 # Don't limit below 21, because that's the header of the testfile
File.open(File.join(CACHE_DIR, UNIDATA_FILE), "r") do | f |
- until f.eof? || (max_test_lines > 21 and lines > max_test_lines)
+ until f.eof? || (max_test_lines > 21 && lines > max_test_lines)
lines += 1
line = f.gets.chomp!
next if line.empty? || line.start_with?("#")
cols, comment = line.split("#")
# Cluster breaks are represented by ÷
- clusters = cols.split("÷").map{|e| e.strip}.reject{|e| e.empty? }
+ clusters = cols.split("÷").map { |e| e.strip }.reject { |e| e.empty? }
clusters = clusters.map do |cluster|
# Codepoints within each cluster are separated by ×
- codepoints = cluster.split("×").map{|e| e.strip}.reject{|e| e.empty? }
+ codepoints = cluster.split("×").map { |e| e.strip }.reject { |e| e.empty? }
# codepoints are in hex in the test suite, pack wants them as integers
- codepoints.map{|codepoint| codepoint.to_i(16)}
+ codepoints.map { |codepoint| codepoint.to_i(16) }
end
# The tests contain a solitary U+D800 <Non Private Use High
diff --git a/activesupport/test/multibyte_normalization_conformance_test.rb b/activesupport/test/multibyte_normalization_conformance_test.rb
index 4f1ace4346..ebc9f92d23 100644
--- a/activesupport/test/multibyte_normalization_conformance_test.rb
+++ b/activesupport/test/multibyte_normalization_conformance_test.rb
@@ -83,22 +83,22 @@ class MultibyteNormalizationConformanceTest < ActiveSupport::TestCase
end
end
- protected
+ private
def each_line_of_norm_tests(&block)
lines = 0
max_test_lines = 0 # Don't limit below 38, because that's the header of the testfile
File.open(File.join(CACHE_DIR, UNIDATA_FILE), "r") do | f |
- until f.eof? || (max_test_lines > 38 and lines > max_test_lines)
+ until f.eof? || (max_test_lines > 38 && lines > max_test_lines)
lines += 1
line = f.gets.chomp!
next if line.empty? || line.start_with?("#")
cols, comment = line.split("#")
- cols = cols.split(";").map{|e| e.strip}.reject{|e| e.empty? }
+ cols = cols.split(";").map { |e| e.strip }.reject { |e| e.empty? }
next unless cols.length == 5
# codepoints are in hex in the test suite, pack wants them as integers
- cols.map!{|c| c.split.map{|codepoint| codepoint.to_i(16)}.pack("U*") }
+ cols.map! { |c| c.split.map { |codepoint| codepoint.to_i(16) }.pack("U*") }
cols << comment
yield(*cols)
@@ -107,6 +107,6 @@ class MultibyteNormalizationConformanceTest < ActiveSupport::TestCase
end
def inspect_codepoints(str)
- str.to_s.unpack("U*").map{|cp| cp.to_s(16) }.join(" ")
+ str.to_s.unpack("U*").map { |cp| cp.to_s(16) }.join(" ")
end
end
diff --git a/activesupport/test/multibyte_test_helpers.rb b/activesupport/test/multibyte_test_helpers.rb
index 29a384729a..2201860d8a 100644
--- a/activesupport/test/multibyte_test_helpers.rb
+++ b/activesupport/test/multibyte_test_helpers.rb
@@ -30,10 +30,10 @@ module MultibyteTestHelpers
end
def inspect_codepoints(str)
- str.to_s.unpack("U*").map{|cp| cp.to_s(16) }.join(" ")
+ str.to_s.unpack("U*").map { |cp| cp.to_s(16) }.join(" ")
end
- def assert_equal_codepoints(expected, actual, message=nil)
+ def assert_equal_codepoints(expected, actual, message = nil)
assert_equal(inspect_codepoints(expected), inspect_codepoints(actual), message)
end
end
diff --git a/activesupport/test/notifications/evented_notification_test.rb b/activesupport/test/notifications/evented_notification_test.rb
index 688971c858..24c5befec3 100644
--- a/activesupport/test/notifications/evented_notification_test.rb
+++ b/activesupport/test/notifications/evented_notification_test.rb
@@ -7,7 +7,7 @@ module ActiveSupport
attr_reader :events
def initialize
- @events = []
+ @events = []
end
def start(name, id, payload)
diff --git a/activesupport/test/notifications/instrumenter_test.rb b/activesupport/test/notifications/instrumenter_test.rb
index e454e6897b..7eacc5cbe7 100644
--- a/activesupport/test/notifications/instrumenter_test.rb
+++ b/activesupport/test/notifications/instrumenter_test.rb
@@ -22,11 +22,11 @@ module ActiveSupport
super
@notifier = TestNotifier.new
@instrumenter = Instrumenter.new @notifier
- @payload = { foo: Object.new }
+ @payload = { foo: Object.new }
end
def test_instrument
- called = false
+ called = false
instrumenter.instrument("foo", payload) {
called = true
}
diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb
index 106aa9c892..11f743519f 100644
--- a/activesupport/test/notifications_test.rb
+++ b/activesupport/test/notifications_test.rb
@@ -31,7 +31,7 @@ module Notifications
expected = [name, name]
events = []
- callback = lambda {|*_| events << _.first}
+ callback = lambda { |*_| events << _.first }
ActiveSupport::Notifications.subscribed(callback, name) do
ActiveSupport::Notifications.instrument(name)
ActiveSupport::Notifications.instrument(name2)
@@ -273,7 +273,7 @@ module Notifications
assert !not_child.parent_of?(parent)
end
- protected
+ private
def random_id
@random_id ||= SecureRandom.hex(10)
end
diff --git a/activesupport/test/number_helper_i18n_test.rb b/activesupport/test/number_helper_i18n_test.rb
index 32e6e22525..4f58e6607a 100644
--- a/activesupport/test/number_helper_i18n_test.rb
+++ b/activesupport/test/number_helper_i18n_test.rb
@@ -26,9 +26,9 @@ module ActiveSupport
decimal_units: {
format: "%n %u",
units: {
- deci: {one: "Tenth", other: "Tenths"},
+ deci: { one: "Tenth", other: "Tenths" },
unit: "u",
- ten: {one: "Ten", other: "Tens"},
+ ten: { one: "Ten", other: "Tens" },
thousand: "t",
million: "m",
billion: "b",
@@ -37,10 +37,10 @@ module ActiveSupport
}
}
},
- percentage: { format: {delimiter: "", precision: 2, strip_insignificant_zeros: true} },
- precision: { format: {delimiter: "", significant: true} }
+ percentage: { format: { delimiter: "", precision: 2, strip_insignificant_zeros: true } },
+ precision: { format: { delimiter: "", significant: true } }
},
- custom_units_for_number_to_human: {mili: "mm", centi: "cm", deci: "dm", unit: "m", ten: "dam", hundred: "hm", thousand: "km"}
+ custom_units_for_number_to_human: { mili: "mm", centi: "cm", deci: "dm", unit: "m", ten: "dam", hundred: "hm", thousand: "km" }
end
def teardown
diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb
index cd6f9ad234..dc0c34d4e2 100644
--- a/activesupport/test/number_helper_test.rb
+++ b/activesupport/test/number_helper_test.rb
@@ -150,7 +150,7 @@ module ActiveSupport
assert_equal("111.23460000000000000000", number_helper.number_to_rounded(Rational(1112346, 10000), precision: 20))
assert_equal("111.23460000000000000000", number_helper.number_to_rounded("111.2346", precision: 20))
assert_equal("111.23460000000000000000", number_helper.number_to_rounded(BigDecimal(111.2346, Float::DIG), precision: 20))
- assert_equal("111.2346" + "0"*96, number_helper.number_to_rounded("111.2346", precision: 100))
+ assert_equal("111.2346" + "0" * 96, number_helper.number_to_rounded("111.2346", precision: 100))
assert_equal("111.2346", number_helper.number_to_rounded(Rational(1112346, 10000), precision: 4))
assert_equal("0.00", number_helper.number_to_rounded(Rational(0, 1), precision: 2))
end
@@ -166,42 +166,42 @@ module ActiveSupport
def test_to_rounded_with_significant_digits
[@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
assert_equal "124000", number_helper.number_to_rounded(123987, precision: 3, significant: true)
- assert_equal "120000000", number_helper.number_to_rounded(123987876, precision: 2, significant: true )
- assert_equal "40000", number_helper.number_to_rounded("43523", precision: 1, significant: true )
- assert_equal "9775", number_helper.number_to_rounded(9775, precision: 4, significant: true )
- assert_equal "5.4", number_helper.number_to_rounded(5.3923, precision: 2, significant: true )
- assert_equal "5", number_helper.number_to_rounded(5.3923, precision: 1, significant: true )
- assert_equal "1", number_helper.number_to_rounded(1.232, precision: 1, significant: true )
- assert_equal "7", number_helper.number_to_rounded(7, precision: 1, significant: true )
- assert_equal "1", number_helper.number_to_rounded(1, precision: 1, significant: true )
- assert_equal "53", number_helper.number_to_rounded(52.7923, precision: 2, significant: true )
- assert_equal "9775.00", number_helper.number_to_rounded(9775, precision: 6, significant: true )
- assert_equal "5.392900", number_helper.number_to_rounded(5.3929, precision: 7, significant: true )
- assert_equal "0.0", number_helper.number_to_rounded(0, precision: 2, significant: true )
- assert_equal "0", number_helper.number_to_rounded(0, precision: 1, significant: true )
- assert_equal "0.0001", number_helper.number_to_rounded(0.0001, precision: 1, significant: true )
- assert_equal "0.000100", number_helper.number_to_rounded(0.0001, precision: 3, significant: true )
- assert_equal "0.0001", number_helper.number_to_rounded(0.0001111, precision: 1, significant: true )
+ assert_equal "120000000", number_helper.number_to_rounded(123987876, precision: 2, significant: true)
+ assert_equal "40000", number_helper.number_to_rounded("43523", precision: 1, significant: true)
+ assert_equal "9775", number_helper.number_to_rounded(9775, precision: 4, significant: true)
+ assert_equal "5.4", number_helper.number_to_rounded(5.3923, precision: 2, significant: true)
+ assert_equal "5", number_helper.number_to_rounded(5.3923, precision: 1, significant: true)
+ assert_equal "1", number_helper.number_to_rounded(1.232, precision: 1, significant: true)
+ assert_equal "7", number_helper.number_to_rounded(7, precision: 1, significant: true)
+ assert_equal "1", number_helper.number_to_rounded(1, precision: 1, significant: true)
+ assert_equal "53", number_helper.number_to_rounded(52.7923, precision: 2, significant: true)
+ assert_equal "9775.00", number_helper.number_to_rounded(9775, precision: 6, significant: true)
+ assert_equal "5.392900", number_helper.number_to_rounded(5.3929, precision: 7, significant: true)
+ assert_equal "0.0", number_helper.number_to_rounded(0, precision: 2, significant: true)
+ assert_equal "0", number_helper.number_to_rounded(0, precision: 1, significant: true)
+ assert_equal "0.0001", number_helper.number_to_rounded(0.0001, precision: 1, significant: true)
+ assert_equal "0.000100", number_helper.number_to_rounded(0.0001, precision: 3, significant: true)
+ assert_equal "0.0001", number_helper.number_to_rounded(0.0001111, precision: 1, significant: true)
assert_equal "10.0", number_helper.number_to_rounded(9.995, precision: 3, significant: true)
assert_equal "9.99", number_helper.number_to_rounded(9.994, precision: 3, significant: true)
assert_equal "11.0", number_helper.number_to_rounded(10.995, precision: 3, significant: true)
- assert_equal "9775.0000000000000000", number_helper.number_to_rounded(9775, precision: 20, significant: true )
- assert_equal "9775.0000000000000000", number_helper.number_to_rounded(9775.0, precision: 20, significant: true )
- assert_equal "9775.0000000000000000", number_helper.number_to_rounded(Rational(9775, 1), precision: 20, significant: true )
- assert_equal "97.750000000000000000", number_helper.number_to_rounded(Rational(9775, 100), precision: 20, significant: true )
- assert_equal "9775.0000000000000000", number_helper.number_to_rounded(BigDecimal(9775), precision: 20, significant: true )
- assert_equal "9775.0000000000000000", number_helper.number_to_rounded("9775", precision: 20, significant: true )
- assert_equal "9775." + "0"*96, number_helper.number_to_rounded("9775", precision: 100, significant: true )
+ assert_equal "9775.0000000000000000", number_helper.number_to_rounded(9775, precision: 20, significant: true)
+ assert_equal "9775.0000000000000000", number_helper.number_to_rounded(9775.0, precision: 20, significant: true)
+ assert_equal "9775.0000000000000000", number_helper.number_to_rounded(Rational(9775, 1), precision: 20, significant: true)
+ assert_equal "97.750000000000000000", number_helper.number_to_rounded(Rational(9775, 100), precision: 20, significant: true)
+ assert_equal "9775.0000000000000000", number_helper.number_to_rounded(BigDecimal(9775), precision: 20, significant: true)
+ assert_equal "9775.0000000000000000", number_helper.number_to_rounded("9775", precision: 20, significant: true)
+ assert_equal "9775." + "0" * 96, number_helper.number_to_rounded("9775", precision: 100, significant: true)
assert_equal("97.7", number_helper.number_to_rounded(Rational(9772, 100), precision: 3, significant: true))
end
end
def test_to_rounded_with_strip_insignificant_zeros
[@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
- assert_equal "9775.43", number_helper.number_to_rounded(9775.43, precision: 4, strip_insignificant_zeros: true )
- assert_equal "9775.2", number_helper.number_to_rounded(9775.2, precision: 6, significant: true, strip_insignificant_zeros: true )
- assert_equal "0", number_helper.number_to_rounded(0, precision: 6, significant: true, strip_insignificant_zeros: true )
+ assert_equal "9775.43", number_helper.number_to_rounded(9775.43, precision: 4, strip_insignificant_zeros: true)
+ assert_equal "9775.2", number_helper.number_to_rounded(9775.2, precision: 6, significant: true, strip_insignificant_zeros: true)
+ assert_equal "0", number_helper.number_to_rounded(0, precision: 6, significant: true, strip_insignificant_zeros: true)
end
end
@@ -210,8 +210,8 @@ module ActiveSupport
# Zero precision with significant is a mistake (would always return zero),
# so we treat it as if significant was false (increases backwards compatibility for number_to_human_size)
assert_equal "124", number_helper.number_to_rounded(123.987, precision: 0, significant: true)
- assert_equal "12", number_helper.number_to_rounded(12, precision: 0, significant: true )
- assert_equal "12", number_helper.number_to_rounded("12.3", precision: 0, significant: true )
+ assert_equal "12", number_helper.number_to_rounded(12, precision: 0, significant: true)
+ assert_equal "12", number_helper.number_to_rounded("12.3", precision: 0, significant: true)
end
end
@@ -244,23 +244,6 @@ module ActiveSupport
end
end
- def test_number_to_human_size_with_si_prefix
- assert_deprecated do
- [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
- assert_equal "3 Bytes", number_helper.number_to_human_size(3.14159265, prefix: :si)
- assert_equal "123 Bytes", number_helper.number_to_human_size(123.0, prefix: :si)
- assert_equal "123 Bytes", number_helper.number_to_human_size(123, prefix: :si)
- assert_equal "1.23 KB", number_helper.number_to_human_size(1234, prefix: :si)
- assert_equal "12.3 KB", number_helper.number_to_human_size(12345, prefix: :si)
- assert_equal "1.23 MB", number_helper.number_to_human_size(1234567, prefix: :si)
- assert_equal "1.23 GB", number_helper.number_to_human_size(1234567890, prefix: :si)
- assert_equal "1.23 TB", number_helper.number_to_human_size(1234567890123, prefix: :si)
- assert_equal "1.23 PB", number_helper.number_to_human_size(1234567890123456, prefix: :si)
- assert_equal "1.23 EB", number_helper.number_to_human_size(1234567890123456789, prefix: :si)
- end
- end
- end
-
def test_number_to_human_size_with_options_hash
[@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
assert_equal "1.2 MB", number_helper.number_to_human_size(1234567, precision: 2)
@@ -316,13 +299,13 @@ module ActiveSupport
def test_number_to_human_with_custom_units
[@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
#Only integers
- volume = {unit: "ml", thousand: "lt", million: "m3"}
+ volume = { unit: "ml", thousand: "lt", million: "m3" }
assert_equal "123 lt", number_helper.number_to_human(123456, units: volume)
assert_equal "12 ml", number_helper.number_to_human(12, units: volume)
assert_equal "1.23 m3", number_helper.number_to_human(1234567, units: volume)
#Including fractionals
- distance = {mili: "mm", centi: "cm", deci: "dm", unit: "m", ten: "dam", hundred: "hm", thousand: "km"}
+ distance = { mili: "mm", centi: "cm", deci: "dm", unit: "m", ten: "dam", hundred: "hm", thousand: "km" }
assert_equal "1.23 mm", number_helper.number_to_human(0.00123, units: distance)
assert_equal "1.23 cm", number_helper.number_to_human(0.0123, units: distance)
assert_equal "1.23 dm", number_helper.number_to_human(0.123, units: distance)
@@ -335,21 +318,21 @@ module ActiveSupport
assert_equal "12.3 km", number_helper.number_to_human(12300, units: distance)
#The quantifiers don't need to be a continuous sequence
- gangster = {hundred: "hundred bucks", million: "thousand quids"}
+ 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 "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 "})
+ 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 " })
end
end
def test_number_to_human_with_custom_units_that_are_missing_the_needed_key
[@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
- assert_equal "123", number_helper.number_to_human(123, units: { thousand: "k"})
+ assert_equal "123", number_helper.number_to_human(123, units: { thousand: "k" })
assert_equal "123", number_helper.number_to_human(123, units: {})
end
end
@@ -357,7 +340,7 @@ module ActiveSupport
def test_number_to_human_with_custom_format
[@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
assert_equal "123 times Thousand", number_helper.number_to_human(123456, format: "%n times %u")
- volume = {unit: "ml", thousand: "lt", million: "m3"}
+ volume = { unit: "ml", thousand: "lt", million: "m3" }
assert_equal "123.lt", number_helper.number_to_human(123456, units: volume, format: "%n.%u")
end
end
diff --git a/activesupport/test/option_merger_test.rb b/activesupport/test/option_merger_test.rb
index 2bac550452..c5a6d304ee 100644
--- a/activesupport/test/option_merger_test.rb
+++ b/activesupport/test/option_merger_test.rb
@@ -3,11 +3,11 @@ require "active_support/core_ext/object/with_options"
class OptionMergerTest < ActiveSupport::TestCase
def setup
- @options = {hello: "world"}
+ @options = { hello: "world" }
end
def test_method_with_options_merges_options_when_options_are_present
- local_options = {cool: true}
+ local_options = { cool: true }
with_options(@options) do |o|
assert_equal local_options, method_with_options(local_options)
@@ -24,7 +24,7 @@ class OptionMergerTest < ActiveSupport::TestCase
end
def test_method_with_options_allows_to_overwrite_options
- local_options = {hello: "moon"}
+ local_options = { hello: "moon" }
assert_equal @options.keys, local_options.keys
with_options(@options) do |o|
diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb
index f7977b276b..2cefab3832 100644
--- a/activesupport/test/ordered_hash_test.rb
+++ b/activesupport/test/ordered_hash_test.rb
@@ -94,13 +94,13 @@ class OrderedHashTest < ActiveSupport::TestCase
def test_each
values = []
- assert_equal @ordered_hash, @ordered_hash.each {|key, value| values << value}
+ assert_equal @ordered_hash, @ordered_hash.each { |key, value| values << value }
assert_equal @values, values
assert_kind_of Enumerator, @ordered_hash.each
end
def test_each_with_index
- @ordered_hash.each_with_index { |pair, index| assert_equal [@keys[index], @values[index]], pair}
+ @ordered_hash.each_with_index { |pair, index| assert_equal [@keys[index], @values[index]], pair }
end
def test_each_pair
@@ -129,22 +129,22 @@ class OrderedHashTest < ActiveSupport::TestCase
copy = @ordered_hash.dup
copy.delete("pink")
assert_equal copy, @ordered_hash.delete_if { |k, _| k == "pink" }
- assert !@ordered_hash.keys.include?("pink")
+ assert_not_includes @ordered_hash.keys, "pink"
end
def test_reject!
(copy = @ordered_hash.dup).delete("pink")
@ordered_hash.reject! { |k, _| k == "pink" }
assert_equal copy, @ordered_hash
- assert !@ordered_hash.keys.include?("pink")
+ assert_not_includes @ordered_hash.keys, "pink"
end
def test_reject
copy = @ordered_hash.dup
new_ordered_hash = @ordered_hash.reject { |k, _| k == "pink" }
assert_equal copy, @ordered_hash
- assert !new_ordered_hash.keys.include?("pink")
- assert @ordered_hash.keys.include?("pink")
+ assert_not_includes new_ordered_hash.keys, "pink"
+ assert_includes @ordered_hash.keys, "pink"
assert_instance_of ActiveSupport::OrderedHash, new_ordered_hash
end
@@ -154,7 +154,7 @@ class OrderedHashTest < ActiveSupport::TestCase
end
def test_merge
- other_hash = ActiveSupport::OrderedHash.new
+ other_hash = ActiveSupport::OrderedHash.new
other_hash["purple"] = "800080"
other_hash["violet"] = "ee82ee"
merged = @ordered_hash.merge other_hash
@@ -191,7 +191,7 @@ class OrderedHashTest < ActiveSupport::TestCase
def test_shift
pair = @ordered_hash.shift
assert_equal [@keys.first, @values.first], pair
- assert !@ordered_hash.keys.include?(pair.first)
+ assert_not_includes @ordered_hash.keys, pair.first
end
def test_keys
@@ -201,7 +201,7 @@ class OrderedHashTest < ActiveSupport::TestCase
end
def test_inspect
- assert @ordered_hash.inspect.include?(@hash.inspect)
+ assert_includes @ordered_hash.inspect, @hash.inspect
end
def test_json
@@ -211,7 +211,7 @@ class OrderedHashTest < ActiveSupport::TestCase
end
def test_alternate_initialization_with_splat
- alternate = ActiveSupport::OrderedHash[1,2,3,4]
+ alternate = ActiveSupport::OrderedHash[1, 2, 3, 4]
assert_kind_of ActiveSupport::OrderedHash, alternate
assert_equal [1, 3], alternate.keys
end
@@ -230,7 +230,7 @@ class OrderedHashTest < ActiveSupport::TestCase
def test_alternate_initialization_raises_exception_on_odd_length_args
assert_raises ArgumentError do
- ActiveSupport::OrderedHash[1,2,3,4,5]
+ ActiveSupport::OrderedHash[1, 2, 3, 4, 5]
end
end
@@ -242,7 +242,7 @@ class OrderedHashTest < ActiveSupport::TestCase
end
def test_nested_under_indifferent_access
- flash = {a: ActiveSupport::OrderedHash[:b, 1, :c, 2]}.with_indifferent_access
+ flash = { a: ActiveSupport::OrderedHash[:b, 1, :c, 2] }.with_indifferent_access
assert_kind_of ActiveSupport::OrderedHash, flash[:a]
end
diff --git a/activesupport/test/reloader_test.rb b/activesupport/test/reloader_test.rb
index c6d852cf39..67d8c4b0e3 100644
--- a/activesupport/test/reloader_test.rb
+++ b/activesupport/test/reloader_test.rb
@@ -21,13 +21,13 @@ class ReloaderTest < ActiveSupport::TestCase
r = new_reloader { true }
invoked = false
r.to_run { invoked = true }
- r.wrap { }
+ r.wrap {}
assert invoked
r = new_reloader { false }
invoked = false
r.to_run { invoked = true }
- r.wrap { }
+ r.wrap {}
assert !invoked
end
@@ -39,7 +39,7 @@ class ReloaderTest < ActiveSupport::TestCase
reloader.executor.to_run { called << :executor_run }
reloader.executor.to_complete { called << :executor_complete }
- reloader.wrap { }
+ reloader.wrap {}
assert_equal [:executor_run, :reloader_run, :prepare, :reloader_complete, :executor_complete], called
called = []
@@ -49,7 +49,7 @@ class ReloaderTest < ActiveSupport::TestCase
reloader.check = lambda { false }
called = []
- reloader.wrap { }
+ reloader.wrap {}
assert_equal [:executor_run, :executor_complete], called
called = []
diff --git a/activesupport/test/rescuable_test.rb b/activesupport/test/rescuable_test.rb
index 7e5c3d1a8f..f7eb047d44 100644
--- a/activesupport/test/rescuable_test.rb
+++ b/activesupport/test/rescuable_test.rb
@@ -137,6 +137,6 @@ class RescuableTest < ActiveSupport::TestCase
def test_rescue_falls_back_to_exception_cause
@stargate.dispatch :fall_back_to_cause
- assert_equal "unhandled RuntimeError with a handleable cause", @stargate.result
+ assert_equal "dex", @stargate.result
end
end
diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb
index 36e262745c..36c068b91f 100644
--- a/activesupport/test/safe_buffer_test.rb
+++ b/activesupport/test/safe_buffer_test.rb
@@ -58,7 +58,7 @@ class SafeBufferTest < ActiveSupport::TestCase
str = "hello!"
data = { "str" => ActiveSupport::SafeBuffer.new(str) }
yaml = YAML.dump data
- assert_equal({"str" => str}, YAML.load(yaml))
+ assert_equal({ "str" => str }, YAML.load(yaml))
end
test "Should work with primitive-like-strings in to_yaml conversion" do
@@ -130,7 +130,7 @@ class SafeBufferTest < ActiveSupport::TestCase
end
test "Should be safe when sliced if original value was safe" do
- new_buffer = @buffer[0,0]
+ new_buffer = @buffer[0, 0]
assert_not_nil new_buffer
assert new_buffer.html_safe?, "should be safe"
end
@@ -175,6 +175,6 @@ class SafeBufferTest < ActiveSupport::TestCase
test "Should not affect frozen objects when accessing characters" do
x = "Hello".html_safe
- assert_equal x[/a/, 1], nil
+ assert_nil x[/a/, 1]
end
end
diff --git a/activesupport/test/share_lock_test.rb b/activesupport/test/share_lock_test.rb
index 8155d46934..a5970591fa 100644
--- a/activesupport/test/share_lock_test.rb
+++ b/activesupport/test/share_lock_test.rb
@@ -17,7 +17,7 @@ class ShareLockTest < ActiveSupport::TestCase
def test_sharing_doesnt_block
with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_latch|
- assert_threads_not_stuck(Thread.new {@lock.sharing {} })
+ assert_threads_not_stuck(Thread.new { @lock.sharing {} })
end
end
diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb
index d3d2ed8d7a..af7fc44d66 100644
--- a/activesupport/test/test_case_test.rb
+++ b/activesupport/test/test_case_test.rb
@@ -138,6 +138,15 @@ class AssertDifferenceTest < ActiveSupport::TestCase
end
end
+ def test_assert_changes_with_from_option_with_nil
+ error = assert_raises Minitest::Assertion do
+ assert_changes "@object.num", from: nil do
+ @object.increment
+ end
+ end
+ assert_equal "\"@object.num\" isn't nil", error.message
+ end
+
def test_assert_changes_with_to_option
assert_changes "@object.num", to: 1 do
@object.increment
@@ -202,6 +211,15 @@ class AssertDifferenceTest < ActiveSupport::TestCase
end
end
+ def test_assert_changes_with_message
+ error = assert_raises Minitest::Assertion do
+ assert_changes "@object.num", "@object.num should 1", to: 1 do
+ end
+ end
+
+ assert_equal "@object.num should 1.\n\"@object.num\" didn't change to 1", error.message
+ end
+
def test_assert_no_changes_pass
assert_no_changes "@object.num" do
# ...
@@ -239,7 +257,7 @@ class SetupAndTeardownTest < ActiveSupport::TestCase
def teardown
end
- protected
+ private
def reset_callback_record
@called_back = []
@@ -264,7 +282,7 @@ class SubclassSetupAndTeardownTest < SetupAndTeardownTest
assert_equal [:foo, :sentinel, :bar], self.class._teardown_callbacks.map(&:raw_filter)
end
- protected
+ private
def bar
@called_back << :bar
end
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index f89b608270..4794b55742 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -55,7 +55,7 @@ class TimeZoneTest < ActiveSupport::TestCase
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"].dup
def zone.time_now; Time.local(2000); end
assert_instance_of ActiveSupport::TimeWithZone, zone.now
- assert_equal Time.utc(2000,1,1,5), zone.now.utc
+ assert_equal Time.utc(2000, 1, 1, 5), zone.now.utc
assert_equal Time.utc(2000), zone.now.time
assert_equal zone, zone.now.time_zone
end
@@ -65,10 +65,10 @@ class TimeZoneTest < ActiveSupport::TestCase
with_env_tz "US/Eastern" do
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"].dup
def zone.time_now
- Time.local(2006,4,2,2) # 2AM springs forward to 3AM
+ Time.local(2006, 4, 2, 2) # 2AM springs forward to 3AM
end
- assert_equal Time.utc(2006,4,2,3), zone.now.time
+ assert_equal Time.utc(2006, 4, 2, 3), zone.now.time
assert_equal true, zone.now.dst?
end
end
@@ -79,7 +79,7 @@ class TimeZoneTest < ActiveSupport::TestCase
def zone.time_now
Time.at(1162098000) # equivalent to 1AM DST
end
- assert_equal Time.utc(2006,10,29,1), zone.now.time
+ assert_equal Time.utc(2006, 10, 29, 1), zone.now.time
assert_equal true, zone.now.dst?
end
end
@@ -162,25 +162,25 @@ class TimeZoneTest < ActiveSupport::TestCase
def test_local_with_old_date
time = ActiveSupport::TimeZone["Hawaii"].local(1850, 2, 5, 15, 30, 45)
- assert_equal [45,30,15,5,2,1850], time.to_a[0,6]
+ assert_equal [45, 30, 15, 5, 2, 1850], time.to_a[0, 6]
assert_equal ActiveSupport::TimeZone["Hawaii"], time.time_zone
end
def test_local_enforces_spring_dst_rules
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
- twz = zone.local(2006,4,2,1,59,59) # 1 second before DST start
- assert_equal Time.utc(2006,4,2,1,59,59), twz.time
- assert_equal Time.utc(2006,4,2,6,59,59), twz.utc
+ twz = zone.local(2006, 4, 2, 1, 59, 59) # 1 second before DST start
+ assert_equal Time.utc(2006, 4, 2, 1, 59, 59), twz.time
+ assert_equal Time.utc(2006, 4, 2, 6, 59, 59), twz.utc
assert_equal false, twz.dst?
assert_equal "EST", twz.zone
- twz2 = zone.local(2006,4,2,2) # 2AM does not exist because at 2AM, time springs forward to 3AM
- assert_equal Time.utc(2006,4,2,3), twz2.time # twz is created for 3AM
- assert_equal Time.utc(2006,4,2,7), twz2.utc
+ twz2 = zone.local(2006, 4, 2, 2) # 2AM does not exist because at 2AM, time springs forward to 3AM
+ assert_equal Time.utc(2006, 4, 2, 3), twz2.time # twz is created for 3AM
+ assert_equal Time.utc(2006, 4, 2, 7), twz2.utc
assert_equal true, twz2.dst?
assert_equal "EDT", twz2.zone
- twz3 = zone.local(2006,4,2,2,30) # 2:30AM does not exist because at 2AM, time springs forward to 3AM
- assert_equal Time.utc(2006,4,2,3,30), twz3.time # twz is created for 3:30AM
- assert_equal Time.utc(2006,4,2,7,30), twz3.utc
+ twz3 = zone.local(2006, 4, 2, 2, 30) # 2:30AM does not exist because at 2AM, time springs forward to 3AM
+ assert_equal Time.utc(2006, 4, 2, 3, 30), twz3.time # twz is created for 3:30AM
+ assert_equal Time.utc(2006, 4, 2, 7, 30), twz3.utc
assert_equal true, twz3.dst?
assert_equal "EDT", twz3.zone
end
@@ -189,9 +189,9 @@ class TimeZoneTest < ActiveSupport::TestCase
# 1AM during fall DST transition is ambiguous, it could be either DST or non-DST 1AM
# Mirroring Time.local behavior, this method selects the DST time
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
- twz = zone.local(2006,10,29,1)
- assert_equal Time.utc(2006,10,29,1), twz.time
- assert_equal Time.utc(2006,10,29,5), twz.utc
+ twz = zone.local(2006, 10, 29, 1)
+ assert_equal Time.utc(2006, 10, 29, 1), twz.time
+ assert_equal Time.utc(2006, 10, 29, 5), twz.utc
assert_equal true, twz.dst?
assert_equal "EDT", twz.zone
end
@@ -200,7 +200,7 @@ class TimeZoneTest < ActiveSupport::TestCase
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
secs = 946684800.0
twz = zone.at(secs)
- assert_equal Time.utc(1999,12,31,19), twz.time
+ assert_equal Time.utc(1999, 12, 31, 19), twz.time
assert_equal Time.utc(2000), twz.utc
assert_equal zone, twz.time_zone
assert_equal secs, twz.to_f
@@ -218,7 +218,7 @@ class TimeZoneTest < ActiveSupport::TestCase
def test_parse
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
twz = zone.parse("1999-12-31 19:00:00")
- assert_equal Time.utc(1999,12,31,19), twz.time
+ assert_equal Time.utc(1999, 12, 31, 19), twz.time
assert_equal Time.utc(2000), twz.utc
assert_equal zone, twz.time_zone
end
@@ -234,14 +234,14 @@ class TimeZoneTest < ActiveSupport::TestCase
def test_parse_with_old_date
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
twz = zone.parse("1883-12-31 19:00:00")
- assert_equal [0,0,19,31,12,1883], twz.to_a[0,6]
+ assert_equal [0, 0, 19, 31, 12, 1883], twz.to_a[0, 6]
assert_equal zone, twz.time_zone
end
def test_parse_far_future_date_with_time_zone_offset_in_string
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
twz = zone.parse("2050-12-31 19:00:00 -10:00") # i.e., 2050-01-01 05:00:00 UTC
- assert_equal [0,0,0,1,1,2051], twz.to_a[0,6]
+ assert_equal [0, 0, 0, 1, 1, 2051], twz.to_a[0, 6]
assert_equal zone, twz.time_zone
end
@@ -253,9 +253,9 @@ class TimeZoneTest < ActiveSupport::TestCase
def test_parse_with_incomplete_date
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
- zone.stub(:now, zone.local(1999,12,31)) do
+ zone.stub(:now, zone.local(1999, 12, 31)) do
twz = zone.parse("19:00:00")
- assert_equal Time.utc(1999,12,31,19), twz.time
+ assert_equal Time.utc(1999, 12, 31, 19), twz.time
end
end
@@ -272,7 +272,7 @@ class TimeZoneTest < ActiveSupport::TestCase
with_env_tz("EET") do
zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"]
twz = zone.parse("2012-03-25 03:29:00")
- assert_equal [0, 29, 3, 25, 3, 2012], twz.to_a[0,6]
+ assert_equal [0, 29, 3, 25, 3, 2012], twz.to_a[0, 6]
end
end
@@ -280,7 +280,7 @@ class TimeZoneTest < ActiveSupport::TestCase
with_env_tz("EET") do
zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"]
twz = zone.parse("2012-03-11 02:29:00")
- assert_equal [0, 29, 3, 11, 3, 2012], twz.to_a[0,6]
+ assert_equal [0, 29, 3, 11, 3, 2012], twz.to_a[0, 6]
end
end
@@ -317,9 +317,9 @@ class TimeZoneTest < ActiveSupport::TestCase
def test_strptime
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
twz = zone.strptime("1999-12-31 12:00:00", "%Y-%m-%d %H:%M:%S")
- assert_equal Time.utc(1999,12,31,17), twz
- assert_equal Time.utc(1999,12,31,12), twz.time
- assert_equal Time.utc(1999,12,31,17), twz.utc
+ assert_equal Time.utc(1999, 12, 31, 17), twz
+ assert_equal Time.utc(1999, 12, 31, 12), twz.time
+ assert_equal Time.utc(1999, 12, 31, 17), twz.utc
assert_equal zone, twz.time_zone
end
@@ -327,9 +327,9 @@ class TimeZoneTest < ActiveSupport::TestCase
with_tz_default ActiveSupport::TimeZone["Pacific Time (US & Canada)"] do
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
twz = zone.strptime("1999-12-31 12:00:00", "%Y-%m-%d %H:%M:%S")
- assert_equal Time.utc(1999,12,31,17), twz
- assert_equal Time.utc(1999,12,31,12), twz.time
- assert_equal Time.utc(1999,12,31,17), twz.utc
+ assert_equal Time.utc(1999, 12, 31, 17), twz
+ assert_equal Time.utc(1999, 12, 31, 12), twz.time
+ assert_equal Time.utc(1999, 12, 31, 17), twz.utc
assert_equal zone, twz.time_zone
end
end
@@ -337,45 +337,45 @@ class TimeZoneTest < ActiveSupport::TestCase
def test_strptime_with_explicit_time_zone_as_abbrev
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
twz = zone.strptime("1999-12-31 12:00:00 PST", "%Y-%m-%d %H:%M:%S %Z")
- assert_equal Time.utc(1999,12,31,20), twz
- assert_equal Time.utc(1999,12,31,15), twz.time
- assert_equal Time.utc(1999,12,31,20), twz.utc
+ assert_equal Time.utc(1999, 12, 31, 20), twz
+ assert_equal Time.utc(1999, 12, 31, 15), twz.time
+ assert_equal Time.utc(1999, 12, 31, 20), twz.utc
assert_equal zone, twz.time_zone
end
def test_strptime_with_explicit_time_zone_as_h_offset
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
twz = zone.strptime("1999-12-31 12:00:00 -08", "%Y-%m-%d %H:%M:%S %:::z")
- assert_equal Time.utc(1999,12,31,20), twz
- assert_equal Time.utc(1999,12,31,15), twz.time
- assert_equal Time.utc(1999,12,31,20), twz.utc
+ assert_equal Time.utc(1999, 12, 31, 20), twz
+ assert_equal Time.utc(1999, 12, 31, 15), twz.time
+ assert_equal Time.utc(1999, 12, 31, 20), twz.utc
assert_equal zone, twz.time_zone
end
def test_strptime_with_explicit_time_zone_as_hm_offset
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
twz = zone.strptime("1999-12-31 12:00:00 -08:00", "%Y-%m-%d %H:%M:%S %:z")
- assert_equal Time.utc(1999,12,31,20), twz
- assert_equal Time.utc(1999,12,31,15), twz.time
- assert_equal Time.utc(1999,12,31,20), twz.utc
+ assert_equal Time.utc(1999, 12, 31, 20), twz
+ assert_equal Time.utc(1999, 12, 31, 15), twz.time
+ assert_equal Time.utc(1999, 12, 31, 20), twz.utc
assert_equal zone, twz.time_zone
end
def test_strptime_with_explicit_time_zone_as_hms_offset
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
twz = zone.strptime("1999-12-31 12:00:00 -08:00:00", "%Y-%m-%d %H:%M:%S %::z")
- assert_equal Time.utc(1999,12,31,20), twz
- assert_equal Time.utc(1999,12,31,15), twz.time
- assert_equal Time.utc(1999,12,31,20), twz.utc
+ assert_equal Time.utc(1999, 12, 31, 20), twz
+ assert_equal Time.utc(1999, 12, 31, 15), twz.time
+ assert_equal Time.utc(1999, 12, 31, 20), twz.utc
assert_equal zone, twz.time_zone
end
def test_strptime_with_almost_explicit_time_zone
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
twz = zone.strptime("1999-12-31 12:00:00 %Z", "%Y-%m-%d %H:%M:%S %%Z")
- assert_equal Time.utc(1999,12,31,17), twz
- assert_equal Time.utc(1999,12,31,12), twz.time
- assert_equal Time.utc(1999,12,31,17), twz.utc
+ assert_equal Time.utc(1999, 12, 31, 17), twz
+ assert_equal Time.utc(1999, 12, 31, 12), twz.time
+ assert_equal Time.utc(1999, 12, 31, 17), twz.utc
assert_equal zone, twz.time_zone
end
@@ -395,10 +395,28 @@ class TimeZoneTest < ActiveSupport::TestCase
end
end
+ def test_strptime_with_timestamp_seconds
+ with_env_tz "US/Eastern" do
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ time_str = "1470272280"
+ time = zone.strptime(time_str, "%s")
+ assert_equal Time.at(1470272280), time
+ end
+ end
+
+ def test_strptime_with_timestamp_milliseconds
+ with_env_tz "US/Eastern" do
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ time_str = "1470272280000"
+ time = zone.strptime(time_str, "%Q")
+ assert_equal Time.at(1470272280), time
+ end
+ end
+
def test_utc_offset_lazy_loaded_from_tzinfo_when_not_passed_in_to_initialize
tzinfo = TZInfo::Timezone.get("America/New_York")
zone = ActiveSupport::TimeZone.create(tzinfo.name, nil, tzinfo)
- assert_equal nil, zone.instance_variable_get("@utc_offset")
+ assert_nil zone.instance_variable_get("@utc_offset")
assert_equal(-18_000, zone.utc_offset)
end
@@ -478,8 +496,8 @@ class TimeZoneTest < ActiveSupport::TestCase
def test_all_sorted
all = ActiveSupport::TimeZone.all
- 1.upto( all.length-1 ) do |i|
- assert all[i-1] < all[i]
+ 1.upto(all.length - 1) do |i|
+ assert all[i - 1] < all[i]
end
end
@@ -505,13 +523,13 @@ class TimeZoneTest < ActiveSupport::TestCase
end
def test_us_zones
- assert ActiveSupport::TimeZone.us_zones.include?(ActiveSupport::TimeZone["Hawaii"])
- assert !ActiveSupport::TimeZone.us_zones.include?(ActiveSupport::TimeZone["Kuala Lumpur"])
+ assert_includes ActiveSupport::TimeZone.us_zones, ActiveSupport::TimeZone["Hawaii"]
+ assert_not_includes ActiveSupport::TimeZone.us_zones, ActiveSupport::TimeZone["Kuala Lumpur"]
end
def test_country_zones
- assert ActiveSupport::TimeZone.country_zones("ru").include?(ActiveSupport::TimeZone["Moscow"])
- assert !ActiveSupport::TimeZone.country_zones(:ru).include?(ActiveSupport::TimeZone["Kuala Lumpur"])
+ assert_includes ActiveSupport::TimeZone.country_zones("ru"), ActiveSupport::TimeZone["Moscow"]
+ assert_not_includes ActiveSupport::TimeZone.country_zones(:ru), ActiveSupport::TimeZone["Kuala Lumpur"]
end
def test_to_yaml
diff --git a/activesupport/test/transliterate_test.rb b/activesupport/test/transliterate_test.rb
index fd417d4250..040ddd25fc 100644
--- a/activesupport/test/transliterate_test.rb
+++ b/activesupport/test/transliterate_test.rb
@@ -13,7 +13,7 @@ class TransliterateTest < ActiveSupport::TestCase
# create string with range of Unicode's western characters with
# diacritics, excluding the division and multiplication signs which for
# some reason or other are floating in the middle of all the letters.
- string = (0xC0..0x17E).to_a.reject {|c| [0xD7, 0xF7].include?(c)}.pack("U*")
+ string = (0xC0..0x17E).to_a.reject { |c| [0xD7, 0xF7].include?(c) }.pack("U*")
string.each_char do |char|
assert_match %r{^[a-zA-Z']*$}, ActiveSupport::Inflector.transliterate(char)
end
@@ -21,7 +21,7 @@ class TransliterateTest < ActiveSupport::TestCase
def test_transliterate_should_work_with_custom_i18n_rules_and_uncomposed_utf8
char = [117, 776].pack("U*") # "ü" as ASCII "u" plus COMBINING DIAERESIS
- I18n.backend.store_translations(:de, i18n: {transliterate: {rule: {"ü" => "ue"}}})
+ I18n.backend.store_translations(:de, i18n: { transliterate: { rule: { "ü" => "ue" } } })
default_locale, I18n.locale = I18n.locale, :de
assert_equal "ue", ActiveSupport::Inflector.transliterate(char)
ensure
diff --git a/activesupport/test/xml_mini/jdom_engine_test.rb b/activesupport/test/xml_mini/jdom_engine_test.rb
index a776ad25c4..e783cea67c 100644
--- a/activesupport/test/xml_mini/jdom_engine_test.rb
+++ b/activesupport/test/xml_mini/jdom_engine_test.rb
@@ -1,37 +1,9 @@
-if RUBY_PLATFORM.include?("java")
- require "abstract_unit"
- require "active_support/xml_mini"
- require "active_support/core_ext/hash/conversions"
-
- class JDOMEngineTest < ActiveSupport::TestCase
- include ActiveSupport
+require_relative "xml_mini_engine_test"
+XMLMiniEngineTest.run_with_platform("java") do
+ class JDOMEngineTest < XMLMiniEngineTest
FILES_DIR = File.dirname(__FILE__) + "/../fixtures/xml"
- def setup
- @default_backend = XmlMini.backend
- XmlMini.backend = "JDOM"
- end
-
- def teardown
- XmlMini.backend = @default_backend
- end
-
- def test_file_from_xml
- hash = Hash.from_xml(<<-eoxml)
- <blog>
- <logo type="file" name="logo.png" content_type="image/png">
- </logo>
- </blog>
- eoxml
- assert hash.has_key?("blog")
- assert hash["blog"].has_key?("logo")
-
- file = hash["blog"]["logo"]
- assert_equal "logo.png", file.original_filename
- assert_equal "image/png", file.content_type
- end
-
def test_not_allowed_to_expand_entities_to_files
attack_xml = <<-EOT
<!DOCTYPE member [
@@ -63,124 +35,17 @@ if RUBY_PLATFORM.include?("java")
assert_equal "x", Hash.from_xml(attack_xml)["member"]
end
- def test_exception_thrown_on_expansion_attack
- assert_raise Java::OrgXmlSax::SAXParseException do
- attack_xml = <<-EOT
- <!DOCTYPE member [
- <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;">
- <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;">
- <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;">
- <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;">
- <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;">
- <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;">
- <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
- ]>
- <member>
- &a;
- </member>
- EOT
- Hash.from_xml(attack_xml)
+ private
+ def engine
+ "JDOM"
end
- end
-
- def test_setting_JDOM_as_backend
- XmlMini.backend = "JDOM"
- assert_equal XmlMini_JDOM, XmlMini.backend
- end
-
- def test_blank_returns_empty_hash
- assert_equal({}, XmlMini.parse(nil))
- assert_equal({}, XmlMini.parse(""))
- end
-
- def test_array_type_makes_an_array
- assert_equal_rexml(<<-eoxml)
- <blog>
- <posts type="array">
- <post>a post</post>
- <post>another post</post>
- </posts>
- </blog>
- eoxml
- end
- def test_one_node_document_as_hash
- assert_equal_rexml(<<-eoxml)
- <products/>
- eoxml
- end
-
- def test_one_node_with_attributes_document_as_hash
- assert_equal_rexml(<<-eoxml)
- <products foo="bar"/>
- eoxml
- end
-
- def test_products_node_with_book_node_as_hash
- assert_equal_rexml(<<-eoxml)
- <products>
- <book name="awesome" id="12345" />
- </products>
- eoxml
- end
-
- def test_products_node_with_two_book_nodes_as_hash
- assert_equal_rexml(<<-eoxml)
- <products>
- <book name="awesome" id="12345" />
- <book name="america" id="67890" />
- </products>
- eoxml
- end
-
- def test_single_node_with_content_as_hash
- assert_equal_rexml(<<-eoxml)
- <products>
- hello world
- </products>
- eoxml
- end
-
- def test_children_with_children
- assert_equal_rexml(<<-eoxml)
- <root>
- <products>
- <book name="america" id="67890" />
- </products>
- </root>
- eoxml
- end
-
- def test_children_with_text
- assert_equal_rexml(<<-eoxml)
- <root>
- <products>
- hello everyone
- </products>
- </root>
- eoxml
- end
-
- def test_children_with_non_adjacent_text
- assert_equal_rexml(<<-eoxml)
- <root>
- good
- <products>
- hello everyone
- </products>
- morning
- </root>
- eoxml
- end
+ def expansion_attack_error
+ Java::OrgXmlSax::SAXParseException
+ end
- private
- def assert_equal_rexml(xml)
- parsed_xml = XmlMini.parse(xml)
- hash = XmlMini.with_backend("REXML") { XmlMini.parse(xml) }
- assert_equal(hash, parsed_xml)
+ def extended_engine?
+ false
end
end
-
-else
- # don't run these test because we aren't running in JRuby
end
diff --git a/activesupport/test/xml_mini/libxml_engine_test.rb b/activesupport/test/xml_mini/libxml_engine_test.rb
index 81b0d3c407..f3394ad7f2 100644
--- a/activesupport/test/xml_mini/libxml_engine_test.rb
+++ b/activesupport/test/xml_mini/libxml_engine_test.rb
@@ -1,203 +1,19 @@
-begin
- require "libxml"
-rescue LoadError
- # Skip libxml tests
-else
- require "abstract_unit"
- require "active_support/xml_mini"
- require "active_support/core_ext/hash/conversions"
-
- class LibxmlEngineTest < ActiveSupport::TestCase
- include ActiveSupport
+require_relative "xml_mini_engine_test"
+XMLMiniEngineTest.run_with_gem("libxml") do
+ class LibxmlEngineTest < XMLMiniEngineTest
def setup
- @default_backend = XmlMini.backend
- XmlMini.backend = "LibXML"
-
+ super
LibXML::XML::Error.set_handler(&lambda { |error| }) #silence libxml, exceptions will do
end
- def teardown
- XmlMini.backend = @default_backend
- end
-
- def test_exception_thrown_on_expansion_attack
- assert_raise LibXML::XML::Error do
- attack_xml = %{<?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE member [
- <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;">
- <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;">
- <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;">
- <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;">
- <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;">
- <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;">
- <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
- ]>
- <member>
- &a;
- </member>
- }
- Hash.from_xml(attack_xml)
+ private
+ def engine
+ "LibXML"
end
- end
-
- def test_setting_libxml_as_backend
- XmlMini.backend = "LibXML"
- assert_equal XmlMini_LibXML, XmlMini.backend
- end
-
- def test_blank_returns_empty_hash
- assert_equal({}, XmlMini.parse(nil))
- assert_equal({}, XmlMini.parse(""))
- end
-
- def test_array_type_makes_an_array
- assert_equal_rexml(<<-eoxml)
- <blog>
- <posts type="array">
- <post>a post</post>
- <post>another post</post>
- </posts>
- </blog>
- eoxml
- end
-
- def test_one_node_document_as_hash
- assert_equal_rexml(<<-eoxml)
- <products/>
- eoxml
- end
-
- def test_one_node_with_attributes_document_as_hash
- assert_equal_rexml(<<-eoxml)
- <products foo="bar"/>
- eoxml
- end
-
- def test_products_node_with_book_node_as_hash
- assert_equal_rexml(<<-eoxml)
- <products>
- <book name="awesome" id="12345" />
- </products>
- eoxml
- end
-
- def test_products_node_with_two_book_nodes_as_hash
- assert_equal_rexml(<<-eoxml)
- <products>
- <book name="awesome" id="12345" />
- <book name="america" id="67890" />
- </products>
- eoxml
- end
-
- def test_single_node_with_content_as_hash
- assert_equal_rexml(<<-eoxml)
- <products>
- hello world
- </products>
- eoxml
- end
-
- def test_children_with_children
- assert_equal_rexml(<<-eoxml)
- <root>
- <products>
- <book name="america" id="67890" />
- </products>
- </root>
- eoxml
- end
-
- def test_children_with_text
- assert_equal_rexml(<<-eoxml)
- <root>
- <products>
- hello everyone
- </products>
- </root>
- eoxml
- end
-
- def test_children_with_non_adjacent_text
- assert_equal_rexml(<<-eoxml)
- <root>
- good
- <products>
- hello everyone
- </products>
- morning
- </root>
- eoxml
- end
-
- def test_parse_from_io
- io = StringIO.new(<<-eoxml)
- <root>
- good
- <products>
- hello everyone
- </products>
- morning
- </root>
- eoxml
- assert_equal_rexml(io)
- end
-
- def test_children_with_simple_cdata
- assert_equal_rexml(<<-eoxml)
- <root>
- <products>
- <![CDATA[cdatablock]]>
- </products>
- </root>
- eoxml
- end
- def test_children_with_multiple_cdata
- assert_equal_rexml(<<-eoxml)
- <root>
- <products>
- <![CDATA[cdatablock1]]><![CDATA[cdatablock2]]>
- </products>
- </root>
- eoxml
- end
-
- def test_children_with_text_and_cdata
- assert_equal_rexml(<<-eoxml)
- <root>
- <products>
- hello <![CDATA[cdatablock]]>
- morning
- </products>
- </root>
- eoxml
- end
-
- def test_children_with_blank_text
- assert_equal_rexml(<<-eoxml)
- <root>
- <products> </products>
- </root>
- eoxml
- end
-
- def test_children_with_blank_text_and_attribute
- assert_equal_rexml(<<-eoxml)
- <root>
- <products type="file"> </products>
- </root>
- eoxml
- end
-
- private
- def assert_equal_rexml(xml)
- parsed_xml = XmlMini.parse(xml)
- xml.rewind if xml.respond_to?(:rewind)
- hash = XmlMini.with_backend("REXML") { XmlMini.parse(xml) }
- assert_equal(hash, parsed_xml)
+ def expansion_attack_error
+ LibXML::XML::Error
end
end
-
end
diff --git a/activesupport/test/xml_mini/libxmlsax_engine_test.rb b/activesupport/test/xml_mini/libxmlsax_engine_test.rb
index e25fa2813c..f457e160d6 100644
--- a/activesupport/test/xml_mini/libxmlsax_engine_test.rb
+++ b/activesupport/test/xml_mini/libxmlsax_engine_test.rb
@@ -1,195 +1,14 @@
-begin
- require "libxml"
-rescue LoadError
- # Skip libxml tests
-else
- require "abstract_unit"
- require "active_support/xml_mini"
- require "active_support/core_ext/hash/conversions"
+require_relative "xml_mini_engine_test"
- class LibXMLSAXEngineTest < ActiveSupport::TestCase
- include ActiveSupport
-
- def setup
- @default_backend = XmlMini.backend
- XmlMini.backend = "LibXMLSAX"
- end
-
- def teardown
- XmlMini.backend = @default_backend
- end
-
- def test_exception_thrown_on_expansion_attack
- assert_raise LibXML::XML::Error do
- attack_xml = <<-EOT
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE member [
- <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;">
- <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;">
- <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;">
- <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;">
- <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;">
- <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;">
- <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
- ]>
- <member>
- &a;
- </member>
- EOT
-
- Hash.from_xml(attack_xml)
+XMLMiniEngineTest.run_with_gem("libxml") do
+ class LibXMLSAXEngineTest < XMLMiniEngineTest
+ private
+ def engine
+ "LibXMLSAX"
end
- end
-
- def test_setting_libxml_as_backend
- XmlMini.backend = "LibXMLSAX"
- assert_equal XmlMini_LibXMLSAX, XmlMini.backend
- end
-
- def test_blank_returns_empty_hash
- assert_equal({}, XmlMini.parse(nil))
- assert_equal({}, XmlMini.parse(""))
- end
-
- def test_array_type_makes_an_array
- assert_equal_rexml(<<-eoxml)
- <blog>
- <posts type="array">
- <post>a post</post>
- <post>another post</post>
- </posts>
- </blog>
- eoxml
- end
-
- def test_one_node_document_as_hash
- assert_equal_rexml(<<-eoxml)
- <products/>
- eoxml
- end
-
- def test_one_node_with_attributes_document_as_hash
- assert_equal_rexml(<<-eoxml)
- <products foo="bar"/>
- eoxml
- end
-
- def test_products_node_with_book_node_as_hash
- assert_equal_rexml(<<-eoxml)
- <products>
- <book name="awesome" id="12345" />
- </products>
- eoxml
- end
-
- def test_products_node_with_two_book_nodes_as_hash
- assert_equal_rexml(<<-eoxml)
- <products>
- <book name="awesome" id="12345" />
- <book name="america" id="67890" />
- </products>
- eoxml
- end
- def test_single_node_with_content_as_hash
- assert_equal_rexml(<<-eoxml)
- <products>
- hello world
- </products>
- eoxml
- end
-
- def test_children_with_children
- assert_equal_rexml(<<-eoxml)
- <root>
- <products>
- <book name="america" id="67890" />
- </products>
- </root>
- eoxml
- end
-
- def test_children_with_text
- assert_equal_rexml(<<-eoxml)
- <root>
- <products>
- hello everyone
- </products>
- </root>
- eoxml
- end
-
- def test_children_with_non_adjacent_text
- assert_equal_rexml(<<-eoxml)
- <root>
- good
- <products>
- hello everyone
- </products>
- morning
- </root>
- eoxml
- end
-
- def test_parse_from_io
- io = StringIO.new(<<-eoxml)
- <root>
- good
- <products>
- hello everyone
- </products>
- morning
- </root>
- eoxml
- assert_equal_rexml(io)
- end
-
- def test_children_with_simple_cdata
- assert_equal_rexml(<<-eoxml)
- <root>
- <products>
- <![CDATA[cdatablock]]>
- </products>
- </root>
- eoxml
- end
-
- def test_children_with_multiple_cdata
- assert_equal_rexml(<<-eoxml)
- <root>
- <products>
- <![CDATA[cdatablock1]]><![CDATA[cdatablock2]]>
- </products>
- </root>
- eoxml
- end
-
- def test_children_with_text_and_cdata
- assert_equal_rexml(<<-eoxml)
- <root>
- <products>
- hello <![CDATA[cdatablock]]>
- morning
- </products>
- </root>
- eoxml
- end
-
- def test_children_with_blank_text
- assert_equal_rexml(<<-eoxml)
- <root>
- <products> </products>
- </root>
- eoxml
- end
-
- private
- def assert_equal_rexml(xml)
- parsed_xml = XmlMini.parse(xml)
- xml.rewind if xml.respond_to?(:rewind)
- hash = XmlMini.with_backend("REXML") { XmlMini.parse(xml) }
- assert_equal(hash, parsed_xml)
+ def expansion_attack_error
+ LibXML::XML::Error
end
end
-
end
diff --git a/activesupport/test/xml_mini/nokogiri_engine_test.rb b/activesupport/test/xml_mini/nokogiri_engine_test.rb
index 44b82da4e4..3151e75fc0 100644
--- a/activesupport/test/xml_mini/nokogiri_engine_test.rb
+++ b/activesupport/test/xml_mini/nokogiri_engine_test.rb
@@ -1,215 +1,14 @@
-begin
- require "nokogiri"
-rescue LoadError
- # Skip nokogiri tests
-else
- require "abstract_unit"
- require "active_support/xml_mini"
- require "active_support/core_ext/hash/conversions"
+require_relative "xml_mini_engine_test"
- class NokogiriEngineTest < ActiveSupport::TestCase
- def setup
- @default_backend = ActiveSupport::XmlMini.backend
- ActiveSupport::XmlMini.backend = "Nokogiri"
- end
-
- def teardown
- ActiveSupport::XmlMini.backend = @default_backend
- end
-
- def test_file_from_xml
- hash = Hash.from_xml(<<-eoxml)
- <blog>
- <logo type="file" name="logo.png" content_type="image/png">
- </logo>
- </blog>
- eoxml
- assert hash.has_key?("blog")
- assert hash["blog"].has_key?("logo")
-
- file = hash["blog"]["logo"]
- assert_equal "logo.png", file.original_filename
- assert_equal "image/png", file.content_type
- end
-
- def test_exception_thrown_on_expansion_attack
- assert_raise Nokogiri::XML::SyntaxError do
- attack_xml = <<-EOT
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE member [
- <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;">
- <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;">
- <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;">
- <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;">
- <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;">
- <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;">
- <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
- ]>
- <member>
- &a;
- </member>
- EOT
- Hash.from_xml(attack_xml)
+XMLMiniEngineTest.run_with_gem("nokogiri") do
+ class NokogiriEngineTest < XMLMiniEngineTest
+ private
+ def engine
+ "Nokogiri"
end
- end
-
- def test_setting_nokogiri_as_backend
- ActiveSupport::XmlMini.backend = "Nokogiri"
- assert_equal ActiveSupport::XmlMini_Nokogiri, ActiveSupport::XmlMini.backend
- end
-
- def test_blank_returns_empty_hash
- assert_equal({}, ActiveSupport::XmlMini.parse(nil))
- assert_equal({}, ActiveSupport::XmlMini.parse(""))
- end
-
- def test_array_type_makes_an_array
- assert_equal_rexml(<<-eoxml)
- <blog>
- <posts type="array">
- <post>a post</post>
- <post>another post</post>
- </posts>
- </blog>
- eoxml
- end
-
- def test_one_node_document_as_hash
- assert_equal_rexml(<<-eoxml)
- <products/>
- eoxml
- end
-
- def test_one_node_with_attributes_document_as_hash
- assert_equal_rexml(<<-eoxml)
- <products foo="bar"/>
- eoxml
- end
-
- def test_products_node_with_book_node_as_hash
- assert_equal_rexml(<<-eoxml)
- <products>
- <book name="awesome" id="12345" />
- </products>
- eoxml
- end
-
- def test_products_node_with_two_book_nodes_as_hash
- assert_equal_rexml(<<-eoxml)
- <products>
- <book name="awesome" id="12345" />
- <book name="america" id="67890" />
- </products>
- eoxml
- end
- def test_single_node_with_content_as_hash
- assert_equal_rexml(<<-eoxml)
- <products>
- hello world
- </products>
- eoxml
- end
-
- def test_children_with_children
- assert_equal_rexml(<<-eoxml)
- <root>
- <products>
- <book name="america" id="67890" />
- </products>
- </root>
- eoxml
- end
-
- def test_children_with_text
- assert_equal_rexml(<<-eoxml)
- <root>
- <products>
- hello everyone
- </products>
- </root>
- eoxml
- end
-
- def test_children_with_non_adjacent_text
- assert_equal_rexml(<<-eoxml)
- <root>
- good
- <products>
- hello everyone
- </products>
- morning
- </root>
- eoxml
- end
-
- def test_parse_from_io
- io = StringIO.new(<<-eoxml)
- <root>
- good
- <products>
- hello everyone
- </products>
- morning
- </root>
- eoxml
- assert_equal_rexml(io)
- end
-
- def test_children_with_simple_cdata
- assert_equal_rexml(<<-eoxml)
- <root>
- <products>
- <![CDATA[cdatablock]]>
- </products>
- </root>
- eoxml
- end
-
- def test_children_with_multiple_cdata
- assert_equal_rexml(<<-eoxml)
- <root>
- <products>
- <![CDATA[cdatablock1]]><![CDATA[cdatablock2]]>
- </products>
- </root>
- eoxml
- end
-
- def test_children_with_text_and_cdata
- assert_equal_rexml(<<-eoxml)
- <root>
- <products>
- hello <![CDATA[cdatablock]]>
- morning
- </products>
- </root>
- eoxml
- end
-
- def test_children_with_blank_text
- assert_equal_rexml(<<-eoxml)
- <root>
- <products> </products>
- </root>
- eoxml
- end
-
- def test_children_with_blank_text_and_attribute
- assert_equal_rexml(<<-eoxml)
- <root>
- <products type="file"> </products>
- </root>
- eoxml
- end
-
- private
- def assert_equal_rexml(xml)
- parsed_xml = ActiveSupport::XmlMini.parse(xml)
- xml.rewind if xml.respond_to?(:rewind)
- hash = ActiveSupport::XmlMini.with_backend("REXML") { ActiveSupport::XmlMini.parse(xml) }
- assert_equal(hash, parsed_xml)
+ def expansion_attack_error
+ Nokogiri::XML::SyntaxError
end
end
-
end
diff --git a/activesupport/test/xml_mini/nokogirisax_engine_test.rb b/activesupport/test/xml_mini/nokogirisax_engine_test.rb
index 24b35cadf6..7dafbdaf48 100644
--- a/activesupport/test/xml_mini/nokogirisax_engine_test.rb
+++ b/activesupport/test/xml_mini/nokogirisax_engine_test.rb
@@ -1,216 +1,14 @@
-begin
- require "nokogiri"
-rescue LoadError
- # Skip nokogiri tests
-else
- require "abstract_unit"
- require "active_support/xml_mini"
- require "active_support/core_ext/hash/conversions"
+require_relative "xml_mini_engine_test"
- class NokogiriSAXEngineTest < ActiveSupport::TestCase
- def setup
- @default_backend = ActiveSupport::XmlMini.backend
- ActiveSupport::XmlMini.backend = "NokogiriSAX"
- end
-
- def teardown
- ActiveSupport::XmlMini.backend = @default_backend
- end
-
- def test_file_from_xml
- hash = Hash.from_xml(<<-eoxml)
- <blog>
- <logo type="file" name="logo.png" content_type="image/png">
- </logo>
- </blog>
- eoxml
- assert hash.has_key?("blog")
- assert hash["blog"].has_key?("logo")
-
- file = hash["blog"]["logo"]
- assert_equal "logo.png", file.original_filename
- assert_equal "image/png", file.content_type
- end
-
- def test_exception_thrown_on_expansion_attack
- assert_raise RuntimeError do
- attack_xml = <<-EOT
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE member [
- <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;">
- <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;">
- <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;">
- <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;">
- <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;">
- <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;">
- <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
- ]>
- <member>
- &a;
- </member>
- EOT
-
- Hash.from_xml(attack_xml)
+XMLMiniEngineTest.run_with_gem("nokogiri") do
+ class NokogiriSAXEngineTest < XMLMiniEngineTest
+ private
+ def engine
+ "NokogiriSAX"
end
- end
-
- def test_setting_nokogirisax_as_backend
- ActiveSupport::XmlMini.backend = "NokogiriSAX"
- assert_equal ActiveSupport::XmlMini_NokogiriSAX, ActiveSupport::XmlMini.backend
- end
-
- def test_blank_returns_empty_hash
- assert_equal({}, ActiveSupport::XmlMini.parse(nil))
- assert_equal({}, ActiveSupport::XmlMini.parse(""))
- end
-
- def test_array_type_makes_an_array
- assert_equal_rexml(<<-eoxml)
- <blog>
- <posts type="array">
- <post>a post</post>
- <post>another post</post>
- </posts>
- </blog>
- eoxml
- end
-
- def test_one_node_document_as_hash
- assert_equal_rexml(<<-eoxml)
- <products/>
- eoxml
- end
-
- def test_one_node_with_attributes_document_as_hash
- assert_equal_rexml(<<-eoxml)
- <products foo="bar"/>
- eoxml
- end
-
- def test_products_node_with_book_node_as_hash
- assert_equal_rexml(<<-eoxml)
- <products>
- <book name="awesome" id="12345" />
- </products>
- eoxml
- end
-
- def test_products_node_with_two_book_nodes_as_hash
- assert_equal_rexml(<<-eoxml)
- <products>
- <book name="awesome" id="12345" />
- <book name="america" id="67890" />
- </products>
- eoxml
- end
- def test_single_node_with_content_as_hash
- assert_equal_rexml(<<-eoxml)
- <products>
- hello world
- </products>
- eoxml
- end
-
- def test_children_with_children
- assert_equal_rexml(<<-eoxml)
- <root>
- <products>
- <book name="america" id="67890" />
- </products>
- </root>
- eoxml
- end
-
- def test_children_with_text
- assert_equal_rexml(<<-eoxml)
- <root>
- <products>
- hello everyone
- </products>
- </root>
- eoxml
- end
-
- def test_children_with_non_adjacent_text
- assert_equal_rexml(<<-eoxml)
- <root>
- good
- <products>
- hello everyone
- </products>
- morning
- </root>
- eoxml
- end
-
- def test_parse_from_io
- io = StringIO.new(<<-eoxml)
- <root>
- good
- <products>
- hello everyone
- </products>
- morning
- </root>
- eoxml
- assert_equal_rexml(io)
- end
-
- def test_children_with_simple_cdata
- assert_equal_rexml(<<-eoxml)
- <root>
- <products>
- <![CDATA[cdatablock]]>
- </products>
- </root>
- eoxml
- end
-
- def test_children_with_multiple_cdata
- assert_equal_rexml(<<-eoxml)
- <root>
- <products>
- <![CDATA[cdatablock1]]><![CDATA[cdatablock2]]>
- </products>
- </root>
- eoxml
- end
-
- def test_children_with_text_and_cdata
- assert_equal_rexml(<<-eoxml)
- <root>
- <products>
- hello <![CDATA[cdatablock]]>
- morning
- </products>
- </root>
- eoxml
- end
-
- def test_children_with_blank_text
- assert_equal_rexml(<<-eoxml)
- <root>
- <products> </products>
- </root>
- eoxml
- end
-
- def test_children_with_blank_text_and_attribute
- assert_equal_rexml(<<-eoxml)
- <root>
- <products type="file"> </products>
- </root>
- eoxml
- end
-
- private
- def assert_equal_rexml(xml)
- parsed_xml = ActiveSupport::XmlMini.parse(xml)
- xml.rewind if xml.respond_to?(:rewind)
- hash = ActiveSupport::XmlMini.with_backend("REXML") { ActiveSupport::XmlMini.parse(xml) }
- assert_equal(hash, parsed_xml)
+ def expansion_attack_error
+ RuntimeError
end
end
-
end
diff --git a/activesupport/test/xml_mini/rexml_engine_test.rb b/activesupport/test/xml_mini/rexml_engine_test.rb
index e616b73162..c51f0d3c20 100644
--- a/activesupport/test/xml_mini/rexml_engine_test.rb
+++ b/activesupport/test/xml_mini/rexml_engine_test.rb
@@ -1,44 +1,25 @@
-require "abstract_unit"
-require "active_support/xml_mini"
+require_relative "xml_mini_engine_test"
-class REXMLEngineTest < ActiveSupport::TestCase
+class REXMLEngineTest < XMLMiniEngineTest
def test_default_is_rexml
assert_equal ActiveSupport::XmlMini_REXML, ActiveSupport::XmlMini.backend
end
- def test_set_rexml_as_backend
- ActiveSupport::XmlMini.backend = "REXML"
- assert_equal ActiveSupport::XmlMini_REXML, ActiveSupport::XmlMini.backend
- end
-
- def test_parse_from_io
- ActiveSupport::XmlMini.backend = "REXML"
- io = StringIO.new(<<-eoxml)
- <root>
- good
- <products>
- hello everyone
- </products>
- morning
- </root>
- eoxml
- hash = ActiveSupport::XmlMini.parse(io)
- assert hash.has_key?("root")
- assert hash["root"].has_key?("products")
- assert_match "good", hash["root"]["__content__"]
- products = hash["root"]["products"]
- assert products.has_key?("__content__")
- assert_match "hello everyone", products["__content__"]
- end
-
def test_parse_from_empty_string
- ActiveSupport::XmlMini.backend = "REXML"
assert_equal({}, ActiveSupport::XmlMini.parse(""))
end
def test_parse_from_frozen_string
- ActiveSupport::XmlMini.backend = "REXML"
xml_string = "<root></root>".freeze
- assert_equal({"root" => {}}, ActiveSupport::XmlMini.parse(xml_string))
+ assert_equal({ "root" => {} }, ActiveSupport::XmlMini.parse(xml_string))
end
+
+ private
+ def engine
+ "REXML"
+ end
+
+ def expansion_attack_error
+ RuntimeError
+ end
end
diff --git a/activesupport/test/xml_mini/xml_mini_engine_test.rb b/activesupport/test/xml_mini/xml_mini_engine_test.rb
new file mode 100644
index 0000000000..5be9084c9d
--- /dev/null
+++ b/activesupport/test/xml_mini/xml_mini_engine_test.rb
@@ -0,0 +1,257 @@
+require "abstract_unit"
+require "active_support/xml_mini"
+require "active_support/core_ext/hash/conversions"
+
+class XMLMiniEngineTest < ActiveSupport::TestCase
+ def self.run_with_gem(gem_name)
+ require gem_name
+ yield
+ rescue LoadError
+ # Skip tests unless gem is available
+ end
+
+ def self.run_with_platform(platform_name)
+ yield if RUBY_PLATFORM.include?(platform_name)
+ end
+
+ def self.inherited(base)
+ base.include EngineTests
+ super
+ end
+
+ def setup
+ @default_backend = ActiveSupport::XmlMini.backend
+ ActiveSupport::XmlMini.backend = engine
+ super
+ end
+
+ def teardown
+ ActiveSupport::XmlMini.backend = @default_backend
+ super
+ end
+
+ module EngineTests
+ def test_file_from_xml
+ hash = Hash.from_xml(<<-eoxml)
+ <blog>
+ <logo type="file" name="logo.png" content_type="image/png">
+ </logo>
+ </blog>
+ eoxml
+ assert hash.key?("blog")
+ assert hash["blog"].key?("logo")
+
+ file = hash["blog"]["logo"]
+ assert_equal "logo.png", file.original_filename
+ assert_equal "image/png", file.content_type
+ end
+
+ def test_exception_thrown_on_expansion_attack
+ assert_raise expansion_attack_error do
+ Hash.from_xml(<<-eoxml)
+ <?xml version="1.0" encoding="UTF-8"?>
+ <!DOCTYPE member [
+ <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;">
+ <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;">
+ <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;">
+ <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;">
+ <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;">
+ <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;">
+ <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
+ ]>
+ <member>
+ &a;
+ </member>
+ eoxml
+ end
+ end
+
+ def test_setting_backend
+ assert_engine_class ActiveSupport::XmlMini.backend
+ end
+
+ def test_blank_returns_empty_hash
+ assert_equal({}, ActiveSupport::XmlMini.parse(nil))
+ assert_equal({}, ActiveSupport::XmlMini.parse(""))
+ end
+
+ def test_array_type_makes_an_array
+ assert_equal_rexml(<<-eoxml)
+ <blog>
+ <posts type="array">
+ <post>a post</post>
+ <post>another post</post>
+ </posts>
+ </blog>
+ eoxml
+ end
+
+ def test_one_node_document_as_hash
+ assert_equal_rexml(<<-eoxml)
+ <products/>
+ eoxml
+ end
+
+ def test_one_node_with_attributes_document_as_hash
+ assert_equal_rexml(<<-eoxml)
+ <products foo="bar"/>
+ eoxml
+ end
+
+ def test_products_node_with_book_node_as_hash
+ assert_equal_rexml(<<-eoxml)
+ <products>
+ <book name="awesome" id="12345" />
+ </products>
+ eoxml
+ end
+
+ def test_products_node_with_two_book_nodes_as_hash
+ assert_equal_rexml(<<-eoxml)
+ <products>
+ <book name="awesome" id="12345" />
+ <book name="america" id="67890" />
+ </products>
+ eoxml
+ end
+
+ def test_single_node_with_content_as_hash
+ assert_equal_rexml(<<-eoxml)
+ <products>
+ hello world
+ </products>
+ eoxml
+ end
+
+ def test_children_with_children
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ <products>
+ <book name="america" id="67890" />
+ </products>
+ </root>
+ eoxml
+ end
+
+ def test_children_with_text
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ <products>
+ hello everyone
+ </products>
+ </root>
+ eoxml
+ end
+
+ def test_children_with_non_adjacent_text
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ good
+ <products>
+ hello everyone
+ </products>
+ morning
+ </root>
+ eoxml
+ end
+
+ def test_parse_from_io
+ skip_unless_extended_engine
+
+ assert_equal_rexml(StringIO.new(<<-eoxml))
+ <root>
+ good
+ <products>
+ hello everyone
+ </products>
+ morning
+ </root>
+ eoxml
+ end
+
+ def test_children_with_simple_cdata
+ skip_unless_extended_engine
+
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ <products>
+ <![CDATA[cdatablock]]>
+ </products>
+ </root>
+ eoxml
+ end
+
+ def test_children_with_multiple_cdata
+ skip_unless_extended_engine
+
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ <products>
+ <![CDATA[cdatablock1]]><![CDATA[cdatablock2]]>
+ </products>
+ </root>
+ eoxml
+ end
+
+ def test_children_with_text_and_cdata
+ skip_unless_extended_engine
+
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ <products>
+ hello <![CDATA[cdatablock]]>
+ morning
+ </products>
+ </root>
+ eoxml
+ end
+
+ def test_children_with_blank_text
+ skip_unless_extended_engine
+
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ <products> </products>
+ </root>
+ eoxml
+ end
+
+ def test_children_with_blank_text_and_attribute
+ skip_unless_extended_engine
+
+ assert_equal_rexml(<<-eoxml)
+ <root>
+ <products type="file"> </products>
+ </root>
+ eoxml
+ end
+
+ private
+ def engine
+ raise NotImplementedError
+ end
+
+ def assert_engine_class(actual)
+ assert_equal ActiveSupport.const_get("XmlMini_#{engine}"), actual
+ end
+
+ def assert_equal_rexml(xml)
+ parsed_xml = ActiveSupport::XmlMini.parse(xml)
+ xml.rewind if xml.respond_to?(:rewind)
+ hash = ActiveSupport::XmlMini.with_backend("REXML") { ActiveSupport::XmlMini.parse(xml) }
+ assert_equal(hash, parsed_xml)
+ end
+
+ def expansion_attack_error
+ raise NotImplementedError
+ end
+
+ def extended_engine?
+ true
+ end
+
+ def skip_unless_extended_engine
+ skip "#{engine} is not an extended engine" unless extended_engine?
+ end
+ end
+end
diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb
index 2d79e71343..b8caa1d74c 100644
--- a/activesupport/test/xml_mini_test.rb
+++ b/activesupport/test/xml_mini_test.rb
@@ -63,16 +63,16 @@ module XmlMiniTest
def setup
@xml = ActiveSupport::XmlMini
- @options = {skip_instruct: true, builder: Builder::XmlMarkup.new}
+ @options = { skip_instruct: true, builder: Builder::XmlMarkup.new }
end
test "#to_tag accepts a callable object and passes options with the builder" do
- @xml.to_tag(:some_tag, lambda {|o| o[:builder].br }, @options)
+ @xml.to_tag(:some_tag, lambda { |o| o[:builder].br }, @options)
assert_xml "<br/>"
end
test "#to_tag accepts a callable object and passes options and tag name" do
- @xml.to_tag(:tag, lambda {|o, t| o[:builder].b(t) }, @options)
+ @xml.to_tag(:tag, lambda { |o, t| o[:builder].b(t) }, @options)
assert_xml "<b>tag</b>"
end
@@ -93,57 +93,57 @@ module XmlMiniTest
test "#to_tag should use the type value in the options hash" do
@xml.to_tag(:b, "blue", @options.merge(type: "color"))
- assert_xml( "<b type=\"color\">blue</b>" )
+ assert_xml("<b type=\"color\">blue</b>")
end
test "#to_tag accepts symbol types" do
@xml.to_tag(:b, :name, @options)
- assert_xml( "<b type=\"symbol\">name</b>" )
+ assert_xml("<b type=\"symbol\">name</b>")
end
test "#to_tag accepts boolean types" do
@xml.to_tag(:b, true, @options)
- assert_xml( "<b type=\"boolean\">true</b>")
+ assert_xml("<b type=\"boolean\">true</b>")
end
test "#to_tag accepts float types" do
@xml.to_tag(:b, 3.14, @options)
- assert_xml( "<b type=\"float\">3.14</b>")
+ assert_xml("<b type=\"float\">3.14</b>")
end
test "#to_tag accepts decimal types" do
@xml.to_tag(:b, ::BigDecimal.new("1.2"), @options)
- assert_xml( "<b type=\"decimal\">1.2</b>")
+ assert_xml("<b type=\"decimal\">1.2</b>")
end
test "#to_tag accepts date types" do
- @xml.to_tag(:b, Date.new(2001,2,3), @options)
- assert_xml( "<b type=\"date\">2001-02-03</b>")
+ @xml.to_tag(:b, Date.new(2001, 2, 3), @options)
+ assert_xml("<b type=\"date\">2001-02-03</b>")
end
test "#to_tag accepts datetime types" do
- @xml.to_tag(:b, DateTime.new(2001,2,3,4,5,6,"+7"), @options)
- assert_xml( "<b type=\"dateTime\">2001-02-03T04:05:06+07:00</b>")
+ @xml.to_tag(:b, DateTime.new(2001, 2, 3, 4, 5, 6, "+7"), @options)
+ assert_xml("<b type=\"dateTime\">2001-02-03T04:05:06+07:00</b>")
end
test "#to_tag accepts time types" do
@xml.to_tag(:b, Time.new(1993, 02, 24, 12, 0, 0, "+09:00"), @options)
- assert_xml( "<b type=\"dateTime\">1993-02-24T12:00:00+09:00</b>")
+ assert_xml("<b type=\"dateTime\">1993-02-24T12:00:00+09:00</b>")
end
test "#to_tag accepts array types" do
@xml.to_tag(:b, ["first_name", "last_name"], @options)
- assert_xml( "<b type=\"array\"><b>first_name</b><b>last_name</b></b>" )
+ assert_xml("<b type=\"array\"><b>first_name</b><b>last_name</b></b>")
end
test "#to_tag accepts hash types" do
@xml.to_tag(:b, { first_name: "Bob", last_name: "Marley" }, @options)
- assert_xml( "<b><first-name>Bob</first-name><last-name>Marley</last-name></b>" )
+ assert_xml("<b><first-name>Bob</first-name><last-name>Marley</last-name></b>")
end
test "#to_tag should not add type when skip types option is set" do
@xml.to_tag(:b, "Bob", @options.merge(skip_types: 1))
- assert_xml( "<b>Bob</b>" )
+ assert_xml("<b>Bob</b>")
end
test "#to_tag should dasherize the space when passed a string with spaces as a key" do
@@ -237,22 +237,22 @@ module XmlMiniTest
assert_equal :symbol, parser.call("symbol")
assert_equal :symbol, parser.call(:symbol)
assert_equal :'123', parser.call(123)
- assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) }
+ assert_raises(ArgumentError) { parser.call(Date.new(2013, 11, 12, 02, 11)) }
end
def test_date
parser = @parsing["date"]
- assert_equal Date.new(2013,11,12), parser.call("2013-11-12T0211Z")
+ assert_equal Date.new(2013, 11, 12), parser.call("2013-11-12T0211Z")
assert_raises(TypeError) { parser.call(1384190018) }
assert_raises(ArgumentError) { parser.call("not really a date") }
end
def test_datetime
parser = @parsing["datetime"]
- assert_equal Time.new(2013,11,12,02,11,00,0), parser.call("2013-11-12T02:11:00Z")
- assert_equal DateTime.new(2013,11,12), parser.call("2013-11-12T0211Z")
- assert_equal DateTime.new(2013,11,12,02,11), parser.call("2013-11-12T02:11Z")
- assert_equal DateTime.new(2013,11,12,02,11), parser.call("2013-11-12T11:11+9")
+ assert_equal Time.new(2013, 11, 12, 02, 11, 00, 0), parser.call("2013-11-12T02:11:00Z")
+ assert_equal DateTime.new(2013, 11, 12), parser.call("2013-11-12T0211Z")
+ assert_equal DateTime.new(2013, 11, 12, 02, 11), parser.call("2013-11-12T02:11Z")
+ assert_equal DateTime.new(2013, 11, 12, 02, 11), parser.call("2013-11-12T11:11+9")
assert_raises(ArgumentError) { parser.call("1384190018") }
end
@@ -262,7 +262,7 @@ module XmlMiniTest
assert_equal 123, parser.call(123.003)
assert_equal 123, parser.call("123")
assert_equal 0, parser.call("")
- assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) }
+ assert_raises(ArgumentError) { parser.call(Date.new(2013, 11, 12, 02, 11)) }
end
def test_float
@@ -273,7 +273,7 @@ module XmlMiniTest
assert_equal 0.0, parser.call("")
assert_equal 123, parser.call(123)
assert_equal 123.05, parser.call(123.05)
- assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) }
+ assert_raises(ArgumentError) { parser.call(Date.new(2013, 11, 12, 02, 11)) }
end
def test_decimal
@@ -284,7 +284,7 @@ module XmlMiniTest
assert_equal 0.0, parser.call("")
assert_equal 123, parser.call(123)
assert_raises(ArgumentError) { parser.call(123.04) }
- assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) }
+ assert_raises(ArgumentError) { parser.call(Date.new(2013, 11, 12, 02, 11)) }
end
def test_boolean
@@ -305,7 +305,7 @@ module XmlMiniTest
assert_equal "[]", parser.call("[]")
assert_equal "[]", parser.call([])
assert_equal "{}", parser.call({})
- assert_raises(ArgumentError) { parser.call(Date.new(2013,11,12,02,11)) }
+ assert_raises(ArgumentError) { parser.call(Date.new(2013, 11, 12, 02, 11)) }
end
def test_yaml
@@ -316,14 +316,14 @@ product:
description : Basketball
YAML
expected = {
- "product"=> [
- {"sku"=>"BL394D", "quantity"=>4, "description"=>"Basketball"}
+ "product" => [
+ { "sku" => "BL394D", "quantity" => 4, "description" => "Basketball" }
]
}
parser = @parsing["yaml"]
assert_equal(expected, parser.call(yaml))
- assert_equal({1 => "test"}, parser.call(1 => "test"))
- assert_equal({"1 => 'test'"=>nil}, parser.call("{1 => 'test'}"))
+ assert_equal({ 1 => "test" }, parser.call(1 => "test"))
+ assert_equal({ "1 => 'test'" => nil }, parser.call("{1 => 'test'}"))
end
def test_base64Binary_and_binary
@@ -342,11 +342,11 @@ vehemence of any carnal pleasure.
EXPECTED
parser = @parsing["base64Binary"]
- assert_equal expected_base64.gsub(/\n/," ").strip, parser.call(base64)
+ assert_equal expected_base64.gsub(/\n/, " ").strip, parser.call(base64)
parser.call("NON BASE64 INPUT")
parser = @parsing["binary"]
- assert_equal expected_base64.gsub(/\n/," ").strip, parser.call(base64, "encoding" => "base64")
+ assert_equal expected_base64.gsub(/\n/, " ").strip, parser.call(base64, "encoding" => "base64")
assert_equal "IGNORED INPUT", parser.call("IGNORED INPUT", {})
end
end
diff --git a/ci/travis.rb b/ci/travis.rb
index 1d658bae47..c49a87d864 100755
--- a/ci/travis.rb
+++ b/ci/travis.rb
@@ -136,7 +136,7 @@ class Build
end
end
-if ENV["GEM"]=="aj:integration"
+if ENV["GEM"] == "aj:integration"
ENV["QC_DATABASE_URL"] = "postgres://postgres@localhost/active_jobs_qc_int_test"
ENV["QUE_DATABASE_URL"] = "postgres://postgres@localhost/active_jobs_que_int_test"
end
diff --git a/guides/Rakefile b/guides/Rakefile
index 353966fc55..d2591f523c 100644
--- a/guides/Rakefile
+++ b/guides/Rakefile
@@ -13,8 +13,9 @@ namespace :guides do
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 do
- unless `kindlerb -v 2> /dev/null` =~ /kindlerb 0.1.1/
- abort "Please `gem install kindlerb` and make sure you have `kindlegen` in your PATH"
+ require "kindlerb"
+ unless Kindlerb.kindlegen_available?
+ abort "Please run `setupkindlerb` to install kindlegen"
end
unless `convert` =~ /convert/
abort "Please install ImageMagick`"
diff --git a/guides/assets/stylesheets/main.css b/guides/assets/stylesheets/main.css
index ed558e4793..b56699a0d0 100644
--- a/guides/assets/stylesheets/main.css
+++ b/guides/assets/stylesheets/main.css
@@ -16,7 +16,6 @@
.large {font-size: larger;}
.hide {display: none;}
-li ul, li ol { margin:0 1.5em; }
ul, ol { margin: 0 1.5em 1.5em 1.5em; }
ul { list-style-type: disc; }
@@ -602,6 +601,8 @@ h6 {
font-weight: normal;
}
+#subCol li ul, li ol { margin:0 1.5em; }
+
div.code_container {
background: #EEE url(../images/tab_grey.gif) no-repeat left top;
padding: 0.25em 1em 0.5em 48px;
diff --git a/guides/assets/stylesheets/responsive-tables.css b/guides/assets/stylesheets/responsive-tables.css
index f5fbcbf948..f5fbcbf948 100755..100644
--- a/guides/assets/stylesheets/responsive-tables.css
+++ b/guides/assets/stylesheets/responsive-tables.css
diff --git a/guides/assets/stylesheets/syntaxhighlighter/shCore.css b/guides/assets/stylesheets/syntaxhighlighter/shCore.css
index 34f6864a15..7e1e199343 100644
--- a/guides/assets/stylesheets/syntaxhighlighter/shCore.css
+++ b/guides/assets/stylesheets/syntaxhighlighter/shCore.css
@@ -33,7 +33,7 @@
height: auto !important;
left: auto !important;
line-height: 1.1em !important;
- margin: 0 !important;
+ margin: 0 0 0.5px 0 !important;
outline: 0 !important;
overflow: visible !important;
padding: 0 !important;
diff --git a/guides/bug_report_templates/action_controller_master.rb b/guides/bug_report_templates/action_controller_master.rb
index cd0a3be85c..7644f6fe4a 100644
--- a/guides/bug_report_templates/action_controller_master.rb
+++ b/guides/bug_report_templates/action_controller_master.rb
@@ -8,13 +8,13 @@ end
gemfile(true) do
source "https://rubygems.org"
gem "rails", github: "rails/rails"
+ gem "arel", github: "rails/arel"
end
require "action_controller/railtie"
class TestApp < Rails::Application
config.root = File.dirname(__FILE__)
- 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/active_job_gem.rb b/guides/bug_report_templates/active_job_gem.rb
new file mode 100644
index 0000000000..debc46ad54
--- /dev/null
+++ b/guides/bug_report_templates/active_job_gem.rb
@@ -0,0 +1,32 @@
+begin
+ require "bundler/inline"
+rescue LoadError => e
+ $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
+ raise e
+end
+
+gemfile(true) do
+ source "https://rubygems.org"
+ # Activate the gem you are reporting the issue against.
+ gem "activejob", "5.0.0"
+end
+
+require "minitest/autorun"
+require "active_job"
+
+# Ensure backward compatibility with Minitest 4
+Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
+
+class BuggyJob < ActiveJob::Base
+ def perform
+ puts "performed"
+ end
+end
+
+class BuggyJobTest < ActiveJob::TestCase
+ def test_stuff
+ assert_enqueued_with(job: BuggyJob) do
+ BuggyJob.perform_later
+ end
+ end
+end
diff --git a/guides/bug_report_templates/active_job_master.rb b/guides/bug_report_templates/active_job_master.rb
new file mode 100644
index 0000000000..7591470440
--- /dev/null
+++ b/guides/bug_report_templates/active_job_master.rb
@@ -0,0 +1,32 @@
+begin
+ require "bundler/inline"
+rescue LoadError => e
+ $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
+ raise e
+end
+
+gemfile(true) do
+ source "https://rubygems.org"
+ gem "rails", github: "rails/rails"
+ gem "arel", github: "rails/arel"
+end
+
+require "active_job"
+require "minitest/autorun"
+
+# Ensure backward compatibility with Minitest 4
+Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
+
+class BuggyJob < ActiveJob::Base
+ def perform
+ puts "performed"
+ end
+end
+
+class BuggyJobTest < ActiveJob::TestCase
+ def test_stuff
+ assert_enqueued_with(job: BuggyJob) do
+ BuggyJob.perform_later
+ end
+ end
+end
diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb
index 7265a671b0..8bbc1ef19e 100644
--- a/guides/bug_report_templates/active_record_master.rb
+++ b/guides/bug_report_templates/active_record_master.rb
@@ -8,6 +8,7 @@ end
gemfile(true) do
source "https://rubygems.org"
gem "rails", github: "rails/rails"
+ gem "arel", github: "rails/arel"
gem "sqlite3"
end
diff --git a/guides/bug_report_templates/active_record_migrations_gem.rb b/guides/bug_report_templates/active_record_migrations_gem.rb
new file mode 100644
index 0000000000..ba80e6b4ad
--- /dev/null
+++ b/guides/bug_report_templates/active_record_migrations_gem.rb
@@ -0,0 +1,65 @@
+begin
+ require "bundler/inline"
+rescue LoadError => e
+ $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
+ raise e
+end
+
+gemfile(true) do
+ source "https://rubygems.org"
+ # Activate the gem you are reporting the issue against.
+ gem "activerecord", "5.0.0.1"
+ gem "sqlite3"
+end
+
+require "active_record"
+require "minitest/autorun"
+require "logger"
+
+# Ensure backward compatibility with Minitest 4
+Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
+
+# This connection will do for database-independent bug reports.
+ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
+ActiveRecord::Base.logger = Logger.new(STDOUT)
+
+ActiveRecord::Schema.define do
+ create_table :payments, force: true do |t|
+ t.decimal :amount, precision: 10, scale: 0, default: 0, null: false
+ end
+end
+
+class Payment < ActiveRecord::Base
+end
+
+class ChangeAmountToAddScale < ActiveRecord::Migration[5.0]
+ def change
+ reversible do |dir|
+ dir.up do
+ change_column :payments, :amount, :decimal, precision: 10, scale: 2, default: 0, null: false
+ end
+
+ dir.down do
+ change_column :payments, :amount, :decimal, precision: 10, scale: 0, default: 0, null: false
+ end
+ end
+ end
+end
+
+class BugTest < Minitest::Test
+ def test_migration_up
+ migrator = ActiveRecord::Migrator.new(:up, [ChangeAmountToAddScale])
+ migrator.run
+ 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
+ Payment.reset_column_information
+
+ assert_equal "decimal(10,0)", Payment.columns.last.sql_type
+ end
+end
diff --git a/guides/bug_report_templates/active_record_migrations_master.rb b/guides/bug_report_templates/active_record_migrations_master.rb
new file mode 100644
index 0000000000..84a4b71909
--- /dev/null
+++ b/guides/bug_report_templates/active_record_migrations_master.rb
@@ -0,0 +1,65 @@
+begin
+ require "bundler/inline"
+rescue LoadError => e
+ $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
+ raise e
+end
+
+gemfile(true) do
+ source "https://rubygems.org"
+ gem "rails", github: "rails/rails"
+ gem "arel", github: "rails/arel"
+ gem "sqlite3"
+end
+
+require "active_record"
+require "minitest/autorun"
+require "logger"
+
+# Ensure backward compatibility with Minitest 4
+Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
+
+# This connection will do for database-independent bug reports.
+ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
+ActiveRecord::Base.logger = Logger.new(STDOUT)
+
+ActiveRecord::Schema.define do
+ create_table :payments, force: true do |t|
+ t.decimal :amount, precision: 10, scale: 0, default: 0, null: false
+ end
+end
+
+class Payment < ActiveRecord::Base
+end
+
+class ChangeAmountToAddScale < ActiveRecord::Migration[5.0]
+ def change
+ reversible do |dir|
+ dir.up do
+ change_column :payments, :amount, :decimal, precision: 10, scale: 2, default: 0, null: false
+ end
+
+ dir.down do
+ change_column :payments, :amount, :decimal, precision: 10, scale: 0, default: 0, null: false
+ end
+ end
+ end
+end
+
+class BugTest < Minitest::Test
+ def test_migration_up
+ migrator = ActiveRecord::Migrator.new(:up, [ChangeAmountToAddScale])
+ migrator.run
+ 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
+ Payment.reset_column_information
+
+ assert_equal "decimal(10,0)", Payment.columns.last.sql_type
+ end
+end
diff --git a/guides/bug_report_templates/benchmark.rb b/guides/bug_report_templates/benchmark.rb
new file mode 100644
index 0000000000..a0b541d012
--- /dev/null
+++ b/guides/bug_report_templates/benchmark.rb
@@ -0,0 +1,50 @@
+begin
+ require "bundler/inline"
+rescue LoadError => e
+ $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
+ raise e
+end
+
+gemfile(true) do
+ source "https://rubygems.org"
+ gem "rails", github: "rails/rails"
+ gem "arel", github: "rails/arel"
+ gem "benchmark-ips"
+end
+
+require "active_support"
+require "active_support/core_ext/object/blank"
+
+# Your patch goes here.
+class String
+ def fast_blank?
+ true
+ end
+end
+
+# Enumerate some representative scenarios here.
+#
+# It is very easy to make an optimization that improves performance for a
+# specific scenario you care about but regresses on other common cases.
+# Therefore, you should test your change against a list of representative
+# scenarios. Ideally, they should be based on real-world scenarios extracted
+# from production applications.
+SCENARIOS = {
+ "Empty" => "",
+ "Single Space" => " ",
+ "Two Spaces" => " ",
+ "Mixed Whitspaces" => " \t\r\n",
+ "Very Long String" => " " * 100
+}
+
+SCENARIOS.each_pair do |name, value|
+ puts
+ puts " #{name} ".center(80, "=")
+ puts
+
+ Benchmark.ips do |x|
+ x.report("blank?") { value.blank? }
+ x.report("fast_blank?") { value.fast_blank? }
+ x.compare!
+ end
+end
diff --git a/guides/bug_report_templates/generic_master.rb b/guides/bug_report_templates/generic_master.rb
index d3a7ae4ac4..ed45726e92 100644
--- a/guides/bug_report_templates/generic_master.rb
+++ b/guides/bug_report_templates/generic_master.rb
@@ -8,6 +8,7 @@ end
gemfile(true) do
source "https://rubygems.org"
gem "rails", github: "rails/rails"
+ gem "arel", github: "rails/arel"
end
require "active_support"
diff --git a/guides/rails_guides/generator.rb b/guides/rails_guides/generator.rb
index 39a57191eb..a818ca9d72 100644
--- a/guides/rails_guides/generator.rb
+++ b/guides/rails_guides/generator.rb
@@ -68,7 +68,7 @@ module RailsGuides
GUIDES_RE = /\.(?:erb|md)\z/
- def initialize(output=nil)
+ def initialize(output = nil)
set_flags_from_environment
if kindle?
diff --git a/guides/rails_guides/helpers.rb b/guides/rails_guides/helpers.rb
index c7d3a38ba9..6f4b0b492c 100644
--- a/guides/rails_guides/helpers.rb
+++ b/guides/rails_guides/helpers.rb
@@ -19,14 +19,14 @@ module RailsGuides
end
def documents_flat
- documents_by_section.flat_map {|section| section["documents"]}
+ documents_by_section.flat_map { |section| section["documents"] }
end
def finished_documents(documents)
documents.reject { |document| document["work_in_progress"] }
end
- def docs_for_menu(position=nil)
+ def docs_for_menu(position = nil)
if position.nil?
documents_by_section
elsif position == "L"
diff --git a/guides/rails_guides/indexer.rb b/guides/rails_guides/indexer.rb
index 56df6a5842..c58b6b85a2 100644
--- a/guides/rails_guides/indexer.rb
+++ b/guides/rails_guides/indexer.rb
@@ -17,7 +17,7 @@ module RailsGuides
private
- def process(string, current_level=3, counters=[1])
+ def process(string, current_level = 3, counters = [1])
s = StringScanner.new(string)
level_hash = {}
diff --git a/guides/rails_guides/kindle.rb b/guides/rails_guides/kindle.rb
index 675df8e55b..9536d0bd3b 100644
--- a/guides/rails_guides/kindle.rb
+++ b/guides/rails_guides/kindle.rb
@@ -1,9 +1,6 @@
#!/usr/bin/env ruby
-unless `which kindlerb`
- abort "Please gem install kindlerb"
-end
-
+require "kindlerb"
require "nokogiri"
require "fileutils"
require "yaml"
@@ -19,7 +16,7 @@ module Kindle
puts "=> Arranging html pages in document order"
toc = File.read("toc.ncx")
doc = Nokogiri::XML(toc).xpath("//ncx:content", "ncx" => "http://www.daisy.org/z3986/2005/ncx/")
- html_pages = doc.select {|c| c[:src]}.map {|c| c[:src]}.uniq
+ html_pages = doc.select { |c| c[:src] }.map { |c| c[:src] }.uniq
generate_front_matter(html_pages)
@@ -28,22 +25,21 @@ module Kindle
generate_document_metadata(mobi_outfile)
puts "Creating MOBI document with kindlegen. This may take a while."
- cmd = "kindlerb . > #{File.absolute_path logfile} 2>&1"
- puts cmd
- system(cmd)
- puts "MOBI document generated at #{File.expand_path(mobi_outfile, output_dir)}"
+ if Kindlerb.run(output_dir)
+ puts "MOBI document generated at #{File.expand_path(mobi_outfile, output_dir)}"
+ end
end
end
def generate_front_matter(html_pages)
frontmatter = []
- html_pages.delete_if {|x|
+ html_pages.delete_if { |x|
if x =~ /(toc|welcome|credits|copyright).html/
frontmatter << x unless x =~ /toc/
true
end
}
- html = frontmatter.map {|x|
+ html = frontmatter.map { |x|
Nokogiri::HTML(File.open(x)).at("body").inner_html
}.join("\n")
@@ -56,7 +52,7 @@ module Kindle
h2["id"] = h2.inner_text.gsub(/\s/, "-")
end
add_head_section fdoc, "Front Matter"
- File.open("frontmatter.html","w") {|f| f.puts fdoc.to_html}
+ File.open("frontmatter.html", "w") { |f| f.puts fdoc.to_html }
html_pages.unshift "frontmatter.html"
end
@@ -67,18 +63,18 @@ module Kindle
doc = Nokogiri::HTML(File.open(page))
title = doc.at("title").inner_text.gsub("Ruby on Rails Guides: ", "")
title = page.capitalize.gsub(".html", "") if title.strip == ""
- File.open("sections/%03d/_section.txt" % section_idx, "w") {|f| f.puts title}
- doc.xpath("//h3[@id]").each_with_index do |h3,item_idx|
+ File.open("sections/%03d/_section.txt" % section_idx, "w") { |f| f.puts title }
+ doc.xpath("//h3[@id]").each_with_index do |h3, item_idx|
subsection = h3.inner_text
- content = h3.xpath("./following-sibling::*").take_while {|x| x.name != "h3"}.map(&:to_html)
+ content = h3.xpath("./following-sibling::*").take_while { |x| x.name != "h3" }.map(&:to_html)
item = Nokogiri::HTML(h3.to_html + content.join("\n"))
item_path = "sections/%03d/%03d.html" % [section_idx, item_idx]
add_head_section(item, subsection)
item.search("img").each do |img|
img["src"] = "#{Dir.pwd}/#{img['src']}"
end
- item.xpath("//li/p").each {|p| p.swap(p.children); p.remove}
- File.open(item_path, "w") {|f| f.puts item.to_html}
+ item.xpath("//li/p").each { |p| p.swap(p.children); p.remove }
+ File.open(item_path, "w") { |f| f.puts item.to_html }
end
end
end
@@ -101,7 +97,7 @@ module Kindle
"mobi_outfile" => mobi_outfile
}
puts document.to_yaml
- File.open("_document.yml", "w"){|f| f.puts document.to_yaml}
+ File.open("_document.yml", "w") { |f| f.puts document.to_yaml }
end
def add_head_section(doc, title)
diff --git a/guides/rails_guides/levenshtein.rb b/guides/rails_guides/levenshtein.rb
index a3e7620444..40c6a5c372 100644
--- a/guides/rails_guides/levenshtein.rb
+++ b/guides/rails_guides/levenshtein.rb
@@ -1,6 +1,8 @@
module RailsGuides
module Levenshtein
- # This code is based directly on the Text gem implementation
+ # This code is based directly on the Text gem implementation.
+ # Copyright (c) 2006-2013 Paul Battley, Michael Neumann, Tim Fletcher.
+ #
# Returns a value representing the "cost" of transforming str1 into str2
def self.distance(str1, str2)
s = str1
@@ -18,12 +20,12 @@ module RailsGuides
str2_codepoint_enumerable = str2.each_codepoint
str1.each_codepoint.with_index do |char1, i|
- e = i+1
+ e = i + 1
str2_codepoint_enumerable.with_index do |char2, j|
cost = (char1 == char2) ? 0 : 1
x = [
- d[j+1] + 1, # insertion
+ d[j + 1] + 1, # insertion
e + 1, # deletion
d[j] + cost # substitution
].min
diff --git a/guides/rails_guides/markdown.rb b/guides/rails_guides/markdown.rb
index 33563d669c..009e5aff99 100644
--- a/guides/rails_guides/markdown.rb
+++ b/guides/rails_guides/markdown.rb
@@ -158,7 +158,7 @@ module RailsGuides
@view.content_for(:header_section) { @header }
@view.content_for(:page_title) { @title }
@view.content_for(:index_section) { @index }
- @view.render(layout: @layout, text: @body)
+ @view.render(layout: @layout, html: @body.html_safe)
end
end
end
diff --git a/guides/rails_guides/markdown/renderer.rb b/guides/rails_guides/markdown/renderer.rb
index 4ff76c519b..deab741023 100644
--- a/guides/rails_guides/markdown/renderer.rb
+++ b/guides/rails_guides/markdown/renderer.rb
@@ -1,7 +1,7 @@
module RailsGuides
class Markdown
class Renderer < Redcarpet::Render::HTML
- def initialize(options={})
+ def initialize(options = {})
super
end
@@ -52,7 +52,7 @@ HTML
"ruby; html-script: true"
when "html"
"xml" # HTML is understood, but there are .xml rules in the CSS
- else
+ else
"plain"
end
end
@@ -67,14 +67,15 @@ HTML
# as a list item, but as a paragraph starting with a plain
# asterisk.
body.gsub(/^(TIP|IMPORTANT|CAUTION|WARNING|NOTE|INFO|TODO)[.:](.*?)(\n(?=\n)|\Z)/m) do
- css_class = case $1
- when "CAUTION", "IMPORTANT"
- "warning"
- when "TIP"
- "info"
- else
- $1.downcase
- end
+ css_class = \
+ case $1
+ when "CAUTION", "IMPORTANT"
+ "warning"
+ when "TIP"
+ "info"
+ else
+ $1.downcase
+ end
%(<div class="#{css_class}"><p>#{$2.strip}</p></div>)
end
end
diff --git a/guides/source/2_2_release_notes.md b/guides/source/2_2_release_notes.md
index c6bac34d18..ac5833e069 100644
--- a/guides/source/2_2_release_notes.md
+++ b/guides/source/2_2_release_notes.md
@@ -45,7 +45,6 @@ The internal documentation of Rails, in the form of code comments, has been impr
* [A Guide to Testing Rails Applications](testing.html)
* [Securing Rails Applications](security.html)
* [Debugging Rails Applications](debugging_rails_applications.html)
-* [Performance Testing Rails Applications](performance_testing.html)
* [The Basics of Creating Rails Plugins](plugins.html)
All told, the Guides provide tens of thousands of words of guidance for beginning and intermediate Rails developers.
diff --git a/guides/source/3_1_release_notes.md b/guides/source/3_1_release_notes.md
index feee0f9920..fd90cf9886 100644
--- a/guides/source/3_1_release_notes.md
+++ b/guides/source/3_1_release_notes.md
@@ -99,7 +99,7 @@ gem 'jquery-rails'
# config.assets.manifest = YOUR_PATH
# Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
- # config.assets.precompile `= %w( search.js )
+ # config.assets.precompile `= %w( admin.js admin.css )
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md
index 3710247582..39753cbd6f 100644
--- a/guides/source/5_0_release_notes.md
+++ b/guides/source/5_0_release_notes.md
@@ -125,7 +125,7 @@ store_listing.price_in_cents # => 10
StoreListing.new.my_string # => "new default"
StoreListing.new.my_default_proc # => 2015-05-30 11:04:48 -0600
model = StoreListing.new(field_without_db_column: ["1", "2", "3"])
-model.attributes #=> {field_without_db_column: [1, 2, 3]}
+model.attributes # => {field_without_db_column: [1, 2, 3]}
```
**Creating Custom Types:**
@@ -171,7 +171,7 @@ instead of waiting for the suite to complete.
- Complete exception backtrace output using `-b` option.
- Integration with `Minitest` to allow options like `-s` for test seed data,
`-n` for running specific test by name, `-v` for better verbose output and so forth.
-- Colored test output
+- Colored test output.
Railties
--------
@@ -212,7 +212,7 @@ Please refer to the [Changelog][railties] for detailed changes.
([Pull Request](https://github.com/rails/rails/pull/22173))
* Deprecated the tasks in the `rails` task namespace in favor of the `app` namespace.
- (e.g. `rails:update` and `rails:template` tasks is renamed to `app:update` and `app:template`.)
+ (e.g. `rails:update` and `rails:template` tasks are renamed to `app:update` and `app:template`.)
([Pull Request](https://github.com/rails/rails/pull/23439))
### Notable changes
@@ -417,7 +417,7 @@ Please refer to the [Changelog][action-pack] for detailed changes.
`ActionDispatch::IntegrationTest` instead.
([commit](https://github.com/rails/rails/commit/4414c5d1795e815b102571425974a8b1d46d932d))
-* Rails will only generate "weak", instead of strong ETags.
+* Rails generates weak ETags by default.
([Pull Request](https://github.com/rails/rails/pull/17573))
* Controller actions without an explicit `render` call and with no
@@ -432,11 +432,6 @@ Please refer to the [Changelog][action-pack] for detailed changes.
* Added request encoding and response parsing to integration tests.
([Pull Request](https://github.com/rails/rails/pull/21671))
-* Update default rendering policies when the controller action did
- not explicitly indicate a response.
- ([Pull Request](https://github.com/rails/rails/pull/23827))
-
-
* Add `ActionController#helpers` to get access to the view context
at the controller level.
([Pull Request](https://github.com/rails/rails/pull/24866))
@@ -458,6 +453,9 @@ Please refer to the [Changelog][action-pack] for detailed changes.
`ActionController::Live`.
([More details in this issue](https://github.com/rails/rails/issues/25581))
+* Introduce `Response#strong_etag=` and `#weak_etag=` and analogous
+ options for `fresh_when` and `stale?`.
+ ([Pull Request](https://github.com/rails/rails/pull/24387))
Action View
-------------
@@ -587,8 +585,8 @@ Please refer to the [Changelog][active-record] for detailed changes.
gem. ([Pull Request](https://github.com/rails/rails/pull/21161))
* Removed support for the legacy `mysql` database adapter from core. Most users should
- be able to use `mysql2`. It will be converted to a separate gem when when we find someone
- to maintain it. ([Pull Request 1](https://github.com/rails/rails/pull/22642)],
+ be able to use `mysql2`. It will be converted to a separate gem when we find someone
+ to maintain it. ([Pull Request 1](https://github.com/rails/rails/pull/22642),
[Pull Request 2](https://github.com/rails/rails/pull/22715))
* Removed support for the `protected_attributes` gem.
@@ -600,6 +598,9 @@ Please refer to the [Changelog][active-record] for detailed changes.
* Removed support for `activerecord-deprecated_finders` gem.
([commit](https://github.com/rails/rails/commit/78dab2a8569408658542e462a957ea5a35aa4679))
+* Removed `ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES` constant.
+ ([commit](https://github.com/rails/rails/commit/a502703c3d2151d4d3b421b29fefdac5ad05df61))
+
### Deprecations
* Deprecated passing a class as a value in a query. Users should pass strings
@@ -703,9 +704,6 @@ Please refer to the [Changelog][active-record] for detailed changes.
operator to combine WHERE or HAVING clauses.
([commit](https://github.com/rails/rails/commit/b0b37942d729b6bdcd2e3178eda7fa1de203b3d0))
-* Added `:time` option added for `#touch`.
- ([Pull Request](https://github.com/rails/rails/pull/18956))
-
* Added `ActiveRecord::Base.suppress` to prevent the receiver from being saved
during the given block.
([Pull Request](https://github.com/rails/rails/pull/18910))
@@ -805,6 +803,15 @@ Please refer to the [Changelog][active-record] for detailed changes.
than the current time.
([Pull Request](https://github.com/rails/rails/pull/18956))
+* Change transaction callbacks to not swallow errors.
+ Before this change any errors raised inside a transaction callback
+ were getting rescued and printed in the logs, unless you used
+ the (newly deprecated) `raise_in_transactional_callbacks = true` option.
+
+ Now these errors are not rescued anymore and just bubble up, matching the
+ behavior of other callbacks.
+ ([commit](https://github.com/rails/rails/commit/07d3d402341e81ada0214f2cb2be1da69eadfe72))
+
Active Model
------------
@@ -962,8 +969,10 @@ Please refer to the [Changelog][active-support] for detailed changes.
`ActiveSupport::Cache::MemCachedStore#escape_key`, and
`ActiveSupport::Cache::FileStore#key_file_path`.
Use `normalize_key` instead.
+ ([Pull Request](https://github.com/rails/rails/pull/22215),
+ [commit](https://github.com/rails/rails/commit/a8f773b0))
- Deprecated `ActiveSupport::Cache::LocaleCache#set_cache_value` in favor of `write_cache_value`.
+* Deprecated `ActiveSupport::Cache::LocaleCache#set_cache_value` in favor of `write_cache_value`.
([Pull Request](https://github.com/rails/rails/pull/22215))
* Deprecated passing arguments to `assert_nothing_raised`.
@@ -994,7 +1003,8 @@ Please refer to the [Changelog][active-support] for detailed changes.
* Added `#on_weekend?`, `#on_weekday?`, `#next_weekday`, `#prev_weekday` methods to `Date`,
`Time`, and `DateTime`.
- ([Pull Request](https://github.com/rails/rails/pull/18335))
+ ([Pull Request](https://github.com/rails/rails/pull/18335),
+ [Pull Request](https://github.com/rails/rails/pull/23687))
* Added `same_time` option to `#next_week` and `#prev_week` for `Date`, `Time`,
and `DateTime`.
@@ -1002,7 +1012,7 @@ Please refer to the [Changelog][active-support] for detailed changes.
* Added `#prev_day` and `#next_day` counterparts to `#yesterday` and
`#tomorrow` for `Date`, `Time`, and `DateTime`.
- ([Pull Request](httpshttps://github.com/rails/rails/pull/18335))
+ ([Pull Request](https://github.com/rails/rails/pull/18335))
* Added `SecureRandom.base58` for generation of random base58 strings.
([commit](https://github.com/rails/rails/commit/b1093977110f18ae0cafe56c3d99fc22a7d54d1b))
@@ -1045,9 +1055,6 @@ Please refer to the [Changelog][active-support] for detailed changes.
* Added `Array#second_to_last` and `Array#third_to_last` methods.
([Pull Request](https://github.com/rails/rails/pull/23583))
-* Added `#on_weekday?` method to `Date`, `Time`, and `DateTime`.
- ([Pull Request](https://github.com/rails/rails/pull/23687))
-
* Publish `ActiveSupport::Executor` and `ActiveSupport::Reloader` APIs to allow
components and libraries to manage, and participate in, the execution of
application code, and the application reloading process.
diff --git a/guides/source/action_cable_overview.md b/guides/source/action_cable_overview.md
index 118b0b52b2..319277ef68 100644
--- a/guides/source/action_cable_overview.md
+++ b/guides/source/action_cable_overview.md
@@ -62,7 +62,7 @@ module ApplicationCable
self.current_user = find_verified_user
end
- protected
+ private
def find_verified_user
if current_user = User.find_by(id: cookies.signed[:user_id])
current_user
@@ -240,9 +240,9 @@ WebNotificationsChannel.broadcast_to(
```
The `WebNotificationsChannel.broadcast_to` call places a message in the current
-subscription adapter (Redis by default)'s pubsub queue under a separate
-broadcasting name for each user. For a user with an ID of 1, the broadcasting
-name would be `web_notifications:1`.
+subscription adapter (by default `redis` for production and `async` for development and
+test environments)'s pubsub queue under a separate broadcasting name for each user.
+For a user with an ID of 1, the broadcasting name would be `web_notifications:1`.
The channel has been instructed to stream everything that arrives at
`web_notifications:1` directly to the client by invoking the `received`
@@ -422,7 +422,7 @@ App.cable.subscriptions.create "AppearanceChannel",
buttonSelector = "[data-behavior~=appear_away]"
install: ->
- $(document).on "page:change.appearance", =>
+ $(document).on "turbolinks:load.appearance", =>
@appear()
$(document).on "click.appearance", buttonSelector, =>
@@ -510,10 +510,10 @@ WebNotificationsChannel.broadcast_to(
The `WebNotificationsChannel.broadcast_to` call places a message in the current
subscription adapter's pubsub queue under a separate broadcasting name for each
user. For a user with an ID of 1, the broadcasting name would be
-"web_notifications_1".
+`web_notifications:1`.
The channel has been instructed to stream everything that arrives at
-"web_notifications_1" directly to the client by invoking the `received`
+`web_notifications:1` directly to the client by invoking the `received`
callback. The data passed as argument is the hash sent as the second parameter
to the server-side broadcast call, JSON encoded for the trip across the wire,
and unpacked for the data argument arriving to `received`.
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index 7b1138c7d4..40eb838d32 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -258,6 +258,17 @@ scalar values, map the key to an empty array:
params.permit(id: [])
```
+Sometimes it is not possible or convenient to declare the valid keys of
+a hash parameter or its internal structure. Just map to an empty hash:
+
+```ruby
+params.permit(preferences: {})
+```
+
+but be careful because this opens the door to arbitrary input. In this
+case, `permit` ensures values in the returned structure are permitted
+scalars and filters out anything else.
+
To whitelist an entire hash of parameters, the `permit!` method can be
used:
@@ -265,9 +276,10 @@ used:
params.require(:log_entry).permit!
```
-This will mark the `:log_entry` parameters hash and any sub-hash of it as
-permitted. Extreme care should be taken when using `permit!`, as it
-will allow all current and future model attributes to be mass-assigned.
+This marks the `:log_entry` parameters hash and any sub-hash of it as
+permitted and does not check for permitted scalars, anything is accepted.
+Extreme care should be taken when using `permit!`, as it will allow all current
+and future model attributes to be mass-assigned.
#### Nested Parameters
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index 34847832fd..0825d54cb7 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -400,7 +400,7 @@ class UserMailer < ApplicationMailer
mail(to: @user.email,
subject: 'Welcome to My Awesome Site') do |format|
format.html { render 'another_template' }
- format.text { render text: 'Render text' }
+ format.text { render plain: 'Render text' }
end
end
end
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index e11466e79f..c835adeab6 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -254,12 +254,6 @@ as if we had written:
<%= render partial: "product", locals: { product: @product } %>
```
-With the `as` option we can specify a different name for the local variable. For example, if we wanted it to be `item` instead of `product` we would do:
-
-```erb
-<%= render partial: "product", as: "item" %>
-```
-
The `object` option can be used to directly specify which object is rendered into the partial; useful when the template's object is elsewhere (e.g. in a different instance variable or in a local variable).
For example, instead of:
@@ -274,12 +268,18 @@ we would do:
<%= render partial: "product", object: @item %>
```
-The `object` and `as` options can also be used together:
+With the `as` option we can specify a different name for the said local variable. For example, if we wanted it to be `item` instead of `product` we would do:
```erb
<%= render partial: "product", object: @item, as: "item" %>
```
+This is equivalent to
+
+```erb
+<%= render partial: "product", locals: { item: @item } %>
+```
+
#### Rendering Collections
It is very common that a template will need to iterate over a collection and render a sub-template for each of the elements. This pattern has been implemented as a single method that accepts an array and renders a partial for each one of the elements in the array.
@@ -1493,7 +1493,7 @@ strip_links('Blog: <a href="http://myblog.com/">Visit</a>.')
#### strip_tags(html)
Strips all HTML tags from the html, including comments.
-This uses the html-scanner tokenizer and so its HTML parsing ability is limited by that of html-scanner.
+This functionality is powered by the rails-html-sanitizer gem.
```ruby
strip_tags("Strip <i>these</i> tags!")
diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md
index c9f70dc87b..c65d1e6de5 100644
--- a/guides/source/active_job_basics.md
+++ b/guides/source/active_job_basics.md
@@ -34,8 +34,9 @@ Delayed Job and Resque. Picking your queuing backend becomes more of an operatio
concern, then. And you'll be able to switch between them without having to rewrite
your jobs.
-NOTE: Rails by default comes with an "immediate runner" queuing implementation.
-That means that each job that has been enqueued will run immediately.
+NOTE: Rails by default comes with an asynchronous queuing implementation that
+runs jobs with an in-process thread pool. Jobs will run asynchronously, but any
+jobs in the queue will be dropped upon restart.
Creating a Job
@@ -109,7 +110,7 @@ That's it!
Job Execution
-------------
-For enqueuing and executing jobs in production you need to set up a queuing backend,
+For enqueuing and executing jobs in production you need to set up a queuing backend,
that is to say you need to decide for a 3rd-party queuing library that Rails should use.
Rails itself only provides an in-process queuing system, which only keeps the jobs in RAM.
If the process crashes or the machine is reset, then all outstanding jobs are lost with the
diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md
index a7975c7772..868daf2435 100644
--- a/guides/source/active_record_callbacks.md
+++ b/guides/source/active_record_callbacks.md
@@ -36,7 +36,7 @@ class User < ApplicationRecord
before_validation :ensure_login_has_a_value
- protected
+ private
def ensure_login_has_a_value
if login.nil?
self.login = email unless email.blank?
@@ -66,7 +66,7 @@ class User < ApplicationRecord
# :on takes an array as well
after_validation :set_location, on: [ :create, :update ]
- protected
+ private
def normalize_name
self.name = name.downcase.titleize
end
@@ -77,7 +77,7 @@ class User < ApplicationRecord
end
```
-It is considered good practice to declare callback methods as protected or private. If left public, they can be called from outside of the model and violate the principle of object encapsulation.
+It is considered good practice to declare callback methods as private. If left public, they can be called from outside of the model and violate the principle of object encapsulation.
Available Callbacks
-------------------
@@ -431,4 +431,4 @@ class PictureFile < ApplicationRecord
end
```
-WARNING. The `after_commit` and `after_rollback` callbacks are guaranteed to be called for all models created, updated, or destroyed within a transaction block. If any exceptions are raised within one of these callbacks, they will be ignored so that they don't interfere with the other callbacks. As such, if your callback code could raise an exception, you'll need to rescue it and handle it appropriately within the callback.
+WARNING. The `after_commit` and `after_rollback` callbacks are called for all models created, updated, or destroyed within a transaction block. However, if an exception is raised within one of these callbacks, the exception will bubble up and any remaining `after_commit` or `after_rollback` methods will _not_ be executed. As such, if your callback code could raise an exception, you'll need to rescue it and handle it within the callback in order to allow other callbacks to run.
diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md
index a45becf670..6e7e29ed60 100644
--- a/guides/source/active_record_migrations.md
+++ b/guides/source/active_record_migrations.md
@@ -229,7 +229,7 @@ As always, what has been generated for you is just a starting point. You can add
or remove from it as you see fit by editing the
`db/migrate/YYYYMMDDHHMMSS_add_details_to_products.rb` file.
-Also, the generator accepts column type as `references`(also available as
+Also, the generator accepts column type as `references` (also available as
`belongs_to`). For instance:
```bash
@@ -467,6 +467,8 @@ the first time (i.e. on the date the migration is applied).
Some adapters may support additional options; see the adapter specific API docs
for further information.
+NOTE: `null` and `default` cannot be specified via command line.
+
### Foreign Keys
While it's not required you might want to add foreign key constraints to
@@ -956,10 +958,10 @@ ActiveRecord::Schema.define(version: 20080906171750) do
create_table "products", force: true do |t|
t.string "name"
- t.text "description"
+ t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
- t.string "part_number"
+ t.string "part_number"
end
end
```
@@ -1018,10 +1020,10 @@ such features, the `execute` method can be used to execute arbitrary SQL.
Migrations and Seed Data
------------------------
-The main purpose of Rails' migration feature is to issue commands that modify the
-schema using a consistent process. Migrations can also be used
-to add or modify data. This is useful in an existing database that can't be destroyed
-and recreated, such as a production database.
+The main purpose of Rails' migration feature is to issue commands that modify the
+schema using a consistent process. Migrations can also be used
+to add or modify data. This is useful in an existing database that can't be destroyed
+and recreated, such as a production database.
```ruby
class AddInitialProducts < ActiveRecord::Migration[5.0]
@@ -1037,10 +1039,10 @@ class AddInitialProducts < ActiveRecord::Migration[5.0]
end
```
-To add initial data after a database is created, Rails has a built-in
-'seeds' feature that makes the process quick and easy. This is especially
-useful when reloading the database frequently in development and test environments.
-It's easy to get started with this feature: just fill up `db/seeds.rb` with some
+To add initial data after a database is created, Rails has a built-in
+'seeds' feature that makes the process quick and easy. This is especially
+useful when reloading the database frequently in development and test environments.
+It's easy to get started with this feature: just fill up `db/seeds.rb` with some
Ruby code, and run `rails db:seed`:
```ruby
diff --git a/guides/source/active_record_postgresql.md b/guides/source/active_record_postgresql.md
index d7e35490ef..6d07291b07 100644
--- a/guides/source/active_record_postgresql.md
+++ b/guides/source/active_record_postgresql.md
@@ -111,7 +111,7 @@ profile.settings = {"color" => "yellow", "resolution" => "1280x1024"}
profile.save!
Profile.where("settings->'color' = ?", "yellow")
-#=> #<ActiveRecord::Relation [#<Profile id: 1, settings: {"color"=>"yellow", "resolution"=>"1280x1024"}>]>
+# => #<ActiveRecord::Relation [#<Profile id: 1, settings: {"color"=>"yellow", "resolution"=>"1280x1024"}>]>
```
### JSON
@@ -422,7 +422,7 @@ device = Device.create
device.id # => "814865cd-5a1d-4771-9306-4268f188fe9e"
```
-NOTE: `uuid_generate_v4()` (from `uuid-ossp`) is assumed if no `:default` option was
+NOTE: `gen_random_uuid()` (from `pgcrypto`) is assumed if no `:default` option was
passed to `create_table`.
Full Text Search
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 6f941d0e4e..31220f9be2 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -50,7 +50,7 @@ class Role < ApplicationRecord
end
```
-Active Record will perform queries on the database for you and is compatible with most database systems, including MySQL, MariaDB, PostgreSQL and SQLite. Regardless of which database system you're using, the Active Record method format will always be the same.
+Active Record will perform queries on the database for you and is compatible with most database systems, including MySQL, MariaDB, PostgreSQL, and SQLite. Regardless of which database system you're using, the Active Record method format will always be the same.
Retrieving Objects from the Database
------------------------------------
@@ -81,10 +81,9 @@ The methods are:
* `reorder`
* `reverse_order`
* `select`
-* `distinct`
* `where`
-All of the above methods return an instance of `ActiveRecord::Relation`.
+Finder methods that return a collection, such as `where` and `group`, return an instance of `ActiveRecord::Relation`. Methods that find a single entity, such as `find` and `first`, return a single instance of the model.
The primary operation of `Model.find(options)` can be summarized as:
@@ -954,6 +953,9 @@ class Client < ApplicationRecord
end
```
+NOTE: Please note that the optimistic locking will be ignored if you update the
+locking column's value.
+
### Pessimistic Locking
Pessimistic locking uses a locking mechanism provided by the underlying database. Using `lock` when building a relation obtains an exclusive lock on the selected rows. Relations using `lock` are usually wrapped inside a transaction for preventing deadlock conditions.
@@ -1014,13 +1016,13 @@ There are multiple ways to use the `joins` method.
You can just supply the raw SQL specifying the `JOIN` clause to `joins`:
```ruby
-Author.joins("INNER JOIN posts ON posts.author_id = author.id AND posts.published = 't'")
+Author.joins("INNER JOIN posts ON posts.author_id = authors.id AND posts.published = 't'")
```
This will result in the following SQL:
```sql
-SELECT clients.* FROM clients INNER JOIN posts ON posts.author_id = author.id AND posts.published = 't'
+SELECT authors.* FROM authors INNER JOIN posts ON posts.author_id = authors.id AND posts.published = 't'
```
#### Using Array/Hash of Named Associations
@@ -1251,9 +1253,9 @@ articles, all the articles would still be loaded. By using `joins` (an INNER
JOIN), the join conditions **must** match, otherwise no records will be
returned.
-NOTE: If an association is eager loaded as part of a join, any fields from a custom select clause will not present be on the loaded models.
+NOTE: If an association is eager loaded as part of a join, any fields from a custom select clause will not present be on the loaded models.
This is because it is ambiguous whether they should appear on the parent record, or the child.
-
+
Scopes
------
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index aba4c6a97b..67bed4c8da 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -135,36 +135,53 @@ NOTE: Defined in `active_support/core_ext/object/blank.rb`.
### `duplicable?`
-A few fundamental objects in Ruby are singletons. For example, in the whole life of a program the integer 1 refers always to the same instance:
+In Ruby 2.4 most objects can be duplicated via `dup` or `clone` except
+methods and certain numbers. Though Ruby 2.2 and 2.3 can't duplicate `nil`,
+`false`, `true`, and symbols as well as instances `Float`, `Fixnum`,
+and `Bignum` instances.
```ruby
-1.object_id # => 3
-Math.cos(0).to_i.object_id # => 3
+"foo".dup # => "foo"
+"".dup # => ""
+1.method(:+).dup # => TypeError: allocator undefined for Method
+Complex(0).dup # => TypeError: can't copy Complex
```
-Hence, there's no way these objects can be duplicated through `dup` or `clone`:
+Active Support provides `duplicable?` to query an object about this:
```ruby
-true.dup # => TypeError: can't dup TrueClass
+"foo".duplicable? # => true
+"".duplicable? # => true
+Rational(1).duplicable? # => false
+Complex(1).duplicable? # => false
+1.method(:+).duplicable? # => false
```
-Some numbers which are not singletons are not duplicable either:
+`duplicable?` matches Ruby's `dup` according to the Ruby version.
+
+So in 2.4:
```ruby
-0.0.clone # => allocator undefined for Float
-(2**1024).clone # => allocator undefined for Bignum
+nil.dup # => nil
+:my_symbol.dup # => :my_symbol
+1.dup # => 1
+
+nil.duplicable? # => true
+:my_symbol.duplicable? # => true
+1.duplicable? # => true
```
-Active Support provides `duplicable?` to programmatically query an object about this property:
+Whereas in 2.2 and 2.3:
```ruby
-"foo".duplicable? # => true
-"".duplicable? # => true
-0.0.duplicable? # => false
-false.duplicable? # => false
-```
+nil.dup # => TypeError: can't dup NilClass
+:my_symbol.dup # => TypeError: can't dup Symbol
+1.dup # => TypeError: can't dup Fixnum
-By definition all objects are `duplicable?` except `nil`, `false`, `true`, symbols, numbers, class, module, and method objects.
+nil.duplicable? # => false
+:my_symbol.duplicable? # => false
+1.duplicable? # => false
+```
WARNING: Any class can disallow duplication by removing `dup` and `clone` or raising exceptions from them. Thus only `rescue` can tell whether a given arbitrary object is duplicable. `duplicable?` depends on the hard-coded list above, but it is much faster than `rescue`. Use it only if you know the hard-coded list is enough in your use case.
@@ -511,56 +528,6 @@ NOTE: Defined in `active_support/core_ext/object/inclusion.rb`.
Extensions to `Module`
----------------------
-### `alias_method_chain`
-
-**This method is deprecated in favour of using Module#prepend.**
-
-Using plain Ruby you can wrap methods with other methods, that's called _alias chaining_.
-
-For example, let's say you'd like params to be strings in functional tests, as they are in real requests, but still want the convenience of assigning integers and other kind of values. To accomplish that you could wrap `ActionDispatch::IntegrationTest#process` this way in `test/test_helper.rb`:
-
-```ruby
-ActionDispatch::IntegrationTest.class_eval do
- # save a reference to the original process method
- alias_method :original_process, :process
-
- # now redefine process and delegate to original_process
- def process('GET', path, params: nil, headers: nil, env: nil, xhr: false)
- params = Hash[*params.map {|k, v| [k, v.to_s]}.flatten]
- original_process('GET', path, params: params)
- end
-end
-```
-
-That's the method `get`, `post`, etc., delegate the work to.
-
-That technique has a risk, it could be the case that `:original_process` was taken. To try to avoid collisions people choose some label that characterizes what the chaining is about:
-
-```ruby
-ActionDispatch::IntegrationTest.class_eval do
- def process_with_stringified_params(...)
- params = Hash[*params.map {|k, v| [k, v.to_s]}.flatten]
- process_without_stringified_params(method, path, params: params)
- end
- alias_method :process_without_stringified_params, :process
- alias_method :process, :process_with_stringified_params
-end
-```
-
-The method `alias_method_chain` provides a shortcut for that pattern:
-
-```ruby
-ActionDispatch::IntegrationTest.class_eval do
- def process_with_stringified_params(...)
- params = Hash[*params.map {|k, v| [k, v.to_s]}.flatten]
- process_without_stringified_params(method, path, params: params)
- end
- alias_method_chain :process, :stringified_params
-end
-```
-
-NOTE: Defined in `active_support/core_ext/module/aliasing.rb`.
-
### Attributes
#### `alias_attribute`
@@ -2036,7 +2003,7 @@ Addition only assumes the elements respond to `+`:
```ruby
[[1, 2], [2, 3], [3, 4]].sum # => [1, 2, 2, 3, 3, 4]
%w(foo bar baz).sum # => "foobarbaz"
-{a: 1, b: 2, c: 3}.sum # => [:b, 2, :c, 3, :a, 1]
+{a: 1, b: 2, c: 3}.sum # => [:b, 2, :c, 3, :a, 1]
```
The sum of an empty collection is zero by default, but this is customizable:
@@ -2661,7 +2628,7 @@ The method `transform_keys` accepts a block and returns a hash that has applied
```ruby
{nil => nil, 1 => 1, a: :a}.transform_keys { |key| key.to_s.upcase }
-# => {"" => nil, "A" => :a, "1" => 1}
+# => {"" => nil, "1" => 1, "A" => :a}
```
In case of key collision, one of the values will be chosen. The chosen value may not always be the same given the same hash:
@@ -2703,7 +2670,7 @@ The method `stringify_keys` returns a hash that has a stringified version of the
```ruby
{nil => nil, 1 => 1, a: :a}.stringify_keys
-# => {"" => nil, "a" => :a, "1" => 1}
+# => {"" => nil, "1" => 1, "a" => :a}
```
In case of key collision, one of the values will be chosen. The chosen value may not always be the same given the same hash:
@@ -2745,7 +2712,7 @@ The method `symbolize_keys` returns a hash that has a symbolized version of the
```ruby
{nil => nil, 1 => 1, "a" => "a"}.symbolize_keys
-# => {1=>1, nil=>nil, :a=>"a"}
+# => {nil=>nil, 1=>1, :a=>"a"}
```
WARNING. Note in the previous example only one key was symbolized.
@@ -2822,7 +2789,7 @@ Ruby has built-in support for taking slices out of strings and arrays. Active Su
```ruby
{a: 1, b: 2, c: 3}.slice(:a, :c)
-# => {:c=>3, :a=>1}
+# => {:a=>1, :c=>3}
{a: 1, b: 2, c: 3}.slice(:b, :X)
# => {:b=>2} # non-existing keys are ignored
diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md
index 03af3cf819..03c9183eb3 100644
--- a/guides/source/active_support_instrumentation.md
+++ b/guides/source/active_support_instrumentation.md
@@ -226,17 +226,36 @@ Action View
}
```
+### render_collection.action_view
+
+| Key | Value |
+| ------------- | ------------------------------------- |
+| `:identifier` | Full path to template |
+| `:count` | Size of collection |
+| `:cache_hits` | Number of partials fetched from cache |
+
+`:cache_hits` is only included if the collection is rendered with `cached: true`.
+
+```ruby
+{
+ identifier: "/Users/adam/projects/notifications/app/views/posts/_post.html.erb",
+ count: 3,
+ cache_hits: 0
+}
+```
+
Active Record
------------
### sql.active_record
-| Key | Value |
-| ---------------- | --------------------- |
-| `:sql` | SQL statement |
-| `:name` | Name of the operation |
-| `:connection_id` | `self.object_id` |
-| `:binds` | Bind parameters |
+| Key | Value |
+| ---------------- | ---------------------------------------- |
+| `:sql` | SQL statement |
+| `:name` | Name of the operation |
+| `:connection_id` | `self.object_id` |
+| `:binds` | Bind parameters |
+| `:cached` | `true` is added when cached queries used |
INFO. The adapters will add their own data as well.
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index e6631a513c..25717e04e4 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -485,7 +485,7 @@ which contains these lines:
Rails creates both `app/assets/javascripts/application.js` and
`app/assets/stylesheets/application.css` regardless of whether the
---skip-sprockets option is used when creating a new rails application. This is
+--skip-sprockets option is used when creating a new Rails application. This is
so you can easily add asset pipelining later if you like.
The directives that work in JavaScript files also work in stylesheets
@@ -586,6 +586,19 @@ 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
+when an asset lookup is performed and nothing is found. If you turn off "asset fallback"
+then an error will be raised when an asset cannot be found.
+
+```ruby
+config.assets.unknown_asset_fallback = false
+```
+
+If "asset fallback" is enabled then when an asset cannot be found the path will be
+output instead and no error raised. The asset fallback behavior is enabled by default.
+
### Turning Digests Off
You can turn off digests by updating `config/environments/development.rb` to
@@ -724,7 +737,7 @@ If you have other manifests or individual stylesheets and JavaScript files to
include, you can add them to the `precompile` array in `config/initializers/assets.rb`:
```ruby
-Rails.application.config.assets.precompile += ['admin.js', 'admin.css', 'swfObject.js']
+Rails.application.config.assets.precompile += %w( admin.js admin.css )
```
NOTE. Always specify an expected compiled filename that ends with .js or .css,
@@ -1024,7 +1037,7 @@ to tell our CDN (and browser) that the asset is "public", that means any cache
can store the request. Also we commonly want to set `max-age` which is how long
the cache will store the object before invalidating the cache. The `max-age`
value is set to seconds with a maximum possible value of `31536000` which is one
-year. You can do this in your rails application by setting
+year. You can do this in your Rails application by setting
```
config.public_file_server.headers = {
@@ -1109,9 +1122,9 @@ Windows you have a JavaScript runtime installed in your operating system.
### Serving GZipped version of assets
-By default, gzipped version of compiled assets will be generated, along
-with the non-gzipped version of assets. Gzipped assets help reduce the transmission of
-data over the wire. You can configure this by setting the `gzip` flag.
+By default, gzipped version of compiled assets will be generated, along with
+the non-gzipped version of assets. Gzipped assets help reduce the transmission
+of data over the wire. You can configure this by setting the `gzip` flag.
```ruby
config.assets.gzip = false # disable gzipped assets generation
@@ -1214,35 +1227,25 @@ Sprockets.
Making Your Library or Gem a Pre-Processor
------------------------------------------
-As Sprockets uses [Tilt](https://github.com/rtomayko/tilt) as a generic
-interface to different templating engines, your gem should just implement the
-Tilt template protocol. Normally, you would subclass `Tilt::Template` and
-reimplement the `prepare` method, which initializes your template, and the
-`evaluate` method, which returns the processed source. The original source is
-stored in `data`. Have a look at
-[`Tilt::Template`](https://github.com/rtomayko/tilt/blob/master/lib/tilt/template.rb)
-sources to learn more.
+Sprockets uses Processors, Transformers, Compressors, and Exporters to extend
+Sprockets functionality. Have a look at
+[Extending Sprockets](https://github.com/rails/sprockets/blob/master/guides/extending_sprockets.md)
+to learn more. Here we registered a preprocessor to add a comment to the end
+of text/css (.css) files.
```ruby
-module BangBang
- class Template < ::Tilt::Template
- def prepare
- # Do any initialization here
- end
-
- # Adds a "!" to original template.
- def evaluate(scope, locals, &block)
- "#{data}!"
- end
+module AddComment
+ def self.call(input)
+ { data: input[:data] + "/* Hello From my sprockets extension */" }
end
end
```
-Now that you have a `Template` class, it's time to associate it with an
-extension for template files:
+Now that you have a module that modifies the input data, it's time to register
+it as a preprocessor for your mime type.
```ruby
-Sprockets.register_engine '.bang', BangBang::Template
+Sprockets.register_preprocessor 'text/css', AddComment
```
Upgrading from Old Versions of Rails
@@ -1291,7 +1294,7 @@ config.assets.digest = true
# Precompile additional assets (application.js, application.css, and all
# non-JS/CSS are already added)
-# config.assets.precompile += %w( search.js )
+# config.assets.precompile += %w( admin.js admin.css )
```
Rails 4 and above no longer set default config values for Sprockets in `test.rb`, so
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index 3993fdb1dd..6e68935f9b 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -387,7 +387,7 @@ The corresponding migration might look like this:
class CreateSuppliers < ActiveRecord::Migration[5.0]
def change
create_table :suppliers do |t|
- t.string :name
+ t.string :name
t.timestamps
end
@@ -550,8 +550,8 @@ But what if you want to reload the cache, because data might have been changed b
```ruby
author.books # retrieves books from the database
author.books.size # uses the cached copy of books
-author.books.reload.empty? # discards the cached copy of books
- # and goes back to the database
+author.books.reload.empty? # discards the cached copy of books
+ # and goes back to the database
```
### Avoiding Name Collisions
@@ -709,55 +709,73 @@ class Book < ApplicationRecord
end
```
-By default, Active Record doesn't know about the connection between these associations. This can lead to two copies of an object getting out of sync:
+Active Record will attempt to automatically identify that these two models share a bi-directional association based on the association name. In this way, Active Record will only load one copy of the `Author` object, making your application more efficient and preventing inconsistent data:
```ruby
a = Author.first
b = a.books.first
a.first_name == b.author.first_name # => true
-a.first_name = 'Manny'
-a.first_name == b.author.first_name # => false
+a.first_name = 'David'
+a.first_name == b.author.first_name # => true
```
-This happens because `a` and `b.author` are two different in-memory representations of the same data, and neither one is automatically refreshed from changes to the other. Active Record provides the `:inverse_of` option so that you can inform it of these relations:
+Active Record supports automatic identification for most associations with standard names. However, Active Record will not automatically identify bi-directional associations that contain any of the following options:
+
+* `:conditions`
+* `:through`
+* `:polymorphic`
+* `:class_name`
+* `:foreign_key`
+
+For example, consider the following model declarations:
```ruby
class Author < ApplicationRecord
- has_many :books, inverse_of: :author
+ has_many :books
end
class Book < ApplicationRecord
- belongs_to :author, inverse_of: :books
+ belongs_to :writer, class_name: 'Author', foreign_key: 'author_id'
end
```
-With these changes, Active Record will only load one copy of the author object, preventing inconsistencies and making your application more efficient:
+Active Record will no longer automatically recognize the bi-directional association:
```ruby
a = Author.first
b = a.books.first
-a.first_name == b.author.first_name # => true
-a.first_name = 'Manny'
-a.first_name == b.author.first_name # => true
+a.first_name == b.writer.first_name # => true
+a.first_name = 'David'
+a.first_name == b.writer.first_name # => false
```
-There are a few limitations to `inverse_of` support:
+Active Record provides the `:inverse_of` option so you can explicitly declare bi-directional associations:
+
+```ruby
+class Author < ApplicationRecord
+ has_many :books, inverse_of: 'writer'
+end
+
+class Book < ApplicationRecord
+ belongs_to :writer, class_name: 'Author', foreign_key: 'author_id'
+end
+```
+
+By including the `:inverse_of` option in the `has_many` association declaration, Active Record will now recognize the bi-directional association:
+
+```ruby
+a = Author.first
+b = a.books.first
+a.first_name == b.writer.first_name # => true
+a.first_name = 'David'
+a.first_name == b.writer.first_name # => true
+```
+
+There are a few limitations to `:inverse_of` support:
* They do not work with `:through` associations.
* They do not work with `:polymorphic` associations.
* They do not work with `:as` associations.
-* For `belongs_to` associations, `has_many` inverse associations are ignored.
-
-Every association will attempt to automatically find the inverse association
-and set the `:inverse_of` option heuristically (based on the association name).
-Most associations with standard names will be supported. However, associations
-that contain the following options will not have their inverses set
-automatically:
-
-* `:conditions`
-* `:through`
-* `:polymorphic`
-* `:foreign_key`
Detailed Association Reference
------------------------------
@@ -1007,7 +1025,7 @@ class Author < ApplicationRecord
end
```
-In this case, saving or destroying an book will update the timestamp on the associated author. You can also specify a particular timestamp attribute to update:
+In this case, saving or destroying a book will update the timestamp on the associated author. You can also specify a particular timestamp attribute to update:
```ruby
class Book < ApplicationRecord
@@ -1841,7 +1859,7 @@ article = Article.create(name: 'a1')
person.articles << article
person.articles << article
person.articles.inspect # => [#<Article id: 5, name: "a1">, #<Article id: 5, name: "a1">]
-Reading.all.inspect # => [#<Reading id: 12, person_id: 5, article_id: 5>, #<Reading id: 13, person_id: 5, article_id: 5>]
+Reading.all.inspect # => [#<Reading id: 12, person_id: 5, article_id: 5>, #<Reading id: 13, person_id: 5, article_id: 5>]
```
In the above case there are two readings and `person.articles` brings out both of
@@ -1860,7 +1878,7 @@ article = Article.create(name: 'a1')
person.articles << article
person.articles << article
person.articles.inspect # => [#<Article id: 7, name: "a1">]
-Reading.all.inspect # => [#<Reading id: 16, person_id: 7, article_id: 7>, #<Reading id: 17, person_id: 7, article_id: 7>]
+Reading.all.inspect # => [#<Reading id: 16, person_id: 7, article_id: 7>, #<Reading id: 17, person_id: 7, article_id: 7>]
```
In the above case there are still two readings. However `person.articles` shows
@@ -1994,11 +2012,9 @@ The `collection.delete` method removes one or more objects from the collection b
@part.assemblies.delete(@assembly1)
```
-WARNING: This does not trigger callbacks on the join records.
-
##### `collection.destroy(object, ...)`
-The `collection.destroy` method removes one or more objects from the collection by running `destroy` on each record in the join table, including running callbacks. This does not destroy the objects.
+The `collection.destroy` method removes one or more objects from the collection by deleting records in the join table. This does not destroy the objects.
```ruby
@part.assemblies.destroy(@assembly1)
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index a1b0029c47..fd7626250c 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -381,7 +381,7 @@ config.cache_store = :memory_store, { size: 64.megabytes }
```
If you're running multiple Ruby on Rails server processes (which is the case
-if you're using mongrel_cluster or Phusion Passenger), then your Rails server
+if you're using Phusion Passenger or puma clustered mode), then your Rails server
process instances won't be able to share cache data with each other. This cache
store is not appropriate for large application deployments. However, it can
work well for small, low traffic sites with only a couple of server processes,
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index 9d7ecce947..c8d559745e 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -407,8 +407,8 @@ db:fixtures:load Loads fixtures into the ...
db:migrate Migrate the database ...
db:migrate:status Display status of migrations
db:rollback Rolls the schema back to ...
-db:schema:cache:clear Clears a db/schema_cache.dump file
-db:schema:cache:dump Creates a db/schema_cache.dump file
+db:schema:cache:clear Clears a db/schema_cache.yml file
+db:schema:cache:dump Creates a db/schema_cache.yml file
db:schema:dump Creates a db/schema.rb file ...
db:schema:load Loads a schema.rb file ...
db:seed Loads the seed data ...
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 7239105b29..b0334bfe4a 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -71,7 +71,7 @@ These configuration methods are to be called on a `Rails::Railtie` object, such
* `config.beginning_of_week` sets the default beginning of week for the
application. Accepts a valid week day symbol (e.g. `:monday`).
-* `config.cache_store` configures which cache store to use for Rails caching. Options include one of the symbols `:memory_store`, `:file_store`, `:mem_cache_store`, `:null_store`, or an object that implements the cache API. Defaults to `:file_store` if the directory `tmp/cache` exists, and to `:memory_store` otherwise.
+* `config.cache_store` configures which cache store to use for Rails caching. Options include one of the symbols `:memory_store`, `:file_store`, `:mem_cache_store`, `:null_store`, or an object that implements the cache API. Defaults to `:file_store`.
* `config.colorize_logging` specifies whether or not to use ANSI color codes when logging information. Defaults to `true`.
@@ -110,7 +110,7 @@ numbers. By default, Rails filters out passwords by adding `Rails.application.co
* `config.force_ssl` forces all requests to be served over HTTPS by using the `ActionDispatch::SSL` middleware, and sets `config.action_mailer.default_url_options` to be `{ protocol: 'https' }`. This can be configured by setting `config.ssl_options` - see the [ActionDispatch::SSL documentation](http://edgeapi.rubyonrails.org/classes/ActionDispatch/SSL.html) for details.
-* `config.log_formatter` defines the formatter of the Rails logger. This option defaults to an instance of `ActiveSupport::Logger::SimpleFormatter` for all modes except production, where it defaults to `Logger::Formatter`. If you are setting a value for `config.logger` you must manually pass the value of your formatter to your logger before it is wrapped in an `ActiveSupport::TaggedLogging` instance, Rails will not do it for you.
+* `config.log_formatter` defines the formatter of the Rails logger. This option defaults to an instance of `ActiveSupport::Logger::SimpleFormatter` for all modes. If you are setting a value for `config.logger` you must manually pass the value of your formatter to your logger before it is wrapped in an `ActiveSupport::TaggedLogging` instance, Rails will not do it for you.
* `config.log_level` defines the verbosity of the Rails logger. This option
defaults to `:debug` for all environments. The available log levels are: `:debug`,
@@ -131,7 +131,7 @@ defaults to `:debug` for all environments. The available log levels are: `:debug
mylogger = MyLogger.new(STDOUT)
mylogger.formatter = config.log_formatter
- config.logger = ActiveSupport::TaggedLogging.new(mylogger)
+ config.logger = ActiveSupport::TaggedLogging.new(mylogger)
```
* `config.middleware` allows you to configure the application's middleware. This is covered in depth in the [Configuring Middleware](#configuring-middleware) section below.
@@ -169,14 +169,18 @@ pipeline is enabled. It is set to `true` by default.
* `config.assets.precompile` allows you to specify additional assets (other than `application.css` and `application.js`) which are to be precompiled when `rake assets:precompile` is run.
+* `config.assets.unknown_asset_fallback` allows you to modify the behavior of the asset pipeline when an asset is not in the pipeline, if you use sprockets-rails 3.2.0 or newer. Defaults to `true`.
+
* `config.assets.prefix` defines the prefix where assets are served from. Defaults to `/assets`.
* `config.assets.manifest` defines the full path to be used for the asset precompiler's manifest file. Defaults to a file named `manifest-<random>.json` in the `config.assets.prefix` directory within the public folder.
-* `config.assets.digest` enables the use of MD5 fingerprints in asset names. Set to `true` by default.
+* `config.assets.digest` enables the use of SHA256 fingerprints in asset names. Set to `true` by default.
* `config.assets.debug` disables the concatenation and compression of assets. Set to `true` by default in `development.rb`.
+* `config.assets.version` is an option string that is used in SHA256 hash generation. This can be changed to force all files to be recompiled.
+
* `config.assets.compile` is a boolean that can be used to turn on live Sprockets compilation in production.
* `config.assets.logger` accepts a logger conforming to the interface of Log4r or the default Ruby `Logger` class. Defaults to the same configured at `config.logger`. Setting `config.assets.logger` to `false` will turn off served assets logging.
@@ -460,23 +464,23 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
```ruby
config.action_dispatch.rescue_responses = {
- 'ActionController::RoutingError' => :not_found,
- 'AbstractController::ActionNotFound' => :not_found,
- 'ActionController::MethodNotAllowed' => :method_not_allowed,
- 'ActionController::UnknownHttpMethod' => :method_not_allowed,
- 'ActionController::NotImplemented' => :not_implemented,
- 'ActionController::UnknownFormat' => :not_acceptable,
- 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity,
- 'ActionController::InvalidCrossOriginRequest' => :unprocessable_entity,
- 'ActionDispatch::ParamsParser::ParseError' => :bad_request,
- 'ActionController::BadRequest' => :bad_request,
- 'ActionController::ParameterMissing' => :bad_request,
- 'Rack::QueryParser::ParameterTypeError' => :bad_request,
- 'Rack::QueryParser::InvalidParameterError' => :bad_request,
- 'ActiveRecord::RecordNotFound' => :not_found,
- 'ActiveRecord::StaleObjectError' => :conflict,
- 'ActiveRecord::RecordInvalid' => :unprocessable_entity,
- 'ActiveRecord::RecordNotSaved' => :unprocessable_entity
+ 'ActionController::RoutingError' => :not_found,
+ 'AbstractController::ActionNotFound' => :not_found,
+ 'ActionController::MethodNotAllowed' => :method_not_allowed,
+ 'ActionController::UnknownHttpMethod' => :method_not_allowed,
+ 'ActionController::NotImplemented' => :not_implemented,
+ 'ActionController::UnknownFormat' => :not_acceptable,
+ 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity,
+ 'ActionController::InvalidCrossOriginRequest' => :unprocessable_entity,
+ 'ActionDispatch::Http::Parameters::ParseError' => :bad_request,
+ 'ActionController::BadRequest' => :bad_request,
+ 'ActionController::ParameterMissing' => :bad_request,
+ 'Rack::QueryParser::ParameterTypeError' => :bad_request,
+ 'Rack::QueryParser::InvalidParameterError' => :bad_request,
+ 'ActiveRecord::RecordNotFound' => :not_found,
+ 'ActiveRecord::StaleObjectError' => :conflict,
+ 'ActiveRecord::RecordInvalid' => :unprocessable_entity,
+ 'ActiveRecord::RecordNotSaved' => :unprocessable_entity
}
```
@@ -1179,7 +1183,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`: 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 `ActionDispatch::Callbacks.to_prepare`.
* `disable_dependency_loading`: Disables the automatic dependency loading if the `config.eager_load` is set to `true`.
@@ -1196,7 +1200,7 @@ development:
timeout: 5000
```
-Since the connection pooling is handled inside of Active Record by default, all application servers (Thin, mongrel, Unicorn etc.) should behave the same. The database connection pool is initially empty. As demand for connections increases it will create them until it reaches the connection pool limit.
+Since the connection pooling is handled inside of Active Record by default, all application servers (Thin, Puma, Unicorn etc.) should behave the same. The database connection pool is initially empty. As demand for connections increases it will create them until it reaches the connection pool limit.
Any one request will check out a connection the first time it requires access to the database. At the end of the request it will check the connection back in. This means that the additional connection slot will be available again for the next request in the queue.
@@ -1217,21 +1221,25 @@ NOTE. If you are running in a multi-threaded environment, there could be a chanc
Custom configuration
--------------------
-You can configure your own code through the Rails configuration object with custom configuration. It works like this:
+You can configure your own code through the Rails configuration object with
+custom configuration under either the `config.x` namespace, or `config` directly.
+The key difference between these two is that you should be using `config.x` if you
+are defining _nested_ configuration (ex: `config.x.nested.nested.hi`), and just
+`config` for _single level_ configuration (ex: `config.hello`).
```ruby
- config.payment_processing.schedule = :daily
- config.payment_processing.retries = 3
+ config.x.payment_processing.schedule = :daily
+ config.x.payment_processing.retries = 3
config.super_debugger = true
```
These configuration points are then available through the configuration object:
```ruby
- Rails.configuration.payment_processing.schedule # => :daily
- Rails.configuration.payment_processing.retries # => 3
- Rails.configuration.super_debugger # => true
- Rails.configuration.super_debugger.not_set # => nil
+ Rails.configuration.x.payment_processing.schedule # => :daily
+ Rails.configuration.x.payment_processing.retries # => 3
+ Rails.configuration.x.payment_processing.not_set # => nil
+ Rails.configuration.super_debugger # => true
```
You can also use `Rails::Application.config_for` to load whole configuration files:
@@ -1290,7 +1298,7 @@ evented file system monitor to detect changes when `config.cache_classes` is
```ruby
group :development do
- gem 'listen', '~> 3.0.4'
+ gem 'listen', '>= 3.0.5', '< 3.2'
end
```
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index ba8d085f79..830a546570 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -40,7 +40,9 @@ Then, don't get your hopes up! Unless you have a "Code Red, Mission Critical, th
Having a way to reproduce your issue will be very helpful for others to help confirm, investigate and ultimately fix your issue. You can do this by providing an executable test case. To make this process easier, we have prepared several bug report templates for you to use as a starting point:
* Template for Active Record (models, database) issues: [gem](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_gem.rb) / [master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_master.rb)
+* Template for testing Active Record (migration) issues: [gem](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_migrations_gem.rb) / [master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_migrations_master.rb)
* Template for Action Pack (controllers, routing) issues: [gem](https://github.com/rails/rails/blob/master/guides/bug_report_templates/action_controller_gem.rb) / [master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/action_controller_master.rb)
+* Template for Active Job issues: [gem](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_job_gem.rb) / [master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_job_master.rb)
* Generic template for other issues: [gem](https://github.com/rails/rails/blob/master/guides/bug_report_templates/generic_gem.rb) / [master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/generic_master.rb)
These templates include the boilerplate code to set up a test case against either a released version of Rails (`*_gem.rb`) or edge Rails (`*_master.rb`).
@@ -268,33 +270,24 @@ The above are guidelines - please use your best judgment in using them.
### Benchmark Your Code
-If your change has an impact on the performance of Rails, please use the
-[benchmark-ips](https://github.com/evanphx/benchmark-ips) gem to provide
-benchmark results for comparison.
-
-Here's an example of using benchmark-ips:
-
-```ruby
-require 'benchmark/ips'
-
-Benchmark.ips do |x|
- x.report('addition') { 1 + 2 }
- x.report('addition with send') { 1.send(:+, 2) }
-end
-```
-
-This will generate a report with the following information:
-
-```
-Calculating -------------------------------------
- addition 132.013k i/100ms
- addition with send 125.413k i/100ms
--------------------------------------------------
- addition 9.677M (± 1.7%) i/s - 48.449M
- addition with send 6.794M (± 1.1%) i/s - 33.987M
-```
-
-Please see the benchmark/ips [README](https://github.com/evanphx/benchmark-ips/blob/master/README.md) for more information.
+For changes that might have an impact on performance, please benchmark your
+code and measure the impact. Please share the benchmark script you used as well
+as the results. You should consider including this information in your commit
+message, which allows future contributors to easily verify your findings and
+determine if they are still relevant. (For example, future optimizations in the
+Ruby VM might render certain optimizations unnecessary.)
+
+It is very easy to make an optimization that improves performance for a
+specific scenario you care about but regresses on other common cases.
+Therefore, you should test your change against a list of representative
+scenarios. Ideally, they should be based on real-world scenarios extracted
+from production applications.
+
+You can use the [benchmark template](https://github.com/rails/rails/blob/master/guides/bug_report_templates/benchmark.rb)
+as a starting point. It includes the boilerplate code to setup a benchmark
+using the [benchmark-ips](https://github.com/evanphx/benchmark-ips) gem. The
+template is designed for testing relatively self-contained changes that can be
+inlined into the script.
### Running Tests
@@ -677,7 +670,7 @@ $ git format-patch master --stdout > ~/my_changes.patch
Switch over to the target branch and apply your changes:
```bash
-$ git checkout -b my_backport_branch 3-2-stable
+$ git checkout -b my_backport_branch 4-2-stable
$ git apply ~/my_changes.patch
```
diff --git a/guides/source/credits.html.erb b/guides/source/credits.html.erb
index 511d76041b..5adbd12ac0 100644
--- a/guides/source/credits.html.erb
+++ b/guides/source/credits.html.erb
@@ -22,7 +22,7 @@ Ruby on Rails Guides: Credits
<h3 class="section">Rails Guides Designers</h3>
<%= author('Jason Zimdars', 'jz') do %>
- Jason Zimdars is an experienced creative director and web designer who has lead UI and UX design for numerous websites and web applications. You can see more of his design and writing at <a href="http://www.thinkcage.com/">Thinkcage.com</a> or follow him on <a href="http://twitter.com/JZ">Twitter</a>.
+ Jason Zimdars is an experienced creative director and web designer who has lead UI and UX design for numerous websites and web applications. You can see more of his design and writing at <a href="http://www.thinkcage.com/">Thinkcage.com</a> or follow him on <a href="https://twitter.com/jasonzimdars">Twitter</a>.
<% end %>
<h3 class="section">Rails Guides Authors</h3>
@@ -32,7 +32,7 @@ Ruby on Rails Guides: Credits
<% end %>
<%= author('Oscar Del Ben', 'oscardelben', 'oscardelben.jpg') do %>
-Oscar Del Ben is a software engineer at <a href="http://www.wildfireapp.com/">Wildfire</a>. He's a regular open source contributor (<a href="https://github.com/oscardelben">GitHub account</a>) and tweets regularly at <a href="https://twitter.com/oscardelben">@oscardelben</a>.
+Oscar Del Ben is a software engineer at <a href="http://www.businessinsider.com/google-buys-wildfire-2012-8">Wildfire</a>. He's a regular open source contributor (<a href="https://github.com/oscardelben">GitHub account</a>) and tweets regularly at <a href="https://twitter.com/oscardelben">@oscardelben</a>.
<% end %>
<%= author('Frederick Cheung', 'fcheung') do %>
diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md
index cc24e6f666..16c7e782bc 100644
--- a/guides/source/development_dependencies_install.md
+++ b/guides/source/development_dependencies_install.md
@@ -162,6 +162,10 @@ $ cd actionpack
$ bundle exec ruby -Itest path/to/test.rb -n test_name
```
+### Railties Setup
+
+Some Railties tests depend on a JavaScript runtime environment, such as having [Node.js](https://nodejs.org/) installed.
+
### Active Record Setup
Active Record's test suite runs three times: once for SQLite3, once for MySQL, and once for PostgreSQL. We are going to see now how to set up the environment for them.
@@ -288,3 +292,46 @@ NOTE: Using the rake task to create the test databases ensures they have the cor
NOTE: You'll see the following warning (or localized warning) during activating HStore extension in PostgreSQL 9.1.x or earlier: "WARNING: => is deprecated as an operator".
If you're using another database, check the file `activerecord/test/config.yml` or `activerecord/test/config.example.yml` for default connection information. You can edit `activerecord/test/config.yml` to provide different credentials on your machine if you must, but obviously you should not push any such changes back to Rails.
+
+### Action Cable Setup
+
+Action Cable uses Redis as its default subscriptions adapter ([read more](action_cable_overview.html#broadcasting)). Thus, in order to have Action Cable's tests passing you need to install and have Redis running.
+
+#### Install Redis From Source
+
+Redis' documentation discourage installations with package managers as those are usually outdated. Installing from source and bringing the server up is straight forward and well documented on [Redis' documentation](http://redis.io/download#installation).
+
+#### Install Redis From Package Manager
+
+On OS X, you can run:
+
+```bash
+$ brew install redis
+```
+
+Follow the instructions given by Homebrew to start these.
+
+In Ubuntu just run:
+
+```bash
+$ sudo apt-get install redis-server
+```
+
+On Fedora or CentOS (requires EPEL enabled), just run:
+
+```bash
+$ sudo yum install redis
+```
+
+If you are running Arch Linux just run:
+
+```bash
+$ sudo pacman -S redis
+$ sudo systemctl start redis
+```
+
+FreeBSD users will have to run the following:
+
+```bash
+# portmaster databases/redis
+```
diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml
index 627e23422d..2925fb4b58 100644
--- a/guides/source/documents.yaml
+++ b/guides/source/documents.yaml
@@ -100,9 +100,9 @@
url: configuring.html
description: This guide covers the basic configuration settings for a Rails application.
-
- name: Rails Command Line Tools and Rake Tasks
+ name: The Rails Command Line
url: command_line.html
- description: This guide covers the command line tools and rake tasks provided by Rails.
+ description: This guide covers the command line tools provided by Rails.
-
name: Asset Pipeline
url: asset_pipeline.html
diff --git a/guides/source/engines.md b/guides/source/engines.md
index f9a37e45ac..0020112a1c 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -46,7 +46,7 @@ see how to hook it into an application.
Engines can also be isolated from their host applications. This means that an
application is able to have a path provided by a routing helper such as
-`articles_path` and use an engine also that provides a path also called
+`articles_path` and use an engine that also provides a path also called
`articles_path`, and the two would not clash. Along with this, controllers, models
and table names are also namespaced. You'll see how to do this later in this
guide.
@@ -184,7 +184,7 @@ end
By inheriting from the `Rails::Engine` class, this gem notifies Rails that
there's an engine at the specified path, and will correctly mount the engine
inside the application, performing tasks such as adding the `app` directory of
-the engine to the load path for models, mailers, controllers and views.
+the engine to the load path for models, mailers, controllers, and views.
The `isolate_namespace` method here deserves special notice. This call is
responsible for isolating the controllers, models, routes and other things into
@@ -1364,7 +1364,7 @@ You can define assets for precompilation in `engine.rb`:
```ruby
initializer "blorgh.assets.precompile" do |app|
- app.config.assets.precompile += %w(admin.css admin.js)
+ app.config.assets.precompile += %w( admin.js admin.css )
end
```
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index 048fe190e8..8ad76ad01e 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -438,8 +438,6 @@ output:
Whenever Rails sees that the internal value of an option being generated matches this value, it will add the `selected` attribute to that option.
-TIP: The second argument to `options_for_select` must be exactly equal to the desired internal value. In particular if the value is the integer `2` you cannot pass `"2"` to `options_for_select` - you must pass `2`. Be aware of values extracted from the `params` hash as they are all strings.
-
WARNING: When `:include_blank` or `:prompt` are not present, `:include_blank` is forced true if the select attribute `required` is true, display `size` is one and `multiple` is not true.
You can add arbitrary attributes to the options using hashes:
diff --git a/guides/source/generators.md b/guides/source/generators.md
index 32bbdc554a..d0b6cef3fd 100644
--- a/guides/source/generators.md
+++ b/guides/source/generators.md
@@ -208,7 +208,15 @@ $ bin/rails generate scaffold User name:string
Looking at this output, it's easy to understand how generators work in Rails 3.0 and above. The scaffold generator doesn't actually generate anything, it just invokes others to do the work. This allows us to add/replace/remove any of those invocations. For instance, the scaffold generator invokes the scaffold_controller generator, which invokes erb, test_unit and helper generators. Since each generator has a single responsibility, they are easy to reuse, avoiding code duplication.
-Our first customization on the workflow will be to stop generating stylesheet, JavaScript and test fixture files for scaffolds. We can achieve that by changing our configuration to the following:
+If we want to avoid generating the default `app/assets/stylesheets/scaffolds.scss` file when scaffolding a new resource we can disable `scaffold_stylesheet`:
+
+```ruby
+ config.generators do |g|
+ g.scaffold_stylesheet false
+ end
+```
+
+The next customization on the workflow will be to stop generating stylesheet, JavaScript and test fixture files for scaffolds altogether. We can achieve that by changing our configuration to the following:
```ruby
config.generators do |g|
@@ -451,6 +459,26 @@ $ rails new thud -m https://gist.github.com/radar/722911/raw/
Whilst the final section of this guide doesn't cover how to generate the most awesome template known to man, it will take you through the methods available at your disposal so that you can develop it yourself. These same methods are also available for generators.
+Adding Command Line Arguments
+-----------------------------
+Rails generators can be easily modified to accept custom command line arguments. This functionality comes from [Thor](http://www.rubydoc.info/github/erikhuda/thor/master/Thor/Base/ClassMethods#class_option-instance_method):
+
+```
+class_option :scope, type: :string, default: 'read_products'
+```
+
+Now our generator can be invoked as follows:
+
+```bash
+rails generate initializer --scope write_products
+```
+
+The command line arguments are accessed through the `options` method inside the generator class. e.g:
+
+```ruby
+@scope = options['scope']
+```
+
Generator methods
-----------------
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index 89b1d3ca03..8a451ab793 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -68,7 +68,7 @@ The Rails philosophy includes two major guiding principles:
again, our code is more maintainable, more extensible, and less buggy.
* **Convention Over Configuration:** Rails has opinions about the best way to do many
things in a web application, and defaults to this set of conventions, rather than
- require that you specify every minutiae through endless configuration files.
+ require that you specify minutiae through endless configuration files.
Creating a New Rails Project
----------------------------
@@ -182,6 +182,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.
Hello, Rails!
-------------
@@ -353,6 +354,7 @@ resource. You need to add the _article resource_ to the
```ruby
Rails.application.routes.draw do
+ get 'welcome/index'
resources :articles
@@ -472,7 +474,7 @@ one here because the `ArticlesController` inherits from `ApplicationController`.
The next part of the message contains `request.formats` which specifies
the format of template to be served in response. It is set to `text/html` as we
requested this page via browser, so Rails is looking for an HTML template.
-`request.variants` specifies what kind of physical devices would be served by
+`request.variant` specifies what kind of physical devices would be served by
the response and helps Rails determine which template to use in the response.
It is empty because no information has been provided.
@@ -484,7 +486,7 @@ to find a template called `articles/new` within `app/views` for the
application. The format for this template can only be `html` and the default
handler for HTML is `erb`. Rails uses other handlers for other formats.
`builder` handler is used to build XML templates and `coffee` handler uses
-CoffeeScript to build JavaScript templates. Because you want to create a new
+CoffeeScript to build JavaScript templates. Since you want to create a new
HTML form, you will be using the `ERB` language which is designed to embed Ruby
in HTML.
@@ -527,7 +529,7 @@ method called `form_for`. To use this method, add this code into
<% end %>
```
-If you refresh the page now, you'll see the exact same form as in the example.
+If you refresh the page now, you'll see the exact same form from our example above.
Building forms in Rails is really just that easy!
When you call `form_for`, you pass it an identifying object for this
@@ -631,8 +633,7 @@ this situation, the only parameters that matter are the ones from the form.
TIP: Ensure you have a firm grasp of the `params` method, as you'll use it fairly regularly. Let's consider an example URL: **http://www.example.com/?username=dhh&email=dhh@email.com**. In this URL, `params[:username]` would equal "dhh" and `params[:email]` would equal "dhh@email.com".
-If you re-submit the form one more time you'll now no longer get the missing
-template error. Instead, you'll see something that looks like the following:
+If you re-submit the form one more time, you'll see something that looks like the following:
```ruby
<ActionController::Parameters {"title"=>"First Article!", "text"=>"This is my first article."} permitted: false>
@@ -826,7 +827,7 @@ NOTE: A frequent practice is to place the standard CRUD actions in each
controller in the following order: `index`, `show`, `new`, `edit`, `create`, `update`
and `destroy`. You may use any order you choose, but keep in mind that these
are public methods; as mentioned earlier in this guide, they must be placed
-before any private or protected method in the controller in order to work.
+before declaring `private` visibility in the controller.
Given that, let's add the `show` action, as follows:
@@ -1154,9 +1155,9 @@ new articles. Create a file called `app/views/articles/edit.html.erb` and make
it look as follows:
```html+erb
-<h1>Editing article</h1>
+<h1>Edit article</h1>
-<%= form_for :article, url: article_path(@article), method: :patch do |f| %>
+<%= form_for(@article) do |f| %>
<% if @article.errors.any? %>
<div id="error_explanation">
@@ -1194,14 +1195,15 @@ it look as follows:
This time we point the form to the `update` action, which is not defined yet
but will be very soon.
-The `method: :patch` option tells Rails that we want this form to be submitted
+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.
The first parameter of `form_for` can be an object, say, `@article` which would
cause the helper to fill in the form with the fields of the object. Passing in a
symbol (`:article`) with the same name as the instance variable (`@article`)
-also automagically leads to the same behavior. This is what is happening here.
+also automagically leads to the same behavior.
More details can be found in [form_for documentation]
(http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for).
@@ -1654,8 +1656,8 @@ This creates five files and one empty directory:
| app/views/comments/ | Views of the controller are stored here |
| test/controllers/comments_controller_test.rb | The test for the controller |
| app/helpers/comments_helper.rb | A view helper file |
-| app/assets/javascripts/comment.coffee | CoffeeScript for the controller |
-| app/assets/stylesheets/comment.scss | Cascading style sheet for the controller |
+| app/assets/javascripts/comments.coffee | CoffeeScript for the controller |
+| app/assets/stylesheets/comments.scss | Cascading style sheet for the controller |
Like with any blog, our readers will create their comments directly after
reading the article, and once they have added their comment, will be sent back
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index 850f0def03..fd54bca4ff 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -166,7 +166,7 @@ def set_locale
I18n.locale = extract_locale_from_tld || I18n.default_locale
end
-# Get locale from top-level domain or return nil if such locale is not available
+# Get locale from top-level domain or return +nil+ if such locale is not available
# You have to put something like:
# 127.0.0.1 application.com
# 127.0.0.1 application.it
@@ -866,7 +866,7 @@ This way you can provide special translations for various error messages at diff
#### Error Message Interpolation
-The translated model name, translated attribute name, and value are always available for interpolation.
+The translated model name, translated attribute name, and value are always available for interpolation as `model`, `attribute` and `value` respectively.
So, for example, instead of the default error message `"cannot be blank"` you could use the attribute name like this : `"Please fill in your %{attribute}"`.
@@ -888,6 +888,7 @@ So, for example, instead of the default error message `"cannot be blank"` you co
| inclusion | - | :inclusion | - |
| exclusion | - | :exclusion | - |
| associated | - | :invalid | - |
+| non-optional association | - | :required | - |
| numericality | - | :not_a_number | - |
| numericality | :greater_than | :greater_than | count |
| numericality | :greater_than_or_equal_to | :greater_than_or_equal_to | count |
@@ -1122,7 +1123,7 @@ I18n support in Ruby on Rails was introduced in the release 2.2 and is still evo
Thus we encourage everybody to experiment with new ideas and features in gems or other libraries and make them available to the community. (Don't forget to announce your work on our [mailing list](http://groups.google.com/group/rails-i18n)!)
-If you find your own locale (language) missing from our [example translations data](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) repository for Ruby on Rails, please [_fork_](https://github.com/guides/fork-a-project-and-submit-your-modifications) the repository, add your data and send a [pull request](https://github.com/guides/pull-requests).
+If you find your own locale (language) missing from our [example translations data](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) repository for Ruby on Rails, please [_fork_](https://github.com/guides/fork-a-project-and-submit-your-modifications) the repository, add your data and send a [pull request](https://help.github.com/articles/about-pull-requests/).
Resources
diff --git a/guides/source/initialization.md b/guides/source/initialization.md
index a2eec03eba..aa7bbcc19b 100644
--- a/guides/source/initialization.md
+++ b/guides/source/initialization.md
@@ -74,7 +74,7 @@ This file is as follows:
```ruby
#!/usr/bin/env ruby
-APP_PATH = File.expand_path('../../config/application', __FILE__)
+APP_PATH = File.expand_path('../config/application', __dir__)
require_relative '../config/boot'
require 'rails/commands'
```
@@ -86,7 +86,7 @@ The `APP_PATH` constant will be used later in `rails/commands`. The `config/boot
`config/boot.rb` contains:
```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.
```
@@ -131,7 +131,7 @@ Once `config/boot.rb` has finished, the next file that is required is
`ARGV` array simply contains `server` which will be passed over:
```ruby
-ARGV << '--help' if ARGV.empty?
+require "rails/command"
aliases = {
"g" => "generate",
@@ -146,33 +146,37 @@ aliases = {
command = ARGV.shift
command = aliases[command] || command
-require 'rails/commands/commands_tasks'
-
-Rails::CommandsTasks.new(ARGV).run_command!(command)
+Rails::Command.invoke command, ARGV
```
-TIP: As you can see, an empty ARGV list will make Rails show the help
-snippet.
-
If we had used `s` rather than `server`, Rails would have used the `aliases`
defined here to find the matching command.
-### `rails/commands/commands_tasks.rb`
+### `rails/command.rb`
-When one types a valid Rails command, `run_command!` a method of the same name
-is called. If Rails doesn't recognize the command, it tries to run a Rake task
-of the same name.
+When one types a Rails command, `invoke` tries to lookup a command for the given
+namespace and executing the command if found.
-```ruby
-COMMAND_WHITELIST = %w(plugin generate destroy console server dbconsole application runner new version help)
+If Rails doesn't recognize the command, it hands the reins over to Rake
+to run a task of the same name.
-def run_command!(command)
- command = parse_command(command)
+As shown, `Rails::Command` displays the help output automatically if the `args`
+are empty.
- if COMMAND_WHITELIST.include?(command)
- send(command)
- else
- run_rake_task(command)
+```ruby
+module Rails::Command
+ class << self
+ def invoke(namespace, args = [], **config)
+ namespace = namespace.to_s
+ namespace = "help" if namespace.blank? || Thor::HELP_MAPPINGS.include?(namespace)
+ namespace = "version" if %w( -v --version ).include? namespace
+
+ if command = find_by_namespace(namespace)
+ command.perform(namespace, args, config)
+ else
+ find_by_namespace("rake").perform(namespace, args, config)
+ end
+ end
end
end
```
@@ -180,53 +184,39 @@ end
With the `server` command, Rails will further run the following code:
```ruby
-def set_application_directory!
- Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exist?(File.expand_path("config.ru"))
-end
-
-def server
- set_application_directory!
- require_command!("server")
-
- Rails::Server.new.tap do |server|
- # We need to require application after the server sets environment,
- # otherwise the --environment option given to the server won't propagate.
- require APP_PATH
- Dir.chdir(Rails.application.root)
- server.start
+module Rails
+ module Command
+ class ServerCommand < Base # :nodoc:
+ def perform
+ set_application_directory!
+
+ Rails::Server.new.tap do |server|
+ # Require application after server sets environment to propagate
+ # the --environment option.
+ require APP_PATH
+ Dir.chdir(Rails.application.root)
+ server.start
+ end
+ end
+ end
end
end
-
-def require_command!(command)
- require "rails/commands/#{command}"
-end
```
This file will change into the Rails root directory (a path two directories up
from `APP_PATH` which points at `config/application.rb`), but only if the
-`config.ru` file isn't found. This then requires `rails/commands/server` which
-sets up the `Rails::Server` class.
-
-```ruby
-require 'fileutils'
-require 'optparse'
-require 'action_dispatch'
-require 'rails'
-
-module Rails
- class Server < ::Rack::Server
-```
-
-`fileutils` and `optparse` are standard Ruby libraries which provide helper functions for working with files and parsing options.
+`config.ru` file isn't found. This then starts up the `Rails::Server` class.
### `actionpack/lib/action_dispatch.rb`
Action Dispatch is the routing component of the Rails framework.
It adds functionality like routing, session, and common middlewares.
-### `rails/commands/server.rb`
+### `rails/commands/server/server_command.rb`
-The `Rails::Server` class is defined in this file by inheriting from `Rack::Server`. When `Rails::Server.new` is called, this calls the `initialize` method in `rails/commands/server.rb`:
+The `Rails::Server` class is defined in this file by inheriting from
+`Rack::Server`. When `Rails::Server.new` is called, this calls the `initialize`
+method in `rails/commands/server/server_command.rb`:
```ruby
def initialize(*)
@@ -252,7 +242,10 @@ end
In this case, `options` will be `nil` so nothing happens in this method.
-After `super` has finished in `Rack::Server`, we jump back to `rails/commands/server.rb`. At this point, `set_environment` is called within the context of the `Rails::Server` object and this method doesn't appear to do much at first glance:
+After `super` has finished in `Rack::Server`, we jump back to
+`rails/commands/server/server_command.rb`. At this point, `set_environment`
+is called within the context of the `Rails::Server` object and this method
+doesn't appear to do much at first glance:
```ruby
def set_environment
@@ -289,17 +282,15 @@ With the `default_options` set to this:
```ruby
def default_options
- environment = ENV['RACK_ENV'] || 'development'
- default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
-
- {
- :environment => environment,
- :pid => nil,
- :Port => 9292,
- :Host => default_host,
- :AccessLog => [],
- :config => "config.ru"
- }
+ super.merge(
+ Port: ENV.fetch("PORT", 3000).to_i,
+ Host: ENV.fetch("HOST", "localhost").dup,
+ DoNotReverseLookup: true,
+ environment: (ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development").dup,
+ daemonize: false,
+ caching: nil,
+ pid: Options::DEFAULT_PID_PATH,
+ restart_cmd: restart_command)
end
```
@@ -311,22 +302,25 @@ def opt_parser
end
```
-The class **is** defined in `Rack::Server`, but is overwritten in `Rails::Server` to take different arguments. Its `parse!` method begins like this:
+The class **is** defined in `Rack::Server`, but is overwritten in
+`Rails::Server` to take different arguments. Its `parse!` method looks
+like this:
```ruby
def parse!(args)
args, options = args.dup, {}
- opt_parser = OptionParser.new do |opts|
- opts.banner = "Usage: rails server [mongrel, thin, etc] [options]"
- opts.on("-p", "--port=port", Integer,
- "Runs Rails on the specified port.", "Default: 3000") { |v| options[:Port] = v }
- ...
+ option_parser(options).parse! args
+
+ options[:log_stdout] = options[:daemonize].blank? && (options[:environment] || Rails.env) == "development"
+ options[:server] = args.shift
+ options
+end
```
This method will set up keys for the `options` which Rails will then be
able to use to determine how its server should run. After `initialize`
-has finished, we jump back into `rails/server` where `APP_PATH` (which was
+has finished, we jump back into the server command where `APP_PATH` (which was
set earlier) is required.
### `config/application`
@@ -345,6 +339,7 @@ def start
print_boot_information
trap(:INT) { exit }
create_tmp_directories
+ setup_dev_caching
log_to_stdout if options[:log_stdout]
super
@@ -352,7 +347,6 @@ def start
end
private
-
def print_boot_information
...
puts "=> Run `rails server -h` for more startup options"
@@ -364,21 +358,30 @@ private
end
end
+ def setup_dev_caching
+ if options[:environment] == "development"
+ Rails::DevCaching.enable_by_argument(options[:caching])
+ end
+ end
+
def log_to_stdout
wrapped_app # touch the app so the logger is set up
-
- console = ActiveSupport::Logger.new($stdout)
+
+ console = ActiveSupport::Logger.new(STDOUT)
console.formatter = Rails.logger.formatter
console.level = Rails.logger.level
-
- Rails.logger.extend(ActiveSupport::Logger.broadcast(console))
+
+ unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDOUT)
+ Rails.logger.extend(ActiveSupport::Logger.broadcast(console))
+ end
end
```
This is where the first output of the Rails initialization happens. This method
creates a trap for `INT` signals, so if you `CTRL-C` the server, it will exit the
process. As we can see from the code here, it will create the `tmp/cache`,
-`tmp/pids`, and `tmp/sockets` directories. It then calls `wrapped_app` which is
+`tmp/pids`, and `tmp/sockets` directories. It then enables caching in development
+if `rails server` is called with `--dev-caching`. Finally, it calls `wrapped_app` which is
responsible for creating the Rack app, before creating and assigning an instance
of `ActiveSupport::Logger`.
@@ -538,7 +541,7 @@ require "rails"
sprockets/railtie
).each do |railtie|
begin
- require "#{railtie}"
+ require railtie
rescue LoadError
end
end
@@ -663,7 +666,7 @@ DEFAULT_OPTIONS = {
}
def self.run(app, options = {})
- options = DEFAULT_OPTIONS.merge(options)
+ options = DEFAULT_OPTIONS.merge(options)
if options[:Verbose]
app = Rack::CommonLogger.new(app, STDOUT)
diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb
index 943fd3fd7f..bb50761b30 100644
--- a/guides/source/layout.html.erb
+++ b/guides/source/layout.html.erb
@@ -88,7 +88,7 @@
<div id="container">
<div class="wrapper">
<div id="mainCol">
- <%= yield.html_safe %>
+ <%= yield %>
<h3>Feedback</h3>
<p>
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index 2722789c49..c8702f54fc 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -630,6 +630,8 @@ to use in this case.
redirect_back(fallback_location: root_path)
```
+NOTE: `redirect_to` and `redirect_back` do not halt and return immediately from method execution, but simply set HTTP responses. Statements occurring after them in a method will be executed. You can halt by an explicit `return` or some other halting mechanism, if needed.
+
#### Getting a Different Redirect Status Code
Rails uses HTTP status code 302, a temporary redirect, when you call `redirect_to`. If you'd like to use a different status code, perhaps 301, a permanent redirect, you can use the `:status` option:
@@ -749,7 +751,7 @@ When Rails renders a view as a response, it does so by combining the view with t
### Asset Tag Helpers
-Asset tag helpers provide methods for generating HTML that link views to feeds, JavaScript, stylesheets, images, videos and audios. There are six asset tag helpers available in Rails:
+Asset tag helpers provide methods for generating HTML that link views to feeds, JavaScript, stylesheets, images, videos, and audios. There are six asset tag helpers available in Rails:
* `auto_discovery_link_tag`
* `javascript_include_tag`
@@ -1080,7 +1082,7 @@ definitions for several similar resources:
* `shared/_search_filters.html.erb`
```html+erb
- <%= form_for(@q) do |f| %>
+ <%= form_for(search) do |f| %>
<h1>Search form:</h1>
<fieldset>
<%= yield f %>
@@ -1278,7 +1280,7 @@ When rendering collections it is also possible to use the `:layout` option:
<%= render partial: "product", collection: @products, layout: "special_layout" %>
```
-The layout will be rendered together with the partial for each item in the collection. The current object and object_counter variables will be available in the layout as well, the same way they do within the partial.
+The layout will be rendered together with the partial for each item in the collection. The current object and object_counter variables will be available in the layout as well, the same way they are within the partial.
### Using Nested Layouts
diff --git a/guides/source/plugins.md b/guides/source/plugins.md
index ff84861b8c..760ff431c0 100644
--- a/guides/source/plugins.md
+++ b/guides/source/plugins.md
@@ -67,7 +67,7 @@ This will tell you that everything got generated properly and you are ready to s
Extending Core Classes
----------------------
-This section will explain how to add a method to String that will be available anywhere in your rails application.
+This section will explain how to add a method to String that will be available anywhere in your Rails application.
In this example you will add a method to String named `to_squawk`. To begin, create a new test file with a few assertions:
diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md
index ed935e1008..340933c7ee 100644
--- a/guides/source/rails_on_rack.md
+++ b/guides/source/rails_on_rack.md
@@ -181,7 +181,6 @@ $ bin/rails middleware
(in /Users/lifo/Rails/blog)
use ActionDispatch::Static
use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x00000001c304c8>
-use Rack::Runtime
...
run Rails.application.routes
```
diff --git a/guides/source/routing.md b/guides/source/routing.md
index 756e0fefd7..86492a9332 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -553,29 +553,23 @@ In particular, simple routing makes it very easy to map legacy URLs to new Rails
### Bound Parameters
-When you set up a regular route, you supply a series of symbols that Rails maps to parts of an incoming HTTP request. Two of these symbols are special: `:controller` maps to the name of a controller in your application, and `:action` maps to the name of an action within that controller. For example, consider this route:
+When you set up a regular route, you supply a series of symbols that Rails maps to parts of an incoming HTTP request. For example, consider this route:
```ruby
-get ':controller(/:action(/:id))'
+get 'photos(/:id)', to: :display
```
-If an incoming request of `/photos/show/1` is processed by this route (because it hasn't matched any previous route in the file), then the result will be to invoke the `show` action of the `PhotosController`, and to make the final parameter `"1"` available as `params[:id]`. This route will also route the incoming request of `/photos` to `PhotosController#index`, since `:action` and `:id` are optional parameters, denoted by parentheses.
+If an incoming request of `/photos/1` is processed by this route (because it hasn't matched any previous route in the file), then the result will be to invoke the `display` action of the `PhotosController`, and to make the final parameter `"1"` available as `params[:id]`. This route will also route the incoming request of `/photos` to `PhotosController#display`, since `:id` is an optional parameter, denoted by parentheses.
### Dynamic Segments
-You can set up as many dynamic segments within a regular route as you like. Anything other than `:controller` or `:action` will be available to the action as part of `params`. If you set up this route:
+You can set up as many dynamic segments within a regular route as you like. Any segment will be available to the action as part of `params`. If you set up this route:
```ruby
-get ':controller/:action/:id/:user_id'
+get 'photos/:id/:user_id', to: 'photos#show'
```
-An incoming path of `/photos/show/1/2` will be dispatched to the `show` action of the `PhotosController`. `params[:id]` will be `"1"`, and `params[:user_id]` will be `"2"`.
-
-NOTE: You can't use `:namespace` or `:module` with a `:controller` path segment. If you need to do this then use a constraint on :controller that matches the namespace you require. e.g:
-
-```ruby
-get ':controller(/:action(/:id))', controller: /admin\/[^\/]+/
-```
+An incoming path of `/photos/1/2` will be dispatched to the `show` action of the `PhotosController`. `params[:id]` will be `"1"`, and `params[:user_id]` will be `"2"`.
TIP: By default, dynamic segments don't accept dots - this is because the dot is used as a separator for formatted routes. If you need to use a dot within a dynamic segment, add a constraint that overrides this – for example, `id: /[^\/]+/` allows anything except a slash.
@@ -584,39 +578,39 @@ TIP: By default, dynamic segments don't accept dots - this is because the dot is
You can specify static segments when creating a route by not prepending a colon to a fragment:
```ruby
-get ':controller/:action/:id/with_user/:user_id'
+get 'photos/:id/with_user/:user_id', to: 'photos#show'
```
-This route would respond to paths such as `/photos/show/1/with_user/2`. In this case, `params` would be `{ controller: 'photos', action: 'show', id: '1', user_id: '2' }`.
+This route would respond to paths such as `/photos/1/with_user/2`. In this case, `params` would be `{ controller: 'photos', action: 'show', id: '1', user_id: '2' }`.
### The Query String
The `params` will also include any parameters from the query string. For example, with this route:
```ruby
-get ':controller/:action/:id'
+get 'photos/:id', to: 'photos#show'
```
-An incoming path of `/photos/show/1?user_id=2` will be dispatched to the `show` action of the `Photos` controller. `params` will be `{ controller: 'photos', action: 'show', id: '1', user_id: '2' }`.
+An incoming path of `/photos/1?user_id=2` will be dispatched to the `show` action of the `Photos` controller. `params` will be `{ controller: 'photos', action: 'show', id: '1', user_id: '2' }`.
### Defining Defaults
-You do not need to explicitly use the `:controller` and `:action` symbols within a route. You can supply them as defaults:
+You can define defaults in a route by supplying a hash for the `:defaults` option. This even applies to parameters that you do not specify as dynamic segments. For example:
```ruby
-get 'photos/:id', to: 'photos#show'
+get 'photos/:id', to: 'photos#show', defaults: { format: 'jpg' }
```
-With this route, Rails will match an incoming path of `/photos/12` to the `show` action of `PhotosController`.
+Rails would match `photos/12` to the `show` action of `PhotosController`, and set `params[:format]` to `"jpg"`.
-You can also define other defaults in a route by supplying a hash for the `:defaults` option. This even applies to parameters that you do not specify as dynamic segments. For example:
+You can also use `defaults` in a block format to define the defaults for multiple items:
```ruby
-get 'photos/:id', to: 'photos#show', defaults: { format: 'jpg' }
+defaults format: :json do
+ resources :photos
+end
```
-Rails would match `photos/12` to the `show` action of `PhotosController`, and set `params[:format]` to `"jpg"`.
-
NOTE: You cannot override defaults via query parameters - this is for security reasons. The only defaults that can be overridden are dynamic segments via substitution in the URL path.
### Naming Routes
diff --git a/guides/source/security.md b/guides/source/security.md
index 2d1bc3b5b3..a81a782cf2 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -131,7 +131,7 @@ It works like this:
* The user takes the cookie from the first step (which they previously copied) and replaces the current cookie in the browser.
* The user has their original credit back.
-Including a nonce (a random value) in the session solves replay attacks. A nonce is valid only once, and the server has to keep track of all the valid nonces. It gets even more complicated if you have several application servers (mongrels). Storing nonces in a database table would defeat the entire purpose of CookieStore (avoiding accessing the database).
+Including a nonce (a random value) in the session solves replay attacks. A nonce is valid only once, and the server has to keep track of all the valid nonces. It gets even more complicated if you have several application servers. Storing nonces in a database table would defeat the entire purpose of CookieStore (avoiding accessing the database).
The best _solution against it is not to store this kind of data in a session, but in the database_. In this case store the credit in the database and the logged_in_user_id in the session.
@@ -287,7 +287,7 @@ Another class of security vulnerabilities surrounds the use of redirection and f
WARNING: _Redirection in a web application is an underestimated cracker tool: Not only can the attacker forward the user to a trap web site, they may also create a self-contained attack._
-Whenever the user is allowed to pass (parts of) the URL for redirection, it is possibly vulnerable. The most obvious attack would be to redirect users to a fake web application which looks and feels exactly as the original one. This so-called phishing attack works by sending an unsuspicious link in an email to the users, injecting the link by XSS in the web application or putting the link into an external site. It is unsuspicious, because the link starts with the URL to the web application and the URL to the malicious site is hidden in the redirection parameter: http://www.example.com/site/redirect?to= www.attacker.com. Here is an example of a legacy action:
+Whenever the user is allowed to pass (parts of) the URL for redirection, it is possibly vulnerable. The most obvious attack would be to redirect users to a fake web application which looks and feels exactly as the original one. This so-called phishing attack works by sending an unsuspicious link in an email to the users, injecting the link by XSS in the web application or putting the link into an external site. It is unsuspicious, because the link starts with the URL to the web application and the URL to the malicious site is hidden in the redirection parameter: http://www.example.com/site/redirect?to=www.attacker.com. Here is an example of a legacy action:
```ruby
def legacy
@@ -377,7 +377,7 @@ In 2007 there was the first tailor-made trojan which stole information from an I
Having one single place in the admin interface or Intranet, where the input has not been sanitized, makes the entire application vulnerable. Possible exploits include stealing the privileged administrator's cookie, injecting an iframe to steal the administrator's password or installing malicious software through browser security holes to take over the administrator's computer.
-Refer to the Injection section for countermeasures against XSS. It is _recommended to use the SafeErb plugin_ also in an Intranet or administration interface.
+Refer to the Injection section for countermeasures against XSS.
**CSRF** Cross-Site Request Forgery (CSRF), also known as Cross-Site Reference Forgery (XSRF), is a gigantic attack method, it allows the attacker to do everything the administrator or Intranet user may do. As you have already seen above how CSRF works, here are a few examples of what attackers can do in the Intranet or admin interface.
@@ -762,7 +762,7 @@ s = sanitize(user_input, tags: tags, attributes: %w(href title))
This allows only the given tags and does a good job, even against all kinds of tricks and malformed tags.
-As a second step, _it is good practice to escape all output of the application_, especially when re-displaying user input, which hasn't been input-filtered (as in the search form example earlier on). _Use `escapeHTML()` (or its alias `h()`) method_ to replace the HTML input characters &amp;, &quot;, &lt;, and &gt; by their uninterpreted representations in HTML (`&amp;`, `&quot;`, `&lt;`, and `&gt;`). However, it can easily happen that the programmer forgets to use it, so _it is recommended to use the SafeErb gem. SafeErb reminds you to escape strings from external sources.
+As a second step, _it is good practice to escape all output of the application_, especially when re-displaying user input, which hasn't been input-filtered (as in the search form example earlier on). _Use `escapeHTML()` (or its alias `h()`) method_ to replace the HTML input characters &amp;, &quot;, &lt;, and &gt; by their uninterpreted representations in HTML (`&amp;`, `&quot;`, `&lt;`, and `&gt;`).
##### Obfuscation and Encoding Injection
@@ -965,7 +965,7 @@ When `params[:token]` is one of: `[nil]`, `[nil, nil, ...]` or
`['foo', nil]` it will bypass the test for `nil`, but `IS NULL` or
`IN ('foo', NULL)` where clauses still will be added to the SQL query.
-To keep rails secure by default, `deep_munge` replaces some of the values with
+To keep Rails secure by default, `deep_munge` replaces some of the values with
`nil`. Below table shows what the parameters look like based on `JSON` sent in
request:
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 26d50bec0c..6f783089a9 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -37,10 +37,12 @@ controllers/ helpers/ mailers/ test_helper.rb
fixtures/ integration/ models/
```
-The `models` directory is meant to hold tests for your models, the `controllers` directory is meant to hold tests for your controllers and the `integration` directory is meant to hold tests that involve any number of controllers interacting. There is also a directory for testing your mailers and one for testing view helpers.
+The `helpers`, `mailers`, and `models` directories are meant to hold tests for view helpers, mailers, and models, respectively. The `controllers` directory is meant to hold tests for controllers, routes, and views. The `integration` directory is meant to hold tests for interactions between controllers.
Fixtures are a way of organizing test data; they reside in the `fixtures` directory.
+A `jobs` directory will also be created when an associated test is first generated.
+
The `test_helper.rb` file holds the default configuration for your tests.
@@ -237,8 +239,8 @@ Run options: --seed 1808
Error:
ArticleTest#test_should_report_error:
-NameError: undefined local variable or method `some_undefined_variable' for #<ArticleTest:0x007fee3aa71798>
- test/models/article_test.rb:11:in `block in <class:ArticleTest>'
+NameError: undefined local variable or method 'some_undefined_variable' for #<ArticleTest:0x007fee3aa71798>
+ test/models/article_test.rb:11:in 'block in <class:ArticleTest>'
bin/rails test test/models/article_test.rb:9
@@ -310,7 +312,6 @@ specify to make your test failure messages clearer.
| `assert_not_in_delta( expected, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are not within `delta` of each other.|
| `assert_throws( symbol, [msg] ) { block }` | Ensures that the given block throws the symbol.|
| `assert_raises( exception1, exception2, ... ) { block }` | Ensures that the given block raises one of the given exceptions.|
-| `assert_nothing_raised { block }` | Ensures that the given block doesn't raise any exceptions.|
| `assert_instance_of( class, obj, [msg] )` | Ensures that `obj` is an instance of `class`.|
| `assert_not_instance_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class`.|
| `assert_kind_of( class, obj, [msg] )` | Ensures that `obj` is an instance of `class` or is descending from it.|
@@ -321,7 +322,6 @@ specify to make your test failure messages clearer.
| `assert_not_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is false.|
| `assert_predicate ( obj, predicate, [msg] )` | Ensures that `obj.predicate` is true, e.g. `assert_predicate str, :empty?`|
| `assert_not_predicate ( obj, predicate, [msg] )` | Ensures that `obj.predicate` is false, e.g. `assert_not_predicate str, :empty?`|
-| `assert_send( array, [msg] )` | Ensures that executing the method listed in `array[1]` on the object in `array[0]` with the parameters of `array[2 and up]` is true, e.g. assert_send [@user, :full_name, 'Sam Smith']. This one is weird eh?|
| `flunk( [msg] )` | Ensures failure. This is useful to explicitly mark a test that isn't finished yet.|
The above are a subset of assertions that minitest supports. For an exhaustive &
@@ -341,6 +341,7 @@ 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_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.|
@@ -367,7 +368,7 @@ documentation](http://docs.seattlerb.org/minitest).
We can run all of our tests at once by using the `bin/rails test` command.
-Or we can run a single test by passing the `bin/rails test` command the filename containing the test cases.
+Or we can run a single test file by passing the `bin/rails test` command the filename containing the test cases.
```bash
$ bin/rails test test/models/article_test.rb
@@ -412,7 +413,7 @@ You can also run an entire directory of tests by providing the path to the direc
$ bin/rails test test/controllers # run all tests from specific directory
```
-The test runner provides lot of other features too like failing fast, deferring test output
+The test runner also provides a lot of other features like failing fast, deferring test output
at the end of test run and so on. Check the documentation of the test runner as follows:
```bash
@@ -759,18 +760,13 @@ and also ensuring that the right response body has been generated.
The `get` method kicks off the web request and populates the results into the `@response`. It can accept up to 6 arguments:
-* The action of the controller you are requesting.
- This can be in the form of a string or a route (i.e. `articles_url`).
-
+* The URI of the controller action you are requesting.
+ This can be in the form of a string or a route helper (e.g. `articles_url`).
* `params`: option with a hash of request parameters to pass into the action
(e.g. query string parameters or article variables).
-
* `headers`: for setting the headers that will be passed with the request.
-
* `env`: for customizing the request environment as needed.
-
* `xhr`: whether the request is Ajax request or not. Can be set to true for marking the request as Ajax.
-
* `as`: for encoding the request with different content type. Supports `:json` by default.
All of these keyword arguments are optional.
@@ -778,13 +774,13 @@ All of these keyword arguments are optional.
Example: Calling the `:show` action, passing an `id` of 12 as the `params` and setting `HTTP_REFERER` header:
```ruby
-get :show, params: { id: 12 }, headers: { "HTTP_REFERER" => "http://example.com/home" }
+get article_url, params: { id: 12 }, headers: { "HTTP_REFERER" => "http://example.com/home" }
```
Another example: Calling the `:update` action, passing an `id` of 12 as the `params` as an Ajax request.
```ruby
-patch update_url, params: { id: 12 }, xhr: true
+patch article_url, params: { id: 12 }, xhr: true
```
NOTE: If you try running `test_should_create_article` test from `articles_controller_test.rb` it will fail on account of the newly added model level validation and rightly so.
@@ -803,6 +799,13 @@ end
Now you can try running all the tests and they should pass.
+NOTE: If you followed the steps in the Basic Authentication section, you'll need to add the following to the `setup` block to get all the tests passing:
+
+```ruby
+request.headers['Authorization'] = ActionController::HttpAuthentication::Basic.
+ encode_credentials('dhh', 'secret')
+```
+
### Available Request Types for Functional Tests
If you're familiar with the HTTP protocol, you'll know that `get` is a type of request. There are 6 request types supported in Rails functional tests:
@@ -862,9 +865,9 @@ You also have access to three instance variables in your functional tests, after
class ArticlesControllerTest < ActionDispatch::IntegrationTest
test "should get index" do
get articles_url
-
- assert_equal "index", @controller.action_name
- assert_equal "application/x-www-form-urlencoded", @request.media_type
+
+ assert_equal "index", @controller.action_name
+ assert_equal "application/x-www-form-urlencoded", @request.media_type
assert_match "Articles", @response.body
end
end
@@ -1054,7 +1057,7 @@ To avoid code duplication, you can add your own test helpers.
Sign in helper can be a good example:
```ruby
-#test/test_helper.rb
+# test/test_helper.rb
module SignInHelper
def sign_in_as(user)
@@ -1085,7 +1088,7 @@ end
Testing Routes
--------------
-Like everything else in your Rails application, you can test your routes.
+Like everything else in your Rails application, you can test your routes. Route tests reside in `test/controllers/` or are part of controller tests.
NOTE: If your application has complex routes, Rails provides a number of useful helpers to test them.
@@ -1320,8 +1323,8 @@ end
This test is pretty simple and only asserts that the job get the work done
as expected.
-By default, `ActiveJob::TestCase` will set the queue adapter to `:async` so that
-your jobs are performed in an async fashion. It will also ensure that all previously performed
+By default, `ActiveJob::TestCase` will set the queue adapter to `:test` so that
+your jobs are performed inline. It will also ensure that all previously performed
and enqueued jobs are cleared before any test run so you can safely assume that
no jobs have already been executed in the scope of each test.
@@ -1360,7 +1363,7 @@ Here is an example using the [`travel_to`](http://api.rubyonrails.org/classes/Ac
user = User.create(name: 'Gaurish', activation_date: Date.new(2004, 10, 24))
assert_not user.applicable_for_gifting?
travel_to Date.new(2004, 11, 24) do
- assert_equal Date.new(2004, 10, 24), user.activation_date # inside the travel_to block `Date.current` is mocked
+ assert_equal Date.new(2004, 10, 24), user.activation_date # inside the `travel_to` block `Date.current` is mocked
assert user.applicable_for_gifting?
end
assert_equal Date.new(2004, 10, 24), user.activation_date # The change was visible only inside the `travel_to` block.
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index e3b0f42a95..8a3b3b84b4 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -44,7 +44,7 @@ TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterp
### The Update Task
-Rails provides the `app:update` task (`rails:update` on 4.2 and earlier). After updating the Rails version
+Rails provides the `app:update` task (`rake rails:update` on 4.2 and earlier). After updating the Rails version
in the Gemfile, run this task.
This will help you with the creation of new files and changes of old files in an
interactive session.
@@ -94,6 +94,8 @@ class ApplicationRecord < ActiveRecord::Base
end
```
+Then make sure that all your models inherit from it.
+
### Halting Callback Chains via `throw(:abort)`
In Rails 4.2, when a 'before' callback returns `false` in Active Record
@@ -147,18 +149,18 @@ documentation.
### Autoloading is Disabled After Booting in the Production Environment
-Autoloading of paths in `config.autoload_paths` is now disabled after booting in
-the production environment by default. Eager loading the application is part of
-the boot process. Top-level constants should still work as they are still
-autoloaded, meaning you don't need to manually require them.
+Autoloading is now disabled after booting in the production environment by
+default.
+
+Eager loading the application is part of the boot process, so top-level
+constants are fine and are still autoloaded, no need to require their files.
-Constants in deeper places are only executed at runtime, like regular method
-bodies. These should also still work because their Ruby definition files will be
-eager loaded during the boot process as well.
+Constants in deeper places only executed at runtime, like regular method bodies,
+are also fine because the file defining them will have been eager loaded while booting.
-For the vast majority of applications this change requires no action. But in the
-rare situation where your application needs autoloading in the production
-environment, you can set `Rails.application.config.enable_dependency_loading` to
+For the vast majority of applications this change needs no action. But in the
+very rare event that your application needs autoloading while running in
+production mode, set `Rails.application.config.enable_dependency_loading` to
true.
### XML Serialization
@@ -323,7 +325,7 @@ should support caching.
#### Configure the Output of `db:structure:dump`
-If you're using `schema_search_path` or other PostgreSQL extentions, you can control how the schema is
+If you're using `schema_search_path` or other PostgreSQL extensions, you can control how the schema is
dumped. Set to `:all` to generate all dumps, or to `:schema_search_path` to generate from schema search path.
config.active_record.dump_schemas = :all
@@ -1276,6 +1278,10 @@ Also check your environment settings for `config.action_dispatch.best_standards_
Rails 4.0 removes the `j` alias for `ERB::Util#json_escape` since `j` is already used for `ActionView::Helpers::JavaScriptHelper#escape_javascript`.
+#### Cache
+
+The caching method changed between Rails 3.x and 4.0. You should [change the cache namespace](http://guides.rubyonrails.org/caching_with_rails.html#activesupport-cache-store) and roll out with a cold cache.
+
### Helpers Loading Order
The order in which helpers from more than one directory are loaded has changed in Rails 4.0. Previously, they were gathered and then sorted alphabetically. After upgrading to Rails 4.0, helpers will preserve the order of loaded directories and will be sorted alphabetically only within each directory. Unless you explicitly use the `helpers_path` parameter, this change will only impact the way of loading helpers from engines. If you rely on the ordering, you should check if correct methods are available after upgrade. If you would like to change the order in which engines are loaded, you can use `config.railties_order=` method.
@@ -1424,7 +1430,7 @@ config.assets.digest = true
# config.assets.manifest = YOUR_PATH
# Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
-# config.assets.precompile += %w( search.js )
+# config.assets.precompile += %w( admin.js admin.css )
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
diff --git a/guides/w3c_validator.rb b/guides/w3c_validator.rb
index f56feeb696..c0a32c6b91 100644
--- a/guides/w3c_validator.rb
+++ b/guides/w3c_validator.rb
@@ -67,7 +67,7 @@ module RailsGuides
def select_only(guides)
prefixes = ENV["ONLY"].split(",").map(&:strip)
guides.select do |guide|
- prefixes.any? {|p| guide.start_with?("./output/#{p}")}
+ prefixes.any? { |p| guide.start_with?("./output/#{p}") }
end
end
@@ -81,7 +81,7 @@ module RailsGuides
error_summary += "\n #{name}"
error_detail += "\n\n #{name} has #{errors.size} validation error(s):\n"
errors.each do |error|
- error_detail += "\n "+error.to_s.delete("\n")
+ error_detail += "\n " + error.to_s.delete("\n")
end
end
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 70f4b84237..d7d1a66863 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,53 @@
+* The `log:clear` task clear all environments log files by default.
+
+ *Yuji Yaginuma*
+
+* Add Webpack support in new apps via the --webpack option, which will delegate to the rails/webpacker gem.
+
+ To generate a new app that has Webpack dependencies configured and binstubs for webpack and webpack-watcher:
+
+ `rails new myapp --webpack`
+
+ To generate a new app that has Webpack + React configured and an example intalled:
+
+ `rails new myapp --webpack=react`
+
+ *DHH*
+
+* Add Yarn support in new apps with a yarn binstub and vendor/package.json. Skippable via --skip-yarn option.
+
+ *Liceth Ovalles*, *Guillermo Iguaran*, *DHH*
+
+* Removed jquery-rails from default stack, instead rails-ujs that is shipped
+ with Action View is included as default UJS adapter.
+
+ *Guillermo Iguaran*
+
+* The config file `secrets.yml` is now loaded in with all keys as symbols.
+ This allows secrets files to contain more complex information without all
+ child keys being strings while parent keys are symbols.
+
+ *Isaac Sloan*
+
+* Add `:skip_sprockets` to `Rails::PluginBuilder::PASSTHROUGH_OPTIONS`
+
+ *Tsukuru Tanimichi*
+
+* Allow the use of listen's 3.1.x branch
+
+ *Esteban Santana Santana*
+
+* Run `Minitest.after_run` hooks when running `rails test`.
+
+ *Michael Grosser*
+
+* Run `before_configuration` callbacks as soon as application constant
+ inherits from `Rails::Application`.
+
+ Fixes #19880.
+
+ *Yuji Yaginuma*
+
* A generated app should not include Uglifier with `--skip-javascript` option.
*Ben Pickles*
@@ -17,7 +67,7 @@
*John Meehan*
-* Display name of the class defining the initializer along with the initializer
+* Display name of the class defining the initializer along with the initializer
name in the output of `rails initializers`.
Before:
diff --git a/railties/MIT-LICENSE b/railties/MIT-LICENSE
index 1f496cf280..f9e4444f07 100644
--- a/railties/MIT-LICENSE
+++ b/railties/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2016 David Heinemeier Hansson
+Copyright (c) 2004-2017 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/railties/Rakefile b/railties/Rakefile
index 202644fb26..680ed03f75 100644
--- a/railties/Rakefile
+++ b/railties/Rakefile
@@ -18,6 +18,7 @@ namespace :test do
"lib",
"#{File.dirname(__FILE__)}/../activesupport/lib",
"#{File.dirname(__FILE__)}/../actionpack/lib",
+ "#{File.dirname(__FILE__)}/../actionview/lib",
"#{File.dirname(__FILE__)}/../activemodel/lib"
]
ruby "-w", "-I#{dash_i.join ':'}", file
diff --git a/railties/bin/test b/railties/bin/test
new file mode 100755
index 0000000000..a7beb14b27
--- /dev/null
+++ b/railties/bin/test
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+
+COMPONENT_ROOT = File.expand_path("..", __dir__)
+require File.expand_path("../tools/test", COMPONENT_ROOT)
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index e9c96c7b43..ee48043a50 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -46,14 +46,14 @@ module Rails
def backtrace_cleaner
@backtrace_cleaner ||= begin
- # Relies on Active Support, so we have to lazy load to postpone definition until AS has been loaded
+ # Relies on Active Support, so we have to lazy load to postpone definition until Active Support has been loaded
require "rails/backtrace_cleaner"
Rails::BacktraceCleaner.new
end
end
# Returns a Pathname object of the current Rails project,
- # otherwise it returns nil if there is no project:
+ # otherwise it returns +nil+ if there is no project:
#
# Rails.root
# # => #<Pathname:/Users/someuser/some/path/project>
@@ -100,7 +100,7 @@ module Rails
end
# Returns a Pathname object of the public folder of the current
- # Rails project, otherwise it returns nil if there is no project:
+ # Rails project, otherwise it returns +nil+ if there is no project:
#
# Rails.public_path
# # => #<Pathname:/Users/someuser/some/path/project/public>
diff --git a/railties/lib/rails/app_loader.rb b/railties/lib/rails/app_loader.rb
index 107e47de55..525d5f0161 100644
--- a/railties/lib/rails/app_loader.rb
+++ b/railties/lib/rails/app_loader.rb
@@ -50,7 +50,7 @@ EOS
# If we exhaust the search there is no executable, this could be a
# call to generate a new application, so restore the original cwd.
- Dir.chdir(original_cwd) and return if Pathname.new(Dir.pwd).root?
+ Dir.chdir(original_cwd) && return if Pathname.new(Dir.pwd).root?
# Otherwise keep moving upwards in search of an executable.
Dir.chdir("..")
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 9c150965bf..f96432c89f 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -87,6 +87,7 @@ module Rails
super
Rails.app_class = base
add_lib_to_load_path!(find_root(base.called_from))
+ ActiveSupport.run_load_hooks(:before_configuration, base)
end
def instance
@@ -146,7 +147,6 @@ module Rails
def run_load_hooks! # :nodoc:
return self if @ran_load_hooks
@ran_load_hooks = true
- ActiveSupport.run_load_hooks(:before_configuration, self)
@initial_variable_values.each do |variable_name, value|
if INITIAL_VARIABLES.include?(variable_name)
@@ -274,7 +274,7 @@ module Rails
# Sends the initializers to the +initializer+ method defined in the
# Rails::Initializable module. Each Rails::Application class has its own
# set of initializers, as defined by the Initializable module.
- def initializer(name, opts={}, &block)
+ def initializer(name, opts = {}, &block)
self.class.initializer(name, opts, &block)
end
@@ -347,7 +347,7 @@ module Rails
# Initialize the application passing the given group. By default, the
# group is :default
- def initialize!(group=:default) #:nodoc:
+ def initialize!(group = :default) #:nodoc:
raise "Application has been already initialized." if @initialized
run_initializers(group, self)
@initialized = true
@@ -394,8 +394,8 @@ module Rails
shared_secrets = all_secrets["shared"]
env_secrets = all_secrets[Rails.env]
- secrets.merge!(shared_secrets.symbolize_keys) if shared_secrets
- secrets.merge!(env_secrets.symbolize_keys) if env_secrets
+ secrets.merge!(shared_secrets.deep_symbolize_keys) if shared_secrets
+ secrets.merge!(env_secrets.deep_symbolize_keys) if env_secrets
end
# Fallback to config.secret_key_base if secrets.secret_key_base isn't set
diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb
index 14c0a8cbe4..d070aca2dd 100644
--- a/railties/lib/rails/application/default_middleware_stack.rb
+++ b/railties/lib/rails/application/default_middleware_stack.rb
@@ -41,12 +41,11 @@ module Rails
middleware.use ::Rack::Runtime
middleware.use ::Rack::MethodOverride unless config.api_only
middleware.use ::ActionDispatch::RequestId
+ middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies
- # Must come after Rack::MethodOverride to properly log overridden methods
middleware.use ::Rails::Rack::Logger, config.log_tags
middleware.use ::ActionDispatch::ShowExceptions, show_exceptions_app
middleware.use ::ActionDispatch::DebugExceptions, app, config.debug_exception_response_format
- middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies
unless config.cache_classes
middleware.use ::ActionDispatch::Reloader, app.reloader
diff --git a/railties/lib/rails/application_controller.rb b/railties/lib/rails/application_controller.rb
index f8394b8e0a..a98e51fd28 100644
--- a/railties/lib/rails/application_controller.rb
+++ b/railties/lib/rails/application_controller.rb
@@ -2,7 +2,7 @@ class Rails::ApplicationController < ActionController::Base # :nodoc:
self.view_paths = File.expand_path("../templates", __FILE__)
layout "application"
- protected
+ private
def require_local!
unless local_request?
diff --git a/railties/lib/rails/cli.rb b/railties/lib/rails/cli.rb
index 26ef3822ba..973b746068 100644
--- a/railties/lib/rails/cli.rb
+++ b/railties/lib/rails/cli.rb
@@ -7,9 +7,11 @@ Rails::AppLoader.exec_app
require "rails/ruby_version_check"
Signal.trap("INT") { puts; exit(1) }
+require "rails/command"
+
if ARGV.first == "plugin"
ARGV.shift
- require "rails/commands/plugin"
+ Rails::Command.invoke :plugin, ARGV
else
- require "rails/commands/application"
+ Rails::Command.invoke :application, ARGV
end
diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb
index f7e1b5194c..9c4bd16aad 100644
--- a/railties/lib/rails/code_statistics.rb
+++ b/railties/lib/rails/code_statistics.rb
@@ -9,7 +9,7 @@ class CodeStatistics #:nodoc:
"Job tests",
"Integration tests"]
- HEADERS = {lines: " Lines", code_lines: " LOC", classes: "Classes", methods: "Methods"}
+ HEADERS = { lines: " Lines", code_lines: " LOC", classes: "Classes", methods: "Methods" }
def initialize(*pairs)
@pairs = pairs
@@ -32,7 +32,7 @@ class CodeStatistics #:nodoc:
private
def calculate_statistics
- Hash[@pairs.map{|pair| [pair.first, calculate_directory_statistics(pair.last)]}]
+ Hash[@pairs.map { |pair| [pair.first, calculate_directory_statistics(pair.last)] }]
end
def calculate_directory_statistics(directory, pattern = /^(?!\.).*?\.(rb|js|coffee|rake)$/)
@@ -70,7 +70,7 @@ class CodeStatistics #:nodoc:
end
def width_for(label)
- [@statistics.values.sum {|s| s.send(label) }.to_s.size, HEADERS[label].length].max
+ [@statistics.values.sum { |s| s.send(label) }.to_s.size, HEADERS[label].length].max
end
def print_header
@@ -106,7 +106,7 @@ class CodeStatistics #:nodoc:
code = calculate_code
tests = calculate_tests
- puts " Code LOC: #{code} Test LOC: #{tests} Code to Test Ratio: 1:#{sprintf("%.1f", tests.to_f/code)}"
+ puts " Code LOC: #{code} Test LOC: #{tests} Code to Test Ratio: 1:#{sprintf("%.1f", tests.to_f / code)}"
puts ""
end
end
diff --git a/railties/lib/rails/command.rb b/railties/lib/rails/command.rb
new file mode 100644
index 0000000000..ddb953543f
--- /dev/null
+++ b/railties/lib/rails/command.rb
@@ -0,0 +1,99 @@
+require "active_support"
+require "active_support/dependencies/autoload"
+require "active_support/core_ext/enumerable"
+require "active_support/core_ext/object/blank"
+require "active_support/core_ext/hash/transform_values"
+
+require "thor"
+
+module Rails
+ module Command
+ extend ActiveSupport::Autoload
+
+ autoload :Behavior
+ autoload :Base
+
+ include Behavior
+
+ class << self
+ def hidden_commands # :nodoc:
+ @hidden_commands ||= []
+ end
+
+ def environment # :nodoc:
+ ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
+ end
+
+ # Receives a namespace, arguments and the behavior to invoke the command.
+ def invoke(namespace, args = [], **config)
+ namespace = namespace.to_s
+ namespace = "help" if namespace.blank? || Thor::HELP_MAPPINGS.include?(namespace)
+ namespace = "version" if %w( -v --version ).include? namespace
+
+ if command = find_by_namespace(namespace)
+ command.perform(namespace, args, config)
+ else
+ find_by_namespace("rake").perform(namespace, args, config)
+ end
+ end
+
+ # Rails finds namespaces similar to thor, it only adds one rule:
+ #
+ # Command names must end with "_command.rb". This is required because Rails
+ # looks in load paths and loads the command just before it's going to be used.
+ #
+ # find_by_namespace :webrat, :rails, :integration
+ #
+ # Will search for the following commands:
+ #
+ # "rails:webrat", "webrat:integration", "webrat"
+ #
+ # Notice that "rails:commands:webrat" could be loaded as well, what
+ # Rails looks for is the first and last parts of the namespace.
+ def find_by_namespace(name) # :nodoc:
+ lookups = [ name, "rails:#{name}" ]
+
+ lookup(lookups)
+
+ namespaces = subclasses.index_by(&:namespace)
+ namespaces[(lookups & namespaces.keys).first]
+ end
+
+ # Returns the root of the Rails engine or app running the command.
+ def root
+ if defined?(ENGINE_ROOT)
+ Pathname.new(ENGINE_ROOT)
+ elsif defined?(APP_PATH)
+ Pathname.new(File.expand_path("../..", APP_PATH))
+ end
+ end
+
+ def print_commands # :nodoc:
+ sorted_groups.each { |b, n| print_list(b, n) }
+ end
+
+ def sorted_groups # :nodoc:
+ lookup!
+
+ groups = (subclasses - hidden_commands).group_by { |c| c.namespace.split(":").first }
+ groups.transform_values! { |commands| commands.flat_map(&:printing_commands).sort }
+
+ rails = groups.delete("rails")
+ [[ "rails", rails ]] + groups.sort.to_a
+ end
+
+ private
+ def command_type # :doc:
+ @command_type ||= "command"
+ end
+
+ def lookup_paths # :doc:
+ @lookup_paths ||= %w( rails/commands commands )
+ end
+
+ def file_lookup_paths # :doc:
+ @file_lookup_paths ||= [ "{#{lookup_paths.join(',')}}", "**", "*_command.rb" ]
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/command/actions.rb b/railties/lib/rails/command/actions.rb
new file mode 100644
index 0000000000..31b656ec31
--- /dev/null
+++ b/railties/lib/rails/command/actions.rb
@@ -0,0 +1,42 @@
+module Rails
+ module Command
+ module Actions
+ # Change to the application's path if there is no config.ru file in current directory.
+ # 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"))
+ end
+
+ if defined?(ENGINE_PATH)
+ def require_application_and_environment!
+ require ENGINE_PATH
+ end
+
+ def load_tasks
+ Rake.application.init("rails")
+ Rake.application.load_rakefile
+ end
+
+ def load_generators
+ engine = ::Rails::Engine.find(ENGINE_ROOT)
+ Rails::Generators.namespace = engine.railtie_namespace
+ engine.load_generators
+ end
+ else
+ def require_application_and_environment!
+ require APP_PATH
+ Rails.application.require_environment!
+ end
+
+ def load_tasks
+ Rails.application.load_tasks
+ end
+
+ def load_generators
+ Rails.application.load_generators
+ end
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/command/base.rb b/railties/lib/rails/command/base.rb
new file mode 100644
index 0000000000..7ae190433a
--- /dev/null
+++ b/railties/lib/rails/command/base.rb
@@ -0,0 +1,143 @@
+require "thor"
+require "erb"
+
+require "active_support/core_ext/string/filters"
+require "active_support/core_ext/string/inflections"
+
+require "rails/command/actions"
+
+module Rails
+ module Command
+ class Base < Thor
+ class Error < Thor::Error # :nodoc:
+ end
+
+ include Actions
+
+ class << self
+ # Returns true when the app is a Rails engine.
+ def engine?
+ defined?(ENGINE_ROOT)
+ end
+
+ # Tries to get the description from a USAGE file one folder above the command
+ # root.
+ def desc(usage = nil, description = nil, options = {})
+ if usage
+ super
+ else
+ @desc ||= ERB.new(File.read(usage_path)).result(binding) if usage_path
+ end
+ end
+
+ # Convenience method to get the namespace from the class name. It's the
+ # same as Thor default except that the Command at the end of the class
+ # is removed.
+ def namespace(name = nil)
+ if name
+ super
+ else
+ @namespace ||= super.chomp("_command").sub(/:command:/, ":")
+ end
+ end
+
+ # Convenience method to hide this command from the available ones when
+ # running rails command.
+ def hide_command!
+ Rails::Command.hidden_commands << self
+ end
+
+ def inherited(base) #:nodoc:
+ super
+
+ if base.name && base.name !~ /Base$/
+ Rails::Command.subclasses << base
+ end
+ end
+
+ def perform(command, args, config) # :nodoc:
+ command = nil if Thor::HELP_MAPPINGS.include?(args.first)
+
+ dispatch(command, args.dup, nil, config)
+ end
+
+ def printing_commands
+ namespace.sub(/^rails:/, "")
+ end
+
+ def executable
+ "bin/rails #{command_name}"
+ end
+
+ # Use Rails' default banner.
+ def banner(*)
+ "#{executable} #{arguments.map(&:usage).join(' ')} [options]".squish!
+ end
+
+ # Sets the base_name taking into account the current class namespace.
+ #
+ # Rails::Command::TestCommand.base_name # => 'rails'
+ def base_name
+ @base_name ||= begin
+ if base = name.to_s.split("::").first
+ base.underscore
+ end
+ end
+ end
+
+ # Return command name without namespaces.
+ #
+ # Rails::Command::TestCommand.command_name # => 'test'
+ def command_name
+ @command_name ||= begin
+ if command = name.to_s.split("::").last
+ command.chomp!("Command")
+ command.underscore
+ end
+ end
+ end
+
+ # Path to lookup a USAGE description in a file.
+ def usage_path
+ if default_command_root
+ path = File.join(default_command_root, "USAGE")
+ path if File.exist?(path)
+ end
+ end
+
+ # Default file root to place extra files a command might need, placed
+ # one folder above the command file.
+ #
+ # For a `Rails::Command::TestCommand` placed in `rails/command/test_command.rb`
+ # would return `rails/test`.
+ def default_command_root
+ path = File.expand_path(File.join("../commands", command_name), __dir__)
+ path if File.exist?(path)
+ end
+
+ private
+ # Allow the command method to be called perform.
+ def create_command(meth)
+ if meth == "perform"
+ alias_method command_name, meth
+ else
+ # Prevent exception about command without usage.
+ # Some commands define their documentation differently.
+ @usage ||= ""
+ @desc ||= ""
+
+ super
+ end
+ end
+ end
+
+ def help
+ if command_name = self.class.command_name
+ self.class.command_help(shell, command_name)
+ else
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/command/behavior.rb b/railties/lib/rails/command/behavior.rb
new file mode 100644
index 0000000000..4a92f72f16
--- /dev/null
+++ b/railties/lib/rails/command/behavior.rb
@@ -0,0 +1,123 @@
+require "active_support"
+
+module Rails
+ module Command
+ module Behavior #:nodoc:
+ extend ActiveSupport::Concern
+
+ class_methods do
+ # Remove the color from output.
+ def no_color!
+ Thor::Base.shell = Thor::Shell::Basic
+ end
+
+ # Track all command subclasses.
+ def subclasses
+ @subclasses ||= []
+ end
+
+ private
+
+ # This code is based directly on the Text gem implementation.
+ # Copyright (c) 2006-2013 Paul Battley, Michael Neumann, Tim Fletcher.
+ #
+ # Returns a value representing the "cost" of transforming str1 into str2.
+ def levenshtein_distance(str1, str2) # :doc:
+ s = str1
+ t = str2
+ n = s.length
+ m = t.length
+
+ return m if (0 == n)
+ return n if (0 == m)
+
+ d = (0..m).to_a
+ x = nil
+
+ # avoid duplicating an enumerable object in the loop
+ str2_codepoint_enumerable = str2.each_codepoint
+
+ str1.each_codepoint.with_index do |char1, i|
+ e = i + 1
+
+ str2_codepoint_enumerable.with_index do |char2, j|
+ cost = (char1 == char2) ? 0 : 1
+ x = [
+ d[j + 1] + 1, # insertion
+ e + 1, # deletion
+ d[j] + cost # substitution
+ ].min
+ d[j] = e
+ e = x
+ end
+
+ d[m] = x
+ end
+
+ x
+ end
+
+ # Prints a list of generators.
+ def print_list(base, namespaces)
+ return if namespaces.empty?
+ puts "#{base.camelize}:"
+
+ namespaces.each do |namespace|
+ puts(" #{namespace}")
+ end
+
+ puts
+ end
+
+ # Receives namespaces in an array and tries to find matching generators
+ # in the load path.
+ def lookup(namespaces)
+ paths = namespaces_to_paths(namespaces)
+
+ paths.each do |raw_path|
+ lookup_paths.each do |base|
+ path = "#{base}/#{raw_path}_#{command_type}"
+
+ begin
+ require path
+ return
+ rescue LoadError => e
+ raise unless e.message =~ /#{Regexp.escape(path)}$/
+ rescue Exception => e
+ warn "[WARNING] Could not load #{command_type} #{path.inspect}. Error: #{e.message}.\n#{e.backtrace.join("\n")}"
+ end
+ end
+ end
+ end
+
+ # This will try to load any command in the load path to show in help.
+ def lookup!
+ $LOAD_PATH.each do |base|
+ Dir[File.join(base, *file_lookup_paths)].each do |path|
+ begin
+ path = path.sub("#{base}/", "")
+ require path
+ rescue Exception
+ # No problem
+ end
+ end
+ end
+ end
+
+ # Convert namespaces to paths by replacing ":" for "/" and adding
+ # an extra lookup. For example, "rails:model" should be searched
+ # in both: "rails/model/model_generator" and "rails/model_generator".
+ def namespaces_to_paths(namespaces)
+ paths = []
+ namespaces.each do |namespace|
+ pieces = namespace.split(":")
+ paths << pieces.dup.push(pieces.last).join("/")
+ paths << pieces.join("/")
+ end
+ paths.uniq!
+ paths
+ end
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/command/environment_argument.rb b/railties/lib/rails/command/environment_argument.rb
new file mode 100644
index 0000000000..05eac34155
--- /dev/null
+++ b/railties/lib/rails/command/environment_argument.rb
@@ -0,0 +1,34 @@
+require "active_support"
+
+module Rails
+ module Command
+ module EnvironmentArgument #:nodoc:
+ extend ActiveSupport::Concern
+
+ included do
+ argument :environment, optional: true, banner: "environment"
+ end
+
+ private
+ def extract_environment_option_from_argument
+ if environment
+ self.options = options.merge(environment: acceptable_environment(environment))
+ elsif !options[:environment]
+ self.options = options.merge(environment: Rails::Command.environment)
+ end
+ end
+
+ def acceptable_environment(env = nil)
+ if available_environments.include? env
+ env
+ else
+ %w( production development test ).detect { |e| e =~ /^#{env}/ } || env
+ end
+ end
+
+ def available_environments
+ Dir["config/environments/*.rb"].map { |fname| File.basename(fname, ".*") }
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb
index d64b355aec..fff0119c65 100644
--- a/railties/lib/rails/commands.rb
+++ b/railties/lib/rails/commands.rb
@@ -1,4 +1,4 @@
-ARGV << "--help" if ARGV.empty?
+require "rails/command"
aliases = {
"g" => "generate",
@@ -13,6 +13,4 @@ aliases = {
command = ARGV.shift
command = aliases[command] || command
-require "rails/commands/commands_tasks"
-
-Rails::CommandsTasks.new(ARGV).run_command!(command)
+Rails::Command.invoke command, ARGV
diff --git a/railties/lib/rails/commands/application.rb b/railties/lib/rails/commands/application/application_command.rb
index f6e7771cf3..7675d3b3d1 100644
--- a/railties/lib/rails/commands/application.rb
+++ b/railties/lib/rails/commands/application/application_command.rb
@@ -11,7 +11,19 @@ module Rails
end
end
end
-end
-args = Rails::Generators::ARGVScrubber.new(ARGV).prepare!
-Rails::Generators::AppGenerator.start args
+ module Command
+ class ApplicationCommand < Base # :nodoc:
+ hide_command!
+
+ def help
+ perform # Punt help output to the generator.
+ end
+
+ def perform(*args)
+ Rails::Generators::AppGenerator.start \
+ Rails::Generators::ARGVScrubber.new(args).prepare!
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/commands/commands_tasks.rb b/railties/lib/rails/commands/commands_tasks.rb
deleted file mode 100644
index 02f6472330..0000000000
--- a/railties/lib/rails/commands/commands_tasks.rb
+++ /dev/null
@@ -1,180 +0,0 @@
-require "rails/commands/rake_proxy"
-
-module Rails
- # This is a class which takes in a rails command and initiates the appropriate
- # initiation sequence.
- #
- # Warning: This class mutates ARGV because some commands require manipulating
- # it before they are run.
- class CommandsTasks # :nodoc:
- include Rails::RakeProxy
-
- attr_reader :argv
-
- HELP_MESSAGE = <<-EOT
-Usage: rails COMMAND [ARGS]
-
-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")
- new Create a new Rails application. "rails new my_app" creates a
- new application called MyApp in "./my_app"
-
-All commands can be run with -h (or --help) for more information.
-
-In addition to those commands, there are:
-EOT
-
- ADDITIONAL_COMMANDS = [
- [ "destroy", 'Undo code generated with "generate" (short-cut alias: "d")' ],
- [ "plugin new", "Generates skeleton for developing a Rails plugin" ],
- [ "runner",
- 'Run a piece of code in the application environment (short-cut alias: "r")' ]
- ]
-
- COMMAND_WHITELIST = %w(plugin generate destroy console server dbconsole runner new version help test)
-
- def initialize(argv)
- @argv = argv
- end
-
- def run_command!(command)
- command = parse_command(command)
-
- if COMMAND_WHITELIST.include?(command)
- send(command)
- else
- run_rake_task(command)
- end
- end
-
- def plugin
- require_command!("plugin")
- end
-
- def generate
- generate_or_destroy(:generate)
- end
-
- def destroy
- generate_or_destroy(:destroy)
- end
-
- def console
- require_command!("console")
- options = Rails::Console.parse_arguments(argv)
-
- # RAILS_ENV needs to be set before config/application is required
- ENV["RAILS_ENV"] = options[:environment] if options[:environment]
-
- # shift ARGV so IRB doesn't freak
- shift_argv!
-
- require_application_and_environment!
- Rails::Console.start(Rails.application, options)
- end
-
- def server
- set_application_directory!
- require_command!("server")
-
- Rails::Server.new.tap do |server|
- # We need to require application after the server sets environment,
- # otherwise the --environment option given to the server won't propagate.
- require APP_PATH
- Dir.chdir(Rails.application.root)
- server.start
- end
- end
-
- def test
- require_command!("test")
- end
-
- def dbconsole
- require_command!("dbconsole")
- Rails::DBConsole.start
- end
-
- def runner
- require_command!("runner")
- end
-
- def new
- if %w(-h --help).include?(argv.first)
- require_command!("application")
- else
- exit_with_initialization_warning!
- end
- end
-
- def version
- argv.unshift "--version"
- require_command!("application")
- end
-
- def help
- write_help_message
- write_commands ADDITIONAL_COMMANDS + formatted_rake_tasks
- end
-
- private
-
- def exit_with_initialization_warning!
- puts "Can't initialize a new Rails application within the directory of another, please change to a non-Rails directory first.\n"
- puts "Type 'rails' for help."
- exit(1)
- end
-
- def shift_argv!
- argv.shift if argv.first && argv.first[0] != "-"
- end
-
- def require_command!(command)
- require "rails/commands/#{command}"
- end
-
- def generate_or_destroy(command)
- require "rails/generators"
- require_application_and_environment!
- Rails.application.load_generators
- require_command!(command)
- end
-
- # Change to the application's path if there is no config.ru file in current directory.
- # 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"))
- end
-
- def require_application_and_environment!
- require APP_PATH
- Rails.application.require_environment!
- end
-
- def write_help_message
- puts HELP_MESSAGE
- end
-
- def write_commands(commands)
- width = commands.map { |name, _| name.size }.max || 10
- commands.each { |command| printf(" %-#{width}s %s\n", *command) }
- end
-
- def parse_command(command)
- case command
- when "--version", "-v"
- "version"
- when "--help", "-h"
- "help"
- else
- command
- end
- end
- end
-end
diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console/console_command.rb
index e00887323e..62e3aa19df 100644
--- a/railties/lib/rails/commands/console.rb
+++ b/railties/lib/rails/commands/console/console_command.rb
@@ -1,12 +1,10 @@
-require "optparse"
require "irb"
require "irb/completion"
-require "rails/commands/console_helper"
+
+require "rails/command/environment_argument"
module Rails
class Console
- include ConsoleHelper
-
module BacktraceCleaner
def filter_backtrace(bt)
if result = super
@@ -15,26 +13,13 @@ module Rails
end
end
- class << self
- def parse_arguments(arguments)
- options = {}
-
- OptionParser.new do |opt|
- opt.banner = "Usage: rails console [environment] [options]"
- opt.on("-s", "--sandbox", "Rollback database modifications on exit.") { |v| options[:sandbox] = v }
- opt.on("-e", "--environment=name", String,
- "Specifies the environment to run this console under (test/development/production).",
- "Default: development") { |v| options[:environment] = v.strip }
- opt.parse!(arguments)
- end
-
- set_options_env(arguments, options)
- end
+ def self.start(*args)
+ new(*args).start
end
attr_reader :options, :app, :console
- def initialize(app, options={})
+ def initialize(app, options = {})
@app = app
@options = options
@@ -53,7 +38,7 @@ module Rails
end
def environment
- options[:environment] ||= super
+ options[:environment]
end
alias_method :environment?, :environment
@@ -77,4 +62,28 @@ module Rails
console.start
end
end
+
+ module Command
+ class ConsoleCommand < Base # :nodoc:
+ include EnvironmentArgument
+
+ class_option :sandbox, aliases: "-s", type: :boolean, default: false,
+ desc: "Rollback database modifications on exit."
+
+ class_option :environment, aliases: "-e", type: :string,
+ desc: "Specifies the environment to run this console under (test/development/production)."
+
+ 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
+ end
+ end
end
diff --git a/railties/lib/rails/commands/console_helper.rb b/railties/lib/rails/commands/console_helper.rb
deleted file mode 100644
index 0b7f1c4249..0000000000
--- a/railties/lib/rails/commands/console_helper.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-require "active_support/concern"
-
-module Rails
- module ConsoleHelper # :nodoc:
- extend ActiveSupport::Concern
-
- module ClassMethods
- def start(*args)
- new(*args).start
- end
-
- private
- def set_options_env(arguments, options)
- if arguments.first && arguments.first[0] != "-"
- env = arguments.first
- if available_environments.include? env
- options[:environment] = env
- else
- options[:environment] = %w(production development test).detect { |e| e =~ /^#{env}/ } || env
- end
- end
- options
- end
-
- def available_environments
- Dir["config/environments/*.rb"].map { |fname| File.basename(fname, ".*") }
- end
- end
-
- def environment
- ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
- end
- end
-end
diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole/dbconsole_command.rb
index d79a88b025..588fb06b15 100644
--- a/railties/lib/rails/commands/dbconsole.rb
+++ b/railties/lib/rails/commands/dbconsole/dbconsole_command.rb
@@ -1,58 +1,17 @@
-require "erb"
-require "yaml"
-require "optparse"
-require "rails/commands/console_helper"
+require "rails/command/environment_argument"
module Rails
class DBConsole
- include ConsoleHelper
-
- attr_reader :arguments
-
- class << self
- def parse_arguments(arguments)
- options = {}
-
- OptionParser.new do |opt|
- opt.banner = "Usage: rails dbconsole [environment] [options]"
- opt.on("-p", "--include-password", "Automatically provide the password from database.yml") do |v|
- options["include_password"] = true
- end
-
- opt.on("--mode [MODE]", ["html", "list", "line", "column"],
- "Automatically put the sqlite3 database in the specified mode (html, list, line, column).") do |mode|
- options["mode"] = mode
- end
-
- opt.on("--header") do |h|
- options["header"] = h
- end
-
- opt.on("-h", "--help", "Show this help message.") do
- puts opt
- exit
- end
-
- opt.on("-e", "--environment=name", String,
- "Specifies the environment to run this console under (test/development/production).",
- "Default: development"
- ) { |v| options[:environment] = v.strip }
-
- opt.parse!(arguments)
- abort opt.to_s unless (0..1).include?(arguments.size)
- end
-
- set_options_env(arguments, options)
- end
+ def self.start(*args)
+ new(*args).start
end
- def initialize(arguments = ARGV)
- @arguments = arguments
+ def initialize(options = {})
+ @options = options
end
def start
- options = self.class.parse_arguments(arguments)
- ENV["RAILS_ENV"] = options[:environment] || environment
+ ENV["RAILS_ENV"] = @options[:environment] || environment
case config["adapter"]
when /^(jdbc)?mysql/
@@ -69,7 +28,7 @@ module Rails
"sslkey" => "--ssl-key"
}.map { |opt, arg| "#{arg}=#{config[opt]}" if config[opt] }.compact
- if config["password"] && options["include_password"]
+ if config["password"] && @options["include_password"]
args << "--password=#{config['password']}"
elsif config["password"] && !config["password"].to_s.empty?
args << "-p"
@@ -83,14 +42,14 @@ module Rails
ENV["PGUSER"] = config["username"] if config["username"]
ENV["PGHOST"] = config["host"] if config["host"]
ENV["PGPORT"] = config["port"].to_s if config["port"]
- ENV["PGPASSWORD"] = config["password"].to_s if config["password"] && options["include_password"]
+ ENV["PGPASSWORD"] = config["password"].to_s if config["password"] && @options["include_password"]
find_cmd_and_exec("psql", config["database"])
when "sqlite3"
args = []
- args << "-#{options['mode']}" if options["mode"]
- args << "-header" if options["header"]
+ args << "-#{@options['mode']}" if @options["mode"]
+ args << "-header" if @options["header"]
args << File.expand_path(config["database"], Rails.respond_to?(:root) ? Rails.root : nil)
find_cmd_and_exec("sqlite3", *args)
@@ -100,7 +59,7 @@ module Rails
if config["username"]
logon = config["username"]
- logon << "/#{config['password']}" if config["password"] && options["include_password"]
+ logon << "/#{config['password']}" if config["password"] && @options["include_password"]
logon << "@#{config['database']}" if config["database"]
end
@@ -137,22 +96,22 @@ module Rails
end
def environment
- Rails.respond_to?(:env) ? Rails.env : super
+ Rails.respond_to?(:env) ? Rails.env : Rails::Command.environment
end
- protected
- def configurations
+ private
+ def configurations # :doc:
require APP_PATH
ActiveRecord::Base.configurations = Rails.application.config.database_configuration
ActiveRecord::Base.configurations
end
- def find_cmd_and_exec(commands, *args)
+ def find_cmd_and_exec(commands, *args) # :doc:
commands = Array(commands)
dirs_on_path = ENV["PATH"].to_s.split(File::PATH_SEPARATOR)
unless (ext = RbConfig::CONFIG["EXEEXT"]).empty?
- commands = commands.map{|cmd| "#{cmd}#{ext}"}
+ commands = commands.map { |cmd| "#{cmd}#{ext}" }
end
full_path_command = nil
@@ -170,4 +129,27 @@ module Rails
end
end
end
+
+ module Command
+ class DbconsoleCommand < Base # :nodoc:
+ include EnvironmentArgument
+
+ class_option :include_password, aliases: "-p", type: :boolean,
+ desc: "Automatically provide the password from database.yml"
+
+ 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 :environment, aliases: "-e", type: :string,
+ desc: "Specifies the environment to run this console under (test/development/production)."
+
+ def perform
+ extract_environment_option_from_argument
+
+ Rails::DBConsole.start(options)
+ end
+ end
+ end
end
diff --git a/railties/lib/rails/commands/destroy.rb b/railties/lib/rails/commands/destroy.rb
deleted file mode 100644
index 71c8c5e526..0000000000
--- a/railties/lib/rails/commands/destroy.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-require "rails/generators"
-
-#if no argument/-h/--help is passed to rails destroy command, then
-#it generates the help associated.
-if [nil, "-h", "--help"].include?(ARGV.first)
- Rails::Generators.help "destroy"
- exit
-end
-
-name = ARGV.shift
-Rails::Generators.invoke name, ARGV, behavior: :revoke, destination_root: Rails.root
diff --git a/railties/lib/rails/commands/destroy/destroy_command.rb b/railties/lib/rails/commands/destroy/destroy_command.rb
new file mode 100644
index 0000000000..5b552b2070
--- /dev/null
+++ b/railties/lib/rails/commands/destroy/destroy_command.rb
@@ -0,0 +1,21 @@
+require "rails/generators"
+
+module Rails
+ module Command
+ class DestroyCommand < Base # :nodoc:
+ def help
+ Rails::Generators.help self.class.command_name
+ end
+
+ def perform(*)
+ generator = args.shift
+ return help unless generator
+
+ require_application_and_environment!
+ Rails.application.load_generators
+
+ Rails::Generators.invoke generator, args, behavior: :revoke, destination_root: Rails.root
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/commands/generate.rb b/railties/lib/rails/commands/generate.rb
deleted file mode 100644
index ba6f14073e..0000000000
--- a/railties/lib/rails/commands/generate.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-require "rails/generators"
-
-#if no argument/-h/--help is passed to rails generate command, then
-#it generates the help associated.
-if [nil, "-h", "--help"].include?(ARGV.first)
- Rails::Generators.help "generate"
- exit
-end
-
-name = ARGV.shift
-
-root = defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root
-Rails::Generators.invoke name, ARGV, behavior: :invoke, destination_root: root
diff --git a/railties/lib/rails/commands/generate/generate_command.rb b/railties/lib/rails/commands/generate/generate_command.rb
new file mode 100644
index 0000000000..aa8dab71b0
--- /dev/null
+++ b/railties/lib/rails/commands/generate/generate_command.rb
@@ -0,0 +1,23 @@
+require "rails/generators"
+
+module Rails
+ module Command
+ class GenerateCommand < Base # :nodoc:
+ def help
+ Rails::Generators.help self.class.command_name
+ end
+
+ def perform(*)
+ generator = args.shift
+ return help unless generator
+
+ require_application_and_environment!
+ load_generators
+
+ ARGV.shift
+
+ Rails::Generators.invoke generator, args, behavior: :invoke, destination_root: Rails::Command.root
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/commands/help/USAGE b/railties/lib/rails/commands/help/USAGE
new file mode 100644
index 0000000000..348f41861f
--- /dev/null
+++ b/railties/lib/rails/commands/help/USAGE
@@ -0,0 +1,27 @@
+Usage: bin/rails COMMAND [args] [options]
+<% if engine? %>
+The common Rails commands available for engines are:
+ generate Generate new code (short-cut alias: "g")
+ destroy Undo code generated with "generate" (short-cut alias: "d")
+ test Run tests (short-cut alias: "t")
+
+All commands can be run with -h for more information.
+
+If you want to run any commands that need to be run in context
+of the application, like `bin/rails server` or `bin/rails console`,
+you should do it from the application's directory (typically test/dummy).
+<% else %>
+The most common rails commands are:
+ generate Generate new code (short-cut alias: "g")
+ console Start the Rails console (short-cut alias: "c")
+ 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")
+ new Create a new Rails application. "rails new my_app" creates a
+ new application called MyApp in "./my_app"
+
+All commands can be run with -h (or --help) for more information.
+<% end %>
+In addition to those commands, there are:
+
diff --git a/railties/lib/rails/commands/help/help_command.rb b/railties/lib/rails/commands/help/help_command.rb
new file mode 100644
index 0000000000..90d37217fc
--- /dev/null
+++ b/railties/lib/rails/commands/help/help_command.rb
@@ -0,0 +1,13 @@
+module Rails
+ module Command
+ class HelpCommand < Base # :nodoc:
+ hide_command!
+
+ def help(*)
+ puts self.class.desc
+
+ Rails::Command.print_commands
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/commands/new/new_command.rb b/railties/lib/rails/commands/new/new_command.rb
new file mode 100644
index 0000000000..74d1fa5021
--- /dev/null
+++ b/railties/lib/rails/commands/new/new_command.rb
@@ -0,0 +1,15 @@
+module Rails
+ module Command
+ class NewCommand < Base # :nodoc:
+ def help
+ Rails::Command.invoke :application, [ "--help" ]
+ end
+
+ def perform(*)
+ puts "Can't initialize a new Rails application within the directory of another, please change to a non-Rails directory first.\n"
+ puts "Type 'rails' for help."
+ exit 1
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/commands/plugin.rb b/railties/lib/rails/commands/plugin.rb
deleted file mode 100644
index 56413aa7b0..0000000000
--- a/railties/lib/rails/commands/plugin.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-if ARGV.first != "new"
- ARGV[0] = "--help"
-else
- ARGV.shift
- unless ARGV.delete("--no-rc")
- customrc = ARGV.index{ |x| x.include?("--rc=") }
- railsrc = if customrc
- File.expand_path(ARGV.delete_at(customrc).gsub(/--rc=/, ""))
- else
- File.join(File.expand_path("~"), ".railsrc")
- end
-
- if File.exist?(railsrc)
- extra_args_string = File.read(railsrc)
- extra_args = extra_args_string.split(/\n+/).flat_map(&:split)
- puts "Using #{extra_args.join(" ")} from #{railsrc}"
- ARGV.insert(1, *extra_args)
- end
- end
-end
-
-require "rails/generators"
-require "rails/generators/rails/plugin/plugin_generator"
-Rails::Generators::PluginGenerator.start
diff --git a/railties/lib/rails/commands/plugin/plugin_command.rb b/railties/lib/rails/commands/plugin/plugin_command.rb
new file mode 100644
index 0000000000..b40ab006af
--- /dev/null
+++ b/railties/lib/rails/commands/plugin/plugin_command.rb
@@ -0,0 +1,43 @@
+module Rails
+ module Command
+ class PluginCommand < Base # :nodoc:
+ hide_command!
+
+ def help
+ run_plugin_generator %w( --help )
+ end
+
+ def self.banner(*) # :nodoc:
+ "#{executable} new [options]"
+ end
+
+ class_option :rc, type: :string, default: File.join("~", ".railsrc"),
+ desc: "Initialize the plugin command with previous defaults. Uses .railsrc in your home directory by default."
+
+ class_option :no_rc, desc: "Skip evaluating .railsrc."
+
+ def perform(type = nil, *plugin_args)
+ plugin_args << "--help" unless type == "new"
+
+ unless options.key?("no_rc") # Thor's not so indifferent access hash.
+ railsrc = File.expand_path(options[:rc])
+
+ if File.exist?(railsrc)
+ extra_args = File.read(railsrc).split(/\n+/).flat_map(&:split)
+ puts "Using #{extra_args.join(" ")} from #{railsrc}"
+ plugin_args.insert(1, *extra_args)
+ end
+ end
+
+ run_plugin_generator plugin_args
+ end
+
+ private
+ def run_plugin_generator(plugin_args)
+ require "rails/generators"
+ require "rails/generators/rails/plugin/plugin_generator"
+ Rails::Generators::PluginGenerator.start plugin_args
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/commands/rake/rake_command.rb b/railties/lib/rails/commands/rake/rake_command.rb
new file mode 100644
index 0000000000..075b1fd23d
--- /dev/null
+++ b/railties/lib/rails/commands/rake/rake_command.rb
@@ -0,0 +1,49 @@
+module Rails
+ module Command
+ class RakeCommand < Base # :nodoc:
+ extend Rails::Command::Actions
+
+ namespace "rake"
+
+ class << self
+ def printing_commands
+ formatted_rake_tasks.map(&:first)
+ end
+
+ def perform(task, *)
+ require_rake
+
+ ARGV.unshift(task) # Prepend the task, so Rake knows how to run it.
+
+ Rake.application.standard_exception_handling do
+ Rake.application.init("rails")
+ Rake.application.load_rakefile
+ Rake.application.top_level
+ end
+ end
+
+ private
+ def rake_tasks
+ require_rake
+
+ return @rake_tasks if defined?(@rake_tasks)
+
+ require_application_and_environment!
+
+ Rake::TaskManager.record_task_metadata = true
+ Rake.application.instance_variable_set(:@name, "rails")
+ load_tasks
+ @rake_tasks = Rake.application.tasks.select(&:comment)
+ end
+
+ def formatted_rake_tasks
+ rake_tasks.map { |t| [ t.name_with_args, t.comment ] }
+ end
+
+ def require_rake
+ require "rake" # Defer booting Rake until we know it's needed.
+ end
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/commands/rake_proxy.rb b/railties/lib/rails/commands/rake_proxy.rb
deleted file mode 100644
index b9e50f3e5a..0000000000
--- a/railties/lib/rails/commands/rake_proxy.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-require "active_support"
-
-module Rails
- module RakeProxy #:nodoc:
- private
- def run_rake_task(command)
- require_rake
-
- ARGV.unshift(command) # Prepend the command, so Rake knows how to run it.
-
- Rake.application.standard_exception_handling do
- Rake.application.init("rails")
- Rake.application.load_rakefile
- Rake.application.top_level
- end
- end
-
- def rake_tasks
- require_rake
-
- return @rake_tasks if defined?(@rake_tasks)
-
- ActiveSupport::Deprecation.silence do
- require_application_and_environment!
- end
-
- Rake::TaskManager.record_task_metadata = true
- Rake.application.instance_variable_set(:@name, "rails")
- Rails.application.load_tasks
- @rake_tasks = Rake.application.tasks.select(&:comment)
- end
-
- def formatted_rake_tasks
- rake_tasks.map { |t| [ t.name_with_args, t.comment ] }
- end
-
- def require_rake
- require "rake" # Defer booting Rake until we know it's needed.
- end
- end
-end
diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb
deleted file mode 100644
index b74addf587..0000000000
--- a/railties/lib/rails/commands/runner.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-require "optparse"
-
-options = { environment: (ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development").dup }
-code_or_file = nil
-command = "bin/rails runner"
-
-if ARGV.first.nil?
- ARGV.push "-h"
-end
-
-ARGV.clone.options do |opts|
- opts.banner = "Usage: rails runner [options] [<'Some.ruby(code)'> | <filename.rb>]"
-
- opts.separator ""
-
- opts.on("-e", "--environment=name", String,
- "Specifies the environment for the runner to operate under (test/development/production).",
- "Default: development") { |v| options[:environment] = v }
-
- opts.separator ""
-
- opts.on("-h", "--help",
- "Show this help message.") { $stdout.puts opts; exit }
-
- opts.separator ""
- opts.separator "Examples: "
-
- opts.separator " rails runner 'puts Rails.env'"
- opts.separator " This runs the code `puts Rails.env` after loading the app"
- opts.separator ""
- opts.separator " rails runner path/to/filename.rb"
- opts.separator " This runs the Ruby file located at `path/to/filename.rb` after loading the app"
-
- if RbConfig::CONFIG["host_os"] !~ /mswin|mingw/
- opts.separator ""
- opts.separator "You can also use runner as a shebang line for your executables:"
- opts.separator " -------------------------------------------------------------"
- opts.separator " #!/usr/bin/env #{File.expand_path(command)}"
- opts.separator ""
- opts.separator " Product.all.each { |p| p.price *= 2 ; p.save! }"
- opts.separator " -------------------------------------------------------------"
- end
-
- opts.order! { |o| code_or_file ||= o } rescue retry
-end
-
-ARGV.delete(code_or_file)
-
-ENV["RAILS_ENV"] = options[:environment]
-
-require APP_PATH
-Rails.application.require_environment!
-Rails.application.load_runner
-
-if code_or_file.nil?
- $stderr.puts "Run '#{command} -h' for help."
- exit 1
-elsif File.exist?(code_or_file)
- $0 = code_or_file
- Kernel.load code_or_file
-else
- begin
- eval(code_or_file, binding, __FILE__, __LINE__)
- rescue SyntaxError, NameError => e
- $stderr.puts "Please specify a valid ruby command or the path of a script to run."
- $stderr.puts "Run '#{command} -h' for help."
- $stderr.puts
- $stderr.puts e
- exit 1
- end
-end
diff --git a/railties/lib/rails/commands/runner/USAGE b/railties/lib/rails/commands/runner/USAGE
new file mode 100644
index 0000000000..b2a6e8493d
--- /dev/null
+++ b/railties/lib/rails/commands/runner/USAGE
@@ -0,0 +1,17 @@
+Examples:
+
+Run `puts Rails.env` after loading the app:
+
+ <%= executable %> 'puts Rails.env'
+
+Run the Ruby file located at `path/to/filename.rb` after loading the app:
+
+ <%= executable %> path/to/filename.rb
+
+<% unless Gem.win_platform? %>
+You can also use the runner command as a shebang line for your executables:
+
+ #!/usr/bin/env <%= File.expand_path(executable) %>
+
+ Product.all.each { |p| p.price *= 2 ; p.save! }
+<% end %>
diff --git a/railties/lib/rails/commands/runner/runner_command.rb b/railties/lib/rails/commands/runner/runner_command.rb
new file mode 100644
index 0000000000..4989a7837d
--- /dev/null
+++ b/railties/lib/rails/commands/runner/runner_command.rb
@@ -0,0 +1,46 @@
+module Rails
+ module Command
+ class RunnerCommand < Base # :nodoc:
+ class_option :environment, aliases: "-e", type: :string,
+ default: Rails::Command.environment.dup,
+ desc: "The environment for the runner to operate under (test/development/production)"
+
+ def help
+ super
+ puts self.class.desc
+ end
+
+ def self.banner(*)
+ "#{super} [<'Some.ruby(code)'> | <filename.rb>]"
+ end
+
+ def perform(code_or_file = nil, *file_argv)
+ unless code_or_file
+ help
+ exit 1
+ end
+
+ ENV["RAILS_ENV"] = options[:environment]
+
+ require_application_and_environment!
+ Rails.application.load_runner
+
+ if File.exist?(code_or_file)
+ $0 = code_or_file
+ ARGV.replace(file_argv)
+ Kernel.load code_or_file
+ else
+ begin
+ eval(code_or_file, binding, __FILE__, __LINE__)
+ rescue SyntaxError, NameError => error
+ $stderr.puts "Please specify a valid ruby command or the path of a script to run."
+ $stderr.puts "Run '#{self.class.executable} -h' for help."
+ $stderr.puts
+ $stderr.puts error
+ exit 1
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
deleted file mode 100644
index 0339849bfe..0000000000
--- a/railties/lib/rails/commands/server.rb
+++ /dev/null
@@ -1,139 +0,0 @@
-require "fileutils"
-require "optparse"
-require "action_dispatch"
-require "rails"
-require "rails/dev_caching"
-
-module Rails
- class Server < ::Rack::Server
- class Options
- DEFAULT_PID_PATH = File.expand_path("tmp/pids/server.pid").freeze
-
- def parse!(args)
- args, options = args.dup, {}
-
- option_parser(options).parse! args
-
- options[:log_stdout] = options[:daemonize].blank? && (options[:environment] || Rails.env) == "development"
- options[:server] = args.shift
- options
- end
-
- private
-
- def option_parser(options)
- OptionParser.new do |opts|
- opts.banner = "Usage: rails server [mongrel, thin etc] [options]"
- opts.on("-p", "--port=port", Integer,
- "Runs Rails on the specified port.", "Default: 3000") { |v| options[:Port] = v }
- opts.on("-b", "--binding=IP", String,
- "Binds Rails to the specified IP.", "Default: localhost") { |v| options[:Host] = v }
- opts.on("-c", "--config=file", String,
- "Uses a custom rackup configuration.") { |v| options[:config] = v }
- opts.on("-d", "--daemon", "Runs server as a Daemon.") { options[:daemonize] = true }
- opts.on("-e", "--environment=name", String,
- "Specifies the environment to run this server under (test/development/production).",
- "Default: development") { |v| options[:environment] = v }
- opts.on("-P", "--pid=pid", String,
- "Specifies the PID file.",
- "Default: tmp/pids/server.pid") { |v| options[:pid] = v }
- opts.on("-C", "--[no-]dev-caching",
- "Specifies whether to perform caching in development.",
- "true or false") { |v| options[:caching] = v }
-
- opts.separator ""
-
- opts.on("-h", "--help", "Shows this help message.") { puts opts; exit }
- end
- end
- end
-
- def initialize(*)
- super
- set_environment
- end
-
- # TODO: this is no longer required but we keep it for the moment to support older config.ru files.
- def app
- @app ||= begin
- app = super
- app.respond_to?(:to_app) ? app.to_app : app
- end
- end
-
- def opt_parser
- Options.new
- end
-
- def set_environment
- ENV["RAILS_ENV"] ||= options[:environment]
- end
-
- def start
- print_boot_information
- trap(:INT) { exit }
- create_tmp_directories
- setup_dev_caching
- log_to_stdout if options[:log_stdout]
-
- super
- ensure
- # The '-h' option calls exit before @options is set.
- # If we call 'options' with it unset, we get double help banners.
- puts "Exiting" unless @options && options[:daemonize]
- end
-
- def middleware
- Hash.new([])
- end
-
- def default_options
- super.merge(
- Port: ENV.fetch("PORT", 3000).to_i,
- Host: ENV.fetch("HOST", "localhost").dup,
- DoNotReverseLookup: true,
- environment: (ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development").dup,
- daemonize: false,
- caching: nil,
- pid: Options::DEFAULT_PID_PATH,
- restart_cmd: restart_command)
- end
-
- private
-
- def setup_dev_caching
- if options[:environment] == "development"
- Rails::DevCaching.enable_by_argument(options[:caching])
- end
- end
-
- def print_boot_information
- url = "#{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}"
- puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}"
- puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}"
- puts "=> Run `rails server -h` for more startup options"
- end
-
- def create_tmp_directories
- %w(cache pids sockets).each do |dir_to_make|
- FileUtils.mkdir_p(File.join(Rails.root, "tmp", dir_to_make))
- end
- end
-
- def log_to_stdout
- wrapped_app # touch the app so the logger is set up
-
- console = ActiveSupport::Logger.new(STDOUT)
- console.formatter = Rails.logger.formatter
- console.level = Rails.logger.level
-
- unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDOUT)
- Rails.logger.extend(ActiveSupport::Logger.broadcast(console))
- end
- end
-
- def restart_command
- "bin/rails server #{ARGV.join(' ')}"
- end
- end
-end
diff --git a/railties/lib/rails/commands/server/server_command.rb b/railties/lib/rails/commands/server/server_command.rb
new file mode 100644
index 0000000000..15c636103b
--- /dev/null
+++ b/railties/lib/rails/commands/server/server_command.rb
@@ -0,0 +1,173 @@
+require "fileutils"
+require "optparse"
+require "action_dispatch"
+require "rails"
+require "rails/dev_caching"
+
+module Rails
+ class Server < ::Rack::Server
+ class Options
+ def parse!(args)
+ Rails::Command::ServerCommand.new([], args).server_options
+ end
+ end
+
+ def initialize(options = nil)
+ @default_options = options || {}
+ super(@default_options)
+ set_environment
+ end
+
+ # TODO: this is no longer required but we keep it for the moment to support older config.ru files.
+ def app
+ @app ||= begin
+ app = super
+ app.respond_to?(:to_app) ? app.to_app : app
+ end
+ end
+
+ def opt_parser
+ Options.new
+ end
+
+ def set_environment
+ ENV["RAILS_ENV"] ||= options[:environment]
+ end
+
+ def start
+ print_boot_information
+ trap(:INT) { exit }
+ create_tmp_directories
+ setup_dev_caching
+ log_to_stdout if options[:log_stdout]
+
+ super
+ ensure
+ # The '-h' option calls exit before @options is set.
+ # If we call 'options' with it unset, we get double help banners.
+ puts "Exiting" unless @options && options[:daemonize]
+ end
+
+ def middleware
+ Hash.new([])
+ end
+
+ def default_options
+ super.merge(@default_options)
+ end
+
+ private
+ def setup_dev_caching
+ if options[:environment] == "development"
+ Rails::DevCaching.enable_by_argument(options[:caching])
+ end
+ end
+
+ def print_boot_information
+ url = "#{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}"
+ puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}"
+ puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}"
+ puts "=> Run `rails server -h` for more startup options"
+ end
+
+ def create_tmp_directories
+ %w(cache pids sockets).each do |dir_to_make|
+ FileUtils.mkdir_p(File.join(Rails.root, "tmp", dir_to_make))
+ end
+ end
+
+ def log_to_stdout
+ wrapped_app # touch the app so the logger is set up
+
+ console = ActiveSupport::Logger.new(STDOUT)
+ console.formatter = Rails.logger.formatter
+ console.level = Rails.logger.level
+
+ unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDOUT)
+ Rails.logger.extend(ActiveSupport::Logger.broadcast(console))
+ end
+ end
+
+ def restart_command
+ "bin/rails server #{ARGV.join(' ')}"
+ end
+ end
+
+ module Command
+ class ServerCommand < Base # :nodoc:
+ DEFAULT_PID_PATH = File.expand_path("tmp/pids/server.pid").freeze
+
+ class_option :port, aliases: "-p", type: :numeric,
+ desc: "Runs Rails on the specified port.", banner: :port, default: 3000
+ class_option :binding, aliases: "-b", type: :string, default: "localhost",
+ desc: "Binds Rails to the specified IP.", banner: :IP
+ class_option :config, aliases: "-c", type: :string, default: "config.ru",
+ desc: "Uses a custom rackup configuration.", banner: :file
+ class_option :daemon, aliases: "-d", type: :boolean, default: false,
+ desc: "Runs server as a Daemon."
+ class_option :environment, aliases: "-e", type: :string,
+ desc: "Specifies the environment to run this server under (development/test/production).", banner: :name
+ class_option :pid, aliases: "-P", type: :string, default: DEFAULT_PID_PATH,
+ desc: "Specifies the PID file."
+ class_option "dev-caching", aliases: "-C", type: :boolean, default: nil,
+ desc: "Specifies whether to perform caching in development."
+
+ def initialize(args = [], local_options = {}, config = {})
+ @original_options = local_options
+ super
+ @server = self.args.shift
+ @log_stdout = options[:daemon].blank? && (options[:environment] || Rails.env) == "development"
+ end
+
+ def perform
+ set_application_directory!
+ Rails::Server.new(server_options).tap do |server|
+ # Require application after server sets environment to propagate
+ # the --environment option.
+ require APP_PATH
+ Dir.chdir(Rails.application.root)
+ server.start
+ end
+ end
+
+ no_commands do
+ def server_options
+ {
+ server: @server,
+ log_stdout: @log_stdout,
+ Port: port,
+ Host: host,
+ DoNotReverseLookup: true,
+ config: options[:config],
+ environment: environment,
+ daemonize: options[:daemon],
+ pid: options[:pid],
+ caching: options["dev-caching"],
+ restart_cmd: restart_command
+ }
+ end
+ end
+
+ private
+ def port
+ ENV.fetch("PORT", options[:port]).to_i
+ end
+
+ def host
+ ENV.fetch("HOST", options[:binding])
+ end
+
+ def environment
+ options[:environment] || Rails::Command.environment
+ end
+
+ def restart_command
+ "bin/rails server #{@server} #{@original_options.join(" ")}"
+ end
+
+ def self.banner(*)
+ "rails server [puma, thin etc] [options]"
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/commands/test.rb b/railties/lib/rails/commands/test.rb
deleted file mode 100644
index 219c2fa4e0..0000000000
--- a/railties/lib/rails/commands/test.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-require "rails/test_unit/minitest_plugin"
-
-if defined?(ENGINE_ROOT)
- $: << File.expand_path("test", ENGINE_ROOT)
-else
- $: << File.expand_path("../../test", APP_PATH)
-end
-
-exit Minitest.run(ARGV)
diff --git a/railties/lib/rails/commands/test/test_command.rb b/railties/lib/rails/commands/test/test_command.rb
new file mode 100644
index 0000000000..7bf8f61137
--- /dev/null
+++ b/railties/lib/rails/commands/test/test_command.rb
@@ -0,0 +1,20 @@
+require "rails/command"
+require "rails/test_unit/minitest_plugin"
+
+module Rails
+ module Command
+ class TestCommand < Base # :nodoc:
+ def help
+ perform # Hand over help printing to minitest.
+ end
+
+ def perform(*)
+ $LOAD_PATH << Rails::Command.root.join("test")
+
+ Minitest.run_via[:rails] = true
+
+ require "active_support/testing/autorun"
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/commands/version/version_command.rb b/railties/lib/rails/commands/version/version_command.rb
new file mode 100644
index 0000000000..ac745594ee
--- /dev/null
+++ b/railties/lib/rails/commands/version/version_command.rb
@@ -0,0 +1,9 @@
+module Rails
+ module Command
+ class VersionCommand < Base # :nodoc:
+ def perform
+ Rails::Command.invoke :application, [ "--version" ]
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb
index 7dfab969e8..fc7d4909f6 100644
--- a/railties/lib/rails/configuration.rb
+++ b/railties/lib/rails/configuration.rb
@@ -91,8 +91,8 @@ module Rails
attr_reader :hidden_namespaces
def initialize
- @aliases = Hash.new { |h,k| h[k] = {} }
- @options = Hash.new { |h,k| h[k] = {} }
+ @aliases = Hash.new { |h, k| h[k] = {} }
+ @options = Hash.new { |h, k| h[k] = {} }
@fallbacks = {}
@templates = []
@colorize_logging = true
diff --git a/railties/lib/rails/console/app.rb b/railties/lib/rails/console/app.rb
index 541d5e3dad..affadc8e09 100644
--- a/railties/lib/rails/console/app.rb
+++ b/railties/lib/rails/console/app.rb
@@ -5,7 +5,7 @@ module Rails
module ConsoleMethods
# reference the global "app" instance, created on demand. To recreate the
# instance, pass a non-false value as the parameter.
- def app(create=false)
+ def app(create = false)
@app_integration_instance = nil if create
@app_integration_instance ||= new_session do |sess|
sess.host! "www.example.com"
@@ -27,7 +27,7 @@ module Rails
end
# reloads the environment
- def reload!(print=true)
+ def reload!(print = true)
puts "Reloading..." if print
Rails.application.reloader.reload!
true
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index 363c65b186..2dd1fb3273 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -337,7 +337,7 @@ module Rails
# == Loading priority
#
# In order to change engine's priority you can use +config.railties_order+ in the main application.
- # It will affect the priority of loading views, helpers, assets and all the other files
+ # It will affect the priority of loading views, helpers, assets, and all the other files
# related to engine or application.
#
# # load Blog::Engine with highest priority, followed by application and other railties
@@ -402,7 +402,7 @@ module Rails
end
unless mod.respond_to?(:railtie_routes_url_helpers)
- define_method(:railtie_routes_url_helpers) {|include_path_helpers = true| railtie.routes.url_helpers(include_path_helpers) }
+ define_method(:railtie_routes_url_helpers) { |include_path_helpers = true| railtie.routes.url_helpers(include_path_helpers) }
end
end
end
@@ -436,7 +436,7 @@ module Rails
# Load console and invoke the registered hooks.
# Check <tt>Rails::Railtie.console</tt> for more info.
- def load_console(app=self)
+ def load_console(app = self)
require "rails/console/app"
require "rails/console/helpers"
run_console_blocks(app)
@@ -445,14 +445,14 @@ module Rails
# Load Rails runner and invoke the registered hooks.
# Check <tt>Rails::Railtie.runner</tt> for more info.
- def load_runner(app=self)
+ def load_runner(app = self)
run_runner_blocks(app)
self
end
# Load Rake, railties tasks and invoke the registered hooks.
# Check <tt>Rails::Railtie.rake_tasks</tt> for more info.
- def load_tasks(app=self)
+ def load_tasks(app = self)
require "rake"
run_tasks_blocks(app)
self
@@ -460,7 +460,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)
+ def load_generators(app = self)
require "rails/generators"
run_generators_blocks(app)
Rails::Generators.configure!(app.config.generators)
@@ -590,8 +590,8 @@ module Rails
initializer :add_view_paths do
views = paths["app/views"].existent
unless views.empty?
- ActiveSupport.on_load(:action_controller){ prepend_view_path(views) if respond_to?(:prepend_view_path) }
- ActiveSupport.on_load(:action_mailer){ prepend_view_path(views) }
+ ActiveSupport.on_load(:action_controller) { prepend_view_path(views) if respond_to?(:prepend_view_path) }
+ ActiveSupport.on_load(:action_mailer) { prepend_view_path(views) }
end
end
@@ -643,22 +643,24 @@ module Rails
protected
- def load_config_initializer(initializer)
- ActiveSupport::Notifications.instrument("load_config_initializer.railties", initializer: initializer) do
- load(initializer)
- end
- end
-
def run_tasks_blocks(*) #:nodoc:
super
paths["lib/tasks"].existent.sort.each { |ext| load(ext) }
end
- def has_migrations? #:nodoc:
+ private
+
+ def load_config_initializer(initializer) # :doc:
+ ActiveSupport::Notifications.instrument("load_config_initializer.railties", initializer: initializer) do
+ load(initializer)
+ end
+ end
+
+ def has_migrations?
paths["db/migrate"].existent.any?
end
- def self.find_root_with_flag(flag, root_path, default=nil) #:nodoc:
+ def self.find_root_with_flag(flag, root_path, default = nil) #:nodoc:
while root_path && File.directory?(root_path) && !File.exist?("#{root_path}/#{flag}")
parent = File.dirname(root_path)
@@ -671,24 +673,22 @@ module Rails
Pathname.new File.realpath root
end
- def default_middleware_stack #:nodoc:
+ def default_middleware_stack
ActionDispatch::MiddlewareStack.new
end
- def _all_autoload_once_paths #:nodoc:
+ def _all_autoload_once_paths
config.autoload_once_paths
end
- def _all_autoload_paths #:nodoc:
+ def _all_autoload_paths
@_all_autoload_paths ||= (config.autoload_paths + config.eager_load_paths + config.autoload_once_paths).uniq
end
- def _all_load_paths #:nodoc:
+ def _all_load_paths
@_all_load_paths ||= (config.paths.load_paths + _all_autoload_paths).uniq
end
- private
-
def build_request(env)
env.merge!(env_config)
req = ActionDispatch::Request.new env
diff --git a/railties/lib/rails/engine/commands.rb b/railties/lib/rails/engine/commands.rb
index dfbeea36b8..a23ae44b0b 100644
--- a/railties/lib/rails/engine/commands.rb
+++ b/railties/lib/rails/engine/commands.rb
@@ -1,6 +1,4 @@
-require "rails/engine/commands_tasks"
-
-ARGV << "--help" if ARGV.empty?
+require "rails/command"
aliases = {
"g" => "generate",
@@ -11,4 +9,4 @@ aliases = {
command = ARGV.shift
command = aliases[command] || command
-Rails::Engine::CommandsTasks.new(ARGV).run_command!(command)
+Rails::Command.invoke command, ARGV
diff --git a/railties/lib/rails/engine/commands_tasks.rb b/railties/lib/rails/engine/commands_tasks.rb
deleted file mode 100644
index e21a7183fc..0000000000
--- a/railties/lib/rails/engine/commands_tasks.rb
+++ /dev/null
@@ -1,118 +0,0 @@
-require "rails/commands/rake_proxy"
-
-module Rails
- class Engine
- class CommandsTasks # :nodoc:
- include Rails::RakeProxy
-
- attr_reader :argv
-
- HELP_MESSAGE = <<-EOT
-Usage: rails COMMAND [ARGS]
-
-The common Rails commands available for engines are:
- generate Generate new code (short-cut alias: "g")
- destroy Undo code generated with "generate" (short-cut alias: "d")
- test Run tests (short-cut alias: "t")
-
-All commands can be run with -h for more information.
-
-If you want to run any commands that need to be run in context
-of the application, like `rails server` or `rails console`,
-you should do it from application's directory (typically test/dummy).
-
-In addition to those commands, there are:
- EOT
-
- COMMAND_WHITELIST = %w(generate destroy version help test)
-
- def initialize(argv)
- @argv = argv
- end
-
- def run_command!(command)
- command = parse_command(command)
-
- if COMMAND_WHITELIST.include?(command)
- send(command)
- else
- run_rake_task(command)
- end
- end
-
- def generate
- generate_or_destroy(:generate)
- end
-
- def destroy
- generate_or_destroy(:destroy)
- end
-
- def test
- require_command!("test")
- end
-
- def version
- argv.unshift "--version"
- require_command!("application")
- end
-
- def help
- write_help_message
- write_commands(formatted_rake_tasks)
- end
-
- private
-
- def require_command!(command)
- require "rails/commands/#{command}"
- end
-
- def generate_or_destroy(command)
- load_generators
- require_command!(command)
- end
-
- def load_generators
- require "rails/generators"
- require ENGINE_PATH
-
- engine = ::Rails::Engine.find(ENGINE_ROOT)
- Rails::Generators.namespace = engine.railtie_namespace
- engine.load_generators
- end
-
- def write_help_message
- puts HELP_MESSAGE
- end
-
- def write_commands(commands)
- width = commands.map { |name, _| name.size }.max || 10
- commands.each { |command| printf(" %-#{width}s %s\n", *command) }
- end
-
- def parse_command(command)
- case command
- when "--version", "-v"
- "version"
- when "--help", "-h"
- "help"
- else
- command
- end
- end
-
- def rake_tasks
- require_rake
-
- return @rake_tasks if defined?(@rake_tasks)
-
- load_generators
- Rake::TaskManager.record_task_metadata = true
- Rake.application.init("rails")
- Rake.application.load_rakefile
- @rake_tasks = Rake.application.tasks.select(&:comment)
- end
- end
- end
-end
diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb
index 147b904679..0c40173c38 100644
--- a/railties/lib/rails/engine/configuration.rb
+++ b/railties/lib/rails/engine/configuration.rb
@@ -7,7 +7,7 @@ module Rails
attr_accessor :middleware
attr_writer :eager_load_paths, :autoload_once_paths, :autoload_paths
- def initialize(root=nil)
+ def initialize(root = nil)
super()
@root = root
@generators = app_generators.dup
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 708c0b11d6..e1980a42ad 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -2,6 +2,7 @@ activesupport_path = File.expand_path("../../../../activesupport/lib", __FILE__)
$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
require "thor/group"
+require "rails/command"
require "active_support"
require "active_support/core_ext/object/blank"
@@ -13,6 +14,8 @@ require "active_support/core_ext/string/inflections"
module Rails
module Generators
+ include Rails::Command::Behavior
+
autoload :Actions, "rails/generators/actions"
autoload :ActiveModel, "rails/generators/active_model"
autoload :Base, "rails/generators/base"
@@ -64,335 +67,247 @@ module Rails
}
}
- def self.configure!(config) #:nodoc:
- api_only! if config.api_only
- no_color! unless config.colorize_logging
- aliases.deep_merge! config.aliases
- options.deep_merge! config.options
- fallbacks.merge! config.fallbacks
- templates_path.concat config.templates
- templates_path.uniq!
- hide_namespaces(*config.hidden_namespaces)
- end
-
- def self.templates_path #:nodoc:
- @templates_path ||= []
- end
-
- def self.aliases #:nodoc:
- @aliases ||= DEFAULT_ALIASES.dup
- end
-
- def self.options #:nodoc:
- @options ||= DEFAULT_OPTIONS.dup
- end
-
- # Hold configured generators fallbacks. If a plugin developer wants a
- # generator group to fallback to another group in case of missing generators,
- # they can add a fallback.
- #
- # For example, shoulda is considered a test_framework and is an extension
- # of test_unit. However, most part of shoulda generators are similar to
- # test_unit ones.
- #
- # Shoulda then can tell generators to search for test_unit generators when
- # some of them are not available by adding a fallback:
- #
- # Rails::Generators.fallbacks[:shoulda] = :test_unit
- def self.fallbacks
- @fallbacks ||= {}
- end
-
- # Configure generators for API only applications. It basically hides
- # everything that is usually browser related, such as assets and session
- # migration generators, and completely disable helpers and assets
- # so generators such as scaffold won't create them.
- def self.api_only!
- hide_namespaces "assets", "helper", "css", "js"
-
- options[:rails].merge!(
- api: true,
- assets: false,
- helper: false,
- template_engine: nil
- )
-
- if ARGV.first == "mailer"
- options[:rails].merge!(template_engine: :erb)
+ class << self
+ def configure!(config) #:nodoc:
+ api_only! if config.api_only
+ no_color! unless config.colorize_logging
+ aliases.deep_merge! config.aliases
+ options.deep_merge! config.options
+ fallbacks.merge! config.fallbacks
+ templates_path.concat config.templates
+ templates_path.uniq!
+ hide_namespaces(*config.hidden_namespaces)
end
- end
-
- # Remove the color from output.
- def self.no_color!
- Thor::Base.shell = Thor::Shell::Basic
- end
- # Track all generators subclasses.
- def self.subclasses
- @subclasses ||= []
- end
-
- # Rails finds namespaces similar to thor, it only adds one rule:
- #
- # Generators names must end with "_generator.rb". This is required because Rails
- # looks in load paths and loads the generator just before it's going to be used.
- #
- # find_by_namespace :webrat, :rails, :integration
- #
- # Will search for the following generators:
- #
- # "rails:webrat", "webrat:integration", "webrat"
- #
- # Notice that "rails:generators:webrat" could be loaded as well, what
- # Rails looks for is the first and last parts of the namespace.
- def self.find_by_namespace(name, base=nil, context=nil) #:nodoc:
- lookups = []
- lookups << "#{base}:#{name}" if base
- lookups << "#{name}:#{context}" if context
-
- unless base || context
- unless name.to_s.include?(?:)
- lookups << "#{name}:#{name}"
- lookups << "rails:#{name}"
- end
- lookups << "#{name}"
+ def templates_path #:nodoc:
+ @templates_path ||= []
end
- lookup(lookups)
+ def aliases #:nodoc:
+ @aliases ||= DEFAULT_ALIASES.dup
+ end
- namespaces = Hash[subclasses.map { |klass| [klass.namespace, klass] }]
+ def options #:nodoc:
+ @options ||= DEFAULT_OPTIONS.dup
+ end
- lookups.each do |namespace|
- klass = namespaces[namespace]
- return klass if klass
+ # Hold configured generators fallbacks. If a plugin developer wants a
+ # generator group to fallback to another group in case of missing generators,
+ # they can add a fallback.
+ #
+ # For example, shoulda is considered a test_framework and is an extension
+ # of test_unit. However, most part of shoulda generators are similar to
+ # test_unit ones.
+ #
+ # Shoulda then can tell generators to search for test_unit generators when
+ # some of them are not available by adding a fallback:
+ #
+ # Rails::Generators.fallbacks[:shoulda] = :test_unit
+ def fallbacks
+ @fallbacks ||= {}
end
- invoke_fallbacks_for(name, base) || invoke_fallbacks_for(context, name)
- end
+ # Configure generators for API only applications. It basically hides
+ # everything that is usually browser related, such as assets and session
+ # migration generators, and completely disable helpers and assets
+ # so generators such as scaffold won't create them.
+ def api_only!
+ hide_namespaces "assets", "helper", "css", "js"
+
+ options[:rails].merge!(
+ api: true,
+ assets: false,
+ helper: false,
+ template_engine: nil
+ )
+
+ if ARGV.first == "mailer"
+ options[:rails].merge!(template_engine: :erb)
+ end
+ end
- # Receives a namespace, arguments and the behavior to invoke the generator.
- # It's used as the default entry point for generate, destroy and update
- # commands.
- def self.invoke(namespace, args=ARGV, config={})
- names = namespace.to_s.split(":")
- if klass = find_by_namespace(names.pop, names.any? && names.join(":"))
- args << "--help" if args.empty? && klass.arguments.any?(&:required?)
- klass.start(args, config)
- else
- options = sorted_groups.flat_map(&:last)
- suggestions = options.sort_by {|suggested| levenshtein_distance(namespace.to_s, suggested) }.first(3)
- msg = "Could not find generator '#{namespace}'. "
- msg << "Maybe you meant #{ suggestions.map {|s| "'#{s}'"}.to_sentence(last_word_connector: " or ", locale: :en) }\n"
- msg << "Run `rails generate --help` for more options."
- puts msg
+ # Remove the color from output.
+ def no_color!
+ Thor::Base.shell = Thor::Shell::Basic
end
- end
- # Returns an array of generator namespaces that are hidden.
- # Generator namespaces may be hidden for a variety of reasons.
- # Some are aliased such as "rails:migration" and can be
- # invoked with the shorter "migration", others are private to other generators
- # such as "css:scaffold".
- def self.hidden_namespaces
- @hidden_namespaces ||= begin
- orm = options[:rails][:orm]
- test = options[:rails][:test_framework]
- template = options[:rails][:template_engine]
- css = options[:rails][:stylesheet_engine]
-
- [
- "rails",
- "resource_route",
- "#{orm}:migration",
- "#{orm}:model",
- "#{test}:controller",
- "#{test}:helper",
- "#{test}:integration",
- "#{test}:mailer",
- "#{test}:model",
- "#{test}:scaffold",
- "#{test}:view",
- "#{test}:job",
- "#{template}:controller",
- "#{template}:scaffold",
- "#{template}:mailer",
- "#{css}:scaffold",
- "#{css}:assets",
- "css:assets",
- "css:scaffold"
- ]
+ # Returns an array of generator namespaces that are hidden.
+ # Generator namespaces may be hidden for a variety of reasons.
+ # Some are aliased such as "rails:migration" and can be
+ # invoked with the shorter "migration", others are private to other generators
+ # such as "css:scaffold".
+ def hidden_namespaces
+ @hidden_namespaces ||= begin
+ orm = options[:rails][:orm]
+ test = options[:rails][:test_framework]
+ template = options[:rails][:template_engine]
+ css = options[:rails][:stylesheet_engine]
+
+ [
+ "rails",
+ "resource_route",
+ "#{orm}:migration",
+ "#{orm}:model",
+ "#{test}:controller",
+ "#{test}:helper",
+ "#{test}:integration",
+ "#{test}:mailer",
+ "#{test}:model",
+ "#{test}:scaffold",
+ "#{test}:view",
+ "#{test}:job",
+ "#{template}:controller",
+ "#{template}:scaffold",
+ "#{template}:mailer",
+ "#{css}:scaffold",
+ "#{css}:assets",
+ "css:assets",
+ "css:scaffold"
+ ]
+ end
end
- end
- class << self
def hide_namespaces(*namespaces)
hidden_namespaces.concat(namespaces)
end
alias hide_namespace hide_namespaces
- end
- # Show help message with available generators.
- def self.help(command = "generate")
- puts "Usage: rails #{command} GENERATOR [args] [options]"
- puts
- puts "General options:"
- puts " -h, [--help] # Print generator's options and usage"
- puts " -p, [--pretend] # Run but do not make any changes"
- puts " -f, [--force] # Overwrite files that already exist"
- puts " -s, [--skip] # Skip files that already exist"
- puts " -q, [--quiet] # Suppress status output"
- puts
- puts "Please choose a generator below."
- puts
-
- print_generators
- end
+ # Show help message with available generators.
+ def help(command = "generate")
+ puts "Usage: rails #{command} GENERATOR [args] [options]"
+ puts
+ puts "General options:"
+ puts " -h, [--help] # Print generator's options and usage"
+ puts " -p, [--pretend] # Run but do not make any changes"
+ puts " -f, [--force] # Overwrite files that already exist"
+ puts " -s, [--skip] # Skip files that already exist"
+ puts " -q, [--quiet] # Suppress status output"
+ puts
+ puts "Please choose a generator below."
+ puts
- def self.public_namespaces
- lookup!
- subclasses.map(&:namespace)
- end
+ print_generators
+ end
- def self.print_generators
- sorted_groups.each { |b, n| print_list(b, n) }
- end
+ def public_namespaces
+ lookup!
+ subclasses.map(&:namespace)
+ end
- def self.sorted_groups
- namespaces = public_namespaces
- namespaces.sort!
- groups = Hash.new { |h,k| h[k] = [] }
- namespaces.each do |namespace|
- base = namespace.split(":").first
- groups[base] << namespace
+ def print_generators
+ sorted_groups.each { |b, n| print_list(b, n) }
end
- rails = groups.delete("rails")
- rails.map! { |n| n.sub(/^rails:/, "") }
- rails.delete("app")
- rails.delete("plugin")
- hidden_namespaces.each { |n| groups.delete(n.to_s) }
+ def sorted_groups
+ namespaces = public_namespaces
+ namespaces.sort!
- [["rails", rails]] + groups.sort.to_a
- end
+ groups = Hash.new { |h, k| h[k] = [] }
+ namespaces.each do |namespace|
+ base = namespace.split(":").first
+ groups[base] << namespace
+ end
- protected
-
- # This code is based directly on the Text gem implementation
- # Returns a value representing the "cost" of transforming str1 into str2
- def self.levenshtein_distance(str1, str2)
- s = str1
- t = str2
- n = s.length
- m = t.length
-
- return m if (0 == n)
- return n if (0 == m)
-
- d = (0..m).to_a
- x = nil
-
- # avoid duplicating an enumerable object in the loop
- str2_codepoint_enumerable = str2.each_codepoint
-
- str1.each_codepoint.with_index do |char1, i|
- e = i+1
-
- str2_codepoint_enumerable.with_index do |char2, j|
- cost = (char1 == char2) ? 0 : 1
- x = [
- d[j+1] + 1, # insertion
- e + 1, # deletion
- d[j] + cost # substitution
- ].min
- d[j] = e
- e = x
- end
+ rails = groups.delete("rails")
+ rails.map! { |n| n.sub(/^rails:/, "") }
+ rails.delete("app")
+ rails.delete("plugin")
- d[m] = x
- end
+ hidden_namespaces.each { |n| groups.delete(n.to_s) }
- x
+ [[ "rails", rails ]] + groups.sort.to_a
end
- # Prints a list of generators.
- def self.print_list(base, namespaces) #:nodoc:
- namespaces = namespaces.reject do |n|
- hidden_namespaces.include?(n)
+ # Rails finds namespaces similar to thor, it only adds one rule:
+ #
+ # Generators names must end with "_generator.rb". This is required because Rails
+ # looks in load paths and loads the generator just before it's going to be used.
+ #
+ # find_by_namespace :webrat, :rails, :integration
+ #
+ # Will search for the following generators:
+ #
+ # "rails:webrat", "webrat:integration", "webrat"
+ #
+ # Notice that "rails:generators:webrat" could be loaded as well, what
+ # Rails looks for is the first and last parts of the namespace.
+ def find_by_namespace(name, base = nil, context = nil) #:nodoc:
+ lookups = []
+ lookups << "#{base}:#{name}" if base
+ lookups << "#{name}:#{context}" if context
+
+ unless base || context
+ unless name.to_s.include?(?:)
+ lookups << "#{name}:#{name}"
+ lookups << "rails:#{name}"
+ end
+ lookups << "#{name}"
end
- return if namespaces.empty?
- puts "#{base.camelize}:"
+ lookup(lookups)
- namespaces.each do |namespace|
- puts(" #{namespace}")
+ namespaces = Hash[subclasses.map { |klass| [klass.namespace, klass] }]
+ lookups.each do |namespace|
+
+ klass = namespaces[namespace]
+ return klass if klass
end
- puts
+ invoke_fallbacks_for(name, base) || invoke_fallbacks_for(context, name)
end
- # Try fallbacks for the given base.
- def self.invoke_fallbacks_for(name, base) #:nodoc:
- return nil unless base && fallbacks[base.to_sym]
- invoked_fallbacks = []
+ # Receives a namespace, arguments and the behavior to invoke the generator.
+ # It's used as the default entry point for generate, destroy and update
+ # commands.
+ def invoke(namespace, args = ARGV, config = {})
+ names = namespace.to_s.split(":")
+ if klass = find_by_namespace(names.pop, names.any? && names.join(":"))
+ args << "--help" if args.empty? && klass.arguments.any?(&:required?)
+ klass.start(args, config)
+ else
+ options = sorted_groups.flat_map(&:last)
+ suggestions = options.sort_by { |suggested| levenshtein_distance(namespace.to_s, suggested) }.first(3)
+ msg = "Could not find generator '#{namespace}'. "
+ msg << "Maybe you meant #{ suggestions.map { |s| "'#{s}'" }.to_sentence(last_word_connector: " or ", locale: :en) }\n"
+ msg << "Run `rails generate --help` for more options."
+ puts msg
+ end
+ end
- Array(fallbacks[base.to_sym]).each do |fallback|
- next if invoked_fallbacks.include?(fallback)
- invoked_fallbacks << fallback
+ private
- klass = find_by_namespace(name, fallback)
- return klass if klass
+ def print_list(base, namespaces) # :doc:
+ namespaces = namespaces.reject { |n| hidden_namespaces.include?(n) }
+ super
end
- nil
- end
+ # Try fallbacks for the given base.
+ def invoke_fallbacks_for(name, base)
+ return nil unless base && fallbacks[base.to_sym]
+ invoked_fallbacks = []
+
+ Array(fallbacks[base.to_sym]).each do |fallback|
+ next if invoked_fallbacks.include?(fallback)
+ invoked_fallbacks << fallback
- # Receives namespaces in an array and tries to find matching generators
- # in the load path.
- def self.lookup(namespaces) #:nodoc:
- paths = namespaces_to_paths(namespaces)
-
- paths.each do |raw_path|
- ["rails/generators", "generators"].each do |base|
- path = "#{base}/#{raw_path}_generator"
-
- begin
- require path
- return
- rescue LoadError => e
- raise unless e.message =~ /#{Regexp.escape(path)}$/
- rescue Exception => e
- warn "[WARNING] Could not load generator #{path.inspect}. Error: #{e.message}.\n#{e.backtrace.join("\n")}"
- end
+ klass = find_by_namespace(name, fallback)
+ return klass if klass
end
+
+ nil
end
- end
- # This will try to load any generator in the load path to show in help.
- def self.lookup! #:nodoc:
- $LOAD_PATH.each do |base|
- Dir[File.join(base, "{rails/generators,generators}", "**", "*_generator.rb")].each do |path|
- begin
- path = path.sub("#{base}/", "")
- require path
- rescue Exception
- # No problem
- end
- end
+ def command_type # :doc:
+ @command_type ||= "generator"
end
- end
- # Convert namespaces to paths by replacing ":" for "/" and adding
- # an extra lookup. For example, "rails:model" should be searched
- # in both: "rails/model/model_generator" and "rails/model_generator".
- def self.namespaces_to_paths(namespaces) #:nodoc:
- paths = []
- namespaces.each do |namespace|
- pieces = namespace.split(":")
- paths << pieces.dup.push(pieces.last).join("/")
- paths << pieces.join("/")
+ def lookup_paths # :doc:
+ @lookup_paths ||= %w( rails/generators generators )
end
- paths.uniq!
- paths
- end
+
+ def file_lookup_paths # :doc:
+ @file_lookup_paths ||= [ "{#{lookup_paths.join(',')}}", "**", "*_generator.rb" ]
+ end
+ end
end
end
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index ab9dc019e2..d058d82cea 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -68,7 +68,7 @@ module Rails
# add_source "http://gems.github.com/" do
# gem "rspec-rails"
# end
- def add_source(source, options={}, &block)
+ def add_source(source, options = {}, &block)
log :source, source
in_root do
@@ -96,7 +96,7 @@ module Rails
# environment(nil, env: "development") do
# "config.action_controller.asset_host = 'localhost:3000'"
# end
- def environment(data=nil, options={})
+ def environment(data = nil, options = {})
sentinel = /class [a-z_:]+ < Rails::Application/i
env_file_sentinel = /Rails\.application\.configure do/
data = yield if !data && block_given?
@@ -118,7 +118,7 @@ module Rails
# git :init
# git add: "this.file that.rb"
# git add: "onefile.rb", rm: "badfile.cxx"
- def git(commands={})
+ def git(commands = {})
if commands.is_a?(Symbol)
run "git #{commands}"
else
@@ -137,7 +137,7 @@ module Rails
# end
#
# vendor("foreign.rb", "# Foreign code is fun")
- def vendor(filename, data=nil, &block)
+ def vendor(filename, data = nil, &block)
log :vendor, filename
create_file("vendor/#{filename}", data, verbose: false, &block)
end
@@ -150,7 +150,7 @@ module Rails
# end
#
# lib("foreign.rb", "# Foreign code is fun")
- def lib(filename, data=nil, &block)
+ def lib(filename, data = nil, &block)
log :lib, filename
create_file("lib/#{filename}", data, verbose: false, &block)
end
@@ -170,7 +170,7 @@ module Rails
# end
#
# rakefile('seed.rake', 'puts "Planting seeds"')
- def rakefile(filename, data=nil, &block)
+ def rakefile(filename, data = nil, &block)
log :rakefile, filename
create_file("lib/tasks/#{filename}", data, verbose: false, &block)
end
@@ -188,7 +188,7 @@ module Rails
# end
#
# initializer("api.rb", "API_KEY = '123456'")
- def initializer(filename, data=nil, &block)
+ def initializer(filename, data = nil, &block)
log :initializer, filename
create_file("config/initializers/#{filename}", data, verbose: false, &block)
end
@@ -210,7 +210,7 @@ module Rails
# rake("db:migrate")
# rake("db:migrate", env: "production")
# rake("gems:install", sudo: true)
- def rake(command, options={})
+ def rake(command, options = {})
execute_command :rake, command, options
end
@@ -219,7 +219,7 @@ module Rails
# rails("db:migrate")
# rails("db:migrate", env: "production")
# rails("gems:install", sudo: true)
- def rails_command(command, options={})
+ def rails_command(command, options = {})
execute_command :rails, command, options
end
@@ -260,12 +260,12 @@ module Rails
@after_bundle_callbacks << block
end
- protected
+ private
# Define log for backwards compatibility. If just one argument is sent,
# invoke say, otherwise invoke say_status. Differently from say and
# similarly to say_status, this method respects the quiet? option given.
- def log(*args)
+ def log(*args) # :doc:
if args.size == 1
say args.first.to_s unless options.quiet?
else
@@ -276,16 +276,16 @@ module Rails
# Runs the supplied command using either "rake ..." or "rails ..."
# based on the executor parameter provided.
- def execute_command(executor, command, options={})
+ def execute_command(executor, command, options = {}) # :doc:
log executor, command
env = options[:env] || ENV["RAILS_ENV"] || "development"
- sudo = options[:sudo] && RbConfig::CONFIG["host_os"] !~ /mswin|mingw/ ? "sudo " : ""
+ sudo = options[:sudo] && !Gem.win_platform? ? "sudo " : ""
in_root { run("#{sudo}#{extify(executor)} #{command} RAILS_ENV=#{env}", verbose: false) }
end
# Add an extension to the given name based on the platform.
- def extify(name)
- if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
+ def extify(name) # :doc:
+ if Gem.win_platform?
"#{name}.bat"
else
name
@@ -294,7 +294,7 @@ module Rails
# Surround string with single quotes if there is no quotes.
# Otherwise fall back to double quotes
- def quote(value)
+ def quote(value) # :doc:
return value.inspect unless value.is_a? String
if value.include?("'")
diff --git a/railties/lib/rails/generators/actions/create_migration.rb b/railties/lib/rails/generators/actions/create_migration.rb
index 587c61fd42..f677e545e5 100644
--- a/railties/lib/rails/generators/actions/create_migration.rb
+++ b/railties/lib/rails/generators/actions/create_migration.rb
@@ -37,9 +37,9 @@ module Rails
end
alias :exists? :existing_migration
- protected
+ private
- def on_conflict_behavior
+ def on_conflict_behavior # :doc:
options = base.options.merge(config)
if identical?
say_status :identical, :blue, relative_existing_migration
@@ -60,7 +60,7 @@ module Rails
end
end
- def say_status(status, color, message = relative_destination)
+ def say_status(status, color, message = relative_destination) # :doc:
base.shell.say_status(status, message, color) if config[:verbose]
end
end
diff --git a/railties/lib/rails/generators/active_model.rb b/railties/lib/rails/generators/active_model.rb
index 6183944bb0..2679d06fe4 100644
--- a/railties/lib/rails/generators/active_model.rb
+++ b/railties/lib/rails/generators/active_model.rb
@@ -39,13 +39,13 @@ module Rails
# GET edit
# PATCH/PUT update
# DELETE destroy
- def self.find(klass, params=nil)
+ def self.find(klass, params = nil)
"#{klass}.find(#{params})"
end
# GET new
# POST create
- def self.build(klass, params=nil)
+ def self.build(klass, params = nil)
if params
"#{klass}.new(#{params})"
else
@@ -59,7 +59,7 @@ module Rails
end
# PATCH/PUT update
- def update(params=nil)
+ def update(params = nil)
"#{name}.update(#{params})"
end
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 7d061ed27c..ea88afe9f4 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -30,15 +30,18 @@ module Rails
class_option :database, type: :string, aliases: "-d", default: "sqlite3",
desc: "Preconfigure for selected database (options: #{DATABASES.join('/')})"
- class_option :javascript, type: :string, aliases: "-j", default: "jquery",
+ class_option :javascript, type: :string, aliases: "-j",
desc: "Preconfigure for selected JavaScript library"
+ class_option :webpack, type: :string, default: nil,
+ desc: "Preconfigure for app-like JavaScript with Webpack"
+
+ class_option :skip_yarn, type: :boolean, default: false,
+ desc: "Don't use Yarn for managing JavaScript dependencies"
+
class_option :skip_gemfile, type: :boolean, default: false,
desc: "Don't create a Gemfile"
- class_option :skip_bundle, type: :boolean, aliases: "-B", default: false,
- desc: "Don't run bundle install"
-
class_option :skip_git, type: :boolean, aliases: "-G", default: false,
desc: "Skip .gitignore file"
@@ -67,6 +70,9 @@ module Rails
class_option :skip_listen, type: :boolean, default: false,
desc: "Don't generate configuration that depends on the listen gem"
+ class_option :skip_coffee, type: :boolean, default: false,
+ desc: "Don't use CoffeeScript"
+
class_option :skip_javascript, type: :boolean, aliases: "-J", default: false,
desc: "Skip JavaScript files"
@@ -99,9 +105,9 @@ module Rails
convert_database_option_for_jruby
end
- protected
+ private
- def gemfile_entry(name, *args)
+ def gemfile_entry(name, *args) # :doc:
options = args.extract_options!
version = args.first
github = options[:github]
@@ -117,11 +123,12 @@ module Rails
self
end
- def gemfile_entries
+ def gemfile_entries # :doc:
[rails_gemfile_entry,
database_gemfile_entry,
webserver_gemfile_entry,
assets_gemfile_entry,
+ webpacker_gemfile_entry,
javascript_gemfile_entry,
jbuilder_gemfile_entry,
psych_gemfile_entry,
@@ -129,13 +136,13 @@ module Rails
@extra_entries].flatten.find_all(&@gem_filter)
end
- def add_gem_entry_filter
+ def add_gem_entry_filter # :doc:
@gem_filter = lambda { |next_filter, entry|
yield(entry) && next_filter.call(entry)
}.curry[@gem_filter]
end
- def builder
+ def builder # :doc:
@builder ||= begin
builder_class = get_builder_class
builder_class.include(ActionMethods)
@@ -143,61 +150,62 @@ module Rails
end
end
- def build(meth, *args)
+ def build(meth, *args) # :doc:
builder.send(meth, *args) if builder.respond_to?(meth)
end
- def create_root
+ def create_root # :doc:
valid_const?
empty_directory "."
FileUtils.cd(destination_root) unless options[:pretend]
end
- def apply_rails_template
+ def apply_rails_template # :doc:
apply rails_template if rails_template
rescue Thor::Error, LoadError, Errno::ENOENT => e
raise Error, "The template [#{rails_template}] could not be loaded. Error: #{e}"
end
- def set_default_accessors!
+ def set_default_accessors! # :doc:
self.destination_root = File.expand_path(app_path, destination_root)
- self.rails_template = case options[:template]
- when /^https?:\/\//
- options[:template]
- when String
- File.expand_path(options[:template], Dir.pwd)
+ self.rails_template = \
+ case options[:template]
+ when /^https?:\/\//
+ options[:template]
+ when String
+ File.expand_path(options[:template], Dir.pwd)
else
- options[:template]
- end
+ options[:template]
+ end
end
- def database_gemfile_entry
+ def database_gemfile_entry # :doc:
return [] if options[:skip_active_record]
gem_name, gem_version = gem_for_database
GemfileEntry.version gem_name, gem_version,
"Use #{options[:database]} as the database for Active Record"
end
- def webserver_gemfile_entry
+ def webserver_gemfile_entry # :doc:
return [] if options[:skip_puma]
comment = "Use Puma as the app server"
GemfileEntry.new("puma", "~> 3.0", comment)
end
- def include_all_railties?
+ def include_all_railties? # :doc:
options.values_at(:skip_active_record, :skip_action_mailer, :skip_test, :skip_sprockets, :skip_action_cable).none?
end
- def comment_if(value)
+ def comment_if(value) # :doc:
options[value] ? "# " : ""
end
- def keeps?
+ def keeps? # :doc:
!options[:skip_keeps]
end
- def sqlite3?
+ def sqlite3? # :doc:
!options[:skip_active_record] && options[:database] == "sqlite3"
end
@@ -235,6 +243,7 @@ module Rails
def rails_gemfile_entry
dev_edge_common = [
+ GemfileEntry.github("arel", "rails/arel")
]
if options.dev?
[
@@ -242,7 +251,7 @@ module Rails
] + dev_edge_common
elsif options.edge?
[
- GemfileEntry.github("rails", "rails/rails", "5-0-stable")
+ GemfileEntry.github("rails", "rails/rails")
] + dev_edge_common
else
[GemfileEntry.version("rails",
@@ -308,6 +317,13 @@ module Rails
gems
end
+ def webpacker_gemfile_entry
+ return [] unless options[:webpack]
+
+ comment = "Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker"
+ GemfileEntry.github "webpacker", "rails/webpacker", nil, comment
+ end
+
def jbuilder_gemfile_entry
comment = "Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder"
GemfileEntry.new "jbuilder", "~> 2.5", comment, {}, options[:api]
@@ -321,9 +337,13 @@ module Rails
if options[:skip_javascript] || options[:skip_sprockets]
[]
else
- gems = [coffee_gemfile_entry, javascript_runtime_gemfile_entry]
- gems << GemfileEntry.version("#{options[:javascript]}-rails", nil,
- "Use #{options[:javascript]} as the JavaScript library")
+ gems = [javascript_runtime_gemfile_entry]
+ gems << coffee_gemfile_entry unless options[:skip_coffee]
+
+ if options[:javascript]
+ gems << GemfileEntry.version("#{options[:javascript]}-rails", nil,
+ "Use #{options[:javascript]} as the JavaScript library")
+ end
unless options[:skip_turbolinks]
gems << GemfileEntry.version("turbolinks", "~> 5",
@@ -403,6 +423,13 @@ module Rails
bundle_command("install") if bundle_install?
end
+ def run_webpack
+ if !(webpack = options[:webpack]).nil?
+ rails_command "webpacker:install"
+ rails_command "webpacker:install:#{webpack}" unless webpack == "webpack"
+ end
+ end
+
def generate_spring_binstubs
if bundle_install? && spring_install?
bundle_command("exec spring binstub --all")
diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb
index 5b4cde30c0..cc6ae3860f 100644
--- a/railties/lib/rails/generators/base.rb
+++ b/railties/lib/rails/generators/base.rb
@@ -20,14 +20,14 @@ module Rails
strict_args_position!
# Returns the source root for this generator using default_source_root as default.
- def self.source_root(path=nil)
+ def self.source_root(path = nil)
@_source_root = path if path
@_source_root ||= default_source_root
end
# Tries to get the description from a USAGE file one folder above the source
# root otherwise uses a default description.
- def self.desc(description=nil)
+ def self.desc(description = nil)
return super if description
@desc ||= if usage_path
@@ -40,7 +40,7 @@ module Rails
# Convenience method to get the namespace from the class name. It's the
# same as Thor default except that the Generator at the end of the class
# is removed.
- def self.namespace(name=nil)
+ def self.namespace(name = nil)
return super if name
@namespace ||= super.sub(/_generator$/, "").sub(/:generators:/, ":")
end
@@ -168,7 +168,7 @@ module Rails
names.each do |name|
unless class_options.key?(name)
defaults = if options[:type] == :boolean
- { }
+ {}
elsif [true, false].include?(default_value_for_option(name, options))
{ banner: "" }
else
@@ -195,7 +195,7 @@ module Rails
end
# Make class option aware of Rails::Generators.options and Rails::Generators.aliases.
- def self.class_option(name, options={}) #:nodoc:
+ def self.class_option(name, options = {}) #:nodoc:
options[:desc] = "Indicates when to generate #{name.to_s.humanize.downcase}" unless options.key?(:desc)
options[:aliases] = default_aliases_for_option(name, options)
options[:default] = default_value_for_option(name, options)
@@ -239,11 +239,11 @@ module Rails
end
end
- protected
+ private
# Check whether the given class names are already taken by user
# application or Ruby on Rails.
- def class_collisions(*class_names) #:nodoc:
+ def class_collisions(*class_names)
return unless behavior == :invoke
class_names.flatten.each do |class_name|
@@ -264,7 +264,7 @@ module Rails
end
# Takes in an array of nested modules and extracts the last module
- def extract_last_module(nesting)
+ def extract_last_module(nesting) # :doc:
nesting.inject(Object) do |last_module, nest|
break unless last_module.const_defined?(nest, false)
last_module.const_get(nest)
@@ -272,12 +272,12 @@ module Rails
end
# Use Rails default banner.
- def self.banner
- "rails generate #{namespace.sub(/^rails:/,'')} #{arguments.map(&:usage).join(' ')} [options]".gsub(/\s+/, " ")
+ def self.banner # :doc:
+ "rails generate #{namespace.sub(/^rails:/, '')} #{arguments.map(&:usage).join(' ')} [options]".gsub(/\s+/, " ")
end
# Sets the base_name taking into account the current class namespace.
- def self.base_name
+ def self.base_name # :doc:
@base_name ||= begin
if base = name.to_s.split("::").first
base.underscore
@@ -287,7 +287,7 @@ module Rails
# Removes the namespaces and get the generator name. For example,
# Rails::Generators::ModelGenerator will return "model" as generator name.
- def self.generator_name
+ def self.generator_name # :doc:
@generator_name ||= begin
if generator = name.to_s.split("::").last
generator.sub!(/Generator$/, "")
@@ -298,21 +298,21 @@ module Rails
# Returns the default value for the option name given doing a lookup in
# Rails::Generators.options.
- def self.default_value_for_option(name, options)
+ def self.default_value_for_option(name, options) # :doc:
default_for_option(Rails::Generators.options, name, options, options[:default])
end
# Returns default aliases for the option name given doing a lookup in
# Rails::Generators.aliases.
- def self.default_aliases_for_option(name, options)
+ def self.default_aliases_for_option(name, options) # :doc:
default_for_option(Rails::Generators.aliases, name, options, options[:aliases])
end
# Returns default for the option name given doing a lookup in config.
- def self.default_for_option(config, name, options, default)
- if generator_name and c = config[generator_name.to_sym] and c.key?(name)
+ def self.default_for_option(config, name, options, default) # :doc:
+ if generator_name && (c = config[generator_name.to_sym]) && c.key?(name)
c[name]
- elsif base_name and c = config[base_name.to_sym] and c.key?(name)
+ elsif base_name && (c = config[base_name.to_sym]) && c.key?(name)
c[name]
elsif config[:rails].key?(name)
config[:rails][name]
@@ -343,7 +343,7 @@ module Rails
# Small macro to add ruby as an option to the generator with proper
# default value plus an instance helper method called shebang.
- def self.add_shebang_option!
+ def self.add_shebang_option! # :doc:
class_option :ruby, type: :string, aliases: "-r", default: Thor::Util.ruby_command,
desc: "Path to the Ruby binary of your choice", banner: "PATH"
@@ -361,7 +361,7 @@ module Rails
}
end
- def self.usage_path
+ def self.usage_path # :doc:
paths = [
source_root && File.expand_path("../USAGE", source_root),
default_generator_root && File.join(default_generator_root, "USAGE")
@@ -369,7 +369,7 @@ module Rails
paths.compact.detect { |path| File.exist? path }
end
- def self.default_generator_root
+ def self.default_generator_root # :doc:
path = File.expand_path(File.join(base_name, generator_name), base_root)
path if File.exist?(path)
end
diff --git a/railties/lib/rails/generators/erb.rb b/railties/lib/rails/generators/erb.rb
index d01502002f..d5e326d6ee 100644
--- a/railties/lib/rails/generators/erb.rb
+++ b/railties/lib/rails/generators/erb.rb
@@ -3,7 +3,7 @@ require "rails/generators/named_base"
module Erb # :nodoc:
module Generators # :nodoc:
class Base < Rails::Generators::NamedBase #:nodoc:
- protected
+ private
def formats
[format]
diff --git a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
index f150240908..677f8041ae 100644
--- a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
+++ b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
@@ -26,7 +26,7 @@ module Erb # :nodoc:
end
end
- protected
+ private
def formats
[:text, :html]
diff --git a/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb
index 154d85f381..0d77ef21da 100644
--- a/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb
@@ -21,7 +21,7 @@ module Erb # :nodoc:
end
end
- protected
+ private
def available_views
%w(index edit show new _form)
diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb
index 61181b7b97..baed7bf1e3 100644
--- a/railties/lib/rails/generators/generated_attribute.rb
+++ b/railties/lib/rails/generators/generated_attribute.rb
@@ -56,7 +56,7 @@ module Rails
end
end
- def initialize(name, type=nil, index_type=false, attr_options={})
+ def initialize(name, type = nil, index_type = false, attr_options = {})
@name = name
@type = type || :string
@has_index = INDEX_OPTIONS.include?(index_type)
@@ -151,7 +151,7 @@ module Rails
end
def inject_options
- "".tap { |s| options_for_migration.each { |k,v| s << ", #{k}: #{v.inspect}" } }
+ "".tap { |s| options_for_migration.each { |k, v| s << ", #{k}: #{v.inspect}" } }
end
def inject_index_options
diff --git a/railties/lib/rails/generators/migration.rb b/railties/lib/rails/generators/migration.rb
index 7290e235a1..0d63b9a5c9 100644
--- a/railties/lib/rails/generators/migration.rb
+++ b/railties/lib/rails/generators/migration.rb
@@ -52,7 +52,7 @@ module Rails
#
# migration_template "migration.rb", "db/migrate/add_foo_to_bar.rb"
def migration_template(source, destination, config = {})
- source = File.expand_path(find_in_source_paths(source.to_s))
+ source = File.expand_path(find_in_source_paths(source.to_s))
set_migration_assigns!(destination)
context = instance_eval("binding")
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index 6b1e4125cb..e3660b012a 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -32,145 +32,145 @@ module Rails
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 :file_name
+ private
+
# FIXME: We are avoiding to use alias because a bug on thor that make
# this method public and add it to the task list.
- def singular_name
+ def singular_name # :doc:
file_name
end
# Wrap block with namespace of current application
# if namespace exists and is not skipped
- def module_namespacing(&block)
+ def module_namespacing(&block) # :doc:
content = capture(&block)
content = wrap_with_namespace(content) if namespaced?
concat(content)
end
- def indent(content, multiplier = 2)
+ def indent(content, multiplier = 2) # :doc:
spaces = " " * multiplier
- content.each_line.map {|line| line.blank? ? line : "#{spaces}#{line}" }.join
+ content.each_line.map { |line| line.blank? ? line : "#{spaces}#{line}" }.join
end
- def wrap_with_namespace(content)
+ def wrap_with_namespace(content) # :doc:
content = indent(content).chomp
"module #{namespace.name}\n#{content}\nend\n"
end
- def inside_template
+ def inside_template # :doc:
@inside_template = true
yield
ensure
@inside_template = false
end
- def inside_template?
+ def inside_template? # :doc:
@inside_template
end
- def namespace
+ def namespace # :doc:
Rails::Generators.namespace
end
- def namespaced?
+ def namespaced? # :doc:
!options[:skip_namespace] && namespace
end
- def file_path
+ def file_path # :doc:
@file_path ||= (class_path + [file_name]).join("/")
end
- def class_path
+ def class_path # :doc:
inside_template? || !namespaced? ? regular_class_path : namespaced_class_path
end
- def regular_class_path
+ def regular_class_path # :doc:
@class_path
end
- def namespaced_file_path
- @namespaced_file_path ||= namespaced_class_path.join("/")
- end
-
- def namespaced_class_path
+ def namespaced_class_path # :doc:
@namespaced_class_path ||= [namespaced_path] + @class_path
end
- def namespaced_path
+ def namespaced_path # :doc:
@namespaced_path ||= namespace.name.split("::").first.underscore
end
- def class_name
+ def class_name # :doc:
(class_path + [file_name]).map!(&:camelize).join("::")
end
- def human_name
+ def human_name # :doc:
@human_name ||= singular_name.humanize
end
- def plural_name
+ def plural_name # :doc:
@plural_name ||= singular_name.pluralize
end
- def i18n_scope
+ def i18n_scope # :doc:
@i18n_scope ||= file_path.tr("/", ".")
end
- def table_name
+ def table_name # :doc:
@table_name ||= begin
base = pluralize_table_names? ? plural_name : singular_name
(class_path + [base]).join("_")
end
end
- def uncountable?
+ def uncountable? # :doc:
singular_name == plural_name
end
- def index_helper
+ def index_helper # :doc:
uncountable? ? "#{plural_table_name}_index" : plural_table_name
end
- def show_helper
+ def show_helper # :doc:
"#{singular_table_name}_url(@#{singular_table_name})"
end
- def edit_helper
+ def edit_helper # :doc:
"edit_#{show_helper}"
end
- def new_helper
+ def new_helper # :doc:
"new_#{singular_table_name}_url"
end
- def singular_table_name
+ def singular_table_name # :doc:
@singular_table_name ||= (pluralize_table_names? ? table_name.singularize : table_name)
end
- def plural_table_name
+ def plural_table_name # :doc:
@plural_table_name ||= (pluralize_table_names? ? table_name : table_name.pluralize)
end
- def plural_file_name
+ def plural_file_name # :doc:
@plural_file_name ||= file_name.pluralize
end
- def fixture_file_name
+ def fixture_file_name # :doc:
@fixture_file_name ||= (pluralize_table_names? ? plural_file_name : file_name)
end
- def route_url
- @route_url ||= class_path.collect {|dname| "/" + dname }.join + "/" + plural_file_name
+ def route_url # :doc:
+ @route_url ||= class_path.collect { |dname| "/" + dname }.join + "/" + plural_file_name
end
- def url_helper_prefix
+ def url_helper_prefix # :doc:
@url_helper_prefix ||= (class_path + [file_name]).join("_")
end
# Tries to retrieve the application name or simply return application.
- def application_name
+ def application_name # :doc:
if defined?(Rails) && Rails.application
Rails.application.class.name.split("::").first.underscore
else
@@ -178,20 +178,20 @@ module Rails
end
end
- def assign_names!(name) #:nodoc:
+ def assign_names!(name)
@class_path = name.include?("/") ? name.split("/") : name.split("::")
@class_path.map!(&:underscore)
@file_name = @class_path.pop
end
# Convert attributes array into GeneratedAttribute objects.
- def parse_attributes! #:nodoc:
+ def parse_attributes!
self.attributes = (attributes || []).map do |attr|
Rails::Generators::GeneratedAttribute.parse(attr)
end
end
- def attributes_names
+ def attributes_names # :doc:
@attributes_names ||= attributes.each_with_object([]) do |a, names|
names << a.column_name
names << "password_confirmation" if a.password_digest?
@@ -199,11 +199,11 @@ module Rails
end
end
- def pluralize_table_names?
+ def pluralize_table_names? # :doc:
!defined?(ActiveRecord::Base) || ActiveRecord::Base.pluralize_table_names
end
- def mountable_engine?
+ def mountable_engine? # :doc:
defined?(ENGINE_ROOT) && namespaced?
end
@@ -217,7 +217,7 @@ module Rails
# If the generator is invoked with class name Admin, it will check for
# the presence of "AdminDecorator".
#
- def self.check_class_collision(options={})
+ def self.check_class_collision(options = {}) # :doc:
define_method :check_class_collision do
name = if self.respond_to?(:controller_class_name) # for ScaffoldBase
controller_class_name
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index a1b1649547..8efdfdcb44 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -56,7 +56,7 @@ module Rails
def app
directory "app"
- keep_file "app/assets/images"
+ keep_file "app/assets/images"
empty_directory_with_keep_file "app/assets/javascripts/channels" unless options[:skip_action_cable]
keep_file "app/controllers/concerns"
@@ -151,19 +151,12 @@ module Rails
end
def vendor
- vendor_javascripts
- vendor_stylesheets
- end
+ empty_directory_with_keep_file "vendor"
- def vendor_javascripts
- unless options[:skip_javascript]
- empty_directory_with_keep_file "vendor/assets/javascripts"
+ unless options[:skip_yarn]
+ template "package.json", "vendor/package.json"
end
end
-
- def vendor_stylesheets
- empty_directory_with_keep_file "vendor/assets/stylesheets"
- end
end
module Generators
@@ -182,6 +175,9 @@ module Rails
class_option :api, type: :boolean,
desc: "Preconfigure smaller stack for API only apps"
+ class_option :skip_bundle, type: :boolean, aliases: "-B", default: false,
+ desc: "Don't run bundle install"
+
def initialize(*args)
super
@@ -193,9 +189,11 @@ module Rails
raise Error, "Invalid value for --database option. Supported for preconfiguration are: #{DATABASES.join(", ")}."
end
- # Force sprockets to be skipped when generating API only apps.
+ # Force sprockets and yarn to be skipped when generating API only apps.
# Can't modify options hash as it's frozen by default.
- self.options = options.merge(skip_sprockets: true, skip_javascript: true).freeze if options[:api]
+ if options[:api]
+ self.options = options.merge(skip_sprockets: true, skip_javascript: true, skip_yarn: true).freeze
+ end
end
public_task :set_default_accessors!
@@ -205,8 +203,8 @@ module Rails
build(:readme)
build(:rakefile)
build(:configru)
- build(:gitignore) unless options[:skip_git]
- build(:gemfile) unless options[:skip_gemfile]
+ build(:gitignore) unless options[:skip_git]
+ build(:gemfile) unless options[:skip_gemfile]
end
def create_app_files
@@ -241,6 +239,7 @@ module Rails
end
def create_db_files
+ return if options[:skip_active_record]
build(:db)
end
@@ -266,6 +265,10 @@ module Rails
def create_vendor_files
build(:vendor)
+
+ if options[:skip_yarn]
+ remove_file "vendor/package.json"
+ end
end
def delete_app_assets_if_api_option
@@ -273,7 +276,6 @@ module Rails
remove_dir "app/assets"
remove_dir "lib/assets"
remove_dir "tmp/cache/assets"
- remove_dir "vendor/assets"
end
end
@@ -321,10 +323,10 @@ module Rails
def delete_action_mailer_files_skipping_action_mailer
if options[:skip_action_mailer]
- remove_file "app/mailers/application_mailer.rb"
remove_file "app/views/layouts/mailer.html.erb"
remove_file "app/views/layouts/mailer.text.erb"
remove_dir "app/mailers"
+ remove_dir "test/mailers"
end
end
@@ -348,23 +350,27 @@ module Rails
end
end
+ def delete_bin_yarn_if_skip_yarn_option
+ remove_file "bin/yarn" if options[:skip_yarn]
+ end
+
def finish_template
build(:leftovers)
end
public_task :apply_rails_template, :run_bundle
- public_task :generate_spring_binstubs
+ public_task :run_webpack, :generate_spring_binstubs
def run_after_bundle_callbacks
@after_bundle_callbacks.each(&:call)
end
- protected
-
def self.banner
"rails new #{arguments.map(&:usage).join(' ')} [options]"
end
+ private
+
# Define file as an alias to create_file for backwards compatibility.
def file(*args, &block)
create_file(*args, &block)
@@ -421,7 +427,7 @@ module Rails
"/opt/local/var/run/mysql4/mysqld.sock", # mac + darwinports + mysql4
"/opt/local/var/run/mysql5/mysqld.sock", # mac + darwinports + mysql5
"/opt/lampp/var/mysql/mysql.sock" # xampp for linux
- ].find { |f| File.exist?(f) } unless RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
+ ].find { |f| File.exist?(f) } unless Gem.win_platform?
end
def get_builder_class
@@ -479,7 +485,7 @@ module Rails
end
def railsrc(argv)
- if (customrc = argv.index{ |x| x.include?("--rc=") })
+ if (customrc = argv.index { |x| x.include?("--rc=") })
fname = File.expand_path(argv[customrc].gsub(/--rc=/, ""))
yield(argv.take(customrc) + argv.drop(customrc + 1), fname)
else
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index 7af5fcd3c1..f1015b16d5 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -1,5 +1,10 @@
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
+
<% gemfile_entries.each do |gem| -%>
<% if gem.comment -%>
@@ -39,7 +44,7 @@ group :development do
<%- end -%>
<%- end -%>
<% if depend_on_listen? -%>
- gem 'listen', '~> 3.0.5'
+ gem 'listen', '>= 3.0.5', '< 3.2'
<% end -%>
<% if spring_install? -%>
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
index c88426ec06..25870f19c8 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
@@ -1,8 +1,8 @@
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
-// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
-// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
+// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
+// vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file. JavaScript code in this file should be added after the last require_* statement.
@@ -11,9 +11,11 @@
// about supported directives.
//
<% unless options[:skip_javascript] -%>
+<% if options[:javascript] -%>
//= require <%= options[:javascript] %>
-//= require <%= options[:javascript] %>_ujs
-<% if gemfile_entries.any? { |m| m.name == "turbolinks" } -%>
+<% end -%>
+//= require rails-ujs
+<% unless options[:skip_turbolinks] -%>
//= require turbolinks
<% end -%>
<% end -%>
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 0ebd7fe829..865300bef9 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,8 +2,8 @@
* 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, vendor/assets/stylesheets,
- * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
+ * 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
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
diff --git a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
index d51f79bd49..5460155b3e 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
@@ -7,7 +7,7 @@
<%- if options[:skip_javascript] -%>
<%%= stylesheet_link_tag 'application', media: 'all' %>
<%- else -%>
- <%- if gemfile_entries.any? { |m| m.name == 'turbolinks' } -%>
+ <%- unless options[:skip_turbolinks] -%>
<%%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
<%- else -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/setup b/railties/lib/rails/generators/rails/app/templates/bin/setup.tt
index acae810c1a..c6607dbb2b 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/setup
+++ b/railties/lib/rails/generators/rails/app/templates/bin/setup.tt
@@ -16,7 +16,12 @@ chdir APP_ROOT do
puts '== Installing dependencies =='
system! 'gem install bundler --conservative'
system('bundle check') || system!('bundle install')
+<% unless options[:skip_yarn] %>
+ # Install JavaScript dependencies if using Yarn
+ # system('bin/yarn')
+<% end %>
+<% unless options.skip_active_record -%>
# puts "\n== Copying sample files =="
# unless File.exist?('config/database.yml')
# cp 'config/database.yml.sample', 'config/database.yml'
@@ -24,6 +29,7 @@ chdir APP_ROOT do
puts "\n== Preparing database =="
system! 'bin/rails db:setup'
+<% end -%>
puts "\n== Removing old logs and tempfiles =="
system! 'bin/rails log:clear tmp:clear'
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/update b/railties/lib/rails/generators/rails/app/templates/bin/update.tt
index 770a605fed..d23af018c7 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/update
+++ b/railties/lib/rails/generators/rails/app/templates/bin/update.tt
@@ -16,9 +16,10 @@ chdir APP_ROOT do
puts '== Installing dependencies =='
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 -%>
puts "\n== Removing old logs and tempfiles =="
system! 'bin/rails log:clear tmp:clear'
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/yarn b/railties/lib/rails/generators/rails/app/templates/bin/yarn
new file mode 100644
index 0000000000..872438cecb
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/bin/yarn
@@ -0,0 +1,9 @@
+VENDOR_PATH = File.expand_path('../vendor', __dir__)
+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"
+ end
+end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml
index a2b2a64ba6..8bc8735a8e 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml
@@ -1,4 +1,4 @@
-# MySQL. Versions 5.0 and up are supported.
+# MySQL. Versions 5.1.10 and up are supported.
#
# Install the MySQL driver:
# gem install activerecord-jdbcmysql-adapter
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
index d987cf303b..269af1470d 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
@@ -1,4 +1,4 @@
-# MySQL. Versions 5.0 and up are supported.
+# MySQL. Versions 5.1.10 and up are supported.
#
# Install the MySQL driver
# gem install mysql2
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt
index 01ef3e6630..f5d03fb117 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt
@@ -3,9 +3,14 @@
# Version of your assets, change this if you want to expire all your assets.
Rails.application.config.assets.version = '1.0'
-# Add additional assets to the asset load path
+# Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path
+<%- unless options[:skip_yarn] -%>
+# Add Yarn node_modules folder to the asset load path.
+Rails.application.config.assets.paths << Rails.root.join('vendor/node_modules')
+<%- end -%>
# Precompile additional assets.
-# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
-# Rails.application.config.assets.precompile += %w( search.js )
+# application.js, application.css, and all non-JS/CSS in the app/assets
+# folder are already added.
+# Rails.application.config.assets.precompile += %w( admin.js admin.css )
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt
index e539f4c457..3ad3eba98a 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt
@@ -32,3 +32,9 @@ ActiveSupport.halt_callback_chains_on_return_false = <%= options[:update] ? true
# Configure SSL options to enable HSTS with subdomains. Previous versions had false.
Rails.application.config.ssl_options = { hsts: { subdomains: true } }
<%- end -%>
+<%- 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 = <%= options[:update] ? true : false %>
+<%- end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/puma.rb b/railties/lib/rails/generators/rails/app/templates/config/puma.rb
index 121ad7080e..1e19380dcb 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/puma.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/puma.rb
@@ -1,13 +1,13 @@
# Puma can serve each request in a thread from an internal thread pool.
-# The `threads` method setting takes two numbers a minimum and maximum.
+# The `threads` method setting takes two numbers: a minimum and maximum.
# Any libraries that use thread pools should be configured to match
# the maximum value specified for Puma. Default is set to 5 threads for minimum
-# and maximum, this matches the default thread size of Active Record.
+# and maximum; this matches the default thread size of Active Record.
#
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
threads threads_count, threads_count
-# Specifies the `port` that Puma will listen on to receive requests, default is 3000.
+# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
#
port ENV.fetch("PORT") { 3000 }
@@ -32,16 +32,25 @@ environment ENV.fetch("RAILS_ENV") { "development" }
#
# preload_app!
+# If you are preloading your application and using Active Record, it's
+# recommended that you close any connections to the database before workers
+# are forked to prevent connection leakage.
+#
+# before_fork do
+# ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord)
+# end
+
# The code in the `on_worker_boot` will be called if you are using
# clustered mode by specifying a number of `workers`. After each worker
-# process is booted this block will be run, if you are using `preload_app!`
-# option you will want to use this block to reconnect to any threads
-# or connections that may have been created at application boot, Ruby
+# process is booted, this block will be run. If you are using the `preload_app!`
+# option, you will want to use this block to reconnect to any threads
+# or connections that may have been created at application boot, as Ruby
# cannot share connections between processes.
#
# on_worker_boot do
# ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
# end
+#
# Allow puma to be restarted by `rails restart` command.
plugin :tmp_restart
diff --git a/railties/lib/rails/generators/rails/app/templates/gitignore b/railties/lib/rails/generators/rails/app/templates/gitignore
index 0e66cc4237..709b341387 100644
--- a/railties/lib/rails/generators/rails/app/templates/gitignore
+++ b/railties/lib/rails/generators/rails/app/templates/gitignore
@@ -21,5 +21,8 @@
!/tmp/.keep
<% end -%>
-# Ignore Byebug command history file.
+<% unless options[:skip_yarn] -%>
+/vendor/node_modules
+
+<% end -%>
.byebug_history
diff --git a/railties/lib/rails/generators/rails/app/templates/package.json b/railties/lib/rails/generators/rails/app/templates/package.json
new file mode 100644
index 0000000000..46db57dcbe
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/package.json
@@ -0,0 +1,5 @@
+{
+ "name": "<%= app_name %>",
+ "private": true,
+ "dependencies": {}
+}
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 87b8fe3516..2f92168eef 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,3 @@
-ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
diff --git a/railties/lib/rails/generators/rails/assets/assets_generator.rb b/railties/lib/rails/generators/rails/assets/assets_generator.rb
index 265dada2ca..95d00c2d39 100644
--- a/railties/lib/rails/generators/rails/assets/assets_generator.rb
+++ b/railties/lib/rails/generators/rails/assets/assets_generator.rb
@@ -7,7 +7,7 @@ module Rails
class_option :javascript_engine, desc: "Engine for JavaScripts"
class_option :stylesheet_engine, desc: "Engine for Stylesheets"
- protected
+ private
def asset_name
file_name
diff --git a/railties/lib/rails/generators/rails/controller/controller_generator.rb b/railties/lib/rails/generators/rails/controller/controller_generator.rb
index 213de37cce..06bdb8b5ce 100644
--- a/railties/lib/rails/generators/rails/controller/controller_generator.rb
+++ b/railties/lib/rails/generators/rails/controller/controller_generator.rb
@@ -3,6 +3,8 @@ module Rails
class ControllerGenerator < NamedBase # :nodoc:
argument :actions, type: :array, default: [], banner: "action action"
class_option :skip_routes, type: :boolean, desc: "Don't add routes to config/routes.rb."
+ class_option :helper, type: :boolean
+ class_option :assets, type: :boolean
check_class_collision suffix: "Controller"
@@ -14,7 +16,7 @@ module Rails
unless options[:skip_routes]
actions.reverse_each do |action|
# route prepends two spaces onto the front of the string that is passed, this corrects that.
- route generate_routing_code(action)[2..-1]
+ route indent(generate_routing_code(action), 2)[2..-1]
end
end
end
@@ -32,27 +34,30 @@ module Rails
# end
# end
def generate_routing_code(action)
- depth = regular_class_path.length
+ depth = 0
+ lines = []
+
# Create 'namespace' ladder
# namespace :foo do
# namespace :bar do
- namespace_ladder = regular_class_path.each_with_index.map do |ns, i|
- indent(" namespace :#{ns} do\n", i * 2)
- end.join
+ regular_class_path.each do |ns|
+ lines << indent("namespace :#{ns} do\n", depth * 2)
+ depth += 1
+ end
# Create route
# get 'baz/index'
- route = indent(%{ get '#{file_name}/#{action}'\n}, depth * 2)
+ lines << indent(%{get '#{file_name}/#{action}'\n}, depth * 2)
# Create `end` ladder
# end
# end
- end_ladder = (1..depth).reverse_each.map do |i|
- indent("end\n", i * 2)
- end.join
+ until depth.zero?
+ depth -= 1
+ lines << indent("end\n", depth * 2)
+ end
- # Combine the 3 parts to generate complete route entry
- namespace_ladder + route + end_ladder
+ lines.join
end
end
end
diff --git a/railties/lib/rails/generators/rails/generator/generator_generator.rb b/railties/lib/rails/generators/rails/generator/generator_generator.rb
index 8040ec5e7b..299a7da5f1 100644
--- a/railties/lib/rails/generators/rails/generator/generator_generator.rb
+++ b/railties/lib/rails/generators/rails/generator/generator_generator.rb
@@ -12,7 +12,7 @@ module Rails
hook_for :test_framework
- protected
+ private
def generator_dir
if options[:namespace]
diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
index 9ffeab4fbe..4cf4f8cd9a 100644
--- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
@@ -81,7 +81,7 @@ task default: :test
end
PASSTHROUGH_OPTIONS = [
- :skip_active_record, :skip_action_mailer, :skip_javascript, :database,
+ :skip_active_record, :skip_action_mailer, :skip_javascript, :skip_sprockets, :database,
:javascript, :quiet, :pretend, :force, :skip
]
@@ -270,8 +270,8 @@ task default: :test
@name ||= begin
# same as ActiveSupport::Inflector#underscore except not replacing '-'
underscored = original_name.dup
- underscored.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
- underscored.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
+ underscored.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
+ underscored.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
underscored.downcase!
underscored
@@ -283,10 +283,10 @@ task default: :test
end
def namespaced_name
- @namespaced_name ||= name.gsub("-", "/")
+ @namespaced_name ||= name.tr("-", "/")
end
- protected
+ private
def create_dummy_app(path = nil)
dummy_path(path) if path
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 62b94618fd..c0fbb84a93 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt
+++ b/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt
@@ -5,4 +5,6 @@ require 'rails/test_unit/minitest_plugin'
Rails::TestUnitReporter.executable = 'bin/test'
-exit Minitest.run(ARGV)
+Minitest.run_via[:rails] = true
+
+require "active_support/testing/autorun"
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 a5eebcb19f..e84e403018 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,6 +1,3 @@
-# Configure Rails Environment
-ENV["RAILS_ENV"] = "test"
-
require File.expand_path("../../<%= options[:dummy_path] -%>/config/environment.rb", __FILE__)
<% unless options[:skip_active_record] -%>
ActiveRecord::Migrator.migrations_paths = [File.expand_path("../../<%= options[:dummy_path] -%>/db/migrate", __FILE__)]
diff --git a/railties/lib/rails/generators/resource_helpers.rb b/railties/lib/rails/generators/resource_helpers.rb
index 6d80003271..978b053308 100644
--- a/railties/lib/rails/generators/resource_helpers.rb
+++ b/railties/lib/rails/generators/resource_helpers.rb
@@ -23,10 +23,14 @@ module Rails
assign_controller_names!(controller_name.pluralize)
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 :controller_name, :controller_file_name
+ private
+
def controller_class_path
if options[:model_name]
@controller_class_path
@@ -73,7 +77,7 @@ module Rails
end
# Initialize ORM::Generators::ActiveModel to access instance methods.
- def orm_instance(name=singular_table_name)
+ def orm_instance(name = singular_table_name)
@orm_instance ||= orm_class.new(name)
end
end
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 59f8d40343..6b6e094453 100644
--- a/railties/lib/rails/generators/test_unit/generator/generator_generator.rb
+++ b/railties/lib/rails/generators/test_unit/generator/generator_generator.rb
@@ -12,7 +12,7 @@ module TestUnit # :nodoc:
template "generator_test.rb", File.join("test/lib/generators", class_path, "#{file_name}_generator_test.rb")
end
- protected
+ private
def generator_path
if options[:namespace]
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 806279788e..67bff8e4f9 100644
--- a/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb
+++ b/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb
@@ -17,7 +17,7 @@ module TestUnit # :nodoc:
template "preview.rb", File.join("test/mailers/previews", class_path, "#{file_name}_mailer_preview.rb")
end
- protected
+ private
def file_name
@_file_name ||= super.gsub(/_mailer/i, "")
end
diff --git a/railties/lib/rails/generators/testing/behaviour.rb b/railties/lib/rails/generators/testing/behaviour.rb
index a1e5a233b9..64d641d096 100644
--- a/railties/lib/rails/generators/testing/behaviour.rb
+++ b/railties/lib/rails/generators/testing/behaviour.rb
@@ -82,23 +82,23 @@ module Rails
Rails::Generators::GeneratedAttribute.parse([name, attribute_type, index].compact.join(":"))
end
- protected
+ private
- def destination_root_is_set? # :nodoc:
+ def destination_root_is_set?
raise "You need to configure your Rails::Generators::TestCase destination root." unless destination_root
end
- def ensure_current_path # :nodoc:
+ def ensure_current_path
cd current_path
end
# Clears all files and directories in destination.
- def prepare_destination
+ def prepare_destination # :doc:
rm_rf(destination_root)
mkdir_p(destination_root)
end
- def migration_file_name(relative) # :nodoc:
+ def migration_file_name(relative)
absolute = File.expand_path(relative, destination_root)
dirname, file_name = File.dirname(absolute), File.basename(absolute).sub(/\.rb$/, "")
Dir.glob("#{dirname}/[0-9]*_*.rb").grep(/\d+_#{file_name}.rb$/).first
diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb
index f5aa074703..8b553aea79 100644
--- a/railties/lib/rails/info_controller.rb
+++ b/railties/lib/rails/info_controller.rb
@@ -21,8 +21,8 @@ class Rails::InfoController < Rails::ApplicationController # :nodoc:
path = URI.parser.escape path
normalized_path = with_leading_slash path
render json: {
- exact: match_route {|it| it.match normalized_path },
- fuzzy: match_route {|it| it.spec.to_s.match path }
+ exact: match_route { |it| it.match normalized_path },
+ fuzzy: match_route { |it| it.spec.to_s.match path }
}
else
@routes_inspector = ActionDispatch::Routing::RoutesInspector.new(_routes.routes)
@@ -33,9 +33,9 @@ class Rails::InfoController < Rails::ApplicationController # :nodoc:
private
def match_route
- _routes.routes.select {|route|
+ _routes.routes.select { |route|
yield route.path
- }.map {|route| route.path.spec.to_s }
+ }.map { |route| route.path.spec.to_s }
end
def with_leading_slash(path)
diff --git a/railties/lib/rails/initializable.rb b/railties/lib/rails/initializable.rb
index 81b1cd8110..a2615d5efd 100644
--- a/railties/lib/rails/initializable.rb
+++ b/railties/lib/rails/initializable.rb
@@ -53,7 +53,7 @@ module Rails
end
end
- def run_initializers(group=:default, *args)
+ def run_initializers(group = :default, *args)
return if instance_variable_defined?(:@ran)
initializers.tsort_each do |initializer|
initializer.run(*args) if initializer.belongs_to?(group)
diff --git a/railties/lib/rails/mailers_controller.rb b/railties/lib/rails/mailers_controller.rb
index 66073899c2..000ce40fbc 100644
--- a/railties/lib/rails/mailers_controller.rb
+++ b/railties/lib/rails/mailers_controller.rb
@@ -40,15 +40,15 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc:
end
end
- protected
- def show_previews?
+ private
+ def show_previews? # :doc:
ActionMailer::Base.show_previews
end
- def find_preview
+ def find_preview # :doc:
candidates = []
- params[:path].to_s.scan(%r{/|$}){ candidates << $` }
- preview = candidates.detect{ |candidate| ActionMailer::Preview.exists?(candidate) }
+ params[:path].to_s.scan(%r{/|$}) { candidates << $` }
+ preview = candidates.detect { |candidate| ActionMailer::Preview.exists?(candidate) }
if preview
@preview = ActionMailer::Preview.find(preview)
@@ -57,19 +57,19 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc:
end
end
- def find_preferred_part(*formats)
+ def find_preferred_part(*formats) # :doc:
formats.each do |format|
if part = @email.find_first_mime_type(format)
return part
end
end
- if formats.any?{ |f| @email.mime_type == f }
+ if formats.any? { |f| @email.mime_type == f }
@email
end
end
- def find_part(format)
+ def find_part(format) # :doc:
if part = @email.find_first_mime_type(format)
part
elsif @email.mime_type == format
diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb
index 88ec2ba85b..10925de8b2 100644
--- a/railties/lib/rails/paths.rb
+++ b/railties/lib/rails/paths.rb
@@ -30,7 +30,7 @@ module Rails
# root["config/routes"].inspect # => ["config/routes.rb"]
#
# The +add+ method accepts the following options as arguments:
- # eager_load, autoload, autoload_once and glob.
+ # eager_load, autoload, autoload_once, and glob.
#
# Finally, the +Path+ object also provides a few helpers:
#
@@ -45,7 +45,6 @@ module Rails
attr_accessor :path
def initialize(path)
- @current = nil
@path = path
@root = {}
end
diff --git a/railties/lib/rails/rack/logger.rb b/railties/lib/rails/rack/logger.rb
index e3fee75603..853fc26051 100644
--- a/railties/lib/rails/rack/logger.rb
+++ b/railties/lib/rails/rack/logger.rb
@@ -27,45 +27,43 @@ module Rails
end
end
- protected
+ private
- def call_app(request, env)
- instrumenter = ActiveSupport::Notifications.instrumenter
- instrumenter.start "request.action_dispatch", request: request
- logger.info { started_request_message(request) }
- resp = @app.call(env)
- resp[2] = ::Rack::BodyProxy.new(resp[2]) { finish(request) }
- resp
- rescue Exception
- finish(request)
- raise
- ensure
- ActiveSupport::LogSubscriber.flush_all!
- end
+ def call_app(request, env) # :doc:
+ instrumenter = ActiveSupport::Notifications.instrumenter
+ instrumenter.start "request.action_dispatch", request: request
+ logger.info { started_request_message(request) }
+ resp = @app.call(env)
+ resp[2] = ::Rack::BodyProxy.new(resp[2]) { finish(request) }
+ resp
+ rescue Exception
+ finish(request)
+ raise
+ ensure
+ ActiveSupport::LogSubscriber.flush_all!
+ end
- # Started GET "/session/new" for 127.0.0.1 at 2012-09-26 14:51:42 -0700
- def started_request_message(request)
- 'Started %s "%s" for %s at %s' % [
- request.request_method,
- request.filtered_path,
- request.ip,
- Time.now.to_default_s ]
- end
+ # Started GET "/session/new" for 127.0.0.1 at 2012-09-26 14:51:42 -0700
+ def started_request_message(request) # :doc:
+ 'Started %s "%s" for %s at %s' % [
+ request.request_method,
+ request.filtered_path,
+ request.ip,
+ Time.now.to_default_s ]
+ end
- def compute_tags(request)
- @taggers.collect do |tag|
- case tag
- when Proc
- tag.call(request)
- when Symbol
- request.send(tag)
- else
- tag
+ def compute_tags(request) # :doc:
+ @taggers.collect do |tag|
+ case tag
+ when Proc
+ tag.call(request)
+ when Symbol
+ request.send(tag)
+ else
+ tag
+ end
end
end
- end
-
- private
def finish(request)
instrumenter = ActiveSupport::Notifications.instrumenter
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index eb3f5d4ee9..474118c0a9 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -132,27 +132,19 @@ module Rails
end
def rake_tasks(&blk)
- @rake_tasks ||= []
- @rake_tasks << blk if blk
- @rake_tasks
+ register_block_for(:rake_tasks, &blk)
end
def console(&blk)
- @load_console ||= []
- @load_console << blk if blk
- @load_console
+ register_block_for(:load_console, &blk)
end
def runner(&blk)
- @load_runner ||= []
- @load_runner << blk if blk
- @load_runner
+ register_block_for(:runner, &blk)
end
def generators(&blk)
- @generators ||= []
- @generators << blk if blk
- @generators
+ register_block_for(:generators, &blk)
end
def abstract_railtie?
@@ -181,8 +173,8 @@ module Rails
instance.configure(&block)
end
- protected
- def generate_railtie_name(string) #:nodoc:
+ private
+ def generate_railtie_name(string)
ActiveSupport::Inflector.underscore(string).tr("/", "_")
end
@@ -195,6 +187,16 @@ module Rails
super
end
end
+
+ # receives an instance variable identifier, set the variable value if is
+ # blank and append given block to value, which will be used later in
+ # `#each_registered_block(type, &block)`
+ def register_block_for(type, &blk)
+ var_name = "@#{type}"
+ blocks = instance_variable_defined?(var_name) ? instance_variable_get(var_name) : instance_variable_set(var_name, [])
+ blocks << blk if blk
+ blocks
+ end
end
delegate :railtie_name, to: :class
@@ -241,6 +243,7 @@ module Rails
private
+ # run `&block` in every registered block in `#register_block_for`
def each_registered_block(type, &block)
klass = self.class
while klass.respond_to?(type)
diff --git a/railties/lib/rails/railtie/configurable.rb b/railties/lib/rails/railtie/configurable.rb
index 39f1f87575..2a8295426e 100644
--- a/railties/lib/rails/railtie/configurable.rb
+++ b/railties/lib/rails/railtie/configurable.rb
@@ -24,7 +24,7 @@ module Rails
class_eval(&block)
end
- protected
+ private
def method_missing(*args, &block)
instance.send(*args, &block)
diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb
index f0df76d3f3..3a48c4c496 100644
--- a/railties/lib/rails/source_annotation_extractor.rb
+++ b/railties/lib/rails/source_annotation_extractor.rb
@@ -44,7 +44,7 @@ class SourceAnnotationExtractor
#
# If +options+ has a flag <tt>:tag</tt> the tag is shown as in the example above.
# Otherwise the string contains just line and text.
- def to_s(options={})
+ def to_s(options = {})
s = "[#{line.to_s.rjust(options[:indent])}] "
s << "[#{tag}] " if options[:tag]
s << text
@@ -66,7 +66,7 @@ class SourceAnnotationExtractor
# See <tt>#find_in</tt> for a list of file extensions that will be taken into account.
#
# This class method is the single entry point for the rake tasks.
- def self.enumerate(tag, options={})
+ def self.enumerate(tag, options = {})
extractor = new(tag)
dirs = options.delete(:dirs) || Annotation.directories
extractor.display(extractor.find(dirs), options)
@@ -116,7 +116,7 @@ class SourceAnnotationExtractor
# Otherwise it returns an empty hash.
def extract_annotations_from(file, pattern)
lineno = 0
- result = File.readlines(file).inject([]) do |list, line|
+ result = File.readlines(file, encoding: Encoding::BINARY).inject([]) do |list, line|
lineno += 1
next list unless line =~ pattern
list << Annotation.new(lineno, $1, $2)
@@ -126,7 +126,7 @@ class SourceAnnotationExtractor
# Prints the mapping from filenames to annotations in +results+ ordered by filename.
# The +options+ hash is passed to each annotation's +to_s+.
- def display(results, options={})
+ def display(results, options = {})
options[:indent] = results.flat_map { |f, a| a.map(&:line) }.max.to_s.size
results.keys.sort.each do |file|
puts "#{file}:"
diff --git a/railties/lib/rails/tasks/log.rake b/railties/lib/rails/tasks/log.rake
index c376234fee..ba796845d7 100644
--- a/railties/lib/rails/tasks/log.rake
+++ b/railties/lib/rails/tasks/log.rake
@@ -3,7 +3,7 @@ namespace :log do
##
# Truncates all/specified log files
# ENV['LOGS']
- # - defaults to standard environment log files i.e. 'development,test,production'
+ # - defaults to all environments log files i.e. 'development,test,production'
# - ENV['LOGS']=all truncates all files i.e. log/*.log
# - ENV['LOGS']='test,development' truncates only specified files
desc "Truncates all/specified *.log files in log/ to zero bytes (specify which logs with LOGS=test,development)"
@@ -19,7 +19,7 @@ namespace :log do
elsif ENV["LOGS"]
log_files_to_truncate(ENV["LOGS"])
else
- log_files_to_truncate("development,test,production")
+ log_files_to_truncate(all_environments.join(","))
end
end
@@ -33,4 +33,8 @@ namespace :log do
f = File.open(file, "w")
f.close
end
+
+ def all_environments
+ Dir["config/environments/*.rb"].map { |fname| File.basename(fname, ".*") }
+ end
end
diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake
index 634b645d40..f5e5b9ae87 100644
--- a/railties/lib/rails/tasks/routes.rake
+++ b/railties/lib/rails/tasks/routes.rake
@@ -7,7 +7,7 @@ task routes: :environment do
all_routes = Rails.application.routes.routes
require "action_dispatch/routing/inspector"
inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes)
- if ARGV.any?{ |argv| argv.start_with? "CONTROLLER" }
+ if ARGV.any? { |argv| argv.start_with? "CONTROLLER" }
puts <<-eow.strip_heredoc
Passing `CONTROLLER` to `bin/rails routes` is deprecated and will be removed in Rails 5.1.
Please use `bin/rails routes -c controller_name` instead.
diff --git a/railties/lib/rails/tasks/statistics.rake b/railties/lib/rails/tasks/statistics.rake
index a6cdd1e99c..ba1697186e 100644
--- a/railties/lib/rails/tasks/statistics.rake
+++ b/railties/lib/rails/tasks/statistics.rake
@@ -8,9 +8,8 @@ STATS_DIRECTORIES = [
%w(Models app/models),
%w(Mailers app/mailers),
%w(Channels app/channels),
- %w(Javascripts app/assets/javascripts),
+ %w(JavaScripts app/assets/javascripts),
%w(Libraries lib/),
- %w(Tasks lib/tasks),
%w(APIs app/apis),
%w(Controller\ tests test/controllers),
%w(Helper\ tests test/helpers),
diff --git a/railties/lib/rails/templates/rails/mailers/email.html.erb b/railties/lib/rails/templates/rails/mailers/email.html.erb
index fed96fbc85..c63781ed0c 100644
--- a/railties/lib/rails/templates/rails/mailers/email.html.erb
+++ b/railties/lib/rails/templates/rails/mailers/email.html.erb
@@ -88,7 +88,10 @@
<% unless @email.attachments.nil? || @email.attachments.empty? %>
<dt>Attachments:</dt>
<dd>
- <%= @email.attachments.map { |a| a.respond_to?(:original_filename) ? a.original_filename : a.filename }.join(', ') %>
+ <% @email.attachments.each do |a| %>
+ <% filename = a.respond_to?(:original_filename) ? a.original_filename : a.filename %>
+ <%= link_to filename, "data:application/octet-stream;charset=utf-8;base64,#{Base64.encode64(a.body.to_s)}", download: filename %>
+ <% end %>
</dd>
<% end %>
diff --git a/railties/lib/rails/test_unit/minitest_plugin.rb b/railties/lib/rails/test_unit/minitest_plugin.rb
index e15c6b3a38..6e196a32ab 100644
--- a/railties/lib/rails/test_unit/minitest_plugin.rb
+++ b/railties/lib/rails/test_unit/minitest_plugin.rb
@@ -61,19 +61,30 @@ module Minitest
# as the patterns would also contain the other Rake tasks.
def self.rake_run(patterns) # :nodoc:
@rake_patterns = patterns
- passed = run(Shellwords.split(ENV["TESTOPTS"] || ""))
- exit passed unless passed
- passed
+ autorun
end
+ module RunRespectingRakeTestopts
+ def run(args = [])
+ if defined?(@rake_patterns)
+ args = Shellwords.split(ENV["TESTOPTS"] || "")
+ end
+
+ super
+ end
+ end
+
+ singleton_class.prepend RunRespectingRakeTestopts
+
# Owes great inspiration to test runner trailblazers like RSpec,
# minitest-reporters, maxitest and others.
def self.plugin_rails_init(options)
- self.run_with_rails_extension = true
-
ENV["RAILS_ENV"] = options[:environment] || "test"
- ::Rails::TestRequirer.require_files(options[:patterns]) unless run_with_autorun
+ # If run via `ruby` we've been passed the files to run directly.
+ unless run_via[:ruby]
+ ::Rails::TestRequirer.require_files(options[:patterns])
+ end
unless options[:full_backtrace] || ENV["BACKTRACE"]
# Plugin can run without Rails loaded, check before filtering.
@@ -86,8 +97,7 @@ module Minitest
reporter << ::Rails::TestUnitReporter.new(options[:io], options)
end
- mattr_accessor(:run_with_autorun) { false }
- mattr_accessor(:run_with_rails_extension) { false }
+ mattr_accessor(:run_via) { Hash.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 d0fc795515..746120e6a1 100644
--- a/railties/lib/rails/test_unit/railtie.rb
+++ b/railties/lib/rails/test_unit/railtie.rb
@@ -14,7 +14,9 @@ module Rails
end
initializer "test_unit.line_filtering" do
- ActiveSupport::TestCase.extend Rails::LineFiltering
+ ActiveSupport.on_load(:active_support_test_case) {
+ ActiveSupport::TestCase.extend Rails::LineFiltering
+ }
end
rake_tasks do
diff --git a/railties/test/app_loader_test.rb b/railties/test/app_loader_test.rb
index 7fd5c72c1c..85f5502b4d 100644
--- a/railties/test/app_loader_test.rb
+++ b/railties/test/app_loader_test.rb
@@ -17,7 +17,7 @@ class AppLoaderTest < ActiveSupport::TestCase
end
end
- def write(filename, contents=nil)
+ def write(filename, contents = nil)
FileUtils.mkdir_p(File.dirname(filename))
File.write(filename, contents)
end
diff --git a/railties/test/application/asset_debugging_test.rb b/railties/test/application/asset_debugging_test.rb
index ccecf2c09e..3e17a1efa5 100644
--- a/railties/test/application/asset_debugging_test.rb
+++ b/railties/test/application/asset_debugging_test.rb
@@ -42,7 +42,7 @@ module ApplicationTests
test "assets are concatenated when debug is off and compile is off either if debug_assets param is provided" do
# config.assets.debug and config.assets.compile are false for production environment
ENV["RAILS_ENV"] = "production"
- output = Dir.chdir(app_path){ `bin/rails assets:precompile --trace 2>&1` }
+ output = Dir.chdir(app_path) { `bin/rails assets:precompile --trace 2>&1` }
assert $?.success?, output
# Load app env
@@ -68,5 +68,102 @@ module ApplicationTests
assert_match(/<script src="\/assets\/application(\.self)?-([0-z]+)\.js\?body=1"><\/script>/, last_response.body)
assert_match(/<script src="\/assets\/xmlhr(\.self)?-([0-z]+)\.js\?body=1"><\/script>/, last_response.body)
end
+
+ test "public path and tag methods are not over-written by the asset pipeline" do
+ contents = "doesnotexist"
+ cases = {
+ asset_path: %r{/#{contents}},
+ image_path: %r{/images/#{contents}},
+ video_path: %r{/videos/#{contents}},
+ audio_path: %r{/audios/#{contents}},
+ font_path: %r{/fonts/#{contents}},
+ javascript_path: %r{/javascripts/#{contents}},
+ stylesheet_path: %r{/stylesheets/#{contents}},
+ image_tag: %r{<img src="/images/#{contents}"},
+ favicon_link_tag: %r{<link rel="shortcut icon" type="image/x-icon" href="/images/#{contents}" />},
+ stylesheet_link_tag: %r{<link rel="stylesheet" media="screen" href="/stylesheets/#{contents}.css" />},
+ javascript_include_tag: %r{<script src="/javascripts/#{contents}.js">},
+ audio_tag: %r{<audio src="/audios/#{contents}"></audio>},
+ video_tag: %r{<video src="/videos/#{contents}"></video>}
+ }
+
+ cases.each do |(view_method, tag_match)|
+ app_file "app/views/posts/index.html.erb", "<%= #{view_method} '#{contents}', skip_pipeline: true %>"
+
+ app "development"
+
+ class ::PostsController < ActionController::Base ; end
+
+ get "/posts?debug_assets=true"
+
+ body = last_response.body
+ assert_match(tag_match, body, "Expected `#{view_method}` to produce a match to #{tag_match}, but did not: #{body}")
+ end
+ end
+
+ test "public url methods are not over-written by the asset pipeline" do
+ contents = "doesnotexist"
+ cases = {
+ asset_url: %r{http://example.org/#{contents}},
+ image_url: %r{http://example.org/images/#{contents}},
+ video_url: %r{http://example.org/videos/#{contents}},
+ audio_url: %r{http://example.org/audios/#{contents}},
+ font_url: %r{http://example.org/fonts/#{contents}},
+ javascript_url: %r{http://example.org/javascripts/#{contents}},
+ stylesheet_url: %r{http://example.org/stylesheets/#{contents}},
+ }
+
+ cases.each do |(view_method, tag_match)|
+ app_file "app/views/posts/index.html.erb", "<%= #{view_method} '#{contents}', skip_pipeline: true %>"
+
+ app "development"
+
+ class ::PostsController < ActionController::Base ; end
+
+ get "/posts?debug_assets=true"
+
+ body = last_response.body
+ assert_match(tag_match, body, "Expected `#{view_method}` to produce a match to #{tag_match}, but did not: #{body}")
+ end
+ end
+
+ test "{ skip_pipeline: true } does not use the asset pipeline" do
+ cases = {
+ /\/assets\/application-.*.\.js/ => {},
+ /application.js/ => { skip_pipeline: true },
+ }
+ cases.each do |(tag_match, options_hash)|
+ app_file "app/views/posts/index.html.erb", "<%= asset_path('application.js', #{options_hash}) %>"
+
+ app "development"
+
+ class ::PostsController < ActionController::Base ; end
+
+ get "/posts?debug_assets=true"
+
+ body = last_response.body.strip
+ assert_match(tag_match, body, "Expected `asset_path` with `#{options_hash}` to produce a match to #{tag_match}, but did not: #{body}")
+ end
+ end
+
+ test "public_compute_asset_path does not use the asset pipeline" do
+ cases = {
+ compute_asset_path: /\/assets\/application-.*.\.js/,
+ public_compute_asset_path: /application.js/,
+ }
+
+ cases.each do |(view_method, tag_match)|
+ app_file "app/views/posts/index.html.erb", "<%= #{ view_method } 'application.js' %>"
+
+ app "development"
+
+ class ::PostsController < ActionController::Base ; end
+
+ get "/posts?debug_assets=true"
+
+ body = last_response.body.strip
+ assert_match(tag_match, body, "Expected `#{view_method}` to produce a match to #{ tag_match }, but did not: #{ body }")
+ end
+ end
end
end
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index e84e01a41b..f38cacd6da 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -375,7 +375,7 @@ module ApplicationTests
class ::OmgController < ActionController::Base
def index
flash[:cool_story] = true
- render text: "ok"
+ render plain: "ok"
end
end
@@ -384,7 +384,7 @@ module ApplicationTests
get "/assets/demo.js"
assert_match "alert()", last_response.body
- assert_equal nil, last_response.headers["Set-Cookie"]
+ assert_nil last_response.headers["Set-Cookie"]
end
test "files in any assets/ directories are not added to Sprockets" do
@@ -475,9 +475,9 @@ module ApplicationTests
class ::PostsController < ActionController::Base; end
- get "/posts", {}, "HTTPS"=>"off"
+ get "/posts", {}, "HTTPS" => "off"
assert_match('src="http://example.com/assets/application.self.js', last_response.body)
- get "/posts", {}, "HTTPS"=>"on"
+ get "/posts", {}, "HTTPS" => "on"
assert_match('src="https://example.com/assets/application.self.js', last_response.body)
end
diff --git a/railties/test/application/bin_setup_test.rb b/railties/test/application/bin_setup_test.rb
index 2f076ea2f6..f62313f3e1 100644
--- a/railties/test/application/bin_setup_test.rb
+++ b/railties/test/application/bin_setup_test.rb
@@ -40,6 +40,7 @@ module ApplicationTests
output = `bin/setup 2>&1`
assert_equal(<<-OUTPUT, output)
== Installing dependencies ==
+Resolving dependencies...
The Gemfile's dependencies are satisfied
== Preparing database ==
diff --git a/railties/test/application/configuration/custom_test.rb b/railties/test/application/configuration/custom_test.rb
index 13fc98f0d6..3c675eec71 100644
--- a/railties/test/application/configuration/custom_test.rb
+++ b/railties/test/application/configuration/custom_test.rb
@@ -28,7 +28,7 @@ module ApplicationTests
assert_equal 3, x.payment_processing.retries
assert_equal true, x.super_debugger
assert_equal false, x.hyper_debugger
- assert_equal nil, x.nil_debugger
+ assert_nil x.nil_debugger
assert_nil x.i_do_not_exist.zomg
end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 94d3bbd165..31c3b5cf52 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -398,7 +398,7 @@ module ApplicationTests
class ::OmgController < ActionController::Base
def index
cookies.signed[:some_key] = "some_value"
- render text: cookies[:some_key]
+ render plain: cookies[:some_key]
end
end
@@ -614,7 +614,7 @@ module ApplicationTests
app "development"
assert_equal "b3c631c314c0bbca50c1b2843150fe33", app.config.secret_token
- assert_equal nil, app.secrets.secret_key_base
+ assert_nil app.secrets.secret_key_base
assert_equal app.key_generator.class, ActiveSupport::LegacyKeyGenerator
end
@@ -630,12 +630,26 @@ module ApplicationTests
app "development"
assert_equal "", app.config.secret_token
- assert_equal nil, app.secrets.secret_key_base
+ assert_nil app.secrets.secret_key_base
assert_raise ArgumentError, /\AA secret is required/ do
app.key_generator
end
end
+ test "that nested keys are symbolized the same as parents for hashes more than one level deep" do
+ app_file "config/secrets.yml", <<-YAML
+ development:
+ smtp_settings:
+ address: "smtp.example.com"
+ user_name: "postmaster@example.com"
+ password: "697361616320736c6f616e2028656c6f7265737429"
+ YAML
+
+ app "development"
+
+ assert_equal "697361616320736c6f616e2028656c6f7265737429", app.secrets.smtp_settings[:password]
+ end
+
test "protect from forgery is the default in a new app" do
make_basic_app
@@ -704,7 +718,7 @@ module ApplicationTests
end
def update
- render text: "update"
+ render plain: "update"
end
private
@@ -978,7 +992,7 @@ module ApplicationTests
class ::OmgController < ActionController::Base
def index
- render text: env["action_dispatch.show_exceptions"]
+ render plain: env["action_dispatch.show_exceptions"]
end
end
@@ -1008,7 +1022,7 @@ module ApplicationTests
app_file "app/controllers/posts_controller.rb", <<-RUBY
class PostsController < ApplicationController
def create
- render text: params[:post].inspect
+ render plain: params[:post].inspect
end
end
RUBY
@@ -1029,7 +1043,7 @@ module ApplicationTests
app_file "app/controllers/posts_controller.rb", <<-RUBY
class PostsController < ActionController::Base
def create
- render text: params[:post].permitted? ? "permitted" : "forbidden"
+ render plain: params[:post].permitted? ? "permitted" : "forbidden"
end
end
RUBY
@@ -1043,7 +1057,7 @@ module ApplicationTests
app "development"
- post "/posts", post: {"title" =>"zomg"}
+ post "/posts", post: { "title" => "zomg" }
assert_equal "permitted", last_response.body
end
@@ -1051,7 +1065,7 @@ module ApplicationTests
app_file "app/controllers/posts_controller.rb", <<-RUBY
class PostsController < ActionController::Base
def create
- render text: params.require(:post).permit(:name)
+ render plain: params.require(:post).permit(:name)
end
end
RUBY
@@ -1067,7 +1081,7 @@ module ApplicationTests
assert_equal :raise, ActionController::Parameters.action_on_unpermitted_parameters
- post "/posts", post: {"title" =>"zomg"}
+ post "/posts", post: { "title" => "zomg" }
assert_match "We're sorry, but something went wrong", last_response.body
end
@@ -1090,7 +1104,7 @@ module ApplicationTests
app_file "app/controllers/posts_controller.rb", <<-RUBY
class PostsController < ActionController::Base
def create
- render text: params.permit(post: [:title])
+ render plain: params.permit(post: [:title])
end
end
RUBY
@@ -1107,7 +1121,7 @@ module ApplicationTests
assert_equal :raise, ActionController::Parameters.action_on_unpermitted_parameters
- post "/posts", post: {"title" =>"zomg"}, format: "json"
+ post "/posts", post: { "title" => "zomg" }, format: "json"
assert_equal 200, last_response.status
end
@@ -1137,8 +1151,8 @@ module ApplicationTests
class ::OmgController < ActionController::Base
def index
respond_to do |format|
- format.html { render text: "HTML" }
- format.xml { render text: "XML" }
+ format.html { render plain: "HTML" }
+ format.xml { render plain: "XML" }
end
end
end
@@ -1190,7 +1204,7 @@ module ApplicationTests
application.config.session_store :disabled
end
- assert_equal nil, app.config.session_store
+ assert_nil app.config.session_store
end
test "default session store initializer sets session store to cookie store" do
@@ -1518,5 +1532,24 @@ module ApplicationTests
assert_equal :default, Rails.configuration.debug_exception_response_format
end
+
+ test "controller force_ssl declaration can be used even if session_store is disabled" do
+ make_basic_app do |application|
+ application.config.session_store :disabled
+ end
+
+ class ::OmgController < ActionController::Base
+ force_ssl
+
+ def index
+ render plain: "Yay! You're on Rails!"
+ end
+ end
+
+ get "/"
+
+ assert_equal 301, last_response.status
+ assert_equal "https://example.org/", last_response.location
+ end
end
end
diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb
index 3b8062f74b..72f340df34 100644
--- a/railties/test/application/console_test.rb
+++ b/railties/test/application/console_test.rb
@@ -126,7 +126,7 @@ class FullStackConsoleTest < ActiveSupport::TestCase
end
end
- assert output.include?(expected), "#{expected.inspect} expected, but got:\n\n#{output}"
+ assert_includes output, expected, "#{expected.inspect} expected, but got:\n\n#{output}"
end
def write_prompt(command, expected_output = nil)
diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb
index 0d7ccb2278..d2ce14f594 100644
--- a/railties/test/application/generators_test.rb
+++ b/railties/test/application/generators_test.rb
@@ -29,7 +29,7 @@ module ApplicationTests
end
test "allow running plugin new generator inside Rails app directory" do
- FileUtils.cd(rails_root){ `ruby bin/rails plugin new vendor/plugins/bukkits` }
+ FileUtils.cd(rails_root) { `ruby bin/rails plugin new vendor/plugins/bukkits` }
assert File.exist?(File.join(rails_root, "vendor/plugins/bukkits/test/dummy/config/application.rb"))
end
@@ -134,10 +134,10 @@ module ApplicationTests
require "#{app_path}/config/environment"
Rails.application.load_generators
- assert Rails::Generators.hidden_namespaces.include?("assets")
- assert Rails::Generators.hidden_namespaces.include?("helper")
- assert Rails::Generators.hidden_namespaces.include?("js")
- assert Rails::Generators.hidden_namespaces.include?("css")
+ assert_includes Rails::Generators.hidden_namespaces, "assets"
+ assert_includes Rails::Generators.hidden_namespaces, "helper"
+ assert_includes Rails::Generators.hidden_namespaces, "js"
+ assert_includes Rails::Generators.hidden_namespaces, "css"
assert Rails::Generators.options[:rails][:api]
assert_equal false, Rails::Generators.options[:rails][:assets]
assert_equal false, Rails::Generators.options[:rails][:helper]
@@ -165,9 +165,24 @@ module ApplicationTests
config.api_only = true
RUBY
- FileUtils.cd(rails_root){ `bin/rails generate mailer notifier foo` }
+ FileUtils.cd(rails_root) { `bin/rails generate mailer notifier foo` }
assert File.exist?(File.join(rails_root, "app/views/notifier_mailer/foo.text.erb"))
assert File.exist?(File.join(rails_root, "app/views/notifier_mailer/foo.html.erb"))
end
+
+ test "ARGV is mutated as expected" do
+ require "#{app_path}/config/environment"
+ Rails::Command.const_set("APP_PATH", "rails/all")
+
+ FileUtils.cd(rails_root) do
+ ARGV = ["mailer", "notifier", "foo"]
+ Rails::Command.const_set("ARGV", ARGV)
+ quietly { Rails::Command.invoke :generate, ARGV }
+
+ assert_equal ["notifier", "foo"], ARGV
+ end
+
+ Rails::Command.send(:remove_const, "APP_PATH")
+ end
end
end
diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb
index b2cd339c1a..90927159dd 100644
--- a/railties/test/application/initializers/frameworks_test.rb
+++ b/railties/test/application/initializers/frameworks_test.rb
@@ -185,7 +185,7 @@ module ApplicationTests
test "if there's no config.active_support.bare, all of ActiveSupport is required" do
use_frameworks []
require "#{app_path}/config/environment"
- assert_nothing_raised { [1,2,3].sample }
+ assert_nothing_raised { [1, 2, 3].sample }
end
test "config.active_support.bare does not require all of ActiveSupport" do
@@ -219,7 +219,7 @@ module ApplicationTests
end
require "#{app_path}/config/environment"
ActiveRecord::Base.connection.drop_table("posts") # force drop posts table for test.
- assert ActiveRecord::Base.connection.schema_cache.tables("posts")
+ assert ActiveRecord::Base.connection.schema_cache.data_sources("posts")
end
test "expire schema cache dump" do
@@ -227,10 +227,8 @@ module ApplicationTests
`rails generate model post title:string;
bin/rails db:migrate db:schema:cache:dump db:rollback`
end
- silence_warnings {
- require "#{app_path}/config/environment"
- assert !ActiveRecord::Base.connection.schema_cache.tables("posts")
- }
+ require "#{app_path}/config/environment"
+ assert !ActiveRecord::Base.connection.schema_cache.data_sources("posts")
end
test "active record establish_connection uses Rails.env if DATABASE_URL is not set" do
diff --git a/railties/test/application/initializers/hooks_test.rb b/railties/test/application/initializers/hooks_test.rb
index 0309d02730..36926c50ff 100644
--- a/railties/test/application/initializers/hooks_test.rb
+++ b/railties/test/application/initializers/hooks_test.rb
@@ -31,7 +31,7 @@ module ApplicationTests
RUBY
require "#{app_path}/config/environment"
- assert_equal [1,2,3], $initialization_callbacks
+ assert_equal [1, 2, 3], $initialization_callbacks
end
test "hooks block works correctly with eager_load" do
@@ -46,7 +46,7 @@ module ApplicationTests
RUBY
require "#{app_path}/config/environment"
- assert_equal [1,2,3,4], $initialization_callbacks
+ assert_equal [1, 2, 3, 4], $initialization_callbacks
end
test "after_initialize runs after frameworks have been initialized" do
diff --git a/railties/test/application/initializers/i18n_test.rb b/railties/test/application/initializers/i18n_test.rb
index 0ddaf346e0..206e42703b 100644
--- a/railties/test/application/initializers/i18n_test.rb
+++ b/railties/test/application/initializers/i18n_test.rb
@@ -30,7 +30,7 @@ module ApplicationTests
end
def assert_no_fallbacks
- assert !I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks)
+ assert_not_includes I18n.backend.class.included_modules, I18n::Backend::Fallbacks
end
# Locales
@@ -62,8 +62,8 @@ module ApplicationTests
"#{app_path}/config/locales/en.yml", "#{app_path}/config/another_locale.yml"
], Rails.application.config.i18n.load_path
- assert I18n.load_path.include?("#{app_path}/config/locales/en.yml")
- assert I18n.load_path.include?("#{app_path}/config/another_locale.yml")
+ assert_includes I18n.load_path, "#{app_path}/config/locales/en.yml"
+ assert_includes I18n.load_path, "#{app_path}/config/another_locale.yml"
end
test "load_path is populated before eager loaded models" do
@@ -214,7 +214,7 @@ fr:
test "config.i18n.fallbacks = true initializes I18n.fallbacks with default settings" do
I18n::Railtie.config.i18n.fallbacks = true
load_app
- assert I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks)
+ assert_includes I18n.backend.class.included_modules, I18n::Backend::Fallbacks
assert_fallbacks de: [:de, :en]
end
@@ -222,7 +222,7 @@ fr:
I18n::Railtie.config.i18n.fallbacks = true
I18n::Railtie.config.i18n.backend = Class.new(I18n::Backend::Simple).new
load_app
- assert I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks)
+ assert_includes I18n.backend.class.included_modules, I18n::Backend::Fallbacks
assert_fallbacks de: [:de, :en]
end
diff --git a/railties/test/application/initializers/load_path_test.rb b/railties/test/application/initializers/load_path_test.rb
index caab7c0c8a..dbefb22837 100644
--- a/railties/test/application/initializers/load_path_test.rb
+++ b/railties/test/application/initializers/load_path_test.rb
@@ -19,7 +19,7 @@ module ApplicationTests
RUBY
require "#{app_path}/config/environment"
- assert $:.include?("#{app_path}/app/models")
+ assert_includes $:, "#{app_path}/app/models"
end
test "initializing an application allows to load code on lib path inside application class definition" do
@@ -36,7 +36,7 @@ module ApplicationTests
require "#{app_path}/config/environment"
end
- assert $:.include?("#{app_path}/lib")
+ assert_includes $:, "#{app_path}/lib"
end
test "initializing an application eager load any path under app" do
diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb
index d57384d256..c75a25bc6f 100644
--- a/railties/test/application/loading_test.rb
+++ b/railties/test/application/loading_test.rb
@@ -298,7 +298,7 @@ class LoadingTest < ActiveSupport::TestCase
end
MIGRATION
- Dir.chdir(app_path) { `rake db:migrate`}
+ Dir.chdir(app_path) { `rake db:migrate` }
require "#{rails_root}/config/environment"
get "/title"
@@ -357,7 +357,7 @@ class LoadingTest < ActiveSupport::TestCase
assert Rails.application.initialized?
end
- protected
+ private
def setup_ar!
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
diff --git a/railties/test/application/middleware/cache_test.rb b/railties/test/application/middleware/cache_test.rb
index be44aa774f..dc1d816dc5 100644
--- a/railties/test/application/middleware/cache_test.rb
+++ b/railties/test/application/middleware/cache_test.rb
@@ -19,7 +19,7 @@ module ApplicationTests
class ExpiresController < ApplicationController
def expires_header
expires_in 10, public: !params[:private]
- render text: SecureRandom.hex(16)
+ render plain: SecureRandom.hex(16)
end
def expires_etag
@@ -32,12 +32,12 @@ module ApplicationTests
end
def keeps_if_modified_since
- render :text => request.headers['If-Modified-Since']
+ render plain: request.headers['If-Modified-Since']
end
private
def render_conditionally(headers)
if stale?(headers.merge(public: !params[:private]))
- render text: SecureRandom.hex(16)
+ render plain: SecureRandom.hex(16)
end
end
end
@@ -137,7 +137,7 @@ module ApplicationTests
body = last_response.body
etag = last_response.headers["ETag"]
- get "/expires/expires_etag", {private: true}, "If-None-Match" => etag
+ get "/expires/expires_etag", { private: true }, "If-None-Match" => etag
assert_equal "miss", last_response.headers["X-Rack-Cache"]
assert_not_equal body, last_response.body
end
@@ -171,7 +171,7 @@ module ApplicationTests
body = last_response.body
last = last_response.headers["Last-Modified"]
- get "/expires/expires_last_modified", {private: true}, "If-Modified-Since" => last
+ get "/expires/expires_last_modified", { private: true }, "If-Modified-Since" => last
assert_equal "miss", last_response.headers["X-Rack-Cache"]
assert_not_equal body, last_response.body
end
diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb
index 2873425703..959a629ede 100644
--- a/railties/test/application/middleware/session_test.rb
+++ b/railties/test/application/middleware/session_test.rb
@@ -70,7 +70,7 @@ module ApplicationTests
end
def read_session
- render text: session[:foo].inspect
+ render plain: session[:foo].inspect
end
end
RUBY
@@ -111,7 +111,7 @@ module ApplicationTests
end
def read_cookie
- render text: cookies[:foo].inspect
+ render plain: cookies[:foo].inspect
end
end
RUBY
@@ -149,15 +149,15 @@ module ApplicationTests
end
def read_session
- render text: session[:foo]
+ render plain: session[:foo]
end
def read_encrypted_cookie
- render text: cookies.encrypted[:_myapp_session]['foo']
+ render plain: cookies.encrypted[:_myapp_session]['foo']
end
def read_raw_cookie
- render text: cookies[:_myapp_session]
+ render plain: cookies[:_myapp_session]
end
end
RUBY
@@ -173,7 +173,7 @@ module ApplicationTests
secret = app.key_generator.generate_key("encrypted cookie")
sign_secret = app.key_generator.generate_key("signed encrypted cookie")
- encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
+ encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret)
get "/foo/read_raw_cookie"
assert_equal 1, encryptor.decrypt_and_verify(last_response.body)["foo"]
@@ -194,15 +194,15 @@ module ApplicationTests
end
def read_session
- render text: session[:foo]
+ render plain: session[:foo]
end
def read_encrypted_cookie
- render text: cookies.encrypted[:_myapp_session]['foo']
+ render plain: cookies.encrypted[:_myapp_session]['foo']
end
def read_raw_cookie
- render text: cookies[:_myapp_session]
+ render plain: cookies[:_myapp_session]
end
end
RUBY
@@ -222,7 +222,7 @@ module ApplicationTests
secret = app.key_generator.generate_key("encrypted cookie")
sign_secret = app.key_generator.generate_key("signed encrypted cookie")
- encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
+ encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret)
get "/foo/read_raw_cookie"
assert_equal 1, encryptor.decrypt_and_verify(last_response.body)["foo"]
@@ -249,15 +249,15 @@ module ApplicationTests
end
def read_session
- render text: session[:foo]
+ render plain: session[:foo]
end
def read_encrypted_cookie
- render text: cookies.encrypted[:_myapp_session]['foo']
+ render plain: cookies.encrypted[:_myapp_session]['foo']
end
def read_raw_cookie
- render text: cookies[:_myapp_session]
+ render plain: cookies[:_myapp_session]
end
end
RUBY
@@ -281,7 +281,7 @@ module ApplicationTests
secret = app.key_generator.generate_key("encrypted cookie")
sign_secret = app.key_generator.generate_key("signed encrypted cookie")
- encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
+ encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret)
get "/foo/read_raw_cookie"
assert_equal 2, encryptor.decrypt_and_verify(last_response.body)["foo"]
@@ -308,15 +308,15 @@ module ApplicationTests
end
def read_session
- render text: session[:foo]
+ render plain: session[:foo]
end
def read_signed_cookie
- render text: cookies.signed[:_myapp_session]['foo']
+ render plain: cookies.signed[:_myapp_session]['foo']
end
def read_raw_cookie
- render text: cookies[:_myapp_session]
+ render plain: cookies[:_myapp_session]
end
end
RUBY
@@ -370,7 +370,7 @@ module ApplicationTests
assert_equal 200, last_response.status
assert_equal "It worked!", last_response.body
- refute Rails.application.middleware.include?(ActionDispatch::Flash)
+ assert_not_includes Rails.application.middleware, ActionDispatch::Flash
end
test "cookie_only is set to true even if user tries to overwrite it" do
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index 3b48ada190..0a6e5b52e9 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -30,10 +30,10 @@ module ApplicationTests
"Rack::Runtime",
"Rack::MethodOverride",
"ActionDispatch::RequestId",
- "Rails::Rack::Logger", # must come after Rack::MethodOverride to properly log overridden methods
+ "ActionDispatch::RemoteIp",
+ "Rails::Rack::Logger",
"ActionDispatch::ShowExceptions",
"ActionDispatch::DebugExceptions",
- "ActionDispatch::RemoteIp",
"ActionDispatch::Reloader",
"ActionDispatch::Callbacks",
"ActiveRecord::Migration::CheckPending",
@@ -58,10 +58,10 @@ module ApplicationTests
"ActiveSupport::Cache::Strategy::LocalCache",
"Rack::Runtime",
"ActionDispatch::RequestId",
- "Rails::Rack::Logger", # must come after Rack::MethodOverride to properly log overridden methods
+ "ActionDispatch::RemoteIp",
+ "Rails::Rack::Logger",
"ActionDispatch::ShowExceptions",
"ActionDispatch::DebugExceptions",
- "ActionDispatch::RemoteIp",
"ActionDispatch::Reloader",
"ActionDispatch::Callbacks",
"Rack::Head",
@@ -70,10 +70,41 @@ module ApplicationTests
], middleware
end
+ test "middleware dependencies" do
+ boot!
+
+ # The following array-of-arrays describes dependencies between
+ # middlewares: the first item in each list depends on the
+ # remaining items (and therefore must occur later in the
+ # middleware stack).
+
+ dependencies = [
+ # Logger needs a fully "corrected" request environment
+ %w(Rails::Rack::Logger Rack::MethodOverride ActionDispatch::RequestId ActionDispatch::RemoteIp),
+
+ # Serving public/ doesn't invoke user code, so it should skip
+ # locks etc
+ %w(ActionDispatch::Executor ActionDispatch::Static),
+
+ # Errors during reload must be reported
+ %w(ActionDispatch::Reloader ActionDispatch::ShowExceptions ActionDispatch::DebugExceptions),
+
+ # Outright dependencies
+ %w(ActionDispatch::Static Rack::Sendfile),
+ %w(ActionDispatch::Flash ActionDispatch::Session::CookieStore),
+ %w(ActionDispatch::Session::CookieStore ActionDispatch::Cookies),
+ ]
+
+ require "tsort"
+ sorted = TSort.tsort((middleware | dependencies.flatten).method(:each),
+ lambda { |n, &b| dependencies.each { |m, *ds| ds.each(&b) if m == n } })
+ assert_equal sorted, middleware
+ end
+
test "Rack::Cache is not included by default" do
boot!
- assert !middleware.include?("Rack::Cache"), "Rack::Cache is not included in the default stack unless you set config.action_dispatch.rack_cache"
+ assert_not_includes middleware, "Rack::Cache", "Rack::Cache is not included in the default stack unless you set config.action_dispatch.rack_cache"
end
test "Rack::Cache is present when action_dispatch.rack_cache is set" do
@@ -81,7 +112,7 @@ module ApplicationTests
boot!
- assert middleware.include?("Rack::Cache")
+ assert_includes middleware, "Rack::Cache"
end
test "ActiveRecord::Migration::CheckPending is present when active_record.migration_error is set to :page_load" do
@@ -89,27 +120,27 @@ module ApplicationTests
boot!
- assert middleware.include?("ActiveRecord::Migration::CheckPending")
+ assert_includes middleware, "ActiveRecord::Migration::CheckPending"
end
test "ActionDispatch::SSL is present when force_ssl is set" do
add_to_config "config.force_ssl = true"
boot!
- assert middleware.include?("ActionDispatch::SSL")
+ assert_includes middleware, "ActionDispatch::SSL"
end
test "ActionDispatch::SSL is configured with options when given" do
add_to_config "config.force_ssl = true"
- add_to_config "config.ssl_options = { host: 'example.com' }"
+ add_to_config "config.ssl_options = { redirect: { host: 'example.com' } }"
boot!
- assert_equal [{host: "example.com"}], Rails.application.middleware.first.args
+ assert_equal [{ redirect: { host: "example.com" } }], Rails.application.middleware.first.args
end
test "removing Active Record omits its middleware" do
use_frameworks []
boot!
- assert !middleware.include?("ActiveRecord::Migration::CheckPending")
+ assert_not_includes middleware, "ActiveRecord::Migration::CheckPending"
end
test "includes executor" do
@@ -139,20 +170,20 @@ module ApplicationTests
test "removes static asset server if public_file_server.enabled is disabled" do
add_to_config "config.public_file_server.enabled = false"
boot!
- assert !middleware.include?("ActionDispatch::Static")
+ assert_not_includes middleware, "ActionDispatch::Static"
end
test "can delete a middleware from the stack" do
add_to_config "config.middleware.delete ActionDispatch::Static"
boot!
- assert !middleware.include?("ActionDispatch::Static")
+ assert_not_includes middleware, "ActionDispatch::Static"
end
test "can delete a middleware from the stack even if insert_before is added after delete" do
add_to_config "config.middleware.delete Rack::Runtime"
add_to_config "config.middleware.insert_before(Rack::Runtime, Rack::Config)"
boot!
- assert middleware.include?("Rack::Config")
+ assert_includes middleware, "Rack::Config"
assert_not middleware.include?("Rack::Runtime")
end
@@ -160,21 +191,21 @@ module ApplicationTests
add_to_config "config.middleware.delete Rack::Runtime"
add_to_config "config.middleware.insert_after(Rack::Runtime, Rack::Config)"
boot!
- assert middleware.include?("Rack::Config")
+ assert_includes middleware, "Rack::Config"
assert_not middleware.include?("Rack::Runtime")
end
test "includes exceptions middlewares even if action_dispatch.show_exceptions is disabled" do
add_to_config "config.action_dispatch.show_exceptions = false"
boot!
- assert middleware.include?("ActionDispatch::ShowExceptions")
- assert middleware.include?("ActionDispatch::DebugExceptions")
+ assert_includes middleware, "ActionDispatch::ShowExceptions"
+ assert_includes middleware, "ActionDispatch::DebugExceptions"
end
test "removes ActionDispatch::Reloader if cache_classes is true" do
add_to_config "config.cache_classes = true"
boot!
- assert !middleware.include?("ActionDispatch::Reloader")
+ assert_not_includes middleware, "ActionDispatch::Reloader"
end
test "use middleware" do
@@ -227,9 +258,9 @@ module ApplicationTests
class ::OmgController < ActionController::Base
def index
if params[:nothing]
- render text: ""
+ render plain: ""
else
- render text: "OMG"
+ render plain: "OMG"
end
end
end
@@ -239,23 +270,23 @@ module ApplicationTests
get "/"
assert_equal 200, last_response.status
assert_equal "OMG", last_response.body
- assert_equal "text/html; charset=utf-8", last_response.headers["Content-Type"]
+ assert_equal "text/plain; charset=utf-8", last_response.headers["Content-Type"]
assert_equal "max-age=0, private, must-revalidate", last_response.headers["Cache-Control"]
assert_equal etag, last_response.headers["Etag"]
get "/", {}, "HTTP_IF_NONE_MATCH" => etag
assert_equal 304, last_response.status
assert_equal "", last_response.body
- assert_equal nil, last_response.headers["Content-Type"]
+ assert_nil last_response.headers["Content-Type"]
assert_equal "max-age=0, private, must-revalidate", last_response.headers["Cache-Control"]
assert_equal etag, last_response.headers["Etag"]
get "/?nothing=true"
assert_equal 200, last_response.status
assert_equal "", last_response.body
- assert_equal "text/html; charset=utf-8", last_response.headers["Content-Type"]
+ assert_equal "text/plain; charset=utf-8", last_response.headers["Content-Type"]
assert_equal "no-cache", last_response.headers["Cache-Control"]
- assert_equal nil, last_response.headers["Etag"]
+ assert_nil last_response.headers["Etag"]
end
test "ORIGINAL_FULLPATH is passed to env" do
diff --git a/railties/test/application/paths_test.rb b/railties/test/application/paths_test.rb
index 8eb1d42b33..515205296c 100644
--- a/railties/test/application/paths_test.rb
+++ b/railties/test/application/paths_test.rb
@@ -55,9 +55,9 @@ module ApplicationTests
test "booting up Rails yields a list of paths that are eager" do
eager_load = @paths.eager_load
- assert eager_load.include?(root("app/controllers"))
- assert eager_load.include?(root("app/helpers"))
- assert eager_load.include?(root("app/models"))
+ assert_includes eager_load, root("app/controllers")
+ assert_includes eager_load, root("app/helpers")
+ assert_includes eager_load, root("app/models")
end
test "environments has a glob equal to the current environment" do
diff --git a/railties/test/application/rake/log_test.rb b/railties/test/application/rake/log_test.rb
new file mode 100644
index 0000000000..fdd3c71fe8
--- /dev/null
+++ b/railties/test/application/rake/log_test.rb
@@ -0,0 +1,33 @@
+require "isolation/abstract_unit"
+
+module ApplicationTests
+ module RakeTests
+ class LogTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def setup
+ build_app
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ test "log:clear clear all environments log files by default" do
+ Dir.chdir(app_path) do
+ File.open("config/environments/staging.rb", "w")
+
+ File.write("log/staging.log", "staging")
+ File.write("log/test.log", "test")
+ File.write("log/dummy.log", "dummy")
+
+ `rails log:clear`
+
+ assert_equal 0, File.size("log/test.log")
+ assert_equal 0, File.size("log/staging.log")
+ assert_equal 5, File.size("log/dummy.log")
+ end
+ end
+ end
+ end
+end
diff --git a/railties/test/application/rake/migrations_test.rb b/railties/test/application/rake/migrations_test.rb
index d608fa1976..76cb302c62 100644
--- a/railties/test/application/rake/migrations_test.rb
+++ b/railties/test/application/rake/migrations_test.rb
@@ -57,7 +57,7 @@ module ApplicationTests
end
test "migration status when schema migrations table is not present" do
- output = Dir.chdir(app_path){ `bin/rails db:migrate:status 2>&1` }
+ output = Dir.chdir(app_path) { `bin/rails db:migrate:status 2>&1` }
assert_equal "Schema migrations table does not exist yet.\n", output
end
diff --git a/railties/test/application/rake/restart_test.rb b/railties/test/application/rake/restart_test.rb
index 28da8164a7..6ebd2d5461 100644
--- a/railties/test/application/rake/restart_test.rb
+++ b/railties/test/application/rake/restart_test.rb
@@ -13,32 +13,32 @@ module ApplicationTests
teardown_app
end
- test "rake restart touches tmp/restart.txt" do
+ test "rails restart touches tmp/restart.txt" do
Dir.chdir(app_path) do
- `rake restart`
+ `bin/rails restart`
assert File.exist?("tmp/restart.txt")
prev_mtime = File.mtime("tmp/restart.txt")
sleep(1)
- `rake restart`
+ `bin/rails restart`
curr_mtime = File.mtime("tmp/restart.txt")
assert_not_equal prev_mtime, curr_mtime
end
end
- test "rake restart should work even if tmp folder does not exist" do
+ test "rails restart should work even if tmp folder does not exist" do
Dir.chdir(app_path) do
FileUtils.remove_dir("tmp")
- `rake restart`
+ `bin/rails restart`
assert File.exist?("tmp/restart.txt")
end
end
- test "rake restart removes server.pid also" do
+ test "rails restart removes server.pid also" do
Dir.chdir(app_path) do
FileUtils.mkdir_p("tmp/pids")
FileUtils.touch("tmp/pids/server.pid")
- `rake restart`
+ `bin/rails restart`
assert_not File.exist?("tmp/pids/server.pid")
end
end
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index 5bb97a593d..d80a45a83f 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -54,7 +54,7 @@ module ApplicationTests
Rails.application.initialize!
RUBY
- assert_match("SuperMiddleware", Dir.chdir(app_path){ `bin/rails middleware` })
+ assert_match("SuperMiddleware", Dir.chdir(app_path) { `bin/rails middleware` })
end
def test_initializers_are_executed_in_rake_tasks
@@ -69,7 +69,7 @@ module ApplicationTests
end
RUBY
- output = Dir.chdir(app_path){ `bin/rails do_nothing` }
+ output = Dir.chdir(app_path) { `bin/rails do_nothing` }
assert_match "Doing something...", output
end
@@ -128,7 +128,7 @@ module ApplicationTests
end
RUBY
- output = Dir.chdir(app_path){ `bin/rails routes` }
+ output = Dir.chdir(app_path) { `bin/rails routes` }
assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
end
@@ -140,13 +140,13 @@ module ApplicationTests
end
RUBY
- output = Dir.chdir(app_path){ `bin/rails routes CONTROLLER=cart` }
+ output = Dir.chdir(app_path) { `bin/rails routes CONTROLLER=cart` }
assert_equal ["Passing `CONTROLLER` to `bin/rails routes` is deprecated and will be removed in Rails 5.1.",
"Please use `bin/rails routes -c controller_name` instead.",
"Prefix Verb URI Pattern Controller#Action",
" cart GET /cart(.:format) cart#show\n"].join("\n"), output
- output = Dir.chdir(app_path){ `bin/rails routes -c cart` }
+ output = Dir.chdir(app_path) { `bin/rails routes -c cart` }
assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
end
@@ -159,18 +159,38 @@ module ApplicationTests
end
RUBY
expected_output = [" Prefix Verb URI Pattern Controller#Action",
- " admin_post POST /admin/post(.:format) admin/posts#create",
" new_admin_post GET /admin/post/new(.:format) admin/posts#new",
"edit_admin_post GET /admin/post/edit(.:format) admin/posts#edit",
- " GET /admin/post(.:format) admin/posts#show",
+ " admin_post GET /admin/post(.:format) admin/posts#show",
" PATCH /admin/post(.:format) admin/posts#update",
" PUT /admin/post(.:format) admin/posts#update",
- " DELETE /admin/post(.:format) admin/posts#destroy\n"].join("\n")
+ " DELETE /admin/post(.:format) admin/posts#destroy",
+ " POST /admin/post(.:format) admin/posts#create\n"].join("\n")
+
+ output = Dir.chdir(app_path) { `bin/rails routes -c Admin::PostController` }
+ assert_equal expected_output, output
- output = Dir.chdir(app_path){ `bin/rails routes -c Admin::PostController` }
+ output = Dir.chdir(app_path) { `bin/rails routes -c PostController` }
assert_equal expected_output, output
+ end
+
+ def test_singular_resource_output_in_rake_routes
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ resource :post
+ end
+ RUBY
+
+ expected_output = [" Prefix Verb URI Pattern Controller#Action",
+ " new_post GET /post/new(.:format) posts#new",
+ "edit_post GET /post/edit(.:format) posts#edit",
+ " post GET /post(.:format) posts#show",
+ " PATCH /post(.:format) posts#update",
+ " PUT /post(.:format) posts#update",
+ " DELETE /post(.:format) posts#destroy",
+ " POST /post(.:format) posts#create\n"].join("\n")
- output = Dir.chdir(app_path){ `bin/rails routes -c PostController` }
+ output = Dir.chdir(app_path) { `bin/rails routes -c PostController` }
assert_equal expected_output, output
end
@@ -183,13 +203,13 @@ module ApplicationTests
end
RUBY
- output = Dir.chdir(app_path){ `bin/rails routes -g show` }
+ output = Dir.chdir(app_path) { `bin/rails routes -g show` }
assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
- output = Dir.chdir(app_path){ `bin/rails routes -g POST` }
+ output = Dir.chdir(app_path) { `bin/rails routes -g POST` }
assert_equal "Prefix Verb URI Pattern Controller#Action\n POST /cart(.:format) cart#create\n", output
- output = Dir.chdir(app_path){ `bin/rails routes -g basketballs` }
+ output = Dir.chdir(app_path) { `bin/rails routes -g basketballs` }
assert_equal " Prefix Verb URI Pattern Controller#Action\n" \
"basketballs GET /basketballs(.:format) basketball#index\n", output
end
@@ -202,13 +222,13 @@ module ApplicationTests
end
RUBY
- output = Dir.chdir(app_path){ `bin/rails routes -c cart` }
+ output = Dir.chdir(app_path) { `bin/rails routes -c cart` }
assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
- output = Dir.chdir(app_path){ `bin/rails routes -c Cart` }
+ output = Dir.chdir(app_path) { `bin/rails routes -c Cart` }
assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
- output = Dir.chdir(app_path){ `bin/rails routes -c CartController` }
+ output = Dir.chdir(app_path) { `bin/rails routes -c CartController` }
assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
end
@@ -218,7 +238,7 @@ module ApplicationTests
end
RUBY
- assert_equal <<-MESSAGE.strip_heredoc, Dir.chdir(app_path){ `bin/rails routes` }
+ assert_equal <<-MESSAGE.strip_heredoc, Dir.chdir(app_path) { `bin/rails routes` }
You don't have any routes defined!
Please add some routes in config/routes.rb.
@@ -234,7 +254,7 @@ module ApplicationTests
end
RUBY
- output = Dir.chdir(app_path){ `bin/rake --rakefile Rakefile routes` }
+ output = Dir.chdir(app_path) { `bin/rake --rakefile Rakefile routes` }
assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
end
@@ -247,7 +267,7 @@ module ApplicationTests
end
RUBY
- output = Dir.chdir(app_path){ `bin/rails log_something RAILS_ENV=production && cat log/production.log` }
+ output = Dir.chdir(app_path) { `bin/rails log_something RAILS_ENV=production && cat log/production.log` }
assert_match "Sample log message", output
end
@@ -367,14 +387,14 @@ module ApplicationTests
bin/rails generate model product name:string;
bin/rails db:migrate db:schema:cache:dump`
end
- assert File.exist?(File.join(app_path, "db", "schema_cache.dump"))
+ assert File.exist?(File.join(app_path, "db", "schema_cache.yml"))
end
def test_rake_clear_schema_cache
Dir.chdir(app_path) do
`bin/rails db:schema:cache:dump db:schema:cache:clear`
end
- assert !File.exist?(File.join(app_path, "db", "schema_cache.dump"))
+ assert !File.exist?(File.join(app_path, "db", "schema_cache.yml"))
end
def test_copy_templates
diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb
index c70af162ae..c515e2b270 100644
--- a/railties/test/application/routing_test.rb
+++ b/railties/test/application/routing_test.rb
@@ -65,7 +65,7 @@ module ApplicationTests
controller :foo, <<-RUBY
class FooController < ApplicationController
def index
- render text: "foo"
+ render plain: "foo"
end
end
RUBY
@@ -156,7 +156,7 @@ module ApplicationTests
controller :foo, <<-RUBY
class FooController < ApplicationController
def index
- render text: my_blog_path
+ render plain: my_blog_path
end
end
RUBY
@@ -176,7 +176,7 @@ module ApplicationTests
controller :foo, <<-RUBY
class FooController < ApplicationController
def index
- render text: "foo"
+ render plain: "foo"
end
end
RUBY
@@ -184,7 +184,7 @@ module ApplicationTests
controller :bar, <<-RUBY
class BarController < ActionController::Base
def index
- render text: "bar"
+ render plain: "bar"
end
end
RUBY
@@ -206,7 +206,7 @@ module ApplicationTests
controller "foo", <<-RUBY
class FooController < ApplicationController
def index
- render text: "foo"
+ render plain: "foo"
end
end
RUBY
@@ -215,7 +215,7 @@ module ApplicationTests
module Admin
class FooController < ApplicationController
def index
- render text: "admin::foo"
+ render plain: "admin::foo"
end
end
end
@@ -263,16 +263,16 @@ module ApplicationTests
assert_equal "WIN", last_response.body
end
- {"development" => "baz", "production" => "bar"}.each do |mode, expected|
+ { "development" => "baz", "production" => "bar" }.each do |mode, expected|
test "reloads routes when configuration is changed in #{mode}" do
controller :foo, <<-RUBY
class FooController < ApplicationController
def bar
- render text: "bar"
+ render plain: "bar"
end
def baz
- render text: "baz"
+ render plain: "baz"
end
end
RUBY
@@ -332,7 +332,7 @@ module ApplicationTests
controller :foo, <<-RUBY
class FooController < ApplicationController
def index
- render :text => "foo"
+ render plain: "foo"
end
end
RUBY
@@ -356,7 +356,7 @@ module ApplicationTests
controller :foo, <<-RUBY
class FooController < ApplicationController
def index
- render text: "foo"
+ render plain: "foo"
end
end
RUBY
@@ -364,7 +364,7 @@ module ApplicationTests
controller :bar, <<-RUBY
class BarController < ApplicationController
def index
- render text: "bar"
+ render plain: "bar"
end
end
RUBY
@@ -427,7 +427,7 @@ module ApplicationTests
controller :foo, <<-RUBY
class FooController < ApplicationController
def index
- render text: "foo"
+ render plain: "foo"
end
end
RUBY
@@ -435,7 +435,7 @@ module ApplicationTests
controller :bar, <<-RUBY
class BarController < ApplicationController
def index
- render text: "bar"
+ render plain: "bar"
end
end
RUBY
@@ -482,7 +482,7 @@ module ApplicationTests
controller "yazilar", <<-RUBY
class YazilarController < ApplicationController
def index
- render text: 'yazilar#index'
+ render plain: 'yazilar#index'
end
end
RUBY
diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb
index 4dc766dfda..7d058f6ee6 100644
--- a/railties/test/application/runner_test.rb
+++ b/railties/test/application/runner_test.rb
@@ -43,6 +43,15 @@ module ApplicationTests
assert_match "42", Dir.chdir(app_path) { `bin/rails runner "bin/count_users.rb"` }
end
+ def test_no_minitest_loaded_in_production_mode
+ app_file "bin/print_features.rb", <<-SCRIPT
+ p $LOADED_FEATURES.grep(/minitest/)
+ SCRIPT
+ assert_match "[]", Dir.chdir(app_path) {
+ `RAILS_ENV=production bin/rails runner "bin/print_features.rb"`
+ }
+ end
+
def test_should_set_dollar_0_to_file
app_file "bin/dollar0.rb", <<-SCRIPT
puts $0
@@ -59,6 +68,14 @@ module ApplicationTests
assert_match "bin/program_name.rb", Dir.chdir(app_path) { `bin/rails runner "bin/program_name.rb"` }
end
+ def test_passes_extra_args_to_file
+ app_file "bin/program_name.rb", <<-SCRIPT
+ p ARGV
+ SCRIPT
+
+ assert_match %w( a b ).to_s, Dir.chdir(app_path) { `bin/rails runner "bin/program_name.rb" a b` }
+ end
+
def test_with_hook
add_to_config <<-RUBY
runner do |app|
@@ -82,7 +99,7 @@ module ApplicationTests
def test_runner_detects_bad_script_name
output = Dir.chdir(app_path) { `bin/rails runner "iuiqwiourowe" 2>&1` }
assert_not $?.success?
- assert_match "undefined local variable or method `iuiqwiourowe' for main:Object", output
+ assert_match "undefined local variable or method `iuiqwiourowe' for", output
end
def test_environment_with_rails_env
diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb
index b442891769..0939587960 100644
--- a/railties/test/application/test_runner_test.rb
+++ b/railties/test/application/test_runner_test.rb
@@ -495,7 +495,7 @@ module ApplicationTests
create_test_file :models, "post", pass: false
# This specifically verifies TEST for backwards compatibility with rake test
# as bin/rails test already supports running tests from a single file more cleanly.
- output = Dir.chdir(app_path) { `bin/rake test TEST=test/models/post_test.rb` }
+ output = Dir.chdir(app_path) { `bin/rake test TEST=test/models/post_test.rb` }
assert_match "PostTest", output, "passing TEST= should run selected test"
assert_no_match "AccountTest", output, "passing TEST= should only run selected test"
@@ -504,7 +504,7 @@ module ApplicationTests
def test_pass_rake_options
create_test_file :models, "account"
- output = Dir.chdir(app_path) { `bin/rake --rakefile Rakefile --trace=stdout test` }
+ output = Dir.chdir(app_path) { `bin/rake --rakefile Rakefile --trace=stdout test` }
assert_match "1 runs, 1 assertions", output
assert_match "Execute test", output
@@ -512,26 +512,26 @@ module ApplicationTests
def test_rails_db_create_all_restores_db_connection
create_test_file :models, "account"
- output = Dir.chdir(app_path) { `bin/rails db:create:all db:migrate && echo ".tables" | rails dbconsole` }
+ output = Dir.chdir(app_path) { `bin/rails db:create:all db:migrate && echo ".tables" | rails dbconsole` }
assert_match "ar_internal_metadata", output, "tables should be dumped"
end
def test_rails_db_create_all_restores_db_connection_after_drop
create_test_file :models, "account"
Dir.chdir(app_path) { `bin/rails db:create:all` } # create all to avoid warnings
- output = Dir.chdir(app_path) { `bin/rails db:drop:all db:create:all db:migrate && echo ".tables" | rails dbconsole` }
+ output = Dir.chdir(app_path) { `bin/rails db:drop:all db:create:all db:migrate && echo ".tables" | rails dbconsole` }
assert_match "ar_internal_metadata", output, "tables should be dumped"
end
def test_rake_passes_TESTOPTS_to_minitest
create_test_file :models, "account"
- output = Dir.chdir(app_path) { `bin/rake test TESTOPTS=-v` }
+ output = Dir.chdir(app_path) { `bin/rake test TESTOPTS=-v` }
assert_match "AccountTest#test_truth", output, "passing TEST= should run selected test"
end
def test_rake_passes_multiple_TESTOPTS_to_minitest
create_test_file :models, "account"
- output = Dir.chdir(app_path) { `bin/rake test TESTOPTS='-v --seed=1234'` }
+ output = Dir.chdir(app_path) { `bin/rake test TESTOPTS='-v --seed=1234'` }
assert_match "AccountTest#test_truth", output, "passing TEST= should run selected test"
assert_match "seed=1234", output, "passing TEST= should run selected test"
end
diff --git a/railties/test/application/test_test.rb b/railties/test/application/test_test.rb
index ef65c7a893..32d2a6857c 100644
--- a/railties/test/application/test_test.rb
+++ b/railties/test/application/test_test.rb
@@ -12,7 +12,7 @@ module ApplicationTests
teardown_app
end
- test "truth" do
+ test "simple successful test" do
app_file "test/unit/foo_test.rb", <<-RUBY
require 'test_helper'
@@ -26,6 +26,38 @@ module ApplicationTests
assert_successful_test_run "unit/foo_test.rb"
end
+ test "after_run" do
+ app_file "test/unit/foo_test.rb", <<-RUBY
+ require 'test_helper'
+
+ Minitest.after_run { puts "WORLD" }
+ Minitest.after_run { puts "HELLO" }
+
+ class FooTest < ActiveSupport::TestCase
+ def test_truth
+ assert true
+ end
+ end
+ RUBY
+
+ result = assert_successful_test_run "unit/foo_test.rb"
+ assert_equal ["HELLO", "WORLD"], result.scan(/HELLO|WORLD/) # only once and in correct order
+ end
+
+ test "simple failed test" do
+ app_file "test/unit/foo_test.rb", <<-RUBY
+ require 'test_helper'
+
+ class FooTest < ActiveSupport::TestCase
+ def test_truth
+ assert false
+ end
+ end
+ RUBY
+
+ assert_unsuccessful_run "unit/foo_test.rb", "Failed assertion"
+ end
+
test "integration test" do
controller "posts", <<-RUBY
class PostsController < ActionController::Base
@@ -101,7 +133,7 @@ module ApplicationTests
File.delete "#{app_path}/config/initializers/disable_maintain_test_schema.rb"
result = assert_successful_test_run("models/user_test.rb")
- assert !result.include?("create_table(:users)")
+ assert_not_includes result, "create_table(:users)"
end
test "sql structure migrations" do
@@ -289,7 +321,7 @@ Expected: ["id", "name"]
def assert_unsuccessful_run(name, message)
result = run_test_file(name)
assert_not_equal 0, $?.to_i
- assert result.include?(message)
+ assert_includes result, message
result
end
diff --git a/railties/test/application/url_generation_test.rb b/railties/test/application/url_generation_test.rb
index ced6c6b0a6..37f129475c 100644
--- a/railties/test/application/url_generation_test.rb
+++ b/railties/test/application/url_generation_test.rb
@@ -27,7 +27,7 @@ module ApplicationTests
class ::OmgController < ::ApplicationController
def index
- render text: omg_path
+ render plain: omg_path
end
end
diff --git a/railties/test/backtrace_cleaner_test.rb b/railties/test/backtrace_cleaner_test.rb
index 17201d6f77..f71e56f323 100644
--- a/railties/test/backtrace_cleaner_test.rb
+++ b/railties/test/backtrace_cleaner_test.rb
@@ -15,7 +15,7 @@ class BacktraceCleanerTest < ActiveSupport::TestCase
test "should format installed gems not in Gem.default_dir correctly" do
target_dir = Gem.path.detect { |p| p != Gem.default_dir }
# skip this test if default_dir is the only directory on Gem.path
- if @target_dir
+ if target_dir
backtrace = [ "#{target_dir}/gems/nosuchgem-1.2.3/lib/foo.rb" ]
result = @cleaner.clean(backtrace, :all)
assert_equal "nosuchgem (1.2.3) lib/foo.rb", result[0]
diff --git a/railties/test/code_statistics_calculator_test.rb b/railties/test/code_statistics_calculator_test.rb
index 2dba3c6e96..1bd4225f34 100644
--- a/railties/test/code_statistics_calculator_test.rb
+++ b/railties/test/code_statistics_calculator_test.rb
@@ -24,7 +24,7 @@ class CodeStatisticsCalculatorTest < ActiveSupport::TestCase
end
end
- test "count number of methods in MiniTest file" do
+ test "count number of methods in Minitest file" do
code = <<-RUBY
class FooTest < ActionController::TestCase
test 'expectation' do
@@ -52,7 +52,7 @@ class CodeStatisticsCalculatorTest < ActiveSupport::TestCase
assert_equal 3, @code_statistics_calculator.classes
assert_equal 4, @code_statistics_calculator.methods
- code_statistics_calculator_2 = CodeStatisticsCalculator.new(2, 3, 4, 5)
+ code_statistics_calculator_2 = CodeStatisticsCalculator.new(2, 3, 4, 5)
@code_statistics_calculator.add(code_statistics_calculator_2)
assert_equal 3, @code_statistics_calculator.lines
diff --git a/railties/test/commands/console_test.rb b/railties/test/commands/console_test.rb
index 96ac7c0661..4fc082e4ca 100644
--- a/railties/test/commands/console_test.rb
+++ b/railties/test/commands/console_test.rb
@@ -1,6 +1,7 @@
require "abstract_unit"
require "env_helpers"
-require "rails/commands/console"
+require "rails/command"
+require "rails/commands/console/console_command"
class Rails::ConsoleTest < ActiveSupport::TestCase
include EnvHelpers
@@ -102,13 +103,21 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
end
def test_rails_env_is_dev_when_argument_is_dev_and_dev_env_is_present
- stubbed_console = Class.new(Rails::Console) do
- def available_environments
+ Rails::Command::ConsoleCommand.class_eval do
+ alias_method :old_environments, :available_environments
+
+ define_method :available_environments do
["dev"]
end
end
- options = stubbed_console.parse_arguments(["dev"])
- assert_match("dev", options[:environment])
+
+ assert_match("dev", parse_arguments(["dev"])[:environment])
+ ensure
+ Rails::Command::ConsoleCommand.class_eval do
+ undef_method :available_environments
+ alias_method :available_environments, :old_environments
+ undef_method :old_environments
+ end
end
attr_reader :output
@@ -148,6 +157,21 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
end
def parse_arguments(args)
- Rails::Console.parse_arguments(args)
+ Rails::Command::ConsoleCommand.class_eval do
+ alias_method :old_perform, :perform
+ define_method(:perform) do
+ extract_environment_option_from_argument
+
+ options
+ end
+ end
+
+ Rails::Command.invoke(:console, args)
+ ensure
+ Rails::Command::ConsoleCommand.class_eval do
+ undef_method :perform
+ alias_method :perform, :old_perform
+ undef_method :old_perform
+ end
end
end
diff --git a/railties/test/commands/dbconsole_test.rb b/railties/test/commands/dbconsole_test.rb
index 545460e933..0f8c5dbb79 100644
--- a/railties/test/commands/dbconsole_test.rb
+++ b/railties/test/commands/dbconsole_test.rb
@@ -1,6 +1,7 @@
require "abstract_unit"
require "minitest/mock"
-require "rails/commands/dbconsole"
+require "rails/command"
+require "rails/commands/dbconsole/dbconsole_command"
class Rails::DBConsoleTest < ActiveSupport::TestCase
def setup
@@ -9,20 +10,20 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
def teardown
Rails::DBConsole.send(:remove_const, "APP_PATH")
- %w[PGUSER PGHOST PGPORT PGPASSWORD DATABASE_URL].each{|key| ENV.delete(key)}
+ %w[PGUSER PGHOST PGPORT PGPASSWORD DATABASE_URL].each { |key| ENV.delete(key) }
end
def test_config_with_db_config_only
config_sample = {
- "test"=> {
- "adapter"=> "sqlite3",
- "host"=> "localhost",
- "port"=> "9000",
- "database"=> "foo_test",
- "user"=> "foo",
- "password"=> "bar",
- "pool"=> "5",
- "timeout"=> "3000"
+ "test" => {
+ "adapter" => "sqlite3",
+ "host" => "localhost",
+ "port" => "9000",
+ "database" => "foo_test",
+ "user" => "foo",
+ "password" => "bar",
+ "pool" => "5",
+ "timeout" => "3000"
}
}
app_db_config(config_sample) do
@@ -97,16 +98,14 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
end
def test_rails_env_is_development_when_argument_is_dev
- Rails::DBConsole.stub(:available_environments, ["development", "test"]) do
- options = Rails::DBConsole.send(:parse_arguments, ["dev"])
- assert_match("development", options[:environment])
+ stub_available_environments([ "development", "test" ]) do
+ assert_match("development", parse_arguments([ "dev" ])[:environment])
end
end
def test_rails_env_is_dev_when_argument_is_dev_and_dev_env_is_present
- Rails::DBConsole.stub(:available_environments, ["dev"]) do
- options = Rails::DBConsole.send(:parse_arguments, ["dev"])
- assert_match("dev", options[:environment])
+ stub_available_environments([ "dev" ]) do
+ assert_match("dev", parse_arguments([ "dev" ])[:environment])
end
end
@@ -123,7 +122,7 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
end
def test_mysql_include_password
- start({adapter: "mysql2", database: "db", username: "user", password: "qwerty"}, ["-p"])
+ start({ adapter: "mysql2", database: "db", username: "user", password: "qwerty" }, ["-p"])
assert !aborted
assert_equal [%w[mysql mysql5], "--user=user", "--password=qwerty", "db"], dbconsole.find_cmd_and_exec_args
end
@@ -145,7 +144,7 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
end
def test_postgresql_include_password
- start({adapter: "postgresql", database: "db", username: "user", password: "q1w2e3"}, ["-p"])
+ start({ adapter: "postgresql", database: "db", username: "user", password: "q1w2e3" }, ["-p"])
assert !aborted
assert_equal ["psql", "db"], dbconsole.find_cmd_and_exec_args
assert_equal "user", ENV["PGUSER"]
@@ -159,13 +158,13 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
end
def test_sqlite3_mode
- start({adapter: "sqlite3", database: "db.sqlite3"}, ["--mode", "html"])
+ start({ adapter: "sqlite3", database: "db.sqlite3" }, ["--mode", "html"])
assert !aborted
assert_equal ["sqlite3", "-html", Rails.root.join("db.sqlite3").to_s], dbconsole.find_cmd_and_exec_args
end
def test_sqlite3_header
- start({adapter: "sqlite3", database: "db.sqlite3"}, ["--header"])
+ start({ adapter: "sqlite3", database: "db.sqlite3" }, ["--header"])
assert_equal ["sqlite3", "-header", Rails.root.join("db.sqlite3").to_s], dbconsole.find_cmd_and_exec_args
end
@@ -190,7 +189,7 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
end
def test_oracle_include_password
- start({adapter: "oracle", database: "db", username: "user", password: "secret"}, ["-p"])
+ start({ adapter: "oracle", database: "db", username: "user", password: "secret" }, ["-p"])
assert !aborted
assert_equal ["sqlplus", "user/secret@db"], dbconsole.find_cmd_and_exec_args
end
@@ -203,20 +202,16 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
def test_print_help_short
stdout = capture(:stdout) do
- start({}, ["-h"])
+ Rails::Command.invoke(:dbconsole, ["-h"])
end
- assert aborted
- assert_equal "", output
- assert_match(/Usage:.*dbconsole/, stdout)
+ assert_match(/bin\/rails dbconsole \[environment\]/, stdout)
end
def test_print_help_long
stdout = capture(:stdout) do
- start({}, ["--help"])
+ Rails::Command.invoke(:dbconsole, ["--help"])
end
- assert aborted
- assert_equal "", output
- assert_match(/Usage:.*dbconsole/, stdout)
+ assert_match(/bin\/rails dbconsole \[environment\]/, stdout)
end
attr_reader :aborted, :output
@@ -230,21 +225,22 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
end
end
- def dbconsole
- @dbconsole ||= Class.new(Rails::DBConsole) do
+ def make_dbconsole
+ Class.new(Rails::DBConsole) do
attr_reader :find_cmd_and_exec_args
def find_cmd_and_exec(*args)
@find_cmd_and_exec_args = args
end
- end.new(nil)
+ end
end
+ attr_reader :dbconsole
+
def start(config = {}, argv = [])
- dbconsole.stub(:config, config.stringify_keys) do
- dbconsole.stub(:arguments, argv) do
- capture_abort { dbconsole.start }
- end
+ @dbconsole = make_dbconsole.new(parse_arguments(argv))
+ @dbconsole.stub(:config, config.stringify_keys) do
+ capture_abort { @dbconsole.start }
end
end
@@ -258,4 +254,41 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
end
end
end
+
+ def stub_available_environments(environments)
+ Rails::Command::DbconsoleCommand.class_eval do
+ alias_method :old_environments, :available_environments
+
+ define_method :available_environments do
+ environments
+ end
+ end
+
+ yield
+ ensure
+ Rails::Command::DbconsoleCommand.class_eval do
+ undef_method :available_environments
+ alias_method :available_environments, :old_environments
+ undef_method :old_environments
+ end
+ end
+
+ def parse_arguments(args)
+ Rails::Command::DbconsoleCommand.class_eval do
+ alias_method :old_perform, :perform
+ define_method(:perform) do
+ extract_environment_option_from_argument
+
+ options
+ end
+ end
+
+ Rails::Command.invoke(:dbconsole, args)
+ ensure
+ Rails::Command::DbconsoleCommand.class_eval do
+ undef_method :perform
+ alias_method :perform, :old_perform
+ undef_method :old_perform
+ end
+ end
end
diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb
index c1ec73d95c..527529aa52 100644
--- a/railties/test/commands/server_test.rb
+++ b/railties/test/commands/server_test.rb
@@ -1,36 +1,41 @@
require "abstract_unit"
require "env_helpers"
-require "rails/commands/server"
+require "rails/command"
+require "rails/commands/server/server_command"
class Rails::ServerTest < ActiveSupport::TestCase
include EnvHelpers
def test_environment_with_server_option
- args = ["thin", "-e", "production"]
- options = Rails::Server::Options.new.parse!(args)
+ args = ["thin", "-e", "production"]
+ options = parse_arguments(args)
assert_equal "production", options[:environment]
assert_equal "thin", options[:server]
end
def test_environment_without_server_option
- args = ["-e", "production"]
- options = Rails::Server::Options.new.parse!(args)
+ args = ["-e", "production"]
+ options = parse_arguments(args)
assert_equal "production", options[:environment]
assert_nil options[:server]
end
def test_server_option_without_environment
- args = ["thin"]
- options = Rails::Server::Options.new.parse!(args)
- assert_nil options[:environment]
- assert_equal "thin", options[:server]
+ args = ["thin"]
+ with_rack_env nil do
+ with_rails_env nil do
+ options = parse_arguments(args)
+ assert_equal "development", options[:environment]
+ assert_equal "thin", options[:server]
+ end
+ end
end
def test_environment_with_rails_env
with_rack_env nil do
with_rails_env "production" do
- server = Rails::Server.new
- assert_equal "production", server.options[:environment]
+ options = parse_arguments
+ assert_equal "production", options[:environment]
end
end
end
@@ -38,40 +43,39 @@ class Rails::ServerTest < ActiveSupport::TestCase
def test_environment_with_rack_env
with_rails_env nil do
with_rack_env "production" do
- server = Rails::Server.new
- assert_equal "production", server.options[:environment]
+ options = parse_arguments
+ assert_equal "production", options[:environment]
end
end
end
def test_environment_with_port
switch_env "PORT", "1234" do
- server = Rails::Server.new
- assert_equal 1234, server.options[:Port]
+ options = parse_arguments
+ assert_equal 1234, options[:Port]
end
end
def test_environment_with_host
switch_env "HOST", "1.2.3.4" do
- server = Rails::Server.new
- assert_equal "1.2.3.4", server.options[:Host]
+ options = parse_arguments
+ assert_equal "1.2.3.4", options[:Host]
end
end
def test_caching_without_option
args = []
- options = Rails::Server::Options.new.parse!(args)
- merged_options = Rails::Server.new.default_options.merge(options)
- assert_equal nil, merged_options[:caching]
+ options = parse_arguments(args)
+ assert_nil options[:caching]
end
def test_caching_with_option
args = ["--dev-caching"]
- options = Rails::Server::Options.new.parse!(args)
+ options = parse_arguments(args)
assert_equal true, options[:caching]
args = ["--no-dev-caching"]
- options = Rails::Server::Options.new.parse!(args)
+ options = parse_arguments(args)
assert_equal false, options[:caching]
end
@@ -79,38 +83,38 @@ class Rails::ServerTest < ActiveSupport::TestCase
with_rack_env nil do
with_rails_env nil do
args = []
- options = Rails::Server::Options.new.parse!(args)
+ options = parse_arguments(args)
assert_equal true, options[:log_stdout]
args = ["-e", "development"]
- options = Rails::Server::Options.new.parse!(args)
+ options = parse_arguments(args)
assert_equal true, options[:log_stdout]
args = ["-e", "production"]
- options = Rails::Server::Options.new.parse!(args)
+ options = parse_arguments(args)
assert_equal false, options[:log_stdout]
with_rack_env "development" do
args = []
- options = Rails::Server::Options.new.parse!(args)
+ options = parse_arguments(args)
assert_equal true, options[:log_stdout]
end
with_rack_env "production" do
args = []
- options = Rails::Server::Options.new.parse!(args)
+ options = parse_arguments(args)
assert_equal false, options[:log_stdout]
end
with_rails_env "development" do
args = []
- options = Rails::Server::Options.new.parse!(args)
+ options = parse_arguments(args)
assert_equal true, options[:log_stdout]
end
with_rails_env "production" do
args = []
- options = Rails::Server::Options.new.parse!(args)
+ options = parse_arguments(args)
assert_equal false, options[:log_stdout]
end
end
@@ -118,25 +122,29 @@ class Rails::ServerTest < ActiveSupport::TestCase
end
def test_default_options
- server = Rails::Server.new
- old_default_options = server.default_options
+ old_default_options = parse_arguments
Dir.chdir("..") do
- assert_equal old_default_options, server.default_options
+ default_options = parse_arguments
+ assert_equal old_default_options, default_options
end
end
def test_restart_command_contains_customized_options
original_args = ARGV.dup
- args = ["-p", "4567"]
+ args = %w(-p 4567 -b 127.0.0.1 -c dummy_config.ru -d -e test -P tmp/server.pid -C)
ARGV.replace args
- options = Rails::Server::Options.new.parse! args
- server = Rails::Server.new options
- expected = "bin/rails server -p 4567"
+ options = parse_arguments(args)
+ expected = "bin/rails server -p 4567 -b 127.0.0.1 -c dummy_config.ru -d -e test -P tmp/server.pid -C"
- assert_equal expected, server.default_options[:restart_cmd]
+ assert_equal expected, options[:restart_cmd]
ensure
ARGV.replace original_args
end
+
+ private
+ def parse_arguments(args = [])
+ Rails::Command::ServerCommand.new([], args).server_options
+ end
end
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index 9d0d0dc59d..360e8e97d7 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -34,7 +34,7 @@ class ActionsTest < Rails::Generators::TestCase
end
def test_create_file_should_write_block_contents_to_file_path
- action(:create_file, "lib/test_file.rb"){ "heres block data" }
+ action(:create_file, "lib/test_file.rb") { "heres block data" }
assert_file "lib/test_file.rb", "heres block data"
end
@@ -78,7 +78,7 @@ class ActionsTest < Rails::Generators::TestCase
def test_gem_should_insert_on_separate_lines
run_generator
- File.open("Gemfile", "a") {|f| f.write("# Some content...") }
+ File.open("Gemfile", "a") { |f| f.write("# Some content...") }
action :gem, "rspec"
action :gem, "rspec-rails"
@@ -369,9 +369,9 @@ F
assert_equal("", action(:log, :yes, "YES"))
end
- protected
+ private
def action(*args, &block)
- capture(:stdout){ generator.send(*args, &block) }
+ capture(:stdout) { generator.send(*args, &block) }
end
end
diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb
index bbb814ef4e..c54d9cc599 100644
--- a/railties/test/generators/api_app_generator_test.rb
+++ b/railties/test/generators/api_app_generator_test.rb
@@ -35,7 +35,6 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase
assert_file "Gemfile" do |content|
assert_no_match(/gem 'coffee-rails'/, content)
- assert_no_match(/gem 'jquery-rails'/, content)
assert_no_match(/gem 'sass-rails'/, content)
assert_no_match(/gem 'web-console'/, content)
assert_match(/# gem 'jbuilder'/, content)
@@ -106,10 +105,10 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase
%w(app/assets
app/helpers
app/views/layouts/application.html.erb
+ bin/yarn
config/initializers/assets.rb
config/initializers/cookies_serializer.rb
lib/assets
- vendor/assets
test/helpers
tmp/cache/assets
public/404.html
@@ -117,6 +116,8 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase
public/500.html
public/apple-touch-icon-precomposed.png
public/apple-touch-icon.png
- public/favicon.ico)
+ public/favicon.icon
+ vendor/package.json
+ )
end
end
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 3b149d6cf9..20de2258c5 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -43,9 +43,6 @@ DEFAULT_APP_FILES = %w(
test/mailers
test/integration
vendor
- vendor/assets
- vendor/assets/stylesheets
- vendor/assets/javascripts
tmp
tmp/cache
tmp/cache/assets
@@ -77,7 +74,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
def test_invalid_application_name_raises_an_error
- content = capture(:stderr){ run_generator [File.join(destination_root, "43-things")] }
+ content = capture(:stderr) { run_generator [File.join(destination_root, "43-things")] }
assert_equal "Invalid application name 43-things. Please give a name which does not start with numbers.\n", content
end
@@ -356,12 +353,19 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_generator_if_skip_active_record_is_given
run_generator [destination_root, "--skip-active-record"]
+ assert_no_directory "db/"
assert_no_file "config/database.yml"
assert_no_file "app/models/application_record.rb"
assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/
assert_file "test/test_helper.rb" do |helper_content|
assert_no_match(/fixtures :all/, helper_content)
end
+ assert_file "bin/setup" do |setup_content|
+ assert_no_match(/db:setup/, setup_content)
+ end
+ assert_file "bin/update" do |update_content|
+ assert_no_match(/db:migrate/, update_content)
+ end
assert_file "config/initializers/new_framework_defaults.rb" do |initializer_content|
assert_no_match(/belongs_to_required_by_default/, initializer_content)
@@ -381,6 +385,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_no_match(/config\.action_mailer/, content)
end
assert_no_directory "app/mailers"
+ assert_no_directory "test/mailers"
end
def test_generator_has_assets_gems
@@ -397,7 +402,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_match(/#\s+require\s+["']sprockets\/railtie["']/, content)
end
assert_file "Gemfile" do |content|
- assert_no_match(/jquery-rails/, content)
assert_no_match(/sass-rails/, content)
assert_no_match(/uglifier/, content)
assert_no_match(/coffee-rails/, content)
@@ -410,6 +414,16 @@ 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_file "config/initializers/new_framework_defaults.rb" do |content|
+ assert_no_match(/unknown_asset_fallback/, content)
+ end
+ end
+
+ def test_generator_if_skip_yarn_is_given
+ run_generator [destination_root, "--skip-yarn"]
+
+ assert_no_file "vendor/package.json"
+ assert_no_file "bin/yarn"
end
def test_generator_if_skip_action_cable_is_given
@@ -437,29 +451,25 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
- def test_jquery_is_the_default_javascript_library
+ def test_rails_ujs_is_the_default_ujs_library
run_generator
assert_file "app/assets/javascripts/application.js" do |contents|
- assert_match %r{^//= require jquery}, contents
- assert_match %r{^//= require jquery_ujs}, contents
+ assert_match %r{^//= require rails-ujs}, contents
end
- assert_gem "jquery-rails"
end
- def test_other_javascript_libraries
- run_generator [destination_root, "-j", "prototype"]
+ def test_inclusion_of_javascript_libraries_if_required
+ run_generator [destination_root, "-j", "jquery"]
assert_file "app/assets/javascripts/application.js" do |contents|
- assert_match %r{^//= require prototype}, contents
- assert_match %r{^//= require prototype_ujs}, contents
+ assert_match %r{^//= require jquery}, contents
end
- assert_gem "prototype-rails"
+ assert_gem "jquery-rails"
end
def test_javascript_is_skipped_if_required
run_generator [destination_root, "--skip-javascript"]
assert_no_file "app/assets/javascripts"
- assert_no_file "vendor/assets/javascripts"
assert_file "app/views/layouts/application.html.erb" do |contents|
assert_match(/stylesheet_link_tag\s+'application', media: 'all' %>/, contents)
@@ -468,7 +478,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "Gemfile" do |content|
assert_no_match(/coffee-rails/, content)
- assert_no_match(/jquery-rails/, content)
assert_no_match(/uglifier/, content)
end
@@ -477,6 +486,30 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_coffeescript_is_skipped_if_required
+ run_generator [destination_root, "--skip-coffee"]
+
+ assert_file "Gemfile" do |content|
+ assert_no_match(/coffee-rails/, content)
+ assert_match(/uglifier/, content)
+ end
+ end
+
+ def test_generator_for_yarn
+ run_generator([destination_root])
+ assert_file "vendor/package.json", /dependencies/
+ assert_file "config/initializers/assets.rb", /node_modules/
+ end
+
+ def test_generator_for_yarn_skipped
+ run_generator([destination_root, "--skip-yarn"])
+ assert_no_file "vendor/package.json"
+
+ assert_file "config/initializers/assets.rb" do |content|
+ assert_no_match(/node_modules/, content)
+ end
+ end
+
def test_inclusion_of_jbuilder
run_generator
assert_gem "jbuilder"
@@ -605,7 +638,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_edge_option
assert_generates_with_bundler edge: true
- assert_file "Gemfile", %r{^gem\s+["']rails["'],\s+github:\s+["']#{Regexp.escape("rails/rails")}["'],\s+branch:\s+["']#{Regexp.escape("5-0-stable")}["']$}
+ assert_file "Gemfile", %r{^gem\s+["']rails["'],\s+github:\s+["']#{Regexp.escape("rails/rails")}["']$}
end
def test_spring
@@ -715,7 +748,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
test/helpers
test/integration
tmp
- vendor/assets/stylesheets
)
folders_with_keep.each do |folder|
assert_file("#{folder}/.keep")
@@ -763,7 +795,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_equal 3, @sequence_step
end
- protected
+ private
def stub_rails_application(root)
Rails.application.config.root = root
diff --git a/railties/test/generators/controller_generator_test.rb b/railties/test/generators/controller_generator_test.rb
index dd19c5e166..af86a0136f 100644
--- a/railties/test/generators/controller_generator_test.rb
+++ b/railties/test/generators/controller_generator_test.rb
@@ -19,7 +19,7 @@ class ControllerGeneratorTest < Rails::Generators::TestCase
def test_check_class_collision
Object.send :const_set, :ObjectController, Class.new
- content = capture(:stderr){ run_generator ["object"] }
+ content = capture(:stderr) { run_generator ["object"] }
assert_match(/The name 'ObjectController' is either already used in your application or reserved/, content)
ensure
Object.send :remove_const, :ObjectController
@@ -65,7 +65,7 @@ class ControllerGeneratorTest < Rails::Generators::TestCase
def test_add_routes
run_generator
- assert_file "config/routes.rb", /get 'account\/foo'/, /get 'account\/bar'/
+ assert_file "config/routes.rb", /^ get 'account\/foo'/, /^ get 'account\/bar'/
end
def test_skip_routes
diff --git a/railties/test/generators/generator_generator_test.rb b/railties/test/generators/generator_generator_test.rb
index d68cdc29b0..5ff8bb0357 100644
--- a/railties/test/generators/generator_generator_test.rb
+++ b/railties/test/generators/generator_generator_test.rb
@@ -12,7 +12,7 @@ class GeneratorGeneratorTest < Rails::Generators::TestCase
lib/generators/awesome
lib/generators/awesome/USAGE
lib/generators/awesome/templates
- ).each{ |path| assert_file path }
+ ).each { |path| assert_file path }
assert_file "lib/generators/awesome/awesome_generator.rb",
/class AwesomeGenerator < Rails::Generators::NamedBase/
@@ -28,7 +28,7 @@ class GeneratorGeneratorTest < Rails::Generators::TestCase
lib/generators/rails/awesome
lib/generators/rails/awesome/USAGE
lib/generators/rails/awesome/templates
- ).each{ |path| assert_file path }
+ ).each { |path| assert_file path }
assert_file "lib/generators/rails/awesome/awesome_generator.rb",
/class Rails::AwesomeGenerator < Rails::Generators::NamedBase/
@@ -44,7 +44,7 @@ class GeneratorGeneratorTest < Rails::Generators::TestCase
lib/generators/
lib/generators/USAGE
lib/generators/templates
- ).each{ |path| assert_file path }
+ ).each { |path| assert_file path }
assert_file "lib/generators/awesome_generator.rb",
/class AwesomeGenerator < Rails::Generators::NamedBase/
@@ -60,7 +60,7 @@ class GeneratorGeneratorTest < Rails::Generators::TestCase
lib/generators/rails
lib/generators/rails/USAGE
lib/generators/rails/templates
- ).each{ |path| assert_file path }
+ ).each { |path| assert_file path }
assert_file "lib/generators/rails/awesome_generator.rb",
/class Rails::AwesomeGenerator < Rails::Generators::NamedBase/
diff --git a/railties/test/generators/generator_test.rb b/railties/test/generators/generator_test.rb
index c4e4747468..904bade658 100644
--- a/railties/test/generators/generator_test.rb
+++ b/railties/test/generators/generator_test.rb
@@ -20,7 +20,7 @@ module Rails
end
def test_construction
- klass = make_builder_class
+ klass = make_builder_class
assert klass.start(["new", "blah"])
end
diff --git a/railties/test/generators/helper_generator_test.rb b/railties/test/generators/helper_generator_test.rb
index e64cb4cbe0..d9e6e0a85a 100644
--- a/railties/test/generators/helper_generator_test.rb
+++ b/railties/test/generators/helper_generator_test.rb
@@ -14,7 +14,7 @@ class HelperGeneratorTest < Rails::Generators::TestCase
end
def test_check_class_collision
- content = capture(:stderr){ run_generator ["object"] }
+ content = capture(:stderr) { run_generator ["object"] }
assert_match(/The name 'ObjectHelper' is either already used in your application or reserved/, content)
end
diff --git a/railties/test/generators/mailer_generator_test.rb b/railties/test/generators/mailer_generator_test.rb
index 1edaa1b37f..7d69d7470d 100644
--- a/railties/test/generators/mailer_generator_test.rb
+++ b/railties/test/generators/mailer_generator_test.rb
@@ -30,7 +30,7 @@ class MailerGeneratorTest < Rails::Generators::TestCase
def test_check_class_collision
Object.send :const_set, :NotifierMailer, Class.new
- content = capture(:stderr){ run_generator }
+ content = capture(:stderr) { run_generator }
assert_match(/The name 'NotifierMailer' is either already used in your application or reserved/, content)
ensure
Object.send :remove_const, :NotifierMailer
@@ -59,7 +59,7 @@ class MailerGeneratorTest < Rails::Generators::TestCase
def test_check_test_class_collision
Object.send :const_set, :NotifierMailerTest, Class.new
- content = capture(:stderr){ run_generator }
+ content = capture(:stderr) { run_generator }
assert_match(/The name 'NotifierMailerTest' is either already used in your application or reserved/, content)
ensure
Object.send :remove_const, :NotifierMailerTest
@@ -67,7 +67,7 @@ class MailerGeneratorTest < Rails::Generators::TestCase
def test_check_preview_class_collision
Object.send :const_set, :NotifierMailerPreview, Class.new
- content = capture(:stderr){ run_generator }
+ content = capture(:stderr) { run_generator }
assert_match(/The name 'NotifierMailerPreview' is either already used in your application or reserved/, content)
ensure
Object.send :remove_const, :NotifierMailerPreview
diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb
index 6451aa75c9..2b9f3ed7f2 100644
--- a/railties/test/generators/model_generator_test.rb
+++ b/railties/test/generators/model_generator_test.rb
@@ -212,10 +212,10 @@ class ModelGeneratorTest < Rails::Generators::TestCase
def test_migration_without_timestamps
ActiveRecord::Base.timestamped_migrations = false
run_generator ["account"]
- assert_file "db/migrate/001_create_accounts.rb", /class CreateAccounts < ActiveRecord::Migration\[[0-9.]+\]/
+ assert_file "db/migrate/001_create_accounts.rb", /class CreateAccounts < ActiveRecord::Migration\[[0-9.]+\]/
run_generator ["project"]
- assert_file "db/migrate/002_create_projects.rb", /class CreateProjects < ActiveRecord::Migration\[[0-9.]+\]/
+ assert_file "db/migrate/002_create_projects.rb", /class CreateProjects < ActiveRecord::Migration\[[0-9.]+\]/
ensure
ActiveRecord::Base.timestamped_migrations = true
end
@@ -275,7 +275,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
def test_migration_error_is_not_shown_on_revoke
run_generator
- error = capture(:stderr){ run_generator ["Account"], behavior: :revoke }
+ error = capture(:stderr) { run_generator ["Account"], behavior: :revoke }
assert_no_match(/Another migration is already named create_accounts/, error)
end
@@ -300,7 +300,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
assert_file "test/fixtures/accounts.yml", /name: MyString/, /age: 1/
assert_generated_fixture("test/fixtures/accounts.yml",
- "one"=>{"name"=>"MyString", "age"=>1}, "two"=>{"name"=>"MyString", "age"=>1})
+ "one" => { "name" => "MyString", "age" => 1 }, "two" => { "name" => "MyString", "age" => 1 })
end
def test_fixtures_use_the_references_ids
@@ -308,7 +308,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
assert_file "test/fixtures/line_items.yml", /product: one\n cart: one/
assert_generated_fixture("test/fixtures/line_items.yml",
- "one"=>{"product"=>"one", "cart"=>"one"}, "two"=>{"product"=>"two", "cart"=>"two"})
+ "one" => { "product" => "one", "cart" => "one" }, "two" => { "product" => "two", "cart" => "two" })
end
def test_fixtures_use_the_references_ids_and_type
@@ -316,15 +316,15 @@ class ModelGeneratorTest < Rails::Generators::TestCase
assert_file "test/fixtures/line_items.yml", /product: one\n product_type: Product\n cart: one/
assert_generated_fixture("test/fixtures/line_items.yml",
- "one"=>{"product"=>"one", "product_type"=>"Product", "cart"=>"one"},
- "two"=>{"product"=>"two", "product_type"=>"Product", "cart"=>"two"})
+ "one" => { "product" => "one", "product_type" => "Product", "cart" => "one" },
+ "two" => { "product" => "two", "product_type" => "Product", "cart" => "two" })
end
def test_fixtures_respect_reserved_yml_keywords
run_generator ["LineItem", "no:integer", "Off:boolean", "ON:boolean"]
assert_generated_fixture("test/fixtures/line_items.yml",
- "one"=>{"no"=>1, "Off"=>false, "ON"=>false}, "two"=>{"no"=>1, "Off"=>false, "ON"=>false})
+ "one" => { "no" => 1, "Off" => false, "ON" => false }, "two" => { "no" => 1, "Off" => false, "ON" => false })
end
def test_fixture_is_skipped
@@ -343,13 +343,13 @@ class ModelGeneratorTest < Rails::Generators::TestCase
ActiveRecord::Base.pluralize_table_names = false
run_generator
assert_generated_fixture("test/fixtures/account.yml",
- "one"=>{"name"=>"MyString", "age"=>1}, "two"=>{"name"=>"MyString", "age"=>1})
+ "one" => { "name" => "MyString", "age" => 1 }, "two" => { "name" => "MyString", "age" => 1 })
ensure
ActiveRecord::Base.pluralize_table_names = original_pluralize_table_name
end
def test_check_class_collision
- content = capture(:stderr){ run_generator ["object"] }
+ content = capture(:stderr) { run_generator ["object"] }
assert_match(/The name 'Object' is either already used in your application or reserved/, content)
end
diff --git a/railties/test/generators/named_base_test.rb b/railties/test/generators/named_base_test.rb
index c58468dfc1..3015b5363b 100644
--- a/railties/test/generators/named_base_test.rb
+++ b/railties/test/generators/named_base_test.rb
@@ -106,9 +106,9 @@ class NamedBaseTest < Rails::Generators::TestCase
def test_hide_namespace
g = generator ["Hidden"]
g.class.stub(:namespace, "hidden") do
- assert !Rails::Generators.hidden_namespaces.include?("hidden")
+ assert_not_includes Rails::Generators.hidden_namespaces, "hidden"
g.class.hide!
- assert Rails::Generators.hidden_namespaces.include?("hidden")
+ assert_includes Rails::Generators.hidden_namespaces, "hidden"
end
end
@@ -131,7 +131,7 @@ class NamedBaseTest < Rails::Generators::TestCase
assert_name g, "admin.foos", :controller_i18n_scope
end
- protected
+ private
def assert_name(generator, value, method)
assert_equal value, generator.send(method)
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index f5b1532e5f..9921a80342 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -27,19 +27,19 @@ class PluginGeneratorTest < Rails::Generators::TestCase
include SharedGeneratorTests
def test_invalid_plugin_name_raises_an_error
- content = capture(:stderr){ run_generator [File.join(destination_root, "my_plugin-31fr-extension")] }
+ content = capture(:stderr) { run_generator [File.join(destination_root, "my_plugin-31fr-extension")] }
assert_equal "Invalid plugin name my_plugin-31fr-extension. Please give a name which does not contain a namespace starting with numeric characters.\n", content
- content = capture(:stderr){ run_generator [File.join(destination_root, "things4.3")] }
+ content = capture(:stderr) { run_generator [File.join(destination_root, "things4.3")] }
assert_equal "Invalid plugin name things4.3. Please give a name which uses only alphabetic, numeric, \"_\" or \"-\" characters.\n", content
- content = capture(:stderr){ run_generator [File.join(destination_root, "43things")] }
+ content = capture(:stderr) { run_generator [File.join(destination_root, "43things")] }
assert_equal "Invalid plugin name 43things. Please give a name which does not start with numbers.\n", content
- content = capture(:stderr){ run_generator [File.join(destination_root, "plugin")] }
+ content = capture(:stderr) { run_generator [File.join(destination_root, "plugin")] }
assert_equal "Invalid plugin name plugin. Please give a name which does not match one of the reserved rails words: application, destroy, plugin, runner, test\n", content
- content = capture(:stderr){ run_generator [File.join(destination_root, "Digest")] }
+ content = capture(:stderr) { run_generator [File.join(destination_root, "Digest")] }
assert_equal "Invalid plugin name Digest, constant Digest is already in use. Please choose another plugin name.\n", content
end
@@ -100,6 +100,14 @@ class PluginGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_generating_adds_dummy_app_in_full_mode_without_sprockets
+ run_generator [destination_root, "-S", "--full"]
+
+ assert_file "test/dummy/config/environments/production.rb" do |contents|
+ assert_no_match(/config\.assets/, contents)
+ end
+ end
+
def test_generating_adds_dummy_app_rake_tasks_without_unit_test_files
run_generator [destination_root, "-T", "--mountable", "--dummy-path", "my_dummy_app"]
assert_file "Rakefile", /APP_RAKEFILE/
@@ -205,7 +213,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
def test_edge_option
assert_generates_without_bundler(edge: true)
- assert_file "Gemfile", %r{^gem\s+["']rails["'],\s+github:\s+["']#{Regexp.escape("rails/rails")}["'],\s+branch:\s+["']#{Regexp.escape("5-0-stable")}["']$}
+ assert_file "Gemfile", %r{^gem\s+["']rails["'],\s+github:\s+["']#{Regexp.escape("rails/rails")}["']$}
end
def test_generation_does_not_run_bundle_install_with_full_and_mountable
@@ -499,7 +507,6 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_no_match("gemspec", contents)
assert_match(/gem 'rails'/, contents)
assert_match_sqlite3(contents)
- assert_no_match(/# gem "jquery-rails"/, contents)
end
end
@@ -707,10 +714,10 @@ class PluginGeneratorTest < Rails::Generators::TestCase
end
end
- protected
+ private
def action(*args, &block)
- silence(:stdout){ generator.send(*args, &block) }
+ silence(:stdout) { generator.send(*args, &block) }
end
def default_files
diff --git a/railties/test/generators/plugin_test_runner_test.rb b/railties/test/generators/plugin_test_runner_test.rb
index 04b4b10254..0bdf3b2726 100644
--- a/railties/test/generators/plugin_test_runner_test.rb
+++ b/railties/test/generators/plugin_test_runner_test.rb
@@ -86,6 +86,12 @@ class PluginTestRunnerTest < ActiveSupport::TestCase
assert_match(%r{cannot load such file.+test/not_exists\.rb}, error)
end
+ def test_executed_only_once
+ create_test_file "foo"
+ result = run_test_command("test/foo_test.rb")
+ assert_equal 1, result.scan(/1 runs, 1 assertions, 0 failures/).length
+ end
+
private
def plugin_path
"#{@destination_root}/bukkits"
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index 766162b625..08b0e34fe2 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -28,11 +28,11 @@ module SharedGeneratorTests
def test_plugin_new_generate_pretend
run_generator ["testapp", "--pretend"]
- default_files.each{ |path| assert_no_file File.join("testapp",path) }
+ default_files.each { |path| assert_no_file File.join("testapp", path) }
end
def test_invalid_database_option_raises_an_error
- content = capture(:stderr){ run_generator([destination_root, "-d", "unknown"]) }
+ content = capture(:stderr) { run_generator([destination_root, "-d", "unknown"]) }
assert_match(/Invalid value for \-\-database option/, content)
end
@@ -44,14 +44,14 @@ module SharedGeneratorTests
def test_name_collision_raises_an_error
reserved_words = %w[application destroy plugin runner test]
reserved_words.each do |reserved|
- content = capture(:stderr){ run_generator [File.join(destination_root, 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)
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)] }
+ 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)
end
end
@@ -68,7 +68,7 @@ module SharedGeneratorTests
def test_template_raises_an_error_with_invalid_path
quietly do
- content = capture(:stderr){ run_generator([destination_root, "-m", "non/existent/path"]) }
+ content = capture(:stderr) { run_generator([destination_root, "-m", "non/existent/path"]) }
assert_match(/The template \[.*\] could not be loaded/, content)
assert_match(/non\/existent\/path/, content)
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index 83c1d6811c..68ba435393 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -23,32 +23,32 @@ class GeneratorsTest < Rails::Generators::TestCase
def test_invoke_when_generator_is_not_found
name = :unknown
- output = capture(:stdout){ Rails::Generators.invoke name }
+ output = capture(:stdout) { Rails::Generators.invoke name }
assert_match "Could not find generator '#{name}'", output
assert_match "`rails generate --help`", output
end
def test_generator_suggestions
name = :migrationz
- output = capture(:stdout){ Rails::Generators.invoke name }
+ output = capture(:stdout) { Rails::Generators.invoke name }
assert_match "Maybe you meant 'migration'", output
end
def test_generator_multiple_suggestions
name = :tas
- output = capture(:stdout){ Rails::Generators.invoke name }
+ output = capture(:stdout) { Rails::Generators.invoke name }
assert_match "Maybe you meant 'task', 'job' or", output
end
def test_help_when_a_generator_with_required_arguments_is_invoked_without_arguments
- output = capture(:stdout){ Rails::Generators.invoke :model, [] }
+ output = capture(:stdout) { Rails::Generators.invoke :model, [] }
assert_match(/Description:/, output)
end
def test_should_give_higher_preference_to_rails_generators
assert File.exist?(File.join(@path, "generators", "model_generator.rb"))
assert_called_with(Rails::Generators::ModelGenerator, :start, [["Account"], {}]) do
- warnings = capture(:stderr){ Rails::Generators.invoke :model, ["Account"] }
+ warnings = capture(:stderr) { Rails::Generators.invoke :model, ["Account"] }
assert warnings.empty?
end
end
@@ -115,7 +115,7 @@ class GeneratorsTest < Rails::Generators::TestCase
end
def test_rails_generators_help_with_builtin_information
- output = capture(:stdout){ Rails::Generators.help }
+ output = capture(:stdout) { Rails::Generators.help }
assert_match(/Rails:/, output)
assert_match(/^ model$/, output)
assert_match(/^ scaffold_controller$/, output)
@@ -123,19 +123,19 @@ class GeneratorsTest < Rails::Generators::TestCase
end
def test_rails_generators_help_does_not_include_app_nor_plugin_new
- output = capture(:stdout){ Rails::Generators.help }
+ output = capture(:stdout) { Rails::Generators.help }
assert_no_match(/app/, output)
assert_no_match(/[^:]plugin/, output)
end
def test_rails_generators_with_others_information
- output = capture(:stdout){ Rails::Generators.help }
+ output = capture(:stdout) { Rails::Generators.help }
assert_match(/Fixjour:/, output)
assert_match(/^ fixjour$/, output)
end
def test_rails_generators_does_not_show_active_record_hooks
- output = capture(:stdout){ Rails::Generators.help }
+ output = capture(:stdout) { Rails::Generators.help }
assert_match(/ActiveRecord:/, output)
assert_match(/^ active_record:fixjour$/, output)
end
@@ -214,7 +214,7 @@ class GeneratorsTest < Rails::Generators::TestCase
# Create template
mkdir_p(File.dirname(template))
- File.open(template, "w"){ |f| f.write "empty" }
+ File.open(template, "w") { |f| f.write "empty" }
capture(:stdout) do
Rails::Generators.invoke :model, ["user"], destination_root: destination_root
@@ -229,7 +229,7 @@ class GeneratorsTest < Rails::Generators::TestCase
def test_source_paths_for_not_namespaced_generators
mspec = Rails::Generators.find_by_namespace :fixjour
- assert mspec.source_paths.include?(File.join(Rails.root, "lib", "templates", "fixjour"))
+ assert_includes mspec.source_paths, File.join(Rails.root, "lib", "templates", "fixjour")
end
def test_usage_with_embedded_ruby
@@ -239,8 +239,8 @@ class GeneratorsTest < Rails::Generators::TestCase
end
def test_hide_namespace
- assert !Rails::Generators.hidden_namespaces.include?("special:namespace")
+ assert_not_includes Rails::Generators.hidden_namespaces, "special:namespace"
Rails::Generators.hide_namespace("special:namespace")
- assert Rails::Generators.hidden_namespaces.include?("special:namespace")
+ assert_includes Rails::Generators.hidden_namespaces, "special:namespace"
end
end
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index fb3818b92c..1902eac862 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -66,7 +66,7 @@ module TestHelpers
def extract_body(response)
"".tap do |body|
- response[2].each {|chunk| body << chunk }
+ response[2].each { |chunk| body << chunk }
end
end
@@ -104,7 +104,7 @@ module TestHelpers
# Build an application by invoking the generator and going through the whole stack.
def build_app(options = {})
@prev_rails_env = ENV["RAILS_ENV"]
- ENV["RAILS_ENV"] = "development"
+ ENV["RAILS_ENV"] = "development"
ENV["SECRET_KEY_BASE"] ||= SecureRandom.hex(16)
FileUtils.rm_rf(app_path)
@@ -117,11 +117,6 @@ module TestHelpers
end
end
- gemfile_path = "#{app_path}/Gemfile"
- if options[:gemfile].blank? && File.exist?(gemfile_path)
- File.delete gemfile_path
- end
-
routes = File.read("#{app_path}/config/routes.rb")
if routes =~ /(\n\s*end\s*)\Z/
File.open("#{app_path}/config/routes.rb", "w") do |f|
@@ -167,7 +162,6 @@ module TestHelpers
require "rails"
require "action_controller/railtie"
require "action_view/railtie"
- require "action_dispatch/middleware/flash"
@app = Class.new(Rails::Application)
@app.config.eager_load = false
@@ -192,7 +186,7 @@ module TestHelpers
controller :foo, <<-RUBY
class FooController < ApplicationController
def index
- render text: "foo"
+ render plain: "foo"
end
end
RUBY
@@ -214,7 +208,7 @@ module TestHelpers
def write(file, string)
path = "#{@path}/#{file}"
FileUtils.mkdir_p(File.dirname(path))
- File.open(path, "w") {|f| f.puts string }
+ File.open(path, "w") { |f| f.puts string }
end
def delete(file)
@@ -308,7 +302,7 @@ module TestHelpers
remove_from_config "config.active_record.*"
end
- $:.reject! {|path| path =~ %r'/(#{to_remove.join('|')})/' }
+ $:.reject! { |path| path =~ %r'/(#{to_remove.join('|')})/' }
end
end
end
diff --git a/railties/test/json_params_parsing_test.rb b/railties/test/json_params_parsing_test.rb
index eac731a942..7fff3bb465 100644
--- a/railties/test/json_params_parsing_test.rb
+++ b/railties/test/json_params_parsing_test.rb
@@ -3,10 +3,10 @@ require "action_dispatch"
require "active_record"
class JsonParamsParsingTest < ActionDispatch::IntegrationTest
- test "prevent null query" do
+ def test_prevent_null_query
# Make sure we have data to find
klass = Class.new(ActiveRecord::Base) do
- def self.name; 'Foo'; end
+ def self.name; "Foo"; end
establish_connection adapter: "sqlite3", database: ":memory:"
connection.create_table "foos" do |t|
t.string :title
@@ -26,22 +26,24 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
end
}
- assert_nil app.call(make_env({ 't' => nil }))
- assert_nil app.call(make_env({ 't' => [nil] }))
+ assert_nil app.call(make_env("t" => nil))
+ assert_nil app.call(make_env("t" => [nil]))
[[[nil]], [[[nil]]]].each do |data|
- assert_nil app.call(make_env({ 't' => data }))
+ assert_nil app.call(make_env("t" => data))
end
+ ensure
+ klass.connection.drop_table("foos")
end
private
- def make_env json
+ def make_env(json)
data = JSON.dump json
content_length = data.length
{
- 'CONTENT_LENGTH' => content_length,
- 'CONTENT_TYPE' => 'application/json',
- 'rack.input' => StringIO.new(data)
+ "CONTENT_LENGTH" => content_length,
+ "CONTENT_TYPE" => "application/json",
+ "rack.input" => StringIO.new(data)
}
end
end
diff --git a/railties/test/path_generation_test.rb b/railties/test/path_generation_test.rb
index 4d2dba0e2b..c0b03d0c15 100644
--- a/railties/test/path_generation_test.rb
+++ b/railties/test/path_generation_test.rb
@@ -38,7 +38,7 @@ class PathGenerationTest < ActiveSupport::TestCase
host = uri_or_host.host unless path
path ||= uri_or_host.path
- params = {"PATH_INFO" => path,
+ params = { "PATH_INFO" => path,
"REQUEST_METHOD" => method,
"HTTP_HOST" => host }
diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb
index 5e96f9f32f..7b2551062a 100644
--- a/railties/test/paths_test.rb
+++ b/railties/test/paths_test.rb
@@ -103,7 +103,7 @@ class PathsTest < ActiveSupport::TestCase
@root.add "app", with: "/app"
@root["app"].autoload_once!
assert @root["app"].autoload_once?
- assert @root.autoload_once.include?(@root["app"].expanded.first)
+ assert_includes @root.autoload_once, @root["app"].expanded.first
end
end
@@ -114,14 +114,14 @@ class PathsTest < ActiveSupport::TestCase
@root["app"].skip_autoload_once!
assert !@root["app"].autoload_once?
- assert !@root.autoload_once.include?(@root["app"].expanded.first)
+ assert_not_includes @root.autoload_once, @root["app"].expanded.first
end
test "it is possible to add a path without assignment and specify it should be loaded only once" do
File.stub(:exist?, true) do
@root.add "app", with: "/app", autoload_once: true
assert @root["app"].autoload_once?
- assert @root.autoload_once.include?("/app")
+ assert_includes @root.autoload_once, "/app"
end
end
@@ -129,8 +129,8 @@ class PathsTest < ActiveSupport::TestCase
File.stub(:exist?, true) do
@root.add "app", with: ["/app", "/app2"], autoload_once: true
assert @root["app"].autoload_once?
- assert @root.autoload_once.include?("/app")
- assert @root.autoload_once.include?("/app2")
+ assert_includes @root.autoload_once, "/app"
+ assert_includes @root.autoload_once, "/app2"
end
end
@@ -139,7 +139,7 @@ class PathsTest < ActiveSupport::TestCase
@root["app"] = "/app"
@root["app"].autoload_once!
@root["app"].autoload_once!
- assert_equal 1, @root.autoload_once.select {|p| p == @root["app"].expanded.first }.size
+ assert_equal 1, @root.autoload_once.select { |p| p == @root["app"].expanded.first }.size
end
end
@@ -157,7 +157,7 @@ class PathsTest < ActiveSupport::TestCase
@root["app"] = "/app"
@root["app"].eager_load!
assert @root["app"].eager_load?
- assert @root.eager_load.include?(@root["app"].to_a.first)
+ assert_includes @root.eager_load, @root["app"].to_a.first
end
end
@@ -168,14 +168,14 @@ class PathsTest < ActiveSupport::TestCase
@root["app"].skip_eager_load!
assert !@root["app"].eager_load?
- assert !@root.eager_load.include?(@root["app"].to_a.first)
+ assert_not_includes @root.eager_load, @root["app"].to_a.first
end
test "it is possible to add a path without assignment and mark it as eager" do
File.stub(:exist?, true) do
@root.add "app", with: "/app", eager_load: true
assert @root["app"].eager_load?
- assert @root.eager_load.include?("/app")
+ assert_includes @root.eager_load, "/app"
end
end
@@ -183,8 +183,8 @@ class PathsTest < ActiveSupport::TestCase
File.stub(:exist?, true) do
@root.add "app", with: ["/app", "/app2"], eager_load: true
assert @root["app"].eager_load?
- assert @root.eager_load.include?("/app")
- assert @root.eager_load.include?("/app2")
+ assert_includes @root.eager_load, "/app"
+ assert_includes @root.eager_load, "/app2"
end
end
@@ -193,8 +193,8 @@ class PathsTest < ActiveSupport::TestCase
@root.add "app", with: "/app", eager_load: true, autoload_once: true
assert @root["app"].eager_load?
assert @root["app"].autoload_once?
- assert @root.eager_load.include?("/app")
- assert @root.autoload_once.include?("/app")
+ assert_includes @root.eager_load, "/app"
+ assert_includes @root.autoload_once, "/app"
end
end
@@ -203,7 +203,7 @@ class PathsTest < ActiveSupport::TestCase
@root["app"] = "/app"
@root["app"].eager_load!
@root["app"].eager_load!
- assert_equal 1, @root.eager_load.select {|p| p == @root["app"].expanded.first }.size
+ assert_equal 1, @root.eager_load.select { |p| p == @root["app"].expanded.first }.size
end
end
diff --git a/railties/test/rack_logger_test.rb b/railties/test/rack_logger_test.rb
index e14eff41ce..7dd91a2465 100644
--- a/railties/test/rack_logger_test.rb
+++ b/railties/test/rack_logger_test.rb
@@ -47,7 +47,7 @@ module Rails
end
def test_notification
- logger = TestLogger.new { }
+ logger = TestLogger.new {}
assert_difference("subscriber.starts.length") do
assert_difference("subscriber.finishes.length") do
diff --git a/railties/test/rails_info_test.rb b/railties/test/rails_info_test.rb
index 3c893fb3e0..9f4c5bb025 100644
--- a/railties/test/rails_info_test.rb
+++ b/railties/test/rails_info_test.rb
@@ -17,7 +17,7 @@ class InfoTest < ActiveSupport::TestCase
def test_property_with_block_swallows_exceptions_and_ignores_property
assert_nothing_raised do
Rails::Info.module_eval do
- property("Bogus") {raise}
+ property("Bogus") { raise }
end
end
assert !property_defined?("Bogus")
@@ -32,7 +32,7 @@ class InfoTest < ActiveSupport::TestCase
def test_property_with_block
Rails::Info.module_eval do
- property("Goodbye") {"World"}
+ property("Goodbye") { "World" }
end
assert_property "Goodbye", "World"
end
@@ -48,13 +48,13 @@ class InfoTest < ActiveSupport::TestCase
end
html = Rails::Info.to_html
- assert html.include?('<tr><td class="name">Middleware</td>')
+ assert_includes html, '<tr><td class="name">Middleware</td>'
properties.value_for("Middleware").each do |value|
- assert html.include?("<li>#{CGI.escapeHTML(value)}</li>")
+ assert_includes html, "<li>#{CGI.escapeHTML(value)}</li>"
end
end
- protected
+ private
def properties
Rails::Info.properties
end
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 84867dcdec..52d691b73b 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -98,7 +98,7 @@ module RailtiesTest
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
@@ -133,7 +133,7 @@ module RailtiesTest
boot_rails
Dir.chdir(app_path) do
- output = `bundle exec rake railties:install:migrations`.split("\n")
+ 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)
@@ -169,7 +169,7 @@ module RailtiesTest
boot_rails
Dir.chdir(app_path) do
- output = `bundle exec rake railties:install:migrations`.split("\n")
+ 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)
@@ -327,7 +327,7 @@ module RailtiesTest
controller "foo", <<-RUBY
class FooController < ActionController::Base
def index
- render :text => "foo"
+ render plain: "foo"
end
end
RUBY
@@ -341,7 +341,7 @@ module RailtiesTest
@plugin.write "app/controllers/bar_controller.rb", <<-RUBY
class BarController < ActionController::Base
def index
- render :text => "bar"
+ render plain: "bar"
end
end
RUBY
@@ -436,7 +436,7 @@ YAML
@plugin.write "app/controllers/admin/foo/bar_controller.rb", <<-RUBY
class Admin::Foo::BarController < ApplicationController
def index
- render text: "Rendered from namespace"
+ render plain: "Rendered from namespace"
end
end
RUBY
@@ -536,7 +536,7 @@ YAML
controller "foo", <<-RUBY
class FooController < ActionController::Base
def index
- render text: params[:username]
+ render plain: params[:username]
end
end
RUBY
@@ -700,7 +700,7 @@ YAML
end
def show
- render text: foo_path
+ render plain: foo_path
end
def from_app
@@ -712,7 +712,7 @@ YAML
end
def polymorphic_path_without_namespace
- render text: polymorphic_path(Post.new)
+ render plain: polymorphic_path(Post.new)
end
end
RUBY
@@ -835,7 +835,7 @@ YAML
@plugin.write "app/controllers/bukkits/awesome/foo_controller.rb", <<-RUBY
class Bukkits::Awesome::FooController < ActionController::Base
def index
- render :text => "ok"
+ render plain: "ok"
end
end
RUBY
@@ -890,7 +890,7 @@ YAML
boot_rails
- assert_equal AppTemplate.railtie_namespace, AppTemplate::Engine
+ assert_equal AppTemplate::Engine, AppTemplate.railtie_namespace
end
test "properly reload routes" do
@@ -1220,7 +1220,7 @@ YAML
fullpath: \#{request.fullpath}
path: \#{request.path}
TEXT
- render text: text
+ render plain: text
end
end
end
@@ -1257,7 +1257,7 @@ YAML
app_file "app/controllers/bar_controller.rb", <<-RUBY
class BarController < ApplicationController
def index
- render text: bukkits.bukkit_path
+ render plain: bukkits.bukkit_path
end
end
RUBY
@@ -1278,7 +1278,7 @@ YAML
@plugin.write "app/controllers/bukkits/bukkit_controller.rb", <<-RUBY
class Bukkits::BukkitController < ActionController::Base
def index
- render text: main_app.bar_path
+ render plain: main_app.bar_path
end
end
RUBY
@@ -1306,7 +1306,7 @@ YAML
app_file "app/controllers/bar_controller.rb", <<-RUBY
class BarController < ApplicationController
def index
- render text: bukkits.bukkit_path
+ render plain: bukkits.bukkit_path
end
end
RUBY
@@ -1327,7 +1327,7 @@ YAML
@plugin.write "app/controllers/bukkits/bukkit_controller.rb", <<-RUBY
class Bukkits::BukkitController < ActionController::Base
def index
- render text: main_app.bar_path
+ render plain: main_app.bar_path
end
end
RUBY
diff --git a/railties/test/railties/generators_test.rb b/railties/test/railties/generators_test.rb
index 732898d0c0..5c691b9ec4 100644
--- a/railties/test/railties/generators_test.rb
+++ b/railties/test/railties/generators_test.rb
@@ -29,7 +29,7 @@ module RailtiesTests
`#{Gem.ruby} #{RAILS_FRAMEWORK_ROOT}/railties/exe/rails #{cmd}`
end
- def build_engine(is_mountable=false)
+ def build_engine(is_mountable = false)
FileUtils.rm_rf(engine_path)
FileUtils.mkdir_p(engine_path)
diff --git a/railties/test/railties/mounted_engine_test.rb b/railties/test/railties/mounted_engine_test.rb
index 9db42c0c38..5838d0d7e7 100644
--- a/railties/test/railties/mounted_engine_test.rb
+++ b/railties/test/railties/mounted_engine_test.rb
@@ -50,7 +50,7 @@ module ApplicationTests
@simple_plugin.write "app/controllers/weblogs_controller.rb", <<-RUBY
class WeblogsController < ActionController::Base
def index
- render text: request.url
+ render plain: request.url
end
end
RUBY
@@ -74,7 +74,7 @@ module ApplicationTests
module Metrics
class GeneratingController < ActionController::Base
def generate_blog_route
- render text: blog.post_path(1)
+ render plain: blog.post_path(1)
end
def generate_blog_route_in_view
@@ -122,14 +122,14 @@ module ApplicationTests
module Blog
class PostsController < ActionController::Base
def index
- render text: blog.post_path(1)
+ render plain: blog.post_path(1)
end
def generate_application_route
path = main_app.url_for(controller: "/main",
action: "index",
only_path: true)
- render text: path
+ render plain: path
end
def application_route_in_view
@@ -137,7 +137,7 @@ module ApplicationTests
end
def engine_polymorphic_path
- render text: polymorphic_path(Post.new)
+ render plain: polymorphic_path(Post.new)
end
def engine_asset_path
@@ -150,7 +150,7 @@ module ApplicationTests
app_file "app/controllers/application_generating_controller.rb", <<-RUBY
class ApplicationGeneratingController < ActionController::Base
def engine_route
- render text: blog.posts_path
+ render plain: blog.posts_path
end
def engine_route_in_view
@@ -158,7 +158,7 @@ module ApplicationTests
end
def weblog_engine_route
- render text: weblog.weblogs_path
+ render plain: weblog.weblogs_path
end
def weblog_engine_route_in_view
@@ -166,15 +166,15 @@ module ApplicationTests
end
def url_for_engine_route
- render text: blog.url_for(controller: "blog/posts", action: "index", user: "john", only_path: true)
+ render plain: blog.url_for(controller: "blog/posts", action: "index", user: "john", only_path: true)
end
def polymorphic_route
- render text: polymorphic_url([blog, Blog::Post.new])
+ render plain: polymorphic_url([blog, Blog::Post.new])
end
def application_polymorphic_path
- render text: polymorphic_path(Blog::Post.new)
+ render plain: polymorphic_path(Blog::Post.new)
end
end
RUBY
diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb
index 994019267c..30cd525266 100644
--- a/railties/test/railties/railtie_test.rb
+++ b/railties/test/railties/railtie_test.rb
@@ -79,6 +79,13 @@ module RailtiesTest
assert_equal app_path, $before_configuration
end
+ test "before_configuration callbacks run as soon as the application constant inherits from Rails::Application" do
+ $before_configuration = false
+ class Foo < Rails::Railtie ; config.before_configuration { $before_configuration = true } ; end
+ class Application < Rails::Application ; end
+ assert $before_configuration
+ end
+
test "railtie can add after_initialize callbacks" do
$after_initialize = false
class Foo < Rails::Railtie ; config.after_initialize { $after_initialize = true } ; end
@@ -128,7 +135,7 @@ module RailtiesTest
require "rdoc/task"
Rails.application.load_tasks
- assert $ran_block.include?("my_tie")
+ assert_includes $ran_block, "my_tie"
end
test "generators block is executed when MyApp.load_generators is called" do
diff --git a/tools/profile b/tools/profile
index f7c3b11f3e..01e513c67a 100755
--- a/tools/profile
+++ b/tools/profile
@@ -12,7 +12,7 @@ module CodeTools
Error = Class.new(StandardError)
attr_reader :path, :mode
- def initialize(path, mode=nil)
+ def initialize(path, mode = nil)
assert_ruby_file_exists(path)
@path, @mode = path, mode
require "benchmark"
@@ -71,8 +71,8 @@ module CodeTools
fail Error.new("No such file") unless File.exist?(path)
fail Error.new("#{path} is a directory") if File.directory?(path)
ruby_extension = File.extname(path) == ".rb"
- ruby_executable = File.open(path, "rb") {|f| f.readline } =~ [/\A#!.*ruby/]
- fail Error.new("Not a ruby file") unless ruby_extension or ruby_executable
+ ruby_executable = File.open(path, "rb") { |f| f.readline } =~ [/\A#!.*ruby/]
+ fail Error.new("Not a ruby file") unless ruby_extension || ruby_executable
end
module RequireProfiler
diff --git a/tools/test.rb b/tools/test.rb
index 7819c13ee2..ce546b382d 100644
--- a/tools/test.rb
+++ b/tools/test.rb
@@ -4,6 +4,8 @@ require "bundler"
Bundler.setup
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.
@@ -12,4 +14,7 @@ module Rails
end
end
+ActiveSupport::TestCase.extend Rails::LineFiltering
Rails::TestUnitReporter.executable = "bin/test"
+Minitest.run_via[:rails] = true
+require "active_support/testing/autorun"