aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml7
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--Gemfile14
-rw-r--r--Gemfile.lock157
-rw-r--r--RELEASING_RAILS.rdoc2
-rw-r--r--actionmailer/CHANGELOG.md31
-rw-r--r--actionmailer/README.rdoc2
-rw-r--r--actionmailer/Rakefile14
-rw-r--r--actionmailer/actionmailer.gemspec2
-rwxr-xr-xactionmailer/bin/test4
-rw-r--r--actionmailer/lib/action_mailer.rb1
-rw-r--r--actionmailer/lib/action_mailer/base.rb39
-rw-r--r--actionmailer/lib/action_mailer/delivery_job.rb2
-rw-r--r--actionmailer/lib/action_mailer/delivery_methods.rb3
-rw-r--r--actionmailer/lib/action_mailer/inline_preview_interceptor.rb61
-rw-r--r--actionmailer/lib/action_mailer/log_subscriber.rb2
-rw-r--r--actionmailer/lib/action_mailer/preview.rb2
-rw-r--r--actionmailer/lib/action_mailer/test_helper.rb2
-rw-r--r--actionmailer/test/abstract_unit.rb1
-rw-r--r--actionmailer/test/delivery_methods_test.rb1
-rw-r--r--actionmailer/test/i18n_with_controller_test.rb5
-rw-r--r--actionmailer/test/mail_helper_test.rb14
-rw-r--r--actionmailer/test/message_delivery_test.rb15
-rw-r--r--actionmailer/test/test_helper_test.rb11
-rw-r--r--actionpack/CHANGELOG.md135
-rw-r--r--actionpack/README.rdoc2
-rw-r--r--actionpack/Rakefile14
-rw-r--r--actionpack/actionpack.gemspec2
-rwxr-xr-xactionpack/bin/test4
-rw-r--r--actionpack/lib/abstract_controller/base.rb10
-rw-r--r--actionpack/lib/abstract_controller/helpers.rb2
-rw-r--r--actionpack/lib/action_controller.rb4
-rw-r--r--actionpack/lib/action_controller/api.rb147
-rw-r--r--actionpack/lib/action_controller/base.rb11
-rw-r--r--actionpack/lib/action_controller/caching.rb2
-rw-r--r--actionpack/lib/action_controller/form_builder.rb48
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb2
-rw-r--r--actionpack/lib/action_controller/metal.rb59
-rw-r--r--actionpack/lib/action_controller/metal/basic_implicit_render.rb11
-rw-r--r--actionpack/lib/action_controller/metal/conditional_get.rb4
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb6
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb12
-rw-r--r--actionpack/lib/action_controller/metal/head.rb14
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb6
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb40
-rw-r--r--actionpack/lib/action_controller/metal/implicit_render.rb26
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb8
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb13
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb2
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb15
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb41
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb8
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb235
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb6
-rw-r--r--actionpack/lib/action_controller/middleware.rb39
-rw-r--r--actionpack/lib/action_controller/template_assertions.rb9
-rw-r--r--actionpack/lib/action_controller/test_case.rb435
-rw-r--r--actionpack/lib/action_dispatch.rb1
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb8
-rw-r--r--actionpack/lib/action_dispatch/http/filter_parameters.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/filter_redirect.rb11
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/mime_types.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/parameter_filter.rb26
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb17
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb109
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb53
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb8
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/formatter.rb47
-rw-r--r--actionpack/lib/action_dispatch/journey/nodes/node.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/route.rb9
-rw-r--r--actionpack/lib/action_dispatch/journey/router.rb3
-rw-r--r--actionpack/lib/action_dispatch/journey/router/utils.rb8
-rw-r--r--actionpack/lib/action_dispatch/journey/routes.rb8
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb176
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb14
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb13
-rw-r--r--actionpack/lib/action_dispatch/middleware/load_interlock.rb21
-rw-r--r--actionpack/lib/action_dispatch/middleware/params_parser.rb40
-rw-r--r--actionpack/lib/action_dispatch/middleware/reloader.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb26
-rw-r--r--actionpack/lib/action_dispatch/middleware/request_id.rb13
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb10
-rw-r--r--actionpack/lib/action_dispatch/middleware/ssl.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/stack.rb87
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb64
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb60
-rw-r--r--actionpack/lib/action_dispatch/request/utils.rb49
-rw-r--r--actionpack/lib/action_dispatch/routing.rb1
-rw-r--r--actionpack/lib/action_dispatch/routing/inspector.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb568
-rw-r--r--actionpack/lib/action_dispatch/routing/redirection.rb6
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb112
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb26
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb11
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb8
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb24
-rw-r--r--actionpack/lib/action_dispatch/testing/test_process.rb8
-rw-r--r--actionpack/lib/action_dispatch/testing/test_request.rb31
-rw-r--r--actionpack/lib/action_dispatch/testing/test_response.rb3
-rw-r--r--actionpack/test/abstract_unit.rb73
-rw-r--r--actionpack/test/assertions/response_assertions_test.rb4
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb235
-rw-r--r--actionpack/test/controller/api/conditional_get_test.rb57
-rw-r--r--actionpack/test/controller/api/data_streaming_test.rb26
-rw-r--r--actionpack/test/controller/api/force_ssl_test.rb20
-rw-r--r--actionpack/test/controller/api/implicit_render_test.rb15
-rw-r--r--actionpack/test/controller/api/params_wrapper_test.rb26
-rw-r--r--actionpack/test/controller/api/redirect_to_test.rb19
-rw-r--r--actionpack/test/controller/api/renderers_test.rb38
-rw-r--r--actionpack/test/controller/api/url_for_test.rb20
-rw-r--r--actionpack/test/controller/base_test.rb8
-rw-r--r--actionpack/test/controller/caching_test.rb22
-rw-r--r--actionpack/test/controller/content_type_test.rb26
-rw-r--r--actionpack/test/controller/default_url_options_with_before_action_test.rb2
-rw-r--r--actionpack/test/controller/filters_test.rb186
-rw-r--r--actionpack/test/controller/flash_test.rb53
-rw-r--r--actionpack/test/controller/force_ssl_test.rb22
-rw-r--r--actionpack/test/controller/form_builder_test.rb17
-rw-r--r--actionpack/test/controller/helper_test.rb21
-rw-r--r--actionpack/test/controller/http_basic_authentication_test.rb23
-rw-r--r--actionpack/test/controller/http_digest_authentication_test.rb13
-rw-r--r--actionpack/test/controller/http_token_authentication_test.rb21
-rw-r--r--actionpack/test/controller/integration_test.rb47
-rw-r--r--actionpack/test/controller/live_stream_test.rb32
-rw-r--r--actionpack/test/controller/log_subscriber_test.rb6
-rw-r--r--actionpack/test/controller/mime/respond_to_test.rb200
-rw-r--r--actionpack/test/controller/new_base/base_test.rb6
-rw-r--r--actionpack/test/controller/new_base/content_negotiation_test.rb2
-rw-r--r--actionpack/test/controller/new_base/content_type_test.rb18
-rw-r--r--actionpack/test/controller/new_base/metal_test.rb43
-rw-r--r--actionpack/test/controller/new_base/render_test.rb6
-rw-r--r--actionpack/test/controller/new_base/render_text_test.rb53
-rw-r--r--actionpack/test/controller/parameters/mutators_test.rb4
-rw-r--r--actionpack/test/controller/parameters/nested_parameters_test.rb2
-rw-r--r--actionpack/test/controller/parameters/parameters_permit_test.rb15
-rw-r--r--actionpack/test/controller/permitted_params_test.rb4
-rw-r--r--actionpack/test/controller/redirect_test.rb18
-rw-r--r--actionpack/test/controller/render_json_test.rb2
-rw-r--r--actionpack/test/controller/render_test.rb67
-rw-r--r--actionpack/test/controller/request/test_request_test.rb6
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb28
-rw-r--r--actionpack/test/controller/rescue_test.rb22
-rw-r--r--actionpack/test/controller/resources_test.rb105
-rw-r--r--actionpack/test/controller/routing_test.rb14
-rw-r--r--actionpack/test/controller/send_file_test.rb2
-rw-r--r--actionpack/test/controller/test_case_test.rb157
-rw-r--r--actionpack/test/controller/url_rewriter_test.rb3
-rw-r--r--actionpack/test/controller/webservice_test.rb12
-rw-r--r--actionpack/test/dispatch/cookies_test.rb6
-rw-r--r--actionpack/test/dispatch/exception_wrapper_test.rb32
-rw-r--r--actionpack/test/dispatch/live_response_test.rb14
-rw-r--r--actionpack/test/dispatch/mapper_test.rb70
-rw-r--r--actionpack/test/dispatch/middleware_stack/middleware_test.rb77
-rw-r--r--actionpack/test/dispatch/middleware_stack_test.rb55
-rw-r--r--actionpack/test/dispatch/prefix_generation_test.rb24
-rw-r--r--actionpack/test/dispatch/request/json_params_parsing_test.rb2
-rw-r--r--actionpack/test/dispatch/request/multipart_params_parsing_test.rb13
-rw-r--r--actionpack/test/dispatch/request_test.rb2
-rw-r--r--actionpack/test/dispatch/response_test.rb11
-rw-r--r--actionpack/test/dispatch/routing/concerns_test.rb2
-rw-r--r--actionpack/test/dispatch/routing/inspector_test.rb16
-rw-r--r--actionpack/test/dispatch/routing/route_set_test.rb14
-rw-r--r--actionpack/test/dispatch/routing_test.rb102
-rw-r--r--actionpack/test/dispatch/session/cache_store_test.rb6
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb12
-rw-r--r--actionpack/test/dispatch/session/mem_cache_store_test.rb6
-rw-r--r--actionpack/test/dispatch/session/test_session_test.rb10
-rw-r--r--actionpack/test/dispatch/show_exceptions_test.rb2
-rw-r--r--actionpack/test/dispatch/ssl_test.rb11
-rw-r--r--actionpack/test/dispatch/static_test.rb44
-rw-r--r--actionpack/test/dispatch/template_assertions_test.rb110
-rw-r--r--actionpack/test/dispatch/test_request_test.rb20
-rw-r--r--actionpack/test/dispatch/test_response_test.rb7
-rw-r--r--actionpack/test/dispatch/url_generation_test.rb2
-rw-r--r--actionpack/test/fixtures/customers/_commented_customer.html.erb1
-rw-r--r--actionpack/test/fixtures/customers/_customer.html.erb1
-rw-r--r--actionpack/test/fixtures/layouts/standard.html.erb2
-rw-r--r--actionpack/test/fixtures/multipart/utf8_filename10
-rw-r--r--actionpack/test/fixtures/public/foo/other-index.html1
-rw-r--r--actionpack/test/fixtures/public/other-index.html1
-rw-r--r--actionpack/test/fixtures/公共/foo/other-index.html1
-rw-r--r--actionpack/test/fixtures/公共/other-index.html1
-rw-r--r--actionpack/test/journey/route_test.rb32
-rw-r--r--actionpack/test/journey/router_test.rb77
-rw-r--r--actionpack/test/journey/routes_test.rb20
-rw-r--r--actionview/CHANGELOG.md77
-rw-r--r--actionview/README.rdoc2
-rw-r--r--actionview/Rakefile38
-rw-r--r--actionview/actionview.gemspec2
-rwxr-xr-xactionview/bin/test4
-rw-r--r--actionview/lib/action_view/dependency_tracker.rb38
-rw-r--r--actionview/lib/action_view/digestor.rb19
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb11
-rw-r--r--actionview/lib/action_view/helpers/asset_url_helper.rb63
-rw-r--r--actionview/lib/action_view/helpers/atom_feed_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/cache_helper.rb75
-rw-r--r--actionview/lib/action_view/helpers/capture_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/controller_helper.rb1
-rw-r--r--actionview/lib/action_view/helpers/date_helper.rb37
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb67
-rw-r--r--actionview/lib/action_view/helpers/form_options_helper.rb45
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb27
-rw-r--r--actionview/lib/action_view/helpers/javascript_helper.rb6
-rw-r--r--actionview/lib/action_view/helpers/number_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/rendering_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/tag_helper.rb27
-rw-r--r--actionview/lib/action_view/helpers/tags/base.rb12
-rw-r--r--actionview/lib/action_view/helpers/text_helper.rb25
-rw-r--r--actionview/lib/action_view/helpers/translation_helper.rb71
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb41
-rw-r--r--actionview/lib/action_view/lookup_context.rb15
-rw-r--r--actionview/lib/action_view/path_set.rb9
-rw-r--r--actionview/lib/action_view/railtie.rb14
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer.rb6
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb4
-rw-r--r--actionview/lib/action_view/rendering.rb4
-rw-r--r--actionview/lib/action_view/routing_url_for.rb10
-rw-r--r--actionview/lib/action_view/template.rb34
-rw-r--r--actionview/lib/action_view/template/handlers/erb.rb11
-rw-r--r--actionview/lib/action_view/template/resolver.rb38
-rw-r--r--actionview/lib/action_view/test_case.rb16
-rw-r--r--actionview/lib/action_view/view_paths.rb6
-rw-r--r--actionview/test/abstract_unit.rb14
-rw-r--r--actionview/test/actionpack/abstract/layouts_test.rb4
-rw-r--r--actionview/test/actionpack/abstract/render_test.rb3
-rw-r--r--actionview/test/actionpack/controller/layout_test.rb26
-rw-r--r--actionview/test/actionpack/controller/render_test.rb106
-rw-r--r--actionview/test/actionpack/controller/view_paths_test.rb4
-rw-r--r--actionview/test/activerecord/form_helper_activerecord_test.rb4
-rw-r--r--actionview/test/activerecord/render_partial_with_record_identification_test.rb18
-rw-r--r--actionview/test/fixtures/actionpack/layout_tests/alt/layouts/alt.erb1
-rw-r--r--actionview/test/fixtures/actionpack/layouts/standard.text.erb1
-rw-r--r--actionview/test/fixtures/actionpack/test/hyphen-ated.erb2
-rw-r--r--actionview/test/fixtures/digestor/events/_completed.html.erb0
-rw-r--r--actionview/test/fixtures/digestor/events/index.html.erb1
-rw-r--r--actionview/test/fixtures/test/_partial_name_in_local_assigns.erb1
-rw-r--r--actionview/test/template/asset_tag_helper_test.rb13
-rw-r--r--actionview/test/template/atom_feed_helper_test.rb22
-rw-r--r--actionview/test/template/capture_helper_test.rb6
-rw-r--r--actionview/test/template/controller_helper_test.rb21
-rw-r--r--actionview/test/template/date_helper_test.rb10
-rw-r--r--actionview/test/template/digestor_test.rb73
-rw-r--r--actionview/test/template/form_helper_test.rb24
-rw-r--r--actionview/test/template/form_options_helper_test.rb7
-rw-r--r--actionview/test/template/form_tag_helper_test.rb7
-rw-r--r--actionview/test/template/record_tag_helper_test.rb3
-rw-r--r--actionview/test/template/render_test.rb25
-rw-r--r--actionview/test/template/template_test.rb32
-rw-r--r--actionview/test/template/test_case_test.rb58
-rw-r--r--actionview/test/template/text_helper_test.rb16
-rw-r--r--actionview/test/template/translation_helper_test.rb29
-rw-r--r--actionview/test/template/url_helper_test.rb17
-rw-r--r--activejob/CHANGELOG.md32
-rw-r--r--activejob/README.md2
-rw-r--r--activejob/Rakefile19
-rw-r--r--activejob/activejob.gemspec2
-rw-r--r--activejob/lib/active_job/arguments.rb28
-rw-r--r--activejob/lib/active_job/base.rb2
-rw-r--r--activejob/lib/active_job/core.rb12
-rw-r--r--activejob/lib/active_job/enqueuing.rb1
-rw-r--r--activejob/lib/active_job/queue_adapter.rb6
-rw-r--r--activejob/lib/active_job/queue_adapters.rb98
-rw-r--r--activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb8
-rw-r--r--activejob/lib/active_job/queue_adapters/inline_adapter.rb2
-rw-r--r--activejob/lib/active_job/queue_adapters/qu_adapter.rb8
-rw-r--r--activejob/lib/active_job/queue_adapters/que_adapter.rb8
-rw-r--r--activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb8
-rw-r--r--activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb4
-rw-r--r--activejob/lib/active_job/queue_adapters/sneakers_adapter.rb2
-rw-r--r--activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb2
-rw-r--r--activejob/lib/active_job/queue_name.rb2
-rw-r--r--activejob/lib/active_job/test_helper.rb21
-rw-r--r--activejob/lib/active_job/translation.rb11
-rw-r--r--activejob/lib/rails/generators/job/job_generator.rb2
-rw-r--r--activejob/test/cases/argument_serialization_test.rb7
-rw-r--r--activejob/test/cases/job_serialization_test.rb17
-rw-r--r--activejob/test/cases/test_case_test.rb8
-rw-r--r--activejob/test/cases/test_helper_test.rb44
-rw-r--r--activejob/test/cases/translation_test.rb20
-rw-r--r--activejob/test/helper.rb2
-rw-r--r--activejob/test/integration/queuing_test.rb35
-rw-r--r--activejob/test/jobs/translated_hello_job.rb10
-rw-r--r--activejob/test/support/integration/adapters/que.rb26
-rw-r--r--activejob/test/support/integration/adapters/queue_classic.rb22
-rw-r--r--activejob/test/support/integration/adapters/sidekiq.rb80
-rw-r--r--activejob/test/support/integration/dummy_app_template.rb6
-rw-r--r--activejob/test/support/integration/test_case_helpers.rb8
-rw-r--r--activejob/test/support/que/inline.rb6
-rw-r--r--activemodel/CHANGELOG.md22
-rw-r--r--activemodel/README.rdoc13
-rw-r--r--activemodel/Rakefile19
-rw-r--r--activemodel/activemodel.gemspec2
-rwxr-xr-xactivemodel/bin/test4
-rw-r--r--activemodel/lib/active_model.rb1
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb28
-rw-r--r--activemodel/lib/active_model/dirty.rb27
-rw-r--r--activemodel/lib/active_model/errors.rb8
-rw-r--r--activemodel/lib/active_model/naming.rb2
-rw-r--r--activemodel/lib/active_model/serialization.rb36
-rw-r--r--activemodel/lib/active_model/serializers/xml.rb238
-rw-r--r--activemodel/lib/active_model/validations.rb7
-rw-r--r--activemodel/lib/active_model/validations/acceptance.rb7
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb2
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb4
-rw-r--r--activemodel/lib/active_model/validations/helper_methods.rb13
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb6
-rw-r--r--activemodel/lib/active_model/validations/length.rb15
-rw-r--r--activemodel/lib/active_model/validations/validates.rb2
-rw-r--r--activemodel/lib/active_model/validations/with.rb10
-rw-r--r--activemodel/test/cases/attribute_assignment_test.rb2
-rw-r--r--activemodel/test/cases/dirty_test.rb13
-rw-r--r--activemodel/test/cases/errors_test.rb59
-rw-r--r--activemodel/test/cases/helper.rb7
-rw-r--r--activemodel/test/cases/serialization_test.rb25
-rw-r--r--activemodel/test/cases/serializers/xml_serialization_test.rb250
-rw-r--r--activemodel/test/cases/validations/acceptance_validation_test.rb14
-rw-r--r--activemodel/test/cases/validations/exclusion_validation_test.rb17
-rw-r--r--activemodel/test/cases/validations/i18n_validation_test.rb160
-rw-r--r--activemodel/test/cases/validations/with_validation_test.rb8
-rw-r--r--activemodel/test/models/contact.rb1
-rw-r--r--activerecord/CHANGELOG.md437
-rw-r--r--activerecord/README.rdoc6
-rw-r--r--activerecord/Rakefile27
-rw-r--r--activerecord/activerecord.gemspec2
-rwxr-xr-xactiverecord/bin/test19
-rw-r--r--activerecord/lib/active_record.rb3
-rw-r--r--activerecord/lib/active_record/aggregations.rb2
-rw-r--r--activerecord/lib/active_record/associations.rb55
-rw-r--r--activerecord/lib/active_record/associations/association.rb17
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb1
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/builder/association.rb2
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb29
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb12
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb21
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb19
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb9
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb12
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb9
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb8
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb17
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb18
-rw-r--r--activerecord/lib/active_record/attribute/user_provided_default.rb32
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb3
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb38
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb7
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb12
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb2
-rw-r--r--activerecord/lib/active_record/attributes.rb20
-rw-r--r--activerecord/lib/active_record/autosave_association.rb9
-rw-r--r--activerecord/lib/active_record/base.rb12
-rw-r--r--activerecord/lib/active_record/callbacks.rb19
-rw-r--r--activerecord/lib/active_record/collection_cache_key.rb31
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb489
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb39
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb38
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb87
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb36
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb214
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb41
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/column.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb50
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb87
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb46
-rw-r--r--activerecord/lib/active_record/connection_adapters/schema_cache.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb103
-rw-r--r--activerecord/lib/active_record/connection_adapters/statement_pool.rb34
-rw-r--r--activerecord/lib/active_record/connection_handling.rb2
-rw-r--r--activerecord/lib/active_record/core.rb23
-rw-r--r--activerecord/lib/active_record/enum.rb46
-rw-r--r--activerecord/lib/active_record/errors.rb13
-rw-r--r--activerecord/lib/active_record/explain_subscriber.rb2
-rw-r--r--activerecord/lib/active_record/fixtures.rb16
-rw-r--r--activerecord/lib/active_record/inheritance.rb14
-rw-r--r--activerecord/lib/active_record/locale/en.yml4
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb19
-rw-r--r--activerecord/lib/active_record/migration.rb32
-rw-r--r--activerecord/lib/active_record/migration/command_recorder.rb38
-rw-r--r--activerecord/lib/active_record/model_schema.rb23
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb24
-rw-r--r--activerecord/lib/active_record/persistence.rb45
-rw-r--r--activerecord/lib/active_record/querying.rb2
-rw-r--r--activerecord/lib/active_record/railtie.rb13
-rw-r--r--activerecord/lib/active_record/railties/databases.rake46
-rw-r--r--activerecord/lib/active_record/reflection.rb16
-rw-r--r--activerecord/lib/active_record/relation.rb65
-rw-r--r--activerecord/lib/active_record/relation/batches.rb98
-rw-r--r--activerecord/lib/active_record/relation/batches/batch_enumerator.rb67
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb18
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb4
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb40
-rw-r--r--activerecord/lib/active_record/relation/merger.rb25
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb6
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb29
-rw-r--r--activerecord/lib/active_record/relation/where_clause.rb2
-rw-r--r--activerecord/lib/active_record/sanitization.rb8
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb20
-rw-r--r--activerecord/lib/active_record/schema_migration.rb1
-rw-r--r--activerecord/lib/active_record/scoping/default.rb1
-rw-r--r--activerecord/lib/active_record/secure_token.rb2
-rw-r--r--activerecord/lib/active_record/serialization.rb2
-rw-r--r--activerecord/lib/active_record/serializers/xml_serializer.rb193
-rw-r--r--activerecord/lib/active_record/suppressor.rb3
-rw-r--r--activerecord/lib/active_record/table_metadata.rb2
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb25
-rw-r--r--activerecord/lib/active_record/tasks/postgresql_database_tasks.rb28
-rw-r--r--activerecord/lib/active_record/touch_later.rb50
-rw-r--r--activerecord/lib/active_record/transactions.rb23
-rw-r--r--activerecord/lib/active_record/type/decimal.rb2
-rw-r--r--activerecord/lib/active_record/type/integer.rb9
-rw-r--r--activerecord/lib/active_record/validations.rb1
-rw-r--r--activerecord/lib/active_record/validations/absence.rb24
-rw-r--r--activerecord/lib/active_record/validations/length.rb5
-rw-r--r--activerecord/lib/active_record/validations/presence.rb18
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb6
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/migration_generator.rb9
-rw-r--r--activerecord/test/cases/adapters/mysql/active_schema_test.rb39
-rw-r--r--activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb3
-rw-r--r--activerecord/test/cases/adapters/mysql/charset_collation_test.rb54
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb11
-rw-r--r--activerecord/test/cases/adapters/mysql/consistency_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/enum_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/quoting_test.rb38
-rw-r--r--activerecord/test/cases/adapters/mysql/reserved_word_test.rb40
-rw-r--r--activerecord/test/cases/adapters/mysql/schema_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/sp_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql/sql_types_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/statement_pool_test.rb28
-rw-r--r--activerecord/test/cases/adapters/mysql/table_options_test.rb42
-rw-r--r--activerecord/test/cases/adapters/mysql/unsigned_type_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb39
-rw-r--r--activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/boolean_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb3
-rw-r--r--activerecord/test/cases/adapters/mysql2/charset_collation_test.rb54
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb11
-rw-r--r--activerecord/test/cases/adapters/mysql2/enum_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/explain_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/quoting_test.rb21
-rw-r--r--activerecord/test/cases/adapters/mysql2/reserved_word_test.rb40
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb81
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/sql_types_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/table_options_test.rb42
-rw-r--r--activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/active_schema_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/array_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/bit_string_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/change_schema_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/cidr_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/citext_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/collation_test.rb53
-rw-r--r--activerecord/test/cases/adapters/postgresql/composite_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/domain_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/enum_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/explain_test.rb30
-rw-r--r--activerecord/test/cases/adapters/postgresql/extension_migration_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/full_text_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/geometric_test.rb123
-rw-r--r--activerecord/test/cases/adapters/postgresql/hstore_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/infinity_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/integer_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/json_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/ltree_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/money_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/network_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/numbers_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/quoting_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/range_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/rename_table_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb11
-rw-r--r--activerecord/test/cases/adapters/postgresql/serial_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/statement_pool_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/timestamp_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/type_lookup_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/utils_test.rb5
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/view_test.rb7
-rw-r--r--activerecord/test/cases/adapters/postgresql/xml_test.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/collation_test.rb53
-rw-r--r--activerecord/test/cases/adapters/sqlite3/copy_table_test.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/explain_test.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/quoting_test.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb10
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb3
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb78
-rw-r--r--activerecord/test/cases/associations/eager_test.rb2
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb83
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb121
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb111
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb28
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb40
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb40
-rw-r--r--activerecord/test/cases/associations/nested_through_associations_test.rb2
-rw-r--r--activerecord/test/cases/associations_test.rb17
-rw-r--r--activerecord/test/cases/attributes_test.rb22
-rw-r--r--activerecord/test/cases/autosave_association_test.rb51
-rw-r--r--activerecord/test/cases/base_test.rb24
-rw-r--r--activerecord/test/cases/batches_test.rb232
-rw-r--r--activerecord/test/cases/calculations_test.rb40
-rw-r--r--activerecord/test/cases/callbacks_test.rb2
-rw-r--r--activerecord/test/cases/collection_cache_key_test.rb70
-rw-r--r--activerecord/test/cases/connection_adapters/adapter_leasing_test.rb6
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb46
-rw-r--r--activerecord/test/cases/connection_adapters/type_lookup_test.rb6
-rw-r--r--activerecord/test/cases/connection_management_test.rb25
-rw-r--r--activerecord/test/cases/connection_pool_test.rb199
-rw-r--r--activerecord/test/cases/counter_cache_test.rb13
-rw-r--r--activerecord/test/cases/dirty_test.rb42
-rw-r--r--activerecord/test/cases/disconnected_test.rb4
-rw-r--r--activerecord/test/cases/enum_test.rb136
-rw-r--r--activerecord/test/cases/explain_subscriber_test.rb5
-rw-r--r--activerecord/test/cases/fixtures_test.rb38
-rw-r--r--activerecord/test/cases/helper.rb3
-rw-r--r--activerecord/test/cases/inheritance_test.rb119
-rw-r--r--activerecord/test/cases/invertible_migration_test.rb16
-rw-r--r--activerecord/test/cases/locking_test.rb12
-rw-r--r--activerecord/test/cases/migration/change_schema_test.rb2
-rw-r--r--activerecord/test/cases/migration/columns_test.rb7
-rw-r--r--activerecord/test/cases/migration/command_recorder_test.rb46
-rw-r--r--activerecord/test/cases/migration/foreign_key_test.rb31
-rw-r--r--activerecord/test/cases/migration/postgresql_geometric_types_test.rb93
-rw-r--r--activerecord/test/cases/migration/references_foreign_key_test.rb20
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb45
-rw-r--r--activerecord/test/cases/persistence_test.rb27
-rw-r--r--activerecord/test/cases/primary_keys_test.rb14
-rw-r--r--activerecord/test/cases/relation/delegation_test.rb2
-rw-r--r--activerecord/test/cases/relation/mutation_test.rb17
-rw-r--r--activerecord/test/cases/relation_test.rb24
-rw-r--r--activerecord/test/cases/relations_test.rb66
-rw-r--r--activerecord/test/cases/reload_models_test.rb2
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb29
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb18
-rw-r--r--activerecord/test/cases/serialization_test.rb2
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb21
-rw-r--r--activerecord/test/cases/suppressor_test.rb33
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb15
-rw-r--r--activerecord/test/cases/tasks/postgresql_rake_test.rb12
-rw-r--r--activerecord/test/cases/test_case.rb26
-rw-r--r--activerecord/test/cases/timestamp_test.rb11
-rw-r--r--activerecord/test/cases/touch_later_test.rb114
-rw-r--r--activerecord/test/cases/transactions_test.rb11
-rw-r--r--activerecord/test/cases/type/integer_test.rb2
-rw-r--r--activerecord/test/cases/validations/absence_validation_test.rb75
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb44
-rw-r--r--activerecord/test/cases/view_test.rb2
-rw-r--r--activerecord/test/cases/xml_serialization_test.rb447
-rw-r--r--activerecord/test/fixtures/books.yml20
-rw-r--r--activerecord/test/fixtures/doubloons.yml3
-rw-r--r--activerecord/test/fixtures/naked/yml/parrots.yml2
-rw-r--r--activerecord/test/fixtures/nodes.yml29
-rw-r--r--activerecord/test/fixtures/trees.yml3
-rw-r--r--activerecord/test/models/aircraft.rb1
-rw-r--r--activerecord/test/models/book.rb4
-rw-r--r--activerecord/test/models/carrier.rb2
-rw-r--r--activerecord/test/models/company.rb3
-rw-r--r--activerecord/test/models/customer_carrier.rb14
-rw-r--r--activerecord/test/models/doubloon.rb12
-rw-r--r--activerecord/test/models/member.rb3
-rw-r--r--activerecord/test/models/member_detail.rb7
-rw-r--r--activerecord/test/models/membership.rb15
-rw-r--r--activerecord/test/models/node.rb5
-rw-r--r--activerecord/test/models/post.rb16
-rw-r--r--activerecord/test/models/professor.rb5
-rw-r--r--activerecord/test/models/ship.rb12
-rw-r--r--activerecord/test/models/shop_account.rb6
-rw-r--r--activerecord/test/models/topic.rb2
-rw-r--r--activerecord/test/models/tree.rb3
-rw-r--r--activerecord/test/models/vehicle.rb7
-rw-r--r--activerecord/test/schema/mysql2_specific_schema.rb14
-rw-r--r--activerecord/test/schema/mysql_specific_schema.rb14
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb12
-rw-r--r--activerecord/test/schema/schema.rb60
-rw-r--r--activerecord/test/support/connection.rb1
-rw-r--r--activesupport/CHANGELOG.md109
-rw-r--r--activesupport/README.rdoc2
-rw-r--r--activesupport/Rakefile14
-rw-r--r--activesupport/activesupport.gemspec3
-rwxr-xr-xactivesupport/bin/test4
-rw-r--r--activesupport/lib/active_support.rb8
-rw-r--r--activesupport/lib/active_support/array_inquirer.rb14
-rw-r--r--activesupport/lib/active_support/backtrace_cleaner.rb2
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb1
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb9
-rw-r--r--activesupport/lib/active_support/cache/memory_store.rb2
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache.rb2
-rw-r--r--activesupport/lib/active_support/callbacks.rb22
-rw-r--r--activesupport/lib/active_support/concern.rb2
-rw-r--r--activesupport/lib/active_support/concurrency/latch.rb24
-rw-r--r--activesupport/lib/active_support/concurrency/share_lock.rb142
-rw-r--r--activesupport/lib/active_support/core_ext/array/inquiry.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/array/wrap.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/class.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/class/delegating_attributes.rb45
-rw-r--r--activesupport/lib/active_support/core_ext/date/conversions.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/calculations.rb53
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb15
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/hash/except.rb17
-rw-r--r--activesupport/lib/active_support/core_ext/load_error.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/aliasing.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/concerning.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/numeric.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/inquiry.rb26
-rw-r--r--activesupport/lib/active_support/core_ext/object/deep_dup.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/object/duplicable.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/object/try.rb74
-rw-r--r--activesupport/lib/active_support/core_ext/object/with_options.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/range/each.rb38
-rw-r--r--activesupport/lib/active_support/core_ext/range/include_range.rb40
-rw-r--r--activesupport/lib/active_support/core_ext/string/filters.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/time/zones.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/uri.rb2
-rw-r--r--activesupport/lib/active_support/dependencies.rb101
-rw-r--r--activesupport/lib/active_support/dependencies/interlock.rb47
-rw-r--r--activesupport/lib/active_support/deprecation/behaviors.rb12
-rw-r--r--activesupport/lib/active_support/deprecation/proxy_wrappers.rb69
-rw-r--r--activesupport/lib/active_support/duration.rb4
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb18
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb19
-rw-r--r--activesupport/lib/active_support/inflector/inflections.rb37
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb42
-rw-r--r--activesupport/lib/active_support/inflector/transliterate.rb18
-rw-r--r--activesupport/lib/active_support/json/decoding.rb10
-rw-r--r--activesupport/lib/active_support/json/encoding.rb4
-rw-r--r--activesupport/lib/active_support/log_subscriber/test_helper.rb4
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb2
-rw-r--r--activesupport/lib/active_support/message_verifier.rb6
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb8
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb6
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb2
-rw-r--r--activesupport/lib/active_support/number_helper.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_currency_converter.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb4
-rw-r--r--activesupport/lib/active_support/ordered_options.rb16
-rw-r--r--activesupport/lib/active_support/rails.rb4
-rw-r--r--activesupport/lib/active_support/railtie.rb7
-rw-r--r--activesupport/lib/active_support/rescuable.rb2
-rw-r--r--activesupport/lib/active_support/subscriber.rb4
-rw-r--r--activesupport/lib/active_support/test_case.rb13
-rw-r--r--activesupport/lib/active_support/testing/assertions.rb20
-rw-r--r--activesupport/lib/active_support/testing/autorun.rb9
-rw-r--r--activesupport/lib/active_support/testing/composite_filter.rb54
-rw-r--r--activesupport/lib/active_support/testing/isolation.rb8
-rw-r--r--activesupport/lib/active_support/testing/method_call_assertions.rb39
-rw-r--r--activesupport/lib/active_support/testing/time_helpers.rb2
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb16
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb72
-rw-r--r--activesupport/lib/active_support/xml_mini.rb3
-rw-r--r--activesupport/lib/active_support/xml_mini/jdom.rb11
-rw-r--r--activesupport/lib/active_support/xml_mini/rexml.rb11
-rw-r--r--activesupport/test/abstract_unit.rb7
-rw-r--r--activesupport/test/autoloading_fixtures/a/c/e/f.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/a/c/em/f.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/d.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/e.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/em.rb2
-rw-r--r--activesupport/test/caching_test.rb192
-rw-r--r--activesupport/test/concern_test.rb25
-rw-r--r--activesupport/test/core_ext/class/delegating_attributes_test.rb122
-rw-r--r--activesupport/test/core_ext/date_ext_test.rb33
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb75
-rw-r--r--activesupport/test/core_ext/duration_test.rb34
-rw-r--r--activesupport/test/core_ext/enumerable_test.rb14
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb52
-rw-r--r--activesupport/test/core_ext/marshal_test.rb32
-rw-r--r--activesupport/test/core_ext/module/attribute_accessor_test.rb6
-rw-r--r--activesupport/test/core_ext/numeric_ext_test.rb102
-rw-r--r--activesupport/test/core_ext/object/deep_dup_test.rb6
-rw-r--r--activesupport/test/core_ext/object/json_gem_encoding_test.rb66
-rw-r--r--activesupport/test/core_ext/object/try_test.rb76
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb178
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb103
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb139
-rw-r--r--activesupport/test/dependencies_test.rb41
-rw-r--r--activesupport/test/deprecation_test.rb16
-rw-r--r--activesupport/test/hash_with_indifferent_access_test.rb11
-rw-r--r--activesupport/test/inflector_test.rb154
-rw-r--r--activesupport/test/json/encoding_test.rb141
-rw-r--r--activesupport/test/json/encoding_test_cases.rb88
-rw-r--r--activesupport/test/log_subscriber_test.rb9
-rw-r--r--activesupport/test/message_verifier_test.rb1
-rw-r--r--activesupport/test/multibyte_unicode_database_test.rb5
-rw-r--r--activesupport/test/number_helper_test.rb20
-rw-r--r--activesupport/test/ordered_options_test.rb15
-rw-r--r--activesupport/test/share_lock_test.rb333
-rw-r--r--activesupport/test/testing/method_call_assertions_test.rb111
-rw-r--r--activesupport/test/time_travel_test.rb101
-rw-r--r--activesupport/test/time_zone_test.rb98
-rw-r--r--activesupport/test/xml_mini_test.rb2
-rwxr-xr-xci/travis.rb32
-rw-r--r--guides/CHANGELOG.md4
-rw-r--r--guides/Rakefile2
-rw-r--r--guides/assets/images/getting_started/rails_welcome.pngbin94542 -> 142320 bytes
-rw-r--r--guides/bug_report_templates/action_controller_gem.rb17
-rw-r--r--guides/bug_report_templates/action_controller_master.rb23
-rw-r--r--guides/bug_report_templates/active_record_gem.rb16
-rw-r--r--guides/bug_report_templates/active_record_master.rb22
-rw-r--r--guides/bug_report_templates/generic_gem.rb25
-rw-r--r--guides/bug_report_templates/generic_master.rb26
-rw-r--r--guides/rails_guides/levenshtein.rb7
-rw-r--r--guides/source/3_0_release_notes.md2
-rw-r--r--guides/source/3_1_release_notes.md4
-rw-r--r--guides/source/3_2_release_notes.md2
-rw-r--r--guides/source/4_0_release_notes.md2
-rw-r--r--guides/source/action_controller_overview.md16
-rw-r--r--guides/source/action_mailer_basics.md34
-rw-r--r--guides/source/action_view_overview.md141
-rw-r--r--guides/source/active_job_basics.md80
-rw-r--r--guides/source/active_model_basics.md71
-rw-r--r--guides/source/active_record_basics.md6
-rw-r--r--guides/source/active_record_migrations.md52
-rw-r--r--guides/source/active_record_postgresql.md80
-rw-r--r--guides/source/active_record_querying.md25
-rw-r--r--guides/source/active_record_validations.md35
-rw-r--r--guides/source/active_support_core_extensions.md38
-rw-r--r--guides/source/active_support_instrumentation.md25
-rw-r--r--guides/source/api_app.md408
-rw-r--r--guides/source/api_documentation_guidelines.md3
-rw-r--r--guides/source/asset_pipeline.md45
-rw-r--r--guides/source/association_basics.md121
-rw-r--r--guides/source/autoloading_and_reloading_constants.md4
-rw-r--r--guides/source/caching_with_rails.md393
-rw-r--r--guides/source/command_line.md18
-rw-r--r--guides/source/configuring.md117
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md84
-rw-r--r--guides/source/credits.html.erb2
-rw-r--r--guides/source/debugging_rails_applications.md105
-rw-r--r--guides/source/development_dependencies_install.md10
-rw-r--r--guides/source/documents.yaml14
-rw-r--r--guides/source/engines.md29
-rw-r--r--guides/source/form_helpers.md11
-rw-r--r--guides/source/generators.md8
-rw-r--r--guides/source/getting_started.md61
-rw-r--r--guides/source/i18n.md186
-rw-r--r--guides/source/initialization.md17
-rw-r--r--guides/source/kindle/welcome.html.erb4
-rw-r--r--guides/source/layouts_and_rendering.md37
-rw-r--r--guides/source/nested_model_forms.md2
-rw-r--r--guides/source/rails_application_templates.md2
-rw-r--r--guides/source/rails_on_rack.md22
-rw-r--r--guides/source/routing.md35
-rw-r--r--guides/source/ruby_on_rails_guides_guidelines.md6
-rw-r--r--guides/source/security.md55
-rw-r--r--guides/source/testing.md209
-rw-r--r--guides/source/upgrading_ruby_on_rails.md26
-rw-r--r--guides/source/working_with_javascript_in_rails.md32
-rw-r--r--install.rb4
-rw-r--r--rails.gemspec2
-rw-r--r--railties/CHANGELOG.md115
-rw-r--r--railties/Rakefile18
-rwxr-xr-xrailties/exe/rails (renamed from railties/bin/rails)0
-rw-r--r--railties/lib/rails.rb10
-rw-r--r--railties/lib/rails/app_loader.rb (renamed from railties/lib/rails/app_rails_loader.rb)4
-rw-r--r--railties/lib/rails/application.rb7
-rw-r--r--railties/lib/rails/application/bootstrap.rb2
-rw-r--r--railties/lib/rails/application/configuration.rb13
-rw-r--r--railties/lib/rails/application/default_middleware_stack.rb38
-rw-r--r--railties/lib/rails/application/finisher.rb6
-rw-r--r--railties/lib/rails/application_controller.rb2
-rw-r--r--railties/lib/rails/cli.rb4
-rw-r--r--railties/lib/rails/code_statistics.rb6
-rw-r--r--railties/lib/rails/commands/commands_tasks.rb11
-rw-r--r--railties/lib/rails/commands/console.rb31
-rw-r--r--railties/lib/rails/commands/console_helper.rb34
-rw-r--r--railties/lib/rails/commands/dbconsole.rb140
-rw-r--r--railties/lib/rails/commands/server.rb39
-rw-r--r--railties/lib/rails/commands/test.rb10
-rw-r--r--railties/lib/rails/configuration.rb3
-rw-r--r--railties/lib/rails/console/app.rb5
-rw-r--r--railties/lib/rails/engine.rb75
-rw-r--r--railties/lib/rails/engine/commands.rb6
-rw-r--r--railties/lib/rails/generators.rb26
-rw-r--r--railties/lib/rails/generators/actions.rb22
-rw-r--r--railties/lib/rails/generators/app_base.rb12
-rw-r--r--railties/lib/rails/generators/base.rb6
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb2
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/index.html.erb2
-rw-r--r--railties/lib/rails/generators/named_base.rb8
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb41
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile20
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/setup15
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/update28
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt14
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb14
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/routes.rb55
-rw-r--r--railties/lib/rails/generators/rails/app/templates/gitignore5
-rw-r--r--railties/lib/rails/generators/rails/controller/controller_generator.rb10
-rw-r--r--railties/lib/rails/generators/rails/plugin/plugin_generator.rb37
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb1
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css3
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/test/integration/navigation_test.rb4
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb3
-rw-r--r--railties/lib/rails/generators/rails/resource/resource_generator.rb1
-rw-r--r--railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb1
-rw-r--r--railties/lib/rails/generators/rails/scaffold/USAGE2
-rw-r--r--railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb3
-rw-r--r--railties/lib/rails/generators/rails/scaffold/templates/scaffold.css27
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb5
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb61
-rw-r--r--railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb6
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb15
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb43
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb10
-rw-r--r--railties/lib/rails/generators/testing/assertions.rb2
-rw-r--r--railties/lib/rails/info_controller.rb2
-rw-r--r--railties/lib/rails/mailers_controller.rb26
-rw-r--r--railties/lib/rails/paths.rb10
-rw-r--r--railties/lib/rails/rack/logger.rb4
-rw-r--r--railties/lib/rails/ruby_version_check.rb6
-rw-r--r--railties/lib/rails/tasks.rb6
-rw-r--r--railties/lib/rails/tasks/dev.rake15
-rw-r--r--railties/lib/rails/tasks/framework.rake34
-rw-r--r--railties/lib/rails/tasks/restart.rake1
-rw-r--r--railties/lib/rails/templates/rails/mailers/email.html.erb15
-rw-r--r--railties/lib/rails/templates/rails/mailers/index.html.erb4
-rw-r--r--railties/lib/rails/templates/rails/mailers/mailer.html.erb2
-rw-r--r--railties/lib/rails/templates/rails/welcome/index.html.erb13
-rw-r--r--railties/lib/rails/test_help.rb9
-rw-r--r--railties/lib/rails/test_unit/minitest_plugin.rb52
-rw-r--r--railties/lib/rails/test_unit/reporter.rb22
-rw-r--r--railties/lib/rails/test_unit/runner.rb137
-rw-r--r--railties/lib/rails/test_unit/sub_test_task.rb126
-rw-r--r--railties/lib/rails/test_unit/test_requirer.rb28
-rw-r--r--railties/lib/rails/test_unit/testing.rake15
-rw-r--r--railties/railties.gemspec6
-rw-r--r--railties/test/app_loader_test.rb (renamed from railties/test/app_rails_loader_test.rb)18
-rw-r--r--railties/test/application/asset_debugging_test.rb2
-rw-r--r--railties/test/application/assets_test.rb17
-rw-r--r--railties/test/application/bin_setup_test.rb54
-rw-r--r--railties/test/application/configuration/base_test.rb37
-rw-r--r--railties/test/application/configuration/custom_test.rb72
-rw-r--r--railties/test/application/configuration_test.rb30
-rw-r--r--railties/test/application/console_test.rb12
-rw-r--r--railties/test/application/generators_test.rb35
-rw-r--r--railties/test/application/initializers/frameworks_test.rb33
-rw-r--r--railties/test/application/initializers/i18n_test.rb73
-rw-r--r--railties/test/application/loading_test.rb4
-rw-r--r--railties/test/application/mailer_previews_test.rb248
-rw-r--r--railties/test/application/middleware/session_test.rb18
-rw-r--r--railties/test/application/middleware/static_test.rb21
-rw-r--r--railties/test/application/middleware_test.rb58
-rw-r--r--railties/test/application/multiple_applications_test.rb2
-rw-r--r--railties/test/application/per_request_digest_cache_test.rb63
-rw-r--r--railties/test/application/rake/dbs_test.rb66
-rw-r--r--railties/test/application/rake/dev_test.rb35
-rw-r--r--railties/test/application/rake/framework_test.rb48
-rw-r--r--railties/test/application/rake/migrations_test.rb98
-rw-r--r--railties/test/application/rake/notes_test.rb7
-rw-r--r--railties/test/application/rake/restart_test.rb12
-rw-r--r--railties/test/application/rake_test.rb98
-rw-r--r--railties/test/application/runner_test.rb20
-rw-r--r--railties/test/application/test_runner_test.rb167
-rw-r--r--railties/test/application/test_test.rb6
-rw-r--r--railties/test/code_statistics_test.rb20
-rw-r--r--railties/test/commands/dbconsole_test.rb18
-rw-r--r--railties/test/commands/server_test.rb16
-rw-r--r--railties/test/generators/actions_test.rb87
-rw-r--r--railties/test/generators/api_app_generator_test.rb97
-rw-r--r--railties/test/generators/app_generator_test.rb41
-rw-r--r--railties/test/generators/controller_generator_test.rb4
-rw-r--r--railties/test/generators/named_base_test.rb23
-rw-r--r--railties/test/generators/plugin_generator_test.rb57
-rw-r--r--railties/test/generators/scaffold_controller_generator_test.rb69
-rw-r--r--railties/test/generators/scaffold_generator_test.rb140
-rw-r--r--railties/test/generators/shared_generator_tests.rb17
-rw-r--r--railties/test/generators_test.rb58
-rw-r--r--railties/test/isolation/abstract_unit.rb6
-rw-r--r--railties/test/path_generation_test.rb8
-rw-r--r--railties/test/paths_test.rb160
-rw-r--r--railties/test/railties/generators_test.rb10
-rw-r--r--railties/test/test_info_test.rb60
-rw-r--r--railties/test/test_unit/reporter_test.rb14
-rw-r--r--railties/test/test_unit/runner_test.rb111
-rwxr-xr-xtools/profile4
-rw-r--r--tools/test.rb12
914 files changed, 17984 insertions, 9489 deletions
diff --git a/.travis.yml b/.travis.yml
index 0038668ae7..605d1ff247 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -19,17 +19,14 @@ env:
- "GEM=ar:sqlite3"
- "GEM=ar:postgresql"
- "GEM=aj:integration"
+ - "GEM=guides"
rvm:
- - 2.2.1
+ - 2.2.2
- ruby-head
- - rbx-2
- - jruby-head
matrix:
allow_failures:
- env: "GEM=ar:mysql"
- rvm: ruby-head
- - rvm: rbx-2
- - rvm: jruby-head
fast_finish: true
notifications:
email: false
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 421e7088cb..699b6fd2d1 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -8,7 +8,7 @@ Ruby on Rails is a volunteer effort. We encourage you to pitch in. [Join the tea
*We only accept bug reports and pull requests on GitHub*.
-* If you have a question about how to use Ruby on Rails, please [ask the rubyonrails-talk mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-talk).
+* If you have a question about how to use Ruby on Rails, please [ask it on the rubyonrails-talk mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-talk).
* If you have a change or new feature in mind, please [suggest it on the rubyonrails-core mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core) and start writing code.
diff --git a/Gemfile b/Gemfile
index 4d6b17202f..95718d67ae 100644
--- a/Gemfile
+++ b/Gemfile
@@ -5,6 +5,9 @@ gemspec
# We need a newish Rake since Active Job sets its test tasks' descriptions.
gem 'rake', '>= 10.3'
+# Active Job depends on the URI::GID::MissingModelIDError, which isn't released yet.
+gem 'globalid', github: 'rails/globalid'
+
# This needs to be with require false as it is
# loaded after loading the test library to
# ensure correct loading order
@@ -18,11 +21,12 @@ gem 'arel', github: 'rails/arel', branch: 'master'
gem 'mail', github: 'mikel/mail'
gem 'sprockets', '~> 3.0.0.rc.1'
+gem 'sprockets-rails', github: 'rails/sprockets-rails', branch: 'master'
# require: false so bcrypt is loaded only when has_secure_password is used.
# This is to avoid ActiveModel (and by extension the entire framework)
# being dependent on a binary library.
-gem 'bcrypt', '~> 3.1.7', require: false
+gem 'bcrypt', '~> 3.1.10', require: false
# This needs to be with require false to avoid
# it being automatically loaded by sprockets
@@ -30,7 +34,7 @@ gem 'uglifier', '>= 1.3.0', require: false
group :doc do
gem 'sdoc', '~> 0.4.0'
- gem 'redcarpet', '~> 3.2.2', platforms: :ruby
+ gem 'redcarpet', '~> 3.2.3', platforms: :ruby
gem 'w3c_validators'
gem 'kindlerb', '0.1.1'
end
@@ -45,8 +49,8 @@ group :job do
gem 'sidekiq', require: false
gem 'sucker_punch', require: false
gem 'delayed_job', require: false
- gem 'queue_classic', require: false, platforms: :ruby
- gem 'sneakers', '0.1.1.pre', require: false
+ gem 'queue_classic', github: "QueueClassic/queue_classic", require: false, platforms: :ruby
+ gem 'sneakers', require: false
gem 'que', require: false
gem 'backburner', require: false
gem 'qu-rails', github: "bkeepers/qu", branch: "master", require: false
@@ -65,7 +69,7 @@ group :test do
platforms :mri do
gem 'stackprof'
- # gem 'byebug'
+ gem 'byebug'
end
gem 'benchmark-ips'
diff --git a/Gemfile.lock b/Gemfile.lock
index d054d1e222..aa0f5f7b2b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,4 +1,11 @@
GIT
+ remote: git://github.com/QueueClassic/queue_classic.git
+ revision: d144db29f1436e9e8b3c7a1a1ecd4442316a9ecd
+ specs:
+ queue_classic (3.2.0.alpha)
+ pg (>= 0.17, < 0.19)
+
+GIT
remote: git://github.com/bkeepers/qu.git
revision: d098e2657c92e89a6413bebd9c033930759c061f
branch: master
@@ -13,28 +20,45 @@ GIT
GIT
remote: git://github.com/mikel/mail.git
- revision: b159e0a542962fdd5e292a48cfffa560d7cf412e
+ revision: 64ef1a12efcdda53fd63e1456c2c564044bf82ce
specs:
mail (2.6.3.edge)
mime-types (>= 1.16, < 3)
GIT
remote: git://github.com/rails/arel.git
- revision: aac9da257f291ad8d2d4f914528881c240848bb2
+ revision: d5432b4616ff43fbb14540d351eed351e21bb20e
branch: master
specs:
arel (7.0.0.alpha)
GIT
+ remote: git://github.com/rails/globalid.git
+ revision: 8178ff2dc898a8f49dd71f6eb46f2a09712462de
+ specs:
+ globalid (0.3.6)
+ activesupport (>= 4.1.0)
+
+GIT
remote: git://github.com/rails/jquery-rails.git
- revision: 272abdd319bb3381b23182b928b25320590096b0
+ revision: 38053f45402f1ccc4cce5dd8c7005ec707376db3
branch: master
specs:
- jquery-rails (4.0.3)
+ jquery-rails (4.0.4)
rails-dom-testing (~> 1.0)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
+GIT
+ remote: git://github.com/rails/sprockets-rails.git
+ revision: ad4a43bd1bb19c86a8bf94a2ad5e477686161490
+ branch: master
+ specs:
+ sprockets-rails (3.0.0)
+ actionpack (>= 4.0)
+ activesupport (>= 4.0)
+ sprockets (>= 3.0.0)
+
PATH
remote: .
specs:
@@ -68,6 +92,7 @@ PATH
activesupport (= 5.0.0.alpha)
arel (= 7.0.0.alpha)
activesupport (5.0.0.alpha)
+ concurrent-ruby (~> 0.9.0)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
@@ -95,65 +120,68 @@ GEM
remote: https://rubygems.org/
specs:
amq-protocol (1.9.2)
- backburner (0.4.6)
- beaneater (~> 0.3.1)
- dante (~> 0.1.5)
+ backburner (1.0.0)
+ beaneater (~> 1.0)
+ dante (> 0.1.5)
bcrypt (3.1.10)
- beaneater (0.3.3)
- benchmark-ips (2.1.1)
+ bcrypt (3.1.10-x64-mingw32)
+ bcrypt (3.1.10-x86-mingw32)
+ beaneater (1.0.0)
+ benchmark-ips (2.3.0)
builder (3.2.2)
- bunny (1.1.9)
+ bunny (1.7.0)
amq-protocol (>= 1.9.2)
- celluloid (0.16.0)
+ byebug (5.0.0)
+ columnize (= 0.9.0)
+ celluloid (0.16.1)
timers (~> 4.0.0)
coffee-rails (4.1.0)
coffee-script (>= 2.2.0)
railties (>= 4.0.0, < 5.0)
- coffee-script (2.3.0)
+ coffee-script (2.4.1)
coffee-script-source
execjs
- coffee-script-source (1.9.0)
- connection_pool (2.1.1)
- dalli (2.7.2)
- dante (0.1.5)
+ coffee-script-source (1.9.1.1)
+ columnize (0.9.0)
+ concurrent-ruby (0.9.1)
+ connection_pool (2.2.0)
+ dalli (2.7.4)
+ dante (0.2.0)
delayed_job (4.0.6)
activesupport (>= 3.0, < 5.0)
delayed_job_active_record (4.0.3)
activerecord (>= 3.0, < 5.0)
delayed_job (>= 3.0, < 4.1)
erubis (2.7.0)
- execjs (2.3.0)
- globalid (0.3.3)
- activesupport (>= 4.1.0)
+ execjs (2.5.2)
hitimes (1.2.2)
+ hitimes (1.2.2-x86-mingw32)
i18n (0.7.0)
- json (1.8.2)
+ json (1.8.3)
kindlerb (0.1.1)
mustache
nokogiri
- loofah (2.0.1)
+ loofah (2.0.2)
nokogiri (>= 1.5.9)
metaclass (0.0.4)
method_source (0.8.2)
- mime-types (2.4.3)
+ mime-types (2.6.1)
mini_portile (0.6.2)
minitest (5.3.3)
mocha (0.14.0)
metaclass (~> 0.0.1)
mono_logger (1.1.0)
- multi_json (1.11.0)
- mustache (1.0.0)
+ multi_json (1.11.2)
+ mustache (1.0.2)
mysql (2.9.1)
- mysql2 (0.3.18)
+ mysql2 (0.3.19)
nokogiri (1.6.6.2)
mini_portile (~> 0.6.0)
- pg (0.18.1)
+ pg (0.18.2)
psych (2.0.13)
- que (0.9.2)
- queue_classic (3.1.0)
- pg (>= 0.17, < 0.19)
+ que (0.10.0)
racc (1.4.12)
- rack (1.6.0)
+ rack (1.6.4)
rack-cache (1.2)
rack (>= 0.4)
rack-protection (1.5.3)
@@ -162,7 +190,7 @@ GEM
rack (>= 1.0)
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
- rails-dom-testing (1.0.5)
+ rails-dom-testing (1.0.6)
activesupport (>= 4.2.0.beta, < 5.0)
nokogiri (~> 1.6.0)
rails-deprecated_sanitizer (>= 1.0.1)
@@ -170,9 +198,9 @@ GEM
loofah (~> 2.0)
rake (10.4.2)
rdoc (4.2.0)
- redcarpet (3.2.2)
+ redcarpet (3.2.3)
redis (3.2.1)
- redis-namespace (1.5.1)
+ redis-namespace (1.5.2)
redis (~> 3.0, >= 3.0.4)
resque (1.25.2)
mono_logger (~> 1.0)
@@ -185,51 +213,46 @@ GEM
redis (~> 3.0)
resque (~> 1.25)
rufus-scheduler (~> 3.0)
- rufus-scheduler (3.0.9)
- tzinfo
+ rufus-scheduler (3.1.3)
sdoc (0.4.1)
json (~> 1.7, >= 1.7.7)
rdoc (~> 4.0)
- sequel (4.19.0)
+ sequel (4.25.0)
serverengine (1.5.10)
sigdump (~> 0.2.2)
- sidekiq (3.3.2)
- celluloid (>= 0.16.0)
- connection_pool (>= 2.1.1)
- json
- redis (>= 3.0.6)
- redis-namespace (>= 1.3.1)
- sigdump (0.2.2)
- sinatra (1.4.5)
+ sidekiq (3.4.2)
+ celluloid (~> 0.16.0)
+ connection_pool (~> 2.2, >= 2.2.0)
+ json (~> 1.0)
+ redis (~> 3.2, >= 3.2.1)
+ redis-namespace (~> 1.5, >= 1.5.2)
+ sigdump (0.2.3)
+ sinatra (1.4.6)
rack (~> 1.4)
rack-protection (~> 1.4)
- tilt (~> 1.3, >= 1.3.4)
- sneakers (0.1.1.pre)
- bunny (~> 1.1.3)
- serverengine
+ tilt (>= 1.3, < 3)
+ sneakers (1.0.4)
+ bunny (~> 1.7.0)
+ serverengine (~> 1.5.5)
thor
- thread
- sprockets (3.0.0.rc.1)
+ thread (~> 0.1.7)
+ sprockets (3.0.3)
rack (~> 1.0)
- sprockets-rails (2.2.4)
- actionpack (>= 3.0)
- activesupport (>= 3.0)
- sprockets (>= 2.8, < 4.0)
sqlite3 (1.3.10)
stackprof (0.2.7)
- sucker_punch (1.3.2)
+ sucker_punch (1.5.0)
celluloid (~> 0.16.0)
thor (0.19.1)
- thread (0.1.5)
- thread_safe (0.3.4)
- tilt (1.4.1)
+ thread (0.1.7)
+ thread_safe (0.3.5)
+ tilt (2.0.1)
timers (4.0.1)
hitimes
turbolinks (2.5.3)
coffee-rails
tzinfo (1.2.2)
thread_safe (~> 0.1)
- uglifier (2.7.0)
+ uglifier (2.7.1)
execjs (>= 0.3.0)
json (>= 1.8.0)
vegas (0.1.11)
@@ -240,6 +263,8 @@ GEM
PLATFORMS
ruby
+ x64-mingw32
+ x86-mingw32
DEPENDENCIES
activerecord-jdbcmysql-adapter (>= 1.3.0)
@@ -247,12 +272,14 @@ DEPENDENCIES
activerecord-jdbcsqlite3-adapter (>= 1.3.0)
arel!
backburner
- bcrypt (~> 3.1.7)
+ bcrypt (~> 3.1.10)
benchmark-ips
+ byebug
coffee-rails (~> 4.1.0)
dalli (>= 2.2.1)
delayed_job
delayed_job_active_record
+ globalid!
jquery-rails!
json
kindlerb (= 0.1.1)
@@ -267,22 +294,26 @@ DEPENDENCIES
qu-rails!
qu-redis
que
- queue_classic
+ queue_classic!
racc (>= 1.4.6)
rack-cache (~> 1.2)
rails!
rake (>= 10.3)
- redcarpet (~> 3.2.2)
+ redcarpet (~> 3.2.3)
resque
resque-scheduler
sdoc (~> 0.4.0)
sequel
sidekiq
- sneakers (= 0.1.1.pre)
+ sneakers
sprockets (~> 3.0.0.rc.1)
+ sprockets-rails!
sqlite3 (~> 1.3.6)
stackprof
sucker_punch
turbolinks
uglifier (>= 1.3.0)
w3c_validators
+
+BUNDLED WITH
+ 1.10.5
diff --git a/RELEASING_RAILS.rdoc b/RELEASING_RAILS.rdoc
index 0e09234b82..a020b958e1 100644
--- a/RELEASING_RAILS.rdoc
+++ b/RELEASING_RAILS.rdoc
@@ -96,7 +96,7 @@ Run `rake install` to generate the gems and install them locally. Then try
generating a new app and ensure that nothing explodes.
This will stop you from looking silly when you push an RC to rubygems.org and
-then realise it is broken.
+then realize it is broken.
=== Release the gem.
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index 86ecb3ee88..7c1b0b215a 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,3 +1,34 @@
+* Add `config.action_mailer.deliver_later_queue_name` configuration to set the
+ mailer queue name.
+
+ *Chris McGrath*
+
+* `assert_emails` in block form use the given number as expected value.
+ This makes the error message much easier to understand.
+
+ *Yuji Yaginuma*
+
+* Add support for inline images in mailer previews by using an interceptor
+ class to convert cid: urls in image src attributes to data urls.
+
+ *Andrew White*
+
+* Mailer preview now uses `url_for` to fix links to emails for apps running on
+ a subdirectory.
+
+ *Remo Mueller*
+
+* Mailer previews no longer crash when the `mail` method wasn't called
+ (`NullMail`).
+
+ Fixes #19849.
+
+ *Yves Senn*
+
+* Make sure labels and values line up in mailer previews.
+
+ *Yves Senn*
+
* Add `assert_enqueued_emails` and `assert_no_enqueued_emails`.
Example:
diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc
index a4e660d621..e5c2ed8c77 100644
--- a/actionmailer/README.rdoc
+++ b/actionmailer/README.rdoc
@@ -146,7 +146,7 @@ The Base class has the full list of configuration options. Here's an example:
The latest version of Action Mailer can be installed with RubyGems:
- % [sudo] gem install actionmailer
+ % gem install actionmailer
Source code can be downloaded as part of the Rails project on GitHub
diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile
index 8f4de8fafa..7197ea5e27 100644
--- a/actionmailer/Rakefile
+++ b/actionmailer/Rakefile
@@ -1,5 +1,4 @@
require 'rake/testtask'
-require 'rubygems/package_task'
desc "Default Task"
task default: [ :test ]
@@ -20,16 +19,3 @@ namespace :test do
end or raise "Failures"
end
end
-
-spec = eval(File.read('actionmailer.gemspec'))
-
-Gem::PackageTask.new(spec) do |p|
- p.gem_spec = spec
-end
-
-desc "Release to rubygems"
-task release: :package do
- require 'rake/gemcutter'
- Rake::Gemcutter::Tasks.new(spec).define
- Rake::Task['gem:push'].invoke
-end
diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec
index 41913899ff..782b208ef4 100644
--- a/actionmailer/actionmailer.gemspec
+++ b/actionmailer/actionmailer.gemspec
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
s.summary = 'Email composition, delivery, and receiving framework (part of Rails).'
s.description = 'Email on Rails. Compose, deliver, receive, and test emails using the familiar controller/view pattern. First-class support for multipart email and attachments.'
- s.required_ruby_version = '>= 2.2.1'
+ s.required_ruby_version = '>= 2.2.2'
s.license = 'MIT'
diff --git a/actionmailer/bin/test b/actionmailer/bin/test
new file mode 100755
index 0000000000..404cabba51
--- /dev/null
+++ b/actionmailer/bin/test
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+COMPONENT_ROOT = File.expand_path("../../", __FILE__)
+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 17d8dcc208..291a8c1e34 100644
--- a/actionmailer/lib/action_mailer.rb
+++ b/actionmailer/lib/action_mailer.rb
@@ -40,6 +40,7 @@ module ActionMailer
autoload :Base
autoload :DeliveryMethods
+ autoload :InlinePreviewInterceptor
autoload :MailHelper
autoload :Preview
autoload :Previews, 'action_mailer/preview'
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index c7f09ed192..6f49e130d8 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -134,25 +134,28 @@ module ActionMailer
#
# = Sending mail
#
- # Once a mailer action and template are defined, you can deliver your message or create it and save it
- # for delivery later:
+ # Once a mailer action and template are defined, you can deliver your message or defer its creation and
+ # delivery for later:
#
# NotifierMailer.welcome(User.first).deliver_now # sends the email
# mail = NotifierMailer.welcome(User.first) # => an ActionMailer::MessageDelivery object
- # mail.deliver_now # sends the email
+ # mail.deliver_now # generates and sends the email now
#
- # The <tt>ActionMailer::MessageDelivery</tt> class is a wrapper around a <tt>Mail::Message</tt> object. If
- # you want direct access to the <tt>Mail::Message</tt> object you can call the <tt>message</tt> method on
- # the <tt>ActionMailer::MessageDelivery</tt> object.
+ # The <tt>ActionMailer::MessageDelivery</tt> class is a wrapper around a delegate that will call
+ # your method to generate the mail. If you want direct access to delegator, or <tt>Mail::Message</tt>,
+ # you can call the <tt>message</tt> method on the <tt>ActionMailer::MessageDelivery</tt> object.
#
# NotifierMailer.welcome(User.first).message # => a Mail::Message object
#
- # Action Mailer is nicely integrated with Active Job so you can send emails in the background (example: outside
- # of the request-response cycle, so the user doesn't have to wait on it):
+ # Action Mailer is nicely integrated with Active Job so you can generate and send emails in the background
+ # (example: outside of the request-response cycle, so the user doesn't have to wait on it):
#
# NotifierMailer.welcome(User.first).deliver_later # enqueue the email sending to Active Job
#
+ # Note that <tt>deliver_later</tt> will execute your method from the background job.
+ #
# You never instantiate your mailer class. Rather, you just call the method you defined on the class itself.
+ # All instance methods are expected to return a message object to be sent.
#
# = Multipart Emails
#
@@ -254,8 +257,8 @@ module ActionMailer
# <tt>ActionMailer::Base</tt> sets the following:
#
# * <tt>mime_version: "1.0"</tt>
- # * <tt>charset: "UTF-8",</tt>
- # * <tt>content_type: "text/plain",</tt>
+ # * <tt>charset: "UTF-8"</tt>
+ # * <tt>content_type: "text/plain"</tt>
# * <tt>parts_order: [ "text/plain", "text/enriched", "text/html" ]</tt>
#
# <tt>parts_order</tt> and <tt>charset</tt> are not actually valid <tt>Mail::Message</tt> header fields,
@@ -283,7 +286,7 @@ module ActionMailer
# end
#
# Note that the proc is evaluated right at the start of the mail message generation, so if you
- # set something in the defaults using a proc, and then set the same thing inside of your
+ # set something in the default using a proc, and then set the same thing inside of your
# mailer method, it will get over written by the mailer method.
#
# It is also possible to set these default options that will be used in all mailers through
@@ -316,8 +319,9 @@ module ActionMailer
# callbacks in the same manner that you would use callbacks in classes that
# inherit from <tt>ActionController::Base</tt>.
#
- # Note that unless you have a specific reason to do so, you should prefer using before_action
- # rather than after_action in your Action Mailer classes so that headers are parsed properly.
+ # Note that unless you have a specific reason to do so, you should prefer
+ # using <tt>before_action</tt> rather than <tt>after_action</tt> in your
+ # Action Mailer classes so that headers are parsed properly.
#
# = Previewing emails
#
@@ -376,8 +380,8 @@ module ActionMailer
# * <tt>:password</tt> - If your mail server requires authentication, set the password in this setting.
# * <tt>:authentication</tt> - If your mail server requires authentication, you need to specify the
# authentication type here.
- # This is a symbol and one of <tt>:plain</tt> (will send the password in the clear), <tt>:login</tt> (will
- # send password Base64 encoded) or <tt>:cram_md5</tt> (combines a Challenge/Response mechanism to exchange
+ # This is a symbol and one of <tt>:plain</tt> (will send the password Base64 encoded), <tt>:login</tt> (will
+ # send the password Base64 encoded) or <tt>:cram_md5</tt> (combines a Challenge/Response mechanism to exchange
# information and a cryptographic Message Digest 5 algorithm to hash important information)
# * <tt>:enable_starttls_auto</tt> - Detects if STARTTLS is enabled in your SMTP server and starts
# to use it. Defaults to <tt>true</tt>.
@@ -409,6 +413,8 @@ module ActionMailer
#
# * <tt>deliveries</tt> - Keeps an array of all the emails sent out through the Action Mailer with
# <tt>delivery_method :test</tt>. Most useful for unit and functional testing.
+ #
+ # * <tt>deliver_later_queue_name</tt> - The name of the queue used with <tt>deliver_later</tt>
class Base < AbstractController::Base
include DeliveryMethods
include Previews
@@ -593,6 +599,7 @@ module ActionMailer
class NullMail #:nodoc:
def body; '' end
+ def header; {} end
def respond_to?(string, include_all=false)
true
@@ -813,7 +820,7 @@ module ActionMailer
# Set configure delivery behavior
wrap_delivery_behavior!(headers.delete(:delivery_method), headers.delete(:delivery_method_options))
- # Assign all headers except parts_order, content_type and body
+ # Assign all headers except parts_order, content_type, body, template_name, and template_path
assignable = headers.except(:parts_order, :content_type, :body, :template_name, :template_path)
assignable.each { |k, v| m[k] = v }
diff --git a/actionmailer/lib/action_mailer/delivery_job.rb b/actionmailer/lib/action_mailer/delivery_job.rb
index e864ab7a4d..52772af2d3 100644
--- a/actionmailer/lib/action_mailer/delivery_job.rb
+++ b/actionmailer/lib/action_mailer/delivery_job.rb
@@ -4,7 +4,7 @@ module ActionMailer
# The <tt>ActionMailer::DeliveryJob</tt> class is used when you
# want to send emails outside of the request-response cycle.
class DeliveryJob < ActiveJob::Base # :nodoc:
- queue_as :mailers
+ queue_as { ActionMailer::Base.deliver_later_queue_name }
def perform(mailer, mail_method, delivery_method, *args) #:nodoc:
mailer.constantize.public_send(mail_method, *args).send(delivery_method)
diff --git a/actionmailer/lib/action_mailer/delivery_methods.rb b/actionmailer/lib/action_mailer/delivery_methods.rb
index aedcd81e52..4758b55a2a 100644
--- a/actionmailer/lib/action_mailer/delivery_methods.rb
+++ b/actionmailer/lib/action_mailer/delivery_methods.rb
@@ -16,6 +16,9 @@ module ActionMailer
cattr_accessor :perform_deliveries
self.perform_deliveries = true
+ cattr_accessor :deliver_later_queue_name
+ self.deliver_later_queue_name = :mailers
+
self.delivery_methods = {}.freeze
self.delivery_method = :smtp
diff --git a/actionmailer/lib/action_mailer/inline_preview_interceptor.rb b/actionmailer/lib/action_mailer/inline_preview_interceptor.rb
new file mode 100644
index 0000000000..6d02b39225
--- /dev/null
+++ b/actionmailer/lib/action_mailer/inline_preview_interceptor.rb
@@ -0,0 +1,61 @@
+require 'base64'
+
+module ActionMailer
+ # Implements a mailer preview interceptor that converts image tag src attributes
+ # that use inline cid: style urls to data: style urls so that they are visible
+ # when previewing a HTML email in a web browser.
+ #
+ # This interceptor is enabled by default. To disable it, delete it from the
+ # <tt>ActionMailer::Base.preview_interceptors</tt> array:
+ #
+ # ActionMailer::Base.preview_interceptors.delete(ActionMailer::InlinePreviewInterceptor)
+ #
+ class InlinePreviewInterceptor
+ PATTERN = /src=(?:"cid:[^"]+"|'cid:[^']+')/i
+
+ include Base64
+
+ def self.previewing_email(message) #:nodoc:
+ new(message).transform!
+ end
+
+ def initialize(message) #:nodoc:
+ @message = message
+ end
+
+ def transform! #:nodoc:
+ return message if html_part.blank?
+
+ html_source.gsub!(PATTERN) do |match|
+ if part = find_part(match[9..-2])
+ %[src="#{data_url(part)}"]
+ else
+ match
+ end
+ end
+
+ message
+ end
+
+ private
+ def message
+ @message
+ end
+
+ def html_part
+ @html_part ||= message.html_part
+ end
+
+ def html_source
+ html_part.body.raw_source
+ end
+
+ def data_url(part)
+ "data:#{part.mime_type};base64,#{strict_encode64(part.body.raw_source)}"
+ end
+
+ def find_part(cid)
+ message.all_parts.find{ |p| p.attachment? && p.cid == cid }
+ end
+ end
+end
diff --git a/actionmailer/lib/action_mailer/log_subscriber.rb b/actionmailer/lib/action_mailer/log_subscriber.rb
index 5b57c75ec3..c2f671fdac 100644
--- a/actionmailer/lib/action_mailer/log_subscriber.rb
+++ b/actionmailer/lib/action_mailer/log_subscriber.rb
@@ -2,7 +2,7 @@ require 'active_support/log_subscriber'
module ActionMailer
# Implements the ActiveSupport::LogSubscriber for logging notifications when
- # email is delivered and received.
+ # email is delivered or received.
class LogSubscriber < ActiveSupport::LogSubscriber
# An email was delivered.
def deliver(event)
diff --git a/actionmailer/lib/action_mailer/preview.rb b/actionmailer/lib/action_mailer/preview.rb
index 44cf6665ba..25ad7ee721 100644
--- a/actionmailer/lib/action_mailer/preview.rb
+++ b/actionmailer/lib/action_mailer/preview.rb
@@ -21,7 +21,7 @@ module ActionMailer
# :nodoc:
mattr_accessor :preview_interceptors, instance_writer: false
- self.preview_interceptors = []
+ self.preview_interceptors = [ActionMailer::InlinePreviewInterceptor]
end
module ClassMethods
diff --git a/actionmailer/lib/action_mailer/test_helper.rb b/actionmailer/lib/action_mailer/test_helper.rb
index 524e6e3af1..4d03a616d2 100644
--- a/actionmailer/lib/action_mailer/test_helper.rb
+++ b/actionmailer/lib/action_mailer/test_helper.rb
@@ -34,7 +34,7 @@ module ActionMailer
original_count = ActionMailer::Base.deliveries.size
yield
new_count = ActionMailer::Base.deliveries.size
- assert_equal original_count + number, new_count, "#{number} emails expected, but #{new_count - original_count} were sent"
+ assert_equal number, new_count - original_count, "#{number} emails expected, but #{new_count - original_count} were sent"
else
assert_equal number, ActionMailer::Base.deliveries.size
end
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index ae91903c95..706249a2f6 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -11,7 +11,6 @@ end
require 'active_support/testing/autorun'
require 'action_mailer'
require 'action_mailer/test_case'
-require 'mail'
# Emulate AV railtie
require 'action_view'
diff --git a/actionmailer/test/delivery_methods_test.rb b/actionmailer/test/delivery_methods_test.rb
index 2e4e019bf7..78507ce7dc 100644
--- a/actionmailer/test/delivery_methods_test.rb
+++ b/actionmailer/test/delivery_methods_test.rb
@@ -1,5 +1,4 @@
require 'abstract_unit'
-require 'mail'
class MyCustomDelivery
end
diff --git a/actionmailer/test/i18n_with_controller_test.rb b/actionmailer/test/i18n_with_controller_test.rb
index 010e44d045..5ffde06a80 100644
--- a/actionmailer/test/i18n_with_controller_test.rb
+++ b/actionmailer/test/i18n_with_controller_test.rb
@@ -1,6 +1,7 @@
require 'abstract_unit'
require 'action_view'
require 'action_controller'
+require 'active_support/deprecation'
class I18nTestMailer < ActionMailer::Base
configure do |c|
@@ -54,7 +55,9 @@ class ActionMailerI18nWithControllerTest < ActionDispatch::IntegrationTest
def test_send_mail
Mail::SMTP.any_instance.expects(:deliver!)
with_translation 'de', email_subject: '[Anmeldung] Willkommen' do
- get '/test/send_mail'
+ ActiveSupport::Deprecation.silence do
+ get '/test/send_mail'
+ end
assert_equal "Mail sent - Subject: [Anmeldung] Willkommen", @response.body
end
end
diff --git a/actionmailer/test/mail_helper_test.rb b/actionmailer/test/mail_helper_test.rb
index 24ccaab8df..ff6b25b0c7 100644
--- a/actionmailer/test/mail_helper_test.rb
+++ b/actionmailer/test/mail_helper_test.rb
@@ -59,6 +59,12 @@ The second
end
end
+ def use_cache
+ mail_with_defaults do |format|
+ format.html { render(inline: "<% cache(:foo) do %>Greetings from a cache helper block<% end %>") }
+ end
+ end
+
protected
def mail_with_defaults(&block)
@@ -107,5 +113,11 @@ class MailerHelperTest < ActionMailer::TestCase
TEXT
assert_equal expected.gsub("\n", "\r\n"), mail.body.encoded
end
-end
+ def test_use_cache
+ assert_nothing_raised do
+ mail = HelperMailer.use_cache
+ assert_equal "Greetings from a cache helper block", mail.body.encoded
+ end
+ end
+end
diff --git a/actionmailer/test/message_delivery_test.rb b/actionmailer/test/message_delivery_test.rb
index e4dd269494..862ce26187 100644
--- a/actionmailer/test/message_delivery_test.rb
+++ b/actionmailer/test/message_delivery_test.rb
@@ -2,7 +2,6 @@ require 'abstract_unit'
require 'active_job'
require 'minitest/mock'
require 'mailers/delayed_mailer'
-require 'active_support/core_ext/numeric/time'
class MessageDeliveryTest < ActiveSupport::TestCase
include ActiveJob::TestHelper
@@ -10,6 +9,8 @@ class MessageDeliveryTest < ActiveSupport::TestCase
setup do
@previous_logger = ActiveJob::Base.logger
@previous_delivery_method = ActionMailer::Base.delivery_method
+ @previous_deliver_later_queue_name = ActionMailer::Base.deliver_later_queue_name
+ ActionMailer::Base.deliver_later_queue_name = :test_queue
ActionMailer::Base.delivery_method = :test
ActiveJob::Base.logger = Logger.new(nil)
@mail = DelayedMailer.test_message(1, 2, 3)
@@ -21,6 +22,7 @@ class MessageDeliveryTest < ActiveSupport::TestCase
teardown do
ActiveJob::Base.logger = @previous_logger
ActionMailer::Base.delivery_method = @previous_delivery_method
+ ActionMailer::Base.deliver_later_queue_name = @previous_deliver_later_queue_name
end
test 'should have a message' do
@@ -81,4 +83,15 @@ class MessageDeliveryTest < ActiveSupport::TestCase
end
end
+ test 'should enqueue the job on the correct queue' do
+ assert_performed_with(job: ActionMailer::DeliveryJob, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3], queue: "test_queue") do
+ @mail.deliver_later
+ end
+ end
+
+ test 'can override the queue when enqueuing mail' do
+ assert_performed_with(job: ActionMailer::DeliveryJob, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3], queue: "another_queue") do
+ @mail.deliver_later(queue: :another_queue)
+ end
+ end
end
diff --git a/actionmailer/test/test_helper_test.rb b/actionmailer/test/test_helper_test.rb
index 089933e245..0a4bc75d3e 100644
--- a/actionmailer/test/test_helper_test.rb
+++ b/actionmailer/test/test_helper_test.rb
@@ -112,6 +112,17 @@ class TestHelperMailerTest < ActionMailer::TestCase
assert_match(/1 .* but 2/, error.message)
end
+ def test_assert_emails_message
+ TestHelperMailer.test.deliver_now
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_emails 2 do
+ TestHelperMailer.test.deliver_now
+ end
+ end
+ assert_match "Expected: 2", error.message
+ assert_match "Actual: 1", error.message
+ end
+
def test_assert_no_emails_failure
error = assert_raise ActiveSupport::TestCase::Assertion do
assert_no_emails do
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 4ab0857a66..8eea4ccd41 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,105 @@
+* Using strings or symbols for middleware class names is deprecated. Convert
+ things like this:
+
+ middleware.use "Foo::Bar"
+
+ to this:
+
+ middleware.use Foo::Bar
+
+* ActionController::TestSession now accepts a default value as well as
+ a block for generating a default value based off the key provided.
+
+ This fixes calls to session#fetch in ApplicationController instances that
+ take more two arguments or a block from raising `ArgumentError: wrong
+ number of arguments (2 for 1)` when performing controller tests.
+
+ *Matthew Gerrior*
+
+* Fix `ActionController::Parameters#fetch` overwriting `KeyError` returned by
+ default block.
+
+ *Jonas Schuber Erlandsson*, *Roque Pinel*
+
+* `ActionController::Parameters` no longer inherits from
+ `HashWithIndifferentAccess`
+
+ Inheriting from `HashWithIndifferentAccess` allowed users to call any
+ enumerable methods on `Parameters` object, resulting in a risk of losing the
+ `permitted?` status or even getting back a pure `Hash` object instead of
+ a `Parameters` object with proper sanitization.
+
+ By not inheriting from `HashWithIndifferentAccess`, we are able to make
+ sure that all methods that are defined in `Parameters` object will return
+ a proper `Parameters` object with a correct `permitted?` flag.
+
+ *Prem Sichanugrist*
+
+* Replaced `ActiveSupport::Concurrency::Latch` with `Concurrent::CountDownLatch`
+ from the concurrent-ruby gem.
+
+ *Jerry D'Antonio*
+
+* Add ability to filter parameters based on parent keys.
+
+ # matches {credit_card: {code: "xxxx"}}
+ # doesn't match {file: { code: "xxxx"}}
+ config.filter_parameters += [ "credit_card.code" ]
+
+ See #13897.
+
+ *Guillaume Malette*
+
+* Deprecate passing first parameter as `Hash` and default status code for `head` method.
+
+ *Mehmet Emin İNAÇ*
+
+* Adds`Rack::Utils::ParameterTypeError` and `Rack::Utils::InvalidParameterError`
+ to the rescue_responses hash in `ExceptionWrapper` (Rack recommends
+ integrators serve 400s for both of these).
+
+ *Grey Baker*
+
+* Add support for API only apps.
+ ActionController::API is added as a replacement of
+ ActionController::Base for this kind of applications.
+
+ *Santiago Pastorino & Jorge Bejar*
+
+* Remove `assigns` and `assert_template`. Both methods have been extracted
+ into a gem at https://github.com/rails/rails-controller-testing.
+
+ See #18950.
+
+ *Alan Guo Xiang Tan*
+
+* `FileHandler` and `Static` middleware initializers accept `index` argument
+ to configure the directory index file name. Defaults to `index` (as in
+ `index.html`).
+
+ See #20017.
+
+ *Eliot Sykes*
+
+* Deprecate `:nothing` option for `render` method.
+
+ *Mehmet Emin İNAÇ*
+
+* Fix `rake routes` not showing the right format when
+ nesting multiple routes.
+
+ See #18373.
+
+ *Ravil Bayramgalin*
+
+* Add ability to override default form builder for a controller.
+
+ class AdminController < ApplicationController
+ default_form_builder AdminFormBuilder
+ end
+
+ *Kevin McPhillips*
+
* For actions with no corresponding templates, render `head :no_content`
instead of raising an error. This allows for slimmer API controller
methods that simply work, without needing further instructions.
@@ -27,14 +129,24 @@
*Kohei Suzuki*
-* Fix handling of empty X_FORWARDED_HOST header in raw_host_with_port
+* Fix handling of empty `X_FORWARDED_HOST` header in `raw_host_with_port`.
- Previously, an empty X_FORWARDED_HOST header would cause
- Actiondispatch::Http:URL.raw_host_with_port to return nil, causing
- Actiondispatch::Http:URL.host to raise a NoMethodError.
+ Previously, an empty `X_FORWARDED_HOST` header would cause
+ `Actiondispatch::Http:URL.raw_host_with_port` to return `nil`, causing
+ `Actiondispatch::Http:URL.host` to raise a `NoMethodError`.
*Adam Forsyth*
+* Allow `Bearer` as token-keyword in `Authorization-Header`.
+
+ Aditionally to `Token`, the keyword `Bearer` is acceptable as a keyword
+ for the auth-token. The `Bearer` keyword is described in the original
+ OAuth RFC and used in libraries like Angular-JWT.
+
+ See #19094.
+
+ *Peter Schröder*
+
* Drop request class from RouteSet constructor.
If you would like to use a custom request class, please subclass and implement
@@ -94,7 +206,8 @@
*arthurnn*
* `ActionController#translate` supports symbols as shortcuts.
- When shortcut is given it also lookups without action name.
+ When a shortcut is given it also performs the lookup without the action
+ name.
*Max Melentiev*
@@ -162,13 +275,13 @@
* Preserve default url options when generating URLs.
- Fixes an issue that would cause default_url_options to be lost when
+ Fixes an issue that would cause `default_url_options` to be lost when
generating URLs with fewer positional arguments than parameters in the
route definition.
*Tekin Suleyman*
-* Deprecate *_via_redirect integration test methods.
+* Deprecate `*_via_redirect` integration test methods.
Use `follow_redirect!` manually after the request call for the same behavior.
@@ -191,11 +304,11 @@
*Jonas Baumann*
-* Deprecate all *_filter callbacks in favor of *_action callbacks.
+* Deprecate all `*_filter` callbacks in favor of `*_action` callbacks.
*Rafael Mendonça França*
-* Allow you to pass `prepend: false` to protect_from_forgery to have the
+* Allow you to pass `prepend: false` to `protect_from_forgery` to have the
verification callback appended instead of prepended to the chain.
This allows you to let the verification step depend on prior callbacks.
@@ -266,7 +379,7 @@
* Ensure `append_info_to_payload` is called even if an exception is raised.
- Fixes an issue where when an exception is raised in the request the additonal
+ Fixes an issue where when an exception is raised in the request the additional
payload data is not available.
See:
@@ -297,7 +410,7 @@
* Stop converting empty arrays in `params` to `nil`.
- This behaviour was introduced in response to CVE-2012-2660, CVE-2012-2694
+ This behavior was introduced in response to CVE-2012-2660, CVE-2012-2694
and CVE-2013-0155
ActiveRecord now issues a safe query when passing an empty array into
diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc
index 02a24a7412..44c980b070 100644
--- a/actionpack/README.rdoc
+++ b/actionpack/README.rdoc
@@ -28,7 +28,7 @@ can be used outside of Rails.
The latest version of Action Pack can be installed with RubyGems:
- % [sudo] gem install actionpack
+ % gem install actionpack
Source code can be downloaded as part of the Rails project on GitHub
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index 3bd27f8d64..601263bfac 100644
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -1,5 +1,4 @@
require 'rake/testtask'
-require 'rubygems/package_task'
test_files = Dir.glob('test/**/*_test.rb')
@@ -24,19 +23,6 @@ namespace :test do
end
end
-spec = eval(File.read('actionpack.gemspec'))
-
-Gem::PackageTask.new(spec) do |p|
- p.gem_spec = spec
-end
-
-desc "Release to rubygems"
-task :release => :package do
- require 'rake/gemcutter'
- Rake::Gemcutter::Tasks.new(spec).define
- Rake::Task['gem:push'].invoke
-end
-
task :lines do
load File.expand_path('..', File.dirname(__FILE__)) + '/tools/line_statistics'
files = FileList["lib/**/*.rb"]
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index b6b70a027c..1bba9df969 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
s.summary = 'Web-flow and rendering framework putting the VC in MVC (part of Rails).'
s.description = 'Web apps on Rails. Simple, battle-tested conventions for building and testing MVC web applications. Works with any Rack-compatible server.'
- s.required_ruby_version = '>= 2.2.1'
+ s.required_ruby_version = '>= 2.2.2'
s.license = 'MIT'
diff --git a/actionpack/bin/test b/actionpack/bin/test
new file mode 100755
index 0000000000..404cabba51
--- /dev/null
+++ b/actionpack/bin/test
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+COMPONENT_ROOT = File.expand_path("../../", __FILE__)
+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 c95b9a4097..784092867c 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -88,7 +88,7 @@ module AbstractController
# Returns the full controller name, underscored, without the ending Controller.
#
# class MyApp::MyPostsController < AbstractController::Base
- # end
+ #
# end
#
# MyApp::MyPostsController.controller_path # => "my_app/my_posts"
@@ -96,7 +96,7 @@ module AbstractController
# ==== Returns
# * <tt>String</tt>
def controller_path
- @controller_path ||= name.sub(/Controller$/, '').underscore unless anonymous?
+ @controller_path ||= name.sub(/Controller$/, ''.freeze).underscore unless anonymous?
end
# Refresh the cached action_methods when a new action_method is added.
@@ -148,9 +148,6 @@ module AbstractController
#
# ==== Parameters
# * <tt>action_name</tt> - The name of an action to be tested
- #
- # ==== Returns
- # * <tt>TrueClass</tt>, <tt>FalseClass</tt>
def available_action?(action_name)
_find_action_name(action_name).present?
end
@@ -171,9 +168,6 @@ module AbstractController
# ==== Parameters
# * <tt>name</tt> - The name of an action to be tested
#
- # ==== Returns
- # * <tt>TrueClass</tt>, <tt>FalseClass</tt>
- #
# :api: private
def action_method?(name)
self.class.action_methods.include?(name)
diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb
index 109eff10eb..d84c238a62 100644
--- a/actionpack/lib/abstract_controller/helpers.rb
+++ b/actionpack/lib/abstract_controller/helpers.rb
@@ -181,7 +181,7 @@ module AbstractController
end
def default_helper_module!
- module_name = name.sub(/Controller$/, '')
+ module_name = name.sub(/Controller$/, ''.freeze)
module_path = module_name.underscore
helper module_path
rescue LoadError => e
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index 7667e469d3..89fc4520d3 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -7,14 +7,15 @@ require 'action_controller/metal/strong_parameters'
module ActionController
extend ActiveSupport::Autoload
+ autoload :API
autoload :Base
autoload :Caching
autoload :Metal
autoload :Middleware
autoload :Renderer
+ autoload :FormBuilder
autoload_under "metal" do
- autoload :Compatibility
autoload :ConditionalGet
autoload :Cookies
autoload :DataStreaming
@@ -24,6 +25,7 @@ module ActionController
autoload :Head
autoload :Helpers
autoload :HttpAuthentication
+ autoload :BasicImplicitRender
autoload :ImplicitRender
autoload :Instrumentation
autoload :MimeResponds
diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb
new file mode 100644
index 0000000000..b4594bf302
--- /dev/null
+++ b/actionpack/lib/action_controller/api.rb
@@ -0,0 +1,147 @@
+require 'action_view'
+require 'action_controller'
+require 'action_controller/log_subscriber'
+
+module ActionController
+ # API Controller is a lightweight version of <tt>ActionController::Base</tt>,
+ # created for applications that don't require all functionalities that a complete
+ # \Rails controller provides, allowing you to create controllers with just the
+ # features that you need for API only applications.
+ #
+ # An API Controller is different from a normal controller in the sense that
+ # by default it doesn't include a number of features that are usually required
+ # by browser access only: layouts and templates rendering, cookies, sessions,
+ # flash, assets, and so on. This makes the entire controller stack thinner,
+ # suitable for API applications. It doesn't mean you won't have such
+ # features if you need them: they're all available for you to include in
+ # your application, they're just not part of the default API Controller stack.
+ #
+ # By default, only the ApplicationController in a \Rails application inherits
+ # from <tt>ActionController::API</tt>. All other controllers in turn inherit
+ # from ApplicationController.
+ #
+ # A sample controller could look like this:
+ #
+ # class PostsController < ApplicationController
+ # def index
+ # @posts = Post.all
+ # render json: @posts
+ # end
+ # end
+ #
+ # Request, response and parameters objects all work the exact same way as
+ # <tt>ActionController::Base</tt>.
+ #
+ # == Renders
+ #
+ # The default API Controller stack includes all renderers, which means you
+ # can use <tt>render :json</tt> and brothers freely in your controllers. Keep
+ # in mind that templates are not going to be rendered, so you need to ensure
+ # your controller is calling either <tt>render</tt> or <tt>redirect</tt> in
+ # all actions, otherwise it will return 204 No Content response.
+ #
+ # def show
+ # @post = Post.find(params[:id])
+ # render json: @post
+ # end
+ #
+ # == Redirects
+ #
+ # Redirects are used to move from one action to another. You can use the
+ # <tt>redirect</tt> method in your controllers in the same way as
+ # <tt>ActionController::Base</tt>. For example:
+ #
+ # def create
+ # redirect_to root_url and return if not_authorized?
+ # # do stuff here
+ # end
+ #
+ # == Adding new behavior
+ #
+ # In some scenarios you may want to add back some functionality provided by
+ # <tt>ActionController::Base</tt> that is not present by default in
+ # <tt>ActionController::API</tt>, for instance <tt>MimeResponds</tt>. This
+ # module gives you the <tt>respond_to</tt> method. Adding it is quite simple,
+ # you just need to include the module in a specific controller or in
+ # +ApplicationController+ in case you want it available in your entire
+ # application:
+ #
+ # class ApplicationController < ActionController::API
+ # include ActionController::MimeResponds
+ # end
+ #
+ # class PostsController < ApplicationController
+ # def index
+ # @posts = Post.all
+ #
+ # respond_to do |format|
+ # format.json { render json: @posts }
+ # format.xml { render xml: @posts }
+ # end
+ # end
+ # end
+ #
+ # Quite straightforward. Make sure to check <tt>ActionController::Base</tt>
+ # available modules if you want to include any other functionality that is
+ # not provided by <tt>ActionController::API</tt> out of the box.
+ class API < Metal
+ abstract!
+
+ # Shortcut helper that returns all the ActionController::API modules except
+ # the ones passed as arguments:
+ #
+ # class MyAPIBaseController < ActionController::Metal
+ # ActionController::API.without_modules(:ForceSSL, :UrlFor).each do |left|
+ # include left
+ # end
+ # end
+ #
+ # This gives better control over what you want to exclude and makes it easier
+ # to create an API controller class, instead of listing the modules required
+ # manually.
+ def self.without_modules(*modules)
+ modules = modules.map do |m|
+ m.is_a?(Symbol) ? ActionController.const_get(m) : m
+ end
+
+ MODULES - modules
+ end
+
+ MODULES = [
+ AbstractController::Rendering,
+
+ UrlFor,
+ Redirecting,
+ Rendering,
+ Renderers::All,
+ ConditionalGet,
+ RackDelegation,
+ BasicImplicitRender,
+ StrongParameters,
+
+ ForceSSL,
+ DataStreaming,
+
+ # Before callbacks should also be executed as early as possible, so
+ # also include them at the bottom.
+ AbstractController::Callbacks,
+
+ # Append rescue at the bottom to wrap as much as possible.
+ Rescue,
+
+ # Add instrumentations hooks at the bottom, to ensure they instrument
+ # all the methods properly.
+ Instrumentation,
+
+ # Params wrapper should come before instrumentation so they are
+ # properly showed in logs
+ ParamsWrapper
+ ]
+
+ MODULES.each do |mod|
+ include mod
+ end
+
+ ActiveSupport.run_load_hooks(:action_controller, self)
+ end
+end
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index e6038396f9..55734b9774 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -50,9 +50,9 @@ module ActionController
#
# == Parameters
#
- # All request parameters, whether they come from a GET or POST request, or from the URL, are available through the params method
- # which returns a hash. For example, an action that was performed through <tt>/posts?category=All&limit=5</tt> will include
- # <tt>{ "category" => "All", "limit" => "5" }</tt> in params.
+ # All request parameters, whether they come from a query string in the URL or form data submitted through a POST request are
+ # available through the params method which returns a hash. For example, an action that was performed through
+ # <tt>/posts?category=All&limit=5</tt> will include <tt>{ "category" => "All", "limit" => "5" }</tt> in params.
#
# It's also possible to construct multi-dimensional parameter hashes by specifying keys using brackets, such as:
#
@@ -183,7 +183,7 @@ module ActionController
# Shortcut helper that returns all the modules included in
# ActionController::Base except the ones passed as arguments:
#
- # class MetalController
+ # class MyBaseController < ActionController::Metal
# ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left|
# include left
# end
@@ -221,6 +221,7 @@ module ActionController
Cookies,
Flash,
+ FormBuilder,
RequestForgeryProtection,
ForceSSL,
Streaming,
@@ -251,7 +252,7 @@ module ActionController
# Define some internal variables that should not be propagated to the view.
PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [
- :@_status, :@_headers, :@_params, :@_env, :@_response, :@_request,
+ :@_status, :@_headers, :@_params, :@_response, :@_request,
:@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout ]
def _protected_ivars # :nodoc:
diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb
index de85e0c1a7..a4e4992cfe 100644
--- a/actionpack/lib/action_controller/caching.rb
+++ b/actionpack/lib/action_controller/caching.rb
@@ -8,7 +8,7 @@ module ActionController
#
# You can read more about each approach by clicking the modules below.
#
- # Note: To turn off all caching, set
+ # Note: To turn off all caching provided by Action Controller, set
# config.action_controller.perform_caching = false
#
# == \Caching stores
diff --git a/actionpack/lib/action_controller/form_builder.rb b/actionpack/lib/action_controller/form_builder.rb
new file mode 100644
index 0000000000..f2656ca894
--- /dev/null
+++ b/actionpack/lib/action_controller/form_builder.rb
@@ -0,0 +1,48 @@
+module ActionController
+ # Override the default form builder for all views rendered by this
+ # controller and any of its descendants. Accepts a subclass of
+ # +ActionView::Helpers::FormBuilder+.
+ #
+ # For example, given a form builder:
+ #
+ # class AdminFormBuilder < ActionView::Helpers::FormBuilder
+ # def special_field(name)
+ # end
+ # end
+ #
+ # The controller specifies a form builder as its default:
+ #
+ # class AdminAreaController < ApplicationController
+ # default_form_builder AdminFormBuilder
+ # end
+ #
+ # Then in the view any form using +form_for+ will be an instance of the
+ # specified form builder:
+ #
+ # <%= form_for(@instance) do |builder| %>
+ # <%= builder.special_field(:name) %>
+ # <% end %>
+ module FormBuilder
+ extend ActiveSupport::Concern
+
+ included do
+ class_attribute :_default_form_builder, instance_accessor: false
+ end
+
+ module ClassMethods
+ # Set the form builder to be used as the default for all forms
+ # in the views rendered by this controller and its subclasses.
+ #
+ # ==== Parameters
+ # * <tt>builder</tt> - Default form builder, an instance of +ActionView::Helpers::FormBuilder+
+ def default_form_builder(builder)
+ self._default_form_builder = builder
+ end
+ end
+
+ # Default form builder for the controller
+ def default_form_builder
+ self.class._default_form_builder
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index 87609d8aa7..4c9f14e409 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -25,7 +25,7 @@ module ActionController
status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
end
message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms"
- message << " (#{additions.join(" | ")})" unless additions.blank?
+ message << " (#{additions.join(" | ".freeze)})" unless additions.blank?
message
end
end
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index ae111e4951..914b0d4b30 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/array/extract_options'
require 'action_dispatch/middleware/stack'
+require 'active_support/deprecation'
module ActionController
# Extend ActionDispatch middleware stack to make it aware of options
@@ -11,22 +12,14 @@ module ActionController
#
class MiddlewareStack < ActionDispatch::MiddlewareStack #:nodoc:
class Middleware < ActionDispatch::MiddlewareStack::Middleware #:nodoc:
- def initialize(klass, *args, &block)
- options = args.extract_options!
- @only = Array(options.delete(:only)).map(&:to_s)
- @except = Array(options.delete(:except)).map(&:to_s)
- args << options unless options.empty?
- super
+ def initialize(klass, args, actions, strategy, block)
+ @actions = actions
+ @strategy = strategy
+ super(klass, args, block)
end
def valid?(action)
- if @only.present?
- @only.include?(action)
- elsif @except.present?
- !@except.include?(action)
- else
- true
- end
+ @strategy.call @actions, action
end
end
@@ -37,6 +30,32 @@ module ActionController
middleware.valid?(action) ? middleware.build(a) : a
end
end
+
+ private
+
+ INCLUDE = ->(list, action) { list.include? action }
+ EXCLUDE = ->(list, action) { !list.include? action }
+ NULL = ->(list, action) { true }
+
+ def build_middleware(klass, args, block)
+ options = args.extract_options!
+ only = Array(options.delete(:only)).map(&:to_s)
+ except = Array(options.delete(:except)).map(&:to_s)
+ args << options unless options.empty?
+
+ strategy = NULL
+ list = nil
+
+ if only.any?
+ strategy = INCLUDE
+ list = only
+ elsif except.any?
+ strategy = EXCLUDE
+ list = except
+ end
+
+ Middleware.new(get_class(klass), args, list, strategy, block)
+ end
end
# <tt>ActionController::Metal</tt> is the simplest possible controller, providing a
@@ -98,11 +117,10 @@ module ActionController
class Metal < AbstractController::Base
abstract!
- attr_internal_writer :env
-
def env
- @_env ||= {}
+ @_request.env
end
+ deprecate :env
# Returns the last part of the controller's name, underscored, without the ending
# <tt>Controller</tt>. For instance, PostsController returns <tt>posts</tt>.
@@ -197,8 +215,7 @@ module ActionController
def set_request!(request) #:nodoc:
@_request = request
- @_env = request.env
- @_env['action_controller.instance'] = self
+ @_request.controller_instance = self
end
def to_a #:nodoc:
@@ -232,13 +249,13 @@ module ActionController
end
# Returns a Rack endpoint for the given action name.
- def self.action(name, klass = ActionDispatch::Request)
+ def self.action(name)
if middleware_stack.any?
middleware_stack.build(name) do |env|
- new.dispatch(name, klass.new(env))
+ new.dispatch(name, ActionDispatch::Request.new(env))
end
else
- lambda { |env| new.dispatch(name, klass.new(env)) }
+ lambda { |env| new.dispatch(name, ActionDispatch::Request.new(env)) }
end
end
end
diff --git a/actionpack/lib/action_controller/metal/basic_implicit_render.rb b/actionpack/lib/action_controller/metal/basic_implicit_render.rb
new file mode 100644
index 0000000000..6c6f8381ff
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/basic_implicit_render.rb
@@ -0,0 +1,11 @@
+module ActionController
+ module BasicImplicitRender
+ def send_action(method, *args)
+ super.tap { default_render unless performed? }
+ end
+
+ def default_render(*args)
+ head :no_content
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index 47bcfdb1e9..bb3ad9b850 100644
--- a/actionpack/lib/action_controller/metal/conditional_get.rb
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -40,7 +40,7 @@ module ActionController
# * <tt>:etag</tt>.
# * <tt>:last_modified</tt>.
# * <tt>:public</tt> By default the Cache-Control header is private, set this to
- # +true+ if you want your application to be cachable by other devices (proxy caches).
+ # +true+ if you want your application to be cacheable by other devices (proxy caches).
# * <tt>:template</tt> By default, the template digest for the current
# controller/action is included in ETags. If the action renders a
# different template, you can include its digest instead. If the action
@@ -111,7 +111,7 @@ module ActionController
# * <tt>:etag</tt>.
# * <tt>:last_modified</tt>.
# * <tt>:public</tt> By default the Cache-Control header is private, set this to
- # +true+ if you want your application to be cachable by other devices (proxy caches).
+ # +true+ if you want your application to be cacheable by other devices (proxy caches).
# * <tt>:template</tt> By default, the template digest for the current
# controller/action is included in ETags. If the action renders a
# different template, you can include its digest instead. If the action
diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb
index 1abd8d3a33..e6d7f958bb 100644
--- a/actionpack/lib/action_controller/metal/data_streaming.rb
+++ b/actionpack/lib/action_controller/metal/data_streaming.rb
@@ -85,6 +85,10 @@ module ActionController #:nodoc:
@to_path = path
end
+ def body
+ File.binread(to_path)
+ end
+
# Stream the file's contents if Rack::Sendfile isn't present.
def each
File.open(to_path, 'rb') do |file|
@@ -126,7 +130,7 @@ module ActionController #:nodoc:
# See +send_file+ for more information on HTTP Content-* headers and caching.
def send_data(data, options = {}) #:doc:
send_file_headers! options
- render options.slice(:status, :content_type).merge(:text => data)
+ render options.slice(:status, :content_type).merge(body: data)
end
private
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index d920668184..e31d65aac2 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -55,10 +55,10 @@ module ActionController
# You can pass any of the following options to affect the before_action callback
# * <tt>only</tt> - The callback should be run only for this action
# * <tt>except</tt> - The callback should be run for all actions except this action
- # * <tt>if</tt> - A symbol naming an instance method or a proc; the callback
- # will be called only when it returns a true value.
- # * <tt>unless</tt> - A symbol naming an instance method or a proc; the callback
- # will be called only when it returns a false value.
+ # * <tt>if</tt> - A symbol naming an instance method or a proc; the
+ # callback will be called only when it returns a true value.
+ # * <tt>unless</tt> - A symbol naming an instance method or a proc; the
+ # callback will be called only when it returns a false value.
def force_ssl(options = {})
action_options = options.slice(*ACTION_OPTIONS)
redirect_options = options.except(*ACTION_OPTIONS)
@@ -71,8 +71,8 @@ module ActionController
# Redirect the existing request to use the HTTPS protocol.
#
# ==== Parameters
- # * <tt>host_or_options</tt> - Either a host name or any of the url & redirect options
- # available to the <tt>force_ssl</tt> method.
+ # * <tt>host_or_options</tt> - Either a host name or any of the url &
+ # redirect options available to the <tt>force_ssl</tt> method.
def force_ssl_redirect(host_or_options = nil)
unless request.ssl?
options = {
diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb
index 70f42bf565..f445094bdc 100644
--- a/actionpack/lib/action_controller/metal/head.rb
+++ b/actionpack/lib/action_controller/metal/head.rb
@@ -17,8 +17,18 @@ module ActionController
#
# See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list of valid +status+ symbols.
def head(status, options = {})
- options, status = status, nil if status.is_a?(Hash)
- status ||= options.delete(:status) || :ok
+ 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
+ end
+
+ status ||= :ok
+
location = options.delete(:location)
content_type = options.delete(:content_type)
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index 4038101fe0..fcaf3e6425 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -44,7 +44,7 @@ module ActionController
# the output might look like this:
#
# 23 Aug 11:30 | Carolina Railhawks Soccer Match
- # N/A | Carolina Railhaws Training Workshop
+ # N/A | Carolina Railhawks Training Workshop
#
module Helpers
extend ActiveSupport::Concern
@@ -73,7 +73,7 @@ module ActionController
# Provides a proxy to access helpers methods from outside the view.
def helpers
- @helper_proxy ||= begin
+ @helper_proxy ||= begin
proxy = ActionView::Base.new
proxy.config = config.inheritable_copy
proxy.extend(_helpers)
@@ -100,7 +100,7 @@ module ActionController
def all_helpers_from_path(path)
helpers = Array(path).flat_map do |_path|
extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/
- names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') }
+ names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1'.freeze) }
names.sort!
end
helpers.uniq!
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index c492b7fb64..bbb38cf8fc 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -74,16 +74,16 @@ module ActionController
end
end
- def authenticate_or_request_with_http_basic(realm = "Application", &login_procedure)
- authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm)
+ def authenticate_or_request_with_http_basic(realm = "Application", message = nil, &login_procedure)
+ authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm, message)
end
def authenticate_with_http_basic(&login_procedure)
HttpAuthentication::Basic.authenticate(request, &login_procedure)
end
- def request_http_basic_authentication(realm = "Application")
- HttpAuthentication::Basic.authentication_request(self, realm)
+ def request_http_basic_authentication(realm = "Application", message = nil)
+ HttpAuthentication::Basic.authentication_request(self, realm, message)
end
end
@@ -94,7 +94,7 @@ module ActionController
end
def has_basic_credentials?(request)
- request.authorization.present? && (auth_scheme(request) == 'Basic')
+ request.authorization.present? && (auth_scheme(request).downcase == 'basic')
end
def user_name_and_password(request)
@@ -117,10 +117,11 @@ module ActionController
"Basic #{::Base64.strict_encode64("#{user_name}:#{password}")}"
end
- def authentication_request(controller, realm)
- controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.gsub('"'.freeze, "".freeze)}")
+ def authentication_request(controller, realm, message)
+ message ||= "HTTP Basic: Access denied.\n"
+ controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.tr('"'.freeze, "".freeze)}")
controller.status = 401
- controller.response_body = "HTTP Basic: Access denied.\n"
+ controller.response_body = message
end
end
@@ -170,8 +171,8 @@ module ActionController
extend self
module ControllerMethods
- def authenticate_or_request_with_http_digest(realm = "Application", &password_procedure)
- authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm)
+ def authenticate_or_request_with_http_digest(realm = "Application", message = nil, &password_procedure)
+ authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm, message)
end
# Authenticate with HTTP Digest, returns true or false
@@ -396,21 +397,21 @@ module ActionController
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
module Token
TOKEN_KEY = 'token='
- TOKEN_REGEX = /^Token /
+ TOKEN_REGEX = /^(Token|Bearer) /
AUTHN_PAIR_DELIMITERS = /(?:,|;|\t+)/
extend self
module ControllerMethods
- def authenticate_or_request_with_http_token(realm = "Application", &login_procedure)
- authenticate_with_http_token(&login_procedure) || request_http_token_authentication(realm)
+ def authenticate_or_request_with_http_token(realm = "Application", message = nil, &login_procedure)
+ authenticate_with_http_token(&login_procedure) || request_http_token_authentication(realm, message)
end
def authenticate_with_http_token(&login_procedure)
Token.authenticate(self, &login_procedure)
end
- def request_http_token_authentication(realm = "Application")
- Token.authentication_request(self, realm)
+ def request_http_token_authentication(realm = "Application", message = nil)
+ Token.authentication_request(self, realm, message)
end
end
@@ -492,15 +493,16 @@ module ActionController
"Token #{values * ", "}"
end
- # Sets a WWW-Authenticate to let the client know a token is desired.
+ # Sets a WWW-Authenticate header to let the client know a token is desired.
#
# controller - ActionController::Base instance for the outgoing response.
# realm - String realm to use in the header.
#
# Returns nothing.
- def authentication_request(controller, realm)
- controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.gsub('"'.freeze, "".freeze)}")
- controller.__send__ :render, :text => "HTTP Token: Access denied.\n", :status => :unauthorized
+ def authentication_request(controller, realm, message = nil)
+ message ||= "HTTP Token: Access denied.\n"
+ controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"'.freeze, "".freeze)}")
+ controller.__send__ :render, plain: message, status: :unauthorized
end
end
end
diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb
index 1573ea7099..17fcc2fa02 100644
--- a/actionpack/lib/action_controller/metal/implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/implicit_render.rb
@@ -1,17 +1,29 @@
module ActionController
module ImplicitRender
- def send_action(method, *args)
- ret = super
- default_render unless performed?
- ret
- end
+ include BasicImplicitRender
+
+ # Renders the template corresponding to the controller action, if it exists.
+ # The action name, format, and variant are all taken into account.
+ # For example, the "new" action with an HTML format and variant "phone"
+ # would try to render the <tt>new.html+phone.erb</tt> template.
+ #
+ # If no template is found <tt>ActionController::BasicImplicitRender</tt>'s implementation is called, unless
+ # a block is passed. In that case, it will override the super implementation.
+ #
+ # default_render do
+ # head 404 # No template was found
+ # end
def default_render(*args)
if template_exists?(action_name.to_s, _prefixes, variants: request.variant)
render(*args)
else
- logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger
- head :no_content
+ if block_given?
+ yield(*args)
+ else
+ logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger
+ super
+ end
end
end
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index fab1be3459..1db68db20f 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -156,16 +156,16 @@ module ActionController #:nodoc:
# It works for both inline:
#
# respond_to do |format|
- # format.html.any { render text: "any" }
- # format.html.phone { render text: "phone" }
+ # format.html.any { render html: "any" }
+ # format.html.phone { render html: "phone" }
# end
#
# and block syntax:
#
# respond_to do |format|
# format.html do |variant|
- # variant.any(:tablet, :phablet){ render text: "any" }
- # variant.phone { render text: "phone" }
+ # variant.any(:tablet, :phablet){ render html: "any" }
+ # variant.phone { render html: "phone" }
# end
# end
#
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index 0a04848eba..e680432127 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -4,18 +4,17 @@ require 'active_support/core_ext/module/anonymous'
require 'action_dispatch/http/mime_type'
module ActionController
- # Wraps the parameters hash into a nested hash. This will allow clients to submit
- # POST requests without having to specify any root elements.
+ # Wraps the parameters hash into a nested hash. This will allow clients to
+ # submit requests without having to specify any root elements.
#
# This functionality is enabled in +config/initializers/wrap_parameters.rb+
- # and can be customized. If you are upgrading to \Rails 3.1, this file will
- # need to be created for the functionality to be enabled.
+ # and can be customized.
#
# You could also turn it on per controller by setting the format array to
# a non-empty array:
#
# class UsersController < ApplicationController
- # wrap_parameters format: [:json, :xml]
+ # wrap_parameters format: [:json, :xml, :url_encoded_form, :multipart_form]
# end
#
# If you enable +ParamsWrapper+ for +:json+ format, instead of having to
@@ -41,7 +40,7 @@ module ActionController
# wrap_parameters :person, include: [:username, :password]
# end
#
- # On ActiveRecord models with no +:include+ or +:exclude+ option set,
+ # On Active Record models with no +:include+ or +:exclude+ option set,
# it will only wrap the parameters returned by the class method
# <tt>attribute_names</tt>.
#
@@ -251,7 +250,7 @@ module ActionController
private
- # Returns the wrapper key which will be used to stored wrapped parameters.
+ # Returns the wrapper key which will be used to store wrapped parameters.
def _wrapper_key
_wrapper_options.name
end
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index 45d3962494..cb74c4f0d4 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -94,7 +94,7 @@ module ActionController
# This method is the opposite of add method.
#
- # Usage:
+ # To remove a csv renderer:
#
# ActionController::Renderers.remove(:csv)
def self.remove(key)
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index 2d15c39d88..a3b0645dc0 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -1,3 +1,6 @@
+require 'active_support/deprecation'
+require 'active_support/core_ext/string/filters'
+
module ActionController
module Rendering
extend ActiveSupport::Concern
@@ -74,11 +77,23 @@ module ActionController
def _normalize_options(options) #:nodoc:
_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
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 367b736035..d21a778d8d 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -13,9 +13,14 @@ module ActionController #:nodoc:
# by including a token in the rendered HTML for your application. This token is
# stored as a random string in the session, to which an attacker does not have
# access. When a request reaches your application, \Rails verifies the received
- # token with the token in the session. Only HTML and JavaScript requests are checked,
- # so this will not protect your XML API (presumably you'll have a different
- # authentication scheme there anyway).
+ # token with the token in the session. All requests are checked except GET requests
+ # as these should be idempotent. Keep in mind that all session-oriented requests
+ # should be CSRF protected, including JavaScript and HTML requests.
+ #
+ # Since HTML and JavaScript requests are typically made from the browser, we
+ # need to ensure to verify request authenticity for the web browser. We can
+ # use session-oriented authentication for these types of requests, by using
+ # the `protect_from_forgery` method in our controllers.
#
# GET requests are not protected since they don't have side effects like writing
# to the database and don't leak sensitive information. JavaScript requests are
@@ -26,15 +31,21 @@ module ActionController #:nodoc:
# Ajax) requests are allowed to make GET requests for JavaScript responses.
#
# It's important to remember that XML or JSON requests are also affected and if
- # you're building an API you'll need something like:
+ # you're building an API you should change forgery protection method in
+ # <tt>ApplicationController</tt> (by default: <tt>:exception</tt>):
#
# class ApplicationController < ActionController::Base
# protect_from_forgery unless: -> { request.format.json? }
# end
#
- # CSRF protection is turned on with the <tt>protect_from_forgery</tt> method,
- # which checks the token and resets the session if it doesn't match what was expected.
- # A call to this method is generated for new \Rails applications by default.
+ # CSRF protection is turned on with the <tt>protect_from_forgery</tt> method.
+ # By default <tt>protect_from_forgery</tt> protects your session with
+ # <tt>:null_session</tt> method, which provides an empty session
+ # during request.
+ #
+ # We may want to disable CSRF protection for APIs since they are typically
+ # designed to be state-less. That is, the request API client will handle
+ # the session for you instead of Rails.
#
# The token parameter is named <tt>authenticity_token</tt> by default. The name and
# value of this token must be added to every layout that renders forms by including
@@ -86,10 +97,10 @@ module ActionController #:nodoc:
# Valid Options:
#
# * <tt>:only/:except</tt> - Only apply forgery protection to a subset of actions. Like <tt>only: [ :create, :create_all ]</tt>.
- # * <tt>:if/:unless</tt> - Turn off the forgery protection entirely depending on the passed proc or method reference.
+ # * <tt>:if/:unless</tt> - Turn off the forgery protection entirely depending on the passed Proc or method reference.
# * <tt>:prepend</tt> - By default, the verification of the authentication token is added to the front of the
# callback chain. If you need to make the verification depend on other callbacks, like authentication methods
- # (say cookies vs oauth), this might not work for you. Pass <tt>prepend: false</tt> to just add the
+ # (say cookies vs OAuth), this might not work for you. Pass <tt>prepend: false</tt> to just add the
# verification callback in the position of the protect_from_forgery call. This means any callbacks added
# before are run first.
# * <tt>:with</tt> - Set the method to handle unverified request.
@@ -128,7 +139,7 @@ module ActionController #:nodoc:
request.session = NullSessionHash.new(request.env)
request.env['action_dispatch.request.flash_hash'] = nil
request.env['rack.session.options'] = { skip: true }
- request.env['action_dispatch.cookies'] = NullCookieJar.build(request)
+ request.cookie_jar = NullCookieJar.build(request, {})
end
protected
@@ -149,14 +160,6 @@ module ActionController #:nodoc:
end
class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc:
- def self.build(request)
- key_generator = request.env[ActionDispatch::Cookies::GENERATOR_KEY]
- host = request.host
- secure = request.ssl?
-
- new(key_generator, host, secure, options_for_env({}))
- end
-
def write(*)
# nothing
end
@@ -247,7 +250,7 @@ module ActionController #:nodoc:
# Returns true or false if a request is verified. Checks:
#
- # * is it a GET or HEAD request? Gets should be safe and idempotent
+ # * 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?
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index 04401cad7b..a6115674aa 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -110,9 +110,9 @@ module ActionController #:nodoc:
# This means that, if you have <code>yield :title</code> in your layout
# and you want to use streaming, you would have to render the whole template
# (and eventually trigger all queries) before streaming the title and all
- # assets, which kills the purpose of streaming. For this reason Rails 3.1
- # introduces a new helper called +provide+ that does the same as +content_for+
- # but tells the layout to stop searching for other entries and continue rendering.
+ # assets, which kills the purpose of streaming. For this purpose, you can use
+ # a helper called +provide+ that does the same as +content_for+ but tells the
+ # layout to stop searching for other entries and continue rendering.
#
# For instance, the template above using +provide+ would be:
#
@@ -199,7 +199,7 @@ module ActionController #:nodoc:
def _process_options(options) #:nodoc:
super
if options[:stream]
- if env["HTTP_VERSION"] == "HTTP/1.0"
+ if request.version == "HTTP/1.0"
options.delete(:stream)
else
headers["Cache-Control"] ||= "no-cache"
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index c98e937423..e78f1f0d7e 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -11,9 +11,9 @@ module ActionController
#
# params = ActionController::Parameters.new(a: {})
# params.fetch(:b)
- # # => ActionController::ParameterMissing: param not found: b
+ # # => ActionController::ParameterMissing: param is missing or the value is empty: b
# params.require(:a)
- # # => ActionController::ParameterMissing: param not found: a
+ # # => ActionController::ParameterMissing: param is missing or the value is empty: a
class ParameterMissing < KeyError
attr_reader :param # :nodoc:
@@ -23,11 +23,13 @@ module ActionController
end
end
- # Raised when a supplied parameter is not expected.
+ # Raised when a supplied parameter is not expected and
+ # ActionController::Parameters.action_on_unpermitted_parameters
+ # is set to <tt>:raise</tt>.
#
# params = ActionController::Parameters.new(a: "123", b: "456")
# params.permit(:c)
- # # => ActionController::UnpermittedParameters: found unexpected keys: a, b
+ # # => ActionController::UnpermittedParameters: found unpermitted parameters: a, b
class UnpermittedParameters < IndexError
attr_reader :params # :nodoc:
@@ -102,10 +104,12 @@ module ActionController
# params = ActionController::Parameters.new(key: 'value')
# params[:key] # => "value"
# params["key"] # => "value"
- class Parameters < ActiveSupport::HashWithIndifferentAccess
+ class Parameters
cattr_accessor :permit_all_parameters, instance_accessor: false
cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false
+ delegate :keys, :key?, :has_key?, :empty?, :inspect, to: :@parameters
+
# By default, never raise an UnpermittedParameters exception if these
# params are present. The default includes both 'controller' and 'action'
# because they are added by Rails and should be of no concern. One way
@@ -142,11 +146,22 @@ module ActionController
# params = ActionController::Parameters.new(name: 'Francesco')
# params.permitted? # => true
# Person.new(params) # => #<Person id: nil, name: "Francesco">
- def initialize(attributes = nil)
- super(attributes)
+ def initialize(parameters = {})
+ @parameters = parameters.with_indifferent_access
@permitted = self.class.permit_all_parameters
end
+ # Returns true if another +Parameters+ object contains the same content and
+ # permitted flag, or other Hash-like object contains the same content. This
+ # override is in place so you can perform a comparison with `Hash`.
+ def ==(other_hash)
+ if other_hash.respond_to?(:permitted?)
+ super
+ else
+ @parameters == other_hash
+ end
+ end
+
# Returns a safe +Hash+ representation of this parameter with all
# unpermitted keys removed.
#
@@ -160,7 +175,7 @@ module ActionController
# safe_params.to_h # => {"name"=>"Senjougahara Hitagi"}
def to_h
if permitted?
- to_hash
+ @parameters.to_h
else
slice(*self.class.always_permitted_parameters).permit!.to_h
end
@@ -168,20 +183,17 @@ module ActionController
# Returns an unsafe, unfiltered +Hash+ representation of this parameter.
def to_unsafe_h
- to_hash
+ @parameters.to_h
end
alias_method :to_unsafe_hash, :to_unsafe_h
# Convert all hashes in values into parameters, then yield each pair like
# the same way as <tt>Hash#each_pair</tt>
def each_pair(&block)
- super do |key, value|
- convert_hashes_to_parameters(key, value)
+ @parameters.each_pair do |key, value|
+ yield key, convert_hashes_to_parameters(key, value)
end
-
- super
end
-
alias_method :each, :each_pair
# Attribute that keeps track of converted arrays, if any, to avoid double
@@ -236,10 +248,10 @@ module ActionController
# # => {"name"=>"Francesco"}
#
# ActionController::Parameters.new(person: nil).require(:person)
- # # => ActionController::ParameterMissing: param not found: person
+ # # => ActionController::ParameterMissing: param is missing or the value is empty: person
#
# ActionController::Parameters.new(person: {}).require(:person)
- # # => ActionController::ParameterMissing: param not found: person
+ # # => ActionController::ParameterMissing: param is missing or the value is empty: person
def require(key)
value = self[key]
if value.present? || value == false
@@ -345,7 +357,13 @@ module ActionController
# params[:person] # => {"name"=>"Francesco"}
# params[:none] # => nil
def [](key)
- convert_hashes_to_parameters(key, super)
+ convert_hashes_to_parameters(key, @parameters[key])
+ end
+
+ # Assigns a value to a given +key+. The given key may still get filtered out
+ # when +permit+ is called.
+ def []=(key, value)
+ @parameters[key] = value
end
# Returns a parameter for the given +key+. If the +key+
@@ -356,13 +374,19 @@ module ActionController
#
# params = ActionController::Parameters.new(person: { name: 'Francesco' })
# params.fetch(:person) # => {"name"=>"Francesco"}
- # params.fetch(:none) # => ActionController::ParameterMissing: param not found: none
+ # params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none
# params.fetch(:none, 'Francesco') # => "Francesco"
# params.fetch(:none) { 'Francesco' } # => "Francesco"
- def fetch(key, *args)
- convert_hashes_to_parameters(key, super, false)
- rescue KeyError
- raise ActionController::ParameterMissing.new(key)
+ def fetch(key, *args, &block)
+ convert_value_to_parameters(
+ @parameters.fetch(key) {
+ if block_given?
+ yield
+ else
+ args.fetch(0) { raise ActionController::ParameterMissing.new(key) }
+ end
+ }
+ )
end
# Returns a new <tt>ActionController::Parameters</tt> instance that
@@ -373,7 +397,24 @@ module ActionController
# params.slice(:a, :b) # => {"a"=>1, "b"=>2}
# params.slice(:d) # => {}
def slice(*keys)
- new_instance_with_inherited_permitted_status(super)
+ new_instance_with_inherited_permitted_status(@parameters.slice(*keys))
+ end
+
+ # Returns current <tt>ActionController::Parameters</tt> instance which
+ # contains only the given +keys+.
+ def slice!(*keys)
+ @parameters.slice!(*keys)
+ self
+ end
+
+ # Returns a new <tt>ActionController::Parameters</tt> instance that
+ # filters out the given +keys+.
+ #
+ # params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
+ # params.except(:a, :b) # => {"c"=>3}
+ # params.except(:d) # => {"a"=>1,"b"=>2,"c"=>3}
+ def except(*keys)
+ new_instance_with_inherited_permitted_status(@parameters.except(*keys))
end
# Removes and returns the key/value pairs matching the given keys.
@@ -382,7 +423,7 @@ module ActionController
# params.extract!(:a, :b) # => {"a"=>1, "b"=>2}
# params # => {"c"=>3}
def extract!(*keys)
- new_instance_with_inherited_permitted_status(super)
+ new_instance_with_inherited_permitted_status(@parameters.extract!(*keys))
end
# Returns a new <tt>ActionController::Parameters</tt> with the results of
@@ -391,36 +432,80 @@ module ActionController
# params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
# params.transform_values { |x| x * 2 }
# # => {"a"=>2, "b"=>4, "c"=>6}
- def transform_values
- if block_given?
- new_instance_with_inherited_permitted_status(super)
+ def transform_values(&block)
+ if block
+ new_instance_with_inherited_permitted_status(
+ @parameters.transform_values(&block)
+ )
else
- super
+ @parameters.transform_values
end
end
- # This method is here only to make sure that the returned object has the
- # correct +permitted+ status. It should not matter since the parent of
- # this object is +HashWithIndifferentAccess+
- def transform_keys # :nodoc:
- if block_given?
- new_instance_with_inherited_permitted_status(super)
+ # Performs values transformation and returns the altered
+ # <tt>ActionController::Parameters</tt> instance.
+ def transform_values!(&block)
+ @parameters.transform_values!(&block)
+ self
+ end
+
+ # Returns a new <tt>ActionController::Parameters</tt> instance with the
+ # results of running +block+ once for every key. The values are unchanged.
+ def transform_keys(&block)
+ if block
+ new_instance_with_inherited_permitted_status(
+ @parameters.transform_keys(&block)
+ )
else
- super
+ @parameters.transform_keys
end
end
+ # Performs keys transfomration and returns the altered
+ # <tt>ActionController::Parameters</tt> instance.
+ def transform_keys!(&block)
+ @parameters.transform_keys!(&block)
+ self
+ end
+
# Deletes and returns a key-value pair from +Parameters+ whose key is equal
# to key. If the key is not found, returns the default value. If the
# optional code block is given and the key is not found, pass in the key
# and return the result of block.
def delete(key, &block)
- convert_hashes_to_parameters(key, super, false)
+ convert_value_to_parameters(@parameters.delete(key))
+ end
+
+ # Returns a new instance of <tt>ActionController::Parameters</tt> with only
+ # items that the block evaluates to true.
+ def select(&block)
+ new_instance_with_inherited_permitted_status(@parameters.select(&block))
end
# Equivalent to Hash#keep_if, but returns nil if no changes were made.
def select!(&block)
- convert_value_to_parameters(super)
+ @parameters.select!(&block)
+ self
+ end
+ alias_method :keep_if, :select!
+
+ # Returns a new instance of <tt>ActionController::Parameters</tt> with items
+ # that the block evaluates to true removed.
+ def reject(&block)
+ new_instance_with_inherited_permitted_status(@parameters.reject(&block))
+ end
+
+ # Removes items that the block evaluates to true and returns self.
+ def reject!(&block)
+ @parameters.reject!(&block)
+ self
+ end
+ alias_method :delete_if, :reject!
+
+ # Return values that were assigned to the given +keys+. Note that all the
+ # +Hash+ objects will be converted to <tt>ActionController::Parameters</tt>.
+ def values_at(*keys)
+ convert_value_to_parameters(@parameters.values_at(*keys))
end
# Returns an exact copy of the <tt>ActionController::Parameters</tt>
@@ -437,11 +522,30 @@ module ActionController
end
end
+ # Returns a new <tt>ActionController::Parameters</tt> with all keys from
+ # +other_hash+ merges into current hash.
+ def merge(other_hash)
+ new_instance_with_inherited_permitted_status(
+ @parameters.merge(other_hash)
+ )
+ 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.
+ def stringify_keys # :nodoc:
+ dup
+ end
+
protected
def permitted=(new_permitted)
@permitted = new_permitted
end
+ def fields_for_style?
+ @parameters.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) }
+ end
+
private
def new_instance_with_inherited_permitted_status(hash)
self.class.new(hash).tap do |new_instance|
@@ -449,40 +553,41 @@ module ActionController
end
end
- def convert_hashes_to_parameters(key, value, assign_if_converted=true)
+ def convert_hashes_to_parameters(key, value)
converted = convert_value_to_parameters(value)
- self[key] = converted if assign_if_converted && !converted.equal?(value)
+ @parameters[key] = converted unless converted.equal?(value)
converted
end
def convert_value_to_parameters(value)
- if value.is_a?(Array) && !converted_arrays.member?(value)
+ case value
+ when Array
+ return value if converted_arrays.member?(value)
converted = value.map { |_| convert_value_to_parameters(_) }
converted_arrays << converted
converted
- elsif value.is_a?(Parameters) || !value.is_a?(Hash)
- value
- else
+ when Hash
self.class.new(value)
+ else
+ value
end
end
def each_element(object)
- if object.is_a?(Array)
- object.map { |el| yield el }.compact
- elsif fields_for_style?(object)
- hash = object.class.new
- object.each { |k,v| hash[k] = yield v }
- hash
- else
- yield object
+ case object
+ when Array
+ object.grep(Parameters).map { |el| yield el }.compact
+ when Parameters
+ if object.fields_for_style?
+ hash = object.class.new
+ object.each { |k,v| hash[k] = yield v }
+ hash
+ else
+ yield object
+ end
end
end
- def fields_for_style?(object)
- object.is_a?(Hash) && object.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) }
- end
-
def unpermitted_parameters!(params)
unpermitted_keys = unpermitted_keys(params)
if unpermitted_keys.any?
@@ -544,14 +649,8 @@ module ActionController
end
def array_of_permitted_scalars?(value)
- if value.is_a?(Array)
- value.all? {|element| permitted_scalar?(element)}
- end
- end
-
- def array_of_permitted_scalars_filter(params, key)
- if has_key?(key) && array_of_permitted_scalars?(self[key])
- params[key] = self[key]
+ if value.is_a?(Array) && value.all? {|element| permitted_scalar?(element)}
+ yield value
end
end
@@ -562,17 +661,17 @@ module ActionController
# Slicing filters out non-declared keys.
slice(*filter.keys).each do |key, value|
next unless value
+ next unless has_key? key
if filter[key] == EMPTY_ARRAY
# Declaration { comment_ids: [] }.
- array_of_permitted_scalars_filter(params, key)
+ array_of_permitted_scalars?(self[key]) do |val|
+ params[key] = val
+ end
else
# Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
params[key] = each_element(value) do |element|
- if element.is_a?(Hash)
- element = self.class.new(element) unless element.respond_to?(:permit)
- element.permit(*Array.wrap(filter[key]))
- end
+ element.permit(*Array.wrap(filter[key]))
end
end
end
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index 5a0e5c62e4..dbf7241a14 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -41,7 +41,11 @@ module ActionController
if original_script_name
options[:original_script_name] = original_script_name
else
- options[:script_name] = same_origin ? request.script_name.dup : script_name
+ if same_origin
+ options[:script_name] = request.script_name.empty? ? "".freeze : request.script_name.dup
+ else
+ options[:script_name] = script_name
+ end
end
options.freeze
else
diff --git a/actionpack/lib/action_controller/middleware.rb b/actionpack/lib/action_controller/middleware.rb
deleted file mode 100644
index 437fec3dc6..0000000000
--- a/actionpack/lib/action_controller/middleware.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-module ActionController
- class Middleware < Metal
- class ActionMiddleware
- def initialize(controller, app)
- @controller, @app = controller, app
- end
-
- def call(env)
- request = ActionDispatch::Request.new(env)
- @controller.build(@app).dispatch(:index, request)
- end
- end
-
- class << self
- alias build new
-
- def new(app)
- ActionMiddleware.new(self, app)
- end
- end
-
- attr_internal :app
-
- def process(action)
- response = super
- self.status, self.headers, self.response_body = response if response.is_a?(Array)
- response
- end
-
- def initialize(app)
- super()
- @_app = app
- end
-
- def index
- call(env)
- end
- end
-end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/template_assertions.rb b/actionpack/lib/action_controller/template_assertions.rb
new file mode 100644
index 0000000000..0179f4afcd
--- /dev/null
+++ b/actionpack/lib/action_controller/template_assertions.rb
@@ -0,0 +1,9 @@
+module ActionController
+ module TemplateAssertions
+ def assert_template(options = {}, message = nil)
+ raise NoMethodError,
+ "assert_template has been extracted to a gem. To continue using it,
+ add `gem 'rails-controller-testing'` to your Gemfile."
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 33c24999f9..39069f7378 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -2,222 +2,54 @@ require 'rack/session/abstract/id'
require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/module/anonymous'
require 'active_support/core_ext/hash/keys'
-
+require 'action_controller/template_assertions'
require 'rails-dom-testing'
module ActionController
- module TemplateAssertions
- extend ActiveSupport::Concern
+ class TestRequest < ActionDispatch::TestRequest #:nodoc:
+ DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
+ DEFAULT_ENV.delete 'PATH_INFO'
- included do
- setup :setup_subscriptions
- teardown :teardown_subscriptions
+ def self.new_session
+ TestSession.new
end
- RENDER_TEMPLATE_INSTANCE_VARIABLES = %w{partials templates layouts files}.freeze
-
- def setup_subscriptions
- RENDER_TEMPLATE_INSTANCE_VARIABLES.each do |instance_variable|
- instance_variable_set("@_#{instance_variable}", Hash.new(0))
- end
-
- @_subscribers = []
-
- @_subscribers << ActiveSupport::Notifications.subscribe("render_template.action_view") do |_name, _start, _finish, _id, payload|
- path = payload[:layout]
- if path
- @_layouts[path] += 1
- if path =~ /^layouts\/(.*)/
- @_layouts[$1] += 1
- end
- end
- end
-
- @_subscribers << ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload|
- if virtual_path = payload[:virtual_path]
- partial = virtual_path =~ /^.*\/_[^\/]*$/
-
- if partial
- @_partials[virtual_path] += 1
- @_partials[virtual_path.split("/").last] += 1
- end
-
- @_templates[virtual_path] += 1
- else
- path = payload[:identifier]
- if path
- @_files[path] += 1
- @_files[path.split("/").last] += 1
- end
- end
- end
+ # Create a new test request with default `env` values
+ def self.create
+ env = {}
+ env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
+ env["rack.request.cookie_hash"] = {}.with_indifferent_access
+ new(default_env.merge(env), new_session)
end
- def teardown_subscriptions
- @_subscribers.each do |subscriber|
- ActiveSupport::Notifications.unsubscribe(subscriber)
- end
+ def self.default_env
+ DEFAULT_ENV
end
+ private_class_method :default_env
- def process(*args)
- reset_template_assertion
- super
- end
+ def initialize(env, session)
+ super(env)
- def reset_template_assertion
- RENDER_TEMPLATE_INSTANCE_VARIABLES.each do |instance_variable|
- ivar_name = "@_#{instance_variable}"
- if instance_variable_defined?(ivar_name)
- instance_variable_get(ivar_name).clear
- end
- end
+ self.session = session
+ self.session_options = TestSession::DEFAULT_OPTIONS
end
- # Asserts that the request was rendered with the appropriate template file or partials.
- #
- # # assert that the "new" view template was rendered
- # assert_template "new"
- #
- # # assert that the exact template "admin/posts/new" was rendered
- # assert_template %r{\Aadmin/posts/new\Z}
- #
- # # assert that the layout 'admin' was rendered
- # assert_template layout: 'admin'
- # assert_template layout: 'layouts/admin'
- # assert_template layout: :admin
- #
- # # assert that no layout was rendered
- # assert_template layout: nil
- # assert_template layout: false
- #
- # # assert that the "_customer" partial was rendered twice
- # assert_template partial: '_customer', count: 2
- #
- # # assert that no partials were rendered
- # assert_template partial: false
- #
- # # assert that a file was rendered
- # assert_template file: "README.rdoc"
- #
- # # assert that no file was rendered
- # assert_template file: nil
- # assert_template file: false
- #
- # In a view test case, you can also assert that specific locals are passed
- # to partials:
- #
- # # assert that the "_customer" partial was rendered with a specific object
- # assert_template partial: '_customer', locals: { customer: @customer }
- def assert_template(options = {}, message = nil)
- # Force body to be read in case the template is being streamed.
- response.body
-
- case options
- when NilClass, Regexp, String, Symbol
- options = options.to_s if Symbol === options
- rendered = @_templates
- msg = message || sprintf("expecting <%s> but rendering with <%s>",
- options.inspect, rendered.keys)
- matches_template =
- case options
- when String
- !options.empty? && rendered.any? do |t, num|
- options_splited = options.split(File::SEPARATOR)
- t_splited = t.split(File::SEPARATOR)
- t_splited.last(options_splited.size) == options_splited
- end
- when Regexp
- rendered.any? { |t,num| t.match(options) }
- when NilClass
- rendered.blank?
- end
- assert matches_template, msg
- when Hash
- options.assert_valid_keys(:layout, :partial, :locals, :count, :file)
-
- if options.key?(:layout)
- expected_layout = options[:layout]
- msg = message || sprintf("expecting layout <%s> but action rendered <%s>",
- expected_layout, @_layouts.keys)
-
- case expected_layout
- when String, Symbol
- assert_includes @_layouts.keys, expected_layout.to_s, msg
- when Regexp
- assert(@_layouts.keys.any? {|l| l =~ expected_layout }, msg)
- when nil, false
- assert(@_layouts.empty?, msg)
- else
- raise ArgumentError, "assert_template only accepts a String, Symbol, Regexp, nil or false for :layout"
- end
- end
-
- if options[:file]
- assert_includes @_files.keys, options[:file]
- elsif options.key?(:file)
- assert @_files.blank?, "expected no files but #{@_files.keys} was rendered"
- end
-
- if expected_partial = options[:partial]
- if expected_locals = options[:locals]
- if defined?(@_rendered_views)
- view = expected_partial.to_s.sub(/^_/, '').sub(/\/_(?=[^\/]+\z)/, '/')
-
- partial_was_not_rendered_msg = "expected %s to be rendered but it was not." % view
- assert_includes @_rendered_views.rendered_views, view, partial_was_not_rendered_msg
-
- msg = 'expecting %s to be rendered with %s but was with %s' % [expected_partial,
- expected_locals,
- @_rendered_views.locals_for(view)]
- assert(@_rendered_views.view_rendered?(view, options[:locals]), msg)
- else
- warn "the :locals option to #assert_template is only supported in a ActionView::TestCase"
- end
- elsif expected_count = options[:count]
- actual_count = @_partials[expected_partial]
- msg = message || sprintf("expecting %s to be rendered %s time(s) but rendered %s time(s)",
- expected_partial, expected_count, actual_count)
- assert(actual_count == expected_count.to_i, msg)
- else
- msg = message || sprintf("expecting partial <%s> but action rendered <%s>",
- options[:partial], @_partials.keys)
- assert_includes @_partials, expected_partial, msg
- end
- elsif options.key?(:partial)
- assert @_partials.empty?,
- "Expected no partials to be rendered"
- end
- else
- raise ArgumentError, "assert_template only accepts a String, Symbol, Hash, Regexp, or nil"
- end
+ def query_string=(string)
+ @env[Rack::QUERY_STRING] = string
end
- end
-
- class TestRequest < ActionDispatch::TestRequest #:nodoc:
- DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
- DEFAULT_ENV.delete 'PATH_INFO'
- def initialize(env = {})
- super
-
- self.session = TestSession.new
- self.session_options = TestSession::DEFAULT_OPTIONS
+ def request_parameters=(params)
+ @env["action_dispatch.request.request_parameters"] = params
end
def assign_parameters(routes, controller_path, action, parameters = {})
- parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action)
- extra_keys = routes.extra_keys(parameters)
- non_path_parameters = get? ? query_parameters : request_parameters
- parameters.each do |key, value|
- if value.is_a?(Array) && (value.frozen? || value.any?(&:frozen?))
- value = value.map{ |v| v.duplicable? ? v.dup : v }
- elsif value.is_a?(Hash) && (value.frozen? || value.any?{ |k,v| v.frozen? })
- value = Hash[value.map{ |k,v| [k, v.duplicable? ? v.dup : v] }]
- elsif value.frozen? && value.duplicable?
- value = value.dup
- end
+ parameters = parameters.symbolize_keys
+ generated_path, query_string_keys = routes.generate_extras(parameters.merge(:controller => controller_path, :action => action))
+ non_path_parameters = {}
+ path_parameters = {}
- if extra_keys.include?(key)
+ parameters.each do |key, value|
+ if query_string_keys.include?(key) || key == :action || key == :controller
non_path_parameters[key] = value
else
if value.is_a?(Array)
@@ -230,72 +62,83 @@ module ActionController
end
end
- # Clear the combined params hash in case it was already referenced.
- @env.delete("action_dispatch.request.parameters")
-
- # Clear the filter cache variables so they're not stale
- @filtered_parameters = @filtered_env = @filtered_path = nil
+ if get?
+ if self.query_string.blank?
+ self.query_string = non_path_parameters.to_query
+ end
+ else
+ if ENCODER.should_multipart?(non_path_parameters)
+ @env['CONTENT_TYPE'] = ENCODER.content_type
+ data = ENCODER.build_multipart non_path_parameters
+ else
+ @env['CONTENT_TYPE'] ||= 'application/x-www-form-urlencoded'
+
+ # FIXME: setting `request_parametes` is normally handled by the
+ # params parser middleware, and we should remove this roundtripping
+ # when we switch to caling `call` on the controller
+
+ case content_mime_type.ref
+ when :json
+ data = ActiveSupport::JSON.encode(non_path_parameters)
+ params = ActiveSupport::JSON.decode(data).with_indifferent_access
+ self.request_parameters = params
+ when :xml
+ data = non_path_parameters.to_xml
+ params = Hash.from_xml(data)['hash']
+ self.request_parameters = params
+ when :url_encoded_form
+ data = non_path_parameters.to_query
+ else
+ raise "Unknown Content-Type: #{content_type}"
+ end
+ end
- params = self.request_parameters.dup
- %w(controller action only_path).each do |k|
- params.delete(k)
- params.delete(k.to_sym)
+ @env['CONTENT_LENGTH'] = data.length.to_s
+ @env['rack.input'] = StringIO.new(data)
end
- data = params.to_query
- @env['CONTENT_LENGTH'] = data.length.to_s
- @env['rack.input'] = StringIO.new(data)
- end
+ @env["PATH_INFO"] ||= generated_path
+ path_parameters[:controller] = controller_path
+ path_parameters[:action] = action
- def recycle!
- @formats = nil
- @env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
- @env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
- @method = @request_method = nil
- @fullpath = @ip = @remote_ip = @protocol = nil
- @env['action_dispatch.request.query_parameters'] = {}
- @set_cookies ||= {}
- @set_cookies.update(Hash[cookie_jar.instance_variable_get("@set_cookies").map{ |k,o| [k,o[:value]] }])
- deleted_cookies = cookie_jar.instance_variable_get("@delete_cookies")
- @set_cookies.reject!{ |k,v| deleted_cookies.include?(k) }
- cookie_jar.update(rack_cookies)
- cookie_jar.update(cookies)
- cookie_jar.update(@set_cookies)
- cookie_jar.recycle!
+ self.path_parameters = path_parameters
end
- private
+ ENCODER = Class.new do
+ include Rack::Test::Utils
+
+ def should_multipart?(params)
+ # FIXME: lifted from Rack-Test. We should push this separation upstream
+ multipart = false
+ query = lambda { |value|
+ case value
+ when Array
+ value.each(&query)
+ when Hash
+ value.values.each(&query)
+ when Rack::Test::UploadedFile
+ multipart = true
+ end
+ }
+ params.values.each(&query)
+ multipart
+ end
- def default_env
- DEFAULT_ENV
- end
- end
+ public :build_multipart
- class TestResponse < ActionDispatch::TestResponse
- def recycle!
- initialize
- end
+ def content_type
+ "multipart/form-data; boundary=#{Rack::Test::MULTIPART_BOUNDARY}"
+ end
+ end.new
end
class LiveTestResponse < Live::Response
- def recycle!
- @body = nil
- initialize
- end
-
- def body
- @body ||= super
- end
-
# Was the response successful?
alias_method :success?, :successful?
# Was the URL not found?
alias_method :missing?, :not_found?
- # Were we redirected?
- alias_method :redirect?, :redirection?
-
# Was there a server-side error?
alias_method :error?, :server_error?
end
@@ -328,6 +171,10 @@ module ActionController
clear
end
+ def fetch(*args, &block)
+ @data.fetch(*args, &block)
+ end
+
private
def load!
@@ -354,7 +201,7 @@ module ActionController
# class BooksControllerTest < ActionController::TestCase
# def test_create
# # Simulate a POST response with the given HTTP parameters.
- # post(:create, book: { title: "Love Hina" })
+ # post(:create, params: { book: { title: "Love Hina" }})
#
# # Assert that the controller tried to redirect us to
# # the created book's URI.
@@ -384,7 +231,7 @@ module ActionController
# request. You can modify this object before sending the HTTP request. For example,
# you might want to set some session properties before sending a GET request.
# <b>@response</b>::
- # An ActionController::TestResponse object, representing the response
+ # An ActionDispatch::TestResponse object, representing the response
# of the last HTTP response. In the above example, <tt>@response</tt> becomes valid
# after calling +post+. If the various assert methods are not sufficient, then you
# may use this object to inspect the HTTP response in detail.
@@ -407,21 +254,15 @@ module ActionController
# In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
# can be used against. These collections are:
#
- # * assigns: Instance variables assigned in the action that are available for the view.
# * session: Objects being saved in the session.
# * flash: The flash objects currently in the session.
# * cookies: \Cookies being sent to the user on this request.
#
# These collections can be used just like any other hash:
#
- # assert_not_nil assigns(:person) # makes sure that a @person instance variable was set
# assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
# assert flash.empty? # makes sure that there's nothing in the flash
#
- # For historic reasons, the assigns hash uses string-based keys. So <tt>assigns[:person]</tt> won't work, but <tt>assigns["person"]</tt> will. To
- # appease our yearning for symbols, though, an alternative accessor has been devised using a method call instead of index referencing.
- # So <tt>assigns(:person)</tt> will work just like <tt>assigns["person"]</tt>, but again, <tt>assigns[:person]</tt> will not work.
- #
# On top of the collections, you have the complete url that a given action redirected to available in <tt>redirect_to_url</tt>.
#
# For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
@@ -512,7 +353,9 @@ 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)
- process_with_kwargs("GET", action, *args)
+ res = process_with_kwargs("GET", action, *args)
+ cookies.update res.cookies
+ res
end
# Simulate a POST request with the given parameters and set/volley the response.
@@ -560,19 +403,6 @@ module ActionController
end
alias xhr :xml_http_request
- def paramify_values(hash_or_array_or_value)
- case hash_or_array_or_value
- when Hash
- Hash[hash_or_array_or_value.map{|key, value| [key, paramify_values(value)] }]
- when Array
- hash_or_array_or_value.map {|i| paramify_values(i)}
- when Rack::Test::UploadedFile, ActionDispatch::Http::UploadedFile
- hash_or_array_or_value
- else
- hash_or_array_or_value.to_param
- end
- end
-
# Simulate a HTTP request to +action+ by specifying request method,
# parameters and set/volley the response.
#
@@ -604,7 +434,7 @@ module ActionController
def process(action, *args)
check_required_ivars
- if kwarg_request?(*args)
+ 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
@@ -632,10 +462,6 @@ module ActionController
parameters ||= {}
- # Ensure that numbers and symbols passed as params are converted to
- # proper params, as is the case when engaging rack.
- parameters = paramify_values(parameters) if html_format?(parameters)
-
if format.present?
parameters[:format] = format
end
@@ -646,8 +472,13 @@ module ActionController
@controller.extend(Testing::Functional)
end
- @request.recycle!
- @response.recycle!
+ self.cookies.update @request.cookies
+ @request.env['HTTP_COOKIE'] = cookies.to_header
+ @request.env['action_dispatch.cookies'] = nil
+
+ @request = TestRequest.new scrub_env!(@request.env), @request.session
+ @response = build_response @response_klass
+ @response.request = @request
@controller.recycle!
@request.env['REQUEST_METHOD'] = http_method
@@ -669,22 +500,21 @@ module ActionController
@controller.request = @request
@controller.response = @response
- build_request_uri(action, parameters)
-
- name = @request.parameters[:action]
+ @request.env["SCRIPT_NAME"] ||= @controller.config.relative_url_root
@controller.recycle!
- @controller.process(name)
+ @controller.process(action)
+
+ @request.env.delete 'HTTP_COOKIE'
if cookies = @request.env['action_dispatch.cookies']
unless @response.committed?
cookies.write(@response)
+ self.cookies.update(cookies.instance_variable_get(:@cookies))
end
end
@response.prepare!
- @assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {}
-
if flash_value = @request.flash.to_session_value
@request.session['flash'] = flash_value
else
@@ -695,6 +525,7 @@ module ActionController
@request.env.delete 'HTTP_X_REQUESTED_WITH'
@request.env.delete 'HTTP_ACCEPT'
end
+ @request.query_string = ''
@response
end
@@ -702,11 +533,11 @@ module ActionController
def setup_controller_request_and_response
@controller = nil unless defined? @controller
- response_klass = TestResponse
+ @response_klass = ActionDispatch::TestResponse
if klass = self.class.controller_class
if klass < ActionController::Live
- response_klass = LiveTestResponse
+ @response_klass = LiveTestResponse
end
unless @controller
begin
@@ -717,8 +548,8 @@ module ActionController
end
end
- @request = build_request
- @response = build_response response_klass
+ @request = TestRequest.create
+ @response = build_response @response_klass
@response.request = @request
if @controller
@@ -727,10 +558,6 @@ module ActionController
end
end
- def build_request
- TestRequest.new
- end
-
def build_response(klass)
klass.new
end
@@ -744,12 +571,20 @@ module ActionController
private
+ def scrub_env!(env)
+ env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
+ env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
+ env.delete 'action_dispatch.request.query_parameters'
+ env.delete 'action_dispatch.request.request_parameters'
+ env
+ end
+
def process_with_kwargs(http_method, action, *args)
- if kwarg_request?(*args)
+ if kwarg_request?(args)
args.first.merge!(method: http_method)
process(action, *args)
else
- non_kwarg_request_warning if args.present?
+ non_kwarg_request_warning if args.any?
args = args.unshift(http_method)
process(action, *args)
@@ -757,7 +592,7 @@ module ActionController
end
REQUEST_KWARGS = %i(params session flash method body xhr)
- def kwarg_request?(*args)
+ 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) }
@@ -790,22 +625,6 @@ module ActionController
end
end
- def build_request_uri(action, parameters)
- unless @request.env["PATH_INFO"]
- options = @controller.respond_to?(:url_options) ? @controller.__send__(:url_options).merge(parameters) : parameters
- options.update(
- :action => action,
- :relative_url_root => nil,
- :_recall => @request.path_parameters)
-
- url, query_string = @routes.path_for(options).split("?", 2)
-
- @request.env["SCRIPT_NAME"] = @controller.config.relative_url_root
- @request.env["PATH_INFO"] = url
- @request.env["QUERY_STRING"] = query_string || ""
- end
- end
-
def html_format?(parameters)
return true unless parameters.key?(:format)
Mime.fetch(parameters[:format]) { Mime['html'] }.html?
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index dcd3ee0644..f6336c8c7a 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -52,6 +52,7 @@ module ActionDispatch
autoload :DebugExceptions
autoload :ExceptionWrapper
autoload :Flash
+ autoload :LoadInterlock
autoload :ParamsParser
autoload :PublicExceptions
autoload :Reloader
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index 747d295261..cc1cb3f0f0 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -151,11 +151,11 @@ module ActionDispatch
control.merge! @cache_control
if control.empty?
- headers[CACHE_CONTROL] = DEFAULT_CACHE_CONTROL
+ self[CACHE_CONTROL] = DEFAULT_CACHE_CONTROL
elsif control[:no_cache]
- headers[CACHE_CONTROL] = NO_CACHE
+ self[CACHE_CONTROL] = NO_CACHE
if control[:extras]
- headers[CACHE_CONTROL] += ", #{control[:extras].join(', ')}"
+ self[CACHE_CONTROL] += ", #{control[:extras].join(', ')}"
end
else
extras = control[:extras]
@@ -167,7 +167,7 @@ module ActionDispatch
options << MUST_REVALIDATE if control[:must_revalidate]
options.concat(extras) if extras
- headers[CACHE_CONTROL] = options.join(", ")
+ self[CACHE_CONTROL] = options.join(", ")
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb
index 2b851cc28d..3170389b36 100644
--- a/actionpack/lib/action_dispatch/http/filter_parameters.rb
+++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb
@@ -16,7 +16,7 @@ module ActionDispatch
# env["action_dispatch.parameter_filter"] = [:foo, "bar"]
# => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
#
- # env["action_dispatch.parameter_filter"] = lambda do |k,v|
+ # env["action_dispatch.parameter_filter"] = -> (k, v) do
# v.reverse! if k =~ /secret/i
# end
# => reverses the value to all keys matching /secret/i
diff --git a/actionpack/lib/action_dispatch/http/filter_redirect.rb b/actionpack/lib/action_dispatch/http/filter_redirect.rb
index bf79963351..94c1f2b41f 100644
--- a/actionpack/lib/action_dispatch/http/filter_redirect.rb
+++ b/actionpack/lib/action_dispatch/http/filter_redirect.rb
@@ -5,8 +5,7 @@ module ActionDispatch
FILTERED = '[FILTERED]'.freeze # :nodoc:
def filtered_location # :nodoc:
- filters = location_filter
- if !filters.empty? && location_filter_match?(filters)
+ if location_filter_match?
FILTERED
else
location
@@ -15,7 +14,7 @@ module ActionDispatch
private
- def location_filter
+ def location_filters
if request
request.env['action_dispatch.redirect_filter'] || []
else
@@ -23,12 +22,12 @@ module ActionDispatch
end
end
- def location_filter_match?(filters)
- filters.any? do |filter|
+ def location_filter_match?
+ location_filters.any? do |filter|
if String === filter
location.include?(filter)
elsif Regexp === filter
- location.match(filter)
+ location =~ filter
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 7e585aa244..a639f8a8f8 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -45,7 +45,7 @@ module Mime
#
# respond_to do |format|
# format.html
- # format.ics { render text: @post.to_ics, mime_type: Mime::Type.lookup("text/calendar") }
+ # format.ics { render body: @post.to_ics, mime_type: Mime::Type.lookup("text/calendar") }
# format.xml { render xml: @post }
# end
# end
@@ -211,7 +211,7 @@ module Mime
# This method is opposite of register method.
#
- # Usage:
+ # To unregister a MIME type:
#
# Mime::Type.unregister(:mobile)
def unregister(symbol)
diff --git a/actionpack/lib/action_dispatch/http/mime_types.rb b/actionpack/lib/action_dispatch/http/mime_types.rb
index 0e4da36038..01a10c693b 100644
--- a/actionpack/lib/action_dispatch/http/mime_types.rb
+++ b/actionpack/lib/action_dispatch/http/mime_types.rb
@@ -27,7 +27,7 @@ Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form
# http://www.ietf.org/rfc/rfc4627.txt
# http://www.json.org/JSONRequest.html
-Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest )
+Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest application/vnd.api+json )
Mime::Type.register "application/pdf", :pdf, [], %w(pdf)
Mime::Type.register "application/zip", :zip, [], %w(zip)
diff --git a/actionpack/lib/action_dispatch/http/parameter_filter.rb b/actionpack/lib/action_dispatch/http/parameter_filter.rb
index df4b073a17..e826551f4b 100644
--- a/actionpack/lib/action_dispatch/http/parameter_filter.rb
+++ b/actionpack/lib/action_dispatch/http/parameter_filter.rb
@@ -30,36 +30,46 @@ module ActionDispatch
when Regexp
regexps << item
else
- strings << item.to_s
+ strings << Regexp.escape(item.to_s)
end
end
- regexps << Regexp.new(strings.join('|'), true) unless strings.empty?
- new regexps, blocks
+ deep_regexps, regexps = regexps.partition { |r| r.to_s.include?("\\.".freeze) }
+ deep_strings, strings = strings.partition { |s| s.include?("\\.".freeze) }
+
+ regexps << Regexp.new(strings.join('|'.freeze), true) unless strings.empty?
+ deep_regexps << Regexp.new(deep_strings.join('|'.freeze), true) unless deep_strings.empty?
+
+ new regexps, deep_regexps, blocks
end
- attr_reader :regexps, :blocks
+ attr_reader :regexps, :deep_regexps, :blocks
- def initialize(regexps, blocks)
+ def initialize(regexps, deep_regexps, blocks)
@regexps = regexps
+ @deep_regexps = deep_regexps.any? ? deep_regexps : nil
@blocks = blocks
end
- def call(original_params)
+ def call(original_params, parents = [])
filtered_params = {}
original_params.each do |key, value|
+ parents.push(key) if deep_regexps
if regexps.any? { |r| key =~ r }
value = FILTERED
+ elsif deep_regexps && (joined = parents.join('.')) && deep_regexps.any? { |r| joined =~ r }
+ value = FILTERED
elsif value.is_a?(Hash)
- value = call(value)
+ value = call(value, parents)
elsif value.is_a?(Array)
- value = value.map { |v| v.is_a?(Hash) ? call(v) : v }
+ value = value.map { |v| v.is_a?(Hash) ? call(v, parents) : v }
elsif blocks.any?
key = key.dup if key.duplicable?
value = value.dup if value.duplicable?
blocks.each { |b| b.call(key, value) }
end
+ parents.pop if deep_regexps
filtered_params[key] = value
end
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index c2f05ecc86..4defb7f858 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -37,22 +37,7 @@ module ActionDispatch
# Convert nested Hash to HashWithIndifferentAccess.
#
def normalize_encode_params(params)
- case params
- when Hash
- if params.has_key?(:tempfile)
- UploadedFile.new(params)
- else
- params.each_with_object({}) do |(key, val), new_hash|
- new_hash[key] = if val.is_a?(Array)
- val.map! { |el| normalize_encode_params(el) }
- else
- normalize_encode_params(val)
- end
- end.with_indifferent_access
- end
- else
- params
- end
+ ActionDispatch::Request::Utils.normalize_encode_params params
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index a1f84e5ace..de28cd0998 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -20,6 +20,8 @@ module ActionDispatch
include ActionDispatch::Http::FilterParameters
include ActionDispatch::Http::URL
+ HTTP_X_REQUEST_ID = "HTTP_X_REQUEST_ID".freeze # :nodoc:
+
autoload :Session, 'action_dispatch/request/session'
autoload :Utils, 'action_dispatch/request/utils'
@@ -32,12 +34,14 @@ module ActionDispatch
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
- HTTP_NEGOTIATE HTTP_PRAGMA ].freeze
+ HTTP_NEGOTIATE HTTP_PRAGMA HTTP_CLIENT_IP
+ HTTP_X_FORWARDED_FOR HTTP_VERSION
+ ].freeze
ENV_METHODS.each do |env|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
def #{env.sub(/^HTTP_/n, '').downcase} # def accept_charset
- @env["#{env}"] # @env["HTTP_ACCEPT_CHARSET"]
+ @env["#{env}".freeze] # @env["HTTP_ACCEPT_CHARSET".freeze]
end # end
METHOD
end
@@ -50,7 +54,6 @@ module ActionDispatch
@original_fullpath = nil
@fullpath = nil
@ip = nil
- @request_id = nil
end
def check_path_parameters!
@@ -102,13 +105,17 @@ module ActionDispatch
# the application should use), this \method returns the overridden
# value, not the original.
def request_method
- @request_method ||= check_method(env["REQUEST_METHOD"])
+ @request_method ||= check_method(super)
end
def routes # :nodoc:
env["action_dispatch.routes".freeze]
end
+ def routes=(routes) # :nodoc:
+ env["action_dispatch.routes".freeze] = routes
+ end
+
def original_script_name # :nodoc:
env['ORIGINAL_SCRIPT_NAME'.freeze]
end
@@ -117,12 +124,31 @@ module ActionDispatch
env[_routes.env_key]
end
+ def engine_script_name=(name) # :nodoc:
+ env[routes.env_key] = name.dup
+ end
+
def request_method=(request_method) #:nodoc:
if check_method(request_method)
@request_method = env["REQUEST_METHOD"] = request_method
end
end
+ def controller_instance # :nodoc:
+ env['action_controller.instance'.freeze]
+ end
+
+ def controller_instance=(controller) # :nodoc:
+ env['action_controller.instance'.freeze] = controller
+ end
+
+ def show_exceptions? # :nodoc:
+ # We're treating `nil` as "unset", and we want the default setting to be
+ # `true`. This logic should be extracted to `env_config` and calculated
+ # once.
+ !(env['action_dispatch.show_exceptions'.freeze] == false)
+ end
+
# Returns a symbol form of the #request_method
def request_method_symbol
HTTP_METHOD_LOOKUP[request_method]
@@ -140,47 +166,11 @@ module ActionDispatch
HTTP_METHOD_LOOKUP[method]
end
- # Is this a GET (or HEAD) request?
- # Equivalent to <tt>request.request_method_symbol == :get</tt>.
- def get?
- HTTP_METHOD_LOOKUP[request_method] == :get
- end
-
- # Is this a POST request?
- # Equivalent to <tt>request.request_method_symbol == :post</tt>.
- def post?
- HTTP_METHOD_LOOKUP[request_method] == :post
- end
-
- # Is this a PATCH request?
- # Equivalent to <tt>request.request_method == :patch</tt>.
- def patch?
- HTTP_METHOD_LOOKUP[request_method] == :patch
- end
-
- # Is this a PUT request?
- # Equivalent to <tt>request.request_method_symbol == :put</tt>.
- def put?
- HTTP_METHOD_LOOKUP[request_method] == :put
- end
-
- # Is this a DELETE request?
- # Equivalent to <tt>request.request_method_symbol == :delete</tt>.
- def delete?
- HTTP_METHOD_LOOKUP[request_method] == :delete
- end
-
- # Is this a HEAD request?
- # Equivalent to <tt>request.request_method_symbol == :head</tt>.
- def head?
- HTTP_METHOD_LOOKUP[request_method] == :head
- end
-
# Provides access to the request's HTTP headers, for example:
#
# request.headers["Content-Type"] # => "text/plain"
def headers
- Http::Headers.new(@env)
+ @headers ||= Http::Headers.new(@env)
end
# Returns a +String+ with the last requested path including their params.
@@ -234,15 +224,23 @@ module ActionDispatch
end
alias :xhr? :xml_http_request?
+ # Returns the IP address of client as a +String+.
def ip
@ip ||= super
end
- # Originating IP address, usually set by the RemoteIp middleware.
+ # Returns the IP address of client as a +String+,
+ # usually set by the RemoteIp middleware.
def remote_ip
@remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s
end
+ def remote_ip=(remote_ip)
+ @env["action_dispatch.remote_ip".freeze] = remote_ip
+ end
+
+ ACTION_DISPATCH_REQUEST_ID = "action_dispatch.request_id".freeze # :nodoc:
+
# Returns the unique request id, which is based on either the X-Request-Id header that can
# be generated by a firewall, load balancer, or web server or by the RequestId middleware
# (which sets the action_dispatch.request_id environment variable).
@@ -250,11 +248,19 @@ module ActionDispatch
# This unique ID is useful for tracing a request from end-to-end as part of logging or debugging.
# This relies on the rack variable set by the ActionDispatch::RequestId middleware.
def request_id
- @request_id ||= env["action_dispatch.request_id"]
+ env[ACTION_DISPATCH_REQUEST_ID]
+ end
+
+ def request_id=(id) # :nodoc:
+ env[ACTION_DISPATCH_REQUEST_ID] = id
end
alias_method :uuid, :request_id
+ def x_request_id # :nodoc:
+ @env[HTTP_X_REQUEST_ID]
+ end
+
# Returns the lowercase name of the HTTP server software.
def server_software
(@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
@@ -282,6 +288,8 @@ module ActionDispatch
end
end
+ # Returns true if the request's content MIME type is
+ # +application/x-www-form-urlencoded+ or +multipart/form-data+.
def form_data?
FORM_DATA_MEDIA_TYPES.include?(content_mime_type.to_s)
end
@@ -311,7 +319,7 @@ module ActionDispatch
# Override Rack's GET method to support indifferent access
def GET
- @env["action_dispatch.request.query_parameters"] ||= Utils.deep_munge(normalize_encode_params(super || {}))
+ @env["action_dispatch.request.query_parameters"] ||= normalize_encode_params(super || {})
rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
raise ActionController::BadRequest.new(:query, e)
end
@@ -319,7 +327,7 @@ module ActionDispatch
# Override Rack's POST method to support indifferent access
def POST
- @env["action_dispatch.request.request_parameters"] ||= Utils.deep_munge(normalize_encode_params(super || {}))
+ @env["action_dispatch.request.request_parameters"] ||= normalize_encode_params(super || {})
rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
raise ActionController::BadRequest.new(:request, e)
end
@@ -339,10 +347,13 @@ module ActionDispatch
LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip
end
- protected
- def parse_query(*)
- Utils.deep_munge(super)
- end
+ def request_parameters=(params)
+ env["action_dispatch.request.request_parameters".freeze] = params
+ end
+
+ def logger
+ env["action_dispatch.logger".freeze]
+ end
private
def check_method(name)
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index a895d1ab18..fd92e89231 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -40,10 +40,9 @@ module ActionDispatch # :nodoc:
attr_writer :sending_file
- # Get and set headers for this response.
- attr_accessor :header
+ # Get headers for this response.
+ attr_reader :header
- alias_method :headers=, :header=
alias_method :headers, :header
delegate :[], :[]=, :to => :@header
@@ -61,7 +60,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.
- attr_accessor :charset
+ attr_reader :charset
CONTENT_TYPE = "Content-Type".freeze
SET_COOKIE = "Set-Cookie".freeze
@@ -81,11 +80,21 @@ module ActionDispatch # :nodoc:
@response = response
@buf = buf
@closed = false
+ @str_body = nil
+ end
+
+ def body
+ @str_body ||= begin
+ buf = ''
+ each { |chunk| buf << chunk }
+ buf
+ end
end
def write(string)
raise IOError, "closed stream" if closed?
+ @str_body = nil
@response.commit!
@buf.push string
end
@@ -117,8 +126,9 @@ module ActionDispatch # :nodoc:
super()
header = merge_default_headers(header, default_headers)
+ @header = header
- self.body, self.header, self.status = body, header, status
+ self.body, self.status = body, status
@sending_file = false
@blank = false
@@ -127,7 +137,7 @@ module ActionDispatch # :nodoc:
@sending = false
@sent = false
@content_type = nil
- @charset = nil
+ @charset = self.class.default_charset
if content_type = self[CONTENT_TYPE]
type, charset = content_type.split(/;\s*charset=/)
@@ -187,6 +197,15 @@ module ActionDispatch # :nodoc:
@content_type = content_type.to_s
end
+ # 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)
+ @charset = charset.nil? ? self.class.default_charset : charset
+ end
+
# The response code of the request.
def response_code
@status
@@ -213,9 +232,7 @@ module ActionDispatch # :nodoc:
# Returns the content of the response as a string. This contains the contents
# of any calls to <tt>render</tt>.
def body
- strings = []
- each { |part| strings << part.to_s }
- strings.join
+ @stream.body
end
EMPTY = " "
@@ -274,10 +291,11 @@ module ActionDispatch # :nodoc:
end
# Turns the Response into a Rack-compatible array of the status, headers,
- # and body. Allows explict splatting:
+ # and body. Allows explicit splatting:
#
# status, headers, body = *response
def to_a
+ commit!
rack_response @status, @header.to_hash
end
alias prepare! to_a
@@ -302,6 +320,9 @@ module ActionDispatch # :nodoc:
private
def before_committed
+ return if committed?
+ assign_default_content_type_and_charset!
+ handle_conditional_get!
end
def before_sending
@@ -319,16 +340,15 @@ module ActionDispatch # :nodoc:
body.respond_to?(:each) ? body : [body]
end
- def assign_default_content_type_and_charset!(headers)
- return if headers[CONTENT_TYPE].present?
+ def assign_default_content_type_and_charset!
+ return if self[CONTENT_TYPE].present?
@content_type ||= Mime::HTML
- @charset ||= self.class.default_charset unless @charset == false
type = @content_type.to_s.dup
- type << "; charset=#{@charset}" if append_charset?
+ type << "; charset=#{charset}" if append_charset?
- headers[CONTENT_TYPE] = type
+ self[CONTENT_TYPE] = type
end
def append_charset?
@@ -372,9 +392,6 @@ module ActionDispatch # :nodoc:
end
def rack_response(status, header)
- assign_default_content_type_and_charset!(header)
- handle_conditional_get!
-
header[SET_COOKIE] = header[SET_COOKIE].join("\n") if header[SET_COOKIE].respond_to?(:join)
if NO_CONTENT_CODES.include?(@status)
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index 540e11a4a0..a221f4c5af 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -28,7 +28,13 @@ module ActionDispatch
raise(ArgumentError, ':tempfile is required') unless @tempfile
@original_filename = hash[:filename]
- @original_filename &&= @original_filename.encode "UTF-8"
+ if @original_filename
+ begin
+ @original_filename.encode!(Encoding::UTF_8)
+ rescue EncodingError
+ @original_filename.force_encoding(Encoding::UTF_8)
+ end
+ end
@content_type = hash[:type]
@headers = hash[:head]
end
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index f5b709ccd6..6fcf49030b 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -245,7 +245,7 @@ module ActionDispatch
# req = Request.new 'HTTP_HOST' => 'example.com:8080'
# req.host # => "example.com"
def host
- raw_host_with_port.sub(/:\d+$/, '')
+ raw_host_with_port.sub(/:\d+$/, ''.freeze)
end
# Returns a \host:\port string for this request, such as "example.com" or
diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb
index 992c1a9efe..d8bb10ffab 100644
--- a/actionpack/lib/action_dispatch/journey/formatter.rb
+++ b/actionpack/lib/action_dispatch/journey/formatter.rb
@@ -14,7 +14,7 @@ module ActionDispatch
def generate(name, options, path_parameters, parameterize = nil)
constraints = path_parameters.merge(options)
- missing_keys = []
+ missing_keys = nil # need for variable scope
match_route(name, constraints) do |route|
parameterized_parts = extract_parameterized_parts(route, options, path_parameters, parameterize)
@@ -25,22 +25,22 @@ module ActionDispatch
next unless name || route.dispatcher?
missing_keys = missing_keys(route, parameterized_parts)
- next unless missing_keys.empty?
+ next if missing_keys && !missing_keys.empty?
params = options.dup.delete_if do |key, _|
parameterized_parts.key?(key) || route.defaults.key?(key)
end
defaults = route.defaults
required_parts = route.required_parts
- parameterized_parts.delete_if do |key, value|
- value.to_s == defaults[key].to_s && !required_parts.include?(key)
+ parameterized_parts.keep_if do |key, value|
+ defaults[key].nil? || value.to_s != defaults[key].to_s || required_parts.include?(key)
end
return [route.format(parameterized_parts), params]
end
- message = "No route matches #{Hash[constraints.sort].inspect}"
- message << " missing required keys: #{missing_keys.sort.inspect}" unless missing_keys.empty?
+ 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?
raise ActionController::UrlGenerationError, message
end
@@ -54,12 +54,12 @@ module ActionDispatch
def extract_parameterized_parts(route, options, recall, parameterize = nil)
parameterized_parts = recall.merge(options)
- keys_to_keep = route.parts.reverse.drop_while { |part|
+ keys_to_keep = route.parts.reverse_each.drop_while { |part|
!options.key?(part) || (options[part] || recall[part]).nil?
} | route.required_parts
- (parameterized_parts.keys - keys_to_keep).each do |bad_key|
- parameterized_parts.delete(bad_key)
+ parameterized_parts.delete_if do |bad_key, _|
+ !keys_to_keep.include?(bad_key)
end
if parameterize
@@ -110,15 +110,36 @@ module ActionDispatch
routes
end
+ module RegexCaseComparator
+ DEFAULT_INPUT = /[-_.a-zA-Z0-9]+\/[-_.a-zA-Z0-9]+/
+ DEFAULT_REGEX = /\A#{DEFAULT_INPUT}\Z/
+
+ def self.===(regex)
+ DEFAULT_INPUT == regex
+ end
+ end
+
# Returns an array populated with missing keys if any are present.
def missing_keys(route, parts)
- missing_keys = []
+ missing_keys = nil
tests = route.path.requirements
route.required_parts.each { |key|
- if tests.key?(key)
- missing_keys << key unless /\A#{tests[key]}\Z/ === parts[key]
+ case tests[key]
+ when nil
+ unless parts[key]
+ missing_keys ||= []
+ missing_keys << key
+ end
+ when RegexCaseComparator
+ unless RegexCaseComparator::DEFAULT_REGEX === parts[key]
+ missing_keys ||= []
+ missing_keys << key
+ end
else
- missing_keys << key unless parts[key]
+ unless /\A#{tests[key]}\Z/ === parts[key]
+ missing_keys ||= []
+ missing_keys << key
+ end
end
}
missing_keys
diff --git a/actionpack/lib/action_dispatch/journey/nodes/node.rb b/actionpack/lib/action_dispatch/journey/nodes/node.rb
index bb01c087bc..cf6542b370 100644
--- a/actionpack/lib/action_dispatch/journey/nodes/node.rb
+++ b/actionpack/lib/action_dispatch/journey/nodes/node.rb
@@ -30,7 +30,7 @@ module ActionDispatch
end
def name
- left.tr '*:', ''
+ left.tr '*:'.freeze, ''.freeze
end
def type
diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb
index 4d5c18984a..cbc985640a 100644
--- a/actionpack/lib/action_dispatch/journey/route.rb
+++ b/actionpack/lib/action_dispatch/journey/route.rb
@@ -11,7 +11,7 @@ module ActionDispatch
##
# +path+ is a path constraint.
# +constraints+ is a hash of constraints to be applied to this route.
- def initialize(name, app, path, constraints, defaults = {})
+ def initialize(name, app, path, constraints, required_defaults, defaults)
@name = name
@app = app
@path = path
@@ -19,6 +19,7 @@ module ActionDispatch
@constraints = constraints
@defaults = defaults
@required_defaults = nil
+ @_required_defaults = required_defaults || []
@required_parts = nil
@parts = nil
@decorated_ast = nil
@@ -36,7 +37,7 @@ module ActionDispatch
def requirements # :nodoc:
# needed for rails `rake routes`
- path.requirements.merge(@defaults).delete_if { |_,v|
+ @defaults.merge(path.requirements).delete_if { |_,v|
/.+?/ == v
}
end
@@ -73,7 +74,7 @@ module ActionDispatch
end
def required_default?(key)
- (constraints[:required_defaults] || []).include?(key)
+ @_required_defaults.include?(key)
end
def required_defaults
@@ -92,8 +93,6 @@ module ActionDispatch
def matches?(request)
constraints.all? do |method, value|
- next true unless request.respond_to?(method)
-
case value
when Regexp, String
value === request.send(method).to_s
diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb
index cc4bd6105d..b84aad8eb6 100644
--- a/actionpack/lib/action_dispatch/journey/router.rb
+++ b/actionpack/lib/action_dispatch/journey/router.rb
@@ -121,7 +121,8 @@ module ActionDispatch
end
def match_head_routes(routes, req)
- head_routes = match_routes(routes, req)
+ verb_specific_routes = routes.reject { |route| route.verb == // }
+ head_routes = match_routes(verb_specific_routes, req)
if head_routes.empty?
begin
diff --git a/actionpack/lib/action_dispatch/journey/router/utils.rb b/actionpack/lib/action_dispatch/journey/router/utils.rb
index 2b0a6575d4..9793ca1c7a 100644
--- a/actionpack/lib/action_dispatch/journey/router/utils.rb
+++ b/actionpack/lib/action_dispatch/journey/router/utils.rb
@@ -14,10 +14,10 @@ module ActionDispatch
# normalize_path("/%ab") # => "/%AB"
def self.normalize_path(path)
path = "/#{path}"
- path.squeeze!('/')
- path.sub!(%r{/+\Z}, '')
+ path.squeeze!('/'.freeze)
+ path.sub!(%r{/+\Z}, ''.freeze)
path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase }
- path = '/' if path == ''
+ path = '/' if path == ''.freeze
path
end
@@ -55,7 +55,7 @@ module ActionDispatch
def unescape_uri(uri)
encoding = uri.encoding == US_ASCII ? UTF_8 : uri.encoding
- uri.gsub(ESCAPED) { [$&[1, 2].hex].pack('C') }.force_encoding(encoding)
+ uri.gsub(ESCAPED) { |match| [match[1, 2].hex].pack('C') }.force_encoding(encoding)
end
protected
diff --git a/actionpack/lib/action_dispatch/journey/routes.rb b/actionpack/lib/action_dispatch/journey/routes.rb
index a6d1980db2..5990964b57 100644
--- a/actionpack/lib/action_dispatch/journey/routes.rb
+++ b/actionpack/lib/action_dispatch/journey/routes.rb
@@ -16,6 +16,10 @@ module ActionDispatch
@simulator = nil
end
+ def empty?
+ routes.empty?
+ end
+
def length
routes.length
end
@@ -59,8 +63,8 @@ module ActionDispatch
end
# Add a route to the routing table.
- def add_route(app, path, conditions, defaults, name = nil)
- route = Route.new(name, app, path, conditions, defaults)
+ def add_route(app, path, conditions, required_defaults, defaults, name = nil)
+ route = Route.new(name, app, path, conditions, required_defaults, defaults)
route.precedence = routes.length
routes << route
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index b7687ca100..cf4f654ed6 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -8,8 +8,50 @@ require 'active_support/json'
module ActionDispatch
class Request < Rack::Request
def cookie_jar
- env['action_dispatch.cookies'] ||= Cookies::CookieJar.build(self)
+ env['action_dispatch.cookies'.freeze] ||= Cookies::CookieJar.build(self, cookies)
end
+
+ # :stopdoc:
+ def have_cookie_jar?
+ env.key? 'action_dispatch.cookies'.freeze
+ end
+
+ def cookie_jar=(jar)
+ env['action_dispatch.cookies'.freeze] = jar
+ end
+
+ def key_generator
+ env[Cookies::GENERATOR_KEY]
+ end
+
+ def signed_cookie_salt
+ env[Cookies::SIGNED_COOKIE_SALT]
+ end
+
+ def encrypted_cookie_salt
+ env[Cookies::ENCRYPTED_COOKIE_SALT]
+ end
+
+ def encrypted_signed_cookie_salt
+ env[Cookies::ENCRYPTED_SIGNED_COOKIE_SALT]
+ end
+
+ def secret_token
+ env[Cookies::SECRET_TOKEN]
+ end
+
+ def secret_key_base
+ env[Cookies::SECRET_KEY_BASE]
+ end
+
+ def cookies_serializer
+ env[Cookies::COOKIES_SERIALIZER]
+ end
+
+ def cookies_digest
+ env[Cookies::COOKIES_DIGEST]
+ end
+ # :startdoc:
end
# \Cookies are read and written through ActionController#cookies.
@@ -79,6 +121,9 @@ module ActionDispatch
# domain: %w(.example.com .example.org) # Allow the cookie
# # for concrete domain names.
#
+ # * <tt>:tld_length</tt> - When using <tt>:domain => :all</tt>, this option can be used to explicitly
+ # set the TLD length when using a short (<= 3 character) domain that is being interpreted as part of a TLD.
+ # For example, to share cookies between user1.lvh.me and user2.lvh.me, set <tt>:tld_length</tt> to 1.
# * <tt>:expires</tt> - The time at which this cookie expires, as a \Time object.
# * <tt>:secure</tt> - Whether this cookie is only transmitted to HTTPS servers.
# Default is +false+.
@@ -115,7 +160,7 @@ module ActionDispatch
# cookies.permanent.signed[:remember_me] = current_user.id
# # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
def permanent
- @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
+ @permanent ||= PermanentCookieJar.new(self)
end
# Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
@@ -135,10 +180,10 @@ module ActionDispatch
# cookies.signed[:discount] # => 45
def signed
@signed ||=
- if @options[:upgrade_legacy_signed_cookies]
- UpgradeLegacySignedCookieJar.new(self, @key_generator, @options)
+ if upgrade_legacy_signed_cookies?
+ UpgradeLegacySignedCookieJar.new(self)
else
- SignedCookieJar.new(self, @key_generator, @options)
+ SignedCookieJar.new(self)
end
end
@@ -158,10 +203,10 @@ module ActionDispatch
# cookies.encrypted[:discount] # => 45
def encrypted
@encrypted ||=
- if @options[:upgrade_legacy_signed_cookies]
- UpgradeLegacyEncryptedCookieJar.new(self, @key_generator, @options)
+ if upgrade_legacy_signed_cookies?
+ UpgradeLegacyEncryptedCookieJar.new(self)
else
- EncryptedCookieJar.new(self, @key_generator, @options)
+ EncryptedCookieJar.new(self)
end
end
@@ -169,22 +214,36 @@ module ActionDispatch
# Used by ActionDispatch::Session::CookieStore to avoid the need to introduce new cookie stores.
def signed_or_encrypted
@signed_or_encrypted ||=
- if @options[:secret_key_base].present?
+ if request.secret_key_base.present?
encrypted
else
signed
end
end
+
+ protected
+
+ def request; @parent_jar.request; end
+
+ private
+
+ def upgrade_legacy_signed_cookies?
+ request.secret_token.present? && request.secret_key_base.present?
+ end
+
+ def key_generator
+ request.key_generator
+ end
end
# Passing the ActiveSupport::MessageEncryptor::NullSerializer downstream
# to the Message{Encryptor,Verifier} allows us to handle the
# (de)serialization step within the cookie jar, which gives us the
# opportunity to detect and migrate legacy cookies.
- module VerifyAndUpgradeLegacySignedMessage
+ module VerifyAndUpgradeLegacySignedMessage # :nodoc:
def initialize(*args)
super
- @legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token], serializer: ActiveSupport::MessageEncryptor::NullSerializer)
+ @legacy_verifier = ActiveSupport::MessageVerifier.new(request.secret_token, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
end
def verify_and_upgrade_legacy_signed_message(name, signed_message)
@@ -213,38 +272,18 @@ module ActionDispatch
# $& => example.local
DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
- def self.options_for_env(env) #:nodoc:
- { signed_cookie_salt: env[SIGNED_COOKIE_SALT] || '',
- encrypted_cookie_salt: env[ENCRYPTED_COOKIE_SALT] || '',
- encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT] || '',
- secret_token: env[SECRET_TOKEN],
- secret_key_base: env[SECRET_KEY_BASE],
- upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?,
- serializer: env[COOKIES_SERIALIZER],
- digest: env[COOKIES_DIGEST]
- }
- end
-
- def self.build(request)
- env = request.env
- key_generator = env[GENERATOR_KEY]
- options = options_for_env env
-
- host = request.host
- secure = request.ssl?
-
- new(key_generator, host, secure, options).tap do |hash|
- hash.update(request.cookies)
+ def self.build(req, cookies)
+ new(req).tap do |hash|
+ hash.update(cookies)
end
end
- def initialize(key_generator, host = nil, secure = false, options = {})
- @key_generator = key_generator
+ attr_reader :request
+
+ def initialize(request)
@set_cookies = {}
@delete_cookies = {}
- @host = host
- @secure = secure
- @options = options
+ @request = request
@cookies = {}
@committed = false
end
@@ -280,6 +319,10 @@ module ActionDispatch
self
end
+ def to_header
+ @cookies.map { |k,v| "#{k}=#{v}" }.join ';'
+ end
+
def handle_options(options) #:nodoc:
options[:path] ||= "/"
@@ -289,12 +332,12 @@ module ActionDispatch
# if host is not ip and matches domain regexp
# (ip confirms to domain regexp so we explicitly check for ip)
- options[:domain] = if (@host !~ /^[\d.]+$/) && (@host =~ domain_regexp)
+ options[:domain] = if (request.host !~ /^[\d.]+$/) && (request.host =~ domain_regexp)
".#{$&}"
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| @host.include? domain.sub(/^\./, '') }
+ options[:domain] = options[:domain].find {|domain| request.host.include? domain.sub(/^\./, '') }
end
end
@@ -353,27 +396,20 @@ module ActionDispatch
@delete_cookies.each { |k, v| ::Rack::Utils.delete_cookie_header!(headers, k, v) }
end
- def recycle! #:nodoc:
- @set_cookies = {}
- @delete_cookies = {}
- end
-
mattr_accessor :always_write_cookie
self.always_write_cookie = false
private
def write_cookie?(cookie)
- @secure || !cookie[:secure] || always_write_cookie
+ request.ssl? || !cookie[:secure] || always_write_cookie
end
end
class PermanentCookieJar #:nodoc:
include ChainedCookieJars
- def initialize(parent_jar, key_generator, options = {})
+ def initialize(parent_jar)
@parent_jar = parent_jar
- @key_generator = key_generator
- @options = options
end
def [](name)
@@ -392,7 +428,7 @@ module ActionDispatch
end
end
- class JsonSerializer
+ class JsonSerializer # :nodoc:
def self.load(value)
ActiveSupport::JSON.decode(value)
end
@@ -402,12 +438,12 @@ module ActionDispatch
end
end
- module SerializedCookieJars
+ module SerializedCookieJars # :nodoc:
MARSHAL_SIGNATURE = "\x04\x08".freeze
protected
def needs_migration?(value)
- @options[:serializer] == :hybrid && value.start_with?(MARSHAL_SIGNATURE)
+ request.cookies_serializer == :hybrid && value.start_with?(MARSHAL_SIGNATURE)
end
def serialize(value)
@@ -427,7 +463,7 @@ module ActionDispatch
end
def serializer
- serializer = @options[:serializer] || :marshal
+ serializer = request.cookies_serializer || :marshal
case serializer
when :marshal
Marshal
@@ -439,7 +475,7 @@ module ActionDispatch
end
def digest
- @options[:digest] || 'SHA1'
+ request.cookies_digest || 'SHA1'
end
end
@@ -447,19 +483,22 @@ module ActionDispatch
include ChainedCookieJars
include SerializedCookieJars
- def initialize(parent_jar, key_generator, options = {})
+ def initialize(parent_jar)
@parent_jar = parent_jar
- @options = options
- secret = key_generator.generate_key(@options[:signed_cookie_salt])
+ secret = key_generator.generate_key(request.signed_cookie_salt)
@verifier = ActiveSupport::MessageVerifier.new(secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
end
+ # Returns the value of the cookie by +name+ if it is untampered,
+ # returns +nil+ otherwise or if no such cookie exists.
def [](name)
if signed_message = @parent_jar[name]
deserialize name, verify(signed_message)
end
end
+ # Signs and sets the cookie named +name+. The second argument may be the cookie's
+ # value or a hash of options as documented above.
def []=(name, options)
if options.is_a?(Hash)
options.symbolize_keys!
@@ -482,8 +521,8 @@ module ActionDispatch
# UpgradeLegacySignedCookieJar is used instead of SignedCookieJar if
# secrets.secret_token and secrets.secret_key_base are both set. It reads
- # legacy cookies signed with the old dummy key generator and re-saves
- # them using the new key generator to provide a smooth upgrade path.
+ # legacy cookies signed with the old dummy key generator and signs and
+ # re-saves them using the new key generator to provide a smooth upgrade path.
class UpgradeLegacySignedCookieJar < SignedCookieJar #:nodoc:
include VerifyAndUpgradeLegacySignedMessage
@@ -498,25 +537,29 @@ module ActionDispatch
include ChainedCookieJars
include SerializedCookieJars
- def initialize(parent_jar, key_generator, options = {})
+ def initialize(parent_jar)
+ @parent_jar = parent_jar
+
if ActiveSupport::LegacyKeyGenerator === key_generator
raise "You didn't set secrets.secret_key_base, which is required for this cookie jar. " +
"Read the upgrade documentation to learn more about this new config option."
end
- @parent_jar = parent_jar
- @options = options
- secret = key_generator.generate_key(@options[:encrypted_cookie_salt])
- sign_secret = key_generator.generate_key(@options[:encrypted_signed_cookie_salt])
+ secret = key_generator.generate_key(request.encrypted_cookie_salt || '')
+ 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
+ # Returns the value of the cookie by +name+ if it is untampered,
+ # returns +nil+ otherwise or if no such cookie exists.
def [](name)
if encrypted_message = @parent_jar[name]
deserialize name, decrypt_and_verify(encrypted_message)
end
end
+ # Encrypts and sets the cookie named +name+. The second argument may be the cookie's
+ # value or a hash of options as documented above.
def []=(name, options)
if options.is_a?(Hash)
options.symbolize_keys!
@@ -557,9 +600,12 @@ module ActionDispatch
end
def call(env)
+ request = ActionDispatch::Request.new env
+
status, headers, body = @app.call(env)
- if cookie_jar = env['action_dispatch.cookies']
+ if request.have_cookie_jar?
+ cookie_jar = request.cookie_jar
unless cookie_jar.committed?
cookie_jar.write(headers)
if headers[HTTP_HEADER].respond_to?(:join)
diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
index 9082aac271..226a688fe2 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -44,6 +44,7 @@ module ActionDispatch
end
def call(env)
+ request = ActionDispatch::Request.new env
_, headers, body = response = @app.call(env)
if headers['X-Cascade'] == 'pass'
@@ -53,14 +54,15 @@ module ActionDispatch
response
rescue Exception => exception
- raise exception if env['action_dispatch.show_exceptions'] == false
+ raise exception unless request.show_exceptions?
render_exception(env, exception)
end
private
def render_exception(env, exception)
- wrapper = ExceptionWrapper.new(env, exception)
+ backtrace_cleaner = env['action_dispatch.backtrace_cleaner']
+ wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
log_error(env, wrapper)
if env['action_dispatch.show_detailed_exceptions']
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index d176a73633..039efc3af8 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -17,7 +17,9 @@ module ActionDispatch
'ActionController::InvalidCrossOriginRequest' => :unprocessable_entity,
'ActionDispatch::ParamsParser::ParseError' => :bad_request,
'ActionController::BadRequest' => :bad_request,
- 'ActionController::ParameterMissing' => :bad_request
+ 'ActionController::ParameterMissing' => :bad_request,
+ 'Rack::Utils::ParameterTypeError' => :bad_request,
+ 'Rack::Utils::InvalidParameterError' => :bad_request
)
cattr_accessor :rescue_templates
@@ -29,10 +31,10 @@ module ActionDispatch
'ActionView::Template::Error' => 'template_error'
)
- attr_reader :env, :exception, :line_number, :file
+ attr_reader :backtrace_cleaner, :exception, :line_number, :file
- def initialize(env, exception)
- @env = env
+ def initialize(backtrace_cleaner, exception)
+ @backtrace_cleaner = backtrace_cleaner
@exception = original_exception(exception)
expand_backtrace if exception.is_a?(SyntaxError) || exception.try(:original_exception).try(:is_a?, SyntaxError)
@@ -123,10 +125,6 @@ module ActionDispatch
end
end
- def backtrace_cleaner
- @backtrace_cleaner ||= @env['action_dispatch.backtrace_cleaner']
- end
-
def source_fragment(path, line)
return unless Rails.respond_to?(:root) && Rails.root
full_path = Rails.root.join(path)
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index 59639a010e..23da169b22 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -8,6 +8,14 @@ module ActionDispatch
def flash
@env[Flash::KEY] ||= Flash::FlashHash.from_session_value(session["flash"])
end
+
+ def flash=(flash)
+ @env[Flash::KEY] = flash
+ end
+
+ def flash_hash # :nodoc:
+ @env[Flash::KEY]
+ end
end
# The flash provides a way to pass temporary primitive-types (String, Array, Hash) between actions. Anything you place in the flash will be exposed
@@ -263,14 +271,15 @@ module ActionDispatch
end
def call(env)
+ req = ActionDispatch::Request.new env
@app.call(env)
ensure
session = Request::Session.find(env) || {}
- flash_hash = env[KEY]
+ flash_hash = req.flash_hash
if flash_hash && (flash_hash.present? || session.key?('flash'))
session["flash"] = flash_hash.to_session_value
- env[KEY] = flash_hash.dup
+ req.flash = flash_hash.dup
end
if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?)
diff --git a/actionpack/lib/action_dispatch/middleware/load_interlock.rb b/actionpack/lib/action_dispatch/middleware/load_interlock.rb
new file mode 100644
index 0000000000..07f498319c
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/load_interlock.rb
@@ -0,0 +1,21 @@
+require 'active_support/dependencies'
+require 'rack/body_proxy'
+
+module ActionDispatch
+ class LoadInterlock
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ interlock = ActiveSupport::Dependencies.interlock
+ interlock.start_running
+ response = @app.call(env)
+ body = Rack::BodyProxy.new(response[2]) { interlock.done_running }
+ response[2] = body
+ response
+ ensure
+ interlock.done_running unless body
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb
index 29d43faeed..e65279a285 100644
--- a/actionpack/lib/action_dispatch/middleware/params_parser.rb
+++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb
@@ -13,48 +13,42 @@ module ActionDispatch
end
end
- DEFAULT_PARSERS = { Mime::JSON => :json }
+ DEFAULT_PARSERS = {
+ Mime::JSON => lambda { |raw_post|
+ data = ActiveSupport::JSON.decode(raw_post)
+ data = {:_json => data} unless data.is_a?(Hash)
+ Request::Utils.normalize_encode_params(data)
+ }
+ }
def initialize(app, parsers = {})
@app, @parsers = app, DEFAULT_PARSERS.merge(parsers)
end
def call(env)
- if params = parse_formatted_parameters(env)
- env["action_dispatch.request.request_parameters"] = params
- end
+ request = Request.new(env)
+
+ request.request_parameters = parse_formatted_parameters(request, @parsers)
@app.call(env)
end
private
- def parse_formatted_parameters(env)
- request = Request.new(env)
-
- return false if request.content_length.zero?
+ def parse_formatted_parameters(request, parsers)
+ return if request.content_length.zero?
- strategy = @parsers[request.content_mime_type]
+ strategy = parsers.fetch(request.content_mime_type) { return nil }
- return false unless strategy
+ strategy.call(request.raw_post)
- case strategy
- when Proc
- strategy.call(request.raw_post)
- when :json
- data = ActiveSupport::JSON.decode(request.raw_post)
- data = {:_json => data} unless data.is_a?(Hash)
- Request::Utils.deep_munge(data).with_indifferent_access
- else
- false
- end
rescue => e # JSON or Ruby code block errors
- logger(env).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
+ logger(request).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
raise ParseError.new(e.message, e)
end
- def logger(env)
- env['action_dispatch.logger'] || ActiveSupport::Logger.new($stderr)
+ def logger(request)
+ request.logger || ActiveSupport::Logger.new($stderr)
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb
index 15b5a48535..6c7fba00cb 100644
--- a/actionpack/lib/action_dispatch/middleware/reloader.rb
+++ b/actionpack/lib/action_dispatch/middleware/reloader.rb
@@ -11,9 +11,9 @@ module ActionDispatch
# the response body. This is important for streaming responses such as the
# following:
#
- # self.response_body = lambda { |response, output|
+ # self.response_body = -> (response, output) do
# # code here which refers to application models
- # }
+ # end
#
# Cleanup callbacks will not be called until after the response_body lambda
# is evaluated, ensuring that it can refer to application models and other
diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
index 7c4236518d..aee2334da9 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -74,18 +74,19 @@ module ActionDispatch
# requests. For those requests that do need to know the IP, the
# GetIp#calculate_ip method will calculate the memoized client IP address.
def call(env)
- env["action_dispatch.remote_ip"] = GetIp.new(env, self)
- @app.call(env)
+ req = ActionDispatch::Request.new env
+ req.remote_ip = GetIp.new(req, check_ip, proxies)
+ @app.call(req.env)
end
# The GetIp class exists as a way to defer processing of the request data
# into an actual IP address. If the ActionDispatch::Request#remote_ip method
# is called, this class will calculate the value and then memoize it.
class GetIp
- def initialize(env, middleware)
- @env = env
- @check_ip = middleware.check_ip
- @proxies = middleware.proxies
+ def initialize(req, check_ip, proxies)
+ @req = req
+ @check_ip = check_ip
+ @proxies = proxies
end
# Sort through the various IP address headers, looking for the IP most
@@ -108,11 +109,11 @@ module ActionDispatch
# the last address left, which was presumably set by one of those proxies.
def calculate_ip
# Set by the Rack web server, this is a single value.
- remote_addr = ips_from('REMOTE_ADDR').last
+ remote_addr = ips_from(@req.remote_addr).last
# Could be a CSV list and/or repeated headers that were concatenated.
- client_ips = ips_from('HTTP_CLIENT_IP').reverse
- forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR').reverse
+ client_ips = ips_from(@req.client_ip).reverse
+ forwarded_ips = ips_from(@req.x_forwarded_for).reverse
# +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set.
# If they are both set, it means that this request passed through two
@@ -123,8 +124,8 @@ module ActionDispatch
if should_check_ip && !forwarded_ips.include?(client_ips.last)
# We don't know which came from the proxy, and which from the user
raise IpSpoofAttackError, "IP spoofing attack?! " +
- "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect} " +
- "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}"
+ "HTTP_CLIENT_IP=#{@req.client_ip.inspect} " +
+ "HTTP_X_FORWARDED_FOR=#{@req.x_forwarded_for.inspect}"
end
# We assume these things about the IP headers:
@@ -147,8 +148,9 @@ module ActionDispatch
protected
def ips_from(header)
+ return [] unless header
# Split the comma-separated list into an array of strings
- ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : []
+ ips = header.strip.split(/[,\s]+/)
ips.select do |ip|
begin
# Only return IPs that are valid according to the IPAddr#new method
diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb
index b9ca524309..1555ff72af 100644
--- a/actionpack/lib/action_dispatch/middleware/request_id.rb
+++ b/actionpack/lib/action_dispatch/middleware/request_id.rb
@@ -13,22 +13,23 @@ module ActionDispatch
# from multiple pieces of the stack.
class RequestId
X_REQUEST_ID = "X-Request-Id".freeze # :nodoc:
- ACTION_DISPATCH_REQUEST_ID = "action_dispatch.request_id".freeze # :nodoc:
- HTTP_X_REQUEST_ID = "HTTP_X_REQUEST_ID".freeze # :nodoc:
def initialize(app)
@app = app
end
def call(env)
- env[ACTION_DISPATCH_REQUEST_ID] = external_request_id(env) || internal_request_id
- @app.call(env).tap { |_status, headers, _body| headers[X_REQUEST_ID] = env[ACTION_DISPATCH_REQUEST_ID] }
+ req = ActionDispatch::Request.new env
+ req.request_id = make_request_id(req.x_request_id)
+ @app.call(env).tap { |_status, headers, _body| headers[X_REQUEST_ID] = req.request_id }
end
private
- def external_request_id(env)
- if request_id = env[HTTP_X_REQUEST_ID].presence
+ def make_request_id(request_id)
+ if request_id.presence
request_id.gsub(/[^\w\-]/, "".freeze).first(255)
+ else
+ internal_request_id
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index f0779279c1..12d8dab8eb 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -27,19 +27,21 @@ module ActionDispatch
end
def call(env)
+ request = ActionDispatch::Request.new env
@app.call(env)
rescue Exception => exception
- if env['action_dispatch.show_exceptions'] == false
- raise exception
- else
+ if request.show_exceptions?
render_exception(env, exception)
+ else
+ raise exception
end
end
private
def render_exception(env, exception)
- wrapper = ExceptionWrapper.new(env, exception)
+ backtrace_cleaner = env['action_dispatch.backtrace_cleaner']
+ wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
status = wrapper.status_code
env["action_dispatch.exception"] = wrapper.exception
env["action_dispatch.original_path"] = env["PATH_INFO"]
diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb
index 0c7caef25d..7b3d8bcc5b 100644
--- a/actionpack/lib/action_dispatch/middleware/ssl.rb
+++ b/actionpack/lib/action_dispatch/middleware/ssl.rb
@@ -22,7 +22,7 @@ module ActionDispatch
if request.ssl?
status, headers, body = @app.call(env)
- headers = hsts_headers.merge(headers)
+ headers.reverse_merge!(hsts_headers)
flag_cookies_as_secure!(headers)
[status, headers, body]
else
diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb
index bbf734f103..0430ce3b9a 100644
--- a/actionpack/lib/action_dispatch/middleware/stack.rb
+++ b/actionpack/lib/action_dispatch/middleware/stack.rb
@@ -4,36 +4,15 @@ require "active_support/dependencies"
module ActionDispatch
class MiddlewareStack
class Middleware
- attr_reader :args, :block, :name, :classcache
+ attr_reader :args, :block, :klass
- def initialize(klass_or_name, *args, &block)
- @klass = nil
-
- if klass_or_name.respond_to?(:name)
- @klass = klass_or_name
- @name = @klass.name
- else
- @name = klass_or_name.to_s
- end
-
- @classcache = ActiveSupport::Dependencies::Reference
- @args, @block = args, block
+ def initialize(klass, args, block)
+ @klass = klass
+ @args = args
+ @block = block
end
- def klass
- @klass || classcache[@name]
- end
-
- def ==(middleware)
- case middleware
- when Middleware
- klass == middleware.klass
- when Class
- klass == middleware
- else
- normalize(@name) == normalize(middleware)
- end
- end
+ def name; klass.name; end
def inspect
klass.to_s
@@ -42,12 +21,6 @@ module ActionDispatch
def build(app)
klass.new(app, *args, &block)
end
-
- private
-
- def normalize(object)
- object.to_s.strip.sub(/^::/, '')
- end
end
include Enumerable
@@ -75,19 +48,17 @@ module ActionDispatch
middlewares[i]
end
- def unshift(*args, &block)
- middleware = self.class::Middleware.new(*args, &block)
- middlewares.unshift(middleware)
+ def unshift(klass, *args, &block)
+ middlewares.unshift(build_middleware(klass, args, block))
end
def initialize_copy(other)
self.middlewares = other.middlewares.dup
end
- def insert(index, *args, &block)
+ def insert(index, klass, *args, &block)
index = assert_index(index, :before)
- middleware = self.class::Middleware.new(*args, &block)
- middlewares.insert(index, middleware)
+ middlewares.insert(index, build_middleware(klass, args, block))
end
alias_method :insert_before, :insert
@@ -104,26 +75,48 @@ module ActionDispatch
end
def delete(target)
- middlewares.delete target
+ target = get_class target
+ middlewares.delete_if { |m| m.klass == target }
end
- def use(*args, &block)
- middleware = self.class::Middleware.new(*args, &block)
- middlewares.push(middleware)
+ def use(klass, *args, &block)
+ middlewares.push(build_middleware(klass, args, block))
end
- def build(app = nil, &block)
- app ||= block
- raise "MiddlewareStack#build requires an app" unless app
+ def build(app = Proc.new)
middlewares.freeze.reverse.inject(app) { |a, e| e.build(a) }
end
protected
def assert_index(index, where)
- i = index.is_a?(Integer) ? index : middlewares.index(index)
+ 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
+
+ private
+
+ 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)
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index fdd1bc4e69..9462ae4278 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -3,32 +3,39 @@ require 'active_support/core_ext/uri'
module ActionDispatch
# This middleware returns a file's contents from disk in the body response.
- # When initialized it can accept an optional 'Cache-Control' header which
+ # When initialized, it can accept an optional 'Cache-Control' header, which
# will be set when a response containing a file's contents is delivered.
#
# This middleware will render the file specified in `env["PATH_INFO"]`
- # where the base path is in the +root+ directory. For example if the +root+
- # is set to `public/` then a request with `env["PATH_INFO"]` of
- # `assets/application.js` will return a response with contents of a file
+ # where the base path is in the +root+ directory. For example, if the +root+
+ # is set to `public/`, then a request with `env["PATH_INFO"]` of
+ # `assets/application.js` will return a response with the contents of a file
# located at `public/assets/application.js` if the file exists. If the file
- # does not exist a 404 "File not Found" response will be returned.
+ # does not exist, a 404 "File not Found" response will be returned.
class FileHandler
- def initialize(root, cache_control)
+ def initialize(root, cache_control, index: 'index')
@root = root.chomp('/')
@compiled_root = /^#{Regexp.escape(root)}/
headers = cache_control && { 'Cache-Control' => cache_control }
@file_server = ::Rack::File.new(@root, headers)
+ @index = index
end
+ # Takes a path to a file. If the file is found, has valid encoding, and has
+ # correct read permissions, the return value is a URI-escaped string
+ # representing the filename. Otherwise, false is returned.
+ #
+ # Used by the `Static` class to check the existence of a valid file
+ # in the server's `public/` directory (see Static#call).
def match?(path)
path = URI.parser.unescape(path)
return false unless path.valid_encoding?
path = Rack::Utils.clean_path_info path
- paths = [path, "#{path}#{ext}", "#{path}/index#{ext}"]
+ paths = [path, "#{path}#{ext}", "#{path}/#{@index}#{ext}"]
if match = paths.detect { |p|
- path = File.join(@root, p)
+ path = File.join(@root, p.force_encoding('UTF-8'.freeze))
begin
File.file?(path) && File.readable?(path)
rescue SystemCallError
@@ -41,26 +48,30 @@ module ActionDispatch
end
def call(env)
- path = env['PATH_INFO']
+ serve ActionDispatch::Request.new env
+ end
+
+ def serve(request)
+ path = request.path_info
gzip_path = gzip_file_path(path)
- if gzip_path && gzip_encoding_accepted?(env)
- env['PATH_INFO'] = gzip_path
- status, headers, body = @file_server.call(env)
+ if gzip_path && gzip_encoding_accepted?(request)
+ request.path_info = gzip_path
+ status, headers, body = @file_server.call(request.env)
if status == 304
return [status, headers, body]
end
headers['Content-Encoding'] = 'gzip'
headers['Content-Type'] = content_type(path)
else
- status, headers, body = @file_server.call(env)
+ status, headers, body = @file_server.call(request.env)
end
headers['Vary'] = 'Accept-Encoding' if gzip_path
return [status, headers, body]
ensure
- env['PATH_INFO'] = path
+ request.path_info = path
end
private
@@ -69,11 +80,11 @@ module ActionDispatch
end
def content_type(path)
- ::Rack::Mime.mime_type(::File.extname(path), 'text/plain')
+ ::Rack::Mime.mime_type(::File.extname(path), 'text/plain'.freeze)
end
- def gzip_encoding_accepted?(env)
- env['HTTP_ACCEPT_ENCODING'] =~ /\bgzip\b/i
+ def gzip_encoding_accepted?(request)
+ request.accept_encoding =~ /\bgzip\b/i
end
def gzip_file_path(path)
@@ -88,7 +99,7 @@ module ActionDispatch
end
# This middleware will attempt to return the contents of a file's body from
- # disk in the response. If a file is not found on disk, the request will be
+ # disk in the response. If a file is not found on disk, the request will be
# delegated to the application stack. This middleware is commonly initialized
# to serve assets from a server's `public/` directory.
#
@@ -97,22 +108,23 @@ 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, cache_control=nil)
+ def initialize(app, path, cache_control = nil, index: 'index')
@app = app
- @file_handler = FileHandler.new(path, cache_control)
+ @file_handler = FileHandler.new(path, cache_control, index: index)
end
def call(env)
- case env['REQUEST_METHOD']
- when 'GET', 'HEAD'
- path = env['PATH_INFO'].chomp('/')
+ req = ActionDispatch::Request.new env
+
+ if req.get? || req.head?
+ path = req.path_info.chomp('/'.freeze)
if match = @file_handler.match?(path)
- env["PATH_INFO"] = match
- return @file_handler.call(env)
+ req.path_info = match
+ return @file_handler.serve(req)
end
end
- @app.call(env)
+ @app.call(req.env)
end
end
end
diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb
index 973627f106..a8a3cd20b9 100644
--- a/actionpack/lib/action_dispatch/request/session.rb
+++ b/actionpack/lib/action_dispatch/request/session.rb
@@ -9,14 +9,15 @@ module ActionDispatch
# Singleton object used to determine if an optional param wasn't specified
Unspecified = Object.new
-
+
+ # Creates a session hash, merging the properties of the previous session if any
def self.create(store, env, default_options)
session_was = find env
session = Request::Session.new(store, env)
session.merge! session_was if session_was
set(env, session)
- Options.set(env, Request::Session::Options.new(store, env, default_options))
+ Options.set(env, Request::Session::Options.new(store, default_options))
session
end
@@ -37,20 +38,19 @@ module ActionDispatch
env[ENV_SESSION_OPTIONS_KEY]
end
- def initialize(by, env, default_options)
+ def initialize(by, default_options)
@by = by
- @env = env
@delegate = default_options.dup
end
def [](key)
- if key == :id
- @delegate.fetch(key) {
- @delegate[:id] = @by.send(:extract_session_id, @env)
- }
- else
- @delegate[key]
- end
+ @delegate[key]
+ end
+
+ def id(env)
+ @delegate.fetch(:id) {
+ @by.send(:extract_session_id, env)
+ }
end
def []=(k,v); @delegate[k] = v; end
@@ -67,7 +67,7 @@ module ActionDispatch
end
def id
- options[:id]
+ options.id(@env)
end
def options
@@ -77,19 +77,21 @@ module ActionDispatch
def destroy
clear
options = self.options || {}
- new_sid = @by.send(:destroy_session, @env, options[:id], options)
- options[:id] = new_sid # Reset session id with a new value or nil
+ @by.send(:destroy_session, @env, options.id(@env), options)
# Load the new sid to be written with the response
@loaded = false
load_for_write!
end
+ # Returns value of the key stored in the session or
+ # nil if the given key is not found in the session.
def [](key)
load_for_read!
@delegate[key.to_s]
end
+ # Returns true if the session has the given key or false.
def has_key?(key)
load_for_read!
@delegate.key?(key.to_s)
@@ -97,39 +99,69 @@ module ActionDispatch
alias :key? :has_key?
alias :include? :has_key?
+ # Returns keys of the session as Array.
def keys
@delegate.keys
end
+ # Returns values of the session as Array.
def values
@delegate.values
end
+ # Writes given value to given key of the session.
def []=(key, value)
load_for_write!
@delegate[key.to_s] = value
end
+ # Clears the session.
def clear
load_for_write!
@delegate.clear
end
+ # Returns the session as Hash.
def to_hash
load_for_read!
@delegate.dup.delete_if { |_,v| v.nil? }
end
+ # Updates the session with given Hash.
+ #
+ # session.to_hash
+ # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2"}
+ #
+ # session.update({ "foo" => "bar" })
+ # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2", "foo" => "bar"}
+ #
+ # session.to_hash
+ # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2", "foo" => "bar"}
def update(hash)
load_for_write!
@delegate.update stringify_keys(hash)
end
+ # Deletes given key from the session.
def delete(key)
load_for_write!
@delegate.delete key.to_s
end
+ # Returns value of given key from the session, or raises +KeyError+
+ # if can't find given key in case of not setted dafault value.
+ # Returns default value if specified.
+ #
+ # session.fetch(:foo)
+ # # => KeyError: key not found: "foo"
+ #
+ # session.fetch(:foo, :bar)
+ # # => :bar
+ #
+ # session.fetch(:foo) do
+ # :bar
+ # end
+ # # => :bar
def fetch(key, default=Unspecified, &block)
load_for_read!
if default == Unspecified
diff --git a/actionpack/lib/action_dispatch/request/utils.rb b/actionpack/lib/action_dispatch/request/utils.rb
index 1c9371d89c..3973ea6346 100644
--- a/actionpack/lib/action_dispatch/request/utils.rb
+++ b/actionpack/lib/action_dispatch/request/utils.rb
@@ -5,24 +5,45 @@ module ActionDispatch
mattr_accessor :perform_deep_munge
self.perform_deep_munge = true
- class << self
- # Remove nils from the params hash
- def deep_munge(hash, keys = [])
- return hash unless perform_deep_munge
+ def self.normalize_encode_params(params)
+ if perform_deep_munge
+ NoNilParamEncoder.normalize_encode_params params
+ else
+ ParamEncoder.normalize_encode_params params
+ end
+ end
- hash.each do |k, v|
- keys << k
- case v
- when Array
- v.grep(Hash) { |x| deep_munge(x, keys) }
- v.compact!
- when Hash
- deep_munge(v, keys)
+ class ParamEncoder # :nodoc:
+ # Convert nested Hash to HashWithIndifferentAccess.
+ #
+ def self.normalize_encode_params(params)
+ case params
+ when Array
+ handle_array params
+ when Hash
+ if params.has_key?(:tempfile)
+ ActionDispatch::Http::UploadedFile.new(params)
+ else
+ params.each_with_object({}) do |(key, val), new_hash|
+ new_hash[key] = normalize_encode_params(val)
+ end.with_indifferent_access
end
- keys.pop
+ else
+ params
end
+ end
+
+ def self.handle_array(params)
+ params.map! { |el| normalize_encode_params(el) }
+ end
+ end
- hash
+ # Remove nils from the params hash
+ class NoNilParamEncoder < ParamEncoder # :nodoc:
+ def self.handle_array(params)
+ list = super
+ list.compact!
+ list
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index ce03164ca9..a42cf72f60 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -232,7 +232,6 @@ module ActionDispatch
# def send_to_jail
# get '/jail'
# assert_response :success
- # assert_template "jail/front"
# end
#
# def goes_to_login
diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb
index c513737fc2..48c10a7d4c 100644
--- a/actionpack/lib/action_dispatch/routing/inspector.rb
+++ b/actionpack/lib/action_dispatch/routing/inspector.rb
@@ -45,7 +45,7 @@ module ActionDispatch
end
def internal?
- controller.to_s =~ %r{\Arails/(info|mailers|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}\z}
+ controller.to_s =~ %r{\Arails/(info|mailers|welcome)}
end
def engine?
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 34b5b48f3a..b80720218f 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -16,7 +16,10 @@ module ActionDispatch
class Constraints < Endpoint #:nodoc:
attr_reader :app, :constraints
- def initialize(app, constraints, dispatcher_p)
+ SERVE = ->(app, req) { app.serve req }
+ CALL = ->(app, req) { app.call req.env }
+
+ def initialize(app, constraints, strategy)
# Unwrap Constraints objects. I don't actually think it's possible
# to pass a Constraints object to this constructor, but there were
# multiple places that kept testing children of this object. I
@@ -26,12 +29,12 @@ module ActionDispatch
app = app.app
end
- @dispatcher = dispatcher_p
+ @strategy = strategy
@app, @constraints, = app, constraints
end
- def dispatcher?; @dispatcher; end
+ def dispatcher?; @strategy == SERVE; end
def matches?(req)
@constraints.all? do |constraint|
@@ -43,11 +46,7 @@ module ActionDispatch
def serve(req)
return [ 404, {'X-Cascade' => 'pass'}, [] ] unless matches?(req)
- if dispatcher?
- @app.serve req
- else
- @app.call req.env
- end
+ @strategy.call @app, req
end
private
@@ -62,65 +61,74 @@ module ActionDispatch
attr_reader :requirements, :conditions, :defaults
attr_reader :to, :default_controller, :default_action, :as, :anchor
- def self.build(scope, set, path, as, options)
+ def self.build(scope, set, path, as, controller, default_action, to, via, formatted, options)
options = scope[:options].merge(options) if scope[:options]
- options.delete :only
- options.delete :except
- options.delete :shallow_path
- options.delete :shallow_prefix
- options.delete :shallow
+ defaults = (scope[:defaults] || {}).dup
+ scope_constraints = scope[:constraints] || {}
- defaults = (scope[:defaults] || {}).merge options.delete(:defaults) || {}
+ new set, path, defaults, as, controller, default_action, scope[:module], to, formatted, scope_constraints, scope[:blocks] || [], via, options
+ end
- new scope, set, path, defaults, as, options
+ def self.check_via(via)
+ if via.empty?
+ msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
+ "If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \
+ "If you want to expose your action to GET, use `get` in the router:\n" \
+ " Instead of: match \"controller#action\"\n" \
+ " Do: get \"controller#action\""
+ raise ArgumentError, msg
+ end
+ via
end
- def initialize(scope, set, path, defaults, as, options)
- @requirements, @conditions = {}, {}
+ def initialize(set, path, defaults, as, controller, default_action, modyoule, to, formatted, scope_constraints, blocks, via, options)
@defaults = defaults
@set = set
- @to = options.delete :to
- @default_controller = options.delete(:controller) || scope[:controller]
- @default_action = options.delete(:action) || scope[:action]
+ @to = to
+ @default_controller = controller
+ @default_action = default_action
@as = as
@anchor = options.delete :anchor
- formatted = options.delete :format
- via = Array(options.delete(:via) { [] })
- options_constraints = options.delete :constraints
+ options_constraints = options.delete(:constraints) || {}
path = normalize_path! path, formatted
ast = path_ast path
path_params = path_params ast
- options = normalize_options!(options, formatted, path_params, ast, scope[:module])
+ options = normalize_options!(options, formatted, path_params, ast, modyoule)
+ split_options = constraints(options, path_params)
- split_constraints(path_params, scope[:constraints]) if scope[:constraints]
- constraints = constraints(options, path_params)
-
- split_constraints path_params, constraints
-
- @blocks = blocks(options_constraints, scope[:blocks])
+ constraints = scope_constraints.merge Hash[split_options[:constraints] || []]
if options_constraints.is_a?(Hash)
- split_constraints path_params, options_constraints
- options_constraints.each do |key, default|
- if URL_OPTIONS.include?(key) && (String === default || Fixnum === default)
- @defaults[key] ||= default
- end
- end
+ @defaults = Hash[options_constraints.find_all { |key, default|
+ URL_OPTIONS.include?(key) && (String === default || Fixnum === default)
+ }].merge @defaults
+ @blocks = blocks
+ constraints.merge! options_constraints
+ else
+ @blocks = blocks(options_constraints)
end
- normalize_format!(formatted)
+ requirements, conditions = split_constraints path_params, constraints
+ verify_regexp_requirements requirements.map(&:last).grep(Regexp)
+
+ formats = normalize_format(formatted)
+ @requirements = formats[:requirements].merge Hash[requirements]
+ @conditions = Hash[conditions]
+ @defaults = formats[:defaults].merge(@defaults).merge(normalize_defaults(options))
+
+ @conditions[:required_defaults] = (split_options[:required_defaults] || []).map(&:first)
@conditions[:path_info] = path
@conditions[:parsed_path_info] = ast
-
- add_request_method(via, @conditions)
- normalize_defaults!(options)
+ unless via == [:all]
+ @conditions[:request_method] = via.map { |m| m.to_s.dasherize.upcase }
+ end
end
def to_route
@@ -178,74 +186,50 @@ module ActionDispatch
end
def split_constraints(path_params, constraints)
- constraints.each_pair do |key, requirement|
- if path_params.include?(key) || key == :controller
- verify_regexp_requirement(requirement) if requirement.is_a?(Regexp)
- @requirements[key] = requirement
- else
- @conditions[key] = requirement
- end
+ constraints.partition do |key, requirement|
+ path_params.include?(key) || key == :controller
end
end
- def normalize_format!(formatted)
- if formatted == true
- @requirements[:format] ||= /.+/
- elsif Regexp === formatted
- @requirements[:format] = formatted
- @defaults[:format] = nil
- elsif String === formatted
- @requirements[:format] = Regexp.compile(formatted)
- @defaults[:format] = formatted
- end
- end
-
- def verify_regexp_requirement(requirement)
- if requirement.source =~ ANCHOR_CHARACTERS_REGEX
- raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
- end
-
- if requirement.multiline?
- raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
+ def normalize_format(formatted)
+ case formatted
+ when true
+ { requirements: { format: /.+/ },
+ defaults: {} }
+ when Regexp
+ { requirements: { format: formatted },
+ defaults: { format: nil } }
+ when String
+ { requirements: { format: Regexp.compile(formatted) },
+ defaults: { format: formatted } }
+ else
+ { requirements: { }, defaults: { } }
end
end
- def normalize_defaults!(options)
- options.each_pair do |key, default|
- unless Regexp === default
- @defaults[key] = default
+ def verify_regexp_requirements(requirements)
+ requirements.each do |requirement|
+ if requirement.source =~ ANCHOR_CHARACTERS_REGEX
+ raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
end
- end
- end
- def verify_callable_constraint(callable_constraint)
- unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?)
- raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?"
+ if requirement.multiline?
+ raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
+ end
end
end
- def add_request_method(via, conditions)
- return if via == [:all]
-
- if via.empty?
- msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
- "If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \
- "If you want to expose your action to GET, use `get` in the router:\n" \
- " Instead of: match \"controller#action\"\n" \
- " Do: get \"controller#action\""
- raise ArgumentError, msg
- end
-
- conditions[:request_method] = via.map { |m| m.to_s.dasherize.upcase }
+ def normalize_defaults(options)
+ Hash[options.reject { |_, default| Regexp === default }]
end
def app(blocks)
if to.respond_to?(:call)
- Constraints.new(to, blocks, false)
+ Constraints.new(to, blocks, Constraints::CALL)
elsif blocks.any?
- Constraints.new(dispatcher(defaults), blocks, true)
+ Constraints.new(dispatcher(defaults.key?(:controller)), blocks, Constraints::SERVE)
else
- dispatcher(defaults)
+ dispatcher(defaults.key?(:controller))
end
end
@@ -303,27 +287,25 @@ module ActionDispatch
yield
end
- def blocks(options_constraints, scope_blocks)
- if options_constraints && !options_constraints.is_a?(Hash)
- verify_callable_constraint(options_constraints)
- [options_constraints]
- else
- scope_blocks || []
+ def blocks(callable_constraint)
+ unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?)
+ raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?"
end
+ [callable_constraint]
end
def constraints(options, path_params)
- constraints = {}
- required_defaults = []
- options.each_pair do |key, option|
+ options.group_by do |key, option|
if Regexp === option
- constraints[key] = option
+ :constraints
else
- required_defaults << key unless path_params.include?(key)
+ if path_params.include?(key)
+ :path_params
+ else
+ :required_defaults
+ end
end
end
- @conditions[:required_defaults] = required_defaults
- constraints
end
def path_params(ast)
@@ -335,8 +317,8 @@ module ActionDispatch
parser.parse path
end
- def dispatcher(defaults)
- @set.dispatcher defaults
+ def dispatcher(raise_on_name_error)
+ @set.dispatcher raise_on_name_error
end
end
@@ -418,7 +400,7 @@ module ActionDispatch
# A pattern can also point to a +Rack+ endpoint i.e. anything that
# responds to +call+:
#
- # match 'photos/:id', to: lambda {|hash| [200, {}, ["Coming soon"]] }, via: :get
+ # match 'photos/:id', to: -> (hash) { [200, {}, ["Coming soon"]] }, via: :get
# match 'photos/:id', to: PhotoRackApp, via: :get
# # Yes, controller actions are just rack endpoints
# match 'photos/:id', to: PhotosController.action(:show), via: :get
@@ -443,6 +425,21 @@ module ActionDispatch
# dynamic segment used to generate the routes).
# You can access that segment from your controller using
# <tt>params[<:param>]</tt>.
+ # In your router:
+ #
+ # resources :user, param: :name
+ #
+ # You can override <tt>ActiveRecord::Base#to_param</tt> of a related
+ # model to construct an URL:
+ #
+ # class User < ActiveRecord::Base
+ # def to_param
+ # name
+ # end
+ # end
+ #
+ # user = User.find_by(name: 'Phusion')
+ # user_path(user) # => "/users/Phusion"
#
# [:path]
# The path prefix for the routes.
@@ -470,7 +467,7 @@ module ActionDispatch
# +call+ or a string representing a controller's action.
#
# match 'path', to: 'controller#action', via: :get
- # match 'path', to: lambda { |env| [200, {}, ["Success!"]] }, via: :get
+ # match 'path', to: -> (env) { [200, {}, ["Success!"]] }, via: :get
# match 'path', to: RackApp, via: :get
#
# [:on]
@@ -670,7 +667,11 @@ module ActionDispatch
def map_method(method, args, &block)
options = args.extract_options!
options[:via] = method
- match(*args, options, &block)
+ if options.key?(:defaults)
+ defaults(options.delete(:defaults)) { match(*args, options, &block) }
+ else
+ match(*args, options, &block)
+ end
self
end
end
@@ -773,8 +774,8 @@ module ActionDispatch
end
if options[:constraints].is_a?(Hash)
- defaults = options[:constraints].select do
- |k, v| URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum))
+ defaults = options[:constraints].select do |k, v|
+ URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum))
end
(options[:defaults] ||= {}).reverse_merge!(defaults)
@@ -782,16 +783,25 @@ module ActionDispatch
block, options[:constraints] = options[:constraints], {}
end
+ if options.key?(:only) || options.key?(:except)
+ scope[:action_options] = { only: options.delete(:only),
+ except: options.delete(:except) }
+ end
+
+ if options.key? :anchor
+ raise ArgumentError, 'anchor is ignored unless passed to `match`'
+ end
+
@scope.options.each do |option|
if option == :blocks
value = block
elsif option == :options
value = options
else
- value = options.delete(option)
+ value = options.delete(option) { POISON }
end
- if value
+ unless POISON == value
scope[option] = send("merge_#{option}_scope", @scope[option], value)
end
end
@@ -803,14 +813,18 @@ module ActionDispatch
@scope = @scope.parent
end
+ POISON = Object.new # :nodoc:
+
# Scopes routes to a specific controller
#
# controller "food" do
- # match "bacon", action: "bacon"
+ # match "bacon", action: :bacon, via: :get
# end
- def controller(controller, options={})
- options[:controller] = controller
- scope(options) { yield }
+ def controller(controller)
+ @scope = @scope.new(controller: controller)
+ yield
+ ensure
+ @scope = @scope.parent
end
# Scopes routes to a specific namespace. For example:
@@ -856,13 +870,14 @@ module ActionDispatch
defaults = {
module: path,
- path: options.fetch(:path, path),
as: options.fetch(:as, path),
shallow_path: options.fetch(:path, path),
shallow_prefix: options.fetch(:as, path)
}
- scope(defaults.merge!(options)) { yield }
+ path_scope(options.delete(:path) { path }) do
+ scope(defaults.merge!(options)) { yield }
+ end
end
# === Parameter Restriction
@@ -899,7 +914,7 @@ module ActionDispatch
#
# Requests to routes can be constrained based on specific criteria:
#
- # constraints(lambda { |req| req.env["HTTP_USER_AGENT"] =~ /iPhone/ }) do
+ # constraints(-> (req) { req.env["HTTP_USER_AGENT"] =~ /iPhone/ }) do
# resources :iphones
# end
#
@@ -930,7 +945,10 @@ module ActionDispatch
# end
# Using this, the +:id+ parameter here will default to 'home'.
def defaults(defaults = {})
- scope(:defaults => defaults) { yield }
+ @scope = @scope.new(defaults: merge_defaults_scope(@scope[:defaults], defaults))
+ yield
+ ensure
+ @scope = @scope.parent
end
private
@@ -962,6 +980,14 @@ module ActionDispatch
child
end
+ def merge_via_scope(parent, child) #:nodoc:
+ child
+ end
+
+ def merge_format_scope(parent, child) #:nodoc:
+ child
+ end
+
def merge_path_names_scope(parent, child) #:nodoc:
merge_options_scope(parent, child)
end
@@ -981,16 +1007,12 @@ module ActionDispatch
end
def merge_options_scope(parent, child) #:nodoc:
- (parent || {}).except(*override_keys(child)).merge!(child)
+ (parent || {}).merge(child)
end
def merge_shallow_scope(parent, child) #:nodoc:
child ? true : false
end
-
- def override_keys(child) #:nodoc:
- child.key?(:only) || child.key?(:except) ? [:only, :except] : []
- end
end
# Resource routing allows you to quickly declare all of the common routes
@@ -1040,27 +1062,34 @@ module ActionDispatch
CANONICAL_ACTIONS = %w(index create new show update destroy)
class Resource #:nodoc:
- attr_reader :controller, :path, :options, :param
+ attr_reader :controller, :path, :param
- def initialize(entities, options = {})
+ def initialize(entities, api_only, shallow, options = {})
@name = entities.to_s
@path = (options[:path] || @name).to_s
@controller = (options[:controller] || @name).to_s
@as = options[:as]
@param = (options[:param] || :id).to_sym
@options = options
- @shallow = false
+ @shallow = shallow
+ @api_only = api_only
+ @only = options.delete :only
+ @except = options.delete :except
end
def default_actions
- [:index, :create, :new, :show, :update, :destroy, :edit]
+ if @api_only
+ [:index, :create, :show, :update, :destroy]
+ else
+ [:index, :create, :new, :show, :update, :destroy, :edit]
+ end
end
def actions
- if only = @options[:only]
- Array(only).map(&:to_sym)
- elsif except = @options[:except]
- default_actions - Array(except).map(&:to_sym)
+ if @only
+ Array(@only).map(&:to_sym)
+ elsif @except
+ default_actions - Array(@except).map(&:to_sym)
else
default_actions
end
@@ -1087,7 +1116,7 @@ module ActionDispatch
end
def resource_scope
- { :controller => controller }
+ controller
end
alias :collection_scope :path
@@ -1110,17 +1139,15 @@ module ActionDispatch
"#{path}/:#{nested_param}"
end
- def shallow=(value)
- @shallow = value
- end
-
def shallow?
@shallow
end
+
+ def singleton?; false; end
end
class SingletonResource < Resource #:nodoc:
- def initialize(entities, options)
+ def initialize(entities, api_only, shallow, options)
super
@as = nil
@controller = (options[:controller] || plural).to_s
@@ -1128,7 +1155,11 @@ module ActionDispatch
end
def default_actions
- [:show, :create, :update, :destroy, :new, :edit]
+ if @api_only
+ [:show, :create, :update, :destroy]
+ else
+ [:show, :create, :update, :destroy, :new, :edit]
+ end
end
def plural
@@ -1144,6 +1175,8 @@ module ActionDispatch
alias :member_scope :path
alias :nested_scope :path
+
+ def singleton?; true; end
end
def resources_path_names(options)
@@ -1178,20 +1211,23 @@ module ActionDispatch
return self
end
- resource_scope(:resource, SingletonResource.new(resources.pop, options)) do
- yield if block_given?
+ with_scope_level(:resource) do
+ options = apply_action_options options
+ resource_scope(SingletonResource.new(resources.pop, api_only?, @scope[:shallow], options)) do
+ yield if block_given?
- concerns(options[:concerns]) if options[:concerns]
+ concerns(options[:concerns]) if options[:concerns]
- collection do
- post :create
- end if parent_resource.actions.include?(:create)
+ collection do
+ post :create
+ end if parent_resource.actions.include?(:create)
- new do
- get :new
- end if parent_resource.actions.include?(:new)
+ new do
+ get :new
+ end if parent_resource.actions.include?(:new)
- set_member_mappings_for_resource
+ set_member_mappings_for_resource
+ end
end
self
@@ -1336,21 +1372,24 @@ module ActionDispatch
return self
end
- resource_scope(:resources, Resource.new(resources.pop, options)) do
- yield if block_given?
+ with_scope_level(:resources) do
+ options = apply_action_options options
+ resource_scope(Resource.new(resources.pop, api_only?, @scope[:shallow], options)) do
+ yield if block_given?
- concerns(options[:concerns]) if options[:concerns]
+ concerns(options[:concerns]) if options[:concerns]
- collection do
- get :index if parent_resource.actions.include?(:index)
- post :create if parent_resource.actions.include?(:create)
- end
+ collection do
+ get :index if parent_resource.actions.include?(:index)
+ post :create if parent_resource.actions.include?(:create)
+ end
- new do
- get :new
- end if parent_resource.actions.include?(:new)
+ new do
+ get :new
+ end if parent_resource.actions.include?(:new)
- set_member_mappings_for_resource
+ set_member_mappings_for_resource
+ end
end
self
@@ -1374,7 +1413,7 @@ module ActionDispatch
end
with_scope_level(:collection) do
- scope(parent_resource.collection_scope) do
+ path_scope(parent_resource.collection_scope) do
yield
end
end
@@ -1398,9 +1437,11 @@ module ActionDispatch
with_scope_level(:member) do
if shallow?
- shallow_scope(parent_resource.member_scope) { yield }
+ shallow_scope {
+ path_scope(parent_resource.member_scope) { yield }
+ }
else
- scope(parent_resource.member_scope) { yield }
+ path_scope(parent_resource.member_scope) { yield }
end
end
end
@@ -1411,7 +1452,7 @@ module ActionDispatch
end
with_scope_level(:new) do
- scope(parent_resource.new_scope(action_path(:new))) do
+ path_scope(parent_resource.new_scope(action_path(:new))) do
yield
end
end
@@ -1424,9 +1465,15 @@ module ActionDispatch
with_scope_level(:nested) do
if shallow? && shallow_nesting_depth >= 1
- shallow_scope(parent_resource.nested_scope, nested_options) { yield }
+ shallow_scope do
+ path_scope(parent_resource.nested_scope) do
+ scope(nested_options) { yield }
+ end
+ end
else
- scope(parent_resource.nested_scope, nested_options) { yield }
+ path_scope(parent_resource.nested_scope) do
+ scope(nested_options) { yield }
+ end
end
end
end
@@ -1441,18 +1488,22 @@ module ActionDispatch
end
def shallow
- scope(:shallow => true) do
- yield
- end
+ @scope = @scope.new(shallow: true)
+ yield
+ ensure
+ @scope = @scope.parent
end
def shallow?
- parent_resource.instance_of?(Resource) && @scope[:shallow]
+ !parent_resource.singleton? && @scope[:shallow]
end
- # match 'path' => 'controller#action'
- # match 'path', to: 'controller#action'
- # match 'path', 'otherpath', on: :member, via: :get
+ # Matches a url pattern to one or more routes.
+ # For more information, see match[rdoc-ref:Base#match].
+ #
+ # match 'path' => 'controller#action', via: patch
+ # match 'path', to: 'controller#action', via: :post
+ # match 'path', 'otherpath', on: :member, via: :get
def match(path, *rest)
if rest.empty? && Hash === path
options = path
@@ -1488,48 +1539,69 @@ module ActionDispatch
options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
end
- paths.each do |_path|
- route_options = options.dup
- route_options[:path] ||= _path if _path.is_a?(String)
+ 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] }
- path_without_format = _path.to_s.sub(/\(\.:format\)$/, '')
- if using_match_shorthand?(path_without_format, route_options)
- route_options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
- route_options[:to].tr!("-", "_")
- end
+ path_types = paths.group_by(&:class)
+ path_types.fetch(String, []).each do |_path|
+ route_options = options.dup
+ process_path(route_options, controller, _path, option_path || _path, to, via, formatted)
+ end
- decomposed_match(_path, route_options)
+ path_types.fetch(Symbol, []).each do |action|
+ route_options = options.dup
+ decomposed_match(action, controller, route_options, option_path, to, via, formatted)
end
+
self
end
- def using_match_shorthand?(path, options)
- path && (options[:to] || options[:action]).nil? && path =~ %r{^/?[-\w]+/[-\w/]+$}
+ def process_path(options, controller, path, option_path, to, via, formatted)
+ path_without_format = path.sub(/\(\.:format\)$/, '')
+ if using_match_shorthand?(path_without_format, to, options[:action])
+ to ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
+ to.tr!("-", "_")
+ end
+
+ decomposed_match(path, controller, options, option_path, to, via, formatted)
+ end
+
+ def using_match_shorthand?(path, to, action)
+ return false if to || action
+
+ path =~ %r{^/?[-\w]+/[-\w/]+$}
end
- def decomposed_match(path, options) # :nodoc:
+ def decomposed_match(path, controller, options, _path, to, via, formatted) # :nodoc:
if on = options.delete(:on)
- send(on) { decomposed_match(path, options) }
+ send(on) { decomposed_match(path, controller, options, _path, to, via, formatted) }
else
case @scope.scope_level
when :resources
- nested { decomposed_match(path, options) }
+ nested { decomposed_match(path, controller, options, _path, to, via, formatted) }
when :resource
- member { decomposed_match(path, options) }
+ member { decomposed_match(path, controller, options, _path, to, via, formatted) }
else
- add_route(path, options)
+ add_route(path, controller, options, _path, to, via, formatted)
end
end
end
- def add_route(action, options) # :nodoc:
- path = path_for_action(action, options.delete(:path))
+ def add_route(action, controller, options, _path, to, via, formatted) # :nodoc:
+ path = path_for_action(action, _path)
raise ArgumentError, "path is required" if path.blank?
- action = action.to_s.dup
+ action = action.to_s
+
+ default_action = options.delete(:action) || @scope[:action]
if action =~ /^[\w\-\/]+$/
- options[:action] ||= action.tr('-', '_') unless action.include?("/")
+ default_action ||= action.tr('-', '_') unless action.include?("/")
else
action = nil
end
@@ -1540,7 +1612,7 @@ module ActionDispatch
name_for_action(options.delete(:as), action)
end
- mapping = Mapping.build(@scope, @set, URI.parser.escape(path), as, options)
+ mapping = Mapping.build(@scope, @set, URI.parser.escape(path), as, controller, default_action, to, via, formatted, options)
app, conditions, requirements, defaults, as, anchor = mapping.to_route
@set.add_route(app, conditions, requirements, defaults, as, anchor)
end
@@ -1556,7 +1628,7 @@ module ActionDispatch
if @scope.resources?
with_scope_level(:root) do
- scope(parent_resource.path) do
+ path_scope(parent_resource.path) do
super(options)
end
end
@@ -1601,23 +1673,20 @@ module ActionDispatch
return true
end
- unless action_options?(options)
- options.merge!(scope_action_options) if scope_action_options?
- end
-
false
end
- def action_options?(options) #:nodoc:
- options[:only] || options[:except]
+ def apply_action_options(options) # :nodoc:
+ return options if action_options? options
+ options.merge scope_action_options
end
- def scope_action_options? #:nodoc:
- @scope[:options] && (@scope[:options][:only] || @scope[:options][:except])
+ def action_options?(options) #:nodoc:
+ options[:only] || options[:except]
end
def scope_action_options #:nodoc:
- @scope[:options].slice(:only, :except)
+ @scope[:action_options] || {}
end
def resource_scope? #:nodoc:
@@ -1632,18 +1701,6 @@ module ActionDispatch
@scope.nested?
end
- def with_exclusive_scope
- begin
- @scope = @scope.new(:as => nil, :path => nil)
-
- with_scope_level(:exclusive) do
- yield
- end
- ensure
- @scope = @scope.parent
- end
- end
-
def with_scope_level(kind)
@scope = @scope.new_level(kind)
yield
@@ -1651,16 +1708,11 @@ module ActionDispatch
@scope = @scope.parent
end
- def resource_scope(kind, resource) #:nodoc:
- resource.shallow = @scope[:shallow]
+ def resource_scope(resource) #:nodoc:
@scope = @scope.new(:scope_level_resource => resource)
- @nesting.push(resource)
- with_scope_level(kind) do
- scope(parent_resource.resource_scope) { yield }
- end
+ controller(resource.resource_scope) { yield }
ensure
- @nesting.pop
@scope = @scope.parent
end
@@ -1673,12 +1725,10 @@ module ActionDispatch
options
end
- def nesting_depth #:nodoc:
- @nesting.size
- end
-
def shallow_nesting_depth #:nodoc:
- @nesting.select(&:shallow?).size
+ @scope.find_all { |node|
+ node.frame[:scope_level_resource]
+ }.count { |node| node.frame[:scope_level_resource].shallow? }
end
def param_constraint? #:nodoc:
@@ -1693,27 +1743,28 @@ module ActionDispatch
resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
end
- def shallow_scope(path, options = {}) #:nodoc:
+ def shallow_scope #:nodoc:
scope = { :as => @scope[:shallow_prefix],
:path => @scope[:shallow_path] }
@scope = @scope.new scope
- scope(path, options) { yield }
+ yield
ensure
@scope = @scope.parent
end
def path_for_action(action, path) #:nodoc:
- if path.blank? && canonical_action?(action)
+ return "#{@scope[:path]}/#{path}" if path
+
+ if canonical_action?(action)
@scope[:path].to_s
else
- "#{@scope[:path]}/#{action_path(action, path)}"
+ "#{@scope[:path]}/#{action_path(action)}"
end
end
- def action_path(name, path = nil) #:nodoc:
- name = name.to_sym if name.is_a?(String)
- path || @scope[:path_names][name] || name.to_s
+ def action_path(name) #:nodoc:
+ @scope[:path_names][name.to_sym] || name
end
def prefix_name_for_action(as, action) #:nodoc:
@@ -1765,6 +1816,18 @@ module ActionDispatch
delete :destroy if parent_resource.actions.include?(:destroy)
end
end
+
+ def api_only?
+ @set.api_only?
+ end
+ private
+
+ def path_scope(path)
+ @scope = @scope.new(path: merge_path_scope(@scope[:path], path))
+ yield
+ ensure
+ @scope = @scope.parent
+ end
end
# Routing Concerns allow you to declare common routes that can be reused
@@ -1875,14 +1938,14 @@ module ActionDispatch
class Scope # :nodoc:
OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
:controller, :action, :path_names, :constraints,
- :shallow, :blocks, :defaults, :options]
+ :shallow, :blocks, :defaults, :via, :format, :options]
RESOURCE_SCOPES = [:resource, :resources]
RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
attr_reader :parent, :scope_level
- def initialize(hash, parent = {}, scope_level = nil)
+ def initialize(hash, parent = NULL, scope_level = nil)
@hash = hash
@parent = parent
@scope_level = scope_level
@@ -1930,27 +1993,34 @@ module ActionDispatch
end
def new_level(level)
- self.class.new(self, self, level)
- end
-
- def fetch(key, &block)
- @hash.fetch(key, &block)
+ self.class.new(frame, self, level)
end
def [](key)
- @hash.fetch(key) { @parent[key] }
+ scope = find { |node| node.frame.key? key }
+ scope && scope.frame[key]
end
- def []=(k,v)
- @hash[k] = v
+ include Enumerable
+
+ def each
+ node = self
+ loop do
+ break if node.equal? NULL
+ yield node
+ node = node.parent
+ end
end
+
+ def frame; @hash; end
+
+ NULL = Scope.new(nil, nil)
end
def initialize(set) #:nodoc:
@set = set
@scope = Scope.new({ :path_names => @set.resources_path_names })
@concerns = {}
- @nesting = []
end
include Base
diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb
index 3c1c4fadf6..d6987f4d09 100644
--- a/actionpack/lib/action_dispatch/routing/redirection.rb
+++ b/actionpack/lib/action_dispatch/routing/redirection.rb
@@ -24,7 +24,7 @@ module ActionDispatch
def serve(req)
req.check_path_parameters!
uri = URI.parse(path(req.path_parameters, req))
-
+
unless uri.host
if relative_path?(uri.path)
uri.path = "#{req.script_name}/#{uri.path}"
@@ -32,7 +32,7 @@ module ActionDispatch
uri.path = req.script_name.empty? ? "/" : req.script_name
end
end
-
+
uri.scheme ||= req.scheme
uri.host ||= req.host
uri.port ||= req.port unless req.standard_port?
@@ -124,7 +124,7 @@ module ActionDispatch
url_options[:script_name] = request.script_name
end
end
-
+
ActionDispatch::Http::URL.url_for url_options
end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 0f3734dd74..2fe61c7aa6 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -21,8 +21,8 @@ module ActionDispatch
alias inspect to_s
class Dispatcher < Routing::Endpoint
- def initialize(defaults)
- @defaults = defaults
+ def initialize(raise_on_name_error)
+ @raise_on_name_error = raise_on_name_error
@controller_class_names = ThreadSafe::Cache.new
end
@@ -34,12 +34,11 @@ module ActionDispatch
prepare_params!(params)
- # Just raise undefined constant errors if a controller was specified as default.
- unless controller = controller(params, @defaults.key?(:controller))
+ controller = controller(params, @raise_on_name_error) do
return [404, {'X-Cascade' => 'pass'}, []]
end
- dispatch(controller, params[:action], req.env)
+ dispatch(controller, params[:action], req)
end
def prepare_params!(params)
@@ -53,24 +52,26 @@ module ActionDispatch
# segment, as in :controller(/:action), we should simply return nil and
# delegate the control back to Rack cascade. Besides, if this is not a default
# controller, it means we should respect the @scope[:module] parameter.
- def controller(params, default_controller=true)
- if params && params.key?(:controller)
- controller_param = params[:controller]
- controller_reference(controller_param)
- end
+ def controller(params, raise_on_name_error=true)
+ controller_reference params.fetch(:controller) { yield }
rescue NameError => e
- raise ActionController::RoutingError, e.message, e.backtrace if default_controller
+ raise ActionController::RoutingError, e.message, e.backtrace if raise_on_name_error
+ yield
end
- private
+ protected
+
+ attr_reader :controller_class_names
def controller_reference(controller_param)
- const_name = @controller_class_names[controller_param] ||= "#{controller_param.camelize}Controller"
+ const_name = controller_class_names[controller_param] ||= "#{controller_param.camelize}Controller"
ActiveSupport::Dependencies.constantize(const_name)
end
- def dispatch(controller, action, env)
- controller.action(action).call(env)
+ private
+
+ def dispatch(controller, action, req)
+ controller.action(action).call(req.env)
end
def normalize_controller!(params)
@@ -226,7 +227,7 @@ module ActionDispatch
params = parameterize_args(args) { |missing_key|
missing_keys << missing_key
}
- constraints = Hash[@route.requirements.merge(params).sort]
+ 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}"
@@ -267,9 +268,13 @@ module ActionDispatch
path_params -= controller_options.keys
path_params -= result.keys
end
- path_params -= inner_options.keys
- path_params.take(args.size).each do |param|
- result[param] = args.shift
+ inner_options.each_key do |key|
+ path_params.delete(key)
+ end
+
+ args.each_with_index do |arg, index|
+ param = path_params[index]
+ result[param] = arg if param
end
end
@@ -309,7 +314,7 @@ module ActionDispatch
attr_accessor :formatter, :set, :named_routes, :default_scope, :router
attr_accessor :disable_clear_and_finalize, :resources_path_names
- attr_accessor :default_url_options
+ attr_accessor :default_url_options, :dispatcher_class
attr_reader :env_key
alias :routes :set
@@ -319,17 +324,23 @@ module ActionDispatch
end
def self.new_with_config(config)
+ route_set_config = DEFAULT_CONFIG
+
+ # engines apparently don't have this set
if config.respond_to? :relative_url_root
- new Config.new config.relative_url_root
- else
- # engines apparently don't have this set
- new
+ route_set_config.relative_url_root = config.relative_url_root
end
+
+ if config.respond_to? :api_only
+ route_set_config.api_only = config.api_only
+ end
+
+ new route_set_config
end
- Config = Struct.new :relative_url_root
+ Config = Struct.new :relative_url_root, :api_only
- DEFAULT_CONFIG = Config.new(nil)
+ DEFAULT_CONFIG = Config.new(nil, false)
def initialize(config = DEFAULT_CONFIG)
self.named_routes = NamedRouteCollection.new
@@ -346,12 +357,17 @@ module ActionDispatch
@set = Journey::Routes.new
@router = Journey::Router.new @set
@formatter = Journey::Formatter.new @set
+ @dispatcher_class = Routing::RouteSet::Dispatcher
end
def relative_url_root
@config.relative_url_root
end
+ def api_only?
+ @config.api_only
+ end
+
def request_class
ActionDispatch::Request
end
@@ -399,8 +415,8 @@ module ActionDispatch
@prepend.each { |blk| eval_block(blk) }
end
- def dispatcher(defaults)
- Routing::RouteSet::Dispatcher.new(defaults)
+ def dispatcher(raise_on_name_error)
+ dispatcher_class.new(raise_on_name_error)
end
module MountedHelpers
@@ -511,10 +527,11 @@ module ActionDispatch
path = conditions.delete :path_info
ast = conditions.delete :parsed_path_info
+ required_defaults = conditions.delete :required_defaults
path = build_path(path, ast, requirements, anchor)
- conditions = build_conditions(conditions, path.names.map(&:to_sym))
+ conditions = build_conditions(conditions)
- route = @set.add_route(app, path, conditions, defaults, name)
+ route = @set.add_route(app, path, conditions, required_defaults, defaults, name)
named_routes[name] = route if name
route
end
@@ -550,7 +567,7 @@ module ActionDispatch
end
private :build_path
- def build_conditions(current_conditions, path_values)
+ def build_conditions(current_conditions)
conditions = current_conditions.dup
# Rack-Mount requires that :request_method be a regular expression.
@@ -563,8 +580,7 @@ module ActionDispatch
end
conditions.keep_if do |k, _|
- k == :action || k == :controller || k == :required_defaults ||
- request_class.public_method_defined?(k) || path_values.include?(k)
+ request_class.public_method_defined?(k)
end
end
private :build_conditions
@@ -573,10 +589,8 @@ module ActionDispatch
PARAMETERIZE = lambda do |name, value|
if name == :controller
value
- elsif value.is_a?(Array)
- value.map(&:to_param).join('/')
- elsif param = value.to_param
- param
+ else
+ value.to_param
end
end
@@ -584,8 +598,8 @@ module ActionDispatch
def initialize(named_route, options, recall, set)
@named_route = named_route
- @options = options.dup
- @recall = recall.dup
+ @options = options
+ @recall = recall
@set = set
normalize_recall!
@@ -607,7 +621,7 @@ module ActionDispatch
def use_recall_for(key)
if @recall[key] && (!@options.key?(key) || @options[key] == @recall[key])
if !named_route_exists? || segment_keys.include?(key)
- @options[key] = @recall.delete(key)
+ @options[key] = @recall[key]
end
end
end
@@ -661,12 +675,18 @@ module ActionDispatch
# Remove leading slashes from controllers
def normalize_controller!
- @options[:controller] = controller.sub(%r{^/}, '') if controller
+ if controller
+ if controller.start_with?("/".freeze)
+ @options[:controller] = controller[1..-1]
+ else
+ @options[:controller] = controller
+ end
+ end
end
# Move 'index' action from options to recall
def normalize_action!
- if @options[:action] == 'index'
+ if @options[:action] == 'index'.freeze
@recall[:action] = @options.delete(:action)
end
end
@@ -795,12 +815,12 @@ module ActionDispatch
if app.matches?(req) && app.dispatcher?
dispatcher = app.app
- if dispatcher.controller(params, false)
- dispatcher.prepare_params!(params)
- return params
- else
+ dispatcher.controller(params, false) do
raise ActionController::RoutingError, "A route matches #{path.inspect}, but references missing controller: #{params[:controller].camelize}Controller"
end
+
+ dispatcher.prepare_params!(params)
+ return params
end
end
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index eb554ec383..967bbd62f8 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -52,9 +52,11 @@ module ActionDispatch
# argument.
#
# For convenience reasons, mailers provide a shortcut for ActionController::UrlFor#url_for.
- # So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlFor#url_for'
- # in full. However, mailers don't have hostname information, and that's why you'll still
- # have to specify the <tt>:host</tt> argument when generating URLs in mailers.
+ # So within mailers, you only have to type +url_for+ instead of 'ActionController::UrlFor#url_for'
+ # in full. However, mailers don't have hostname information, and you still have to provide
+ # the +:host+ argument or set the default host that will be used in all mailers using the
+ # configuration option +config.action_mailer.default_url_options+. For more information on
+ # url_for in mailers read the ActionMailer#Base documentation.
#
#
# == URL generation for named routes
@@ -147,6 +149,20 @@ module ActionDispatch
# # => 'http://somehost.org/myapp/tasks/testing'
# url_for controller: 'tasks', action: 'testing', host: 'somehost.org', script_name: "/myapp", only_path: true
# # => '/myapp/tasks/testing'
+ #
+ # Missing routes keys may be filled in from the current request's parameters
+ # (e.g. +:controller+, +:action+, +:id+ and any other parameters that are
+ # placed in the path). Given that the current action has been reached
+ # through `GET /users/1`:
+ #
+ # url_for(only_path: true) # => '/users/1'
+ # url_for(only_path: true, action: 'edit') # => '/users/1/edit'
+ # url_for(only_path: true, action: 'edit', id: 2) # => '/users/2/edit'
+ #
+ # Notice that no +:id+ parameter was provided to the first +url_for+ call
+ # and the helper used the one from the route's path. Any path parameter
+ # implicitly used by +url_for+ can always be overwritten like shown on the
+ # last +url_for+ calls.
def url_for(options = nil)
case options
when nil
@@ -155,6 +171,10 @@ module ActionDispatch
route_name = options.delete :use_route
_routes.url_for(options.symbolize_keys.reverse_merge!(url_options),
route_name)
+ when ActionController::Parameters
+ route_name = options.delete :use_route
+ _routes.url_for(options.to_unsafe_h.symbolize_keys.
+ reverse_merge!(url_options), route_name)
when String
options
when Symbol
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index 13a72220b3..b6e21b0d28 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -3,6 +3,13 @@ module ActionDispatch
module Assertions
# A small suite of assertions that test responses from \Rails applications.
module ResponseAssertions
+ RESPONSE_PREDICATES = { # :nodoc:
+ success: :successful?,
+ missing: :not_found?,
+ redirect: :redirection?,
+ error: :server_error?,
+ }
+
# Asserts that the response is one of the following types:
#
# * <tt>:success</tt> - Status code was in the 200-299 range
@@ -20,11 +27,9 @@ module ActionDispatch
# # assert that the response code was status code 401 (unauthorized)
# assert_response 401
def assert_response(type, message = nil)
- message ||= "Expected response to be a <#{type}>, but was <#{@response.response_code}>"
-
if Symbol === type
if [:success, :missing, :redirect, :error].include?(type)
- assert @response.send("#{type}?"), message
+ assert_predicate @response, RESPONSE_PREDICATES[type], message
else
code = Rack::Utils::SYMBOL_TO_STATUS_CODE[type]
if code.nil?
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index c94eea9134..54e24ed6bf 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -86,8 +86,8 @@ module ActionDispatch
end
# Load routes.rb if it hasn't been loaded.
- generated_path, extra_keys = @routes.generate_extras(options, defaults)
- found_extras = options.reject { |k, _| ! extra_keys.include? k }
+ generated_path, query_string_keys = @routes.generate_extras(options, defaults)
+ found_extras = options.reject { |k, _| ! query_string_keys.include? k }
msg = message || sprintf("found extras <%s>, not <%s>", found_extras, extras)
assert_equal(extras, found_extras, msg)
@@ -165,7 +165,7 @@ module ActionDispatch
# ROUTES TODO: These assertions should really work in an integration context
def method_missing(selector, *args, &block)
- if defined?(@controller) && @controller && @routes && @routes.named_routes.route_defined?(selector)
+ if defined?(@controller) && @controller && defined?(@routes) && @routes && @routes.named_routes.route_defined?(selector)
@controller.send(selector, *args, &block)
else
super
@@ -183,7 +183,7 @@ module ActionDispatch
end
# Assume given controller
- request = ActionController::TestRequest.new
+ request = ActionController::TestRequest.create
if path =~ %r{://}
fail_on(URI::InvalidURIError, msg) do
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 9390e2937a..0cdc6d4e77 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -2,6 +2,7 @@ require 'stringio'
require 'uri'
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/object/try'
+require 'active_support/core_ext/string/strip'
require 'rack/test'
require 'minitest'
@@ -80,7 +81,7 @@ module ActionDispatch
#
# xhr :get, '/feed', params: { since: 201501011400 }
def xml_http_request(request_method, path, *args)
- if kwarg_request?(*args)
+ if kwarg_request?(args)
params, headers, env = args.first.values_at(:params, :headers, :env)
else
params = args[0]
@@ -290,16 +291,16 @@ module ActionDispatch
end
def process_with_kwargs(http_method, path, *args)
- if kwarg_request?(*args)
+ if kwarg_request?(args)
process(http_method, path, *args)
else
- non_kwarg_request_warning if args.present?
+ 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)
- def kwarg_request?(*args)
+ def kwarg_request?(args)
args[0].respond_to?(:keys) && args[0].keys.any? { |k| REQUEST_KWARGS.include?(k) }
end
@@ -324,7 +325,11 @@ module ActionDispatch
if path =~ %r{://}
location = URI.parse(path)
https! URI::HTTPS === location if location.scheme
- host! "#{location.host}:#{location.port}" if location.host
+ if url_host = location.host
+ default = Rack::Request::DEFAULT_PORTS[location.scheme]
+ url_host += ":#{location.port}" if default != location.port
+ host! url_host
+ end
path = location.query ? "#{location.path}?#{location.query}" : location.path
end
@@ -373,7 +378,7 @@ module ActionDispatch
@html_document = nil
@url_options = nil
- @controller = session.last_request.env['action_controller.instance']
+ @controller = @request.controller_instance
response.status
end
@@ -390,7 +395,7 @@ module ActionDispatch
attr_reader :app
- def before_setup
+ def before_setup # :nodoc:
@app = nil
@integration_session = nil
super
@@ -428,7 +433,6 @@ module ActionDispatch
# reset the html_document variable, except for cookies/assigns calls
unless method == 'cookies' || method == 'assigns'
@html_document = nil
- reset_template_assertion
end
integration_session.__send__(method, *args).tap do
@@ -583,7 +587,7 @@ module ActionDispatch
# https!(false)
# get "/articles/all"
# assert_response :success
- # assert assigns(:articles)
+ # assert_select 'h1', 'Articles'
# end
# end
#
@@ -622,7 +626,7 @@ module ActionDispatch
# def browses_site
# get "/products/all"
# assert_response :success
- # assert assigns(:products)
+ # assert_select 'h1', 'Products'
# end
# end
#
diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb
index 630e6a9b78..c28d701b48 100644
--- a/actionpack/lib/action_dispatch/testing/test_process.rb
+++ b/actionpack/lib/action_dispatch/testing/test_process.rb
@@ -5,9 +5,9 @@ require 'active_support/core_ext/hash/indifferent_access'
module ActionDispatch
module TestProcess
def assigns(key = nil)
- assigns = {}.with_indifferent_access
- @controller.view_assigns.each { |k, v| assigns.regular_writer(k, v) }
- key.nil? ? assigns : assigns[key]
+ raise NoMethodError,
+ "assigns has been extracted to a gem. To continue using it,
+ add `gem 'rails-controller-testing'` to your Gemfile."
end
def session
@@ -19,7 +19,7 @@ module ActionDispatch
end
def cookies
- @request.cookie_jar
+ @cookie_jar ||= Cookies::CookieJar.build(@request, @request.cookies)
end
def redirect_to_url
diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb
index 4b9a088265..ad1a7f7109 100644
--- a/actionpack/lib/action_dispatch/testing/test_request.rb
+++ b/actionpack/lib/action_dispatch/testing/test_request.rb
@@ -4,19 +4,22 @@ require 'rack/utils'
module ActionDispatch
class TestRequest < Request
DEFAULT_ENV = Rack::MockRequest.env_for('/',
- 'HTTP_HOST' => 'test.host',
- 'REMOTE_ADDR' => '0.0.0.0',
- 'HTTP_USER_AGENT' => 'Rails Testing'
+ 'HTTP_HOST' => 'test.host',
+ 'REMOTE_ADDR' => '0.0.0.0',
+ 'HTTP_USER_AGENT' => 'Rails Testing',
)
- def self.new(env = {})
- super
+ # Create a new test request with default `env` values
+ def self.create(env = {})
+ env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
+ env["rack.request.cookie_hash"] ||= {}.with_indifferent_access
+ new(default_env.merge(env))
end
- def initialize(env = {})
- env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
- super(default_env.merge(env))
+ def self.default_env
+ DEFAULT_ENV
end
+ private_class_method :default_env
def request_method=(method)
@env['REQUEST_METHOD'] = method.to_s.upcase
@@ -62,17 +65,5 @@ module ActionDispatch
@env.delete('action_dispatch.request.accepts')
@env['HTTP_ACCEPT'] = Array(mime_types).collect(&:to_s).join(",")
end
-
- alias :rack_cookies :cookies
-
- def cookies
- @cookies ||= {}.with_indifferent_access
- end
-
- private
-
- def default_env
- DEFAULT_ENV
- end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb
index a9b88ac5fd..6a31d6243f 100644
--- a/actionpack/lib/action_dispatch/testing/test_response.rb
+++ b/actionpack/lib/action_dispatch/testing/test_response.rb
@@ -16,9 +16,6 @@ module ActionDispatch
# Was the URL not found?
alias_method :missing?, :not_found?
- # Were we redirected?
- alias_method :redirect?, :redirection?
-
# Was there a server-side error?
alias_method :error?, :server_error?
end
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 62ff1be5c9..60e2cea8a2 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -14,7 +14,11 @@ silence_warnings do
end
require 'drb'
-require 'drb/unix'
+begin
+ require 'drb/unix'
+rescue LoadError
+ puts "'drb/unix' is not available"
+end
require 'tempfile'
PROCESS_COUNT = (ENV['N'] || 4).to_i
@@ -59,6 +63,10 @@ FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
SharedTestRoutes = ActionDispatch::Routing::RouteSet.new
+SharedTestRoutes.draw do
+ get ':controller(/:action)'
+end
+
module ActionDispatch
module SharedRoutes
def before_setup
@@ -66,35 +74,10 @@ module ActionDispatch
super
end
end
-
- # Hold off drawing routes until all the possible controller classes
- # have been loaded.
- module DrawOnce
- class << self
- attr_accessor :drew
- end
- self.drew = false
-
- def before_setup
- super
- return if DrawOnce.drew
-
- SharedTestRoutes.draw do
- get ':controller(/:action)'
- end
-
- ActionDispatch::IntegrationTest.app.routes.draw do
- get ':controller(/:action)'
- end
-
- DrawOnce.drew = true
- end
- end
end
module ActiveSupport
class TestCase
- include ActionDispatch::DrawOnce
if RUBY_ENGINE == "ruby" && PROCESS_COUNT > 0
parallelize_me!
end
@@ -115,23 +98,25 @@ class RoutedRackApp
end
class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
- include ActionDispatch::SharedRoutes
-
def self.build_app(routes = nil)
RoutedRackApp.new(routes || ActionDispatch::Routing::RouteSet.new) do |middleware|
- middleware.use "ActionDispatch::ShowExceptions", ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public")
- middleware.use "ActionDispatch::DebugExceptions"
- middleware.use "ActionDispatch::Callbacks"
- middleware.use "ActionDispatch::ParamsParser"
- middleware.use "ActionDispatch::Cookies"
- middleware.use "ActionDispatch::Flash"
- middleware.use "Rack::Head"
+ middleware.use ActionDispatch::ShowExceptions, ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public")
+ middleware.use ActionDispatch::DebugExceptions
+ middleware.use ActionDispatch::Callbacks
+ middleware.use ActionDispatch::ParamsParser
+ middleware.use ActionDispatch::Cookies
+ middleware.use ActionDispatch::Flash
+ middleware.use Rack::Head
yield(middleware) if block_given?
end
end
self.app = build_app
+ app.routes.draw do
+ get ':controller(/:action)'
+ end
+
# 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
@@ -145,14 +130,10 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
end
end
- def self.stub_controllers
- old_dispatcher = ActionDispatch::Routing::RouteSet::Dispatcher
- ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher }
- ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, StubDispatcher }
- yield ActionDispatch::Routing::RouteSet.new
- ensure
- ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher }
- ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, old_dispatcher }
+ def self.stub_controllers(config = nil)
+ route_set = ActionDispatch::Routing::RouteSet.new(*[config].compact)
+ route_set.dispatcher_class = StubDispatcher
+ yield route_set
end
def with_routing(&block)
@@ -228,6 +209,10 @@ class Rack::TestCase < ActionDispatch::IntegrationTest
end
module ActionController
+ class API
+ extend AbstractController::Railties::RoutesHelpers.with(SharedTestRoutes)
+ end
+
class Base
# This stub emulates the Railtie including the URL helpers from a Rails application
extend AbstractController::Railties::RoutesHelpers.with(SharedTestRoutes)
@@ -376,7 +361,7 @@ module RoutingTestHelpers
end
class ResourcesController < ActionController::Base
- def index() render :nothing => true end
+ def index() head :ok end
alias_method :show, :index
end
diff --git a/actionpack/test/assertions/response_assertions_test.rb b/actionpack/test/assertions/response_assertions_test.rb
index 5e64cae7e2..82c747680d 100644
--- a/actionpack/test/assertions/response_assertions_test.rb
+++ b/actionpack/test/assertions/response_assertions_test.rb
@@ -7,7 +7,7 @@ module ActionDispatch
include ResponseAssertions
FakeResponse = Struct.new(:response_code) do
- [:success, :missing, :redirect, :error].each do |sym|
+ [:successful, :not_found, :redirection, :server_error].each do |sym|
define_method("#{sym}?") do
sym == response_code
end
@@ -16,7 +16,7 @@ module ActionDispatch
def test_assert_response_predicate_methods
[:success, :missing, :redirect, :error].each do |sym|
- @response = FakeResponse.new sym
+ @response = FakeResponse.new RESPONSE_PREDICATES[sym].to_s.sub(/\?/, '').to_sym
assert_response sym
assert_raises(Minitest::Assertion) {
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index 21586e2193..beeafc2e53 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -5,9 +5,6 @@ class ActionPackAssertionsController < ActionController::Base
def nothing() head :ok end
- def hello_world() render :template => "test/hello_world"; end
- def hello_repeating_in_path() render :template => "test/hello/hello"; end
-
def hello_xml_world() render :template => "test/hello_xml_world"; end
def hello_xml_world_pdf
@@ -20,8 +17,6 @@ class ActionPackAssertionsController < ActionController::Base
render :template => "test/hello_xml_world"
end
- def partial() render :partial => 'test/partial'; end
-
def redirect_internal() redirect_to "/nothing"; end
def redirect_to_action() redirect_to :action => "flash_me", :id => 1, :params => { "panda" => "fun" }; end
@@ -48,12 +43,12 @@ class ActionPackAssertionsController < ActionController::Base
def flash_me
flash['hello'] = 'my name is inigo montoya...'
- render :text => "Inconceivable!"
+ render plain: "Inconceivable!"
end
def flash_me_naked
flash.clear
- render :text => "wow!"
+ render plain: "wow!"
end
def assign_this
@@ -62,40 +57,30 @@ class ActionPackAssertionsController < ActionController::Base
end
def render_based_on_parameters
- render :text => "Mr. #{params[:name]}"
+ render plain: "Mr. #{params[:name]}"
end
def render_url
- render :text => "<div>#{url_for(:action => 'flash_me', :only_path => true)}</div>"
+ render html: "<div>#{url_for(action: 'flash_me', only_path: true)}</div>"
end
def render_text_with_custom_content_type
- render :text => "Hello!", :content_type => Mime::RSS
- end
-
- def render_with_layout
- @variable_for_layout = nil
- render "test/hello_world", :layout => "layouts/standard"
- end
-
- def render_with_layout_and_partial
- @variable_for_layout = nil
- render "test/hello_world_with_partial", :layout => "layouts/standard"
+ render body: "Hello!", content_type: Mime::RSS
end
def session_stuffing
session['xmas'] = 'turkey'
- render :text => "ho ho ho"
+ render plain: "ho ho ho"
end
def raise_exception_on_get
raise "get" if request.get?
- render :text => "request method: #{request.env['REQUEST_METHOD']}"
+ render plain: "request method: #{request.env['REQUEST_METHOD']}"
end
def raise_exception_on_post
raise "post" if request.post?
- render :text => "request method: #{request.env['REQUEST_METHOD']}"
+ render plain: "request method: #{request.env['REQUEST_METHOD']}"
end
def render_file_absolute_path
@@ -116,14 +101,14 @@ class AssertResponseWithUnexpectedErrorController < ActionController::Base
end
def show
- render :text => "Boom", :status => 500
+ render plain: "Boom", status: 500
end
end
module Admin
class InnerModuleController < ActionController::Base
def index
- render :nothing => true
+ head :ok
end
def redirect_to_index
@@ -304,14 +289,6 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
assert session.empty?
end
- def test_render_template_action
- process :nothing
- assert_template nil
-
- process :hello_world
- assert_template 'hello_world'
- end
-
def test_redirection_location
process :redirect_internal
assert_equal 'http://test.host/nothing', @response.redirect_url
@@ -341,7 +318,7 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
def test_missing_response_code
process :response404
- assert @response.missing?
+ assert @response.not_found?
end
def test_client_error_response_code
@@ -369,12 +346,12 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
def test_successful_response_code
process :nothing
- assert @response.success?
+ assert @response.successful?
end
def test_response_object
process :nothing
- assert_kind_of ActionController::TestResponse, @response
+ assert_kind_of ActionDispatch::TestResponse, @response
end
def test_render_based_on_parameters
@@ -455,192 +432,6 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
end
end
-class AssertTemplateTest < ActionController::TestCase
- tests ActionPackAssertionsController
-
- def test_with_invalid_hash_keys_raises_argument_error
- assert_raise(ArgumentError) do
- assert_template foo: "bar"
- end
- end
-
- def test_with_partial
- get :partial
- assert_template :partial => '_partial'
- end
-
- def test_file_with_absolute_path_success
- get :render_file_absolute_path
- assert_template :file => File.expand_path('../../../README.rdoc', __FILE__)
- end
-
- def test_file_with_relative_path_success
- get :render_file_relative_path
- assert_template :file => 'README.rdoc'
- end
-
- def test_with_file_failure
- get :render_file_absolute_path
- assert_raise(ActiveSupport::TestCase::Assertion) do
- assert_template :file => 'test/hello_world'
- end
-
- get :render_file_absolute_path
- assert_raise(ActiveSupport::TestCase::Assertion) do
- assert_template file: nil
- end
- end
-
- def test_with_nil_passes_when_no_template_rendered
- get :nothing
- assert_template nil
- end
-
- def test_with_nil_fails_when_template_rendered
- get :hello_world
- assert_raise(ActiveSupport::TestCase::Assertion) do
- assert_template nil
- end
- end
-
- def test_with_empty_string_fails_when_template_rendered
- get :hello_world
- assert_raise(ActiveSupport::TestCase::Assertion) do
- assert_template ""
- end
- end
-
- def test_with_empty_string_fails_when_no_template_rendered
- get :nothing
- assert_raise(ActiveSupport::TestCase::Assertion) do
- assert_template ""
- end
- end
-
- def test_passes_with_correct_string
- get :hello_world
- assert_template 'hello_world'
- assert_template 'test/hello_world'
- end
-
- def test_passes_with_correct_symbol
- get :hello_world
- assert_template :hello_world
- end
-
- def test_fails_with_incorrect_string
- get :hello_world
- assert_raise(ActiveSupport::TestCase::Assertion) do
- assert_template 'hello_planet'
- end
- end
-
- def test_fails_with_incorrect_string_that_matches
- get :hello_world
- assert_raise(ActiveSupport::TestCase::Assertion) do
- assert_template 'est/he'
- end
- end
-
- def test_fails_with_repeated_name_in_path
- get :hello_repeating_in_path
- assert_raise(ActiveSupport::TestCase::Assertion) do
- assert_template 'test/hello'
- end
- end
-
- def test_fails_with_incorrect_symbol
- get :hello_world
- assert_raise(ActiveSupport::TestCase::Assertion) do
- assert_template :hello_planet
- end
- end
-
- def test_fails_with_incorrect_symbol_that_matches
- get :hello_world
- assert_raise(ActiveSupport::TestCase::Assertion) do
- assert_template :"est/he"
- end
- end
-
- def test_fails_with_wrong_layout
- get :render_with_layout
- assert_raise(ActiveSupport::TestCase::Assertion) do
- assert_template :layout => "application"
- end
- end
-
- def test_fails_expecting_no_layout
- get :render_with_layout
- assert_raise(ActiveSupport::TestCase::Assertion) do
- assert_template :layout => nil
- end
- end
-
- def test_fails_expecting_not_known_layout
- get :render_with_layout
- assert_raise(ArgumentError) do
- assert_template :layout => 1
- end
- end
-
- def test_passes_with_correct_layout
- get :render_with_layout
- assert_template :layout => "layouts/standard"
- end
-
- def test_passes_with_layout_and_partial
- get :render_with_layout_and_partial
- assert_template :layout => "layouts/standard"
- end
-
- def test_passed_with_no_layout
- get :hello_world
- assert_template :layout => nil
- end
-
- def test_passed_with_no_layout_false
- get :hello_world
- assert_template :layout => false
- end
-
- def test_passes_with_correct_layout_without_layouts_prefix
- get :render_with_layout
- assert_template :layout => "standard"
- end
-
- def test_passes_with_correct_layout_symbol
- get :render_with_layout
- assert_template :layout => :standard
- end
-
- def test_assert_template_reset_between_requests
- get :hello_world
- assert_template 'test/hello_world'
-
- get :nothing
- assert_template nil
-
- get :partial
- assert_template partial: 'test/_partial'
-
- get :nothing
- assert_template partial: nil
-
- get :render_with_layout
- assert_template layout: 'layouts/standard'
-
- get :nothing
- assert_template layout: nil
-
- get :render_file_relative_path
- assert_template file: 'README.rdoc'
-
- get :nothing
- assert_template file: nil
- end
-end
-
class ActionPackHeaderTest < ActionController::TestCase
tests ActionPackAssertionsController
diff --git a/actionpack/test/controller/api/conditional_get_test.rb b/actionpack/test/controller/api/conditional_get_test.rb
new file mode 100644
index 0000000000..b4f1673be0
--- /dev/null
+++ b/actionpack/test/controller/api/conditional_get_test.rb
@@ -0,0 +1,57 @@
+require 'abstract_unit'
+require 'active_support/core_ext/integer/time'
+require 'active_support/core_ext/numeric/time'
+
+class ConditionalGetApiController < ActionController::API
+ before_action :handle_last_modified_and_etags, only: :two
+
+ def one
+ if stale?(last_modified: Time.now.utc.beginning_of_day, etag: [:foo, 123])
+ render plain: "Hi!"
+ end
+ end
+
+ def two
+ render plain: "Hi!"
+ end
+
+ private
+
+ def handle_last_modified_and_etags
+ fresh_when(last_modified: Time.now.utc.beginning_of_day, etag: [ :foo, 123 ])
+ end
+end
+
+class ConditionalGetApiTest < ActionController::TestCase
+ tests ConditionalGetApiController
+
+ def setup
+ @last_modified = Time.now.utc.beginning_of_day.httpdate
+ end
+
+ def test_request_gets_last_modified
+ get :two
+ assert_equal @last_modified, @response.headers['Last-Modified']
+ assert_response :success
+ end
+
+ def test_request_obeys_last_modified
+ @request.if_modified_since = @last_modified
+ get :two
+ assert_response :not_modified
+ end
+
+ def test_last_modified_works_with_less_than_too
+ @request.if_modified_since = 5.years.ago.httpdate
+ get :two
+ assert_response :success
+ end
+
+ def test_request_not_modified
+ @request.if_modified_since = @last_modified
+ get :one
+ assert_equal 304, @response.status.to_i
+ assert @response.body.blank?
+ assert_equal @last_modified, @response.headers['Last-Modified']
+ end
+end
diff --git a/actionpack/test/controller/api/data_streaming_test.rb b/actionpack/test/controller/api/data_streaming_test.rb
new file mode 100644
index 0000000000..0e7d97d1f4
--- /dev/null
+++ b/actionpack/test/controller/api/data_streaming_test.rb
@@ -0,0 +1,26 @@
+require 'abstract_unit'
+
+module TestApiFileUtils
+ def file_path() File.expand_path(__FILE__) end
+ def file_data() @data ||= File.open(file_path, 'rb') { |f| f.read } end
+end
+
+class DataStreamingApiController < ActionController::API
+ include TestApiFileUtils
+
+ def one; end
+ def two
+ send_data(file_data, {})
+ end
+end
+
+class DataStreamingApiTest < ActionController::TestCase
+ include TestApiFileUtils
+ tests DataStreamingApiController
+
+ def test_data
+ response = process('two')
+ assert_kind_of String, response.body
+ assert_equal file_data, response.body
+ end
+end
diff --git a/actionpack/test/controller/api/force_ssl_test.rb b/actionpack/test/controller/api/force_ssl_test.rb
new file mode 100644
index 0000000000..8578340d82
--- /dev/null
+++ b/actionpack/test/controller/api/force_ssl_test.rb
@@ -0,0 +1,20 @@
+require 'abstract_unit'
+
+class ForceSSLApiController < ActionController::API
+ force_ssl
+
+ def one; end
+ def two
+ head :ok
+ end
+end
+
+class ForceSSLApiTest < ActionController::TestCase
+ tests ForceSSLApiController
+
+ def test_redirects_to_https
+ get :two
+ assert_response 301
+ assert_equal "https://test.host/force_ssl_api/two", redirect_to_url
+ end
+end
diff --git a/actionpack/test/controller/api/implicit_render_test.rb b/actionpack/test/controller/api/implicit_render_test.rb
new file mode 100644
index 0000000000..26f9cd8f78
--- /dev/null
+++ b/actionpack/test/controller/api/implicit_render_test.rb
@@ -0,0 +1,15 @@
+require 'abstract_unit'
+
+class ImplicitRenderAPITestController < ActionController::API
+ def empty_action
+ end
+end
+
+class ImplicitRenderAPITest < ActionController::TestCase
+ tests ImplicitRenderAPITestController
+
+ def test_implicit_no_content_response
+ get :empty_action
+ assert_response :no_content
+ end
+end
diff --git a/actionpack/test/controller/api/params_wrapper_test.rb b/actionpack/test/controller/api/params_wrapper_test.rb
new file mode 100644
index 0000000000..53b3a0c3cc
--- /dev/null
+++ b/actionpack/test/controller/api/params_wrapper_test.rb
@@ -0,0 +1,26 @@
+require 'abstract_unit'
+
+class ParamsWrapperForApiTest < ActionController::TestCase
+ class UsersController < ActionController::API
+ attr_accessor :last_parameters
+
+ wrap_parameters :person, format: [:json]
+
+ def test
+ self.last_parameters = params.except(:controller, :action).to_unsafe_h
+ head :ok
+ end
+ end
+
+ class Person; end
+
+ tests UsersController
+
+ def test_specify_wrapper_name
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :test, params: { 'username' => 'sikachu' }
+
+ expected = { 'username' => 'sikachu', 'person' => { 'username' => 'sikachu' }}
+ assert_equal expected, @controller.last_parameters
+ end
+end
diff --git a/actionpack/test/controller/api/redirect_to_test.rb b/actionpack/test/controller/api/redirect_to_test.rb
new file mode 100644
index 0000000000..18877c4b3a
--- /dev/null
+++ b/actionpack/test/controller/api/redirect_to_test.rb
@@ -0,0 +1,19 @@
+require 'abstract_unit'
+
+class RedirectToApiController < ActionController::API
+ def one
+ redirect_to action: "two"
+ end
+
+ def two; end
+end
+
+class RedirectToApiTest < ActionController::TestCase
+ tests RedirectToApiController
+
+ def test_redirect_to
+ get :one
+ assert_response :redirect
+ assert_equal "http://test.host/redirect_to_api/two", redirect_to_url
+ end
+end
diff --git a/actionpack/test/controller/api/renderers_test.rb b/actionpack/test/controller/api/renderers_test.rb
new file mode 100644
index 0000000000..9405538833
--- /dev/null
+++ b/actionpack/test/controller/api/renderers_test.rb
@@ -0,0 +1,38 @@
+require 'abstract_unit'
+require 'active_support/core_ext/hash/conversions'
+
+class RenderersApiController < ActionController::API
+ class Model
+ def to_json(options = {})
+ { a: 'b' }.to_json(options)
+ end
+
+ def to_xml(options = {})
+ { a: 'b' }.to_xml(options)
+ end
+ end
+
+ def one
+ render json: Model.new
+ end
+
+ def two
+ render xml: Model.new
+ end
+end
+
+class RenderersApiTest < ActionController::TestCase
+ tests RenderersApiController
+
+ def test_render_json
+ get :one
+ assert_response :success
+ assert_equal({ a: 'b' }.to_json, @response.body)
+ end
+
+ def test_render_xml
+ get :two
+ assert_response :success
+ assert_equal({ a: 'b' }.to_xml, @response.body)
+ end
+end
diff --git a/actionpack/test/controller/api/url_for_test.rb b/actionpack/test/controller/api/url_for_test.rb
new file mode 100644
index 0000000000..0d8691a091
--- /dev/null
+++ b/actionpack/test/controller/api/url_for_test.rb
@@ -0,0 +1,20 @@
+require 'abstract_unit'
+
+class UrlForApiController < ActionController::API
+ def one; end
+ def two; end
+end
+
+class UrlForApiTest < ActionController::TestCase
+ tests UrlForApiController
+
+ def setup
+ super
+ @request.host = 'www.example.com'
+ end
+
+ def test_url_for
+ get :one
+ assert_equal "http://www.example.com/url_for_api/one", @controller.url_for
+ end
+end
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
index f7ad8e5158..d9374ce9c3 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -13,7 +13,7 @@ end
class NonEmptyController < ActionController::Base
def public_action
- render :nothing => true
+ head :ok
end
end
@@ -29,7 +29,7 @@ end
class OptionalDefaultUrlOptionsController < ActionController::Base
def show
- render nothing: true
+ head :ok
end
def default_url_options
@@ -53,7 +53,7 @@ end
class ActionMissingController < ActionController::Base
def action_missing(action)
- render :text => "Response for #{action}"
+ render plain: "Response for #{action}"
end
end
@@ -127,8 +127,6 @@ class PerformActionTest < ActionController::TestCase
# a more accurate simulation of what happens in "real life".
@controller.logger = ActiveSupport::Logger.new(nil)
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
@request.host = "www.nextangle.com"
end
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index 2d6607041d..5698159eba 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -22,8 +22,6 @@ class FragmentCachingMetalTest < ActionController::TestCase
@controller.perform_caching = true
@controller.cache_store = @store
@params = { controller: 'posts', action: 'index' }
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
@controller.params = @params
@controller.request = @request
@controller.response = @response
@@ -52,8 +50,6 @@ class FragmentCachingTest < ActionController::TestCase
@controller.perform_caching = true
@controller.cache_store = @store
@params = {:controller => 'posts', :action => 'index'}
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
@controller.params = @params
@controller.request = @request
@controller.response = @response
@@ -166,6 +162,8 @@ class FunctionalCachingController < CachingController
end
def formatted_fragment_cached_with_variant
+ request.variant = :phone if params[:v] == "phone"
+
respond_to do |format|
format.html.phone
format.html
@@ -183,8 +181,6 @@ class FunctionalFragmentCachingTest < ActionController::TestCase
@controller = FunctionalCachingController.new
@controller.perform_caching = true
@controller.cache_store = @store
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
end
def test_fragment_caching
@@ -268,9 +264,7 @@ CACHED
def test_fragment_caching_with_variant
- @request.variant = :phone
-
- get :formatted_fragment_cached_with_variant, format: "html"
+ get :formatted_fragment_cached_with_variant, format: "html", params: { v: :phone }
assert_response :success
expected_body = "<body>\n<p>PHONE</p>\n</body>\n"
@@ -352,6 +346,8 @@ class ViewCacheDependencyTest < ActionController::TestCase
end
class CollectionCacheController < ActionController::Base
+ attr_accessor :partial_rendered_times
+
def index
@customers = [Customer.new('david', params[:id] || 1)]
end
@@ -377,14 +373,17 @@ class AutomaticCollectionCacheTest < ActionController::TestCase
super
@controller = CollectionCacheController.new
@controller.perform_caching = true
+ @controller.partial_rendered_times = 0
@controller.cache_store = ActiveSupport::Cache::MemoryStore.new
+ ActionView::PartialRenderer.collection_cache = @controller.cache_store
end
def test_collection_fetches_cached_views
get :index
+ assert_equal 1, @controller.partial_rendered_times
- ActionView::PartialRenderer.expects(:collection_with_template).never
get :index
+ assert_equal 1, @controller.partial_rendered_times
end
def test_preserves_order_when_reading_from_cache_plus_rendering
@@ -402,8 +401,9 @@ class AutomaticCollectionCacheTest < ActionController::TestCase
def test_caching_works_with_beginning_comment
get :index_with_comment
+ assert_equal 1, @controller.partial_rendered_times
- ActionView::PartialRenderer.expects(:collection_with_template).never
get :index_with_comment
+ assert_equal 1, @controller.partial_rendered_times
end
end
diff --git a/actionpack/test/controller/content_type_test.rb b/actionpack/test/controller/content_type_test.rb
index 89667df3a4..c5bbc479c9 100644
--- a/actionpack/test/controller/content_type_test.rb
+++ b/actionpack/test/controller/content_type_test.rb
@@ -4,29 +4,29 @@ class OldContentTypeController < ActionController::Base
# :ported:
def render_content_type_from_body
response.content_type = Mime::RSS
- render :text => "hello world!"
+ render body: "hello world!"
end
# :ported:
def render_defaults
- render :text => "hello world!"
+ render body: "hello world!"
end
# :ported:
def render_content_type_from_render
- render :text => "hello world!", :content_type => Mime::RSS
+ render body: "hello world!", :content_type => Mime::RSS
end
# :ported:
def render_charset_from_body
response.charset = "utf-16"
- render :text => "hello world!"
+ render body: "hello world!"
end
# :ported:
def render_nil_charset_from_body
response.charset = nil
- render :text => "hello world!"
+ render body: "hello world!"
end
def render_default_for_erb
@@ -42,10 +42,10 @@ class OldContentTypeController < ActionController::Base
def render_default_content_types_for_respond_to
respond_to do |format|
- format.html { render :text => "hello world!" }
- format.xml { render :action => "render_default_content_types_for_respond_to" }
- format.js { render :text => "hello world!" }
- format.rss { render :text => "hello world!", :content_type => Mime::XML }
+ format.html { render body: "hello world!" }
+ format.xml { render action: "render_default_content_types_for_respond_to" }
+ format.js { render body: "hello world!" }
+ format.rss { render body: "hello world!", content_type: Mime::XML }
end
end
end
@@ -64,14 +64,14 @@ class ContentTypeTest < ActionController::TestCase
def test_render_defaults
get :render_defaults
assert_equal "utf-8", @response.charset
- assert_equal Mime::HTML, @response.content_type
+ assert_equal Mime::TEXT, @response.content_type
end
def test_render_changed_charset_default
with_default_charset "utf-16" do
get :render_defaults
assert_equal "utf-16", @response.charset
- assert_equal Mime::HTML, @response.content_type
+ assert_equal Mime::TEXT, @response.content_type
end
end
@@ -92,14 +92,14 @@ class ContentTypeTest < ActionController::TestCase
# :ported:
def test_charset_from_body
get :render_charset_from_body
- assert_equal Mime::HTML, @response.content_type
+ assert_equal Mime::TEXT, @response.content_type
assert_equal "utf-16", @response.charset
end
# :ported:
def test_nil_charset_from_body
get :render_nil_charset_from_body
- assert_equal Mime::HTML, @response.content_type
+ assert_equal Mime::TEXT, @response.content_type
assert_equal "utf-8", @response.charset, @response.headers.inspect
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 230f40d7ad..12fbe0424e 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
@@ -6,7 +6,7 @@ class ControllerWithBeforeActionAndDefaultUrlOptions < ActionController::Base
after_action { I18n.locale = "en" }
def target
- render :text => "final response"
+ render plain: "final response"
end
def redirect
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
index a1ce12a13e..08271012e9 100644
--- a/actionpack/test/controller/filters_test.rb
+++ b/actionpack/test/controller/filters_test.rb
@@ -13,16 +13,6 @@ class ActionController::Base
filters.map!(&:raw_filter)
end
end
-
- def assigns(key = nil)
- assigns = {}
- instance_variables.each do |ivar|
- next if ActionController::Base.protected_instance_variables.include?(ivar)
- assigns[ivar[1..-1]] = instance_variable_get(ivar)
- end
-
- key.nil? ? assigns : assigns[key.to_s]
- end
end
class FilterTest < ActionController::TestCase
@@ -50,7 +40,7 @@ class FilterTest < ActionController::TestCase
before_action :ensure_login, :except => [:go_wild]
def go_wild
- render :text => "gobble"
+ render plain: "gobble"
end
end
@@ -61,7 +51,7 @@ class FilterTest < ActionController::TestCase
(1..3).each do |i|
define_method "fail_#{i}" do
- render :text => i.to_s
+ render plain: i.to_s
end
end
@@ -232,7 +222,7 @@ class FilterTest < ActionController::TestCase
skip_before_action :clean_up_tmp, only: :login, if: -> { true }
def login
- render text: 'ok'
+ render plain: 'ok'
end
end
@@ -244,7 +234,7 @@ class FilterTest < ActionController::TestCase
skip_before_action :clean_up_tmp, if: -> { true }, except: :login
def login
- render text: 'ok'
+ render plain: 'ok'
end
end
@@ -268,11 +258,11 @@ class FilterTest < ActionController::TestCase
before_action :ensure_login, :only => :index
def index
- render :text => 'ok'
+ render plain: 'ok'
end
def public
- render :text => 'ok'
+ render plain: 'ok'
end
end
@@ -282,7 +272,7 @@ class FilterTest < ActionController::TestCase
before_action :ensure_login
def index
- render :text => 'ok'
+ render plain: 'ok'
end
private
@@ -393,7 +383,7 @@ class FilterTest < ActionController::TestCase
before_action(AuditFilter)
def show
- render :text => "hello"
+ render plain: "hello"
end
end
@@ -431,11 +421,11 @@ class FilterTest < ActionController::TestCase
before_action :second, :only => :foo
def foo
- render :text => 'foo'
+ render plain: 'foo'
end
def bar
- render :text => 'bar'
+ render plain: 'bar'
end
protected
@@ -452,7 +442,7 @@ class FilterTest < ActionController::TestCase
before_action :choose
%w(foo bar baz).each do |action|
- define_method(action) { render :text => action }
+ define_method(action) { render plain: action }
end
private
@@ -481,7 +471,7 @@ class FilterTest < ActionController::TestCase
@ran_filter << 'between_before_all_and_after_all'
end
def show
- render :text => 'hello'
+ render plain: 'hello'
end
end
@@ -491,7 +481,7 @@ class FilterTest < ActionController::TestCase
def around(controller)
yield
rescue ErrorToRescue => ex
- controller.__send__ :render, :text => "I rescued this: #{ex.inspect}"
+ controller.__send__ :render, plain: "I rescued this: #{ex.inspect}"
end
end
@@ -560,7 +550,7 @@ class FilterTest < ActionController::TestCase
def test_after_actions_are_not_run_if_around_action_does_not_yield
controller = NonYieldingAroundFilterController.new
test_process(controller, "index")
- assert_equal ["filter_one", "it didn't yield"], controller.assigns['filters']
+ assert_equal ["filter_one", "it didn't yield"], controller.instance_variable_get(:@filters)
end
def test_added_action_to_inheritance_graph
@@ -577,140 +567,141 @@ class FilterTest < ActionController::TestCase
def test_running_actions
test_process(PrependingController)
- assert_equal %w( wonderful_life ensure_login ), assigns["ran_filter"]
+ assert_equal %w( wonderful_life ensure_login ),
+ @controller.instance_variable_get(:@ran_filter)
end
def test_running_actions_with_proc
test_process(ProcController)
- assert assigns["ran_proc_action"]
+ assert @controller.instance_variable_get(:@ran_proc_action)
end
def test_running_actions_with_implicit_proc
test_process(ImplicitProcController)
- assert assigns["ran_proc_action"]
+ assert @controller.instance_variable_get(:@ran_proc_action)
end
def test_running_actions_with_class
test_process(AuditController)
- assert assigns["was_audited"]
+ assert @controller.instance_variable_get(:@was_audited)
end
def test_running_anomolous_yet_valid_condition_actions
test_process(AnomolousYetValidConditionController)
- assert_equal %w( ensure_login ), assigns["ran_filter"]
- assert assigns["ran_class_action"]
- assert assigns["ran_proc_action1"]
- assert assigns["ran_proc_action2"]
+ assert_equal %w( ensure_login ), @controller.instance_variable_get(:@ran_filter)
+ assert @controller.instance_variable_get(:@ran_class_action)
+ assert @controller.instance_variable_get(:@ran_proc_action1)
+ assert @controller.instance_variable_get(:@ran_proc_action2)
test_process(AnomolousYetValidConditionController, "show_without_action")
- assert_nil assigns["ran_filter"]
- assert !assigns["ran_class_action"]
- assert !assigns["ran_proc_action1"]
- assert !assigns["ran_proc_action2"]
+ assert_not @controller.instance_variable_defined?(:@ran_filter)
+ assert_not @controller.instance_variable_defined?(:@ran_class_action)
+ assert_not @controller.instance_variable_defined?(:@ran_proc_action1)
+ assert_not @controller.instance_variable_defined?(:@ran_proc_action2)
end
def test_running_conditional_options
test_process(ConditionalOptionsFilter)
- assert_equal %w( ensure_login ), assigns["ran_filter"]
+ assert_equal %w( ensure_login ), @controller.instance_variable_get(:@ran_filter)
end
def test_running_conditional_skip_options
test_process(ConditionalOptionsSkipFilter)
- assert_equal %w( ensure_login ), assigns["ran_filter"]
+ assert_equal %w( ensure_login ), @controller.instance_variable_get(:@ran_filter)
end
def test_if_is_ignored_when_used_with_only
test_process(SkipFilterUsingOnlyAndIf, 'login')
- assert_nil assigns['ran_filter']
+ assert_not @controller.instance_variable_defined?(:@ran_filter)
end
def test_except_is_ignored_when_used_with_if
test_process(SkipFilterUsingIfAndExcept, 'login')
- assert_equal %w(ensure_login), assigns["ran_filter"]
+ assert_equal %w(ensure_login), @controller.instance_variable_get(:@ran_filter)
end
def test_skipping_class_actions
test_process(ClassController)
- assert_equal true, assigns["ran_class_action"]
+ assert_equal true, @controller.instance_variable_get(:@ran_class_action)
skipping_class_controller = Class.new(ClassController) do
skip_before_action ConditionalClassFilter
end
test_process(skipping_class_controller)
- assert_nil assigns['ran_class_action']
+ assert_not @controller.instance_variable_defined?(:@ran_class_action)
end
def test_running_collection_condition_actions
test_process(ConditionalCollectionFilterController)
- assert_equal %w( ensure_login ), assigns["ran_filter"]
+ assert_equal %w( ensure_login ), @controller.instance_variable_get(:@ran_filter)
test_process(ConditionalCollectionFilterController, "show_without_action")
- assert_nil assigns["ran_filter"]
+ assert_not @controller.instance_variable_defined?(:@ran_filter)
test_process(ConditionalCollectionFilterController, "another_action")
- assert_nil assigns["ran_filter"]
+ assert_not @controller.instance_variable_defined?(:@ran_filter)
end
def test_running_only_condition_actions
test_process(OnlyConditionSymController)
- assert_equal %w( ensure_login ), assigns["ran_filter"]
+ assert_equal %w( ensure_login ), @controller.instance_variable_get(:@ran_filter)
test_process(OnlyConditionSymController, "show_without_action")
- assert_nil assigns["ran_filter"]
+ assert_not @controller.instance_variable_defined?(:@ran_filter)
test_process(OnlyConditionProcController)
- assert assigns["ran_proc_action"]
+ assert @controller.instance_variable_get(:@ran_proc_action)
test_process(OnlyConditionProcController, "show_without_action")
- assert !assigns["ran_proc_action"]
+ assert_not @controller.instance_variable_defined?(:@ran_proc_action)
test_process(OnlyConditionClassController)
- assert assigns["ran_class_action"]
+ assert @controller.instance_variable_get(:@ran_class_action)
test_process(OnlyConditionClassController, "show_without_action")
- assert !assigns["ran_class_action"]
+ assert_not @controller.instance_variable_defined?(:@ran_class_action)
end
def test_running_except_condition_actions
test_process(ExceptConditionSymController)
- assert_equal %w( ensure_login ), assigns["ran_filter"]
+ assert_equal %w( ensure_login ), @controller.instance_variable_get(:@ran_filter)
test_process(ExceptConditionSymController, "show_without_action")
- assert_nil assigns["ran_filter"]
+ assert_not @controller.instance_variable_defined?(:@ran_filter)
test_process(ExceptConditionProcController)
- assert assigns["ran_proc_action"]
+ assert @controller.instance_variable_get(:@ran_proc_action)
test_process(ExceptConditionProcController, "show_without_action")
- assert !assigns["ran_proc_action"]
+ assert_not @controller.instance_variable_defined?(:@ran_proc_action)
test_process(ExceptConditionClassController)
- assert assigns["ran_class_action"]
+ assert @controller.instance_variable_get(:@ran_class_action)
test_process(ExceptConditionClassController, "show_without_action")
- assert !assigns["ran_class_action"]
+ assert_not @controller.instance_variable_defined?(:@ran_class_action)
end
def test_running_only_condition_and_conditional_options
test_process(OnlyConditionalOptionsFilter, "show")
- assert_not assigns["ran_conditional_index_proc"]
+ assert_not @controller.instance_variable_defined?(:@ran_conditional_index_proc)
end
def test_running_before_and_after_condition_actions
test_process(BeforeAndAfterConditionController)
- assert_equal %w( ensure_login clean_up_tmp), assigns["ran_filter"]
+ assert_equal %w( ensure_login clean_up_tmp), @controller.instance_variable_get(:@ran_filter)
test_process(BeforeAndAfterConditionController, "show_without_action")
- assert_nil assigns["ran_filter"]
+ assert_not @controller.instance_variable_defined?(:@ran_filter)
end
def test_around_action
test_process(AroundFilterController)
- assert assigns["before_ran"]
- assert assigns["after_ran"]
+ assert @controller.instance_variable_get(:@before_ran)
+ assert @controller.instance_variable_get(:@after_ran)
end
def test_before_after_class_action
test_process(BeforeAfterClassFilterController)
- assert assigns["before_ran"]
- assert assigns["after_ran"]
+ assert @controller.instance_variable_get(:@before_ran)
+ assert @controller.instance_variable_get(:@after_ran)
end
def test_having_properties_in_around_action
test_process(AroundFilterController)
- assert_equal "before and after", assigns["execution_log"]
+ assert_equal "before and after", @controller.instance_variable_get(:@execution_log)
end
def test_prepending_and_appending_around_action
@@ -723,33 +714,33 @@ class FilterTest < ActionController::TestCase
def test_rendering_breaks_actioning_chain
response = test_process(RenderingController)
assert_equal "something else", response.body
- assert !assigns["ran_action"]
+ assert_not @controller.instance_variable_defined?(:@ran_action)
end
def test_before_action_rendering_breaks_actioning_chain_for_after_action
test_process(RenderingController)
- assert_equal %w( before_action_rendering ), assigns["ran_filter"]
- assert !assigns["ran_action"]
+ assert_equal %w( before_action_rendering ), @controller.instance_variable_get(:@ran_filter)
+ assert_not @controller.instance_variable_defined?(:@ran_action)
end
def test_before_action_redirects_breaks_actioning_chain_for_after_action
test_process(BeforeActionRedirectionController)
assert_response :redirect
assert_equal "http://test.host/filter_test/before_action_redirection/target_of_redirection", redirect_to_url
- assert_equal %w( before_action_redirects ), assigns["ran_filter"]
+ assert_equal %w( before_action_redirects ), @controller.instance_variable_get(:@ran_filter)
end
def test_before_action_rendering_breaks_actioning_chain_for_preprend_after_action
test_process(RenderingForPrependAfterActionController)
- assert_equal %w( before_action_rendering ), assigns["ran_filter"]
- assert !assigns["ran_action"]
+ assert_equal %w( before_action_rendering ), @controller.instance_variable_get(:@ran_filter)
+ assert_not @controller.instance_variable_defined?(:@ran_action)
end
def test_before_action_redirects_breaks_actioning_chain_for_preprend_after_action
test_process(BeforeActionRedirectionForPrependAfterActionController)
assert_response :redirect
assert_equal "http://test.host/filter_test/before_action_redirection_for_prepend_after_action/target_of_redirection", redirect_to_url
- assert_equal %w( before_action_redirects ), assigns["ran_filter"]
+ assert_equal %w( before_action_redirects ), @controller.instance_variable_get(:@ran_filter)
end
def test_actions_with_mixed_specialization_run_in_order
@@ -766,35 +757,34 @@ class FilterTest < ActionController::TestCase
def test_dynamic_dispatch
%w(foo bar baz).each do |action|
- request = ActionController::TestRequest.new
- request.query_parameters[:choose] = action
- response = DynamicDispatchController.action(action).call(request.env).last
+ @request.query_parameters[:choose] = action
+ response = DynamicDispatchController.action(action).call(@request.env).last
assert_equal action, response.body
end
end
def test_running_prepended_before_and_after_action
test_process(PrependingBeforeAndAfterController)
- assert_equal %w( before_all between_before_all_and_after_all after_all ), assigns["ran_filter"]
+ assert_equal %w( before_all between_before_all_and_after_all after_all ), @controller.instance_variable_get(:@ran_filter)
end
def test_skipping_and_limiting_controller
test_process(SkippingAndLimitedController, "index")
- assert_equal %w( ensure_login ), assigns["ran_filter"]
+ assert_equal %w( ensure_login ), @controller.instance_variable_get(:@ran_filter)
test_process(SkippingAndLimitedController, "public")
- assert_nil assigns["ran_filter"]
+ assert_not @controller.instance_variable_defined?(:@ran_filter)
end
def test_skipping_and_reordering_controller
test_process(SkippingAndReorderingController, "index")
- assert_equal %w( find_record ensure_login ), assigns["ran_filter"]
+ assert_equal %w( find_record ensure_login ), @controller.instance_variable_get(:@ran_filter)
end
def test_conditional_skipping_of_actions
test_process(ConditionalSkippingController, "login")
- assert_nil assigns["ran_filter"]
+ assert_not @controller.instance_variable_defined?(:@ran_filter)
test_process(ConditionalSkippingController, "change_password")
- assert_equal %w( ensure_login find_user ), assigns["ran_filter"]
+ assert_equal %w( ensure_login find_user ), @controller.instance_variable_get(:@ran_filter)
test_process(ConditionalSkippingController, "login")
assert !@controller.instance_variable_defined?("@ran_after_action")
@@ -804,23 +794,23 @@ class FilterTest < ActionController::TestCase
def test_conditional_skipping_of_actions_when_parent_action_is_also_conditional
test_process(ChildOfConditionalParentController)
- assert_equal %w( conditional_in_parent_before conditional_in_parent_after ), assigns['ran_filter']
+ assert_equal %w( conditional_in_parent_before conditional_in_parent_after ), @controller.instance_variable_get(:@ran_filter)
test_process(ChildOfConditionalParentController, 'another_action')
- assert_nil assigns['ran_filter']
+ assert_not @controller.instance_variable_defined?(:@ran_filter)
end
def test_condition_skipping_of_actions_when_siblings_also_have_conditions
test_process(ChildOfConditionalParentController)
- assert_equal %w( conditional_in_parent_before conditional_in_parent_after ), assigns['ran_filter']
+ assert_equal %w( conditional_in_parent_before conditional_in_parent_after ), @controller.instance_variable_get(:@ran_filter)
test_process(AnotherChildOfConditionalParentController)
- assert_equal %w( conditional_in_parent_after ), assigns['ran_filter']
+ assert_equal %w( conditional_in_parent_after ), @controller.instance_variable_get(:@ran_filter)
test_process(ChildOfConditionalParentController)
- assert_equal %w( conditional_in_parent_before conditional_in_parent_after ), assigns['ran_filter']
+ assert_equal %w( conditional_in_parent_before conditional_in_parent_after ), @controller.instance_variable_get(:@ran_filter)
end
def test_changing_the_requirements
test_process(ChangingTheRequirementsController, "go_wild")
- assert_nil assigns['ran_filter']
+ assert_not @controller.instance_variable_defined?(:@ran_filter)
end
def test_a_rescuing_around_action
@@ -829,27 +819,25 @@ class FilterTest < ActionController::TestCase
response = test_process(RescuedController)
end
- assert response.success?
+ assert response.successful?
assert_equal("I rescued this: #<FilterTest::ErrorToRescue: Something made the bad noise.>", response.body)
end
def test_actions_obey_only_and_except_for_implicit_actions
test_process(ImplicitActionsController, 'show')
- assert_equal 'Except', assigns(:except)
- assert_nil assigns(:only)
+ assert_equal 'Except', @controller.instance_variable_get(:@except)
+ assert_not @controller.instance_variable_defined?(:@only)
assert_equal 'show', response.body
test_process(ImplicitActionsController, 'edit')
- assert_equal 'Only', assigns(:only)
- assert_nil assigns(:except)
+ assert_equal 'Only', @controller.instance_variable_get(:@only)
+ assert_not @controller.instance_variable_defined?(:@except)
assert_equal 'edit', response.body
end
private
def test_process(controller, action = "show")
@controller = controller.is_a?(Class) ? controller.new : controller
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
process(action)
end
@@ -1010,8 +998,8 @@ class YieldingAroundFiltersTest < ActionController::TestCase
def test_with_proc
test_process(ControllerWithProcFilter,'no_raise')
- assert assigns['before']
- assert assigns['after']
+ assert @controller.instance_variable_get(:@before)
+ assert @controller.instance_variable_get(:@after)
end
def test_nested_actions
@@ -1032,12 +1020,12 @@ class YieldingAroundFiltersTest < ActionController::TestCase
def test_action_order_with_all_action_types
test_process(ControllerWithAllTypesOfFilters,'no_raise')
- assert_equal 'before around (before yield) around_again (before yield) around_again (after yield) after around (after yield)', assigns['ran_filter'].join(' ')
+ 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')
- assert_equal 'before around (before yield) around (after yield)', assigns['ran_filter'].join(' ')
+ assert_equal 'before around (before yield) around (after yield)', @controller.instance_variable_get(:@ran_filter).join(' ')
end
def test_first_action_in_multiple_before_action_chain_halts
@@ -1063,7 +1051,7 @@ class YieldingAroundFiltersTest < ActionController::TestCase
def test_skipping_with_skip_action_callback
test_process(SkipFilterUsingSkipActionCallback,'no_raise')
- assert_equal 'before around (before yield) around (after yield)', assigns['ran_filter'].join(' ')
+ assert_equal 'before around (before yield) around (after yield)', @controller.instance_variable_get(:@ran_filter).join(' ')
end
def test_deprecated_skip_action_callback
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index 0ff0a1ef61..b063d769a4 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -57,7 +57,7 @@ class FlashTest < ActionController::TestCase
def std_action
@flash_copy = {}.update(flash)
- render :nothing => true
+ head :ok
end
def filter_halting_action
@@ -103,54 +103,55 @@ class FlashTest < ActionController::TestCase
get :set_flash
get :use_flash
- assert_equal "hello", assigns["flash_copy"]["that"]
- assert_equal "hello", assigns["flashy"]
+ assert_equal "hello", @controller.instance_variable_get(:@flash_copy)["that"]
+ assert_equal "hello", @controller.instance_variable_get(:@flashy)
get :use_flash
- assert_nil assigns["flash_copy"]["that"], "On second flash"
+ assert_nil @controller.instance_variable_get(:@flash_copy)["that"], "On second flash"
end
def test_keep_flash
get :set_flash
get :use_flash_and_keep_it
- assert_equal "hello", assigns["flash_copy"]["that"]
- assert_equal "hello", assigns["flashy"]
+ assert_equal "hello", @controller.instance_variable_get(:@flash_copy)["that"]
+ assert_equal "hello", @controller.instance_variable_get(:@flashy)
get :use_flash
- assert_equal "hello", assigns["flash_copy"]["that"], "On second flash"
+ assert_equal "hello", @controller.instance_variable_get(:@flash_copy)["that"], "On second flash"
get :use_flash
- assert_nil assigns["flash_copy"]["that"], "On third flash"
+ assert_nil @controller.instance_variable_get(:@flash_copy)["that"], "On third flash"
end
def test_flash_now
get :set_flash_now
- assert_equal "hello", assigns["flash_copy"]["that"]
- assert_equal "bar" , assigns["flash_copy"]["foo"]
- assert_equal "hello", assigns["flashy"]
+ assert_equal "hello", @controller.instance_variable_get(:@flash_copy)["that"]
+ assert_equal "bar", @controller.instance_variable_get(:@flash_copy)["foo"]
+ assert_equal "hello", @controller.instance_variable_get(:@flashy)
get :attempt_to_use_flash_now
- assert_nil assigns["flash_copy"]["that"]
- assert_nil assigns["flash_copy"]["foo"]
- assert_nil assigns["flashy"]
+ assert_nil @controller.instance_variable_get(:@flash_copy)["that"]
+ assert_nil @controller.instance_variable_get(:@flash_copy)["foo"]
+ assert_nil @controller.instance_variable_get(:@flashy)
end
def test_update_flash
get :set_flash
get :use_flash_and_update_it
- assert_equal "hello", assigns["flash_copy"]["that"]
- assert_equal "hello again", assigns["flash_copy"]["this"]
+ assert_equal "hello", @controller.instance_variable_get(:@flash_copy)["that"]
+ assert_equal "hello again", @controller.instance_variable_get(:@flash_copy)["this"]
get :use_flash
- assert_nil assigns["flash_copy"]["that"], "On second flash"
- assert_equal "hello again", assigns["flash_copy"]["this"], "On second flash"
+ assert_nil @controller.instance_variable_get(:@flash_copy)["that"], "On second flash"
+ assert_equal "hello again",
+ @controller.instance_variable_get(:@flash_copy)["this"], "On second flash"
end
def test_flash_after_reset_session
get :use_flash_after_reset_session
- assert_equal "hello", assigns["flashy_that"]
- assert_equal "good-bye", assigns["flashy_this"]
- assert_nil assigns["flashy_that_reset"]
+ assert_equal "hello", @controller.instance_variable_get(:@flashy_that)
+ assert_equal "good-bye", @controller.instance_variable_get(:@flashy_this)
+ assert_nil @controller.instance_variable_get(:@flashy_that_reset)
end
def test_does_not_set_the_session_if_the_flash_is_empty
@@ -160,13 +161,13 @@ class FlashTest < ActionController::TestCase
def test_sweep_after_halted_action_chain
get :std_action
- assert_nil assigns["flash_copy"]["foo"]
+ assert_nil @controller.instance_variable_get(:@flash_copy)["foo"]
get :filter_halting_action
- assert_equal "bar", assigns["flash_copy"]["foo"]
+ assert_equal "bar", @controller.instance_variable_get(:@flash_copy)["foo"]
get :std_action # follow redirection
- assert_equal "bar", assigns["flash_copy"]["foo"]
+ assert_equal "bar", @controller.instance_variable_get(:@flash_copy)["foo"]
get :std_action
- assert_nil assigns["flash_copy"]["foo"]
+ assert_nil @controller.instance_variable_get(:@flash_copy)["foo"]
end
def test_keep_and_discard_return_values
@@ -328,7 +329,7 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest
@app = self.class.build_app(set) do |middleware|
middleware.use ActionDispatch::Session::CookieStore, :key => SessionKey
middleware.use ActionDispatch::Flash
- middleware.delete "ActionDispatch::ShowExceptions"
+ middleware.delete ActionDispatch::ShowExceptions
end
yield
diff --git a/actionpack/test/controller/force_ssl_test.rb b/actionpack/test/controller/force_ssl_test.rb
index 04222745d9..22f1cc7c22 100644
--- a/actionpack/test/controller/force_ssl_test.rb
+++ b/actionpack/test/controller/force_ssl_test.rb
@@ -2,11 +2,11 @@ require 'abstract_unit'
class ForceSSLController < ActionController::Base
def banana
- render :text => "monkey"
+ render plain: "monkey"
end
def cheeseburger
- render :text => "sikachu"
+ render plain: "sikachu"
end
end
@@ -26,7 +26,7 @@ class ForceSSLCustomOptions < ForceSSLController
force_ssl :notice => 'Foo, Bar!', :only => :redirect_notice
def force_ssl_action
- render :text => action_name
+ render plain: action_name
end
alias_method :redirect_host, :force_ssl_action
@@ -40,15 +40,15 @@ class ForceSSLCustomOptions < ForceSSLController
alias_method :redirect_notice, :force_ssl_action
def use_flash
- render :text => flash[:message]
+ render plain: flash[:message]
end
def use_alert
- render :text => flash[:alert]
+ render plain: flash[:alert]
end
def use_notice
- render :text => flash[:notice]
+ render plain: flash[:notice]
end
end
@@ -85,10 +85,10 @@ end
class RedirectToSSL < ForceSSLController
def banana
- force_ssl_redirect || render(:text => 'monkey')
+ force_ssl_redirect || render(plain: 'monkey')
end
def cheeseburger
- force_ssl_redirect('secure.cheeseburger.host') || render(:text => 'ihaz')
+ force_ssl_redirect('secure.cheeseburger.host') || render(plain: 'ihaz')
end
end
@@ -240,8 +240,8 @@ class ForceSSLFlashTest < ActionController::TestCase
@request.env.delete('PATH_INFO')
get :use_flash
- assert_equal "hello", assigns["flash_copy"]["that"]
- assert_equal "hello", assigns["flashy"]
+ assert_equal "hello", @controller.instance_variable_get("@flash_copy")["that"]
+ assert_equal "hello", @controller.instance_variable_get("@flashy")
end
end
@@ -315,7 +315,7 @@ class RedirectToSSLTest < ActionController::TestCase
assert_equal "https://secure.cheeseburger.host/redirect_to_ssl/cheeseburger", redirect_to_url
end
- def test_banana_does_not_redirect_if_already_https
+ def test_cheeseburgers_does_not_redirect_if_already_https
request.env['HTTPS'] = 'on'
get :cheeseburger
assert_response 200
diff --git a/actionpack/test/controller/form_builder_test.rb b/actionpack/test/controller/form_builder_test.rb
new file mode 100644
index 0000000000..99eeaf9ab6
--- /dev/null
+++ b/actionpack/test/controller/form_builder_test.rb
@@ -0,0 +1,17 @@
+require 'abstract_unit'
+
+class FormBuilderController < ActionController::Base
+ class SpecializedFormBuilder < ActionView::Helpers::FormBuilder ; end
+
+ default_form_builder SpecializedFormBuilder
+end
+
+class ControllerFormBuilderTest < ActiveSupport::TestCase
+ setup do
+ @controller = FormBuilderController.new
+ end
+
+ def test_default_form_builder_assigned
+ assert_equal FormBuilderController::SpecializedFormBuilder, @controller.default_form_builder
+ end
+end
diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb
index e263ed341f..3ecfedefd1 100644
--- a/actionpack/test/controller/helper_test.rb
+++ b/actionpack/test/controller/helper_test.rb
@@ -73,14 +73,8 @@ module LocalAbcHelper
end
class HelperPathsTest < ActiveSupport::TestCase
- def setup
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
- end
-
def test_helpers_paths_priority
- request = ActionController::TestRequest.new
- responses = HelpersPathsController.action(:index).call(request.env)
+ responses = HelpersPathsController.action(:index).call(ActionController::TestRequest::DEFAULT_ENV.dup)
# helpers1_pack was given as a second path, so pack1_helper should be
# included as the second one
@@ -141,8 +135,7 @@ class HelperTest < ActiveSupport::TestCase
end
def call_controller(klass, action)
- request = ActionController::TestRequest.new
- klass.action(action).call(request.env)
+ klass.action(action).call(ActionController::TestRequest::DEFAULT_ENV.dup)
end
def test_helper_for_nested_controller
@@ -158,7 +151,7 @@ class HelperTest < ActiveSupport::TestCase
assert_equal "test: baz", call_controller(Fun::PdfController, "test").last.body
#
# request = ActionController::TestRequest.new
- # response = ActionController::TestResponse.new
+ # response = ActionDispatch::TestResponse.new
# request.action = 'test'
#
# assert_equal 'test: baz', Fun::PdfController.process(request, response).body
@@ -249,7 +242,7 @@ class HelperTest < ActiveSupport::TestCase
end
-class IsolatedHelpersTest < ActiveSupport::TestCase
+class IsolatedHelpersTest < ActionController::TestCase
class A < ActionController::Base
def index
render :inline => '<%= shout %>'
@@ -273,13 +266,11 @@ class IsolatedHelpersTest < ActiveSupport::TestCase
end
def call_controller(klass, action)
- request = ActionController::TestRequest.new
- klass.action(action).call(request.env)
+ klass.action(action).call(@request.env)
end
def setup
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
+ super
@request.action = 'index'
end
diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb
index 20962a90cb..0a5e5402b9 100644
--- a/actionpack/test/controller/http_basic_authentication_test.rb
+++ b/actionpack/test/controller/http_basic_authentication_test.rb
@@ -9,19 +9,19 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
http_basic_authenticate_with :name => "David", :password => "Goliath", :only => :search
def index
- render :text => "Hello Secret"
+ render plain: "Hello Secret"
end
def display
- render :text => 'Definitely Maybe'
+ render plain: 'Definitely Maybe' if @logged_in
end
def show
- render :text => 'Only for loooooong credentials'
+ render plain: 'Only for loooooong credentials'
end
def search
- render :text => 'All inline'
+ render plain: 'All inline'
end
private
@@ -36,7 +36,7 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
if authenticate_with_http_basic { |username, password| username == 'pretty' && password == 'please' }
@logged_in = true
else
- request_http_basic_authentication("SuperSecret")
+ request_http_basic_authentication("SuperSecret", "Authentication Failed\n")
end
end
@@ -100,11 +100,19 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
assert_no_match(/\n/, result)
end
+ test "succesful authentication with uppercase authorization scheme" do
+ @request.env['HTTP_AUTHORIZATION'] = "BASIC #{::Base64.encode64("lifo:world")}"
+ get :index
+
+ assert_response :success
+ assert_equal 'Hello Secret', @response.body, 'Authentication failed when authorization scheme BASIC'
+ end
+
test "authentication request without credential" do
get :display
assert_response :unauthorized
- assert_equal "HTTP Basic: Access denied.\n", @response.body
+ assert_equal "Authentication Failed\n", @response.body
assert_equal 'Basic realm="SuperSecret"', @response.headers['WWW-Authenticate']
end
@@ -113,7 +121,7 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
get :display
assert_response :unauthorized
- assert_equal "HTTP Basic: Access denied.\n", @response.body
+ assert_equal "Authentication Failed\n", @response.body
assert_equal 'Basic realm="SuperSecret"', @response.headers['WWW-Authenticate']
end
@@ -122,7 +130,6 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
get :display
assert_response :success
- assert assigns(:logged_in)
assert_equal 'Definitely Maybe', @response.body
end
diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb
index 52a0bc9aa3..f06912bd5a 100644
--- a/actionpack/test/controller/http_digest_authentication_test.rb
+++ b/actionpack/test/controller/http_digest_authentication_test.rb
@@ -10,11 +10,11 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
'dhh' => ::Digest::MD5::hexdigest(["dhh","SuperSecret","secret"].join(":"))}
def index
- render :text => "Hello Secret"
+ render plain: "Hello Secret"
end
def display
- render :text => 'Definitely Maybe'
+ render plain: 'Definitely Maybe' if @logged_in
end
private
@@ -124,7 +124,6 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
get :display
assert_response :success
- assert assigns(:logged_in)
assert_equal 'Definitely Maybe', @response.body
end
@@ -134,7 +133,6 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
get :display
assert_response :success
- assert assigns(:logged_in)
assert_equal 'Definitely Maybe', @response.body
end
@@ -144,7 +142,6 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
get :display
assert_response :success
- assert assigns(:logged_in)
assert_equal 'Definitely Maybe', @response.body
end
@@ -156,7 +153,6 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
get :display
assert_response :success
- assert assigns(:logged_in)
assert_equal 'Definitely Maybe', @response.body
end
@@ -167,7 +163,6 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
get :display
assert_response :success
- assert assigns(:logged_in)
assert_equal 'Definitely Maybe', @response.body
end
@@ -180,7 +175,6 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
get :display
assert_response :success
- assert assigns(:logged_in)
assert_equal 'Definitely Maybe', @response.body
end
@@ -191,7 +185,6 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
get :display
assert_response :success
- assert assigns(:logged_in)
assert_equal 'Definitely Maybe', @response.body
end
@@ -201,7 +194,6 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
put :display
assert_response :success
- assert assigns(:logged_in)
assert_equal 'Definitely Maybe', @response.body
end
@@ -244,7 +236,6 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
get :display
assert_response :success
- assert assigns(:logged_in)
assert_equal 'Definitely Maybe', @response.body
end
diff --git a/actionpack/test/controller/http_token_authentication_test.rb b/actionpack/test/controller/http_token_authentication_test.rb
index a758df2ec6..9c5a01c318 100644
--- a/actionpack/test/controller/http_token_authentication_test.rb
+++ b/actionpack/test/controller/http_token_authentication_test.rb
@@ -7,15 +7,15 @@ class HttpTokenAuthenticationTest < ActionController::TestCase
before_action :authenticate_long_credentials, only: :show
def index
- render :text => "Hello Secret"
+ render plain: "Hello Secret"
end
def display
- render :text => 'Definitely Maybe'
+ render plain: 'Definitely Maybe'
end
def show
- render :text => 'Only for loooooong credentials'
+ render plain: 'Only for loooooong credentials'
end
private
@@ -30,7 +30,7 @@ class HttpTokenAuthenticationTest < ActionController::TestCase
if authenticate_with_http_token { |token, options| token == '"quote" pretty' && options[:algorithm] == 'test' }
@logged_in = true
else
- request_http_token_authentication("SuperSecret")
+ request_http_token_authentication("SuperSecret", "Authentication Failed\n")
end
end
@@ -80,18 +80,25 @@ class HttpTokenAuthenticationTest < ActionController::TestCase
end
test "authentication request with badly formatted header" do
- @request.env['HTTP_AUTHORIZATION'] = "Token foobar"
+ @request.env['HTTP_AUTHORIZATION'] = 'Token token$"lifo"'
get :index
assert_response :unauthorized
assert_equal "HTTP Token: Access denied.\n", @response.body, "Authentication header was not properly parsed"
end
+ test "successful authentication request with Bearer instead of Token" do
+ @request.env['HTTP_AUTHORIZATION'] = 'Bearer lifo'
+ get :index
+
+ assert_response :success
+ end
+
test "authentication request without credential" do
get :display
assert_response :unauthorized
- assert_equal "HTTP Token: Access denied.\n", @response.body
+ assert_equal "Authentication Failed\n", @response.body
assert_equal 'Token realm="SuperSecret"', @response.headers['WWW-Authenticate']
end
@@ -100,7 +107,7 @@ class HttpTokenAuthenticationTest < ActionController::TestCase
get :display
assert_response :unauthorized
- assert_equal "HTTP Token: Access denied.\n", @response.body
+ assert_equal "Authentication Failed\n", @response.body
assert_equal 'Token realm="SuperSecret"', @response.headers['WWW-Authenticate']
end
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index a87059bee4..dc4c32b07e 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -315,8 +315,6 @@ class IntegrationTestTest < ActiveSupport::TestCase
session1 = @test.open_session { |sess| }
session2 = @test.open_session # implicit session
- assert_respond_to session1, :assert_template, "open_session makes assert_template available"
- assert_respond_to session2, :assert_template, "open_session makes assert_template available"
assert !session1.equal?(session2)
end
@@ -361,28 +359,28 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
class IntegrationController < ActionController::Base
def get
respond_to do |format|
- format.html { render :text => "OK", :status => 200 }
- format.js { render :text => "JS OK", :status => 200 }
+ format.html { render plain: "OK", status: 200 }
+ format.js { render plain: "JS OK", status: 200 }
format.xml { render :xml => "<root></root>", :status => 200 }
end
end
def get_with_params
- render :text => "foo: #{params[:foo]}", :status => 200
+ render plain: "foo: #{params[:foo]}", status: 200
end
def post
- render :text => "Created", :status => 201
+ render plain: "Created", status: 201
end
def method
- render :text => "method: #{request.method.downcase}"
+ render plain: "method: #{request.method.downcase}"
end
def cookie_monster
cookies["cookie_1"] = nil
cookies["cookie_3"] = "chocolate"
- render :text => "Gone", :status => 410
+ render plain: "Gone", status: 410
end
def set_cookie
@@ -391,7 +389,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
end
def get_cookie
- render :text => cookies["foo"]
+ render plain: cookies["foo"]
end
def redirect
@@ -757,12 +755,31 @@ class MetalIntegrationTest < ActionDispatch::IntegrationTest
assert_equal "http://test.com/", @request.env["HTTP_REFERER"]
end
+ def test_ignores_common_ports_in_host
+ get "http://test.com"
+ assert_equal "test.com", @request.env["HTTP_HOST"]
+
+ get "https://test.com"
+ assert_equal "test.com", @request.env["HTTP_HOST"]
+ end
+
+ def test_keeps_uncommon_ports_in_host
+ get "http://test.com:123"
+ assert_equal "test.com:123", @request.env["HTTP_HOST"]
+
+ get "http://test.com:443"
+ assert_equal "test.com:443", @request.env["HTTP_HOST"]
+
+ get "https://test.com:80"
+ assert_equal "test.com:80", @request.env["HTTP_HOST"]
+ end
+
end
class ApplicationIntegrationTest < ActionDispatch::IntegrationTest
class TestController < ActionController::Base
def index
- render :text => "index"
+ render plain: "index"
end
end
@@ -849,7 +866,7 @@ end
class EnvironmentFilterIntegrationTest < ActionDispatch::IntegrationTest
class TestController < ActionController::Base
def post
- render :text => "Created", :status => 201
+ render plain: "Created", status: 201
end
end
@@ -882,15 +899,15 @@ end
class UrlOptionsIntegrationTest < ActionDispatch::IntegrationTest
class FooController < ActionController::Base
def index
- render :text => "foo#index"
+ render plain: "foo#index"
end
def show
- render :text => "foo#show"
+ render plain: "foo#show"
end
def edit
- render :text => "foo#show"
+ render plain: "foo#show"
end
end
@@ -900,7 +917,7 @@ class UrlOptionsIntegrationTest < ActionDispatch::IntegrationTest
end
def index
- render :text => "foo#index"
+ render plain: "foo#index"
end
end
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
index 0c65270ec1..6ba361f2f7 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -1,5 +1,5 @@
require 'abstract_unit'
-require 'active_support/concurrency/latch'
+require 'concurrent/atomics'
Thread.abort_on_exception = true
module ActionController
@@ -125,7 +125,7 @@ module ActionController
end
def render_text
- render :text => 'zomg'
+ render plain: 'zomg'
end
def default_header
@@ -145,7 +145,7 @@ module ActionController
response.headers['Content-Type'] = 'text/event-stream'
%w{ hello world }.each do |word|
response.stream.write word
- latch.await
+ latch.wait
end
response.stream.close
end
@@ -162,7 +162,7 @@ module ActionController
end
def with_stale
- render text: 'stale' if stale?(etag: "123", template: false)
+ render plain: 'stale' if stale?(etag: "123", template: false)
end
def exception_in_view
@@ -212,7 +212,7 @@ module ActionController
# .. plus one more, because the #each frees up a slot:
response.stream.write '.'
- latch.release
+ latch.count_down
# This write will block, and eventually raise
response.stream.write 'x'
@@ -233,7 +233,7 @@ module ActionController
end
logger.info 'Work complete'
- latch.release
+ latch.count_down
end
end
@@ -278,7 +278,7 @@ module ActionController
def test_async_stream
rubinius_skip "https://github.com/rubinius/rubinius/issues/2934"
- @controller.latch = ActiveSupport::Concurrency::Latch.new
+ @controller.latch = Concurrent::CountDownLatch.new
parts = ['hello', 'world']
@controller.request = @request
@@ -289,8 +289,8 @@ module ActionController
resp.stream.each do |part|
assert_equal parts.shift, part
ol = @controller.latch
- @controller.latch = ActiveSupport::Concurrency::Latch.new
- ol.release
+ @controller.latch = Concurrent::CountDownLatch.new
+ ol.count_down
end
}
@@ -300,23 +300,23 @@ module ActionController
end
def test_abort_with_full_buffer
- @controller.latch = ActiveSupport::Concurrency::Latch.new
+ @controller.latch = Concurrent::CountDownLatch.new
@request.parameters[:format] = 'plain'
@controller.request = @request
@controller.response = @response
- got_error = ActiveSupport::Concurrency::Latch.new
+ got_error = Concurrent::CountDownLatch.new
@response.stream.on_error do
ActionController::Base.logger.warn 'Error while streaming'
- got_error.release
+ got_error.count_down
end
t = Thread.new(@response) { |resp|
resp.await_commit
_, _, body = resp.to_a
body.each do |part|
- @controller.latch.await
+ @controller.latch.wait
body.close
break
end
@@ -325,13 +325,13 @@ module ActionController
capture_log_output do |output|
@controller.process :overfill_buffer_and_die
t.join
- got_error.await
+ got_error.wait
assert_match 'Error while streaming', output.rewind && output.read
end
end
def test_ignore_client_disconnect
- @controller.latch = ActiveSupport::Concurrency::Latch.new
+ @controller.latch = Concurrent::CountDownLatch.new
@controller.request = @request
@controller.response = @response
@@ -349,7 +349,7 @@ module ActionController
@controller.process :ignore_client_disconnect
t.join
Timeout.timeout(3) do
- @controller.latch.await
+ @controller.latch.wait
end
assert_match 'Work complete', output.rewind && output.read
end
diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb
index 03a4ad7823..7835d2768a 100644
--- a/actionpack/test/controller/log_subscriber_test.rb
+++ b/actionpack/test/controller/log_subscriber_test.rb
@@ -10,7 +10,7 @@ module Another
end
rescue_from SpecialException do
- head :status => 406
+ head 406
end
before_action :redirector, only: :never_executed
@@ -19,7 +19,7 @@ module Another
end
def show
- render :nothing => true
+ head :ok
end
def redirector
@@ -170,7 +170,7 @@ class ACLogSubscriberTest < ActionController::TestCase
def test_process_action_with_view_runtime
get :show
wait
- assert_match(/\(Views: [\d.]+ms\)/, logs[1])
+ assert_match(/Completed 200 OK in [\d]ms/, logs[1])
end
def test_append_info_to_payload_is_called_even_with_exception
diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb
index 7aef8a50ce..64eb33f78f 100644
--- a/actionpack/test/controller/mime/respond_to_test.rb
+++ b/actionpack/test/controller/mime/respond_to_test.rb
@@ -4,43 +4,50 @@ require "active_support/log_subscriber/test_helper"
class RespondToController < ActionController::Base
layout :set_layout
+ before_action {
+ case params[:v]
+ when String then request.variant = params[:v].to_sym
+ when Array then request.variant = params[:v].map(&:to_sym)
+ end
+ }
+
def html_xml_or_rss
respond_to do |type|
- type.html { render :text => "HTML" }
- type.xml { render :text => "XML" }
- type.rss { render :text => "RSS" }
- type.all { render :text => "Nothing" }
+ type.html { render body: "HTML" }
+ type.xml { render body: "XML" }
+ type.rss { render body: "RSS" }
+ type.all { render body: "Nothing" }
end
end
def js_or_html
respond_to do |type|
- type.html { render :text => "HTML" }
- type.js { render :text => "JS" }
- type.all { render :text => "Nothing" }
+ type.html { render body: "HTML" }
+ type.js { render body: "JS" }
+ type.all { render body: "Nothing" }
end
end
def json_or_yaml
respond_to do |type|
- type.json { render :text => "JSON" }
- type.yaml { render :text => "YAML" }
+ type.json { render body: "JSON" }
+ type.yaml { render body: "YAML" }
end
end
def html_or_xml
respond_to do |type|
- type.html { render :text => "HTML" }
- type.xml { render :text => "XML" }
- type.all { render :text => "Nothing" }
+ type.html { render body: "HTML" }
+ type.xml { render body: "XML" }
+ type.all { render body: "Nothing" }
end
end
def json_xml_or_html
respond_to do |type|
- type.json { render :text => 'JSON' }
+ type.json { render body: 'JSON' }
type.xml { render :xml => 'XML' }
- type.html { render :text => 'HTML' }
+ type.html { render body: 'HTML' }
end
end
@@ -49,14 +56,14 @@ class RespondToController < ActionController::Base
request.format = :xml
respond_to do |type|
- type.html { render :text => "HTML" }
- type.xml { render :text => "XML" }
+ type.html { render body: "HTML" }
+ type.xml { render body: "XML" }
end
end
def just_xml
respond_to do |type|
- type.xml { render :text => "XML" }
+ type.xml { render body: "XML" }
end
end
@@ -74,52 +81,52 @@ class RespondToController < ActionController::Base
def using_defaults_with_all
respond_to do |type|
type.html
- type.all{ render text: "ALL" }
+ type.all{ render body: "ALL" }
end
end
def made_for_content_type
respond_to do |type|
- type.rss { render :text => "RSS" }
- type.atom { render :text => "ATOM" }
- type.all { render :text => "Nothing" }
+ type.rss { render body: "RSS" }
+ type.atom { render body: "ATOM" }
+ type.all { render body: "Nothing" }
end
end
def custom_type_handling
respond_to do |type|
- type.html { render :text => "HTML" }
- type.custom("application/crazy-xml") { render :text => "Crazy XML" }
- type.all { render :text => "Nothing" }
+ type.html { render body: "HTML" }
+ type.custom("application/crazy-xml") { render body: "Crazy XML" }
+ type.all { render body: "Nothing" }
end
end
def custom_constant_handling
respond_to do |type|
- type.html { render :text => "HTML" }
- type.mobile { render :text => "Mobile" }
+ type.html { render body: "HTML" }
+ type.mobile { render body: "Mobile" }
end
end
def custom_constant_handling_without_block
respond_to do |type|
- type.html { render :text => "HTML" }
+ type.html { render body: "HTML" }
type.mobile
end
end
def handle_any
respond_to do |type|
- type.html { render :text => "HTML" }
- type.any(:js, :xml) { render :text => "Either JS or XML" }
+ type.html { render body: "HTML" }
+ type.any(:js, :xml) { render body: "Either JS or XML" }
end
end
def handle_any_any
respond_to do |type|
- type.html { render :text => 'HTML' }
- type.any { render :text => 'Whatever you ask for, I got it' }
+ type.html { render body: 'HTML' }
+ type.any { render body: 'Whatever you ask for, I got it' }
end
end
@@ -160,15 +167,15 @@ class RespondToController < ActionController::Base
request.variant = :mobile
respond_to do |type|
- type.html { render text: "mobile" }
+ type.html { render body: "mobile" }
end
end
def multiple_variants_for_format
respond_to do |type|
type.html do |html|
- html.tablet { render text: "tablet" }
- html.phone { render text: "phone" }
+ html.tablet { render body: "tablet" }
+ html.phone { render body: "phone" }
end
end
end
@@ -176,7 +183,7 @@ class RespondToController < ActionController::Base
def variant_plus_none_for_format
respond_to do |format|
format.html do |variant|
- variant.phone { render text: "phone" }
+ variant.phone { render body: "phone" }
variant.none
end
end
@@ -184,9 +191,9 @@ class RespondToController < ActionController::Base
def variant_inline_syntax
respond_to do |format|
- format.js { render text: "js" }
- format.html.none { render text: "none" }
- format.html.phone { render text: "phone" }
+ format.js { render body: "js" }
+ format.html.none { render body: "none" }
+ format.html.phone { render body: "phone" }
end
end
@@ -201,8 +208,8 @@ class RespondToController < ActionController::Base
def variant_any
respond_to do |format|
format.html do |variant|
- variant.any(:tablet, :phablet){ render text: "any" }
- variant.phone { render text: "phone" }
+ variant.any(:tablet, :phablet){ render body: "any" }
+ variant.phone { render body: "phone" }
end
end
end
@@ -210,23 +217,23 @@ class RespondToController < ActionController::Base
def variant_any_any
respond_to do |format|
format.html do |variant|
- variant.any { render text: "any" }
- variant.phone { render text: "phone" }
+ variant.any { render body: "any" }
+ variant.phone { render body: "phone" }
end
end
end
def variant_inline_any
respond_to do |format|
- format.html.any(:tablet, :phablet){ render text: "any" }
- format.html.phone { render text: "phone" }
+ format.html.any(:tablet, :phablet){ render body: "any" }
+ format.html.phone { render body: "phone" }
end
end
def variant_inline_any_any
respond_to do |format|
- format.html.phone { render text: "phone" }
- format.html.any { render text: "any" }
+ format.html.phone { render body: "phone" }
+ format.html.any { render body: "any" }
end
end
@@ -239,16 +246,16 @@ class RespondToController < ActionController::Base
def variant_any_with_none
respond_to do |format|
- format.html.any(:none, :phone){ render text: "none or phone" }
+ format.html.any(:none, :phone){ render body: "none or phone" }
end
end
def format_any_variant_any
respond_to do |format|
- format.html { render text: "HTML" }
+ format.html { render body: "HTML" }
format.any(:js, :xml) do |variant|
- variant.phone{ render text: "phone" }
- variant.any(:tablet, :phablet){ render text: "tablet" }
+ variant.phone{ render body: "phone" }
+ variant.any(:tablet, :phablet){ render body: "tablet" }
end
end
end
@@ -612,8 +619,7 @@ class RespondToControllerTest < ActionController::TestCase
logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
old_logger, ActionController::Base.logger = ActionController::Base.logger, logger
- @request.variant = :invalid
- get :variant_with_implicit_rendering
+ get :variant_with_implicit_rendering, params: { v: :invalid }
assert_response :no_content
assert_equal 1, logger.logged(:info).select{ |s| s =~ /No template found/ }.size, "Implicit head :no_content not logged"
ensure
@@ -626,28 +632,24 @@ class RespondToControllerTest < ActionController::TestCase
end
def test_variant_with_implicit_rendering
- @request.variant = :implicit
- get :variant_with_implicit_rendering
+ get :variant_with_implicit_rendering, params: { v: :implicit }
assert_response :no_content
end
def test_variant_with_implicit_template_rendering
- @request.variant = :mobile
- get :variant_with_implicit_rendering
+ get :variant_with_implicit_rendering, params: { v: :mobile }
assert_equal "text/html", @response.content_type
assert_equal "mobile", @response.body
end
def test_variant_with_format_and_custom_render
- @request.variant = :phone
- get :variant_with_format_and_custom_render
+ get :variant_with_format_and_custom_render, params: { v: :phone }
assert_equal "text/html", @response.content_type
assert_equal "mobile", @response.body
end
def test_multiple_variants_for_format
- @request.variant = :tablet
- get :multiple_variants_for_format
+ get :multiple_variants_for_format, params: { v: :tablet }
assert_equal "text/html", @response.content_type
assert_equal "tablet", @response.body
end
@@ -667,32 +669,27 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal "text/html", @response.content_type
assert_equal "none", @response.body
- @request.variant = :phone
- get :variant_inline_syntax
+ get :variant_inline_syntax, params: { v: :phone }
assert_equal "text/html", @response.content_type
assert_equal "phone", @response.body
end
def test_variant_inline_syntax_without_block
- @request.variant = :phone
- get :variant_inline_syntax_without_block
+ get :variant_inline_syntax_without_block, params: { v: :phone }
assert_equal "text/html", @response.content_type
assert_equal "phone", @response.body
end
def test_variant_any
- @request.variant = :phone
- get :variant_any
+ get :variant_any, params: { v: :phone }
assert_equal "text/html", @response.content_type
assert_equal "phone", @response.body
- @request.variant = :tablet
- get :variant_any
+ get :variant_any, params: { v: :tablet }
assert_equal "text/html", @response.content_type
assert_equal "any", @response.body
- @request.variant = :phablet
- get :variant_any
+ get :variant_any, params: { v: :phablet }
assert_equal "text/html", @response.content_type
assert_equal "any", @response.body
end
@@ -702,54 +699,45 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal "text/html", @response.content_type
assert_equal "any", @response.body
- @request.variant = :phone
- get :variant_any_any
+ get :variant_any_any, params: { v: :phone }
assert_equal "text/html", @response.content_type
assert_equal "phone", @response.body
- @request.variant = :yolo
- get :variant_any_any
+ get :variant_any_any, params: { v: :yolo }
assert_equal "text/html", @response.content_type
assert_equal "any", @response.body
end
def test_variant_inline_any
- @request.variant = :phone
- get :variant_any
+ get :variant_any, params: { v: :phone }
assert_equal "text/html", @response.content_type
assert_equal "phone", @response.body
- @request.variant = :tablet
- get :variant_inline_any
+ get :variant_inline_any, params: { v: :tablet }
assert_equal "text/html", @response.content_type
assert_equal "any", @response.body
- @request.variant = :phablet
- get :variant_inline_any
+ get :variant_inline_any, params: { v: :phablet }
assert_equal "text/html", @response.content_type
assert_equal "any", @response.body
end
def test_variant_inline_any_any
- @request.variant = :phone
- get :variant_inline_any_any
+ get :variant_inline_any_any, params: { v: :phone }
assert_equal "text/html", @response.content_type
assert_equal "phone", @response.body
- @request.variant = :yolo
- get :variant_inline_any_any
+ get :variant_inline_any_any, params: { v: :yolo }
assert_equal "text/html", @response.content_type
assert_equal "any", @response.body
end
def test_variant_any_implicit_render
- @request.variant = :tablet
- get :variant_any_implicit_render
+ get :variant_any_implicit_render, params: { v: :tablet }
assert_equal "text/html", @response.content_type
assert_equal "tablet", @response.body
- @request.variant = :phablet
- get :variant_any_implicit_render
+ get :variant_any_implicit_render, params: { v: :phablet }
assert_equal "text/html", @response.content_type
assert_equal "phablet", @response.body
end
@@ -759,37 +747,53 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal "text/html", @response.content_type
assert_equal "none or phone", @response.body
- @request.variant = :phone
- get :variant_any_with_none
+ get :variant_any_with_none, params: { v: :phone }
assert_equal "text/html", @response.content_type
assert_equal "none or phone", @response.body
end
def test_format_any_variant_any
- @request.variant = :tablet
- get :format_any_variant_any, format: :js
+ get :format_any_variant_any, format: :js, params: { v: :tablet }
assert_equal "text/javascript", @response.content_type
assert_equal "tablet", @response.body
end
def test_variant_negotiation_inline_syntax
- @request.variant = [:tablet, :phone]
- get :variant_inline_syntax_without_block
+ get :variant_inline_syntax_without_block, params: { v: [:tablet, :phone] }
assert_equal "text/html", @response.content_type
assert_equal "phone", @response.body
end
def test_variant_negotiation_block_syntax
- @request.variant = [:tablet, :phone]
- get :variant_plus_none_for_format
+ get :variant_plus_none_for_format, params: { v: [:tablet, :phone] }
assert_equal "text/html", @response.content_type
assert_equal "phone", @response.body
end
def test_variant_negotiation_without_block
- @request.variant = [:tablet, :phone]
- get :variant_inline_syntax_without_block
+ get :variant_inline_syntax_without_block, params: { v: [:tablet, :phone] }
assert_equal "text/html", @response.content_type
assert_equal "phone", @response.body
end
end
+
+class RespondToWithBlockOnDefaultRenderController < ActionController::Base
+ def show
+ default_render do
+ render body: 'default_render yielded'
+ end
+ end
+end
+
+class RespondToWithBlockOnDefaultRenderControllerTest < ActionController::TestCase
+ def setup
+ super
+ @request.host = "www.example.com"
+ end
+
+ def test_default_render_uses_block_when_no_template_exists
+ get :show
+ assert_equal "default_render yielded", @response.body
+ assert_equal "text/plain", @response.content_type
+ end
+end
diff --git a/actionpack/test/controller/new_base/base_test.rb b/actionpack/test/controller/new_base/base_test.rb
index 964f22eb03..0755dafe93 100644
--- a/actionpack/test/controller/new_base/base_test.rb
+++ b/actionpack/test/controller/new_base/base_test.rb
@@ -6,7 +6,7 @@ module Dispatching
before_action :authenticate
def index
- render :text => "success"
+ render body: "success"
end
def modify_response_body
@@ -22,7 +22,7 @@ module Dispatching
end
def show_actions
- render :text => "actions: #{action_methods.to_a.sort.join(', ')}"
+ render body: "actions: #{action_methods.to_a.sort.join(', ')}"
end
protected
@@ -51,7 +51,7 @@ module Dispatching
assert_body "success"
assert_status 200
- assert_content_type "text/html; charset=utf-8"
+ assert_content_type "text/plain; charset=utf-8"
end
# :api: plugin
diff --git a/actionpack/test/controller/new_base/content_negotiation_test.rb b/actionpack/test/controller/new_base/content_negotiation_test.rb
index c8166280fc..c0e92b3b05 100644
--- a/actionpack/test/controller/new_base/content_negotiation_test.rb
+++ b/actionpack/test/controller/new_base/content_negotiation_test.rb
@@ -9,7 +9,7 @@ module ContentNegotiation
)]
def all
- render :text => self.formats.inspect
+ render plain: self.formats.inspect
end
end
diff --git a/actionpack/test/controller/new_base/content_type_test.rb b/actionpack/test/controller/new_base/content_type_test.rb
index 88453988dd..0445a837ca 100644
--- a/actionpack/test/controller/new_base/content_type_test.rb
+++ b/actionpack/test/controller/new_base/content_type_test.rb
@@ -3,16 +3,16 @@ require 'abstract_unit'
module ContentType
class BaseController < ActionController::Base
def index
- render :text => "Hello world!"
+ render body: "Hello world!"
end
def set_on_response_obj
response.content_type = Mime::RSS
- render :text => "Hello world!"
+ render body: "Hello world!"
end
def set_on_render
- render :text => "Hello world!", :content_type => Mime::RSS
+ render body: "Hello world!", content_type: Mime::RSS
end
end
@@ -30,17 +30,17 @@ module ContentType
class CharsetController < ActionController::Base
def set_on_response_obj
response.charset = "utf-16"
- render :text => "Hello world!"
+ render body: "Hello world!"
end
def set_as_nil_on_response_obj
response.charset = nil
- render :text => "Hello world!"
+ render body: "Hello world!"
end
end
class ExplicitContentTypeTest < Rack::TestCase
- test "default response is HTML and UTF8" do
+ test "default response is text/plain and UTF8" do
with_routing do |set|
set.draw do
get ':controller', :action => 'index'
@@ -49,7 +49,7 @@ module ContentType
get "/content_type/base"
assert_body "Hello world!"
- assert_header "Content-Type", "text/html; charset=utf-8"
+ assert_header "Content-Type", "text/plain; charset=utf-8"
end
end
@@ -99,14 +99,14 @@ module ContentType
get "/content_type/charset/set_on_response_obj"
assert_body "Hello world!"
- assert_header "Content-Type", "text/html; charset=utf-16"
+ assert_header "Content-Type", "text/plain; charset=utf-16"
end
test "setting the charset of the response as nil directly on the response object" do
get "/content_type/charset/set_as_nil_on_response_obj"
assert_body "Hello world!"
- assert_header "Content-Type", "text/html; charset=utf-8"
+ assert_header "Content-Type", "text/plain; charset=utf-8"
end
end
end
diff --git a/actionpack/test/controller/new_base/metal_test.rb b/actionpack/test/controller/new_base/metal_test.rb
deleted file mode 100644
index 537b93387a..0000000000
--- a/actionpack/test/controller/new_base/metal_test.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-require 'abstract_unit'
-
-module MetalTest
- class MetalMiddleware < ActionController::Middleware
- def call(env)
- if env["PATH_INFO"] =~ /authed/
- app.call(env)
- else
- [401, headers, "Not authed!"]
- end
- end
- end
-
- class Endpoint
- def call(env)
- [200, {}, "Hello World"]
- end
- end
-
- class TestMiddleware < ActiveSupport::TestCase
- def setup
- @app = Rack::Builder.new do
- use MetalTest::MetalMiddleware
- run MetalTest::Endpoint.new
- end.to_app
- end
-
- test "it can call the next app by using @app" do
- env = Rack::MockRequest.env_for("/authed")
- response = @app.call(env)
-
- assert_equal ["Hello World"], response[2]
- end
-
- test "it can return a response using the normal AC::Metal techniques" do
- env = Rack::MockRequest.env_for("/")
- response = @app.call(env)
-
- assert_equal ["Not authed!"], response[2]
- assert_equal 401, response[0]
- end
- end
-end
diff --git a/actionpack/test/controller/new_base/render_test.rb b/actionpack/test/controller/new_base/render_test.rb
index 11a19ab783..963f2c2f5c 100644
--- a/actionpack/test/controller/new_base/render_test.rb
+++ b/actionpack/test/controller/new_base/render_test.rb
@@ -37,14 +37,14 @@ module Render
private
def secretz
- render :text => "FAIL WHALE!"
+ render plain: "FAIL WHALE!"
end
end
class DoubleRenderController < ActionController::Base
def index
- render :text => "hello"
- render :text => "world"
+ render plain: "hello"
+ render plain: "world"
end
end
diff --git a/actionpack/test/controller/new_base/render_text_test.rb b/actionpack/test/controller/new_base/render_text_test.rb
index 10bad57cd6..435bb18dce 100644
--- a/actionpack/test/controller/new_base/render_text_test.rb
+++ b/actionpack/test/controller/new_base/render_text_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'active_support/deprecation'
module RenderText
class MinimalController < ActionController::Metal
@@ -73,7 +74,10 @@ module RenderText
class RenderTextTest < Rack::TestCase
test "rendering text from a minimal controller" do
- get "/render_text/minimal/index"
+ ActiveSupport::Deprecation.silence do
+ get "/render_text/minimal/index"
+ end
+
assert_body "Hello World!"
assert_status 200
end
@@ -82,7 +86,10 @@ module RenderText
with_routing do |set|
set.draw { get ':controller', action: 'index' }
- get "/render_text/simple"
+ ActiveSupport::Deprecation.silence do
+ get "/render_text/simple"
+ end
+
assert_body "hello david"
assert_status 200
end
@@ -92,7 +99,9 @@ module RenderText
with_routing do |set|
set.draw { get ':controller', action: 'index' }
- get "/render_text/with_layout"
+ ActiveSupport::Deprecation.silence do
+ get "/render_text/with_layout"
+ end
assert_body "hello david"
assert_status 200
@@ -100,59 +109,81 @@ module RenderText
end
test "rendering text, while also providing a custom status code" do
- get "/render_text/with_layout/custom_code"
+ 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
- get "/render_text/with_layout/with_nil"
+ 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
- get "/render_text/with_layout/with_nil_and_status"
+ 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
- get "/render_text/with_layout/with_false"
+ 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
- get "/render_text/with_layout/with_layout_true"
+ 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
- get "/render_text/with_layout/with_custom_layout"
+ 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
- get "/render_text/with_layout/with_layout_false"
+ 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
- get "/render_text/with_layout/with_layout_nil"
+ 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/parameters/mutators_test.rb b/actionpack/test/controller/parameters/mutators_test.rb
index 744d8664be..6c57c4caeb 100644
--- a/actionpack/test/controller/parameters/mutators_test.rb
+++ b/actionpack/test/controller/parameters/mutators_test.rb
@@ -62,11 +62,15 @@ class ParametersMutatorsTest < ActiveSupport::TestCase
end
test "select! retains permitted status" do
+ jruby_skip "https://github.com/jruby/jruby/issues/3137"
+
@params.permit!
assert @params.select! { |k| k != "person" }.permitted?
end
test "select! retains unpermitted status" do
+ jruby_skip "https://github.com/jruby/jruby/issues/3137"
+
assert_not @params.select! { |k| k != "person" }.permitted?
end
diff --git a/actionpack/test/controller/parameters/nested_parameters_test.rb b/actionpack/test/controller/parameters/nested_parameters_test.rb
index 3b1257e8d5..7151a8567c 100644
--- a/actionpack/test/controller/parameters/nested_parameters_test.rb
+++ b/actionpack/test/controller/parameters/nested_parameters_test.rb
@@ -136,7 +136,7 @@ class NestedParametersTest < ActiveSupport::TestCase
authors_attributes: {
:'0' => { name: 'William Shakespeare', age_of_death: '52' },
:'1' => { name: 'Unattributed Assistant' },
- :'2' => { name: %w(injected names)}
+ :'2' => { name: %w(injected names) }
}
}
})
diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb
index 2ed486516d..9f7d14e85d 100644
--- a/actionpack/test/controller/parameters/parameters_permit_test.rb
+++ b/actionpack/test/controller/parameters/parameters_permit_test.rb
@@ -194,6 +194,19 @@ class ParametersPermitTest < ActiveSupport::TestCase
assert_equal "monkey", @params.fetch(:foo) { "monkey" }
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 }
+ end
+
+ test 'KeyError in fetch block should not be covered up' do
+ params = ActionController::Parameters.new
+ e = assert_raises(KeyError) do
+ params.fetch(:missing_key) { {}.fetch(:also_missing) }
+ end
+ assert_match(/:also_missing$/, e.message)
+ end
+
test "not permitted is sticky beyond merges" do
assert !@params.merge(a: "b").permitted?
end
@@ -253,7 +266,6 @@ class ParametersPermitTest < ActiveSupport::TestCase
assert @params.to_h.is_a? Hash
assert_not @params.to_h.is_a? ActionController::Parameters
- assert_equal @params.to_hash, @params.to_h
end
test "to_h returns converted hash when .permit_all_parameters is set" do
@@ -284,6 +296,5 @@ class ParametersPermitTest < ActiveSupport::TestCase
test "to_unsafe_h returns unfiltered params" do
assert @params.to_h.is_a? Hash
assert_not @params.to_h.is_a? ActionController::Parameters
- assert_equal @params.to_hash, @params.to_unsafe_h
end
end
diff --git a/actionpack/test/controller/permitted_params_test.rb b/actionpack/test/controller/permitted_params_test.rb
index 7136fafae5..7c753a45a5 100644
--- a/actionpack/test/controller/permitted_params_test.rb
+++ b/actionpack/test/controller/permitted_params_test.rb
@@ -2,11 +2,11 @@ require 'abstract_unit'
class PeopleController < ActionController::Base
def create
- render text: params[:person].permitted? ? "permitted" : "forbidden"
+ render plain: params[:person].permitted? ? "permitted" : "forbidden"
end
def create_with_permit
- render text: params[:person].permit(:name).permitted? ? "permitted" : "forbidden"
+ render plain: params[:person].permit(:name).permitted? ? "permitted" : "forbidden"
end
end
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index efd790de63..91b30ede6a 100644
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -1,13 +1,10 @@
require 'abstract_unit'
-class WorkshopsController < ActionController::Base
-end
-
class RedirectController < ActionController::Base
# empty method not used anywhere to ensure methods like
# `status` and `location` aren't called on `redirect_to` calls
- def status; render :text => 'called status'; end
- def location; render :text => 'called location'; end
+ def status; render plain: 'called status'; end
+ def location; render plain: 'called location'; end
def simple_redirect
redirect_to :action => "hello_world"
@@ -53,11 +50,6 @@ class RedirectController < ActionController::Base
redirect_to :controller => 'module_test/module_redirect', :action => "hello_world"
end
- def redirect_with_assigns
- @hello = "world"
- redirect_to :action => "hello_world"
- end
-
def redirect_to_url
redirect_to "http://www.rubyonrails.org/"
end
@@ -218,12 +210,6 @@ class RedirectTest < ActionController::TestCase
assert_redirected_to :controller => 'module_test/module_redirect', :action => 'hello_world'
end
- def test_redirect_with_assigns
- get :redirect_with_assigns
- assert_response :redirect
- assert_equal "world", assigns["hello"]
- end
-
def test_redirect_to_url
get :redirect_to_url
assert_response :redirect
diff --git a/actionpack/test/controller/render_json_test.rb b/actionpack/test/controller/render_json_test.rb
index b1ad16bc55..3773900cc4 100644
--- a/actionpack/test/controller/render_json_test.rb
+++ b/actionpack/test/controller/render_json_test.rb
@@ -28,7 +28,7 @@ class RenderJsonTest < ActionController::TestCase
end
def render_json_render_to_string
- render :text => render_to_string(:json => '[]')
+ render plain: render_to_string(json: '[]')
end
def render_json_hello_world
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 79e2104789..9acdc29aeb 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -10,20 +10,25 @@ class TestControllerWithExtraEtags < ActionController::Base
etag { nil }
def fresh
- render text: "stale" if stale?(etag: '123', template: false)
+ render plain: "stale" if stale?(etag: '123', template: false)
end
def array
- render text: "stale" if stale?(etag: %w(1 2 3), template: false)
+ render plain: "stale" if stale?(etag: %w(1 2 3), template: false)
end
def with_template
if stale? template: 'test/hello_world'
- render text: 'stale'
+ render plain: 'stale'
end
end
end
+class ImplicitRenderTestController < ActionController::Base
+ def empty_action
+ end
+end
+
class TestController < ActionController::Base
protect_from_forgery
@@ -120,6 +125,10 @@ 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
@@ -129,6 +138,14 @@ 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
@@ -142,31 +159,31 @@ class TestController < ActionController::Base
end
def head_with_location_header
- head :location => "/foo"
+ head :ok, :location => "/foo"
end
def head_with_location_object
- head :location => Customer.new("david", 1)
+ head :ok, :location => Customer.new("david", 1)
end
def head_with_symbolic_status
- head :status => params[:status].intern
+ head params[:status].intern
end
def head_with_integer_status
- head :status => params[:status].to_i
+ head params[:status].to_i
end
def head_with_string_status
- head :status => params[:status]
+ head params[:status]
end
def head_with_custom_header
- head :x_custom_header => "something"
+ head :ok, :x_custom_header => "something"
end
def head_with_www_authenticate_header
- head 'WWW-Authenticate' => 'something'
+ head :ok, 'WWW-Authenticate' => 'something'
end
def head_with_status_code_first
@@ -270,6 +287,12 @@ 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.stubs(:now).returns(time)
@@ -453,6 +476,15 @@ class MetalRenderTest < ActionController::TestCase
end
end
+class ImplicitRenderTest < ActionController::TestCase
+ tests ImplicitRenderTestController
+
+ def test_implicit_no_content_response
+ get :empty_action
+ assert_response :no_content
+ end
+end
+
class HeadRenderTest < ActionController::TestCase
tests TestController
@@ -466,6 +498,19 @@ 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?
@@ -577,7 +622,7 @@ class HttpCacheForeverTest < ActionController::TestCase
class HttpCacheForeverController < ActionController::Base
def cache_me_forever
http_cache_forever(public: params[:public], version: params[:version] || 'v1') do
- render text: 'hello'
+ render plain: 'hello'
end
end
end
diff --git a/actionpack/test/controller/request/test_request_test.rb b/actionpack/test/controller/request/test_request_test.rb
index 77a2f68b1c..e5d698d5c2 100644
--- a/actionpack/test/controller/request/test_request_test.rb
+++ b/actionpack/test/controller/request/test_request_test.rb
@@ -1,11 +1,7 @@
require 'abstract_unit'
require 'stringio'
-class ActionController::TestRequestTest < ActiveSupport::TestCase
-
- def setup
- @request = ActionController::TestRequest.new
- end
+class ActionController::TestRequestTest < ActionController::TestCase
def test_test_request_has_session_options_initialized
assert @request.session_options
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index 8887f291cf..868520a219 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -12,30 +12,14 @@ module RequestForgeryProtectionActions
render :inline => "<%= button_to('New', '/') %>"
end
- def external_form
- render :inline => "<%= form_tag('http://farfar.away/form', :authenticity_token => 'external_token') {} %>"
- end
-
- def external_form_without_protection
- render :inline => "<%= form_tag('http://farfar.away/form', :authenticity_token => false) {} %>"
- end
-
def unsafe
- render :text => 'pwn'
+ render plain: 'pwn'
end
def meta
render :inline => "<%= csrf_meta_tags %>"
end
- def external_form_for
- render :inline => "<%= form_for(:some_resource, :authenticity_token => 'external_token') {} %>"
- end
-
- def form_for_without_protection
- render :inline => "<%= form_for(:some_resource, :authenticity_token => false ) {} %>"
- end
-
def form_for_remote
render :inline => "<%= form_for(:some_resource, :remote => true ) {} %>"
end
@@ -70,7 +54,6 @@ module RequestForgeryProtectionActions
negotiate_same_origin
end
- def rescue_action(e) raise e end
end
# sample controllers
@@ -89,17 +72,17 @@ class RequestForgeryProtectionControllerUsingNullSession < ActionController::Bas
def signed
cookies.signed[:foo] = 'bar'
- render :nothing => true
+ head :ok
end
def encrypted
cookies.encrypted[:foo] = 'bar'
- render :nothing => true
+ head :ok
end
def try_to_reset_session
reset_session
- render :nothing => true
+ head :ok
end
end
@@ -501,11 +484,10 @@ end
class FreeCookieControllerTest < ActionController::TestCase
def setup
@controller = FreeCookieController.new
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
@token = "cf50faa3fe97702ca1ae"
SecureRandom.stubs(:base64).returns(@token)
+ super
end
def test_should_not_render_form_with_token_tag
diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb
index 4898b0c57f..e767323773 100644
--- a/actionpack/test/controller/rescue_test.rb
+++ b/actionpack/test/controller/rescue_test.rb
@@ -43,29 +43,29 @@ class RescueController < ActionController::Base
rescue_from NotAllowed, :with => proc { head :forbidden }
rescue_from 'RescueController::NotAllowedToRescueAsString', :with => proc { head :forbidden }
- rescue_from InvalidRequest, :with => proc { |exception| render :text => exception.message }
- rescue_from 'InvalidRequestToRescueAsString', :with => proc { |exception| render :text => exception.message }
+ rescue_from InvalidRequest, with: proc { |exception| render plain: exception.message }
+ rescue_from 'InvalidRequestToRescueAsString', with: proc { |exception| render plain: exception.message }
rescue_from BadGateway do
- head :status => 502
+ head 502
end
rescue_from 'BadGatewayToRescueAsString' do
- head :status => 502
+ head 502
end
rescue_from ResourceUnavailable do |exception|
- render :text => exception.message
+ render plain: exception.message
end
rescue_from 'ResourceUnavailableToRescueAsString' do |exception|
- render :text => exception.message
+ render plain: exception.message
end
rescue_from ActionView::TemplateError do
- render :text => 'action_view templater error'
+ render plain: 'action_view templater error'
end
rescue_from IOError do
- render :text => 'io error'
+ render plain: 'io error'
end
before_action(only: :before_action_raises) { raise 'umm nice' }
@@ -74,7 +74,7 @@ class RescueController < ActionController::Base
end
def raises
- render :text => 'already rendered'
+ render plain: 'already rendered'
raise "don't panic!"
end
@@ -302,7 +302,7 @@ class RescueTest < ActionDispatch::IntegrationTest
rescue_from RecordInvalid, :with => :show_errors
def foo
- render :text => "foo"
+ render plain: "foo"
end
def invalid
@@ -315,7 +315,7 @@ class RescueTest < ActionDispatch::IntegrationTest
protected
def show_errors(exception)
- render :text => exception.message
+ render plain: exception.message
end
end
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index f3da2df3ef..04d6cc1792 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -1128,14 +1128,14 @@ class ResourcesTest < ActionController::TestCase
end
def assert_restful_routes_for(controller_name, options = {})
- options[:options] ||= {}
- options[:options][:controller] = options[:controller] || controller_name.to_s
+ route_options = (options[:options] ||= {}).dup
+ route_options[:controller] = options[:controller] || controller_name.to_s
if options[:shallow]
options[:shallow_options] ||= {}
- options[:shallow_options][:controller] = options[:options][:controller]
+ options[:shallow_options][:controller] = route_options[:controller]
else
- options[:shallow_options] = options[:options]
+ options[:shallow_options] = route_options
end
new_action = @routes.resources_path_names[:new] || "new"
@@ -1154,7 +1154,7 @@ class ResourcesTest < ActionController::TestCase
edit_member_path = "#{member_path}/#{edit_action}"
formatted_edit_member_path = "#{member_path}/#{edit_action}.xml"
- with_options(options[:options]) do |controller|
+ with_options(route_options) do |controller|
controller.assert_routing collection_path, :action => 'index'
controller.assert_routing new_path, :action => 'new'
controller.assert_routing "#{collection_path}.xml", :action => 'index', :format => 'xml'
@@ -1168,23 +1168,23 @@ class ResourcesTest < ActionController::TestCase
controller.assert_routing formatted_edit_member_path, :action => 'edit', :id => '1', :format => 'xml'
end
- assert_recognizes(options[:options].merge(:action => 'index'), :path => collection_path, :method => :get)
- assert_recognizes(options[:options].merge(:action => 'new'), :path => new_path, :method => :get)
- assert_recognizes(options[:options].merge(:action => 'create'), :path => collection_path, :method => :post)
+ assert_recognizes(route_options.merge(:action => 'index'), :path => collection_path, :method => :get)
+ assert_recognizes(route_options.merge(:action => 'new'), :path => new_path, :method => :get)
+ assert_recognizes(route_options.merge(:action => 'create'), :path => collection_path, :method => :post)
assert_recognizes(options[:shallow_options].merge(:action => 'show', :id => '1'), :path => member_path, :method => :get)
assert_recognizes(options[:shallow_options].merge(:action => 'edit', :id => '1'), :path => edit_member_path, :method => :get)
assert_recognizes(options[:shallow_options].merge(:action => 'update', :id => '1'), :path => member_path, :method => :put)
assert_recognizes(options[:shallow_options].merge(:action => 'destroy', :id => '1'), :path => member_path, :method => :delete)
- assert_recognizes(options[:options].merge(:action => 'index', :format => 'xml'), :path => "#{collection_path}.xml", :method => :get)
- assert_recognizes(options[:options].merge(:action => 'new', :format => 'xml'), :path => "#{new_path}.xml", :method => :get)
- assert_recognizes(options[:options].merge(:action => 'create', :format => 'xml'), :path => "#{collection_path}.xml", :method => :post)
+ assert_recognizes(route_options.merge(:action => 'index', :format => 'xml'), :path => "#{collection_path}.xml", :method => :get)
+ assert_recognizes(route_options.merge(:action => 'new', :format => 'xml'), :path => "#{new_path}.xml", :method => :get)
+ assert_recognizes(route_options.merge(:action => 'create', :format => 'xml'), :path => "#{collection_path}.xml", :method => :post)
assert_recognizes(options[:shallow_options].merge(:action => 'show', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :get)
assert_recognizes(options[:shallow_options].merge(:action => 'edit', :id => '1', :format => 'xml'), :path => formatted_edit_member_path, :method => :get)
assert_recognizes(options[:shallow_options].merge(:action => 'update', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :put)
assert_recognizes(options[:shallow_options].merge(:action => 'destroy', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :delete)
- yield options[:options] if block_given?
+ yield route_options if block_given?
end
# test named routes like foo_path and foos_path map to the correct options.
@@ -1195,22 +1195,20 @@ class ResourcesTest < ActionController::TestCase
end
singular_name ||= controller_name.to_s.singularize
- options[:options] ||= {}
- options[:options][:controller] = options[:controller] || controller_name.to_s
+ route_options = (options[:options] ||= {}).dup
+ route_options[:controller] = options[:controller] || controller_name.to_s
if options[:shallow]
options[:shallow_options] ||= {}
- options[:shallow_options][:controller] = options[:options][:controller]
+ options[:shallow_options][:controller] = route_options[:controller]
else
- options[:shallow_options] = options[:options]
+ options[:shallow_options] = route_options
end
- @controller = "#{options[:options][:controller].camelize}Controller".constantize.new
+ @controller = "#{route_options[:controller].camelize}Controller".constantize.new
@controller.singleton_class.include(@routes.url_helpers)
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
- get :index, params: options[:options]
- options[:options].delete :action
+ get :index, params: route_options
+ route_options.delete :action
path = "#{options[:as] || controller_name}"
shallow_path = "/#{options[:shallow] ? options[:namespace] : options[:path_prefix]}#{path}"
@@ -1225,29 +1223,29 @@ class ResourcesTest < ActionController::TestCase
edit_action = options[:path_names][:edit] || "edit"
end
- assert_named_route "#{full_path}", "#{name_prefix}#{controller_name}_path", options[:options]
- assert_named_route "#{full_path}.xml", "#{name_prefix}#{controller_name}_path", options[:options].merge(:format => 'xml')
+ assert_named_route "#{full_path}", "#{name_prefix}#{controller_name}_path", route_options
+ assert_named_route "#{full_path}.xml", "#{name_prefix}#{controller_name}_path", route_options.merge(:format => 'xml')
assert_named_route "#{shallow_path}/1", "#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1')
assert_named_route "#{shallow_path}/1.xml", "#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1', :format => 'xml')
- assert_named_route "#{full_path}/#{new_action}", "new_#{name_prefix}#{singular_name}_path", options[:options]
- assert_named_route "#{full_path}/#{new_action}.xml", "new_#{name_prefix}#{singular_name}_path", options[:options].merge(:format => 'xml')
+ assert_named_route "#{full_path}/#{new_action}", "new_#{name_prefix}#{singular_name}_path", route_options
+ assert_named_route "#{full_path}/#{new_action}.xml", "new_#{name_prefix}#{singular_name}_path", route_options.merge(:format => 'xml')
assert_named_route "#{shallow_path}/1/#{edit_action}", "edit_#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1')
assert_named_route "#{shallow_path}/1/#{edit_action}.xml", "edit_#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1', :format => 'xml')
- yield options[:options] if block_given?
+ yield route_options if block_given?
end
def assert_singleton_routes_for(singleton_name, options = {})
- options[:options] ||= {}
- options[:options][:controller] = options[:controller] || singleton_name.to_s.pluralize
+ route_options = (options[:options] ||= {}).dup
+ route_options[:controller] = options[:controller] || singleton_name.to_s.pluralize
full_path = "/#{options[:path_prefix]}#{options[:as] || singleton_name}"
new_path = "#{full_path}/new"
edit_path = "#{full_path}/edit"
formatted_edit_path = "#{full_path}/edit.xml"
- with_options options[:options] do |controller|
+ with_options route_options do |controller|
controller.assert_routing full_path, :action => 'show'
controller.assert_routing new_path, :action => 'new'
controller.assert_routing edit_path, :action => 'edit'
@@ -1256,42 +1254,41 @@ class ResourcesTest < ActionController::TestCase
controller.assert_routing formatted_edit_path, :action => 'edit', :format => 'xml'
end
- assert_recognizes(options[:options].merge(:action => 'show'), :path => full_path, :method => :get)
- assert_recognizes(options[:options].merge(:action => 'new'), :path => new_path, :method => :get)
- assert_recognizes(options[:options].merge(:action => 'edit'), :path => edit_path, :method => :get)
- assert_recognizes(options[:options].merge(:action => 'create'), :path => full_path, :method => :post)
- assert_recognizes(options[:options].merge(:action => 'update'), :path => full_path, :method => :put)
- assert_recognizes(options[:options].merge(:action => 'destroy'), :path => full_path, :method => :delete)
+ assert_recognizes(route_options.merge(:action => 'show'), :path => full_path, :method => :get)
+ assert_recognizes(route_options.merge(:action => 'new'), :path => new_path, :method => :get)
+ assert_recognizes(route_options.merge(:action => 'edit'), :path => edit_path, :method => :get)
+ assert_recognizes(route_options.merge(:action => 'create'), :path => full_path, :method => :post)
+ assert_recognizes(route_options.merge(:action => 'update'), :path => full_path, :method => :put)
+ assert_recognizes(route_options.merge(:action => 'destroy'), :path => full_path, :method => :delete)
- assert_recognizes(options[:options].merge(:action => 'show', :format => 'xml'), :path => "#{full_path}.xml", :method => :get)
- assert_recognizes(options[:options].merge(:action => 'new', :format => 'xml'), :path => "#{new_path}.xml", :method => :get)
- assert_recognizes(options[:options].merge(:action => 'edit', :format => 'xml'), :path => formatted_edit_path, :method => :get)
- assert_recognizes(options[:options].merge(:action => 'create', :format => 'xml'), :path => "#{full_path}.xml", :method => :post)
- assert_recognizes(options[:options].merge(:action => 'update', :format => 'xml'), :path => "#{full_path}.xml", :method => :put)
- assert_recognizes(options[:options].merge(:action => 'destroy', :format => 'xml'), :path => "#{full_path}.xml", :method => :delete)
+ assert_recognizes(route_options.merge(:action => 'show', :format => 'xml'), :path => "#{full_path}.xml", :method => :get)
+ assert_recognizes(route_options.merge(:action => 'new', :format => 'xml'), :path => "#{new_path}.xml", :method => :get)
+ assert_recognizes(route_options.merge(:action => 'edit', :format => 'xml'), :path => formatted_edit_path, :method => :get)
+ assert_recognizes(route_options.merge(:action => 'create', :format => 'xml'), :path => "#{full_path}.xml", :method => :post)
+ assert_recognizes(route_options.merge(:action => 'update', :format => 'xml'), :path => "#{full_path}.xml", :method => :put)
+ assert_recognizes(route_options.merge(:action => 'destroy', :format => 'xml'), :path => "#{full_path}.xml", :method => :delete)
- yield options[:options] if block_given?
+ yield route_options if block_given?
end
def assert_singleton_named_routes_for(singleton_name, options = {})
- (options[:options] ||= {})[:controller] ||= singleton_name.to_s.pluralize
- @controller = "#{options[:options][:controller].camelize}Controller".constantize.new
+ route_options = (options[:options] ||= {}).dup
+ controller_name = route_options[:controller] || options[:controller] || singleton_name.to_s.pluralize
+ @controller = "#{controller_name.camelize}Controller".constantize.new
@controller.singleton_class.include(@routes.url_helpers)
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
- get :show, params: options[:options]
- options[:options].delete :action
+ get :show, params: route_options
+ route_options.delete :action
full_path = "/#{options[:path_prefix]}#{options[:as] || singleton_name}"
name_prefix = options[:name_prefix]
- assert_named_route "#{full_path}", "#{name_prefix}#{singleton_name}_path", options[:options]
- assert_named_route "#{full_path}.xml", "#{name_prefix}#{singleton_name}_path", options[:options].merge(:format => 'xml')
+ assert_named_route "#{full_path}", "#{name_prefix}#{singleton_name}_path", route_options
+ assert_named_route "#{full_path}.xml", "#{name_prefix}#{singleton_name}_path", route_options.merge(:format => 'xml')
- assert_named_route "#{full_path}/new", "new_#{name_prefix}#{singleton_name}_path", options[:options]
- assert_named_route "#{full_path}/new.xml", "new_#{name_prefix}#{singleton_name}_path", options[:options].merge(:format => 'xml')
- assert_named_route "#{full_path}/edit", "edit_#{name_prefix}#{singleton_name}_path", options[:options]
- assert_named_route "#{full_path}/edit.xml", "edit_#{name_prefix}#{singleton_name}_path", options[:options].merge(:format => 'xml')
+ assert_named_route "#{full_path}/new", "new_#{name_prefix}#{singleton_name}_path", route_options
+ assert_named_route "#{full_path}/new.xml", "new_#{name_prefix}#{singleton_name}_path", route_options.merge(:format => 'xml')
+ assert_named_route "#{full_path}/edit", "edit_#{name_prefix}#{singleton_name}_path", route_options
+ assert_named_route "#{full_path}/edit.xml", "edit_#{name_prefix}#{singleton_name}_path", route_options.merge(:format => 'xml')
end
def assert_named_route(expected, route, options)
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 2d08987ca6..a333290ade 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -8,8 +8,6 @@ class MilestonesController < ActionController::Base
alias_method :show, :index
end
-ROUTING = ActionDispatch::Routing
-
# See RFC 3986, section 3.3 for allowed path characters.
class UriReservedCharactersRoutingTest < ActiveSupport::TestCase
include RoutingTestHelpers
@@ -337,9 +335,11 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_route_with_colon_first
rs.draw do
- get '/:controller/:action/:id', :action => 'index', :id => nil
- get ':url', :controller => 'tiny_url', :action => 'translate'
+ get '/:controller/:action/:id', action: 'index', id: nil
+ get ':url', controller: 'content', action: 'translate'
end
+
+ assert_equal({controller: 'content', action: 'translate', url: 'example'}, rs.recognize_path('/example'))
end
def test_route_with_regexp_for_controller
@@ -871,7 +871,7 @@ class RouteSetTest < ActiveSupport::TestCase
def default_route_set
@default_route_set ||= begin
- set = ROUTING::RouteSet.new
+ set = ActionDispatch::Routing::RouteSet.new
set.draw do
get '/:controller(/:action(/:id))'
end
@@ -1748,13 +1748,13 @@ class RouteSetTest < ActiveSupport::TestCase
include ActionDispatch::RoutingVerbs
- class TestSet < ROUTING::RouteSet
+ class TestSet < ActionDispatch::Routing::RouteSet
def initialize(block)
@block = block
super()
end
- class Dispatcher < ROUTING::RouteSet::Dispatcher
+ class Dispatcher < ActionDispatch::Routing::RouteSet::Dispatcher
def initialize(defaults, set, block)
super(defaults)
@block = block
diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb
index 36c57ec9b2..c0ddcf7f50 100644
--- a/actionpack/test/controller/send_file_test.rb
+++ b/actionpack/test/controller/send_file_test.rb
@@ -34,8 +34,6 @@ class SendFileTest < ActionController::TestCase
def setup
@controller = SendFileController.new
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
end
def test_file_nostream
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index e348749f78..1c5de983d8 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -6,70 +6,78 @@ require 'rails/engine'
class TestCaseTest < ActionController::TestCase
class TestController < ActionController::Base
def no_op
- render text: 'dummy'
+ render plain: 'dummy'
end
def set_flash
flash["test"] = ">#{flash["test"]}<"
- render text: 'ignore me'
+ render plain: 'ignore me'
end
def delete_flash
flash.delete("test")
- render :text => 'ignore me'
+ render plain: 'ignore me'
end
def set_flash_now
flash.now["test_now"] = ">#{flash["test_now"]}<"
- render text: 'ignore me'
+ render plain: 'ignore me'
end
def set_session
session['string'] = 'A wonder'
session[:symbol] = 'it works'
- render text: 'Success'
+ render plain: 'Success'
end
def reset_the_session
reset_session
- render text: 'ignore me'
+ render plain: 'ignore me'
end
def render_raw_post
raise ActiveSupport::TestCase::Assertion, "#raw_post is blank" if request.raw_post.blank?
- render text: request.raw_post
+ render plain: request.raw_post
end
def render_body
- render text: request.body.read
+ render plain: request.body.read
end
def test_params
- render text: params.inspect
+ render plain: ::JSON.dump(params.to_unsafe_h)
+ end
+
+ def test_query_parameters
+ render plain: ::JSON.dump(request.query_parameters)
+ end
+
+ def test_request_parameters
+ render plain: request.request_parameters.inspect
end
def test_uri
- render text: request.fullpath
+ render plain: request.fullpath
end
def test_format
- render text: request.format
+ render plain: request.format
end
def test_query_string
- render text: request.query_string
+ render plain: request.query_string
end
def test_protocol
- render text: request.protocol
+ render plain: request.protocol
end
def test_headers
- render text: request.headers.env.to_json
+ render plain: request.headers.env.to_json
end
def test_html_output
- render text: <<HTML
+ render plain: <<HTML
<html>
<body>
<a href="/"><img src="/images/button.png" /></a>
@@ -91,7 +99,7 @@ HTML
def test_xml_output
response.content_type = "application/xml"
- render text: <<XML
+ 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>
@@ -100,15 +108,15 @@ XML
end
def test_only_one_param
- render text: (params[:left] && params[:right]) ? "EEP, Both here!" : "OK"
+ render plain: (params[:left] && params[:right]) ? "EEP, Both here!" : "OK"
end
def test_remote_addr
- render text: (request.remote_addr || "not specified")
+ render plain: (request.remote_addr || "not specified")
end
def test_file_upload
- render text: params[:file].size
+ render plain: params[:file].size
end
def test_send_file
@@ -129,13 +137,7 @@ XML
def delete_cookie
cookies.delete("foo")
- render nothing: true
- end
-
- def test_assigns
- @foo = "foo"
- @foo_hash = { foo: :bar }
- render nothing: true
+ head :ok
end
def test_without_body
@@ -156,8 +158,6 @@ XML
def setup
super
@controller = TestController.new
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
@request.env['PATH_INFO'] = nil
@routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
r.draw do
@@ -166,22 +166,11 @@ XML
end
end
- class ViewAssignsController < ActionController::Base
- def test_assigns
- @foo = "foo"
- render nothing: true
- end
-
- def view_assigns
- { "bar" => "bar" }
- end
- end
-
class DefaultUrlOptionsCachingController < ActionController::Base
before_action { @dynamic_opt = 'opt' }
def test_url_options_reset
- render text: url_for(params)
+ render plain: url_for(params)
end
def default_url_options
@@ -238,7 +227,7 @@ XML
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"}), @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
@@ -438,30 +427,6 @@ XML
assert_equal "OK", @response.body
end
- def test_assigns
- process :test_assigns
- # assigns can be accessed using assigns(key)
- # or assigns[key], where key is a string or
- # a symbol
- assert_equal "foo", assigns(:foo)
- assert_equal "foo", assigns("foo")
- assert_equal "foo", assigns[:foo]
- assert_equal "foo", assigns["foo"]
-
- # but the assigned variable should not have its own keys stringified
- expected_hash = { foo: :bar }
- assert_equal expected_hash, assigns(:foo_hash)
- end
-
- def test_view_assigns
- @controller = ViewAssignsController.new
- process :test_assigns
- assert_equal nil, assigns(:foo)
- assert_equal nil, assigns[:foo]
- assert_equal "bar", assigns(:bar)
- assert_equal "bar", assigns[:bar]
- end
-
def test_should_not_impose_childless_html_tags_in_xml
process :test_xml_output
@@ -518,7 +483,7 @@ XML
assert_deprecated {
get :test_params, page: { name: "Page name", month: '4', year: '2004', day: '6' }
}
- parsed_params = eval(@response.body)
+ parsed_params = ::JSON.parse(@response.body)
assert_equal(
{
'controller' => 'test_case_test/test', 'action' => 'test_params',
@@ -537,7 +502,7 @@ XML
day: '6'
}
}
- parsed_params = eval(@response.body)
+ parsed_params = ::JSON.parse(@response.body)
assert_equal(
{
'controller' => 'test_case_test/test', 'action' => 'test_params',
@@ -547,6 +512,18 @@ XML
)
end
+ def test_query_param_named_action
+ get :test_query_parameters, params: {action: 'foobar'}
+ parsed_params = JSON.parse(@response.body)
+ assert_equal({'action' => 'foobar'}, parsed_params)
+ end
+
+ def test_request_param_named_action
+ post :test_request_parameters, params: {action: 'foobar'}
+ parsed_params = eval(@response.body)
+ assert_equal({'action' => 'foobar'}, parsed_params)
+ end
+
def test_kwarg_params_passing_with_session_and_flash
get :test_params, params: {
page: {
@@ -557,7 +534,7 @@ XML
}
}, session: { 'foo' => 'bar' }, flash: { notice: 'created' }
- parsed_params = eval(@response.body)
+ 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'}},
@@ -572,7 +549,7 @@ XML
get :test_params, params: {
page: { name: "Page name", month: 4, year: 2004, day: 6 }
}
- parsed_params = eval(@response.body)
+ 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'}},
@@ -582,17 +559,17 @@ XML
def test_params_passing_with_fixnums_when_not_html_request
get :test_params, params: { format: 'json', count: 999 }
- parsed_params = eval(@response.body)
+ parsed_params = ::JSON.parse(@response.body)
assert_equal(
{'controller' => 'test_case_test/test', 'action' => 'test_params',
- 'format' => 'json', 'count' => 999 },
+ 'format' => 'json', 'count' => '999' },
parsed_params
)
end
def test_params_passing_path_parameter_is_string_when_not_html_request
get :test_params, params: { format: 'json', id: 1 }
- parsed_params = eval(@response.body)
+ parsed_params = ::JSON.parse(@response.body)
assert_equal(
{'controller' => 'test_case_test/test', 'action' => 'test_params',
'format' => 'json', 'id' => '1' },
@@ -602,7 +579,7 @@ XML
def test_deprecated_params_passing_path_parameter_is_string_when_not_html_request
assert_deprecated { get :test_params, format: 'json', id: 1 }
- parsed_params = eval(@response.body)
+ parsed_params = ::JSON.parse(@response.body)
assert_equal(
{'controller' => 'test_case_test/test', 'action' => 'test_params',
'format' => 'json', 'id' => '1' },
@@ -616,7 +593,7 @@ XML
frozen: 'icy'.freeze, frozens: ['icy'.freeze].freeze, deepfreeze: { frozen: 'icy'.freeze }.freeze
}
end
- parsed_params = eval(@response.body)
+ parsed_params = ::JSON.parse(@response.body)
assert_equal(
{'controller' => 'test_case_test/test', 'action' => 'test_params',
'frozen' => 'icy', 'frozens' => ['icy'], 'deepfreeze' => { 'frozen' => 'icy' }},
@@ -714,13 +691,13 @@ XML
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"}), @response.body)
+ 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"}), @response.body)
+ assert_equal({"id"=>"1", "controller"=>"test_case_test/test", "action"=>"test_params"}, ::JSON.parse(@response.body))
end
def test_xhr_with_session
@@ -741,12 +718,6 @@ XML
assert_equal 'it works', session[:symbol], "Test session hash should allow indifferent access"
end
- def test_header_properly_reset_after_get_request
- get :test_params
- @request.recycle!
- assert_nil @request.instance_variable_get("@request_method")
- end
-
def test_deprecated_params_reset_between_post_requests
assert_deprecated { post :no_op, foo: "bar" }
assert_equal "bar", @request.params[:foo]
@@ -908,6 +879,11 @@ XML
assert_equal File.open(path, READ_PLAIN).read, plain_file_upload.read
end
+ def test_fixture_file_upload_should_be_able_access_to_tempfile
+ file = fixture_file_upload(FILES_DIR + "/mona_lisa.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: {
@@ -932,7 +908,7 @@ XML
filename = 'mona_lisa.jpg'
path = "#{FILES_DIR}/#{filename}"
assert_deprecated {
- post :test_file_upload, file: ActionDispatch::Http::UploadedFile.new(filename: path, type: "image/jpg", tempfile: File.open(path))
+ post :test_file_upload, file: Rack::Test::UploadedFile.new(path, "image/jpg", true)
}
assert_equal '159528', @response.body
end
@@ -941,7 +917,7 @@ XML
filename = 'mona_lisa.jpg'
path = "#{FILES_DIR}/#{filename}"
post :test_file_upload, params: {
- file: ActionDispatch::Http::UploadedFile.new(filename: path, type: "image/jpg", tempfile: File.open(path))
+ file: Rack::Test::UploadedFile.new(path, "image/jpg", true)
}
assert_equal '159528', @response.body
end
@@ -973,10 +949,11 @@ class ResponseDefaultHeadersTest < ActionController::TestCase
end
end
- setup do
+ def before_setup
@original = ActionDispatch::Response.default_headers
@defaults = { 'A' => '1', 'B' => '2' }
ActionDispatch::Response.default_headers = @defaults
+ super
end
teardown do
@@ -986,8 +963,6 @@ class ResponseDefaultHeadersTest < ActionController::TestCase
def setup
super
@controller = TestController.new
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
@request.env['PATH_INFO'] = nil
@routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
r.draw do
@@ -1022,7 +997,7 @@ module EngineControllerTests
class BarController < ActionController::Base
def index
- render text: 'bar'
+ render plain: 'bar'
end
end
@@ -1108,7 +1083,7 @@ class AnonymousControllerTest < ActionController::TestCase
def setup
@controller = Class.new(ActionController::Base) do
def index
- render text: params[:controller]
+ render plain: params[:controller]
end
end.new
@@ -1129,11 +1104,11 @@ class RoutingDefaultsTest < ActionController::TestCase
def setup
@controller = Class.new(ActionController::Base) do
def post
- render text: request.fullpath
+ render plain: request.fullpath
end
def project
- render text: request.fullpath
+ render plain: request.fullpath
end
end.new
diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb
index d9a1ae7d4f..5f2abc9606 100644
--- a/actionpack/test/controller/url_rewriter_test.rb
+++ b/actionpack/test/controller/url_rewriter_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
require 'controller/fake_controllers'
-class UrlRewriterTests < ActiveSupport::TestCase
+class UrlRewriterTests < ActionController::TestCase
class Rewriter
def initialize(request)
@options = {
@@ -16,7 +16,6 @@ class UrlRewriterTests < ActiveSupport::TestCase
end
def setup
- @request = ActionController::TestRequest.new
@params = {}
@rewriter = Rewriter.new(@request) #.new(@request, @params)
@routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb
index 21fa670bb6..b26f037c36 100644
--- a/actionpack/test/controller/webservice_test.rb
+++ b/actionpack/test/controller/webservice_test.rb
@@ -5,16 +5,22 @@ class WebServiceTest < ActionDispatch::IntegrationTest
class TestController < ActionController::Base
def assign_parameters
if params[:full]
- render :text => dump_params_keys
+ render plain: dump_params_keys
else
- render :text => (params.keys - ['controller', 'action']).sort.join(", ")
+ render plain: (params.keys - ['controller', 'action']).sort.join(", ")
end
end
def dump_params_keys(hash = params)
hash.keys.sort.inject("") do |s, k|
value = hash[k]
- value = Hash === value ? "(#{dump_params_keys(value)})" : ""
+
+ if value.is_a?(Hash) || value.is_a?(ActionController::Parameters)
+ value = "(#{dump_params_keys(value)})"
+ else
+ value = ""
+ end
+
s << ", " unless s.empty?
s << "#{k}#{value}"
end
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 6223a52a76..aca28ae8d1 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -280,7 +280,7 @@ class CookiesTest < ActionController::TestCase
def test_setting_the_same_value_to_cookie
request.cookies[:user_name] = 'david'
get :authenticate
- assert response.cookies.empty?
+ assert_predicate response.cookies, :empty?
end
def test_setting_the_same_value_to_permanent_cookie
@@ -360,7 +360,7 @@ class CookiesTest < ActionController::TestCase
def test_delete_unexisting_cookie
request.cookies.clear
get :delete_cookie
- assert @response.cookies.empty?
+ assert_predicate @response.cookies, :empty?
end
def test_deleted_cookie_predicate
@@ -378,7 +378,7 @@ class CookiesTest < ActionController::TestCase
def test_cookies_persist_throughout_request
response = get :authenticate
- assert response.headers["Set-Cookie"] =~ /user_name=david/
+ assert_match(/user_name=david/, response.headers["Set-Cookie"])
end
def test_set_permanent_cookie
diff --git a/actionpack/test/dispatch/exception_wrapper_test.rb b/actionpack/test/dispatch/exception_wrapper_test.rb
index 7a29a7ff97..3dac582407 100644
--- a/actionpack/test/dispatch/exception_wrapper_test.rb
+++ b/actionpack/test/dispatch/exception_wrapper_test.rb
@@ -19,15 +19,13 @@ module ActionDispatch
setup do
Rails.stubs(:root).returns(Pathname.new('.'))
- cleaner = ActiveSupport::BacktraceCleaner.new
- cleaner.add_silencer { |line| line !~ /^lib/ }
-
- @environment = { 'action_dispatch.backtrace_cleaner' => cleaner }
+ @cleaner = ActiveSupport::BacktraceCleaner.new
+ @cleaner.add_silencer { |line| line !~ /^lib/ }
end
test '#source_extracts fetches source fragments for every backtrace entry' do
exception = TestError.new("lib/file.rb:42:in `index'")
- wrapper = ExceptionWrapper.new({}, exception)
+ wrapper = ExceptionWrapper.new(nil, exception)
wrapper.expects(:source_fragment).with('lib/file.rb', 42).returns('foo')
@@ -37,7 +35,7 @@ module ActionDispatch
test '#source_extracts works with Windows paths' do
exc = TestError.new("c:/path/to/rails/app/controller.rb:27:in 'index':")
- wrapper = ExceptionWrapper.new({}, exc)
+ wrapper = ExceptionWrapper.new(nil, exc)
wrapper.expects(:source_fragment).with('c:/path/to/rails/app/controller.rb', 27).returns('nothing')
assert_equal [ code: 'nothing', line_number: 27 ], wrapper.source_extracts
@@ -46,7 +44,7 @@ module ActionDispatch
test '#source_extracts works with non standard backtrace' do
exc = TestError.new('invalid')
- wrapper = ExceptionWrapper.new({}, exc)
+ wrapper = ExceptionWrapper.new(nil, exc)
wrapper.expects(:source_fragment).with('invalid', 0).returns('nothing')
assert_equal [ code: 'nothing', line_number: 0 ], wrapper.source_extracts
@@ -54,14 +52,14 @@ module ActionDispatch
test '#application_trace returns traces only from the application' do
exception = TestError.new(caller.prepend("lib/file.rb:42:in `index'"))
- wrapper = ExceptionWrapper.new(@environment, exception)
+ wrapper = ExceptionWrapper.new(@cleaner, exception)
assert_equal [ "lib/file.rb:42:in `index'" ], wrapper.application_trace
end
test '#application_trace cannot be nil' do
- nil_backtrace_wrapper = ExceptionWrapper.new(@environment, BadlyDefinedError.new)
- nil_cleaner_wrapper = ExceptionWrapper.new({}, BadlyDefinedError.new)
+ nil_backtrace_wrapper = ExceptionWrapper.new(@cleaner, BadlyDefinedError.new)
+ nil_cleaner_wrapper = ExceptionWrapper.new(nil, BadlyDefinedError.new)
assert_equal [], nil_backtrace_wrapper.application_trace
assert_equal [], nil_cleaner_wrapper.application_trace
@@ -69,14 +67,14 @@ module ActionDispatch
test '#framework_trace returns traces outside the application' do
exception = TestError.new(caller.prepend("lib/file.rb:42:in `index'"))
- wrapper = ExceptionWrapper.new(@environment, exception)
+ wrapper = ExceptionWrapper.new(@cleaner, exception)
assert_equal caller, wrapper.framework_trace
end
test '#framework_trace cannot be nil' do
- nil_backtrace_wrapper = ExceptionWrapper.new(@environment, BadlyDefinedError.new)
- nil_cleaner_wrapper = ExceptionWrapper.new({}, BadlyDefinedError.new)
+ nil_backtrace_wrapper = ExceptionWrapper.new(@cleaner, BadlyDefinedError.new)
+ nil_cleaner_wrapper = ExceptionWrapper.new(nil, BadlyDefinedError.new)
assert_equal [], nil_backtrace_wrapper.framework_trace
assert_equal [], nil_cleaner_wrapper.framework_trace
@@ -84,14 +82,14 @@ module ActionDispatch
test '#full_trace returns application and framework traces' do
exception = TestError.new(caller.prepend("lib/file.rb:42:in `index'"))
- wrapper = ExceptionWrapper.new(@environment, exception)
+ wrapper = ExceptionWrapper.new(@cleaner, exception)
assert_equal exception.backtrace, wrapper.full_trace
end
test '#full_trace cannot be nil' do
- nil_backtrace_wrapper = ExceptionWrapper.new(@environment, BadlyDefinedError.new)
- nil_cleaner_wrapper = ExceptionWrapper.new({}, BadlyDefinedError.new)
+ nil_backtrace_wrapper = ExceptionWrapper.new(@cleaner, BadlyDefinedError.new)
+ nil_cleaner_wrapper = ExceptionWrapper.new(nil, BadlyDefinedError.new)
assert_equal [], nil_backtrace_wrapper.full_trace
assert_equal [], nil_cleaner_wrapper.full_trace
@@ -99,7 +97,7 @@ module ActionDispatch
test '#traces returns every trace by category enumerated with an index' do
exception = TestError.new("lib/file.rb:42:in `index'", "/gems/rack.rb:43:in `index'")
- wrapper = ExceptionWrapper.new(@environment, exception)
+ wrapper = ExceptionWrapper.new(@cleaner, exception)
assert_equal({
'Application Trace' => [ id: 0, trace: "lib/file.rb:42:in `index'" ],
diff --git a/actionpack/test/dispatch/live_response_test.rb b/actionpack/test/dispatch/live_response_test.rb
index 512f3a8a7a..5cfa5f7b3b 100644
--- a/actionpack/test/dispatch/live_response_test.rb
+++ b/actionpack/test/dispatch/live_response_test.rb
@@ -1,5 +1,5 @@
require 'abstract_unit'
-require 'active_support/concurrency/latch'
+require 'concurrent/atomics'
module ActionController
module Live
@@ -27,18 +27,18 @@ module ActionController
end
def test_parallel
- latch = ActiveSupport::Concurrency::Latch.new
+ latch = Concurrent::CountDownLatch.new
t = Thread.new {
@response.stream.write 'foo'
- latch.await
+ latch.wait
@response.stream.close
}
@response.await_commit
@response.each do |part|
assert_equal 'foo', part
- latch.release
+ latch.count_down
end
assert t.join
end
@@ -62,15 +62,15 @@ module ActionController
def test_headers_cannot_be_written_after_webserver_reads
@response.stream.write 'omg'
- latch = ActiveSupport::Concurrency::Latch.new
+ latch = Concurrent::CountDownLatch.new
t = Thread.new {
@response.stream.each do |chunk|
- latch.release
+ latch.count_down
end
}
- latch.await
+ latch.wait
assert @response.headers.frozen?
e = assert_raises(ActionDispatch::IllegalStateError) do
@response.headers['Content-Length'] = "zomg"
diff --git a/actionpack/test/dispatch/mapper_test.rb b/actionpack/test/dispatch/mapper_test.rb
index 889f9a4736..8734380008 100644
--- a/actionpack/test/dispatch/mapper_test.rb
+++ b/actionpack/test/dispatch/mapper_test.rb
@@ -19,10 +19,18 @@ module ActionDispatch
ActionDispatch::Request
end
+ def dispatcher_class
+ RouteSet::Dispatcher
+ end
+
def add_route(*args)
routes << args
end
+ def defaults
+ routes.map { |x| x[3] }
+ end
+
def conditions
routes.map { |x| x[1] }
end
@@ -36,13 +44,71 @@ module ActionDispatch
Mapper.new FakeSet.new
end
+ def test_scope_raises_on_anchor
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ assert_raises(ArgumentError) do
+ mapper.scope(anchor: false) do
+ end
+ end
+ end
+
+ def test_blows_up_without_via
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ assert_raises(ArgumentError) do
+ mapper.match '/', :to => 'posts#index', :as => :main
+ end
+ end
+
+ def test_unscoped_formatted
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ mapper.get '/foo', :to => 'posts#index', :as => :main, :format => true
+ assert_equal({:controller=>"posts", :action=>"index"},
+ fakeset.defaults.first)
+ assert_equal "/foo.:format", fakeset.conditions.first[:path_info]
+ end
+
+ def test_scoped_formatted
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ mapper.scope(format: true) do
+ mapper.get '/foo', :to => 'posts#index', :as => :main
+ end
+ assert_equal({:controller=>"posts", :action=>"index"},
+ fakeset.defaults.first)
+ assert_equal "/foo.:format", fakeset.conditions.first[:path_info]
+ end
+
+ def test_random_keys
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ mapper.scope(omg: :awesome) do
+ mapper.get '/', :to => 'posts#index', :as => :main
+ end
+ assert_equal({:omg=>:awesome, :controller=>"posts", :action=>"index"},
+ fakeset.defaults.first)
+ assert_equal ["GET"], fakeset.conditions.first[:request_method]
+ end
+
def test_mapping_requirements
- options = { :controller => 'foo', :action => 'bar', :via => :get }
- m = Mapper::Mapping.build({}, FakeSet.new, '/store/:name(*rest)', nil, options)
+ options = { }
+ scope = Mapper::Scope.new({})
+ m = Mapper::Mapping.build(scope, FakeSet.new, '/store/:name(*rest)', nil, 'foo', 'bar', nil, [:get], nil, options)
_, _, requirements, _ = m.to_route
assert_equal(/.+?/, requirements[:rest])
end
+ def test_via_scope
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ mapper.scope(via: :put) do
+ mapper.match '/', :to => 'posts#index', :as => :main
+ end
+ assert_equal ["PUT"], fakeset.conditions.first[:request_method]
+ end
+
def test_map_slash
fakeset = FakeSet.new
mapper = Mapper.new fakeset
diff --git a/actionpack/test/dispatch/middleware_stack/middleware_test.rb b/actionpack/test/dispatch/middleware_stack/middleware_test.rb
deleted file mode 100644
index 9607f026db..0000000000
--- a/actionpack/test/dispatch/middleware_stack/middleware_test.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-require 'abstract_unit'
-require 'action_dispatch/middleware/stack'
-
-module ActionDispatch
- class MiddlewareStack
- class MiddlewareTest < ActiveSupport::TestCase
- class Omg; end
-
- {
- 'concrete' => Omg,
- 'anonymous' => Class.new
- }.each do |name, klass|
-
- define_method("test_#{name}_klass") do
- mw = Middleware.new klass
- assert_equal klass, mw.klass
- end
-
- define_method("test_#{name}_==") do
- mw1 = Middleware.new klass
- mw2 = Middleware.new klass
- assert_equal mw1, mw2
- end
-
- end
-
- def test_string_class
- mw = Middleware.new Omg.name
- assert_equal Omg, mw.klass
- end
-
- def test_double_equal_works_with_classes
- k = Class.new
- mw = Middleware.new k
- assert_operator mw, :==, k
-
- result = mw != Class.new
- assert result, 'middleware should not equal other anon class'
- end
-
- def test_double_equal_works_with_strings
- mw = Middleware.new Omg
- assert_operator mw, :==, Omg.name
- end
-
- def test_double_equal_normalizes_strings
- mw = Middleware.new Omg
- assert_operator mw, :==, "::#{Omg.name}"
- end
-
- def test_middleware_loads_classnames_from_cache
- mw = Class.new(Middleware) {
- attr_accessor :classcache
- }.new(Omg.name)
-
- fake_cache = { mw.name => Omg }
- mw.classcache = fake_cache
-
- assert_equal Omg, mw.klass
-
- fake_cache[mw.name] = Middleware
- assert_equal Middleware, mw.klass
- end
-
- def test_middleware_always_returns_class
- mw = Class.new(Middleware) {
- attr_accessor :classcache
- }.new(Omg)
-
- fake_cache = { mw.name => Middleware }
- mw.classcache = fake_cache
-
- assert_equal Omg, mw.klass
- end
- end
- end
-end
diff --git a/actionpack/test/dispatch/middleware_stack_test.rb b/actionpack/test/dispatch/middleware_stack_test.rb
index 948a690979..33aa616474 100644
--- a/actionpack/test/dispatch/middleware_stack_test.rb
+++ b/actionpack/test/dispatch/middleware_stack_test.rb
@@ -4,6 +4,7 @@ class MiddlewareStackTest < ActiveSupport::TestCase
class FooMiddleware; end
class BarMiddleware; end
class BazMiddleware; end
+ class HiyaMiddleware; end
class BlockMiddleware
attr_reader :block
def initialize(&block)
@@ -17,6 +18,20 @@ 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
+ end
+ end
+
test "use should push middleware as class onto the stack" do
assert_difference "@stack.size" do
@stack.use BazMiddleware
@@ -25,17 +40,21 @@ class MiddlewareStackTest < ActiveSupport::TestCase
end
test "use should push middleware as a string onto the stack" do
- assert_difference "@stack.size" do
- @stack.use "MiddlewareStackTest::BazMiddleware"
+ assert_deprecated do
+ assert_difference "@stack.size" do
+ @stack.use "MiddlewareStackTest::BazMiddleware"
+ end
+ assert_equal BazMiddleware, @stack.last.klass
end
- assert_equal BazMiddleware, @stack.last.klass
end
test "use should push middleware as a symbol onto the stack" do
- assert_difference "@stack.size" do
- @stack.use :"MiddlewareStackTest::BazMiddleware"
+ assert_deprecated do
+ assert_difference "@stack.size" do
+ @stack.use :"MiddlewareStackTest::BazMiddleware"
+ end
+ assert_equal BazMiddleware, @stack.last.klass
end
- assert_equal BazMiddleware, @stack.last.klass
end
test "use should push middleware class with arguments onto the stack" do
@@ -88,30 +107,28 @@ class MiddlewareStackTest < ActiveSupport::TestCase
end
test "unshift adds a new middleware at the beginning of the stack" do
- @stack.unshift :"MiddlewareStackTest::BazMiddleware"
- assert_equal BazMiddleware, @stack.first.klass
+ assert_deprecated do
+ @stack.unshift :"MiddlewareStackTest::BazMiddleware"
+ assert_equal BazMiddleware, @stack.first.klass
+ end
end
test "raise an error on invalid index" do
assert_raise RuntimeError do
- @stack.insert("HiyaMiddleware", BazMiddleware)
+ @stack.insert(HiyaMiddleware, BazMiddleware)
end
assert_raise RuntimeError do
- @stack.insert_after("HiyaMiddleware", BazMiddleware)
+ @stack.insert_after(HiyaMiddleware, BazMiddleware)
end
end
test "lazy evaluates middleware class" do
- assert_difference "@stack.size" do
- @stack.use "MiddlewareStackTest::BazMiddleware"
+ assert_deprecated do
+ assert_difference "@stack.size" do
+ @stack.use "MiddlewareStackTest::BazMiddleware"
+ end
+ assert_equal BazMiddleware, @stack.last.klass
end
- assert_equal BazMiddleware, @stack.last.klass
- end
-
- test "lazy compares so unloaded constants are not loaded" do
- @stack.use "UnknownMiddleware"
- @stack.use :"MiddlewareStackTest::BazMiddleware"
- assert @stack.include?("::MiddlewareStackTest::BazMiddleware")
end
end
diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb
index f90d5499d7..d75e31db62 100644
--- a/actionpack/test/dispatch/prefix_generation_test.rb
+++ b/actionpack/test/dispatch/prefix_generation_test.rb
@@ -73,26 +73,26 @@ module TestGenerationPrefix
include RailsApplication.routes.mounted_helpers
def index
- render :text => posts_path
+ render plain: posts_path
end
def show
- render :text => post_path(:id => params[:id])
+ render plain: post_path(id: params[:id])
end
def url_to_application
path = main_app.url_for(:controller => "outside_engine_generating",
:action => "index",
:only_path => true)
- render :text => path
+ render plain: path
end
def polymorphic_path_for_engine
- render :text => polymorphic_path(Post.new)
+ render plain: polymorphic_path(Post.new)
end
def conflicting
- render :text => "engine"
+ render plain: "engine"
end
end
@@ -101,28 +101,28 @@ module TestGenerationPrefix
include RailsApplication.routes.url_helpers
def index
- render :text => blog_engine.post_path(:id => 1)
+ render plain: blog_engine.post_path(id: 1)
end
def polymorphic_path_for_engine
- render :text => blog_engine.polymorphic_path(Post.new)
+ render plain: blog_engine.polymorphic_path(Post.new)
end
def polymorphic_path_for_app
- render :text => polymorphic_path(Post.new)
+ render plain: polymorphic_path(Post.new)
end
def polymorphic_with_url_for
- render :text => blog_engine.url_for(Post.new)
+ render plain: blog_engine.url_for(Post.new)
end
def conflicting
- render :text => "application"
+ render plain: "application"
end
def ivar_usage
@blog_engine = "Not the engine route helper"
- render :text => blog_engine.post_path(:id => 1)
+ render plain: blog_engine.post_path(id: 1)
end
end
@@ -378,7 +378,7 @@ module TestGenerationPrefix
include RailsApplication.routes.mounted_helpers
def show
- render :text => post_path(:id => params[:id])
+ render plain: post_path(id: params[:id])
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 d77341bc64..c2300a0142 100644
--- a/actionpack/test/dispatch/request/json_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb
@@ -113,7 +113,7 @@ class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest
def parse
self.class.last_request_parameters = request.request_parameters
- self.class.last_parameters = params
+ self.class.last_parameters = params.to_unsafe_h
head :ok
end
end
diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
index 50f69c53cb..b36fbd3c76 100644
--- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
@@ -17,7 +17,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
end
def read
- render :text => "File: #{params[:uploaded_data].read}"
+ render plain: "File: #{params[:uploaded_data].read}"
end
end
@@ -63,6 +63,17 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
assert_equal 'contents', file.read
end
+ test "parses utf8 filename with percent character" do
+ params = parse_multipart('utf8_filename')
+ assert_equal %w(file foo), params.keys.sort
+ assert_equal 'bar', params['foo']
+
+ file = params['file']
+ assert_equal 'ファイル%名.txt', file.original_filename
+ assert_equal "text/plain", file.content_type
+ assert_equal 'contents', file.read
+ end
+
test "parses boundary problem file" do
params = parse_multipart('boundary_problem_file')
assert_equal %w(file foo), params.keys.sort
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index f208cfda89..ff63c10e8d 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -663,6 +663,7 @@ class RequestMethod < BaseRequestTest
assert_equal 'GET', request.request_method
assert_equal 'GET', request.env["REQUEST_METHOD"]
+ assert request.get?
end
test "invalid http method raises exception" do
@@ -988,6 +989,7 @@ class RequestParameterFilter < BaseRequestTest
[{'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|
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index 5fbd19acdf..780e7dc3e2 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -42,6 +42,13 @@ class ResponseTest < ActiveSupport::TestCase
assert_equal Encoding::UTF_8, response.body.encoding
end
+ def test_response_charset_writer
+ @response.charset = 'utf-16'
+ assert_equal 'utf-16', @response.charset
+ @response.charset = nil
+ assert_equal 'utf-8', @response.charset
+ end
+
test "simple output" do
@response.body = "Hello, World!"
@@ -72,12 +79,14 @@ class ResponseTest < ActiveSupport::TestCase
test "content type" do
[204, 304].each do |c|
+ @response = ActionDispatch::Response.new
@response.status = c.to_s
_, headers, _ = @response.to_a
assert !headers.has_key?("Content-Type"), "#{c} should not have Content-Type header"
end
[200, 302, 404, 500].each do |c|
+ @response = ActionDispatch::Response.new
@response.status = c.to_s
_, headers, _ = @response.to_a
assert headers.has_key?("Content-Type"), "#{c} did not have Content-Type header"
@@ -169,6 +178,8 @@ class ResponseTest < ActiveSupport::TestCase
end
test "read content type without charset" do
+ jruby_skip "https://github.com/jruby/jruby/issues/3138"
+
original = ActionDispatch::Response.default_charset
begin
ActionDispatch::Response.default_charset = 'utf-16'
diff --git a/actionpack/test/dispatch/routing/concerns_test.rb b/actionpack/test/dispatch/routing/concerns_test.rb
index 7ef513b0c8..361ceca677 100644
--- a/actionpack/test/dispatch/routing/concerns_test.rb
+++ b/actionpack/test/dispatch/routing/concerns_test.rb
@@ -109,6 +109,8 @@ class RoutingConcernsTest < ActionDispatch::IntegrationTest
end
def test_concerns_executes_block_in_context_of_current_mapper
+ jruby_skip "https://github.com/jruby/jruby/issues/3143"
+
mapper = ActionDispatch::Routing::Mapper.new(ActionDispatch::Routing::RouteSet.new)
mapper.concern :test_concern do
resources :things
diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb
index 3df022c64b..4047214843 100644
--- a/actionpack/test/dispatch/routing/inspector_test.rb
+++ b/actionpack/test/dispatch/routing/inspector_test.rb
@@ -313,6 +313,22 @@ module ActionDispatch
assert_equal ["Prefix Verb URI Pattern Controller#Action",
" GET /:controller(/:action) (?-mix:api\\/[^\\/]+)#:action"], output
end
+
+ def test_inspect_routes_shows_resources_route_when_assets_disabled
+ @set = ActionDispatch::Routing::RouteSet.new
+ app = ActiveSupport::OrderedOptions.new
+
+ Rails.stubs(:application).returns(app)
+
+ output = draw do
+ get '/cart', to: 'cart#show'
+ end
+
+ assert_equal [
+ "Prefix Verb URI Pattern Controller#Action",
+ " cart GET /cart(.:format) cart#show"
+ ], output
+ end
end
end
end
diff --git a/actionpack/test/dispatch/routing/route_set_test.rb b/actionpack/test/dispatch/routing/route_set_test.rb
index fe52c50336..9327fe12c6 100644
--- a/actionpack/test/dispatch/routing/route_set_test.rb
+++ b/actionpack/test/dispatch/routing/route_set_test.rb
@@ -17,6 +17,16 @@ module ActionDispatch
@set = RouteSet.new
end
+ test "not being empty when route is added" do
+ assert empty?
+
+ draw do
+ get 'foo', to: SimpleApp.new('foo#index')
+ end
+
+ assert_not empty?
+ end
+
test "url helpers are added when route is added" do
draw do
get 'foo', to: SimpleApp.new('foo#index')
@@ -136,6 +146,10 @@ module ActionDispatch
def url_helpers
@set.url_helpers
end
+
+ def empty?
+ @set.empty?
+ end
end
end
end
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 55fc160ac8..280b258da6 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -167,6 +167,44 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal '/session/reset', reset_session_path
end
+ def test_session_singleton_resource_for_api_app
+ config = ActionDispatch::Routing::RouteSet::Config.new
+ config.api_only = true
+
+ self.class.stub_controllers(config) do |routes|
+ routes.draw do
+ resource :session do
+ get :create
+ post :reset
+ end
+ end
+ @app = RoutedRackApp.new routes
+ end
+
+ get '/session'
+ assert_equal 'sessions#create', @response.body
+ assert_equal '/session', session_path
+
+ post '/session'
+ assert_equal 'sessions#create', @response.body
+
+ put '/session'
+ assert_equal 'sessions#update', @response.body
+
+ delete '/session'
+ assert_equal 'sessions#destroy', @response.body
+
+ post '/session/reset'
+ assert_equal 'sessions#reset', @response.body
+ assert_equal '/session/reset', reset_session_path
+
+ get '/session/new'
+ assert_equal 'Not Found', @response.body
+
+ get '/session/edit'
+ assert_equal 'Not Found', @response.body
+ end
+
def test_session_info_nested_singleton_resource
draw do
resource :session do
@@ -509,6 +547,40 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal '/projects/1/edit', edit_project_path(:id => '1')
end
+ def test_projects_for_api_app
+ config = ActionDispatch::Routing::RouteSet::Config.new
+ config.api_only = true
+
+ self.class.stub_controllers(config) do |routes|
+ routes.draw do
+ resources :projects, controller: :project
+ end
+ @app = RoutedRackApp.new routes
+ end
+
+ get '/projects'
+ assert_equal 'project#index', @response.body
+ assert_equal '/projects', projects_path
+
+ post '/projects'
+ assert_equal 'project#create', @response.body
+
+ get '/projects.xml'
+ assert_equal 'project#index', @response.body
+ assert_equal '/projects.xml', projects_path(format: 'xml')
+
+ get '/projects/1'
+ assert_equal 'project#show', @response.body
+ assert_equal '/projects/1', project_path(id: '1')
+
+ get '/projects/1.xml'
+ assert_equal 'project#show', @response.body
+ assert_equal '/projects/1.xml', project_path(id: '1', format: 'xml')
+
+ get '/projects/1/edit'
+ assert_equal 'Not Found', @response.body
+ end
+
def test_projects_with_post_action_and_new_path_on_collection
draw do
resources :projects, :controller => :project do
@@ -3492,12 +3564,11 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
mount lambda { |env| [200, {}, [env['REQUEST_METHOD']]] }, at: '/'
end
- # TODO: HEAD request should match `get /home` rather than the
+ # HEAD request should match `get /home` rather than the
# lower-precedence Rack app mounted at `/`.
head '/home'
assert_response :ok
- #assert_equal 'test#index', @response.body
- assert_equal 'HEAD', @response.body
+ assert_equal 'test#index', @response.body
# But the Rack app can still respond to its own HEAD requests.
head '/foobar'
@@ -3655,7 +3726,7 @@ class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest
module ::Admin
class StorageFilesController < ActionController::Base
def index
- render :text => "admin/storage_files#index"
+ render plain: "admin/storage_files#index"
end
end
end
@@ -3750,7 +3821,7 @@ class TestDefaultScope < ActionDispatch::IntegrationTest
module ::Blog
class PostsController < ActionController::Base
def index
- render :text => "blog/posts#index"
+ render plain: "blog/posts#index"
end
end
end
@@ -4090,13 +4161,13 @@ end
class TestNamedRouteUrlHelpers < ActionDispatch::IntegrationTest
class CategoriesController < ActionController::Base
def show
- render :text => "categories#show"
+ render plain: "categories#show"
end
end
class ProductsController < ActionController::Base
def show
- render :text => "products#show"
+ render plain: "products#show"
end
end
@@ -4191,7 +4262,7 @@ end
class TestInvalidUrls < ActionDispatch::IntegrationTest
class FooController < ActionController::Base
def show
- render :text => "foo#show"
+ render plain: "foo#show"
end
end
@@ -4476,12 +4547,25 @@ class TestUrlGenerationErrors < ActionDispatch::IntegrationTest
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}"
+
+ # Optimized url helper
+ 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') }
+ assert_equal message, error.message
+ end
end
class TestDefaultUrlOptions < ActionDispatch::IntegrationTest
class PostsController < ActionController::Base
def archive
- render :text => "posts#archive"
+ render plain: "posts#archive"
end
end
diff --git a/actionpack/test/dispatch/session/cache_store_test.rb b/actionpack/test/dispatch/session/cache_store_test.rb
index 22a46b0930..dbb996973d 100644
--- a/actionpack/test/dispatch/session/cache_store_test.rb
+++ b/actionpack/test/dispatch/session/cache_store_test.rb
@@ -18,11 +18,11 @@ class CacheStoreTest < ActionDispatch::IntegrationTest
end
def get_session_value
- render :text => "foo: #{session[:foo].inspect}"
+ render plain: "foo: #{session[:foo].inspect}"
end
def get_session_id
- render :text => "#{request.session.id}"
+ render plain: "#{request.session.id}"
end
def call_reset_session
@@ -170,7 +170,7 @@ class CacheStoreTest < ActionDispatch::IntegrationTest
@app = self.class.build_app(set) do |middleware|
@cache = ActiveSupport::Cache::MemoryStore.new
middleware.use ActionDispatch::Session::CacheStore, :key => '_session_id', :cache => @cache
- middleware.delete "ActionDispatch::ShowExceptions"
+ middleware.delete ActionDispatch::ShowExceptions
end
yield
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index e7f4235de8..e432c65c62 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -16,25 +16,25 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
end
def persistent_session_id
- render :text => session[:session_id]
+ render plain: session[:session_id]
end
def set_session_value
session[:foo] = "bar"
- render :text => Rack::Utils.escape(Verifier.generate(session.to_hash))
+ render plain: Rack::Utils.escape(Verifier.generate(session.to_hash))
end
def get_session_value
- render :text => "foo: #{session[:foo].inspect}"
+ render plain: "foo: #{session[:foo].inspect}"
end
def get_session_id
- render :text => "id: #{request.session.id}"
+ render plain: "id: #{request.session.id}"
end
def get_class_after_reset_session
reset_session
- render :text => "class: #{session.class}"
+ render plain: "class: #{session.class}"
end
def call_session_clear
@@ -348,7 +348,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
@app = self.class.build_app(set) do |middleware|
middleware.use ActionDispatch::Session::CookieStore, options
- middleware.delete "ActionDispatch::ShowExceptions"
+ middleware.delete ActionDispatch::ShowExceptions
end
yield
diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb
index 9a5d5131c0..3fed9bad4f 100644
--- a/actionpack/test/dispatch/session/mem_cache_store_test.rb
+++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb
@@ -19,11 +19,11 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
end
def get_session_value
- render :text => "foo: #{session[:foo].inspect}"
+ render plain: "foo: #{session[:foo].inspect}"
end
def get_session_id
- render :text => "#{request.session.id}"
+ render plain: "#{request.session.id}"
end
def call_reset_session
@@ -192,7 +192,7 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
@app = self.class.build_app(set) do |middleware|
middleware.use ActionDispatch::Session::MemCacheStore, :key => '_session_id', :namespace => "mem_cache_store_test:#{SecureRandom.hex(10)}"
- middleware.delete "ActionDispatch::ShowExceptions"
+ middleware.delete ActionDispatch::ShowExceptions
end
yield
diff --git a/actionpack/test/dispatch/session/test_session_test.rb b/actionpack/test/dispatch/session/test_session_test.rb
index d30461a623..59c030176b 100644
--- a/actionpack/test/dispatch/session/test_session_test.rb
+++ b/actionpack/test/dispatch/session/test_session_test.rb
@@ -40,4 +40,14 @@ class ActionController::TestSessionTest < ActiveSupport::TestCase
assert_equal %w(one two), session.keys
assert_equal %w(1 2), session.values
end
+
+ def test_fetch_returns_default
+ session = ActionController::TestSession.new(one: '1')
+ assert_equal('2', session.fetch(:two, '2'))
+ end
+
+ def test_fetch_returns_block_value
+ session = ActionController::TestSession.new(one: '1')
+ assert_equal(2, session.fetch('2') { |key| key.to_i })
+ end
end
diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb
index 72eaa916bc..cbb12a2209 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
when "/bad_params"
raise ActionDispatch::ParamsParser::ParseError.new("", StandardError.new)
when "/method_not_allowed"
- raise ActionController::MethodNotAllowed
+ raise ActionController::MethodNotAllowed, 'PUT'
when "/unknown_http_method"
raise ActionController::UnknownHttpMethod
when "/not_found_original_exception"
diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb
index 7ced41bc2e..017e9ba2dd 100644
--- a/actionpack/test/dispatch/ssl_test.rb
+++ b/actionpack/test/dispatch/ssl_test.rb
@@ -216,4 +216,15 @@ class SSLTest < ActionDispatch::IntegrationTest
assert_equal "https://example.co.uk/path?key=value",
response.headers['Location']
end
+
+ def test_keeps_original_headers_behavior
+ headers = Rack::Utils::HeaderHash.new(
+ "Content-Type" => "text/html",
+ "Connection" => ["close"]
+ )
+ self.app = ActionDispatch::SSL.new(lambda { |env| [200, headers, ["OK"]] })
+
+ get "https://example.org/"
+ assert_equal "close", response.headers["Connection"]
+ end
end
diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb
index 288a2084f6..13dec8b618 100644
--- a/actionpack/test/dispatch/static_test.rb
+++ b/actionpack/test/dispatch/static_test.rb
@@ -2,6 +2,20 @@ require 'abstract_unit'
require 'zlib'
module StaticTests
+ def setup
+ silence_warnings do
+ @default_internal_encoding = Encoding.default_internal
+ @default_external_encoding = Encoding.default_external
+ end
+ end
+
+ def teardown
+ silence_warnings do
+ Encoding.default_internal = @default_internal_encoding
+ Encoding.default_external = @default_external_encoding
+ end
+ end
+
def test_serves_dynamic_content
assert_equal "Hello, World!", get("/nofile").body
end
@@ -10,6 +24,18 @@ module StaticTests
assert_equal "Hello, World!", get("/doorkeeper%E3E4").body
end
+ def test_handles_urls_with_ascii_8bit
+ assert_equal "Hello, World!", get("/doorkeeper%E3E4".force_encoding('ASCII-8BIT')).body
+ end
+
+ def test_handles_urls_with_ascii_8bit_on_win_31j
+ silence_warnings do
+ Encoding.default_internal = "Windows-31J"
+ Encoding.default_external = "Windows-31J"
+ end
+ assert_equal "Hello, World!", get("/doorkeeper%E3E4".force_encoding('ASCII-8BIT')).body
+ end
+
def test_sets_cache_control
response = get("/index.html")
assert_html "/index.html", response
@@ -31,6 +57,7 @@ module StaticTests
def test_serves_static_index_file_in_directory
assert_html "/foo/index.html", get("/foo/index.html")
+ assert_html "/foo/index.html", get("/foo/index")
assert_html "/foo/index.html", get("/foo/")
assert_html "/foo/index.html", get("/foo")
end
@@ -129,7 +156,7 @@ module StaticTests
def test_does_not_modify_path_info
file_name = "/gzip/application-a71b3024f80aea3181c09774ca17e712.js"
- env = {'PATH_INFO' => file_name, 'HTTP_ACCEPT_ENCODING' => 'gzip'}
+ env = {'PATH_INFO' => file_name, 'HTTP_ACCEPT_ENCODING' => 'gzip', "REQUEST_METHOD" => 'POST'}
@app.call(env)
assert_equal file_name, env['PATH_INFO']
end
@@ -208,6 +235,7 @@ class StaticTest < ActiveSupport::TestCase
}
def setup
+ super
@root = "#{FIXTURE_LOAD_PATH}/public"
@app = ActionDispatch::Static.new(DummyApp, @root, "public, max-age=60")
end
@@ -233,10 +261,24 @@ class StaticTest < ActiveSupport::TestCase
}
assert_equal(DummyApp.call(nil), @app.call(env))
end
+
+ def test_non_default_static_index
+ @app = ActionDispatch::Static.new(DummyApp, @root, "public, max-age=60", index: "other-index")
+ assert_html "/other-index.html", get("/other-index.html")
+ assert_html "/other-index.html", get("/other-index")
+ assert_html "/other-index.html", get("/")
+ assert_html "/other-index.html", get("")
+ assert_html "/foo/other-index.html", get("/foo/other-index.html")
+ assert_html "/foo/other-index.html", get("/foo/other-index")
+ assert_html "/foo/other-index.html", get("/foo/")
+ assert_html "/foo/other-index.html", get("/foo")
+ end
+
end
class StaticEncodingTest < StaticTest
def setup
+ super
@root = "#{FIXTURE_LOAD_PATH}/公共"
@app = ActionDispatch::Static.new(DummyApp, @root, "public, max-age=60")
end
diff --git a/actionpack/test/dispatch/template_assertions_test.rb b/actionpack/test/dispatch/template_assertions_test.rb
deleted file mode 100644
index 7278754b49..0000000000
--- a/actionpack/test/dispatch/template_assertions_test.rb
+++ /dev/null
@@ -1,110 +0,0 @@
-require 'abstract_unit'
-
-class AssertTemplateController < ActionController::Base
- def render_with_partial
- render partial: 'test/partial'
- end
-
- def render_with_template
- render 'test/hello_world'
- end
-
- def render_with_layout
- @variable_for_layout = 'hello'
- render 'test/hello_world', layout: "layouts/standard"
- end
-
- def render_with_file
- render file: 'README.rdoc'
- end
-
- def render_nothing
- head :ok
- end
-end
-
-class AssertTemplateControllerTest < ActionDispatch::IntegrationTest
- def test_template_reset_between_requests
- get '/assert_template/render_with_template'
- assert_template 'test/hello_world'
-
- get '/assert_template/render_nothing'
- assert_template nil
- end
-
- def test_partial_reset_between_requests
- get '/assert_template/render_with_partial'
- assert_template partial: 'test/_partial'
-
- get '/assert_template/render_nothing'
- assert_template partial: nil
- end
-
- def test_layout_reset_between_requests
- get '/assert_template/render_with_layout'
- assert_template layout: 'layouts/standard'
-
- get '/assert_template/render_nothing'
- assert_template layout: nil
- end
-
- def test_file_reset_between_requests
- get '/assert_template/render_with_file'
- assert_template file: 'README.rdoc'
-
- get '/assert_template/render_nothing'
- assert_template file: nil
- end
-
- def test_template_reset_between_requests_when_opening_a_session
- open_session do |session|
- session.get '/assert_template/render_with_template'
- session.assert_template 'test/hello_world'
-
- session.get '/assert_template/render_nothing'
- session.assert_template nil
- end
- end
-
- def test_partial_reset_between_requests_when_opening_a_session
- open_session do |session|
- session.get '/assert_template/render_with_partial'
- session.assert_template partial: 'test/_partial'
-
- session.get '/assert_template/render_nothing'
- session.assert_template partial: nil
- end
- end
-
- def test_layout_reset_between_requests_when_opening_a_session
- open_session do |session|
- session.get '/assert_template/render_with_layout'
- session.assert_template layout: 'layouts/standard'
-
- session.get '/assert_template/render_nothing'
- session.assert_template layout: nil
- end
- end
-
- def test_file_reset_between_requests_when_opening_a_session
- open_session do |session|
- session.get '/assert_template/render_with_file'
- session.assert_template file: 'README.rdoc'
-
- session.get '/assert_template/render_nothing'
- session.assert_template file: nil
- end
- end
-
- def test_assigns_do_not_reset_template_assertion
- get '/assert_template/render_with_layout'
- assert_equal 'hello', assigns(:variable_for_layout)
- assert_template layout: 'layouts/standard'
- end
-
- def test_cookies_do_not_reset_template_assertion
- get '/assert_template/render_with_layout'
- cookies
- assert_template layout: 'layouts/standard'
- end
-end
diff --git a/actionpack/test/dispatch/test_request_test.rb b/actionpack/test/dispatch/test_request_test.rb
index cc35d4594e..ede1cec4e6 100644
--- a/actionpack/test/dispatch/test_request_test.rb
+++ b/actionpack/test/dispatch/test_request_test.rb
@@ -2,7 +2,7 @@ require 'abstract_unit'
class TestRequestTest < ActiveSupport::TestCase
test "sane defaults" do
- env = ActionDispatch::TestRequest.new.env
+ env = ActionDispatch::TestRequest.create.env
assert_equal "GET", env.delete("REQUEST_METHOD")
assert_equal "off", env.delete("HTTPS")
@@ -24,12 +24,10 @@ class TestRequestTest < ActiveSupport::TestCase
assert_equal true, env.delete("rack.multithread")
assert_equal true, env.delete("rack.multiprocess")
assert_equal false, env.delete("rack.run_once")
-
- assert env.empty?, env.inspect
end
test "cookie jar" do
- req = ActionDispatch::TestRequest.new
+ req = ActionDispatch::TestRequest.create({})
assert_equal({}, req.cookies)
assert_equal nil, req.env["HTTP_COOKIE"]
@@ -57,38 +55,38 @@ class TestRequestTest < ActiveSupport::TestCase
test "does not complain when Rails.application is nil" do
Rails.stubs(:application).returns(nil)
- req = ActionDispatch::TestRequest.new
+ req = ActionDispatch::TestRequest.create({})
assert_equal false, req.env.empty?
end
test "default remote address is 0.0.0.0" do
- req = ActionDispatch::TestRequest.new
+ req = ActionDispatch::TestRequest.create({})
assert_equal '0.0.0.0', req.remote_addr
end
test "allows remote address to be overridden" do
- req = ActionDispatch::TestRequest.new('REMOTE_ADDR' => '127.0.0.1')
+ req = ActionDispatch::TestRequest.create('REMOTE_ADDR' => '127.0.0.1')
assert_equal '127.0.0.1', req.remote_addr
end
test "default host is test.host" do
- req = ActionDispatch::TestRequest.new
+ req = ActionDispatch::TestRequest.create({})
assert_equal 'test.host', req.host
end
test "allows host to be overridden" do
- req = ActionDispatch::TestRequest.new('HTTP_HOST' => 'www.example.com')
+ req = ActionDispatch::TestRequest.create('HTTP_HOST' => 'www.example.com')
assert_equal 'www.example.com', req.host
end
test "default user agent is 'Rails Testing'" do
- req = ActionDispatch::TestRequest.new
+ req = ActionDispatch::TestRequest.create({})
assert_equal 'Rails Testing', req.user_agent
end
test "allows user agent to be overridden" do
- req = ActionDispatch::TestRequest.new('HTTP_USER_AGENT' => 'GoogleBot')
+ req = ActionDispatch::TestRequest.create('HTTP_USER_AGENT' => 'GoogleBot')
assert_equal 'GoogleBot', req.user_agent
end
diff --git a/actionpack/test/dispatch/test_response_test.rb b/actionpack/test/dispatch/test_response_test.rb
index dc17668def..a4f9d56a6a 100644
--- a/actionpack/test/dispatch/test_response_test.rb
+++ b/actionpack/test/dispatch/test_response_test.rb
@@ -11,10 +11,9 @@ class TestResponseTest < ActiveSupport::TestCase
end
test "helpers" do
- assert_response_code_range 200..299, :success?
- assert_response_code_range [404], :missing?
- assert_response_code_range 300..399, :redirect?
- assert_response_code_range 500..599, :error?
+ assert_response_code_range 200..299, :successful?
+ assert_response_code_range [404], :not_found?
+ assert_response_code_range 300..399, :redirection?
assert_response_code_range 500..599, :server_error?
assert_response_code_range 400..499, :client_error?
end
diff --git a/actionpack/test/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb
index ce1e1d0a6a..fd4ede4d1b 100644
--- a/actionpack/test/dispatch/url_generation_test.rb
+++ b/actionpack/test/dispatch/url_generation_test.rb
@@ -8,7 +8,7 @@ module TestUrlGeneration
class ::MyRouteGeneratingController < ActionController::Base
include Routes.url_helpers
def index
- render :text => foo_path
+ render plain: foo_path
end
end
diff --git a/actionpack/test/fixtures/customers/_commented_customer.html.erb b/actionpack/test/fixtures/customers/_commented_customer.html.erb
index d5f6e3b491..8cc9c1ec13 100644
--- a/actionpack/test/fixtures/customers/_commented_customer.html.erb
+++ b/actionpack/test/fixtures/customers/_commented_customer.html.erb
@@ -1,4 +1,5 @@
<%# I'm a comment %>
<% cache customer do %>
+ <% controller.partial_rendered_times += 1 %>
<%= customer.name %>, <%= customer.id %>
<% end %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/customers/_customer.html.erb b/actionpack/test/fixtures/customers/_customer.html.erb
index 67e9f6d411..5105090d4b 100644
--- a/actionpack/test/fixtures/customers/_customer.html.erb
+++ b/actionpack/test/fixtures/customers/_customer.html.erb
@@ -1,3 +1,4 @@
<% cache customer do %>
+ <% controller.partial_rendered_times += 1 %>
<%= customer.name %>, <%= customer.id %>
<% end %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/layouts/standard.html.erb b/actionpack/test/fixtures/layouts/standard.html.erb
index 5e6c24fe39..48882dca35 100644
--- a/actionpack/test/fixtures/layouts/standard.html.erb
+++ b/actionpack/test/fixtures/layouts/standard.html.erb
@@ -1 +1 @@
-<html><%= yield %><%= @variable_for_layout %></html> \ No newline at end of file
+<html><%= yield %><%= @variable_for_layout %></html>
diff --git a/actionpack/test/fixtures/multipart/utf8_filename b/actionpack/test/fixtures/multipart/utf8_filename
new file mode 100644
index 0000000000..60738d53b0
--- /dev/null
+++ b/actionpack/test/fixtures/multipart/utf8_filename
@@ -0,0 +1,10 @@
+--AaB03x
+Content-Disposition: form-data; name="foo"
+
+bar
+--AaB03x
+Content-Disposition: form-data; name="file"; filename="ファイル%名.txt"
+Content-Type: text/plain
+
+contents
+--AaB03x--
diff --git a/actionpack/test/fixtures/public/foo/other-index.html b/actionpack/test/fixtures/public/foo/other-index.html
new file mode 100644
index 0000000000..51c90c26ea
--- /dev/null
+++ b/actionpack/test/fixtures/public/foo/other-index.html
@@ -0,0 +1 @@
+/foo/other-index.html \ No newline at end of file
diff --git a/actionpack/test/fixtures/public/other-index.html b/actionpack/test/fixtures/public/other-index.html
new file mode 100644
index 0000000000..0820dfcb6e
--- /dev/null
+++ b/actionpack/test/fixtures/public/other-index.html
@@ -0,0 +1 @@
+/other-index.html \ No newline at end of file
diff --git a/actionpack/test/fixtures/公共/foo/other-index.html b/actionpack/test/fixtures/公共/foo/other-index.html
new file mode 100644
index 0000000000..51c90c26ea
--- /dev/null
+++ b/actionpack/test/fixtures/公共/foo/other-index.html
@@ -0,0 +1 @@
+/foo/other-index.html \ No newline at end of file
diff --git a/actionpack/test/fixtures/公共/other-index.html b/actionpack/test/fixtures/公共/other-index.html
new file mode 100644
index 0000000000..0820dfcb6e
--- /dev/null
+++ b/actionpack/test/fixtures/公共/other-index.html
@@ -0,0 +1 @@
+/other-index.html \ No newline at end of file
diff --git a/actionpack/test/journey/route_test.rb b/actionpack/test/journey/route_test.rb
index 21d867aca0..eff96a0abc 100644
--- a/actionpack/test/journey/route_test.rb
+++ b/actionpack/test/journey/route_test.rb
@@ -7,7 +7,7 @@ module ActionDispatch
app = Object.new
path = Path::Pattern.from_string '/:controller(/:action(/:id(.:format)))'
defaults = {}
- route = Route.new("name", app, path, {}, defaults)
+ route = Route.new("name", app, path, {}, [], defaults)
assert_equal app, route.app
assert_equal path, route.path
@@ -18,30 +18,38 @@ module ActionDispatch
app = Object.new
path = Path::Pattern.from_string '/:controller(/:action(/:id(.:format)))'
defaults = {}
- route = Route.new("name", app, path, {}, defaults)
+ route = Route.new("name", app, path, {}, [], defaults)
route.ast.grep(Nodes::Terminal).each do |node|
assert_equal route, node.memo
end
end
+ def test_path_requirements_override_defaults
+ strexp = Router::Strexp.build(':name', { name: /love/ }, ['/'])
+ path = Path::Pattern.new strexp
+ defaults = { name: 'tender' }
+ route = Route.new('name', nil, path, nil, [], defaults)
+ assert_equal(/love/, route.requirements[:name])
+ end
+
def test_ip_address
path = Path::Pattern.from_string '/messages/:id(.:format)'
- route = Route.new("name", nil, path, {:ip => '192.168.1.1'},
+ route = Route.new("name", nil, path, {:ip => '192.168.1.1'}, [],
{ :controller => 'foo', :action => 'bar' })
assert_equal '192.168.1.1', route.ip
end
def test_default_ip
path = Path::Pattern.from_string '/messages/:id(.:format)'
- route = Route.new("name", nil, path, {},
+ route = Route.new("name", nil, path, {}, [],
{ :controller => 'foo', :action => 'bar' })
assert_equal(//, route.ip)
end
def test_format_with_star
path = Path::Pattern.from_string '/:controller/*extra'
- route = Route.new("name", nil, path, {},
+ route = Route.new("name", nil, path, {}, [],
{ :controller => 'foo', :action => 'bar' })
assert_equal '/foo/himom', route.format({
:controller => 'foo',
@@ -51,7 +59,7 @@ module ActionDispatch
def test_connects_all_match
path = Path::Pattern.from_string '/:controller(/:action(/:id(.:format)))'
- route = Route.new("name", nil, path, {:action => 'bar'}, { :controller => 'foo' })
+ route = Route.new("name", nil, path, {:action => 'bar'}, [], { :controller => 'foo' })
assert_equal '/foo/bar/10', route.format({
:controller => 'foo',
@@ -62,34 +70,34 @@ module ActionDispatch
def test_extras_are_not_included_if_optional
path = Path::Pattern.from_string '/page/:id(/:action)'
- route = Route.new("name", nil, path, { }, { :action => 'show' })
+ route = Route.new("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.new("name", nil, path, { }, { :action => 'show' })
+ route = Route.new("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.new("name", nil, path, { }, { :action => 'show' })
+ route = Route.new("name", nil, path, { }, [], { :action => 'show' })
assert_equal '/pages/10', route.format({:id => 10, :section => nil})
end
def test_score
- constraints = {:required_defaults => [:controller, :action]}
+ constraints = {}
defaults = {:controller=>"pages", :action=>"show"}
path = Path::Pattern.from_string "/page/:id(/:action)(.:format)"
- specific = Route.new "name", nil, path, constraints, defaults
+ specific = Route.new "name", nil, path, constraints, [:controller, :action], defaults
path = Path::Pattern.from_string "/:controller(/:action(/:id))(.:format)"
- generic = Route.new "name", nil, path, constraints
+ generic = Route.new "name", nil, path, constraints, [], {}
knowledge = {:id=>20, :controller=>"pages", :action=>"show"}
diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb
index 19c61b5914..802fb93c69 100644
--- a/actionpack/test/journey/router_test.rb
+++ b/actionpack/test/journey/router_test.rb
@@ -35,7 +35,7 @@ module ActionDispatch
exp = Router::Strexp.build '/foo-bar-baz', {}, ['/.?']
path = Path::Pattern.new exp
- routes.add_route nil, path, {}, {:id => nil}, {}
+ routes.add_route nil, path, {}, [], {:id => nil}, {}
env = rails_env 'PATH_INFO' => '/foo-bar-baz'
called = false
@@ -52,7 +52,7 @@ module ActionDispatch
exp = Router::Strexp.build '/%E3%81%BB%E3%81%92', {}, ['/.?']
path = Path::Pattern.new exp
- routes.add_route nil, path, {}, {:id => nil}, {}
+ routes.add_route nil, path, {}, [], {:id => nil}, {}
env = rails_env 'PATH_INFO' => '/%E3%81%BB%E3%81%92'
called = false
@@ -71,7 +71,7 @@ module ActionDispatch
exp = Router::Strexp.build '/foo(/:id)', {}, ['/.?']
path = Path::Pattern.new exp
- routes.add_route nil, path, requirements, {:id => nil}, {}
+ routes.add_route nil, path, requirements, [], {:id => nil}, {}
env = rails_env({'PATH_INFO' => '/foo/10'}, klass)
router.recognize(env) do |r, params|
@@ -91,7 +91,7 @@ module ActionDispatch
exp = Router::Strexp.build '/foo(/:id)', {}, ['/.?']
path = Path::Pattern.new exp
- router.routes.add_route nil, path, requirements, {:id => nil}, {}
+ router.routes.add_route nil, path, requirements, [], {:id => nil}, {}
env = rails_env({'PATH_INFO' => '/foo/10'}, klass)
router.recognize(env) do |r, params|
@@ -118,7 +118,7 @@ module ActionDispatch
exp = Router::Strexp.build '/bar', {}, ['/.?']
path = Path::Pattern.new exp
- routes.add_route nil, path, {}, {}, {}
+ routes.add_route nil, path, {}, [], {}, {}
env = rails_env({'PATH_INFO' => '/foo',
'custom.path_info' => '/bar'}, CustomPathRequest)
@@ -192,7 +192,7 @@ module ActionDispatch
route_name = "gorby_thunderhorse"
pattern = Router::Strexp.build("/foo/:id", { :id => /\d+/ }, ['/', '.', '?'], false)
path = Path::Pattern.new pattern
- @router.routes.add_route nil, path, {}, {}, route_name
+ @router.routes.add_route nil, path, {}, [], {}, route_name
error = assert_raises(ActionController::UrlGenerationError) do
@formatter.generate(route_name, { }, { })
@@ -234,7 +234,7 @@ module ActionDispatch
def test_defaults_merge_correctly
path = Path::Pattern.from_string '/foo(/:id)'
- @router.routes.add_route nil, path, {}, {:id => nil}, {}
+ @router.routes.add_route nil, path, {}, [], {:id => nil}, {}
env = rails_env 'PATH_INFO' => '/foo/10'
@router.recognize(env) do |r, params|
@@ -317,7 +317,7 @@ module ActionDispatch
def test_nil_path_parts_are_ignored
path = Path::Pattern.from_string "/:controller(/:action(.:format))"
- @router.routes.add_route @app, path, {}, {}, {}
+ @router.routes.add_route @app, path, {}, [], {}, {}
params = { :controller => "tasks", :format => nil }
extras = { :action => 'lol' }
@@ -332,7 +332,7 @@ module ActionDispatch
str = Router::Strexp.build("/", Hash[params], ['/', '.', '?'], true)
path = Path::Pattern.new str
- @router.routes.add_route @app, path, {}, {}, {}
+ @router.routes.add_route @app, path, {}, [], {}, {}
path, _ = @formatter.generate(nil, Hash[params], {})
assert_equal '/', path
@@ -340,7 +340,7 @@ module ActionDispatch
def test_generate_calls_param_proc
path = Path::Pattern.from_string '/:controller(/:action)'
- @router.routes.add_route @app, path, {}, {}, {}
+ @router.routes.add_route @app, path, {}, [], {}, {}
parameterized = []
params = [ [:controller, "tasks"],
@@ -357,7 +357,7 @@ module ActionDispatch
def test_generate_id
path = Path::Pattern.from_string '/:controller(/:action)'
- @router.routes.add_route @app, path, {}, {}, {}
+ @router.routes.add_route @app, path, {}, [], {}, {}
path, params = @formatter.generate(
nil, {:id=>1, :controller=>"tasks", :action=>"show"}, {})
@@ -367,7 +367,7 @@ module ActionDispatch
def test_generate_escapes
path = Path::Pattern.from_string '/:controller(/:action)'
- @router.routes.add_route @app, path, {}, {}, {}
+ @router.routes.add_route @app, path, {}, [], {}, {}
path, _ = @formatter.generate(nil,
{ :controller => "tasks",
@@ -378,7 +378,7 @@ module ActionDispatch
def test_generate_escapes_with_namespaced_controller
path = Path::Pattern.from_string '/:controller(/:action)'
- @router.routes.add_route @app, path, {}, {}, {}
+ @router.routes.add_route @app, path, {}, [], {}, {}
path, _ = @formatter.generate(
nil, { :controller => "admin/tasks",
@@ -389,7 +389,7 @@ module ActionDispatch
def test_generate_extra_params
path = Path::Pattern.from_string '/:controller(/:action)'
- @router.routes.add_route @app, path, {}, {}, {}
+ @router.routes.add_route @app, path, {}, [], {}, {}
path, params = @formatter.generate(
nil, { :id => 1,
@@ -401,9 +401,36 @@ module ActionDispatch
assert_equal({:id => 1, :relative_url_root => nil}, params)
end
+ def test_generate_missing_keys_no_matches_different_format_keys
+ path = Path::Pattern.from_string '/:controller/:action/:name'
+ @router.routes.add_route @app, path, {}, [], {}, {}
+ primarty_parameters = {
+ :id => 1,
+ :controller => "tasks",
+ :action => "show",
+ :relative_url_root => nil
+ }
+ redirection_parameters = {
+ 'action'=>'show',
+ }
+ missing_key = 'name'
+ 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}"
+
+ error = assert_raises(ActionController::UrlGenerationError) do
+ @formatter.generate(
+ nil, request_parameters, request_parameters)
+ end
+ assert_equal message, error.message
+ end
+
def test_generate_uses_recall_if_needed
path = Path::Pattern.from_string '/:controller(/:action(/:id))'
- @router.routes.add_route @app, path, {}, {}, {}
+ @router.routes.add_route @app, path, {}, [], {}, {}
path, params = @formatter.generate(
nil,
@@ -415,7 +442,7 @@ module ActionDispatch
def test_generate_with_name
path = Path::Pattern.from_string '/:controller(/:action)'
- @router.routes.add_route @app, path, {}, {}, "tasks"
+ @router.routes.add_route @app, path, {}, [], {}, "tasks"
path, params = @formatter.generate(
"tasks",
@@ -433,7 +460,7 @@ module ActionDispatch
define_method("test_recognize_#{expected.keys.map(&:to_s).join('_')}") do
path = Path::Pattern.from_string "/:controller(/:action(/:id))"
app = Object.new
- route = @router.routes.add_route(app, path, {}, {}, {})
+ route = @router.routes.add_route(app, path, {}, [], {}, {})
env = rails_env 'PATH_INFO' => request_path
called = false
@@ -455,7 +482,7 @@ module ActionDispatch
define_method("test_recognize_#{name}") do
path = Path::Pattern.from_string '/:segment/*splat'
app = Object.new
- route = @router.routes.add_route(app, path, {}, {}, {})
+ route = @router.routes.add_route(app, path, {}, [], {}, {})
env = rails_env 'PATH_INFO' => request_path
called = false
@@ -478,7 +505,7 @@ module ActionDispatch
)
path = Path::Pattern.new strexp
app = Object.new
- route = @router.routes.add_route(app, path, {}, {}, {})
+ route = @router.routes.add_route(app, path, {}, [], {}, {})
env = rails_env 'PATH_INFO' => '/admin/users/show/10'
called = false
@@ -499,7 +526,7 @@ module ActionDispatch
def test_recognize_literal
path = Path::Pattern.from_string "/books(/:action(.:format))"
app = Object.new
- route = @router.routes.add_route(app, path, {}, {:controller => 'books'})
+ route = @router.routes.add_route(app, path, {}, [], {:controller => 'books'})
env = rails_env 'PATH_INFO' => '/books/list.rss'
expected = { :controller => 'books', :action => 'list', :format => 'rss' }
@@ -517,7 +544,7 @@ module ActionDispatch
path = Path::Pattern.from_string "/books(/:action(.:format))"
app = Object.new
conditions = { request_method: 'HEAD' }
- @router.routes.add_route(app, path, conditions, {})
+ @router.routes.add_route(app, path, conditions, [], {})
env = rails_env(
'PATH_INFO' => '/books/list.rss',
@@ -538,7 +565,7 @@ module ActionDispatch
conditions = {
:request_method => 'GET'
}
- @router.routes.add_route(app, path, conditions, {})
+ @router.routes.add_route(app, path, conditions, [], {})
env = rails_env 'PATH_INFO' => '/books/list.rss',
"REQUEST_METHOD" => "HEAD"
@@ -555,7 +582,7 @@ module ActionDispatch
path = Path::Pattern.from_string "/books(/:action(.:format))"
app = Object.new
conditions = { request_method: 'GET' }
- @router.routes.add_route(app, path, conditions, {})
+ @router.routes.add_route(app, path, conditions, [], {})
env = rails_env 'PATH_INFO' => '/books/list.rss',
"REQUEST_METHOD" => "POST"
@@ -570,7 +597,7 @@ module ActionDispatch
conditions = conditions.dup
conditions[:request_method] = 'POST'
- post = @router.routes.add_route(app, path, conditions, {})
+ post = @router.routes.add_route(app, path, conditions, [], {})
called = false
@router.recognize(env) do |r, params|
@@ -590,7 +617,7 @@ module ActionDispatch
else
path = Path::Pattern.new path
end
- router.routes.add_route @app, path, {}, {}, {}
+ router.routes.add_route @app, path, {}, [], {}, {}
end
end
diff --git a/actionpack/test/journey/routes_test.rb b/actionpack/test/journey/routes_test.rb
index b54d961f66..b9dac8751c 100644
--- a/actionpack/test/journey/routes_test.rb
+++ b/actionpack/test/journey/routes_test.rb
@@ -13,10 +13,12 @@ module ActionDispatch
path = Path::Pattern.new exp
requirements = { :hello => /world/ }
- routes.add_route nil, path, requirements, {:id => nil}, {}
+ routes.add_route nil, path, requirements, [], {:id => nil}, {}
+ assert_not routes.empty?
assert_equal 1, routes.length
routes.clear
+ assert routes.empty?
assert_equal 0, routes.length
end
@@ -24,9 +26,9 @@ module ActionDispatch
routes = Routes.new
path = Path::Pattern.from_string '/hello'
- routes.add_route nil, path, {}, {}, {}
+ routes.add_route nil, path, {}, [], {}, {}
ast = routes.ast
- routes.add_route nil, path, {}, {}, {}
+ routes.add_route nil, path, {}, [], {}, {}
assert_not_equal ast, routes.ast
end
@@ -34,16 +36,16 @@ module ActionDispatch
routes = Routes.new
path = Path::Pattern.from_string '/hello'
- routes.add_route nil, path, {}, {}, {}
+ routes.add_route nil, path, {}, [], {}, {}
sim = routes.simulator
- routes.add_route nil, path, {}, {}, {}
+ routes.add_route nil, path, {}, [], {}, {}
assert_not_equal sim, routes.simulator
end
def test_partition_route
path = Path::Pattern.from_string '/hello'
- anchored_route = @routes.add_route nil, path, {}, {}, {}
+ anchored_route = @routes.add_route nil, path, {}, [], {}, {}
assert_equal [anchored_route], @routes.anchored_routes
assert_equal [], @routes.custom_routes
@@ -52,7 +54,7 @@ module ActionDispatch
)
path = Path::Pattern.new strexp
- custom_route = @routes.add_route nil, path, {}, {}, {}
+ custom_route = @routes.add_route nil, path, {}, [], {}, {}
assert_equal [custom_route], @routes.custom_routes
assert_equal [anchored_route], @routes.anchored_routes
end
@@ -63,8 +65,8 @@ module ActionDispatch
one = Path::Pattern.from_string '/hello'
two = Path::Pattern.from_string '/aaron'
- routes.add_route nil, one, {}, {}, 'aaron'
- routes.add_route nil, two, {}, {}, 'aaron'
+ routes.add_route nil, one, {}, [], {}, 'aaron'
+ routes.add_route nil, two, {}, [], {}, 'aaron'
assert_equal '/hello', routes.named_routes['aaron'].path.spec.to_s
end
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index 80aacf7234..edc78118fb 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,75 @@
+* Add a break_sequence option to word_wrap so you can specify a custom break.
+
+ * Mauricio Gomez *
+
+* Add wildcard matching to explicit dependencies.
+
+ Turns:
+
+ ```erb
+ <% # Template Dependency: recordings/threads/events/subscribers_changed %>
+ <% # Template Dependency: recordings/threads/events/completed %>
+ <% # Template Dependency: recordings/threads/events/uncompleted %>
+ ```
+
+ Into:
+
+ ```erb
+ <% # Template Dependency: recordings/threads/events/* %>
+ ```
+
+ *Kasper Timm Hansen*
+
+* Allow defining explicit collection caching using a `# Template Collection: ...`
+ directive inside templates.
+
+ *Dov Murik*
+
+* Asset helpers raise `ArgumentError` when `nil` is passed as a source.
+
+ *Anton Kolomiychuk*
+
+* Always attach the template digest to the cache key for collection caching
+ even when `virtual_path` is not available from the view context.
+ Which could happen if the rendering was done directly in the controller
+ and not in a template.
+
+ Fixes #20535
+
+ *Roque Pinel*
+
+* Improve detection of partial templates eligible for collection caching,
+ now allowing multi-line comments at the beginning of the template file.
+
+ *Dov Murik*
+
+* Raise an ArgumentError when a false value for `include_blank` is passed to a
+ required select field (to comply with the HTML5 spec).
+
+ *Grey Baker*
+
+* Do not put partial name to `local_assigns` when rendering without
+ an object or a collection.
+
+ *Henrik Nygren*
+
+* Remove `:rescue_format` option for `translate` helper since it's no longer
+ supported by I18n.
+
+ *Bernard Potocki*
+
+* `translate` should handle `raise` flag correctly in case of both main and default
+ translation is missing.
+
+ Fixes #19967
+
+ *Bernard Potocki*
+
+* Load the `default_form_builder` from the controller on initialization, which overrides
+ the global config if it is present.
+
+ *Kevin McPhillips*
+
* Accept lambda as `child_index` option in `fields_for` method.
*Karol Galanciak*
@@ -9,8 +81,7 @@
*Adam Prescott*
* `translate` should accept nils as members of the `:default`
- parameter without raising a translation missing error. Fixes a
- regression introduced 362557e.
+ parameter without raising a translation missing error.
Fixes #19419
@@ -104,7 +175,7 @@
*Angelo Capilleri*
-* Allow entries without a link tag in AtomFeedHelper.
+* Allow entries without a link tag in `AtomFeedHelper`.
*Daniel Gomez de Souza*
diff --git a/actionview/README.rdoc b/actionview/README.rdoc
index 5bb62c7562..8b1f85f748 100644
--- a/actionview/README.rdoc
+++ b/actionview/README.rdoc
@@ -9,7 +9,7 @@ used to inline short Ruby snippets inside HTML), and XML Builder.
The latest version of Action View can be installed with RubyGems:
- % [sudo] gem install actionview
+ % gem install actionview
Source code can be downloaded as part of the Rails project on GitHub
diff --git a/actionview/Rakefile b/actionview/Rakefile
index 2b752b83df..93be50721d 100644
--- a/actionview/Rakefile
+++ b/actionview/Rakefile
@@ -1,5 +1,4 @@
require 'rake/testtask'
-require 'rubygems/package_task'
desc "Default Task"
task :default => :test
@@ -45,39 +44,8 @@ namespace :test do
end
end
-spec = eval(File.read('actionview.gemspec'))
-
-Gem::PackageTask.new(spec) do |p|
- p.gem_spec = spec
-end
-
-desc "Release to rubygems"
-task :release => :package do
- require 'rake/gemcutter'
- Rake::Gemcutter::Tasks.new(spec).define
- Rake::Task['gem:push'].invoke
-end
-
task :lines do
- lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
-
- FileList["lib/**/*.rb"].each do |file_name|
- next if file_name =~ /vendor/
- File.open(file_name, 'r') do |f|
- while line = f.gets
- lines += 1
- next if line =~ /^\s*$/
- next if line =~ /^\s*#/
- codelines += 1
- end
- end
- puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
-
- total_lines += lines
- total_codelines += codelines
-
- lines, codelines = 0, 0
- end
-
- puts "Total: Lines #{total_lines}, LOC #{total_codelines}"
+ load File.expand_path('..', File.dirname(__FILE__)) + '/tools/line_statistics'
+ files = FileList["lib/**/*.rb"]
+ CodeTools::LineStatistics.new(files).print_loc
end
diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec
index d8ea9d562c..612e94021d 100644
--- a/actionview/actionview.gemspec
+++ b/actionview/actionview.gemspec
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
s.summary = 'Rendering framework putting the V in MVC (part of Rails).'
s.description = 'Simple, battle-tested conventions and helpers for building web pages.'
- s.required_ruby_version = '>= 2.2.1'
+ s.required_ruby_version = '>= 2.2.2'
s.license = 'MIT'
diff --git a/actionview/bin/test b/actionview/bin/test
new file mode 100755
index 0000000000..404cabba51
--- /dev/null
+++ b/actionview/bin/test
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+COMPONENT_ROOT = File.expand_path("../../", __FILE__)
+require File.expand_path("../tools/test", COMPONENT_ROOT)
+exit Minitest.run(ARGV)
diff --git a/actionview/lib/action_view/dependency_tracker.rb b/actionview/lib/action_view/dependency_tracker.rb
index 7a7e116dbb..6e8c7f8203 100644
--- a/actionview/lib/action_view/dependency_tracker.rb
+++ b/actionview/lib/action_view/dependency_tracker.rb
@@ -1,16 +1,18 @@
require 'thread_safe'
+require 'action_view/path_set'
module ActionView
class DependencyTracker # :nodoc:
@trackers = ThreadSafe::Cache.new
- def self.find_dependencies(name, template)
+ def self.find_dependencies(name, template, view_paths = nil)
tracker = @trackers[template.handler]
+ return [] unless tracker.present?
- if tracker.present?
- tracker.call(name, template)
+ if tracker.respond_to?(:supports_view_paths?) && tracker.supports_view_paths?
+ tracker.call(name, template, view_paths)
else
- []
+ tracker.call(name, template)
end
end
@@ -82,12 +84,16 @@ module ActionView
(?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
/xm
- def self.call(name, template)
- new(name, template).dependencies
+ def self.supports_view_paths? # :nodoc:
+ true
+ end
+
+ def self.call(name, template, view_paths = nil)
+ new(name, template, view_paths).dependencies
end
- def initialize(name, template)
- @name, @template = name, template
+ def initialize(name, template, view_paths = nil)
+ @name, @template, @view_paths = name, template, view_paths
end
def dependencies
@@ -142,8 +148,22 @@ module ActionView
end
end
+ def resolve_directories(wildcard_dependencies)
+ return [] unless @view_paths
+
+ wildcard_dependencies.each_with_object([]) do |query, templates|
+ @view_paths.find_all_with_query(query).each do |template|
+ templates << "#{File.dirname(query)}/#{File.basename(template).split('.').first}"
+ end
+ end
+ end
+
def explicit_dependencies
- source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
+ dependencies = source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
+
+ wildcards, explicits = dependencies.partition { |dependency| dependency[-1] == '*' }
+
+ (explicits + resolve_directories(wildcards)).uniq
end
end
diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb
index 1f103786cb..9a8a4feb2e 100644
--- a/actionview/lib/action_view/digestor.rb
+++ b/actionview/lib/action_view/digestor.rb
@@ -8,11 +8,18 @@ module ActionView
@@cache = ThreadSafe::Cache.new
@@digest_monitor = Monitor.new
+ class PerRequestDigestCacheExpiry < Struct.new(:app) # :nodoc:
+ def call(env)
+ ActionView::Digestor.cache.clear
+ app.call(env)
+ end
+ end
+
class << self
# Supported options:
#
# * <tt>name</tt> - Template name
- # * <tt>finder</tt> - An instance of ActionView::LookupContext
+ # * <tt>finder</tt> - An instance of <tt>ActionView::LookupContext</tt>
# * <tt>dependencies</tt> - An array of dependent views
# * <tt>partial</tt> - Specifies whether the template is a partial
def digest(options)
@@ -41,10 +48,7 @@ module ActionView
Digestor
end
- digest = klass.new(options).digest
- # Store the actual digest if config.cache_template_loading is true
- @@cache[cache_key] = stored_digest = digest if ActionView::Resolver.caching?
- digest
+ @@cache[cache_key] = stored_digest = klass.new(options).digest
ensure
# something went wrong or ActionView::Resolver.caching? is false, make sure not to corrupt the @@cache
@@cache.delete_pair(cache_key, false) if pre_stored && !stored_digest
@@ -68,9 +72,10 @@ module ActionView
end
def dependencies
- DependencyTracker.find_dependencies(name, template)
+ DependencyTracker.find_dependencies(name, template, finder.view_paths)
rescue ActionView::MissingTemplate
- [] # File doesn't exist, so no dependencies
+ logger.try :error, " '#{name}' file doesn't exist, so no dependencies"
+ []
end
def nested_dependencies
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index 60fc9ee1a2..e506c782d6 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -60,7 +60,7 @@ module ActionView
tag_options = {
"src" => path_to_javascript(source, path_options)
}.merge!(options)
- content_tag(:script, "", tag_options)
+ content_tag("script".freeze, "", tag_options)
}.join("\n").html_safe
end
@@ -207,6 +207,7 @@ module ActionView
# # => <img alt="Icon" class="menu_icon" src="/icons/icon.gif" />
def image_tag(source, options={})
options = options.symbolize_keys
+ check_for_image_tag_errors(options)
src = options[:src] = path_to_image(source)
@@ -236,7 +237,7 @@ module ActionView
# image_alt('underscored_file_name.png')
# # => Underscored file name
def image_alt(src)
- File.basename(src, '.*').sub(/-[[:xdigit:]]{32}\z/, '').tr('-_', ' ').capitalize
+ File.basename(src, '.*'.freeze).sub(/-[[:xdigit:]]{32}\z/, ''.freeze).tr('-_'.freeze, ' '.freeze).capitalize
end
# Returns an HTML video tag for the +sources+. If +sources+ is a string,
@@ -325,6 +326,12 @@ module ActionView
[size, size]
end
end
+
+ def check_for_image_tag_errors(options)
+ if options[:size] && (options[:height] || options[:width])
+ raise ArgumentError, "Cannot pass a :size option with a :height or :width option"
+ end
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb
index 29733442c1..6ac8185b0f 100644
--- a/actionview/lib/action_view/helpers/asset_url_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_url_helper.rb
@@ -31,26 +31,33 @@ module ActionView
# stylesheet_link_tag("application")
# # => <link href="http://assets.example.com/assets/application.css" media="screen" rel="stylesheet" />
#
- # Browsers typically open at most two simultaneous connections to a single
- # host, which means your assets often have to wait for other assets to finish
- # downloading. You can alleviate this by using a <tt>%d</tt> wildcard in the
- # +asset_host+. For example, "assets%d.example.com". If that wildcard is
- # present Rails distributes asset requests among the corresponding four hosts
- # "assets0.example.com", ..., "assets3.example.com". With this trick browsers
- # will open eight simultaneous connections rather than two.
+ # Browsers open a limited number of simultaneous connections to a single
+ # host. The exact number varies by browser and version. This limit may cause
+ # 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
+ # "assets0.example.com", ..., "assets3.example.com".
#
# image_tag("rails.png")
# # => <img alt="Rails" src="http://assets0.example.com/assets/rails.png" />
# stylesheet_link_tag("application")
# # => <link href="http://assets2.example.com/assets/application.css" media="screen" rel="stylesheet" />
#
- # To do this, you can either setup four actual hosts, or you can use wildcard
- # DNS to CNAME the wildcard to a single asset host. You can read more about
- # setting up your DNS CNAME records from your ISP.
+ # This may improve the asset loading performance of your application.
+ # It is also possible the combination of additional connection overhead
+ # (DNS, SSL) and the overall browser connection limits may result in this
+ # solution being slower. You should be sure to measure your actual
+ # performance across targeted browers both before and after this change.
+ #
+ # To implement the corresponding hosts you can either setup four actual
+ # hosts or use wildcard DNS to CNAME the wilcard to a single asset host.
+ # You can read more about setting up your DNS CNAME records from your ISP.
#
# Note: This is purely a browser performance optimization and is not meant
# for server load balancing. See http://www.die.net/musings/page_load_time/
- # for background.
+ # for background and http://www.browserscope.org/?category=network for
+ # connection limit data.
#
# Alternatively, you can exert more control over the asset host by setting
# +asset_host+ to a proc like this:
@@ -121,11 +128,13 @@ module ActionView
# asset_path "application", type: :stylesheet # => /assets/application.css
# asset_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
def asset_path(source, options = {})
+ raise ArgumentError, "nil is not a valid asset source" if source.nil?
+
source = source.to_s
return "" unless source.present?
return source if source =~ URI_REGEXP
- tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, '')
+ tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, ''.freeze)
if extname = compute_asset_extname(source, options)
source = "#{source}#{extname}"
@@ -248,6 +257,11 @@ module ActionView
# Computes the full URL to a JavaScript asset in the public javascripts directory.
# This will use +javascript_path+ internally, so most of their behaviors will be the same.
+ # Since +javascript_url+ is based on +asset_url+ method you can set :host options. If :host
+ # options is set, it overwrites global +config.action_controller.asset_host+ setting.
+ #
+ # javascript_url "js/xmlhr.js", host: "http://stage.example.com" # => http://stage.example.com/assets/dir/xmlhr.js
+ #
def javascript_url(source, options = {})
url_to_asset(source, {type: :javascript}.merge!(options))
end
@@ -270,6 +284,11 @@ module ActionView
# Computes the full URL to a stylesheet asset in the public stylesheets directory.
# This will use +stylesheet_path+ internally, so most of their behaviors will be the same.
+ # Since +stylesheet_url+ is based on +asset_url+ method you can set :host options. If :host
+ # options is set, it overwrites global +config.action_controller.asset_host+ setting.
+ #
+ # stylesheet_url "css/style.css", host: "http://stage.example.com" # => http://stage.example.com/css/style.css
+ #
def stylesheet_url(source, options = {})
url_to_asset(source, {type: :stylesheet}.merge!(options))
end
@@ -295,6 +314,11 @@ module ActionView
# Computes the full URL to an image asset.
# This will use +image_path+ internally, so most of their behaviors will be the same.
+ # Since +image_url+ is based on +asset_url+ method you can set :host options. If :host
+ # options is set, it overwrites global +config.action_controller.asset_host+ setting.
+ #
+ # image_url "edit.png", host: "http://stage.example.com" # => http://stage.example.com/edit.png
+ #
def image_url(source, options = {})
url_to_asset(source, {type: :image}.merge!(options))
end
@@ -316,6 +340,11 @@ module ActionView
# Computes the full URL to a video asset in the public videos directory.
# This will use +video_path+ internally, so most of their behaviors will be the same.
+ # Since +video_url+ is based on +asset_url+ method you can set :host options. If :host
+ # options is set, it overwrites global +config.action_controller.asset_host+ setting.
+ #
+ # video_url "hd.avi", host: "http://stage.example.com" # => http://stage.example.com/hd.avi
+ #
def video_url(source, options = {})
url_to_asset(source, {type: :video}.merge!(options))
end
@@ -337,6 +366,11 @@ module ActionView
# Computes the full URL to an audio asset in the public audios directory.
# This will use +audio_path+ internally, so most of their behaviors will be the same.
+ # Since +audio_url+ is based on +asset_url+ method you can set :host options. If :host
+ # options is set, it overwrites global +config.action_controller.asset_host+ setting.
+ #
+ # audio_url "horse.wav", host: "http://stage.example.com" # => http://stage.example.com/horse.wav
+ #
def audio_url(source, options = {})
url_to_asset(source, {type: :audio}.merge!(options))
end
@@ -357,6 +391,11 @@ module ActionView
# Computes the full URL to a font asset.
# This will use +font_path+ internally, so most of their behaviors will be the same.
+ # Since +font_url+ is based on +asset_url+ method you can set :host options. If :host
+ # options is set, it overwrites global +config.action_controller.asset_host+ setting.
+ #
+ # font_url "font.ttf", host: "http://stage.example.com" # => http://stage.example.com/font.ttf
+ #
def font_url(source, options = {})
url_to_asset(source, {type: :font}.merge!(options))
end
diff --git a/actionview/lib/action_view/helpers/atom_feed_helper.rb b/actionview/lib/action_view/helpers/atom_feed_helper.rb
index d8be4e5678..bb1cdd0f8d 100644
--- a/actionview/lib/action_view/helpers/atom_feed_helper.rb
+++ b/actionview/lib/action_view/helpers/atom_feed_helper.rb
@@ -16,7 +16,7 @@ module ActionView
# end
#
# app/controllers/posts_controller.rb:
- # class PostsController < ApplicationController::Base
+ # class PostsController < ApplicationController
# # GET /posts.html
# # GET /posts.atom
# def index
@@ -51,7 +51,7 @@ module ActionView
# * <tt>:language</tt>: Defaults to "en-US".
# * <tt>:root_url</tt>: The HTML alternative that this feed is doubling for. Defaults to / on the current host.
# * <tt>:url</tt>: The URL for this feed. Defaults to the current URL.
- # * <tt>:id</tt>: The id for this feed. Defaults to "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}"
+ # * <tt>:id</tt>: The id for this feed. Defaults to "tag:localhost,2005:/posts", in this case.
# * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you
# created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified,
# 2005 is used (as an "I don't care" value).
diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb
index 0e2a5f90f4..636d7d4cc3 100644
--- a/actionview/lib/action_view/helpers/cache_helper.rb
+++ b/actionview/lib/action_view/helpers/cache_helper.rb
@@ -39,7 +39,7 @@ module ActionView
# This will include both records as part of the cache key and updating either of them will
# expire the cache.
#
- # ==== Template digest
+ # ==== \Template digest
#
# The template digest that's added to the cache key is computed by taking an md5 of the
# contents of the entire template file. This ensures that your caches will automatically
@@ -75,7 +75,8 @@ module ActionView
# render(topics) => render("topics/topic")
# render(message.topics) => render("topics/topic")
#
- # It's not possible to derive all render calls like that, though. Here are a few examples of things that can't be derived:
+ # It's not possible to derive all render calls like that, though.
+ # Here are a few examples of things that can't be derived:
#
# render group_of_attachments
# render @project.documents.where(published: true).order('created_at')
@@ -97,19 +98,33 @@ module ActionView
# <%# Template Dependency: todolists/todolist %>
# <%= render_sortable_todolists @project.todolists %>
#
- # The pattern used to match these is /# Template Dependency: ([^ ]+)/, so it's important that you type it out just so.
+ # In some cases, like a single table inheritance setup, you might have
+ # a bunch of explicit dependencies. Instead of writing every template out,
+ # you can use a wildcard to match any template in a directory:
+ #
+ # <%# Template Dependency: events/* %>
+ # <%= render_categorizable_events @person.events %>
+ #
+ # This marks every template in the directory as a dependency. To find those
+ # templates, the wildcard path must be absolutely defined from app/views or paths
+ # otherwise added with +prepend_view_path+ or +append_view_path+.
+ # This way the wildcard for `app/views/recordings/events` would be `recordings/events/*` etc.
+ #
+ # The pattern used to match explicit dependencies is <tt>/# Template Dependency: (\S+)/</tt>,
+ # so it's important that you type it out just so.
# You can only declare one template dependency per line.
#
# === External dependencies
#
- # If you use a helper method, for example, inside of a cached block and you then update that helper,
- # you'll have to bump the cache as well. It doesn't really matter how you do it, but the md5 of the template file
+ # If you use a helper method, for example, inside a cached block and
+ # you then update that helper, you'll have to bump the cache as well.
+ # It doesn't really matter how you do it, but the md5 of the template file
# must change. One recommendation is to simply be explicit in a comment, like:
#
# <%# Helper Dependency Updated: May 6, 2012 at 6pm %>
# <%= some_helper_method(person) %>
#
- # Now all you'll have to do is change that timestamp when the helper method changes.
+ # Now all you have to do is change that timestamp when the helper method changes.
#
# === Automatic Collection Caching
#
@@ -118,7 +133,7 @@ module ActionView
# <%= render @notifications %>
# <%= render partial: 'notifications/notification', collection: @notifications %>
#
- # If the notifications/_notification partial starts with a cache call like so:
+ # If the notifications/_notification partial starts with a cache call as:
#
# <% cache notification do %>
# <%= notification.name %>
@@ -127,14 +142,30 @@ module ActionView
# The collection can then automatically use any cached renders for that
# template by reading them at once instead of one by one.
#
- # See ActionView::Template::Handlers::ERB.resource_cache_call_pattern for more
- # information on what cache calls make a template eligible for this collection caching.
+ # See ActionView::Template::Handlers::ERB.resource_cache_call_pattern for
+ # more information on what cache calls make a template eligible for this
+ # collection caching.
#
# The automatic cache multi read can be turned off like so:
#
# <%= render @notifications, cache: false %>
- def cache(name = {}, options = nil, &block)
- if controller.perform_caching
+ #
+ # === Explicit Collection Caching
+ #
+ # If the partial template doesn't start with a clean cache call as
+ # mentioned above, you can still benefit from collection caching by
+ # adding a special comment format anywhere in the template, like:
+ #
+ # <%# Template Collection: notification %>
+ # <% my_helper_that_calls_cache(some_arg, notification) do %>
+ # <%= notification.name %>
+ # <% end %>
+ #
+ # The pattern used to match these is <tt>/# Template Collection: (\S+)/</tt>,
+ # so it's important that you type it out just so.
+ # You can only declare one collection in a partial template file.
+ def cache(name = {}, options = {}, &block)
+ if controller.respond_to?(:perform_caching) && controller.perform_caching
safe_concat(fragment_for(cache_fragment_name(name, options), options, &block))
else
yield
@@ -149,7 +180,7 @@ module ActionView
# <b>All the topics on this project</b>
# <%= render project.topics %>
# <% end %>
- def cache_if(condition, name = {}, options = nil, &block)
+ def cache_if(condition, name = {}, options = {}, &block)
if condition
cache(name, options, &block)
else
@@ -165,22 +196,23 @@ module ActionView
# <b>All the topics on this project</b>
# <%= render project.topics %>
# <% end %>
- def cache_unless(condition, name = {}, options = nil, &block)
+ def cache_unless(condition, name = {}, options = {}, &block)
cache_if !condition, name, options, &block
end
# This helper returns the name of a cache key for a given fragment cache
- # call. By supplying skip_digest: true to cache, the digestion of cache
+ # call. By supplying +skip_digest:+ true to cache, the digestion of cache
# fragments can be manually bypassed. This is useful when cache fragments
# cannot be manually expired unless you know the exact key which is the
# case when using memcached.
- def cache_fragment_name(name = {}, options = nil)
- skip_digest = options && options[:skip_digest]
-
+ #
+ # The digest will be generated using +virtual_path:+ if it is provided.
+ #
+ def cache_fragment_name(name = {}, skip_digest: nil, virtual_path: nil)
if skip_digest
name
else
- fragment_name_with_digest(name)
+ fragment_name_with_digest(name, virtual_path)
end
end
@@ -194,10 +226,11 @@ module ActionView
private
- def fragment_name_with_digest(name) #:nodoc:
- if @virtual_path
+ def fragment_name_with_digest(name, virtual_path) #:nodoc:
+ virtual_path ||= @virtual_path
+ if virtual_path
names = Array(name.is_a?(Hash) ? controller.url_for(name).split("://").last : name)
- digest = Digestor.digest name: @virtual_path, finder: lookup_context, dependencies: view_cache_dependencies
+ digest = Digestor.digest name: virtual_path, finder: lookup_context, dependencies: view_cache_dependencies
[ *names, digest ]
else
diff --git a/actionview/lib/action_view/helpers/capture_helper.rb b/actionview/lib/action_view/helpers/capture_helper.rb
index a67ba580f1..93c7cba395 100644
--- a/actionview/lib/action_view/helpers/capture_helper.rb
+++ b/actionview/lib/action_view/helpers/capture_helper.rb
@@ -115,7 +115,7 @@ module ActionView
# <li><%= link_to 'Home', action: 'index' %></li>
# <% end %>
#
- # And in other place:
+ # And in another place:
#
# <% content_for :navigation do %>
# <li><%= link_to 'Login', action: 'login' %></li>
diff --git a/actionview/lib/action_view/helpers/controller_helper.rb b/actionview/lib/action_view/helpers/controller_helper.rb
index 74ef25f7c1..3569fba8c6 100644
--- a/actionview/lib/action_view/helpers/controller_helper.rb
+++ b/actionview/lib/action_view/helpers/controller_helper.rb
@@ -14,6 +14,7 @@ module ActionView
if @_controller = controller
@_request = controller.request if controller.respond_to?(:request)
@_config = controller.config.inheritable_copy if controller.respond_to?(:config)
+ @_default_form_builder = controller.default_form_builder if controller.respond_to?(:default_form_builder)
end
end
diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb
index 46ce7cf0be..94fbaa2dca 100644
--- a/actionview/lib/action_view/helpers/date_helper.rb
+++ b/actionview/lib/action_view/helpers/date_helper.rb
@@ -68,6 +68,27 @@ module ActionView
# distance_of_time_in_words(from_time, to_time, include_seconds: true) # => about 6 years
# distance_of_time_in_words(to_time, from_time, include_seconds: true) # => about 6 years
# distance_of_time_in_words(Time.now, Time.now) # => less than a minute
+ #
+ # With the <tt>scope</tt> option, you can define a custom scope for Rails
+ # to look up the translation.
+ #
+ # For example you can define the following in your locale (e.g. en.yml).
+ #
+ # datetime:
+ # distance_in_words:
+ # short:
+ # about_x_hours:
+ # one: 'an hour'
+ # other: '%{count} hours'
+ #
+ # See https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/en.yml
+ # for more examples.
+ #
+ # Which will then result in the following:
+ #
+ # from_time = Time.now
+ # distance_of_time_in_words(from_time, from_time + 50.minutes, scope: 'datetime.distance_in_words.short') # => "an hour"
+ # distance_of_time_in_words(from_time, from_time + 3.hours, scope: 'datetime.distance_in_words.short') # => "3 hours"
def distance_of_time_in_words(from_time, to_time = 0, options = {})
options = {
scope: :'datetime.distance_in_words'
@@ -464,7 +485,7 @@ module ActionView
# The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
# Override the field name using the <tt>:field_name</tt> option, 'second' by default.
#
- # my_time = Time.now + 16.minutes
+ # my_time = Time.now + 16.seconds
#
# # Generates a select field for seconds that defaults to the seconds for the time in my_time.
# select_second(my_time)
@@ -488,7 +509,7 @@ module ActionView
# selected. The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
# Override the field name using the <tt>:field_name</tt> option, 'minute' by default.
#
- # my_time = Time.now + 6.hours
+ # my_time = Time.now + 10.minutes
#
# # Generates a select field for minutes that defaults to the minutes for the time in my_time.
# select_minute(my_time)
@@ -660,7 +681,7 @@ module ActionView
content = args.first || I18n.l(date_or_time, :format => format)
datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.iso8601
- content_tag(:time, content, options.reverse_merge(:datetime => datetime), &block)
+ content_tag("time".freeze, content, options.reverse_merge(:datetime => datetime), &block)
end
end
@@ -788,7 +809,7 @@ module ActionView
1.upto(12) do |month_number|
options = { :value => month_number }
options[:selected] = "selected" if month == month_number
- month_options << content_tag(:option, month_name(month_number), options) + "\n"
+ month_options << content_tag("option".freeze, month_name(month_number), options) + "\n"
end
build_select(:month, month_options.join)
end
@@ -950,7 +971,7 @@ module ActionView
tag_options[:selected] = "selected" if selected == i
text = options[:use_two_digit_numbers] ? sprintf("%02d", i) : value
text = options[:ampm] ? AMPM_TRANSLATION[i] : text
- select_options << content_tag(:option, text, tag_options)
+ select_options << content_tag("option".freeze, text, tag_options)
end
(select_options.join("\n") + "\n").html_safe
@@ -970,11 +991,11 @@ module ActionView
select_options[:class] = [select_options[:class], type].compact.join(' ') if @options[:with_css_classes]
select_html = "\n"
- select_html << content_tag(:option, '', :value => '') + "\n" if @options[:include_blank]
+ select_html << content_tag("option".freeze, '', :value => '') + "\n" if @options[:include_blank]
select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt]
select_html << select_options_as_html
- (content_tag(:select, select_html.html_safe, select_options) + "\n").html_safe
+ (content_tag("select".freeze, select_html.html_safe, select_options) + "\n").html_safe
end
# Builds a prompt option tag with supplied options or from default options.
@@ -991,7 +1012,7 @@ module ActionView
I18n.translate(:"datetime.prompts.#{type}", :locale => @options[:locale])
end
- prompt ? content_tag(:option, prompt, :value => '') : ''
+ prompt ? content_tag("option".freeze, prompt, :value => '') : ''
end
# Builds hidden input tag for date part and value.
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index ece117b547..3a9acafaa2 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -114,6 +114,8 @@ module ActionView
include ModelNaming
include RecordIdentifier
+ attr_internal :default_form_builder
+
# Creates a form that allows the user to create or update the attributes
# of a specific model object.
#
@@ -1233,7 +1235,7 @@ module ActionView
end
def default_form_builder_class
- builder = ActionView::Base.default_form_builder
+ builder = default_form_builder || ActionView::Base.default_form_builder
builder.respond_to?(:constantize) ? builder.constantize : builder
end
end
@@ -1629,7 +1631,7 @@ module ActionView
# target labels for radio_button tags (where the value is used in the ID of the input tag).
#
# ==== Examples
- # label(:post, :title)
+ # label(:title)
# # => <label for="post_title">Title</label>
#
# You can localize your labels based on model and attribute names.
@@ -1642,7 +1644,7 @@ module ActionView
#
# Which then will result in
#
- # label(:post, :body)
+ # label(:body)
# # => <label for="post_body">Write your entire text here</label>
#
# Localization can also be based purely on the translation of the attribute-name
@@ -1653,21 +1655,22 @@ module ActionView
# post:
# cost: "Total cost"
#
- # label(:post, :cost)
+ # label(:cost)
# # => <label for="post_cost">Total cost</label>
#
- # label(:post, :title, "A short title")
+ # label(:title, "A short title")
# # => <label for="post_title">A short title</label>
#
- # label(:post, :title, "A short title", class: "title_label")
+ # label(:title, "A short title", class: "title_label")
# # => <label for="post_title" class="title_label">A short title</label>
#
- # label(:post, :privacy, "Public Post", value: "public")
+ # label(:privacy, "Public Post", value: "public")
# # => <label for="post_privacy_public">Public Post</label>
#
- # label(:post, :terms) do
+ # label(:terms) do
# 'Accept <a href="/terms">Terms</a>.'.html_safe
# end
+ # # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label>
def label(method, text = nil, options = {}, &block)
@template.label(@object_name, method, text, objectify_options(options), &block)
end
@@ -1716,16 +1719,17 @@ module ActionView
# hashes instead of arrays.
#
# # Let's say that @post.validated? is 1:
- # check_box("post", "validated")
+ # check_box("validated")
# # => <input name="post[validated]" type="hidden" value="0" />
# # <input checked="checked" type="checkbox" id="post_validated" name="post[validated]" value="1" />
#
# # Let's say that @puppy.gooddog is "no":
- # check_box("puppy", "gooddog", {}, "yes", "no")
+ # check_box("gooddog", {}, "yes", "no")
# # => <input name="puppy[gooddog]" type="hidden" value="no" />
# # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
#
- # check_box("eula", "accepted", { class: 'eula_check' }, "yes", "no")
+ # # Let's say that @eula.accepted is "no":
+ # check_box("accepted", { class: 'eula_check' }, "yes", "no")
# # => <input name="eula[accepted]" type="hidden" value="no" />
# # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
@@ -1740,13 +1744,14 @@ module ActionView
# +options+ hash. You may pass HTML options there as well.
#
# # Let's say that @post.category returns "rails":
- # radio_button("post", "category", "rails")
- # radio_button("post", "category", "java")
+ # radio_button("category", "rails")
+ # radio_button("category", "java")
# # => <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" />
#
- # radio_button("user", "receive_newsletter", "yes")
- # radio_button("user", "receive_newsletter", "no")
+ # # Let's say that @user.category 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" />
# # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
def radio_button(method, tag_value, options = {})
@@ -1759,14 +1764,17 @@ module ActionView
# shown.
#
# ==== Examples
- # hidden_field(:signup, :pass_confirm)
- # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />
+ # # Let's say that @signup.pass_confirm returns true:
+ # hidden_field(:pass_confirm)
+ # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="true" />
#
- # hidden_field(:post, :tag_list)
- # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" />
+ # # Let's say that @post.tag_list returns "blog, ruby":
+ # hidden_field(:tag_list)
+ # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="blog, ruby" />
#
- # hidden_field(:user, :token)
- # # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
+ # # Let's say that @user.token returns "abcde":
+ # hidden_field(:token)
+ # # => <input type="hidden" id="user_token" name="user[token]" value="abcde" />
#
def hidden_field(method, options = {})
@emitted_hidden_id = true if method == :id
@@ -1787,19 +1795,24 @@ module ActionView
# * <tt>:accept</tt> - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations.
#
# ==== Examples
- # file_field(:user, :avatar)
+ # # Let's say that @user has avatar:
+ # file_field(:avatar)
# # => <input type="file" id="user_avatar" name="user[avatar]" />
#
- # file_field(:post, :image, :multiple => true)
- # # => <input type="file" id="post_image" name="post[image]" multiple="true" />
+ # # Let's say that @post has image:
+ # file_field(:image, :multiple => true)
+ # # => <input type="file" id="post_image" name="post[image][]" multiple="multiple" />
#
- # file_field(:post, :attached, accept: 'text/html')
+ # # Let's say that @post has attached:
+ # file_field(:attached, accept: 'text/html')
# # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
#
- # file_field(:post, :image, accept: 'image/png,image/gif,image/jpeg')
+ # # Let's say that @post has image:
+ # file_field(:image, accept: 'image/png,image/gif,image/jpeg')
# # => <input type="file" id="post_image" name="post[image]" accept="image/png,image/gif,image/jpeg" />
#
- # file_field(:attachment, :file, class: 'file_input')
+ # # Let's say that @attachment has file:
+ # file_field(:file, class: 'file_input')
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
def file_field(method, options = {})
self.multipart = true
diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb
index 8a5928477f..728e633bbf 100644
--- a/actionview/lib/action_view/helpers/form_options_helper.rb
+++ b/actionview/lib/action_view/helpers/form_options_helper.rb
@@ -35,8 +35,8 @@ module ActionView
# <select name="post[person_id]" id="post_person_id">
# <option value="">None</option>
# <option value="1">David</option>
- # <option value="2" selected="selected">Sam</option>
- # <option value="3">Tobias</option>
+ # <option value="2" selected="selected">Eileen</option>
+ # <option value="3">Rafael</option>
# </select>
#
# * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this prepends an option with a generic prompt -- "Please select" -- or the given prompt string.
@@ -48,8 +48,8 @@ module ActionView
# <select name="post[person_id]" id="post_person_id">
# <option value="">Select Person</option>
# <option value="1">David</option>
- # <option value="2">Sam</option>
- # <option value="3">Tobias</option>
+ # <option value="2">Eileen</option>
+ # <option value="3">Rafael</option>
# </select>
#
# * <tt>:index</tt> - like the other form helpers, +select+ can accept an <tt>:index</tt> option to manually set the ID used in the resulting output. Unlike other helpers, +select+ expects this
@@ -80,7 +80,7 @@ module ActionView
#
# When used with the <tt>collection_select</tt> helper, <tt>:disabled</tt> can also be a Proc that identifies those options that should be disabled.
#
- # collection_select(:post, :category_id, Category.all, :id, :name, {disabled: lambda{|category| category.archived? }})
+ # collection_select(:post, :category_id, Category.all, :id, :name, {disabled: -> (category) { category.archived? }})
#
# If the categories "2008 stuff" and "Christmas" return true when the method <tt>archived?</tt> is called, this would return:
# <select name="post[category_id]" id="post_category_id">
@@ -112,8 +112,8 @@ module ActionView
# <select name="post[person_id]" id="post_person_id">
# <option value=""></option>
# <option value="1" selected="selected">David</option>
- # <option value="2">Sam</option>
- # <option value="3">Tobias</option>
+ # <option value="2">Eileen</option>
+ # <option value="3">Rafael</option>
# </select>
#
# assuming the associated person has ID 1.
@@ -410,7 +410,7 @@ module ActionView
# * +collection+ - An array of objects representing the <tt><optgroup></tt> tags.
# * +group_method+ - The name of a method which, when called on a member of +collection+, returns an
# array of child objects representing the <tt><option></tt> tags.
- # * group_label_method+ - The name of a method which, when called on a member of +collection+, returns a
+ # * +group_label_method+ - The name of a method which, when called on a member of +collection+, returns a
# string to be used as the +label+ attribute for its <tt><optgroup></tt> tag.
# * +option_key_method+ - The name of a method which, when called on a child object of a member of
# +collection+, returns a value to be used as the +value+ attribute for its <tt><option></tt> tag.
@@ -456,7 +456,7 @@ module ActionView
option_tags = options_from_collection_for_select(
group.send(group_method), option_key_method, option_value_method, selected_key)
- content_tag(:optgroup, option_tags, label: group.send(group_label_method))
+ content_tag("optgroup".freeze, option_tags, label: group.send(group_label_method))
end.join.html_safe
end
@@ -528,7 +528,7 @@ module ActionView
body = "".html_safe
if prompt
- body.safe_concat content_tag(:option, prompt_text(prompt), value: "")
+ body.safe_concat content_tag("option".freeze, prompt_text(prompt), value: "")
end
grouped_options.each do |container|
@@ -541,7 +541,7 @@ module ActionView
end
html_attributes = { label: label }.merge!(html_attributes)
- body.safe_concat content_tag(:optgroup, options_for_select(container, selected_key), html_attributes)
+ body.safe_concat content_tag("optgroup".freeze, options_for_select(container, selected_key), html_attributes)
end
body
@@ -577,7 +577,7 @@ module ActionView
end
zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected)
- zone_options.safe_concat content_tag(:option, '-------------', value: '', disabled: true)
+ zone_options.safe_concat content_tag("option".freeze, '-------------', value: '', disabled: true)
zone_options.safe_concat "\n"
zones = zones - priority_zones
@@ -707,6 +707,27 @@ module ActionView
# collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b|
# b.label(:"data-value" => b.value) { b.check_box + b.text }
# end
+ #
+ # ==== Gotcha
+ #
+ # When no selection is made for a collection of checkboxes most
+ # web browsers will not send any value.
+ #
+ # For example, if we have a +User+ model with +category_ids+ field and we
+ # have the following code in our update action:
+ #
+ # @user.update(params[:user])
+ #
+ # If no +category_ids+ are selected then we can safely assume this field
+ # will not be updated.
+ #
+ # This is possible thanks to a hidden field generated by the helper method
+ # for every collection of checkboxes.
+ # This hidden field is given the same field name as the checkboxes with a
+ # blank value.
+ #
+ # In the rare case you don't want this hidden field, you can pass the
+ # <tt>include_hidden: false</tt> option to the helper method.
def collection_check_boxes(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block)
Tags::CollectionCheckBoxes.new(object, method, self, collection, value_method, text_method, options, html_options).render(&block)
end
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index 65a0548ffb..d36701955a 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -80,7 +80,7 @@ module ActionView
# associated records. <tt>option_tags</tt> is a string containing the option tags for the select box.
#
# ==== Options
- # * <tt>:multiple</tt> - If set to true the selection will allow multiple choices.
+ # * <tt>:multiple</tt> - If set to true, the selection will allow multiple choices.
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
# * <tt>:include_blank</tt> - If set to true, an empty option will be created. If set to a string, the string will be used as the option's content and the value will be empty.
# * <tt>:prompt</tt> - Create a prompt option with blank value and the text asking user to select something.
@@ -140,15 +140,15 @@ module ActionView
end
if include_blank
- option_tags = content_tag(:option, include_blank, value: '').safe_concat(option_tags)
+ option_tags = content_tag("option".freeze, include_blank, value: '').safe_concat(option_tags)
end
end
if prompt = options.delete(:prompt)
- option_tags = content_tag(:option, prompt, value: '').safe_concat(option_tags)
+ option_tags = content_tag("option".freeze, prompt, value: '').safe_concat(option_tags)
end
- content_tag :select, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys)
+ content_tag "select".freeze, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys)
end
# Creates a standard text field; use these text fields to input smaller chunks of text like a username
@@ -448,8 +448,9 @@ module ActionView
# <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 allows richer labels such as images and emphasis,
- # so this helper will also accept a block.
+ # the button tag does allow for richer labels such as images and emphasis,
+ # so this helper will also accept a block. By default, it will create
+ # a button tag with type `submit`, if type is not given.
#
# ==== Options
# * <tt>:data</tt> - This option can be used to add custom data attributes.
@@ -472,6 +473,15 @@ module ActionView
# button_tag
# # => <button name="button" type="submit">Button</button>
#
+ # button_tag 'Reset', type: 'reset'
+ # # => <button name="button" type="reset">Reset</button>
+ #
+ # button_tag 'Button', type: 'button'
+ # # => <button name="button" type="button">Button</button>
+ #
+ # button_tag 'Reset', type: 'reset', disabled: true
+ # # => <button name="button" type="reset" disabled="disabled">Reset</button>
+ #
# button_tag(type: 'button') do
# content_tag(:strong, 'Ask me!')
# end
@@ -479,6 +489,9 @@ module ActionView
# # <strong>Ask me!</strong>
# # </button>
#
+ # button_tag "Save", data: { confirm: "Are you sure?" }
+ # # => <button name="button" type="submit" data-confirm="Are you sure?">Save</button>
+ #
# button_tag "Checkout", data: { disable_with: "Please wait..." }
# # => <button data-disable-with="Please wait..." name="button" type="submit">Checkout</button>
#
@@ -555,7 +568,7 @@ module ActionView
# # => <fieldset class="format"><p><input id="name" name="name" type="text" /></p></fieldset>
def field_set_tag(legend = nil, options = nil, &block)
output = tag(:fieldset, options, true)
- output.safe_concat(content_tag(:legend, legend)) unless legend.blank?
+ output.safe_concat(content_tag("legend".freeze, legend)) unless legend.blank?
output.concat(capture(&block)) if block_given?
output.safe_concat("</fieldset>")
end
diff --git a/actionview/lib/action_view/helpers/javascript_helper.rb b/actionview/lib/action_view/helpers/javascript_helper.rb
index e237a32cb7..ed7e882c94 100644
--- a/actionview/lib/action_view/helpers/javascript_helper.rb
+++ b/actionview/lib/action_view/helpers/javascript_helper.rb
@@ -47,8 +47,8 @@ module ActionView
# tag.
#
# javascript_tag "alert('All is good')", defer: 'defer'
- #
- # Returns:
+ #
+ # Returns:
# <script defer="defer">
# //<![CDATA[
# alert('All is good')
@@ -70,7 +70,7 @@ module ActionView
content_or_options_with_block
end
- content_tag(:script, javascript_cdata_section(content), html_options)
+ content_tag("script".freeze, javascript_cdata_section(content), html_options)
end
def javascript_cdata_section(content) #:nodoc:
diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb
index ca8d30e4ef..13effa592d 100644
--- a/actionview/lib/action_view/helpers/number_helper.rb
+++ b/actionview/lib/action_view/helpers/number_helper.rb
@@ -279,7 +279,7 @@ module ActionView
# See <tt>number_to_human_size</tt> if you want to print a file
# size.
#
- # You can also define you own unit-quantifier names if you want
+ # You can also define your own unit-quantifier names if you want
# to use other decimal units (eg.: 1500 becomes "1.5
# kilometers", 0.150 becomes "150 milliliters", etc). You may
# define a wide range of unit quantifiers, even fractional ones
diff --git a/actionview/lib/action_view/helpers/rendering_helper.rb b/actionview/lib/action_view/helpers/rendering_helper.rb
index 827932d8e2..c98f2d74a8 100644
--- a/actionview/lib/action_view/helpers/rendering_helper.rb
+++ b/actionview/lib/action_view/helpers/rendering_helper.rb
@@ -18,7 +18,7 @@ module ActionView
# performs HTML escape on the string first. Setting the content type as
# <tt>text/html</tt>.
# * <tt>:body</tt> - Renders the text passed in, and inherits the content
- # type of <tt>text/html</tt> from <tt>ActionDispatch::Response</tt>
+ # type of <tt>text/plain</tt> from <tt>ActionDispatch::Response</tt>
# object.
#
# If no options hash is passed or :update specified, the default is to render a partial and use the second parameter
diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb
index a87c223a71..2562504896 100644
--- a/actionview/lib/action_view/helpers/tag_helper.rb
+++ b/actionview/lib/action_view/helpers/tag_helper.rb
@@ -22,9 +22,10 @@ module ActionView
TAG_PREFIXES = ['aria', 'data', :aria, :data].to_set
- PRE_CONTENT_STRINGS = {
- :textarea => "\n"
- }
+ PRE_CONTENT_STRINGS = Hash.new { "".freeze }
+ PRE_CONTENT_STRINGS[:textarea] = "\n"
+ PRE_CONTENT_STRINGS["textarea"] = "\n"
+
# Returns an empty HTML tag of type +name+ which by default is XHTML
# compliant. Set +open+ to true to create an open tag compatible
@@ -143,24 +144,30 @@ module ActionView
def content_tag_string(name, content, options, escape = true)
tag_options = tag_options(options, escape) if options
content = ERB::Util.unwrapped_html_escape(content) if escape
- "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name.to_sym]}#{content}</#{name}>".html_safe
+ "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}</#{name}>".html_safe
end
def tag_options(options, escape = true)
return if options.blank?
- attrs = []
+ output = ""
+ sep = " ".freeze
options.each_pair do |key, value|
if TAG_PREFIXES.include?(key) && value.is_a?(Hash)
value.each_pair do |k, v|
- attrs << prefix_tag_option(key, k, v, escape)
+ output << sep
+ output << prefix_tag_option(key, k, v, escape)
end
elsif BOOLEAN_ATTRIBUTES.include?(key)
- attrs << boolean_tag_option(key) if value
+ if value
+ output << sep
+ output << boolean_tag_option(key)
+ end
elsif !value.nil?
- attrs << tag_option(key, value, escape)
+ output << sep
+ output << tag_option(key, value, escape)
end
end
- " #{attrs * ' '}" unless attrs.empty?
+ output unless output.empty?
end
def prefix_tag_option(prefix, key, value, escape)
@@ -177,7 +184,7 @@ 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
end
diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb
index acc6443a96..d57f26ba4f 100644
--- a/actionview/lib/action_view/helpers/tags/base.rb
+++ b/actionview/lib/action_view/helpers/tags/base.rb
@@ -120,7 +120,12 @@ module ActionView
def select_content_tag(option_tags, options, html_options)
html_options = html_options.stringify_keys
add_default_name_and_id(html_options)
- options[:include_blank] ||= true unless options[:prompt] || select_not_required?(html_options)
+
+ if placeholder_required?(html_options)
+ raise ArgumentError, "include_blank cannot be false for a required field." if options[:include_blank] == false
+ options[:include_blank] ||= true unless options[:prompt]
+ end
+
value = options.fetch(:selected) { value(object) }
select = content_tag("select", add_options(option_tags, options, value), html_options)
@@ -131,8 +136,9 @@ module ActionView
end
end
- def select_not_required?(html_options)
- !html_options["required"] || html_options["multiple"] || html_options["size"].to_i > 1
+ def placeholder_required?(html_options)
+ # See https://html.spec.whatwg.org/multipage/forms.html#attr-select-required
+ html_options["required"] && !html_options["multiple"] && html_options.fetch("size", 1).to_i == 1
end
def add_options(option_tags, options, value = nil)
diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb
index c216d4401f..432693bc23 100644
--- a/actionview/lib/action_view/helpers/text_helper.rb
+++ b/actionview/lib/action_view/helpers/text_helper.rb
@@ -206,6 +206,11 @@ module ActionView
# +plural+ is supplied, it will use that when count is > 1, otherwise
# it will use the Inflector to determine the plural form.
#
+ # If passed an optional +locale:+ parameter, the word will be pluralized
+ # using rules defined for that language (you must define your own
+ # inflection rules for languages other than English). See
+ # ActiveSupport::Inflector.pluralize
+ #
# pluralize(1, 'person')
# # => 1 person
#
@@ -217,11 +222,14 @@ module ActionView
#
# pluralize(0, 'person')
# # => 0 people
- def pluralize(count, singular, plural = nil)
+ #
+ # pluralize(2, 'Person', locale: :de)
+ # # => 2 Personen
+ def pluralize(count, singular, plural = nil, locale: nil)
word = if (count == 1 || count =~ /^1(\.0+)?$/)
singular
else
- plural || singular.pluralize
+ plural || singular.pluralize(locale)
end
"#{count || 0} #{word}"
@@ -242,12 +250,15 @@ module ActionView
#
# word_wrap('Once upon a time', line_width: 1)
# # => Once\nupon\na\ntime
- def word_wrap(text, options = {})
- line_width = options.fetch(:line_width, 80)
-
+ #
+ # You can also specify a custom +break_sequence+ ("\n" by default)
+ #
+ # word_wrap('Once upon a time', line_width: 1, break_sequence: "\r\n")
+ # # => Once\r\nupon\r\na\r\ntime
+ def word_wrap(text, line_width: 80, break_sequence: "\n")
text.split("\n").collect! do |line|
- line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line
- end * "\n"
+ line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1#{break_sequence}").strip : line
+ end * break_sequence
end
# Returns +text+ transformed into HTML using simple formatting rules.
diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb
index 9d7390f1fd..0615bd2e0d 100644
--- a/actionview/lib/action_view/helpers/translation_helper.rb
+++ b/actionview/lib/action_view/helpers/translation_helper.rb
@@ -7,34 +7,47 @@ module ActionView
module Helpers
module TranslationHelper
include TagHelper
- # Delegates to <tt>I18n#translate</tt> but also performs three additional functions.
+ # Delegates to <tt>I18n#translate</tt> but also performs three additional
+ # functions.
#
- # First, it will ensure that any thrown +MissingTranslation+ messages will be turned
- # into inline spans that:
+ # First, it will ensure that any thrown +MissingTranslation+ messages will
+ # be rendered as inline spans that:
#
- # * have a "translation-missing" class set,
- # * contain the missing key as a title attribute and
- # * a titleized version of the last key segment as a text.
+ # * Have a <tt>translation-missing</tt> class applied
+ # * Contain the missing key as the value of the +title+ attribute
+ # * Have a titleized version of the last key segment as text
#
- # E.g. the value returned for a missing translation key :"blog.post.title" will be
- # <span class="translation_missing" title="translation missing: en.blog.post.title">Title</span>.
- # This way your views will display rather reasonable strings but it will still
- # be easy to spot missing translations.
+ # For example, the value returned for the missing translation key
+ # <tt>"blog.post.title"</tt> will be:
#
- # Second, it'll scope the key by the current partial if the key starts
- # with a period. So if you call <tt>translate(".foo")</tt> from the
- # <tt>people/index.html.erb</tt> template, you'll actually be calling
- # <tt>I18n.translate("people.index.foo")</tt>. This makes it less repetitive
- # to translate many keys within the same partials and gives you a simple framework
- # for scoping them consistently. If you don't prepend the key with a period,
- # nothing is converted.
+ # <span
+ # class="translation_missing"
+ # title="translation missing: en.blog.post.title">Title</span>
#
- # Third, it'll mark the translation as safe HTML if the key has the suffix
- # "_html" or the last element of the key is the word "html". For example,
- # calling translate("footer_html") or translate("footer.html") will return
- # a safe HTML string that won't be escaped by other HTML helper methods. This
- # naming convention helps to identify translations that include HTML tags so that
- # you know what kind of output to expect when you call translate in a template.
+ # This allows for views to display rather reasonable strings while still
+ # giving developers a way to find missing translations.
+ #
+ # If you would prefer missing translations to raise an error, you can
+ # opt out of span-wrapping behavior globally by setting
+ # <tt>ActionView::Base.raise_on_missing_translations = true</tt> or
+ # individually by passing <tt>raise: true</tt> as an option to
+ # <tt>translate</tt>.
+ #
+ # Second, if the key starts with a period <tt>translate</tt> will scope
+ # the key by the current partial. Calling <tt>translate(".foo")</tt> from
+ # the <tt>people/index.html.erb</tt> template is equivalent to calling
+ # <tt>translate("people.index.foo")</tt>. This makes it less
+ # repetitive to translate many keys within the same partial and provides
+ # a convention to scope keys consistently.
+ #
+ # Third, the translation will be marked as <tt>html_safe</tt> if the key
+ # has the suffix "_html" or the last element of the key is "html". Calling
+ # <tt>translate("footer_html")</tt> or <tt>translate("footer.html")</tt>
+ # will return an HTML safe string that won't be escaped by other HTML
+ # helper methods. This naming convention helps to identify translations
+ # that include HTML tags so that you know what kind of output to expect
+ # when you call translate in a template and translators know which keys
+ # they can provide HTML values for.
def translate(key, options = {})
options = options.dup
has_default = options.has_key?(:default)
@@ -47,12 +60,12 @@ module ActionView
# If the user has explicitly decided to NOT raise errors, pass that option to I18n.
# Otherwise, tell I18n to raise an exception, which we rescue further in this method.
# Note: `raise_error` refers to us re-raising the error in this method. I18n is forced to raise by default.
- if options[:raise] == false || (options.key?(:rescue_format) && options[:rescue_format].nil?)
+ if options[:raise] == false
raise_error = false
- options[:raise] = false
+ i18n_raise = false
else
- raise_error = options[:raise] || options[:rescue_format] || ActionView::Base.raise_on_missing_translations
- options[:raise] = true
+ raise_error = options[:raise] || ActionView::Base.raise_on_missing_translations
+ i18n_raise = true
end
if html_safe_translation_key?(key)
@@ -62,11 +75,11 @@ module ActionView
html_safe_options[name] = ERB::Util.html_escape(value.to_s)
end
end
- translation = I18n.translate(scope_key_by_partial(key), html_safe_options)
+ translation = I18n.translate(scope_key_by_partial(key), html_safe_options.merge(raise: i18n_raise))
translation.respond_to?(:html_safe) ? translation.html_safe : translation
else
- I18n.translate(scope_key_by_partial(key), options)
+ I18n.translate(scope_key_by_partial(key), options.merge(raise: i18n_raise))
end
rescue I18n::MissingTranslationData => e
if remaining_defaults.present?
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index 8843a89362..d676a0a931 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -184,9 +184,9 @@ module ActionView
html_options = convert_options_to_data_attributes(options, html_options)
url = url_for(options)
- html_options['href'] ||= url
+ html_options["href".freeze] ||= url
- content_tag(:a, name || url, html_options, &block)
+ content_tag("a".freeze, name || url, html_options, &block)
end
# Generates a form containing a single button that submits to the URL created
@@ -468,65 +468,54 @@ module ActionView
}.compact
extras = extras.empty? ? '' : '?' + extras.join('&')
- html_options["href"] = "mailto:#{email_address}#{extras}"
+ encoded_email_address = ERB::Util.url_encode(email_address).gsub("%40", "@")
+ html_options["href"] = "mailto:#{encoded_email_address}#{extras}"
- content_tag(:a, name || email_address, html_options, &block)
+ content_tag("a".freeze, name || email_address, html_options, &block)
end
# True if the current request URI was generated by the given +options+.
#
# ==== Examples
- # Let's say we're in the <tt>http://www.example.com/shop/checkout?order=desc</tt> action.
+ # Let's say we're in the <tt>http://www.example.com/shop/checkout?order=desc&page=1</tt> action.
#
# current_page?(action: 'process')
# # => false
#
- # current_page?(controller: 'shop', action: 'checkout')
- # # => true
- #
- # current_page?(controller: 'shop', action: 'checkout', order: 'asc')
- # # => false
- #
# current_page?(action: 'checkout')
# # => true
#
# current_page?(controller: 'library', action: 'checkout')
# # => false
#
- # current_page?('http://www.example.com/shop/checkout')
- # # => true
- #
- # current_page?('/shop/checkout')
+ # current_page?(controller: 'shop', action: 'checkout')
# # => true
#
- # Let's say we're in the <tt>http://www.example.com/shop/checkout?order=desc&page=1</tt> action.
- #
- # current_page?(action: 'process')
+ # current_page?(controller: 'shop', action: 'checkout', order: 'asc')
# # => false
#
- # current_page?(controller: 'shop', action: 'checkout')
- # # => true
- #
# current_page?(controller: 'shop', action: 'checkout', order: 'desc', page: '1')
# # => true
#
# current_page?(controller: 'shop', action: 'checkout', order: 'desc', page: '2')
# # => false
#
- # current_page?(controller: 'shop', action: 'checkout', order: 'desc')
- # # => false
+ # current_page?('http://www.example.com/shop/checkout')
+ # # => true
#
- # current_page?(action: 'checkout')
+ # current_page?('/shop/checkout')
# # => true
#
- # current_page?(controller: 'library', action: 'checkout')
- # # => false
+ # current_page?('http://www.example.com/shop/checkout?order=desc&page=1')
+ # # => true
#
# Let's say we're in the <tt>http://www.example.com/products</tt> action with method POST in case of invalid product.
#
# current_page?(controller: 'product', action: 'index')
# # => false
#
+ # We can also pass in the symbol arguments instead of strings.
+ #
def current_page?(options)
unless request
raise "You cannot use helpers that need to determine the current " \
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index 4452dcfed5..f4a10aa393 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -6,10 +6,11 @@ require 'action_view/template/resolver'
module ActionView
# = Action View Lookup Context
#
- # LookupContext is the object responsible to hold all information required to lookup
- # templates, i.e. view paths and details. The LookupContext is also responsible to
- # generate a key, given to view paths, used in the resolver cache lookup. Since
- # this key is generated just once during the request, it speeds up all cache accesses.
+ # <tt>LookupContext</tt> is the object responsible for holding all information
+ # required for looking up templates, i.e. view paths and details.
+ # <tt>LookupContext</tt> is also responsible for generating a key, given to
+ # view paths, used in the resolver cache lookup. Since this key is generated
+ # only once during the request, it speeds up all cache accesses.
class LookupContext #:nodoc:
attr_accessor :prefixes, :rendered_format
@@ -172,13 +173,13 @@ module ActionView
# name instead of the prefix.
def normalize_name(name, prefixes) #:nodoc:
prefixes = prefixes.presence
- parts = name.to_s.split('/')
+ parts = name.to_s.split('/'.freeze)
parts.shift if parts.first.empty?
name = parts.pop
return name, prefixes || [""] if parts.empty?
- parts = parts.join('/')
+ parts = parts.join('/'.freeze)
prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts]
return name, prefixes
@@ -203,7 +204,7 @@ module ActionView
# add :html as fallback to :js.
def formats=(values)
if values
- values.concat(default_formats) if values.delete "*/*"
+ values.concat(default_formats) if values.delete "*/*".freeze
if values == [:js]
values << :html
@html_fallback_for_js = true
diff --git a/actionview/lib/action_view/path_set.rb b/actionview/lib/action_view/path_set.rb
index 91ee2ea8f5..7a88f6bc50 100644
--- a/actionview/lib/action_view/path_set.rb
+++ b/actionview/lib/action_view/path_set.rb
@@ -61,6 +61,15 @@ module ActionView #:nodoc:
find_all(path, prefixes, *args).any?
end
+ def find_all_with_query(query) # :nodoc:
+ paths.each do |resolver|
+ templates = resolver.find_all_with_query(query)
+ return templates unless templates.empty?
+ end
+
+ []
+ end
+
private
def typecast(paths)
diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb
index 9a26cba574..e829d86c99 100644
--- a/actionview/lib/action_view/railtie.rb
+++ b/actionview/lib/action_view/railtie.rb
@@ -42,14 +42,24 @@ module ActionView
end
end
+ initializer "action_view.per_request_digest_cache" do |app|
+ ActiveSupport.on_load(:action_view) do
+ if app.config.consider_all_requests_local
+ app.middleware.use ActionView::Digestor::PerRequestDigestCacheExpiry
+ end
+ end
+ end
+
initializer "action_view.setup_action_pack" do |app|
ActiveSupport.on_load(:action_controller) do
ActionView::RoutingUrlFor.include(ActionDispatch::Routing::UrlFor)
end
end
- rake_tasks do
- load "action_view/tasks/dependencies.rake"
+ rake_tasks do |app|
+ unless app.config.api_only
+ load "action_view/tasks/dependencies.rake"
+ end
end
end
end
diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb
index cd151c0189..1f9e960488 100644
--- a/actionview/lib/action_view/renderer/partial_renderer.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer.rb
@@ -338,7 +338,7 @@ module ActionView
end
object ||= locals[as]
- locals[as] = object
+ locals[as] = object if @has_object
content = @template.render(view, locals) do |*name|
view._layout_for(*name, &block)
@@ -348,8 +348,6 @@ module ActionView
content
end
- private
-
# 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.
@@ -522,7 +520,7 @@ module ActionView
def retrieve_variable(path, as)
variable = as || begin
- base = path[-1] == "/" ? "" : File.basename(path)
+ base = path[-1] == "/".freeze ? "".freeze : File.basename(path)
raise_invalid_identifier(path) unless base =~ /\A_?(.*)(?:\.\w+)*\z/
$1.to_sym
end
diff --git a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
index b77c884e66..1147963882 100644
--- a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
@@ -6,7 +6,7 @@ module ActionView
included do
# Fallback cache store if Action View is used without Rails.
- # Otherwise overriden in Railtie to use Rails.cache.
+ # Otherwise overridden in Railtie to use Rails.cache.
mattr_accessor(:collection_cache) { ActiveSupport::Cache::MemoryStore.new }
end
@@ -51,7 +51,7 @@ module ActionView
end
def expanded_cache_key(key)
- key = @view.fragment_cache_key(@view.cache_fragment_name(key))
+ key = @view.fragment_cache_key(@view.cache_fragment_name(key, virtual_path: @template.virtual_path))
key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0.
end
diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb
index 1e8e7415d1..86a80a5421 100644
--- a/actionview/lib/action_view/rendering.rb
+++ b/actionview/lib/action_view/rendering.rb
@@ -59,7 +59,7 @@ module ActionView
@_view_context_class ||= self.class.view_context_class
end
- # An instance of a view class. The default view class is ActionView::Base
+ # An instance of a view class. The default view class is ActionView::Base.
#
# The view class must have the following methods:
# View.new[lookup_context, assigns, controller]
@@ -103,7 +103,7 @@ module ActionView
view_renderer.render(context, options)
end
- # Assign the rendered format to lookup context.
+ # Assign the rendered format to look up context.
def _process_format(format, options = {}) #:nodoc:
super
lookup_context.formats = [format.to_sym]
diff --git a/actionview/lib/action_view/routing_url_for.rb b/actionview/lib/action_view/routing_url_for.rb
index 0371db07dc..20d6b9a64c 100644
--- a/actionview/lib/action_view/routing_url_for.rb
+++ b/actionview/lib/action_view/routing_url_for.rb
@@ -92,6 +92,16 @@ module ActionView
end
super(options)
+ when ActionController::Parameters
+ unless options.key?(:only_path)
+ if options[:host].nil?
+ options[:only_path] = _generate_paths_by_default
+ else
+ options[:only_path] = false
+ end
+ end
+
+ super(options)
when :back
_back_url
when Array
diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb
index 377ceb534a..e232808dcb 100644
--- a/actionview/lib/action_view/template.rb
+++ b/actionview/lib/action_view/template.rb
@@ -130,7 +130,7 @@ module ActionView
@source = source
@identifier = identifier
@handler = handler
- @cache_name = extract_resource_cache_call_name
+ @cache_name = extract_resource_cache_name
@compiled = false
@original_encoding = nil
@locals = details[:locals] || []
@@ -154,7 +154,7 @@ module ActionView
# 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") do
+ instrument("!render_template".freeze) do
compile!(view)
view.send(method_name, locals, buffer, &block)
end
@@ -190,7 +190,7 @@ module ActionView
end
def inspect
- @inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", '') : identifier
+ @inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", ''.freeze) : identifier
end
# This method is responsible for properly setting the encoding of the
@@ -337,27 +337,41 @@ module ActionView
def method_name #:nodoc:
@method_name ||= begin
m = "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}"
- m.tr!('-', '_')
+ m.tr!('-'.freeze, '_'.freeze)
m
end
end
def identifier_method_name #:nodoc:
- inspect.tr('^a-z_', '_')
+ inspect.tr('^a-z_'.freeze, '_'.freeze)
end
def instrument(action, &block)
payload = { virtual_path: @virtual_path, identifier: @identifier }
- ActiveSupport::Notifications.instrument("#{action}.action_view", payload, &block)
+ 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
end
- def extract_resource_cache_call_name
- $1 if @handler.respond_to?(:resource_cache_call_pattern) &&
- @source =~ @handler.resource_cache_call_pattern
+ EXPLICIT_COLLECTION = /# Template Collection: (?<resource_name>\w+)/
+
+ def extract_resource_cache_name
+ if match = @source.match(EXPLICIT_COLLECTION) || resource_cache_call_match
+ match[:resource_name]
+ end
+ end
+
+ def resource_cache_call_match
+ if @handler.respond_to?(:resource_cache_call_pattern)
+ @source.match(@handler.resource_cache_call_pattern)
+ end
end
def inferred_cache_name
- @inferred_cache_name ||= @virtual_path.split('/').last.sub('_', '')
+ @inferred_cache_name ||= @virtual_path.split('/'.freeze).last.sub('_'.freeze, ''.freeze)
end
end
end
diff --git a/actionview/lib/action_view/template/handlers/erb.rb b/actionview/lib/action_view/template/handlers/erb.rb
index 88a8570706..1f8459c24b 100644
--- a/actionview/lib/action_view/template/handlers/erb.rb
+++ b/actionview/lib/action_view/template/handlers/erb.rb
@@ -125,7 +125,7 @@ module ActionView
# Returns Regexp to extract a cached resource's name from a cache call at the
# first line of a template.
- # The extracted cache name is expected in $1.
+ # The extracted cache name is captured as :resource_name.
#
# <% cache notification do %> # => notification
#
@@ -138,7 +138,14 @@ module ActionView
#
# <% cache notification.event do %> # => nil
def resource_cache_call_pattern
- /\A(?:<%#.*%>\n?)?<% cache\(?\s*(\w+\.?)/
+ /\A
+ (?:<%\#.*%>)* # optional initial comment
+ \s* # followed by optional spaces or newlines
+ <%\s*cache[\(\s] # followed by an ERB call to cache
+ \s* # followed by optional spaces or newlines
+ (?<resource_name>\w+) # capture the cache call argument as :resource_name
+ [\s\)] # followed by a space or close paren
+ /xm
end
private
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index bc0db330ea..28967f40a6 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -52,6 +52,7 @@ module ActionView
def initialize
@data = SmallCache.new(&KEY_BLOCK)
+ @query_cache = SmallCache.new
end
# Cache the templates returned by the block
@@ -70,8 +71,17 @@ module ActionView
end
end
+ def cache_query(query) # :nodoc:
+ if Resolver.caching?
+ @query_cache[query] ||= canonical_no_templates(yield)
+ else
+ yield
+ end
+ end
+
def clear
@data.clear
+ @query_cache.clear
end
private
@@ -116,6 +126,10 @@ module ActionView
end
end
+ def find_all_with_query(query) # :nodoc:
+ @cache.cache_query(query) { find_template_paths(File.join(@path, query)) }
+ end
+
private
delegate :caching?, to: :class
@@ -181,9 +195,9 @@ module ActionView
def query(path, details, formats)
query = build_query(path, details)
- template_paths = find_template_paths query
+ template_paths = find_template_paths(query)
- template_paths.map { |template|
+ template_paths.map do |template|
handler, format, variant = extract_handler_and_format_and_variant(template, formats)
contents = File.binread(template)
@@ -193,36 +207,36 @@ module ActionView
:variant => variant,
:updated_at => mtime(template)
)
- }
+ end
end
def find_template_paths(query)
- Dir[query].reject { |filename|
+ Dir[query].reject do |filename|
File.directory?(filename) ||
# deals with case-insensitive file systems.
!File.fnmatch(query, filename, File::FNM_EXTGLOB)
- }
+ end
end
# Helper for building query glob string based on resolver's pattern.
def build_query(path, details)
query = @pattern.dup
- prefix = path.prefix.empty? ? "" : "#{escape_entry(path.prefix)}\\1"
- query.gsub!(/\:prefix(\/)?/, prefix)
+ prefix = path.prefix.empty? ? '' : "#{escape_entry(path.prefix)}\\1"
+ query.gsub!(/:prefix(\/)?/, prefix)
partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
- query.gsub!(/\:action/, partial)
+ query.gsub!(/:action/, partial)
details.each do |ext, variants|
- query.gsub!(/\:#{ext}/, "{#{variants.compact.uniq.join(',')}}")
+ query.gsub!(/:#{ext}/, "{#{variants.compact.uniq.join(',')}}")
end
File.expand_path(query, @path)
end
def escape_entry(entry)
- entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
+ entry.gsub(/[*?{}\[\]]/, '\\\\\\&'.freeze)
end
# Returns the file mtime from the filesystem.
@@ -234,7 +248,7 @@ module ActionView
# 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(".")
+ pieces = File.basename(path).split('.'.freeze)
pieces.shift
extension = pieces.pop
@@ -270,7 +284,7 @@ module ActionView
#
# ActionController::Base.view_paths = FileSystemResolver.new(
# Rails.root.join("app/views"),
- # ":prefix{/:locale}/:action{.:formats,}{+:variants,}{.:handlers,}"
+ # ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}",
# )
#
# ==== Pattern format and variables
diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb
index 06810ad14d..f6b5696a13 100644
--- a/actionview/lib/action_view/test_case.rb
+++ b/actionview/lib/action_view/test_case.rb
@@ -24,8 +24,8 @@ module ActionView
def initialize
super
self.class.controller_path = ""
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
+ @request = ActionController::TestRequest.create
+ @response = ActionDispatch::TestResponse.new
@request.env.delete('PATH_INFO')
@params = {}
@@ -263,9 +263,15 @@ module ActionView
end
def method_missing(selector, *args)
- if @controller.respond_to?(:_routes) &&
- ( @controller._routes.named_routes.route_defined?(selector) ||
- @controller._routes.mounted_helpers.method_defined?(selector) )
+ begin
+ routes = @controller.respond_to?(:_routes) && @controller._routes
+ rescue
+ # Dont call routes, if there is an error on _routes call
+ end
+
+ if routes &&
+ ( 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/view_paths.rb b/actionview/lib/action_view/view_paths.rb
index 492f67f45d..37722013ce 100644
--- a/actionview/lib/action_view/view_paths.rb
+++ b/actionview/lib/action_view/view_paths.rb
@@ -36,9 +36,9 @@ module ActionView
self.class._prefixes
end
- # LookupContext is the object responsible to hold all information required to lookup
- # templates, i.e. view paths and details. Check ActionView::LookupContext for more
- # information.
+ # <tt>LookupContext</tt> is the object responsible for holding all
+ # information required for looking up templates, i.e. view paths and
+ # details. Check <tt>ActionView::LookupContext</tt> for more information.
def lookup_context
@_lookup_context ||=
ActionView::LookupContext.new(self.class._view_paths, details_for_lookup, _prefixes)
diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb
index 4635c645d0..35f520ed1c 100644
--- a/actionview/test/abstract_unit.rb
+++ b/actionview/test/abstract_unit.rb
@@ -147,13 +147,13 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
def self.build_app(routes = nil)
RoutedRackApp.new(routes || ActionDispatch::Routing::RouteSet.new) do |middleware|
- middleware.use "ActionDispatch::ShowExceptions", ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public")
- middleware.use "ActionDispatch::DebugExceptions"
- middleware.use "ActionDispatch::Callbacks"
- middleware.use "ActionDispatch::ParamsParser"
- middleware.use "ActionDispatch::Cookies"
- middleware.use "ActionDispatch::Flash"
- middleware.use "Rack::Head"
+ middleware.use ActionDispatch::ShowExceptions, ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public")
+ middleware.use ActionDispatch::DebugExceptions
+ middleware.use ActionDispatch::Callbacks
+ middleware.use ActionDispatch::ParamsParser
+ middleware.use ActionDispatch::Cookies
+ middleware.use ActionDispatch::Flash
+ middleware.use Rack::Head
yield(middleware) if block_given?
end
end
diff --git a/actionview/test/actionpack/abstract/layouts_test.rb b/actionview/test/actionpack/abstract/layouts_test.rb
index a6786d9b6b..80bc665b0a 100644
--- a/actionview/test/actionpack/abstract/layouts_test.rb
+++ b/actionview/test/actionpack/abstract/layouts_test.rb
@@ -52,7 +52,7 @@ module AbstractControllerTests
end
def overwrite_skip
- render :text => "Hello text!"
+ render plain: "Hello text!"
end
end
@@ -371,7 +371,7 @@ module AbstractControllerTests
test "layout for anonymous controller" do
klass = Class.new(WithString) do
def index
- render :text => 'index', :layout => true
+ render plain: 'index', layout: true
end
end
diff --git a/actionview/test/actionpack/abstract/render_test.rb b/actionview/test/actionpack/abstract/render_test.rb
index d09f91c1e2..e5721d6416 100644
--- a/actionview/test/actionpack/abstract/render_test.rb
+++ b/actionview/test/actionpack/abstract/render_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'active_support/deprecation'
module AbstractController
module Testing
@@ -33,7 +34,7 @@ module AbstractController
end
def text
- render :text => "With Text"
+ render plain: "With Text"
end
def default
diff --git a/actionview/test/actionpack/controller/layout_test.rb b/actionview/test/actionpack/controller/layout_test.rb
index 7b8a83e2fe..64bc4c41d6 100644
--- a/actionview/test/actionpack/controller/layout_test.rb
+++ b/actionview/test/actionpack/controller/layout_test.rb
@@ -149,75 +149,75 @@ class LayoutSetInResponseTest < ActionController::TestCase
def test_layout_set_when_using_default_layout
@controller = DefaultLayoutController.new
get :hello
- assert_template :layout => "layouts/layout_test"
+ assert_includes @response.body, 'layout_test.erb'
end
def test_layout_set_when_using_streaming_layout
@controller = StreamingLayoutController.new
get :hello
- assert_template :hello
+ assert_includes @response.body, 'layout_test.erb'
end
def test_layout_set_when_set_in_controller
@controller = HasOwnLayoutController.new
get :hello
- assert_template :layout => "layouts/item"
+ assert_includes @response.body, 'item.erb'
end
def test_layout_symbol_set_in_controller_returning_nil_falls_back_to_default
@controller = HasNilLayoutSymbol.new
get :hello
- assert_template layout: "layouts/layout_test"
+ assert_includes @response.body, 'layout_test.erb'
end
def test_layout_proc_set_in_controller_returning_nil_falls_back_to_default
@controller = HasNilLayoutProc.new
get :hello
- assert_template layout: "layouts/layout_test"
+ assert_includes @response.body, 'layout_test.erb'
end
def test_layout_only_exception_when_included
@controller = OnlyLayoutController.new
get :hello
- assert_template :layout => "layouts/item"
+ assert_includes @response.body, 'item.erb'
end
def test_layout_only_exception_when_excepted
@controller = OnlyLayoutController.new
get :goodbye
- assert !@response.body.include?("item.erb"), "#{@response.body.inspect} included 'item.erb'"
+ assert_not_includes @response.body, 'item.erb'
end
def test_layout_except_exception_when_included
@controller = ExceptLayoutController.new
get :hello
- assert_template :layout => "layouts/item"
+ assert_includes @response.body, 'item.erb'
end
def test_layout_except_exception_when_excepted
@controller = ExceptLayoutController.new
get :goodbye
- assert !@response.body.include?("item.erb"), "#{@response.body.inspect} included 'item.erb'"
+ assert_not_includes @response.body, 'item.erb'
end
def test_layout_set_when_using_render
with_template_handler :mab, lambda { |template| template.source.inspect } do
@controller = SetsLayoutInRenderController.new
get :hello
- assert_template :layout => "layouts/third_party_template_library"
+ assert_includes @response.body, 'layouts/third_party_template_library.mab'
end
end
def test_layout_is_not_set_when_none_rendered
@controller = RendersNoLayoutController.new
get :hello
- assert_template :layout => nil
+ assert_equal 'hello.erb', @response.body
end
def test_layout_is_picked_from_the_controller_instances_view_path
@controller = PrependsViewPathController.new
get :hello
- assert_template :layout => /layouts\/alt/
+ assert_includes @response.body, 'alt.erb'
end
def test_absolute_pathed_layout
@@ -262,7 +262,7 @@ unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
@controller = LayoutSymlinkedTest.new
get :hello
assert_response 200
- assert_template :layout => "layouts/symlinked/symlinked_layout"
+ assert_includes @response.body, 'This is my layout'
end
end
end
diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb
index 8b47536a18..8d048ddbcb 100644
--- a/actionview/test/actionpack/controller/render_test.rb
+++ b/actionview/test/actionpack/controller/render_test.rb
@@ -1,5 +1,5 @@
require 'abstract_unit'
-require "active_model"
+require 'active_model'
class ApplicationController < ActionController::Base
self.view_paths = File.join(FIXTURE_LOAD_PATH, "actionpack")
@@ -121,7 +121,7 @@ class TestController < ApplicationController
# :ported:
def render_hello_world_from_variable
@person = "david"
- render :text => "hello #{@person}"
+ render plain: "hello #{@person}"
end
# :ported:
@@ -143,13 +143,13 @@ class TestController < ApplicationController
# :ported:
def render_text_hello_world
- render :text => "hello world"
+ render plain: "hello world"
end
# :ported:
def render_text_hello_world_with_layout
@variable_for_layout = ", I am here!"
- render :text => "hello world", :layout => true
+ render plain: "hello world", :layout => true
end
def hello_world_with_layout_false
@@ -212,26 +212,26 @@ class TestController < ApplicationController
# :ported:
def render_custom_code
- render :text => "hello world", :status => 404
+ render plain: "hello world", :status => 404
end
# :ported:
def render_text_with_nil
- render :text => nil
+ render plain: nil
end
# :ported:
def render_text_with_false
- render :text => false
+ render plain: false
end
def render_text_with_resource
- render :text => Customer.new("David")
+ render plain: Customer.new("David")
end
# :ported:
def render_nothing_with_appendix
- render :text => "appended"
+ render plain: "appended"
end
# This test is testing 3 things:
@@ -262,7 +262,7 @@ class TestController < ApplicationController
# :ported:
def blank_response
- render :text => ' '
+ render plain: ' '
end
# :ported:
@@ -294,7 +294,7 @@ class TestController < ApplicationController
def hello_in_a_string
@customers = [ Customer.new("david"), Customer.new("mary") ]
- render :text => "How's there? " + render_to_string(:template => "test/list")
+ render plain: "How's there? " + render_to_string(:template => "test/list")
end
def accessing_params_in_template
@@ -357,12 +357,12 @@ class TestController < ApplicationController
end
def rendering_nothing_on_layout
- render :nothing => true
+ head :ok
end
def render_to_string_with_assigns
@before = "i'm before the render"
- render_to_string :text => "foo"
+ render_to_string plain: "foo"
@after = "i'm after the render"
render :template => "test/hello_world"
end
@@ -409,8 +409,8 @@ class TestController < ApplicationController
# :ported:
def double_render
- render :text => "hello"
- render :text => "world"
+ render plain: "hello"
+ render plain: "world"
end
def double_redirect
@@ -419,13 +419,13 @@ class TestController < ApplicationController
end
def render_and_redirect
- render :text => "hello"
+ render plain: "hello"
redirect_to :action => "double_render"
end
def render_to_string_and_render
- @stuff = render_to_string :text => "here is some cached stuff"
- render :text => "Hi web users! #{@stuff}"
+ @stuff = render_to_string plain: "here is some cached stuff"
+ render plain: "Hi web users! #{@stuff}"
end
def render_to_string_with_inline_and_render
@@ -454,7 +454,7 @@ class TestController < ApplicationController
# :addressed:
def render_text_with_assigns
@hello = "world"
- render :text => "foo"
+ render plain: "foo"
end
def render_with_assigns_option
@@ -467,7 +467,7 @@ class TestController < ApplicationController
def render_content_type_from_body
response.content_type = Mime::RSS
- render :text => "hello world!"
+ render body: "hello world!"
end
def render_using_layout_around_block
@@ -683,20 +683,19 @@ class RenderTest < ActionController::TestCase
get :hello_world
assert_response 200
assert_response :success
- assert_template "test/hello_world"
assert_equal "<html>Hello world!</html>", @response.body
end
# :ported:
def test_renders_default_template_for_missing_action
get :'hyphen-ated'
- assert_template 'test/hyphen-ated'
+ assert_equal "hyphen-ated.erb", @response.body
end
# :ported:
def test_render
get :render_hello_world
- assert_template "test/hello_world"
+ assert_equal "Hello world!", @response.body
end
def test_line_offset
@@ -712,20 +711,18 @@ class RenderTest < ActionController::TestCase
# :ported: compatibility
def test_render_with_forward_slash
get :render_hello_world_with_forward_slash
- assert_template "test/hello_world"
+ assert_equal "Hello world!", @response.body
end
# :ported:
def test_render_in_top_directory
get :render_template_in_top_directory
- assert_template "shared"
assert_equal "Elastica", @response.body
end
# :ported:
def test_render_in_top_directory_with_slash
get :render_template_in_top_directory_with_slash
- assert_template "shared"
assert_equal "Elastica", @response.body
end
@@ -743,7 +740,7 @@ class RenderTest < ActionController::TestCase
# :ported:
def test_render_action
get :render_action_hello_world
- assert_template "test/hello_world"
+ assert_equal "Hello world!", @response.body
end
def test_render_action_upcased
@@ -756,13 +753,12 @@ class RenderTest < ActionController::TestCase
def test_render_action_hello_world_as_string
get :render_action_hello_world_as_string
assert_equal "Hello world!", @response.body
- assert_template "test/hello_world"
end
# :ported:
def test_render_action_with_symbol
get :render_action_hello_world_with_symbol
- assert_template "test/hello_world"
+ assert_equal "Hello world!", @response.body
end
# :ported:
@@ -774,7 +770,7 @@ class RenderTest < ActionController::TestCase
# :ported:
def test_do_with_render_text_and_layout
get :render_text_hello_world_with_layout
- assert_equal "<html>hello world, I am here!</html>", @response.body
+ assert_equal "{{hello world, I am here!}}\n", @response.body
end
# :ported:
@@ -950,7 +946,7 @@ class RenderTest < ActionController::TestCase
def test_render_to_string_inline
get :render_to_string_with_inline_and_render
- assert_template "test/hello_world"
+ assert_equal 'Hello world!', @response.body
end
# :ported:
@@ -1035,8 +1031,8 @@ class RenderTest < ActionController::TestCase
def test_render_to_string_doesnt_break_assigns
get :render_to_string_with_assigns
- assert_equal "i'm before the render", assigns(:before)
- assert_equal "i'm after the render", assigns(:after)
+ assert_equal "i'm before the render", @controller.instance_variable_get(:@before)
+ assert_equal "i'm after the render", @controller.instance_variable_get(:@after)
end
def test_bad_render_to_string_still_throws_exception
@@ -1045,8 +1041,8 @@ class RenderTest < ActionController::TestCase
def test_render_to_string_that_throws_caught_exception_doesnt_break_assigns
assert_nothing_raised { get :render_to_string_with_caught_exception }
- assert_equal "i'm before the render", assigns(:before)
- assert_equal "i'm after the render", assigns(:after)
+ assert_equal "i'm before the render", @controller.instance_variable_get(:@before)
+ assert_equal "i'm after the render", @controller.instance_variable_get(:@after)
end
def test_accessing_params_in_template_with_layout
@@ -1107,7 +1103,7 @@ class RenderTest < ActionController::TestCase
# :addressed:
def test_render_text_with_assigns
get :render_text_with_assigns
- assert_equal "world", assigns["hello"]
+ assert_equal "world", @controller.instance_variable_get(:@hello)
end
def test_render_text_with_assigns_option
@@ -1173,22 +1169,22 @@ class RenderTest < ActionController::TestCase
def test_render_to_string_partial
get :render_to_string_with_partial
- assert_equal "only partial", assigns(:partial_only)
- assert_equal "Hello: david", assigns(:partial_with_locals)
+ assert_equal "only partial", @controller.instance_variable_get(:@partial_only)
+ assert_equal "Hello: david", @controller.instance_variable_get(:@partial_with_locals)
assert_equal "text/html", @response.content_type
end
def test_render_to_string_with_template_and_html_partial
get :render_to_string_with_template_and_html_partial
- assert_equal "**only partial**\n", assigns(:text)
- assert_equal "<strong>only partial</strong>\n", assigns(:html)
+ assert_equal "**only partial**\n", @controller.instance_variable_get(:@text)
+ assert_equal "<strong>only partial</strong>\n", @controller.instance_variable_get(:@html)
assert_equal "<strong>only html partial</strong>\n", @response.body
assert_equal "text/html", @response.content_type
end
def test_render_to_string_and_render_with_different_formats
get :render_to_string_and_render_with_different_formats
- assert_equal "<strong>only partial</strong>\n", assigns(:html)
+ assert_equal "<strong>only partial</strong>\n", @controller.instance_variable_get(:@html)
assert_equal "**only partial**\n", @response.body
assert_equal "text/plain", @response.content_type
end
@@ -1212,21 +1208,18 @@ class RenderTest < ActionController::TestCase
def test_partial_with_form_builder
get :partial_with_form_builder
- assert_match(/<label/, @response.body)
- assert_template('test/_form')
+ assert_equal "<label for=\"post_title\">Title</label>\n", @response.body
end
def test_partial_with_form_builder_subclass
get :partial_with_form_builder_subclass
- assert_match(/<label/, @response.body)
- assert_template('test/_labelling_form')
+ assert_equal "<label for=\"post_title\">Title</label>\n", @response.body
end
def test_nested_partial_with_form_builder
@controller = Fun::GamesController.new
get :nested_partial_with_form_builder
- assert_match(/<label/, @response.body)
- assert_template('fun/games/_form')
+ assert_equal "<label for=\"post_title\">Title</label>\n", @response.body
end
def test_namespaced_object_partial
@@ -1270,48 +1263,29 @@ class RenderTest < ActionController::TestCase
assert_equal "Bonjour: davidBonjour: mary", @response.body
end
- def test_locals_option_to_assert_template_is_not_supported
- get :partial_collection_with_locals
-
- warning_buffer = StringIO.new
- $stderr = warning_buffer
-
- assert_template partial: 'customer_greeting', locals: { greeting: 'Bonjour' }
- assert_equal "the :locals option to #assert_template is only supported in a ActionView::TestCase\n", warning_buffer.string
- ensure
- $stderr = STDERR
- end
-
def test_partial_collection_with_spacer
get :partial_collection_with_spacer
assert_equal "Hello: davidonly partialHello: mary", @response.body
- assert_template :partial => '_customer'
end
def test_partial_collection_with_spacer_which_uses_render
get :partial_collection_with_spacer_which_uses_render
assert_equal "Hello: davidpartial html\npartial with partial\nHello: mary", @response.body
- assert_template :partial => '_customer'
end
def test_partial_collection_shorthand_with_locals
get :partial_collection_shorthand_with_locals
assert_equal "Bonjour: davidBonjour: mary", @response.body
- assert_template :partial => 'customers/_customer', :count => 2
- assert_template :partial => '_completely_fake_and_made_up_template_that_cannot_possibly_be_rendered', :count => 0
end
def test_partial_collection_shorthand_with_different_types_of_records
get :partial_collection_shorthand_with_different_types_of_records
assert_equal "Bonjour bad customer: mark0Bonjour good customer: craig1Bonjour bad customer: john2Bonjour good customer: zach3Bonjour good customer: brandon4Bonjour bad customer: dan5", @response.body
- assert_template :partial => 'good_customers/_good_customer', :count => 3
- assert_template :partial => 'bad_customers/_bad_customer', :count => 3
end
def test_empty_partial_collection
get :empty_partial_collection
assert_equal " ", @response.body
- assert_template :partial => false
end
def test_partial_with_hash_object
diff --git a/actionview/test/actionpack/controller/view_paths_test.rb b/actionview/test/actionpack/controller/view_paths_test.rb
index 7fba9ff8ff..e99659c802 100644
--- a/actionview/test/actionpack/controller/view_paths_test.rb
+++ b/actionview/test/actionpack/controller/view_paths_test.rb
@@ -23,8 +23,8 @@ class ViewLoadPathsTest < ActionController::TestCase
end
def setup
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
+ @request = ActionController::TestRequest.create
+ @response = ActionDispatch::TestResponse.new
@controller = TestController.new
@paths = TestController.view_paths
end
diff --git a/actionview/test/activerecord/form_helper_activerecord_test.rb b/actionview/test/activerecord/form_helper_activerecord_test.rb
index 0a62f49f35..2769b97445 100644
--- a/actionview/test/activerecord/form_helper_activerecord_test.rb
+++ b/actionview/test/activerecord/form_helper_activerecord_test.rb
@@ -35,10 +35,6 @@ class FormHelperActiveRecordTest < ActionView::TestCase
end
end
- def _routes
- Routes
- end
-
include Routes.url_helpers
def test_nested_fields_for_with_child_index_option_override_on_a_nested_attributes_collection_association
diff --git a/actionview/test/activerecord/render_partial_with_record_identification_test.rb b/actionview/test/activerecord/render_partial_with_record_identification_test.rb
index 409370104d..9772ebb39e 100644
--- a/actionview/test/activerecord/render_partial_with_record_identification_test.rb
+++ b/actionview/test/activerecord/render_partial_with_record_identification_test.rb
@@ -52,43 +52,37 @@ class RenderPartialWithRecordIdentificationTest < ActiveRecordTestCase
def test_rendering_partial_with_has_many_and_belongs_to_association
get :render_with_has_many_and_belongs_to_association
- assert_template 'projects/_project'
- assert_equal assigns(:developer).projects.map(&:name).join, @response.body
+ assert_equal Developer.find(1).projects.map(&:name).join, @response.body
end
def test_rendering_partial_with_has_many_association
get :render_with_has_many_association
- assert_template 'replies/_reply'
assert_equal 'Birdman is better!', @response.body
end
def test_rendering_partial_with_scope
get :render_with_scope
- assert_template 'replies/_reply'
assert_equal 'Birdman is better!Nuh uh!', @response.body
end
def test_render_with_record
get :render_with_record
- assert_template 'developers/_developer'
assert_equal 'David', @response.body
end
def test_render_with_record_collection
get :render_with_record_collection
- assert_template 'developers/_developer'
assert_equal 'DavidJamisfixture_3fixture_4fixture_5fixture_6fixture_7fixture_8fixture_9fixture_10Jamis', @response.body
end
def test_render_with_record_collection_and_spacer_template
get :render_with_record_collection_and_spacer_template
- assert_equal assigns(:developer).projects.map(&:name).join('only partial'), @response.body
+ assert_equal Developer.find(1).projects.map(&:name).join('only partial'), @response.body
end
def test_rendering_partial_with_has_one_association
mascot = Company.find(1).mascot
get :render_with_has_one_association
- assert_template 'mascots/_mascot'
assert_equal mascot.name, @response.body
end
end
@@ -130,13 +124,11 @@ class RenderPartialWithRecordIdentificationAndNestedControllersTest < ActiveReco
def test_render_with_record_in_nested_controller
get :render_with_record_in_nested_controller
- assert_template %r{\Afun/games/_game\Z}
assert_equal "Fun Pong\n", @response.body
end
def test_render_with_record_collection_in_nested_controller
get :render_with_record_collection_in_nested_controller
- assert_template %r{\Afun/games/_game\Z}
assert_equal "Fun Pong\nFun Tank\n", @response.body
end
end
@@ -149,7 +141,6 @@ class RenderPartialWithRecordIdentificationAndNestedControllersWithoutPrefixTest
ActionView::Base.prefix_partial_path_with_controller_namespace = false
get :render_with_record_in_nested_controller
- assert_template %r{\Agames/_game\Z}
assert_equal "Just Pong\n", @response.body
ensure
ActionView::Base.prefix_partial_path_with_controller_namespace = old_config
@@ -160,7 +151,6 @@ class RenderPartialWithRecordIdentificationAndNestedControllersWithoutPrefixTest
ActionView::Base.prefix_partial_path_with_controller_namespace = false
get :render_with_record_collection_in_nested_controller
- assert_template %r{\Agames/_game\Z}
assert_equal "Just Pong\nJust Tank\n", @response.body
ensure
ActionView::Base.prefix_partial_path_with_controller_namespace = old_config
@@ -172,13 +162,11 @@ class RenderPartialWithRecordIdentificationAndNestedDeeperControllersTest < Acti
def test_render_with_record_in_deeper_nested_controller
get :render_with_record_in_deeper_nested_controller
- assert_template %r{\Afun/serious/games/_game\Z}
assert_equal "Serious Chess\n", @response.body
end
def test_render_with_record_collection_in_deeper_nested_controller
get :render_with_record_collection_in_deeper_nested_controller
- assert_template %r{\Afun/serious/games/_game\Z}
assert_equal "Serious Chess\nSerious Sudoku\nSerious Solitaire\n", @response.body
end
end
@@ -191,7 +179,6 @@ class RenderPartialWithRecordIdentificationAndNestedDeeperControllersWithoutPref
ActionView::Base.prefix_partial_path_with_controller_namespace = false
get :render_with_record_in_deeper_nested_controller
- assert_template %r{\Agames/_game\Z}
assert_equal "Just Chess\n", @response.body
ensure
ActionView::Base.prefix_partial_path_with_controller_namespace = old_config
@@ -202,7 +189,6 @@ class RenderPartialWithRecordIdentificationAndNestedDeeperControllersWithoutPref
ActionView::Base.prefix_partial_path_with_controller_namespace = false
get :render_with_record_collection_in_deeper_nested_controller
- assert_template %r{\Agames/_game\Z}
assert_equal "Just Chess\nJust Sudoku\nJust Solitaire\n", @response.body
ensure
ActionView::Base.prefix_partial_path_with_controller_namespace = old_config
diff --git a/actionview/test/fixtures/actionpack/layout_tests/alt/layouts/alt.erb b/actionview/test/fixtures/actionpack/layout_tests/alt/layouts/alt.erb
index e69de29bb2..60b81525b5 100644
--- a/actionview/test/fixtures/actionpack/layout_tests/alt/layouts/alt.erb
+++ b/actionview/test/fixtures/actionpack/layout_tests/alt/layouts/alt.erb
@@ -0,0 +1 @@
+alt.erb
diff --git a/actionview/test/fixtures/actionpack/layouts/standard.text.erb b/actionview/test/fixtures/actionpack/layouts/standard.text.erb
new file mode 100644
index 0000000000..a58afb1aa2
--- /dev/null
+++ b/actionview/test/fixtures/actionpack/layouts/standard.text.erb
@@ -0,0 +1 @@
+{{<%= yield %><%= @variable_for_layout %>}}
diff --git a/actionview/test/fixtures/actionpack/test/hyphen-ated.erb b/actionview/test/fixtures/actionpack/test/hyphen-ated.erb
index cd0875583a..28dbe94ee1 100644
--- a/actionview/test/fixtures/actionpack/test/hyphen-ated.erb
+++ b/actionview/test/fixtures/actionpack/test/hyphen-ated.erb
@@ -1 +1 @@
-Hello world!
+hyphen-ated.erb \ No newline at end of file
diff --git a/actionview/test/fixtures/digestor/events/_completed.html.erb b/actionview/test/fixtures/digestor/events/_completed.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionview/test/fixtures/digestor/events/_completed.html.erb
diff --git a/actionview/test/fixtures/digestor/events/index.html.erb b/actionview/test/fixtures/digestor/events/index.html.erb
new file mode 100644
index 0000000000..bc45e41bcb
--- /dev/null
+++ b/actionview/test/fixtures/digestor/events/index.html.erb
@@ -0,0 +1 @@
+<% # Template Dependency: events/* %> \ No newline at end of file
diff --git a/actionview/test/fixtures/test/_partial_name_in_local_assigns.erb b/actionview/test/fixtures/test/_partial_name_in_local_assigns.erb
new file mode 100644
index 0000000000..28ee9f41c5
--- /dev/null
+++ b/actionview/test/fixtures/test/_partial_name_in_local_assigns.erb
@@ -0,0 +1 @@
+<%= local_assigns.has_key?(:partial_name_in_local_assigns) %> \ No newline at end of file
diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb
index 02dce71496..01fc66bed6 100644
--- a/actionview/test/template/asset_tag_helper_test.rb
+++ b/actionview/test/template/asset_tag_helper_test.rb
@@ -310,6 +310,11 @@ class AssetTagHelperTest < ActionView::TestCase
AssetPathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
+ def test_asset_path_tag_raises_an_error_for_nil_source
+ e = assert_raise(ArgumentError) { asset_path(nil) }
+ assert_equal("nil is not a valid asset source", e.message)
+ end
+
def test_asset_path_tag_to_not_create_duplicate_slashes
@controller.config.asset_host = "host/"
assert_dom_equal('http://host/foo', asset_path("foo"))
@@ -464,6 +469,14 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal({:size => '16x10'}, options)
end
+ def test_image_tag_raises_an_error_for_competing_size_arguments
+ exception = assert_raise(ArgumentError) do
+ image_tag("gold.png", :height => "100", :width => "200", :size => "45x70")
+ end
+
+ assert_equal("Cannot pass a :size option with a :height or :width option", exception.message)
+ end
+
def test_favicon_link_tag
FaviconLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
diff --git a/actionview/test/template/atom_feed_helper_test.rb b/actionview/test/template/atom_feed_helper_test.rb
index 525d99750d..591cd71404 100644
--- a/actionview/test/template/atom_feed_helper_test.rb
+++ b/actionview/test/template/atom_feed_helper_test.rb
@@ -14,7 +14,7 @@ class ScrollsController < ActionController::Base
FEEDS["defaults"] = <<-EOT
atom_feed(:schema_date => '2008') do |feed|
feed.title("My great blog!")
- feed.updated((@scrolls.first.created_at))
+ feed.updated(@scrolls.first.created_at)
@scrolls.each do |scroll|
feed.entry(scroll) do |entry|
@@ -31,7 +31,7 @@ class ScrollsController < ActionController::Base
FEEDS["entry_options"] = <<-EOT
atom_feed do |feed|
feed.title("My great blog!")
- feed.updated((@scrolls.first.created_at))
+ feed.updated(@scrolls.first.created_at)
@scrolls.each do |scroll|
feed.entry(scroll, :url => "/otherstuff/" + scroll.to_param.to_s, :updated => Time.utc(2007, 1, scroll.id)) do |entry|
@@ -48,7 +48,7 @@ class ScrollsController < ActionController::Base
FEEDS["entry_type_options"] = <<-EOT
atom_feed(:schema_date => '2008') do |feed|
feed.title("My great blog!")
- feed.updated((@scrolls.first.created_at))
+ feed.updated(@scrolls.first.created_at)
@scrolls.each do |scroll|
feed.entry(scroll, :type => 'text/xml') do |entry|
@@ -65,7 +65,7 @@ class ScrollsController < ActionController::Base
FEEDS["entry_url_false_option"] = <<-EOT
atom_feed do |feed|
feed.title("My great blog!")
- feed.updated((@scrolls.first.created_at))
+ feed.updated(@scrolls.first.created_at)
@scrolls.each do |scroll|
feed.entry(scroll, :url => false) do |entry|
@@ -82,7 +82,7 @@ class ScrollsController < ActionController::Base
FEEDS["xml_block"] = <<-EOT
atom_feed do |feed|
feed.title("My great blog!")
- feed.updated((@scrolls.first.created_at))
+ feed.updated(@scrolls.first.created_at)
feed.author do |author|
author.name("DHH")
@@ -100,7 +100,7 @@ class ScrollsController < ActionController::Base
atom_feed({'xmlns:app' => 'http://www.w3.org/2007/app',
'xmlns:openSearch' => 'http://a9.com/-/spec/opensearch/1.1/'}) do |feed|
feed.title("My great blog!")
- feed.updated((@scrolls.first.created_at))
+ feed.updated(@scrolls.first.created_at)
@scrolls.each do |scroll|
feed.entry(scroll) do |entry|
@@ -118,7 +118,7 @@ class ScrollsController < ActionController::Base
FEEDS["feed_with_overridden_ids"] = <<-EOT
atom_feed({:id => 'tag:test.rubyonrails.org,2008:test/'}) do |feed|
feed.title("My great blog!")
- feed.updated((@scrolls.first.created_at))
+ feed.updated(@scrolls.first.created_at)
@scrolls.each do |scroll|
feed.entry(scroll, :id => "tag:test.rubyonrails.org,2008:"+scroll.id.to_s) do |entry|
@@ -137,7 +137,7 @@ class ScrollsController < ActionController::Base
atom_feed(:schema_date => '2008',
:instruct => {'xml-stylesheet' => { :href=> 't.css', :type => 'text/css' }}) do |feed|
feed.title("My great blog!")
- feed.updated((@scrolls.first.created_at))
+ feed.updated(@scrolls.first.created_at)
@scrolls.each do |scroll|
feed.entry(scroll) do |entry|
@@ -155,7 +155,7 @@ class ScrollsController < ActionController::Base
atom_feed(:schema_date => '2008',
:instruct => {'target1' => [{ :a => '1', :b => '2' }, { :c => '3', :d => '4' }]}) do |feed|
feed.title("My great blog!")
- feed.updated((@scrolls.first.created_at))
+ feed.updated(@scrolls.first.created_at)
@scrolls.each do |scroll|
feed.entry(scroll) do |entry|
@@ -172,7 +172,7 @@ class ScrollsController < ActionController::Base
FEEDS["feed_with_xhtml_content"] = <<-'EOT'
atom_feed do |feed|
feed.title("My great blog!")
- feed.updated((@scrolls.first.created_at))
+ feed.updated(@scrolls.first.created_at)
@scrolls.each do |scroll|
feed.entry(scroll) do |entry|
@@ -197,7 +197,7 @@ class ScrollsController < ActionController::Base
new_xml = Builder::XmlMarkup.new(:target=>'')
atom_feed(:xml => new_xml) do |feed|
feed.title("My great blog!")
- feed.updated((@scrolls.first.created_at))
+ feed.updated(@scrolls.first.created_at)
@scrolls.each do |scroll|
feed.entry(scroll) do |entry|
diff --git a/actionview/test/template/capture_helper_test.rb b/actionview/test/template/capture_helper_test.rb
index f213da5934..1e099d482c 100644
--- a/actionview/test/template/capture_helper_test.rb
+++ b/actionview/test/template/capture_helper_test.rb
@@ -210,10 +210,4 @@ class CaptureHelperTest < ActionView::TestCase
def alt_encoding(output_buffer)
output_buffer.encoding == Encoding::US_ASCII ? Encoding::UTF_8 : Encoding::US_ASCII
end
-
- def view_with_controller
- TestController.new.view_context.tap do |view|
- view.output_buffer = ActionView::OutputBuffer.new
- end
- end
end
diff --git a/actionview/test/template/controller_helper_test.rb b/actionview/test/template/controller_helper_test.rb
new file mode 100644
index 0000000000..b5e94ea4f1
--- /dev/null
+++ b/actionview/test/template/controller_helper_test.rb
@@ -0,0 +1,21 @@
+require 'abstract_unit'
+
+class ControllerHelperTest < ActionView::TestCase
+ tests ActionView::Helpers::ControllerHelper
+
+ class SpecializedFormBuilder < ActionView::Helpers::FormBuilder ; end
+
+ def test_assign_controller_sets_default_form_builder
+ @controller = OpenStruct.new(default_form_builder: SpecializedFormBuilder)
+ assign_controller(@controller)
+
+ assert_equal SpecializedFormBuilder, self.default_form_builder
+ end
+
+ def test_assign_controller_skips_default_form_builder
+ @controller = OpenStruct.new
+ assign_controller(@controller)
+
+ assert_nil self.default_form_builder
+ end
+end
diff --git a/actionview/test/template/date_helper_test.rb b/actionview/test/template/date_helper_test.rb
index bfb073680e..9212420ec9 100644
--- a/actionview/test/template/date_helper_test.rb
+++ b/actionview/test/template/date_helper_test.rb
@@ -130,7 +130,7 @@ class DateHelperTest < ActionView::TestCase
def test_distance_in_words_with_mathn_required
# test we avoid Integer#/ (redefined by mathn)
- require 'mathn'
+ silence_warnings { require "mathn" }
from = Time.utc(2004, 6, 6, 21, 45, 0)
assert_distance_of_time_in_words(from)
end
@@ -3217,12 +3217,4 @@ class DateHelperTest < ActionView::TestCase
expected = '<time datetime="2013-02-20T00:00:00+00:00">20 Feb 00:00</time>'
assert_equal expected, time_tag(time, :format => :short)
end
-
- protected
- def with_env_tz(new_tz = 'US/Eastern')
- old_tz, ENV['TZ'] = ENV['TZ'], new_tz
- yield
- ensure
- old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
- end
end
diff --git a/actionview/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb
index c2b8439df3..dde757b5a2 100644
--- a/actionview/test/template/digestor_test.rb
+++ b/actionview/test/template/digestor_test.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'fileutils'
+require 'action_view/dependency_tracker'
class FixtureTemplate
attr_reader :source, :handler
@@ -15,12 +16,13 @@ end
class FixtureFinder
FIXTURES_DIR = "#{File.dirname(__FILE__)}/../fixtures/digestor"
- attr_reader :details
+ attr_reader :details, :view_paths
attr_accessor :formats
attr_accessor :variants
def initialize
@details = {}
+ @view_paths = ActionView::PathSet.new(['digestor'])
@formats = []
@variants = []
end
@@ -75,6 +77,34 @@ class TemplateDigestorTest < ActionView::TestCase
end
end
+ def test_explicit_dependency_wildcard
+ assert_digest_difference("events/index") do
+ change_template("events/_completed")
+ end
+ end
+
+ def test_explicit_dependency_wildcard_picks_up_added_file
+ old_caching, ActionView::Resolver.caching = ActionView::Resolver.caching, false
+
+ assert_digest_difference("events/index") do
+ add_template("events/_uncompleted")
+ end
+ ensure
+ remove_template("events/_uncompleted")
+ ActionView::Resolver.caching = old_caching
+ end
+
+ def test_explicit_dependency_wildcard_picks_up_removed_file
+ old_caching, ActionView::Resolver.caching = ActionView::Resolver.caching, false
+ add_template("events/_subscribers_changed")
+
+ assert_digest_difference("events/index") do
+ remove_template("events/_subscribers_changed")
+ end
+ ensure
+ ActionView::Resolver.caching = old_caching
+ end
+
def test_second_level_dependency
assert_digest_difference("messages/show") do
change_template("comments/_comments")
@@ -111,6 +141,18 @@ class TemplateDigestorTest < ActionView::TestCase
end
end
+ def test_logging_of_missing_template_for_dependencies
+ assert_logged "'messages/something_missing' file doesn't exist, so no dependencies" do
+ dependencies("messages/something_missing")
+ end
+ end
+
+ def test_logging_of_missing_template_for_nested_dependencies
+ assert_logged "'messages/something_missing' file doesn't exist, so no dependencies" do
+ nested_dependencies("messages/something_missing")
+ end
+ end
+
def test_nested_template_directory
assert_digest_difference("messages/show") do
change_template("messages/actions/_move")
@@ -207,7 +249,7 @@ class TemplateDigestorTest < ActionView::TestCase
end
def test_variants
- assert_digest_difference("messages/new", false, variants: [:iphone]) do
+ assert_digest_difference("messages/new", variants: [:iphone]) do
change_template("messages/new", :iphone)
change_template("messages/_header", :iphone)
end
@@ -227,16 +269,6 @@ class TemplateDigestorTest < ActionView::TestCase
assert_not_equal digest_phone, digest_fridge_phone
end
- def test_cache_template_loading
- resolver_before = ActionView::Resolver.caching
- ActionView::Resolver.caching = false
- assert_digest_difference("messages/edit", true) do
- change_template("comments/_comment")
- end
- ensure
- ActionView::Resolver.caching = resolver_before
- end
-
def test_digest_cache_cleanup_with_recursion
first_digest = digest("level/_recursion")
second_digest = digest("level/_recursion")
@@ -279,9 +311,9 @@ class TemplateDigestorTest < ActionView::TestCase
end
end
- def assert_digest_difference(template_name, persistent = false, options = {})
+ def assert_digest_difference(template_name, options = {})
previous_digest = digest(template_name, options)
- ActionView::Digestor.cache.clear unless persistent
+ ActionView::Digestor.cache.clear
yield
@@ -298,6 +330,14 @@ class TemplateDigestorTest < ActionView::TestCase
ActionView::Digestor.digest({ name: template_name, finder: finder }.merge(options))
end
+ def dependencies(template_name)
+ ActionView::Digestor.new({ name: template_name, finder: finder }).dependencies
+ end
+
+ def nested_dependencies(template_name)
+ ActionView::Digestor.new({ name: template_name, finder: finder }).nested_dependencies
+ end
+
def finder
@finder ||= FixtureFinder.new
end
@@ -309,4 +349,9 @@ class TemplateDigestorTest < ActionView::TestCase
f.write "\nTHIS WAS CHANGED!"
end
end
+ alias_method :add_template, :change_template
+
+ def remove_template(template_name)
+ File.delete("digestor/#{template_name}.html.erb")
+ end
end
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index 5c55b154d3..b8cb5bd746 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -3269,6 +3269,30 @@ class FormHelperTest < ActionView::TestCase
ActionView::Base.default_form_builder = old_default_form_builder
end
+ def test_form_builder_override
+ self.default_form_builder = LabelledFormBuilder
+
+ output_buffer = fields_for(:post, @post) do |f|
+ concat f.text_field(:title)
+ end
+
+ expected = "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>"
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_lazy_loading_form_builder_override
+ self.default_form_builder = "FormHelperTest::LabelledFormBuilder"
+
+ output_buffer = fields_for(:post, @post) do |f|
+ concat f.text_field(:title)
+ end
+
+ expected = "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>"
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_fields_for_with_labelled_builder
output_buffer = fields_for(:post, @post, builder: LabelledFormBuilder) do |f|
concat f.text_field(:title)
diff --git a/actionview/test/template/form_options_helper_test.rb b/actionview/test/template/form_options_helper_test.rb
index d25fa3706f..d7daba8bf3 100644
--- a/actionview/test/template/form_options_helper_test.rb
+++ b/actionview/test/template/form_options_helper_test.rb
@@ -645,6 +645,13 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_select_with_include_blank_false_and_required
+ @post = Post.new
+ @post.category = "<mus>"
+ e = assert_raises(ArgumentError) { select("post", "category", %w( abe <mus> hest), { include_blank: false }, required: 'required') }
+ assert_match(/include_blank cannot be false for a required field./, e.message)
+ end
+
def test_select_with_blank_as_string
@post = Post.new
@post.category = "<mus>"
diff --git a/actionview/test/template/form_tag_helper_test.rb b/actionview/test/template/form_tag_helper_test.rb
index cad1c82309..f602c82c42 100644
--- a/actionview/test/template/form_tag_helper_test.rb
+++ b/actionview/test/template/form_tag_helper_test.rb
@@ -510,6 +510,13 @@ class FormTagHelperTest < ActionView::TestCase
)
end
+ def test_button_tag_with_data_disable_with_option
+ assert_dom_equal(
+ %(<button name="button" type="submit" data-disable-with="Please wait...">Checkout</button>),
+ button_tag("Checkout", data: { disable_with: "Please wait..." })
+ )
+ end
+
def test_image_submit_tag_with_confirmation
assert_dom_equal(
%(<input alt="Save" type="image" src="/images/save.gif" data-confirm="Are you sure?" />),
diff --git a/actionview/test/template/record_tag_helper_test.rb b/actionview/test/template/record_tag_helper_test.rb
index 4b7b653916..bfc5d04bed 100644
--- a/actionview/test/template/record_tag_helper_test.rb
+++ b/actionview/test/template/record_tag_helper_test.rb
@@ -2,7 +2,7 @@ require 'abstract_unit'
class RecordTagPost
extend ActiveModel::Naming
- include ActiveModel::Conversion
+
attr_accessor :id, :body
def initialize
@@ -14,7 +14,6 @@ class RecordTagPost
end
class RecordTagHelperTest < ActionView::TestCase
- include RenderERBUtils
tests ActionView::Helpers::RecordTagHelper
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index 22665b6844..9c2c9507b7 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -7,7 +7,10 @@ end
module RenderTestCases
def setup_view(paths)
@assigns = { :secret => 'in the sauce' }
- @view = ActionView::Base.new(paths, @assigns)
+ @view = Class.new(ActionView::Base) do
+ def view_cache_dependencies; end
+ end.new(paths, @assigns)
+
@controller_view = TestController.new.view_context
# Reload and register danish language for testing
@@ -277,6 +280,14 @@ module RenderTestCases
assert_nil @view.render(:partial => "test/customer", :collection => nil)
end
+ def test_render_partial_without_object_does_not_put_partial_name_to_local_assigns
+ assert_equal 'false', @view.render(partial: 'test/partial_name_in_local_assigns')
+ end
+
+ def test_render_partial_with_nil_object_puts_partial_name_to_local_assigns
+ assert_equal 'true', @view.render(partial: 'test/partial_name_in_local_assigns', object: nil)
+ end
+
def test_render_partial_with_nil_values_in_collection
assert_equal "Hello: davidHello: Anonymous", @view.render(:partial => "test/customer", :collection => [ Customer.new("david"), nil ])
end
@@ -608,7 +619,7 @@ class CachedCollectionViewRenderTest < CachedViewRenderTest
test "with custom key" do
customer = Customer.new("david")
- key = ActionController::Base.new.fragment_cache_key([customer, 'key'])
+ key = cache_key([customer, 'key'], "test/_customer")
ActionView::PartialRenderer.collection_cache.write(key, 'Hello')
@@ -618,7 +629,7 @@ class CachedCollectionViewRenderTest < CachedViewRenderTest
test "automatic caching with inferred cache name" do
customer = CachedCustomer.new("david")
- key = ActionController::Base.new.fragment_cache_key(customer)
+ key = cache_key(customer, "test/_cached_customer")
ActionView::PartialRenderer.collection_cache.write(key, 'Cached')
@@ -628,11 +639,17 @@ class CachedCollectionViewRenderTest < CachedViewRenderTest
test "automatic caching with as name" do
customer = CachedCustomer.new("david")
- key = ActionController::Base.new.fragment_cache_key(customer)
+ key = cache_key(customer, "test/_cached_customer_as")
ActionView::PartialRenderer.collection_cache.write(key, 'Cached')
assert_equal "Cached",
@view.render(partial: "test/cached_customer_as", collection: [customer], as: :buyer)
end
+
+ private
+ def cache_key(names, virtual_path)
+ digest = ActionView::Digestor.digest name: virtual_path, finder: @view.lookup_context, dependencies: []
+ @view.fragment_cache_key([ *Array(names), digest ])
+ end
end
diff --git a/actionview/test/template/template_test.rb b/actionview/test/template/template_test.rb
index aae6a9aa09..d3b51cd629 100644
--- a/actionview/test/template/template_test.rb
+++ b/actionview/test/template/template_test.rb
@@ -190,6 +190,38 @@ class TestERBTemplate < ActiveSupport::TestCase
assert_match(/\xFC/, e.message)
end
+ def test_not_eligible_for_collection_caching_without_cache_call
+ [
+ "<%= 'Hello' %>",
+ "<% cache_customer = 42 %>",
+ "<% cache customer.name do %><% end %>",
+ "<% my_cache customer do %><% end %>"
+ ].each do |body|
+ template = new_template(body, virtual_path: "test/foo/_customer")
+ assert_not template.eligible_for_collection_caching?, "Template #{body.inspect} should not be eligible for collection caching"
+ end
+ end
+
+ def test_eligible_for_collection_caching_with_cache_call_or_explicit
+ [
+ "<% cache customer do %><% end %>",
+ "<% cache(customer) do %><% end %>",
+ "<% cache( customer) do %><% end %>",
+ "<% cache( customer ) do %><% end %>",
+ "<%cache customer do %><% end %>",
+ "<% cache customer do %><% end %>",
+ " <% cache customer do %>\n<% end %>\n",
+ "<%# comment %><% cache customer do %><% end %>",
+ "<%# comment %>\n<% cache customer do %><% end %>",
+ "<%# comment\n line 2\n line 3 %>\n<% cache customer do %><% end %>",
+ "<%# comment 1 %>\n<%# comment 2 %>\n<% cache customer do %><% end %>",
+ "<%# comment 1 %>\n<%# Template Collection: customer %>\n<% my_cache customer do %><% end %>"
+ ].each do |body|
+ template = new_template(body, virtual_path: "test/foo/_customer")
+ assert template.eligible_for_collection_caching?, "Template #{body.inspect} should be eligible for collection caching"
+ end
+ end
+
def with_external_encoding(encoding)
old = Encoding.default_external
Encoding::Converter.new old, encoding if old != encoding
diff --git a/actionview/test/template/test_case_test.rb b/actionview/test/template/test_case_test.rb
index c6cc47fb4f..05c3dc0613 100644
--- a/actionview/test/template/test_case_test.rb
+++ b/actionview/test/template/test_case_test.rb
@@ -306,64 +306,6 @@ module ActionView
end
end
- class RenderTemplateTest < ActionView::TestCase
- test "supports specifying templates with a Regexp" do
- controller.controller_path = "fun"
- render(:template => "fun/games/hello_world")
- assert_template %r{\Afun/games/hello_world\Z}
- end
-
- test "supports specifying partials" do
- controller.controller_path = "test"
- render(:template => "test/calling_partial_with_layout")
- assert_template :partial => "_partial_for_use_in_layout"
- end
-
- test "supports specifying locals (passing)" do
- controller.controller_path = "test"
- render(:template => "test/calling_partial_with_layout")
- assert_template :partial => "_partial_for_use_in_layout", :locals => { :name => "David" }
- end
-
- test "supports specifying locals (failing)" do
- controller.controller_path = "test"
- render(:template => "test/calling_partial_with_layout")
- e = assert_raise ActiveSupport::TestCase::Assertion do
- assert_template :partial => "_partial_for_use_in_layout", :locals => { :name => "Somebody Else" }
- end
- assert_match(/Somebody Else.*David/m, e.message)
- end
-
- test 'supports different locals on the same partial' do
- controller.controller_path = "test"
- render(:template => "test/render_two_partials")
- assert_template partial: '_partial', locals: { 'first' => '1' }
- assert_template partial: '_partial', locals: { 'second' => '2' }
- end
-
- test 'raises descriptive error message when template was not rendered' do
- controller.controller_path = "test"
- render(template: "test/hello_world_with_partial")
- e = assert_raise ActiveSupport::TestCase::Assertion do
- assert_template partial: 'i_was_never_rendered', locals: { 'did_not' => 'happen' }
- end
- assert_match "i_was_never_rendered to be rendered but it was not.", e.message
- assert_match 'Expected ["/test/partial"] to include "i_was_never_rendered"', e.message
- end
-
- test 'specifying locals works when the partial is inside a directory with underline prefix' do
- controller.controller_path = "test"
- render(template: 'test/render_partial_inside_directory')
- assert_template partial: 'test/_directory/_partial_with_locales', locals: { 'name' => 'Jane' }
- end
-
- test 'specifying locals works when the partial is inside a directory without underline prefix' do
- controller.controller_path = "test"
- render(template: 'test/render_partial_inside_directory')
- assert_template partial: 'test/_directory/partial_with_locales', locals: { 'name' => 'Jane' }
- end
- end
-
module AHelperWithInitialize
def initialize(*)
super
diff --git a/actionview/test/template/text_helper_test.rb b/actionview/test/template/text_helper_test.rb
index f1b84c4786..fae1965ffa 100644
--- a/actionview/test/template/text_helper_test.rb
+++ b/actionview/test/template/text_helper_test.rb
@@ -366,6 +366,10 @@ class TextHelperTest < ActionView::TestCase
assert_equal options, passed_options
end
+ def test_word_wrap_with_custom_break_sequence
+ assert_equal("1234567890\r\n1234567890\r\n1234567890", word_wrap("1234567890 " * 3, line_width: 2, break_sequence: "\r\n"))
+ end
+
def test_pluralization
assert_equal("1 count", pluralize(1, "count"))
assert_equal("2 counts", pluralize(2, "count"))
@@ -383,6 +387,18 @@ class TextHelperTest < ActionView::TestCase
assert_equal("12 berries", pluralize(12, "berry"))
end
+ def test_pluralization_with_locale
+ ActiveSupport::Inflector.inflections(:de) do |inflect|
+ inflect.plural(/(person)$/i, '\1en')
+ inflect.singular(/(person)en$/i, '\1')
+ end
+
+ assert_equal("2 People", pluralize(2, "Person", locale: :en))
+ assert_equal("2 Personen", pluralize(2, "Person", locale: :de))
+
+ ActiveSupport::Inflector.inflections(:de).clear
+ end
+
def test_cycle_class
value = Cycle.new("one", 2, "3")
assert_equal("one", value.to_s)
diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb
index df096b3c3a..5dc281adb2 100644
--- a/actionview/test/template/translation_helper_test.rb
+++ b/actionview/test/template/translation_helper_test.rb
@@ -41,8 +41,8 @@ class TranslationHelperTest < ActiveSupport::TestCase
I18n.backend.reload!
end
- def test_delegates_to_i18n_setting_the_rescue_format_option_to_html
- I18n.expects(:translate).with(:foo, :locale => 'en', :raise=>true).returns("")
+ def test_delegates_setting_to_i18n
+ I18n.expects(:translate).with(:foo, :locale => 'en', :raise => true).returns("")
translate :foo, :locale => 'en'
end
@@ -58,12 +58,6 @@ class TranslationHelperTest < ActiveSupport::TestCase
assert_equal true, translate(:"translations.missing").html_safe?
end
- def test_returns_missing_translation_message_using_nil_as_rescue_format
- expected = 'translation missing: en.translations.missing'
- assert_equal expected, translate(:"translations.missing", :rescue_format => nil)
- assert_equal false, translate(:"translations.missing", :rescue_format => nil).html_safe?
- end
-
def test_raises_missing_translation_message_with_raise_config_option
ActionView::Base.raise_on_missing_translations = true
@@ -96,12 +90,6 @@ class TranslationHelperTest < ActiveSupport::TestCase
I18n.exception_handler = old_exception_handler
end
- def test_i18n_translate_defaults_to_nil_rescue_format
- expected = 'translation missing: en.translations.missing'
- assert_equal expected, I18n.translate(:"translations.missing")
- assert_equal false, I18n.translate(:"translations.missing").html_safe?
- end
-
def test_translation_returning_an_array
expected = %w(foo bar)
assert_equal expected, translate(:"translations.array")
@@ -157,6 +145,19 @@ class TranslationHelperTest < ActiveSupport::TestCase
assert_equal true, translation.html_safe?
end
+ def test_translate_with_missing_default
+ translation = translate(:'translations.missing', :default => :'translations.missing_html')
+ expected = '<span class="translation_missing" title="translation missing: en.translations.missing_html">Missing Html</span>'
+ assert_equal expected, translation
+ assert_equal true, translation.html_safe?
+ end
+
+ def test_translate_with_missing_default_and_raise_option
+ assert_raise(I18n::MissingTranslationData) do
+ translate(:'translations.missing', :default => :'translations.missing_html', :raise => true)
+ end
+ end
+
def test_translate_with_two_defaults_named_html
translation = translate(:'translations.missing', :default => [:'translations.missing_html', :'translations.hello_html'])
assert_equal '<a>Hello World</a>', translation
diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb
index ef4df0407a..416d30938a 100644
--- a/actionview/test/template/url_helper_test.rb
+++ b/actionview/test/template/url_helper_test.rb
@@ -379,6 +379,11 @@ class UrlHelperTest < ActiveSupport::TestCase
assert_dom_equal %{<a href="/">Listing</a>}, link_to_if(true, "Listing", url_hash)
end
+ def test_link_to_if_with_block
+ assert_equal "Fallback", link_to_if(false, "Showing", url_hash) { "Fallback" }
+ assert_dom_equal %{<a href="/">Listing</a>}, link_to_if(true, "Listing", url_hash) { "Fallback" }
+ end
+
def request_for_url(url, opts = {})
env = Rack::MockRequest.env_for("http://www.example.com#{url}", opts)
ActionDispatch::Request.new(env)
@@ -479,6 +484,11 @@ class UrlHelperTest < ActiveSupport::TestCase
link_to_unless_current("Listing", "http://www.example.com/")
end
+ def test_link_to_unless_with_block
+ assert_dom_equal %{<a href="/">Showing</a>}, link_to_unless(false, "Showing", url_hash) { "Fallback" }
+ assert_equal "Fallback", link_to_unless(true, "Listing", url_hash) { "Fallback" }
+ end
+
def test_mail_to
assert_dom_equal %{<a href="mailto:david@loudthinking.com">david@loudthinking.com</a>}, mail_to("david@loudthinking.com")
assert_dom_equal %{<a href="mailto:david@loudthinking.com">David Heinemeier Hansson</a>}, mail_to("david@loudthinking.com", "David Heinemeier Hansson")
@@ -490,6 +500,13 @@ class UrlHelperTest < ActiveSupport::TestCase
mail_to("david@loudthinking.com", "David Heinemeier Hansson", class: "admin")
end
+ def test_mail_to_with_special_characters
+ assert_dom_equal(
+ %{<a href="mailto:%23%21%24%25%26%27%2A%2B-%2F%3D%3F%5E_%60%7B%7D%7C%7E@example.org">#!$%&amp;&#39;*+-/=?^_`{}|~@example.org</a>},
+ mail_to("#!$%&'*+-/=?^_`{}|~@example.org")
+ )
+ end
+
def test_mail_with_options
assert_dom_equal(
%{<a href="mailto:me@example.com?cc=ccaddress%40example.com&amp;bcc=bccaddress%40example.com&amp;body=This%20is%20the%20body%20of%20the%20message.&amp;subject=This%20is%20an%20example%20email&amp;reply-to=foo%40bar.com">My email</a>},
diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md
index 85a437a1dd..637096e935 100644
--- a/activejob/CHANGELOG.md
+++ b/activejob/CHANGELOG.md
@@ -1,4 +1,34 @@
-* A generated job now inherents from `app/jobs/application_job.rb` by default.
+* Implement `provider_job_id` for `queue_classic` adapter. This requires the
+ latest, currently unreleased, version of queue_classic.
+
+ *Yves Senn*
+
+* `assert_enqueued_with` and `assert_performed_with` now returns the matched
+ job instance for further assertions.
+
+ *Jean Boussier*
+
+* Include I18n.locale into job serialization/deserialization and use it around
+ `perform`.
+
+ Fixes #20799.
+
+ *Johannes Opper*
+
+* Allow `DelayedJob`, `Sidekiq`, `qu`, and `que` to report the job id back to
+ `ActiveJob::Base` as `provider_job_id`.
+
+ Fixes #18821.
+
+ *Kevin Deisz*, *Jeroen van Baarsen*
+
+* `assert_enqueued_jobs` and `assert_performed_jobs` in block form use the
+ given number as expected value. This makes the error message much easier to
+ understand.
+
+ *y-yagi*
+
+* A generated job now inherits from `app/jobs/application_job.rb` by default.
*Jeroen van Baarsen*
diff --git a/activejob/README.md b/activejob/README.md
index 5170ebee6e..f9a3183b1a 100644
--- a/activejob/README.md
+++ b/activejob/README.md
@@ -102,7 +102,7 @@ see the API Documentation for [ActiveJob::QueueAdapters](http://api.rubyonrails.
The latest version of Active Job can be installed with RubyGems:
```
- % [sudo] gem install activejob
+ % gem install activejob
```
Source code can be downloaded as part of the Rails project on GitHub
diff --git a/activejob/Rakefile b/activejob/Rakefile
index 6c7e6aae6e..8c86df3c91 100644
--- a/activejob/Rakefile
+++ b/activejob/Rakefile
@@ -1,5 +1,4 @@
require 'rake/testtask'
-require 'rubygems/package_task'
ACTIVEJOB_ADAPTERS = %w(inline delayed_job qu que queue_classic resque sidekiq sneakers sucker_punch backburner test)
ACTIVEJOB_ADAPTERS -= %w(queue_classic) if defined?(JRUBY_VERSION)
@@ -35,7 +34,7 @@ namespace :test do
t.libs << 'test'
t.test_files = FileList['test/cases/**/*_test.rb']
t.verbose = true
- t.warning = true
+ t.warning = false
t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
end
@@ -54,7 +53,7 @@ namespace :test do
t.libs << 'test'
t.test_files = FileList['test/integration/**/*_test.rb']
t.verbose = true
- t.warning = true
+ t.warning = false
t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
end
end
@@ -74,17 +73,3 @@ def run_without_aborting(tasks)
abort "Errors running #{errors.join(', ')}" if errors.any?
end
-
-
-spec = eval(File.read('activejob.gemspec'))
-
-Gem::PackageTask.new(spec) do |p|
- p.gem_spec = spec
-end
-
-desc 'Release to rubygems'
-task release: :package do
- require 'rake/gemcutter'
- Rake::Gemcutter::Tasks.new(spec).define
- Rake::Task['gem:push'].invoke
-end
diff --git a/activejob/activejob.gemspec b/activejob/activejob.gemspec
index ef8db3bcd3..24e38e495f 100644
--- a/activejob/activejob.gemspec
+++ b/activejob/activejob.gemspec
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
s.summary = 'Job framework with pluggable queues.'
s.description = 'Declare job classes that can be run by a variety of queueing backends.'
- s.required_ruby_version = '>= 2.2.1'
+ s.required_ruby_version = '>= 2.2.2'
s.license = 'MIT'
diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb
index 622c37098e..8e462bfe5d 100644
--- a/activejob/lib/active_job/arguments.rb
+++ b/activejob/lib/active_job/arguments.rb
@@ -5,6 +5,8 @@ module ActiveJob
#
# Wraps the original exception raised as +original_exception+.
class DeserializationError < StandardError
+ # The original exception that was raised during deserialization of job
+ # arguments.
attr_reader :original_exception
def initialize(e) #:nodoc:
@@ -14,16 +16,17 @@ module ActiveJob
end
end
- # Raised when an unsupported argument type is being set as job argument. We
+ # Raised when an unsupported argument type is set as a job argument. We
# currently support NilClass, Fixnum, Float, String, TrueClass, FalseClass,
- # Bignum and object that can be represented as GlobalIDs (ex: Active Record).
- # Also raised if you set the key for a Hash something else than a string or
- # a symbol.
- class SerializationError < ArgumentError
- end
+ # Bignum and objects that can be represented as GlobalIDs (ex: Active Record).
+ # Raised if you set the key for a Hash something else than a string or
+ # a symbol. Also raised when trying to serialize an object which can't be
+ # identified with a Global ID - such as an unpersisted Active Record model.
+ class SerializationError < ArgumentError; end
module Arguments
extend self
+ # :nodoc:
TYPE_WHITELIST = [ NilClass, Fixnum, Float, String, TrueClass, FalseClass, Bignum ]
# Serializes a set of arguments. Whitelisted types are returned
@@ -43,8 +46,11 @@ module ActiveJob
end
private
+ # :nodoc:
GLOBALID_KEY = '_aj_globalid'.freeze
+ # :nodoc:
SYMBOL_KEYS_KEY = '_aj_symbol_keys'.freeze
+ # :nodoc:
WITH_INDIFFERENT_ACCESS_KEY = '_aj_hash_with_indifferent_access'.freeze
private_constant :GLOBALID_KEY, :SYMBOL_KEYS_KEY, :WITH_INDIFFERENT_ACCESS_KEY
@@ -53,7 +59,7 @@ module ActiveJob
when *TYPE_WHITELIST
argument
when GlobalID::Identification
- { GLOBALID_KEY => argument.to_global_id.to_s }
+ convert_to_global_id_hash(argument)
when Array
argument.map { |arg| serialize_argument(arg) }
when ActiveSupport::HashWithIndifferentAccess
@@ -113,6 +119,7 @@ module ActiveJob
result
end
+ # :nodoc:
RESERVED_KEYS = [
GLOBALID_KEY, GLOBALID_KEY.to_sym,
SYMBOL_KEYS_KEY, SYMBOL_KEYS_KEY.to_sym,
@@ -140,5 +147,12 @@ module ActiveJob
end
end
end
+
+ def convert_to_global_id_hash(argument)
+ { GLOBALID_KEY => argument.to_global_id.to_s }
+ rescue URI::GID::MissingModelIdError
+ raise SerializationError, "Unable to serialize #{argument.class} " \
+ "without an id. (Maybe you forgot to call save?)"
+ end
end
end
diff --git a/activejob/lib/active_job/base.rb b/activejob/lib/active_job/base.rb
index fd49b3fda5..5d7c4cfb91 100644
--- a/activejob/lib/active_job/base.rb
+++ b/activejob/lib/active_job/base.rb
@@ -5,6 +5,7 @@ require 'active_job/enqueuing'
require 'active_job/execution'
require 'active_job/callbacks'
require 'active_job/logging'
+require 'active_job/translation'
module ActiveJob #:nodoc:
# = Active Job
@@ -60,6 +61,7 @@ module ActiveJob #:nodoc:
include Execution
include Callbacks
include Logging
+ include Translation
ActiveSupport.run_load_hooks(:active_job, self)
end
diff --git a/activejob/lib/active_job/core.rb b/activejob/lib/active_job/core.rb
index ddd7d1361c..eac7279309 100644
--- a/activejob/lib/active_job/core.rb
+++ b/activejob/lib/active_job/core.rb
@@ -1,4 +1,6 @@
module ActiveJob
+ # Provides general behavior that will be included into every Active Job
+ # object that inherits from ActiveJob::Base.
module Core
extend ActiveSupport::Concern
@@ -15,6 +17,12 @@ module ActiveJob
# Queue in which the job will reside.
attr_writer :queue_name
+
+ # ID optionally provided by adapter
+ attr_accessor :provider_job_id
+
+ # I18n.locale to be used during the job.
+ attr_accessor :locale
end
# These methods will be included into any Active Job object, adding
@@ -63,7 +71,8 @@ module ActiveJob
'job_class' => self.class.name,
'job_id' => job_id,
'queue_name' => queue_name,
- 'arguments' => serialize_arguments(arguments)
+ 'arguments' => serialize_arguments(arguments),
+ 'locale' => I18n.locale
}
end
@@ -91,6 +100,7 @@ module ActiveJob
self.job_id = job_data['job_id']
self.queue_name = job_data['queue_name']
self.serialized_arguments = job_data['arguments']
+ self.locale = job_data['locale'] || I18n.locale
end
private
diff --git a/activejob/lib/active_job/enqueuing.rb b/activejob/lib/active_job/enqueuing.rb
index 430c17e1bf..98d92385dd 100644
--- a/activejob/lib/active_job/enqueuing.rb
+++ b/activejob/lib/active_job/enqueuing.rb
@@ -1,6 +1,7 @@
require 'active_job/arguments'
module ActiveJob
+ # Provides behavior for enqueuing and retrying jobs.
module Enqueuing
extend ActiveSupport::Concern
diff --git a/activejob/lib/active_job/queue_adapter.rb b/activejob/lib/active_job/queue_adapter.rb
index 9c4519432d..457015b741 100644
--- a/activejob/lib/active_job/queue_adapter.rb
+++ b/activejob/lib/active_job/queue_adapter.rb
@@ -4,7 +4,7 @@ require 'active_support/core_ext/string/inflections'
module ActiveJob
# The <tt>ActiveJob::QueueAdapter</tt> module is used to load the
- # correct adapter. The default queue adapter is the :inline queue.
+ # correct adapter. The default queue adapter is the +:inline+ queue.
module QueueAdapter #:nodoc:
extend ActiveSupport::Concern
@@ -15,12 +15,14 @@ module ActiveJob
# Includes the setter method for changing the active queue adapter.
module ClassMethods
+ # Returns the backend queue provider. The default queue adapter
+ # is the +:inline+ queue. See QueueAdapters for more information.
def queue_adapter
_queue_adapter
end
# Specify the backend queue provider. The default queue adapter
- # is the :inline queue. See QueueAdapters for more
+ # is the +:inline+ queue. See QueueAdapters for more
# information.
def queue_adapter=(name_or_adapter_or_class)
self._queue_adapter = interpret_adapter(name_or_adapter_or_class)
diff --git a/activejob/lib/active_job/queue_adapters.rb b/activejob/lib/active_job/queue_adapters.rb
index b3d91dc562..e8ceabaeba 100644
--- a/activejob/lib/active_job/queue_adapters.rb
+++ b/activejob/lib/active_job/queue_adapters.rb
@@ -15,25 +15,87 @@ module ActiveJob
#
# === Backends Features
#
- # | | Async | Queues | Delayed | Priorities | Timeout | Retries |
- # |-------------------|-------|--------|-----------|------------|---------|---------|
- # | Backburner | Yes | Yes | Yes | Yes | Job | Global |
- # | Delayed Job | Yes | Yes | Yes | Job | Global | Global |
- # | Qu | Yes | Yes | No | No | No | Global |
- # | Que | Yes | Yes | Yes | Job | No | Job |
- # | queue_classic | Yes | Yes | No* | No | No | No |
- # | Resque | Yes | Yes | Yes (Gem) | Queue | Global | Yes |
- # | Sidekiq | Yes | Yes | Yes | Queue | No | Job |
- # | Sneakers | Yes | Yes | No | Queue | Queue | No |
- # | Sucker Punch | Yes | Yes | No | No | No | No |
- # | Active Job Inline | No | Yes | N/A | N/A | N/A | N/A |
- # | Active Job | Yes | Yes | Yes | No | No | No |
+ # | | Async | Queues | Delayed | Priorities | Timeout | Retries |
+ # |-------------------|-------|--------|------------|------------|---------|---------|
+ # | Backburner | Yes | Yes | Yes | Yes | Job | Global |
+ # | Delayed Job | Yes | Yes | Yes | Job | Global | Global |
+ # | Qu | Yes | Yes | No | No | No | Global |
+ # | Que | Yes | Yes | Yes | Job | No | Job |
+ # | queue_classic | Yes | Yes | Yes* | No | No | No |
+ # | Resque | Yes | Yes | Yes (Gem) | Queue | Global | Yes |
+ # | Sidekiq | Yes | Yes | Yes | Queue | No | Job |
+ # | Sneakers | Yes | Yes | No | Queue | Queue | No |
+ # | Sucker Punch | Yes | Yes | No | No | No | No |
+ # | Active Job Inline | No | Yes | N/A | N/A | N/A | N/A |
+ #
+ # ==== Async
+ #
+ # Yes: The Queue Adapter runs the jobs in a separate or forked process.
+ #
+ # No: The job is run in the same process.
+ #
+ # ==== Queues
+ #
+ # Yes: Jobs may set which queue they are run in with queue_as or by using the set
+ # method.
+ #
+ # ==== Delayed
+ #
+ # Yes: The adapter will run the job in the future through perform_later.
+ #
+ # (Gem): An additional gem is required to use perform_later with this adapter.
+ #
+ # No: The adapter will run jobs at the next opportunity and cannot use perform_later.
+ #
+ # N/A: The adapter does not support queueing.
#
# NOTE:
- # queue_classic does not support Job scheduling. However you can implement this
- # yourself or you can use the queue_classic-later gem. See the documentation for
- # ActiveJob::QueueAdapters::QueueClassicAdapter.
+ # queue_classic supports job scheduling since version 3.1.
+ # For older versions you can use the queue_classic-later gem.
+ #
+ # ==== Priorities
+ #
+ # The order in which jobs are processed can be configured differently depending
+ # on the adapter.
+ #
+ # Job: Any class inheriting from the adapter may set the priority on the job
+ # object relative to other jobs.
+ #
+ # Queue: The adapter can set the priority for job queues, when setting a queue
+ # with Active Job this will be respected.
+ #
+ # Yes: Allows the priority to be set on the job object, at the queue level or
+ # as default configuration option.
+ #
+ # No: Does not allow the priority of jobs to be configured.
+ #
+ # N/A: The adapter does not support queueing, and therefore sorting them.
+ #
+ # ==== Timeout
+ #
+ # When a job will stop after the allotted time.
+ #
+ # Job: The timeout can be set for each instance of the job class.
+ #
+ # Queue: The timeout is set for all jobs on the queue.
+ #
+ # Global: The adapter is configured that all jobs have a maximum run time.
+ #
+ # N/A: This adapter does not run in a separate process, and therefore timeout
+ # is unsupported.
+ #
+ # ==== Retries
+ #
+ # Job: The number of retries can be set per instance of the job class.
+ #
+ # Yes: The Number of retries can be configured globally, for each instance or
+ # on the queue. This adapter may also present failed instances of the job class
+ # that can be restarted.
+ #
+ # Global: The adapter has a global number of retries.
#
+ # N/A: The adapter does not run in a separate process, and therefore doesn't
+ # support retries.
module QueueAdapters
extend ActiveSupport::Autoload
@@ -53,6 +115,10 @@ module ActiveJob
private_constant :ADAPTER
class << self
+ # Returns adapter for specified name.
+ #
+ # ActiveJob::QueueAdapters.lookup(:sidekiq)
+ # # => ActiveJob::QueueAdapters::SidekiqAdapter
def lookup(name)
const_get(name.to_s.camelize << ADAPTER)
end
diff --git a/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb b/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb
index 852a6ee326..ac83da2b9c 100644
--- a/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb
@@ -14,11 +14,15 @@ module ActiveJob
# Rails.application.config.active_job.queue_adapter = :delayed_job
class DelayedJobAdapter
def enqueue(job) #:nodoc:
- Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name)
+ delayed_job = Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name)
+ job.provider_job_id = delayed_job.id
+ delayed_job
end
def enqueue_at(job, timestamp) #:nodoc:
- Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name, run_at: Time.at(timestamp))
+ delayed_job = Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name, run_at: Time.at(timestamp))
+ job.provider_job_id = delayed_job.id
+ delayed_job
end
class JobWrapper #:nodoc:
diff --git a/activejob/lib/active_job/queue_adapters/inline_adapter.rb b/activejob/lib/active_job/queue_adapters/inline_adapter.rb
index 1d06324c18..8ad5f4de07 100644
--- a/activejob/lib/active_job/queue_adapters/inline_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/inline_adapter.rb
@@ -14,7 +14,7 @@ module ActiveJob
end
def enqueue_at(*) #:nodoc:
- raise NotImplementedError.new("Use a queueing backend to enqueue jobs in the future. Read more at http://guides.rubyonrails.org/active_job_basics.html")
+ raise NotImplementedError, "Use a queueing backend to enqueue jobs in the future. Read more at http://guides.rubyonrails.org/active_job_basics.html"
end
end
end
diff --git a/activejob/lib/active_job/queue_adapters/qu_adapter.rb b/activejob/lib/active_job/queue_adapters/qu_adapter.rb
index 94584ef9d8..0e198922fc 100644
--- a/activejob/lib/active_job/queue_adapters/qu_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/qu_adapter.rb
@@ -17,13 +17,17 @@ module ActiveJob
# Rails.application.config.active_job.queue_adapter = :qu
class QuAdapter
def enqueue(job, *args) #:nodoc:
- Qu::Payload.new(klass: JobWrapper, args: [job.serialize]).tap do |payload|
+ qu_job = Qu::Payload.new(klass: JobWrapper, args: [job.serialize]).tap do |payload|
payload.instance_variable_set(:@queue, job.queue_name)
end.push
+
+ # qu_job can be nil depending on the configured backend
+ job.provider_job_id = qu_job.id unless qu_job.nil?
+ qu_job
end
def enqueue_at(job, timestamp, *args) #:nodoc:
- raise NotImplementedError
+ raise NotImplementedError, "This queueing backend does not support scheduling jobs. To see what features are supported go to http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html"
end
class JobWrapper < Qu::Job #:nodoc:
diff --git a/activejob/lib/active_job/queue_adapters/que_adapter.rb b/activejob/lib/active_job/queue_adapters/que_adapter.rb
index 84cc2845b0..90947aa98d 100644
--- a/activejob/lib/active_job/queue_adapters/que_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/que_adapter.rb
@@ -16,11 +16,15 @@ module ActiveJob
# Rails.application.config.active_job.queue_adapter = :que
class QueAdapter
def enqueue(job) #:nodoc:
- JobWrapper.enqueue job.serialize, queue: job.queue_name
+ que_job = JobWrapper.enqueue job.serialize
+ job.provider_job_id = que_job.attrs["job_id"]
+ que_job
end
def enqueue_at(job, timestamp) #:nodoc:
- JobWrapper.enqueue job.serialize, queue: job.queue_name, run_at: Time.at(timestamp)
+ que_job = JobWrapper.enqueue job.serialize, run_at: Time.at(timestamp)
+ job.provider_job_id = que_job.attrs["job_id"]
+ que_job
end
class JobWrapper < Que::Job #:nodoc:
diff --git a/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb b/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb
index 059754a87f..0ee41407d8 100644
--- a/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb
@@ -18,7 +18,9 @@ module ActiveJob
# Rails.application.config.active_job.queue_adapter = :queue_classic
class QueueClassicAdapter
def enqueue(job) #:nodoc:
- build_queue(job.queue_name).enqueue("#{JobWrapper.name}.perform", job.serialize)
+ qc_job = build_queue(job.queue_name).enqueue("#{JobWrapper.name}.perform", job.serialize)
+ job.provider_job_id = qc_job["id"] if qc_job.is_a?(Hash)
+ qc_job
end
def enqueue_at(job, timestamp) #:nodoc:
@@ -28,7 +30,9 @@ module ActiveJob
'the QC::Queue needs to respond to `enqueue_at(timestamp, method, *args)`. ' \
'You can implement this yourself or you can use the queue_classic-later gem.'
end
- queue.enqueue_at(timestamp, "#{JobWrapper.name}.perform", job.serialize)
+ qc_job = queue.enqueue_at(timestamp, "#{JobWrapper.name}.perform", job.serialize)
+ job.provider_job_id = qc_job["id"] if qc_job.is_a?(Hash)
+ qc_job
end
# Builds a <tt>QC::Queue</tt> object to schedule jobs on.
diff --git a/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb b/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb
index 743d5ea333..c321776bf5 100644
--- a/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb
@@ -17,7 +17,7 @@ module ActiveJob
class SidekiqAdapter
def enqueue(job) #:nodoc:
#Sidekiq::Client does not support symbols as keys
- Sidekiq::Client.push \
+ job.provider_job_id = Sidekiq::Client.push \
'class' => JobWrapper,
'wrapped' => job.class.to_s,
'queue' => job.queue_name,
@@ -25,7 +25,7 @@ module ActiveJob
end
def enqueue_at(job, timestamp) #:nodoc:
- Sidekiq::Client.push \
+ job.provider_job_id = Sidekiq::Client.push \
'class' => JobWrapper,
'wrapped' => job.class.to_s,
'queue' => job.queue_name,
diff --git a/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb b/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb
index f5737487ca..f102c6567e 100644
--- a/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb
@@ -28,7 +28,7 @@ module ActiveJob
end
def enqueue_at(job, timestamp) #:nodoc:
- raise NotImplementedError
+ raise NotImplementedError, "This queueing backend does not support scheduling jobs. To see what features are supported go to http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html"
end
class JobWrapper #:nodoc:
diff --git a/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb b/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb
index 64c93e8198..c6c35f8ab4 100644
--- a/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb
@@ -23,7 +23,7 @@ module ActiveJob
end
def enqueue_at(job, timestamp) #:nodoc:
- raise NotImplementedError
+ raise NotImplementedError, "This queueing backend does not support scheduling jobs. To see what features are supported go to http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html"
end
class JobWrapper #:nodoc:
diff --git a/activejob/lib/active_job/queue_name.rb b/activejob/lib/active_job/queue_name.rb
index 9ae0345120..65786a49ff 100644
--- a/activejob/lib/active_job/queue_name.rb
+++ b/activejob/lib/active_job/queue_name.rb
@@ -39,7 +39,7 @@ module ActiveJob
self.queue_name_delimiter = '_' # set default delimiter to '_'
end
- # Returns the name of the queue the job will be run on
+ # Returns the name of the queue the job will be run on.
def queue_name
if @queue_name.is_a?(Proc)
@queue_name = self.class.queue_name_from_part(instance_exec(&@queue_name))
diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb
index 4efb4b72d2..74a12884ff 100644
--- a/activejob/lib/active_job/test_helper.rb
+++ b/activejob/lib/active_job/test_helper.rb
@@ -7,7 +7,7 @@ module ActiveJob
extend ActiveSupport::Concern
included do
- def before_setup
+ def before_setup # :nodoc:
test_adapter = ActiveJob::QueueAdapters::TestAdapter.new
@old_queue_adapters = (ActiveJob::Base.subclasses << ActiveJob::Base).select do |klass|
@@ -24,7 +24,7 @@ module ActiveJob
super
end
- def after_teardown
+ def after_teardown # :nodoc:
super
@old_queue_adapters.each do |(klass, adapter)|
klass.queue_adapter = adapter
@@ -68,7 +68,7 @@ module ActiveJob
original_count = enqueued_jobs_size(only: only)
yield
new_count = enqueued_jobs_size(only: only)
- assert_equal original_count + number, new_count, "#{number} jobs expected, but #{new_count - original_count} were enqueued"
+ assert_equal number, new_count - original_count, "#{number} jobs expected, but #{new_count - original_count} were enqueued"
else
actual_count = enqueued_jobs_size(only: only)
assert_equal number, actual_count, "#{number} jobs expected, but #{actual_count} were enqueued"
@@ -164,7 +164,7 @@ module ActiveJob
original_count = performed_jobs.size
perform_enqueued_jobs(only: only) { yield }
new_count = performed_jobs.size
- assert_equal original_count + number, new_count,
+ assert_equal number, new_count - original_count,
"#{number} jobs expected, but #{new_count - original_count} were performed"
else
performed_jobs_size = performed_jobs.size
@@ -233,10 +233,11 @@ module ActiveJob
args.assert_valid_keys(:job, :args, :at, :queue)
serialized_args = serialize_args_for_assertion(args)
yield
- matching_job = enqueued_jobs.any? do |job|
+ matching_job = enqueued_jobs.find do |job|
serialized_args.all? { |key, value| value == job[key] }
end
assert matching_job, "No enqueued job found with #{args}"
+ instanciate_job(matching_job)
ensure
queue_adapter.enqueued_jobs = original_enqueued_jobs + enqueued_jobs
end
@@ -254,10 +255,11 @@ module ActiveJob
args.assert_valid_keys(:job, :args, :at, :queue)
serialized_args = serialize_args_for_assertion(args)
perform_enqueued_jobs { yield }
- matching_job = performed_jobs.any? do |job|
+ matching_job = performed_jobs.find do |job|
serialized_args.all? { |key, value| value == job[key] }
end
assert matching_job, "No performed job found with #{args}"
+ instanciate_job(matching_job)
ensure
queue_adapter.performed_jobs = original_performed_jobs + performed_jobs
end
@@ -311,6 +313,13 @@ module ActiveJob
end
serialized_args
end
+
+ def instanciate_job(payload)
+ job = payload[:job].new(*payload[:args])
+ job.scheduled_at = Time.at(payload[:at]) if payload.key?(:at)
+ job.queue_name = payload[:queue]
+ job
+ end
end
end
end
diff --git a/activejob/lib/active_job/translation.rb b/activejob/lib/active_job/translation.rb
new file mode 100644
index 0000000000..67e4cf4ab9
--- /dev/null
+++ b/activejob/lib/active_job/translation.rb
@@ -0,0 +1,11 @@
+module ActiveJob
+ module Translation #:nodoc:
+ extend ActiveSupport::Concern
+
+ included do
+ around_perform do |job, block, _|
+ I18n.with_locale(job.locale, &block)
+ end
+ end
+ end
+end
diff --git a/activejob/lib/rails/generators/job/job_generator.rb b/activejob/lib/rails/generators/job/job_generator.rb
index 86e4c5266c..2115fb9f71 100644
--- a/activejob/lib/rails/generators/job/job_generator.rb
+++ b/activejob/lib/rails/generators/job/job_generator.rb
@@ -1,6 +1,6 @@
require 'rails/generators/named_base'
-module Rails
+module Rails # :nodoc:
module Generators # :nodoc:
class JobGenerator < Rails::Generators::NamedBase # :nodoc:
desc 'This generator creates an active job file at app/jobs'
diff --git a/activejob/test/cases/argument_serialization_test.rb b/activejob/test/cases/argument_serialization_test.rb
index 8b9b62190f..933972a52b 100644
--- a/activejob/test/cases/argument_serialization_test.rb
+++ b/activejob/test/cases/argument_serialization_test.rb
@@ -88,6 +88,13 @@ class ArgumentSerializationTest < ActiveSupport::TestCase
assert_equal "Job with argument: 2", JobBuffer.last_value
end
+ test 'raises a friendly SerializationError for records without ids' do
+ err = assert_raises ActiveJob::SerializationError do
+ ActiveJob::Arguments.serialize [Person.new(nil)]
+ end
+ assert_match 'Unable to serialize Person without an id.', err.message
+ end
+
private
def assert_arguments_unchanged(*args)
assert_arguments_roundtrip args
diff --git a/activejob/test/cases/job_serialization_test.rb b/activejob/test/cases/job_serialization_test.rb
index db22783030..229517774e 100644
--- a/activejob/test/cases/job_serialization_test.rb
+++ b/activejob/test/cases/job_serialization_test.rb
@@ -1,5 +1,6 @@
require 'helper'
require 'jobs/gid_job'
+require 'jobs/hello_job'
require 'models/person'
class JobSerializationTest < ActiveSupport::TestCase
@@ -12,4 +13,20 @@ class JobSerializationTest < ActiveSupport::TestCase
GidJob.perform_later @person
assert_equal "Person with ID: 5", JobBuffer.last_value
end
+
+ test 'serialize includes current locale' do
+ assert_equal :en, HelloJob.new.serialize['locale']
+ end
+
+ test 'deserialize sets locale' do
+ job = HelloJob.new
+ job.deserialize 'locale' => :es
+ assert_equal :es, job.locale
+ end
+
+ test 'deserialize sets default locale' do
+ job = HelloJob.new
+ job.deserialize({})
+ assert_equal :en, job.locale
+ end
end
diff --git a/activejob/test/cases/test_case_test.rb b/activejob/test/cases/test_case_test.rb
index 0a3a20d5a0..ee816e1dd5 100644
--- a/activejob/test/cases/test_case_test.rb
+++ b/activejob/test/cases/test_case_test.rb
@@ -5,11 +5,11 @@ require 'jobs/nested_job'
class ActiveJobTestCaseTest < ActiveJob::TestCase
# this tests that this job class doesn't get its adapter set.
- # that's the correct behaviour since we don't want to break
- # the `class_attribute` inheritence
- class TestClassAttributeInheritenceJob < ActiveJob::Base
+ # that's the correct behavior since we don't want to break
+ # the `class_attribute` inheritance
+ class TestClassAttributeInheritanceJob < ActiveJob::Base
def self.queue_adapter=(*)
- raise 'Attemping to break `class_attribute` inheritence, bad!'
+ raise 'Attemping to break `class_attribute` inheritance, bad!'
end
end
diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb
index 19a2820a6e..8c60f037d2 100644
--- a/activejob/test/cases/test_helper_test.rb
+++ b/activejob/test/cases/test_helper_test.rb
@@ -31,6 +31,17 @@ class EnqueuedJobsTest < ActiveJob::TestCase
end
end
+ def test_assert_enqueued_jobs_message
+ HelloJob.perform_later('sean')
+ e = assert_raises Minitest::Assertion do
+ assert_enqueued_jobs 2 do
+ HelloJob.perform_later('sean')
+ end
+ end
+ assert_match "Expected: 2", e.message
+ assert_match "Actual: 1", e.message
+ end
+
def test_assert_enqueued_jobs_with_no_block
assert_nothing_raised do
HelloJob.perform_later('rafael')
@@ -154,6 +165,17 @@ class EnqueuedJobsTest < ActiveJob::TestCase
end
end
+ def test_assert_enqueued_job_returns
+ job = assert_enqueued_with(job: LoggingJob) do
+ LoggingJob.set(wait_until: 5.minutes.from_now).perform_later(1, 2, 3)
+ end
+
+ assert_instance_of LoggingJob, job
+ assert_in_delta 5.minutes.from_now, job.scheduled_at, 1
+ assert_equal 'default', job.queue_name
+ assert_equal [1, 2, 3], job.arguments
+ end
+
def test_assert_enqueued_job_failure
assert_raise ActiveSupport::TestCase::Assertion do
assert_enqueued_with(job: LoggingJob, queue: 'default') do
@@ -230,6 +252,17 @@ class PerformedJobsTest < ActiveJob::TestCase
end
end
+ def test_assert_performed_jobs_message
+ HelloJob.perform_later('sean')
+ e = assert_raises Minitest::Assertion do
+ assert_performed_jobs 2 do
+ HelloJob.perform_later('sean')
+ end
+ end
+ assert_match "Expected: 2", e.message
+ assert_match "Actual: 1", e.message
+ end
+
def test_assert_performed_jobs_with_no_block
assert_nothing_raised do
perform_enqueued_jobs do
@@ -375,6 +408,17 @@ class PerformedJobsTest < ActiveJob::TestCase
end
end
+ def test_assert_performed_job_returns
+ job = assert_performed_with(job: NestedJob, queue: 'default') do
+ NestedJob.perform_later
+ end
+
+ assert_instance_of NestedJob, job
+ assert_nil job.scheduled_at
+ assert_equal [], job.arguments
+ assert_equal 'default', job.queue_name
+ end
+
def test_assert_performed_job_failure
assert_raise ActiveSupport::TestCase::Assertion do
assert_performed_with(job: LoggingJob, at: Date.tomorrow.noon, queue: 'default') do
diff --git a/activejob/test/cases/translation_test.rb b/activejob/test/cases/translation_test.rb
new file mode 100644
index 0000000000..d5e3aaf9e3
--- /dev/null
+++ b/activejob/test/cases/translation_test.rb
@@ -0,0 +1,20 @@
+require 'helper'
+require 'jobs/translated_hello_job'
+
+class TranslationTest < ActiveSupport::TestCase
+ setup do
+ JobBuffer.clear
+ I18n.available_locales = [:en, :de]
+ @job = TranslatedHelloJob.new('Johannes')
+ end
+
+ teardown do
+ I18n.available_locales = [:en]
+ end
+
+ test 'it performs the job in the given locale' do
+ @job.locale = :de
+ @job.perform_now
+ assert_equal "Johannes says Guten Tag", JobBuffer.last_value
+ end
+end
diff --git a/activejob/test/helper.rb b/activejob/test/helper.rb
index 72ec2b8904..57907042d9 100644
--- a/activejob/test/helper.rb
+++ b/activejob/test/helper.rb
@@ -14,5 +14,3 @@ else
end
require 'active_support/testing/autorun'
-
-ActiveSupport::TestCase.test_order = :random
diff --git a/activejob/test/integration/queuing_test.rb b/activejob/test/integration/queuing_test.rb
index af19a92118..ca8047ef0b 100644
--- a/activejob/test/integration/queuing_test.rb
+++ b/activejob/test/integration/queuing_test.rb
@@ -11,7 +11,7 @@ class QueuingTest < ActiveSupport::TestCase
end
test 'should not run jobs queued on a non-listening queue' do
- skip if adapter_is?(:inline) || adapter_is?(:sucker_punch)
+ skip if adapter_is?(:inline, :sucker_punch, :que)
old_queue = TestJob.queue_name
begin
@@ -26,8 +26,6 @@ class QueuingTest < ActiveSupport::TestCase
test 'should supply a wrapped class name to Sidekiq' do
skip unless adapter_is?(:sidekiq)
- require 'sidekiq/testing'
-
Sidekiq::Testing.fake! do
::HelloJob.perform_later
hash = ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper.jobs.first
@@ -48,7 +46,7 @@ class QueuingTest < ActiveSupport::TestCase
test 'should run job enqueued in the future at the specified time' do
begin
- TestJob.set(wait: 3.seconds).perform_later @id
+ TestJob.set(wait: 5.seconds).perform_later @id
wait_for_jobs_to_finish_for(2.seconds)
assert_not job_executed
wait_for_jobs_to_finish_for(10.seconds)
@@ -57,4 +55,33 @@ class QueuingTest < ActiveSupport::TestCase
skip
end
end
+
+ test 'should supply a provider_job_id when available for immediate jobs' do
+ skip unless adapter_is?(:delayed_job, :sidekiq, :qu, :que, :queue_classic)
+ test_job = TestJob.perform_later @id
+ assert test_job.provider_job_id, 'Provider job id should be set by provider'
+ end
+
+ test 'should supply a provider_job_id when available for delayed jobs' do
+ skip unless adapter_is?(:delayed_job, :sidekiq, :que, :queue_classic)
+ delayed_test_job = TestJob.set(wait: 1.minute).perform_later @id
+ assert delayed_test_job.provider_job_id, 'Provider job id should by set for delayed jobs by provider'
+ end
+
+ test 'current locale is kept while running perform_later' do
+ skip if adapter_is?(:inline)
+
+ begin
+ I18n.available_locales = [:en, :de]
+ I18n.locale = :de
+
+ TestJob.perform_later @id
+ wait_for_jobs_to_finish_for(5.seconds)
+ assert job_executed
+ assert_equal 'de', job_output
+ ensure
+ I18n.available_locales = [:en]
+ I18n.locale = :en
+ end
+ end
end
diff --git a/activejob/test/jobs/translated_hello_job.rb b/activejob/test/jobs/translated_hello_job.rb
new file mode 100644
index 0000000000..9657cd3f54
--- /dev/null
+++ b/activejob/test/jobs/translated_hello_job.rb
@@ -0,0 +1,10 @@
+require_relative '../support/job_buffer'
+
+class TranslatedHelloJob < ActiveJob::Base
+ def perform(greeter = "David")
+ translations = { en: 'Hello', de: 'Guten Tag' }
+ hello = translations[I18n.locale]
+
+ JobBuffer.add("#{greeter} says #{hello}")
+ end
+end
diff --git a/activejob/test/support/integration/adapters/que.rb b/activejob/test/support/integration/adapters/que.rb
index ba7657a42a..0cd8952a28 100644
--- a/activejob/test/support/integration/adapters/que.rb
+++ b/activejob/test/support/integration/adapters/que.rb
@@ -2,6 +2,15 @@ module QueJobsManager
def setup
require 'sequel'
ActiveJob::Base.queue_adapter = :que
+ Que.mode = :off
+ Que.worker_count = 1
+ end
+
+ def clear_jobs
+ Que.clear!
+ end
+
+ 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']
@@ -11,24 +20,17 @@ module QueJobsManager
%x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'create database "#{db}"' -U #{user} -t template1}
Que.connection = Sequel.connect(que_url)
Que.migrate!
- Que.mode = :off
- Que.worker_count = 1
- rescue Sequel::DatabaseConnectionError
- puts "Cannot run integration tests for que. To be able to run integration tests for que you need to install and start postgresql.\n"
- exit
- end
-
- def clear_jobs
- Que.clear!
- end
- def start_workers
@thread = Thread.new do
loop do
- Que::Job.work("integration_tests")
+ Que::Job.work
sleep 0.5
end
end
+
+ rescue Sequel::DatabaseConnectionError
+ puts "Cannot run integration tests for que. To be able to run integration tests for que you need to install and start postgresql.\n"
+ exit
end
def stop_workers
diff --git a/activejob/test/support/integration/adapters/queue_classic.rb b/activejob/test/support/integration/adapters/queue_classic.rb
index f522b2711f..29c04bf625 100644
--- a/activejob/test/support/integration/adapters/queue_classic.rb
+++ b/activejob/test/support/integration/adapters/queue_classic.rb
@@ -3,17 +3,7 @@ module QueueClassicJobsManager
ENV['QC_DATABASE_URL'] ||= 'postgres:///active_jobs_qc_int_test'
ENV['QC_RAILS_DATABASE'] = 'false'
ENV['QC_LISTEN_TIME'] = "0.5"
- uri = URI.parse(ENV['QC_DATABASE_URL'])
- 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}
- %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'create database "#{db}"' -U #{user} -t template1}
ActiveJob::Base.queue_adapter = :queue_classic
- QC::Setup.create
- rescue PG::ConnectionBad
- puts "Cannot run integration tests for queue_classic. To be able to run integration tests for queue_classic you need to install and start postgresql.\n"
- exit
end
def clear_jobs
@@ -21,12 +11,24 @@ module QueueClassicJobsManager
end
def start_workers
+ uri = URI.parse(ENV['QC_DATABASE_URL'])
+ 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}
+ %x{#{"PGPASSWORD=\"#{pass}\"" if pass} psql -c 'create database "#{db}"' -U #{user} -t template1}
+ QC::Setup.create
+
QC.default_conn_adapter.disconnect
QC.default_conn_adapter = nil
@pid = fork do
worker = QC::Worker.new(q_name: 'integration_tests')
worker.start
end
+
+ rescue PG::ConnectionBad
+ puts "Cannot run integration tests for queue_classic. To be able to run integration tests for queue_classic you need to install and start postgresql.\n"
+ exit
end
def stop_workers
diff --git a/activejob/test/support/integration/adapters/sidekiq.rb b/activejob/test/support/integration/adapters/sidekiq.rb
index 6ff18fb56a..4988cdb33f 100644
--- a/activejob/test/support/integration/adapters/sidekiq.rb
+++ b/activejob/test/support/integration/adapters/sidekiq.rb
@@ -1,6 +1,8 @@
-require 'sidekiq/cli'
require 'sidekiq/api'
+require 'sidekiq/testing'
+Sidekiq::Testing.disable!
+
module SidekiqJobsManager
def setup
@@ -17,33 +19,71 @@ module SidekiqJobsManager
end
def start_workers
- fork do
- sidekiq = Sidekiq::CLI.instance
+ continue_read, continue_write = IO.pipe
+ death_read, death_write = IO.pipe
+
+ @pid = fork do
+ continue_read.close
+ death_write.close
+
+ # Celluloid & Sidekiq are not warning-clean :(
+ $VERBOSE = false
+
+ $stdin.reopen('/dev/null')
+ $stdout.sync = true
+ $stderr.sync = true
+
logfile = Rails.root.join("log/sidekiq.log").to_s
- pidfile = Rails.root.join("tmp/sidekiq.pid").to_s
- sidekiq.parse([ "--require", Rails.root.to_s,
- "--queue", "integration_tests",
- "--logfile", logfile,
- "--pidfile", pidfile,
- "--environment", "test",
- "--concurrency", "1",
- "--timeout", "1",
- "--daemon",
- ])
+ Sidekiq::Logging.initialize_logger(logfile)
+
+ self_read, self_write = IO.pipe
+ trap "TERM" do
+ self_write.puts("TERM")
+ end
+
+ Thread.new do
+ begin
+ death_read.read
+ rescue Exception
+ end
+ self_write.puts("TERM")
+ end
+
require 'celluloid'
- require 'sidekiq/scheduled'
+ Celluloid.logger = nil
+ require 'sidekiq/launcher'
+ sidekiq = Sidekiq::Launcher.new({queues: ["integration_tests"],
+ environment: "test",
+ concurrency: 1,
+ timeout: 1,
+ })
Sidekiq.poll_interval = 0.5
Sidekiq::Scheduled.const_set :INITIAL_WAIT, 1
- sidekiq.run
+ begin
+ sidekiq.run
+ continue_write.puts "started"
+ while readable_io = IO.select([self_read])
+ signal = readable_io.first[0].gets.strip
+ raise Interrupt if signal == "TERM"
+ end
+ rescue Interrupt
+ end
+
+ sidekiq.stop
+ exit!
end
- sleep 1
+ continue_write.close
+ death_read.close
+ @worker_lifeline = death_write
+
+ raise "Failed to start worker" unless continue_read.gets == "started\n"
end
def stop_workers
- pidfile = Rails.root.join("tmp/sidekiq.pid").to_s
- Process.kill 'TERM', File.open(pidfile).read.to_i
- FileUtils.rm_f pidfile
- rescue
+ if @pid
+ Process.kill 'TERM', @pid
+ Process.wait @pid
+ end
end
def can_run?
diff --git a/activejob/test/support/integration/dummy_app_template.rb b/activejob/test/support/integration/dummy_app_template.rb
index 09a68738ad..4ffdb8cffa 100644
--- a/activejob/test/support/integration/dummy_app_template.rb
+++ b/activejob/test/support/integration/dummy_app_template.rb
@@ -8,13 +8,17 @@ require "#{File.expand_path("../jobs_manager.rb", __FILE__)}"
JobsManager.current_manager.setup
CODE
+initializer 'i18n.rb', <<-CODE
+I18n.available_locales = [:en, :de]
+CODE
+
file 'app/jobs/test_job.rb', <<-CODE
class TestJob < ActiveJob::Base
queue_as :integration_tests
def perform(x)
File.open(Rails.root.join("tmp/\#{x}"), "w+") do |f|
- f.write x
+ f.write I18n.locale
end
end
end
diff --git a/activejob/test/support/integration/test_case_helpers.rb b/activejob/test/support/integration/test_case_helpers.rb
index bed28b2900..39aee6d407 100644
--- a/activejob/test/support/integration/test_case_helpers.rb
+++ b/activejob/test/support/integration/test_case_helpers.rb
@@ -27,8 +27,8 @@ module TestCaseHelpers
jobs_manager.clear_jobs
end
- def adapter_is?(adapter_class_symbol)
- ActiveJob::Base.queue_adapter.class.name.split("::").last.gsub(/Adapter$/, '').underscore == adapter_class_symbol.to_s
+ def adapter_is?(*adapter_class_symbols)
+ adapter_class_symbols.map(&:to_s).include?(ActiveJob::Base.queue_adapter.class.name.split("::").last.gsub(/Adapter$/, '').underscore)
end
def wait_for_jobs_to_finish_for(seconds=60)
@@ -45,4 +45,8 @@ module TestCaseHelpers
def job_executed
Dummy::Application.root.join("tmp/#{@id}").exist?
end
+
+ def job_output
+ File.read Dummy::Application.root.join("tmp/#{@id}")
+ end
end
diff --git a/activejob/test/support/que/inline.rb b/activejob/test/support/que/inline.rb
index 2e210acb6b..0232da1370 100644
--- a/activejob/test/support/que/inline.rb
+++ b/activejob/test/support/que/inline.rb
@@ -3,7 +3,11 @@ require 'que'
Que::Job.class_eval do
class << self; alias_method :original_enqueue, :enqueue; end
def self.enqueue(*args)
- args.pop if args.last.is_a?(Hash)
+ if args.last.is_a?(Hash)
+ options = args.pop
+ options.delete(:run_at)
+ args << options unless options.empty?
+ end
self.run(*args)
end
end
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 32a2cb4517..f25c901ecc 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,3 +1,21 @@
+* Ensure `method_missing` is called for methods passed to
+ `ActiveModel::Serialization#serializable_hash` that don't exist.
+
+ *Jay Elaraj*
+
+* Remove `ActiveModel::Serializers::Xml` from core.
+
+ *Zachary Scott*
+
+* Add `ActiveModel::Dirty#[attr_name]_previously_changed?` and
+ `ActiveModel::Dirty#[attr_name]_previous_change` to improve access
+ to recorded changes after the model has been saved.
+
+ It makes the dirty-attributes query methods consistent before and after
+ saving.
+
+ *Fernando Tapia Rico*
+
* Deprecate the `:tokenizer` option for `validates_length_of`, in favor of
plain Ruby.
@@ -9,11 +27,11 @@
*Wojciech Wnętrzak*
* Deprecate `ActiveModel::Errors#get`, `ActiveModel::Errors#set` and
- `ActiveModel::Errors#[]=` methods that have inconsistent behaviour.
+ `ActiveModel::Errors#[]=` methods that have inconsistent behavior.
*Wojciech Wnętrzak*
-* Allow symbol as values for `tokenize` of `LengthValidator`
+* Allow symbol as values for `tokenize` of `LengthValidator`.
*Kensuke Naito*
diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc
index 4920666f27..20414c1d61 100644
--- a/activemodel/README.rdoc
+++ b/activemodel/README.rdoc
@@ -154,8 +154,8 @@ behavior out of the box:
* Making objects serializable
- ActiveModel::Serialization provides a standard interface for your object
- to provide +to_json+ or +to_xml+ serialization.
+ <tt>ActiveModel::Serialization</tt> provides a standard interface for your object
+ to provide +to_json+ serialization.
class SerialPerson
include ActiveModel::Serialization
@@ -177,13 +177,6 @@ behavior out of the box:
s = SerialPerson.new
s.to_json # => "{\"name\":null}"
- class SerialPerson
- include ActiveModel::Serializers::Xml
- end
-
- s = SerialPerson.new
- s.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
-
{Learn more}[link:classes/ActiveModel/Serialization.html]
* Internationalization (i18n) support
@@ -242,7 +235,7 @@ behavior out of the box:
The latest version of Active Model can be installed with RubyGems:
- % [sudo] gem install activemodel
+ % gem install activemodel
Source code can be downloaded as part of the Rails project on GitHub
diff --git a/activemodel/Rakefile b/activemodel/Rakefile
index 7256285a41..5a67f0a151 100644
--- a/activemodel/Rakefile
+++ b/activemodel/Rakefile
@@ -1,7 +1,7 @@
-dir = File.dirname(__FILE__)
-
require 'rake/testtask'
+dir = File.dirname(__FILE__)
+
task :default => :test
Rake::TestTask.new do |t|
@@ -19,18 +19,3 @@ namespace :test do
end or raise "Failures"
end
end
-
-require 'rubygems/package_task'
-
-spec = eval(File.read("#{dir}/activemodel.gemspec"))
-
-Gem::PackageTask.new(spec) do |p|
- p.gem_spec = spec
-end
-
-desc "Release to rubygems"
-task :release => :package do
- require 'rake/gemcutter'
- Rake::Gemcutter::Tasks.new(spec).define
- Rake::Task['gem:push'].invoke
-end
diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec
index 1a16f2a1ed..8d00b3aa27 100644
--- a/activemodel/activemodel.gemspec
+++ b/activemodel/activemodel.gemspec
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
s.summary = 'A toolkit for building modeling frameworks (part of Rails).'
s.description = 'A toolkit for building modeling frameworks like Active Record. Rich support for attributes, callbacks, validations, serialization, internationalization, and testing.'
- s.required_ruby_version = '>= 2.2.1'
+ s.required_ruby_version = '>= 2.2.2'
s.license = 'MIT'
diff --git a/activemodel/bin/test b/activemodel/bin/test
new file mode 100755
index 0000000000..404cabba51
--- /dev/null
+++ b/activemodel/bin/test
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+COMPONENT_ROOT = File.expand_path("../../", __FILE__)
+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 8aa1b6f664..4e1b3f7495 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -58,7 +58,6 @@ module ActiveModel
eager_autoload do
autoload :JSON
- autoload :Xml
end
end
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 96be551264..77e4ce3afe 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -23,7 +23,7 @@ module ActiveModel
# The requirements to implement <tt>ActiveModel::AttributeMethods</tt> are to:
#
# * <tt>include ActiveModel::AttributeMethods</tt> in your class.
- # * Call each of its method you want to add, such as +attribute_method_suffix+
+ # * Call each of its methods you want to add, such as +attribute_method_suffix+
# or +attribute_method_prefix+.
# * Call +define_attribute_methods+ after the other methods are called.
# * Define the various generic +_attribute+ methods that you have declared.
@@ -225,9 +225,9 @@ module ActiveModel
end
# Declares the attributes that should be prefixed and suffixed by
- # ActiveModel::AttributeMethods.
+ # <tt>ActiveModel::AttributeMethods</tt>.
#
- # To use, pass attribute names (as strings or symbols), be sure to declare
+ # To use, pass attribute names (as strings or symbols). Be sure to declare
# +define_attribute_methods+ after you define any prefix, suffix or affix
# methods, or they will not hook in.
#
@@ -239,7 +239,7 @@ module ActiveModel
#
# # Call to define_attribute_methods must appear after the
# # attribute_method_prefix, attribute_method_suffix or
- # # attribute_method_affix declares.
+ # # attribute_method_affix declarations.
# define_attribute_methods :name, :age, :address
#
# private
@@ -253,9 +253,9 @@ module ActiveModel
end
# Declares an attribute that should be prefixed and suffixed by
- # ActiveModel::AttributeMethods.
+ # <tt>ActiveModel::AttributeMethods</tt>.
#
- # To use, pass an attribute name (as string or symbol), be sure to declare
+ # To use, pass an attribute name (as string or symbol). Be sure to declare
# +define_attribute_method+ after you define any prefix, suffix or affix
# method, or they will not hook in.
#
@@ -267,7 +267,7 @@ module ActiveModel
#
# # Call to define_attribute_method must appear after the
# # attribute_method_prefix, attribute_method_suffix or
- # # attribute_method_affix declares.
+ # # attribute_method_affix declarations.
# define_attribute_method :name
#
# private
@@ -342,7 +342,7 @@ module ActiveModel
private
# The methods +method_missing+ and +respond_to?+ of this module are
# invoked often in a typical rails, both of which invoke the method
- # +match_attribute_method?+. The latter method iterates through an
+ # +matched_attribute_method+. The latter method iterates through an
# array doing regular expression matches, which results in a lot of
# object creations. Most of the time it returns a +nil+ match. As the
# match result is always the same given a +method_name+, this cache is
@@ -363,7 +363,7 @@ module ActiveModel
end
# Define a method `name` in `mod` that dispatches to `send`
- # using the given `extra` args. This fallbacks `define_method`
+ # 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:
defn = if name =~ NAME_COMPILABLE_REGEXP
@@ -372,7 +372,7 @@ module ActiveModel
"define_method(:'#{name}') do |*args|"
end
- extra = (extra.map!(&:inspect) << "*args").join(", ")
+ extra = (extra.map!(&:inspect) << "*args").join(", ".freeze)
target = if send =~ CALL_COMPILABLE_REGEXP
"#{"self." unless include_private}#{send}(#{extra})"
@@ -419,7 +419,7 @@ module ActiveModel
# returned by <tt>attributes</tt>, as though they were first-class
# methods. So a +Person+ class with a +name+ attribute can for example use
# <tt>Person#name</tt> and <tt>Person#name=</tt> and never directly use
- # the attributes hash -- except for multiple assigns with
+ # the attributes hash -- except for multiple assignments with
# <tt>ActiveRecord::Base#attributes=</tt>.
#
# It's also possible to instantiate related objects, so a <tt>Client</tt>
@@ -429,7 +429,7 @@ module ActiveModel
if respond_to_without_attributes?(method, true)
super
else
- match = match_attribute_method?(method.to_s)
+ match = matched_attribute_method(method.to_s)
match ? attribute_missing(match, *args, &block) : super
end
end
@@ -454,7 +454,7 @@ module ActiveModel
# but found among all methods. Which means that the given method is private.
false
else
- !match_attribute_method?(method.to_s).nil?
+ !matched_attribute_method(method.to_s).nil?
end
end
@@ -466,7 +466,7 @@ module ActiveModel
private
# Returns a struct representing the matching attribute method.
# The struct's attributes are prefix, base and suffix.
- def match_attribute_method?(method_name)
+ def matched_attribute_method(method_name)
matches = self.class.send(:attribute_method_matchers_matching, method_name)
matches.detect { |match| attribute_method?(match.attr_name) }
end
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index c03e5fac79..0169c20e0b 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -76,9 +76,11 @@ module ActiveModel
#
# Reset the changes:
#
- # person.previous_changes # => {"name" => ["Uncle Bob", "Bill"]}
+ # person.previous_changes # => {"name" => ["Uncle Bob", "Bill"]}
+ # person.name_previously_changed? # => true
+ # person.name_previous_change # => ["Uncle Bob", "Bill"]
# person.reload!
- # person.previous_changes # => {}
+ # person.previous_changes # => {}
#
# Rollback the changes:
#
@@ -100,10 +102,10 @@ module ActiveModel
# person.changes # => {"name" => ["Bill", "Bob"]}
#
# If an attribute is modified in-place then make use of
- # +[attribute_name]_will_change!+ to mark that the attribute is changing.
+ # <tt>[attribute_name]_will_change!</tt> to mark that the attribute is changing.
# Otherwise \Active \Model can't track changes to in-place attributes. Note
# that Active Record can detect in-place modifications automatically. You do
- # not need to call +[attribute_name]_will_change!+ on Active Record models.
+ # not need to call <tt>[attribute_name]_will_change!</tt> on Active Record models.
#
# person.name_will_change!
# person.name_change # => ["Bill", "Bill"]
@@ -115,6 +117,7 @@ module ActiveModel
included do
attribute_method_suffix '_changed?', '_change', '_will_change!', '_was'
+ attribute_method_suffix '_previously_changed?', '_previous_change'
attribute_method_affix prefix: 'restore_', suffix: '!'
end
@@ -179,6 +182,11 @@ module ActiveModel
attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
end
+ # Handles <tt>*_previously_changed?</tt> for +method_missing+.
+ def attribute_previously_changed?(attr, options = {}) #:nodoc:
+ previous_changes_include?(attr)
+ end
+
# Restore all previous data of the provided attributes.
def restore_attributes(attributes = changed)
attributes.each { |attr| restore_attribute! attr }
@@ -192,6 +200,12 @@ module ActiveModel
end
alias attribute_changed_by_setter? changes_include?
+ # Returns +true+ if attr_name were changed before the model was saved,
+ # +false+ otherwise.
+ def previous_changes_include?(attr_name)
+ @previously_changed.include?(attr_name)
+ end
+
# Removes current changes and makes them accessible through +previous_changes+.
def changes_applied # :doc:
@previously_changed = changes
@@ -209,6 +223,11 @@ module ActiveModel
[changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
end
+ # Handles <tt>*_previous_change</tt> for +method_missing+.
+ def attribute_previous_change(attr)
+ @previously_changed[attr] if attribute_previously_changed?(attr)
+ end
+
# Handles <tt>*_will_change!</tt> for +method_missing+.
def attribute_will_change!(attr)
return if attribute_changed?(attr)
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index f843b279ce..29e0c977ce 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -43,11 +43,11 @@ module ActiveModel
# end
# end
#
- # The last three methods are required in your object for Errors to be
+ # The last three methods are required in your object for +Errors+ to be
# able to generate error messages correctly and also handle multiple
- # languages. Of course, if you extend your object with ActiveModel::Translation
+ # languages. Of course, if you extend your object with <tt>ActiveModel::Translation</tt>
# you will not need to implement the last two. Likewise, using
- # ActiveModel::Validations will handle the validation related methods
+ # <tt>ActiveModel::Validations</tt> will handle the validation related methods
# for you.
#
# The above allows you to do:
@@ -452,7 +452,6 @@ module ActiveModel
defaults = []
end
- defaults << options.delete(:message)
defaults << :"#{@base.class.i18n_scope}.errors.messages.#{type}" if @base.class.respond_to?(:i18n_scope)
defaults << :"errors.attributes.#{attribute}.#{type}"
defaults << :"errors.messages.#{type}"
@@ -461,6 +460,7 @@ module ActiveModel
defaults.flatten!
key = defaults.shift
+ defaults = options.delete(:message) if options[:message]
value = (attribute != :base ? @base.send(:read_attribute_for_validation, attribute) : nil)
options = {
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index 1f1749af4e..213f2d5b6a 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -192,7 +192,7 @@ module ActiveModel
private
def _singularize(string)
- ActiveSupport::Inflector.underscore(string).tr('/', '_')
+ ActiveSupport::Inflector.underscore(string).tr('/'.freeze, '_'.freeze)
end
end
diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb
index 976f50b13e..58d68d9620 100644
--- a/activemodel/lib/active_model/serialization.rb
+++ b/activemodel/lib/active_model/serialization.rb
@@ -40,7 +40,6 @@ module ActiveModel
#
# class Person
# include ActiveModel::Serializers::JSON
- # include ActiveModel::Serializers::Xml
#
# attr_accessor :name
#
@@ -55,13 +54,11 @@ module ActiveModel
# person.serializable_hash # => {"name"=>nil}
# person.as_json # => {"name"=>nil}
# person.to_json # => "{\"name\":null}"
- # person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
#
# person.name = "Bob"
# person.serializable_hash # => {"name"=>"Bob"}
# person.as_json # => {"name"=>"Bob"}
# person.to_json # => "{\"name\":\"Bob\"}"
- # person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
#
# Valid options are <tt>:only</tt>, <tt>:except</tt>, <tt>:methods</tt> and
# <tt>:include</tt>. The following are all valid examples:
@@ -94,6 +91,37 @@ module ActiveModel
# person.serializable_hash(except: :name) # => {"age"=>22}
# person.serializable_hash(methods: :capitalized_name)
# # => {"name"=>"bob", "age"=>22, "capitalized_name"=>"Bob"}
+ #
+ # Example with <tt>:include</tt> option
+ #
+ # class User
+ # include ActiveModel::Serializers::JSON
+ # attr_accessor :name, :notes # Emulate has_many :notes
+ # def attributes
+ # {'name' => nil}
+ # end
+ # end
+ #
+ # class Note
+ # include ActiveModel::Serializers::JSON
+ # attr_accessor :title, :text
+ # def attributes
+ # {'title' => nil, 'text' => nil}
+ # end
+ # end
+ #
+ # note = Note.new
+ # note.title = 'Battle of Austerlitz'
+ # note.text = 'Some text here'
+ #
+ # user = User.new
+ # user.name = 'Napoleon'
+ # user.notes = [note]
+ #
+ # user.serializable_hash
+ # # => {"name" => "Napoleon"}
+ # user.serializable_hash(include: { notes: { only: 'title' }})
+ # # => {"name" => "Napoleon", "notes" => [{"title"=>"Battle of Austerlitz"}]}
def serializable_hash(options = nil)
options ||= {}
@@ -107,7 +135,7 @@ module ActiveModel
hash = {}
attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) }
- Array(options[:methods]).each { |m| hash[m.to_s] = send(m) if respond_to?(m) }
+ Array(options[:methods]).each { |m| hash[m.to_s] = send(m) }
serializable_add_includes(options) do |association, records, opts|
hash[association.to_s] = if records.respond_to?(:to_ary)
diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb
deleted file mode 100644
index e33c766627..0000000000
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ /dev/null
@@ -1,238 +0,0 @@
-require 'active_support/core_ext/module/attribute_accessors'
-require 'active_support/core_ext/array/conversions'
-require 'active_support/core_ext/hash/conversions'
-require 'active_support/core_ext/hash/slice'
-require 'active_support/core_ext/time/acts_like'
-
-module ActiveModel
- module Serializers
- # == \Active \Model XML Serializer
- module Xml
- extend ActiveSupport::Concern
- include ActiveModel::Serialization
-
- included do
- extend ActiveModel::Naming
- end
-
- class Serializer #:nodoc:
- class Attribute #:nodoc:
- attr_reader :name, :value, :type
-
- def initialize(name, serializable, value)
- @name, @serializable = name, serializable
-
- if value.acts_like?(:time) && value.respond_to?(:in_time_zone)
- value = value.in_time_zone
- end
-
- @value = value
- @type = compute_type
- end
-
- def decorations
- decorations = {}
- decorations[:encoding] = 'base64' if type == :binary
- decorations[:type] = (type == :string) ? nil : type
- decorations[:nil] = true if value.nil?
- decorations
- end
-
- protected
-
- def compute_type
- return if value.nil?
- type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name]
- type ||= :string if value.respond_to?(:to_str)
- type ||= :yaml
- type
- end
- end
-
- class MethodAttribute < Attribute #:nodoc:
- end
-
- attr_reader :options
-
- def initialize(serializable, options = nil)
- @serializable = serializable
- @options = options ? options.dup : {}
- end
-
- def serializable_hash
- @serializable.serializable_hash(@options.except(:include))
- end
-
- def serializable_collection
- methods = Array(options[:methods]).map(&:to_s)
- serializable_hash.map do |name, value|
- name = name.to_s
- if methods.include?(name)
- self.class::MethodAttribute.new(name, @serializable, value)
- else
- self.class::Attribute.new(name, @serializable, value)
- end
- end
- end
-
- def serialize
- require 'builder' unless defined? ::Builder
-
- options[:indent] ||= 2
- options[:builder] ||= ::Builder::XmlMarkup.new(indent: options[:indent])
-
- @builder = options[:builder]
- @builder.instruct! unless options[:skip_instruct]
-
- root = (options[:root] || @serializable.model_name.element).to_s
- root = ActiveSupport::XmlMini.rename_key(root, options)
-
- args = [root]
- args << { xmlns: options[:namespace] } if options[:namespace]
- args << { type: options[:type] } if options[:type] && !options[:skip_types]
-
- @builder.tag!(*args) do
- add_attributes_and_methods
- add_includes
- add_extra_behavior
- add_procs
- yield @builder if block_given?
- end
- end
-
- private
-
- def add_extra_behavior
- end
-
- def add_attributes_and_methods
- serializable_collection.each do |attribute|
- key = ActiveSupport::XmlMini.rename_key(attribute.name, options)
- ActiveSupport::XmlMini.to_tag(key, attribute.value,
- options.merge(attribute.decorations))
- end
- end
-
- def add_includes
- @serializable.send(:serializable_add_includes, options) do |association, records, opts|
- add_associations(association, records, opts)
- end
- end
-
- # TODO: This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well.
- def add_associations(association, records, opts)
- merged_options = opts.merge(options.slice(:builder, :indent))
- merged_options[:skip_instruct] = true
-
- [:skip_types, :dasherize, :camelize].each do |key|
- merged_options[key] = options[key] if merged_options[key].nil? && !options[key].nil?
- end
-
- if records.respond_to?(:to_ary)
- records = records.to_ary
-
- tag = ActiveSupport::XmlMini.rename_key(association.to_s, options)
- type = options[:skip_types] ? { } : { type: "array" }
- association_name = association.to_s.singularize
- merged_options[:root] = association_name
-
- if records.empty?
- @builder.tag!(tag, type)
- else
- @builder.tag!(tag, type) do
- records.each do |record|
- if options[:skip_types]
- record_type = {}
- else
- record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name
- record_type = { type: record_class }
- end
-
- record.to_xml merged_options.merge(record_type)
- end
- end
- end
- else
- merged_options[:root] = association.to_s
-
- unless records.class.to_s.underscore == association.to_s
- merged_options[:type] = records.class.name
- end
-
- records.to_xml merged_options
- end
- end
-
- def add_procs
- if procs = options.delete(:procs)
- Array(procs).each do |proc|
- if proc.arity == 1
- proc.call(options)
- else
- proc.call(options, @serializable)
- end
- end
- end
- end
- end
-
- # Returns XML representing the model. Configuration can be
- # passed through +options+.
- #
- # Without any +options+, the returned XML string will include all the
- # model's attributes.
- #
- # user = User.find(1)
- # user.to_xml
- #
- # <?xml version="1.0" encoding="UTF-8"?>
- # <user>
- # <id type="integer">1</id>
- # <name>David</name>
- # <age type="integer">16</age>
- # <created-at type="dateTime">2011-01-30T22:29:23Z</created-at>
- # </user>
- #
- # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the
- # attributes included, and work similar to the +attributes+ method.
- #
- # To include the result of some method calls on the model use <tt>:methods</tt>.
- #
- # To include associations use <tt>:include</tt>.
- #
- # For further documentation, see <tt>ActiveRecord::Serialization#to_xml</tt>
- def to_xml(options = {}, &block)
- Serializer.new(self, options).serialize(&block)
- end
-
- # Sets the model +attributes+ from an XML string. Returns +self+.
- #
- # class Person
- # include ActiveModel::Serializers::Xml
- #
- # attr_accessor :name, :age, :awesome
- #
- # def attributes=(hash)
- # hash.each do |key, value|
- # instance_variable_set("@#{key}", value)
- # end
- # end
- #
- # def attributes
- # instance_values
- # end
- # end
- #
- # xml = { name: 'bob', age: 22, awesome:true }.to_xml
- # person = Person.new
- # person.from_xml(xml) # => #<Person:0x007fec5e3b3c40 @age=22, @awesome=true, @name="bob">
- # person.name # => "bob"
- # person.age # => 22
- # person.awesome # => true
- def from_xml(xml)
- self.attributes = Hash.from_xml(xml).values.first
- self
- end
- end
- end
-end
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 74d60327d6..5f1dde4aa3 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -87,7 +87,7 @@ module ActiveModel
validates_with BlockValidator, _merge_attributes(attr_names), &block
end
- VALID_OPTIONS_FOR_VALIDATE = [:on, :if, :unless, :prepend].freeze
+ VALID_OPTIONS_FOR_VALIDATE = [:on, :if, :unless, :prepend].freeze # :nodoc:
# Adds a validation method or block to the class. This is useful when
# overriding the +validate+ instance method becomes too unwieldy and
@@ -129,6 +129,9 @@ module ActiveModel
# end
# end
#
+ # Note that the return value of validation methods is not relevant.
+ # It's not possible to halt the validate callback chain.
+ #
# 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
@@ -401,7 +404,7 @@ module ActiveModel
protected
def run_validations! #:nodoc:
- run_callbacks :validate
+ _run_validate_callbacks
errors.empty?
end
diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb
index ee160fb483..1bcfedb35d 100644
--- a/activemodel/lib/active_model/validations/acceptance.rb
+++ b/activemodel/lib/active_model/validations/acceptance.rb
@@ -42,9 +42,10 @@ module ActiveModel
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "must be
# accepted").
- # * <tt>:accept</tt> - Specifies value that is considered accepted.
- # The default value is a string "1", which makes it easy to relate to
- # an HTML checkbox. This should be set to +true+ if you are validating
+ # * <tt>:accept</tt> - Specifies a value that is considered accepted.
+ # Also accepts an array of possible values. The default value is
+ # an array ["1", true], which makes it easy to relate to an HTML
+ # checkbox. This should be set to, or include, +true+ if you are validating
# a database column, since the attribute is typecast from "1" to +true+
# before validation.
#
diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb
index b4301c23e4..4b58ef66e3 100644
--- a/activemodel/lib/active_model/validations/callbacks.rb
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -109,7 +109,7 @@ module ActiveModel
# Overwrite run validations to include callbacks.
def run_validations! #:nodoc:
- run_callbacks(:validation) { super }
+ _run_validation_callbacks { super }
end
end
end
diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb
index f342d27275..6f4276cc2a 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -29,7 +29,9 @@ module ActiveModel
# Configuration options:
# * <tt>:in</tt> - An enumerable object of items that the value shouldn't
# be part of. This can be supplied as a proc, lambda or symbol which returns an
- # enumerable. If the enumerable is a range the test is performed with
+ # enumerable. If the enumerable is a numerical, time or datetime range the test
+ # is performed with <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>. When
+ # using a proc or lambda the instance under validation is passed as an argument.
# * <tt>:within</tt> - A synonym(or alias) for <tt>:in</tt>
# <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>.
# * <tt>:message</tt> - Specifies a custom error message (default is: "is
diff --git a/activemodel/lib/active_model/validations/helper_methods.rb b/activemodel/lib/active_model/validations/helper_methods.rb
new file mode 100644
index 0000000000..2176115334
--- /dev/null
+++ b/activemodel/lib/active_model/validations/helper_methods.rb
@@ -0,0 +1,13 @@
+module ActiveModel
+ module Validations
+ module HelperMethods # :nodoc:
+ private
+ def _merge_attributes(attr_names)
+ options = attr_names.extract_options!.symbolize_keys
+ attr_names.flatten!
+ options[:attributes] = attr_names
+ options
+ end
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb
index c84025f083..03e0ef56d8 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -28,9 +28,9 @@ module ActiveModel
# Configuration options:
# * <tt>:in</tt> - An enumerable object of available items. This can be
# supplied as a proc, lambda or symbol which returns an enumerable. If the
- # enumerable is a numerical range the test is performed with <tt>Range#cover?</tt>,
- # otherwise with <tt>include?</tt>. When using a proc or lambda the instance
- # under validation is passed as an argument.
+ # enumerable is a numerical, time or datetime range the test is performed
+ # with <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>. When using
+ # a proc or lambda the instance under validation is passed as an argument.
# * <tt>:within</tt> - A synonym(or alias) for <tt>:in</tt>
# * <tt>:message</tt> - Specifies a custom error message (default is: "is
# not included in the list").
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index c22a58f9e1..910cca2f49 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -1,8 +1,6 @@
require "active_support/core_ext/string/strip"
module ActiveModel
-
- # == Active \Model Length Validator
module Validations
class LengthValidator < EachValidator # :nodoc:
MESSAGES = { is: :wrong_length, minimum: :too_short, maximum: :too_long }.freeze
@@ -100,8 +98,9 @@ module ActiveModel
module HelperMethods
- # Validates that the specified attribute matches the length restrictions
- # supplied. Only one option can be used at a time:
+ # Validates that the specified attributes match the length restrictions
+ # supplied. Only one constraint option can be used at a time apart from
+ # +:minimum+ and +:maximum+ that can be combined together:
#
# class Person < ActiveRecord::Base
# validates_length_of :first_name, maximum: 30
@@ -120,14 +119,18 @@ module ActiveModel
# end
# end
#
- # Configuration options:
+ # Constraint options:
+ #
# * <tt>:minimum</tt> - The minimum size of the attribute.
# * <tt>:maximum</tt> - The maximum size of the attribute. Allows +nil+ by
- # default if not used with :minimum.
+ # default if not used with +:minimum+.
# * <tt>:is</tt> - The exact size of the attribute.
# * <tt>:within</tt> - A range specifying the minimum and maximum size of
# the attribute.
# * <tt>:in</tt> - A synonym (or alias) for <tt>:within</tt>.
+ #
+ # Other options:
+ #
# * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
# * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
# * <tt>:too_long</tt> - The error message if the attribute goes over the
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index bda436d8d0..1da4df21e7 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -115,7 +115,7 @@ module ActiveModel
key = "#{key.to_s.camelize}Validator"
begin
- validator = key.include?('::') ? key.constantize : const_get(key)
+ validator = key.include?('::'.freeze) ? key.constantize : const_get(key)
rescue NameError
raise ArgumentError, "Unknown validator: '#{key}'"
end
diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb
index a2531327bf..6de01b3392 100644
--- a/activemodel/lib/active_model/validations/with.rb
+++ b/activemodel/lib/active_model/validations/with.rb
@@ -1,15 +1,5 @@
module ActiveModel
module Validations
- module HelperMethods
- private
- def _merge_attributes(attr_names)
- options = attr_names.extract_options!.symbolize_keys
- attr_names.flatten!
- options[:attributes] = attr_names
- options
- end
- end
-
class WithValidator < EachValidator # :nodoc:
def validate_each(record, attr, val)
method_name = options[:with]
diff --git a/activemodel/test/cases/attribute_assignment_test.rb b/activemodel/test/cases/attribute_assignment_test.rb
index 3b01644dd1..64a85e01eb 100644
--- a/activemodel/test/cases/attribute_assignment_test.rb
+++ b/activemodel/test/cases/attribute_assignment_test.rb
@@ -58,8 +58,6 @@ class AttributeAssignmentTest < ActiveModel::TestCase
end
test "assign private attribute" do
- rubinius_skip "https://github.com/rubinius/rubinius/issues/3328"
-
model = Model.new
assert_raises(ActiveModel::UnknownAttributeError) do
model.assign_attributes(metadata: { a: 1 })
diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb
index 66ed8a350a..d17a12ad12 100644
--- a/activemodel/test/cases/dirty_test.rb
+++ b/activemodel/test/cases/dirty_test.rb
@@ -137,6 +137,19 @@ class DirtyTest < ActiveModel::TestCase
assert_equal [nil, "Jericho Cane"], @model.previous_changes['name']
end
+ test "setting new attributes should not affect previous changes" do
+ @model.name = "Jericho Cane"
+ @model.save
+ @model.name = "DudeFella ManGuy"
+ assert_equal [nil, "Jericho Cane"], @model.name_previous_change
+ end
+
+ test "saving should preserve model's previous changed status" do
+ @model.name = "Jericho Cane"
+ @model.save
+ assert @model.name_previously_changed?
+ end
+
test "previous value is preserved when changed after save" do
assert_equal({}, @model.changed_attributes)
@model.name = "Paul"
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index f781a0017f..f6d171bec6 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -149,6 +149,12 @@ class ErrorsTest < ActiveModel::TestCase
assert_equal ["cannot be blank"], person.errors[:name]
end
+ test "add an error message on a specific attribute with a defined type" do
+ person = Person.new
+ person.errors.add(:name, :blank, message: "cannot be blank")
+ assert_equal ["cannot be blank"], person.errors[:name]
+ end
+
test "add an error with a symbol" do
person = Person.new
person.errors.add(:name, :blank)
@@ -293,60 +299,67 @@ class ErrorsTest < ActiveModel::TestCase
test "add_on_empty generates message" do
person = Person.new
- person.errors.expects(:generate_message).with(:name, :empty, {})
- assert_deprecated do
- person.errors.add_on_empty :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_empty generates message for multiple attributes" do
person = Person.new
- person.errors.expects(:generate_message).with(:name, :empty, {})
- person.errors.expects(:generate_message).with(:age, :empty, {})
- assert_deprecated do
- person.errors.add_on_empty [:name, :age]
+ 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
- person.errors.expects(:generate_message).with(:name, :empty, { message: 'custom' })
- assert_deprecated do
- person.errors.add_on_empty :name, message: 'custom'
+ 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 = ''
- person.errors.expects(:generate_message).with(:name, :empty, {})
- assert_deprecated do
- person.errors.add_on_empty :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
- person.errors.expects(:generate_message).with(:name, :blank, {})
- assert_deprecated do
- person.errors.add_on_blank :name
+ 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
- person.errors.expects(:generate_message).with(:name, :blank, {})
- person.errors.expects(:generate_message).with(:age, :blank, {})
- assert_deprecated do
- person.errors.add_on_blank [:name, :age]
+ 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
- person.errors.expects(:generate_message).with(:name, :blank, { message: 'custom' })
- assert_deprecated do
- person.errors.add_on_blank :name, message: 'custom'
+ 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
diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb
index 2b9de5e5d2..c100646837 100644
--- a/activemodel/test/cases/helper.rb
+++ b/activemodel/test/cases/helper.rb
@@ -11,8 +11,9 @@ ActiveSupport::Deprecation.debug = true
I18n.enforce_available_locales = false
require 'active_support/testing/autorun'
+require 'active_support/testing/method_call_assertions'
-require 'mocha/setup' # FIXME: stop using mocha
+require 'minitest/mock'
# Skips the current run on Rubinius using Minitest::Assertions#skip
def rubinius_skip(message = '')
@@ -22,3 +23,7 @@ end
def jruby_skip(message = '')
skip message if defined?(JRUBY_VERSION)
end
+
+class ActiveModel::TestCase
+ include ActiveSupport::Testing::MethodCallAssertions
+end
diff --git a/activemodel/test/cases/serialization_test.rb b/activemodel/test/cases/serialization_test.rb
index 4ae41aa19c..8d3165cd78 100644
--- a/activemodel/test/cases/serialization_test.rb
+++ b/activemodel/test/cases/serialization_test.rb
@@ -16,6 +16,14 @@ class SerializationTest < ActiveModel::TestCase
instance_values.except("address", "friends")
end
+ def method_missing(method_name, *args)
+ if method_name == :bar
+ 'i_am_bar'
+ else
+ super
+ end
+ end
+
def foo
'i_am_foo'
end
@@ -58,23 +66,22 @@ class SerializationTest < ActiveModel::TestCase
end
def test_method_serializable_hash_should_work_with_methods_option
- expected = {"name"=>"David", "gender"=>"male", "foo"=>"i_am_foo", "email"=>"david@example.com"}
- assert_equal expected, @user.serializable_hash(methods: [:foo])
+ 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"}
- assert_equal expected, @user.serializable_hash(only: [], methods: [:foo])
+ 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"}
- assert_equal expected, @user.serializable_hash(except: [:name, :email], methods: [:foo])
+ expected = {"gender"=>"male", "foo"=>"i_am_foo", "bar"=>"i_am_bar"}
+ assert_equal expected, @user.serializable_hash(except: [:name, :email], methods: [:foo, :bar])
end
- def test_should_not_call_methods_that_dont_respond
- expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"}
- assert_equal expected, @user.serializable_hash(methods: [:bar])
+ def test_should_raise_NoMethodError_for_non_existing_method
+ assert_raise(NoMethodError) { @user.serializable_hash(methods: [:nada]) }
end
def test_should_use_read_attribute_for_serialization
diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb
deleted file mode 100644
index 22fca5bd17..0000000000
--- a/activemodel/test/cases/serializers/xml_serialization_test.rb
+++ /dev/null
@@ -1,250 +0,0 @@
-require 'cases/helper'
-require 'models/contact'
-require 'active_support/core_ext/object/instance_variables'
-require 'ostruct'
-
-module Admin
- class Contact < ::Contact
- end
-end
-
-class Customer < Struct.new(:name)
-end
-
-class Address
- include ActiveModel::Serializers::Xml
-
- attr_accessor :street, :city, :state, :zip, :apt_number
-
- def attributes
- instance_values
- end
-end
-
-class SerializableContact < Contact
- def serializable_hash(options={})
- super(options.merge(only: [:name, :age]))
- end
-end
-
-class XmlSerializationTest < ActiveModel::TestCase
- def setup
- @contact = Contact.new
- @contact.name = 'aaron stack'
- @contact.age = 25
- @contact.created_at = Time.utc(2006, 8, 1)
- @contact.awesome = false
- customer = Customer.new
- customer.name = "John"
- @contact.preferences = customer
- @contact.address = Address.new
- @contact.address.city = "Springfield"
- @contact.address.apt_number = 35
- @contact.friends = [Contact.new, Contact.new]
- @contact.contact = SerializableContact.new
- end
-
- test "should serialize default root" do
- xml = @contact.to_xml
- assert_match %r{^<contact>}, xml
- assert_match %r{</contact>$}, xml
- end
-
- test "should serialize namespaced root" do
- xml = Admin::Contact.new(@contact.attributes).to_xml
- assert_match %r{^<contact>}, xml
- assert_match %r{</contact>$}, xml
- end
-
- test "should serialize default root with namespace" do
- xml = @contact.to_xml namespace: "http://xml.rubyonrails.org/contact"
- assert_match %r{^<contact xmlns="http://xml.rubyonrails.org/contact">}, xml
- assert_match %r{</contact>$}, xml
- end
-
- test "should serialize custom root" do
- xml = @contact.to_xml root: 'xml_contact'
- assert_match %r{^<xml-contact>}, xml
- assert_match %r{</xml-contact>$}, xml
- end
-
- test "should allow undasherized tags" do
- xml = @contact.to_xml root: 'xml_contact', dasherize: false
- assert_match %r{^<xml_contact>}, xml
- assert_match %r{</xml_contact>$}, xml
- assert_match %r{<created_at}, xml
- end
-
- test "should allow camelized tags" do
- xml = @contact.to_xml root: 'xml_contact', camelize: true
- assert_match %r{^<XmlContact>}, xml
- assert_match %r{</XmlContact>$}, xml
- assert_match %r{<CreatedAt}, xml
- end
-
- test "should allow lower-camelized tags" do
- xml = @contact.to_xml root: 'xml_contact', camelize: :lower
- assert_match %r{^<xmlContact>}, xml
- assert_match %r{</xmlContact>$}, xml
- assert_match %r{<createdAt}, xml
- end
-
- test "should use serializable hash" do
- @contact = SerializableContact.new
- @contact.name = 'aaron stack'
- @contact.age = 25
-
- xml = @contact.to_xml
- assert_match %r{<name>aaron stack</name>}, xml
- assert_match %r{<age type="integer">25</age>}, xml
- assert_no_match %r{<awesome>}, xml
- end
-
- test "should allow skipped types" do
- xml = @contact.to_xml skip_types: true
- assert_match %r{<age>25</age>}, xml
- end
-
- test "should include yielded additions" do
- xml_output = @contact.to_xml do |xml|
- xml.creator "David"
- end
- assert_match %r{<creator>David</creator>}, xml_output
- end
-
- test "should serialize string" do
- assert_match %r{<name>aaron stack</name>}, @contact.to_xml
- end
-
- test "should serialize nil" do
- assert_match %r{<pseudonyms nil="true"/>}, @contact.to_xml(methods: :pseudonyms)
- end
-
- test "should serialize integer" do
- assert_match %r{<age type="integer">25</age>}, @contact.to_xml
- end
-
- test "should serialize datetime" do
- assert_match %r{<created-at type="dateTime">2006-08-01T00:00:00Z</created-at>}, @contact.to_xml
- end
-
- test "should serialize boolean" do
- assert_match %r{<awesome type="boolean">false</awesome>}, @contact.to_xml
- end
-
- test "should serialize array" do
- assert_match %r{<social type="array">\s*<social>twitter</social>\s*<social>github</social>\s*</social>}, @contact.to_xml(methods: :social)
- end
-
- test "should serialize hash" do
- assert_match %r{<network>\s*<git type="symbol">github</git>\s*</network>}, @contact.to_xml(methods: :network)
- end
-
- test "should serialize yaml" do
- assert_match %r{<preferences type="yaml">--- !ruby/struct:Customer(\s*)\nname: John\n</preferences>}, @contact.to_xml
- end
-
- test "should call proc on object" do
- proc = Proc.new { |options| options[:builder].tag!('nationality', 'unknown') }
- xml = @contact.to_xml(procs: [ proc ])
- assert_match %r{<nationality>unknown</nationality>}, xml
- end
-
- test "should supply serializable to second proc argument" do
- proc = Proc.new { |options, record| options[:builder].tag!('name-reverse', record.name.reverse) }
- xml = @contact.to_xml(procs: [ proc ])
- assert_match %r{<name-reverse>kcats noraa</name-reverse>}, xml
- end
-
- test "should serialize string correctly when type passed" do
- xml = @contact.to_xml type: 'Contact'
- assert_match %r{<contact type="Contact">}, xml
- assert_match %r{<name>aaron stack</name>}, xml
- end
-
- test "include option with singular association" do
- xml = @contact.to_xml include: :address, indent: 0
- assert xml.include?(@contact.address.to_xml(indent: 0, skip_instruct: true))
- end
-
- test "include option with plural association" do
- xml = @contact.to_xml include: :friends, indent: 0
- assert_match %r{<friends type="array">}, xml
- assert_match %r{<friend type="Contact">}, xml
- end
-
- class FriendList
- def initialize(friends)
- @friends = friends
- end
-
- def to_ary
- @friends
- end
- end
-
- test "include option with ary" do
- @contact.friends = FriendList.new(@contact.friends)
- xml = @contact.to_xml include: :friends, indent: 0
- assert_match %r{<friends type="array">}, xml
- assert_match %r{<friend type="Contact">}, xml
- end
-
- test "multiple includes" do
- xml = @contact.to_xml indent: 0, skip_instruct: true, include: [ :address, :friends ]
- assert xml.include?(@contact.address.to_xml(indent: 0, skip_instruct: true))
- assert_match %r{<friends type="array">}, xml
- assert_match %r{<friend type="Contact">}, xml
- end
-
- test "include with options" do
- xml = @contact.to_xml indent: 0, skip_instruct: true, include: { address: { only: :city } }
- assert xml.include?(%(><address><city>Springfield</city></address>))
- end
-
- test "propagates skip_types option to included associations" do
- xml = @contact.to_xml include: :friends, indent: 0, skip_types: true
- assert_match %r{<friends>}, xml
- assert_match %r{<friend>}, xml
- end
-
- test "propagates skip-types option to included associations and attributes" do
- xml = @contact.to_xml skip_types: true, include: :address, indent: 0
- assert_match %r{<address>}, xml
- assert_match %r{<apt-number>}, xml
- end
-
- test "propagates camelize option to included associations and attributes" do
- xml = @contact.to_xml camelize: true, include: :address, indent: 0
- assert_match %r{<Address>}, xml
- assert_match %r{<AptNumber type="integer">}, xml
- end
-
- test "propagates dasherize option to included associations and attributes" do
- xml = @contact.to_xml dasherize: false, include: :address, indent: 0
- assert_match %r{<apt_number type="integer">}, xml
- end
-
- test "don't propagate skip_types if skip_types is defined at the included association level" do
- xml = @contact.to_xml skip_types: true, include: { address: { skip_types: false } }, indent: 0
- assert_match %r{<address>}, xml
- assert_match %r{<apt-number type="integer">}, xml
- end
-
- test "don't propagate camelize if camelize is defined at the included association level" do
- xml = @contact.to_xml camelize: true, include: { address: { camelize: false } }, indent: 0
- assert_match %r{<address>}, xml
- assert_match %r{<apt-number type="integer">}, xml
- end
-
- test "don't propagate dasherize if dasherize is defined at the included association level" do
- xml = @contact.to_xml dasherize: false, include: { address: { dasherize: true } }, indent: 0
- assert_match %r{<address>}, xml
- assert_match %r{<apt-number type="integer">}, xml
- end
-
- test "association with sti" do
- xml = @contact.to_xml(include: :contact)
- assert xml.include?(%(<contact type="SerializableContact">))
- end
-end
diff --git a/activemodel/test/cases/validations/acceptance_validation_test.rb b/activemodel/test/cases/validations/acceptance_validation_test.rb
index 9c2114d83d..d3995ad5af 100644
--- a/activemodel/test/cases/validations/acceptance_validation_test.rb
+++ b/activemodel/test/cases/validations/acceptance_validation_test.rb
@@ -50,6 +50,20 @@ class AcceptanceValidationTest < ActiveModel::TestCase
assert t.valid?
end
+ def test_terms_of_service_agreement_with_multiple_accept_values
+ Topic.validates_acceptance_of(:terms_of_service, accept: [1, "I concur."])
+
+ t = Topic.new("title" => "We should be confirmed", "terms_of_service" => "")
+ assert t.invalid?
+ assert_equal ["must be accepted"], t.errors[:terms_of_service]
+
+ t.terms_of_service = 1
+ assert t.valid?
+
+ t.terms_of_service = "I concur."
+ assert t.valid?
+ end
+
def test_validates_acceptance_of_for_ruby_class
Person.validates_acceptance_of :karma
diff --git a/activemodel/test/cases/validations/exclusion_validation_test.rb b/activemodel/test/cases/validations/exclusion_validation_test.rb
index b269c3691a..005bc15df5 100644
--- a/activemodel/test/cases/validations/exclusion_validation_test.rb
+++ b/activemodel/test/cases/validations/exclusion_validation_test.rb
@@ -1,4 +1,5 @@
require 'cases/helper'
+require 'active_support/core_ext/numeric/time'
require 'models/topic'
require 'models/person'
@@ -64,6 +65,22 @@ class ExclusionValidationTest < ActiveModel::TestCase
assert t.valid?
end
+ def test_validates_exclusion_of_with_range
+ Topic.validates_exclusion_of :content, in: ("a".."g")
+
+ assert Topic.new(content: 'g').invalid?
+ assert Topic.new(content: 'h').valid?
+ end
+
+ def test_validates_exclusion_of_with_time_range
+ Topic.validates_exclusion_of :created_at, in: 6.days.ago..2.days.ago
+
+ assert Topic.new(created_at: 5.days.ago).invalid?
+ assert Topic.new(created_at: 3.days.ago).invalid?
+ assert Topic.new(created_at: 7.days.ago).valid?
+ assert Topic.new(created_at: 1.day.ago).valid?
+ end
+
def test_validates_inclusion_of_with_symbol
Person.validates_exclusion_of :karma, in: :reserved_karmas
diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb
index 70ee7afecc..09d7226b5a 100644
--- a/activemodel/test/cases/validations/i18n_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_validation_test.rb
@@ -30,8 +30,9 @@ class I18nValidationTest < ActiveModel::TestCase
def test_errors_full_messages_translates_human_attribute_name_for_model_attributes
@person.errors.add(:name, 'not found')
- Person.expects(:human_attribute_name).with(:name, default: 'Name').returns("Person's name")
- assert_equal ["Person's name not found"], @person.errors.full_messages
+ assert_called_with(Person, :human_attribute_name, [:name, default: 'Name'], returns: "Person's name") do
+ assert_equal ["Person's name not found"], @person.errors.full_messages
+ end
end
def test_errors_full_messages_uses_format
@@ -54,176 +55,175 @@ class I18nValidationTest < ActiveModel::TestCase
[ "given option that is not reserved", { format: "jpg" }, { format: "jpg" }]
]
- # validates_confirmation_of w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_confirmation_of on generated message #{name}" do
Person.validates_confirmation_of :title, validation_options
@person.title_confirmation = 'foo'
- @person.errors.expects(:generate_message).with(:title_confirmation, :confirmation, generate_message_options.merge(attribute: 'Title'))
- @person.valid?
+ call = [:title_confirmation, :confirmation, generate_message_options.merge(attribute: 'Title')]
+ assert_called_with(@person.errors, :generate_message, call) do
+ @person.valid?
+ end
end
end
- # validates_acceptance_of w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_acceptance_of on generated message #{name}" do
Person.validates_acceptance_of :title, validation_options.merge(allow_nil: false)
- @person.errors.expects(:generate_message).with(:title, :accepted, generate_message_options)
- @person.valid?
+ call = [:title, :accepted, generate_message_options]
+ assert_called_with(@person.errors, :generate_message, call) do
+ @person.valid?
+ end
end
end
- # validates_presence_of w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_presence_of on generated message #{name}" do
Person.validates_presence_of :title, validation_options
- @person.errors.expects(:generate_message).with(:title, :blank, generate_message_options)
- @person.valid?
+ call = [:title, :blank, generate_message_options]
+ assert_called_with(@person.errors, :generate_message, call) do
+ @person.valid?
+ end
end
end
- # validates_length_of :within too short w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
- test "validates_length_of for :withing on generated message when too short #{name}" do
+ test "validates_length_of for :within on generated message when too short #{name}" do
Person.validates_length_of :title, validation_options.merge(within: 3..5)
- @person.errors.expects(:generate_message).with(:title, :too_short, generate_message_options.merge(count: 3))
- @person.valid?
+ call = [:title, :too_short, generate_message_options.merge(count: 3)]
+ assert_called_with(@person.errors, :generate_message, call) do
+ @person.valid?
+ end
end
end
- # validates_length_of :within too long w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_length_of for :too_long generated message #{name}" do
Person.validates_length_of :title, validation_options.merge(within: 3..5)
@person.title = 'this title is too long'
- @person.errors.expects(:generate_message).with(:title, :too_long, generate_message_options.merge(count: 5))
- @person.valid?
+ call = [:title, :too_long, generate_message_options.merge(count: 5)]
+ assert_called_with(@person.errors, :generate_message, call) do
+ @person.valid?
+ end
end
end
- # validates_length_of :is w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_length_of for :is on generated message #{name}" do
Person.validates_length_of :title, validation_options.merge(is: 5)
- @person.errors.expects(:generate_message).with(:title, :wrong_length, generate_message_options.merge(count: 5))
- @person.valid?
+ call = [:title, :wrong_length, generate_message_options.merge(count: 5)]
+ assert_called_with(@person.errors, :generate_message, call) do
+ @person.valid?
+ end
end
end
- # validates_format_of w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_format_of on generated message #{name}" do
Person.validates_format_of :title, validation_options.merge(with: /\A[1-9][0-9]*\z/)
@person.title = '72x'
- @person.errors.expects(:generate_message).with(:title, :invalid, generate_message_options.merge(value: '72x'))
- @person.valid?
+ call = [:title, :invalid, generate_message_options.merge(value: '72x')]
+ assert_called_with(@person.errors, :generate_message, call) do
+ @person.valid?
+ end
end
end
- # validates_inclusion_of w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_inclusion_of on generated message #{name}" do
Person.validates_inclusion_of :title, validation_options.merge(in: %w(a b c))
@person.title = 'z'
- @person.errors.expects(:generate_message).with(:title, :inclusion, generate_message_options.merge(value: 'z'))
- @person.valid?
+ call = [:title, :inclusion, generate_message_options.merge(value: 'z')]
+ assert_called_with(@person.errors, :generate_message, call) do
+ @person.valid?
+ end
end
end
- # validates_inclusion_of using :within w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_inclusion_of using :within on generated message #{name}" do
Person.validates_inclusion_of :title, validation_options.merge(within: %w(a b c))
@person.title = 'z'
- @person.errors.expects(:generate_message).with(:title, :inclusion, generate_message_options.merge(value: 'z'))
- @person.valid?
+ call = [:title, :inclusion, generate_message_options.merge(value: 'z')]
+ assert_called_with(@person.errors, :generate_message, call) do
+ @person.valid?
+ end
end
end
- # validates_exclusion_of w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_exclusion_of generated message #{name}" do
Person.validates_exclusion_of :title, validation_options.merge(in: %w(a b c))
@person.title = 'a'
- @person.errors.expects(:generate_message).with(:title, :exclusion, generate_message_options.merge(value: 'a'))
- @person.valid?
+ call = [:title, :exclusion, generate_message_options.merge(value: 'a')]
+ assert_called_with(@person.errors, :generate_message, call) do
+ @person.valid?
+ end
end
end
- # validates_exclusion_of using :within w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_exclusion_of using :within generated message #{name}" do
Person.validates_exclusion_of :title, validation_options.merge(within: %w(a b c))
@person.title = 'a'
- @person.errors.expects(:generate_message).with(:title, :exclusion, generate_message_options.merge(value: 'a'))
- @person.valid?
+ call = [:title, :exclusion, generate_message_options.merge(value: 'a')]
+ assert_called_with(@person.errors, :generate_message, call) do
+ @person.valid?
+ end
end
end
- # validates_numericality_of without :only_integer w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_numericality_of generated message #{name}" do
Person.validates_numericality_of :title, validation_options
@person.title = 'a'
- @person.errors.expects(:generate_message).with(:title, :not_a_number, generate_message_options.merge(value: 'a'))
- @person.valid?
+ call = [:title, :not_a_number, generate_message_options.merge(value: 'a')]
+ assert_called_with(@person.errors, :generate_message, call) do
+ @person.valid?
+ end
end
end
- # validates_numericality_of with :only_integer w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_numericality_of for :only_integer on generated message #{name}" do
Person.validates_numericality_of :title, validation_options.merge(only_integer: true)
@person.title = '0.0'
- @person.errors.expects(:generate_message).with(:title, :not_an_integer, generate_message_options.merge(value: '0.0'))
- @person.valid?
+ call = [:title, :not_an_integer, generate_message_options.merge(value: '0.0')]
+ assert_called_with(@person.errors, :generate_message, call) do
+ @person.valid?
+ end
end
end
- # validates_numericality_of :odd w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_numericality_of for :odd on generated message #{name}" do
Person.validates_numericality_of :title, validation_options.merge(only_integer: true, odd: true)
@person.title = 0
- @person.errors.expects(:generate_message).with(:title, :odd, generate_message_options.merge(value: 0))
- @person.valid?
+ call = [:title, :odd, generate_message_options.merge(value: 0)]
+ assert_called_with(@person.errors, :generate_message, call) do
+ @person.valid?
+ end
end
end
- # validates_numericality_of :less_than w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_numericality_of for :less_than on generated message #{name}" do
Person.validates_numericality_of :title, validation_options.merge(only_integer: true, less_than: 0)
@person.title = 1
- @person.errors.expects(:generate_message).with(:title, :less_than, generate_message_options.merge(value: 1, count: 0))
- @person.valid?
+ call = [:title, :less_than, generate_message_options.merge(value: 1, count: 0)]
+ assert_called_with(@person.errors, :generate_message, call) do
+ @person.valid?
+ end
end
end
-
- # To make things DRY this macro is defined to define 3 tests for every validation case.
+ # To make things DRY this macro is created to define 3 tests for every validation case.
def self.set_expectations_for_validation(validation, error_type, &block_that_sets_validation)
if error_type == :confirmation
attribute = :title_confirmation
else
attribute = :title
end
- # test "validates_confirmation_of finds custom model key translation when blank"
+
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'}}
@@ -233,7 +233,6 @@ class I18nValidationTest < ActiveModel::TestCase
assert_equal ['custom message'], @person.errors[attribute]
end
- # test "validates_confirmation_of finds custom model key translation with interpolation when blank"
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'} }
@@ -243,7 +242,6 @@ class I18nValidationTest < ActiveModel::TestCase
assert_equal ['custom message with extra information'], @person.errors[attribute]
end
- # test "validates_confirmation_of finds global default key translation when blank"
test "#{validation} finds global default key translation when #{error_type}" do
I18n.backend.store_translations 'en', errors: { messages: {error_type => 'global message'} }
@@ -253,27 +251,19 @@ class I18nValidationTest < ActiveModel::TestCase
end
end
- # validates_confirmation_of w/o mocha
-
set_expectations_for_validation "validates_confirmation_of", :confirmation do |person, options_to_merge|
Person.validates_confirmation_of :title, options_to_merge
person.title_confirmation = 'foo'
end
- # validates_acceptance_of w/o mocha
-
set_expectations_for_validation "validates_acceptance_of", :accepted do |person, options_to_merge|
Person.validates_acceptance_of :title, options_to_merge.merge(allow_nil: false)
end
- # validates_presence_of w/o mocha
-
set_expectations_for_validation "validates_presence_of", :blank do |person, options_to_merge|
Person.validates_presence_of :title, options_to_merge
end
- # validates_length_of :within w/o mocha
-
set_expectations_for_validation "validates_length_of", :too_short do |person, options_to_merge|
Person.validates_length_of :title, options_to_merge.merge(within: 3..5)
end
@@ -283,61 +273,43 @@ class I18nValidationTest < ActiveModel::TestCase
person.title = "too long"
end
- # validates_length_of :is w/o mocha
-
set_expectations_for_validation "validates_length_of", :wrong_length do |person, options_to_merge|
Person.validates_length_of :title, options_to_merge.merge(is: 5)
end
- # validates_format_of w/o mocha
-
set_expectations_for_validation "validates_format_of", :invalid do |person, options_to_merge|
Person.validates_format_of :title, options_to_merge.merge(with: /\A[1-9][0-9]*\z/)
end
- # validates_inclusion_of w/o mocha
-
set_expectations_for_validation "validates_inclusion_of", :inclusion do |person, options_to_merge|
Person.validates_inclusion_of :title, options_to_merge.merge(in: %w(a b c))
end
- # validates_exclusion_of w/o mocha
-
set_expectations_for_validation "validates_exclusion_of", :exclusion do |person, options_to_merge|
Person.validates_exclusion_of :title, options_to_merge.merge(in: %w(a b c))
person.title = 'a'
end
- # validates_numericality_of without :only_integer w/o mocha
-
set_expectations_for_validation "validates_numericality_of", :not_a_number do |person, options_to_merge|
Person.validates_numericality_of :title, options_to_merge
person.title = 'a'
end
- # validates_numericality_of with :only_integer w/o mocha
-
set_expectations_for_validation "validates_numericality_of", :not_an_integer do |person, options_to_merge|
Person.validates_numericality_of :title, options_to_merge.merge(only_integer: true)
person.title = '1.0'
end
- # validates_numericality_of :odd w/o mocha
-
set_expectations_for_validation "validates_numericality_of", :odd do |person, options_to_merge|
Person.validates_numericality_of :title, options_to_merge.merge(only_integer: true, odd: true)
person.title = 0
end
- # validates_numericality_of :less_than w/o mocha
-
set_expectations_for_validation "validates_numericality_of", :less_than do |person, options_to_merge|
Person.validates_numericality_of :title, options_to_merge.merge(only_integer: true, less_than: 0)
person.title = 1
end
- # test with validates_with
-
def test_validations_with_message_symbol_must_translate
I18n.backend.store_translations 'en', errors: { messages: { custom_error: "I am a custom error" } }
Person.validates_presence_of :title, message: :custom_error
diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb
index 01804032f0..03c7943308 100644
--- a/activemodel/test/cases/validations/with_validation_test.rb
+++ b/activemodel/test/cases/validations/with_validation_test.rb
@@ -97,12 +97,14 @@ class ValidatesWithTest < ActiveModel::TestCase
test "passes all configuration options to the validator class" do
topic = Topic.new
- validator = mock()
- validator.expects(:new).with(foo: :bar, if: "1 == 1", class: Topic).returns(validator)
- validator.expects(:validate).with(topic)
+ validator = Minitest::Mock.new
+ validator.expect(:new, validator, [{foo: :bar, if: "1 == 1", class: Topic}])
+ validator.expect(:validate, nil, [topic])
+ validator.expect(:is_a?, false, [Symbol])
Topic.validates_with(validator, if: "1 == 1", foo: :bar)
assert topic.valid?
+ validator.verify
end
test "validates_with with options" do
diff --git a/activemodel/test/models/contact.rb b/activemodel/test/models/contact.rb
index bcfd267a34..113ab0bc1f 100644
--- a/activemodel/test/models/contact.rb
+++ b/activemodel/test/models/contact.rb
@@ -4,7 +4,6 @@ class Contact
include ActiveModel::Validations
include ActiveModel::Serializers::JSON
- include ActiveModel::Serializers::Xml
attr_accessor :id, :name, :age, :created_at, :awesome, :preferences
attr_accessor :address, :friends, :contact
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 780c34147d..bd86dfb88e 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,418 @@
+* Descriptive error message when fixtures contain a missing column.
+
+ Closes #21201.
+
+ *Yves Senn*
+
+* `ActiveRecord::Tasks::PostgreSQLDatabaseTasks` fail if shellout to
+ postgresql commands (like `pg_dump`) is not successful.
+
+ *Bryan Paxton*, *Nate Berkopec*
+
+* Add `ActiveRecord::Relation#in_batches` to work with records and relations
+ in batches.
+
+ Available options are `of` (batch size), `load`, `begin_at`, and `end_at`.
+
+ Examples:
+
+ Person.in_batches.each_record(&:party_all_night!)
+ Person.in_batches.update_all(awesome: true)
+ Person.in_batches.delete_all
+ Person.in_batches.each do |relation|
+ relation.delete_all
+ sleep 10 # Throttles the delete queries
+ end
+
+ Closes #20933.
+
+ *Sina Siadat*
+
+* Added methods for PostgreSQL geometric data types to use in migrations
+
+ Example:
+
+ create_table :foo do |t|
+ t.line :foo_line
+ t.lseg :foo_lseg
+ t.box :foo_box
+ t.path :foo_path
+ t.polygon :foo_polygon
+ t.circle :foo_circle
+ end
+
+ *Mehmet Emin İNAÇ*
+
+* Add `cache_key` to ActiveRecord::Relation.
+
+ Example:
+
+ @users = User.where("name like ?", "%Alberto%")
+ @users.cache_key
+ => "/users/query-5942b155a43b139f2471b872ac54251f-3-20150714212107656125000"
+
+ *Alberto Fernández-Capel*
+
+* Properly allow uniqueness validations on primary keys.
+
+ Fixes #20966.
+
+ *Sean Griffin*, *presskey*
+
+* Don't raise an error if an association failed to destroy when `destroy` was
+ called on the parent (as opposed to `destroy!`).
+
+ Fixes #20991.
+
+ *Sean Griffin*
+
+* `ActiveRecord::RecordNotFound` modified to store model name, primary_key and
+ id of the caller model. It allows the catcher of this exception to make
+ a better decision to what to do with it.
+
+ Example:
+
+ class SomeAbstractController < ActionController::Base
+ rescue_from ActiveRecord::RecordNotFound, with: :redirect_to_404
+
+ private def redirect_to_404(e)
+ return redirect_to(posts_url) if e.model == 'Post'
+ raise
+ end
+ end
+
+ *Sameer Rahmani*
+
+* Deprecate the keys for association `restrict_dependent_destroy` errors in favor
+ of new key names.
+
+ Previously `has_one` and `has_many` associations were using the
+ `one` and `many` keys respectively. Both of these keys have special
+ meaning in I18n (they are considered to be pluralizations) so by
+ renaming them to `has_one` and `has_many` we make the messages more explicit
+ and most importantly they don't clash with linguistical systems that need to
+ validate translation keys (and their pluralizations).
+
+ The `:'restrict_dependent_destroy.one'` key should be replaced with
+ `:'restrict_dependent_destroy.has_one'`, and `:'restrict_dependent_destroy.many'`
+ with `:'restrict_dependent_destroy.has_many'`.
+
+ *Roque Pinel*, *Christopher Dell*
+
+* Fix state being carried over from previous transaction.
+
+ Considering the following example where `name` is a required attribute.
+ Before we had `new_record?` returning `true` for a persisted record:
+
+ author = Author.create! name: 'foo'
+ author.name = nil
+ author.save # => false
+ author.new_record? # => true
+
+ Fixes #20824.
+
+ *Roque Pinel*
+
+* Correctly ignore `mark_for_destruction` when `autosave` isn't set to `true`
+ when validating associations.
+
+ Fixes #20882.
+
+ *Sean Griffin*
+
+* Fix a bug where counter_cache doesn't always work with polymorphic
+ relations.
+
+ Fixes #16407.
+
+ *Stefan Kanev*, *Sean Griffin*
+
+* Ensure that cyclic associations with autosave don't cause duplicate errors
+ to be added to the parent record.
+
+ Fixes #20874.
+
+ *Sean Griffin*
+
+* Ensure that `ActionController::Parameters` can still be passed to nested
+ attributes.
+
+ Fixes #20922.
+
+ *Sean Griffin*
+
+* Deprecate force association reload by passing a truthy argument to
+ association method.
+
+ For collection association, you can call `#reload` on association proxy to
+ force a reload:
+
+ @user.posts.reload # Instead of @user.posts(true)
+
+ For singular association, you can call `#reload` on the parent object to
+ clear its association cache then call the association method:
+
+ @user.reload.profile # Instead of @user.profile(true)
+
+ Passing a truthy argument to force association to reload will be removed in
+ Rails 5.1.
+
+ *Prem Sichanugrist*
+
+* Replaced `ActiveSupport::Concurrency::Latch` with `Concurrent::CountDownLatch`
+ from the concurrent-ruby gem.
+
+ *Jerry D'Antonio*
+
+* Fix through associations using scopes having the scope merged multiple
+ times.
+
+ Fixes #20721.
+ Fixes #20727.
+
+ *Sean Griffin*
+
+* `ActiveRecord::Base.dump_schema_after_migration` applies migration tasks
+ other than `db:migrate`. (eg. `db:rollback`, `db:migrate:dup`, ...)
+
+ Fixes #20743.
+
+ *Yves Senn*
+
+* Add alternate syntax to make `change_column_default` reversible.
+
+ User can pass in `:from` and `:to` to make `change_column_default` command
+ become reversible.
+
+ Example:
+
+ change_column_default :posts, :status, from: nil, to: "draft"
+ change_column_default :users, :authorized, from: true, to: false
+
+ *Prem Sichanugrist*
+
+* Prevent error when using `force_reload: true` on an unassigned polymorphic
+ belongs_to association.
+
+ Fixes #20426.
+
+ *James Dabbs*
+
+* Correctly raise `ActiveRecord::AssociationTypeMismatch` when assigning
+ a wrong type to a namespaced association.
+
+ Fixes #20545.
+
+ *Diego Carrion*
+
+* `validates_absence_of` respects `marked_for_destruction?`.
+
+ Fixes #20449.
+
+ *Yves Senn*
+
+* Include the `Enumerable` module in `ActiveRecord::Relation`
+
+ *Sean Griffin & bogdan*
+
+* Use `Enumerable#sum` in `ActiveRecord::Relation` if a block is given.
+
+ *Sean Griffin*
+
+* Let `WITH` queries (Common Table Expressions) be explainable.
+
+ *Vladimir Kochnev*
+
+* Make `remove_index :table, :column` reversible.
+
+ *Yves Senn*
+
+* Fixed an error which would occur in dirty checking when calling
+ `update_attributes` from a getter.
+
+ Fixes #20531.
+
+ *Sean Griffin*
+
+* Make `remove_foreign_key` reversible. Any foreign key options must be
+ specified, similar to `remove_column`.
+
+ *Aster Ryan*
+
+* Add `:_prefix` and `:_suffix` options to `enum` definition.
+
+ Fixes #17511, #17415.
+
+ *Igor Kapkov*
+
+* Correctly handle decimal arrays with defaults in the schema dumper.
+
+ Fixes #20515.
+
+ *Sean Griffin & jmondo*
+
+* Deprecate the PostgreSQL `:point` type in favor of a new one which will return
+ `Point` objects instead of an `Array`
+
+ *Sean Griffin*
+
+* Ensure symbols passed to `ActiveRecord::Relation#select` are always treated
+ as columns.
+
+ Fixes #20360.
+
+ *Sean Griffin*
+
+* Do not set `sql_mode` if `strict: :default` is specified.
+
+ # config/database.yml
+ production:
+ adapter: mysql2
+ database: foo_prod
+ user: foo
+ strict: :default
+
+ *Ryuta Kamizono*
+
+* Allow proc defaults to be passed to the attributes API. See documentation
+ for examples.
+
+ *Sean Griffin*, *Kir Shatrov*
+
+* SQLite: `:collation` support for string and text columns.
+
+ Example:
+
+ create_table :foo do |t|
+ t.string :string_nocase, collation: 'NOCASE'
+ t.text :text_rtrim, collation: 'RTRIM'
+ end
+
+ add_column :foo, :title, :string, collation: 'RTRIM'
+
+ change_column :foo, :title, :string, collation: 'NOCASE'
+
+ *Akshay Vishnoi*
+
+* Allow the use of symbols or strings to specify enum values in test
+ fixtures:
+
+ awdr:
+ title: "Agile Web Development with Rails"
+ status: :proposed
+
+ *George Claghorn*
+
+* Clear query cache when `ActiveRecord::Base#reload` is called.
+
+ *Shane Hender, Pierre Nespo*
+
+* Include stored procedures and function on the MySQL structure dump.
+
+ *Jonathan Worek*
+
+* Pass `:extend` option for `has_and_belongs_to_many` associations to the
+ underlying `has_many :through`.
+
+ *Jaehyun Shin*
+
+* Deprecate `Relation#uniq` use `Relation#distinct` instead.
+
+ See #9683.
+
+ *Yves Senn*
+
+* Allow single table inheritance instantiation to work when storing
+ demodulized class names.
+
+ *Alex Robbin*
+
+* Correctly pass MySQL options when using `structure_dump` or
+ `structure_load`.
+
+ Specifically, it fixes an issue when using SSL authentication.
+
+ *Alex Coomans*
+
+* Dump indexes in `create_table` instead of `add_index`.
+
+ If the adapter supports indexes in `create_table`, generated SQL is
+ slightly more efficient.
+
+ *Ryuta Kamizono*
+
+* Correctly dump `:options` on `create_table` for MySQL.
+
+ *Ryuta Kamizono*
+
+* PostgreSQL: `:collation` support for string and text columns.
+
+ Example:
+
+ create_table :foos do |t|
+ t.string :string_en, collation: 'en_US.UTF-8'
+ t.text :text_ja, collation: 'ja_JP.UTF-8'
+ end
+
+ *Ryuta Kamizono*
+
+* Remove `ActiveRecord::Serialization::XmlSerializer` from core.
+
+ *Zachary Scott*
+
+* Make `unscope` aware of "less than" and "greater than" conditions.
+
+ *TAKAHASHI Kazuaki*
+
+* `find_by` and `find_by!` raise `ArgumentError` when called without
+ arguments.
+
+ *Kohei Suzuki*
+
+* Revert behavior of `db:schema:load` back to loading the full
+ environment. This ensures that initializers are run.
+
+ Fixes #19545.
+
+ *Yves Senn*
+
+* Fix missing index when using `timestamps` with the `index` option.
+
+ The `index` option used with `timestamps` should be passed to both
+ `column` definitions for `created_at` and `updated_at` rather than just
+ the first.
+
+ *Paul Mucur*
+
+* Rename `:class` to `:anonymous_class` in association options.
+
+ Fixes #19659.
+
+ *Andrew White*
+
+* Autosave existing records on a has many through association when the parent
+ is new.
+
+ Fixes #19782.
+
+ *Sean Griffin*
+
+* Fixed a bug where uniqueness validations would error on out of range values,
+ even if an validation should have prevented it from hitting the database.
+
+ *Andrey Voronkov*
+
+* MySQL: `:charset` and `:collation` support for string and text columns.
+
+ Example:
+
+ create_table :foos do |t|
+ t.string :string_utf8_bin, charset: 'utf8', collation: 'utf8_bin'
+ t.text :text_ascii, charset: 'ascii'
+ end
+
+ *Ryuta Kamizono*
+
* Foreign key related methods in the migration DSL respect
`ActiveRecord::Base.pluralize_table_names = false`.
@@ -5,20 +420,22 @@
*Mehmet Emin İNAÇ*
-* Reduce memory usage from loading types on pg.
+* Reduce memory usage from loading types on PostgreSQL.
Fixes #19578.
*Sean Griffin*
-* Add `config.active_record.warn_on_records_fetched_greater_than` option
+* Add `config.active_record.warn_on_records_fetched_greater_than` option.
When set to an integer, a warning will be logged whenever a result set
- larger than the specified size is returned by a query. Fixes #16463
+ larger than the specified size is returned by a query.
+
+ Fixes #16463.
*Jason Nochlin*
-* Ignore psqlrc when loading database structure.
+* Ignore `.psqlrc` when loading database structure.
*Jason Weathered*
@@ -134,7 +551,7 @@
*Josef Šimánek*
-* Fixed ActiveRecord::Relation#becomes! and changed_attributes issues for type
+* Fixed `ActiveRecord::Relation#becomes!` and `changed_attributes` issues for type
columns.
Fixes #17139.
@@ -317,8 +734,8 @@
*Henrik Nygren*
-* Fixed ActiveRecord::Relation#group method when an argument is an SQL
- reserved key word:
+* Fixed `ActiveRecord::Relation#group` method when an argument is an SQL
+ reserved keyword:
Example:
@@ -327,7 +744,7 @@
*Bogdan Gusiev*
-* Added the `#or` method on ActiveRecord::Relation, allowing use of the OR
+* Added the `#or` method on `ActiveRecord::Relation`, allowing use of the OR
operator to combine WHERE or HAVING clauses.
Example:
@@ -527,7 +944,7 @@
The preferred method to halt a callback chain from now on is to explicitly
`throw(:abort)`.
- In the past, returning `false` in an ActiveRecord `before_` callback had the
+ In the past, returning `false` in an Active Record `before_` callback had the
side effect of halting the callback chain.
This is not recommended anymore and, depending on the value of the
`config.active_support.halt_callback_chains_on_return_false` option, will
@@ -690,7 +1107,7 @@
*Yves Senn*
-* Fix bug with 'ActiveRecord::Type::Numeric' that caused negative values to
+* Fix bug with `ActiveRecord::Type::Numeric` that caused negative values to
be marked as having changed when set to the same negative value.
Closes #18161.
diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc
index f4777919d3..049c5d2b3b 100644
--- a/activerecord/README.rdoc
+++ b/activerecord/README.rdoc
@@ -31,8 +31,8 @@ which might look like this:
PRIMARY KEY (id)
);
-This would also define the following accessors: `Product#name` and
-`Product#name=(new_name)`.
+This would also define the following accessors: <tt>Product#name</tt> and
+<tt>Product#name=(new_name)</tt>.
* Associations between objects defined by simple class methods.
@@ -188,7 +188,7 @@ Admit the Database:
The latest version of Active Record can be installed with RubyGems:
- % [sudo] gem install activerecord
+ % gem install activerecord
Source code can be downloaded as part of the Rails project on GitHub:
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index f1facac21b..8ea22fd901 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -1,5 +1,4 @@
require 'rake/testtask'
-require 'rubygems/package_task'
require File.expand_path(File.dirname(__FILE__)) + "/test/config"
require File.expand_path(File.dirname(__FILE__)) + "/test/support/config"
@@ -84,15 +83,6 @@ end
task "isolated_test_#{adapter}" => ["#{adapter}:env", "test:isolated:#{adapter}"]
end
-rule '.sqlite3' do |t|
- sh %Q{sqlite3 "#{t.name}" "create table a (a integer); drop table a;"}
-end
-
-task :test_sqlite3 => [
- 'test/fixtures/fixture_database.sqlite3',
- 'test/fixtures/fixture_database_2.sqlite3'
-]
-
namespace :db do
namespace :mysql do
desc 'Build the MySQL test databases'
@@ -122,7 +112,7 @@ namespace :db do
# prepare hstore
if %x( createdb --version ).strip.gsub(/(.*)(\d\.\d\.\d)$/, "\\2") < "9.1.0"
- puts "Please prepare hstore data type. See http://www.postgresql.org/docs/9.0/static/hstore.html"
+ puts "Please prepare hstore data type. See http://www.postgresql.org/docs/current/static/hstore.html"
end
end
@@ -151,18 +141,3 @@ task :lines do
files = FileList["lib/active_record/**/*.rb"]
CodeTools::LineStatistics.new(files).print_loc
end
-
-spec = eval(File.read('activerecord.gemspec'))
-
-Gem::PackageTask.new(spec) do |p|
- p.gem_spec = spec
-end
-
-# Publishing ------------------------------------------------------
-
-desc "Release to rubygems"
-task :release => :package do
- require 'rake/gemcutter'
- Rake::Gemcutter::Tasks.new(spec).define
- Rake::Task['gem:push'].invoke
-end
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index c88b9e8718..bd95b57303 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
s.summary = 'Object-relational mapper framework (part of Rails).'
s.description = 'Databases on Rails. Build a persistent domain model by mapping database tables to Ruby classes. Strong conventions for associations, validations, aggregations, migrations, and testing come baked-in.'
- s.required_ruby_version = '>= 2.2.1'
+ s.required_ruby_version = '>= 2.2.2'
s.license = 'MIT'
diff --git a/activerecord/bin/test b/activerecord/bin/test
new file mode 100755
index 0000000000..f8adf2aabc
--- /dev/null
+++ b/activerecord/bin/test
@@ -0,0 +1,19 @@
+#!/usr/bin/env ruby
+COMPONENT_ROOT = File.expand_path("../../", __FILE__)
+require File.expand_path("../tools/test", COMPONENT_ROOT)
+module Minitest
+ def self.plugin_active_record_options(opts, options)
+ opts.separator ""
+ opts.separator "Active Record options:"
+ opts.on("-a", "--adapter [ADAPTER]",
+ "Run tests using a specific adapter (sqlite3, sqlite3_mem, mysql, mysql2, postgresql)") do |adapter|
+ ENV["ARCONN"] = adapter.strip
+ end
+
+ opts
+ end
+end
+
+Minitest.extensions.unshift 'active_record'
+
+exit Minitest.run(ARGV)
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 58a4694880..264f869c68 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -49,10 +49,13 @@ module ActiveRecord
autoload :ModelSchema
autoload :NestedAttributes
autoload :NoTouching
+ autoload :TouchLater
autoload :Persistence
autoload :QueryCache
autoload :Querying
+ autoload :CollectionCacheKey
autoload :ReadonlyAttributes
+ autoload :RecordInvalid, 'active_record/validations'
autoload :Reflection
autoload :RuntimeRegistry
autoload :Sanitization
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index 3d497a30fb..f7b50cd25a 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -142,7 +142,7 @@ module ActiveRecord
# 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.ruby-doc.org/gems/docs/n/netaddr-1.5.0/NetAddr/CIDR.html).
+ # 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
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 5e3e5f709b..a830b0e0e4 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -61,12 +61,18 @@ module ActiveRecord
end
end
- class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc:
+ class ThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc:
def initialize(owner, reflection)
super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
end
end
+ class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ThroughCantAssociateThroughHasOneOrManyReflection #:nodoc:
+ end
+
+ class HasOneThroughCantAssociateThroughHasOneOrManyReflection < ThroughCantAssociateThroughHasOneOrManyReflection #:nodoc:
+ end
+
class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc:
def initialize(owner, reflection)
super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.")
@@ -79,12 +85,18 @@ module ActiveRecord
end
end
- class HasManyThroughNestedAssociationsAreReadonly < ActiveRecordError #:nodoc:
+ class ThroughNestedAssociationsAreReadonly < ActiveRecordError #:nodoc:
def initialize(owner, reflection)
super("Cannot modify association '#{owner.class.name}##{reflection.name}' because it goes through more than one other association.")
end
end
+ class HasManyThroughNestedAssociationsAreReadonly < ThroughNestedAssociationsAreReadonly #:nodoc:
+ end
+
+ class HasOneThroughNestedAssociationsAreReadonly < ThroughNestedAssociationsAreReadonly #:nodoc:
+ end
+
class EagerLoadPolymorphicError < ActiveRecordError #:nodoc:
def initialize(reflection)
super("Cannot eagerly load the polymorphic association #{reflection.name.inspect}")
@@ -185,7 +197,7 @@ module ActiveRecord
super
end
- # Returns the specified association instance if it responds to :loaded?, nil otherwise.
+ # Returns the specified association instance if it exists, nil otherwise.
def association_instance_get(name)
@association_cache[name]
end
@@ -266,7 +278,6 @@ module ActiveRecord
# others.find(*args) | X | X | X
# others.exists? | X | X | X
# others.distinct | X | X | X
- # others.uniq | X | X | X
# others.reset | X | X | X
#
# === Overriding generated methods
@@ -285,7 +296,7 @@ module ActiveRecord
# end
#
# If your model class is <tt>Project</tt>, the module is
- # named <tt>Project::GeneratedFeatureMethods</tt>. The GeneratedFeatureMethods 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.
#
@@ -956,20 +967,16 @@ module ActiveRecord
# 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 doesn't know anything about these inverse relationships and so no object
- # loading optimization is possible. For example:
+ # 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.level == t.dungeon.level # => true
- # d.level = 10
- # d.level == t.dungeon.level # => false
+ # 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 object data from the database, but are actually different in-memory copies
- # of that data. Specifying the <tt>:inverse_of</tt> option on associations lets you tell
- # Active Record about inverse relationships and it will optimise object loading. For
- # example, if we changed our model definitions 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
@@ -984,15 +991,14 @@ module ActiveRecord
# belongs_to :dungeon, inverse_of: :evil_wizard
# end
#
- # Then, from our code snippet above, +d+ and <tt>t.dungeon</tt> are actually the same
- # in-memory instance and our final <tt>d.level == t.dungeon.level</tt> will return +true+.
- #
# 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.
# * for +belongs_to+ associations +has_many+ inverse associations are ignored.
#
+ # For more information, see the documentation for the +:inverse_of+ option.
+ #
# == Deleting from associations
#
# === Dependent associations
@@ -1565,10 +1571,7 @@ module ActiveRecord
#
# class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration
# def change
- # create_table :developers_projects, id: false do |t|
- # t.integer :developer_id
- # t.integer :project_id
- # end
+ # create_join_table :developers, :projects
# end
# end
#
@@ -1718,16 +1721,14 @@ module ActiveRecord
join_model = builder.through_model
- # FIXME: we should move this to the internal constants. Also people
- # should never directly access this constant so I'm not happy about
- # setting it.
const_set join_model.name, join_model
+ private_constant join_model.name
middle_reflection = builder.middle_reflection join_model
Builder::HasMany.define_callbacks self, middle_reflection
Reflection.add_reflection self, middle_reflection.name, middle_reflection
- middle_reflection.parent_reflection = [name.to_s, habtm_reflection]
+ middle_reflection.parent_reflection = habtm_reflection
include Module.new {
class_eval <<-RUBY, __FILE__, __LINE__ + 1
@@ -1743,12 +1744,12 @@ module ActiveRecord
hm_options[:through] = middle_reflection.name
hm_options[:source] = join_model.right_reflection.name
- [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name].each do |k|
+ [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend].each do |k|
hm_options[k] = options[k] if options.key? k
end
has_many name, scope, hm_options, &extension
- self._reflections[name.to_s].parent_reflection = [name.to_s, habtm_reflection]
+ self._reflections[name.to_s].parent_reflection = habtm_reflection
end
end
end
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 930f678ae8..c7b396f3d4 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -211,9 +211,12 @@ module ActiveRecord
# the kind of the class of the associated objects. Meant to be used as
# a sanity check when you are about to assign an associated record.
def raise_on_type_mismatch!(record)
- unless record.is_a?(reflection.klass) || record.is_a?(reflection.class_name.constantize)
- message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
- raise ActiveRecord::AssociationTypeMismatch, message
+ unless record.is_a?(reflection.klass)
+ fresh_class = reflection.class_name.safe_constantize
+ unless fresh_class && record.is_a?(fresh_class)
+ message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
+ raise ActiveRecord::AssociationTypeMismatch, message
+ end
end
end
@@ -248,6 +251,14 @@ module ActiveRecord
initialize_attributes(record)
end
end
+
+ # Returns true if statement cache should be skipped on the association reader.
+ def skip_statement_cache?
+ reflection.scope_chain.any?(&:any?) ||
+ scope.eager_loading? ||
+ klass.scope_attributes? ||
+ reflection.source_reflection.active_record.default_scopes.any?
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 2416167834..a140dc239c 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -149,6 +149,7 @@ module ActiveRecord
scope.where_clause += item.where_clause
scope.order_values |= item.order_values
+ scope.unscope!(*item.unscope_values)
end
reflection = reflection.next
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index 265a65c4c1..260a0c6a2d 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -107,7 +107,7 @@ module ActiveRecord
end
def stale_state
- result = owner._read_attribute(reflection.foreign_key)
+ result = owner._read_attribute(reflection.foreign_key) { |n| owner.send(:missing_attribute, n, caller) }
result && result.to_s
end
end
diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb
index 88406740d8..ba1b1814d1 100644
--- a/activerecord/lib/active_record/associations/builder/association.rb
+++ b/activerecord/lib/active_record/associations/builder/association.rb
@@ -16,7 +16,7 @@ module ActiveRecord::Associations::Builder
end
self.extensions = []
- VALID_OPTIONS = [:class_name, :class, :foreign_key, :validate] # :nodoc:
+ VALID_OPTIONS = [:class_name, :anonymous_class, :foreign_key, :validate] # :nodoc:
def self.build(model, name, scope, options, &block)
if model.dangerous_attribute_method?(name)
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index ec135d49b7..6e4a53f7fb 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -33,16 +33,24 @@ module ActiveRecord::Associations::Builder
if (@_after_create_counter_called ||= false)
@_after_create_counter_called = false
- elsif attribute_changed?(foreign_key) && !new_record? && reflection.constructable?
- model = reflection.klass
+ elsif attribute_changed?(foreign_key) && !new_record?
+ if reflection.polymorphic?
+ model = attribute(reflection.foreign_type).try(:constantize)
+ model_was = attribute_was(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
if foreign_key && model.respond_to?(:increment_counter)
model.increment_counter(cache_column, foreign_key)
end
- if foreign_key_was && model.respond_to?(:decrement_counter)
- model.decrement_counter(cache_column, foreign_key_was)
+
+ if foreign_key_was && model_was.respond_to?(:decrement_counter)
+ model_was.decrement_counter(cache_column, foreign_key_was)
end
end
end
@@ -60,7 +68,7 @@ module ActiveRecord::Associations::Builder
klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
end
- def self.touch_record(o, foreign_key, name, touch) # :nodoc:
+ def self.touch_record(o, foreign_key, name, touch, touch_method) # :nodoc:
old_foreign_id = o.changed_attributes[foreign_key]
if old_foreign_id
@@ -75,9 +83,9 @@ module ActiveRecord::Associations::Builder
if old_record
if touch != true
- old_record.touch touch
+ old_record.send(touch_method, touch)
else
- old_record.touch
+ old_record.send(touch_method)
end
end
end
@@ -85,9 +93,9 @@ module ActiveRecord::Associations::Builder
record = o.send name
if record && record.persisted?
if touch != true
- record.touch touch
+ record.send(touch_method, touch)
else
- record.touch
+ record.send(touch_method)
end
end
end
@@ -98,7 +106,8 @@ module ActiveRecord::Associations::Builder
touch = reflection.options[:touch]
callback = lambda { |record|
- BelongsTo.touch_record(record, foreign_key, n, touch)
+ touch_method = touching_delayed_records? ? :touch : :touch_later
+ BelongsTo.touch_record(record, foreign_key, n, touch, touch_method)
}
model.after_save callback, if: :changed?
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 97b57a6a55..b18d99d54e 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
@@ -46,7 +46,7 @@ module ActiveRecord::Associations::Builder
join_model = Class.new(ActiveRecord::Base) {
class << self;
- attr_accessor :class_resolver
+ attr_accessor :left_model
attr_accessor :name
attr_accessor :table_name_resolver
attr_accessor :left_reflection
@@ -58,7 +58,7 @@ module ActiveRecord::Associations::Builder
end
def self.compute_type(class_name)
- class_resolver.compute_type class_name
+ left_model.compute_type class_name
end
def self.add_left_association(name, options)
@@ -72,13 +72,17 @@ module ActiveRecord::Associations::Builder
self.right_reflection = _reflect_on_association(rhs_name)
end
+ def self.retrieve_connection
+ left_model.retrieve_connection
+ end
+
}
join_model.name = "HABTM_#{association_name.to_s.camelize}"
join_model.table_name_resolver = habtm
- join_model.class_resolver = lhs_model
+ join_model.left_model = lhs_model
- join_model.add_left_association :left_side, class: lhs_model
+ join_model.add_left_association :left_side, anonymous_class: lhs_model
join_model.add_right_association association_name, belongs_to_options(options)
join_model
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 6caadb4ce8..256df3ca11 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -1,3 +1,5 @@
+require "active_support/deprecation"
+
module ActiveRecord
module Associations
# = Active Record Association Collection
@@ -28,6 +30,12 @@ module ActiveRecord
# 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?
reload
@@ -54,8 +62,10 @@ module ActiveRecord
record.send(reflection.association_primary_key)
end
else
- column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
- scope.pluck(column)
+ @association_ids ||= (
+ column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
+ scope.pluck(column)
+ )
end
end
@@ -432,12 +442,7 @@ module ActiveRecord
private
def get_records
- if reflection.scope_chain.any?(&:any?) ||
- scope.eager_loading? ||
- klass.scope_attributes?
-
- return scope.to_a
- end
+ return scope.to_a if skip_statement_cache?
conn = klass.connection
sc = reflection.association_scope_cache(conn, owner) do
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 685c3a5f17..b5a8c81fe4 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -470,15 +470,16 @@ module ActiveRecord
@association.destroy_all
end
- # Deletes the +records+ supplied and removes them from the collection. For
- # +has_many+ associations, the deletion is done according to the strategy
- # specified by the <tt>:dependent</tt> option. Returns an array with the
+ # Deletes the +records+ supplied from the collection according to the strategy
+ # specified by the +:dependent+ option. If no +:dependent+ option is given,
+ # then it will follow the default strategy. Returns an array with the
# deleted records.
#
- # If no <tt>:dependent</tt> option is given, then it will follow the default
- # strategy. The default strategy is <tt>:nullify</tt>. This sets the foreign
- # keys to <tt>NULL</tt>. For, +has_many+ <tt>:through</tt>, the default
- # strategy is +delete_all+.
+ # For +has_many :through+ associations, the default deletion strategy is
+ # +:delete_all+.
+ #
+ # For +has_many+ associations, the default deletion strategy is +:nullify+.
+ # This sets the foreign keys to +NULL+.
#
# class Person < ActiveRecord::Base
# has_many :pets # dependent: :nullify option by default
@@ -780,7 +781,7 @@ module ActiveRecord
# person.pets.any? # => false
#
# person.pets << Pet.new(name: 'Snoop')
- # person.pets.count # => 0
+ # person.pets.count # => 1
# person.pets.any? # => true
#
# You can also pass a +block+ to define criteria. The behavior
@@ -970,7 +971,7 @@ module ActiveRecord
alias_method :append, :<<
def prepend(*args)
- raise NoMethodError, "prepend on association is not defined. Please use << or append"
+ raise NoMethodError, "prepend on association is not defined. Please use <<, push or append"
end
# Equivalent to +delete_all+. The difference is that returns +self+, instead
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 794eb9e183..19cdd7f470 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -16,7 +16,14 @@ module ActiveRecord
when :restrict_with_error
unless empty?
record = klass.human_attribute_name(reflection.name).downcase
- owner.errors.add(:base, :"restrict_dependent_destroy.many", record: record)
+ 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)
throw(:abort)
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 29e8a0edc1..1aa6a2ca74 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -38,12 +38,10 @@ module ActiveRecord
def insert_record(record, validate = true, raise = false)
ensure_not_nested
- if record.new_record?
- if raise
- record.save!(:validate => validate)
- else
- return unless record.save(:validate => validate)
- end
+ if raise
+ record.save!(:validate => validate)
+ else
+ return unless record.save(:validate => validate)
end
save_through_record(record)
@@ -135,7 +133,7 @@ module ActiveRecord
if scope.klass.primary_key
count = scope.destroy_all.length
else
- scope.each { |record| record.run_callbacks :destroy }
+ scope.each(&:_run_destroy_callbacks)
arel = scope.arel
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 41a75b820e..5a92bc5e8a 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -12,7 +12,14 @@ module ActiveRecord
when :restrict_with_error
if load_target
record = klass.human_attribute_name(reflection.name).downcase
- owner.errors.add(:base, :"restrict_dependent_destroy.one", record: record)
+ 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)
throw(:abort)
end
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index 97f4bd3811..6ecc741195 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -10,13 +10,13 @@ module ActiveRecord
# end
#
# class Book < ActiveRecord::Base
- # # columns: title, sales
+ # # columns: title, sales, author_id
# end
#
# When you load an author with all associated books Active Record will make
# multiple queries like this:
#
- # Author.includes(:books).where(:name => ['bell hooks', 'Homer').to_a
+ # Author.includes(:books).where(name: ['bell hooks', 'Homer']).to_a
#
# => SELECT `authors`.* FROM `authors` WHERE `name` IN ('bell hooks', 'Homer')
# => SELECT `books`.* FROM `books` WHERE `author_id` IN (2, 5)
@@ -160,7 +160,7 @@ module ActiveRecord
h
end
- class AlreadyLoaded
+ class AlreadyLoaded # :nodoc:
attr_reader :owners, :reflection
def initialize(klass, owners, reflection, preload_scope)
@@ -175,7 +175,7 @@ module ActiveRecord
end
end
- class NullPreloader
+ class NullPreloader # :nodoc:
def self.new(klass, owners, reflection, preload_scope); self; end
def self.run(preloader); end
def self.preloaded_records; []; end
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index 58d0f7d65d..03cb8cb8c3 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -1,9 +1,17 @@
+require "active_support/deprecation"
+
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
+ 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?
reload
@@ -39,12 +47,7 @@ module ActiveRecord
end
def get_records
- if reflection.scope_chain.any?(&:any?) ||
- scope.eager_loading? ||
- klass.scope_attributes?
-
- return scope.limit(1).to_a
- end
+ return scope.limit(1).to_a if skip_statement_cache?
conn = klass.connection
sc = reflection.association_scope_cache(conn, owner) do
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index af1bce523c..d0ec3e8015 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -15,12 +15,6 @@ module ActiveRecord
scope = super
reflection.chain.drop(1).each do |reflection|
relation = reflection.klass.all
-
- reflection_scope = reflection.scope
- if reflection_scope && reflection_scope.arity.zero?
- relation = relation.merge(reflection_scope)
- end
-
scope.merge!(
relation.except(:select, :create_with, :includes, :preload, :joins, :eager_load)
)
@@ -82,13 +76,21 @@ module ActiveRecord
def ensure_mutable
unless source_reflection.belongs_to?
- raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
+ if reflection.has_one?
+ raise HasOneThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
+ else
+ raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
+ end
end
end
def ensure_not_nested
if reflection.nested?
- raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection)
+ if reflection.has_one?
+ raise HasOneThroughNestedAssociationsAreReadonly.new(owner, reflection)
+ else
+ raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection)
+ end
end
end
diff --git a/activerecord/lib/active_record/attribute/user_provided_default.rb b/activerecord/lib/active_record/attribute/user_provided_default.rb
new file mode 100644
index 0000000000..e0bee8c17e
--- /dev/null
+++ b/activerecord/lib/active_record/attribute/user_provided_default.rb
@@ -0,0 +1,32 @@
+require 'active_record/attribute'
+
+module ActiveRecord
+ class Attribute # :nodoc:
+ class UserProvidedDefault < FromUser
+ def initialize(name, value, type, database_default)
+ super(name, value, type)
+ @database_default = database_default
+ end
+
+ def type_cast(value)
+ if value.is_a?(Proc)
+ super(value.call)
+ else
+ super
+ end
+ end
+
+ def changed_in_place_from?(old_value)
+ super || changed_from?(database_default.value)
+ end
+
+ def with_type(type)
+ self.class.new(name, value_before_type_cast, type, database_default)
+ end
+
+ protected
+
+ attr_reader :database_default
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index cc265e2af6..45fdcaa1cd 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -29,7 +29,8 @@ module ActiveRecord
assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
end
- # Re-raise with the ActiveRecord constant in case of an error
+ # Tries to assign given value to given attribute.
+ # In case of an error, re-raises with the ActiveRecord constant.
def _assign_attribute(k, v) # :nodoc:
super
rescue ActiveModel::UnknownAttributeError
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 9d58a19304..0862306749 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -42,7 +42,7 @@ module ActiveRecord
def [](name)
@method_cache.compute_if_absent(name) do
- safe_name = name.unpack('h*').first
+ safe_name = name.unpack('h*'.freeze).first
temp_method = "__temp__#{safe_name}"
ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
@module.module_eval method_body(temp_method, safe_name), __FILE__, __LINE__
@@ -230,7 +230,15 @@ module ActiveRecord
# person.respond_to(:nothing) # => false
def respond_to?(name, include_private = false)
return false unless super
- name = name.to_s
+
+ case name
+ when :to_partial_path
+ name = "to_partial_path".freeze
+ when :to_model
+ name = "to_model".freeze
+ else
+ name = name.to_s
+ end
# If the result is true then check for the select case.
# For queries selecting a subset of columns, return false for unselected columns.
@@ -377,27 +385,27 @@ module ActiveRecord
#
# For example:
#
- # class PostsController < ActionController::Base
- # after_action :print_accessed_fields, only: :index
+ # class PostsController < ActionController::Base
+ # after_action :print_accessed_fields, only: :index
#
- # def index
- # @posts = Post.all
- # end
+ # def index
+ # @posts = Post.all
+ # end
#
- # private
+ # private
#
- # def print_accessed_fields
- # p @posts.first.accessed_fields
+ # def print_accessed_fields
+ # p @posts.first.accessed_fields
+ # end
# end
- # end
#
# Which allows you to quickly change your code to:
#
- # class PostsController < ActionController::Base
- # def index
- # @posts = Post.select(:id, :title, :author_id, :updated_at)
+ # class PostsController < ActionController::Base
+ # def index
+ # @posts = Post.select(:id, :title, :author_id, :updated_at)
+ # end
# end
- # end
def accessed_fields
@attributes.accessed
end
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 7ba907f786..0171ef3bdf 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -108,6 +108,7 @@ module ActiveRecord
end
def save_changed_attribute(attr, old_value)
+ clear_changed_attributes_cache
if attribute_changed_by_setter?(attr)
clear_attribute_changes(attr) unless _field_changed?(attr, old_value)
else
@@ -176,7 +177,11 @@ module ActiveRecord
@cached_changed_attributes = changed_attributes
yield
ensure
- remove_instance_variable(:@cached_changed_attributes)
+ clear_changed_attributes_cache
+ end
+
+ def clear_changed_attributes_cache
+ remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 0d989c2eca..2363cf7608 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -37,7 +37,7 @@ module ActiveRecord
protected
def define_method_attribute(name)
- safe_name = name.unpack('h*').first
+ safe_name = name.unpack('h*'.freeze).first
temp_method = "__temp__#{safe_name}"
ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index d0d8a968c5..60eecab0d0 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -11,6 +11,18 @@ module ActiveRecord
# serialized object must be of that class on assignment and retrieval.
# Otherwise <tt>SerializationTypeMismatch</tt> will be raised.
#
+ # Empty objects as <tt>{}</tt>, in the case of +Hash+, or <tt>[]</tt>, in the case of
+ # +Array+, will always be persisted as null.
+ #
+ # Keep in mind that database adapters handle certain serialization tasks
+ # for you. For instance: +json+ and +jsonb+ types in PostgreSQL will be
+ # converted between JSON object/array syntax and Ruby +Hash+ or +Array+
+ # objects transparently. There is no need to use +serialize+ in this
+ # case.
+ #
+ # For more complex cases, such as conversion to or from your application
+ # domain objects, consider using the ActiveRecord::Attributes API.
+ #
# ==== Parameters
#
# * +attr_name+ - The field name that should be serialized.
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index ab017c7b54..07d5e7d38e 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -24,7 +24,7 @@ module ActiveRecord
protected
def define_method_attribute=(name)
- safe_name = name.unpack('h*').first
+ 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
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
index 50339b6f69..8b2c4c7170 100644
--- a/activerecord/lib/active_record/attributes.rb
+++ b/activerecord/lib/active_record/attributes.rb
@@ -1,3 +1,5 @@
+require 'active_record/attribute/user_provided_default'
+
module ActiveRecord
# See ActiveRecord::Attributes::ClassMethods for documentation
module Attributes
@@ -80,6 +82,14 @@ module ActiveRecord
#
# StoreListing.new.my_string # => "new default"
#
+ # class Product < ActiveRecord::Base
+ # attribute :my_default_proc, :datetime, default: -> { Time.now }
+ # end
+ #
+ # Product.new.my_default_proc # => 2015-05-30 11:04:48 -0600
+ # sleep 1
+ # Product.new.my_default_proc # => 2015-05-30 11:04:49 -0600
+ #
# Attributes do not need to be backed by a database column.
#
# class MyModel < ActiveRecord::Base
@@ -202,7 +212,8 @@ module ActiveRecord
#
# +default+ The default value to use when no value is provided. If this option
# is not passed, the previous default value (if any) will be used.
- # Otherwise, the default will be +nil+.
+ # Otherwise, the default will be +nil+. A proc can also be passed, and
+ # will be called once each time a new value is needed.
#
# +user_provided_default+ Whether the default value should be cast using
# +cast+ or +deserialize+.
@@ -236,7 +247,12 @@ module ActiveRecord
if value == NO_DEFAULT_PROVIDED
default_attribute = _default_attributes[name].with_type(type)
elsif from_user
- default_attribute = Attribute.from_user(name, value, type)
+ default_attribute = Attribute::UserProvidedDefault.new(
+ name,
+ value,
+ type,
+ _default_attributes[name],
+ )
else
default_attribute = Attribute.from_database(name, value, type)
end
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 0792d19c3e..dbb0e2fab2 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -222,6 +222,7 @@ module ActiveRecord
true
end
validate validation_method
+ after_validation :_ensure_no_duplicate_errors
end
end
end
@@ -324,7 +325,7 @@ module ActiveRecord
# 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)
- return true if record.destroyed? || record.marked_for_destruction?
+ 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)
@@ -456,5 +457,11 @@ module ActiveRecord
end
end
end
+
+ def _ensure_no_duplicate_errors
+ errors.messages.each_key do |attribute|
+ errors[attribute].uniq!
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 93550a69f1..55a7e053bc 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -5,7 +5,6 @@ require 'active_support/dependencies'
require 'active_support/descendants_tracker'
require 'active_support/time'
require 'active_support/core_ext/module/attribute_accessors'
-require 'active_support/core_ext/class/delegating_attributes'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/hash/deep_merge'
require 'active_support/core_ext/hash/slice'
@@ -120,23 +119,22 @@ module ActiveRecord #:nodoc:
# All column values are automatically available through basic accessors on the Active Record
# object, but sometimes you want to specialize this behavior. This can be done by overwriting
# the default accessors (using the same name as the attribute) and calling
- # <tt>read_attribute(attr_name)</tt> and <tt>write_attribute(attr_name, value)</tt> to actually
- # change things.
+ # +super+ to actually change things.
#
# class Song < ActiveRecord::Base
# # Uses an integer of seconds to hold the length of the song
#
# def length=(minutes)
- # write_attribute(:length, minutes.to_i * 60)
+ # super(minutes.to_i * 60)
# end
#
# def length
- # read_attribute(:length) / 60
+ # super / 60
# end
# end
#
# You can alternatively use <tt>self[:attribute]=(value)</tt> and <tt>self[:attribute]</tt>
- # instead of <tt>write_attribute(:attribute, value)</tt> and <tt>read_attribute(:attribute)</tt>.
+ # or <tt>write_attribute(:attribute, value)</tt> and <tt>read_attribute(:attribute)</tt>.
#
# == Attribute query methods
#
@@ -282,6 +280,7 @@ module ActiveRecord #:nodoc:
extend Explain
extend Enum
extend Delegation::DelegateCache
+ extend CollectionCacheKey
include Core
include Persistence
@@ -309,6 +308,7 @@ module ActiveRecord #:nodoc:
include Aggregations
include Transactions
include NoTouching
+ include TouchLater
include Reflection
include Serialization
include Store
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 2fcba8e309..ccdbebbc77 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -194,7 +194,7 @@ module ActiveRecord
#
# If the +before_validation+ callback throws +:abort+, the process will be
# aborted and <tt>Base#save</tt> will return +false+. If Base#save! is called it will raise a
- # ActiveRecord::RecordInvalid exception. Nothing will be appended to the errors object.
+ # <tt>ActiveRecord::RecordInvalid</tt> exception. Nothing will be appended to the errors object.
#
# == Canceling callbacks
#
@@ -206,7 +206,8 @@ module ActiveRecord
# == Ordering callbacks
#
# Sometimes the code needs that the callbacks execute in a specific order. For example, a +before_destroy+
- # callback (+log_children+ in this case) should be executed before the children get destroyed by the +dependent: destroy+ option.
+ # callback (+log_children+ in this case) should be executed before the children get destroyed by the
+ # <tt>dependent: destroy</tt> option.
#
# Let's look at the code below:
#
@@ -289,24 +290,28 @@ module ActiveRecord
end
def destroy #:nodoc:
- run_callbacks(:destroy) { super }
+ _run_destroy_callbacks { super }
+ rescue RecordNotDestroyed => e
+ @_association_destroy_exception = e
+ false
end
def touch(*) #:nodoc:
- run_callbacks(:touch) { super }
+ _run_touch_callbacks { super }
end
private
+
def create_or_update(*) #:nodoc:
- run_callbacks(:save) { super }
+ _run_save_callbacks { super }
end
def _create_record #:nodoc:
- run_callbacks(:create) { super }
+ _run_create_callbacks { super }
end
def _update_record(*) #:nodoc:
- run_callbacks(:update) { super }
+ _run_update_callbacks { super }
end
end
end
diff --git a/activerecord/lib/active_record/collection_cache_key.rb b/activerecord/lib/active_record/collection_cache_key.rb
new file mode 100644
index 0000000000..3c4ca3d116
--- /dev/null
+++ b/activerecord/lib/active_record/collection_cache_key.rb
@@ -0,0 +1,31 @@
+module ActiveRecord
+ module CollectionCacheKey
+
+ def collection_cache_key(collection = all, timestamp_column = :updated_at) # :nodoc:
+ query_signature = Digest::MD5.hexdigest(collection.to_sql)
+ key = "#{collection.model_name.cache_key}/query-#{query_signature}"
+
+ if collection.loaded?
+ size = collection.size
+ timestamp = collection.max_by(&timestamp_column).public_send(timestamp_column)
+ else
+ column_type = type_for_attribute(timestamp_column.to_s)
+ column = "#{connection.quote_table_name(collection.table_name)}.#{connection.quote_column_name(timestamp_column)}"
+
+ query = collection
+ .select("COUNT(*) AS size", "MAX(#{column}) AS timestamp")
+ .unscope(:order)
+ result = connection.select_one(query)
+
+ size = result["size"]
+ timestamp = column_type.deserialize(result["timestamp"])
+ end
+
+ if timestamp
+ "#{key}-#{size}-#{timestamp.utc.to_s(cache_timestamp_format)}"
+ else
+ "#{key}-#{size}"
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 8c50f3d1a3..282af220fb 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -1,7 +1,6 @@
require 'thread'
require 'thread_safe'
require 'monitor'
-require 'set'
module ActiveRecord
# Raised when a connection could not be obtained within the connection
@@ -10,6 +9,12 @@ module ActiveRecord
class ConnectionTimeoutError < ConnectionNotEstablished
end
+ # Raised when a pool was unable to get ahold of all its connections
+ # to perform a "group" action such as +ConnectionPool#disconnect!+
+ # or +ConnectionPool#clear_reloadable_connections!+.
+ class ExclusiveConnectionTimeoutError < ConnectionTimeoutError
+ end
+
module ConnectionAdapters
# Connection pool base class for managing Active Record database
# connections.
@@ -63,6 +68,15 @@ module ActiveRecord
# 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).
+ #
+ #--
+ # Synchronization policy:
+ # * all public methods can be called outside +synchronize+
+ # * access to these i-vars needs to be in +synchronize+:
+ # * @connections
+ # * @now_connecting
+ # * private methods that require being called in a +synchronize+ blocks
+ # are now explicitly documented
class ConnectionPool
# Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool
# with which it shares a Monitor. But could be a generic Queue.
@@ -129,17 +143,15 @@ module ActiveRecord
# - ConnectionTimeoutError if +timeout+ is given and no element
# becomes available within +timeout+ seconds,
def poll(timeout = nil)
- synchronize do
- if timeout
- no_wait_poll || wait_poll(timeout)
- else
- no_wait_poll
- end
- end
+ synchronize { internal_poll(timeout) }
end
private
+ def internal_poll(timeout)
+ no_wait_poll || (timeout && wait_poll(timeout))
+ end
+
def synchronize(&block)
@lock.synchronize(&block)
end
@@ -193,6 +205,80 @@ module ActiveRecord
end
end
+ # Adds the ability to turn a basic fair FIFO queue into one
+ # biased to some thread.
+ module BiasableQueue # :nodoc:
+ class BiasedConditionVariable # :nodoc:
+ # semantics of condition variables guarantee that +broadcast+, +broadcast_on_biased+,
+ # +signal+ and +wait+ methods are only called while holding a lock
+ def initialize(lock, other_cond, preferred_thread)
+ @real_cond = lock.new_cond
+ @other_cond = other_cond
+ @preferred_thread = preferred_thread
+ @num_waiting_on_real_cond = 0
+ end
+
+ def broadcast
+ broadcast_on_biased
+ @other_cond.broadcast
+ end
+
+ def broadcast_on_biased
+ @num_waiting_on_real_cond = 0
+ @real_cond.broadcast
+ end
+
+ def signal
+ if @num_waiting_on_real_cond > 0
+ @num_waiting_on_real_cond -= 1
+ @real_cond
+ else
+ @other_cond
+ end.signal
+ end
+
+ def wait(timeout)
+ if Thread.current == @preferred_thread
+ @num_waiting_on_real_cond += 1
+ @real_cond
+ else
+ @other_cond
+ end.wait(timeout)
+ end
+ end
+
+ def with_a_bias_for(thread)
+ previous_cond = nil
+ new_cond = nil
+ synchronize do
+ previous_cond = @cond
+ @cond = new_cond = BiasedConditionVariable.new(@lock, @cond, thread)
+ end
+ yield
+ ensure
+ synchronize do
+ @cond = previous_cond if previous_cond
+ new_cond.broadcast_on_biased if new_cond # wake up any remaining sleepers
+ end
+ end
+ end
+
+ # Connections must be leased while holding the main pool mutex. This is
+ # an internal subclass that also +.leases+ returned connections while
+ # still in queue's critical section (queue synchronizes with the same
+ # +@lock+ as the main pool) so that a returned connection is already
+ # leased and there is no need to re-enter synchronized block.
+ class ConnectionLeasingQueue < Queue # :nodoc:
+ include BiasableQueue
+
+ private
+ def internal_poll(timeout)
+ conn = super
+ conn.lease if conn
+ conn
+ end
+ end
+
# Every +frequency+ seconds, the reaper will call +reap+ on +pool+.
# A reaper instantiated with a nil frequency will never reap the
# connection pool.
@@ -220,7 +306,7 @@ module ActiveRecord
include MonitorMixin
- attr_accessor :automatic_reconnect, :checkout_timeout
+ attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache
attr_reader :spec, :connections, :size, :reaper
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
@@ -241,56 +327,75 @@ module ActiveRecord
# default max pool size to 5
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
- # The cache of reserved connections mapped to threads
- @reserved_connections = ThreadSafe::Cache.new(:initial_capacity => @size)
+ # The cache of threads mapped to reserved connections, the sole purpose
+ # of the cache is to speed-up +connection+ method, it is not the authoritative
+ # registry of which thread owns which connection, that is tracked by
+ # +connection.owner+ attr on each +connection+ instance.
+ # The invariant works like this: if there is mapping of +thread => conn+,
+ # then that +thread+ does indeed own that +conn+, however an absence of a such
+ # mapping does not mean that the +thread+ doesn't own the said connection, in
+ # that case +conn.owner+ attr should be consulted.
+ # Access and modification of +@thread_cached_conns+ does not require
+ # synchronization.
+ @thread_cached_conns = ThreadSafe::Cache.new(:initial_capacity => @size)
@connections = []
@automatic_reconnect = true
- @available = Queue.new self
+ # Connection pool allows for concurrent (outside the main `synchronize` section)
+ # establishment of new connections. This variable tracks the number of threads
+ # 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
+
+ @available = ConnectionLeasingQueue.new self
end
# Retrieve the connection associated with the current thread, or call
# #checkout to obtain one if necessary.
#
# #connection can be called any number of times; the connection is
- # held in a hash keyed by the thread id.
+ # held in a cache keyed by a thread.
def connection
- # this is correctly done double-checked locking
- # (ThreadSafe::Cache's lookups have volatile semantics)
- @reserved_connections[current_connection_id] || synchronize do
- @reserved_connections[current_connection_id] ||= checkout
- end
+ @thread_cached_conns[connection_cache_key(Thread.current)] ||= checkout
end
# Is there an open connection that is being used for the current thread?
+ #
+ # This method only works for connections that have been abtained through
+ # #connection or #with_connection methods, connections obtained through
+ # #checkout will not be detected by #active_connection?
def active_connection?
- synchronize do
- @reserved_connections.fetch(current_connection_id) {
- return false
- }.in_use?
- end
+ @thread_cached_conns[connection_cache_key(Thread.current)]
end
# Signal that the thread is finished with the current connection.
# #release_connection releases the connection-thread association
# and returns the connection to the pool.
- def release_connection(with_id = current_connection_id)
- synchronize do
- conn = @reserved_connections.delete(with_id)
- checkin conn if conn
+ #
+ # This method only works for connections that have been obtained through
+ # #connection or #with_connection methods, connections obtained through
+ # #checkout will not be automatically released.
+ def release_connection(owner_thread = Thread.current)
+ if conn = @thread_cached_conns.delete(connection_cache_key(owner_thread))
+ checkin conn
end
end
- # If a connection already exists yield it to the block. If no connection
+ # If a connection obtained through #connection or #with_connection methods
+ # already exists yield it to the block. If no such connection
# exists checkout a connection, yield it to the block, and checkin the
# connection when finished.
def with_connection
- connection_id = current_connection_id
- fresh_connection = true unless active_connection?
- yield connection
+ unless conn = @thread_cached_conns[connection_cache_key(Thread.current)]
+ conn = connection
+ fresh_connection = true
+ end
+ yield conn
ensure
- release_connection(connection_id) if fresh_connection
+ release_connection if fresh_connection
end
# Returns true if a connection has already been opened.
@@ -299,32 +404,81 @@ module ActiveRecord
end
# Disconnects all connections in the pool, and clears the pool.
- def disconnect!
- synchronize do
- @reserved_connections.clear
- @connections.each do |conn|
- checkin conn
- conn.disconnect!
+ #
+ # Raises:
+ # - +ExclusiveConnectionTimeoutError+ if unable to gain ownership of all
+ # connections in the pool within a timeout interval (default duration is
+ # +spec.config[:checkout_timeout] * 2+ seconds).
+ def disconnect(raise_on_acquisition_timeout = true)
+ with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
+ synchronize do
+ @connections.each do |conn|
+ checkin conn
+ conn.disconnect!
+ end
+ @connections = []
+ @available.clear
end
- @connections = []
- @available.clear
end
end
- # Clears the cache which maps classes.
- def clear_reloadable_connections!
- synchronize do
- @reserved_connections.clear
- @connections.each do |conn|
- checkin conn
- conn.disconnect! if conn.requires_reloading?
- end
- @connections.delete_if(&:requires_reloading?)
- @available.clear
- @connections.each do |conn|
- @available.add conn
+ # Disconnects all connections in the pool, and clears the pool.
+ #
+ # The pool first tries to gain ownership of all connections, if unable to
+ # do so within a timeout interval (default duration is
+ # +spec.config[:checkout_timeout] * 2+ seconds), the pool is forcefully
+ # disconneted wihout any regard for other connection owning threads.
+ def disconnect!
+ disconnect(false)
+ end
+
+ # Clears the cache which maps classes and re-connects connections that
+ # require reloading.
+ #
+ # Raises:
+ # - +ExclusiveConnectionTimeoutError+ if unable to gain ownership of all
+ # connections in the pool within a timeout interval (default duration is
+ # +spec.config[:checkout_timeout] * 2+ 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|
+ checkin conn
+ 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
+ # require reloading.
+ #
+ # The pool first tries to gain ownership of all connections, if unable to
+ # do so within a timeout interval (default duration is
+ # +spec.config[:checkout_timeout] * 2+ seconds), the pool forcefully
+ # clears the cache and reloads connections without any regard for other
+ # connection owning threads.
+ def clear_reloadable_connections!
+ clear_reloadable_connections(false)
end
# Check-out a database connection from the pool, indicating that you want
@@ -341,12 +495,8 @@ module ActiveRecord
#
# Raises:
# - ConnectionTimeoutError: no connection can be obtained from the pool.
- def checkout
- synchronize do
- conn = acquire_connection
- conn.lease
- checkout_and_verify(conn)
- end
+ def checkout(checkout_timeout = @checkout_timeout)
+ checkout_and_verify(acquire_connection(checkout_timeout))
end
# Check-in a database connection back into the pool, indicating that you
@@ -356,14 +506,12 @@ module ActiveRecord
# calling +checkout+ on this pool.
def checkin(conn)
synchronize do
- owner = conn.owner
+ remove_connection_from_thread_cache conn
- conn.run_callbacks :checkin do
+ conn._run_checkin_callbacks do
conn.expire
end
- release conn, owner
-
@available.add conn
end
end
@@ -371,14 +519,32 @@ module ActiveRecord
# Remove a connection from the connection pool. The connection will
# remain open and active but will no longer be managed by this pool.
def remove(conn)
+ needs_new_connection = false
+
synchronize do
+ remove_connection_from_thread_cache conn
+
@connections.delete conn
@available.delete conn
- release conn, conn.owner
-
- @available.add checkout_new_connection if @available.any_waiting?
+ # @available.any_waiting? => true means that prior to removing this
+ # conn, the pool was at its max size (@connections.size == @size)
+ # this would mean that any threads stuck waiting in the queue wouldn't
+ # know they could checkout_new_connection, so let's do it for them.
+ # Because condition-wait loop is encapsulated in the Queue class
+ # (that in turn is oblivious to ConnectionPool implementation), threads
+ # that are "stuck" there are helpless, they have no way of creating
+ # new connections and are completely reliant on us feeding available
+ # connections into the Queue.
+ needs_new_connection = @available.any_waiting?
end
+
+ # This is intentionally done outside of the synchronized section as we
+ # would like not to hold the main mutex while checking out new connections,
+ # thus there is some chance that needs_new_connection information is now
+ # stale, we can live with that (bulk_make_new_connections will make
+ # sure not to exceed the pool's @size limit).
+ bulk_make_new_connections(1) if needs_new_connection
end
# Recover lost connections for the pool. A lost connection can occur if
@@ -403,7 +569,118 @@ module ActiveRecord
end
end
+ def num_waiting_in_queue # :nodoc:
+ @available.num_waiting
+ end
+
private
+ #--
+ # 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
+ if new_conn = try_to_checkout_new_connection
+ # make the new_conn available to the starving threads stuck @available Queue
+ checkin(new_conn)
+ end
+ 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.
+ 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.
+ 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)
+ yield
+ end
+ end
+
+ 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}
+ end
+
+ newly_checked_out = []
+ timeout_time = Time.now + (@checkout_timeout * 2)
+
+ @available.with_a_bias_for(Thread.current) do
+ while true
+ synchronize do
+ return if collected_conns.size == @connections.size && @now_connecting == 0
+ remaining_timeout = timeout_time - Time.now
+ remaining_timeout = 0 if remaining_timeout < 0
+ conn = checkout_for_exclusive_access(remaining_timeout)
+ collected_conns << conn
+ newly_checked_out << conn
+ end
+ end
+ end
+ rescue ExclusiveConnectionTimeoutError
+ # `raise_on_acquisition_timeout == false` means we are directed to ignore any
+ # timeouts and are expected to just give up: we've obtained as many connections
+ # as possible, note that in a case like that we don't return any of the
+ # `newly_checked_out` connections.
+
+ if raise_on_acquisition_timeout
+ release_newly_checked_out = true
+ raise
+ end
+ rescue Exception # if something else went wrong
+ # this can't be a "naked" rescue, because we have should return conns
+ # even for non-StandardErrors
+ release_newly_checked_out = true
+ raise
+ ensure
+ 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)}
+ end
+ end
+
+ #--
+ # Must be called in a synchronize block.
+ def checkout_for_exclusive_access(checkout_timeout)
+ checkout(checkout_timeout)
+ rescue ConnectionTimeoutError
+ # this block can't be easily moved into attempt_to_checkout_all_existing_connections's
+ # rescue block, because doing so would put it outside of synchronize section, without
+ # being in a critical section thread_report might become inaccurate
+ msg = "could not obtain ownership of all database connections in #{checkout_timeout} seconds"
+
+ thread_report = []
+ @connections.each do |conn|
+ unless conn.owner == Thread.current
+ thread_report << "#{conn} is owned by #{conn.owner}"
+ end
+ end
+
+ msg << " (#{thread_report.join(', ')})" if thread_report.any?
+
+ raise ExclusiveConnectionTimeoutError, msg
+ end
+
+ def with_new_connections_blocked
+ previous_value = nil
+ synchronize do
+ previous_value, @new_cons_enabled = @new_cons_enabled, false
+ end
+ yield
+ ensure
+ synchronize { @new_cons_enabled = previous_value }
+ end
# Acquire a connection by one of 1) immediately removing one
# from the queue of available connections, 2) creating a new
@@ -412,44 +689,82 @@ module ActiveRecord
#
# Raises:
# - ConnectionTimeoutError if a connection could not be acquired
- def acquire_connection
- if conn = @available.poll
+ #
+ #--
+ # 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`
+ # section), this is not the cleanest implementation, as ideally we would
+ # `synchronize { conn.lease }` in this method, but by leaving it to `@available.poll`
+ # and `try_to_checkout_new_connection` we can piggyback on `synchronize` sections
+ # of the said methods and avoid an additional `synchronize` overhead.
+ if conn = @available.poll || try_to_checkout_new_connection
conn
- elsif @connections.size < @size
- checkout_new_connection
else
reap
- @available.poll(@checkout_timeout)
+ @available.poll(checkout_timeout)
end
end
- def release(conn, owner)
- thread_id = owner.object_id
-
- if @reserved_connections[thread_id] == conn
- @reserved_connections.delete thread_id
- end
+ #--
+ # 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
+ alias_method :release, :remove_connection_from_thread_cache
def new_connection
- Base.send(spec.adapter_method, spec.config)
+ Base.send(spec.adapter_method, spec.config).tap do |conn|
+ conn.schema_cache = schema_cache.dup if schema_cache
+ 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.
+ 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
+ @now_connecting += 1
+ end
+ end
+ if do_checkout
+ begin
+ # if successfully incremented @now_connecting establish new connection
+ # outside of synchronized section
+ conn = checkout_new_connection
+ ensure
+ synchronize do
+ if conn
+ adopt_connection(conn)
+ # returned conn needs to be already leased
+ conn.lease
+ end
+ @now_connecting -= 1
+ end
+ end
+ end
end
- def current_connection_id #:nodoc:
- Base.connection_id ||= Thread.current.object_id
+ def adopt_connection(conn)
+ conn.pool = self
+ @connections << conn
end
def checkout_new_connection
raise ConnectionNotEstablished unless @automatic_reconnect
-
- c = new_connection
- c.pool = self
- @connections << c
- c
+ new_connection
end
def checkout_and_verify(c)
- c.run_callbacks :checkout do
+ c._run_checkout_callbacks do
c.verify!
end
c
@@ -618,7 +933,9 @@ module ActiveRecord
# A connection was established in an ancestor process that must have
# subsequently forked. We can't reuse the connection, but we can copy
# the specification and establish a new connection with it.
- establish_connection owner, ancestor_pool.spec
+ establish_connection(owner, ancestor_pool.spec).tap do |pool|
+ pool.schema_cache = ancestor_pool.schema_cache if ancestor_pool.schema_cache
+ end
else
owner_to_pool[owner.name] = nil
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 c0a2111571..30b2fca2ca 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
@@ -18,9 +18,9 @@ module ActiveRecord
end
# Returns the maximum allowed length for an index name. This
- # limit is enforced by rails and Is less than or equal to
+ # limit is enforced by \Rails and is less than or equal to
# <tt>index_name_length</tt>. The gap between
- # <tt>index_name_length</tt> is to allow internal rails
+ # <tt>index_name_length</tt> is to allow internal \Rails
# operations to use prefixes in temporary operations.
def allowed_index_name_length
index_name_length
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 42c794c828..1e13b24867 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -40,8 +40,9 @@ module ActiveRecord
# Returns a single value from a record
def select_value(arel, name = nil, binds = [])
- if result = select_one(arel, name, binds)
- result.values.first
+ arel, binds = binds_from_relation arel, binds
+ if result = select_rows(to_sql(arel, binds), name, binds).first
+ result.first
end
end
@@ -188,7 +189,7 @@ module ActiveRecord
# You should consult the documentation for your database to understand the
# semantics of these different levels:
#
- # * http://www.postgresql.org/docs/9.1/static/transaction-iso.html
+ # * http://www.postgresql.org/docs/current/static/transaction-iso.html
# * https://dev.mysql.com/doc/refman/5.6/en/set-transaction.html
#
# An <tt>ActiveRecord::TransactionIsolationError</tt> will be raised if:
@@ -288,8 +289,12 @@ module ActiveRecord
columns = schema_cache.columns_hash(table_name)
binds = fixture.map do |name, value|
- type = lookup_cast_type_from_column(columns[name])
- Relation::QueryAttribute.new(name, value, type)
+ if column = columns[name]
+ type = lookup_cast_type_from_column(column)
+ Relation::QueryAttribute.new(name, value, type)
+ else
+ raise Fixture::FixtureError, %(table "#{table_name}" has no column named "#{name}".)
+ end
end
key_list = fixture.keys.map { |name| quote_column_name(name) }
value_list = prepare_binds_for_database(binds).map do |value|
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 91c7298983..2c7409b2dc 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -123,6 +123,8 @@ module ActiveRecord
'f'
end
+ # Quote date/time values for use in SQL input. Includes microseconds
+ # if the value is a Time responding to usec.
def quoted_date(value)
if value.acts_like?(:time)
zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
index f754df93b6..18d943f452 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -14,10 +14,6 @@ module ActiveRecord
send m, o
end
- def visit_AddColumn(o)
- "ADD #{accept(o)}"
- end
-
delegate :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql, to: :@conn
private :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql
@@ -25,7 +21,7 @@ module ActiveRecord
def visit_AlterTable(o)
sql = "ALTER TABLE #{quote_table_name(o.name)} "
- sql << o.adds.map { |col| visit_AddColumn col }.join(' ')
+ sql << o.adds.map { |col| accept col }.join(' ')
sql << o.foreign_key_adds.map { |fk| visit_AddForeignKey fk }.join(' ')
sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(' ')
end
@@ -37,6 +33,10 @@ module ActiveRecord
column_sql
end
+ def visit_AddColumnDefinition(o)
+ "ADD #{accept(o.column)}"
+ end
+
def visit_TableDefinition(o)
create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE "
create_sql << "#{quote_table_name(o.name)} "
@@ -70,6 +70,7 @@ module ActiveRecord
column_options[:after] = o.after
column_options[:auto_increment] = o.auto_increment
column_options[:primary_key] = o.primary_key
+ column_options[:collation] = o.collation
column_options
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 cb83d0022c..d17e272ed1 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -1,8 +1,3 @@
-require 'date'
-require 'set'
-require 'bigdecimal'
-require 'bigdecimal/util'
-
module ActiveRecord
module ConnectionAdapters #:nodoc:
# Abstract representation of an index definition on a table. Instances of
@@ -15,13 +10,16 @@ module ActiveRecord
# are typically created by methods in TableDefinition, and added to the
# +columns+ attribute of said TableDefinition object, in order to be used
# for generating a number of table creation or table changing SQL statements.
- class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :sql_type) #:nodoc:
+ class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :collation, :sql_type) #:nodoc:
def primary_key?
primary_key || type.to_sym == :primary_key
end
end
+ class AddColumnDefinition < Struct.new(:column) # :nodoc:
+ end
+
class ChangeColumnDefinition < Struct.new(:column, :name) #:nodoc:
end
@@ -227,7 +225,7 @@ module ActiveRecord
# The +type+ parameter is normally one of the migrations native types,
# which is one of the following:
# <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
- # <tt>:integer</tt>, <tt>:float</tt>, <tt>:decimal</tt>,
+ # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>,
# <tt>:datetime</tt>, <tt>:time</tt>, <tt>:date</tt>,
# <tt>:binary</tt>, <tt>:boolean</tt>.
#
@@ -359,6 +357,7 @@ module ActiveRecord
def column(name, type, options = {})
name = name.to_s
type = type.to_sym
+ options = options.dup
if @columns_hash[name] && @columns_hash[name].primary_key?
raise ArgumentError, "you can't redefine the primary key column '#{name}'. To define a custom primary key, pass { id: false } to create_table."
@@ -399,17 +398,12 @@ module ActiveRecord
column(:updated_at, :datetime, options)
end
- # Adds a reference. Optionally adds a +type+ column, if the
- # +:polymorphic+ option is provided. +references+ and +belongs_to+
- # are interchangeable. The reference column will be an +integer+ by default,
- # the +:type+ option can be used to specify a different type. A foreign
- # key will be created if the +:foreign_key+ option is passed.
+ # Adds a reference.
#
# t.references(:user)
- # t.references(:user, type: "string")
- # t.belongs_to(:supplier, polymorphic: true)
+ # t.belongs_to(:supplier, foreign_key: true)
#
- # See SchemaStatements#add_reference
+ # See SchemaStatements#add_reference for details of the options you can use.
def references(*args, **options)
args.each do |col|
ReferenceDefinition.new(col, **options).add_to(self)
@@ -433,6 +427,7 @@ module ActiveRecord
column.after = options[:after]
column.auto_increment = options[:auto_increment]
column.primary_key = type == :primary_key || options[:primary_key]
+ column.collation = options[:collation]
column
end
@@ -475,7 +470,7 @@ module ActiveRecord
def add_column(name, type, options)
name = name.to_s
type = type.to_sym
- @adds << @td.new_column_definition(name, type, options)
+ @adds << AddColumnDefinition.new(@td.new_column_definition(name, type, options))
end
end
@@ -597,10 +592,11 @@ module ActiveRecord
#
# t.change_default(:qualification, 'new')
# t.change_default(:authorized, 1)
+ # t.change_default(:status, from: nil, to: "draft")
#
# See SchemaStatements#change_column_default
- def change_default(column_name, default)
- @base.change_column_default(name, column_name, default)
+ def change_default(column_name, default_or_changes)
+ @base.change_column_default(name, column_name, default_or_changes)
end
# Removes the column(s) from the table definition.
@@ -642,15 +638,12 @@ module ActiveRecord
@base.rename_column(name, column_name, new_column_name)
end
- # Adds a reference. Optionally adds a +type+ column, if
- # <tt>:polymorphic</tt> option is provided.
+ # Adds a reference.
#
# t.references(:user)
- # t.references(:user, type: "string")
- # t.belongs_to(:supplier, polymorphic: true)
# t.belongs_to(:supplier, foreign_key: true)
#
- # See SchemaStatements#add_reference
+ # See SchemaStatements#add_reference for details of the options you can use.
def references(*args)
options = args.extract_options!
args.each do |ref_name|
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 999cb0ec5a..b944a8631c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
@@ -27,20 +27,31 @@ module ActiveRecord
spec[:type] = schema_type(column)
spec[:null] = 'false' unless column.null
- limit = column.limit || native_database_types[column.type][:limit]
- spec[:limit] = limit.inspect if limit
- spec[:precision] = column.precision.inspect if column.precision
- spec[:scale] = column.scale.inspect if column.scale
+ if limit = schema_limit(column)
+ spec[:limit] = limit
+ end
+
+ if precision = schema_precision(column)
+ spec[:precision] = precision
+ end
+
+ if scale = schema_scale(column)
+ spec[:scale] = scale
+ end
default = schema_default(column) if column.has_default?
spec[:default] = default unless default.nil?
+ if collation = schema_collation(column)
+ spec[:collation] = collation
+ end
+
spec
end
# Lists the valid migration options
def migration_keys
- [:name, :limit, :precision, :scale, :default, :null]
+ [:name, :limit, :precision, :scale, :default, :null, :collation]
end
private
@@ -49,6 +60,19 @@ module ActiveRecord
column.type.to_s
end
+ def schema_limit(column)
+ limit = column.limit || native_database_types[column.type][:limit]
+ limit.inspect if limit
+ end
+
+ def schema_precision(column)
+ column.precision.inspect if column.precision
+ end
+
+ def schema_scale(column)
+ column.scale.inspect if column.scale
+ end
+
def schema_default(column)
type = lookup_cast_type_from_column(column)
default = type.deserialize(column.default)
@@ -56,6 +80,10 @@ module ActiveRecord
type.type_cast_for_schema(default)
end
end
+
+ def schema_collation(column)
+ column.collation.inspect if column.collation
+ end
end
end
end
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 ab1b098e53..a30945d0ee 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -14,6 +14,10 @@ module ActiveRecord
{}
end
+ def table_options(table_name)
+ nil
+ end
+
# Truncates a table alias according to the limits of the current adapter.
def table_alias_for(table_name)
table_name[0...table_alias_length].tr('.', '_')
@@ -120,6 +124,8 @@ module ActiveRecord
# [<tt>:id</tt>]
# Whether to automatically add a primary key column. Defaults to true.
# Join tables for +has_and_belongs_to_many+ should set it to false.
+ #
+ # A Symbol can be used to specify the type of the generated primary key column.
# [<tt>:primary_key</tt>]
# The name of the primary key, if one is to be added automatically.
# Defaults to +id+. If <tt>:id</tt> is false this option is ignored.
@@ -163,6 +169,19 @@ module ActiveRecord
# name varchar(80)
# )
#
+ # ====== Change the primary key column type
+ #
+ # create_table(:tags, id: :string) do |t|
+ # t.column :label, :string
+ # end
+ #
+ # generates:
+ #
+ # CREATE TABLE tags (
+ # id varchar PRIMARY KEY,
+ # label varchar
+ # )
+ #
# ====== Do not add a primary key column
#
# create_table(:categories_suppliers, id: false) do |t|
@@ -390,6 +409,10 @@ module ActiveRecord
# Adds a new column to the named table.
# See TableDefinition#column for details of the options you can use.
+ #
+ # Note: Not all options will be available, generally this command should
+ # ignore most of them. In favor of doing a low-level call to simply
+ # create a column.
def add_column(table_name, column_name, type, options = {})
at = create_alter_table table_name
at.add_column(column_name, type, options)
@@ -437,7 +460,12 @@ module ActiveRecord
#
# change_column_default(:users, :email, nil)
#
- def change_column_default(table_name, column_name, default)
+ # Passing a hash containing +:from+ and +:to+ will make this change
+ # reversible in migration:
+ #
+ # change_column_default(:posts, :state, from: nil, to: "draft")
+ #
+ def change_column_default(table_name, column_name, default_or_changes)
raise NotImplementedError, "change_column_default is not implemented"
end
@@ -533,6 +561,8 @@ module ActiveRecord
#
# CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
#
+ # Note: Partial indexes are only supported for PostgreSQL and SQLite 3.8.0+.
+ #
# ====== Creating an index with a specific method
#
# add_index(:developers, :name, using: 'btree')
@@ -562,7 +592,7 @@ module ActiveRecord
#
# Removes the +index_accounts_on_column+ in the +accounts+ table.
#
- # remove_index :accounts, :column
+ # remove_index :accounts, :branch_id
#
# Removes the index named +index_accounts_on_branch_id+ in the +accounts+ table.
#
@@ -624,11 +654,21 @@ module ActiveRecord
indexes(table_name).detect { |i| i.name == index_name }
end
- # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
- # The reference column is an +integer+ by default, the <tt>:type</tt> option can be used to specify
- # a different type.
+ # Adds a reference. The reference column is an integer by default,
+ # the <tt>:type</tt> option can be used to specify a different type.
+ # Optionally adds a +_type+ column, if <tt>:polymorphic</tt> option is provided.
# <tt>add_reference</tt> and <tt>add_belongs_to</tt> are acceptable.
#
+ # The +options+ hash can include the following keys:
+ # [<tt>:type</tt>]
+ # The reference column type. Defaults to +:integer+.
+ # [<tt>:index</tt>]
+ # Add an appropriate index. Defaults to false.
+ # [<tt>:foreign_key</tt>]
+ # Add an appropriate foreign key. Defaults to false.
+ # [<tt>:polymorphic</tt>]
+ # Whether an additional +_type+ column should be added. Defaults to false.
+ #
# ====== Create a user_id integer column
#
# add_reference(:products, :user)
@@ -637,10 +677,6 @@ module ActiveRecord
#
# add_reference(:products, :user, type: :string)
#
- # ====== Create a supplier_id and supplier_type columns
- #
- # add_belongs_to(:products, :supplier, polymorphic: true)
- #
# ====== Create supplier_id, supplier_type columns and appropriate index
#
# add_reference(:products, :supplier, polymorphic: true, index: true)
@@ -690,8 +726,8 @@ module ActiveRecord
# +to_table+ contains the referenced primary key.
#
# The foreign key will be named after the following pattern: <tt>fk_rails_<identifier></tt>.
- # +identifier+ is a 10 character long random string. A custom name can be specified with
- # the <tt>:name</tt> option.
+ # +identifier+ is a 10 character long string which is deterministically generated from the
+ # +from_table+ and +column+. A custom name can be specified with the <tt>:name</tt> option.
#
# ====== Creating a simple foreign key
#
@@ -725,9 +761,9 @@ module ActiveRecord
# [<tt>:name</tt>]
# The constraint name. Defaults to <tt>fk_rails_<identifier></tt>.
# [<tt>:on_delete</tt>]
- # Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade:+ and +:restrict+
+ # Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
# [<tt>:on_update</tt>]
- # Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade:+ and +:restrict+
+ # Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
def add_foreign_key(from_table, to_table, options = {})
return unless supports_foreign_keys?
@@ -746,7 +782,10 @@ module ActiveRecord
execute schema_creation.accept(at)
end
- # Removes the given foreign key from the table.
+ # Removes the given foreign key from the table. Any option parameters provided
+ # will be used to re-add the foreign key in case of a migration rollback.
+ # It is recommended that you provide any options used when creating the foreign
+ # key so that the migration can be reverted properly.
#
# Removes the foreign key on +accounts.branch_id+.
#
@@ -760,6 +799,7 @@ module ActiveRecord
#
# remove_foreign_key :accounts, name: :special_fk_name
#
+ # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key.
def remove_foreign_key(from_table, options_or_to_table = {})
return unless supports_foreign_keys?
@@ -797,7 +837,10 @@ module ActiveRecord
end
def foreign_key_column_for(table_name) # :nodoc:
- "#{table_name.to_s.singularize}_id"
+ prefix = Base.table_name_prefix
+ suffix = Base.table_name_suffix
+ name = table_name.to_s =~ /#{prefix}(.+)#{suffix}/ ? $1 : table_name.to_s
+ "#{name.singularize}_id"
end
def dump_schema_information #:nodoc:
@@ -909,13 +952,13 @@ module ActiveRecord
def add_index_options(table_name, column_name, options = {}) #:nodoc:
column_names = Array(column_name)
- index_name = index_name(table_name, column: column_names)
options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
- index_type = options[:unique] ? "UNIQUE" : ""
index_type = options[:type].to_s if options.key?(:type)
+ index_type ||= options[:unique] ? "UNIQUE" : ""
index_name = options[:name].to_s if options.key?(:name)
+ index_name ||= index_name(table_name, column: column_names)
max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
if options.key?(:algorithm)
@@ -1028,11 +1071,19 @@ module ActiveRecord
end
end
- def validate_index_length!(table_name, new_name)
+ def validate_index_length!(table_name, new_name) # :nodoc:
if new_name.length > allowed_index_name_length
raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
end
end
+
+ def extract_new_default_value(default_or_changes)
+ if default_or_changes.is_a?(Hash) && default_or_changes.has_key?(:from) && default_or_changes.has_key?(:to)
+ default_or_changes[:to]
+ else
+ default_or_changes
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index ae42e8ef8d..56227ddd80 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -1,13 +1,9 @@
-require 'date'
-require 'bigdecimal'
-require 'bigdecimal/util'
require 'active_record/type'
require 'active_support/core_ext/benchmark'
require 'active_record/connection_adapters/schema_cache'
require 'active_record/connection_adapters/sql_type_metadata'
require 'active_record/connection_adapters/abstract/schema_dumper'
require 'active_record/connection_adapters/abstract/schema_creation'
-require 'monitor'
require 'arel/collectors/bind'
require 'arel/collectors/sql_string'
@@ -70,7 +66,6 @@ module ActiveRecord
include DatabaseLimits
include QueryCache
include ActiveSupport::Callbacks
- include MonitorMixin
include ColumnDumper
SIMPLE_INT = /\A\d+\z/
@@ -112,6 +107,18 @@ module ActiveRecord
@prepared_statements = false
end
+ class Version
+ include Comparable
+
+ def initialize(version_string)
+ @version = version_string.split('.').map(&:to_i)
+ end
+
+ def <=>(version_string)
+ @version <=> version_string.split('.').map(&:to_i)
+ end
+ end
+
class BindCollector < Arel::Collectors::Bind
def compile(bvs, conn)
casted_binds = conn.prepare_binds_for_database(bvs)
@@ -141,12 +148,20 @@ module ActiveRecord
SchemaCreation.new self
end
+ # this method must only be called while holding connection pool's mutex
def lease
- synchronize do
- unless in_use?
- @owner = Thread.current
+ if in_use?
+ msg = 'Cannot lease connection, '
+ if @owner == Thread.current
+ msg << 'it is already leased by the current thread.'
+ else
+ msg << "it is already in use by a different thread: #{@owner}. " <<
+ "Current thread: #{Thread.current}."
end
+ raise ActiveRecordError, msg
end
+
+ @owner = Thread.current
end
def schema_cache=(cache)
@@ -154,6 +169,7 @@ module ActiveRecord
@schema_cache = cache
end
+ # this method must only be called while holding connection pool's mutex
def expire
@owner = nil
end
@@ -387,8 +403,8 @@ module ActiveRecord
end
end
- def new_column(name, default, sql_type_metadata = nil, null = true)
- Column.new(name, default, sql_type_metadata, null)
+ def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil)
+ Column.new(name, default, sql_type_metadata, null, default_function, collation)
end
def lookup_cast_type(sql_type) # :nodoc:
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 22c9076086..af156c9c78 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -1,4 +1,3 @@
-require 'arel/visitors/bind_visitor'
require 'active_support/core_ext/string/strip'
module ActiveRecord
@@ -13,6 +12,10 @@ module ActiveRecord
end
end
+ class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
+ attr_accessor :charset
+ end
+
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
include ColumnMethods
@@ -23,8 +26,15 @@ module ActiveRecord
column.type = :integer
column.auto_increment = true
end
+ column.charset = options[:charset]
column
end
+
+ private
+
+ def create_column_definition(name, type)
+ ColumnDefinition.new(name, type)
+ end
end
class Table < ActiveRecord::ConnectionAdapters::Table
@@ -32,10 +42,6 @@ module ActiveRecord
end
class SchemaCreation < AbstractAdapter::SchemaCreation
- def visit_AddColumn(o)
- add_column_position!(super, column_options(o))
- end
-
private
def visit_DropForeignKey(name)
@@ -55,11 +61,31 @@ module ActiveRecord
create_sql
end
+ def visit_AddColumnDefinition(o)
+ add_column_position!(super, column_options(o.column))
+ end
+
def visit_ChangeColumnDefinition(o)
change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}"
add_column_position!(change_column_sql, column_options(o.column))
end
+ def column_options(o)
+ column_options = super
+ column_options[:charset] = o.charset
+ column_options
+ end
+
+ def add_column_options!(sql, options)
+ if options[:charset]
+ sql << " CHARACTER SET #{options[:charset]}"
+ end
+ if options[:collation]
+ sql << " COLLATE #{options[:collation]}"
+ end
+ super
+ end
+
def add_column_position!(sql, options)
if options[:first]
sql << " FIRST"
@@ -70,8 +96,8 @@ module ActiveRecord
end
def index_in_create(table_name, column_name, options)
- index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options)
- "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}"
+ index_name, index_type, index_columns, _, _, index_using = @conn.add_index_options(table_name, column_name, options)
+ "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns}) "
end
end
@@ -95,15 +121,28 @@ module ActiveRecord
spec
end
- def prepare_column_options(column)
- spec = super
- spec.delete(:precision) if /time/ === column.sql_type && column.precision == 0
- spec.delete(:limit) if :boolean === column.type
- spec
+ private
+
+ def schema_limit(column)
+ super unless column.type == :boolean
+ end
+
+ def schema_precision(column)
+ super unless /time/ === column.sql_type && column.precision == 0
+ end
+
+ def schema_collation(column)
+ if column.collation && table_name = column.instance_variable_get(:@table_name)
+ @collation_cache ||= {}
+ @collation_cache[table_name] ||= select_one("SHOW TABLE STATUS LIKE '#{table_name}'")["Collation"]
+ column.collation.inspect if column.collation != @collation_cache[table_name]
+ end
end
+ public
+
class Column < ConnectionAdapters::Column # :nodoc:
- delegate :strict, :collation, :extra, to: :sql_type_metadata, allow_nil: true
+ delegate :strict, :extra, to: :sql_type_metadata, allow_nil: true
def initialize(*)
super
@@ -157,12 +196,11 @@ module ActiveRecord
end
class MysqlTypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
- attr_reader :collation, :extra, :strict
+ attr_reader :extra, :strict
- def initialize(type_metadata, collation: "", extra: "", strict: false)
+ def initialize(type_metadata, extra: "", strict: false)
super(type_metadata)
@type_metadata = type_metadata
- @collation = collation
@extra = extra
@strict = strict
end
@@ -180,7 +218,7 @@ module ActiveRecord
protected
def attributes_for_hash
- [self.class, @type_metadata, collation, extra, strict]
+ [self.class, @type_metadata, extra, strict]
end
end
@@ -269,7 +307,7 @@ module ActiveRecord
#
# http://bugs.mysql.com/bug.php?id=39170
def supports_transaction_isolation?
- version[0] >= 5
+ version >= '5.0.0'
end
def supports_indexes_in_create?
@@ -281,11 +319,11 @@ module ActiveRecord
end
def supports_views?
- version[0] >= 5
+ version >= '5.0.0'
end
def supports_datetime_with_precision?
- (version[0] == 5 && version[1] >= 6) || version[0] >= 6
+ version >= '5.6.4'
end
def native_database_types
@@ -304,8 +342,8 @@ module ActiveRecord
raise NotImplementedError
end
- def new_column(field, default, sql_type_metadata = nil, null = true) # :nodoc:
- Column.new(field, default, sql_type_metadata, null)
+ def new_column(field, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) # :nodoc:
+ Column.new(field, default, sql_type_metadata, null, default_function, collation)
end
# Must return the MySQL error number from the exception, if the exception has an
@@ -348,6 +386,14 @@ module ActiveRecord
0
end
+ def quoted_date(value)
+ if supports_datetime_with_precision?
+ super
+ else
+ super.sub(/\.\d{6}\z/, '')
+ end
+ end
+
# REFERENTIAL INTEGRITY ====================================
def disable_referential_integrity #:nodoc:
@@ -528,8 +574,8 @@ module ActiveRecord
each_hash(result).map do |field|
field_name = set_field_encoding(field[:Field])
sql_type = field[:Type]
- type_metadata = fetch_type_metadata(sql_type, field[:Collation], field[:Extra])
- new_column(field_name, field[:Default], type_metadata, field[:Null] == "YES")
+ type_metadata = fetch_type_metadata(sql_type, field[:Extra])
+ new_column(field_name, field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation])
end
end
end
@@ -591,12 +637,13 @@ module ActiveRecord
end
end
- def change_column_default(table_name, column_name, default) #:nodoc:
+ def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
+ default = extract_new_default_value(default_or_changes)
column = column_for(table_name, column_name)
change_column table_name, column_name, column.sql_type, :default => default
end
- def change_column_null(table_name, column_name, null, default = nil)
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
column = column_for(table_name, column_name)
unless null || default.nil?
@@ -616,8 +663,8 @@ module ActiveRecord
end
def add_index(table_name, column_name, options = {}) #:nodoc:
- index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
- execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options} #{index_algorithm}"
+ index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
end
def foreign_keys(table_name)
@@ -648,33 +695,25 @@ module ActiveRecord
end
end
+ def table_options(table_name)
+ create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
+
+ # strip create_definitions and partition_options
+ raw_table_options = create_table_info.sub(/\A.*\n\) /m, '').sub(/\n\/\*!.*\*\/\n\z/m, '').strip
+
+ # strip AUTO_INCREMENT
+ raw_table_options.sub(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
+ end
+
# Maps logical Rails types to MySQL-specific data types.
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
case type.to_s
when 'binary'
- case limit
- when 0..0xfff; "varbinary(#{limit})"
- when nil; "blob"
- when 0x1000..0xffffffff; "blob(#{limit})"
- else raise(ActiveRecordError, "No binary type has character length #{limit}")
- end
+ binary_to_sql(limit)
when 'integer'
- case limit
- when 1; 'tinyint'
- when 2; 'smallint'
- when 3; 'mediumint'
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
- when 5..8; 'bigint'
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
- end
+ integer_to_sql(limit)
when 'text'
- case limit
- when 0..0xff; 'tinytext'
- when nil, 0x100..0xffff; 'text'
- when 0x10000..0xffffff; 'mediumtext'
- when 0x1000000..0xffffffff; 'longtext'
- else raise(ActiveRecordError, "No text type has character length #{limit}")
- end
+ text_to_sql(limit)
else
super
end
@@ -788,21 +827,8 @@ module ActiveRecord
end
end
- def fetch_type_metadata(sql_type, collation = "", extra = "")
- MysqlTypeMetadata.new(super(sql_type), collation: collation, extra: extra, strict: strict_mode?)
- end
-
- # MySQL is too stupid to create a temporary table for use subquery, so we have
- # to give it some prompting in the form of a subsubquery. Ugh!
- def subquery_for(key, select)
- subsubselect = select.clone
- subsubselect.projections = [key]
-
- subselect = Arel::SelectManager.new(select.engine)
- subselect.project Arel.sql(key.name)
- # Materialized subquery by adding distinct
- # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
- subselect.from subsubselect.distinct.as('__active_record_temp')
+ def fetch_type_metadata(sql_type, extra = "")
+ MysqlTypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?)
end
def add_index_length(option_strings, column_names, options = {})
@@ -844,7 +870,7 @@ module ActiveRecord
def add_column_sql(table_name, column_name, type, options = {})
td = create_table_definition(table_name)
cd = td.new_column_definition(column_name, type, options)
- schema_creation.visit_AddColumn cd
+ schema_creation.accept(AddColumnDefinition.new(cd))
end
def change_column_sql(table_name, column_name, type, options = {})
@@ -886,8 +912,9 @@ module ActiveRecord
end
def add_index_sql(table_name, column_name, options = {})
- index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
- "ADD #{index_type} INDEX #{index_name} (#{index_columns})"
+ index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
+ index_algorithm[0, 0] = ", " if index_algorithm.present?
+ "ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
end
def remove_index_sql(table_name, options = {})
@@ -905,8 +932,21 @@ module ActiveRecord
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!
+ def subquery_for(key, select)
+ subsubselect = select.clone
+ subsubselect.projections = [key]
+
+ subselect = Arel::SelectManager.new(select.engine)
+ subselect.project Arel.sql(key.name)
+ # Materialized subquery by adding distinct
+ # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
+ subselect.from subsubselect.distinct.as('__active_record_temp')
+ end
+
def version
- @version ||= full_version.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map(&:to_i)
+ @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
end
def mariadb?
@@ -914,7 +954,7 @@ module ActiveRecord
end
def supports_rename_index?
- mariadb? ? false : (version[0] == 5 && version[1] >= 7) || version[0] >= 6
+ mariadb? ? false : version >= '5.7.6'
end
def configure_connection
@@ -928,10 +968,12 @@ module ActiveRecord
wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout)
+ defaults = [':default', :default].to_set
+
# Make MySQL reject illegal values rather than truncating or blanking them, see
# http://dev.mysql.com/doc/refman/5.6/en/sql-mode.html#sqlmode_strict_all_tables
# If the user has provided another value for sql_mode, don't replace it.
- unless variables.has_key?('sql_mode')
+ unless variables.has_key?('sql_mode') || defaults.include?(@config[:strict])
variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
end
@@ -946,7 +988,7 @@ module ActiveRecord
# Gather up all of the SET variables...
variable_assignments = variables.map do |k, v|
- if v == ':default' || v == :default
+ if defaults.include?(v)
"@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
elsif !v.nil?
"@@SESSION.#{k} = #{quote(v)}"
@@ -971,6 +1013,36 @@ module ActiveRecord
TableDefinition.new(native_database_types, name, temporary, options, as)
end
+ def binary_to_sql(limit) # :nodoc:
+ case limit
+ when 0..0xfff; "varbinary(#{limit})"
+ when nil; "blob"
+ when 0x1000..0xffffffff; "blob(#{limit})"
+ else raise(ActiveRecordError, "No binary type has character length #{limit}")
+ end
+ end
+
+ def integer_to_sql(limit) # :nodoc:
+ case limit
+ when 1; 'tinyint'
+ when 2; 'smallint'
+ when 3; 'mediumint'
+ when nil, 4, 11; 'int(11)' # compatibility with MySQL default
+ when 5..8; 'bigint'
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}")
+ end
+ end
+
+ def text_to_sql(limit) # :nodoc:
+ case limit
+ when 0..0xff; 'tinytext'
+ when nil, 0x100..0xffff; 'text'
+ when 0x10000..0xffffff; 'mediumtext'
+ when 0x1000000..0xffffffff; 'longtext'
+ else raise(ActiveRecordError, "No text type has character length #{limit}")
+ end
+ end
+
class MysqlString < Type::String # :nodoc:
def serialize(value)
case value
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index a67127bd71..4b95b0681d 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -12,7 +12,7 @@ module ActiveRecord
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
end
- attr_reader :name, :null, :sql_type_metadata, :default, :default_function
+ attr_reader :name, :null, :sql_type_metadata, :default, :default_function, :collation
delegate :precision, :scale, :limit, :type, :sql_type, to: :sql_type_metadata, allow_nil: true
@@ -22,12 +22,14 @@ module ActiveRecord
# +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
# +sql_type_metadata+ is various information about the type of the column
# +null+ determines if this column allows +NULL+ values.
- def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil)
+ def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil)
@name = name
@sql_type_metadata = sql_type_metadata
@null = null
@default = default
@default_function = default_function
+ @collation = collation
+ @table_name = nil
end
def has_default?
@@ -59,7 +61,7 @@ module ActiveRecord
protected
def attributes_for_hash
- [self.class, name, default, sql_type_metadata, null, default_function]
+ [self.class, name, default, sql_type_metadata, null, default_function, collation]
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 21631be25c..b7db57c9fe 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -1,6 +1,6 @@
require 'active_record/connection_adapters/abstract_mysql_adapter'
-gem 'mysql2', '~> 0.3.13'
+gem 'mysql2', '~> 0.3.18'
require 'mysql2'
module ActiveRecord
@@ -254,7 +254,7 @@ module ActiveRecord
end
def full_version
- @full_version ||= @connection.info[:version]
+ @full_version ||= @connection.server_info[:version]
end
def set_field_encoding field_name
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 45b935f1d6..2ae462d773 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -5,8 +5,10 @@ require 'active_support/core_ext/hash/keys'
gem 'mysql', '~> 2.9'
require 'mysql'
-class Mysql
+class Mysql # :nodoc: all
class Time
+ # Used for casting DateTime fields to a MySQL friendly Time.
+ # This was documented in 48498da0dfed5239ea1eafb243ce47d7e3ce9e8e
def to_date
Date.new(year, month, day)
end
@@ -69,34 +71,10 @@ module ActiveRecord
ADAPTER_NAME = 'MySQL'.freeze
class StatementPool < ConnectionAdapters::StatementPool
- def initialize(connection, max = 1000)
- super
- @cache = Hash.new { |h,pid| h[pid] = {} }
- end
-
- def each(&block); cache.each(&block); end
- def key?(key); cache.key?(key); end
- def [](key); cache[key]; end
- def length; cache.length; end
- def delete(key); cache.delete(key); end
-
- def []=(sql, key)
- while @max <= cache.size
- cache.shift.last[:stmt].close
- end
- cache[sql] = key
- end
-
- def clear
- cache.each_value do |hash|
- hash[:stmt].close
- end
- cache.clear
- end
-
private
- def cache
- @cache[Process.pid]
+
+ def dealloc(stmt)
+ stmt[:stmt].close
end
end
@@ -414,8 +392,11 @@ module ActiveRecord
# place when an error occurs. To support older MySQL versions, we
# need to close the statement and delete the statement from the
# cache.
- stmt.close
- @statements.delete sql
+ if binds.empty?
+ stmt.close
+ else
+ @statements.delete sql
+ end
raise e
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
index be13ead120..bfa03fa136 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
@@ -8,7 +8,8 @@ module ActiveRecord
def serial?
return unless default_function
- %r{\Anextval\('(?<table_name>.+)_#{name}_seq'::regclass\)\z} === default_function
+ table_name = @table_name || '(?<table_name>.+)'
+ %r{\Anextval\('"?#{table_name}_#{name}_seq"?'::regclass\)\z} === default_function
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index 92349e2f9b..68752cdd80 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -12,6 +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/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 3de794f797..25961a9869 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
@@ -45,6 +45,11 @@ module ActiveRecord
delimiter == other.delimiter
end
+ def type_cast_for_schema(value)
+ return super unless value.is_a?(::Array)
+ "[" + value.map { |v| subtype.type_cast_for_schema(v) }.join(", ") + "]"
+ end
+
private
def type_cast_array(value, method)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb
index afc9383f91..87391b5dc7 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb
@@ -9,7 +9,7 @@ module ActiveRecord
def changed_in_place?(raw_old_value, new_value)
# Postgres does not preserve insignificant whitespaces when
- # roundtripping jsonb columns. This causes some false positives for
+ # round-tripping jsonb columns. This causes some false positives for
# the comparison here. Therefore, we need to parse and re-dump the
# raw value here to ensure the insignificant whitespaces are
# consistent with our encoder's output.
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/rails_5_1_point.rb
new file mode 100644
index 0000000000..7427a25ad5
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb
@@ -0,0 +1,50 @@
+module ActiveRecord
+ Point = Struct.new(:x, :y)
+
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ class Rails51Point < Type::Value # :nodoc:
+ include Type::Helpers::Mutable
+
+ def type
+ :point
+ end
+
+ def cast(value)
+ case value
+ when ::String
+ if value[0] == '(' && value[-1] == ')'
+ value = value[1...-1]
+ end
+ x, y = value.split(",")
+ build_point(x, y)
+ when ::Array
+ build_point(*value)
+ else
+ value
+ end
+ end
+
+ def serialize(value)
+ if value.is_a?(ActiveRecord::Point)
+ "(#{number_for_point(value.x)},#{number_for_point(value.y)})"
+ else
+ super
+ end
+ end
+
+ private
+
+ 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
+ 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 b7755c4593..f175730551 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -40,8 +40,7 @@ module ActiveRecord
PGconn.quote_ident(name.to_s)
end
- # Quote date/time values for use in SQL input. Includes microseconds
- # if the value is a Time responding to usec.
+ # Quote date/time values for use in SQL input.
def quoted_date(value) #:nodoc:
if value.year <= 0
bce_year = format("%04d", -value.year + 1)
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 022dbdfa27..6399bddbee 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
@@ -103,6 +103,30 @@ module ActiveRecord
args.each { |name| column(name, :point, options) }
end
+ def line(*args, **options)
+ args.each { |name| column(name, :line, options) }
+ end
+
+ def lseg(*args, **options)
+ args.each { |name| column(name, :lseg, options) }
+ end
+
+ def box(*args, **options)
+ args.each { |name| column(name, :box, options) }
+ end
+
+ def path(*args, **options)
+ args.each { |name| column(name, :path, options) }
+ end
+
+ def polygon(*args, **options)
+ args.each { |name| column(name, :polygon, options) }
+ end
+
+ def circle(*args, **options)
+ args.each { |name| column(name, :circle, options) }
+ end
+
def serial(*args, **options)
args.each { |name| column(name, :serial, options) }
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 168180cfd3..d114cad16b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -8,6 +8,13 @@ module ActiveRecord
o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.array)
super
end
+
+ def add_column_options!(sql, options)
+ if options[:collation]
+ sql << " COLLATE \"#{options[:collation]}\""
+ end
+ super
+ end
end
module SchemaStatements
@@ -63,11 +70,7 @@ module ActiveRecord
# Returns the list of all tables in the schema search path or a specified schema.
def tables(name = nil)
- query(<<-SQL, 'SCHEMA').map { |row| row[0] }
- SELECT tablename
- FROM pg_tables
- WHERE schemaname = ANY (current_schemas(false))
- SQL
+ select_values("SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))", 'SCHEMA')
end
# Returns true if table exists.
@@ -77,7 +80,7 @@ module ActiveRecord
name = Utils.extract_schema_qualified_name(name.to_s)
return false unless name.identifier
- exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
+ select_value(<<-SQL, 'SCHEMA').to_i > 0
SELECT COUNT(*)
FROM pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
@@ -93,15 +96,12 @@ module ActiveRecord
# Returns true if schema exists.
def schema_exists?(name)
- exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
- SELECT COUNT(*)
- FROM pg_namespace
- WHERE nspname = '#{name}'
- SQL
+ select_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = '#{name}'", 'SCHEMA').to_i > 0
end
+ # Verifies existence of an index with a given name.
def index_name_exists?(table_name, index_name, default)
- exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
+ select_value(<<-SQL, 'SCHEMA').to_i > 0
SELECT COUNT(*)
FROM pg_class t
INNER JOIN pg_index d ON t.oid = d.indrelid
@@ -158,55 +158,48 @@ module ActiveRecord
# Returns the list of all column definitions for a table.
def columns(table_name)
# Limit, precision, and scale are all handled by the superclass.
- column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod|
+ column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod, collation|
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, default_function)
+ new_column(column_name, default_value, type_metadata, !notnull, default_function, collation)
end
end
- def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil) # :nodoc:
- PostgreSQLColumn.new(name, default, sql_type_metadata, null, default_function)
+ def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) # :nodoc:
+ PostgreSQLColumn.new(name, default, sql_type_metadata, null, default_function, collation)
end
# Returns the current database name.
def current_database
- query('select current_database()', 'SCHEMA')[0][0]
+ select_value('select current_database()', 'SCHEMA')
end
# Returns the current schema name.
def current_schema
- query('SELECT current_schema', 'SCHEMA')[0][0]
+ select_value('SELECT current_schema', 'SCHEMA')
end
# Returns the current database encoding format.
def encoding
- query(<<-end_sql, 'SCHEMA')[0][0]
- SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
- WHERE pg_database.datname LIKE '#{current_database}'
- end_sql
+ select_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA')
end
# Returns the current database collation.
def collation
- query(<<-end_sql, 'SCHEMA')[0][0]
- SELECT pg_database.datcollate FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
- end_sql
+ select_value("SELECT datcollate FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA')
end
# Returns the current database ctype.
def ctype
- query(<<-end_sql, 'SCHEMA')[0][0]
- SELECT pg_database.datctype FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
- end_sql
+ select_value("SELECT datctype FROM pg_database WHERE datname LIKE '#{current_database}'", 'SCHEMA')
end
# Returns an array of schema names.
def schema_names
- query(<<-SQL, 'SCHEMA').flatten
+ select_values(<<-SQL, 'SCHEMA')
SELECT nspname
FROM pg_namespace
WHERE nspname !~ '^pg_.*'
@@ -239,12 +232,12 @@ module ActiveRecord
# Returns the active schema search path.
def schema_search_path
- @schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0]
+ @schema_search_path ||= select_value('SHOW search_path', 'SCHEMA')
end
# Returns the current client message level.
def client_min_messages
- query('SHOW client_min_messages', 'SCHEMA')[0][0]
+ select_value('SHOW client_min_messages', 'SCHEMA')
end
# Set the client message level.
@@ -262,10 +255,7 @@ module ActiveRecord
end
def serial_sequence(table, column)
- result = exec_query(<<-eosql, 'SCHEMA')
- SELECT pg_get_serial_sequence('#{table}', '#{column}')
- eosql
- result.rows.first.first
+ select_value("SELECT pg_get_serial_sequence('#{table}', '#{column}')", 'SCHEMA')
end
# Sets the sequence of a table's primary key to the specified value.
@@ -276,9 +266,7 @@ module ActiveRecord
if sequence
quoted_sequence = quote_table_name(sequence)
- select_value <<-end_sql, 'SCHEMA'
- SELECT setval('#{quoted_sequence}', #{value})
- end_sql
+ select_value("SELECT setval('#{quoted_sequence}', #{value})", 'SCHEMA')
else
@logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
end
@@ -301,7 +289,7 @@ module ActiveRecord
if pk && sequence
quoted_sequence = quote_table_name(sequence)
- select_value <<-end_sql, 'SCHEMA'
+ select_value(<<-end_sql, 'SCHEMA')
SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
end_sql
end
@@ -363,7 +351,7 @@ module ActiveRecord
# Returns just a table's primary key
def primary_key(table)
- pks = exec_query(<<-end_sql, 'SCHEMA').rows
+ pks = query(<<-end_sql, 'SCHEMA')
SELECT attr.attname
FROM pg_attribute attr
INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey)
@@ -395,20 +383,20 @@ module ActiveRecord
rename_table_indexes(table_name, new_name)
end
- # Adds a new column to the named table.
- # See TableDefinition#column for details of the options you can use.
- def add_column(table_name, column_name, type, options = {})
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
clear_cache!
super
end
- # Changes the column of a table.
- def change_column(table_name, column_name, type, options = {})
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
clear_cache!
quoted_table_name = quote_table_name(table_name)
quoted_column_name = quote_column_name(column_name)
sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale], options[:array])
sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}"
+ if options[:collation]
+ sql << " COLLATE \"#{options[:collation]}\""
+ end
if options[:using]
sql << " USING #{options[:using]}"
elsif options[:cast_as]
@@ -422,11 +410,12 @@ module ActiveRecord
end
# Changes the default value of a table column.
- def change_column_default(table_name, column_name, default) # :nodoc:
+ def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
clear_cache!
column = column_for(table_name, column_name)
return unless column
+ default = extract_new_default_value(default_or_changes)
alter_column_query = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} %s"
if default.nil?
# <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
@@ -437,7 +426,7 @@ module ActiveRecord
end
end
- def change_column_null(table_name, column_name, null, default = nil)
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
clear_cache!
unless null || default.nil?
column = column_for(table_name, column_name)
@@ -447,7 +436,7 @@ module ActiveRecord
end
# Renames a column in a table.
- def rename_column(table_name, column_name, new_column_name)
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
clear_cache!
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
rename_column_indexes(table_name, column_name, new_column_name)
@@ -462,6 +451,8 @@ module ActiveRecord
execute "DROP INDEX #{quote_table_name(index_name)}"
end
+ # Renames an index of a table. Raises error if length of new
+ # index name is greater than allowed limit.
def rename_index(table_name, old_name, new_name)
validate_index_length!(table_name, new_name)
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 58715978f7..b2c49989a4 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
@@ -12,7 +12,7 @@ module ActiveRecord
end
def sql_type
- super.gsub(/\[\]$/, "")
+ super.gsub(/\[\]$/, "".freeze)
end
def ==(other)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 332ac9d88c..2c43c46a3d 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -14,8 +14,6 @@ require "active_record/connection_adapters/postgresql/type_metadata"
require "active_record/connection_adapters/postgresql/utils"
require "active_record/connection_adapters/statement_pool"
-require 'arel/visitors/bind_visitor'
-
require 'ipaddr'
module ActiveRecord
@@ -68,11 +66,11 @@ module ActiveRecord
# defaults to true.
#
# Any further options are used as connection parameters to libpq. See
- # http://www.postgresql.org/docs/9.1/static/libpq-connect.html for the
+ # http://www.postgresql.org/docs/current/static/libpq-connect.html for the
# list of parameters.
#
# In addition, default connection parameters of libpq can be set per environment variables.
- # See http://www.postgresql.org/docs/9.1/static/libpq-envars.html .
+ # See http://www.postgresql.org/docs/current/static/libpq-envars.html .
class PostgreSQLAdapter < AbstractAdapter
ADAPTER_NAME = 'PostgreSQL'.freeze
@@ -211,44 +209,18 @@ module ActiveRecord
def initialize(connection, max)
super
@counter = 0
- @cache = Hash.new { |h,pid| h[pid] = {} }
end
- def each(&block); cache.each(&block); end
- def key?(key); cache.key?(key); end
- def [](key); cache[key]; end
- def length; cache.length; end
-
def next_key
"a#{@counter + 1}"
end
def []=(sql, key)
- while @max <= cache.size
- dealloc(cache.shift.last)
- end
- @counter += 1
- cache[sql] = key
- end
-
- def clear
- cache.each_value do |stmt_key|
- dealloc stmt_key
- end
- cache.clear
- end
-
- def delete(sql_key)
- dealloc cache[sql_key]
- cache.delete sql_key
+ super.tap { @counter += 1 }
end
private
- def cache
- @cache[Process.pid]
- end
-
def dealloc(key)
@connection.query "DEALLOCATE #{key}" if connection_active?
end
@@ -452,7 +424,7 @@ module ActiveRecord
@connection.server_version
end
- # See http://www.postgresql.org/docs/9.1/static/errcodes-appendix.html
+ # See http://www.postgresql.org/docs/current/static/errcodes-appendix.html
FOREIGN_KEY_VIOLATION = "23503"
UNIQUE_VIOLATION = "23505"
@@ -726,7 +698,7 @@ module ActiveRecord
end
# SET statements from :variables config hash
- # http://www.postgresql.org/docs/8.3/static/sql-set.html
+ # http://www.postgresql.org/docs/current/static/sql-set.html
variables = @config[:variables] || {}
variables.map do |k, v|
if v == ':default' || v == :default
@@ -770,9 +742,11 @@ module ActiveRecord
# - 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:
- exec_query(<<-end_sql, 'SCHEMA').rows
+ 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
+ pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
+ (SELECT c.collname FROM pg_collation c, pg_type t
+ WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation)
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
@@ -864,6 +838,8 @@ module ActiveRecord
ActiveRecord::Type.register(:jsonb, OID::Jsonb, adapter: :postgresql)
ActiveRecord::Type.register(:money, OID::Money, adapter: :postgresql)
ActiveRecord::Type.register(:point, OID::Point, adapter: :postgresql)
+ ActiveRecord::Type.register(:legacy_point, OID::Point, adapter: :postgresql)
+ ActiveRecord::Type.register(:rails_5_1_point, OID::Rails51Point, 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 37ff4e4613..981d5d7a3c 100644
--- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
@@ -13,6 +13,14 @@ module ActiveRecord
@tables = {}
end
+ def initialize_dup(other)
+ super
+ @columns = @columns.dup
+ @columns_hash = @columns_hash.dup
+ @primary_keys = @primary_keys.dup
+ @tables = @tables.dup
+ end
+
def primary_keys(table_name)
@primary_keys[table_name] ||= table_exists?(table_name) ? connection.primary_key(table_name) : nil
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb
new file mode 100644
index 0000000000..fe1dcbd710
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb
@@ -0,0 +1,15 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module SQLite3
+ class SchemaCreation < AbstractAdapter::SchemaCreation
+ private
+ def add_column_options!(sql, options)
+ if options[:collation]
+ sql << " COLLATE \"#{options[:collation]}\""
+ end
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 7e184dd510..358039723f 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -1,6 +1,6 @@
require 'active_record/connection_adapters/abstract_adapter'
require 'active_record/connection_adapters/statement_pool'
-require 'arel/visitors/bind_visitor'
+require 'active_record/connection_adapters/sqlite3/schema_creation'
gem 'sqlite3', '~> 1.3.6'
require 'sqlite3'
@@ -65,53 +65,18 @@ module ActiveRecord
boolean: { name: "boolean" }
}
- class Version
- include Comparable
-
- def initialize(version_string)
- @version = version_string.split('.').map(&:to_i)
- end
-
- def <=>(version_string)
- @version <=> version_string.split('.').map(&:to_i)
- end
- end
-
class StatementPool < ConnectionAdapters::StatementPool
- def initialize(connection, max)
- super
- @cache = Hash.new { |h,pid| h[pid] = {} }
- end
-
- def each(&block); cache.each(&block); end
- def key?(key); cache.key?(key); end
- def [](key); cache[key]; end
- def length; cache.length; end
-
- def []=(sql, key)
- while @max <= cache.size
- dealloc(cache.shift.last[:stmt])
- end
- cache[sql] = key
- end
-
- def clear
- cache.each_value do |hash|
- dealloc hash[:stmt]
- end
- cache.clear
- end
-
private
- def cache
- @cache[$$]
- end
def dealloc(stmt)
- stmt.close unless stmt.closed?
+ stmt[:stmt].close unless stmt[:stmt].closed?
end
end
+ def schema_creation # :nodoc:
+ SQLite3::SchemaCreation.new self
+ end
+
def initialize(connection, logger, connection_options, config)
super(connection, logger)
@@ -372,9 +337,10 @@ module ActiveRecord
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)
+ new_column(field['name'], field['dflt_value'], type_metadata, field['notnull'].to_i == 0, nil, collation)
end
end
@@ -444,13 +410,15 @@ module ActiveRecord
end
end
- def change_column_default(table_name, column_name, default) #:nodoc:
+ def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
+ default = extract_new_default_value(default_or_changes)
+
alter_table(table_name) do |definition|
definition[column_name].default = default
end
end
- def change_column_null(table_name, column_name, null, default = nil)
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
unless null || default.nil?
exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
end
@@ -469,6 +437,7 @@ module ActiveRecord
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.collation = options[:collation] if options.include?(:collation)
end
end
end
@@ -482,9 +451,9 @@ module ActiveRecord
protected
def table_structure(table_name)
- structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA').to_hash
+ structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA')
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
- structure
+ table_structure_with_collation(table_name, structure)
end
def alter_table(table_name, options = {}) #:nodoc:
@@ -519,7 +488,7 @@ module ActiveRecord
@definition.column(column_name, column.type,
:limit => column.limit, :default => column.default,
:precision => column.precision, :scale => column.scale,
- :null => column.null)
+ :null => column.null, collation: column.collation)
end
yield @definition if block_given?
end
@@ -581,6 +550,46 @@ module ActiveRecord
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 }' \;"
+
+ # Result will have following sample string
+ # CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+ # "password_digest" varchar COLLATE "NOCASE");
+ result = exec_query(sql, 'SCHEMA').first
+
+ if result
+ # Splitting with left parantheses and picking up last will return all
+ # columns separated with comma(,).
+ columns_string = result["sql"].split('(').last
+
+ columns_string.split(',').each do |column_string|
+ # This regex will match the column name and collation type and will save
+ # the value in $1 and $2 respectively.
+ collation_hash[$1] = $2 if (COLLATE_REGEX =~ column_string)
+ end
+
+ basic_structure.map! do |column|
+ column_name = column['name']
+
+ if collation_hash.has_key? column_name
+ column['collation'] = collation_hash[column_name]
+ end
+
+ column
+ end
+ else
+ basic_structure.to_hash
+ end
+ 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 c6b1bc8b5b..82e9ef3d3d 100644
--- a/activerecord/lib/active_record/connection_adapters/statement_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/statement_pool.rb
@@ -4,35 +4,53 @@ module ActiveRecord
include Enumerable
def initialize(connection, max = 1000)
+ @cache = Hash.new { |h,pid| h[pid] = {} }
@connection = connection
@max = max
end
- def each
- raise NotImplementedError
+ def each(&block)
+ cache.each(&block)
end
def key?(key)
- raise NotImplementedError
+ cache.key?(key)
end
def [](key)
- raise NotImplementedError
+ cache[key]
end
def length
- raise NotImplementedError
+ cache.length
end
- def []=(sql, key)
- raise NotImplementedError
+ def []=(sql, stmt)
+ while @max <= cache.size
+ dealloc(cache.shift.last)
+ end
+ cache[sql] = stmt
end
def clear
- raise NotImplementedError
+ cache.each_value do |stmt|
+ dealloc stmt
+ end
+ cache.clear
end
def delete(key)
+ dealloc cache[key]
+ cache.delete(key)
+ end
+
+ private
+
+ def cache
+ @cache[Process.pid]
+ end
+
+ def dealloc(stmt)
raise NotImplementedError
end
end
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index 24f5849e45..d6b661ff76 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -88,7 +88,7 @@ module ActiveRecord
end
def connection_id
- ActiveRecord::RuntimeRegistry.connection_id
+ ActiveRecord::RuntimeRegistry.connection_id ||= Thread.current.object_id
end
def connection_id=(connection_id)
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 1ad910c4bc..ffce2173ec 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -88,9 +88,10 @@ module ActiveRecord
##
# :singleton-method:
# Specifies which database schemas to dump when calling db:structure:dump.
- # If :schema_search_path (the default), it will dumps any schemas listed in schema_search_path.
- # Use :all to always dumps all schemas regardless of the schema_search_path.
- # A string of comma separated schemas can also be used to pass a custom list of schemas.
+ # If the value is :schema_search_path (the default), any schemas listed in
+ # schema_search_path are dumped. Use :all to dump all schemas regardless
+ # of schema_search_path, or a string of comma separated schemas for a
+ # custom list.
mattr_accessor :dump_schemas, instance_writer: false
self.dump_schemas = :schema_search_path
@@ -161,11 +162,13 @@ module ActiveRecord
}
record = statement.execute([id], self, connection).first
unless record
- raise RecordNotFound, "Couldn't find #{name} with '#{primary_key}'=#{id}"
+ raise RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}",
+ name, primary_key, id)
end
record
rescue RangeError
- raise RecordNotFound, "Couldn't find #{name} with an out of range value for '#{primary_key}'"
+ raise RecordNotFound.new("Couldn't find #{name} with an out of range value for '#{primary_key}'",
+ name, primary_key)
end
def find_by(*args) # :nodoc:
@@ -198,7 +201,7 @@ module ActiveRecord
end
def find_by!(*args) # :nodoc:
- find_by(*args) or raise RecordNotFound.new("Couldn't find #{name}")
+ find_by(*args) or raise RecordNotFound.new("Couldn't find #{name}", name)
end
def initialize_generated_modules # :nodoc:
@@ -302,7 +305,7 @@ module ActiveRecord
assign_attributes(attributes) if attributes
yield self if block_given?
- run_callbacks :initialize
+ _run_initialize_callbacks
end
# Initialize an empty model object from +coder+. +coder+ should be
@@ -329,8 +332,8 @@ module ActiveRecord
self.class.define_attribute_methods
- run_callbacks :find
- run_callbacks :initialize
+ _run_find_callbacks
+ _run_initialize_callbacks
self
end
@@ -366,7 +369,7 @@ module ActiveRecord
@attributes = @attributes.dup
@attributes.reset(self.class.primary_key)
- run_callbacks(:initialize)
+ _run_initialize_callbacks
@new_record = true
@destroyed = false
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index ea88983917..91a13cb0cd 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -32,7 +32,7 @@ module ActiveRecord
# Conversation.active
# Conversation.archived
#
- # Of course, you can also query them directly if the scopes doesn't fit your
+ # Of course, you can also query them directly if the scopes don't fit your
# needs:
#
# Conversation.where(status: [:active, :archived])
@@ -75,6 +75,24 @@ module ActiveRecord
#
# Conversation.where("status <> ?", Conversation.statuses[:archived])
#
+ # You can use the +:_prefix+ or +:_suffix+ options when you need to define
+ # multiple enums with same values. If the passed value is +true+, the methods
+ # are prefixed/suffixed with the name of the enum. It is also possible to
+ # supply a custom value:
+ #
+ # class Conversation < ActiveRecord::Base
+ # enum status: [:active, :archived], _suffix: true
+ # enum comments_status: [:active, :inactive], _prefix: :comments
+ # end
+ #
+ # With the above example, the bang and predicate methods along with the
+ # associated scopes are now prefixed and/or suffixed accordingly:
+ #
+ # conversation.active_status!
+ # conversation.archived_status? # => false
+ #
+ # conversation.comments_inactive!
+ # conversation.comments_active? # => false
module Enum
def self.extended(base) # :nodoc:
@@ -121,6 +139,8 @@ module ActiveRecord
def enum(definitions)
klass = self
+ enum_prefix = definitions.delete(:_prefix)
+ enum_suffix = definitions.delete(:_suffix)
definitions.each do |name, values|
# statuses = { }
enum_values = ActiveSupport::HashWithIndifferentAccess.new
@@ -138,19 +158,31 @@ module ActiveRecord
_enum_methods_module.module_eval do
pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
pairs.each do |value, i|
+ if enum_prefix == true
+ prefix = "#{name}_"
+ elsif enum_prefix
+ prefix = "#{enum_prefix}_"
+ end
+ if enum_suffix == true
+ suffix = "_#{name}"
+ elsif enum_suffix
+ suffix = "_#{enum_suffix}"
+ end
+
+ value_method_name = "#{prefix}#{value}#{suffix}"
enum_values[value] = i
# def active?() status == 0 end
- klass.send(:detect_enum_conflict!, name, "#{value}?")
- define_method("#{value}?") { self[name] == value.to_s }
+ klass.send(:detect_enum_conflict!, name, "#{value_method_name}?")
+ define_method("#{value_method_name}?") { self[name] == value.to_s }
# def active!() update! status: :active end
- klass.send(:detect_enum_conflict!, name, "#{value}!")
- define_method("#{value}!") { update! name => value }
+ klass.send(:detect_enum_conflict!, name, "#{value_method_name}!")
+ define_method("#{value_method_name}!") { update! name => value }
# scope :active, -> { where status: 0 }
- klass.send(:detect_enum_conflict!, name, value, true)
- klass.scope value, -> { klass.where name => value }
+ klass.send(:detect_enum_conflict!, name, value_method_name, true)
+ klass.scope value_method_name, -> { klass.where name => value }
end
end
defined_enums[name.to_s] = enum_values
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 98aee77557..d589620f8a 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -47,6 +47,15 @@ module ActiveRecord
# Raised when Active Record cannot find record by given id or set of ids.
class RecordNotFound < ActiveRecordError
+ attr_reader :model, :primary_key, :id
+
+ def initialize(message = nil, model = nil, primary_key = nil, id = nil)
+ @primary_key = primary_key
+ @model = model
+ @id = id
+
+ super(message)
+ end
end
# Raised by ActiveRecord::Base.save! and ActiveRecord::Base.create! methods when record cannot be
@@ -71,9 +80,9 @@ module ActiveRecord
class RecordNotDestroyed < ActiveRecordError
attr_reader :record
- def initialize(record)
+ def initialize(message, record = nil)
@record = record
- super()
+ super(message)
end
end
diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb
index 6a49936644..9adabd7819 100644
--- a/activerecord/lib/active_record/explain_subscriber.rb
+++ b/activerecord/lib/active_record/explain_subscriber.rb
@@ -19,7 +19,7 @@ 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)
- EXPLAINED_SQLS = /\A\s*(select|update|delete|insert)\b/i
+ 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
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 2c1771dd6c..f1dc56df63 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -110,7 +110,7 @@ module ActiveRecord
# <% 1.upto(1000) do |i| %>
# fix_<%= i %>:
# id: <%= i %>
- # name: guy_<%= 1 %>
+ # name: guy_<%= i %>
# <% end %>
#
# This will create 1000 very simple fixtures.
@@ -615,7 +615,6 @@ module ActiveRecord
# a list of rows to insert to that table.
def table_rows
now = config.default_timezone == :utc ? Time.now.utc : Time.now
- now = now.to_s(:db)
# allow a standard key to be used for doing defaults in YAML
fixtures.delete('DEFAULTS')
@@ -644,6 +643,13 @@ module ActiveRecord
row[primary_key_name] = ActiveRecord::FixtureSet.identify(label, primary_key_type)
end
+ # Resolve enums
+ model_class.defined_enums.each do |name, values|
+ if row.include?(name)
+ row[name] = values.fetch(row[name], row[name])
+ end
+ end
+
# If STI is used, find the correct subclass for association reflection
reflection_class =
if row.include?(inheritance_column_name)
@@ -664,7 +670,7 @@ module ActiveRecord
row[association.foreign_type] = $1
end
- fk_type = association.active_record.type_for_attribute(fk_name).type
+ fk_type = reflection_class.type_for_attribute(fk_name).type
row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type)
end
when :has_many
@@ -821,12 +827,12 @@ module ActiveRecord
module TestFixtures
extend ActiveSupport::Concern
- def before_setup
+ def before_setup # :nodoc:
setup_fixtures
super
end
- def after_teardown
+ def after_teardown # :nodoc:
super
teardown_fixtures
end
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index 24098f72dc..e613d157aa 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -198,14 +198,16 @@ module ActiveRecord
def subclass_from_attributes(attrs)
subclass_name = attrs.with_indifferent_access[inheritance_column]
- if subclass_name.present? && subclass_name != self.name
- subclass = subclass_name.safe_constantize
+ if subclass_name.present?
+ subclass = find_sti_class(subclass_name)
- unless descendants.include?(subclass)
- raise ActiveRecord::SubclassNotFound.new("Invalid single-table inheritance type: #{subclass_name} is not a subclass of #{name}")
- end
+ if subclass.name != self.name
+ unless descendants.include?(subclass)
+ raise ActiveRecord::SubclassNotFound.new("Invalid single-table inheritance type: #{subclass.name} is not a subclass of #{name}")
+ end
- subclass
+ subclass
+ end
end
end
end
diff --git a/activerecord/lib/active_record/locale/en.yml b/activerecord/lib/active_record/locale/en.yml
index 8a3c27e6da..0b35027b2b 100644
--- a/activerecord/lib/active_record/locale/en.yml
+++ b/activerecord/lib/active_record/locale/en.yml
@@ -16,8 +16,8 @@ en:
messages:
record_invalid: "Validation failed: %{errors}"
restrict_dependent_destroy:
- one: "Cannot delete record because a dependent %{record} exists"
- many: "Cannot delete record because dependent %{record} exist"
+ has_one: "Cannot delete record because a dependent %{record} exists"
+ has_many: "Cannot delete record because dependent %{record} exist"
# Append your own errors here or at the model/attributes scope.
# You can define own errors for models or model attributes.
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index af816a278e..4d597a0ab1 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -47,18 +47,21 @@ module ActiveRecord
binds = " " + payload[:binds].map { |attr| render_bind(attr) }.inspect
end
- if odd?
- name = color(name, CYAN, true)
- sql = color(sql, nil, true)
- else
- name = color(name, MAGENTA, true)
- end
+ name = color(name, nil, true)
+ sql = color(sql, sql_color(sql), true)
debug " #{name} #{sql}#{binds}"
end
- def odd?
- @odd = !@odd
+ def sql_color(sql)
+ case sql
+ when /\s*\Ainsert/i then GREEN
+ when /\s*\Aselect/i then BLUE
+ when /\s*\Aupdate/i then YELLOW
+ when /\s*\Adelete/i then RED
+ when /transaction\s*\Z/i then CYAN
+ else MAGENTA
+ end
end
def logger
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index a83b90a95f..b7b508d853 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -138,10 +138,13 @@ module ActiveRecord
# <tt>:name</tt>, <tt>:unique</tt> (e.g.
# <tt>{ name: 'users_name_index', unique: true }</tt>) and <tt>:order</tt>
# (e.g. <tt>{ order: { name: :desc } }</tt>).
- # * <tt>remove_index(table_name, column: column_name)</tt>: Removes the index
- # specified by +column_name+.
+ # * <tt>remove_index(table_name, column: column_names)</tt>: Removes the index
+ # specified by +column_names+.
# * <tt>remove_index(table_name, name: index_name)</tt>: Removes the index
# specified by +index_name+.
+ # * <tt>add_reference(:table_name, :reference_name)</tt>: Adds a new column
+ # +reference_name_id+ by default a integer. See
+ # ActiveRecord::ConnectionAdapters::SchemaStatements#add_reference for details.
#
# == Irreversible transformations
#
@@ -275,21 +278,6 @@ module ActiveRecord
# The phrase "Updating salaries..." would then be printed, along with the
# benchmark for the block when the block completes.
#
- # == About the schema_migrations table
- #
- # Rails versions 2.0 and prior used to create a table called
- # <tt>schema_info</tt> when using migrations. This table contained the
- # version of the schema as of the last applied migration.
- #
- # Starting with Rails 2.1, the <tt>schema_info</tt> table is
- # (automatically) replaced by the <tt>schema_migrations</tt> table, which
- # contains the version numbers of all the migrations applied.
- #
- # As a result, it is now possible to add migration files that are numbered
- # lower than the current schema version: when migrating up, those
- # never-applied "interleaved" migrations will be automatically applied, and
- # when migrating down, never-applied "interleaved" migrations will be skipped.
- #
# == Timestamped Migrations
#
# By default, Rails generates migrations that look like:
@@ -307,9 +295,8 @@ module ActiveRecord
#
# == Reversible Migrations
#
- # Starting with Rails 3.1, you will be able to define reversible migrations.
# Reversible migrations are migrations that know how to go +down+ for you.
- # You simply supply the +up+ logic, and the Migration system will figure out
+ # You simply supply the +up+ logic, and the Migration system figures out
# how to execute the down commands for you.
#
# To define a reversible migration, define the +change+ method in your
@@ -653,7 +640,8 @@ module ActiveRecord
unless @connection.respond_to? :revert
unless arguments.empty? || [:execute, :enable_extension, :disable_extension].include?(method)
arguments[0] = proper_table_name(arguments.first, table_name_options)
- if [:rename_table, :add_foreign_key].include?(method)
+ if [:rename_table, :add_foreign_key].include?(method) ||
+ (method == :remove_foreign_key && !arguments.second.is_a?(Hash))
arguments[1] = proper_table_name(arguments.second, table_name_options)
end
end
@@ -728,7 +716,9 @@ module ActiveRecord
end
end
- def table_name_options(config = ActiveRecord::Base)
+ # Builds a hash for use in ActiveRecord::Migration#proper_table_name using
+ # the Active Record object's table_name prefix and suffix
+ def table_name_options(config = ActiveRecord::Base) #:nodoc:
{
table_name_prefix: config.table_name_prefix,
table_name_suffix: config.table_name_suffix
diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb
index 36256415df..dcc2362397 100644
--- a/activerecord/lib/active_record/migration/command_recorder.rb
+++ b/activerecord/lib/active_record/migration/command_recorder.rb
@@ -72,7 +72,7 @@ module ActiveRecord
[:create_table, :create_join_table, :rename_table, :add_column, :remove_column,
:rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps,
- :change_column_default, :add_reference, :remove_reference, :transaction,
+ :add_reference, :remove_reference, :transaction,
:drop_join_table, :drop_table, :execute_block, :enable_extension,
:change_column, :execute, :remove_columns, :change_column_null,
:add_foreign_key, :remove_foreign_key
@@ -151,19 +151,31 @@ module ActiveRecord
end
def invert_remove_index(args)
- table, options = *args
-
- unless options && options.is_a?(Hash) && options[:column]
- raise ActiveRecord::IrreversibleMigration, "remove_index is only reversible if given a :column option."
+ table, options_or_column = *args
+ if (options = options_or_column).is_a?(Hash)
+ unless options[:column]
+ raise ActiveRecord::IrreversibleMigration, "remove_index is only reversible if given a :column option."
+ end
+ options = options.dup
+ [:add_index, [table, options.delete(:column), options]]
+ elsif (column = options_or_column).present?
+ [:add_index, [table, column]]
end
-
- options = options.dup
- [:add_index, [table, options.delete(:column), options]]
end
alias :invert_add_belongs_to :invert_add_reference
alias :invert_remove_belongs_to :invert_remove_reference
+ def invert_change_column_default(args)
+ table, column, options = *args
+
+ unless options && options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to)
+ raise ActiveRecord::IrreversibleMigration, "change_column_default is only reversible if given a :from and :to option."
+ end
+
+ [:change_column_default, [table, column, from: options[:to], to: options[:from]]]
+ end
+
def invert_change_column_null(args)
args[2] = !args[2]
[:change_column_null, args]
@@ -184,6 +196,16 @@ module ActiveRecord
[:remove_foreign_key, [from_table, options]]
end
+ def invert_remove_foreign_key(args)
+ from_table, to_table, remove_options = args
+ raise ActiveRecord::IrreversibleMigration, "remove_foreign_key is only reversible if given a second table" if to_table.nil? || to_table.is_a?(Hash)
+
+ reversed_args = [from_table, to_table]
+ reversed_args << remove_options if remove_options
+
+ [:add_foreign_key, reversed_args]
+ end
+
# Forwards any missing method call to the \target.
def method_missing(method, *args, &block)
if @delegate.respond_to?(method)
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 3674f672cb..5a6f42ba09 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -310,6 +310,7 @@ module ActiveRecord
def load_schema!
@columns_hash = connection.schema_cache.columns_hash(table_name)
@columns_hash.each do |name, column|
+ warn_if_deprecated_type(column)
define_attribute(
name,
connection.lookup_cast_type_from_column(column),
@@ -356,6 +357,28 @@ 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 #{self.name}:
+
+ attribute :#{column.name}, :legacy_point#{array_arguments}
+
+ If you'd like the new behavior today, you can add this line:
+
+ attribute :#{column.name}, :rails_5_1_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 084ef397a8..c5a1488588 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -147,8 +147,8 @@ module ActiveRecord
# has_many :posts
# accepts_nested_attributes_for :posts, reject_if: :reject_posts
#
- # def reject_posts(attributed)
- # attributed['title'].blank?
+ # def reject_posts(attributes)
+ # attributes['title'].blank?
# end
# end
#
@@ -166,6 +166,11 @@ module ActiveRecord
# member.posts.first.title # => '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!'
# member.posts.second.title # => '[UPDATED] other post'
#
+ # However, the above applies if the parent model is being updated as well.
+ # For example, If you wanted to create a +member+ named _joe_ and wanted to
+ # update the +posts+ at the same time, that would give an
+ # ActiveRecord::RecordNotFound error.
+ #
# By default the associated records are protected from being destroyed. If
# you want to destroy any of the associated records through the attributes
# hash, you have to enable it first using the <tt>:allow_destroy</tt>
@@ -208,7 +213,7 @@ module ActiveRecord
#
# Passing attributes for an associated collection in the form of a hash
# of hashes can be used with hashes generated from HTTP/HTML parameters,
- # where there maybe no natural way to submit an array of hashes.
+ # where there may be no natural way to submit an array of hashes.
#
# === Saving
#
@@ -381,6 +386,9 @@ module ActiveRecord
# 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?)
+ attributes = attributes.to_h
+ end
attributes = attributes.with_indifferent_access
existing_record = send(association_name)
@@ -437,6 +445,9 @@ module ActiveRecord
# ])
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
options = self.nested_attributes_options[association_name]
+ if attributes_collection.respond_to?(:permitted?)
+ attributes_collection = attributes_collection.to_h
+ end
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
@@ -463,6 +474,9 @@ module ActiveRecord
end
attributes_collection.each do |attributes|
+ if attributes.respond_to?(:permitted?)
+ attributes = attributes.to_h
+ end
attributes = attributes.with_indifferent_access
if attributes['id'].blank?
@@ -547,7 +561,9 @@ module ActiveRecord
end
def raise_nested_attributes_record_not_found!(association_name, record_id)
- raise RecordNotFound, "Couldn't find #{self.class._reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
+ model = self.class._reflect_on_association(association_name).klass.name
+ raise RecordNotFound.new("Couldn't find #{model} with ID=#{record_id} for #{self.class.name} with ID=#{id}",
+ model, 'id', record_id)
end
end
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index a6176dffdb..09c36d7b4d 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -148,7 +148,7 @@ module ActiveRecord
# Attributes marked as readonly are silently ignored if the record is
# being updated.
def save!(*args)
- create_or_update(*args) || raise(RecordNotSaved.new(nil, self))
+ create_or_update(*args) || raise(RecordNotSaved.new("Failed to save the record", self))
end
# Deletes the record in the database and freezes this instance to
@@ -193,7 +193,7 @@ module ActiveRecord
# and #destroy! raises ActiveRecord::RecordNotDestroyed.
# See ActiveRecord::Callbacks for further details.
def destroy!
- destroy || raise(ActiveRecord::RecordNotDestroyed, self)
+ destroy || _raise_record_not_destroyed
end
# Returns an instance of the specified +klass+ with the attributes of the
@@ -205,12 +205,13 @@ module ActiveRecord
# instance using the companies/company partial instead of clients/client.
#
# Note: The new instance will share a link to the same attributes as the original class.
- # So any change to the attributes in either instance will affect the other.
+ # Therefore the sti column value will still be the same.
+ # Any change to the attributes on either instance will affect both instances.
+ # If you want to change the sti column as well, use +becomes!+ instead.
def becomes(klass)
became = klass.new
became.instance_variable_set("@attributes", @attributes)
- changed_attributes = @changed_attributes if defined?(@changed_attributes)
- became.instance_variable_set("@changed_attributes", changed_attributes || {})
+ became.instance_variable_set("@changed_attributes", attributes_changed_by_setter)
became.instance_variable_set("@new_record", new_record?)
became.instance_variable_set("@destroyed", destroyed?)
became.instance_variable_set("@errors", errors)
@@ -380,7 +381,7 @@ module ActiveRecord
# # => #<Account id: 1, email: 'account@example.com'>
#
# Attributes are reloaded from the database, and caches busted, in
- # particular the associations cache.
+ # particular the associations cache and the QueryCache.
#
# If the record no longer exists in the database <tt>ActiveRecord::RecordNotFound</tt>
# is raised. Otherwise, in addition to the in-place modification the method
@@ -416,6 +417,8 @@ module ActiveRecord
# end
#
def reload(options = nil)
+ self.class.connection.clear_query_cache
+
fresh_object =
if options && options[:lock]
self.class.unscoped { self.class.lock(options[:lock]).find(id) }
@@ -428,7 +431,7 @@ module ActiveRecord
self
end
- # Saves the record with the updated_at/on attributes set to the current time
+ # Saves the record with the updated_at/on attributes set to the current time
# or the time specified.
# Please note that no validation is performed and only the +after_touch+,
# +after_commit+ and +after_rollback+ callbacks are executed.
@@ -462,9 +465,10 @@ module ActiveRecord
# ball = Ball.new
# ball.touch(:updated_at) # => raises ActiveRecordError
#
- def touch(*names, time: current_time_from_proper_timezone)
+ def touch(*names, time: nil)
raise ActiveRecordError, "cannot touch on a new record object" unless persisted?
+ time ||= current_time_from_proper_timezone
attributes = timestamp_attributes_for_update_in_model
attributes.concat(names)
@@ -476,11 +480,23 @@ module ActiveRecord
changes[column] = write_attribute(column, time)
end
- changes[self.class.locking_column] = increment_lock if locking_enabled?
-
clear_attribute_changes(changes.keys)
primary_key = self.class.primary_key
- self.class.unscoped.where(primary_key => self[primary_key]).update_all(changes) == 1
+ scope = self.class.unscoped.where(primary_key => _read_attribute(primary_key))
+
+ if locking_enabled?
+ locking_column = self.class.locking_column
+ scope = scope.where(locking_column => _read_attribute(locking_column))
+ changes[locking_column] = increment_lock
+ end
+
+ result = scope.update_all(changes) == 1
+
+ if !result && locking_enabled?
+ raise ActiveRecord::StaleObjectError.new(self, "touch")
+ end
+
+ result
else
true
end
@@ -532,5 +548,12 @@ module ActiveRecord
def verify_readonly_attribute(name)
raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
end
+
+ def _raise_record_not_destroyed
+ @_association_destroy_exception ||= nil
+ raise @_association_destroy_exception || RecordNotDestroyed.new("Failed to destroy the record", self)
+ ensure
+ @_association_destroy_exception = nil
+ end
end
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 4e597590e9..87a1988f2f 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -6,7 +6,7 @@ module ActiveRecord
delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, to: :all
delegate :find_by, :find_by!, to: :all
delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, to: :all
- delegate :find_each, :find_in_batches, to: :all
+ delegate :find_each, :find_in_batches, :in_batches, to: :all
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :or,
:where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly,
:having, :create_with, :uniq, :distinct, :references, :none, :unscope, to: :all
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 7e907beec0..459d6256fa 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -16,11 +16,11 @@ module ActiveRecord
config.app_generators.orm :active_record, :migration => true,
:timestamps => true
- config.app_middleware.insert_after "::ActionDispatch::Callbacks",
- "ActiveRecord::QueryCache"
+ config.app_middleware.insert_after ::ActionDispatch::Callbacks,
+ ActiveRecord::QueryCache
- config.app_middleware.insert_after "::ActionDispatch::Callbacks",
- "ActiveRecord::ConnectionAdapters::ConnectionManagement"
+ config.app_middleware.insert_after ::ActionDispatch::Callbacks,
+ ActiveRecord::ConnectionAdapters::ConnectionManagement
config.action_dispatch.rescue_responses.merge!(
'ActiveRecord::RecordNotFound' => :not_found,
@@ -78,7 +78,7 @@ module ActiveRecord
initializer "active_record.migration_error" do
if config.active_record.delete(:migration_error) == :page_load
- config.app_middleware.insert_after "::ActionDispatch::Callbacks",
+ config.app_middleware.insert_after ::ActionDispatch::Callbacks,
"ActiveRecord::Migration::CheckPending"
end
end
@@ -93,6 +93,7 @@ module ActiveRecord
cache = Marshal.load File.binread 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}."
end
@@ -155,8 +156,8 @@ end_warning
ActiveSupport.on_load(:active_record) do
ActionDispatch::Reloader.send(hook) do
if ActiveRecord::Base.connected?
- ActiveRecord::Base.clear_reloadable_connections!
ActiveRecord::Base.clear_cache!
+ ActiveRecord::Base.clear_reloadable_connections!
end
end
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 2591e7492d..6a72d528b4 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -12,7 +12,7 @@ db_namespace = namespace :db do
end
end
- desc 'Creates the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:create:all to create all databases in the config). Without RAILS_ENV it defaults to creating the development and test databases.'
+ desc 'Creates the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:create:all to create all databases in the config). Without RAILS_ENV, it defaults to creating the development and test databases.'
task :create => [:load_config] do
ActiveRecord::Tasks::DatabaseTasks.create_current
end
@@ -23,7 +23,7 @@ db_namespace = namespace :db do
end
end
- desc 'Drops the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config). Without RAILS_ENV it defaults to dropping the development and test databases.'
+ desc 'Drops the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config). Without RAILS_ENV, it defaults to dropping the development and test databases.'
task :drop => [:load_config] do
ActiveRecord::Tasks::DatabaseTasks.drop_current
end
@@ -42,15 +42,18 @@ db_namespace = namespace :db do
desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)."
task :migrate => [:environment, :load_config] do
ActiveRecord::Tasks::DatabaseTasks.migrate
- db_namespace['_dump'].invoke if ActiveRecord::Base.dump_schema_after_migration
+ db_namespace['_dump'].invoke
end
+ # IMPORTANT: This task won't dump the schema if ActiveRecord::Base.dump_schema_after_migration is set to false
task :_dump do
- case ActiveRecord::Base.schema_format
- when :ruby then db_namespace["schema:dump"].invoke
- when :sql then db_namespace["structure:dump"].invoke
- else
- raise "unknown schema format #{ActiveRecord::Base.schema_format}"
+ if ActiveRecord::Base.dump_schema_after_migration
+ case ActiveRecord::Base.schema_format
+ when :ruby then db_namespace["schema:dump"].invoke
+ when :sql then db_namespace["structure:dump"].invoke
+ else
+ raise "unknown schema format #{ActiveRecord::Base.schema_format}"
+ end
end
# Allow this task to be called as many times as required. An example is the
# migrate:redo task, which calls other two internally that depend on this one.
@@ -134,10 +137,7 @@ db_namespace = namespace :db do
end
# desc 'Drops and recreates the database from db/schema.rb for the current environment and loads the seeds.'
- task :reset => [:environment, :load_config] do
- db_namespace["drop"].invoke
- db_namespace["setup"].invoke
- end
+ task :reset => [ 'db:drop', 'db:setup' ]
# desc "Retrieves the charset for the current environment's database"
task :charset => [:environment, :load_config] do
@@ -159,7 +159,7 @@ db_namespace = namespace :db do
end
# desc "Raises an error if there are pending migrations"
- task :abort_if_pending_migrations => :environment do
+ task :abort_if_pending_migrations => [:environment, :load_config] do
pending_migrations = ActiveRecord::Migrator.open(ActiveRecord::Migrator.migrations_paths).pending_migrations
if pending_migrations.any?
@@ -171,17 +171,17 @@ db_namespace = namespace :db do
end
end
- desc 'Create the database, load the schema, and initialize with the seed data (use db:reset to also drop the database first)'
+ desc 'Creates the database, loads the schema, and initializes with the seed data (use db:reset to also drop the database first)'
task :setup => ['db:schema:load_if_ruby', 'db:structure:load_if_sql', :seed]
- desc 'Load the seed data from db/seeds.rb'
+ desc 'Loads the seed data from db/seeds.rb'
task :seed do
db_namespace['abort_if_pending_migrations'].invoke
ActiveRecord::Tasks::DatabaseTasks.load_seed
end
namespace :fixtures do
- desc "Load fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
+ desc "Loads fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
task :load => [:environment, :load_config] do
require 'active_record/fixtures'
@@ -229,7 +229,7 @@ db_namespace = namespace :db do
end
namespace :schema do
- desc 'Create a db/schema.rb file that is portable against any DB supported by AR'
+ desc 'Creates a db/schema.rb file that is portable against any DB supported by AR'
task :dump => [:environment, :load_config] do
require 'active_record/schema_dumper'
filename = ENV['SCHEMA'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, 'schema.rb')
@@ -239,8 +239,8 @@ db_namespace = namespace :db do
db_namespace['schema:dump'].reenable
end
- desc 'Load a schema.rb file into the database'
- task :load => [:load_config] do
+ desc 'Loads a schema.rb file into the database'
+ task :load => [:environment, :load_config] do
ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:ruby, ENV['SCHEMA'])
end
@@ -249,7 +249,7 @@ db_namespace = namespace :db do
end
namespace :cache do
- desc 'Create a db/schema_cache.dump file.'
+ desc 'Creates a db/schema_cache.dump file.'
task :dump => [:environment, :load_config] do
con = ActiveRecord::Base.connection
filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump")
@@ -259,7 +259,7 @@ db_namespace = namespace :db do
open(filename, 'wb') { |f| f.write(Marshal.dump(con.schema_cache)) }
end
- desc 'Clear a db/schema_cache.dump file.'
+ desc 'Clears a db/schema_cache.dump file.'
task :clear => [:environment, :load_config] do
filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump")
FileUtils.rm(filename) if File.exist?(filename)
@@ -269,7 +269,7 @@ db_namespace = namespace :db do
end
namespace :structure do
- desc 'Dump the database structure to db/structure.sql. Specify another file with SCHEMA=db/my_structure.sql'
+ desc 'Dumps the database structure to db/structure.sql. Specify another file with SCHEMA=db/my_structure.sql'
task :dump => [:environment, :load_config] do
filename = ENV['SCHEMA'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "structure.sql")
current_config = ActiveRecord::Tasks::DatabaseTasks.current_config
@@ -285,7 +285,7 @@ db_namespace = namespace :db do
db_namespace['structure:dump'].reenable
end
- desc "Recreate the databases from the structure.sql file"
+ desc "Recreates the databases from the structure.sql file"
task :load => [:load_config] do
ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:sql, ENV['SCHEMA'])
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 4265afc0a5..5360db6a19 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -69,9 +69,11 @@ module ActiveRecord
def reflections
ref = {}
_reflections.each do |name, reflection|
- parent_name, parent_reflection = reflection.parent_reflection
- if parent_name
- ref[parent_name] = parent_reflection
+ parent_reflection = reflection.parent_reflection
+
+ if parent_reflection
+ parent_name = parent_reflection.name
+ ref[parent_name.to_s] = parent_reflection
else
ref[name] = reflection
end
@@ -196,7 +198,7 @@ module ActiveRecord
@scope = scope
@options = options
@active_record = active_record
- @klass = options[:class]
+ @klass = options[:anonymous_class]
@plural_name = active_record.pluralize_table_names ?
name.to_s.pluralize : name.to_s
end
@@ -204,7 +206,7 @@ module ActiveRecord
def autosave=(autosave)
@automatic_inverse_of = false
@options[:autosave] = autosave
- _, parent_reflection = self.parent_reflection
+ parent_reflection = self.parent_reflection
if parent_reflection
parent_reflection.autosave = autosave
end
@@ -272,7 +274,7 @@ module ActiveRecord
end
attr_reader :type, :foreign_type
- attr_accessor :parent_reflection # [:name, Reflection]
+ attr_accessor :parent_reflection # Reflection
def initialize(name, scope, options, active_record)
super
@@ -635,7 +637,7 @@ module ActiveRecord
def initialize(delegate_reflection)
@delegate_reflection = delegate_reflection
- @klass = delegate_reflection.options[:class]
+ @klass = delegate_reflection.options[:anonymous_class]
@source_reflection_name = delegate_reflection.options[:source]
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 85648a7f8f..e47b7b1ed9 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -9,12 +9,13 @@ module ActiveRecord
:extending, :unscope]
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering,
- :reverse_order, :distinct, :create_with, :uniq]
+ :reverse_order, :distinct, :create_with]
CLAUSE_METHODS = [:where, :having, :from]
INVALID_METHODS_FOR_DELETE_ALL = [:limit, :distinct, :offset, :group, :having]
VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS
+ include Enumerable
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
attr_reader :table, :klass, :loaded, :predicate_builder
@@ -275,38 +276,52 @@ module ActiveRecord
# Returns true if there are no records.
def none?
- if block_given?
- to_a.none? { |*block_args| yield(*block_args) }
- else
- empty?
- end
+ return super if block_given?
+ empty?
end
# Returns true if there are any records.
def any?
- if block_given?
- to_a.any? { |*block_args| yield(*block_args) }
- else
- !empty?
- end
+ return super if block_given?
+ !empty?
end
# Returns true if there is exactly one record.
def one?
- if block_given?
- to_a.one? { |*block_args| yield(*block_args) }
- else
- limit_value ? to_a.one? : size == 1
- end
+ return super if block_given?
+ limit_value ? to_a.one? : size == 1
end
# Returns true if there is more than one record.
def many?
- if block_given?
- to_a.many? { |*block_args| yield(*block_args) }
- else
- limit_value ? to_a.many? : size > 1
- end
+ return super if block_given?
+ limit_value ? to_a.many? : size > 1
+ end
+
+ # Returns a cache key that can be used to identify the records fetched by
+ # this query. The cache key is built with a fingerprint of the sql query,
+ # the number of records matched by the query and a timestamp of the last
+ # updated record. When a new record comes to match the query, or any of
+ # the existing records is updated or deleted, the cache key changes.
+ #
+ # Product.where("name like ?", "%Cosmic Encounter%").cache_key
+ # => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000"
+ #
+ # If the collection is loaded, the method will iterate through the records
+ # to generate the timestamp, otherwise it will trigger one SQL query like:
+ #
+ # SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
+ #
+ # You can also pass a custom timestamp column to fetch the timestamp of the
+ # last updated record.
+ #
+ # Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at)
+ #
+ # You can customize the strategy to generate the key on a per model basis
+ # overriding ActiveRecord::Base#collection_cache_key.
+ def cache_key(timestamp_column = :updated_at)
+ @cache_keys ||= {}
+ @cache_keys[timestamp_column] ||= @klass.collection_cache_key(self, timestamp_column)
end
# Scope all queries to the current scope.
@@ -618,6 +633,7 @@ module ActiveRecord
def uniq_value
distinct_value
end
+ deprecate uniq_value: :distinct_value
# Compares two relations for equality.
def ==(other)
@@ -651,6 +667,13 @@ module ActiveRecord
"#<#{self.class.name} [#{entries.join(', ')}]>"
end
+ protected
+
+ def load_records(records)
+ @records = records
+ @loaded = true
+ end
+
private
def exec_queries
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index e07580a563..beb8fa511c 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -1,3 +1,5 @@
+require "active_record/relation/batches/batch_enumerator"
+
module ActiveRecord
module Batches
# Looping through a collection of records from the database
@@ -122,24 +124,102 @@ module ActiveRecord
end
end
+ in_batches(of: batch_size, begin_at: begin_at, end_at: end_at, load: true) do |batch|
+ yield batch.to_a
+ end
+ end
+
+ # Yields ActiveRecord::Relation objects to work with a batch of records.
+ #
+ # Person.where("age > 21").in_batches do |relation|
+ # relation.delete_all
+ # sleep(10) # Throttle the delete queries
+ # end
+ #
+ # If you do not provide a block to #in_batches, it will return a
+ # BatchEnumerator which is enumerable.
+ #
+ # Person.in_batches.with_index do |relation, batch_index|
+ # puts "Processing relation ##{batch_index}"
+ # relation.each { |relation| relation.delete_all }
+ # end
+ #
+ # Examples of calling methods on the returned BatchEnumerator object:
+ #
+ # Person.in_batches.delete_all
+ # Person.in_batches.update_all(awesome: true)
+ # Person.in_batches.each_record(&:party_all_night!)
+ #
+ # ==== Options
+ # * <tt>:of</tt> - Specifies the size of the batch. Default to 1000.
+ # * <tt>:load</tt> - Specifies if the relation should be loaded. Default to false.
+ # * <tt>:begin_at</tt> - Specifies the primary key value to start from, inclusive of the value.
+ # * <tt>:end_at</tt> - Specifies the primary key value to end at, inclusive of the value.
+ #
+ # This is especially useful if you want to work with the
+ # ActiveRecord::Relation object instead of the array of records, or if
+ # you want multiple workers dealing with the same processing queue. You can
+ # make worker 1 handle all the records between id 0 and 10,000 and worker 2
+ # handle from 10,000 and beyond (by setting the +:begin_at+ and +:end_at+
+ # option on each worker).
+ #
+ # # Let's process the next 2000 records
+ # Person.in_batches(of: 2000, begin_at: 2000).update_all(awesome: true)
+ #
+ # An example of calling where query method on the relation:
+ #
+ # Person.in_batches.each do |relation|
+ # relation.update_all('age = age + 1')
+ # relation.where('age > 21').update_all(should_party: true)
+ # relation.where('age <= 21').delete_all
+ # end
+ #
+ # NOTE: If you are going to iterate through each record, you should call
+ # #each_record on the yielded BatchEnumerator:
+ #
+ # Person.in_batches.each_record(&:party_all_night!)
+ #
+ # NOTE: It's not possible to set the order. That is automatically set to
+ # ascending on the primary key ("id ASC") to make the batch ordering
+ # consistent. Therefore the primary key must be orderable, e.g an integer
+ # or a string.
+ #
+ # NOTE: You can't set the limit either, that's used to control the batch
+ # sizes.
+ def in_batches(of: 1000, begin_at: nil, end_at: nil, load: false)
+ relation = self
+ unless block_given?
+ return BatchEnumerator.new(of: of, begin_at: begin_at, end_at: end_at, relation: self)
+ end
+
if logger && (arel.orders.present? || arel.taken.present?)
logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
end
- relation = relation.reorder(batch_order).limit(batch_size)
+ relation = relation.reorder(batch_order).limit(of)
relation = apply_limits(relation, begin_at, end_at)
- records = relation.to_a
+ batch_relation = relation
+
+ loop do
+ if load
+ records = batch_relation.to_a
+ ids = records.map(&:id)
+ yielded_relation = self.where(primary_key => ids)
+ yielded_relation.load_records(records)
+ else
+ ids = batch_relation.pluck(primary_key)
+ yielded_relation = self.where(primary_key => ids)
+ end
- while records.any?
- records_size = records.size
- primary_key_offset = records.last.id
- raise "Primary key not included in the custom select clause" unless primary_key_offset
+ break if ids.empty?
- yield records
+ primary_key_offset = ids.last
+ raise ArgumentError.new("Primary key not included in the custom select clause") unless primary_key_offset
- break if records_size < batch_size
+ yield yielded_relation
- records = relation.where(table[primary_key].gt(primary_key_offset)).to_a
+ break if ids.length < of
+ batch_relation = relation.where(table[primary_key].gt(primary_key_offset))
end
end
diff --git a/activerecord/lib/active_record/relation/batches/batch_enumerator.rb b/activerecord/lib/active_record/relation/batches/batch_enumerator.rb
new file mode 100644
index 0000000000..153aae9584
--- /dev/null
+++ b/activerecord/lib/active_record/relation/batches/batch_enumerator.rb
@@ -0,0 +1,67 @@
+module ActiveRecord
+ module Batches
+ class BatchEnumerator
+ include Enumerable
+
+ def initialize(of: 1000, begin_at: nil, end_at: nil, relation:) #:nodoc:
+ @of = of
+ @relation = relation
+ @begin_at = begin_at
+ @end_at = end_at
+ end
+
+ # Looping through a collection of records from the database (using the
+ # +all+ method, for example) is very inefficient since it will try to
+ # instantiate all the objects at once.
+ #
+ # In that case, batch processing methods allow you to work with the
+ # records in batches, thereby greatly reducing memory consumption.
+ #
+ # Person.in_batches.each_record do |person|
+ # person.do_awesome_stuff
+ # end
+ #
+ # Person.where("age > 21").in_batches(of: 10).each_record do |person|
+ # person.party_all_night!
+ # end
+ #
+ # If you do not provide a block to #each_record, it will return an Enumerator
+ # for chaining with other methods:
+ #
+ # Person.in_batches.each_record.with_index do |person, index|
+ # person.award_trophy(index + 1)
+ # end
+ def each_record
+ return to_enum(:each_record) unless block_given?
+
+ @relation.to_enum(:in_batches, of: @of, begin_at: @begin_at, end_at: @end_at, load: true).each do |relation|
+ relation.to_a.each { |record| yield record }
+ end
+ end
+
+ # Delegates #delete_all, #update_all, #destroy_all methods to each batch.
+ #
+ # People.in_batches.delete_all
+ # People.in_batches.destroy_all('age < 10')
+ # People.in_batches.update_all('age = age + 1')
+ [:delete_all, :update_all, :destroy_all].each do |method|
+ define_method(method) do |*args, &block|
+ @relation.to_enum(:in_batches, of: @of, begin_at: @begin_at, end_at: @end_at, load: false).each do |relation|
+ relation.send(method, *args, &block)
+ end
+ end
+ end
+
+ # Yields an ActiveRecord::Relation object for each batch of records.
+ #
+ # Person.in_batches.each do |relation|
+ # relation.update_all(awesome: true)
+ # end
+ def each
+ enum = @relation.to_enum(:in_batches, of: @of, begin_at: @begin_at, end_at: @end_at, load: false)
+ return enum.each { |relation| yield relation } if block_given?
+ enum
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 8f16de3519..0f6015fa93 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -71,6 +71,7 @@ module ActiveRecord
#
# Person.sum(:age) # => 4562
def sum(*args)
+ return super if block_given?
calculate(:sum, *args)
end
@@ -130,15 +131,15 @@ module ActiveRecord
# the plucked column names, if they can be deduced. Plucking an SQL fragment
# returns String values by default.
#
- # Person.pluck(:id)
- # # SELECT people.id FROM people
- # # => [1, 2, 3]
+ # Person.pluck(:name)
+ # # SELECT people.name FROM people
+ # # => ['David', 'Jeremy', 'Jose']
#
# Person.pluck(:id, :name)
# # SELECT people.id, people.name FROM people
# # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
#
- # Person.pluck('DISTINCT role')
+ # Person.distinct.pluck(:role)
# # SELECT DISTINCT role FROM people
# # => ['admin', 'member', 'guest']
#
@@ -150,6 +151,8 @@ module ActiveRecord
# # SELECT DATEDIFF(updated_at, created_at) FROM people
# # => ['0', '27761', '173']
#
+ # See also +ids+.
+ #
def pluck(*column_names)
column_names.map! do |column_name|
if column_name.is_a?(Symbol) && attribute_alias?(column_name)
@@ -159,6 +162,10 @@ module ActiveRecord
end
end
+ if loaded? && (column_names - @klass.column_names).empty?
+ return @records.pluck(*column_names)
+ end
+
if has_include?(column_names.first)
construct_relation_for_association_calculations.pluck(*column_names)
else
@@ -188,7 +195,8 @@ module ActiveRecord
def perform_calculation(operation, column_name)
operation = operation.to_s.downcase
- # If #count is used with #distinct / #uniq it is considered distinct. (eg. relation.distinct.count)
+ # If #count is used with #distinct (i.e. `relation.distinct.count`) it is
+ # considered distinct.
distinct = self.distinct_value
if operation == "count"
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index d4a8823cfe..d75ec72b1a 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -18,7 +18,7 @@ module ActiveRecord
delegate = Class.new(klass) {
include ClassSpecificRelation
}
- const_set klass.name.gsub('::', '_'), delegate
+ const_set klass.name.gsub('::'.freeze, '_'.freeze), delegate
cache[klass] = delegate
end
end
@@ -39,7 +39,7 @@ module ActiveRecord
BLACKLISTED_ARRAY_METHODS = [
:compact!, :flatten!, :reject!, :reverse!, :rotate!, :map!,
:shuffle!, :slice!, :sort!, :sort_by!, :delete_if,
- :keep_if, :pop, :shift, :delete_at, :compact, :select!
+ :keep_if, :pop, :shift, :delete_at, :select!
].to_set # :nodoc:
delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join, to: :to_a
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 6a3a56f1cc..009b2bad57 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -46,7 +46,7 @@ module ActiveRecord
# # returns the first item or returns a new instance (requires you call .save to persist against the database).
#
# Person.where(name: 'Spartacus', rating: 4).first_or_create
- # # returns the first item or creates it and returns it, available since Rails 3.2.1.
+ # # returns the first item or creates it and returns it.
#
# ==== Alternatives for +find+
#
@@ -57,16 +57,13 @@ module ActiveRecord
# # returns a chainable list of instances with only the mentioned fields.
#
# Person.where(name: 'Spartacus', rating: 4).ids
- # # returns an Array of ids, available since Rails 3.2.1.
+ # # returns an Array of ids.
#
# Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2)
- # # returns an Array of the required fields, available since Rails 3.1.
+ # # returns an Array of the required fields.
def find(*args)
- if block_given?
- to_a.find(*args) { |*block_args| yield(*block_args) }
- else
- find_with_ids(*args)
- end
+ return super if block_given?
+ find_with_ids(*args)
end
# Finds the first record matching the specified conditions. There
@@ -77,18 +74,19 @@ module ActiveRecord
#
# Post.find_by name: 'Spartacus', rating: 4
# Post.find_by "published_at < ?", 2.weeks.ago
- def find_by(*args)
- where(*args).take
+ def find_by(arg, *args)
+ where(arg, *args).take
rescue RangeError
nil
end
# Like <tt>find_by</tt>, except that if no record is found, raises
# an <tt>ActiveRecord::RecordNotFound</tt> error.
- def find_by!(*args)
- where(*args).take!
+ def find_by!(arg, *args)
+ where(arg, *args).take!
rescue RangeError
- raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range value"
+ raise RecordNotFound.new("Couldn't find #{@klass.name} with an out of range value",
+ @klass.name)
end
# Gives a record (or N records if a parameter is supplied) without any implied
@@ -111,23 +109,11 @@ module ActiveRecord
# Find the first record (or first N records if a parameter is supplied).
# If no order is defined it will order by primary key.
#
- # Person.first # returns the first object fetched by SELECT * FROM people
+ # Person.first # returns the first object fetched by SELECT * FROM people ORDER BY people.id LIMIT 1
# Person.where(["user_name = ?", user_name]).first
# Person.where(["user_name = :u", { u: user_name }]).first
# Person.order("created_on DESC").offset(5).first
- # Person.first(3) # returns the first three objects fetched by SELECT * FROM people LIMIT 3
- #
- # ==== Rails 3
- #
- # Person.first # SELECT "people".* FROM "people" LIMIT 1
- #
- # NOTE: Rails 3 may not order this query by the primary key and the order
- # will depend on the database implementation. In order to ensure that behavior,
- # use <tt>User.order(:id).first</tt> instead.
- #
- # ==== Rails 4
- #
- # Person.first # SELECT "people".* FROM "people" ORDER BY "people"."id" ASC LIMIT 1
+ # Person.first(3) # returns the first three objects fetched by SELECT * FROM people ORDER BY people.id LIMIT 3
#
def first(limit = nil)
if limit
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index 65b607ff1c..0b38666ce9 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -1,5 +1,4 @@
require 'active_support/core_ext/hash/keys'
-require "set"
module ActiveRecord
class Relation
@@ -51,7 +50,7 @@ module ActiveRecord
NORMAL_VALUES = Relation::VALUE_METHODS -
Relation::CLAUSE_METHODS -
- [:joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc:
+ [:includes, :preload, :joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc:
def normal_values
NORMAL_VALUES
@@ -76,6 +75,7 @@ module ActiveRecord
merge_multi_values
merge_single_values
merge_clauses
+ merge_preloads
merge_joins
relation
@@ -83,6 +83,27 @@ module ActiveRecord
private
+ def merge_preloads
+ return if other.preload_values.empty? && other.includes_values.empty?
+
+ if other.klass == relation.klass
+ relation.preload!(*other.preload_values) unless other.preload_values.empty?
+ relation.includes!(other.includes_values) unless other.includes_values.empty?
+ else
+ reflection = relation.klass.reflect_on_all_associations.find do |r|
+ r.class_name == other.klass.name
+ end || return
+
+ unless other.preload_values.empty?
+ relation.preload! reflection.name => other.preload_values
+ end
+
+ unless other.includes_values.empty?
+ relation.includes! reflection.name => other.includes_values
+ end
+ end
+ end
+
def merge_joins
return if other.joins_values.blank?
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 43e9afe853..d26db7d4cf 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -52,7 +52,7 @@ module ActiveRecord
key
else
key = key.to_s
- key.split('.').first if key.include?('.')
+ key.split('.'.freeze).first if key.include?('.'.freeze)
end
end.compact
end
@@ -123,10 +123,10 @@ module ActiveRecord
end
def convert_dot_notation_to_hash(attributes)
- dot_notation = attributes.keys.select { |s| s.include?(".") }
+ dot_notation = attributes.keys.select { |s| s.include?(".".freeze) }
dot_notation.each do |key|
- table_name, column_name = key.split(".")
+ table_name, column_name = key.split(".".freeze)
value = attributes.delete(key)
attributes[table_name] ||= {}
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 69ce5cdc2a..706c99c245 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -242,12 +242,9 @@ module ActiveRecord
# Model.select(:field).first.other_field
# # => ActiveModel::MissingAttributeError: missing attribute: other_field
def select(*fields)
- if block_given?
- to_a.select { |*block_args| yield(*block_args) }
- else
- raise ArgumentError, 'Call this with at least one field' if fields.empty?
- spawn._select!(*fields)
- end
+ return super if block_given?
+ raise ArgumentError, 'Call this with at least one field' if fields.empty?
+ spawn._select!(*fields)
end
def _select!(*fields) # :nodoc:
@@ -587,7 +584,7 @@ module ActiveRecord
#
# The two relations must be structurally compatible: they must be scoping the same model, and
# they must differ only by +where+ (if no +group+ has been defined) or +having+ (if a +group+ is
- # present). Neither relation may have a +limit+, +offset+, or +uniq+ set.
+ # present). Neither relation may have a +limit+, +offset+, or +distinct+ set.
#
# Post.where("id = 1").or(Post.where("id = 2"))
# # SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'id = 2'))
@@ -790,6 +787,7 @@ module ActiveRecord
spawn.distinct!(value)
end
alias uniq distinct
+ deprecate uniq: :distinct
# Like #distinct, but modifies relation in place.
def distinct!(value = true) # :nodoc:
@@ -797,6 +795,7 @@ module ActiveRecord
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.
@@ -999,15 +998,13 @@ module ActiveRecord
end
def arel_columns(columns)
- if from_clause.value
- columns
- else
- columns.map do |field|
- if (Symbol === field || String === field) && columns_hash.key?(field.to_s)
- arel_table[field]
- else
- field
- end
+ columns.map do |field|
+ if (Symbol === field || String === field) && columns_hash.key?(field.to_s) && !from_clause.value
+ arel_table[field]
+ elsif Symbol === field
+ connection.quote_table_name(field.to_s)
+ else
+ field
end
end
end
diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb
index f9b9e640ec..1f000b3f0f 100644
--- a/activerecord/lib/active_record/relation/where_clause.rb
+++ b/activerecord/lib/active_record/relation/where_clause.rb
@@ -135,7 +135,7 @@ module ActiveRecord
def predicates_except(columns)
predicates.reject do |node|
case node
- when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThanOrEqual
+ when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right)
columns.include?(subrelation.name.to_s)
end
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index c7f55ebaa1..ba75ffa5a1 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -11,17 +11,15 @@ module ActiveRecord
protected
- # Accepts an array, hash, or string of SQL conditions and sanitizes
+ # Accepts an array or string of SQL conditions and sanitizes
# them into a valid SQL fragment for a WHERE clause.
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
- # { name: "foo'bar", group_id: 4 } returns "name='foo''bar' and group_id='4'"
# "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
def sanitize_sql_for_conditions(condition, table_name = self.table_name)
return nil if condition.blank?
case condition
when Array; sanitize_sql_array(condition)
- when Hash; sanitize_sql_hash_for_conditions(condition, table_name)
else condition
end
end
@@ -121,9 +119,9 @@ module ActiveRecord
end
def replace_named_bind_variables(statement, bind_vars) #:nodoc:
- statement.gsub(/(:?):([a-zA-Z]\w*)/) do
+ statement.gsub(/(:?):([a-zA-Z]\w*)/) do |match|
if $1 == ':' # skip postgresql casts
- $& # return the whole match
+ match # return the whole match
elsif bind_vars.include?(match = $2.to_sym)
replace_bind_variable(bind_vars[match])
else
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index da95920571..c5910fa1ad 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -1,5 +1,4 @@
require 'stringio'
-require 'active_support/core_ext/big_decimal'
module ActiveRecord
# = Active Record Schema Dumper
@@ -105,7 +104,10 @@ HEADER
end
def table(table, stream)
- columns = @connection.columns(table)
+ columns = @connection.columns(table).map do |column|
+ column.instance_variable_set(:@table_name, table)
+ column
+ end
begin
tbl = StringIO.new
@@ -128,6 +130,10 @@ HEADER
tbl.print ", id: false"
end
tbl.print ", force: :cascade"
+
+ table_options = @connection.table_options(table)
+ tbl.print ", options: #{table_options.inspect}" unless table_options.blank?
+
tbl.puts " do |t|"
# then dump all non-primary key columns
@@ -165,11 +171,11 @@ HEADER
tbl.puts
end
+ indexes(table, tbl)
+
tbl.puts " end"
tbl.puts
- indexes(table, tbl)
-
tbl.rewind
stream.print tbl.read
rescue => e
@@ -185,8 +191,7 @@ HEADER
if (indexes = @connection.indexes(table)).any?
add_index_statements = indexes.map do |index|
statement_parts = [
- "add_index #{remove_prefix_and_suffix(index.table).inspect}",
- index.columns.inspect,
+ "t.index #{index.columns.inspect}",
"name: #{index.name.inspect}",
]
statement_parts << 'unique: true' if index.unique
@@ -200,11 +205,10 @@ HEADER
statement_parts << "using: #{index.using.inspect}" if index.using
statement_parts << "type: #{index.type.inspect}" if index.type
- " #{statement_parts.join(', ')}"
+ " #{statement_parts.join(', ')}"
end
stream.puts add_index_statements.sort.join("\n")
- stream.puts
end
end
diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb
index b5038104ac..cb47bf23f7 100644
--- a/activerecord/lib/active_record/schema_migration.rb
+++ b/activerecord/lib/active_record/schema_migration.rb
@@ -1,6 +1,5 @@
require 'active_record/scoping/default'
require 'active_record/scoping/named'
-require 'active_record/base'
module ActiveRecord
class SchemaMigration < ActiveRecord::Base
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index 3590b8846e..a1adf8e3ee 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -100,6 +100,7 @@ module ActiveRecord
end
def build_default_scope(base_rel = relation) # :nodoc:
+ return if abstract_class?
if !Base.is_a?(method(:default_scope).owner)
# The user has defined their own default scope method, so call that
evaluate_default_scope { default_scope }
diff --git a/activerecord/lib/active_record/secure_token.rb b/activerecord/lib/active_record/secure_token.rb
index a3023a0cb4..ca11853da7 100644
--- a/activerecord/lib/active_record/secure_token.rb
+++ b/activerecord/lib/active_record/secure_token.rb
@@ -13,7 +13,7 @@ module ActiveRecord
#
# user = User.new
# user.save
- # user.token # => "4kUgL2pdQMSCQtjE"
+ # user.token # => "pX27zsMN2ViQKta1bGfLmVJE"
# user.auth_token # => "77TMHrHJFvFDwodq8w7Ev2m7"
# user.regenerate_token # => true
# user.regenerate_auth_token # => true
diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb
index 48c12dcf9f..23dc6465af 100644
--- a/activerecord/lib/active_record/serialization.rb
+++ b/activerecord/lib/active_record/serialization.rb
@@ -18,5 +18,3 @@ module ActiveRecord #:nodoc:
end
end
end
-
-require 'active_record/serializers/xml_serializer'
diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb
deleted file mode 100644
index 89b7e0be82..0000000000
--- a/activerecord/lib/active_record/serializers/xml_serializer.rb
+++ /dev/null
@@ -1,193 +0,0 @@
-require 'active_support/core_ext/hash/conversions'
-
-module ActiveRecord #:nodoc:
- module Serialization
- include ActiveModel::Serializers::Xml
-
- # Builds an XML document to represent the model. Some configuration is
- # available through +options+. However more complicated cases should
- # override ActiveRecord::Base#to_xml.
- #
- # By default the generated XML document will include the processing
- # instruction and all the object's attributes. For example:
- #
- # <?xml version="1.0" encoding="UTF-8"?>
- # <topic>
- # <title>The First Topic</title>
- # <author-name>David</author-name>
- # <id type="integer">1</id>
- # <approved type="boolean">false</approved>
- # <replies-count type="integer">0</replies-count>
- # <bonus-time type="dateTime">2000-01-01T08:28:00+12:00</bonus-time>
- # <written-on type="dateTime">2003-07-16T09:28:00+1200</written-on>
- # <content>Have a nice day</content>
- # <author-email-address>david@loudthinking.com</author-email-address>
- # <parent-id></parent-id>
- # <last-read type="date">2004-04-15</last-read>
- # </topic>
- #
- # This behavior can be controlled with <tt>:only</tt>, <tt>:except</tt>,
- # <tt>:skip_instruct</tt>, <tt>:skip_types</tt>, <tt>:dasherize</tt> and <tt>:camelize</tt> .
- # The <tt>:only</tt> and <tt>:except</tt> options are the same as for the
- # +attributes+ method. The default is to dasherize all column names, but you
- # can disable this setting <tt>:dasherize</tt> to +false+. Setting <tt>:camelize</tt>
- # to +true+ will camelize all column names - this also overrides <tt>:dasherize</tt>.
- # To not have the column type included in the XML output set <tt>:skip_types</tt> to +true+.
- #
- # For instance:
- #
- # topic.to_xml(skip_instruct: true, except: [ :id, :bonus_time, :written_on, :replies_count ])
- #
- # <topic>
- # <title>The First Topic</title>
- # <author-name>David</author-name>
- # <approved type="boolean">false</approved>
- # <content>Have a nice day</content>
- # <author-email-address>david@loudthinking.com</author-email-address>
- # <parent-id></parent-id>
- # <last-read type="date">2004-04-15</last-read>
- # </topic>
- #
- # To include first level associations use <tt>:include</tt>:
- #
- # firm.to_xml include: [ :account, :clients ]
- #
- # <?xml version="1.0" encoding="UTF-8"?>
- # <firm>
- # <id type="integer">1</id>
- # <rating type="integer">1</rating>
- # <name>37signals</name>
- # <clients type="array">
- # <client>
- # <rating type="integer">1</rating>
- # <name>Summit</name>
- # </client>
- # <client>
- # <rating type="integer">1</rating>
- # <name>Microsoft</name>
- # </client>
- # </clients>
- # <account>
- # <id type="integer">1</id>
- # <credit-limit type="integer">50</credit-limit>
- # </account>
- # </firm>
- #
- # Additionally, the record being serialized will be passed to a Proc's second
- # parameter. This allows for ad hoc additions to the resultant document that
- # incorporate the context of the record being serialized. And by leveraging the
- # closure created by a Proc, to_xml can be used to add elements that normally fall
- # outside of the scope of the model -- for example, generating and appending URLs
- # associated with models.
- #
- # proc = Proc.new { |options, record| options[:builder].tag!('name-reverse', record.name.reverse) }
- # firm.to_xml procs: [ proc ]
- #
- # <firm>
- # # ... normal attributes as shown above ...
- # <name-reverse>slangis73</name-reverse>
- # </firm>
- #
- # To include deeper levels of associations pass a hash like this:
- #
- # firm.to_xml include: {account: {}, clients: {include: :address}}
- # <?xml version="1.0" encoding="UTF-8"?>
- # <firm>
- # <id type="integer">1</id>
- # <rating type="integer">1</rating>
- # <name>37signals</name>
- # <clients type="array">
- # <client>
- # <rating type="integer">1</rating>
- # <name>Summit</name>
- # <address>
- # ...
- # </address>
- # </client>
- # <client>
- # <rating type="integer">1</rating>
- # <name>Microsoft</name>
- # <address>
- # ...
- # </address>
- # </client>
- # </clients>
- # <account>
- # <id type="integer">1</id>
- # <credit-limit type="integer">50</credit-limit>
- # </account>
- # </firm>
- #
- # To include any methods on the model being called use <tt>:methods</tt>:
- #
- # firm.to_xml methods: [ :calculated_earnings, :real_earnings ]
- #
- # <firm>
- # # ... normal attributes as shown above ...
- # <calculated-earnings>100000000000000000</calculated-earnings>
- # <real-earnings>5</real-earnings>
- # </firm>
- #
- # To call any additional Procs use <tt>:procs</tt>. The Procs are passed a
- # modified version of the options hash that was given to +to_xml+:
- #
- # proc = Proc.new { |options| options[:builder].tag!('abc', 'def') }
- # firm.to_xml procs: [ proc ]
- #
- # <firm>
- # # ... normal attributes as shown above ...
- # <abc>def</abc>
- # </firm>
- #
- # Alternatively, you can yield the builder object as part of the +to_xml+ call:
- #
- # firm.to_xml do |xml|
- # xml.creator do
- # xml.first_name "David"
- # xml.last_name "Heinemeier Hansson"
- # end
- # end
- #
- # <firm>
- # # ... normal attributes as shown above ...
- # <creator>
- # <first_name>David</first_name>
- # <last_name>Heinemeier Hansson</last_name>
- # </creator>
- # </firm>
- #
- # As noted above, you may override +to_xml+ in your ActiveRecord::Base
- # subclasses to have complete control about what's generated. The general
- # form of doing this is:
- #
- # class IHaveMyOwnXML < ActiveRecord::Base
- # def to_xml(options = {})
- # require 'builder'
- # options[:indent] ||= 2
- # xml = options[:builder] ||= ::Builder::XmlMarkup.new(indent: options[:indent])
- # xml.instruct! unless options[:skip_instruct]
- # xml.level_one do
- # xml.tag!(:second_level, 'content')
- # end
- # end
- # end
- def to_xml(options = {}, &block)
- XmlSerializer.new(self, options).serialize(&block)
- end
- end
-
- class XmlSerializer < ActiveModel::Serializers::Xml::Serializer #:nodoc:
- class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc:
- def compute_type
- klass = @serializable.class
- cast_type = klass.type_for_attribute(name)
-
- type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name] || cast_type.type
-
- { :text => :string,
- :time => :datetime }[type] || type
- end
- protected :compute_type
- end
- end
-end
diff --git a/activerecord/lib/active_record/suppressor.rb b/activerecord/lib/active_record/suppressor.rb
index b0b86865fd..b3644bf569 100644
--- a/activerecord/lib/active_record/suppressor.rb
+++ b/activerecord/lib/active_record/suppressor.rb
@@ -37,8 +37,7 @@ module ActiveRecord
end
end
- # Ignore saving events if we're in suppression mode.
- def save!(*args) # :nodoc:
+ def create_or_update(*args) # :nodoc:
SuppressorRegistry.suppressed[self.class.name] ? true : super
end
end
diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb
index 3dd6321a97..41f1c55c3c 100644
--- a/activerecord/lib/active_record/table_metadata.rb
+++ b/activerecord/lib/active_record/table_metadata.rb
@@ -41,7 +41,7 @@ module ActiveRecord
association = klass._reflect_on_association(table_name)
if association && !association.polymorphic?
association_klass = association.klass
- arel_table = association_klass.arel_table
+ arel_table = association_klass.arel_table.alias(table_name)
else
type_caster = TypeCaster::Connection.new(klass, table_name)
association_klass = nil
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index eafbb2c249..673386f0d9 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -59,6 +59,7 @@ module ActiveRecord
args = prepare_command_options('mysqldump')
args.concat(["--result-file", "#{filename}"])
args.concat(["--no-data"])
+ args.concat(["--routines"])
args.concat(["#{configuration['database']}"])
unless Kernel.system(*args)
$stderr.puts "Could not dump the database structure. "\
@@ -130,15 +131,21 @@ IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION;
end
def prepare_command_options(command)
- args = [command]
- args.concat(['--user', configuration['username']]) if configuration['username']
- args << "--password=#{configuration['password']}" if configuration['password']
- args.concat(['--default-character-set', configuration['encoding']]) if configuration['encoding']
- configuration.slice('host', 'port', 'socket').each do |k, v|
- args.concat([ "--#{k}", v.to_s ]) if v
- end
-
- args
+ args = {
+ 'host' => '--host',
+ 'port' => '--port',
+ 'socket' => '--socket',
+ 'username' => '--user',
+ 'password' => '--password',
+ 'encoding' => '--default-character-set',
+ 'sslca' => '--ssl-ca',
+ 'sslcert' => '--ssl-cert',
+ 'sslcapath' => '--ssl-capath',
+ 'sslcipher' => '--ssh-cipher',
+ 'sslkey' => '--ssl-key'
+ }.map { |opt, arg| "#{arg}=#{configuration[opt]}" if configuration[opt] }.compact
+
+ [command, *args]
end
end
end
diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
index d7da95c8a9..55f839444b 100644
--- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
@@ -1,5 +1,3 @@
-require 'shellwords'
-
module ActiveRecord
module Tasks # :nodoc:
class PostgreSQLDatabaseTasks # :nodoc:
@@ -55,19 +53,22 @@ module ActiveRecord
when String
ActiveRecord::Base.dump_schemas
end
+
+ args = ['-i', '-s', '-x', '-O', '-f', filename]
unless search_path.blank?
- search_path = search_path.split(",").map{|search_path_part| "--schema=#{Shellwords.escape(search_path_part.strip)}" }.join(" ")
+ args << search_path.split(',').map do |part|
+ "--schema=#{part.strip}"
+ end.join(' ')
end
-
- command = "pg_dump -i -s -x -O -f #{Shellwords.escape(filename)} #{search_path} #{Shellwords.escape(configuration['database'])}"
- raise 'Error dumping database' unless Kernel.system(command)
-
+ args << configuration['database']
+ run_cmd('pg_dump', args, 'dumping')
File.open(filename, "a") { |f| f << "SET search_path TO #{connection.schema_search_path};\n\n" }
end
def structure_load(filename)
set_psql_env
- Kernel.system("psql -X -q -f #{Shellwords.escape(filename)} #{configuration['database']}")
+ args = [ '-q', '-f', filename, configuration['database'] ]
+ run_cmd('psql', args, 'loading' )
end
private
@@ -93,6 +94,17 @@ module ActiveRecord
ENV['PGPASSWORD'] = configuration['password'].to_s if configuration['password']
ENV['PGUSER'] = configuration['username'].to_s if configuration['username']
end
+
+ def run_cmd(cmd, args, action)
+ fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args)
+ end
+
+ def run_cmd_error(cmd, args, action)
+ msg = "failed to execute:\n"
+ msg << "#{cmd} #{args.join(' ')}\n\n"
+ msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
+ msg
+ end
end
end
end
diff --git a/activerecord/lib/active_record/touch_later.rb b/activerecord/lib/active_record/touch_later.rb
new file mode 100644
index 0000000000..4352a0ffea
--- /dev/null
+++ b/activerecord/lib/active_record/touch_later.rb
@@ -0,0 +1,50 @@
+module ActiveRecord
+ # = Active Record Touch Later
+ module TouchLater
+ extend ActiveSupport::Concern
+
+ included do
+ before_commit_without_transaction_enrollment :touch_deferred_attributes
+ end
+
+ def touch_later(*names) # :nodoc:
+ raise ActiveRecordError, "cannot touch on a new record object" unless persisted?
+
+ @_defer_touch_attrs ||= timestamp_attributes_for_update_in_model
+ @_defer_touch_attrs |= names
+ @_touch_time = current_time_from_proper_timezone
+
+ surreptitiously_touch @_defer_touch_attrs
+ self.class.connection.add_transaction_record self
+ end
+
+ def touch(*names, time: nil) # :nodoc:
+ if has_defer_touch_attrs?
+ names |= @_defer_touch_attrs
+ end
+ super(*names, time: time)
+ end
+
+ private
+ def surreptitiously_touch(attrs)
+ attrs.each { |attr| write_attribute attr, @_touch_time }
+ clear_attribute_changes attrs
+ end
+
+ def touch_deferred_attributes
+ if has_defer_touch_attrs? && persisted?
+ @_touching_delayed_records = true
+ touch(*@_defer_touch_attrs, time: @_touch_time)
+ @_touching_delayed_records, @_defer_touch_attrs, @_touch_time = nil, nil, nil
+ end
+ end
+
+ def has_defer_touch_attrs?
+ defined?(@_defer_touch_attrs) && @_defer_touch_attrs.present?
+ end
+
+ def touching_delayed_records?
+ defined?(@_touching_delayed_records) && @_touching_delayed_records
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 311dacb449..887d7a5903 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -196,17 +196,16 @@ module ActiveRecord
# automatically released. The following example demonstrates the problem:
#
# Model.connection.transaction do # BEGIN
- # Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
+ # Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
# Model.connection.create_table(...) # active_record_1 now automatically released
- # end # RELEASE savepoint active_record_1
+ # end # RELEASE SAVEPOINT active_record_1
# # ^^^^ BOOM! database error!
# end
#
# Note that "TRUNCATE" is also a MySQL DDL statement!
module ClassMethods
- # See ActiveRecord::Transactions::ClassMethods for detailed documentation.
+ # See the ConnectionAdapters::DatabaseStatements#transaction API docs.
def transaction(options = {}, &block)
- # See the ConnectionAdapters::DatabaseStatements#transaction API docs.
connection.transaction(options, &block)
end
@@ -319,8 +318,8 @@ module ActiveRecord
end
def before_committed! # :nodoc:
- run_callbacks :before_commit_without_transaction_enrollment
- run_callbacks :before_commit
+ _run_before_commit_without_transaction_enrollment_callbacks
+ _run_before_commit_callbacks
end
# Call the +after_commit+ callbacks.
@@ -329,8 +328,8 @@ module ActiveRecord
# but call it after the commit of a destroyed object.
def committed!(should_run_callbacks: true) #:nodoc:
if should_run_callbacks && destroyed? || persisted?
- run_callbacks :commit_without_transaction_enrollment
- run_callbacks :commit
+ _run_commit_without_transaction_enrollment_callbacks
+ _run_commit_callbacks
end
ensure
force_clear_transaction_record_state
@@ -340,8 +339,8 @@ module ActiveRecord
# state should be rolled back to the beginning or just to the last savepoint.
def rolledback!(force_restore_state: false, should_run_callbacks: true) #:nodoc:
if should_run_callbacks
- run_callbacks :rollback
- run_callbacks :rollback_without_transaction_enrollment
+ _run_rollback_callbacks
+ _run_rollback_without_transaction_enrollment_callbacks
end
ensure
restore_transaction_record_state(force_restore_state)
@@ -380,6 +379,10 @@ module ActiveRecord
raise ActiveRecord::Rollback unless status
end
status
+ ensure
+ if @transaction_state && @transaction_state.committed?
+ clear_transaction_record_state
+ end
end
protected
diff --git a/activerecord/lib/active_record/type/decimal.rb b/activerecord/lib/active_record/type/decimal.rb
index 867b5f75c7..f200a92d10 100644
--- a/activerecord/lib/active_record/type/decimal.rb
+++ b/activerecord/lib/active_record/type/decimal.rb
@@ -8,7 +8,7 @@ module ActiveRecord
end
def type_cast_for_schema(value)
- value.to_s
+ value.to_s.inspect
end
private
diff --git a/activerecord/lib/active_record/type/integer.rb b/activerecord/lib/active_record/type/integer.rb
index 2a1b04ac7f..c5040c6d3b 100644
--- a/activerecord/lib/active_record/type/integer.rb
+++ b/activerecord/lib/active_record/type/integer.rb
@@ -46,18 +46,21 @@ module ActiveRecord
def ensure_in_range(value)
unless range.cover?(value)
- raise RangeError, "#{value} is out of range for #{self.class} with limit #{limit || DEFAULT_LIMIT}"
+ raise RangeError, "#{value} is out of range for #{self.class} with limit #{_limit}"
end
end
def max_value
- limit = self.limit || DEFAULT_LIMIT
- 1 << (limit * 8 - 1) # 8 bits per byte with one bit for sign
+ 1 << (_limit * 8 - 1) # 8 bits per byte with one bit for sign
end
def min_value
-max_value
end
+
+ def _limit
+ self.limit || DEFAULT_LIMIT
+ end
end
end
end
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index e227212827..34d96b19fe 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -76,4 +76,5 @@ end
require "active_record/validations/associated"
require "active_record/validations/uniqueness"
require "active_record/validations/presence"
+require "active_record/validations/absence"
require "active_record/validations/length"
diff --git a/activerecord/lib/active_record/validations/absence.rb b/activerecord/lib/active_record/validations/absence.rb
new file mode 100644
index 0000000000..2e19e6dc5c
--- /dev/null
+++ b/activerecord/lib/active_record/validations/absence.rb
@@ -0,0 +1,24 @@
+module ActiveRecord
+ module Validations
+ class AbsenceValidator < ActiveModel::Validations::AbsenceValidator # :nodoc:
+ def validate_each(record, attribute, association_or_value)
+ return unless should_validate?(record)
+ if record.class._reflect_on_association(attribute)
+ association_or_value = Array.wrap(association_or_value).reject(&:marked_for_destruction?)
+ end
+ super
+ end
+ end
+
+ module ClassMethods
+ # Validates that the specified attributes are not present (as defined by
+ # Object#present?). If the attribute is an association, the associated object
+ # is considered absent if it was marked for destruction.
+ #
+ # See ActiveModel::Validations::HelperMethods.validates_absence_of for more information.
+ def validates_absence_of(*attr_names)
+ validates_with AbsenceValidator, _merge_attributes(attr_names)
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/validations/length.rb b/activerecord/lib/active_record/validations/length.rb
index 5991fbad8e..69e048eef1 100644
--- a/activerecord/lib/active_record/validations/length.rb
+++ b/activerecord/lib/active_record/validations/length.rb
@@ -22,7 +22,10 @@ module ActiveRecord
end
module ClassMethods
- # See <tt>ActiveModel::Validation::LengthValidator</tt> for more information.
+ # Validates that the specified attributes match the length restrictions supplied.
+ # If the attribute is an association, records that are marked for destruction are not counted.
+ #
+ # See ActiveModel::Validations::HelperMethods.validates_length_of for more information.
def validates_length_of(*attr_names)
validates_with LengthValidator, _merge_attributes(attr_names)
end
diff --git a/activerecord/lib/active_record/validations/presence.rb b/activerecord/lib/active_record/validations/presence.rb
index 75d5bd5a35..23a3985d35 100644
--- a/activerecord/lib/active_record/validations/presence.rb
+++ b/activerecord/lib/active_record/validations/presence.rb
@@ -1,18 +1,12 @@
module ActiveRecord
module Validations
class PresenceValidator < ActiveModel::Validations::PresenceValidator # :nodoc:
- def validate(record)
+ def validate_each(record, attribute, association_or_value)
return unless should_validate?(record)
- super
- attributes.each do |attribute|
- next unless record.class._reflect_on_association(attribute)
- associated_records = Array.wrap(record.send(attribute))
-
- # Superclass validates presence. Ensure present records aren't about to be destroyed.
- if associated_records.present? && associated_records.all?(&:marked_for_destruction?)
- record.errors.add(attribute, :blank, options)
- end
+ if record.class._reflect_on_association(attribute)
+ association_or_value = Array.wrap(association_or_value).reject(&:marked_for_destruction?)
end
+ super
end
end
@@ -43,6 +37,10 @@ module ActiveRecord
# deletes the associated object, thus putting the parent object into an invalid
# state.
#
+ # NOTE: This validation will not fail while using it with an association
+ # if the latter was assigned but not valid. If you want to ensure that
+ # it is both present and valid, you also need to use +validates_associated+.
+ #
# 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.
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 9be4b10a55..32d17a1392 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -17,7 +17,9 @@ module ActiveRecord
value = map_enum_attribute(finder_class, attribute, value)
relation = build_relation(finder_class, table, attribute, value)
- relation = relation.where.not(finder_class.primary_key => record.id) if record.persisted?
+ if record.persisted? && finder_class.primary_key.to_s != attribute.to_s
+ relation = relation.where.not(finder_class.primary_key => record.id)
+ end
relation = scope_relation(record, table, relation)
relation = relation.merge(options[:conditions]) if options[:conditions]
@@ -76,6 +78,8 @@ module ActiveRecord
klass.connection.case_sensitive_comparison(table, attribute, column, value)
end
klass.unscoped.where(comparison)
+ rescue RangeError
+ klass.none
end
def scope_relation(record, table, relation)
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 7a3c6f5e95..0d57de4d65 100644
--- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
@@ -14,10 +14,9 @@ module ActiveRecord
protected
attr_reader :migration_action, :join_tables
- # sets the default migration template that is being used for the generation of the migration
- # depending on the arguments which would be sent out in the command line, the migration template
- # and the table name instance variables are setup.
-
+ # 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
@@ -56,6 +55,8 @@ module ActiveRecord
attributes.select { |a| !a.reference? && a.has_index? }
end
+ # A migration file name can only contain underscores (_), lowercase characters,
+ # and numbers 0-9. Any other file name will raise an IllegalMigrationNameError.
def validate_file_name!
unless file_name =~ /^[_a-z0-9]+$/
raise IllegalMigrationNameError.new(file_name)
diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
index 6577d56240..f0fd95ac16 100644
--- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
@@ -1,7 +1,7 @@
require "cases/helper"
require 'support/connection_helper'
-class ActiveSchemaTest < ActiveRecord::TestCase
+class MysqlActiveSchemaTest < ActiveRecord::MysqlTestCase
include ConnectionHelper
def setup
@@ -59,6 +59,43 @@ class ActiveSchemaTest < ActiveRecord::TestCase
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15, :using => :btree)
end
+ def test_index_in_create
+ def (ActiveRecord::Base.connection).table_exists?(*); false; end
+
+ %w(SPATIAL FULLTEXT UNIQUE).each do |type|
+ expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`) ) ENGINE=InnoDB"
+ actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
+ t.index :last_name, type: type
+ end
+ assert_equal expected, actual
+ end
+
+ expected = "CREATE TABLE `people` ( INDEX `index_people_on_last_name` USING btree (`last_name`(10)) ) ENGINE=InnoDB"
+ actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
+ t.index :last_name, length: 10, using: :btree
+ end
+ assert_equal expected, actual
+ end
+
+ def test_index_in_bulk_change
+ def (ActiveRecord::Base.connection).table_exists?(*); true; end
+ def (ActiveRecord::Base.connection).index_name_exists?(*); false; end
+
+ %w(SPATIAL FULLTEXT UNIQUE).each do |type|
+ expected = "ALTER TABLE `people` ADD #{type} INDEX `index_people_on_last_name` (`last_name`)"
+ actual = ActiveRecord::Base.connection.change_table(:people, bulk: true) do |t|
+ t.index :last_name, type: type
+ end
+ assert_equal expected, actual
+ end
+
+ expected = "ALTER TABLE `peaple` ADD INDEX `index_peaple_on_last_name` USING btree (`last_name`(10)), ALGORITHM = COPY"
+ actual = ActiveRecord::Base.connection.change_table(:peaple, bulk: true) do |t|
+ t.index :last_name, length: 10, using: :btree, algorithm: :copy
+ end
+ assert_equal expected, actual
+ end
+
def test_drop_table
assert_equal "DROP TABLE `people`", drop_table(:people)
end
diff --git a/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb
index 340fc95503..98d44315dd 100644
--- a/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb
+++ b/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb
@@ -1,7 +1,6 @@
require "cases/helper"
-require 'models/person'
-class MysqlCaseSensitivityTest < ActiveRecord::TestCase
+class MysqlCaseSensitivityTest < ActiveRecord::MysqlTestCase
class CollationTest < ActiveRecord::Base
end
diff --git a/activerecord/test/cases/adapters/mysql/charset_collation_test.rb b/activerecord/test/cases/adapters/mysql/charset_collation_test.rb
new file mode 100644
index 0000000000..f2117a97e6
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql/charset_collation_test.rb
@@ -0,0 +1,54 @@
+require "cases/helper"
+require 'support/schema_dumping_helper'
+
+class MysqlCharsetCollationTest < ActiveRecord::MysqlTestCase
+ include SchemaDumpingHelper
+ self.use_transactional_tests = false
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table :charset_collations, force: true do |t|
+ t.string :string_ascii_bin, charset: 'ascii', collation: 'ascii_bin'
+ t.text :text_ucs2_unicode_ci, charset: 'ucs2', collation: 'ucs2_unicode_ci'
+ end
+ end
+
+ teardown do
+ @connection.drop_table :charset_collations, if_exists: true
+ end
+
+ test "string column with charset and collation" do
+ column = @connection.columns(:charset_collations).find { |c| c.name == 'string_ascii_bin' }
+ assert_equal :string, column.type
+ assert_equal 'ascii_bin', column.collation
+ end
+
+ test "text column with charset and collation" do
+ column = @connection.columns(:charset_collations).find { |c| c.name == 'text_ucs2_unicode_ci' }
+ assert_equal :text, column.type
+ assert_equal 'ucs2_unicode_ci', column.collation
+ end
+
+ test "add column with charset and collation" do
+ @connection.add_column :charset_collations, :title, :string, charset: 'utf8', collation: 'utf8_bin'
+
+ column = @connection.columns(:charset_collations).find { |c| c.name == 'title' }
+ assert_equal :string, column.type
+ assert_equal 'utf8_bin', column.collation
+ end
+
+ test "change column with charset and collation" do
+ @connection.add_column :charset_collations, :description, :string, charset: 'utf8', collation: 'utf8_unicode_ci'
+ @connection.change_column :charset_collations, :description, :text, charset: 'utf8', collation: 'utf8_general_ci'
+
+ column = @connection.columns(:charset_collations).find { |c| c.name == 'description' }
+ assert_equal :text, column.type
+ assert_equal 'utf8_general_ci', column.collation
+ end
+
+ test "schema dump includes collation" do
+ output = dump_table_schema("charset_collations")
+ assert_match %r{t.string\s+"string_ascii_bin",\s+limit: 255,\s+collation: "ascii_bin"$}, output
+ assert_match %r{t.text\s+"text_ucs2_unicode_ci",\s+limit: 65535,\s+collation: "ucs2_unicode_ci"$}, output
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb
index 4762ef43b5..ddbc007b87 100644
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -2,7 +2,7 @@ require "cases/helper"
require 'support/connection_helper'
require 'support/ddl_helper'
-class MysqlConnectionTest < ActiveRecord::TestCase
+class MysqlConnectionTest < ActiveRecord::MysqlTestCase
include ConnectionHelper
include DdlHelper
@@ -145,6 +145,15 @@ class MysqlConnectionTest < ActiveRecord::TestCase
end
end
+ def test_mysql_strict_mode_specified_default
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(orig_connection.merge({strict: :default}))
+ global_sql_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.sql_mode"
+ session_sql_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.sql_mode"
+ assert_equal global_sql_mode.rows, session_sql_mode.rows
+ end
+ end
+
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}}))
diff --git a/activerecord/test/cases/adapters/mysql/consistency_test.rb b/activerecord/test/cases/adapters/mysql/consistency_test.rb
index ae190b728d..743f6436e4 100644
--- a/activerecord/test/cases/adapters/mysql/consistency_test.rb
+++ b/activerecord/test/cases/adapters/mysql/consistency_test.rb
@@ -1,6 +1,6 @@
require "cases/helper"
-class MysqlConsistencyTest < ActiveRecord::TestCase
+class MysqlConsistencyTest < ActiveRecord::MysqlTestCase
self.use_transactional_tests = false
class Consistency < ActiveRecord::Base
diff --git a/activerecord/test/cases/adapters/mysql/enum_test.rb b/activerecord/test/cases/adapters/mysql/enum_test.rb
index f4e7a3ef0a..ef8ee0a6e3 100644
--- a/activerecord/test/cases/adapters/mysql/enum_test.rb
+++ b/activerecord/test/cases/adapters/mysql/enum_test.rb
@@ -1,6 +1,6 @@
require "cases/helper"
-class MysqlEnumTest < ActiveRecord::TestCase
+class MysqlEnumTest < ActiveRecord::MysqlTestCase
class EnumTest < ActiveRecord::Base
end
diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
index 48ceef365e..b804cb45b9 100644
--- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
@@ -4,7 +4,7 @@ require 'support/ddl_helper'
module ActiveRecord
module ConnectionAdapters
- class MysqlAdapterTest < ActiveRecord::TestCase
+ class MysqlAdapterTest < ActiveRecord::MysqlTestCase
include DdlHelper
def setup
diff --git a/activerecord/test/cases/adapters/mysql/quoting_test.rb b/activerecord/test/cases/adapters/mysql/quoting_test.rb
index a2206153e9..2024aa36ab 100644
--- a/activerecord/test/cases/adapters/mysql/quoting_test.rb
+++ b/activerecord/test/cases/adapters/mysql/quoting_test.rb
@@ -1,21 +1,29 @@
require "cases/helper"
-module ActiveRecord
- module ConnectionAdapters
- class MysqlAdapter
- class QuotingTest < ActiveRecord::TestCase
- def setup
- @conn = ActiveRecord::Base.connection
- end
+class MysqlQuotingTest < ActiveRecord::MysqlTestCase
+ def setup
+ @conn = ActiveRecord::Base.connection
+ end
+
+ def test_type_cast_true
+ assert_equal 1, @conn.type_cast(true)
+ end
- def test_type_cast_true
- assert_equal 1, @conn.type_cast(true)
- end
+ def test_type_cast_false
+ assert_equal 0, @conn.type_cast(false)
+ end
+
+ def test_quoted_date_precision_for_gte_564
+ @conn.stubs(:full_version).returns('5.6.4')
+ @conn.remove_instance_variable(:@version) if @conn.instance_variable_defined?(:@version)
+ t = Time.now.change(usec: 1)
+ assert_match(/\.000001\z/, @conn.quoted_date(t))
+ end
- def test_type_cast_false
- assert_equal 0, @conn.type_cast(false)
- end
- end
- end
+ def test_quoted_date_precision_for_lt_564
+ @conn.stubs(:full_version).returns('5.6.3')
+ @conn.remove_instance_variable(:@version) if @conn.instance_variable_defined?(:@version)
+ t = Time.now.change(usec: 1)
+ assert_no_match(/\.000001\z/, @conn.quoted_date(t))
end
end
diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
index ec1c394f40..4ea1d9ad36 100644
--- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
@@ -1,29 +1,29 @@
require "cases/helper"
-class Group < ActiveRecord::Base
- Group.table_name = 'group'
- belongs_to :select
- has_one :values
-end
+# a suite of tests to ensure the ConnectionAdapters#MysqlAdapter can handle tables with
+# reserved word names (ie: group, order, values, etc...)
+class MysqlReservedWordTest < ActiveRecord::MysqlTestCase
+ class Group < ActiveRecord::Base
+ Group.table_name = 'group'
+ belongs_to :select
+ has_one :values
+ end
-class Select < ActiveRecord::Base
- Select.table_name = 'select'
- has_many :groups
-end
+ class Select < ActiveRecord::Base
+ Select.table_name = 'select'
+ has_many :groups
+ end
-class Values < ActiveRecord::Base
- Values.table_name = 'values'
-end
+ class Values < ActiveRecord::Base
+ Values.table_name = 'values'
+ end
-class Distinct < ActiveRecord::Base
- Distinct.table_name = 'distinct'
- has_and_belongs_to_many :selects
- has_many :values, :through => :groups
-end
+ class Distinct < ActiveRecord::Base
+ Distinct.table_name = 'distinct'
+ has_and_belongs_to_many :selects
+ has_many :values, :through => :groups
+ end
-# a suite of tests to ensure the ConnectionAdapters#MysqlAdapter can handle tables with
-# reserved word names (ie: group, order, values, etc...)
-class MysqlReservedWordTest < ActiveRecord::TestCase
def setup
@connection = ActiveRecord::Base.connection
diff --git a/activerecord/test/cases/adapters/mysql/schema_test.rb b/activerecord/test/cases/adapters/mysql/schema_test.rb
index b7f9c2ce84..2e18f609fd 100644
--- a/activerecord/test/cases/adapters/mysql/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/schema_test.rb
@@ -4,7 +4,7 @@ require 'models/comment'
module ActiveRecord
module ConnectionAdapters
- class MysqlSchemaTest < ActiveRecord::TestCase
+ class MysqlSchemaTest < ActiveRecord::MysqlTestCase
fixtures :posts
def setup
diff --git a/activerecord/test/cases/adapters/mysql/sp_test.rb b/activerecord/test/cases/adapters/mysql/sp_test.rb
index 3ca2917ca4..a3d5110032 100644
--- a/activerecord/test/cases/adapters/mysql/sp_test.rb
+++ b/activerecord/test/cases/adapters/mysql/sp_test.rb
@@ -1,11 +1,11 @@
require "cases/helper"
require 'models/topic'
-class StoredProcedureTest < ActiveRecord::TestCase
+class StoredProcedureTest < ActiveRecord::MysqlTestCase
fixtures :topics
# Test that MySQL allows multiple results for stored procedures
- if Mysql.const_defined?(:CLIENT_MULTI_RESULTS)
+ if defined?(Mysql) && Mysql.const_defined?(:CLIENT_MULTI_RESULTS)
def test_multi_results_from_find_by_sql
topics = Topic.find_by_sql 'CALL topics();'
assert_equal 1, topics.size
diff --git a/activerecord/test/cases/adapters/mysql/sql_types_test.rb b/activerecord/test/cases/adapters/mysql/sql_types_test.rb
index 1ddb1b91c9..25b28de7f0 100644
--- a/activerecord/test/cases/adapters/mysql/sql_types_test.rb
+++ b/activerecord/test/cases/adapters/mysql/sql_types_test.rb
@@ -1,6 +1,6 @@
require "cases/helper"
-class SqlTypesTest < ActiveRecord::TestCase
+class MysqlSqlTypesTest < ActiveRecord::MysqlTestCase
def test_binary_types
assert_equal 'varbinary(64)', type_to_sql(:binary, 64)
assert_equal 'varbinary(4095)', type_to_sql(:binary, 4095)
diff --git a/activerecord/test/cases/adapters/mysql/statement_pool_test.rb b/activerecord/test/cases/adapters/mysql/statement_pool_test.rb
index 209a0cf464..6be36566de 100644
--- a/activerecord/test/cases/adapters/mysql/statement_pool_test.rb
+++ b/activerecord/test/cases/adapters/mysql/statement_pool_test.rb
@@ -1,23 +1,19 @@
require 'cases/helper'
-module ActiveRecord::ConnectionAdapters
- class MysqlAdapter
- class StatementPoolTest < ActiveRecord::TestCase
- if Process.respond_to?(:fork)
- def test_cache_is_per_pid
- cache = StatementPool.new nil, 10
- cache['foo'] = 'bar'
- assert_equal 'bar', cache['foo']
+class MysqlStatementPoolTest < ActiveRecord::MysqlTestCase
+ if Process.respond_to?(:fork)
+ def test_cache_is_per_pid
+ cache = ActiveRecord::ConnectionAdapters::MysqlAdapter::StatementPool.new nil, 10
+ cache['foo'] = 'bar'
+ assert_equal 'bar', cache['foo']
- pid = fork {
- lookup = cache['foo'];
- exit!(!lookup)
- }
+ pid = fork {
+ lookup = cache['foo'];
+ exit!(!lookup)
+ }
- Process.waitpid pid
- assert $?.success?, 'process should exit successfully'
- end
- end
+ Process.waitpid pid
+ assert $?.success?, 'process should exit successfully'
end
end
end
diff --git a/activerecord/test/cases/adapters/mysql/table_options_test.rb b/activerecord/test/cases/adapters/mysql/table_options_test.rb
new file mode 100644
index 0000000000..99df6d6cba
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql/table_options_test.rb
@@ -0,0 +1,42 @@
+require "cases/helper"
+require 'support/schema_dumping_helper'
+
+class MysqlTableOptionsTest < ActiveRecord::MysqlTestCase
+ include SchemaDumpingHelper
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def teardown
+ @connection.drop_table "mysql_table_options", if_exists: true
+ end
+
+ test "table options with ENGINE" do
+ @connection.create_table "mysql_table_options", force: true, options: "ENGINE=MyISAM"
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{ENGINE=MyISAM}, options
+ end
+
+ test "table options with ROW_FORMAT" do
+ @connection.create_table "mysql_table_options", force: true, options: "ROW_FORMAT=REDUNDANT"
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{ROW_FORMAT=REDUNDANT}, options
+ end
+
+ test "table options with CHARSET" do
+ @connection.create_table "mysql_table_options", force: true, options: "CHARSET=utf8mb4"
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{CHARSET=utf8mb4}, options
+ end
+
+ test "table options with COLLATE" do
+ @connection.create_table "mysql_table_options", force: true, options: "COLLATE=utf8mb4_bin"
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{COLLATE=utf8mb4_bin}, options
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb
index e9edc53f93..ed9398a918 100644
--- a/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb
+++ b/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb
@@ -1,6 +1,6 @@
require "cases/helper"
-class UnsignedTypeTest < ActiveRecord::TestCase
+class MysqlUnsignedTypeTest < ActiveRecord::MysqlTestCase
self.use_transactional_tests = false
class UnsignedType < ActiveRecord::Base
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
index e87cd3886a..6558d60aa1 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -1,7 +1,7 @@
require "cases/helper"
require 'support/connection_helper'
-class ActiveSchemaTest < ActiveRecord::TestCase
+class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
include ConnectionHelper
def setup
@@ -59,6 +59,43 @@ class ActiveSchemaTest < ActiveRecord::TestCase
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15, :using => :btree)
end
+ def test_index_in_create
+ def (ActiveRecord::Base.connection).table_exists?(*); false; end
+
+ %w(SPATIAL FULLTEXT UNIQUE).each do |type|
+ expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`) ) ENGINE=InnoDB"
+ actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
+ t.index :last_name, type: type
+ end
+ assert_equal expected, actual
+ end
+
+ expected = "CREATE TABLE `people` ( INDEX `index_people_on_last_name` USING btree (`last_name`(10)) ) ENGINE=InnoDB"
+ actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
+ t.index :last_name, length: 10, using: :btree
+ end
+ assert_equal expected, actual
+ end
+
+ def test_index_in_bulk_change
+ def (ActiveRecord::Base.connection).table_exists?(*); true; end
+ def (ActiveRecord::Base.connection).index_name_exists?(*); false; end
+
+ %w(SPATIAL FULLTEXT UNIQUE).each do |type|
+ expected = "ALTER TABLE `people` ADD #{type} INDEX `index_people_on_last_name` (`last_name`)"
+ actual = ActiveRecord::Base.connection.change_table(:people, bulk: true) do |t|
+ t.index :last_name, type: type
+ end
+ assert_equal expected, actual
+ end
+
+ expected = "ALTER TABLE `peaple` ADD INDEX `index_peaple_on_last_name` USING btree (`last_name`(10)), ALGORITHM = COPY"
+ actual = ActiveRecord::Base.connection.change_table(:peaple, bulk: true) do |t|
+ t.index :last_name, length: 10, using: :btree, algorithm: :copy
+ end
+ assert_equal expected, actual
+ end
+
def test_drop_table
assert_equal "DROP TABLE `people`", drop_table(:people)
end
diff --git a/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb
index 5e8065d80d..abdf3dbf5b 100644
--- a/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb
@@ -4,7 +4,7 @@ require 'models/topic'
module ActiveRecord
module ConnectionAdapters
class Mysql2Adapter
- class BindParameterTest < ActiveRecord::TestCase
+ class BindParameterTest < ActiveRecord::Mysql2TestCase
fixtures :topics
def test_update_question_marks
diff --git a/activerecord/test/cases/adapters/mysql2/boolean_test.rb b/activerecord/test/cases/adapters/mysql2/boolean_test.rb
index 0d81dd6eee..8575df9e43 100644
--- a/activerecord/test/cases/adapters/mysql2/boolean_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/boolean_test.rb
@@ -1,6 +1,6 @@
require "cases/helper"
-class Mysql2BooleanTest < ActiveRecord::TestCase
+class Mysql2BooleanTest < ActiveRecord::Mysql2TestCase
self.use_transactional_tests = false
class BooleanType < ActiveRecord::Base
diff --git a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
index 09bebf3071..963116f08a 100644
--- a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
@@ -1,7 +1,6 @@
require "cases/helper"
-require 'models/person'
-class Mysql2CaseSensitivityTest < ActiveRecord::TestCase
+class Mysql2CaseSensitivityTest < ActiveRecord::Mysql2TestCase
class CollationTest < ActiveRecord::Base
end
diff --git a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
new file mode 100644
index 0000000000..4fd34def15
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
@@ -0,0 +1,54 @@
+require "cases/helper"
+require 'support/schema_dumping_helper'
+
+class Mysql2CharsetCollationTest < ActiveRecord::Mysql2TestCase
+ include SchemaDumpingHelper
+ self.use_transactional_tests = false
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table :charset_collations, force: true do |t|
+ t.string :string_ascii_bin, charset: 'ascii', collation: 'ascii_bin'
+ t.text :text_ucs2_unicode_ci, charset: 'ucs2', collation: 'ucs2_unicode_ci'
+ end
+ end
+
+ teardown do
+ @connection.drop_table :charset_collations, if_exists: true
+ end
+
+ test "string column with charset and collation" do
+ column = @connection.columns(:charset_collations).find { |c| c.name == 'string_ascii_bin' }
+ assert_equal :string, column.type
+ assert_equal 'ascii_bin', column.collation
+ end
+
+ test "text column with charset and collation" do
+ column = @connection.columns(:charset_collations).find { |c| c.name == 'text_ucs2_unicode_ci' }
+ assert_equal :text, column.type
+ assert_equal 'ucs2_unicode_ci', column.collation
+ end
+
+ test "add column with charset and collation" do
+ @connection.add_column :charset_collations, :title, :string, charset: 'utf8', collation: 'utf8_bin'
+
+ column = @connection.columns(:charset_collations).find { |c| c.name == 'title' }
+ assert_equal :string, column.type
+ assert_equal 'utf8_bin', column.collation
+ end
+
+ test "change column with charset and collation" do
+ @connection.add_column :charset_collations, :description, :string, charset: 'utf8', collation: 'utf8_unicode_ci'
+ @connection.change_column :charset_collations, :description, :text, charset: 'utf8', collation: 'utf8_general_ci'
+
+ column = @connection.columns(:charset_collations).find { |c| c.name == 'description' }
+ assert_equal :text, column.type
+ assert_equal 'utf8_general_ci', column.collation
+ end
+
+ test "schema dump includes collation" do
+ output = dump_table_schema("charset_collations")
+ assert_match %r{t.string\s+"string_ascii_bin",\s+limit: 255,\s+collation: "ascii_bin"$}, output
+ assert_match %r{t.text\s+"text_ucs2_unicode_ci",\s+limit: 65535,\s+collation: "ucs2_unicode_ci"$}, output
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index a8b39b21d4..000bcadebe 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -1,7 +1,7 @@
require "cases/helper"
require 'support/connection_helper'
-class MysqlConnectionTest < ActiveRecord::TestCase
+class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
include ConnectionHelper
fixtures :comments
@@ -84,6 +84,15 @@ class MysqlConnectionTest < ActiveRecord::TestCase
end
end
+ def test_mysql_strict_mode_specified_default
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(orig_connection.merge({strict: :default}))
+ global_sql_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.sql_mode"
+ session_sql_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.sql_mode"
+ assert_equal global_sql_mode.rows, session_sql_mode.rows
+ end
+ end
+
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}}))
diff --git a/activerecord/test/cases/adapters/mysql2/enum_test.rb b/activerecord/test/cases/adapters/mysql2/enum_test.rb
index 6dd9a5ec87..bd732b5eca 100644
--- a/activerecord/test/cases/adapters/mysql2/enum_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/enum_test.rb
@@ -1,6 +1,6 @@
require "cases/helper"
-class Mysql2EnumTest < ActiveRecord::TestCase
+class Mysql2EnumTest < ActiveRecord::Mysql2TestCase
class EnumTest < ActiveRecord::Base
end
diff --git a/activerecord/test/cases/adapters/mysql2/explain_test.rb b/activerecord/test/cases/adapters/mysql2/explain_test.rb
index 2b01d941b8..4fc7414b18 100644
--- a/activerecord/test/cases/adapters/mysql2/explain_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/explain_test.rb
@@ -5,7 +5,7 @@ require 'models/computer'
module ActiveRecord
module ConnectionAdapters
class Mysql2Adapter
- class ExplainTest < ActiveRecord::TestCase
+ class ExplainTest < ActiveRecord::Mysql2TestCase
fixtures :developers
def test_explain_for_one_query
diff --git a/activerecord/test/cases/adapters/mysql2/quoting_test.rb b/activerecord/test/cases/adapters/mysql2/quoting_test.rb
new file mode 100644
index 0000000000..2de7e1b526
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/quoting_test.rb
@@ -0,0 +1,21 @@
+require "cases/helper"
+
+class Mysql2QuotingTest < ActiveRecord::Mysql2TestCase
+ setup do
+ @connection = ActiveRecord::Base.connection
+ end
+
+ test 'quoted date precision for gte 5.6.4' do
+ @connection.stubs(:full_version).returns('5.6.4')
+ @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
+ t = Time.now.change(usec: 1)
+ assert_match(/\.000001\z/, @connection.quoted_date(t))
+ end
+
+ test 'quoted date precision for lt 5.6.4' do
+ @connection.stubs(:full_version).returns('5.6.3')
+ @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
+ t = Time.now.change(usec: 1)
+ assert_no_match(/\.000001\z/, @connection.quoted_date(t))
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
index 799e60a683..ffb4e2c5cf 100644
--- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
@@ -1,29 +1,29 @@
require "cases/helper"
-class Group < ActiveRecord::Base
- Group.table_name = 'group'
- belongs_to :select
- has_one :values
-end
+# a suite of tests to ensure the ConnectionAdapters#MysqlAdapter can handle tables with
+# reserved word names (ie: group, order, values, etc...)
+class Mysql2ReservedWordTest < ActiveRecord::Mysql2TestCase
+ class Group < ActiveRecord::Base
+ Group.table_name = 'group'
+ belongs_to :select
+ has_one :values
+ end
-class Select < ActiveRecord::Base
- Select.table_name = 'select'
- has_many :groups
-end
+ class Select < ActiveRecord::Base
+ Select.table_name = 'select'
+ has_many :groups
+ end
-class Values < ActiveRecord::Base
- Values.table_name = 'values'
-end
+ class Values < ActiveRecord::Base
+ Values.table_name = 'values'
+ end
-class Distinct < ActiveRecord::Base
- Distinct.table_name = 'distinct'
- has_and_belongs_to_many :selects
- has_many :values, :through => :groups
-end
+ class Distinct < ActiveRecord::Base
+ Distinct.table_name = 'distinct'
+ has_and_belongs_to_many :selects
+ has_many :values, :through => :groups
+ end
-# a suite of tests to ensure the ConnectionAdapters#MysqlAdapter can handle tables with
-# reserved word names (ie: group, order, values, etc...)
-class MysqlReservedWordTest < ActiveRecord::TestCase
def setup
@connection = ActiveRecord::Base.connection
diff --git a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
index 417ccf6d11..396f235e77 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
@@ -1,47 +1,42 @@
require "cases/helper"
-module ActiveRecord
- module ConnectionAdapters
- class AbstractMysqlAdapter
- class SchemaMigrationsTest < ActiveRecord::TestCase
- def test_renaming_index_on_foreign_key
- connection.add_index "engines", "car_id"
- connection.add_foreign_key :engines, :cars, name: "fk_engines_cars"
-
- connection.rename_index("engines", "index_engines_on_car_id", "idx_renamed")
- assert_equal ["idx_renamed"], connection.indexes("engines").map(&:name)
- ensure
- connection.remove_foreign_key :engines, name: "fk_engines_cars"
- end
-
- def test_initializes_schema_migrations_for_encoding_utf8mb4
- smtn = ActiveRecord::Migrator.schema_migrations_table_name
- connection.drop_table smtn, if_exists: true
-
- database_name = connection.current_database
- database_info = connection.select_one("SELECT * FROM information_schema.schemata WHERE schema_name = '#{database_name}'")
-
- original_charset = database_info["DEFAULT_CHARACTER_SET_NAME"]
- original_collation = database_info["DEFAULT_COLLATION_NAME"]
-
- execute("ALTER DATABASE #{database_name} DEFAULT CHARACTER SET utf8mb4")
-
- connection.initialize_schema_migrations_table
-
- assert connection.column_exists?(smtn, :version, :string, limit: AbstractMysqlAdapter::MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN)
- ensure
- execute("ALTER DATABASE #{database_name} DEFAULT CHARACTER SET #{original_charset} COLLATE #{original_collation}")
- end
-
- private
- def connection
- @connection ||= ActiveRecord::Base.connection
- end
-
- def execute(sql)
- connection.execute(sql)
- end
- end
- end
+class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase
+ def test_renaming_index_on_foreign_key
+ connection.add_index "engines", "car_id"
+ connection.add_foreign_key :engines, :cars, name: "fk_engines_cars"
+
+ connection.rename_index("engines", "index_engines_on_car_id", "idx_renamed")
+ assert_equal ["idx_renamed"], connection.indexes("engines").map(&:name)
+ ensure
+ connection.remove_foreign_key :engines, name: "fk_engines_cars"
+ end
+
+ def test_initializes_schema_migrations_for_encoding_utf8mb4
+ smtn = ActiveRecord::Migrator.schema_migrations_table_name
+ connection.drop_table smtn, if_exists: true
+
+ database_name = connection.current_database
+ database_info = connection.select_one("SELECT * FROM information_schema.schemata WHERE schema_name = '#{database_name}'")
+
+ original_charset = database_info["DEFAULT_CHARACTER_SET_NAME"]
+ original_collation = database_info["DEFAULT_COLLATION_NAME"]
+
+ execute("ALTER DATABASE #{database_name} DEFAULT CHARACTER SET utf8mb4")
+
+ connection.initialize_schema_migrations_table
+
+ limit = ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN
+ assert connection.column_exists?(smtn, :version, :string, limit: limit)
+ ensure
+ execute("ALTER DATABASE #{database_name} DEFAULT CHARACTER SET #{original_charset} COLLATE #{original_collation}")
+ end
+
+ private
+ def connection
+ @connection ||= ActiveRecord::Base.connection
+ end
+
+ def execute(sql)
+ connection.execute(sql)
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb
index 47707b7d4f..880a2123d2 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb
@@ -4,7 +4,7 @@ require 'models/comment'
module ActiveRecord
module ConnectionAdapters
- class Mysql2SchemaTest < ActiveRecord::TestCase
+ class Mysql2SchemaTest < ActiveRecord::Mysql2TestCase
fixtures :posts
def setup
diff --git a/activerecord/test/cases/adapters/mysql2/sql_types_test.rb b/activerecord/test/cases/adapters/mysql2/sql_types_test.rb
index 1ddb1b91c9..ae505d29c9 100644
--- a/activerecord/test/cases/adapters/mysql2/sql_types_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/sql_types_test.rb
@@ -1,6 +1,6 @@
require "cases/helper"
-class SqlTypesTest < ActiveRecord::TestCase
+class Mysql2SqlTypesTest < ActiveRecord::Mysql2TestCase
def test_binary_types
assert_equal 'varbinary(64)', type_to_sql(:binary, 64)
assert_equal 'varbinary(4095)', type_to_sql(:binary, 4095)
diff --git a/activerecord/test/cases/adapters/mysql2/table_options_test.rb b/activerecord/test/cases/adapters/mysql2/table_options_test.rb
new file mode 100644
index 0000000000..af121ee7d9
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/table_options_test.rb
@@ -0,0 +1,42 @@
+require "cases/helper"
+require 'support/schema_dumping_helper'
+
+class Mysql2TableOptionsTest < ActiveRecord::Mysql2TestCase
+ include SchemaDumpingHelper
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def teardown
+ @connection.drop_table "mysql_table_options", if_exists: true
+ end
+
+ test "table options with ENGINE" do
+ @connection.create_table "mysql_table_options", force: true, options: "ENGINE=MyISAM"
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{ENGINE=MyISAM}, options
+ end
+
+ test "table options with ROW_FORMAT" do
+ @connection.create_table "mysql_table_options", force: true, options: "ROW_FORMAT=REDUNDANT"
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{ROW_FORMAT=REDUNDANT}, options
+ end
+
+ test "table options with CHARSET" do
+ @connection.create_table "mysql_table_options", force: true, options: "CHARSET=utf8mb4"
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{CHARSET=utf8mb4}, options
+ end
+
+ test "table options with COLLATE" do
+ @connection.create_table "mysql_table_options", force: true, options: "COLLATE=utf8mb4_bin"
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", force: :cascade, options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{COLLATE=utf8mb4_bin}, options
+ 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 e9edc53f93..9e06db2519 100644
--- a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
@@ -1,6 +1,6 @@
require "cases/helper"
-class UnsignedTypeTest < ActiveRecord::TestCase
+class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase
self.use_transactional_tests = false
class UnsignedType < ActiveRecord::Base
diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
index 3808db5141..dc7ba314c6 100644
--- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
@@ -1,6 +1,6 @@
require 'cases/helper'
-class PostgresqlActiveSchemaTest < ActiveRecord::TestCase
+class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase
def setup
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
def execute(sql, name = nil) sql end
diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb
index 6edbd9c3a6..380a90d765 100644
--- a/activerecord/test/cases/adapters/postgresql/array_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/array_test.rb
@@ -1,10 +1,9 @@
require "cases/helper"
require 'support/schema_dumping_helper'
-class PostgresqlArrayTest < ActiveRecord::TestCase
+class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
include SchemaDumpingHelper
include InTimeZone
- OID = ActiveRecord::ConnectionAdapters::PostgreSQL::OID
class PgArray < ActiveRecord::Base
self.table_name = 'pg_arrays'
@@ -212,8 +211,9 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
def test_quoting_non_standard_delimiters
strings = ["hello,", "world;"]
- comma_delim = OID::Array.new(ActiveRecord::Type::String.new, ',')
- semicolon_delim = OID::Array.new(ActiveRecord::Type::String.new, ';')
+ oid = ActiveRecord::ConnectionAdapters::PostgreSQL::OID
+ comma_delim = oid::Array.new(ActiveRecord::Type::String.new, ',')
+ semicolon_delim = oid::Array.new(ActiveRecord::Type::String.new, ';')
assert_equal %({"hello,",world;}), comma_delim.serialize(strings)
assert_equal %({hello,;"world;"}), semicolon_delim.serialize(strings)
diff --git a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
index 1a5ff4316c..6f72fa6e0f 100644
--- a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
@@ -2,7 +2,7 @@ require "cases/helper"
require 'support/connection_helper'
require 'support/schema_dumping_helper'
-class PostgresqlBitStringTest < ActiveRecord::TestCase
+class PostgresqlBitStringTest < ActiveRecord::PostgreSQLTestCase
include ConnectionHelper
include SchemaDumpingHelper
diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
index 16db5ab83d..b6bb1929e6 100644
--- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
@@ -1,6 +1,6 @@
require "cases/helper"
-class PostgresqlByteaTest < ActiveRecord::TestCase
+class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase
class ByteaDataType < ActiveRecord::Base
self.table_name = 'bytea_data_type'
end
diff --git a/activerecord/test/cases/adapters/postgresql/change_schema_test.rb b/activerecord/test/cases/adapters/postgresql/change_schema_test.rb
index 5a9796887c..bc12df668d 100644
--- a/activerecord/test/cases/adapters/postgresql/change_schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/change_schema_test.rb
@@ -2,7 +2,7 @@ require 'cases/helper'
module ActiveRecord
class Migration
- class PGChangeSchemaTest < ActiveRecord::TestCase
+ class PGChangeSchemaTest < ActiveRecord::PostgreSQLTestCase
attr_reader :connection
def setup
diff --git a/activerecord/test/cases/adapters/postgresql/cidr_test.rb b/activerecord/test/cases/adapters/postgresql/cidr_test.rb
index 6cb11d17b4..52f2a0096c 100644
--- a/activerecord/test/cases/adapters/postgresql/cidr_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/cidr_test.rb
@@ -3,8 +3,8 @@ require "ipaddr"
module ActiveRecord
module ConnectionAdapters
- class PostgreSQLAdapter
- class CidrTest < ActiveRecord::TestCase
+ class PostgreSQLAdapter < AbstractAdapter
+ class CidrTest < ActiveRecord::PostgreSQLTestCase
test "type casting IPAddr for database" do
type = OID::Cidr.new
ip = IPAddr.new("255.0.0.0/8")
diff --git a/activerecord/test/cases/adapters/postgresql/citext_test.rb b/activerecord/test/cases/adapters/postgresql/citext_test.rb
index f706847890..bd62041e79 100644
--- a/activerecord/test/cases/adapters/postgresql/citext_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/citext_test.rb
@@ -2,7 +2,7 @@ require 'cases/helper'
require 'support/schema_dumping_helper'
if ActiveRecord::Base.connection.supports_extensions?
- class PostgresqlCitextTest < ActiveRecord::TestCase
+ class PostgresqlCitextTest < ActiveRecord::PostgreSQLTestCase
include SchemaDumpingHelper
class Citext < ActiveRecord::Base
self.table_name = 'citexts'
diff --git a/activerecord/test/cases/adapters/postgresql/collation_test.rb b/activerecord/test/cases/adapters/postgresql/collation_test.rb
new file mode 100644
index 0000000000..8470329c35
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/collation_test.rb
@@ -0,0 +1,53 @@
+require "cases/helper"
+require 'support/schema_dumping_helper'
+
+class PostgresqlCollationTest < ActiveRecord::PostgreSQLTestCase
+ include SchemaDumpingHelper
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table :postgresql_collations, force: true do |t|
+ t.string :string_c, collation: 'C'
+ t.text :text_posix, collation: 'POSIX'
+ end
+ end
+
+ def teardown
+ @connection.drop_table :postgresql_collations, if_exists: true
+ end
+
+ test "string column with collation" do
+ column = @connection.columns(:postgresql_collations).find { |c| c.name == 'string_c' }
+ assert_equal :string, column.type
+ assert_equal 'C', column.collation
+ end
+
+ test "text column with collation" do
+ column = @connection.columns(:postgresql_collations).find { |c| c.name == 'text_posix' }
+ assert_equal :text, column.type
+ assert_equal 'POSIX', column.collation
+ end
+
+ test "add column with collation" do
+ @connection.add_column :postgresql_collations, :title, :string, collation: 'C'
+
+ column = @connection.columns(:postgresql_collations).find { |c| c.name == 'title' }
+ assert_equal :string, column.type
+ assert_equal 'C', column.collation
+ end
+
+ test "change column with collation" do
+ @connection.add_column :postgresql_collations, :description, :string
+ @connection.change_column :postgresql_collations, :description, :text, collation: 'POSIX'
+
+ column = @connection.columns(:postgresql_collations).find { |c| c.name == 'description' }
+ assert_equal :text, column.type
+ assert_equal 'POSIX', column.collation
+ end
+
+ test "schema dump includes collation" do
+ output = dump_table_schema("postgresql_collations")
+ assert_match %r{t.string\s+"string_c",\s+collation: "C"$}, output
+ assert_match %r{t.text\s+"text_posix",\s+collation: "POSIX"$}, output
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb
index 16e3f90a47..1de87e5f01 100644
--- a/activerecord/test/cases/adapters/postgresql/composite_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb
@@ -40,7 +40,7 @@ end
# "unknown OID 5653508: failed to recognize type of 'address'. It will be treated as String."
# To take full advantage of composite types, we suggest you register your own +OID::Type+.
# See PostgresqlCompositeWithCustomOIDTest
-class PostgresqlCompositeTest < ActiveRecord::TestCase
+class PostgresqlCompositeTest < ActiveRecord::PostgreSQLTestCase
include PostgresqlCompositeBehavior
def test_column
@@ -77,7 +77,7 @@ class PostgresqlCompositeTest < ActiveRecord::TestCase
end
end
-class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::TestCase
+class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::PostgreSQLTestCase
include PostgresqlCompositeBehavior
class FullAddressType < ActiveRecord::Type::Value
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index 55ad76c8c0..820d41e13b 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -2,7 +2,7 @@ require "cases/helper"
require 'support/connection_helper'
module ActiveRecord
- class PostgresqlConnectionTest < ActiveRecord::TestCase
+ class PostgresqlConnectionTest < ActiveRecord::PostgreSQLTestCase
include ConnectionHelper
class NonExistentTable < ActiveRecord::Base
diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index 2c14252ae4..232c25cb3b 100644
--- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -11,7 +11,7 @@ end
class PostgresqlLtree < ActiveRecord::Base
end
-class PostgresqlDataTypeTest < ActiveRecord::TestCase
+class PostgresqlDataTypeTest < ActiveRecord::PostgreSQLTestCase
self.use_transactional_tests = false
def setup
@@ -69,7 +69,7 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
end
end
-class PostgresqlInternalDataTypeTest < ActiveRecord::TestCase
+class PostgresqlInternalDataTypeTest < ActiveRecord::PostgreSQLTestCase
include DdlHelper
setup do
diff --git a/activerecord/test/cases/adapters/postgresql/domain_test.rb b/activerecord/test/cases/adapters/postgresql/domain_test.rb
index 26e064c937..6102ddacd1 100644
--- a/activerecord/test/cases/adapters/postgresql/domain_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/domain_test.rb
@@ -1,7 +1,7 @@
require "cases/helper"
require 'support/connection_helper'
-class PostgresqlDomainTest < ActiveRecord::TestCase
+class PostgresqlDomainTest < ActiveRecord::PostgreSQLTestCase
include ConnectionHelper
class PostgresqlDomain < ActiveRecord::Base
diff --git a/activerecord/test/cases/adapters/postgresql/enum_test.rb b/activerecord/test/cases/adapters/postgresql/enum_test.rb
index ed084483bc..6816a6514b 100644
--- a/activerecord/test/cases/adapters/postgresql/enum_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/enum_test.rb
@@ -1,7 +1,7 @@
require "cases/helper"
require 'support/connection_helper'
-class PostgresqlEnumTest < ActiveRecord::TestCase
+class PostgresqlEnumTest < ActiveRecord::PostgreSQLTestCase
include ConnectionHelper
class PostgresqlEnum < ActiveRecord::Base
diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb
index 6ffb4c9f33..4d0fd640aa 100644
--- a/activerecord/test/cases/adapters/postgresql/explain_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb
@@ -2,25 +2,19 @@ require "cases/helper"
require 'models/developer'
require 'models/computer'
-module ActiveRecord
- module ConnectionAdapters
- class PostgreSQLAdapter
- class ExplainTest < ActiveRecord::TestCase
- fixtures :developers
+class PostgreSQLExplainTest < ActiveRecord::PostgreSQLTestCase
+ fixtures :developers
- def test_explain_for_one_query
- explain = Developer.where(:id => 1).explain
- assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = $1), explain
- assert_match %(QUERY PLAN), explain
- end
+ def test_explain_for_one_query
+ explain = Developer.where(:id => 1).explain
+ assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = $1), explain
+ assert_match %(QUERY PLAN), explain
+ end
- def test_explain_with_eager_loading
- explain = Developer.where(:id => 1).includes(:audit_logs).explain
- assert_match %(QUERY PLAN), explain
- assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = $1), explain
- assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain
- end
- end
- end
+ def test_explain_with_eager_loading
+ explain = Developer.where(:id => 1).includes(:audit_logs).explain
+ assert_match %(QUERY PLAN), explain
+ assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = $1), explain
+ assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
index 06d427f464..9cfc133308 100644
--- a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
@@ -1,6 +1,6 @@
require "cases/helper"
-class PostgresqlExtensionMigrationTest < ActiveRecord::TestCase
+class PostgresqlExtensionMigrationTest < ActiveRecord::PostgreSQLTestCase
self.use_transactional_tests = false
class EnableHstore < ActiveRecord::Migration
diff --git a/activerecord/test/cases/adapters/postgresql/full_text_test.rb b/activerecord/test/cases/adapters/postgresql/full_text_test.rb
index b83063c94e..bde7513339 100644
--- a/activerecord/test/cases/adapters/postgresql/full_text_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/full_text_test.rb
@@ -1,7 +1,7 @@
require "cases/helper"
require 'support/schema_dumping_helper'
-class PostgresqlFullTextTest < ActiveRecord::TestCase
+class PostgresqlFullTextTest < ActiveRecord::PostgreSQLTestCase
include SchemaDumpingHelper
class Tsvector < ActiveRecord::Base; end
diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
index 41e9572907..0baf985654 100644
--- a/activerecord/test/cases/adapters/postgresql/geometric_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
@@ -2,11 +2,19 @@ require "cases/helper"
require 'support/connection_helper'
require 'support/schema_dumping_helper'
-class PostgresqlPointTest < ActiveRecord::TestCase
+class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase
include ConnectionHelper
include SchemaDumpingHelper
- class PostgresqlPoint < ActiveRecord::Base; end
+ class PostgresqlPoint < ActiveRecord::Base
+ attribute :x, :rails_5_1_point
+ attribute :y, :rails_5_1_point
+ attribute :z, :rails_5_1_point
+ attribute :array_of_points, :rails_5_1_point, array: true
+ attribute :legacy_x, :legacy_point
+ attribute :legacy_y, :legacy_point
+ attribute :legacy_z, :legacy_point
+ end
def setup
@connection = ActiveRecord::Base.connection
@@ -14,11 +22,27 @@ class PostgresqlPointTest < ActiveRecord::TestCase
t.point :x
t.point :y, default: [12.2, 13.3]
t.point :z, default: "(14.4,15.5)"
+ t.point :array_of_points, array: true
+ t.point :legacy_x
+ 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
@@ -32,11 +56,11 @@ class PostgresqlPointTest < ActiveRecord::TestCase
end
def test_default
- assert_equal [12.2, 13.3], PostgresqlPoint.column_defaults['y']
- assert_equal [12.2, 13.3], PostgresqlPoint.new.y
+ assert_equal ActiveRecord::Point.new(12.2, 13.3), PostgresqlPoint.column_defaults['y']
+ assert_equal ActiveRecord::Point.new(12.2, 13.3), PostgresqlPoint.new.y
- assert_equal [14.4, 15.5], PostgresqlPoint.column_defaults['z']
- assert_equal [14.4, 15.5], PostgresqlPoint.new.z
+ assert_equal ActiveRecord::Point.new(14.4, 15.5), PostgresqlPoint.column_defaults['z']
+ assert_equal ActiveRecord::Point.new(14.4, 15.5), PostgresqlPoint.new.z
end
def test_schema_dumping
@@ -49,27 +73,100 @@ class PostgresqlPointTest < ActiveRecord::TestCase
def test_roundtrip
PostgresqlPoint.create! x: [10, 25.2]
record = PostgresqlPoint.first
- assert_equal [10, 25.2], record.x
+ assert_equal ActiveRecord::Point.new(10, 25.2), record.x
- record.x = [1.1, 2.2]
+ record.x = ActiveRecord::Point.new(1.1, 2.2)
record.save!
assert record.reload
- assert_equal [1.1, 2.2], record.x
+ assert_equal ActiveRecord::Point.new(1.1, 2.2), record.x
end
def test_mutation
- p = PostgresqlPoint.create! x: [10, 20]
+ p = PostgresqlPoint.create! x: ActiveRecord::Point.new(10, 20)
+
+ p.x.y = 25
+ p.save!
+ p.reload
+
+ assert_equal ActiveRecord::Point.new(10.0, 25.0), p.x
+ assert_not p.changed?
+ end
+
+ def test_array_assignment
+ p = PostgresqlPoint.new(x: [1, 2])
+
+ assert_equal ActiveRecord::Point.new(1, 2), p.x
+ end
+
+ def test_string_assignment
+ p = PostgresqlPoint.new(x: "(1, 2)")
+
+ assert_equal ActiveRecord::Point.new(1, 2), p.x
+ end
+
+ def test_array_of_points_round_trip
+ expected_value = [
+ ActiveRecord::Point.new(1, 2),
+ ActiveRecord::Point.new(2, 3),
+ ActiveRecord::Point.new(3, 4),
+ ]
+ p = PostgresqlPoint.new(array_of_points: expected_value)
+
+ assert_equal expected_value, p.array_of_points
+ p.save!
+ p.reload
+ assert_equal expected_value, p.array_of_points
+ end
+
+ def test_legacy_column
+ column = PostgresqlPoint.columns_hash["legacy_x"]
+ assert_equal :point, column.type
+ assert_equal "point", column.sql_type
+ assert_not column.array?
+
+ type = PostgresqlPoint.type_for_attribute("legacy_x")
+ assert_not type.binary?
+ end
+
+ def test_legacy_default
+ assert_equal [12.2, 13.3], PostgresqlPoint.column_defaults['legacy_y']
+ assert_equal [12.2, 13.3], PostgresqlPoint.new.legacy_y
+
+ assert_equal [14.4, 15.5], PostgresqlPoint.column_defaults['legacy_z']
+ assert_equal [14.4, 15.5], PostgresqlPoint.new.legacy_z
+ end
+
+ def test_legacy_schema_dumping
+ output = dump_table_schema("postgresql_points")
+ assert_match %r{t\.point\s+"legacy_x"$}, output
+ assert_match %r{t\.point\s+"legacy_y",\s+default: \[12\.2, 13\.3\]$}, output
+ assert_match %r{t\.point\s+"legacy_z",\s+default: \[14\.4, 15\.5\]$}, output
+ end
+
+ def test_legacy_roundtrip
+ PostgresqlPoint.create! legacy_x: [10, 25.2]
+ record = PostgresqlPoint.first
+ assert_equal [10, 25.2], record.legacy_x
+
+ record.legacy_x = [1.1, 2.2]
+ record.save!
+ assert record.reload
+ assert_equal [1.1, 2.2], record.legacy_x
+ end
+
+ def test_legacy_mutation
+ p = PostgresqlPoint.create! legacy_x: [10, 20]
- p.x[1] = 25
+ p.legacy_x[1] = 25
p.save!
p.reload
- assert_equal [10.0, 25.0], p.x
+ assert_equal [10.0, 25.0], p.legacy_x
assert_not p.changed?
end
end
-class PostgresqlGeometricTest < ActiveRecord::TestCase
+class PostgresqlGeometricTest < ActiveRecord::PostgreSQLTestCase
class PostgresqlGeometric < ActiveRecord::Base; end
setup do
diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
index ad9dd311a6..6a2d501646 100644
--- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
@@ -2,7 +2,7 @@ require "cases/helper"
require 'support/schema_dumping_helper'
if ActiveRecord::Base.connection.supports_extensions?
- class PostgresqlHstoreTest < ActiveRecord::TestCase
+ class PostgresqlHstoreTest < ActiveRecord::PostgreSQLTestCase
include SchemaDumpingHelper
class Hstore < ActiveRecord::Base
self.table_name = 'hstores'
diff --git a/activerecord/test/cases/adapters/postgresql/infinity_test.rb b/activerecord/test/cases/adapters/postgresql/infinity_test.rb
index d9d7832094..bfda933fa4 100644
--- a/activerecord/test/cases/adapters/postgresql/infinity_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/infinity_test.rb
@@ -1,6 +1,6 @@
require "cases/helper"
-class PostgresqlInfinityTest < ActiveRecord::TestCase
+class PostgresqlInfinityTest < ActiveRecord::PostgreSQLTestCase
include InTimeZone
class PostgresqlInfinity < ActiveRecord::Base
diff --git a/activerecord/test/cases/adapters/postgresql/integer_test.rb b/activerecord/test/cases/adapters/postgresql/integer_test.rb
index 679a0fc7b3..b4e55964b9 100644
--- a/activerecord/test/cases/adapters/postgresql/integer_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/integer_test.rb
@@ -1,7 +1,7 @@
require "cases/helper"
require "active_support/core_ext/numeric/bytes"
-class PostgresqlIntegerTest < ActiveRecord::TestCase
+class PostgresqlIntegerTest < ActiveRecord::PostgreSQLTestCase
class PgInteger < ActiveRecord::Base
end
diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb
index 6878516aeb..f242f32496 100644
--- a/activerecord/test/cases/adapters/postgresql/json_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/json_test.rb
@@ -188,7 +188,7 @@ module PostgresqlJSONSharedTestCases
end
end
-class PostgresqlJSONTest < ActiveRecord::TestCase
+class PostgresqlJSONTest < ActiveRecord::PostgreSQLTestCase
include PostgresqlJSONSharedTestCases
def column_type
@@ -196,7 +196,7 @@ class PostgresqlJSONTest < ActiveRecord::TestCase
end
end
-class PostgresqlJSONBTest < ActiveRecord::TestCase
+class PostgresqlJSONBTest < ActiveRecord::PostgreSQLTestCase
include PostgresqlJSONSharedTestCases
def column_type
diff --git a/activerecord/test/cases/adapters/postgresql/ltree_test.rb b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
index ce0ad16557..56516c82b4 100644
--- a/activerecord/test/cases/adapters/postgresql/ltree_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
@@ -1,7 +1,7 @@
require "cases/helper"
require 'support/schema_dumping_helper'
-class PostgresqlLtreeTest < ActiveRecord::TestCase
+class PostgresqlLtreeTest < ActiveRecord::PostgreSQLTestCase
include SchemaDumpingHelper
class Ltree < ActiveRecord::Base
self.table_name = 'ltrees'
diff --git a/activerecord/test/cases/adapters/postgresql/money_test.rb b/activerecord/test/cases/adapters/postgresql/money_test.rb
index cedd399380..c031178479 100644
--- a/activerecord/test/cases/adapters/postgresql/money_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/money_test.rb
@@ -1,7 +1,7 @@
require "cases/helper"
require 'support/schema_dumping_helper'
-class PostgresqlMoneyTest < ActiveRecord::TestCase
+class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase
include SchemaDumpingHelper
class PostgresqlMoney < ActiveRecord::Base; end
@@ -56,7 +56,7 @@ class PostgresqlMoneyTest < ActiveRecord::TestCase
def test_schema_dumping
output = dump_table_schema("postgresql_moneys")
assert_match %r{t\.money\s+"wealth",\s+scale: 2$}, output
- assert_match %r{t\.money\s+"depth",\s+scale: 2,\s+default: 150\.55$}, output
+ assert_match %r{t\.money\s+"depth",\s+scale: 2,\s+default: "150\.55"$}, output
end
def test_create_and_update_money
diff --git a/activerecord/test/cases/adapters/postgresql/network_test.rb b/activerecord/test/cases/adapters/postgresql/network_test.rb
index 033695518e..fe6ee4e2d9 100644
--- a/activerecord/test/cases/adapters/postgresql/network_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/network_test.rb
@@ -1,7 +1,7 @@
require "cases/helper"
require 'support/schema_dumping_helper'
-class PostgresqlNetworkTest < ActiveRecord::TestCase
+class PostgresqlNetworkTest < ActiveRecord::PostgreSQLTestCase
include SchemaDumpingHelper
class PostgresqlNetworkAddress < ActiveRecord::Base; end
diff --git a/activerecord/test/cases/adapters/postgresql/numbers_test.rb b/activerecord/test/cases/adapters/postgresql/numbers_test.rb
index d8e01e3b89..ba7e7dc9a3 100644
--- a/activerecord/test/cases/adapters/postgresql/numbers_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/numbers_test.rb
@@ -1,6 +1,6 @@
require "cases/helper"
-class PostgresqlNumberTest < ActiveRecord::TestCase
+class PostgresqlNumberTest < ActiveRecord::PostgreSQLTestCase
class PostgresqlNumber < ActiveRecord::Base; end
setup do
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 9a1b889d4d..6e6850c4a9 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -4,7 +4,7 @@ require 'support/connection_helper'
module ActiveRecord
module ConnectionAdapters
- class PostgreSQLAdapterTest < ActiveRecord::TestCase
+ class PostgreSQLAdapterTest < ActiveRecord::PostgreSQLTestCase
include DdlHelper
include ConnectionHelper
diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
index e4420d9d13..5e6f4dbbb8 100644
--- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
@@ -4,7 +4,7 @@ require 'ipaddr'
module ActiveRecord
module ConnectionAdapters
class PostgreSQLAdapter
- class QuotingTest < ActiveRecord::TestCase
+ class QuotingTest < ActiveRecord::PostgreSQLTestCase
def setup
@conn = ActiveRecord::Base.connection
end
diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb
index bbf96278b0..02b1083430 100644
--- a/activerecord/test/cases/adapters/postgresql/range_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/range_test.rb
@@ -1,12 +1,12 @@
require "cases/helper"
require 'support/connection_helper'
-if ActiveRecord::Base.connection.supports_ranges?
+if ActiveRecord::Base.connection.respond_to?(:supports_ranges?) && ActiveRecord::Base.connection.supports_ranges?
class PostgresqlRange < ActiveRecord::Base
self.table_name = "postgresql_ranges"
end
- class PostgresqlRangeTest < ActiveRecord::TestCase
+ class PostgresqlRangeTest < ActiveRecord::PostgreSQLTestCase
self.use_transactional_tests = false
include ConnectionHelper
diff --git a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb
index 7200ed2771..c895ab9db5 100644
--- a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb
@@ -1,7 +1,7 @@
require 'cases/helper'
require 'support/connection_helper'
-class PostgreSQLReferentialIntegrityTest < ActiveRecord::TestCase
+class PostgreSQLReferentialIntegrityTest < ActiveRecord::PostgreSQLTestCase
self.use_transactional_tests = false
include ConnectionHelper
diff --git a/activerecord/test/cases/adapters/postgresql/rename_table_test.rb b/activerecord/test/cases/adapters/postgresql/rename_table_test.rb
index f507328868..bd64bae308 100644
--- a/activerecord/test/cases/adapters/postgresql/rename_table_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/rename_table_test.rb
@@ -1,6 +1,6 @@
require "cases/helper"
-class PostgresqlRenameTableTest < ActiveRecord::TestCase
+class PostgresqlRenameTableTest < ActiveRecord::PostgreSQLTestCase
def setup
@connection = ActiveRecord::Base.connection
@connection.create_table :before_rename, force: true
diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
index 359a45bbd1..fa6584eae5 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
@@ -3,7 +3,7 @@ require "cases/helper"
class SchemaThing < ActiveRecord::Base
end
-class SchemaAuthorizationTest < ActiveRecord::TestCase
+class SchemaAuthorizationTest < ActiveRecord::PostgreSQLTestCase
self.use_transactional_tests = false
TABLE_NAME = 'schema_things'
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index f925dcad97..35d5581aa7 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -2,7 +2,7 @@ require "cases/helper"
require 'models/default'
require 'support/schema_dumping_helper'
-class SchemaTest < ActiveRecord::TestCase
+class SchemaTest < ActiveRecord::PostgreSQLTestCase
self.use_transactional_tests = false
SCHEMA_NAME = 'test_schema'
@@ -441,7 +441,7 @@ class SchemaTest < ActiveRecord::TestCase
end
end
-class SchemaForeignKeyTest < ActiveRecord::TestCase
+class SchemaForeignKeyTest < ActiveRecord::PostgreSQLTestCase
include SchemaDumpingHelper
setup do
@@ -466,7 +466,7 @@ class SchemaForeignKeyTest < ActiveRecord::TestCase
end
end
-class DefaultsUsingMultipleSchemasAndDomainTest < ActiveSupport::TestCase
+class DefaultsUsingMultipleSchemasAndDomainTest < ActiveRecord::PostgreSQLTestCase
setup do
@connection = ActiveRecord::Base.connection
@connection.execute "DROP SCHEMA IF EXISTS schema_1 CASCADE"
@@ -480,6 +480,7 @@ class DefaultsUsingMultipleSchemasAndDomainTest < ActiveSupport::TestCase
@connection.create_table "defaults" do |t|
t.text "text_col", default: "some value"
t.string "string_col", default: "some value"
+ t.decimal "decimal_col", default: "3.14159265358979323846"
end
Default.reset_column_information
end
@@ -498,6 +499,10 @@ class DefaultsUsingMultipleSchemasAndDomainTest < ActiveSupport::TestCase
assert_equal "some value", Default.new.string_col, "Default of string column was not correctly parsed"
end
+ def test_decimal_defaults_in_new_schema_when_overriding_domain
+ assert_equal BigDecimal.new("3.14159265358979323846"), Default.new.decimal_col, "Default of decimal column was not correctly parsed"
+ end
+
def test_bpchar_defaults_in_new_schema_when_overriding_domain
@connection.execute "ALTER TABLE defaults ADD bpchar_col bpchar DEFAULT 'some value'"
Default.reset_column_information
diff --git a/activerecord/test/cases/adapters/postgresql/serial_test.rb b/activerecord/test/cases/adapters/postgresql/serial_test.rb
index 458a8dae6c..7d30db247b 100644
--- a/activerecord/test/cases/adapters/postgresql/serial_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/serial_test.rb
@@ -1,7 +1,7 @@
require "cases/helper"
require 'support/schema_dumping_helper'
-class PostgresqlSerialTest < ActiveRecord::TestCase
+class PostgresqlSerialTest < ActiveRecord::PostgreSQLTestCase
include SchemaDumpingHelper
class PostgresqlSerial < ActiveRecord::Base; end
@@ -30,7 +30,7 @@ class PostgresqlSerialTest < ActiveRecord::TestCase
end
end
-class PostgresqlBigSerialTest < ActiveRecord::TestCase
+class PostgresqlBigSerialTest < ActiveRecord::PostgreSQLTestCase
include SchemaDumpingHelper
class PostgresqlBigSerial < ActiveRecord::Base; end
diff --git a/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb
index 1497b0abc7..5aab246c99 100644
--- a/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb
@@ -13,7 +13,7 @@ module ActiveRecord
end
end
- class StatementPoolTest < ActiveRecord::TestCase
+ class StatementPoolTest < ActiveRecord::PostgreSQLTestCase
if Process.respond_to?(:fork)
def test_cache_is_per_pid
cache = StatementPool.new nil, 10
diff --git a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb
index a639f98272..4c4866b46b 100644
--- a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb
@@ -2,7 +2,7 @@ require 'cases/helper'
require 'models/developer'
require 'models/topic'
-class PostgresqlTimestampTest < ActiveRecord::TestCase
+class PostgresqlTimestampTest < ActiveRecord::PostgreSQLTestCase
class PostgresqlTimestampWithZone < ActiveRecord::Base; end
self.use_transactional_tests = false
@@ -43,7 +43,7 @@ class PostgresqlTimestampTest < ActiveRecord::TestCase
end
end
-class TimestampTest < ActiveRecord::TestCase
+class PostgresqlTimestampFixtureTest < ActiveRecord::PostgreSQLTestCase
fixtures :topics
def test_group_by_date
diff --git a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
index c0907b8f21..77a99ca778 100644
--- a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
@@ -1,6 +1,6 @@
require 'cases/helper'
-class PostgresqlTypeLookupTest < ActiveRecord::TestCase
+class PostgresqlTypeLookupTest < ActiveRecord::PostgreSQLTestCase
setup do
@connection = ActiveRecord::Base.connection
end
diff --git a/activerecord/test/cases/adapters/postgresql/utils_test.rb b/activerecord/test/cases/adapters/postgresql/utils_test.rb
index 3fdb6888d9..095c1826e5 100644
--- a/activerecord/test/cases/adapters/postgresql/utils_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/utils_test.rb
@@ -1,6 +1,7 @@
require 'cases/helper'
+require 'active_record/connection_adapters/postgresql/utils'
-class PostgreSQLUtilsTest < ActiveSupport::TestCase
+class PostgreSQLUtilsTest < ActiveRecord::PostgreSQLTestCase
Name = ActiveRecord::ConnectionAdapters::PostgreSQL::Name
include ActiveRecord::ConnectionAdapters::PostgreSQL::Utils
@@ -20,7 +21,7 @@ class PostgreSQLUtilsTest < ActiveSupport::TestCase
end
end
-class PostgreSQLNameTest < ActiveSupport::TestCase
+class PostgreSQLNameTest < ActiveRecord::PostgreSQLTestCase
Name = ActiveRecord::ConnectionAdapters::PostgreSQL::Name
test "represents itself as schema.name" do
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index e9379a1019..7127d69e9e 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -11,7 +11,7 @@ module PostgresqlUUIDHelper
end
end
-class PostgresqlUUIDTest < ActiveRecord::TestCase
+class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase
include PostgresqlUUIDHelper
include SchemaDumpingHelper
@@ -135,7 +135,7 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase
end
end
-class PostgresqlUUIDGenerationTest < ActiveRecord::TestCase
+class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase
include PostgresqlUUIDHelper
include SchemaDumpingHelper
@@ -210,7 +210,7 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::TestCase
end
end
-class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase
+class PostgresqlUUIDTestNilDefault < ActiveRecord::PostgreSQLTestCase
include PostgresqlUUIDHelper
include SchemaDumpingHelper
@@ -244,7 +244,7 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase
end
end
-class PostgresqlUUIDTestInverseOf < ActiveRecord::TestCase
+class PostgresqlUUIDTestInverseOf < ActiveRecord::PostgreSQLTestCase
include PostgresqlUUIDHelper
class UuidPost < ActiveRecord::Base
diff --git a/activerecord/test/cases/adapters/postgresql/view_test.rb b/activerecord/test/cases/adapters/postgresql/view_test.rb
index 8a8e1d3b17..2dd6ec5fe6 100644
--- a/activerecord/test/cases/adapters/postgresql/view_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/view_test.rb
@@ -1,7 +1,7 @@
require "cases/helper"
require "cases/view_test"
-class UpdateableViewTest < ActiveRecord::TestCase
+class UpdateableViewTest < ActiveRecord::PostgreSQLTestCase
fixtures :books
class PrintedBook < ActiveRecord::Base
@@ -46,8 +46,9 @@ class UpdateableViewTest < ActiveRecord::TestCase
end
end
-if ActiveRecord::Base.connection.supports_materialized_views?
-class MaterializedViewTest < ActiveRecord::TestCase
+if ActiveRecord::Base.connection.respond_to?(:supports_materialized_views?) &&
+ ActiveRecord::Base.connection.supports_materialized_views?
+class MaterializedViewTest < ActiveRecord::PostgreSQLTestCase
include ViewBehavior
private
diff --git a/activerecord/test/cases/adapters/postgresql/xml_test.rb b/activerecord/test/cases/adapters/postgresql/xml_test.rb
index b097deb2f4..add32699fa 100644
--- a/activerecord/test/cases/adapters/postgresql/xml_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/xml_test.rb
@@ -1,7 +1,7 @@
require 'cases/helper'
require 'support/schema_dumping_helper'
-class PostgresqlXMLTest < ActiveRecord::TestCase
+class PostgresqlXMLTest < ActiveRecord::PostgreSQLTestCase
include SchemaDumpingHelper
class XmlDataType < ActiveRecord::Base
self.table_name = 'xml_data_type'
diff --git a/activerecord/test/cases/adapters/sqlite3/collation_test.rb b/activerecord/test/cases/adapters/sqlite3/collation_test.rb
new file mode 100644
index 0000000000..58a9469ce5
--- /dev/null
+++ b/activerecord/test/cases/adapters/sqlite3/collation_test.rb
@@ -0,0 +1,53 @@
+require "cases/helper"
+require 'support/schema_dumping_helper'
+
+class SQLite3CollationTest < ActiveRecord::SQLite3TestCase
+ include SchemaDumpingHelper
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table :collation_table_sqlite3, force: true do |t|
+ t.string :string_nocase, collation: 'NOCASE'
+ t.text :text_rtrim, collation: 'RTRIM'
+ end
+ end
+
+ def teardown
+ @connection.drop_table :collation_table_sqlite3, if_exists: true
+ end
+
+ test "string column with collation" do
+ column = @connection.columns(:collation_table_sqlite3).find { |c| c.name == 'string_nocase' }
+ assert_equal :string, column.type
+ assert_equal 'NOCASE', column.collation
+ end
+
+ test "text column with collation" do
+ column = @connection.columns(:collation_table_sqlite3).find { |c| c.name == 'text_rtrim' }
+ assert_equal :text, column.type
+ assert_equal 'RTRIM', column.collation
+ end
+
+ test "add column with collation" do
+ @connection.add_column :collation_table_sqlite3, :title, :string, collation: 'RTRIM'
+
+ column = @connection.columns(:collation_table_sqlite3).find { |c| c.name == 'title' }
+ assert_equal :string, column.type
+ assert_equal 'RTRIM', column.collation
+ end
+
+ test "change column with collation" do
+ @connection.add_column :collation_table_sqlite3, :description, :string
+ @connection.change_column :collation_table_sqlite3, :description, :text, collation: 'RTRIM'
+
+ column = @connection.columns(:collation_table_sqlite3).find { |c| c.name == 'description' }
+ assert_equal :text, column.type
+ assert_equal 'RTRIM', column.collation
+ end
+
+ test "schema dump includes collation" do
+ output = dump_table_schema("collation_table_sqlite3")
+ assert_match %r{t.string\s+"string_nocase",\s+collation: "NOCASE"$}, output
+ assert_match %r{t.text\s+"text_rtrim",\s+collation: "RTRIM"$}, output
+ 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 13b754d226..34e3b2e023 100644
--- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
@@ -1,6 +1,6 @@
require "cases/helper"
-class CopyTableTest < ActiveRecord::TestCase
+class CopyTableTest < ActiveRecord::SQLite3TestCase
fixtures :customers
def setup
diff --git a/activerecord/test/cases/adapters/sqlite3/explain_test.rb b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
index 7d66c44798..2aec322582 100644
--- a/activerecord/test/cases/adapters/sqlite3/explain_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
@@ -5,7 +5,7 @@ require 'models/computer'
module ActiveRecord
module ConnectionAdapters
class SQLite3Adapter
- class ExplainTest < ActiveRecord::TestCase
+ class ExplainTest < ActiveRecord::SQLite3TestCase
fixtures :developers
def test_explain_for_one_query
diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
index 243f65df98..87a892db37 100644
--- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
@@ -6,7 +6,7 @@ require 'securerandom'
module ActiveRecord
module ConnectionAdapters
class SQLite3Adapter
- class QuotingTest < ActiveRecord::TestCase
+ class QuotingTest < ActiveRecord::SQLite3TestCase
def setup
@conn = Base.sqlite3_connection :database => ':memory:',
:adapter => 'sqlite3',
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 27f4ba8eb6..7996e7ad50 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -5,7 +5,7 @@ require 'support/ddl_helper'
module ActiveRecord
module ConnectionAdapters
- class SQLite3AdapterTest < ActiveRecord::TestCase
+ class SQLite3AdapterTest < ActiveRecord::SQLite3TestCase
include DdlHelper
self.use_transactional_tests = false
@@ -421,14 +421,14 @@ module ActiveRecord
end
def test_statement_closed
- db = SQLite3::Database.new(ActiveRecord::Base.
+ db = ::SQLite3::Database.new(ActiveRecord::Base.
configurations['arunit']['database'])
- statement = SQLite3::Statement.new(db,
+ statement = ::SQLite3::Statement.new(db,
'CREATE TABLE statement_test (number integer not null)')
- statement.stubs(:step).raises(SQLite3::BusyException, 'busy')
+ statement.stubs(:step).raises(::SQLite3::BusyException, 'busy')
statement.stubs(:columns).once.returns([])
statement.expects(:close).once
- SQLite3::Statement.stubs(:new).returns(statement)
+ ::SQLite3::Statement.stubs(:new).returns(statement)
assert_raises ActiveRecord::StatementInvalid do
@conn.exec_query 'select * from statement_test'
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb
index deedf67c8e..887dcfc96c 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb
@@ -3,7 +3,7 @@ require 'models/owner'
module ActiveRecord
module ConnectionAdapters
- class SQLite3CreateFolder < ActiveRecord::TestCase
+ class SQLite3CreateFolder < ActiveRecord::SQLite3TestCase
def test_sqlite_creates_directory
Dir.mktmpdir do |dir|
dir = Pathname.new(dir)
diff --git a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
index fd0044ac05..ef324183a7 100644
--- a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
@@ -2,7 +2,7 @@ require 'cases/helper'
module ActiveRecord::ConnectionAdapters
class SQLite3Adapter
- class StatementPoolTest < ActiveRecord::TestCase
+ class StatementPoolTest < ActiveRecord::SQLite3TestCase
if Process.respond_to?(:fork)
def test_cache_is_per_pid
@@ -22,4 +22,3 @@ module ActiveRecord::ConnectionAdapters
end
end
end
-
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index c59dc00071..0d2e2264e3 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -19,6 +19,8 @@ require 'models/invoice'
require 'models/line_item'
require 'models/column'
require 'models/record'
+require 'models/admin'
+require 'models/admin/user'
require 'models/ship'
require 'models/treasure'
require 'models/parrot'
@@ -34,6 +36,10 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal companies(:first_firm).name, firm.name
end
+ def test_missing_attribute_error_is_raised_when_no_foreign_key_attribute
+ assert_raises(ActiveModel::MissingAttributeError) { Client.select(:id).first.firm }
+ end
+
def test_belongs_to_does_not_use_order_by
ActiveRecord::SQLCounter.clear_log
Client.find(3).firm
@@ -88,7 +94,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
end
account = model.new
- refute account.valid?
+ assert_not account.valid?
assert_equal [{error: :blank}], account.errors.details[:company]
ensure
ActiveRecord::Base.belongs_to_required_by_default = original_value
@@ -105,7 +111,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
end
account = model.new
- refute account.valid?
+ assert_not account.valid?
assert_equal [{error: :blank}], account.errors.details[:company]
ensure
ActiveRecord::Base.belongs_to_required_by_default = original_value
@@ -127,9 +133,9 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
where("id = :inc", :inc => counter)
}
- has_many :comments, :class => comments
+ has_many :comments, :anonymous_class => comments
}
- belongs_to :post, :class => posts, :inverse_of => false
+ belongs_to :post, :anonymous_class => posts, :inverse_of => false
}
assert_equal 0, counter
@@ -150,6 +156,30 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::AssociationTypeMismatch) { Account.find(1).firm = Project.find(1) }
end
+ def test_raises_type_mismatch_with_namespaced_class
+ assert_nil defined?(Region), "This test requires that there is no top-level Region class"
+
+ ActiveRecord::Base.connection.instance_eval do
+ create_table(:admin_regions) { |t| t.string :name }
+ add_column :admin_users, :region_id, :integer
+ end
+ Admin.const_set "RegionalUser", Class.new(Admin::User) { belongs_to(:region) }
+ Admin.const_set "Region", Class.new(ActiveRecord::Base)
+
+ e = assert_raise(ActiveRecord::AssociationTypeMismatch) {
+ Admin::RegionalUser.new(region: 'wrong value')
+ }
+ assert_match(/^Region\([^)]+\) expected, got String\([^)]+\)$/, e.message)
+ ensure
+ Admin.send :remove_const, "Region" if Admin.const_defined?("Region")
+ Admin.send :remove_const, "RegionalUser" if Admin.const_defined?("RegionalUser")
+
+ ActiveRecord::Base.connection.instance_eval do
+ remove_column :admin_users, :region_id if column_exists?(:admin_users, :region_id)
+ drop_table :admin_regions, if_exists: true
+ end
+ end
+
def test_natural_assignment
apple = Firm.create("name" => "Apple")
citibank = Account.create("credit_limit" => 10)
@@ -266,7 +296,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
client = Client.find(3)
client.firm = nil
client.save
- assert_nil client.firm(true)
+ client.association(:firm).reload
+ assert_nil client.firm
assert_nil client.client_of
end
@@ -274,7 +305,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
client = Client.create(:name => "Primary key client", :firm_name => companies(:first_firm).name)
client.firm_with_primary_key = nil
client.save
- assert_nil client.firm_with_primary_key(true)
+ client.association(:firm_with_primary_key).reload
+ assert_nil client.firm_with_primary_key
assert_nil client.client_of
end
@@ -291,9 +323,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_polymorphic_association_class
sponsor = Sponsor.new
assert_nil sponsor.association(:sponsorable).send(:klass)
+ sponsor.association(:sponsorable).reload
+ assert_nil sponsor.sponsorable
sponsor.sponsorable_type = '' # the column doesn't have to be declared NOT NULL
assert_nil sponsor.association(:sponsorable).send(:klass)
+ sponsor.association(:sponsorable).reload
+ assert_nil sponsor.sponsorable
sponsor.sponsorable = Member.new :name => "Bert"
assert_equal Member, sponsor.association(:sponsorable).send(:klass)
@@ -441,6 +477,24 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_queries(1) { line_item.touch }
end
+ def test_belongs_to_with_touch_on_multiple_records
+ line_item = LineItem.create!(amount: 1)
+ line_item2 = LineItem.create!(amount: 2)
+ Invoice.create!(line_items: [line_item, line_item2])
+
+ assert_queries(1) do
+ LineItem.transaction do
+ line_item.touch
+ line_item2.touch
+ end
+ end
+
+ assert_queries(2) do
+ line_item.touch
+ line_item2.touch
+ end
+ end
+
def test_belongs_to_with_touch_option_on_touch_without_updated_at_attributes
assert_not LineItem.column_names.include?("updated_at")
@@ -526,7 +580,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert final_cut.persisted?
assert firm.persisted?
assert_equal firm, final_cut.firm
- assert_equal firm, final_cut.firm(true)
+ final_cut.association(:firm).reload
+ assert_equal firm, final_cut.firm
end
def test_assignment_before_child_saved_with_primary_key
@@ -538,7 +593,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert final_cut.persisted?
assert firm.persisted?
assert_equal firm, final_cut.firm_with_primary_key
- assert_equal firm, final_cut.firm_with_primary_key(true)
+ final_cut.association(:firm_with_primary_key).reload
+ assert_equal firm, final_cut.firm_with_primary_key
end
def test_new_record_with_foreign_key_but_no_object
@@ -1033,6 +1089,12 @@ 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/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 0ecf2ddfd1..ffbf60e390 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -1167,7 +1167,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_no_queries { assert client.accounts.empty? }
end
- def test_preloading_has_many_through_with_uniq
+ def test_preloading_has_many_through_with_distinct
mary = Author.includes(:unique_categorized_posts).where(:id => authors(:mary).id).first
assert_equal 1, mary.unique_categorized_posts.length
assert_equal 1, mary.unique_categorized_post_ids.length
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 aea9207bfe..7718b29125 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
@@ -3,6 +3,7 @@ require 'models/developer'
require 'models/computer'
require 'models/project'
require 'models/company'
+require 'models/course'
require 'models/customer'
require 'models/order'
require 'models/categorization'
@@ -14,6 +15,7 @@ require 'models/tagging'
require 'models/parrot'
require 'models/person'
require 'models/pirate'
+require 'models/professor'
require 'models/treasure'
require 'models/price_estimate'
require 'models/club'
@@ -83,6 +85,16 @@ class DeveloperWithSymbolClassName < Developer
has_and_belongs_to_many :projects, class_name: :ProjectWithSymbolsForKeys
end
+class DeveloperWithExtendOption < Developer
+ module NamedExtension
+ def category
+ 'sns'
+ end
+ end
+
+ has_and_belongs_to_many :projects, extend: NamedExtension
+end
+
class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects,
:parrots, :pirates, :parrots_pirates, :treasures, :price_estimates, :tags, :taggings, :computers
@@ -147,8 +159,8 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
jamis.projects << action_controller
assert_equal 2, jamis.projects.size
- assert_equal 2, jamis.projects(true).size
- assert_equal 2, action_controller.developers(true).size
+ assert_equal 2, jamis.projects.reload.size
+ assert_equal 2, action_controller.developers.reload.size
end
def test_adding_type_mismatch
@@ -166,9 +178,9 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
action_controller.developers << jamis
- assert_equal 2, jamis.projects(true).size
+ assert_equal 2, jamis.projects.reload.size
assert_equal 2, action_controller.developers.size
- assert_equal 2, action_controller.developers(true).size
+ assert_equal 2, action_controller.developers.reload.size
end
def test_adding_from_the_project_fixed_timestamp
@@ -182,9 +194,9 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
action_controller.developers << jamis
assert_equal updated_at, jamis.updated_at
- assert_equal 2, jamis.projects(true).size
+ assert_equal 2, jamis.projects.reload.size
assert_equal 2, action_controller.developers.size
- assert_equal 2, action_controller.developers(true).size
+ assert_equal 2, action_controller.developers.reload.size
end
def test_adding_multiple
@@ -193,7 +205,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
aredridel.projects.reload
aredridel.projects.push(Project.find(1), Project.find(2))
assert_equal 2, aredridel.projects.size
- assert_equal 2, aredridel.projects(true).size
+ assert_equal 2, aredridel.projects.reload.size
end
def test_adding_a_collection
@@ -202,7 +214,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
aredridel.projects.reload
aredridel.projects.concat([Project.find(1), Project.find(2)])
assert_equal 2, aredridel.projects.size
- assert_equal 2, aredridel.projects(true).size
+ assert_equal 2, aredridel.projects.reload.size
end
def test_habtm_adding_before_save
@@ -217,7 +229,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
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(true).size
+ assert_equal 2, aredridel.projects.reload.size
end
def test_habtm_saving_multiple_relationships
@@ -234,7 +246,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal developers, new_project.developers
end
- def test_habtm_unique_order_preserved
+ def test_habtm_distinct_order_preserved
assert_equal developers(:poor_jamis, :jamis, :david), projects(:active_record).non_unique_developers
assert_equal developers(:poor_jamis, :jamis, :david), projects(:active_record).developers
end
@@ -339,7 +351,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal 'Yet Another Testing Title', another_post.title
end
- def test_uniq_after_the_fact
+ def test_distinct_after_the_fact
dev = developers(:jamis)
dev.projects << projects(:active_record)
dev.projects << projects(:active_record)
@@ -348,13 +360,13 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal 1, dev.projects.distinct.size
end
- def test_uniq_before_the_fact
+ def test_distinct_before_the_fact
projects(:active_record).developers << developers(:jamis)
projects(:active_record).developers << developers(:david)
assert_equal 3, projects(:active_record, :reload).developers.size
end
- def test_uniq_option_prevents_duplicate_push
+ def test_distinct_option_prevents_duplicate_push
project = projects(:active_record)
project.developers << developers(:jamis)
project.developers << developers(:david)
@@ -365,7 +377,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal 3, project.developers.size
end
- def test_uniq_when_association_already_loaded
+ def test_distinct_when_association_already_loaded
project = projects(:active_record)
project.developers << [ developers(:jamis), developers(:david), developers(:jamis), developers(:david) ]
assert_equal 3, Project.includes(:developers).find(project.id).developers.size
@@ -381,8 +393,8 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
david.projects.delete(active_record)
assert_equal 1, david.projects.size
- assert_equal 1, david.projects(true).size
- assert_equal 2, active_record.developers(true).size
+ assert_equal 1, david.projects.reload.size
+ assert_equal 2, active_record.developers.reload.size
end
def test_deleting_array
@@ -390,7 +402,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
david.projects.reload
david.projects.delete(Project.all.to_a)
assert_equal 0, david.projects.size
- assert_equal 0, david.projects(true).size
+ assert_equal 0, david.projects.reload.size
end
def test_deleting_all
@@ -398,7 +410,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
david.projects.reload
david.projects.clear
assert_equal 0, david.projects.size
- assert_equal 0, david.projects(true).size
+ assert_equal 0, david.projects.reload.size
end
def test_removing_associations_on_destroy
@@ -424,7 +436,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert join_records.empty?
assert_equal 1, david.reload.projects.size
- assert_equal 1, david.projects(true).size
+ assert_equal 1, david.projects.reload.size
end
def test_destroying_many
@@ -440,7 +452,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert join_records.empty?
assert_equal 0, david.reload.projects.size
- assert_equal 0, david.projects(true).size
+ assert_equal 0, david.projects.reload.size
end
def test_destroy_all
@@ -456,7 +468,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert join_records.empty?
assert david.projects.empty?
- assert david.projects(true).empty?
+ assert david.projects.reload.empty?
end
def test_destroy_associations_destroys_multiple_associations
@@ -472,11 +484,11 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
join_records = Parrot.connection.select_all("SELECT * FROM parrots_pirates WHERE parrot_id = #{george.id}")
assert join_records.empty?
- assert george.pirates(true).empty?
+ assert george.pirates.reload.empty?
join_records = Parrot.connection.select_all("SELECT * FROM parrots_treasures WHERE parrot_id = #{george.id}")
assert join_records.empty?
- assert george.treasures(true).empty?
+ assert george.treasures.reload.empty?
end
def test_associations_with_conditions
@@ -577,6 +589,11 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal developers(:poor_jamis), projects(:active_record).developers.where("salary < 10000").first
end
+ def test_association_with_extend_option
+ eponine = DeveloperWithExtendOption.create(name: 'Eponine')
+ assert_equal 'sns', eponine.projects.category
+ end
+
def test_replace_with_less
david = developers(:david)
david.projects = [projects(:action_controller)]
@@ -639,7 +656,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
def test_habtm_respects_select
- categories(:technology).select_testing_posts(true).each do |o|
+ categories(:technology).select_testing_posts.reload.each do |o|
assert_respond_to o, :correctness_marker
end
assert_respond_to categories(:technology).select_testing_posts.first, :correctness_marker
@@ -711,7 +728,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_get_ids_for_loaded_associations
developer = developers(:david)
- developer.projects(true)
+ developer.projects.reload
assert_queries(0) do
developer.project_ids
developer.project_ids
@@ -902,4 +919,20 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
DeveloperWithSymbolClassName.new
end
end
+
+ def test_association_force_reload_with_only_true_is_deprecated
+ developer = Developer.find(1)
+
+ assert_deprecated { developer.projects(true) }
+ end
+
+ def test_alternate_database
+ professor = Professor.create(name: "Plum")
+ course = Course.create(name: "Forensics")
+ assert_equal 0, professor.courses.count
+ assert_nothing_raised do
+ professor.courses << course
+ end
+ assert_equal 1, professor.courses.count
+ end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 328ea1061f..f487065d9d 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -121,9 +121,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
developer_project = Class.new(ActiveRecord::Base) {
self.table_name = 'developers_projects'
- belongs_to :developer, :class => dev
+ belongs_to :developer, :anonymous_class => dev
}
- has_many :developer_projects, :class => developer_project, :foreign_key => 'developer_id'
+ has_many :developer_projects, :anonymous_class => developer_project, :foreign_key => 'developer_id'
}
dev = developer.first
named = Developer.find(dev.id)
@@ -142,13 +142,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
comments = Class.new(ActiveRecord::Base) {
self.table_name = 'comments'
self.inheritance_column = 'not_there'
- belongs_to :post, :class => post
+ belongs_to :post, :anonymous_class => post
default_scope -> {
counter += 1
where("id = :inc", :inc => counter)
}
}
- has_many :comments, :class => comments, :foreign_key => 'post_id'
+ has_many :comments, :anonymous_class => comments, :foreign_key => 'post_id'
}
assert_equal 0, counter
post = posts.first
@@ -706,7 +706,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
natural = Client.new("name" => "Natural Company")
companies(:first_firm).clients_of_firm << natural
assert_equal 3, companies(:first_firm).clients_of_firm.size # checking via the collection
- assert_equal 3, companies(:first_firm).clients_of_firm(true).size # checking using the db
+ assert_equal 3, companies(:first_firm).clients_of_firm.reload.size # checking using the db
assert_equal natural, companies(:first_firm).clients_of_firm.last
end
@@ -761,7 +761,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.concat([Client.new("name" => "Natural Company"), Client.new("name" => "Apple")])
assert_equal 4, companies(:first_firm).clients_of_firm.size
- assert_equal 4, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 4, companies(:first_firm).clients_of_firm.reload.size
end
def test_transactions_when_adding_to_persisted
@@ -773,7 +773,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
rescue Client::RaisedOnSave
end
- assert !companies(:first_firm).clients_of_firm(true).include?(good)
+ assert !companies(:first_firm).clients_of_firm.reload.include?(good)
end
def test_transactions_when_adding_to_new_record
@@ -905,12 +905,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
new_client = companies(:first_firm).clients_of_firm.create("name" => "Another Client")
assert new_client.persisted?
assert_equal new_client, companies(:first_firm).clients_of_firm.last
- assert_equal new_client, companies(:first_firm).clients_of_firm(true).last
+ assert_equal new_client, companies(:first_firm).clients_of_firm.reload.last
end
def test_create_many
companies(:first_firm).clients_of_firm.create([{"name" => "Another Client"}, {"name" => "Another Client II"}])
- assert_equal 4, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 4, companies(:first_firm).clients_of_firm.reload.size
end
def test_create_followed_by_save_does_not_load_target
@@ -923,7 +923,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.delete(companies(:first_firm).clients_of_firm.first)
assert_equal 1, companies(:first_firm).clients_of_firm.size
- assert_equal 1, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 1, companies(:first_firm).clients_of_firm.reload.size
end
def test_deleting_before_save
@@ -1079,7 +1079,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 3, companies(:first_firm).clients_of_firm.size
companies(:first_firm).clients_of_firm.delete([companies(:first_firm).clients_of_firm[0], companies(:first_firm).clients_of_firm[1], companies(:first_firm).clients_of_firm[2]])
assert_equal 0, companies(:first_firm).clients_of_firm.size
- assert_equal 0, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 0, companies(:first_firm).clients_of_firm.reload.size
end
def test_delete_all
@@ -1100,7 +1100,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
companies(:first_firm).clients_of_firm.reset
companies(:first_firm).clients_of_firm.delete_all
assert_equal 0, companies(:first_firm).clients_of_firm.size
- assert_equal 0, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 0, companies(:first_firm).clients_of_firm.reload.size
end
def test_transaction_when_deleting_persisted
@@ -1114,7 +1114,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
rescue Client::RaisedOnDestroy
end
- assert_equal [good, bad], companies(:first_firm).clients_of_firm(true)
+ assert_equal [good, bad], companies(:first_firm).clients_of_firm.reload
end
def test_transaction_when_deleting_new_record
@@ -1134,7 +1134,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.clients_of_firm.clear
assert_equal 0, firm.clients_of_firm.size
- assert_equal 0, firm.clients_of_firm(true).size
+ assert_equal 0, firm.clients_of_firm.reload.size
assert_equal [], Client.destroyed_client_ids[firm.id]
# Should not be destroyed since the association is not dependent.
@@ -1170,7 +1170,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.dependent_clients_of_firm.clear
assert_equal 0, firm.dependent_clients_of_firm.size
- assert_equal 0, firm.dependent_clients_of_firm(true).size
+ assert_equal 0, firm.dependent_clients_of_firm.reload.size
assert_equal [], Client.destroyed_client_ids[firm.id]
# Should be destroyed since the association is dependent.
@@ -1203,7 +1203,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.exclusively_dependent_clients_of_firm.clear
assert_equal 0, firm.exclusively_dependent_clients_of_firm.size
- assert_equal 0, firm.exclusively_dependent_clients_of_firm(true).size
+ assert_equal 0, firm.exclusively_dependent_clients_of_firm.reload.size
# no destroy-filters should have been called
assert_equal [], Client.destroyed_client_ids[firm.id]
@@ -1252,7 +1252,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
# break the vanilla firm_id foreign key
assert_equal 3, firm.clients.count
firm.clients.first.update_columns(firm_id: nil)
- assert_equal 2, firm.clients(true).count
+ assert_equal 2, firm.clients.reload.count
assert_equal 2, firm.clients_using_primary_key_with_delete_all.count
old_record = firm.clients_using_primary_key_with_delete_all.first
firm = Firm.first
@@ -1278,7 +1278,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.clients_of_firm.clear
assert_equal 0, firm.clients_of_firm.size
- assert_equal 0, firm.clients_of_firm(true).size
+ assert_equal 0, firm.clients_of_firm.reload.size
end
def test_deleting_a_item_which_is_not_in_the_collection
@@ -1286,7 +1286,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
summit = Client.find_by_name('Summit')
companies(:first_firm).clients_of_firm.delete(summit)
assert_equal 2, companies(:first_firm).clients_of_firm.size
- assert_equal 2, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 2, companies(:first_firm).clients_of_firm.reload.size
assert_equal 2, summit.client_of
end
@@ -1324,7 +1324,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
assert_equal 1, companies(:first_firm).reload.clients_of_firm.size
- assert_equal 1, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 1, companies(:first_firm).clients_of_firm.reload.size
end
def test_destroying_by_fixnum_id
@@ -1335,7 +1335,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
assert_equal 1, companies(:first_firm).reload.clients_of_firm.size
- assert_equal 1, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 1, companies(:first_firm).clients_of_firm.reload.size
end
def test_destroying_by_string_id
@@ -1346,7 +1346,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
assert_equal 1, companies(:first_firm).reload.clients_of_firm.size
- assert_equal 1, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 1, companies(:first_firm).clients_of_firm.reload.size
end
def test_destroying_a_collection
@@ -1359,7 +1359,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
assert_equal 1, companies(:first_firm).reload.clients_of_firm.size
- assert_equal 1, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 1, companies(:first_firm).clients_of_firm.reload.size
end
def test_destroy_all
@@ -1370,7 +1370,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal clients.sort_by(&:id), destroyed.sort_by(&:id)
assert destroyed.all?(&:frozen?), "destroyed clients should be frozen"
assert companies(:first_firm).clients_of_firm.empty?, "37signals has no clients after destroy all"
- assert companies(:first_firm).clients_of_firm(true).empty?, "37signals has no clients after destroy all and refresh"
+ assert companies(:first_firm).clients_of_firm.reload.empty?, "37signals has no clients after destroy all and refresh"
end
def test_dependence
@@ -1447,6 +1447,26 @@ 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')
@@ -1539,7 +1559,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
rescue Client::RaisedOnSave
end
- assert_equal [good], companies(:first_firm).clients_of_firm(true)
+ assert_equal [good], companies(:first_firm).clients_of_firm.reload
end
def test_transactions_when_replacing_on_new_record
@@ -1555,7 +1575,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_get_ids_for_loaded_associations
company = companies(:first_firm)
- company.clients(true)
+ company.clients.reload
assert_queries(0) do
company.client_ids
company.client_ids
@@ -1609,7 +1629,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.client_ids = [companies(:first_client).id, nil, companies(:second_client).id, '']
firm.save!
- assert_equal 2, firm.clients(true).size
+ assert_equal 2, firm.clients.reload.size
assert_equal true, firm.clients.include?(companies(:second_client))
end
@@ -2152,11 +2172,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
car = Car.create!
original_child = FailedBulb.create!(car: car)
- assert_raise(ActiveRecord::RecordNotDestroyed) do
+ error = assert_raise(ActiveRecord::RecordNotDestroyed) do
car.failed_bulbs = [FailedBulb.create!]
end
assert_equal [original_child], car.reload.failed_bulbs
+ assert_equal "Failed to destroy the record", error.message
end
test 'updates counter cache when default scope is given' do
@@ -2272,4 +2293,48 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [first_bulb, second_bulb], car.bulbs
end
+
+ def test_association_force_reload_with_only_true_is_deprecated
+ company = Company.find(1)
+
+ assert_deprecated { company.clients_of_firm(true) }
+ end
+
+ class AuthorWithErrorDestroyingAssociation < ActiveRecord::Base
+ self.table_name = "authors"
+ has_many :posts_with_error_destroying,
+ class_name: "PostWithErrorDestroying",
+ foreign_key: :author_id,
+ dependent: :destroy
+ end
+
+ class PostWithErrorDestroying < ActiveRecord::Base
+ self.table_name = "posts"
+ self.inheritance_column = nil
+ before_destroy -> { throw :abort }
+ end
+
+ def test_destroy_does_not_raise_when_association_errors_on_destroy
+ assert_no_difference "AuthorWithErrorDestroyingAssociation.count" do
+ author = AuthorWithErrorDestroyingAssociation.first
+
+ assert_not author.destroy
+ end
+ end
+
+ def test_destroy_with_bang_bubbles_errors_from_associations
+ error = assert_raises ActiveRecord::RecordNotDestroyed do
+ AuthorWithErrorDestroyingAssociation.first.destroy!
+ end
+
+ assert_instance_of PostWithErrorDestroying, error.record
+ end
+
+ def test_ids_reader_memoization
+ car = Car.create!(name: 'Tofaş')
+ bulb = Bulb.create!(car: car)
+
+ assert_equal [bulb.id], car.bulb_ids
+ assert_no_queries { car.bulb_ids }
+ 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 5f52c65412..b22ce42696 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -84,11 +84,11 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
subscriber = make_model "Subscriber"
subscriber.primary_key = 'nick'
- subscription.belongs_to :book, class: book
- subscription.belongs_to :subscriber, class: subscriber
+ subscription.belongs_to :book, anonymous_class: book
+ subscription.belongs_to :subscriber, anonymous_class: subscriber
- book.has_many :subscriptions, class: subscription
- book.has_many :subscribers, through: :subscriptions, class: subscriber
+ book.has_many :subscriptions, anonymous_class: subscription
+ book.has_many :subscribers, through: :subscriptions, anonymous_class: subscriber
anonbook = book.first
namebook = Book.find anonbook.id
@@ -154,10 +154,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
lesson_student = make_model 'LessonStudent'
lesson_student.table_name = 'lessons_students'
- lesson_student.belongs_to :lesson, :class => lesson
- lesson_student.belongs_to :student, :class => student
- lesson.has_many :lesson_students, :class => lesson_student
- lesson.has_many :students, :through => :lesson_students, :class => student
+ lesson_student.belongs_to :lesson, :anonymous_class => lesson
+ lesson_student.belongs_to :student, :anonymous_class => student
+ lesson.has_many :lesson_students, :anonymous_class => lesson_student
+ lesson.has_many :students, :through => :lesson_students, :anonymous_class => student
[lesson, lesson_student, student]
end
@@ -188,7 +188,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert post.people.include?(person)
end
- assert post.reload.people(true).include?(person)
+ assert post.reload.people.reload.include?(person)
end
def test_delete_all_for_with_dependent_option_destroy
@@ -229,7 +229,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
post = posts(:thinking)
post.people.concat [person]
assert_equal 1, post.people.size
- assert_equal 1, post.people(true).size
+ assert_equal 1, post.people.reload.size
end
def test_associate_existing_record_twice_should_add_to_target_twice
@@ -285,7 +285,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert posts(:thinking).people.include?(new_person)
end
- assert posts(:thinking).reload.people(true).include?(new_person)
+ assert posts(:thinking).reload.people.reload.include?(new_person)
end
def test_associate_new_by_building
@@ -310,8 +310,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
posts(:thinking).save
end
- assert posts(:thinking).reload.people(true).collect(&:first_name).include?("Bob")
- assert posts(:thinking).reload.people(true).collect(&:first_name).include?("Ted")
+ assert posts(:thinking).reload.people.reload.collect(&:first_name).include?("Bob")
+ assert posts(:thinking).reload.people.reload.collect(&:first_name).include?("Ted")
end
def test_build_then_save_with_has_many_inverse
@@ -356,7 +356,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert posts(:welcome).people.empty?
end
- assert posts(:welcome).reload.people(true).empty?
+ assert posts(:welcome).reload.people.reload.empty?
end
def test_destroy_association
@@ -367,7 +367,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
assert posts(:welcome).reload.people.empty?
- assert posts(:welcome).people(true).empty?
+ assert posts(:welcome).people.reload.empty?
end
def test_destroy_all
@@ -378,7 +378,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
assert posts(:welcome).reload.people.empty?
- assert posts(:welcome).people(true).empty?
+ assert posts(:welcome).people.reload.empty?
end
def test_should_raise_exception_for_destroying_mismatching_records
@@ -539,7 +539,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
def test_replace_association
- assert_queries(4){posts(:welcome);people(:david);people(:michael); posts(:welcome).people(true)}
+ 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)
@@ -552,8 +552,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert !posts(:welcome).people.include?(people(:michael))
}
- assert posts(:welcome).reload.people(true).include?(people(:david))
- assert !posts(:welcome).reload.people(true).include?(people(:michael))
+ assert posts(:welcome).reload.people.reload.include?(people(:david))
+ assert !posts(:welcome).reload.people.reload.include?(people(:michael))
end
def test_replace_order_is_preserved
@@ -592,7 +592,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert posts(:thinking).people.collect(&:first_name).include?("Jeb")
end
- assert posts(:thinking).reload.people(true).collect(&:first_name).include?("Jeb")
+ assert posts(:thinking).reload.people.reload.collect(&:first_name).include?("Jeb")
end
def test_through_record_is_built_when_created_with_where
@@ -668,7 +668,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
def test_clear_associations
- assert_queries(2) { posts(:welcome);posts(:welcome).people(true) }
+ assert_queries(2) { posts(:welcome);posts(:welcome).people.reload }
assert_queries(1) do
posts(:welcome).people.clear
@@ -678,7 +678,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert posts(:welcome).people.empty?
end
- assert posts(:welcome).reload.people(true).empty?
+ assert posts(:welcome).reload.people.reload.empty?
end
def test_association_callback_ordering
@@ -750,7 +750,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_get_ids_for_loaded_associations
person = people(:michael)
- person.posts(true)
+ person.posts.reload
assert_queries(0) do
person.post_ids
person.post_ids
@@ -828,14 +828,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(true).include?(category)
+ assert author.named_categories.reload.include?(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(true).include?(category)
+ assert author.named_categories.reload.include?(category)
end
def test_collection_exists
@@ -850,7 +850,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
category = author.named_categories.create(:name => "Primary")
author.named_categories.delete(category)
assert !Categorization.exists?(:author_id => author.id, :named_category_name => category.name)
- assert author.named_categories(true).empty?
+ assert author.named_categories.reload.empty?
end
def test_collection_singular_ids_getter_with_string_primary_keys
@@ -871,10 +871,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_nothing_raised do
book = books(:awdr)
book.subscriber_ids = [subscribers(:second).nick]
- assert_equal [subscribers(:second)], book.subscribers(true)
+ assert_equal [subscribers(:second)], book.subscribers.reload
book.subscriber_ids = []
- assert_equal [], book.subscribers(true)
+ assert_equal [], book.subscribers.reload
end
end
@@ -960,7 +960,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal 1, category.categorizations.where(:special => true).count
end
- def test_joining_has_many_through_with_uniq
+ def test_joining_has_many_through_with_distinct
mary = Author.joins(:unique_categorized_posts).where(:id => authors(:mary).id).first
assert_equal 1, mary.unique_categorized_posts.length
assert_equal 1, mary.unique_categorized_post_ids.length
@@ -1040,14 +1040,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
end
- def test_save_should_not_raise_exception_when_join_record_has_errors
- repair_validations(Categorization) do
- Categorization.validate { |r| r.errors[:base] << 'Invalid Categorization' }
- c = Category.create(:name => 'Fishing', :authors => [Author.first])
- c.save
- end
- end
-
def test_assign_array_to_new_record_builds_join_records
c = Category.new(:name => 'Fishing', :authors => [Author.first])
assert_equal 1, c.categorizations.size
@@ -1072,11 +1064,11 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
end
- def test_create_bang_returns_falsy_when_join_record_has_errors
+ def test_save_returns_falsy_when_join_record_has_errors
repair_validations(Categorization) do
Categorization.validate { |r| r.errors[:base] << 'Invalid Categorization' }
c = Category.new(:name => 'Fishing', :authors => [Author.first])
- assert !c.save
+ assert_not c.save
end
end
@@ -1166,4 +1158,45 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
post_through = organization.posts.build
assert_equal post_direct.author_id, post_through.author_id
end
+
+ def test_has_many_through_with_scope_that_should_not_be_fully_merged
+ Club.has_many :distinct_memberships, -> { distinct }, class_name: "Membership"
+ Club.has_many :special_favourites, through: :distinct_memberships, source: :member
+
+ 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!
+ TenantMembership.create!(
+ member: member,
+ club: club
+ )
+
+ TenantMembership.current_member = member
+
+ tenant_clubs = member.tenant_clubs
+ assert_equal [club], tenant_clubs
+
+ TenantMembership.current_member = nil
+
+ other_member = Member.create!
+ other_club = Club.create!
+ TenantMembership.create!(
+ member: other_member,
+ club: other_club
+ )
+
+ tenant_clubs = other_member.tenant_clubs
+ assert_equal [other_club], tenant_clubs
+ ensure
+ TenantMembership.current_member = nil
+ end
end
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 5c2e5e7b43..5a8afaf4d2 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -178,6 +178,25 @@ 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)
@@ -332,7 +351,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert a.persisted?
assert_equal a, firm.account
assert_equal a, firm.account
- assert_equal a, firm.account(true)
+ firm.association(:account).reload
+ assert_equal a, firm.account
end
def test_save_still_works_after_accessing_nil_has_one
@@ -607,4 +627,10 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
end
end
end
+
+ def test_association_force_reload_with_only_true_is_deprecated
+ firm = Firm.find(1)
+
+ assert_deprecated { firm.account(true) }
+ 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 f8772547a2..b2b46812b9 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -16,6 +16,10 @@ require 'models/owner'
require 'models/post'
require 'models/comment'
require 'models/categorization'
+require 'models/customer'
+require 'models/carrier'
+require 'models/shop_account'
+require 'models/customer_carrier'
class HasOneThroughAssociationsTest < ActiveRecord::TestCase
fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations, :minivans,
@@ -245,12 +249,14 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
assert_not_nil @member_detail.member_type
@member_detail.destroy
assert_queries(1) do
- assert_not_nil @member_detail.member_type(true)
+ @member_detail.association(:member_type).reload
+ assert_not_nil @member_detail.member_type
end
@member_detail.member.destroy
assert_queries(1) do
- assert_nil @member_detail.member_type(true)
+ @member_detail.association(:member_type).reload
+ assert_nil @member_detail.member_type
end
end
@@ -344,4 +350,34 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
end
end
end
+
+ def test_has_one_through_do_not_cache_association_reader_if_the_though_method_has_default_scopes
+ customer = Customer.create!
+ carrier = Carrier.create!
+ customer_carrier = CustomerCarrier.create!(
+ customer: customer,
+ carrier: carrier,
+ )
+ account = ShopAccount.create!(customer_carrier: customer_carrier)
+
+ CustomerCarrier.current_customer = customer
+
+ account_carrier = account.carrier
+ assert_equal carrier, account_carrier
+
+ CustomerCarrier.current_customer = nil
+
+ other_carrier = Carrier.create!
+ other_customer = Customer.create!
+ other_customer_carrier = CustomerCarrier.create!(
+ customer: other_customer,
+ carrier: other_carrier,
+ )
+ other_account = ShopAccount.create!(customer_carrier: other_customer_carrier)
+
+ account_carrier = other_account.carrier
+ assert_equal other_carrier, account_carrier
+ ensure
+ CustomerCarrier.current_customer = nil
+ end
end
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index 213be50e67..f6dddaf5b4 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -35,12 +35,12 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert categories(:sti_test).authors.include?(authors(:mary))
end
- def test_has_many_uniq_through_join_model
+ def test_has_many_distinct_through_join_model
assert_equal 2, authors(:mary).categorized_posts.size
assert_equal 1, authors(:mary).unique_categorized_posts.size
end
- def test_has_many_uniq_through_count
+ def test_has_many_distinct_through_count
author = authors(:mary)
assert !authors(:mary).unique_categorized_posts.loaded?
assert_queries(1) { assert_equal 1, author.unique_categorized_posts.count }
@@ -49,7 +49,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert !authors(:mary).unique_categorized_posts.loaded?
end
- def test_has_many_uniq_through_find
+ def test_has_many_distinct_through_find
assert_equal 1, authors(:mary).unique_categorized_posts.to_a.size
end
@@ -213,7 +213,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
old_count = Tagging.count
post.destroy
assert_equal old_count-1, Tagging.count
- assert_nil posts(:welcome).tagging(true)
+ posts(:welcome).association(:tagging).reload
+ assert_nil posts(:welcome).tagging
end
def test_delete_polymorphic_has_one_with_nullify
@@ -224,7 +225,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
old_count = Tagging.count
post.destroy
assert_equal old_count, Tagging.count
- assert_nil posts(:welcome).tagging(true)
+ posts(:welcome).association(:tagging).reload
+ assert_nil posts(:welcome).tagging
end
def test_has_many_with_piggyback
@@ -461,7 +463,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert saved_post.tags.include?(new_tag)
assert new_tag.persisted?
- assert saved_post.reload.tags(true).include?(new_tag)
+ assert saved_post.reload.tags.reload.include?(new_tag)
new_post = Post.new(:title => "Association replacement works!", :body => "You best believe it.")
@@ -474,7 +476,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
new_post.save!
assert new_post.persisted?
- assert new_post.reload.tags(true).include?(saved_tag)
+ assert new_post.reload.tags.reload.include?(saved_tag)
assert !posts(:thinking).tags.build.persisted?
assert !posts(:thinking).tags.new.persisted?
@@ -490,7 +492,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
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(true).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 },
@@ -498,7 +500,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
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(true).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 },
@@ -506,7 +508,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
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(true).size)
+ assert_equal(count + 4, post_thinking.tags.reload.size)
# Raises if the wrong reflection name is used to set the Edge belongs_to
assert_nothing_raised { vertices(:vertex_1).sinks << vertices(:vertex_5) }
@@ -544,11 +546,11 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
book = Book.create!(:name => 'Getting Real')
book_awdr = books(:awdr)
book_awdr.references << book
- assert_equal(count + 1, book_awdr.references(true).size)
+ assert_equal(count + 1, book_awdr.references.reload.size)
assert_nothing_raised { book_awdr.references.delete(book) }
assert_equal(count, book_awdr.references.size)
- assert_equal(count, book_awdr.references(true).size)
+ assert_equal(count, book_awdr.references.reload.size)
assert_equal(references_before.sort, book_awdr.references.sort)
end
@@ -558,14 +560,14 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
tag = Tag.create!(:name => 'doomed')
post_thinking = posts(:thinking)
post_thinking.tags << tag
- assert_equal(count + 1, post_thinking.taggings(true).size)
- assert_equal(count + 1, post_thinking.reload.tags(true).size)
+ assert_equal(count + 1, post_thinking.taggings.reload.size)
+ assert_equal(count + 1, post_thinking.reload.tags.reload.size)
assert_not_equal(tags_before, post_thinking.tags.sort)
assert_nothing_raised { post_thinking.tags.delete(tag) }
assert_equal(count, post_thinking.tags.size)
- assert_equal(count, post_thinking.tags(true).size)
- assert_equal(count, post_thinking.taggings(true).size)
+ assert_equal(count, post_thinking.tags.reload.size)
+ assert_equal(count, post_thinking.taggings.reload.size)
assert_equal(tags_before, post_thinking.tags.sort)
end
@@ -577,11 +579,11 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
quaked = Tag.create!(:name => 'quaked')
post_thinking = posts(:thinking)
post_thinking.tags << doomed << doomed2
- assert_equal(count + 2, post_thinking.reload.tags(true).size)
+ assert_equal(count + 2, post_thinking.reload.tags.reload.size)
assert_nothing_raised { post_thinking.tags.delete(doomed, doomed2, quaked) }
assert_equal(count, post_thinking.tags.size)
- assert_equal(count, post_thinking.tags(true).size)
+ assert_equal(count, post_thinking.tags.reload.size)
assert_equal(tags_before, post_thinking.tags.sort)
end
@@ -625,7 +627,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert_equal [comments(:does_it_hurt)], authors(:david).special_post_comments
end
- def test_uniq_has_many_through_should_retain_order
+ def test_distinct_has_many_through_should_retain_order
comment_ids = authors(:david).comments.map(&:id)
assert_equal comment_ids.sort, authors(:david).ordered_uniq_comments.map(&:id)
assert_equal comment_ids.sort.reverse, authors(:david).ordered_uniq_comments_desc.map(&:id)
diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb
index 31b68c940e..b040485d99 100644
--- a/activerecord/test/cases/associations/nested_through_associations_test.rb
+++ b/activerecord/test/cases/associations/nested_through_associations_test.rb
@@ -495,7 +495,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
groucho = members(:groucho)
founding = member_types(:founding)
- assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do
+ assert_raises(ActiveRecord::HasOneThroughNestedAssociationsAreReadonly) do
groucho.nested_member_type = founding
end
end
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 3d202a5527..08f790e21b 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -93,8 +93,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"
- 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"
+ 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
end
def test_using_limitable_reflections_helper
@@ -110,10 +112,13 @@ class AssociationsTest < ActiveRecord::TestCase
def test_force_reload_is_uncached
firm = Firm.create!("name" => "A New Firm, Inc")
Client.create!("name" => "TheClient.com", :firm => firm)
- 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 {} }
+
+ 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
diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb
index 927d7950a5..264b275181 100644
--- a/activerecord/test/cases/attributes_test.rb
+++ b/activerecord/test/cases/attributes_test.rb
@@ -125,8 +125,24 @@ module ActiveRecord
assert_equal "from user", model.wibble
end
+ test "procs for default values" do
+ klass = Class.new(OverloadedType) do
+ @@counter = 0
+ attribute :counter, :integer, default: -> { @@counter += 1 }
+ end
+
+ assert_equal 1, klass.new.counter
+ assert_equal 2, klass.new.counter
+ end
+
+ test "user provided defaults are persisted even if unchanged" do
+ model = OverloadedType.create!
+
+ assert_equal "the overloaded default", model.reload.string_with_default
+ end
+
if current_adapter?(:PostgreSQLAdapter)
- test "arrays types can be specified" do
+ test "array types can be specified" do
klass = Class.new(OverloadedType) do
attribute :my_array, :string, limit: 50, array: true
attribute :my_int_array, :integer, array: true
@@ -136,7 +152,7 @@ module ActiveRecord
Type::String.new(limit: 50))
int_array = ConnectionAdapters::PostgreSQL::OID::Array.new(
Type::Integer.new)
- refute_equal string_array, int_array
+ assert_not_equal string_array, int_array
assert_equal string_array, klass.type_for_attribute("my_array")
assert_equal int_array, klass.type_for_attribute("my_int_array")
end
@@ -151,7 +167,7 @@ module ActiveRecord
Type::String.new(limit: 50))
int_range = ConnectionAdapters::PostgreSQL::OID::Range.new(
Type::Integer.new)
- refute_equal string_range, int_range
+ assert_not_equal string_range, int_range
assert_equal string_range, klass.type_for_attribute("my_range")
assert_equal int_range, klass.type_for_attribute("my_int_range")
end
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 8f0d7bd037..37ec3f1106 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -43,7 +43,7 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
reference = Class.new(ActiveRecord::Base) {
self.table_name = "references"
def self.name; 'Reference'; end
- belongs_to :person, autosave: true, class: person
+ belongs_to :person, autosave: true, anonymous_class: person
}
u = person.create!(first_name: 'cool')
@@ -67,6 +67,14 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
assert_no_difference_when_adding_callbacks_twice_for Pirate, :parrots
end
+ def test_cyclic_autosaves_do_not_add_multiple_validations
+ ship = ShipWithoutNestedAttributes.new
+ ship.prisoners.build
+
+ assert_not ship.valid?
+ assert_equal 1, ship.errors[:name].length
+ end
+
private
def assert_no_difference_when_adding_callbacks_twice_for(model, association_name)
@@ -149,7 +157,8 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas
assert_equal a, firm.account
assert firm.save
assert_equal a, firm.account
- assert_equal a, firm.account(true)
+ firm.association(:account).reload
+ assert_equal a, firm.account
end
def test_assignment_before_either_saved
@@ -162,7 +171,8 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas
assert firm.persisted?
assert a.persisted?
assert_equal a, firm.account
- assert_equal a, firm.account(true)
+ firm.association(:account).reload
+ assert_equal a, firm.account
end
def test_not_resaved_when_unchanged
@@ -248,7 +258,8 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
assert apple.save
assert apple.persisted?
assert_equal apple, client.firm
- assert_equal apple, client.firm(true)
+ client.association(:firm).reload
+ assert_equal apple, client.firm
end
def test_assignment_before_either_saved
@@ -261,7 +272,8 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
assert final_cut.persisted?
assert apple.persisted?
assert_equal apple, final_cut.firm
- assert_equal apple, final_cut.firm(true)
+ final_cut.association(:firm).reload
+ assert_equal apple, final_cut.firm
end
def test_store_two_association_with_one_save
@@ -456,7 +468,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
assert_equal new_client, companies(:first_firm).clients_of_firm.last
assert !companies(:first_firm).save
assert !new_client.persisted?
- assert_equal 2, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 2, companies(:first_firm).clients_of_firm.reload.size
end
def test_adding_before_save
@@ -481,7 +493,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
assert_equal no_of_clients + 2, Client.count # Clients were saved to database.
assert_equal 2, new_firm.clients_of_firm.size
- assert_equal 2, new_firm.clients_of_firm(true).size
+ assert_equal 2, new_firm.clients_of_firm.reload.size
end
def test_assign_ids
@@ -510,7 +522,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
company.name += '-changed'
assert_queries(2) { assert company.save }
assert new_client.persisted?
- assert_equal 3, company.clients_of_firm(true).size
+ assert_equal 3, company.clients_of_firm.reload.size
end
def test_build_many_before_save
@@ -519,7 +531,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
company.name += '-changed'
assert_queries(3) { assert company.save }
- assert_equal 4, company.clients_of_firm(true).size
+ assert_equal 4, company.clients_of_firm.reload.size
end
def test_build_via_block_before_save
@@ -530,7 +542,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
company.name += '-changed'
assert_queries(2) { assert company.save }
assert new_client.persisted?
- assert_equal 3, company.clients_of_firm(true).size
+ assert_equal 3, company.clients_of_firm.reload.size
end
def test_build_many_via_block_before_save
@@ -543,7 +555,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
company.name += '-changed'
assert_queries(3) { assert company.save }
- assert_equal 4, company.clients_of_firm(true).size
+ assert_equal 4, company.clients_of_firm.reload.size
end
def test_replace_on_new_object
@@ -1142,6 +1154,13 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
def test_should_not_load_the_associated_model
assert_queries(1) { @pirate.catchphrase = 'Arr'; @pirate.save! }
end
+
+ def test_mark_for_destruction_is_ignored_without_autosave_true
+ ship = ShipWithoutNestedAttributes.new(name: "The Black Flag")
+ ship.parts.build.mark_for_destruction
+
+ assert_not ship.valid?
+ end
end
class TestAutosaveAssociationOnAHasOneThroughAssociation < ActiveRecord::TestCase
@@ -1278,6 +1297,16 @@ module AutosaveAssociationOnACollectionAssociationTests
assert_equal new_names, @pirate.reload.send(@association_name).map(&:name)
end
+ def test_should_update_children_when_autosave_is_true_and_parent_is_new_but_child_is_not
+ parrot = Parrot.create!(name: "Polly")
+ parrot.name = "Squawky"
+ pirate = Pirate.new(parrots: [parrot], catchphrase: "Arrrr")
+
+ pirate.save!
+
+ assert_equal "Squawky", parrot.reload.name
+ end
+
def test_should_automatically_validate_the_associated_models
@pirate.send(@association_name).each { |child| child.name = '' }
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 4306738670..382adbbdc7 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
require "cases/helper"
-require 'active_support/concurrency/latch'
require 'models/post'
require 'models/author'
require 'models/topic'
@@ -29,6 +28,7 @@ require 'models/bird'
require 'models/car'
require 'models/bulb'
require 'rexml/document'
+require 'concurrent/atomics'
class FirstAbstractClass < ActiveRecord::Base
self.abstract_class = true
@@ -1411,15 +1411,13 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_uniq_delegates_to_scoped
- scope = stub
- Bird.stubs(:all).returns(mock(:uniq => scope))
- assert_equal scope, Bird.uniq
+ assert_deprecated do
+ assert_equal Bird.all.distinct, Bird.uniq
+ end
end
def test_distinct_delegates_to_scoped
- scope = stub
- Bird.stubs(:all).returns(mock(:distinct => scope))
- assert_equal scope, Bird.distinct
+ assert_equal Bird.all.distinct, Bird.distinct
end
def test_table_name_with_2_abstract_subclasses
@@ -1508,20 +1506,20 @@ class BasicsTest < ActiveRecord::TestCase
orig_handler = klass.connection_handler
new_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
after_handler = nil
- latch1 = ActiveSupport::Concurrency::Latch.new
- latch2 = ActiveSupport::Concurrency::Latch.new
+ latch1 = Concurrent::CountDownLatch.new
+ latch2 = Concurrent::CountDownLatch.new
t = Thread.new do
klass.connection_handler = new_handler
- latch1.release
- latch2.await
+ latch1.count_down
+ latch2.wait
after_handler = klass.connection_handler
end
- latch1.await
+ latch1.wait
klass.connection_handler = orig_handler
- latch2.release
+ latch2.count_down
t.join
assert_equal after_handler, new_handler
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index 0791dde1f2..48dc8135ab 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -53,7 +53,7 @@ class EachTest < ActiveRecord::TestCase
end
def test_each_should_raise_if_select_is_set_without_id
- assert_raise(RuntimeError) do
+ assert_raise(ArgumentError) do
Post.select(:title).find_each(batch_size: 1) { |post|
flunk "should not call this block"
}
@@ -199,7 +199,7 @@ class EachTest < ActiveRecord::TestCase
def test_find_in_batches_should_return_an_enumerator
enum = nil
- assert_queries(0) do
+ assert_no_queries do
enum = Post.find_in_batches(:batch_size => 1)
end
assert_queries(4) do
@@ -210,6 +210,234 @@ class EachTest < ActiveRecord::TestCase
end
end
+ def test_in_batches_should_not_execute_any_query
+ assert_no_queries do
+ assert_kind_of ActiveRecord::Batches::BatchEnumerator, Post.in_batches(of: 2)
+ end
+ end
+
+ def test_in_batches_should_yield_relation_if_block_given
+ assert_queries(6) do
+ Post.in_batches(of: 2) do |relation|
+ assert_kind_of ActiveRecord::Relation, relation
+ end
+ end
+ end
+
+ def test_in_batches_should_be_enumerable_if_no_block_given
+ assert_queries(6) do
+ Post.in_batches(of: 2).each do |relation|
+ assert_kind_of ActiveRecord::Relation, relation
+ end
+ end
+ end
+
+ def test_in_batches_each_record_should_yield_record_if_block_is_given
+ assert_queries(6) do
+ Post.in_batches(of: 2).each_record do |post|
+ assert post.title.present?
+ assert_kind_of Post, post
+ end
+ end
+ end
+
+ def test_in_batches_each_record_should_return_enumerator_if_no_block_given
+ assert_queries(6) do
+ Post.in_batches(of: 2).each_record.with_index do |post, i|
+ assert post.title.present?
+ assert_kind_of Post, post
+ end
+ end
+ end
+
+ def test_in_batches_each_record_should_be_ordered_by_id
+ ids = Post.order('id ASC').pluck(:id)
+ assert_queries(6) do
+ Post.in_batches(of: 2).each_record.with_index do |post, i|
+ assert_equal ids[i], post.id
+ end
+ end
+ end
+
+ def test_in_batches_update_all_affect_all_records
+ assert_queries(6 + 6) do # 6 selects, 6 updates
+ Post.in_batches(of: 2).update_all(title: "updated-title")
+ end
+ assert_equal Post.all.pluck(:title), ["updated-title"] * Post.count
+ end
+
+ def test_in_batches_delete_all_should_not_delete_records_in_other_batches
+ not_deleted_count = Post.where('id <= 2').count
+ Post.where('id > 2').in_batches(of: 2).delete_all
+ assert_equal 0, Post.where('id > 2').count
+ assert_equal not_deleted_count, Post.count
+ end
+
+ def test_in_batches_should_not_be_loaded
+ Post.in_batches(of: 1) do |relation|
+ assert_not relation.loaded?
+ end
+
+ Post.in_batches(of: 1, load: false) do |relation|
+ assert_not relation.loaded?
+ end
+ end
+
+ def test_in_batches_should_be_loaded
+ Post.in_batches(of: 1, load: true) do |relation|
+ assert relation.loaded?
+ end
+ end
+
+ def test_in_batches_if_not_loaded_executes_more_queries
+ assert_queries(@total + 1) do
+ Post.in_batches(of: 1, load: false) do |relation|
+ assert_not relation.loaded?
+ end
+ end
+ end
+
+ def test_in_batches_should_return_relations
+ assert_queries(@total + 1) do
+ Post.in_batches(of: 1) do |relation|
+ assert_kind_of ActiveRecord::Relation, relation
+ end
+ end
+ end
+
+ def test_in_batches_should_start_from_the_start_option
+ post = Post.order('id ASC').where('id >= ?', 2).first
+ assert_queries(2) do
+ relation = Post.in_batches(of: 1, begin_at: 2).first
+ assert_equal post, relation.first
+ end
+ end
+
+ def test_in_batches_should_end_at_the_end_option
+ post = Post.order('id DESC').where('id <= ?', 5).first
+ assert_queries(7) do
+ relation = Post.in_batches(of: 1, end_at: 5, load: true).reverse_each.first
+ assert_equal post, relation.last
+ end
+ end
+
+ def test_in_batches_shouldnt_execute_query_unless_needed
+ assert_queries(2) do
+ Post.in_batches(of: @total) { |relation| assert_kind_of ActiveRecord::Relation, relation }
+ end
+
+ assert_queries(1) do
+ Post.in_batches(of: @total + 1) { |relation| assert_kind_of ActiveRecord::Relation, relation }
+ end
+ end
+
+ def test_in_batches_should_quote_batch_order
+ c = Post.connection
+ assert_sql(/ORDER BY #{c.quote_table_name('posts')}.#{c.quote_column_name('id')}/) do
+ Post.in_batches(of: 1) do |relation|
+ assert_kind_of ActiveRecord::Relation, relation
+ assert_kind_of Post, relation.first
+ end
+ end
+ end
+
+ def test_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
+ raise StandardError.new("not_a_post had #id called on it")
+ end
+
+ assert_nothing_raised do
+ Post.in_batches(of: 1) do |relation|
+ assert_kind_of ActiveRecord::Relation, relation
+ assert_kind_of Post, relation.first
+
+ relation = [not_a_post] * relation.count
+ end
+ end
+ end
+
+ def test_in_batches_should_not_ignore_default_scope_without_order_statements
+ special_posts_ids = SpecialPostWithDefaultScope.all.map(&:id).sort
+ posts = []
+ SpecialPostWithDefaultScope.in_batches do |relation|
+ posts.concat(relation)
+ end
+ assert_equal special_posts_ids, posts.map(&:id)
+ end
+
+ def test_in_batches_should_not_modify_passed_options
+ assert_nothing_raised do
+ Post.in_batches({ of: 42, begin_at: 1 }.freeze){}
+ end
+ end
+
+ def test_in_batches_should_use_any_column_as_primary_key
+ nick_order_subscribers = Subscriber.order('nick asc')
+ start_nick = nick_order_subscribers.second.nick
+
+ subscribers = []
+ Subscriber.in_batches(of: 1, begin_at: start_nick) do |relation|
+ subscribers.concat(relation)
+ end
+
+ assert_equal nick_order_subscribers[1..-1].map(&:id), subscribers.map(&:id)
+ end
+
+ def test_in_batches_should_use_any_column_as_primary_key_when_start_is_not_specified
+ assert_queries(Subscriber.count + 1) do
+ Subscriber.in_batches(of: 1, load: true) do |relation|
+ assert_kind_of ActiveRecord::Relation, relation
+ assert_kind_of Subscriber, relation.first
+ end
+ end
+ end
+
+ def test_in_batches_should_return_an_enumerator
+ enum = nil
+ assert_no_queries do
+ enum = Post.in_batches(of: 1)
+ end
+ assert_queries(4) do
+ enum.first(4) do |relation|
+ assert_kind_of ActiveRecord::Relation, relation
+ assert_kind_of Post, relation.first
+ end
+ end
+ end
+
+ def test_in_batches_relations_should_not_overlap_with_each_other
+ seen_posts = []
+ Post.in_batches(of: 2, load: true) do |relation|
+ relation.to_a.each do |post|
+ assert_not seen_posts.include?(post)
+ seen_posts << post
+ end
+ end
+ end
+
+ def test_in_batches_relations_with_condition_should_not_overlap_with_each_other
+ seen_posts = []
+ author_id = Post.first.author_id
+ posts_by_author = Post.where(author_id: author_id)
+ Post.in_batches(of: 2) do |batch|
+ seen_posts += batch.where(author_id: author_id)
+ end
+
+ assert_equal posts_by_author.pluck(:id).sort, seen_posts.map(&:id).sort
+ end
+
+ def test_in_batches_relations_update_all_should_not_affect_matching_records_in_other_batches
+ Post.update_all(author_id: 0)
+ person = Post.last
+ person.update_attributes(author_id: 1)
+
+ Post.in_batches(of: 2) do |batch|
+ batch.where('author_id >= 1').update_all('author_id = author_id + 1')
+ end
+ assert_equal 2, person.reload.author_id # incremented only once
+ end
+
def test_find_in_batches_start_deprecated
assert_deprecated do
assert_queries(@total) do
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 8fc996ee74..aa10817527 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -359,7 +359,10 @@ class CalculationsTest < ActiveRecord::TestCase
def test_count_with_distinct
assert_equal 4, Account.select(:credit_limit).distinct.count
- assert_equal 4, Account.select(:credit_limit).uniq.count
+
+ assert_deprecated do
+ assert_equal 4, Account.select(:credit_limit).uniq.count
+ end
end
def test_count_with_aliased_attribute
@@ -504,8 +507,8 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal [ topic.written_on ], relation.pluck(:written_on)
end
- def test_pluck_and_uniq
- assert_equal [50, 53, 55, 60], Account.order(:credit_limit).uniq.pluck(:credit_limit)
+ def test_pluck_and_distinct
+ assert_equal [50, 53, 55, 60], Account.order(:credit_limit).distinct.pluck(:credit_limit)
end
def test_pluck_in_relation
@@ -629,6 +632,27 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal [part.id], ShipPart.joins(:trinkets).pluck(:id)
end
+ def test_pluck_loaded_relation
+ companies = Company.order(:id).limit(3).load
+ assert_no_queries do
+ assert_equal ['37signals', 'Summit', 'Microsoft'], companies.pluck(:name)
+ end
+ end
+
+ def test_pluck_loaded_relation_multiple_columns
+ companies = Company.order(:id).limit(3).load
+ assert_no_queries do
+ assert_equal [[1, '37signals'], [2, 'Summit'], [3, 'Microsoft']], companies.pluck(:id, :name)
+ end
+ end
+
+ def test_pluck_loaded_relation_sql_fragment
+ companies = Company.order(:name).limit(3).load
+ assert_queries 1 do
+ assert_equal ['37signals', 'Apex', 'Ex Nihilo'], companies.pluck('DISTINCT name')
+ end
+ end
+
def test_grouped_calculation_with_polymorphic_relation
part = ShipPart.create!(name: "has trinket")
part.trinkets.create!
@@ -647,4 +671,14 @@ class CalculationsTest < ActiveRecord::TestCase
developer.ratings.includes(comment: :post).where(posts: { id: 1 }).count
end
end
+
+ def test_sum_uses_enumerable_version_when_block_is_given
+ block_called = false
+ relation = Client.all.load
+
+ assert_no_queries do
+ assert_equal 0, relation.sum { block_called = true; 0 }
+ end
+ assert block_called
+ end
end
diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb
index 3ae4a6eade..73ac30e547 100644
--- a/activerecord/test/cases/callbacks_test.rb
+++ b/activerecord/test/cases/callbacks_test.rb
@@ -451,6 +451,7 @@ class CallbacksTest < ActiveRecord::TestCase
assert !david.save
exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! }
assert_equal exc.record, david
+ assert_equal "Failed to save the record", exc.message
end
david = ImmutableDeveloper.find(1)
@@ -494,6 +495,7 @@ class CallbacksTest < ActiveRecord::TestCase
assert !david.destroy
exc = assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! }
assert_equal exc.record, david
+ assert_equal "Failed to destroy the record", exc.message
end
assert_not_nil ImmutableDeveloper.find_by_id(1)
diff --git a/activerecord/test/cases/collection_cache_key_test.rb b/activerecord/test/cases/collection_cache_key_test.rb
new file mode 100644
index 0000000000..724234d7f4
--- /dev/null
+++ b/activerecord/test/cases/collection_cache_key_test.rb
@@ -0,0 +1,70 @@
+require "cases/helper"
+require "models/computer"
+require "models/developer"
+require "models/project"
+require "models/topic"
+require "models/post"
+require "models/comment"
+
+module ActiveRecord
+ class CollectionCacheKeyTest < ActiveRecord::TestCase
+ fixtures :developers, :projects, :developers_projects, :topics, :comments, :posts
+
+ test "collection_cache_key on model" do
+ assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\Z/, Developer.collection_cache_key)
+ end
+
+ test "cache_key for relation" do
+ developers = Developer.where(name: "David")
+ last_developer_timestamp = developers.order(updated_at: :desc).first.updated_at
+
+ assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\Z/, developers.cache_key)
+
+ /\Adevelopers\/query-(\h+)-(\d+)-(\d+)\Z/ =~ developers.cache_key
+
+ assert_equal Digest::MD5.hexdigest(developers.to_sql), $1
+ assert_equal developers.count.to_s, $2
+ assert_equal last_developer_timestamp.to_s(ActiveRecord::Base.cache_timestamp_format), $3
+ end
+
+ test "it triggers at most one query" do
+ developers = Developer.where(name: "David")
+
+ 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
+ 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")
+
+ assert_not_equal developers.cache_key, other_relation.cache_key
+ end
+
+ test "cache_key for empty relation" do
+ developers = Developer.where(name: "Non Existent Developer")
+ assert_match(/\Adevelopers\/query-(\h+)-0\Z/, developers.cache_key)
+ end
+
+ test "cache_key with custom timestamp column" do
+ topics = Topic.where("title like ?", "%Topic%")
+ last_topic_timestamp = topics(:fifth).written_on.utc.to_s(:nsec)
+ assert_match(last_topic_timestamp, topics.cache_key(:written_on))
+ end
+
+ test "cache_key with unknown timestamp column" do
+ topics = Topic.where("title like ?", "%Topic%")
+ assert_raises(ActiveRecord::StatementInvalid) { topics.cache_key(:published_at) }
+ end
+
+ test "collection proxy provides a cache_key" do
+ developers = projects(:active_record).developers
+ assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\Z/, developers.cache_key)
+ end
+ end
+end
diff --git a/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb
index 662e19f35e..580568c8ac 100644
--- a/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb
+++ b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb
@@ -6,7 +6,7 @@ module ActiveRecord
class Pool < ConnectionPool
def insert_connection_for_test!(c)
synchronize do
- @connections << c
+ adopt_connection(c)
@available.add c
end
end
@@ -24,7 +24,9 @@ module ActiveRecord
def test_lease_twice
assert @adapter.lease, 'should lease adapter'
- assert_not @adapter.lease, 'should not lease adapter'
+ assert_raises(ActiveRecordError) do
+ @adapter.lease
+ end
end
def test_expire_mutates_in_use
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index b72f8ca88c..9b1865e8bb 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -46,6 +46,52 @@ module ActiveRecord
def test_connection_pools
assert_equal([@pool], @handler.connection_pools)
end
+
+ if Process.respond_to?(:fork)
+ def test_connection_pool_per_pid
+ object_id = ActiveRecord::Base.connection.object_id
+
+ rd, wr = IO.pipe
+ rd.binmode
+ wr.binmode
+
+ pid = fork {
+ rd.close
+ wr.write Marshal.dump ActiveRecord::Base.connection.object_id
+ wr.close
+ exit!
+ }
+
+ wr.close
+
+ Process.waitpid pid
+ assert_not_equal object_id, Marshal.load(rd.read)
+ rd.close
+ end
+
+ def test_retrieve_connection_pool_copies_schema_cache_from_ancestor_pool
+ @pool.schema_cache = @pool.connection.schema_cache
+ @pool.schema_cache.add('posts')
+
+ rd, wr = IO.pipe
+ rd.binmode
+ wr.binmode
+
+ pid = fork {
+ rd.close
+ pool = @handler.retrieve_connection_pool(@klass)
+ wr.write Marshal.dump pool.schema_cache.size
+ wr.close
+ exit!
+ }
+
+ wr.close
+
+ Process.waitpid pid
+ assert_equal @pool.schema_cache.size, Marshal.load(rd.read)
+ rd.close
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/connection_adapters/type_lookup_test.rb b/activerecord/test/cases/connection_adapters/type_lookup_test.rb
index 05c57985a1..7566863653 100644
--- a/activerecord/test/cases/connection_adapters/type_lookup_test.rb
+++ b/activerecord/test/cases/connection_adapters/type_lookup_test.rb
@@ -81,7 +81,11 @@ module ActiveRecord
def test_bigint_limit
cast_type = @connection.type_map.lookup("bigint")
- assert_equal 8, cast_type.limit
+ if current_adapter?(:OracleAdapter)
+ assert_equal 19, cast_type.limit
+ else
+ assert_equal 8, cast_type.limit
+ end
end
def test_decimal_without_scale
diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb
index f53c496ecd..dff6ea0fb0 100644
--- a/activerecord/test/cases/connection_management_test.rb
+++ b/activerecord/test/cases/connection_management_test.rb
@@ -26,29 +26,6 @@ module ActiveRecord
assert ActiveRecord::Base.connection_handler.active_connections?
end
- if Process.respond_to?(:fork)
- def test_connection_pool_per_pid
- object_id = ActiveRecord::Base.connection.object_id
-
- rd, wr = IO.pipe
- rd.binmode
- wr.binmode
-
- pid = fork {
- rd.close
- wr.write Marshal.dump ActiveRecord::Base.connection.object_id
- wr.close
- exit!
- }
-
- wr.close
-
- Process.waitpid pid
- assert_not_equal object_id, Marshal.load(rd.read)
- rd.close
- end
- end
-
def test_app_delegation
manager = ConnectionManagement.new(@app)
@@ -110,7 +87,7 @@ module ActiveRecord
assert ActiveRecord::Base.connection_handler.active_connections?
end
- test "proxy is polite to it's body and responds to it" do
+ test "proxy is polite to its body and responds to it" do
body = Class.new(String) { def to_path; "/path"; end }.new
app = lambda { |_| [200, {}, body] }
response_body = ConnectionManagement.new(app).call(@env)[2]
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index 8d15a76735..7ef5c93a48 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -1,5 +1,5 @@
require "cases/helper"
-require 'active_support/concurrency/latch'
+require 'concurrent/atomics'
module ActiveRecord
module ConnectionAdapters
@@ -100,7 +100,7 @@ module ActiveRecord
t = Thread.new { @pool.checkout }
# make sure our thread is in the timeout section
- Thread.pass until t.status == "sleep"
+ Thread.pass until @pool.num_waiting_in_queue == 1
connection = cs.first
connection.close
@@ -112,7 +112,7 @@ module ActiveRecord
t = Thread.new { @pool.checkout }
# make sure our thread is in the timeout section
- Thread.pass until t.status == "sleep"
+ Thread.pass until @pool.num_waiting_in_queue == 1
connection = cs.first
@pool.remove connection
@@ -133,15 +133,15 @@ module ActiveRecord
end
def test_reap_inactive
- ready = ActiveSupport::Concurrency::Latch.new
+ ready = Concurrent::CountDownLatch.new
@pool.checkout
child = Thread.new do
@pool.checkout
@pool.checkout
- ready.release
+ ready.count_down
Thread.stop
end
- ready.await
+ ready.wait
assert_equal 3, active_connections(@pool).size
@@ -204,13 +204,13 @@ module ActiveRecord
end
# The connection pool is "fair" if threads waiting for
- # connections receive them the order in which they began
+ # connections receive them in the order in which they began
# waiting. This ensures that we don't timeout one HTTP request
# even while well under capacity in a multi-threaded environment
# such as a Java servlet container.
#
# We don't need strict fairness: if two connections become
- # available at the same time, it's fine of two threads that were
+ # available at the same time, it's fine if two threads that were
# waiting acquire the connections out of order.
#
# Thus this test prepares waiting threads and then trickles in
@@ -234,7 +234,7 @@ module ActiveRecord
mutex.synchronize { errors << e }
end
}
- Thread.pass until t.status == "sleep"
+ Thread.pass until @pool.num_waiting_in_queue == i
t
end
@@ -271,7 +271,7 @@ module ActiveRecord
mutex.synchronize { errors << e }
end
}
- Thread.pass until t.status == "sleep"
+ Thread.pass until @pool.num_waiting_in_queue == i
t
end
@@ -341,6 +341,185 @@ module ActiveRecord
handler.establish_connection anonymous, nil
}
end
+
+ def test_pool_sets_connection_schema_cache
+ connection = pool.checkout
+ schema_cache = SchemaCache.new connection
+ schema_cache.add(:posts)
+ pool.schema_cache = schema_cache
+
+ pool.with_connection do |conn|
+ assert_not_same pool.schema_cache, conn.schema_cache
+ assert_equal pool.schema_cache.size, conn.schema_cache.size
+ assert_same pool.schema_cache.columns(:posts), conn.schema_cache.columns(:posts)
+ end
+
+ pool.checkin connection
+ end
+
+ def test_concurrent_connection_establishment
+ assert_operator @pool.connections.size, :<=, 1
+
+ all_threads_in_new_connection = Concurrent::CountDownLatch.new(@pool.size - @pool.connections.size)
+ all_go = Concurrent::CountDownLatch.new
+
+ @pool.singleton_class.class_eval do
+ define_method(:new_connection) do
+ all_threads_in_new_connection.count_down
+ all_go.wait
+ super()
+ end
+ end
+
+ connecting_threads = []
+ @pool.size.times do
+ connecting_threads << Thread.new { @pool.checkout }
+ end
+
+ begin
+ Timeout.timeout(5) do
+ # the kernel of the whole test is here, everything else is just scaffolding,
+ # this latch will not be released unless conn. pool allows for concurrent
+ # connection creation
+ all_threads_in_new_connection.wait
+ end
+ rescue Timeout::Error
+ flunk 'pool unable to establish connections concurrently or implementation has ' <<
+ 'changed, this test then needs to patch a different :new_connection method'
+ ensure
+ # clean up the threads
+ all_go.count_down
+ connecting_threads.map(&:join)
+ end
+ end
+
+ def test_non_bang_disconnect_and_clear_reloadable_connections_throw_exception_if_threads_dont_return_their_conns
+ @pool.checkout_timeout = 0.001 # no need to delay test suite by waiting the whole full default timeout
+ [:disconnect, :clear_reloadable_connections].each do |group_action_method|
+ @pool.with_connection do |connection|
+ assert_raises(ExclusiveConnectionTimeoutError) do
+ Thread.new { @pool.send(group_action_method) }.join
+ end
+ end
+ end
+ end
+
+ def test_disconnect_and_clear_reloadable_connections_attempt_to_wait_for_threads_to_return_their_conns
+ [:disconnect, :disconnect!, :clear_reloadable_connections, :clear_reloadable_connections!].each do |group_action_method|
+ begin
+ thread = timed_join_result = nil
+ @pool.with_connection do |connection|
+ thread = Thread.new { @pool.send(group_action_method) }
+
+ # give the other `thread` some time to get stuck in `group_action_method`
+ timed_join_result = thread.join(0.3)
+ # thread.join # => `nil` means the other thread hasn't finished running and is still waiting for us to
+ # release our connection
+ assert_nil timed_join_result
+
+ # assert that since this is within default timeout our connection hasn't been forcefully taken away from us
+ assert @pool.active_connection?
+ end
+ ensure
+ thread.join if thread && !timed_join_result # clean up the other thread
+ end
+ end
+ end
+
+ def test_bang_versions_of_disconnect_and_clear_reloadable_connections_if_unable_to_aquire_all_connections_proceed_anyway
+ @pool.checkout_timeout = 0.001 # no need to delay test suite by waiting the whole full default timeout
+ [:disconnect!, :clear_reloadable_connections!].each do |group_action_method|
+ @pool.with_connection do |connection|
+ Thread.new { @pool.send(group_action_method) }.join
+ # assert connection has been forcefully taken away from us
+ assert_not @pool.active_connection?
+ end
+ end
+ end
+
+ def test_disconnect_and_clear_reloadable_connections_are_able_to_preempt_other_waiting_threads
+ 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
+
+ # 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
+ 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
+
+ def test_clear_reloadable_connections_creates_new_connections_for_waiting_threads_if_necessary
+ with_single_connection_pool do |pool|
+ conn = pool.connection # drain the only available connection
+ def conn.requires_reloading? # make sure it gets removed from the pool by clear_reloadable_connections
+ true
+ end
+
+ stuck_thread = Thread.new do
+ pool.with_connection {}
+ end
+
+ # wait for stuck_thread to get in queue
+ Thread.pass until pool.num_waiting_in_queue == 1
+
+ pool.clear_reloadable_connections
+
+ unless stuck_thread.join(2)
+ flunk 'clear_reloadable_connections must not let other connection waiting threads get stuck in queue'
+ end
+
+ assert_equal 0, pool.num_waiting_in_queue
+ end
+ end
+
+ private
+ def with_single_connection_pool
+ one_conn_spec = ActiveRecord::Base.connection_pool.spec.dup
+ one_conn_spec.config[:pool] = 1 # this is safe to do, because .dupped ConnectionSpecification also auto-dups its config
+ yield(pool = ConnectionPool.new(one_conn_spec))
+ ensure
+ pool.disconnect! if pool
+ end
end
end
end
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index 1f5055b2a2..922cb59280 100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
@@ -1,6 +1,7 @@
require 'cases/helper'
require 'models/topic'
require 'models/car'
+require 'models/aircraft'
require 'models/wheel'
require 'models/engine'
require 'models/reply'
@@ -198,4 +199,16 @@ class CounterCacheTest < ActiveRecord::TestCase
assert_equal 2, car.engines_count
assert_equal 2, car.reload.engines_count
end
+
+ test "update counters in a polymorphic relationship" do
+ aircraft = Aircraft.create!
+
+ assert_difference 'aircraft.reload.wheels_count' do
+ aircraft.wheels << Wheel.create!
+ end
+
+ assert_difference 'aircraft.reload.wheels_count', -1 do
+ aircraft.wheels.first.destroy
+ end
+ end
end
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index 3a7cc572e6..f5aaf22e13 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -623,32 +623,6 @@ class DirtyTest < ActiveRecord::TestCase
end
end
- test "defaults with type that implements `serialize`" do
- type = Class.new(ActiveRecord::Type::Value) do
- def cast(value)
- value.to_i
- end
-
- def serialize(value)
- value.to_s
- end
- end
-
- model_class = Class.new(ActiveRecord::Base) do
- self.table_name = 'numeric_data'
- attribute :foo, type.new, default: 1
- end
-
- model = model_class.new
- assert_not model.foo_changed?
-
- model = model_class.new(foo: 1)
- assert_not model.foo_changed?
-
- model = model_class.new(foo: '1')
- assert_not model.foo_changed?
- end
-
test "in place mutation detection" do
pirate = Pirate.create!(catchphrase: "arrrr")
pirate.catchphrase << " matey!"
@@ -729,6 +703,22 @@ class DirtyTest < ActiveRecord::TestCase
assert pirate.catchphrase_changed?(from: "arrrr", to: "arrrr matey!")
end
+ test "getters with side effects are allowed" do
+ klass = Class.new(Pirate) do
+ def catchphrase
+ if super.blank?
+ update_attribute(:catchphrase, "arr") # what could possibly go wrong?
+ end
+ super
+ end
+ end
+
+ pirate = klass.create!(catchphrase: "lol")
+ pirate.update_attribute(:catchphrase, nil)
+
+ assert_equal "arr", pirate.catchphrase
+ end
+
private
def with_partial_writes(klass, on = true)
old = klass.partial_writes?
diff --git a/activerecord/test/cases/disconnected_test.rb b/activerecord/test/cases/disconnected_test.rb
index 55f0e51717..c25089a420 100644
--- a/activerecord/test/cases/disconnected_test.rb
+++ b/activerecord/test/cases/disconnected_test.rb
@@ -21,7 +21,9 @@ class TestDisconnectedAdapter < ActiveRecord::TestCase
@connection.execute "SELECT count(*) from products"
@connection.disconnect!
assert_raises(ActiveRecord::StatementInvalid) do
- @connection.execute "SELECT count(*) from products"
+ silence_warnings do
+ @connection.execute "SELECT count(*) from products"
+ end
end
end
end
diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb
index eea184e530..7c930de97b 100644
--- a/activerecord/test/cases/enum_test.rb
+++ b/activerecord/test/cases/enum_test.rb
@@ -9,69 +9,83 @@ class EnumTest < ActiveRecord::TestCase
end
test "query state by predicate" do
- assert @book.proposed?
+ assert @book.published?
assert_not @book.written?
- assert_not @book.published?
+ assert_not @book.proposed?
- assert @book.unread?
+ assert @book.read?
+ assert @book.in_english?
+ assert @book.author_visibility_visible?
+ assert @book.illustrator_visibility_visible?
+ assert @book.with_medium_font_size?
end
test "query state with strings" do
- assert_equal "proposed", @book.status
- assert_equal "unread", @book.read_status
+ assert_equal "published", @book.status
+ assert_equal "read", @book.read_status
+ assert_equal "english", @book.language
+ assert_equal "visible", @book.author_visibility
+ assert_equal "visible", @book.illustrator_visibility
end
test "find via scope" do
- assert_equal @book, Book.proposed.first
- assert_equal @book, Book.unread.first
+ assert_equal @book, Book.published.first
+ assert_equal @book, Book.read.first
+ assert_equal @book, Book.in_english.first
+ assert_equal @book, Book.author_visibility_visible.first
+ assert_equal @book, Book.illustrator_visibility_visible.first
end
test "find via where with values" do
- proposed, written = Book.statuses[:proposed], Book.statuses[:written]
+ published, written = Book.statuses[:published], Book.statuses[:written]
- assert_equal @book, Book.where(status: proposed).first
- refute_equal @book, Book.where(status: written).first
- assert_equal @book, Book.where(status: [proposed]).first
- refute_equal @book, Book.where(status: [written]).first
- refute_equal @book, Book.where("status <> ?", proposed).first
+ assert_equal @book, Book.where(status: published).first
+ assert_not_equal @book, Book.where(status: written).first
+ assert_equal @book, Book.where(status: [published]).first
+ assert_not_equal @book, Book.where(status: [written]).first
+ assert_not_equal @book, Book.where("status <> ?", published).first
assert_equal @book, Book.where("status <> ?", written).first
end
test "find via where with symbols" do
- assert_equal @book, Book.where(status: :proposed).first
- refute_equal @book, Book.where(status: :written).first
- assert_equal @book, Book.where(status: [:proposed]).first
- refute_equal @book, Book.where(status: [:written]).first
- refute_equal @book, Book.where.not(status: :proposed).first
+ assert_equal @book, Book.where(status: :published).first
+ assert_not_equal @book, Book.where(status: :written).first
+ assert_equal @book, Book.where(status: [:published]).first
+ assert_not_equal @book, Book.where(status: [:written]).first
+ assert_not_equal @book, Book.where.not(status: :published).first
assert_equal @book, Book.where.not(status: :written).first
end
test "find via where with strings" do
- assert_equal @book, Book.where(status: "proposed").first
- refute_equal @book, Book.where(status: "written").first
- assert_equal @book, Book.where(status: ["proposed"]).first
- refute_equal @book, Book.where(status: ["written"]).first
- refute_equal @book, Book.where.not(status: "proposed").first
+ assert_equal @book, Book.where(status: "published").first
+ assert_not_equal @book, Book.where(status: "written").first
+ assert_equal @book, Book.where(status: ["published"]).first
+ assert_not_equal @book, Book.where(status: ["written"]).first
+ assert_not_equal @book, Book.where.not(status: "published").first
assert_equal @book, Book.where.not(status: "written").first
end
test "build from scope" do
assert Book.written.build.written?
- refute Book.written.build.proposed?
+ assert_not Book.written.build.proposed?
end
test "build from where" do
assert Book.where(status: Book.statuses[:written]).build.written?
- refute Book.where(status: Book.statuses[:written]).build.proposed?
+ assert_not Book.where(status: Book.statuses[:written]).build.proposed?
assert Book.where(status: :written).build.written?
- refute Book.where(status: :written).build.proposed?
+ assert_not Book.where(status: :written).build.proposed?
assert Book.where(status: "written").build.written?
- refute Book.where(status: "written").build.proposed?
+ assert_not Book.where(status: "written").build.proposed?
end
test "update by declaration" do
@book.written!
assert @book.written?
+ @book.in_english!
+ assert @book.in_english?
+ @book.author_visibility_visible!
+ assert @book.author_visibility_visible?
end
test "update by setter" do
@@ -96,42 +110,61 @@ class EnumTest < ActiveRecord::TestCase
test "enum changed attributes" do
old_status = @book.status
- @book.status = :published
+ old_language = @book.language
+ @book.status = :proposed
+ @book.language = :spanish
assert_equal old_status, @book.changed_attributes[:status]
+ assert_equal old_language, @book.changed_attributes[:language]
end
test "enum changes" do
old_status = @book.status
- @book.status = :published
- assert_equal [old_status, 'published'], @book.changes[:status]
+ old_language = @book.language
+ @book.status = :proposed
+ @book.language = :spanish
+ assert_equal [old_status, 'proposed'], @book.changes[:status]
+ assert_equal [old_language, 'spanish'], @book.changes[:language]
end
test "enum attribute was" do
old_status = @book.status
+ old_language = @book.language
@book.status = :published
+ @book.language = :spanish
assert_equal old_status, @book.attribute_was(:status)
+ assert_equal old_language, @book.attribute_was(:language)
end
test "enum attribute changed" do
- @book.status = :published
+ @book.status = :proposed
+ @book.language = :french
assert @book.attribute_changed?(:status)
+ assert @book.attribute_changed?(:language)
end
test "enum attribute changed to" do
- @book.status = :published
- assert @book.attribute_changed?(:status, to: 'published')
+ @book.status = :proposed
+ @book.language = :french
+ assert @book.attribute_changed?(:status, to: 'proposed')
+ assert @book.attribute_changed?(:language, to: 'french')
end
test "enum attribute changed from" do
old_status = @book.status
- @book.status = :published
+ old_language = @book.language
+ @book.status = :proposed
+ @book.language = :french
assert @book.attribute_changed?(:status, from: old_status)
+ assert @book.attribute_changed?(:language, from: old_language)
end
test "enum attribute changed from old status to new status" do
old_status = @book.status
- @book.status = :published
- assert @book.attribute_changed?(:status, from: old_status, to: 'published')
+ old_language = @book.language
+ @book.status = :proposed
+ @book.language = :french
+ assert @book.attribute_changed?(:status, from: old_status, to: 'proposed')
+ assert @book.attribute_changed?(:language, from: old_language, to: 'french')
end
test "enum didn't change" do
@@ -141,7 +174,7 @@ class EnumTest < ActiveRecord::TestCase
end
test "persist changes that are dirty" do
- @book.status = :published
+ @book.status = :proposed
assert @book.attribute_changed?(:status)
@book.status = :written
assert @book.attribute_changed?(:status)
@@ -149,7 +182,7 @@ class EnumTest < ActiveRecord::TestCase
test "reverted changes that are not dirty" do
old_status = @book.status
- @book.status = :published
+ @book.status = :proposed
assert @book.attribute_changed?(:status)
@book.status = old_status
assert_not @book.attribute_changed?(:status)
@@ -201,18 +234,22 @@ class EnumTest < ActiveRecord::TestCase
test "building new objects with enum scopes" do
assert Book.written.build.written?
assert Book.read.build.read?
+ assert Book.in_spanish.build.in_spanish?
+ assert Book.illustrator_visibility_invisible.build.illustrator_visibility_invisible?
end
test "creating new objects with enum scopes" do
assert Book.written.create.written?
assert Book.read.create.read?
+ assert Book.in_spanish.create.in_spanish?
+ assert Book.illustrator_visibility_invisible.create.illustrator_visibility_invisible?
end
test "_before_type_cast returns the enum label (required for form fields)" do
if @book.status_came_from_user?
- assert_equal "proposed", @book.status_before_type_cast
+ assert_equal "published", @book.status_before_type_cast
else
- assert_equal "proposed", @book.status
+ assert_equal "published", @book.status
end
end
@@ -355,4 +392,23 @@ class EnumTest < ActiveRecord::TestCase
book2 = klass.single.create!
assert book2.single?
end
+
+ test "query state by predicate with prefix" do
+ assert @book.author_visibility_visible?
+ assert_not @book.author_visibility_invisible?
+ assert @book.illustrator_visibility_visible?
+ assert_not @book.illustrator_visibility_invisible?
+ end
+
+ test "query state by predicate with custom prefix" do
+ assert @book.in_english?
+ assert_not @book.in_spanish?
+ assert_not @book.in_french?
+ 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"
+ assert book.in_english?, "expected fixture to default to english language"
+ end
end
diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb
index 8de2ddb10d..2dee8a26a5 100644
--- a/activerecord/test/cases/explain_subscriber_test.rb
+++ b/activerecord/test/cases/explain_subscriber_test.rb
@@ -48,6 +48,11 @@ if ActiveRecord::Base.connection.supports_explain?
assert queries.empty?
end
+ def test_collects_cte_queries
+ SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: 'with s as (values(3)) select 1 from s')
+ assert_equal 1, queries.size
+ end
+
teardown do
ActiveRecord::ExplainRegistry.reset
end
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index f8acdcb51e..76ea950fb1 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -16,6 +16,7 @@ require 'models/joke'
require 'models/matey'
require 'models/parrot'
require 'models/pirate'
+require 'models/doubloon'
require 'models/post'
require 'models/randomly_named_c1'
require 'models/reply'
@@ -216,6 +217,13 @@ class FixturesTest < ActiveRecord::TestCase
end
end
+ def test_yaml_file_with_invalid_column
+ e = assert_raise(ActiveRecord::Fixture::FixtureError) do
+ ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/naked/yml", "parrots")
+ end
+ assert_equal(%(table "parrots" has no column named "arrr".), e.message)
+ end
+
def test_omap_fixtures
assert_nothing_raised do
fixtures = ActiveRecord::FixtureSet.new(Account.connection, 'categories', Category, FIXTURES_ROOT + "/categories_ordered")
@@ -279,10 +287,10 @@ class HasManyThroughFixture < ActiveSupport::TestCase
treasure = make_model "Treasure"
pt.table_name = "parrots_treasures"
- pt.belongs_to :parrot, :class => parrot
- pt.belongs_to :treasure, :class => treasure
+ pt.belongs_to :parrot, :anonymous_class => parrot
+ pt.belongs_to :treasure, :anonymous_class => treasure
- parrot.has_many :parrot_treasures, :class => pt
+ parrot.has_many :parrot_treasures, :anonymous_class => pt
parrot.has_many :treasures, :through => :parrot_treasures
parrots = File.join FIXTURES_ROOT, 'parrots'
@@ -297,10 +305,10 @@ class HasManyThroughFixture < ActiveSupport::TestCase
parrot = make_model "Parrot"
treasure = make_model "Treasure"
- pt.belongs_to :parrot, :class => parrot
- pt.belongs_to :treasure, :class => treasure
+ pt.belongs_to :parrot, :anonymous_class => parrot
+ pt.belongs_to :treasure, :anonymous_class => treasure
- parrot.has_many :parrot_treasures, :class => pt
+ parrot.has_many :parrot_treasures, :anonymous_class => pt
parrot.has_many :treasures, :through => :parrot_treasures
parrots = File.join FIXTURES_ROOT, 'parrots'
@@ -691,7 +699,7 @@ end
class FoxyFixturesTest < ActiveRecord::TestCase
fixtures :parrots, :parrots_pirates, :pirates, :treasures, :mateys, :ships, :computers,
- :developers, :"admin/accounts", :"admin/users", :live_parrots, :dead_parrots
+ :developers, :"admin/accounts", :"admin/users", :live_parrots, :dead_parrots, :books
if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
require 'models/uuid_parent'
@@ -841,6 +849,13 @@ class FoxyFixturesTest < ActiveRecord::TestCase
assert admin_accounts(:signals37).users.include?(admin_users(:david))
assert_equal 2, admin_accounts(:signals37).users.size
end
+
+ def test_resolves_enums
+ assert books(:awdr).published?
+ assert books(:awdr).read?
+ assert books(:rfr).proposed?
+ assert books(:ddd).published?
+ end
end
class ActiveSupportSubclassWithFixturesTest < ActiveRecord::TestCase
@@ -896,3 +911,12 @@ class FixturesWithDefaultScopeTest < ActiveRecord::TestCase
assert_equal "special", bulbs(:special).name
end
end
+
+class FixturesWithAbstractBelongsTo < ActiveRecord::TestCase
+ fixtures :pirates, :doubloons
+
+ test "creates fixtures with belongs_to associations defined in abstract base classes" do
+ assert_not_nil doubloons(:blackbeards_doubloon)
+ assert_equal pirates(:blackbeard), doubloons(:blackbeards_doubloon).pirate
+ end
+end
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 12c793c408..d9d0f929db 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -47,7 +47,8 @@ end
def mysql_56?
current_adapter?(:MysqlAdapter, :Mysql2Adapter) &&
- ActiveRecord::Base.connection.send(:version).join(".") >= "5.6.0"
+ ActiveRecord::Base.connection.send(:version) >= '5.6.0' &&
+ ActiveRecord::Base.connection.send(:version) < '5.7.0'
end
def mysql_enforcing_gtid_consistency?
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 3268555cb8..f67d85603a 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -7,15 +7,32 @@ require 'models/subscriber'
require 'models/vegetables'
require 'models/shop'
+module InheritanceTestHelper
+ def with_store_full_sti_class(&block)
+ assign_store_full_sti_class true, &block
+ end
+
+ def without_store_full_sti_class(&block)
+ assign_store_full_sti_class false, &block
+ end
+
+ def assign_store_full_sti_class(flag)
+ old_store_full_sti_class = ActiveRecord::Base.store_full_sti_class
+ ActiveRecord::Base.store_full_sti_class = flag
+ yield
+ ensure
+ ActiveRecord::Base.store_full_sti_class = old_store_full_sti_class
+ end
+end
+
class InheritanceTest < ActiveRecord::TestCase
+ include InheritanceTestHelper
fixtures :companies, :projects, :subscribers, :accounts, :vegetables
def test_class_with_store_full_sti_class_returns_full_name
- old = ActiveRecord::Base.store_full_sti_class
- ActiveRecord::Base.store_full_sti_class = true
- assert_equal 'Namespaced::Company', Namespaced::Company.sti_name
- ensure
- ActiveRecord::Base.store_full_sti_class = old
+ with_store_full_sti_class do
+ assert_equal 'Namespaced::Company', Namespaced::Company.sti_name
+ end
end
def test_class_with_blank_sti_name
@@ -33,39 +50,31 @@ class InheritanceTest < ActiveRecord::TestCase
end
def test_class_without_store_full_sti_class_returns_demodulized_name
- old = ActiveRecord::Base.store_full_sti_class
- ActiveRecord::Base.store_full_sti_class = false
- assert_equal 'Company', Namespaced::Company.sti_name
- ensure
- ActiveRecord::Base.store_full_sti_class = old
+ without_store_full_sti_class do
+ assert_equal 'Company', Namespaced::Company.sti_name
+ end
end
def test_should_store_demodulized_class_name_with_store_full_sti_class_option_disabled
- old = ActiveRecord::Base.store_full_sti_class
- ActiveRecord::Base.store_full_sti_class = false
- item = Namespaced::Company.new
- assert_equal 'Company', item[:type]
- ensure
- ActiveRecord::Base.store_full_sti_class = old
+ without_store_full_sti_class do
+ item = Namespaced::Company.new
+ assert_equal 'Company', item[:type]
+ end
end
def test_should_store_full_class_name_with_store_full_sti_class_option_enabled
- old = ActiveRecord::Base.store_full_sti_class
- ActiveRecord::Base.store_full_sti_class = true
- item = Namespaced::Company.new
- assert_equal 'Namespaced::Company', item[:type]
- ensure
- ActiveRecord::Base.store_full_sti_class = old
+ with_store_full_sti_class do
+ item = Namespaced::Company.new
+ assert_equal 'Namespaced::Company', item[:type]
+ end
end
def test_different_namespace_subclass_should_load_correctly_with_store_full_sti_class_option
- old = ActiveRecord::Base.store_full_sti_class
- ActiveRecord::Base.store_full_sti_class = true
- item = Namespaced::Company.create :name => "Wolverine 2"
- assert_not_nil Company.find(item.id)
- assert_not_nil Namespaced::Company.find(item.id)
- ensure
- ActiveRecord::Base.store_full_sti_class = old
+ with_store_full_sti_class do
+ item = Namespaced::Company.create name: "Wolverine 2"
+ assert_not_nil Company.find(item.id)
+ assert_not_nil Namespaced::Company.find(item.id)
+ end
end
def test_company_descends_from_active_record
@@ -204,10 +213,28 @@ class InheritanceTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::SubclassNotFound) { Company.new(:type => 'Account') }
end
+ def test_new_with_unrelated_namespaced_type
+ without_store_full_sti_class do
+ e = assert_raises ActiveRecord::SubclassNotFound do
+ Namespaced::Company.new(type: 'Firm')
+ end
+
+ assert_equal "Invalid single-table inheritance type: Namespaced::Firm is not a subclass of Namespaced::Company", e.message
+ end
+ end
+
+
def test_new_with_complex_inheritance
assert_nothing_raised { Client.new(type: 'VerySpecialClient') }
end
+ def test_new_without_storing_full_sti_class
+ without_store_full_sti_class do
+ item = Company.new(type: 'SpecialCo')
+ assert_instance_of Company::SpecialCo, item
+ end
+ end
+
def test_new_with_autoload_paths
path = File.expand_path('../../models/autoloadable', __FILE__)
ActiveSupport::Dependencies.autoload_paths << path
@@ -331,6 +358,7 @@ class InheritanceTest < ActiveRecord::TestCase
end
class InheritanceComputeTypeTest < ActiveRecord::TestCase
+ include InheritanceTestHelper
fixtures :companies
def setup
@@ -344,27 +372,26 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase
end
def test_instantiation_doesnt_try_to_require_corresponding_file
- ActiveRecord::Base.store_full_sti_class = false
- foo = Firm.first.clone
- foo.type = 'FirmOnTheFly'
- foo.save!
+ without_store_full_sti_class do
+ foo = Firm.first.clone
+ foo.type = 'FirmOnTheFly'
+ foo.save!
- # Should fail without FirmOnTheFly in the type condition.
- assert_raise(ActiveRecord::RecordNotFound) { Firm.find(foo.id) }
+ # Should fail without FirmOnTheFly in the type condition.
+ assert_raise(ActiveRecord::RecordNotFound) { Firm.find(foo.id) }
- # Nest FirmOnTheFly in the test case where Dependencies won't see it.
- self.class.const_set :FirmOnTheFly, Class.new(Firm)
- assert_raise(ActiveRecord::SubclassNotFound) { Firm.find(foo.id) }
+ # Nest FirmOnTheFly in the test case where Dependencies won't see it.
+ self.class.const_set :FirmOnTheFly, Class.new(Firm)
+ assert_raise(ActiveRecord::SubclassNotFound) { Firm.find(foo.id) }
- # Nest FirmOnTheFly in Firm where Dependencies will see it.
- # This is analogous to nesting models in a migration.
- Firm.const_set :FirmOnTheFly, Class.new(Firm)
+ # Nest FirmOnTheFly in Firm where Dependencies will see it.
+ # This is analogous to nesting models in a migration.
+ Firm.const_set :FirmOnTheFly, Class.new(Firm)
- # And instantiate will find the existing constant rather than trying
- # to require firm_on_the_fly.
- assert_nothing_raised { assert_kind_of Firm::FirmOnTheFly, Firm.find(foo.id) }
- ensure
- ActiveRecord::Base.store_full_sti_class = true
+ # And instantiate will find the existing constant rather than trying
+ # to require firm_on_the_fly.
+ assert_nothing_raised { assert_kind_of Firm::FirmOnTheFly, Firm.find(foo.id) }
+ end
end
def test_sti_type_from_attributes_disabled_in_non_sti_class
diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb
index 8144f3e5c5..99230aa3d5 100644
--- a/activerecord/test/cases/invertible_migration_test.rb
+++ b/activerecord/test/cases/invertible_migration_test.rb
@@ -144,13 +144,17 @@ module ActiveRecord
end
def test_exception_on_removing_index_without_column_option
- RemoveIndexMigration1.new.migrate(:up)
- migration = RemoveIndexMigration2.new
- migration.migrate(:up)
+ index_definition = ["horses", [:name, :color]]
+ migration1 = RemoveIndexMigration1.new
+ migration1.migrate(:up)
+ assert migration1.connection.index_exists?(*index_definition)
- assert_raises(IrreversibleMigration) do
- migration.migrate(:down)
- end
+ migration2 = RemoveIndexMigration2.new
+ migration2.migrate(:up)
+ assert_not migration2.connection.index_exists?(*index_definition)
+
+ migration2.migrate(:down)
+ assert migration2.connection.index_exists?(*index_definition)
end
def test_migrate_up
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 9e4998a946..2e1363334d 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -177,6 +177,16 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal 1, p1.lock_version
end
+ def test_touch_stale_object
+ person = Person.create!(first_name: 'Mehmet Emin')
+ stale_person = Person.find(person.id)
+ person.update_attribute(:gender, 'M')
+
+ assert_raises(ActiveRecord::StaleObjectError) do
+ stale_person.touch
+ end
+ end
+
def test_lock_column_name_existing
t1 = LegacyThing.find(1)
t2 = LegacyThing.find(1)
@@ -260,7 +270,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase
car.wheels << Wheel.create!
end
assert_difference 'car.wheels.count', -1 do
- car.destroy
+ car.reload.destroy
end
assert car.destroyed?
end
diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
index 46a62c272f..83e50048ec 100644
--- a/activerecord/test/cases/migration/change_schema_test.rb
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -105,7 +105,7 @@ module ActiveRecord
eight = columns.detect { |c| c.name == "eight_int" }
if current_adapter?(:OracleAdapter)
- assert_equal 'NUMBER(8)', eight.sql_type
+ assert_equal 'NUMBER(19)', eight.sql_type
elsif current_adapter?(:SQLite3Adapter)
assert_equal 'bigint', eight.sql_type
else
diff --git a/activerecord/test/cases/migration/columns_test.rb b/activerecord/test/cases/migration/columns_test.rb
index 5fc7702dfa..ab3f584350 100644
--- a/activerecord/test/cases/migration/columns_test.rb
+++ b/activerecord/test/cases/migration/columns_test.rb
@@ -267,6 +267,13 @@ module ActiveRecord
assert_nil TestModel.new.first_name
end
+ def test_change_column_default_with_from_and_to
+ add_column "test_models", "first_name", :string
+ connection.change_column_default "test_models", "first_name", from: nil, to: "Tester"
+
+ assert_equal "Tester", TestModel.new.first_name
+ end
+
def test_remove_column_no_second_parameter_raises_exception
assert_raise(ArgumentError) { connection.remove_column("funny") }
end
diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb
index 3844b1a92e..99f1dc65b0 100644
--- a/activerecord/test/cases/migration/command_recorder_test.rb
+++ b/activerecord/test/cases/migration/command_recorder_test.rb
@@ -169,6 +169,16 @@ module ActiveRecord
end
end
+ def test_invert_change_column_default_with_from_and_to
+ change = @recorder.inverse_of :change_column_default, [:table, :column, from: "old_value", to: "new_value"]
+ assert_equal [:change_column_default, [:table, :column, from: "new_value", to: "old_value"]], change
+ end
+
+ def test_invert_change_column_default_with_from_and_to_with_boolean
+ change = @recorder.inverse_of :change_column_default, [:table, :column, from: true, to: false]
+ assert_equal [:change_column_default, [:table, :column, from: false, to: true]], change
+ end
+
def test_invert_change_column_null
add = @recorder.inverse_of :change_column_null, [:table, :column, true]
assert_equal [:change_column_null, [:table, :column, false]], add
@@ -206,6 +216,11 @@ module ActiveRecord
end
def test_invert_remove_index
+ add = @recorder.inverse_of :remove_index, [:table, :one]
+ assert_equal [:add_index, [:table, :one]], add
+ end
+
+ def test_invert_remove_index_with_column
add = @recorder.inverse_of :remove_index, [:table, {column: [:one, :two], options: true}]
assert_equal [:add_index, [:table, [:one, :two], options: true]], add
end
@@ -281,17 +296,42 @@ module ActiveRecord
assert_equal [:remove_foreign_key, [:dogs, :people]], enable
end
+ def test_invert_remove_foreign_key
+ enable = @recorder.inverse_of :remove_foreign_key, [:dogs, :people]
+ assert_equal [:add_foreign_key, [:dogs, :people]], enable
+ end
+
def test_invert_add_foreign_key_with_column
enable = @recorder.inverse_of :add_foreign_key, [:dogs, :people, column: "owner_id"]
assert_equal [:remove_foreign_key, [:dogs, column: "owner_id"]], enable
end
+ def test_invert_remove_foreign_key_with_column
+ enable = @recorder.inverse_of :remove_foreign_key, [:dogs, :people, column: "owner_id"]
+ assert_equal [:add_foreign_key, [:dogs, :people, column: "owner_id"]], enable
+ end
+
def test_invert_add_foreign_key_with_column_and_name
enable = @recorder.inverse_of :add_foreign_key, [:dogs, :people, column: "owner_id", name: "fk"]
assert_equal [:remove_foreign_key, [:dogs, name: "fk"]], enable
end
- def test_remove_foreign_key_is_irreversible
+ def test_invert_remove_foreign_key_with_column_and_name
+ enable = @recorder.inverse_of :remove_foreign_key, [:dogs, :people, column: "owner_id", name: "fk"]
+ assert_equal [:add_foreign_key, [:dogs, :people, column: "owner_id", name: "fk"]], enable
+ end
+
+ def test_invert_remove_foreign_key_with_primary_key
+ enable = @recorder.inverse_of :remove_foreign_key, [:dogs, :people, primary_key: "person_id"]
+ assert_equal [:add_foreign_key, [:dogs, :people, primary_key: "person_id"]], enable
+ end
+
+ def test_invert_remove_foreign_key_with_on_delete_on_update
+ enable = @recorder.inverse_of :remove_foreign_key, [:dogs, :people, on_delete: :nullify, on_update: :cascade]
+ assert_equal [:add_foreign_key, [:dogs, :people, on_delete: :nullify, on_update: :cascade]], enable
+ end
+
+ def test_invert_remove_foreign_key_is_irreversible_without_to_table
assert_raises ActiveRecord::IrreversibleMigration do
@recorder.inverse_of :remove_foreign_key, [:dogs, column: "owner_id"]
end
@@ -299,6 +339,10 @@ module ActiveRecord
assert_raises ActiveRecord::IrreversibleMigration do
@recorder.inverse_of :remove_foreign_key, [:dogs, name: "fk"]
end
+
+ assert_raises ActiveRecord::IrreversibleMigration do
+ @recorder.inverse_of :remove_foreign_key, [:dogs]
+ end
end
end
end
diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb
index 7f4790bf3e..72f2fa95f1 100644
--- a/activerecord/test/cases/migration/foreign_key_test.rb
+++ b/activerecord/test/cases/migration/foreign_key_test.rb
@@ -243,6 +243,37 @@ module ActiveRecord
silence_stream($stdout) { migration.migrate(:down) }
end
+ class CreateSchoolsAndClassesMigration < ActiveRecord::Migration
+ def change
+ create_table(:schools)
+
+ create_table(:classes) do |t|
+ t.column :school_id, :integer
+ end
+ add_foreign_key :classes, :schools
+ end
+ end
+
+ def test_add_foreign_key_with_prefix
+ ActiveRecord::Base.table_name_prefix = 'p_'
+ migration = CreateSchoolsAndClassesMigration.new
+ silence_stream($stdout) { migration.migrate(:up) }
+ assert_equal 1, @connection.foreign_keys("p_classes").size
+ ensure
+ silence_stream($stdout) { migration.migrate(:down) }
+ ActiveRecord::Base.table_name_prefix = nil
+ end
+
+ def test_add_foreign_key_with_suffix
+ ActiveRecord::Base.table_name_suffix = '_s'
+ migration = CreateSchoolsAndClassesMigration.new
+ silence_stream($stdout) { migration.migrate(:up) }
+ assert_equal 1, @connection.foreign_keys("classes_s").size
+ ensure
+ silence_stream($stdout) { migration.migrate(:down) }
+ ActiveRecord::Base.table_name_suffix = nil
+ end
+
end
end
end
diff --git a/activerecord/test/cases/migration/postgresql_geometric_types_test.rb b/activerecord/test/cases/migration/postgresql_geometric_types_test.rb
new file mode 100644
index 0000000000..e4772905bb
--- /dev/null
+++ b/activerecord/test/cases/migration/postgresql_geometric_types_test.rb
@@ -0,0 +1,93 @@
+require 'cases/helper'
+
+module ActiveRecord
+ class Migration
+ class PostgreSQLGeometricTypesTest < ActiveRecord::TestCase
+ attr_reader :connection, :table_name
+
+ def setup
+ super
+ @connection = ActiveRecord::Base.connection
+ @table_name = :testings
+ end
+
+ if current_adapter?(:PostgreSQLAdapter)
+ def test_creating_column_with_point_type
+ connection.create_table(table_name) do |t|
+ t.point :foo_point
+ end
+
+ assert_column_exists(:foo_point)
+ assert_type_correct(:foo_point, :point)
+ end
+
+ def test_creating_column_with_line_type
+ connection.create_table(table_name) do |t|
+ t.line :foo_line
+ end
+
+ assert_column_exists(:foo_line)
+ assert_type_correct(:foo_line, :line)
+ end
+
+ def test_creating_column_with_lseg_type
+ connection.create_table(table_name) do |t|
+ t.lseg :foo_lseg
+ end
+
+ assert_column_exists(:foo_lseg)
+ assert_type_correct(:foo_lseg, :lseg)
+ end
+
+ def test_creating_column_with_box_type
+ connection.create_table(table_name) do |t|
+ t.box :foo_box
+ end
+
+ assert_column_exists(:foo_box)
+ assert_type_correct(:foo_box, :box)
+ end
+
+ def test_creating_column_with_path_type
+ connection.create_table(table_name) do |t|
+ t.path :foo_path
+ end
+
+ assert_column_exists(:foo_path)
+ assert_type_correct(:foo_path, :path)
+ end
+
+ def test_creating_column_with_polygon_type
+ connection.create_table(table_name) do |t|
+ t.polygon :foo_polygon
+ end
+
+ assert_column_exists(:foo_polygon)
+ assert_type_correct(:foo_polygon, :polygon)
+ end
+
+ def test_creating_column_with_circle_type
+ connection.create_table(table_name) do |t|
+ t.circle :foo_circle
+ end
+
+ assert_column_exists(:foo_circle)
+ assert_type_correct(:foo_circle, :circle)
+ end
+ end
+
+ private
+ def assert_column_exists(column_name)
+ columns = connection.columns(table_name)
+ assert columns.map(&:name).include?(column_name.to_s)
+ end
+
+ def assert_type_correct(column_name, type)
+ columns = connection.columns(table_name)
+ column = columns.select{ |c| c.name == column_name.to_s }.first
+ assert_equal type.to_s, column.sql_type
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb
index 87348d0f90..1594f99852 100644
--- a/activerecord/test/cases/migration/references_foreign_key_test.rb
+++ b/activerecord/test/cases/migration/references_foreign_key_test.rb
@@ -130,4 +130,24 @@ module ActiveRecord
end
end
end
+else
+class ReferencesWithoutForeignKeySupportTest < ActiveRecord::TestCase
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table(:testing_parents, force: true)
+ end
+
+ teardown do
+ @connection.drop_table("testings", if_exists: true)
+ @connection.drop_table("testing_parents", if_exists: true)
+ end
+
+ test "ignores foreign keys defined with the table" do
+ @connection.create_table :testings do |t|
+ t.references :testing_parent, foreign_key: true
+ end
+
+ assert_includes @connection.tables, "testings"
+ end
+end
end
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 6b4addd52f..b8a0401fe3 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -658,6 +658,16 @@ module NestedAttributesOnACollectionAssociationTests
assert_equal "Couldn't find #{@child_1.class.name} with ID=1234567890 for Pirate with ID=#{@pirate.id}", exception.message
end
+ def test_should_raise_RecordNotFound_if_an_id_belonging_to_a_different_record_is_given
+ other_pirate = Pirate.create! catchphrase: 'Ahoy!'
+ other_child = other_pirate.send(@association_name).create! name: 'Buccaneers Servant'
+
+ exception = assert_raise ActiveRecord::RecordNotFound do
+ @pirate.attributes = { association_getter => [{ id: other_child.id }] }
+ end
+ assert_equal "Couldn't find #{@child_1.class.name} with ID=#{other_child.id} for Pirate with ID=#{@pirate.id}", exception.message
+ end
+
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 = {
@@ -1054,4 +1064,39 @@ class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveR
assert_not part.valid?
assert_equal ["Ship name can't be blank"], part.errors.full_messages
end
+
+ class ProtectedParameters
+ def initialize(hash)
+ @hash = hash
+ end
+
+ def permitted?
+ true
+ end
+
+ def [](key)
+ @hash[key]
+ end
+
+ def to_h
+ @hash
+ end
+ end
+
+ test "strong params style objects can be assigned for singular associations" do
+ params = { name: "Stern", ship_attributes:
+ ProtectedParameters.new(name: "The Black Rock") }
+ part = ShipPart.new(params)
+
+ assert_equal "Stern", part.name
+ assert_equal "The Black Rock", part.ship.name
+ end
+
+ test "strong params style objects can be assigned for collection associations" do
+ params = { trinkets_attributes: ProtectedParameters.new("0" => ProtectedParameters.new(name: "Necklace"), "1" => ProtectedParameters.new(name: "Spoon")) }
+ part = ShipPart.new(params)
+
+ assert_equal "Necklace", part.trinkets[0].name
+ assert_equal "Spoon", part.trinkets[1].name
+ end
end
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 1e93e2a05c..42e7507631 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -897,6 +897,33 @@ class PersistenceTest < ActiveRecord::TestCase
assert_not post.new_record?
end
+ def test_reload_via_querycache
+ ActiveRecord::Base.connection.enable_query_cache!
+ ActiveRecord::Base.connection.clear_query_cache
+ assert ActiveRecord::Base.connection.query_cache_enabled, 'cache should be on'
+ parrot = Parrot.create(:name => 'Shane')
+
+ # populate the cache with the SELECT result
+ found_parrot = Parrot.find(parrot.id)
+ assert_equal parrot.id, found_parrot.id
+
+ # Manually update the 'name' attribute in the DB directly
+ assert_equal 1, ActiveRecord::Base.connection.query_cache.length
+ ActiveRecord::Base.uncached do
+ found_parrot.name = 'Mary'
+ found_parrot.save
+ end
+
+ # Now reload, and verify that it gets the DB version, and not the querycache version
+ found_parrot.reload
+ assert_equal 'Mary', found_parrot.name
+
+ found_parrot = Parrot.find(parrot.id)
+ assert_equal 'Mary', found_parrot.name
+ ensure
+ ActiveRecord::Base.connection.disable_query_cache!
+ end
+
class SaveTest < ActiveRecord::TestCase
self.use_transactional_tests = false
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 83be9a75d8..0745a37ee9 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -175,6 +175,20 @@ class PrimaryKeysTest < ActiveRecord::TestCase
dashboard = Dashboard.first
assert_equal '2', dashboard.id
end
+
+ if current_adapter?(:PostgreSQLAdapter)
+ def test_serial_with_quoted_sequence_name
+ column = MixedCaseMonkey.columns_hash[MixedCaseMonkey.primary_key]
+ assert_equal "nextval('\"mixed_case_monkeys_monkeyID_seq\"'::regclass)", column.default_function
+ assert column.serial?
+ end
+
+ def test_serial_with_unquoted_sequence_name
+ column = Topic.columns_hash[Topic.primary_key]
+ assert_equal "nextval('topics_id_seq'::regclass)", column.default_function
+ assert column.serial?
+ end
+ end
end
class PrimaryKeyWithNoConnectionTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/relation/delegation_test.rb b/activerecord/test/cases/relation/delegation_test.rb
index 29c9d0e2af..989f4e1e5d 100644
--- a/activerecord/test/cases/relation/delegation_test.rb
+++ b/activerecord/test/cases/relation/delegation_test.rb
@@ -28,7 +28,7 @@ module ActiveRecord
module DelegationWhitelistBlacklistTests
ARRAY_DELEGATES = [
:+, :-, :|, :&, :[],
- :all?, :collect, :detect, :each, :each_cons, :each_with_index,
+ :all?, :collect, :compact, :detect, :each, :each_cons, :each_with_index,
:exclude?, :find_all, :flat_map, :group_by, :include?, :length,
:map, :none?, :one?, :partition, :reject, :reverse,
:sample, :second, :sort, :sort_by, :third,
diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb
index 45ead08bd5..ba4d9d2503 100644
--- a/activerecord/test/cases/relation/mutation_test.rb
+++ b/activerecord/test/cases/relation/mutation_test.rb
@@ -81,7 +81,7 @@ module ActiveRecord
assert_equal [], relation.extending_values
end
- (Relation::SINGLE_VALUE_METHODS - [:lock, :reordering, :reverse_order, :create_with]).each do |method|
+ (Relation::SINGLE_VALUE_METHODS - [:lock, :reordering, :reverse_order, :create_with, :uniq]).each do |method|
test "##{method}!" do
assert relation.public_send("#{method}!", :foo).equal?(relation)
assert_equal :foo, relation.public_send("#{method}_value")
@@ -153,13 +153,22 @@ module ActiveRecord
test 'distinct!' do
relation.distinct! :foo
assert_equal :foo, relation.distinct_value
- assert_equal :foo, relation.uniq_value # deprecated access
+
+ assert_deprecated do
+ assert_equal :foo, relation.uniq_value # deprecated access
+ end
end
test 'uniq! was replaced by distinct!' do
- relation.uniq! :foo
+ 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
- assert_equal :foo, relation.uniq_value # deprecated access
end
end
end
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 9353be1ba7..37d3965022 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -242,6 +242,19 @@ module ActiveRecord
assert_equal false, post.respond_to?(:title), "post should not respond_to?(:body) since invoking it raises exception"
end
+ def test_select_quotes_when_using_from_clause
+ 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
+ end
+ quoted_join = ActiveRecord::Base.connection.quote_table_name("join")
+ selected = Post.select(:join).from(Post.select("id as #{quoted_join}")).map(&:join)
+ assert_equal Post.pluck(:id), selected
+ end
+
def test_relation_merging_with_merged_joins_as_strings
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
@@ -276,5 +289,16 @@ module ActiveRecord
assert_equal "type cast from database", UpdateAllTestModel.first.body
end
+
+ private
+
+ def sqlite3_version_includes_quoting_bug?
+ if current_adapter?(:SQLite3Adapter)
+ selected_quoted_column_names = ActiveRecord::Base.connection.exec_query(
+ 'SELECT "join" FROM (SELECT id AS "join" FROM posts) subquery'
+ ).columns
+ ["join"] != selected_quoted_column_names
+ end
+ end
end
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 0cf44388fa..5f48c2b40f 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -17,7 +17,7 @@ require 'models/tyre'
require 'models/minivan'
require 'models/aircraft'
require "models/possession"
-
+require "models/reader"
class RelationTest < ActiveRecord::TestCase
fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments,
@@ -621,6 +621,51 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 1, query.to_a.size
end
+ def test_preloading_with_associations_and_merges
+ post = Post.create! title: 'Uhuu', body: 'body'
+ reader = Reader.create! post_id: post.id, person_id: 1
+ comment = Comment.create! post_id: post.id, body: 'body'
+
+ assert !comment.respond_to?(:readers)
+
+ post_rel = Post.preload(:readers).joins(:readers).where(title: 'Uhuu')
+ result_comment = Comment.joins(:post).merge(post_rel).to_a.first
+ assert_equal comment, result_comment
+
+ assert_no_queries do
+ assert_equal post, result_comment.post
+ assert_equal [reader], result_comment.post.readers.to_a
+ end
+
+ post_rel = Post.includes(:readers).where(title: 'Uhuu')
+ result_comment = Comment.joins(:post).merge(post_rel).first
+ assert_equal comment, result_comment
+
+ assert_no_queries do
+ assert_equal post, result_comment.post
+ assert_equal [reader], result_comment.post.readers.to_a
+ end
+ end
+
+ def test_preloading_with_associations_default_scopes_and_merges
+ post = Post.create! title: 'Uhuu', body: 'body'
+ reader = Reader.create! post_id: post.id, person_id: 1
+
+ post_rel = PostWithPreloadDefaultScope.preload(:readers).joins(:readers).where(title: 'Uhuu')
+ result_post = PostWithPreloadDefaultScope.all.merge(post_rel).to_a.first
+
+ assert_no_queries do
+ assert_equal [reader], result_post.readers.to_a
+ end
+
+ post_rel = PostWithIncludesDefaultScope.includes(:readers).where(title: 'Uhuu')
+ result_post = PostWithIncludesDefaultScope.all.merge(post_rel).to_a.first
+
+ assert_no_queries do
+ assert_equal [reader], result_post.readers.to_a
+ end
+ end
+
def test_loading_with_one_association
posts = Post.preload(:comments)
post = posts.find { |p| p.id == 1 }
@@ -908,7 +953,7 @@ class RelationTest < ActiveRecord::TestCase
def test_delete_all_with_unpermitted_relation_raises_error
assert_raises(ActiveRecord::ActiveRecordError) { Author.limit(10).delete_all }
- assert_raises(ActiveRecord::ActiveRecordError) { Author.uniq.delete_all }
+ assert_raises(ActiveRecord::ActiveRecordError) { Author.distinct.delete_all }
assert_raises(ActiveRecord::ActiveRecordError) { Author.group(:name).delete_all }
assert_raises(ActiveRecord::ActiveRecordError) { Author.having('SUM(id) < 3').delete_all }
assert_raises(ActiveRecord::ActiveRecordError) { Author.offset(10).delete_all }
@@ -1493,14 +1538,17 @@ class RelationTest < ActiveRecord::TestCase
assert_equal ['Foo', 'Foo'], query.map(&:name)
assert_sql(/DISTINCT/) do
assert_equal ['Foo'], query.distinct.map(&:name)
- assert_equal ['Foo'], query.uniq.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_equal ['Foo'], query.uniq(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_equal ['Foo', 'Foo'], query.uniq(true).uniq(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
@@ -1657,6 +1705,10 @@ class RelationTest < ActiveRecord::TestCase
assert_sql(/^((?!ORDER).)*$/) { Post.all.find_by(author_id: 2) }
end
+ test "find_by requires at least one argument" do
+ assert_raises(ArgumentError) { Post.all.find_by }
+ end
+
test "find_by! with hash conditions returns the first matching record" do
assert_equal posts(:eager_other), Post.order(:id).find_by!(author_id: 2)
end
@@ -1679,6 +1731,10 @@ class RelationTest < ActiveRecord::TestCase
end
end
+ test "find_by! requires at least one argument" do
+ assert_raises(ArgumentError) { Post.all.find_by! }
+ end
+
test "loaded relations cannot be mutated by multi value methods" do
relation = Post.all
relation.to_a
diff --git a/activerecord/test/cases/reload_models_test.rb b/activerecord/test/cases/reload_models_test.rb
index 0d16a3526f..431fbf1297 100644
--- a/activerecord/test/cases/reload_models_test.rb
+++ b/activerecord/test/cases/reload_models_test.rb
@@ -3,7 +3,7 @@ require 'models/owner'
require 'models/pet'
class ReloadModelsTest < ActiveRecord::TestCase
- fixtures :pets
+ fixtures :pets, :owners
def test_has_one_with_reload
pet = Pet.find_by_name('parrot')
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 6c099719c0..feb1c29656 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -73,7 +73,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
next if column_set.empty?
lengths = column_set.map do |column|
- if match = column.match(/t\.(?:integer|decimal|float|datetime|timestamp|time|date|text|binary|string|boolean|xml|uuid|point)\s+"/)
+ if match = column.match(/\bt\.\w+\s+"/)
match[0].length
end
end.compact
@@ -168,24 +168,24 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
def test_schema_dumps_index_columns_in_right_order
- index_definition = standard_dump.split(/\n/).grep(/add_index.*companies/).first.strip
+ index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_index/).first.strip
if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter)
- assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index", using: :btree', index_definition
+ assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", using: :btree', index_definition
else
- assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index"', index_definition
+ assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index"', index_definition
end
end
def test_schema_dumps_partial_indices
- index_definition = standard_dump.split(/\n/).grep(/add_index.*company_partial_index/).first.strip
+ index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_partial_index/).first.strip
if current_adapter?(:PostgreSQLAdapter)
- assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)", using: :btree', index_definition
+ assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)", using: :btree', index_definition
elsif current_adapter?(:MysqlAdapter, :Mysql2Adapter)
- assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", using: :btree', index_definition
+ assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", using: :btree', index_definition
elsif current_adapter?(:SQLite3Adapter) && ActiveRecord::Base.connection.supports_partial_index?
- assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "rating > 10"', index_definition
+ assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "rating > 10"', index_definition
else
- assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index"', index_definition
+ assert_equal 't.index ["firm_id", "type"], name: "company_partial_index"', index_definition
end
end
@@ -232,14 +232,14 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_schema_dumps_index_type
output = standard_dump
- assert_match %r{add_index "key_tests", \["awesome"\], name: "index_key_tests_on_awesome", type: :fulltext}, output
- assert_match %r{add_index "key_tests", \["pizza"\], name: "index_key_tests_on_pizza", using: :btree}, output
+ assert_match %r{t\.index \["awesome"\], name: "index_key_tests_on_awesome", type: :fulltext}, output
+ assert_match %r{t\.index \["pizza"\], name: "index_key_tests_on_pizza", using: :btree}, output
end
end
def test_schema_dump_includes_decimal_options
output = dump_all_table_schema([/^[^n]/])
- assert_match %r{precision: 3,[[:space:]]+scale: 2,[[:space:]]+default: 2\.78}, output
+ assert_match %r{precision: 3,[[:space:]]+scale: 2,[[:space:]]+default: "2\.78"}, output
end
if current_adapter?(:PostgreSQLAdapter)
@@ -253,6 +253,11 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{t\.integer\s+"big_int_data_points\",\s+limit: 8,\s+array: true}, output
end
+ def test_schema_dump_allows_array_of_decimal_defaults
+ output = standard_dump
+ assert_match %r{t\.decimal\s+"decimal_array_default",\s+default: \["1.23", "3.45"\],\s+array: true}, output
+ end
+
if ActiveRecord::Base.connection.supports_extensions?
def test_schema_dump_includes_extensions
connection = ActiveRecord::Base.connection
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index 4137b20c4a..86316ab476 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -3,6 +3,7 @@ require 'models/post'
require 'models/comment'
require 'models/developer'
require 'models/computer'
+require 'models/vehicle'
class DefaultScopingTest < ActiveRecord::TestCase
fixtures :developers, :posts, :comments
@@ -153,6 +154,18 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal expected_7, received_7
end
+ def test_unscope_comparison_where_clauses
+ # unscoped for WHERE (`developers`.`id` <= 2)
+ expected = Developer.order('salary DESC').collect(&:name)
+ received = DeveloperOrderedBySalary.where(id: -Float::INFINITY..2).unscope(where: :id).collect { |dev| dev.name }
+ assert_equal expected, received
+
+ # unscoped for WHERE (`developers`.`id` < 2)
+ expected = Developer.order('salary DESC').collect(&:name)
+ received = DeveloperOrderedBySalary.where(id: -Float::INFINITY...2).unscope(where: :id).collect { |dev| dev.name }
+ assert_equal expected, received
+ end
+
def test_unscope_multiple_where_clauses
expected = Developer.order('salary DESC').collect(&:name)
received = DeveloperOrderedBySalary.where(name: 'Jamis').where(id: 1).unscope(where: [:name, :id]).collect(&:name)
@@ -441,4 +454,9 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal 1, scope.where_clause.ast.children.length
assert_equal Developer.where(name: "David"), scope
end
+
+ def test_with_abstract_class_where_clause_should_not_be_duplicated
+ scope = Bus.all
+ assert_equal scope.where_clause.ast.children.length, 1
+ end
end
diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb
index 35b13ea247..14b80f4df4 100644
--- a/activerecord/test/cases/serialization_test.rb
+++ b/activerecord/test/cases/serialization_test.rb
@@ -8,7 +8,7 @@ require 'models/post'
class SerializationTest < ActiveRecord::TestCase
fixtures :books
- FORMATS = [ :xml, :json ]
+ FORMATS = [ :json ]
def setup
@contact_attributes = {
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index 7c92453ee3..6056156698 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -274,4 +274,25 @@ class SerializedAttributeTest < ActiveRecord::TestCase
assert_equal({}, topic.content)
end
+
+ def test_values_cast_from_nil_are_persisted_as_nil
+ # This is required to fulfil the following contract, which must be universally
+ # true in Active Record:
+ #
+ # model.attribute = value
+ # assert_equal model.attribute, model.tap(&:save).reload.attribute
+ Topic.serialize(:content, Hash)
+ topic = Topic.create!(content: {})
+ topic2 = Topic.create!(content: nil)
+
+ assert_equal [topic, topic2], Topic.where(content: nil)
+ end
+
+ def test_nil_is_always_persisted_as_null
+ Topic.serialize(:content, Hash)
+
+ topic = Topic.create!(content: { foo: "bar" })
+ topic.update_attribute :content, nil
+ assert_equal [topic], Topic.where(content: nil)
+ end
end
diff --git a/activerecord/test/cases/suppressor_test.rb b/activerecord/test/cases/suppressor_test.rb
index 1c449d42fe..72c5c16555 100644
--- a/activerecord/test/cases/suppressor_test.rb
+++ b/activerecord/test/cases/suppressor_test.rb
@@ -3,7 +3,38 @@ require 'models/notification'
require 'models/user'
class SuppressorTest < ActiveRecord::TestCase
- def test_suppresses_creation_of_record_generated_by_callback
+ def test_suppresses_create
+ assert_no_difference -> { Notification.count } do
+ Notification.suppress do
+ Notification.create
+ Notification.create!
+ Notification.new.save
+ Notification.new.save!
+ end
+ end
+ end
+
+ def test_suppresses_update
+ user = User.create! token: 'asdf'
+
+ User.suppress do
+ user.update token: 'ghjkl'
+ assert_equal 'asdf', user.reload.token
+
+ user.update! token: 'zxcvbnm'
+ assert_equal 'asdf', user.reload.token
+
+ user.token = 'qwerty'
+ user.save
+ assert_equal 'asdf', user.reload.token
+
+ user.token = 'uiop'
+ user.save!
+ assert_equal 'asdf', user.reload.token
+ end
+ end
+
+ def test_suppresses_create_in_callback
assert_difference -> { User.count } do
assert_no_difference -> { Notification.count } do
Notification.suppress { UserWithNotification.create! }
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index f58535f044..d0deb4c273 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -265,14 +265,14 @@ module ActiveRecord
def test_structure_dump
filename = "awesome-file.sql"
- Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "test-db").returns(true)
+ Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "--routines", "test-db").returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
end
def test_warn_when_external_structure_dump_fails
filename = "awesome-file.sql"
- Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "test-db").returns(false)
+ Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "--routines", "test-db").returns(false)
warnings = capture(:stderr) do
ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
@@ -283,12 +283,21 @@ module ActiveRecord
def test_structure_dump_with_port_number
filename = "awesome-file.sql"
- Kernel.expects(:system).with("mysqldump", "--port", "10000", "--result-file", filename, "--no-data", "test-db").returns(true)
+ Kernel.expects(:system).with("mysqldump", "--port=10000", "--result-file", filename, "--no-data", "--routines", "test-db").returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_dump(
@configuration.merge('port' => 10000),
filename)
end
+
+ def test_structure_dump_with_ssl
+ filename = "awesome-file.sql"
+ Kernel.expects(:system).with("mysqldump", "--ssl-ca=ca.crt", "--result-file", filename, "--no-data", "--routines", "test-db").returns(true)
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(
+ @configuration.merge("sslca" => "ca.crt"),
+ filename)
+ end
end
class MySQLStructureLoadTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb
index 084302cde5..184ff7fc63 100644
--- a/activerecord/test/cases/tasks/postgresql_rake_test.rb
+++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb
@@ -204,7 +204,7 @@ module ActiveRecord
end
def test_structure_dump
- Kernel.expects(:system).with("pg_dump -i -s -x -O -f #{@filename} my-app-db").returns(true)
+ Kernel.expects(:system).with('pg_dump', '-i', '-s', '-x', '-O', '-f', @filename, 'my-app-db').returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
end
@@ -212,7 +212,7 @@ module ActiveRecord
def test_structure_dump_with_schema_search_path
@configuration['schema_search_path'] = 'foo,bar'
- Kernel.expects(:system).with("pg_dump -i -s -x -O -f #{@filename} --schema=foo --schema=bar my-app-db").returns(true)
+ Kernel.expects(:system).with('pg_dump', '-i', '-s', '-x', '-O', '-f', @filename, '--schema=foo --schema=bar', 'my-app-db').returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
end
@@ -220,7 +220,7 @@ module ActiveRecord
def test_structure_dump_with_schema_search_path_and_dump_schemas_all
@configuration['schema_search_path'] = 'foo,bar'
- Kernel.expects(:system).with("pg_dump -i -s -x -O -f #{@filename} my-app-db").returns(true)
+ Kernel.expects(:system).with("pg_dump", '-i', '-s', '-x', '-O', '-f', @filename, 'my-app-db').returns(true)
with_dump_schemas(:all) do
ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
@@ -228,7 +228,7 @@ module ActiveRecord
end
def test_structure_dump_with_dump_schemas_string
- Kernel.expects(:system).with("pg_dump -i -s -x -O -f #{@filename} --schema=foo --schema=bar my-app-db").returns(true)
+ Kernel.expects(:system).with("pg_dump", '-i', '-s', '-x', '-O', '-f', @filename, '--schema=foo --schema=bar', "my-app-db").returns(true)
with_dump_schemas('foo,bar') do
ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
@@ -261,14 +261,14 @@ module ActiveRecord
def test_structure_load
filename = "awesome-file.sql"
- Kernel.expects(:system).with("psql -X -q -f #{filename} my-app-db")
+ Kernel.expects(:system).with('psql', '-q', '-f', filename, @configuration['database']).returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
end
def test_structure_load_accepts_path_with_spaces
filename = "awesome file.sql"
- Kernel.expects(:system).with("psql -X -q -f awesome\\ file.sql my-app-db")
+ Kernel.expects(:system).with('psql', '-q', '-f', filename, @configuration['database']).returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
end
diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb
index e0b01ae8e0..7761ea5612 100644
--- a/activerecord/test/cases/test_case.rb
+++ b/activerecord/test/cases/test_case.rb
@@ -65,6 +65,30 @@ module ActiveRecord
end
end
+ class PostgreSQLTestCase < TestCase
+ def self.run(*args)
+ super if current_adapter?(:PostgreSQLAdapter)
+ end
+ end
+
+ class Mysql2TestCase < TestCase
+ def self.run(*args)
+ super if current_adapter?(:Mysql2Adapter)
+ end
+ end
+
+ class MysqlTestCase < TestCase
+ def self.run(*args)
+ super if current_adapter?(:MysqlAdapter)
+ end
+ end
+
+ class SQLite3TestCase < TestCase
+ def self.run(*args)
+ super if current_adapter?(:SQLite3Adapter)
+ end
+ end
+
class SQLCounter
class << self
attr_accessor :ignored_sql, :log, :log_all
@@ -81,7 +105,7 @@ module ActiveRecord
oracle_ignored = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im, /^\s*select .* from all_constraints/im, /^\s*select .* from all_tab_cols/im]
mysql_ignored = [/^SHOW TABLES/i, /^SHOW FULL FIELDS/, /^SHOW CREATE TABLE /i, /^SHOW VARIABLES /]
postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select tablename\b.*from pg_tables\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im, /^SHOW search_path/i]
- sqlite3_ignored = [/^\s*SELECT name\b.*\bFROM sqlite_master/im]
+ sqlite3_ignored = [/^\s*SELECT name\b.*\bFROM sqlite_master/im, /^\s*SELECT sql\b.*\bFROM sqlite_master/im]
[oracle_ignored, mysql_ignored, postgresql_ignored, sqlite3_ignored].each do |db_ignored_sql|
ignored_sql.concat db_ignored_sql
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 7c89b4b9e8..5dab32995c 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -446,6 +446,17 @@ class TimestampTest < ActiveRecord::TestCase
toy = Toy.first
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)
+ end
+
+ indexes = ActiveRecord::Base.connection.indexes('foos')
+ assert_equal ['created_at', 'updated_at'], indexes.flat_map(&:columns).sort
+ ensure
+ ActiveRecord::Base.connection.drop_table(:foos)
+ end
end
class TimestampsWithoutTransactionTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/touch_later_test.rb b/activerecord/test/cases/touch_later_test.rb
new file mode 100644
index 0000000000..b3c42c8e42
--- /dev/null
+++ b/activerecord/test/cases/touch_later_test.rb
@@ -0,0 +1,114 @@
+require 'cases/helper'
+require 'models/invoice'
+require 'models/line_item'
+require 'models/topic'
+require 'models/node'
+require 'models/tree'
+
+class TouchLaterTest < ActiveRecord::TestCase
+ fixtures :nodes, :trees
+
+ def test_touch_laster_raise_if_non_persisted
+ invoice = Invoice.new
+ Invoice.transaction do
+ assert_not invoice.persisted?
+ assert_raises(ActiveRecord::ActiveRecordError) do
+ invoice.touch_later
+ end
+ end
+ end
+
+ def test_touch_later_dont_set_dirty_attributes
+ invoice = Invoice.create!
+ invoice.touch_later
+ assert_not invoice.changed?
+ end
+
+ def test_touch_later_update_the_attributes
+ time = Time.now.utc - 25.days
+ topic = Topic.create!(updated_at: time, created_at: time)
+ assert_equal time.to_i, topic.updated_at.to_i
+ assert_equal time.to_i, topic.created_at.to_i
+
+ Topic.transaction do
+ topic.touch_later(:created_at)
+ assert_not_equal time.to_i, topic.updated_at.to_i
+ assert_not_equal time.to_i, topic.created_at.to_i
+
+ assert_equal time.to_i, topic.reload.updated_at.to_i
+ assert_equal time.to_i, topic.reload.created_at.to_i
+ end
+ assert_not_equal time.to_i, topic.reload.updated_at.to_i
+ assert_not_equal time.to_i, topic.reload.created_at.to_i
+ end
+
+ def test_touch_touches_immediately
+ time = Time.now.utc - 25.days
+ topic = Topic.create!(updated_at: time, created_at: time)
+ assert_equal time.to_i, topic.updated_at.to_i
+ assert_equal time.to_i, topic.created_at.to_i
+
+ Topic.transaction do
+ topic.touch_later(:created_at)
+ topic.touch
+
+ assert_not_equal time, topic.reload.updated_at
+ assert_not_equal time, topic.reload.created_at
+ end
+ end
+
+ def test_touch_later_an_association_dont_autosave_parent
+ time = Time.now.utc - 25.days
+ line_item = LineItem.create!(amount: 1)
+ invoice = Invoice.create!(line_items: [line_item])
+ invoice.touch(time: time)
+
+ Invoice.transaction do
+ line_item.update(amount: 2)
+ assert_equal time.to_i, invoice.reload.updated_at.to_i
+ end
+
+ assert_not_equal time.to_i, invoice.updated_at.to_i
+ end
+
+ def test_touch_touches_immediately_with_a_custom_time
+ time = Time.now.utc - 25.days
+ topic = Topic.create!(updated_at: time, created_at: time)
+ assert_equal time, topic.updated_at
+ assert_equal time, topic.created_at
+
+ Topic.transaction do
+ topic.touch_later(:created_at)
+ time = Time.now.utc - 2.days
+ topic.touch(time: time)
+
+ assert_equal time.to_i, topic.reload.updated_at.to_i
+ assert_equal time.to_i, topic.reload.created_at.to_i
+ end
+ end
+
+ def test_touch_later_dont_hit_the_db
+ invoice = Invoice.create!
+ assert_queries(0) do
+ invoice.touch_later
+ end
+ end
+
+ def test_touching_three_deep
+ skip "Pending from #19324"
+
+ previous_tree_updated_at = trees(:root).updated_at
+ previous_grandparent_updated_at = nodes(:grandparent).updated_at
+ previous_parent_updated_at = nodes(:parent_a).updated_at
+ previous_child_updated_at = nodes(:child_one_of_a).updated_at
+
+ travel 5.seconds
+
+ Node.create! parent: nodes(:child_one_of_a), tree: trees(:root)
+
+ assert_not_equal nodes(:child_one_of_a).reload.updated_at, previous_child_updated_at
+ assert_not_equal nodes(:parent_a).reload.updated_at, previous_parent_updated_at
+ assert_not_equal nodes(:grandparent).reload.updated_at, previous_grandparent_updated_at
+ assert_not_equal trees(:root).reload.updated_at, previous_tree_updated_at
+ end
+end
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 2468a91969..29a6ec7522 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -175,13 +175,20 @@ class TransactionTest < ActiveRecord::TestCase
assert topic.new_record?, "#{topic.inspect} should be new record"
end
+ def test_transaction_state_is_cleared_when_record_is_persisted
+ author = Author.create! name: 'foo'
+ author.name = nil
+ assert_not author.save
+ assert_not author.new_record?
+ end
+
def test_update_should_rollback_on_failure
author = Author.find(1)
posts_count = author.posts.size
assert posts_count > 0
status = author.update(name: nil, post_ids: [])
assert !status
- assert_equal posts_count, author.posts(true).size
+ assert_equal posts_count, author.posts.reload.size
end
def test_update_should_rollback_on_failure!
@@ -191,7 +198,7 @@ class TransactionTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::RecordInvalid) do
author.update!(name: nil, post_ids: [])
end
- assert_equal posts_count, author.posts(true).size
+ assert_equal posts_count, author.posts.reload.size
end
def test_cancellation_from_returning_false_in_before_filter
diff --git a/activerecord/test/cases/type/integer_test.rb b/activerecord/test/cases/type/integer_test.rb
index 84fb05dd8e..0dcdbd0667 100644
--- a/activerecord/test/cases/type/integer_test.rb
+++ b/activerecord/test/cases/type/integer_test.rb
@@ -21,7 +21,7 @@ module ActiveRecord
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
test "casting ActiveRecord models" do
diff --git a/activerecord/test/cases/validations/absence_validation_test.rb b/activerecord/test/cases/validations/absence_validation_test.rb
new file mode 100644
index 0000000000..dd43ee358c
--- /dev/null
+++ b/activerecord/test/cases/validations/absence_validation_test.rb
@@ -0,0 +1,75 @@
+require "cases/helper"
+require 'models/face'
+require 'models/interest'
+require 'models/man'
+require 'models/topic'
+
+class AbsenceValidationTest < ActiveRecord::TestCase
+ def test_non_association
+ boy_klass = Class.new(Man) do
+ def self.name; "Boy" end
+ validates_absence_of :name
+ end
+
+ assert boy_klass.new.valid?
+ assert_not boy_klass.new(name: "Alex").valid?
+ end
+
+ def test_has_one_marked_for_destruction
+ boy_klass = Class.new(Man) do
+ def self.name; "Boy" end
+ validates_absence_of :face
+ end
+
+ boy = boy_klass.new(face: Face.new)
+ assert_not boy.valid?, "should not be valid if has_one association is present"
+ assert_equal 1, boy.errors[:face].size, "should only add one error"
+
+ boy.face.mark_for_destruction
+ assert boy.valid?, "should be valid if association is marked for destruction"
+ end
+
+ def test_has_many_marked_for_destruction
+ boy_klass = Class.new(Man) do
+ def self.name; "Boy" end
+ validates_absence_of :interests
+ end
+ boy = boy_klass.new
+ boy.interests << [i1 = Interest.new, i2 = Interest.new]
+ assert_not boy.valid?, "should not be valid if has_many association is present"
+
+ i1.mark_for_destruction
+ assert_not boy.valid?, "should not be valid if has_many association is present"
+
+ i2.mark_for_destruction
+ assert boy.valid?
+ end
+
+ def test_does_not_call_to_a_on_associations
+ boy_klass = Class.new(Man) do
+ def self.name; "Boy" end
+ validates_absence_of :face
+ end
+
+ face_with_to_a = Face.new
+ def face_with_to_a.to_a; ['(/)', '(\)']; end
+
+ assert_nothing_raised { boy_klass.new(face: face_with_to_a).valid? }
+ end
+
+ def test_does_not_validate_if_parent_record_is_validate_false
+ repair_validations(Interest) do
+ Interest.validates_absence_of(:topic)
+ interest = Interest.new(topic: Topic.new(title: "Math"))
+ interest.save!(validate: false)
+ assert interest.persisted?
+
+ man = Man.new(interest_ids: [interest.id])
+ man.save!
+
+ assert_equal man.interests.size, 1
+ assert interest.valid?
+ assert man.valid?
+ end
+ end
+end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 062bc733f9..ceca2c8366 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -34,7 +34,22 @@ class TopicWithUniqEvent < Topic
validates :event, uniqueness: true
end
+class BigIntTest < ActiveRecord::Base
+ INT_MAX_VALUE = 2147483647
+ self.table_name = 'cars'
+ validates :engines_count, uniqueness: true, inclusion: { in: 0..INT_MAX_VALUE }
+end
+
+class BigIntReverseTest < ActiveRecord::Base
+ INT_MAX_VALUE = 2147483647
+ self.table_name = 'cars'
+ validates :engines_count, inclusion: { in: 0..INT_MAX_VALUE }
+ validates :engines_count, uniqueness: true
+end
+
class UniquenessValidationTest < ActiveRecord::TestCase
+ INT_MAX_VALUE = 2147483647
+
fixtures :topics, 'warehouse-things'
repair_validations(Topic, Reply)
@@ -86,6 +101,16 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert t2.errors[:title]
end
+ def test_validate_uniqueness_when_integer_out_of_range
+ entry = BigIntTest.create(engines_count: INT_MAX_VALUE + 1)
+ assert_equal entry.errors[:engines_count], ['is not included in the list']
+ end
+
+ def test_validate_uniqueness_when_integer_out_of_range_show_order_does_not_matter
+ entry = BigIntReverseTest.create(engines_count: INT_MAX_VALUE + 1)
+ assert_equal entry.errors[:engines_count], ['is not included in the list']
+ end
+
def test_validates_uniqueness_with_newline_chars
Topic.validates_uniqueness_of(:title, :case_sensitive => false)
@@ -402,4 +427,23 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert reply.valid?
assert topic.valid?
end
+
+ def test_validate_uniqueness_of_custom_primary_key
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "keyboards"
+ self.primary_key = :key_number
+
+ validates_uniqueness_of :key_number
+
+ def self.name
+ "Keyboard"
+ end
+ end
+
+ klass.create!(key_number: 10)
+ key2 = klass.create!(key_number: 11)
+
+ key2.key_number = 10
+ assert_not key2.valid?
+ end
end
diff --git a/activerecord/test/cases/view_test.rb b/activerecord/test/cases/view_test.rb
index 3aed90ba36..f9dca1e196 100644
--- a/activerecord/test/cases/view_test.rb
+++ b/activerecord/test/cases/view_test.rb
@@ -102,7 +102,7 @@ class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase
end
def test_attributes
- assert_equal({"name" => "Agile Web Development with Rails", "status" => 0},
+ assert_equal({"name" => "Agile Web Development with Rails", "status" => 2},
Paperback.first.attributes)
end
diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb
deleted file mode 100644
index b30b50f597..0000000000
--- a/activerecord/test/cases/xml_serialization_test.rb
+++ /dev/null
@@ -1,447 +0,0 @@
-require "cases/helper"
-require "rexml/document"
-require 'models/contact'
-require 'models/post'
-require 'models/author'
-require 'models/comment'
-require 'models/company_in_module'
-require 'models/toy'
-require 'models/topic'
-require 'models/reply'
-require 'models/company'
-
-class XmlSerializationTest < ActiveRecord::TestCase
- def test_should_serialize_default_root
- @xml = Contact.new.to_xml
- assert_match %r{^<contact>}, @xml
- assert_match %r{</contact>$}, @xml
- end
-
- def test_should_serialize_default_root_with_namespace
- @xml = Contact.new.to_xml :namespace=>"http://xml.rubyonrails.org/contact"
- assert_match %r{^<contact xmlns="http://xml\.rubyonrails\.org/contact">}, @xml
- assert_match %r{</contact>$}, @xml
- end
-
- def test_should_serialize_custom_root
- @xml = Contact.new.to_xml :root => 'xml_contact'
- assert_match %r{^<xml-contact>}, @xml
- assert_match %r{</xml-contact>$}, @xml
- end
-
- def test_should_allow_undasherized_tags
- @xml = Contact.new.to_xml :root => 'xml_contact', :dasherize => false
- assert_match %r{^<xml_contact>}, @xml
- assert_match %r{</xml_contact>$}, @xml
- assert_match %r{<created_at}, @xml
- end
-
- def test_should_allow_camelized_tags
- @xml = Contact.new.to_xml :root => 'xml_contact', :camelize => true
- assert_match %r{^<XmlContact>}, @xml
- assert_match %r{</XmlContact>$}, @xml
- assert_match %r{<CreatedAt}, @xml
- end
-
- def test_should_allow_skipped_types
- @xml = Contact.new(:age => 25).to_xml :skip_types => true
- assert %r{<age>25</age>}.match(@xml)
- end
-
- def test_should_include_yielded_additions
- @xml = Contact.new.to_xml do |xml|
- xml.creator "David"
- end
- assert_match %r{<creator>David</creator>}, @xml
- end
-
- def test_to_xml_with_block
- value = "Rockin' the block"
- xml = Contact.new.to_xml(:skip_instruct => true) do |_xml|
- _xml.tag! "arbitrary-element", value
- end
- assert_equal "<contact>", xml.first(9)
- assert xml.include?(%(<arbitrary-element>#{value}</arbitrary-element>))
- end
-
- def test_should_skip_instruct_for_included_records
- @contact = Contact.new
- @contact.alternative = Contact.new(:name => 'Copa Cabana')
- @xml = @contact.to_xml(:include => [ :alternative ])
- assert_equal @xml.index('<?xml '), 0
- assert_nil @xml.index('<?xml ', 1)
- end
-end
-
-class DefaultXmlSerializationTest < ActiveRecord::TestCase
- def setup
- @contact = Contact.new(
- :name => 'aaron stack',
- :age => 25,
- :avatar => 'binarydata',
- :created_at => Time.utc(2006, 8, 1),
- :awesome => false,
- :preferences => { :gem => 'ruby' }
- )
- end
-
- def test_should_serialize_string
- assert_match %r{<name>aaron stack</name>}, @contact.to_xml
- end
-
- def test_should_serialize_integer
- assert_match %r{<age type="integer">25</age>}, @contact.to_xml
- end
-
- def test_should_serialize_binary
- xml = @contact.to_xml
- assert_match %r{YmluYXJ5ZGF0YQ==\n</avatar>}, xml
- assert_match %r{<avatar(.*)(type="binary")}, xml
- assert_match %r{<avatar(.*)(encoding="base64")}, xml
- end
-
- def test_should_serialize_datetime
- assert_match %r{<created-at type=\"dateTime\">2006-08-01T00:00:00Z</created-at>}, @contact.to_xml
- end
-
- def test_should_serialize_boolean
- assert_match %r{<awesome type=\"boolean\">false</awesome>}, @contact.to_xml
- end
-
- def test_should_serialize_hash
- assert_match %r{<preferences>\s*<gem>ruby</gem>\s*</preferences>}m, @contact.to_xml
- end
-
- def test_uses_serializable_hash_with_only_option
- def @contact.serializable_hash(options=nil)
- super(only: %w(name))
- end
-
- xml = @contact.to_xml
- assert_match %r{<name>aaron stack</name>}, xml
- assert_no_match %r{age}, xml
- assert_no_match %r{awesome}, xml
- end
-
- def test_uses_serializable_hash_with_except_option
- def @contact.serializable_hash(options=nil)
- super(except: %w(age))
- end
-
- xml = @contact.to_xml
- assert_match %r{<name>aaron stack</name>}, xml
- assert_match %r{<awesome type=\"boolean\">false</awesome>}, xml
- assert_no_match %r{age}, xml
- end
-
- def test_does_not_include_inheritance_column_from_sti
- @contact = ContactSti.new(@contact.attributes)
- assert_equal 'ContactSti', @contact.type
-
- xml = @contact.to_xml
- assert_match %r{<name>aaron stack</name>}, xml
- assert_no_match %r{<type}, xml
- assert_no_match %r{ContactSti}, xml
- end
-
- def test_serializable_hash_with_default_except_option_and_excluding_inheritance_column_from_sti
- @contact = ContactSti.new(@contact.attributes)
- assert_equal 'ContactSti', @contact.type
-
- def @contact.serializable_hash(options={})
- super({ except: %w(age) }.merge!(options))
- end
-
- xml = @contact.to_xml
- assert_match %r{<name>aaron stack</name>}, xml
- assert_no_match %r{age}, xml
- assert_no_match %r{<type}, xml
- assert_no_match %r{ContactSti}, xml
- end
-end
-
-class DefaultXmlSerializationTimezoneTest < ActiveRecord::TestCase
- def test_should_serialize_datetime_with_timezone
- with_timezone_config zone: "Pacific Time (US & Canada)" do
- toy = Toy.create(:name => 'Mickey', :updated_at => Time.utc(2006, 8, 1))
- assert_match %r{<updated-at type=\"dateTime\">2006-07-31T17:00:00-07:00</updated-at>}, toy.to_xml
- end
- end
-
- def test_should_serialize_datetime_with_timezone_reloaded
- with_timezone_config zone: "Pacific Time (US & Canada)" do
- toy = Toy.create(:name => 'Minnie', :updated_at => Time.utc(2006, 8, 1)).reload
- assert_match %r{<updated-at type=\"dateTime\">2006-07-31T17:00:00-07:00</updated-at>}, toy.to_xml
- end
- end
-end
-
-class NilXmlSerializationTest < ActiveRecord::TestCase
- def setup
- @xml = Contact.new.to_xml(:root => 'xml_contact')
- end
-
- def test_should_serialize_string
- assert_match %r{<name nil="true"/>}, @xml
- end
-
- def test_should_serialize_integer
- assert %r{<age (.*)/>}.match(@xml)
- attributes = $1
- assert_match %r{nil="true"}, attributes
- assert_match %r{type="integer"}, attributes
- end
-
- def test_should_serialize_binary
- assert %r{<avatar (.*)/>}.match(@xml)
- attributes = $1
- assert_match %r{type="binary"}, attributes
- assert_match %r{encoding="base64"}, attributes
- assert_match %r{nil="true"}, attributes
- end
-
- def test_should_serialize_datetime
- assert %r{<created-at (.*)/>}.match(@xml)
- attributes = $1
- assert_match %r{nil="true"}, attributes
- assert_match %r{type="dateTime"}, attributes
- end
-
- def test_should_serialize_boolean
- assert %r{<awesome (.*)/>}.match(@xml)
- attributes = $1
- assert_match %r{type="boolean"}, attributes
- assert_match %r{nil="true"}, attributes
- end
-
- def test_should_serialize_yaml
- assert_match %r{<preferences nil=\"true\"/>}, @xml
- end
-end
-
-class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase
- fixtures :topics, :companies, :accounts, :authors, :posts, :projects
-
- def test_to_xml
- xml = REXML::Document.new(topics(:first).to_xml(:indent => 0))
- bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema
- written_on_in_current_timezone = topics(:first).written_on.xmlschema
-
- assert_equal "topic", xml.root.name
- assert_equal "The First Topic" , xml.elements["//title"].text
- assert_equal "David" , xml.elements["//author-name"].text
- assert_match "Have a nice day", xml.elements["//content"].text
-
- assert_equal "1", xml.elements["//id"].text
- assert_equal "integer" , xml.elements["//id"].attributes['type']
-
- assert_equal "1", xml.elements["//replies-count"].text
- assert_equal "integer" , xml.elements["//replies-count"].attributes['type']
-
- assert_equal written_on_in_current_timezone, xml.elements["//written-on"].text
- assert_equal "dateTime" , xml.elements["//written-on"].attributes['type']
-
- assert_equal "david@loudthinking.com", xml.elements["//author-email-address"].text
-
- assert_equal nil, xml.elements["//parent-id"].text
- assert_equal "integer", xml.elements["//parent-id"].attributes['type']
- assert_equal "true", xml.elements["//parent-id"].attributes['nil']
-
- # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb)
- assert_equal "2004-04-15", xml.elements["//last-read"].text
- assert_equal "date" , xml.elements["//last-read"].attributes['type']
-
- # Oracle and DB2 don't have true boolean or time-only fields
- unless current_adapter?(:OracleAdapter, :DB2Adapter)
- assert_equal "false", xml.elements["//approved"].text
- assert_equal "boolean" , xml.elements["//approved"].attributes['type']
-
- assert_equal bonus_time_in_current_timezone, xml.elements["//bonus-time"].text
- assert_equal "dateTime" , xml.elements["//bonus-time"].attributes['type']
- end
- end
-
- def test_except_option
- xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :replies_count])
- assert_equal "<topic>", xml.first(7)
- assert !xml.include?(%(<title>The First Topic</title>))
- assert xml.include?(%(<author-name>David</author-name>))
-
- xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :author_name, :replies_count])
- assert !xml.include?(%(<title>The First Topic</title>))
- assert !xml.include?(%(<author-name>David</author-name>))
- end
-
- # to_xml used to mess with the hash the user provided which
- # caused the builder to be reused. This meant the document kept
- # getting appended to.
-
- def test_modules
- projects = MyApplication::Business::Project.all
- xml = projects.to_xml
- root = projects.first.class.to_s.underscore.pluralize.tr('/','_').dasherize
- assert_match "<#{root} type=\"array\">", xml
- assert_match "</#{root}>", xml
- end
-
- def test_passing_hash_shouldnt_reuse_builder
- options = {:include=>:posts}
- david = authors(:david)
- first_xml_size = david.to_xml(options).size
- second_xml_size = david.to_xml(options).size
- assert_equal first_xml_size, second_xml_size
- end
-
- def test_include_uses_association_name
- xml = authors(:david).to_xml :include=>:hello_posts, :indent => 0
- assert_match %r{<hello-posts type="array">}, xml
- assert_match %r{<hello-post type="Post">}, xml
- assert_match %r{<hello-post type="StiPost">}, xml
- end
-
- def test_included_associations_should_skip_types
- xml = authors(:david).to_xml :include=>:hello_posts, :indent => 0, :skip_types => true
- assert_match %r{<hello-posts>}, xml
- assert_match %r{<hello-post>}, xml
- assert_match %r{<hello-post>}, xml
- end
-
- def test_including_has_many_association
- xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies, :except => :replies_count)
- assert_equal "<topic>", xml.first(7)
- assert xml.include?(%(<replies type="array"><reply>))
- assert xml.include?(%(<title>The Second Topic of the day</title>))
- end
-
- def test_including_belongs_to_association
- xml = companies(:first_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
- assert !xml.include?("<firm>")
-
- xml = companies(:second_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
- assert xml.include?("<firm>")
- end
-
- def test_including_multiple_associations
- xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ])
- assert_equal "<firm>", xml.first(6)
- assert xml.include?(%(<account>))
- assert xml.include?(%(<clients type="array"><client>))
- end
-
- def test_including_association_with_options
- xml = companies(:first_firm).to_xml(
- :indent => 0, :skip_instruct => true,
- :include => { :clients => { :only => :name } }
- )
-
- assert_equal "<firm>", xml.first(6)
- assert xml.include?(%(<client><name>Summit</name></client>))
- assert xml.include?(%(<clients type="array"><client>))
- end
-
- def test_methods_are_called_on_object
- xml = authors(:david).to_xml :methods => :label, :indent => 0
- assert_match %r{<label>.*</label>}, xml
- end
-
- def test_should_not_call_methods_on_associations_that_dont_respond
- xml = authors(:david).to_xml :include=>:hello_posts, :methods => :label, :indent => 2
- assert !authors(:david).hello_posts.first.respond_to?(:label)
- assert_match %r{^ <label>.*</label>}, xml
- assert_no_match %r{^ <label>}, xml
- end
-
- def test_procs_are_called_on_object
- proc = Proc.new { |options| options[:builder].tag!('nationality', 'Danish') }
- xml = authors(:david).to_xml(:procs => [ proc ])
- assert_match %r{<nationality>Danish</nationality>}, xml
- end
-
- def test_dual_arity_procs_are_called_on_object
- proc = Proc.new { |options, record| options[:builder].tag!('name-reverse', record.name.reverse) }
- xml = authors(:david).to_xml(:procs => [ proc ])
- assert_match %r{<name-reverse>divaD</name-reverse>}, xml
- end
-
- def test_top_level_procs_arent_applied_to_associations
- author_proc = Proc.new { |options| options[:builder].tag!('nationality', 'Danish') }
- xml = authors(:david).to_xml(:procs => [ author_proc ], :include => :posts, :indent => 2)
-
- assert_match %r{^ <nationality>Danish</nationality>}, xml
- assert_no_match %r{^ {6}<nationality>Danish</nationality>}, xml
- end
-
- def test_procs_on_included_associations_are_called
- posts_proc = Proc.new { |options| options[:builder].tag!('copyright', 'DHH') }
- xml = authors(:david).to_xml(
- :indent => 2,
- :include => {
- :posts => { :procs => [ posts_proc ] }
- }
- )
-
- assert_no_match %r{^ <copyright>DHH</copyright>}, xml
- assert_match %r{^ {6}<copyright>DHH</copyright>}, xml
- end
-
- def test_should_include_empty_has_many_as_empty_array
- authors(:david).posts.delete_all
- xml = authors(:david).to_xml :include=>:posts, :indent => 2
-
- assert_equal [], Hash.from_xml(xml)['author']['posts']
- assert_match %r{^ <posts type="array"/>}, xml
- end
-
- def test_should_has_many_array_elements_should_include_type_when_different_from_guessed_value
- xml = authors(:david).to_xml :include=>:posts_with_comments, :indent => 2
-
- assert Hash.from_xml(xml)
- assert_match %r{^ <posts-with-comments type="array">}, xml
- assert_match %r{^ <posts-with-comment type="Post">}, xml
- assert_match %r{^ <posts-with-comment type="StiPost">}, xml
-
- types = Hash.from_xml(xml)['author']['posts_with_comments'].collect {|t| t['type'] }
- assert types.include?('SpecialPost')
- assert types.include?('Post')
- assert types.include?('StiPost')
- end
-
- def test_should_produce_xml_for_methods_returning_array
- xml = authors(:david).to_xml(:methods => :social)
- array = Hash.from_xml(xml)['author']['social']
- assert_equal 2, array.size
- assert array.include? 'twitter'
- assert array.include? 'github'
- end
-
- def test_should_support_aliased_attributes
- xml = Author.select("name as firstname").to_xml
- Author.all.each do |author|
- assert xml.include?(%(<firstname>#{author.name}</firstname>)), xml
- end
- end
-
- def test_array_to_xml_including_has_many_association
- xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :include => :replies)
- assert xml.include?(%(<replies type="array"><reply>))
- end
-
- def test_array_to_xml_including_methods
- xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :methods => [ :topic_id ])
- assert xml.include?(%(<topic-id type="integer">#{topics(:first).topic_id}</topic-id>)), xml
- assert xml.include?(%(<topic-id type="integer">#{topics(:second).topic_id}</topic-id>)), xml
- end
-
- def test_array_to_xml_including_has_one_association
- xml = [ companies(:first_firm), companies(:rails_core) ].to_xml(:indent => 0, :skip_instruct => true, :include => :account)
- assert xml.include?(companies(:first_firm).account.to_xml(:indent => 0, :skip_instruct => true))
- assert xml.include?(companies(:rails_core).account.to_xml(:indent => 0, :skip_instruct => true))
- end
-
- def test_array_to_xml_including_belongs_to_association
- xml = [ companies(:first_client), companies(:second_client), companies(:another_client) ].to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
- assert xml.include?(companies(:first_client).to_xml(:indent => 0, :skip_instruct => true))
- assert xml.include?(companies(:second_client).firm.to_xml(:indent => 0, :skip_instruct => true))
- assert xml.include?(companies(:another_client).firm.to_xml(:indent => 0, :skip_instruct => true))
- end
-end
diff --git a/activerecord/test/fixtures/books.yml b/activerecord/test/fixtures/books.yml
index abe56752c6..a304fba399 100644
--- a/activerecord/test/fixtures/books.yml
+++ b/activerecord/test/fixtures/books.yml
@@ -3,9 +3,29 @@ awdr:
id: 1
name: "Agile Web Development with Rails"
format: "paperback"
+ status: :published
+ read_status: :read
+ language: :english
+ author_visibility: :visible
+ illustrator_visibility: :visible
+ font_size: :medium
rfr:
author_id: 1
id: 2
name: "Ruby for Rails"
format: "ebook"
+ status: "proposed"
+ read_status: "reading"
+
+ddd:
+ author_id: 1
+ id: 3
+ name: "Domain-Driven Design"
+ format: "hardcover"
+ status: 2
+
+tlg:
+ author_id: 1
+ id: 4
+ name: "Thoughtleadering"
diff --git a/activerecord/test/fixtures/doubloons.yml b/activerecord/test/fixtures/doubloons.yml
new file mode 100644
index 0000000000..efd1643971
--- /dev/null
+++ b/activerecord/test/fixtures/doubloons.yml
@@ -0,0 +1,3 @@
+blackbeards_doubloon:
+ pirate: blackbeard
+ weight: 2
diff --git a/activerecord/test/fixtures/naked/yml/parrots.yml b/activerecord/test/fixtures/naked/yml/parrots.yml
new file mode 100644
index 0000000000..3e10331105
--- /dev/null
+++ b/activerecord/test/fixtures/naked/yml/parrots.yml
@@ -0,0 +1,2 @@
+george:
+ arrr: "Curious George"
diff --git a/activerecord/test/fixtures/nodes.yml b/activerecord/test/fixtures/nodes.yml
new file mode 100644
index 0000000000..b8bb8216ee
--- /dev/null
+++ b/activerecord/test/fixtures/nodes.yml
@@ -0,0 +1,29 @@
+grandparent:
+ id: 1
+ tree_id: 1
+ name: Grand Parent
+
+parent_a:
+ id: 2
+ tree_id: 1
+ parent_id: 1
+ name: Parent A
+
+parent_b:
+ id: 3
+ tree_id: 1
+ parent_id: 1
+ name: Parent B
+
+child_one_of_a:
+ id: 4
+ tree_id: 1
+ parent_id: 2
+ name: Child one
+
+child_two_of_b:
+ id: 5
+ tree_id: 1
+ parent_id: 2
+ name: Child two
+
diff --git a/activerecord/test/fixtures/trees.yml b/activerecord/test/fixtures/trees.yml
new file mode 100644
index 0000000000..9e030b7632
--- /dev/null
+++ b/activerecord/test/fixtures/trees.yml
@@ -0,0 +1,3 @@
+root:
+ id: 1
+ name: The Root
diff --git a/activerecord/test/models/aircraft.rb b/activerecord/test/models/aircraft.rb
index 1f35ef45da..c4404a8094 100644
--- a/activerecord/test/models/aircraft.rb
+++ b/activerecord/test/models/aircraft.rb
@@ -1,4 +1,5 @@
class Aircraft < ActiveRecord::Base
self.pluralize_table_names = false
has_many :engines, :foreign_key => "car_id"
+ has_many :wheels, as: :wheelable
end
diff --git a/activerecord/test/models/book.rb b/activerecord/test/models/book.rb
index 2170018068..1927191393 100644
--- a/activerecord/test/models/book.rb
+++ b/activerecord/test/models/book.rb
@@ -10,6 +10,10 @@ class Book < ActiveRecord::Base
enum status: [:proposed, :written, :published]
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
def published!
super
diff --git a/activerecord/test/models/carrier.rb b/activerecord/test/models/carrier.rb
new file mode 100644
index 0000000000..230be118c3
--- /dev/null
+++ b/activerecord/test/models/carrier.rb
@@ -0,0 +1,2 @@
+class Carrier < ActiveRecord::Base
+end
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index 6961f8fd6f..67936e8e5d 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -26,6 +26,9 @@ class Company < AbstractCompany
def private_method
"I am Jack's innermost fears and aspirations"
end
+
+ class SpecialCo < Company
+ end
end
module Namespaced
diff --git a/activerecord/test/models/customer_carrier.rb b/activerecord/test/models/customer_carrier.rb
new file mode 100644
index 0000000000..37186903ff
--- /dev/null
+++ b/activerecord/test/models/customer_carrier.rb
@@ -0,0 +1,14 @@
+class CustomerCarrier < ActiveRecord::Base
+ cattr_accessor :current_customer
+
+ belongs_to :customer
+ belongs_to :carrier
+
+ default_scope -> {
+ if current_customer
+ where(customer: current_customer)
+ else
+ all
+ end
+ }
+end
diff --git a/activerecord/test/models/doubloon.rb b/activerecord/test/models/doubloon.rb
new file mode 100644
index 0000000000..2b11d128e2
--- /dev/null
+++ b/activerecord/test/models/doubloon.rb
@@ -0,0 +1,12 @@
+class AbstractDoubloon < ActiveRecord::Base
+ # This has functionality that might be shared by multiple classes.
+
+ self.abstract_class = true
+ belongs_to :pirate
+end
+
+class Doubloon < AbstractDoubloon
+ # This uses an abstract class that defines attributes and associations.
+
+ self.table_name = 'doubloons'
+end
diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb
index dc0566d8a7..7693c6e515 100644
--- a/activerecord/test/models/member.rb
+++ b/activerecord/test/models/member.rb
@@ -26,6 +26,9 @@ class Member < ActiveRecord::Base
has_many :current_memberships, -> { where :favourite => true }
has_many :clubs, :through => :current_memberships
+ has_many :tenant_memberships
+ has_many :tenant_clubs, through: :tenant_memberships, class_name: 'Club', source: :club
+
has_one :club_through_many, :through => :current_memberships, :source => :club
belongs_to :admittable, polymorphic: true
diff --git a/activerecord/test/models/member_detail.rb b/activerecord/test/models/member_detail.rb
index 9d253aa126..157130986c 100644
--- a/activerecord/test/models/member_detail.rb
+++ b/activerecord/test/models/member_detail.rb
@@ -1,7 +1,8 @@
class MemberDetail < ActiveRecord::Base
- belongs_to :member, :inverse_of => false
+ belongs_to :member, inverse_of: false
belongs_to :organization
- has_one :member_type, :through => :member
+ has_one :member_type, through: :member
+ has_one :membership, through: :member
- has_many :organization_member_details, :through => :organization, :source => :member_details
+ has_many :organization_member_details, through: :organization, source: :member_details
end
diff --git a/activerecord/test/models/membership.rb b/activerecord/test/models/membership.rb
index df7167ee93..e181ba1f11 100644
--- a/activerecord/test/models/membership.rb
+++ b/activerecord/test/models/membership.rb
@@ -18,3 +18,18 @@ class SelectedMembership < Membership
select("'1' as foo")
end
end
+
+class TenantMembership < Membership
+ cattr_accessor :current_member
+
+ belongs_to :member
+ belongs_to :club
+
+ default_scope -> {
+ if current_member
+ where(member: current_member)
+ else
+ all
+ end
+ }
+end
diff --git a/activerecord/test/models/node.rb b/activerecord/test/models/node.rb
new file mode 100644
index 0000000000..07dd2dbccb
--- /dev/null
+++ b/activerecord/test/models/node.rb
@@ -0,0 +1,5 @@
+class Node < ActiveRecord::Base
+ belongs_to :tree, touch: true
+ belongs_to :parent, class_name: 'Node', touch: true, optional: true
+ has_many :children, class_name: 'Node', foreign_key: :parent_id, dependent: :destroy
+end
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 67bfe0c5f1..81a18188d4 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -208,6 +208,22 @@ class PostWithDefaultScope < ActiveRecord::Base
default_scope { order(:title) }
end
+class PostWithPreloadDefaultScope < ActiveRecord::Base
+ self.table_name = 'posts'
+
+ has_many :readers, foreign_key: 'post_id'
+
+ default_scope { preload(:readers) }
+end
+
+class PostWithIncludesDefaultScope < ActiveRecord::Base
+ self.table_name = 'posts'
+
+ has_many :readers, foreign_key: 'post_id'
+
+ default_scope { includes(:readers) }
+end
+
class SpecialPostWithDefaultScope < ActiveRecord::Base
self.table_name = 'posts'
default_scope { where(:id => [1, 5,6]) }
diff --git a/activerecord/test/models/professor.rb b/activerecord/test/models/professor.rb
new file mode 100644
index 0000000000..7654eda0ef
--- /dev/null
+++ b/activerecord/test/models/professor.rb
@@ -0,0 +1,5 @@
+require_dependency 'models/arunit2_model'
+
+class Professor < ARUnit2Model
+ has_and_belongs_to_many :courses
+end
diff --git a/activerecord/test/models/ship.rb b/activerecord/test/models/ship.rb
index 312caef604..95172e4d3e 100644
--- a/activerecord/test/models/ship.rb
+++ b/activerecord/test/models/ship.rb
@@ -19,6 +19,18 @@ class Ship < ActiveRecord::Base
end
end
+class ShipWithoutNestedAttributes < ActiveRecord::Base
+ self.table_name = "ships"
+ has_many :prisoners, inverse_of: :ship, foreign_key: :ship_id
+ has_many :parts, class_name: "ShipPart", foreign_key: :ship_id
+
+ validates :name, presence: true
+end
+
+class Prisoner < ActiveRecord::Base
+ belongs_to :ship, autosave: true, class_name: "ShipWithoutNestedAttributes", inverse_of: :prisoners
+end
+
class FamousShip < ActiveRecord::Base
self.table_name = 'ships'
belongs_to :famous_pirate
diff --git a/activerecord/test/models/shop_account.rb b/activerecord/test/models/shop_account.rb
new file mode 100644
index 0000000000..1580e8b20c
--- /dev/null
+++ b/activerecord/test/models/shop_account.rb
@@ -0,0 +1,6 @@
+class ShopAccount < ActiveRecord::Base
+ belongs_to :customer
+ belongs_to :customer_carrier
+
+ has_one :carrier, through: :customer_carrier
+end
diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb
index f81ffe1d90..d17270021a 100644
--- a/activerecord/test/models/topic.rb
+++ b/activerecord/test/models/topic.rb
@@ -32,7 +32,7 @@ class Topic < ActiveRecord::Base
end
end
- has_many :replies, :dependent => :destroy, :foreign_key => "parent_id"
+ has_many :replies, dependent: :destroy, foreign_key: "parent_id", autosave: true
has_many :approved_replies, -> { approved }, class_name: 'Reply', foreign_key: "parent_id", counter_cache: 'replies_count'
has_many :unique_replies, :dependent => :destroy, :foreign_key => "parent_id"
diff --git a/activerecord/test/models/tree.rb b/activerecord/test/models/tree.rb
new file mode 100644
index 0000000000..dc29cccc9c
--- /dev/null
+++ b/activerecord/test/models/tree.rb
@@ -0,0 +1,3 @@
+class Tree < ActiveRecord::Base
+ has_many :nodes, dependent: :destroy
+end
diff --git a/activerecord/test/models/vehicle.rb b/activerecord/test/models/vehicle.rb
new file mode 100644
index 0000000000..ef26170f1f
--- /dev/null
+++ b/activerecord/test/models/vehicle.rb
@@ -0,0 +1,7 @@
+class Vehicle < ActiveRecord::Base
+ self.abstract_class = true
+ default_scope -> { where("tires_count IS NOT NULL") }
+end
+
+class Bus < Vehicle
+end \ No newline at end of file
diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb
index d6fd0c4ab0..52d3290c84 100644
--- a/activerecord/test/schema/mysql2_specific_schema.rb
+++ b/activerecord/test/schema/mysql2_specific_schema.rb
@@ -24,6 +24,11 @@ ActiveRecord::Schema.define do
add_index :key_tests, :pizza, :using => :btree, :name => 'index_key_tests_on_pizza'
add_index :key_tests, :snacks, :name => 'index_key_tests_on_snack'
+ create_table :collation_tests, id: false, force: true do |t|
+ t.string :string_cs_column, limit: 1, collation: 'utf8_bin'
+ t.string :string_ci_column, limit: 1, collation: 'utf8_general_ci'
+ end
+
ActiveRecord::Base.connection.execute <<-SQL
DROP PROCEDURE IF EXISTS ten;
SQL
@@ -35,15 +40,6 @@ BEGIN
END
SQL
- ActiveRecord::Base.connection.drop_table "collation_tests", if_exists: true
-
- ActiveRecord::Base.connection.execute <<-SQL
-CREATE TABLE collation_tests (
- string_cs_column VARCHAR(1) COLLATE utf8_bin,
- string_ci_column VARCHAR(1) COLLATE utf8_general_ci
-) CHARACTER SET utf8 COLLATE utf8_general_ci
-SQL
-
ActiveRecord::Base.connection.drop_table "enum_tests", if_exists: true
ActiveRecord::Base.connection.execute <<-SQL
diff --git a/activerecord/test/schema/mysql_specific_schema.rb b/activerecord/test/schema/mysql_specific_schema.rb
index b5378341b5..90f5a60d7b 100644
--- a/activerecord/test/schema/mysql_specific_schema.rb
+++ b/activerecord/test/schema/mysql_specific_schema.rb
@@ -24,6 +24,11 @@ ActiveRecord::Schema.define do
add_index :key_tests, :pizza, :using => :btree, :name => 'index_key_tests_on_pizza'
add_index :key_tests, :snacks, :name => 'index_key_tests_on_snack'
+ create_table :collation_tests, id: false, force: true do |t|
+ t.string :string_cs_column, limit: 1, collation: 'utf8_bin'
+ t.string :string_ci_column, limit: 1, collation: 'utf8_general_ci'
+ end
+
ActiveRecord::Base.connection.execute <<-SQL
DROP PROCEDURE IF EXISTS ten;
SQL
@@ -46,15 +51,6 @@ BEGIN
END
SQL
- ActiveRecord::Base.connection.drop_table "collation_tests", if_exists: true
-
- ActiveRecord::Base.connection.execute <<-SQL
-CREATE TABLE collation_tests (
- string_cs_column VARCHAR(1) COLLATE utf8_bin,
- string_ci_column VARCHAR(1) COLLATE utf8_general_ci
-) CHARACTER SET utf8 COLLATE utf8_general_ci
-SQL
-
ActiveRecord::Base.connection.drop_table "enum_tests", if_exists: true
ActiveRecord::Base.connection.execute <<-SQL
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index 008503bc24..df0362573b 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -1,5 +1,16 @@
ActiveRecord::Schema.define do
+ enable_extension!('uuid-ossp', ActiveRecord::Base.connection)
+
+ create_table :uuid_parents, id: :uuid, force: true do |t|
+ t.string :name
+ end
+
+ create_table :uuid_children, id: :uuid, force: true do |t|
+ t.string :name
+ t.uuid :uuid_parent_id
+ end
+
%w(postgresql_times postgresql_oids defaults postgresql_timestamp_with_zones
postgresql_partitioned_table postgresql_partitioned_table_parent).each do |table_name|
drop_table table_name, if_exists: true
@@ -96,5 +107,6 @@ _SQL
create_table :bigint_array, force: true do |t|
t.integer :big_int_data_points, limit: 8, array: true
+ t.decimal :decimal_array_default, array: true, default: [1.23, 3.45]
end
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 9b03c78a46..6f34115534 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -6,20 +6,6 @@ ActiveRecord::Schema.define do
end
end
- #put adapter specific setup here
- case adapter_name
- when "PostgreSQL"
- enable_extension!('uuid-ossp', ActiveRecord::Base.connection)
- create_table :uuid_parents, id: :uuid, force: true do |t|
- t.string :name
- end
- create_table :uuid_children, id: :uuid, force: true do |t|
- t.string :name
- t.uuid :uuid_parent_id
- end
- end
-
-
# ------------------------------------------------------------------- #
# #
# Please keep these create table statements in alphabetical order #
@@ -51,6 +37,7 @@ ActiveRecord::Schema.define do
create_table :aircraft, force: true do |t|
t.string :name
+ t.integer :wheels_count, default: 0, null: false
end
create_table :articles, force: true do |t|
@@ -114,6 +101,10 @@ ActiveRecord::Schema.define do
t.column :status, :integer, default: 0
t.column :read_status, :integer, default: 0
t.column :nullable_status, :integer
+ t.column :language, :integer, default: 0
+ t.column :author_visibility, :integer, default: 0
+ t.column :illustrator_visibility, :integer, default: 0
+ t.column :font_size, :integer, default: 0
end
create_table :booleans, force: true do |t|
@@ -140,6 +131,8 @@ ActiveRecord::Schema.define do
t.timestamps null: false
end
+ create_table :carriers, force: true
+
create_table :categories, force: true do |t|
t.string :name, null: false
t.string :type
@@ -246,6 +239,11 @@ ActiveRecord::Schema.define do
t.string :gps_location
end
+ create_table :customer_carriers, force: true do |t|
+ t.references :customer
+ t.references :carrier
+ end
+
create_table :dashboards, force: true, id: false do |t|
t.string :dashboard_id
t.string :name
@@ -280,6 +278,11 @@ ActiveRecord::Schema.define do
t.string :alias
end
+ create_table :doubloons, force: true do |t|
+ t.integer :pirate_id
+ t.integer :weight
+ end
+
create_table :edges, force: true, id: false do |t|
t.column :source_id, :integer, null: false
t.column :sink_id, :integer, null: false
@@ -685,6 +688,15 @@ ActiveRecord::Schema.define do
t.datetime :updated_at
end
+ create_table :prisoners, force: true do |t|
+ t.belongs_to :ship
+ end
+
+ create_table :shop_accounts, force: true do |t|
+ t.references :customer
+ t.references :customer_carrier
+ end
+
create_table :speedometers, force: true, id: false do |t|
t.string :speedometer_id
t.string :name
@@ -874,6 +886,17 @@ ActiveRecord::Schema.define do
t.string 'from'
end
+ create_table :nodes, force: true do |t|
+ t.integer :tree_id
+ t.integer :parent_id
+ t.string :name
+ t.datetime :updated_at
+ end
+ create_table :trees, force: true do |t|
+ t.string :name
+ t.datetime :updated_at
+ end
+
create_table :hotels, force: true do |t|
end
create_table :departments, force: true do |t|
@@ -930,3 +953,12 @@ end
College.connection.create_table :colleges, force: true do |t|
t.column :name, :string, null: false
end
+
+Professor.connection.create_table :professors, force: true do |t|
+ t.column :name, :string, null: false
+end
+
+Professor.connection.create_table :courses_professors, id: false, force: true do |t|
+ t.references :course
+ t.references :professor
+end
diff --git a/activerecord/test/support/connection.rb b/activerecord/test/support/connection.rb
index d11fd9cfc1..c5334e8596 100644
--- a/activerecord/test/support/connection.rb
+++ b/activerecord/test/support/connection.rb
@@ -1,6 +1,7 @@
require 'active_support/logger'
require 'models/college'
require 'models/course'
+require 'models/professor'
module ARTest
def self.connection_name
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 3ad2392365..eaaa3df061 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,93 @@
+* Deprecate `:prefix` option of `number_to_human_size` with no replacement.
+
+ *Jean Boussier*
+
+* Fix `TimeWithZone#eql?` to properly handle `TimeWithZone` created from `DateTime`:
+ twz = DateTime.now.in_time_zone
+ twz.eql?(twz.dup) => true
+
+ Fixes #14178.
+
+ *Roque Pinel*
+
+* ActiveSupport::HashWithIndifferentAccess `select` and `reject` will now return
+ enumerator if called without block.
+
+ Fixes #20095
+
+ *Bernard Potocki*
+
+* Removed `ActiveSupport::Concurrency::Latch`, superseded by `Concurrent::CountDownLatch`
+ from the concurrent-ruby gem.
+
+ *Jerry D'Antonio*
+
+* Fix not calling `#default` on `HashWithIndifferentAccess#to_hash` when only
+ `default_proc` is set, which could raise.
+
+ *Simon Eskildsen*
+
+* Fix setting `default_proc` on `HashWithIndifferentAccess#dup`
+
+ *Simon Eskildsen*
+
+* Fix a range of values for parameters of the Time#change
+
+ *Nikolay Kondratyev*
+
+* Add `Enumerable#pluck` to get the same values from arrays as from ActiveRecord
+ associations.
+
+ Fixes #20339.
+
+ *Kevin Deisz*
+
+* Add a bang version to `ActiveSupport::OrderedOptions` get methods which will raise
+ an `KeyError` if the value is `.blank?`
+
+ Before:
+
+ if (slack_url = Rails.application.secrets.slack_url).present?
+ # Do something worthwhile
+ else
+ # Raise as important secret password is not specified
+ end
+
+ After:
+
+ slack_url = Rails.application.secrets.slack_url!
+
+ *Aditya Sanghi*, *Gaurish Sharma*
+
+* Remove deprecated `Class#superclass_delegating_accessor`.
+ Use `Class#class_attribute` instead.
+
+ *Akshay Vishnoi*
+
+* Patch `Delegator` to work with `#try`.
+
+ Fixes #5790.
+
+ *Nate Smith*
+
+* Add `Integer#positive?` and `Integer#negative?` query methods
+ in the vein of `Fixnum#zero?`.
+
+ This makes it nicer to do things like `bunch_of_numbers.select(&:positive?)`.
+
+ *DHH*
+
+* Encoding `ActiveSupport::TimeWithZone` to YAML now preserves the timezone information.
+
+ Fixes #9183.
+
+ *Andrew White*
+
+* Added `ActiveSupport::TimeZone#strptime` to allow parsing times as if
+ from a given timezone.
+
+ *Paul A Jungwirth*
+
* `ActiveSupport::Callbacks#skip_callback` now raises an `ArgumentError` if
an unrecognized callback is removed.
@@ -23,7 +113,8 @@
*George Claghorn*
-* Deprecate `alias_method_chain` in favour of `Module#prepend` introduced in Ruby 2.0
+* Deprecate `alias_method_chain` in favour of `Module#prepend` introduced in
+ Ruby 2.0.
*Kir Shatrov*
@@ -32,12 +123,12 @@
*Todd Bealmear*
-* Fixed a problem where String#truncate_words would get stuck with a complex
+* Fixed a problem where `String#truncate_words` would get stuck with a complex
string.
*Henrik Nygren*
-* Fixed a roundtrip problem with AS::SafeBuffer where primitive-like strings
+* Fixed a roundtrip problem with `AS::SafeBuffer` where primitive-like strings
will be dumped as primitives:
Before:
@@ -84,7 +175,7 @@
*Ian Ker-Seymer*
-* Duplicate frozen array when assigning it to a HashWithIndifferentAccess so
+* Duplicate frozen array when assigning it to a `HashWithIndifferentAccess` so
that it doesn't raise a `RuntimeError` when calling `map!` on it in `convert_value`.
Fixes #18550.
@@ -113,7 +204,7 @@
* Add `#on_weekend?`, `#next_weekday`, `#prev_weekday` methods to `Date`,
`Time`, and `DateTime`.
- `#on_weekend?` returns true if the receiving date/time falls on a Saturday
+ `#on_weekend?` returns `true` if the receiving date/time falls on a Saturday
or Sunday.
`#next_weekday` returns a new date/time representing the next day that does
@@ -167,13 +258,13 @@
`Callbacks::CallbackChain.halt_and_display_warning_on_return_false`, will
either not work at all or display a deprecation warning.
-* Add Callbacks::CallbackChain.halt_and_display_warning_on_return_false
+* Add `Callbacks::CallbackChain.halt_and_display_warning_on_return_false`
Setting `Callbacks::CallbackChain.halt_and_display_warning_on_return_false`
- to true will let an app support the deprecated way of halting callback
+ to `true` will let an app support the deprecated way of halting callback
chains by returning `false`.
- Setting the value to false will tell the app to ignore any `false` value
+ Setting the value to `false` will tell the app to ignore any `false` value
returned by callbacks, and only halt the chain upon `throw(:abort)`.
The value can also be set with the Rails configuration option
@@ -186,7 +277,7 @@
*claudiob*
-* Changes arguments and default value of CallbackChain's :terminator option
+* Changes arguments and default value of CallbackChain's `:terminator` option
Chains of callbacks defined without an explicit `:terminator` option will
now be halted as soon as a `before_` callback throws `:abort`.
diff --git a/activesupport/README.rdoc b/activesupport/README.rdoc
index a6424a353a..cd72f53821 100644
--- a/activesupport/README.rdoc
+++ b/activesupport/README.rdoc
@@ -10,7 +10,7 @@ outside of Rails.
The latest version of Active Support can be installed with RubyGems:
- % [sudo] gem install activesupport
+ % gem install activesupport
Source code can be downloaded as part of the Rails project on GitHub:
diff --git a/activesupport/Rakefile b/activesupport/Rakefile
index 7c40df8dc8..81c242d4b1 100644
--- a/activesupport/Rakefile
+++ b/activesupport/Rakefile
@@ -1,5 +1,4 @@
require 'rake/testtask'
-require 'rubygems/package_task'
task :default => :test
Rake::TestTask.new do |t|
@@ -17,16 +16,3 @@ namespace :test do
end or raise "Failures"
end
end
-
-spec = eval(File.read('activesupport.gemspec'))
-
-Gem::PackageTask.new(spec) do |p|
- p.gem_spec = spec
-end
-
-desc "Release to rubygems"
-task :release => :package do
- require 'rake/gemcutter'
- Rake::Gemcutter::Tasks.new(spec).define
- Rake::Task['gem:push'].invoke
-end
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index 2cb455cb41..c18c1c87c9 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
s.summary = 'A toolkit of support libraries and Ruby core extensions extracted from the Rails framework.'
s.description = 'A toolkit of support libraries and Ruby core extensions extracted from the Rails framework. Rich support for multibyte strings, internationalization, time zones, and testing.'
- s.required_ruby_version = '>= 2.2.1'
+ s.required_ruby_version = '>= 2.2.2'
s.license = 'MIT'
@@ -25,4 +25,5 @@ Gem::Specification.new do |s|
s.add_dependency 'tzinfo', '~> 1.1'
s.add_dependency 'minitest', '~> 5.1'
s.add_dependency 'thread_safe','~> 0.3', '>= 0.3.4'
+ s.add_dependency 'concurrent-ruby', '~> 0.9.0'
end
diff --git a/activesupport/bin/test b/activesupport/bin/test
new file mode 100755
index 0000000000..404cabba51
--- /dev/null
+++ b/activesupport/bin/test
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+COMPONENT_ROOT = File.expand_path("../../", __FILE__)
+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 9af3f8b447..588d6c49f9 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -73,6 +73,14 @@ module ActiveSupport
end
cattr_accessor :test_order # :nodoc:
+
+ def self.halt_callback_chains_on_return_false
+ Callbacks::CallbackChain.halt_and_display_warning_on_return_false
+ end
+
+ def self.halt_callback_chains_on_return_false=(value)
+ Callbacks::CallbackChain.halt_and_display_warning_on_return_false = value
+ end
end
autoload :I18n, "active_support/i18n"
diff --git a/activesupport/lib/active_support/array_inquirer.rb b/activesupport/lib/active_support/array_inquirer.rb
index 0ae534da00..e7188d7adb 100644
--- a/activesupport/lib/active_support/array_inquirer.rb
+++ b/activesupport/lib/active_support/array_inquirer.rb
@@ -7,11 +7,17 @@ module ActiveSupport
# variants.phone? # => true
# variants.tablet? # => true
# variants.desktop? # => false
- #
- # variants.any?(:phone, :tablet) # => true
- # variants.any?(:phone, :desktop) # => true
- # variants.any?(:desktop, :watch) # => false
class ArrayInquirer < Array
+ # Passes each element of +candidates+ collection to ArrayInquirer collection.
+ # The method returns true if at least one element is the same. If +candidates+
+ # collection is not given, method returns true.
+ #
+ # variants = ActiveSupport::ArrayInquirer.new([:phone, :tablet])
+ #
+ # variants.any? # => true
+ # variants.any?(:phone, :tablet) # => true
+ # variants.any?('phone', 'desktop') # => true
+ # variants.any?(:desktop, :watch) # => false
def any?(*candidates, &block)
if candidates.none?
super
diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb
index d06f22ad5c..e161ec4cca 100644
--- a/activesupport/lib/active_support/backtrace_cleaner.rb
+++ b/activesupport/lib/active_support/backtrace_cleaner.rb
@@ -25,7 +25,7 @@ module ActiveSupport
# of the backtrace, you can call <tt>BacktraceCleaner#remove_filters!</tt>
# These two methods will give you a completely untouched backtrace.
#
- # Inspired by the Quiet Backtrace gem by Thoughtbot.
+ # Inspired by the Quiet Backtrace gem by thoughtbot.
class BacktraceCleaner
def initialize
@filters, @silencers = [], []
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index d08ecd2f7d..e6a8b84214 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -29,6 +29,7 @@ module ActiveSupport
def clear(options = nil)
root_dirs = Dir.entries(cache_path).reject {|f| (EXCLUDED_DIRS + [".gitkeep"]).include?(f)}
FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)})
+ rescue Errno::ENOENT
end
# Preemptively iterates through all stored keys and removes the ones which have expired.
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index 73ae3acea5..47133bf550 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -26,7 +26,14 @@ module ActiveSupport
class MemCacheStore < Store
ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
- def self.build_mem_cache(*addresses)
+ # Creates a new Dalli::Client instance with specified addresses and options.
+ # By default address is equal localhost:11211.
+ #
+ # ActiveSupport::Cache::MemCacheStore.build_mem_cache
+ # # => #<Dalli::Client:0x007f98a47d2028 @servers=["localhost:11211"], @options={}, @ring=nil>
+ # ActiveSupport::Cache::MemCacheStore.build_mem_cache('localhost:10290')
+ # # => #<Dalli::Client:0x007f98a47b3a60 @servers=["localhost:10290"], @options={}, @ring=nil>
+ def self.build_mem_cache(*addresses) # :nodoc:
addresses = addresses.flatten
options = addresses.extract_options!
addresses = ["localhost:11211"] if addresses.empty?
diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb
index 8a0523d0e2..90bb2c38c3 100644
--- a/activesupport/lib/active_support/cache/memory_store.rb
+++ b/activesupport/lib/active_support/cache/memory_store.rb
@@ -126,7 +126,7 @@ module ActiveSupport
PER_ENTRY_OVERHEAD = 240
- def cached_size(key, entry)
+ def cached_size(key, entry) # :nodoc:
key.to_s.bytesize + entry.size + PER_ENTRY_OVERHEAD
end
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb
index a913736fc3..fe5bc82c30 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb
@@ -120,7 +120,7 @@ module ActiveSupport
super
end
- def set_cache_value(value, name, amount, options)
+ def set_cache_value(value, name, amount, options) # :nodoc:
if local_cache
local_cache.mute do
if value
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 814fd288cf..fefba5b0fd 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -80,8 +80,12 @@ module ActiveSupport
# save
# end
def run_callbacks(kind, &block)
- callbacks = send("_#{kind}_callbacks")
+ send "_run_#{kind}_callbacks", &block
+ end
+
+ private
+ def __run_callbacks__(callbacks, &block)
if callbacks.empty?
yield if block_given?
else
@@ -91,8 +95,6 @@ module ActiveSupport
end
end
- private
-
# A hook invoked every time a before callback is halted.
# This can be overridden in AS::Callback implementors in order
# to provide better debugging/logging.
@@ -638,7 +640,7 @@ module ActiveSupport
# set_callback :save, :after, :after_meth, if: :condition
# set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff }
#
- # The second arguments indicates whether the callback is to be run +:before+,
+ # The second argument indicates whether the callback is to be run +:before+,
# +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This
# means the first example above can also be written as:
#
@@ -739,10 +741,10 @@ module ActiveSupport
# callback chain, preventing following before and around callbacks from
# being called and the event from being triggered.
# This should be a lambda to be executed.
- # The current object and the return result of the callback will be called
- # with the lambda.
+ # The current object and the result lambda of the callback will be provided
+ # to the terminator lambda.
#
- # define_callbacks :validate, terminator: ->(target, result) { result == false }
+ # define_callbacks :validate, terminator: ->(target, result_lambda) { result_lambda.call == false }
#
# In this example, if any before validate callbacks returns +false+,
# any successive before and around callback is not executed.
@@ -806,6 +808,12 @@ module ActiveSupport
names.each do |name|
class_attribute "_#{name}_callbacks"
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
end
end
diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb
index 4082d2d464..0403eb70ca 100644
--- a/activesupport/lib/active_support/concern.rb
+++ b/activesupport/lib/active_support/concern.rb
@@ -132,7 +132,7 @@ module ActiveSupport
end
def class_methods(&class_methods_module_definition)
- mod = const_defined?(:ClassMethods) ?
+ mod = const_defined?(:ClassMethods, false) ?
const_get(:ClassMethods) :
const_set(:ClassMethods, Module.new)
diff --git a/activesupport/lib/active_support/concurrency/latch.rb b/activesupport/lib/active_support/concurrency/latch.rb
index 1507de433e..7b8df0df04 100644
--- a/activesupport/lib/active_support/concurrency/latch.rb
+++ b/activesupport/lib/active_support/concurrency/latch.rb
@@ -1,26 +1,18 @@
-require 'thread'
-require 'monitor'
+require 'concurrent/atomics'
module ActiveSupport
module Concurrency
- class Latch
- def initialize(count = 1)
- @count = count
- @lock = Monitor.new
- @cv = @lock.new_cond
- end
+ class Latch < Concurrent::CountDownLatch
- def release
- @lock.synchronize do
- @count -= 1 if @count > 0
- @cv.broadcast if @count.zero?
- end
+ def initialize(count = 1)
+ ActiveSupport::Deprecation.warn("ActiveSupport::Concurrency::Latch is deprecated. Please use Concurrent::CountDownLatch instead.")
+ super(count)
end
+
+ alias_method :release, :count_down
def await
- @lock.synchronize do
- @cv.wait_while { @count > 0 }
- end
+ wait(nil)
end
end
end
diff --git a/activesupport/lib/active_support/concurrency/share_lock.rb b/activesupport/lib/active_support/concurrency/share_lock.rb
new file mode 100644
index 0000000000..ca48164c54
--- /dev/null
+++ b/activesupport/lib/active_support/concurrency/share_lock.rb
@@ -0,0 +1,142 @@
+require 'thread'
+require 'monitor'
+
+module ActiveSupport
+ module Concurrency
+ # A share/exclusive lock, otherwise known as a read/write lock.
+ #
+ # https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock
+ #--
+ # Note that a pending Exclusive lock attempt does not block incoming
+ # Share requests (i.e., we are "read-preferring"). That seems
+ # consistent with the behavior of "loose" upgrades, but may be the
+ # wrong choice otherwise: it nominally reduces the possibility of
+ # deadlock by risking starvation instead.
+ class ShareLock
+ include MonitorMixin
+
+ # We track Thread objects, instead of just using counters, because
+ # we need exclusive locks to be reentrant, and we need to be able
+ # to upgrade share locks to exclusive.
+
+
+ def initialize
+ super()
+
+ @cv = new_cond
+
+ @sharing = Hash.new(0)
+ @waiting = {}
+ @exclusive_thread = nil
+ @exclusive_depth = 0
+ end
+
+ # Returns false if +no_wait+ is set and the lock is not
+ # immediately available. Otherwise, returns true after the lock
+ # has been acquired.
+ #
+ # +purpose+ and +compatible+ work together; while this thread is
+ # waiting for the exclusive lock, it will yield its share (if any)
+ # to any other attempt whose +purpose+ appears in this attempt's
+ # +compatible+ list. This allows a "loose" upgrade, which, being
+ # less strict, prevents some classes of deadlocks.
+ #
+ # For many resources, loose upgrades are sufficient: if a thread
+ # is awaiting a lock, it is not running any other code. With
+ # +purpose+ matching, it is possible to yield only to other
+ # threads whose activity will not interfere.
+ def start_exclusive(purpose: nil, compatible: [], no_wait: false)
+ synchronize do
+ unless @exclusive_thread == Thread.current
+ if busy?(purpose)
+ return false if no_wait
+
+ loose_shares = @sharing.delete(Thread.current)
+ @waiting[Thread.current] = compatible if loose_shares
+
+ begin
+ @cv.wait_while { busy?(purpose) }
+ ensure
+ @waiting.delete Thread.current
+ @sharing[Thread.current] = loose_shares if loose_shares
+ end
+ end
+ @exclusive_thread = Thread.current
+ end
+ @exclusive_depth += 1
+
+ true
+ end
+ end
+
+ # Relinquish the exclusive lock. Must only be called by the thread
+ # that called start_exclusive (and currently holds the lock).
+ def stop_exclusive
+ synchronize do
+ raise "invalid unlock" if @exclusive_thread != Thread.current
+
+ @exclusive_depth -= 1
+ if @exclusive_depth == 0
+ @exclusive_thread = nil
+ @cv.broadcast
+ end
+ end
+ end
+
+ def start_sharing
+ synchronize do
+ if @exclusive_thread && @exclusive_thread != Thread.current
+ @cv.wait_while { @exclusive_thread }
+ end
+ @sharing[Thread.current] += 1
+ end
+ end
+
+ def stop_sharing
+ synchronize do
+ if @sharing[Thread.current] > 1
+ @sharing[Thread.current] -= 1
+ else
+ @sharing.delete Thread.current
+ @cv.broadcast
+ end
+ end
+ end
+
+ # Execute the supplied block while holding the Exclusive lock. If
+ # +no_wait+ is set and the lock is not immediately available,
+ # returns +nil+ without yielding. Otherwise, returns the result of
+ # the block.
+ #
+ # See +start_exclusive+ for other options.
+ def exclusive(purpose: nil, compatible: [], no_wait: false)
+ if start_exclusive(purpose: purpose, compatible: compatible, no_wait: no_wait)
+ begin
+ yield
+ ensure
+ stop_exclusive
+ end
+ end
+ end
+
+ # Execute the supplied block while holding the Share lock.
+ def sharing
+ start_sharing
+ begin
+ yield
+ ensure
+ stop_sharing
+ end
+ end
+
+ private
+
+ # Must be called within synchronize
+ def busy?(purpose)
+ (@exclusive_thread && @exclusive_thread != Thread.current) ||
+ @waiting.any? { |k, v| k != Thread.current && !v.include?(purpose) } ||
+ @sharing.size > (@sharing[Thread.current] > 0 ? 1 : 0)
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/array/inquiry.rb b/activesupport/lib/active_support/core_ext/array/inquiry.rb
index de623c466c..e8f44cc378 100644
--- a/activesupport/lib/active_support/core_ext/array/inquiry.rb
+++ b/activesupport/lib/active_support/core_ext/array/inquiry.rb
@@ -1,3 +1,5 @@
+require 'active_support/array_inquirer'
+
class Array
# Wraps the array in an +ArrayInquirer+ object, which gives a friendlier way
# to check its string-like contents.
diff --git a/activesupport/lib/active_support/core_ext/array/wrap.rb b/activesupport/lib/active_support/core_ext/array/wrap.rb
index 152eb02218..b611d34c27 100644
--- a/activesupport/lib/active_support/core_ext/array/wrap.rb
+++ b/activesupport/lib/active_support/core_ext/array/wrap.rb
@@ -3,7 +3,7 @@ class Array
#
# Specifically:
#
- # * If the argument is +nil+ an empty list is returned.
+ # * If the argument is +nil+ an empty array is returned.
# * Otherwise, if the argument responds to +to_ary+ it is invoked, and its result returned.
# * Otherwise, returns an array with the argument as its single element.
#
@@ -15,12 +15,13 @@ class Array
#
# * If the argument responds to +to_ary+ the method is invoked. <tt>Kernel#Array</tt>
# moves on to try +to_a+ if the returned value is +nil+, but <tt>Array.wrap</tt> returns
- # +nil+ right away.
+ # an array with the argument as its single element right away.
# * If the returned value from +to_ary+ is neither +nil+ nor an +Array+ object, <tt>Kernel#Array</tt>
# raises an exception, while <tt>Array.wrap</tt> does not, it just returns the value.
- # * It does not call +to_a+ on the argument, but returns an empty array if argument is +nil+.
+ # * It does not call +to_a+ on the argument, if the argument does not respond to +to_ary+
+ # it returns an array with the argument as its single element.
#
- # The second point is easily explained with some enumerables:
+ # The last point is easily explained with some enumerables:
#
# Array(foo: :bar) # => [[:foo, :bar]]
# Array.wrap(foo: :bar) # => [{:foo=>:bar}]
diff --git a/activesupport/lib/active_support/core_ext/class.rb b/activesupport/lib/active_support/core_ext/class.rb
index c750a10bb2..ef903d59b5 100644
--- a/activesupport/lib/active_support/core_ext/class.rb
+++ b/activesupport/lib/active_support/core_ext/class.rb
@@ -1,3 +1,2 @@
require 'active_support/core_ext/class/attribute'
-require 'active_support/core_ext/class/delegating_attributes'
require 'active_support/core_ext/class/subclasses'
diff --git a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
deleted file mode 100644
index 1c305c5970..0000000000
--- a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-require 'active_support/core_ext/kernel/singleton_class'
-require 'active_support/core_ext/module/remove_method'
-require 'active_support/core_ext/module/deprecation'
-
-
-class Class
- def superclass_delegating_accessor(name, options = {})
- # Create private _name and _name= methods that can still be used if the public
- # methods are overridden.
- _superclass_delegating_accessor("_#{name}", options)
-
- # Generate the public methods name, name=, and name?.
- # These methods dispatch to the private _name, and _name= methods, making them
- # overridable.
- singleton_class.send(:define_method, name) { send("_#{name}") }
- singleton_class.send(:define_method, "#{name}?") { !!send("_#{name}") }
- singleton_class.send(:define_method, "#{name}=") { |value| send("_#{name}=", value) }
-
- # If an instance_reader is needed, generate public instance methods name and name?.
- if options[:instance_reader] != false
- define_method(name) { send("_#{name}") }
- define_method("#{name}?") { !!send("#{name}") }
- end
- end
-
- deprecate superclass_delegating_accessor: :class_attribute
-
- private
- # Take the object being set and store it in a method. This gives us automatic
- # inheritance behavior, without having to store the object in an instance
- # variable and look up the superclass chain manually.
- def _stash_object_in_method(object, method, instance_reader = true)
- singleton_class.remove_possible_method(method)
- singleton_class.send(:define_method, method) { object }
- remove_possible_method(method)
- define_method(method) { object } if instance_reader
- end
-
- def _superclass_delegating_accessor(name, options = {})
- singleton_class.send(:define_method, "#{name}=") do |value|
- _stash_object_in_method(value, name, options[:instance_reader] != false)
- end
- send("#{name}=", nil)
- end
-end
diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb
index df419a6e63..31479a1269 100644
--- a/activesupport/lib/active_support/core_ext/date/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date/conversions.rb
@@ -35,6 +35,7 @@ class Date
# date.to_s(:db) # => "2007-11-10"
#
# date.to_formatted_s(:short) # => "10 Nov"
+ # date.to_formatted_s(:number) # => "20071110"
# date.to_formatted_s(:long) # => "November 10, 2007"
# date.to_formatted_s(:long_ordinal) # => "November 10th, 2007"
# date.to_formatted_s(:rfc822) # => "10 Nov 2007"
@@ -82,6 +83,11 @@ class Date
::Time.send(form, year, month, day)
end
+ # Returns a string which represents the time in used time zone as DateTime
+ # defined by XML Schema:
+ #
+ # date = Date.new(2015, 05, 23) # => Sat, 23 May 2015
+ # date.xmlschema # => "2015-05-23T00:00:00+04:00"
def xmlschema
in_time_zone.xmlschema
end
diff --git a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
index 9525c10112..40811dafc0 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
@@ -92,15 +92,28 @@ module DateAndTime
end
# Returns a new date/time at the start of the month.
- # DateTime objects will have a time set to 0:00.
+ #
+ # today = Date.today # => Thu, 18 Jun 2015
+ # today.beginning_of_month # => Mon, 01 Jun 2015
+ #
+ # +DateTime+ objects will have a time set to 0:00.
+ #
+ # now = DateTime.current # => Thu, 18 Jun 2015 15:23:13 +0000
+ # now.beginning_of_month # => Mon, 01 Jun 2015 00:00:00 +0000
def beginning_of_month
first_hour(change(:day => 1))
end
alias :at_beginning_of_month :beginning_of_month
# Returns a new date/time at the start of the quarter.
- # Example: 1st January, 1st July, 1st October.
- # DateTime objects will have a time set to 0:00.
+ #
+ # today = Date.today # => Fri, 10 Jul 2015
+ # today.beginning_of_quarter # => Wed, 01 Jul 2015
+ #
+ # +DateTime+ objects will have a time set to 0:00.
+ #
+ # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000
+ # now.beginning_of_quarter # => Wed, 01 Jul 2015 00:00:00 +0000
def beginning_of_quarter
first_quarter_month = [10, 7, 4, 1].detect { |m| m <= month }
beginning_of_month.change(:month => first_quarter_month)
@@ -108,8 +121,14 @@ module DateAndTime
alias :at_beginning_of_quarter :beginning_of_quarter
# Returns a new date/time at the end of the quarter.
- # Example: 31st March, 30th June, 30th September.
- # DateTime objects will have a time set to 23:59:59.
+ #
+ # today = Date.today # => Fri, 10 Jul 2015
+ # today.end_of_quarter # => Wed, 30 Sep 2015
+ #
+ # +DateTime+ objects will have a time set to 23:59:59.
+ #
+ # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000
+ # now.end_of_quarter # => Wed, 30 Sep 2015 23:59:59 +0000
def end_of_quarter
last_quarter_month = [3, 6, 9, 12].detect { |m| m >= month }
beginning_of_month.change(:month => last_quarter_month).end_of_month
@@ -117,17 +136,35 @@ module DateAndTime
alias :at_end_of_quarter :end_of_quarter
# Return a new date/time at the beginning of the year.
- # Example: 1st January.
- # DateTime objects will have a time set to 0:00.
+ #
+ # today = Date.today # => Fri, 10 Jul 2015
+ # today.beginning_of_year # => Thu, 01 Jan 2015
+ #
+ # +DateTime+ objects will have a time set to 0:00.
+ #
+ # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000
+ # now.beginning_of_year # => Thu, 01 Jan 2015 00:00:00 +0000
def beginning_of_year
change(:month => 1).beginning_of_month
end
alias :at_beginning_of_year :beginning_of_year
# Returns a new date/time representing the given day in the next week.
+ #
+ # today = Date.today # => Thu, 07 May 2015
+ # today.next_week # => Mon, 11 May 2015
+ #
# The +given_day_in_next_week+ defaults to the beginning of the week
# which is determined by +Date.beginning_of_week+ or +config.beginning_of_week+
- # when set. +DateTime+ objects have their time set to 0:00 unless +same_time+ is true.
+ # when set.
+ #
+ # today = Date.today # => Thu, 07 May 2015
+ # today.next_week(:friday) # => Fri, 15 May 2015
+ #
+ # +DateTime+ objects have their time set to 0:00 unless +same_time+ is true.
+ #
+ # now = DateTime.current # => Thu, 07 May 2015 13:31:16 +0000
+ # now.next_week # => Mon, 11 May 2015 00:00:00 +0000
def next_week(given_day_in_next_week = Date.beginning_of_week, same_time: false)
result = first_hour(weeks_since(1).beginning_of_week.days_since(days_span(given_day_in_next_week)))
same_time ? copy_time_to(result) : result
diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
index 55ad384f4f..95617fb8c2 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -168,7 +168,7 @@ class DateTime
if other.kind_of?(Infinity)
super
elsif other.respond_to? :to_datetime
- super other.to_datetime
+ super other.to_datetime rescue nil
else
nil
end
diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb
index 7a893292b3..fc7531d088 100644
--- a/activesupport/lib/active_support/core_ext/enumerable.rb
+++ b/activesupport/lib/active_support/core_ext/enumerable.rb
@@ -71,6 +71,21 @@ module Enumerable
def without(*elements)
reject { |element| elements.include?(element) }
end
+
+ # Convert an enumerable to an array based on the given key.
+ #
+ # [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name)
+ # => ["David", "Rafael", "Aaron"]
+ #
+ # [{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pluck(:id, :name)
+ # => [[1, "David"], [2, "Rafael"]]
+ def pluck(*keys)
+ if keys.many?
+ map { |element| keys.map { |key| element[key] } }
+ else
+ map { |element| element[keys.first] }
+ end
+ end
end
class Range #:nodoc:
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 2149d4439d..8594d9bf2e 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -106,7 +106,25 @@ class Hash
# # => {"hash"=>{"foo"=>1, "bar"=>2}}
#
# +DisallowedType+ is raised if the XML contains attributes with <tt>type="yaml"</tt> or
- # <tt>type="symbol"</tt>. Use <tt>Hash.from_trusted_xml</tt> to parse this XML.
+ # <tt>type="symbol"</tt>. Use <tt>Hash.from_trusted_xml</tt> to
+ # parse this XML.
+ #
+ # Custom +disallowed_types+ can also be passed in the form of an
+ # array.
+ #
+ # xml = <<-XML
+ # <?xml version="1.0" encoding="UTF-8"?>
+ # <hash>
+ # <foo type="integer">1</foo>
+ # <bar type="string">"David"</bar>
+ # </hash>
+ # XML
+ #
+ # hash = Hash.from_xml(xml, ['integer'])
+ # # => ActiveSupport::XMLConverter::DisallowedType: Disallowed type attribute: "integer"
+ #
+ # Note that passing custom disallowed types will override the default types,
+ # which are Symbol and YAML.
def from_xml(xml, disallowed_types = nil)
ActiveSupport::XMLConverter.new(xml, disallowed_types).to_h
end
diff --git a/activesupport/lib/active_support/core_ext/hash/except.rb b/activesupport/lib/active_support/core_ext/hash/except.rb
index 6e397abf51..2f6d38c1f6 100644
--- a/activesupport/lib/active_support/core_ext/hash/except.rb
+++ b/activesupport/lib/active_support/core_ext/hash/except.rb
@@ -1,8 +1,9 @@
class Hash
- # Returns a hash that includes everything but the given keys.
- # hash = { a: true, b: false, c: nil}
- # hash.except(:c) # => { a: true, b: false}
- # hash # => { a: true, b: false, c: nil}
+ # Returns a hash that includes everything except given keys.
+ # hash = { a: true, b: false, c: nil }
+ # hash.except(:c) # => { a: true, b: false }
+ # hash.except(:a, :b) # => { c: nil }
+ # hash # => { a: true, b: false, c: nil }
#
# This is useful for limiting a set of parameters to everything but a few known toggles:
# @person.update(params[:person].except(:admin))
@@ -10,10 +11,10 @@ class Hash
dup.except!(*keys)
end
- # Replaces the hash without the given keys.
- # hash = { a: true, b: false, c: nil}
- # hash.except!(:c) # => { a: true, b: false}
- # hash # => { a: true, b: false }
+ # Removes the given keys from hash and returns it.
+ # hash = { a: true, b: false, c: nil }
+ # hash.except!(:c) # => { a: true, b: false }
+ # hash # => { a: true, b: false }
def except!(*keys)
keys.each { |key| delete(key) }
self
diff --git a/activesupport/lib/active_support/core_ext/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb
index d9fb392752..60732eb41a 100644
--- a/activesupport/lib/active_support/core_ext/load_error.rb
+++ b/activesupport/lib/active_support/core_ext/load_error.rb
@@ -23,7 +23,7 @@ class LoadError
# Returns true if the given path name (except perhaps for the ".rb"
# extension) is the missing file which caused the exception to be raised.
def is_missing?(location)
- location.sub(/\.rb$/, '') == path.sub(/\.rb$/, '')
+ location.sub(/\.rb$/, ''.freeze) == path.sub(/\.rb$/, ''.freeze)
end
end
diff --git a/activesupport/lib/active_support/core_ext/module/aliasing.rb b/activesupport/lib/active_support/core_ext/module/aliasing.rb
index a4c40b25ff..b6934b9c54 100644
--- a/activesupport/lib/active_support/core_ext/module/aliasing.rb
+++ b/activesupport/lib/active_support/core_ext/module/aliasing.rb
@@ -1,4 +1,7 @@
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
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
index d4e6b5a1ac..a77da573fe 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -206,7 +206,7 @@ class Module
# Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
def mattr_accessor(*syms, &blk)
mattr_reader(*syms, &blk)
- mattr_writer(*syms, &blk)
+ mattr_writer(*syms)
end
alias :cattr_accessor :mattr_accessor
end
diff --git a/activesupport/lib/active_support/core_ext/module/concerning.rb b/activesupport/lib/active_support/core_ext/module/concerning.rb
index 07a392404e..65b88b9bbd 100644
--- a/activesupport/lib/active_support/core_ext/module/concerning.rb
+++ b/activesupport/lib/active_support/core_ext/module/concerning.rb
@@ -63,10 +63,10 @@ class Module
#
# == Mix-in noise exiled to its own file:
#
- # Once our chunk of behavior starts pushing the scroll-to-understand it's
+ # Once our chunk of behavior starts pushing the scroll-to-understand-it
# boundary, we give in and move it to a separate file. At this size, the
- # overhead feels in good proportion to the size of our extraction, despite
- # diluting our at-a-glance sense of how things really work.
+ # increased overhead can be a reasonable tradeoff even if it reduces our
+ # at-a-glance perception of how things work.
#
# class Todo
# # Other todo implementation
@@ -99,7 +99,7 @@ class Module
# end
#
# Todo.ancestors
- # # => Todo, Todo::EventTracking, Object
+ # # => [Todo, Todo::EventTracking, Object]
#
# This small step has some wonderful ripple effects. We can
# * grok the behavior of our class in one glance,
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 9b7a429db9..9dc0dee1bd 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -167,7 +167,7 @@ class Module
''
end
- file, line = caller(1, 1).first.split(':', 2)
+ file, line = caller(1, 1).first.split(':'.freeze, 2)
line = line.to_i
to = to.to_s
diff --git a/activesupport/lib/active_support/core_ext/numeric.rb b/activesupport/lib/active_support/core_ext/numeric.rb
index a6bc0624be..bcdc3eace2 100644
--- a/activesupport/lib/active_support/core_ext/numeric.rb
+++ b/activesupport/lib/active_support/core_ext/numeric.rb
@@ -1,3 +1,4 @@
require 'active_support/core_ext/numeric/bytes'
require 'active_support/core_ext/numeric/time'
+require 'active_support/core_ext/numeric/inquiry'
require 'active_support/core_ext/numeric/conversions'
diff --git a/activesupport/lib/active_support/core_ext/numeric/inquiry.rb b/activesupport/lib/active_support/core_ext/numeric/inquiry.rb
new file mode 100644
index 0000000000..7e7ac1b0b2
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/numeric/inquiry.rb
@@ -0,0 +1,26 @@
+unless 1.respond_to?(:positive?) # TODO: Remove this file when we drop support to ruby < 2.3
+class Numeric
+ # Returns true if the number is positive.
+ #
+ # 1.positive? # => true
+ # 0.positive? # => false
+ # -1.positive? # => false
+ def positive?
+ self > 0
+ end
+
+ # Returns true if the number is negative.
+ #
+ # -1.negative? # => true
+ # 0.negative? # => false
+ # 1.negative? # => false
+ def negative?
+ self < 0
+ end
+end
+
+class Complex
+ undef :positive?
+ undef :negative?
+end
+end
diff --git a/activesupport/lib/active_support/core_ext/object/deep_dup.rb b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
index 0191d2e973..ad5b2af161 100644
--- a/activesupport/lib/active_support/core_ext/object/deep_dup.rb
+++ b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
@@ -40,6 +40,7 @@ class Hash
# dup[:a][:c] # => "c"
def deep_dup
each_with_object(dup) do |(key, value), hash|
+ hash.delete(key)
hash[key.deep_dup] = value.deep_dup
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb
index 620f7b6561..befa5aee21 100644
--- a/activesupport/lib/active_support/core_ext/object/duplicable.rb
+++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb
@@ -19,7 +19,7 @@
class Object
# Can you safely dup this object?
#
- # False for +nil+, +false+, +true+, symbol, number objects;
+ # False for +nil+, +false+, +true+, symbol, number, method objects;
# true otherwise.
def duplicable?
true
@@ -78,6 +78,10 @@ end
require 'bigdecimal'
class BigDecimal
+ # BigDecimals are duplicable:
+ #
+ # BigDecimal.new("1.2").duplicable? # => true
+ # BigDecimal.new("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)>
def duplicable?
true
end
diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb
index e0f70b9caa..69be6c4abc 100644
--- a/activesupport/lib/active_support/core_ext/object/try.rb
+++ b/activesupport/lib/active_support/core_ext/object/try.rb
@@ -1,4 +1,34 @@
+require 'delegate'
+
+module ActiveSupport
+ module Tryable #:nodoc:
+ def try(*a, &b)
+ try!(*a, &b) if a.empty? || respond_to?(a.first)
+ end
+
+ def try!(*a, &b)
+ if a.empty? && block_given?
+ if b.arity.zero?
+ instance_eval(&b)
+ else
+ yield self
+ end
+ else
+ public_send(*a, &b)
+ end
+ end
+ end
+end
+
class Object
+ include ActiveSupport::Tryable
+
+ ##
+ # :method: try
+ #
+ # :call-seq:
+ # try(*a, &b)
+ #
# Invokes the public method whose name goes as first argument just like
# +public_send+ does, except that if the receiver does not respond to it the
# call returns +nil+ rather than raising an exception.
@@ -56,30 +86,40 @@ class Object
#
# Please also note that +try+ is defined on +Object+. Therefore, it won't work
# with instances of classes that do not have +Object+ among their ancestors,
- # like direct subclasses of +BasicObject+. For example, using +try+ with
- # +SimpleDelegator+ will delegate +try+ to the target instead of calling it on
- # the delegator itself.
- def try(*a, &b)
- try!(*a, &b) if a.empty? || respond_to?(a.first)
- end
+ # like direct subclasses of +BasicObject+.
+ ##
+ # :method: try!
+ #
+ # :call-seq:
+ # try!(*a, &b)
+ #
# Same as #try, but raises a NoMethodError exception if the receiver is
# not +nil+ and does not implement the tried method.
#
# "a".try!(:upcase) # => "A"
# nil.try!(:upcase) # => nil
# 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Fixnum
- def try!(*a, &b)
- if a.empty? && block_given?
- if b.arity.zero?
- instance_eval(&b)
- else
- yield self
- end
- else
- public_send(*a, &b)
- end
- end
+end
+
+class Delegator
+ include ActiveSupport::Tryable
+
+ ##
+ # :method: try
+ #
+ # :call-seq:
+ # try(a*, &b)
+ #
+ # See Object#try
+
+ ##
+ # :method: try!
+ #
+ # :call-seq:
+ # try!(a*, &b)
+ #
+ # See Object#try!
end
class NilClass
diff --git a/activesupport/lib/active_support/core_ext/object/with_options.rb b/activesupport/lib/active_support/core_ext/object/with_options.rb
index 7d38e1d134..513c8b1d55 100644
--- a/activesupport/lib/active_support/core_ext/object/with_options.rb
+++ b/activesupport/lib/active_support/core_ext/object/with_options.rb
@@ -7,7 +7,7 @@ class Object
# provided. Each method called on the block variable must take an options
# hash as its final argument.
#
- # Without <tt>with_options></tt>, this code contains duplication:
+ # Without <tt>with_options</tt>, this code contains duplication:
#
# class Account < ActiveRecord::Base
# has_many :customers, dependent: :destroy
diff --git a/activesupport/lib/active_support/core_ext/range/each.rb b/activesupport/lib/active_support/core_ext/range/each.rb
index f666480fe6..dc6dad5ced 100644
--- a/activesupport/lib/active_support/core_ext/range/each.rb
+++ b/activesupport/lib/active_support/core_ext/range/each.rb
@@ -1,27 +1,21 @@
-class Range #:nodoc:
+module ActiveSupport
+ module EachTimeWithZone #:nodoc:
+ def each(&block)
+ ensure_iteration_allowed
+ super
+ end
- def each_with_time_with_zone(&block)
- ensure_iteration_allowed
- each_without_time_with_zone(&block)
- end
- # TODO: change to Module#prepend as soon as the fix is backported to MRI 2.2:
- # https://bugs.ruby-lang.org/issues/10847
- alias_method :each_without_time_with_zone, :each
- alias_method :each, :each_with_time_with_zone
+ def step(n = 1, &block)
+ ensure_iteration_allowed
+ super
+ end
- def step_with_time_with_zone(n = 1, &block)
- ensure_iteration_allowed
- step_without_time_with_zone(n, &block)
- end
- # TODO: change to Module#prepend as soon as the fix is backported to MRI 2.2:
- # https://bugs.ruby-lang.org/issues/10847
- alias_method :step_without_time_with_zone, :step
- alias_method :step, :step_with_time_with_zone
+ private
- private
- def ensure_iteration_allowed
- if first.is_a?(Time)
- raise TypeError, "can't iterate from #{first.class}"
- end
+ def ensure_iteration_allowed
+ raise TypeError, "can't iterate from #{first.class}" if first.is_a?(Time)
+ end
end
end
+
+Range.prepend(ActiveSupport::EachTimeWithZone)
diff --git a/activesupport/lib/active_support/core_ext/range/include_range.rb b/activesupport/lib/active_support/core_ext/range/include_range.rb
index 9d20920dd0..c69e1e3fb9 100644
--- a/activesupport/lib/active_support/core_ext/range/include_range.rb
+++ b/activesupport/lib/active_support/core_ext/range/include_range.rb
@@ -1,23 +1,23 @@
-class Range
- # Extends the default Range#include? to support range comparisons.
- # (1..5).include?(1..5) # => true
- # (1..5).include?(2..3) # => true
- # (1..5).include?(2..6) # => false
- #
- # The native Range#include? behavior is untouched.
- # ('a'..'f').include?('c') # => true
- # (5..9).include?(11) # => false
- def include_with_range?(value)
- if value.is_a?(::Range)
- # 1...10 includes 1..9 but it does not include 1..10.
- operator = exclude_end? && !value.exclude_end? ? :< : :<=
- include_without_range?(value.first) && value.last.send(operator, last)
- else
- include_without_range?(value)
+module ActiveSupport
+ module IncludeWithRange #:nodoc:
+ # Extends the default Range#include? to support range comparisons.
+ # (1..5).include?(1..5) # => true
+ # (1..5).include?(2..3) # => true
+ # (1..5).include?(2..6) # => false
+ #
+ # The native Range#include? behavior is untouched.
+ # ('a'..'f').include?('c') # => true
+ # (5..9).include?(11) # => false
+ def include?(value)
+ if value.is_a?(::Range)
+ # 1...10 includes 1..9 but it does not include 1..10.
+ operator = exclude_end? && !value.exclude_end? ? :< : :<=
+ super(value.first) && value.last.send(operator, last)
+ else
+ super
+ end
end
end
- # TODO: change to Module#prepend as soon as the fix is backported to MRI 2.2:
- # https://bugs.ruby-lang.org/issues/10847
- alias_method :include_without_range?, :include?
- alias_method :include?, :include_with_range?
end
+
+Range.prepend(ActiveSupport::IncludeWithRange)
diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb
index 7461d03acc..375ec1aef8 100644
--- a/activesupport/lib/active_support/core_ext/string/filters.rb
+++ b/activesupport/lib/active_support/core_ext/string/filters.rb
@@ -17,9 +17,8 @@ class String
# str.squish! # => "foo bar boo"
# str # => "foo bar boo"
def squish!
- gsub!(/\A[[:space:]]+/, '')
- gsub!(/[[:space:]]+\z/, '')
gsub!(/[[:space:]]+/, ' ')
+ strip!
self
end
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index 97f9720b2b..b2e713077c 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -164,7 +164,7 @@ class String
#
# <%= link_to(@person.name, person_path) %>
# # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a>
- def parameterize(sep = '-')
+ def parameterize(sep = '-'.freeze)
ActiveSupport::Inflector.parameterize(self, sep)
end
@@ -172,7 +172,7 @@ class String
# uses the +pluralize+ method on the last word in the string.
#
# 'RawScaledScorer'.tableize # => "raw_scaled_scorers"
- # 'egg_and_ham'.tableize # => "egg_and_hams"
+ # 'ham_and_egg'.tableize # => "ham_and_eggs"
# 'fancyCategory'.tableize # => "fancy_categories"
def tableize
ActiveSupport::Inflector.tableize(self)
@@ -182,7 +182,7 @@ class String
# Note that this returns a string and not a class. (To convert to an actual class
# follow +classify+ with +constantize+.)
#
- # 'egg_and_hams'.classify # => "EggAndHam"
+ # 'ham_and_eggs'.classify # => "HamAndEgg"
# 'posts'.classify # => "Post"
def classify
ActiveSupport::Inflector.classify(self)
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 1ce68ea7c7..96156deebb 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -98,7 +98,7 @@ class Time
elsif zone
::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec)
else
- raise ArgumentError, 'argument out of range' if new_usec > 999999
+ raise ArgumentError, 'argument out of range' if new_usec >= 1000000
::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec + (new_usec.to_r / 1000000), utc_offset)
end
end
@@ -108,6 +108,12 @@ class Time
# takes a hash with any of these keys: <tt>:years</tt>, <tt>:months</tt>,
# <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>, <tt>:minutes</tt>,
# <tt>:seconds</tt>.
+ #
+ # Time.new(2015, 8, 1, 14, 35, 0).advance(seconds: 1) # => 2015-08-01 14:35:01 -0700
+ # Time.new(2015, 8, 1, 14, 35, 0).advance(minutes: 1) # => 2015-08-01 14:36:00 -0700
+ # Time.new(2015, 8, 1, 14, 35, 0).advance(hours: 1) # => 2015-08-01 15:35:00 -0700
+ # Time.new(2015, 8, 1, 14, 35, 0).advance(days: 1) # => 2015-08-02 14:35:00 -0700
+ # Time.new(2015, 8, 1, 14, 35, 0).advance(weeks: 1) # => 2015-08-08 14:35:00 -0700
def advance(options)
unless options[:weeks].nil?
options[:weeks], partial_weeks = options[:weeks].divmod(1)
diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb
index d683e7c777..133d3938eb 100644
--- a/activesupport/lib/active_support/core_ext/time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/time/zones.rb
@@ -65,7 +65,8 @@ class Time
if !time_zone || time_zone.is_a?(ActiveSupport::TimeZone)
time_zone
else
- # lookup timezone based on identifier (unless we've been passed a TZInfo::Timezone)
+ # Look up the timezone based on the identifier (unless we've been
+ # passed a TZInfo::Timezone)
unless time_zone.respond_to?(:period_for_local)
time_zone = ActiveSupport::TimeZone[time_zone] || TZInfo::Timezone.get(time_zone)
end
diff --git a/activesupport/lib/active_support/core_ext/uri.rb b/activesupport/lib/active_support/core_ext/uri.rb
index bfe0832b37..0b2ff817c3 100644
--- a/activesupport/lib/active_support/core_ext/uri.rb
+++ b/activesupport/lib/active_support/core_ext/uri.rb
@@ -12,7 +12,7 @@ unless str == parser.unescape(parser.escape(str))
# YK: My initial experiments say yes, but let's be sure please
enc = str.encoding
enc = Encoding::UTF_8 if enc == Encoding::US_ASCII
- str.gsub(escaped) { [$&[1, 2].hex].pack('C') }.force_encoding(enc)
+ str.gsub(escaped) { |match| [match[1, 2].hex].pack('C') }.force_encoding(enc)
end
end
end
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 664cc15a29..f76ef04f49 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -12,12 +12,40 @@ require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/load_error'
require 'active_support/core_ext/name_error'
require 'active_support/core_ext/string/starts_ends_with'
+require "active_support/dependencies/interlock"
require 'active_support/inflector'
module ActiveSupport #:nodoc:
module Dependencies #:nodoc:
extend self
+ mattr_accessor :interlock
+ self.interlock = Interlock.new
+
+ # :doc:
+
+ # Execute the supplied block without interference from any
+ # concurrent loads
+ def self.run_interlock
+ Dependencies.interlock.running { yield }
+ end
+
+ # Execute the supplied block while holding an exclusive lock,
+ # preventing any other thread from being inside a #run_interlock
+ # block at the same time
+ def self.load_interlock
+ Dependencies.interlock.loading { yield }
+ end
+
+ # Execute the supplied block while holding an exclusive lock,
+ # preventing any other thread from being inside a #run_interlock
+ # block at the same time
+ def self.unload_interlock
+ Dependencies.interlock.unloading { yield }
+ end
+
+ # :nodoc:
+
# Should we turn on Ruby warnings on the first load of dependent files?
mattr_accessor :warnings_on_first_load
self.warnings_on_first_load = false
@@ -128,7 +156,7 @@ module ActiveSupport #:nodoc:
# Normalize the list of new constants, and add them to the list we will return
new_constants.each do |suffix|
- constants << ([namespace, suffix] - ["Object"]).join("::")
+ constants << ([namespace, suffix] - ["Object"]).join("::".freeze)
end
end
constants
@@ -325,9 +353,11 @@ module ActiveSupport #:nodoc:
def clear
log_call
- loaded.clear
- loading.clear
- remove_unloadable_constants!
+ Dependencies.unload_interlock do
+ loaded.clear
+ loading.clear
+ remove_unloadable_constants!
+ end
end
def require_or_load(file_name, const_path = nil)
@@ -336,39 +366,44 @@ module ActiveSupport #:nodoc:
expanded = File.expand_path(file_name)
return if loaded.include?(expanded)
- # Record that we've seen this file *before* loading it to avoid an
- # infinite loop with mutual dependencies.
- loaded << expanded
- loading << expanded
+ Dependencies.load_interlock do
+ # Maybe it got loaded while we were waiting for our lock:
+ return if loaded.include?(expanded)
- begin
- if load?
- log "loading #{file_name}"
+ # Record that we've seen this file *before* loading it to avoid an
+ # infinite loop with mutual dependencies.
+ loaded << expanded
+ loading << expanded
- # Enable warnings if this file has not been loaded before and
- # warnings_on_first_load is set.
- load_args = ["#{file_name}.rb"]
- load_args << const_path unless const_path.nil?
-
- if !warnings_on_first_load or history.include?(expanded)
- result = load_file(*load_args)
+ begin
+ if load?
+ log "loading #{file_name}"
+
+ # Enable warnings if this file has not been loaded before and
+ # warnings_on_first_load is set.
+ load_args = ["#{file_name}.rb"]
+ load_args << const_path unless const_path.nil?
+
+ if !warnings_on_first_load or history.include?(expanded)
+ result = load_file(*load_args)
+ else
+ enable_warnings { result = load_file(*load_args) }
+ end
else
- enable_warnings { result = load_file(*load_args) }
+ log "requiring #{file_name}"
+ result = require file_name
end
- else
- log "requiring #{file_name}"
- result = require file_name
+ rescue Exception
+ loaded.delete expanded
+ raise
+ ensure
+ loading.pop
end
- rescue Exception
- loaded.delete expanded
- raise
- ensure
- loading.pop
- end
- # Record history *after* loading so first load gets warnings.
- history << expanded
- result
+ # Record history *after* loading so first load gets warnings.
+ history << expanded
+ result
+ end
end
# Is the provided constant path defined?
@@ -401,7 +436,7 @@ module ActiveSupport #:nodoc:
# Search for a file in autoload_paths matching the provided suffix.
def search_for_file(path_suffix)
- path_suffix = path_suffix.sub(/(\.rb)?$/, ".rb")
+ path_suffix = path_suffix.sub(/(\.rb)?$/, ".rb".freeze)
autoload_paths.each do |root|
path = File.join(root, path_suffix)
@@ -486,7 +521,7 @@ module ActiveSupport #:nodoc:
if file_path
expanded = File.expand_path(file_path)
- expanded.sub!(/\.rb\z/, '')
+ expanded.sub!(/\.rb\z/, ''.freeze)
if loading.include?(expanded)
raise "Circular dependency detected while autoloading constant #{qualified_name}"
diff --git a/activesupport/lib/active_support/dependencies/interlock.rb b/activesupport/lib/active_support/dependencies/interlock.rb
new file mode 100644
index 0000000000..fbeb904684
--- /dev/null
+++ b/activesupport/lib/active_support/dependencies/interlock.rb
@@ -0,0 +1,47 @@
+require 'active_support/concurrency/share_lock'
+
+module ActiveSupport #:nodoc:
+ module Dependencies #:nodoc:
+ class Interlock
+ def initialize # :nodoc:
+ @lock = ActiveSupport::Concurrency::ShareLock.new
+ end
+
+ def loading
+ @lock.exclusive(purpose: :load, compatible: [:load]) do
+ yield
+ end
+ end
+
+ def unloading
+ @lock.exclusive(purpose: :unload, compatible: [:load, :unload]) do
+ yield
+ end
+ end
+
+ # Attempt to obtain an "unloading" (exclusive) lock. If possible,
+ # execute the supplied block while holding the lock. If there is
+ # concurrent activity, return immediately (without executing the
+ # block) instead of waiting.
+ def attempt_unloading
+ @lock.exclusive(purpose: :unload, compatible: [:load, :unload], no_wait: true) do
+ yield
+ end
+ end
+
+ def start_running
+ @lock.start_sharing
+ end
+
+ def done_running
+ @lock.stop_sharing
+ end
+
+ def running
+ @lock.sharing do
+ yield
+ end
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb
index 9f9dca8453..28d2d78643 100644
--- a/activesupport/lib/active_support/deprecation/behaviors.rb
+++ b/activesupport/lib/active_support/deprecation/behaviors.rb
@@ -38,6 +38,18 @@ module ActiveSupport
silence: ->(message, callstack) {},
}
+ # Behavior module allows to determine how to display deprecation messages.
+ # You can create a custom behavior or set any from the +DEFAULT_BEHAVIORS+
+ # constant. Available behaviors are:
+ #
+ # [+raise+] Raise <tt>ActiveSupport::DeprecationException</tt>.
+ # [+stderr+] Log all deprecation warnings to +$stderr+.
+ # [+log+] Log all deprecation warnings to +Rails.logger+.
+ # [+notify+] Use +ActiveSupport::Notifications+ to notify +deprecation.rails+.
+ # [+silence+] Do nothing.
+ #
+ # Setting behaviors only affects deprecations that happen after boot time.
+ # For more information you can read the documentation of the +behavior=+ method.
module Behavior
# Whether to print a backtrace along with the warning.
attr_accessor :debug
diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
index a03a66b96b..9b264cbb79 100644
--- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
@@ -25,15 +25,17 @@ module ActiveSupport
end
end
- # This DeprecatedObjectProxy transforms object to deprecated object.
+ # DeprecatedObjectProxy transforms an object into a deprecated one. It
+ # takes an object, a deprecation message and optionally a deprecator. The
+ # deprecator defaults to +ActiveSupport::Deprecator+ if none is specified.
#
- # @old_object = DeprecatedObjectProxy.new(Object.new, "Don't use this object anymore!")
- # @old_object = DeprecatedObjectProxy.new(Object.new, "Don't use this object anymore!", deprecator_instance)
+ # deprecated_object = ActiveSupport::Deprecation::DeprecatedObjectProxy.new(Object.new, "This object is now deprecated")
+ # # => #<Object:0x007fb9b34c34b0>
#
- # When someone executes any method except +inspect+ on proxy object this will
- # trigger +warn+ method on +deprecator_instance+.
- #
- # Default deprecator is <tt>ActiveSupport::Deprecation</tt>
+ # deprecated_object.to_s
+ # DEPRECATION WARNING: This object is now deprecated.
+ # (Backtrace)
+ # # => "#<Object:0x007fb9b34c34b0>"
class DeprecatedObjectProxy < DeprecationProxy
def initialize(object, message, deprecator = ActiveSupport::Deprecation.instance)
@object = object
@@ -51,13 +53,16 @@ module ActiveSupport
end
end
- # This DeprecatedInstanceVariableProxy transforms instance variable to
- # deprecated instance variable.
+ # DeprecatedInstanceVariableProxy transforms an instance variable into a
+ # deprecated one. It takes an instance of a class, a method on that class
+ # and an instance variable. It optionally takes a deprecator as the last
+ # argument. The deprecator defaults to +ActiveSupport::Deprecator+ if none
+ # is specified.
#
# class Example
- # def initialize(deprecator)
- # @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request, deprecator)
- # @_request = :a_request
+ # def initialize
+ # @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request)
+ # @_request = :special_request
# end
#
# def request
@@ -69,12 +74,17 @@ module ActiveSupport
# end
# end
#
- # When someone execute any method on @request variable this will trigger
- # +warn+ method on +deprecator_instance+ and will fetch <tt>@_request</tt>
- # variable via +request+ method and execute the same method on non-proxy
- # instance variable.
+ # example = Example.new
+ # # => #<Example:0x007fb9b31090b8 @_request=:special_request, @request=:special_request>
+ #
+ # example.old_request.to_s
+ # # => DEPRECATION WARNING: @request is deprecated! Call request.to_s instead of
+ # @request.to_s
+ # (Bactrace information…)
+ # "special_request"
#
- # Default deprecator is <tt>ActiveSupport::Deprecation</tt>.
+ # example.request.to_s
+ # # => "special_request"
class DeprecatedInstanceVariableProxy < DeprecationProxy
def initialize(instance, method, var = "@#{method}", deprecator = ActiveSupport::Deprecation.instance)
@instance = instance
@@ -93,15 +103,23 @@ module ActiveSupport
end
end
- # This DeprecatedConstantProxy transforms constant to deprecated constant.
+ # DeprecatedConstantProxy transforms a constant into a deprecated one. It
+ # takes the names of an old (deprecated) constant and of a new constant
+ # (both in string form) and optionally a deprecator. The deprecator defaults
+ # to +ActiveSupport::Deprecator+ if none is specified. The deprecated constant
+ # now returns the value of the new one.
+ #
+ # PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto)
#
- # OLD_CONST = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('OLD_CONST', 'NEW_CONST')
- # OLD_CONST = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('OLD_CONST', 'NEW_CONST', deprecator_instance)
+ # (In a later update, the orignal implementation of `PLANETS` has been removed.)
#
- # When someone use old constant this will trigger +warn+ method on
- # +deprecator_instance+.
+ # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune)
+ # PLANETS = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('PLANETS', 'PLANETS_POST_2006')
#
- # Default deprecator is <tt>ActiveSupport::Deprecation</tt>.
+ # PLANETS.map { |planet| planet.capitalize }
+ # # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead.
+ # (Bactrace information…)
+ # ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
class DeprecatedConstantProxy < DeprecationProxy
def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance)
@old_const = old_const
@@ -109,6 +127,11 @@ module ActiveSupport
@deprecator = deprecator
end
+ # Returns the class of the new constant.
+ #
+ # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune)
+ # PLANETS = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('PLANETS', 'PLANETS_POST_2006')
+ # PLANETS.class # => Array
def class
target.class
end
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 4c0d1197fe..c63b61e97a 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -52,6 +52,10 @@ module ActiveSupport
end
end
+ # Returns the amount of seconds a duration covers as a string.
+ # For more information check to_i method.
+ #
+ # 1.day.to_s # => "86400"
def to_s
@value.to_s
end
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 4f71f13971..0371f760b0 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -92,7 +92,7 @@ module ActiveSupport
# hash = ActiveSupport::HashWithIndifferentAccess.new
# hash[:key] = 'value'
#
- # This value can be later fetched using either +:key+ or +'key'+.
+ # This value can be later fetched using either +:key+ or <tt>'key'</tt>.
def []=(key, value)
regular_writer(convert_key(key), convert_value(value, for: :assignment))
end
@@ -188,7 +188,7 @@ module ActiveSupport
# dup[:a][:c] # => "c"
def dup
self.class.new(self).tap do |new_hash|
- new_hash.default = default
+ set_defaults(new_hash)
end
end
@@ -238,16 +238,20 @@ module ActiveSupport
def to_options!; self end
def select(*args, &block)
+ return to_enum(:select) unless block_given?
dup.tap { |hash| hash.select!(*args, &block) }
end
def reject(*args, &block)
+ return to_enum(:reject) unless block_given?
dup.tap { |hash| hash.reject!(*args, &block) }
end
# Convert to a regular hash with string keys.
def to_hash
- _new_hash = Hash.new(default)
+ _new_hash = Hash.new
+ set_defaults(_new_hash)
+
each do |key, value|
_new_hash[key] = convert_value(value, for: :to_hash)
end
@@ -275,6 +279,14 @@ module ActiveSupport
value
end
end
+
+ def set_defaults(target)
+ if default_proc
+ target.default_proc = default_proc.dup
+ else
+ target.default = default
+ end
+ end
end
end
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index 95f3f6255a..6775eec34b 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -37,10 +37,12 @@ module I18n
enforce_available_locales = I18n.enforce_available_locales if enforce_available_locales.nil?
I18n.enforce_available_locales = false
+ reloadable_paths = []
app.config.i18n.each do |setting, value|
case setting
when :railties_load_path
- app.config.i18n.load_path.unshift(*value)
+ reloadable_paths = value
+ app.config.i18n.load_path.unshift(*value.map(&:existent).flatten)
when :load_path
I18n.load_path += value
else
@@ -53,7 +55,14 @@ module I18n
# Restore available locales check so it will take place from now on.
I18n.enforce_available_locales = enforce_available_locales
- reloader = ActiveSupport::FileUpdateChecker.new(I18n.load_path.dup){ I18n.reload! }
+ directories = watched_dirs_with_extensions(reloadable_paths)
+ reloader = ActiveSupport::FileUpdateChecker.new(I18n.load_path.dup, directories) do
+ I18n.load_path.keep_if { |p| File.exist?(p) }
+ I18n.load_path |= reloadable_paths.map(&:existent).flatten
+
+ I18n.reload!
+ end
+
app.reloaders << reloader
ActionDispatch::Reloader.to_prepare do
reloader.execute_if_updated
@@ -96,5 +105,11 @@ module I18n
raise "Unexpected fallback type #{fallbacks.inspect}"
end
end
+
+ def self.watched_dirs_with_extensions(paths)
+ paths.each_with_object({}) do |path, result|
+ result[path.absolute_current] = path.extensions
+ end
+ end
end
end
diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb
index 486838bd15..42560f3515 100644
--- a/activesupport/lib/active_support/inflector/inflections.rb
+++ b/activesupport/lib/active_support/inflector/inflections.rb
@@ -27,6 +27,37 @@ module ActiveSupport
class Inflections
@__instance__ = ThreadSafe::Cache.new
+ class Uncountables < Array
+ def initialize
+ @regex_array = []
+ super
+ end
+
+ def delete(entry)
+ super entry
+ @regex_array.delete(to_regex(entry))
+ end
+
+ def <<(*word)
+ add(word)
+ end
+
+ def add(words)
+ self.concat(words.flatten.map(&:downcase))
+ @regex_array += self.map {|word| to_regex(word) }
+ self
+ end
+
+ def uncountable?(str)
+ @regex_array.any? { |regex| regex === str }
+ end
+
+ private
+ def to_regex(string)
+ /\b#{::Regexp.escape(string)}\Z/i
+ end
+ end
+
def self.instance(locale = :en)
@__instance__[locale] ||= new
end
@@ -34,7 +65,7 @@ module ActiveSupport
attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex
def initialize
- @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], [], [], {}, /(?=a)b/
+ @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], Uncountables.new, [], {}, /(?=a)b/
end
# Private, for the test suite.
@@ -160,7 +191,7 @@ module ActiveSupport
# uncountable 'money', 'information'
# uncountable %w( money information rice )
def uncountable(*words)
- @uncountables += words.flatten.map(&:downcase)
+ @uncountables.add(words)
end
# Specifies a humanized form of a string by a regular expression rule or
@@ -185,7 +216,7 @@ module ActiveSupport
def clear(scope = :all)
case scope
when :all
- @plurals, @singulars, @uncountables, @humans = [], [], [], []
+ @plurals, @singulars, @uncountables, @humans = [], [], Uncountables.new, []
else
instance_variable_set "@#{scope}", []
end
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index a08c655d69..ab5372ac43 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -68,9 +68,9 @@ module ActiveSupport
def camelize(term, uppercase_first_letter = true)
string = term.to_s
if uppercase_first_letter
- string = string.sub(/^[a-z\d]*/) { inflections.acronyms[$&] || $&.capitalize }
+ string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize }
else
- string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { $&.downcase }
+ string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { |match| match.downcase }
end
string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }
string.gsub!('/'.freeze, '::'.freeze)
@@ -91,10 +91,10 @@ module ActiveSupport
def underscore(camel_cased_word)
return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/
word = camel_cased_word.to_s.gsub('::'.freeze, '/'.freeze)
- word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'}#{$2.downcase}" }
- word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
- word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
- word.tr!("-", "_")
+ word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'.freeze }#{$2.downcase}" }
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze)
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze)
+ word.tr!("-".freeze, "_".freeze)
word.downcase!
word
end
@@ -127,9 +127,9 @@ module ActiveSupport
inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
- result.sub!(/\A_+/, '')
- result.sub!(/_id\z/, '')
- result.tr!('_', ' ')
+ result.sub!(/\A_+/, ''.freeze)
+ result.sub!(/_id\z/, ''.freeze)
+ result.tr!('_'.freeze, ' '.freeze)
result.gsub!(/([a-z\d]*)/i) do |match|
"#{inflections.acronyms[match] || match.downcase}"
@@ -153,14 +153,14 @@ module ActiveSupport
# titleize('TheManWithoutAPast') # => "The Man Without A Past"
# titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark"
def titleize(word)
- humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { $&.capitalize }
+ humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { |match| match.capitalize }
end
# Creates the name of a table like Rails does for models to table names.
# This method uses the #pluralize method on the last word in the string.
#
# tableize('RawScaledScorer') # => "raw_scaled_scorers"
- # tableize('egg_and_ham') # => "egg_and_hams"
+ # tableize('ham_and_egg') # => "ham_and_eggs"
# tableize('fancyCategory') # => "fancy_categories"
def tableize(class_name)
pluralize(underscore(class_name))
@@ -170,7 +170,7 @@ module ActiveSupport
# names to models. Note that this returns a string and not a Class (To
# convert to an actual class follow +classify+ with #constantize).
#
- # classify('egg_and_hams') # => "EggAndHam"
+ # classify('ham_and_eggs') # => "HamAndEgg"
# classify('posts') # => "Post"
#
# Singular names are not handled correctly:
@@ -178,14 +178,14 @@ module ActiveSupport
# classify('calculus') # => "Calculu"
def classify(table_name)
# strip out any leading schema name
- camelize(singularize(table_name.to_s.sub(/.*\./, '')))
+ camelize(singularize(table_name.to_s.sub(/.*\./, ''.freeze)))
end
# Replaces underscores with dashes in the string.
#
# dasherize('puni_puni') # => "puni-puni"
def dasherize(underscored_word)
- underscored_word.tr('_', '-')
+ underscored_word.tr('_'.freeze, '-'.freeze)
end
# Removes the module part from the expression in the string.
@@ -231,8 +231,8 @@ module ActiveSupport
# Tries to find a constant with the name specified in the argument string.
#
- # 'Module'.constantize # => Module
- # 'Test::Unit'.constantize # => Test::Unit
+ # 'Module'.constantize # => Module
+ # 'Foo::Bar'.constantize # => Foo::Bar
#
# The name is assumed to be the one of a top-level constant, no matter
# whether it starts with "::" or not. No lexical context is taken into
@@ -248,7 +248,7 @@ module ActiveSupport
# NameError is raised when the name is not in CamelCase or the constant is
# unknown.
def constantize(camel_cased_word)
- names = camel_cased_word.split('::')
+ names = camel_cased_word.split('::'.freeze)
# Trigger a built-in NameError exception including the ill-formed constant in the message.
Object.const_get(camel_cased_word) if names.empty?
@@ -280,8 +280,8 @@ module ActiveSupport
# Tries to find a constant with the name specified in the argument string.
#
- # safe_constantize('Module') # => Module
- # safe_constantize('Test::Unit') # => Test::Unit
+ # safe_constantize('Module') # => Module
+ # safe_constantize('Foo::Bar') # => Foo::Bar
#
# The name is assumed to be the one of a top-level constant, no matter
# whether it starts with "::" or not. No lexical context is taken into
@@ -354,7 +354,7 @@ module ActiveSupport
# const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?"
# const_regexp("::") # => "::"
def const_regexp(camel_cased_word) #:nodoc:
- parts = camel_cased_word.split("::")
+ parts = camel_cased_word.split("::".freeze)
return Regexp.escape(camel_cased_word) if parts.blank?
@@ -372,7 +372,7 @@ module ActiveSupport
def apply_inflections(word, rules)
result = word.to_s.dup
- if word.empty? || inflections.uncountables.include?(result.downcase[/\b\w+\Z/])
+ if word.empty? || inflections.uncountables.uncountable?(result)
result
else
rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb
index edea142e82..7b28eeb6e2 100644
--- a/activesupport/lib/active_support/inflector/transliterate.rb
+++ b/activesupport/lib/active_support/inflector/transliterate.rb
@@ -58,7 +58,7 @@ module ActiveSupport
# I18n.locale = :de
# transliterate('Jürgen')
# # => "Juergen"
- def transliterate(string, replacement = "?")
+ def transliterate(string, replacement = "?".freeze)
I18n.transliterate(ActiveSupport::Multibyte::Unicode.normalize(
ActiveSupport::Multibyte::Unicode.tidy_bytes(string), :c),
:replacement => replacement)
@@ -75,13 +75,21 @@ module ActiveSupport
# Turn unwanted chars into the separator
parameterized_string.gsub!(/[^a-z0-9\-_]+/i, sep)
unless sep.nil? || sep.empty?
- re_sep = Regexp.escape(sep)
+ if sep == "-".freeze
+ re_duplicate_seperator = /-{2,}/
+ re_leading_trailing_separator = /^-|-$/i
+ else
+ re_sep = Regexp.escape(sep)
+ re_duplicate_seperator = /#{re_sep}{2,}/
+ re_leading_trailing_separator = /^#{re_sep}|#{re_sep}$/i
+ end
# No more than one of the separator in a row.
- parameterized_string.gsub!(/#{re_sep}{2,}/, sep)
+ parameterized_string.gsub!(re_duplicate_seperator, sep)
# Remove leading/trailing separator.
- parameterized_string.gsub!(/^#{re_sep}|#{re_sep}$/i, '')
+ parameterized_string.gsub!(re_leading_trailing_separator, ''.freeze)
end
- parameterized_string.downcase
+ parameterized_string.downcase!
+ parameterized_string
end
end
end
diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb
index 35548f3f56..2932954f03 100644
--- a/activesupport/lib/active_support/json/decoding.rb
+++ b/activesupport/lib/active_support/json/decoding.rb
@@ -9,20 +9,14 @@ module ActiveSupport
module JSON
# matches YAML-formatted dates
DATE_REGEX = /^(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[T \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?))$/
-
+
class << self
# Parses a JSON string (JavaScript Object Notation) into a hash.
# See http://www.json.org for more info.
#
# ActiveSupport::JSON.decode("{\"team\":\"rails\",\"players\":\"36\"}")
# => {"team" => "rails", "players" => "36"}
- def decode(json, options = {})
- if options.present?
- raise ArgumentError, "In Rails 4.1, ActiveSupport::JSON.decode no longer " \
- "accepts an options hash for MultiJSON. MultiJSON reached its end of life " \
- "and has been removed."
- end
-
+ def decode(json)
data = ::JSON.parse(json, quirks_mode: true)
if ActiveSupport.parse_json_times
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index 48f4967892..031c5e9339 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -57,6 +57,10 @@ module ActiveSupport
super.gsub ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, ESCAPED_CHARS
end
end
+
+ def to_s
+ self
+ end
end
# Mark these as private so we don't leak encoding-specific constructs
diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb
index 75f353f62c..70ac4a4d5c 100644
--- a/activesupport/lib/active_support/log_subscriber/test_helper.rb
+++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb
@@ -33,7 +33,7 @@ module ActiveSupport
# you can collect them doing @logger.logged(level), where level is the level
# used in logging, like info, debug, warn and so on.
module TestHelper
- def setup
+ def setup # :nodoc:
@logger = MockLogger.new
@notifier = ActiveSupport::Notifications::Fanout.new
@@ -44,7 +44,7 @@ module ActiveSupport
ActiveSupport::Notifications.notifier = @notifier
end
- def teardown
+ def teardown # :nodoc:
set_logger(nil)
ActiveSupport::Notifications.notifier = @old_notifier
end
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index 92ab6fe648..c82a13511e 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -82,7 +82,7 @@ module ActiveSupport
def _decrypt(encrypted_message)
cipher = new_cipher
- encrypted_data, iv = encrypted_message.split("--").map {|v| ::Base64.strict_decode64(v)}
+ encrypted_data, iv = encrypted_message.split("--".freeze).map {|v| ::Base64.strict_decode64(v)}
cipher.decrypt
cipher.key = @secret
diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb
index eee9bbaead..64c5232cf4 100644
--- a/activesupport/lib/active_support/message_verifier.rb
+++ b/activesupport/lib/active_support/message_verifier.rb
@@ -44,9 +44,9 @@ module ActiveSupport
# tampered_message = signed_message.chop # editing the message invalidates the signature
# verifier.valid_message?(tampered_message) # => false
def valid_message?(signed_message)
- return if signed_message.blank?
+ return if signed_message.nil? || !signed_message.valid_encoding? || signed_message.blank?
- data, digest = signed_message.split("--")
+ data, digest = signed_message.split("--".freeze)
data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data))
end
@@ -74,7 +74,7 @@ module ActiveSupport
def verified(signed_message)
if valid_message?(signed_message)
begin
- data = signed_message.split("--")[0]
+ data = signed_message.split("--".freeze)[0]
@serializer.load(decode(data))
rescue ArgumentError => argument_error
return if argument_error.message =~ %r{invalid base64}
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index 3c0cf9f137..45cf6fc1ef 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -86,8 +86,14 @@ module ActiveSupport #:nodoc:
@wrapped_string.split(*args).map { |i| self.class.new(i) }
end
- # Works like like <tt>String#slice!</tt>, but returns an instance of
+ # Works like <tt>String#slice!</tt>, but returns an instance of
# Chars, or nil if the string was not modified.
+ #
+ # string = 'Welcome'
+ # string.mb_chars.slice!(3) # => #<ActiveSupport::Multibyte::Chars:0x000000038109b8 @wrapped_string="c">
+ # string # => 'Welome'
+ # string.mb_chars.slice!(0..3) # => #<ActiveSupport::Multibyte::Chars:0x00000002eb80a0 @wrapped_string="Welo">
+ # string # => 'me'
def slice!(*args)
chars(@wrapped_string.slice!(*args))
end
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 35efebc65f..b6c991a287 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -58,7 +58,7 @@ module ActiveSupport
# 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*' }.join('|')
+ 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
@@ -273,7 +273,7 @@ module ActiveSupport
compose(reorder_characters(decompose(:compatibility, codepoints)))
else
raise ArgumentError, "#{form} is not a valid normalization variant", caller
- end.pack('U*')
+ end.pack('U*'.freeze)
end
def downcase(string)
@@ -338,7 +338,7 @@ module ActiveSupport
end
# Redefine the === method so we can write shorter rules for grapheme cluster breaks
- @boundary.each do |k,_|
+ @boundary.each_key do |k|
@boundary[k].instance_eval do
def ===(other)
detect { |i| i === other } ? true : false
diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb
index 6bf8c7d5de..0131fe2572 100644
--- a/activesupport/lib/active_support/notifications/fanout.rb
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -111,7 +111,7 @@ module ActiveSupport
end
end
- class Timed < Evented
+ class Timed < Evented # :nodoc:
def publish(name, *args)
@delegate.call name, *args
end
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index 258d9b34e1..38a9ce361d 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -220,8 +220,6 @@ module ActiveSupport
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
# insignificant zeros after the decimal separator (defaults to
# +true+)
- # * <tt>:prefix</tt> - If +:si+ formats the number using the SI
- # prefix (defaults to :binary)
#
# ==== Examples
#
diff --git a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
index cd5a2b3cbb..7986eb50f0 100644
--- a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
@@ -23,7 +23,7 @@ module ActiveSupport
end
def absolute_value(number)
- number.respond_to?("abs") ? number.abs : number.sub(/\A-/, '')
+ number.respond_to?(:abs) ? number.abs : number.sub(/\A-/, '')
end
def options
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 ac0d20b454..a4a8690bcd 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,6 +7,10 @@ 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
diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb
index a33e2c58a9..45864990ce 100644
--- a/activesupport/lib/active_support/ordered_options.rb
+++ b/activesupport/lib/active_support/ordered_options.rb
@@ -6,6 +6,7 @@ module ActiveSupport
# h[:girl] = 'Mary'
# h[:boy] # => 'John'
# h[:girl] # => 'Mary'
+ # h[:dog] # => nil
#
# Using +OrderedOptions+, the above code could be reduced to:
#
@@ -14,6 +15,13 @@ module ActiveSupport
# h.girl = 'Mary'
# h.boy # => 'John'
# h.girl # => 'Mary'
+ # h.dog # => nil
+ #
+ # To raise an exception when the value is blank, append a
+ # bang to the key name, like:
+ #
+ # h.dog! # => raises KeyError
+ #
class OrderedOptions < Hash
alias_method :_get, :[] # preserve the original #[] method
protected :_get # make it protected
@@ -31,7 +39,13 @@ module ActiveSupport
if name_string.chomp!('=')
self[name_string] = args.first
else
- self[name]
+ bangs = name_string.chomp!('!')
+
+ if bangs
+ fetch(name_string.to_sym).presence || raise(KeyError.new("#{name_string} is blank."))
+ else
+ self[name_string]
+ end
end
end
diff --git a/activesupport/lib/active_support/rails.rb b/activesupport/lib/active_support/rails.rb
index b05c3ff126..c8e3a4bf53 100644
--- a/activesupport/lib/active_support/rails.rb
+++ b/activesupport/lib/active_support/rails.rb
@@ -1,8 +1,8 @@
# This is private interface.
#
# Rails components cherry pick from Active Support as needed, but there are a
-# few features that are used for sure some way or another and it is not worth
-# to put individual requires absolutely everywhere. Think blank? for example.
+# few features that are used for sure in some way or another and it is not worth
+# putting individual requires absolutely everywhere. Think blank? for example.
#
# This file is loaded by every Rails component except Active Support itself,
# but it does not belong to the Rails public interface. It is internal to
diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb
index ef22433491..cd0fb51009 100644
--- a/activesupport/lib/active_support/railtie.rb
+++ b/activesupport/lib/active_support/railtie.rb
@@ -13,13 +13,6 @@ module ActiveSupport
end
end
- initializer "active_support.halt_callback_chains_on_return_false", after: :load_config_initializers do |app|
- if app.config.active_support.key? :halt_callback_chains_on_return_false
- ActiveSupport::Callbacks::CallbackChain.halt_and_display_warning_on_return_false = \
- app.config.active_support.halt_callback_chains_on_return_false
- end
- end
-
# Sets the default value for Time.zone
# If assigned value cannot be matched to a TimeZone, an exception will be raised.
initializer "active_support.initialize_time_zone" do |app|
diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb
index 67aac32742..fcf5553061 100644
--- a/activesupport/lib/active_support/rescuable.rb
+++ b/activesupport/lib/active_support/rescuable.rb
@@ -68,7 +68,7 @@ module ActiveSupport
raise ArgumentError, "#{klass} is neither an Exception nor a String"
end
- # put the new handler at the end because the list is read in reverse
+ # Put the new handler at the end because the list is read in reverse.
self.rescue_handlers += [[key, options[:with]]]
end
end
diff --git a/activesupport/lib/active_support/subscriber.rb b/activesupport/lib/active_support/subscriber.rb
index 8db423f0e9..1cd4b807ad 100644
--- a/activesupport/lib/active_support/subscriber.rb
+++ b/activesupport/lib/active_support/subscriber.rb
@@ -5,7 +5,7 @@ module ActiveSupport
# ActiveSupport::Notifications. The subscriber dispatches notifications to
# a registered object based on its given namespace.
#
- # An example would be Active Record subscriber responsible for collecting
+ # An example would be an Active Record subscriber responsible for collecting
# statistics about queries:
#
# module ActiveRecord
@@ -61,7 +61,7 @@ module ActiveSupport
pattern = "#{event}.#{namespace}"
- # don't add multiple subscribers (eg. if methods are redefined)
+ # Don't add multiple subscribers (eg. if methods are redefined).
return if subscriber.patterns.include?(pattern)
subscriber.patterns << pattern
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index 24b8f4b9f9..ae6f00b861 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -9,6 +9,7 @@ require 'active_support/testing/isolation'
require 'active_support/testing/constant_lookup'
require 'active_support/testing/time_helpers'
require 'active_support/testing/file_fixtures'
+require 'active_support/testing/composite_filter'
require 'active_support/core_ext/kernel/reporting'
module ActiveSupport
@@ -36,14 +37,16 @@ module ActiveSupport
# Possible values are +:random+, +:parallel+, +:alpha+, +:sorted+.
# Defaults to +:random+.
def test_order
- test_order = ActiveSupport.test_order
+ ActiveSupport.test_order ||= :random
+ end
- if test_order.nil?
- test_order = :random
- self.test_order = test_order
+ def run(reporter, options = {})
+ if options[:patterns] && options[:patterns].any? { |p| p =~ /:\d+/ }
+ options[:filter] = \
+ Testing::CompositeFilter.new(self, options[:filter], options[:patterns])
end
- test_order
+ super
end
end
diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb
index 8b649c193f..d87ce3474d 100644
--- a/activesupport/lib/active_support/testing/assertions.rb
+++ b/activesupport/lib/active_support/testing/assertions.rb
@@ -23,42 +23,42 @@ module ActiveSupport
# result of what is evaluated in the yielded block.
#
# assert_difference 'Article.count' do
- # post :create, article: {...}
+ # post :create, params: { article: {...} }
# end
#
# An arbitrary expression is passed in and evaluated.
#
- # assert_difference 'assigns(:article).comments(:reload).size' do
- # post :create, comment: {...}
+ # assert_difference 'Article.last.comments(:reload).size' do
+ # post :create, params: { comment: {...} }
# end
#
# An arbitrary positive or negative difference can be specified.
# The default is <tt>1</tt>.
#
# assert_difference 'Article.count', -1 do
- # post :delete, id: ...
+ # post :delete, params: { id: ... }
# end
#
# An array of expressions can also be passed in and evaluated.
#
# assert_difference [ 'Article.count', 'Post.count' ], 2 do
- # post :create, article: {...}
+ # post :create, params: { article: {...} }
# end
#
# A lambda or a list of lambdas can be passed in and evaluated:
#
# assert_difference ->{ Article.count }, 2 do
- # post :create, article: {...}
+ # post :create, params: { article: {...} }
# end
#
# assert_difference [->{ Article.count }, ->{ Post.count }], 2 do
- # post :create, article: {...}
+ # post :create, params: { article: {...} }
# end
#
# An error message can be specified.
#
# assert_difference 'Article.count', -1, 'An Article should be destroyed' do
- # post :delete, id: ...
+ # post :delete, params: { id: ... }
# end
def assert_difference(expression, difference = 1, message = nil, &block)
expressions = Array(expression)
@@ -81,13 +81,13 @@ module ActiveSupport
# changed before and after invoking the passed in block.
#
# assert_no_difference 'Article.count' do
- # post :create, article: invalid_attributes
+ # post :create, params: { article: invalid_attributes }
# end
#
# An error message can be specified.
#
# assert_no_difference 'Article.count', 'An Article should not be created' do
- # post :create, article: invalid_attributes
+ # post :create, params: { article: invalid_attributes }
# end
def assert_no_difference(expression, message = nil, &block)
assert_difference expression, 0, message, &block
diff --git a/activesupport/lib/active_support/testing/autorun.rb b/activesupport/lib/active_support/testing/autorun.rb
index 5aa5f46310..84c6b89340 100644
--- a/activesupport/lib/active_support/testing/autorun.rb
+++ b/activesupport/lib/active_support/testing/autorun.rb
@@ -2,4 +2,11 @@ gem 'minitest'
require 'minitest'
-Minitest.autorun
+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
+end
diff --git a/activesupport/lib/active_support/testing/composite_filter.rb b/activesupport/lib/active_support/testing/composite_filter.rb
new file mode 100644
index 0000000000..bde723e30b
--- /dev/null
+++ b/activesupport/lib/active_support/testing/composite_filter.rb
@@ -0,0 +1,54 @@
+require 'method_source'
+
+module ActiveSupport
+ module Testing
+ class CompositeFilter # :nodoc:
+ def initialize(runnable, filter, patterns)
+ @runnable = runnable
+ @filters = [ derive_regexp(filter), *derive_line_filters(patterns) ].compact
+ end
+
+ def ===(method)
+ @filters.any? { |filter| filter === method }
+ end
+
+ private
+ def derive_regexp(filter)
+ filter =~ %r%/(.*)/% ? Regexp.new($1) : filter
+ end
+
+ def derive_line_filters(patterns)
+ patterns.map do |file_and_line|
+ file, line = file_and_line.split(':')
+ Filter.new(@runnable, file, line) if file
+ end
+ end
+
+ class Filter # :nodoc:
+ def initialize(runnable, file, line)
+ @runnable, @file = runnable, File.expand_path(file)
+ @line = line.to_i if line
+ end
+
+ def ===(method)
+ return unless @runnable.method_defined?(method)
+
+ if @line
+ test_file, test_range = definition_for(@runnable.instance_method(method))
+ test_file == @file && test_range.include?(@line)
+ else
+ @runnable.instance_method(method).source_location.first == @file
+ end
+ end
+
+ private
+ def definition_for(method)
+ file, start_line = method.source_location
+ end_line = method.source.count("\n") + start_line - 1
+
+ return file, start_line..end_line
+ end
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb
index 247df7423b..1de0a19998 100644
--- a/activesupport/lib/active_support/testing/isolation.rb
+++ b/activesupport/lib/active_support/testing/isolation.rb
@@ -69,17 +69,17 @@ module ActiveSupport
else
Tempfile.open("isolation") do |tmpfile|
env = {
- ISOLATION_TEST: self.class.name,
- ISOLATION_OUTPUT: tmpfile.path
+ 'ISOLATION_TEST' => self.class.name,
+ '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}"
+ 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])
+ child = IO.popen(env, command)
begin
Process.wait(child.pid)
diff --git a/activesupport/lib/active_support/testing/method_call_assertions.rb b/activesupport/lib/active_support/testing/method_call_assertions.rb
new file mode 100644
index 0000000000..517f02e3ef
--- /dev/null
+++ b/activesupport/lib/active_support/testing/method_call_assertions.rb
@@ -0,0 +1,39 @@
+module ActiveSupport
+ module Testing
+ module MethodCallAssertions # :nodoc:
+ private
+ def assert_called(object, method_name, message = nil, times: 1)
+ times_called = 0
+
+ object.stub(method_name, -> { times_called += 1 }) { yield }
+
+ error = "Expected #{method_name} to be called #{times} times, " \
+ "but was called #{times_called} times"
+ error = "#{message}.\n#{error}" if message
+ assert_equal times, times_called, error
+ end
+
+ def assert_called_with(object, method_name, args = [], returns: nil)
+ mock = Minitest::Mock.new
+
+ if args.all? { |arg| arg.is_a?(Array) }
+ args.each { |arg| mock.expect(:call, returns, arg) }
+ else
+ mock.expect(:call, returns, args)
+ end
+
+ object.stub(method_name, mock) { yield }
+
+ mock.verify
+ end
+
+ def assert_not_called(object, method_name, message = nil, &block)
+ assert_called(object, method_name, message, times: 0, &block)
+ end
+
+ def stub_any_instance(klass, instance: klass.new)
+ klass.stub(:new, instance) { yield instance }
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb
index 3478b09423..fca0947c5b 100644
--- a/activesupport/lib/active_support/testing/time_helpers.rb
+++ b/activesupport/lib/active_support/testing/time_helpers.rb
@@ -39,7 +39,7 @@ module ActiveSupport
end
end
- # Contain helpers that help you test passage of time.
+ # Contains helpers that help you test passage of time.
module TimeHelpers
# Changes current time to the time in the future or in the past by a given time difference by
# stubbing +Time.now+, +Date.today+, and +DateTime.now+.
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index c28de4e21c..f8f1b9ac2c 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -1,3 +1,4 @@
+require 'active_support/duration'
require 'active_support/values/time_zone'
require 'active_support/core_ext/object/acts_like'
@@ -169,12 +170,13 @@ module ActiveSupport
end
end
- def encode_with(coder)
- if coder.respond_to?(:represent_object)
- coder.represent_object(nil, utc)
- else
- coder.represent_scalar(nil, utc.strftime("%Y-%m-%d %H:%M:%S.%9NZ"))
- end
+ def init_with(coder) #:nodoc:
+ initialize(coder['utc'], coder['zone'], coder['time'])
+ end
+
+ def encode_with(coder) #:nodoc:
+ coder.tag = '!ruby/object:ActiveSupport::TimeWithZone'
+ coder.map = { 'utc' => utc, 'zone' => time_zone, 'time' => time }
end
# Returns a string of the object's date and time in the format used by
@@ -245,7 +247,7 @@ module ActiveSupport
end
def eql?(other)
- utc.eql?(other)
+ other.eql?(utc)
end
def hash
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index da39f0d245..2699a064d7 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -348,24 +348,31 @@ module ActiveSupport
#
# Time.zone.parse('Mar 2000') # => Wed, 01 Mar 2000 00:00:00 HST -10:00
def parse(str, now=now())
- parts = Date._parse(str, false)
- 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]
- TimeWithZone.new(time.utc, self)
- else
- TimeWithZone.new(nil, self, time)
- end
+ parts_to_time(Date._parse(str, false), now)
+ end
+
+ # Parses +str+ according to +format+ and returns an ActiveSupport::TimeWithZone.
+ #
+ # Assumes that +str+ is a time in the time zone +self+,
+ # unless +format+ includes an explicit time zone.
+ # (This is the same behavior as +parse+.)
+ # In either case, the returned TimeWithZone has the timezone of +self+.
+ #
+ # Time.zone = 'Hawaii' # => "Hawaii"
+ # Time.zone.strptime('1999-12-31 14:00:00', '%Y-%m-%d %H:%M:%S') # => Fri, 31 Dec 1999 14:00:00 HST -10:00
+ #
+ # If upper components are missing from the string, they are supplied from
+ # TimeZone#now:
+ #
+ # Time.zone.now # => Fri, 31 Dec 1999 14:00:00 HST -10:00
+ # Time.zone.strptime('22:30:00', '%H:%M:%S') # => Fri, 31 Dec 1999 22:30:00 HST -10:00
+ #
+ # However, if the date component is not provided, but any other upper
+ # 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())
+ parts_to_time(DateTime._strptime(str, format), now)
end
# Returns an ActiveSupport::TimeWithZone instance representing the current
@@ -421,7 +428,36 @@ module ActiveSupport
tzinfo.periods_for_local(time)
end
+ def init_with(coder) #:nodoc:
+ initialize(coder['name'])
+ end
+
+ def encode_with(coder) #:nodoc:
+ coder.tag ="!ruby/object:#{self.class}"
+ coder.map = { 'name' => tzinfo.name }
+ end
+
private
+ def parts_to_time(parts, now)
+ 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]
+ TimeWithZone.new(time.utc, self)
+ else
+ TimeWithZone.new(nil, self, time)
+ end
+ end
+
def time_now
Time.now
end
diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb
index 009ee4db90..df7b081993 100644
--- a/activesupport/lib/active_support/xml_mini.rb
+++ b/activesupport/lib/active_support/xml_mini.rb
@@ -78,6 +78,9 @@ module ActiveSupport
)
end
+ attr_accessor :depth
+ self.depth = 100
+
delegate :parse, :to => :backend
def backend
diff --git a/activesupport/lib/active_support/xml_mini/jdom.rb b/activesupport/lib/active_support/xml_mini/jdom.rb
index f303daa1a7..94751bbc04 100644
--- a/activesupport/lib/active_support/xml_mini/jdom.rb
+++ b/activesupport/lib/active_support/xml_mini/jdom.rb
@@ -46,7 +46,7 @@ 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)
+ merge_element!({CONTENT_KEY => ''}, doc.document_element, XmlMini.depth)
end
end
@@ -58,9 +58,10 @@ module ActiveSupport
# Hash to merge the converted element into.
# element::
# XML element to merge into hash
- def merge_element!(hash, element)
+ def merge_element!(hash, element, depth)
+ raise 'Document too deep!' if depth == 0
delete_empty(hash)
- merge!(hash, element.tag_name, collapse(element))
+ merge!(hash, element.tag_name, collapse(element, depth))
end
def delete_empty(hash)
@@ -71,14 +72,14 @@ module ActiveSupport
#
# element::
# The document element to be collapsed.
- def collapse(element)
+ def collapse(element, depth)
hash = get_attributes(element)
child_nodes = element.child_nodes
if child_nodes.length > 0
(0...child_nodes.length).each do |i|
child = child_nodes.item(i)
- merge_element!(hash, child) unless child.node_type == Node.TEXT_NODE
+ merge_element!(hash, child, depth - 1) unless child.node_type == Node.TEXT_NODE
end
merge_texts!(hash, element) unless empty_content?(element)
hash
diff --git a/activesupport/lib/active_support/xml_mini/rexml.rb b/activesupport/lib/active_support/xml_mini/rexml.rb
index 5c7c78bf70..924ed72345 100644
--- a/activesupport/lib/active_support/xml_mini/rexml.rb
+++ b/activesupport/lib/active_support/xml_mini/rexml.rb
@@ -29,7 +29,7 @@ module ActiveSupport
doc = REXML::Document.new(data)
if doc.root
- merge_element!({}, doc.root)
+ merge_element!({}, doc.root, XmlMini.depth)
else
raise REXML::ParseException,
"The document #{doc.to_s.inspect} does not have a valid root"
@@ -44,19 +44,20 @@ module ActiveSupport
# Hash to merge the converted element into.
# element::
# XML element to merge into hash
- def merge_element!(hash, element)
- merge!(hash, element.name, collapse(element))
+ def merge_element!(hash, element, depth)
+ raise REXML::ParseException, "The document is too deep" if depth == 0
+ merge!(hash, element.name, collapse(element, depth))
end
# Actually converts an XML document element into a data structure.
#
# element::
# The document element to be collapsed.
- def collapse(element)
+ def collapse(element, depth)
hash = get_attributes(element)
if element.has_elements?
- element.each_element {|child| merge_element!(hash, child) }
+ element.each_element {|child| merge_element!(hash, child, depth - 1) }
merge_texts!(hash, element) unless empty_content?(element)
hash
else
diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb
index 7ffcae6007..65a8edbabb 100644
--- a/activesupport/test/abstract_unit.rb
+++ b/activesupport/test/abstract_unit.rb
@@ -15,6 +15,7 @@ silence_warnings do
end
require 'active_support/testing/autorun'
+require 'active_support/testing/method_call_assertions'
ENV['NO_RELOAD'] = '1'
require 'active_support'
@@ -37,4 +38,8 @@ def jruby_skip(message = '')
skip message if defined?(JRUBY_VERSION)
end
-require 'mocha/setup' # FIXME: stop using mocha
+require 'minitest/mock'
+
+class ActiveSupport::TestCase
+ include ActiveSupport::Testing::MethodCallAssertions
+end
diff --git a/activesupport/test/autoloading_fixtures/a/c/e/f.rb b/activesupport/test/autoloading_fixtures/a/c/e/f.rb
deleted file mode 100644
index 57dba5a307..0000000000
--- a/activesupport/test/autoloading_fixtures/a/c/e/f.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-class A::C::E::F
-end \ No newline at end of file
diff --git a/activesupport/test/autoloading_fixtures/a/c/em/f.rb b/activesupport/test/autoloading_fixtures/a/c/em/f.rb
new file mode 100644
index 0000000000..8b28e19148
--- /dev/null
+++ b/activesupport/test/autoloading_fixtures/a/c/em/f.rb
@@ -0,0 +1,2 @@
+class A::C::EM::F
+end \ No newline at end of file
diff --git a/activesupport/test/autoloading_fixtures/d.rb b/activesupport/test/autoloading_fixtures/d.rb
new file mode 100644
index 0000000000..45c794d4ca
--- /dev/null
+++ b/activesupport/test/autoloading_fixtures/d.rb
@@ -0,0 +1,2 @@
+class D
+end \ No newline at end of file
diff --git a/activesupport/test/autoloading_fixtures/e.rb b/activesupport/test/autoloading_fixtures/e.rb
deleted file mode 100644
index 2f59e4fb75..0000000000
--- a/activesupport/test/autoloading_fixtures/e.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-class E
-end \ No newline at end of file
diff --git a/activesupport/test/autoloading_fixtures/em.rb b/activesupport/test/autoloading_fixtures/em.rb
new file mode 100644
index 0000000000..16a1838667
--- /dev/null
+++ b/activesupport/test/autoloading_fixtures/em.rb
@@ -0,0 +1,2 @@
+class EM
+end \ No newline at end of file
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index 4953550c45..74ceff44f9 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -133,37 +133,42 @@ class CacheStoreSettingTest < ActiveSupport::TestCase
end
def test_mem_cache_fragment_cache_store
- Dalli::Client.expects(:new).with(%w[localhost], {})
- store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost"
- assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ assert_called_with(Dalli::Client, :new, [%w[localhost], {}]) do
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost"
+ assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ end
end
def test_mem_cache_fragment_cache_store_with_given_mem_cache
mem_cache = Dalli::Client.new
- Dalli::Client.expects(:new).never
- store = ActiveSupport::Cache.lookup_store :mem_cache_store, mem_cache
- assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ assert_not_called(Dalli::Client, :new) do
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store, mem_cache
+ assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ end
end
def test_mem_cache_fragment_cache_store_with_not_dalli_client
- Dalli::Client.expects(:new).never
- memcache = Object.new
- assert_raises(ArgumentError) do
- ActiveSupport::Cache.lookup_store :mem_cache_store, memcache
+ assert_not_called(Dalli::Client, :new) do
+ memcache = Object.new
+ assert_raises(ArgumentError) do
+ ActiveSupport::Cache.lookup_store :mem_cache_store, memcache
+ end
end
end
def test_mem_cache_fragment_cache_store_with_multiple_servers
- Dalli::Client.expects(:new).with(%w[localhost 192.168.1.1], {})
- store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", '192.168.1.1'
- assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ assert_called_with(Dalli::Client, :new, [%w[localhost 192.168.1.1], {}]) do
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", '192.168.1.1'
+ assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ end
end
def test_mem_cache_fragment_cache_store_with_options
- Dalli::Client.expects(:new).with(%w[localhost 192.168.1.1], { :timeout => 10 })
- store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", '192.168.1.1', :namespace => 'foo', :timeout => 10
- assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
- assert_equal 'foo', store.options[:namespace]
+ assert_called_with(Dalli::Client, :new, [%w[localhost 192.168.1.1], { :timeout => 10 }]) do
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", '192.168.1.1', :namespace => 'foo', :timeout => 10
+ assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ assert_equal 'foo', store.options[:namespace]
+ end
end
def test_object_assigned_fragment_cache_store
@@ -224,13 +229,15 @@ module CacheStoreBehavior
def test_fetch_without_cache_miss
@cache.write('foo', 'bar')
- @cache.expects(:write).never
- assert_equal 'bar', @cache.fetch('foo') { 'baz' }
+ assert_not_called(@cache, :write) do
+ assert_equal 'bar', @cache.fetch('foo') { 'baz' }
+ end
end
def test_fetch_with_cache_miss
- @cache.expects(:write).with('foo', 'baz', @cache.options)
- assert_equal 'baz', @cache.fetch('foo') { 'baz' }
+ assert_called_with(@cache, :write, ['foo', 'baz', @cache.options]) do
+ assert_equal 'baz', @cache.fetch('foo') { 'baz' }
+ end
end
def test_fetch_with_cache_miss_passes_key_to_block
@@ -245,15 +252,18 @@ module CacheStoreBehavior
def test_fetch_with_forced_cache_miss
@cache.write('foo', 'bar')
- @cache.expects(:read).never
- @cache.expects(:write).with('foo', 'bar', @cache.options.merge(:force => true))
- @cache.fetch('foo', :force => true) { 'bar' }
+ assert_not_called(@cache, :read) do
+ assert_called_with(@cache, :write, ['foo', 'bar', @cache.options.merge(:force => true)]) do
+ @cache.fetch('foo', :force => true) { 'bar' }
+ end
+ end
end
def test_fetch_with_cached_nil
@cache.write('foo', nil)
- @cache.expects(:write).never
- assert_nil @cache.fetch('foo') { 'baz' }
+ assert_not_called(@cache, :write) do
+ assert_nil @cache.fetch('foo') { 'baz' }
+ end
end
def test_should_read_and_write_hash
@@ -288,8 +298,9 @@ module CacheStoreBehavior
@cache.write('foo', 'bar', :expires_in => 10)
@cache.write('fu', 'baz')
@cache.write('fud', 'biz')
- Time.stubs(:now).returns(time + 11)
- assert_equal({"fu" => "baz"}, @cache.read_multi('foo', 'fu'))
+ Time.stub(:now, time + 11) do
+ assert_equal({"fu" => "baz"}, @cache.read_multi('foo', 'fu'))
+ end
end
def test_fetch_multi
@@ -303,8 +314,9 @@ module CacheStoreBehavior
end
def test_multi_with_objects
- foo = stub(:title => 'FOO!', :cache_key => 'foo')
- bar = stub(:cache_key => 'bar')
+ cache_struct = Struct.new(:cache_key, :title)
+ foo = cache_struct.new('foo', 'FOO!')
+ bar = cache_struct.new('bar')
@cache.write('bar', 'BAM!')
@@ -387,66 +399,74 @@ module CacheStoreBehavior
def test_expires_in
time = Time.local(2008, 4, 24)
- Time.stubs(:now).returns(time)
- @cache.write('foo', 'bar')
- assert_equal 'bar', @cache.read('foo')
+ Time.stub(:now, time) do
+ @cache.write('foo', 'bar')
+ assert_equal 'bar', @cache.read('foo')
+ end
- Time.stubs(:now).returns(time + 30)
- assert_equal 'bar', @cache.read('foo')
+ Time.stub(:now, time + 30) do
+ assert_equal 'bar', @cache.read('foo')
+ end
- Time.stubs(:now).returns(time + 61)
- assert_nil @cache.read('foo')
+ Time.stub(:now, time + 61) do
+ assert_nil @cache.read('foo')
+ end
end
def test_race_condition_protection_skipped_if_not_defined
@cache.write('foo', 'bar')
time = @cache.send(:read_entry, 'foo', {}).expires_at
- Time.stubs(:now).returns(Time.at(time))
- result = @cache.fetch('foo') do
- assert_equal nil, @cache.read('foo')
- 'baz'
+ Time.stub(:now, Time.at(time)) do
+ result = @cache.fetch('foo') do
+ assert_equal nil, @cache.read('foo')
+ 'baz'
+ end
+ assert_equal 'baz', result
end
- assert_equal 'baz', result
end
def test_race_condition_protection_is_limited
time = Time.now
@cache.write('foo', 'bar', :expires_in => 60)
- Time.stubs(:now).returns(time + 71)
- result = @cache.fetch('foo', :race_condition_ttl => 10) do
- assert_equal nil, @cache.read('foo')
- "baz"
+ Time.stub(:now, time + 71) do
+ result = @cache.fetch('foo', :race_condition_ttl => 10) do
+ assert_equal nil, @cache.read('foo')
+ "baz"
+ end
+ assert_equal "baz", result
end
- assert_equal "baz", result
end
def test_race_condition_protection_is_safe
time = Time.now
@cache.write('foo', 'bar', :expires_in => 60)
- Time.stubs(:now).returns(time + 61)
- begin
- @cache.fetch('foo', :race_condition_ttl => 10) do
- assert_equal 'bar', @cache.read('foo')
- raise ArgumentError.new
+ Time.stub(:now, time + 61) do
+ begin
+ @cache.fetch('foo', :race_condition_ttl => 10) do
+ assert_equal 'bar', @cache.read('foo')
+ raise ArgumentError.new
+ end
+ rescue ArgumentError
end
- rescue ArgumentError
+ assert_equal "bar", @cache.read('foo')
+ end
+ Time.stub(:now, time + 91) do
+ assert_nil @cache.read('foo')
end
- assert_equal "bar", @cache.read('foo')
- Time.stubs(:now).returns(time + 91)
- assert_nil @cache.read('foo')
end
def test_race_condition_protection
time = Time.now
@cache.write('foo', 'bar', :expires_in => 60)
- Time.stubs(:now).returns(time + 61)
- result = @cache.fetch('foo', :race_condition_ttl => 10) do
- assert_equal 'bar', @cache.read('foo')
- "baz"
+ Time.stub(:now, time + 61) do
+ result = @cache.fetch('foo', :race_condition_ttl => 10) do
+ assert_equal 'bar', @cache.read('foo')
+ "baz"
+ end
+ assert_equal "baz", result
end
- assert_equal "baz", result
end
def test_crazy_key_characters
@@ -636,37 +656,37 @@ module AutoloadingCacheBehavior
include DependenciesTestHelpers
def test_simple_autoloading
with_autoloading_fixtures do
- @cache.write('foo', E.new)
+ @cache.write('foo', EM.new)
end
- remove_constants(:E)
+ remove_constants(:EM)
ActiveSupport::Dependencies.clear
with_autoloading_fixtures do
- assert_kind_of E, @cache.read('foo')
+ assert_kind_of EM, @cache.read('foo')
end
- remove_constants(:E)
+ remove_constants(:EM)
ActiveSupport::Dependencies.clear
end
def test_two_classes_autoloading
with_autoloading_fixtures do
- @cache.write('foo', [E.new, ClassFolder.new])
+ @cache.write('foo', [EM.new, ClassFolder.new])
end
- remove_constants(:E, :ClassFolder)
+ remove_constants(:EM, :ClassFolder)
ActiveSupport::Dependencies.clear
with_autoloading_fixtures do
loaded = @cache.read('foo')
assert_kind_of Array, loaded
assert_equal 2, loaded.size
- assert_kind_of E, loaded[0]
+ assert_kind_of EM, loaded[0]
assert_kind_of ClassFolder, loaded[1]
end
- remove_constants(:E, :ClassFolder)
+ remove_constants(:EM, :ClassFolder)
ActiveSupport::Dependencies.clear
end
end
@@ -684,6 +704,7 @@ class FileStoreTest < ActiveSupport::TestCase
def teardown
FileUtils.rm_r(cache_dir)
+ rescue Errno::ENOENT
end
def cache_dir
@@ -703,6 +724,11 @@ class FileStoreTest < ActiveSupport::TestCase
assert File.exist?(filepath)
end
+ def test_clear_without_cache_dir
+ FileUtils.rm_r(cache_dir)
+ @cache.clear
+ end
+
def test_long_keys
@cache.write("a"*10000, 1)
assert_equal 1, @cache.read("a"*10000)
@@ -759,9 +785,10 @@ class FileStoreTest < ActiveSupport::TestCase
end
def test_log_exception_when_cache_read_fails
- File.expects(:exist?).raises(StandardError, "failed")
- @cache.send(:read_entry, "winston", {})
- assert @buffer.string.present?
+ File.stub(:exist?, -> { raise StandardError.new("failed") }) do
+ @cache.send(:read_entry, "winston", {})
+ assert @buffer.string.present?
+ end
end
def test_cleanup_removes_all_expired_entries
@@ -769,11 +796,12 @@ class FileStoreTest < ActiveSupport::TestCase
@cache.write('foo', 'bar', expires_in: 10)
@cache.write('baz', 'qux')
@cache.write('quux', 'corge', expires_in: 20)
- Time.stubs(:now).returns(time + 15)
- @cache.cleanup
- assert_not @cache.exist?('foo')
- assert @cache.exist?('baz')
- assert @cache.exist?('quux')
+ Time.stub(:now, time + 15) do
+ @cache.cleanup
+ assert_not @cache.exist?('foo')
+ assert @cache.exist?('baz')
+ assert @cache.exist?('quux')
+ end
end
def test_write_with_unless_exist
@@ -1010,10 +1038,6 @@ class NullStoreTest < ActiveSupport::TestCase
end
assert_nil @cache.read("name")
end
-
- def test_setting_nil_cache_store
- assert ActiveSupport::Cache.lookup_store.class.name, ActiveSupport::Cache::NullStore.name
- end
end
class CacheStoreLoggerTest < ActiveSupport::TestCase
@@ -1050,9 +1074,9 @@ class CacheEntryTest < ActiveSupport::TestCase
assert !entry.expired?, 'entry not expired'
entry = ActiveSupport::Cache::Entry.new("value", :expires_in => 60)
assert !entry.expired?, 'entry not expired'
- time = Time.now + 61
- Time.stubs(:now).returns(time)
- assert entry.expired?, 'entry is expired'
+ Time.stub(:now, Time.now + 61) do
+ assert entry.expired?, 'entry is expired'
+ end
end
def test_compress_values
diff --git a/activesupport/test/concern_test.rb b/activesupport/test/concern_test.rb
index 253c1adc23..8ea701cfb7 100644
--- a/activesupport/test/concern_test.rb
+++ b/activesupport/test/concern_test.rb
@@ -54,6 +54,11 @@ class ConcernTest < ActiveSupport::TestCase
include Bar, Baz
end
+ module Qux
+ module ClassMethods
+ end
+ end
+
def setup
@klass = Class.new
end
@@ -70,6 +75,26 @@ class ConcernTest < ActiveSupport::TestCase
assert_equal ConcernTest::Baz::ClassMethods, (class << @klass; self.included_modules; end)[0]
end
+ def test_class_methods_are_extended_only_on_expected_objects
+ ::Object.__send__(:include, Qux)
+ Object.extend(Qux::ClassMethods)
+ # module needs to be created after Qux is included in Object or bug won't
+ # be triggered
+ test_module = Module.new do
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def test
+ end
+ end
+ end
+ @klass.include test_module
+ assert_equal false, Object.respond_to?(:test)
+ Qux.class_eval do
+ remove_const :ClassMethods
+ end
+ end
+
def test_included_block_is_ran
@klass.include(Baz)
assert_equal true, @klass.included_ran
diff --git a/activesupport/test/core_ext/class/delegating_attributes_test.rb b/activesupport/test/core_ext/class/delegating_attributes_test.rb
deleted file mode 100644
index 447b1d10ad..0000000000
--- a/activesupport/test/core_ext/class/delegating_attributes_test.rb
+++ /dev/null
@@ -1,122 +0,0 @@
-require 'abstract_unit'
-require 'active_support/core_ext/class/delegating_attributes'
-
-module DelegatingFixtures
- class Parent
- end
-
- class Child < Parent
- ActiveSupport::Deprecation.silence do
- superclass_delegating_accessor :some_attribute
- end
- end
-
- class Mokopuna < Child
- end
-
- class PercysMom
- ActiveSupport::Deprecation.silence do
- superclass_delegating_accessor :superpower
- end
- end
-
- class Percy < PercysMom
- end
-end
-
-class DelegatingAttributesTest < ActiveSupport::TestCase
- include DelegatingFixtures
- attr_reader :single_class
-
- def setup
- @single_class = Class.new(Object)
- end
-
- def test_simple_accessor_declaration
- assert_deprecated do
- single_class.superclass_delegating_accessor :both
- end
-
- # Class should have accessor and mutator
- # the instance should have an accessor only
- assert_respond_to single_class, :both
- assert_respond_to single_class, :both=
- assert single_class.public_instance_methods.map(&:to_s).include?("both")
- assert !single_class.public_instance_methods.map(&:to_s).include?("both=")
- end
-
- def test_simple_accessor_declaration_with_instance_reader_false
- _instance_methods = single_class.public_instance_methods
-
- assert_deprecated do
- single_class.superclass_delegating_accessor :no_instance_reader, :instance_reader => false
- end
-
- assert_respond_to single_class, :no_instance_reader
- assert_respond_to single_class, :no_instance_reader=
- assert !_instance_methods.include?(:no_instance_reader)
- assert !_instance_methods.include?(:no_instance_reader?)
- assert !_instance_methods.include?(:_no_instance_reader)
- end
-
- def test_working_with_simple_attributes
- assert_deprecated do
- single_class.superclass_delegating_accessor :both
- end
-
- single_class.both = "HMMM"
-
- assert_equal "HMMM", single_class.both
- assert_equal true, single_class.both?
-
- assert_equal "HMMM", single_class.new.both
- assert_equal true, single_class.new.both?
-
- single_class.both = false
- assert_equal false, single_class.both?
- end
-
- def test_child_class_delegates_to_parent_but_can_be_overridden
- parent = Class.new
-
- assert_deprecated do
- parent.superclass_delegating_accessor :both
- end
-
- child = Class.new(parent)
- parent.both = "1"
- assert_equal "1", child.both
-
- child.both = "2"
- assert_equal "1", parent.both
- assert_equal "2", child.both
-
- parent.both = "3"
- assert_equal "3", parent.both
- assert_equal "2", child.both
- end
-
- def test_delegation_stops_at_the_right_level
- assert_nil Percy.superpower
- assert_nil PercysMom.superpower
-
- PercysMom.superpower = :heatvision
- assert_equal :heatvision, Percy.superpower
- end
-
- def test_delegation_stops_for_nil
- Mokopuna.some_attribute = nil
- Child.some_attribute="1"
-
- assert_equal "1", Child.some_attribute
- assert_nil Mokopuna.some_attribute
- ensure
- Child.some_attribute=nil
- end
-
- def test_deprecation_warning
- assert_deprecated(/superclass_delegating_accessor is deprecated/) do
- single_class.superclass_delegating_accessor :test_attribute
- end
- end
-end
diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb
index e89be25b53..0fc3f765f5 100644
--- a/activesupport/test/core_ext/date_ext_test.rb
+++ b/activesupport/test/core_ext/date_ext_test.rb
@@ -191,8 +191,9 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
def test_yesterday_constructor_when_zone_is_set
with_env_tz 'UTC' do
with_tz_default ActiveSupport::TimeZone['Eastern Time (US & Canada)'] do # UTC -5
- Time.stubs(:now).returns Time.local(2000, 1, 1)
- assert_equal Date.new(1999, 12, 30), Date.yesterday
+ Time.stub(:now, Time.local(2000, 1, 1)) do
+ assert_equal Date.new(1999, 12, 30), Date.yesterday
+ end
end
end
end
@@ -212,8 +213,9 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
def test_tomorrow_constructor_when_zone_is_set
with_env_tz 'UTC' do
with_tz_default ActiveSupport::TimeZone['Europe/Paris'] do # UTC +1
- Time.stubs(:now).returns Time.local(1999, 12, 31, 23)
- assert_equal Date.new(2000, 1, 2), Date.tomorrow
+ Time.stub(:now, Time.local(1999, 12, 31, 23)) do
+ assert_equal Date.new(2000, 1, 2), Date.tomorrow
+ end
end
end
end
@@ -317,23 +319,26 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
end
def test_past
- Date.stubs(:current).returns(Date.new(2000, 1, 1))
- 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?
+ 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?
+ end
end
def test_future
- Date.stubs(:current).returns(Date.new(2000, 1, 1))
- 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?
+ 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?
+ end
end
def test_current_returns_date_today_when_zone_not_set
with_env_tz 'US/Central' do
- Time.stubs(:now).returns Time.local(1999, 12, 31, 23)
- assert_equal Date.today, Date.current
+ Time.stub(:now, Time.local(1999, 12, 31, 23)) do
+ assert_equal Date.today, Date.current
+ end
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 74319ecd09..6fe38c45ec 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -204,61 +204,69 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_today_with_offset
- Date.stubs(:current).returns(Date.new(2000, 1, 1))
- 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?
+ 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?
+ end
end
def test_today_without_offset
- Date.stubs(:current).returns(Date.new(2000, 1, 1))
- 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?
+ 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?
+ end
end
def test_past_with_offset
- DateTime.stubs(:current).returns(DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400)))
- 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.stubs(:current).returns(DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400)))
- 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.stubs(:current).returns(DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400)))
- 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.stubs(:current).returns(DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400)))
- 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
def test_current_returns_date_today_when_zone_is_not_set
with_env_tz 'US/Eastern' do
- Time.stubs(:now).returns Time.local(1999, 12, 31, 23, 59, 59)
- assert_equal DateTime.new(1999, 12, 31, 23, 59, 59, Rational(-18000, 86400)), DateTime.current
+ Time.stub(:now, Time.local(1999, 12, 31, 23, 59, 59)) do
+ assert_equal DateTime.new(1999, 12, 31, 23, 59, 59, Rational(-18000, 86400)), DateTime.current
+ end
end
end
def test_current_returns_time_zone_today_when_zone_is_set
Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
with_env_tz 'US/Eastern' do
- Time.stubs(:now).returns Time.local(1999, 12, 31, 23, 59, 59)
- assert_equal DateTime.new(1999, 12, 31, 23, 59, 59, Rational(-18000, 86400)), DateTime.current
+ Time.stub(:now, Time.local(1999, 12, 31, 23, 59, 59)) do
+ assert_equal DateTime.new(1999, 12, 31, 23, 59, 59, Rational(-18000, 86400)), DateTime.current
+ end
end
ensure
Time.zone = nil
@@ -335,6 +343,13 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
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"
+ end
+
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
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index c283b546e6..9e97acaffb 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -140,28 +140,30 @@ class DurationTest < ActiveSupport::TestCase
def test_since_and_ago_anchored_to_time_now_when_time_zone_is_not_set
Time.zone = nil
with_env_tz 'US/Eastern' do
- Time.stubs(:now).returns Time.local(2000)
- # since
- assert_not_instance_of ActiveSupport::TimeWithZone, 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
+ 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
+ # ago
+ assert_not_instance_of ActiveSupport::TimeWithZone, 5.seconds.ago
+ assert_equal Time.local(1999,12,31,23,59,55), 5.seconds.ago
+ end
end
end
def test_since_and_ago_anchored_to_time_zone_now_when_time_zone_is_set
Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
with_env_tz 'US/Eastern' do
- Time.stubs(:now).returns Time.local(2000)
- # 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 '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 'Eastern Time (US & Canada)', 5.seconds.ago.time_zone.name
+ 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 '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 'Eastern Time (US & Canada)', 5.seconds.ago.time_zone.name
+ end
end
ensure
Time.zone = nil
diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb
index e5d8ae7882..f09b7d8850 100644
--- a/activesupport/test/core_ext/enumerable_test.rb
+++ b/activesupport/test/core_ext/enumerable_test.rb
@@ -3,6 +3,8 @@ require 'active_support/core_ext/array'
require 'active_support/core_ext/enumerable'
Payment = Struct.new(:price)
+ExpandedPayment = Struct.new(:dollars, :cents)
+
class SummablePayment < Payment
def +(p) self.class.new(price + p.price) end
end
@@ -110,4 +112,16 @@ class EnumerableTests < ActiveSupport::TestCase
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))
end
+
+ def test_pluck
+ payments = GenericEnumerable.new([ Payment.new(5), Payment.new(15), Payment.new(10) ])
+ assert_equal [5, 15, 10], payments.pluck(:price)
+
+ payments = GenericEnumerable.new([
+ ExpandedPayment.new(5, 99),
+ ExpandedPayment.new(15, 0),
+ ExpandedPayment.new(10, 50)
+ ])
+ assert_equal [[5, 99], [15, 0], [10, 50]], payments.pluck(:dollars, :cents)
+ end
end
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index e10bee5e00..4624338df1 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -524,6 +524,10 @@ class HashExtTest < ActiveSupport::TestCase
end
def test_indifferent_reverse_merging
+ hash = HashWithIndifferentAccess.new key: :old_value
+ hash.reverse_merge! key: :new_value
+ assert_equal :old_value, hash[:key]
+
hash = HashWithIndifferentAccess.new('some' => 'value', 'other' => 'value')
hash.reverse_merge!(:some => 'noclobber', :another => 'clobber')
assert_equal 'value', hash[:some]
@@ -547,6 +551,11 @@ class HashExtTest < ActiveSupport::TestCase
assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
end
+ def test_indifferent_select_returns_enumerator
+ enum = ActiveSupport::HashWithIndifferentAccess.new(@strings).select
+ assert_instance_of Enumerator, enum
+ end
+
def test_indifferent_select_returns_a_hash_when_unchanged
hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select {|k,v| true}
@@ -568,6 +577,11 @@ class HashExtTest < ActiveSupport::TestCase
assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
end
+ def test_indifferent_reject_returns_enumerator
+ enum = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject
+ assert_instance_of Enumerator, enum
+ end
+
def test_indifferent_reject_bang
indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
indifferent_strings.reject! {|k,v| v != 1}
@@ -961,10 +975,11 @@ class HashExtTest < ActiveSupport::TestCase
assert_raise(RuntimeError) { original.except!(:a) }
end
- def test_except_with_mocha_expectation_on_original
+ def test_except_does_not_delete_values_in_original
original = { :a => 'x', :b => 'y' }
- original.expects(:delete).never
- original.except(:a)
+ assert_not_called(original, :delete) do
+ original.except(:a)
+ end
end
def test_compact
@@ -998,6 +1013,37 @@ class HashExtTest < ActiveSupport::TestCase
assert hash.key?('a')
assert_equal 1, hash[:a]
end
+
+ def test_dup_with_default_proc
+ hash = HashWithIndifferentAccess.new
+ hash.default_proc = proc { |h, v| raise "walrus" }
+ assert_nothing_raised { hash.dup }
+ end
+
+ def test_dup_with_default_proc_sets_proc
+ hash = HashWithIndifferentAccess.new
+ hash.default_proc = proc { |h, k| k + 1 }
+ new_hash = hash.dup
+
+ assert_equal 3, new_hash[2]
+
+ new_hash.default = 2
+ assert_equal 2, new_hash[:non_existant]
+ end
+
+ def test_to_hash_with_raising_default_proc
+ hash = HashWithIndifferentAccess.new
+ hash.default_proc = proc { |h, k| raise "walrus" }
+
+ 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_nothing_raised { HashWithIndifferentAccess.new_from_hash_copying_default(hash) }
+ end
end
class IWriteMyOwnXML
diff --git a/activesupport/test/core_ext/marshal_test.rb b/activesupport/test/core_ext/marshal_test.rb
index e49330128b..825df439a5 100644
--- a/activesupport/test/core_ext/marshal_test.rb
+++ b/activesupport/test/core_ext/marshal_test.rb
@@ -8,7 +8,7 @@ class MarshalTest < ActiveSupport::TestCase
def teardown
ActiveSupport::Dependencies.clear
- remove_constants(:E, :ClassFolder)
+ remove_constants(:EM, :ClassFolder)
end
test "that Marshal#load still works" do
@@ -22,14 +22,14 @@ class MarshalTest < ActiveSupport::TestCase
test "that a missing class is autoloaded from string" do
dumped = nil
with_autoloading_fixtures do
- dumped = Marshal.dump(E.new)
+ dumped = Marshal.dump(EM.new)
end
- remove_constants(:E)
+ remove_constants(:EM)
ActiveSupport::Dependencies.clear
with_autoloading_fixtures do
- assert_kind_of E, Marshal.load(dumped)
+ assert_kind_of EM, Marshal.load(dumped)
end
end
@@ -50,16 +50,16 @@ class MarshalTest < ActiveSupport::TestCase
test "that more than one missing class is autoloaded" do
dumped = nil
with_autoloading_fixtures do
- dumped = Marshal.dump([E.new, ClassFolder.new])
+ dumped = Marshal.dump([EM.new, ClassFolder.new])
end
- remove_constants(:E, :ClassFolder)
+ remove_constants(:EM, :ClassFolder)
ActiveSupport::Dependencies.clear
with_autoloading_fixtures do
loaded = Marshal.load(dumped)
assert_equal 2, loaded.size
- assert_kind_of E, loaded[0]
+ assert_kind_of EM, loaded[0]
assert_kind_of ClassFolder, loaded[1]
end
end
@@ -67,10 +67,10 @@ class MarshalTest < ActiveSupport::TestCase
test "that a real missing class is causing an exception" do
dumped = nil
with_autoloading_fixtures do
- dumped = Marshal.dump(E.new)
+ dumped = Marshal.dump(EM.new)
end
- remove_constants(:E)
+ remove_constants(:EM)
ActiveSupport::Dependencies.clear
assert_raise(NameError) do
@@ -84,10 +84,10 @@ class MarshalTest < ActiveSupport::TestCase
end
with_autoloading_fixtures do
- dumped = Marshal.dump([E.new, SomeClass.new])
+ dumped = Marshal.dump([EM.new, SomeClass.new])
end
- remove_constants(:E)
+ remove_constants(:EM)
self.class.send(:remove_const, :SomeClass)
ActiveSupport::Dependencies.clear
@@ -96,8 +96,8 @@ class MarshalTest < ActiveSupport::TestCase
Marshal.load(dumped)
end
- assert_nothing_raised("E failed to load while we expect only SomeClass to fail loading") do
- E.new
+ assert_nothing_raised("EM failed to load while we expect only SomeClass to fail loading") do
+ EM.new
end
assert_raise(NameError, "We expected SomeClass to not be loaded but it is!") do
@@ -109,15 +109,15 @@ class MarshalTest < ActiveSupport::TestCase
test "loading classes from files trigger autoloading" do
Tempfile.open("object_serializer_test") do |f|
with_autoloading_fixtures do
- Marshal.dump(E.new, f)
+ Marshal.dump(EM.new, f)
end
f.rewind
- remove_constants(:E)
+ remove_constants(:EM)
ActiveSupport::Dependencies.clear
with_autoloading_fixtures do
- assert_kind_of E, Marshal.load(f)
+ assert_kind_of EM, Marshal.load(f)
end
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 48f3cc579f..128c5e3d1a 100644
--- a/activesupport/test/core_ext/module/attribute_accessor_test.rb
+++ b/activesupport/test/core_ext/module/attribute_accessor_test.rb
@@ -76,4 +76,10 @@ class ModuleAttributeAccessorTest < ActiveSupport::TestCase
assert_equal 'default_reader_value', @module.defr
assert_equal 'default_writer_value', @module.class_variable_get('@@defw')
end
+
+ def test_should_not_invoke_default_value_block_multiple_times
+ count = 0
+ @module.cattr_accessor(:defcount){ count += 1 }
+ assert_equal 1, count
+ end
end
diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb
index b82448458d..0ff8f0f89b 100644
--- a/activesupport/test/core_ext/numeric_ext_test.rb
+++ b/activesupport/test/core_ext/numeric_ext_test.rb
@@ -280,14 +280,16 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
end
def test_to_s__human_size_with_si_prefix
- 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_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)
+ end
end
def test_to_s__human_size_with_options_hash
@@ -389,4 +391,88 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
def test_in_milliseconds
assert_equal 10_000, 10.seconds.in_milliseconds
end
+
+ # 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
+ T_MONE = b.coerce(-1).first
+
+ def test_positive
+ assert_predicate(1, :positive?)
+ assert_not_predicate(0, :positive?)
+ assert_not_predicate(-1, :positive?)
+ assert_predicate(+1.0, :positive?)
+ assert_not_predicate(+0.0, :positive?)
+ assert_not_predicate(-0.0, :positive?)
+ assert_not_predicate(-1.0, :positive?)
+ assert_predicate(+(0.0.next_float), :positive?)
+ assert_not_predicate(-(0.0.next_float), :positive?)
+ assert_predicate(Float::INFINITY, :positive?)
+ assert_not_predicate(-Float::INFINITY, :positive?)
+ assert_not_predicate(Float::NAN, :positive?)
+
+ a = Class.new(Numeric) do
+ def >(x); true; end
+ end.new
+ assert_predicate(a, :positive?)
+
+ a = Class.new(Numeric) do
+ def >(x); false; end
+ end.new
+ assert_not_predicate(a, :positive?)
+
+ assert_predicate(1/2r, :positive?)
+ assert_not_predicate(-1/2r, :positive?)
+
+ assert_predicate(T_ONE, :positive?)
+ assert_not_predicate(T_MONE, :positive?)
+ assert_not_predicate(T_ZERO, :positive?)
+
+ e = assert_raises(NoMethodError) do
+ Complex(1).positive?
+ end
+
+ assert_match(/positive\?/, e.message)
+ end
+
+ def test_negative
+ assert_predicate(-1, :negative?)
+ assert_not_predicate(0, :negative?)
+ assert_not_predicate(1, :negative?)
+ assert_predicate(-1.0, :negative?)
+ assert_not_predicate(-0.0, :negative?)
+ assert_not_predicate(+0.0, :negative?)
+ assert_not_predicate(+1.0, :negative?)
+ assert_predicate(-(0.0.next_float), :negative?)
+ assert_not_predicate(+(0.0.next_float), :negative?)
+ assert_predicate(-Float::INFINITY, :negative?)
+ assert_not_predicate(Float::INFINITY, :negative?)
+ assert_not_predicate(Float::NAN, :negative?)
+
+ a = Class.new(Numeric) do
+ def <(x); true; end
+ end.new
+ assert_predicate(a, :negative?)
+
+ a = Class.new(Numeric) do
+ def <(x); false; end
+ end.new
+ assert_not_predicate(a, :negative?)
+
+ assert_predicate(-1/2r, :negative?)
+ assert_not_predicate(1/2r, :negative?)
+
+ assert_not_predicate(T_ONE, :negative?)
+ assert_predicate(T_MONE, :negative?)
+ assert_not_predicate(T_ZERO, :negative?)
+
+ e = assert_raises(NoMethodError) do
+ Complex(1).negative?
+ end
+
+ assert_match(/negative\?/, e.message)
+ 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 91d558dbb5..791b5e7172 100644
--- a/activesupport/test/core_ext/object/deep_dup_test.rb
+++ b/activesupport/test/core_ext/object/deep_dup_test.rb
@@ -50,4 +50,10 @@ class DeepDupTest < ActiveSupport::TestCase
assert dup.instance_variable_defined?(:@a)
end
+ def test_deep_dup_with_hash_class_key
+ hash = { Fixnum => 1 }
+ dup = hash.deep_dup
+ assert_equal 1, dup.keys.length
+ end
+
end
diff --git a/activesupport/test/core_ext/object/json_gem_encoding_test.rb b/activesupport/test/core_ext/object/json_gem_encoding_test.rb
new file mode 100644
index 0000000000..02ab17fb64
--- /dev/null
+++ b/activesupport/test/core_ext/object/json_gem_encoding_test.rb
@@ -0,0 +1,66 @@
+require 'abstract_unit'
+require 'json'
+require 'json/encoding_test_cases'
+
+# These test cases were added to test that we do not interfere with json gem's
+# output when the AS encoder is loaded, primarily for problems reported in
+# #20775. They need to be executed in isolation to reproduce the scenario
+# correctly, because other test cases might have already loaded additional
+# dependencies.
+
+# The AS::JSON encoder requires the BigDecimal core_ext, which, unfortunately,
+# changes the BigDecimal#to_s output, and consequently the JSON gem output. So
+# we need to require this unfront to ensure we don't get a false failure, but
+# ideally we should just fix the BigDecimal core_ext to not change to_s without
+# arguments.
+require 'active_support/core_ext/big_decimal'
+
+class JsonGemEncodingTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ JSONTest::EncodingTestCases.constants.each_with_index do |name|
+ JSONTest::EncodingTestCases.const_get(name).each_with_index do |(subject, _), i|
+ test("#{name[0..-6].underscore} #{i}") do
+ assert_same_with_or_without_active_support(subject)
+ end
+ end
+ end
+
+ class CustomToJson
+ def to_json(*)
+ '"custom"'
+ end
+ end
+
+ test "custom to_json" do
+ assert_same_with_or_without_active_support(CustomToJson.new)
+ end
+
+ private
+ def require_or_skip(file)
+ require(file) || skip("'#{file}' was already loaded")
+ end
+
+ def assert_same_with_or_without_active_support(subject)
+ begin
+ expected = JSON.generate(subject, quirks_mode: true)
+ rescue JSON::GeneratorError => e
+ exception = e
+ end
+
+ require_or_skip 'active_support/core_ext/object/json'
+
+ if exception
+ assert_raises_with_message JSON::GeneratorError, e.message do
+ JSON.generate(subject, quirks_mode: true)
+ end
+ else
+ assert_equal expected, JSON.generate(subject, quirks_mode: true)
+ end
+ end
+
+ def assert_raises_with_message(exception_class, message, &block)
+ err = assert_raises(exception_class) { block.call }
+ assert_match message, err.message
+ end
+end
diff --git a/activesupport/test/core_ext/object/try_test.rb b/activesupport/test/core_ext/object/try_test.rb
index 89438675c1..5ea0f0eca6 100644
--- a/activesupport/test/core_ext/object/try_test.rb
+++ b/activesupport/test/core_ext/object/try_test.rb
@@ -77,9 +77,9 @@ class ObjectTryTest < ActiveSupport::TestCase
klass = Class.new do
private
- def private_method
- 'private method'
- end
+ def private_method
+ 'private method'
+ end
end
assert_raise(NoMethodError) { klass.new.try!(:private_method) }
@@ -89,11 +89,75 @@ class ObjectTryTest < ActiveSupport::TestCase
klass = Class.new do
private
- def private_method
- 'private method'
- end
+ def private_method
+ 'private method'
+ end
end
assert_nil klass.new.try(:private_method)
end
+
+ class Decorator < SimpleDelegator
+ def delegator_method
+ 'delegator method'
+ end
+
+ def reverse
+ 'overridden reverse'
+ end
+
+ private
+
+ def private_delegator_method
+ 'private delegator method'
+ end
+ end
+
+ def test_try_with_method_on_delegator
+ assert_equal 'delegator method', Decorator.new(@string).try(:delegator_method)
+ end
+
+ def test_try_with_method_on_delegator_target
+ assert_equal 5, Decorator.new(@string).size
+ end
+
+ def test_try_with_overriden_method_on_delegator
+ assert_equal 'overridden reverse', Decorator.new(@string).reverse
+ end
+
+ def test_try_with_private_method_on_delegator
+ assert_nil Decorator.new(@string).try(:private_delegator_method)
+ end
+
+ def test_try_with_private_method_on_delegator_bang
+ assert_raise(NoMethodError) do
+ Decorator.new(@string).try!(:private_delegator_method)
+ end
+ end
+
+ def test_try_with_private_method_on_delegator_target
+ klass = Class.new do
+ private
+
+ def private_method
+ 'private method'
+ end
+ end
+
+ assert_nil Decorator.new(klass.new).try(:private_method)
+ end
+
+ def test_try_with_private_method_on_delegator_target_bang
+ klass = Class.new do
+ private
+
+ def private_method
+ 'private method'
+ end
+ end
+
+ assert_raise(NoMethodError) do
+ Decorator.new(klass.new).try!(:private_method)
+ end
+ end
end
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index cb24147ab3..3a5d6df06d 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -435,117 +435,123 @@ class StringConversionsTest < ActiveSupport::TestCase
def test_standard_time_string_to_time_when_current_time_is_standard_time
with_env_tz "US/Eastern" do
- Time.stubs(:now).returns(Time.local(2012, 1, 1))
- assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time
- assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 -0800".to_time
- assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 -0800".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 -0500".to_time
- assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 -0500".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 5, 0), "2012-01-01 10:00 UTC".to_time
- assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00 UTC".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 PST".to_time
- assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 PST".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 EST".to_time
- assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 EST".to_time(:utc)
+ Time.stub(:now, Time.local(2012, 1, 1)) do
+ assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time
+ assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 -0800".to_time
+ assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 -0800".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 -0500".to_time
+ assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 -0500".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 5, 0), "2012-01-01 10:00 UTC".to_time
+ assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00 UTC".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 PST".to_time
+ assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 PST".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 EST".to_time
+ assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 EST".to_time(:utc)
+ end
end
end
def test_standard_time_string_to_time_when_current_time_is_daylight_savings
with_env_tz "US/Eastern" do
- Time.stubs(:now).returns(Time.local(2012, 7, 1))
- assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time
- assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 -0800".to_time
- assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 -0800".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 -0500".to_time
- assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 -0500".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 5, 0), "2012-01-01 10:00 UTC".to_time
- assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00 UTC".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 PST".to_time
- assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 PST".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 EST".to_time
- assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 EST".to_time(:utc)
+ Time.stub(:now, Time.local(2012, 7, 1)) do
+ assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time
+ assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 -0800".to_time
+ assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 -0800".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 -0500".to_time
+ assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 -0500".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 5, 0), "2012-01-01 10:00 UTC".to_time
+ assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00 UTC".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 PST".to_time
+ assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 PST".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 EST".to_time
+ assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 EST".to_time(:utc)
+ end
end
end
def test_daylight_savings_string_to_time_when_current_time_is_standard_time
with_env_tz "US/Eastern" do
- Time.stubs(:now).returns(Time.local(2012, 1, 1))
- assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time
- assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 -0700".to_time
- assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 -0700".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 -0400".to_time
- assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 -0400".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 6, 0), "2012-07-01 10:00 UTC".to_time
- assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00 UTC".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 PDT".to_time
- assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 PDT".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 EDT".to_time
- assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 EDT".to_time(:utc)
+ Time.stub(:now, Time.local(2012, 1, 1)) do
+ assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time
+ assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 -0700".to_time
+ assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 -0700".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 -0400".to_time
+ assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 -0400".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 6, 0), "2012-07-01 10:00 UTC".to_time
+ assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00 UTC".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 PDT".to_time
+ assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 PDT".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 EDT".to_time
+ assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 EDT".to_time(:utc)
+ end
end
end
def test_daylight_savings_string_to_time_when_current_time_is_daylight_savings
with_env_tz "US/Eastern" do
- Time.stubs(:now).returns(Time.local(2012, 7, 1))
- assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time
- assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 -0700".to_time
- assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 -0700".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 -0400".to_time
- assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 -0400".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 6, 0), "2012-07-01 10:00 UTC".to_time
- assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00 UTC".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 PDT".to_time
- assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 PDT".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 EDT".to_time
- assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 EDT".to_time(:utc)
+ Time.stub(:now, Time.local(2012, 7, 1)) do
+ assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time
+ assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 -0700".to_time
+ assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 -0700".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 -0400".to_time
+ assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 -0400".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 6, 0), "2012-07-01 10:00 UTC".to_time
+ assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00 UTC".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 PDT".to_time
+ assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 PDT".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 EDT".to_time
+ assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 EDT".to_time(:utc)
+ end
end
end
def test_partial_string_to_time_when_current_time_is_standard_time
with_env_tz "US/Eastern" do
- Time.stubs(:now).returns(Time.local(2012, 1, 1))
- assert_equal Time.local(2012, 1, 1, 10, 0), "10:00".to_time
- assert_equal Time.utc(2012, 1, 1, 10, 0), "10:00".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 6, 0), "10:00 -0100".to_time
- assert_equal Time.utc(2012, 1, 1, 11, 0), "10:00 -0100".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 10, 0), "10:00 -0500".to_time
- assert_equal Time.utc(2012, 1, 1, 15, 0), "10:00 -0500".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 5, 0), "10:00 UTC".to_time
- assert_equal Time.utc(2012, 1, 1, 10, 0), "10:00 UTC".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 13, 0), "10:00 PST".to_time
- assert_equal Time.utc(2012, 1, 1, 18, 0), "10:00 PST".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 12, 0), "10:00 PDT".to_time
- assert_equal Time.utc(2012, 1, 1, 17, 0), "10:00 PDT".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 10, 0), "10:00 EST".to_time
- assert_equal Time.utc(2012, 1, 1, 15, 0), "10:00 EST".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 9, 0), "10:00 EDT".to_time
- assert_equal Time.utc(2012, 1, 1, 14, 0), "10:00 EDT".to_time(:utc)
+ Time.stub(:now, Time.local(2012, 1, 1)) do
+ assert_equal Time.local(2012, 1, 1, 10, 0), "10:00".to_time
+ assert_equal Time.utc(2012, 1, 1, 10, 0), "10:00".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 6, 0), "10:00 -0100".to_time
+ assert_equal Time.utc(2012, 1, 1, 11, 0), "10:00 -0100".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 10, 0), "10:00 -0500".to_time
+ assert_equal Time.utc(2012, 1, 1, 15, 0), "10:00 -0500".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 5, 0), "10:00 UTC".to_time
+ assert_equal Time.utc(2012, 1, 1, 10, 0), "10:00 UTC".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 13, 0), "10:00 PST".to_time
+ assert_equal Time.utc(2012, 1, 1, 18, 0), "10:00 PST".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 12, 0), "10:00 PDT".to_time
+ assert_equal Time.utc(2012, 1, 1, 17, 0), "10:00 PDT".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 10, 0), "10:00 EST".to_time
+ assert_equal Time.utc(2012, 1, 1, 15, 0), "10:00 EST".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 9, 0), "10:00 EDT".to_time
+ assert_equal Time.utc(2012, 1, 1, 14, 0), "10:00 EDT".to_time(:utc)
+ end
end
end
def test_partial_string_to_time_when_current_time_is_daylight_savings
with_env_tz "US/Eastern" do
- Time.stubs(:now).returns(Time.local(2012, 7, 1))
- assert_equal Time.local(2012, 7, 1, 10, 0), "10:00".to_time
- assert_equal Time.utc(2012, 7, 1, 10, 0), "10:00".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 7, 0), "10:00 -0100".to_time
- assert_equal Time.utc(2012, 7, 1, 11, 0), "10:00 -0100".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 11, 0), "10:00 -0500".to_time
- assert_equal Time.utc(2012, 7, 1, 15, 0), "10:00 -0500".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 6, 0), "10:00 UTC".to_time
- assert_equal Time.utc(2012, 7, 1, 10, 0), "10:00 UTC".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 14, 0), "10:00 PST".to_time
- assert_equal Time.utc(2012, 7, 1, 18, 0), "10:00 PST".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 13, 0), "10:00 PDT".to_time
- assert_equal Time.utc(2012, 7, 1, 17, 0), "10:00 PDT".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 11, 0), "10:00 EST".to_time
- assert_equal Time.utc(2012, 7, 1, 15, 0), "10:00 EST".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 10, 0), "10:00 EDT".to_time
- assert_equal Time.utc(2012, 7, 1, 14, 0), "10:00 EDT".to_time(:utc)
+ Time.stub(:now, Time.local(2012, 7, 1)) do
+ assert_equal Time.local(2012, 7, 1, 10, 0), "10:00".to_time
+ assert_equal Time.utc(2012, 7, 1, 10, 0), "10:00".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 7, 0), "10:00 -0100".to_time
+ assert_equal Time.utc(2012, 7, 1, 11, 0), "10:00 -0100".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 11, 0), "10:00 -0500".to_time
+ assert_equal Time.utc(2012, 7, 1, 15, 0), "10:00 -0500".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 6, 0), "10:00 UTC".to_time
+ assert_equal Time.utc(2012, 7, 1, 10, 0), "10:00 UTC".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 14, 0), "10:00 PST".to_time
+ assert_equal Time.utc(2012, 7, 1, 18, 0), "10:00 PST".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 13, 0), "10:00 PDT".to_time
+ assert_equal Time.utc(2012, 7, 1, 17, 0), "10:00 PDT".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 11, 0), "10:00 EST".to_time
+ assert_equal Time.utc(2012, 7, 1, 15, 0), "10:00 EST".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 10, 0), "10:00 EDT".to_time
+ assert_equal Time.utc(2012, 7, 1, 14, 0), "10:00 EDT".to_time(:utc)
+ end
end
end
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index d59775001b..b14c04fba6 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -149,6 +149,9 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
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
+ end
end
def test_end_of_hour
@@ -389,6 +392,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
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(ArgumentError) { Time.new(2015, 5, 9, 10, 00, 00, '+03:00').change(nsec: 999999999) }
end
def test_utc_change
@@ -601,13 +605,15 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_days_in_month_feb_in_common_year_without_year_arg
- Time.stubs(:now).returns(Time.utc(2007))
- assert_equal 28, Time.days_in_month(2)
+ Time.stub(:now, Time.utc(2007)) do
+ assert_equal 28, Time.days_in_month(2)
+ end
end
def test_days_in_month_feb_in_leap_year_without_year_arg
- Time.stubs(:now).returns(Time.utc(2008))
- assert_equal 29, Time.days_in_month(2)
+ Time.stub(:now, Time.utc(2008)) do
+ assert_equal 29, Time.days_in_month(2)
+ end
end
def test_last_month_on_31st
@@ -619,68 +625,74 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_today_with_time_local
- Date.stubs(:current).returns(Date.new(2000, 1, 1))
- 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?
+ 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?
+ end
end
def test_today_with_time_utc
- Date.stubs(:current).returns(Date.new(2000, 1, 1))
- 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?
+ 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?
+ end
end
def test_past_with_time_current_as_time_local
with_env_tz 'US/Eastern' do
- Time.stubs(:current).returns(Time.local(2005,2,10,15,30,45))
- 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)')
- Time.stubs(:current).returns(twz)
- 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?
+ 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?
+ end
end
end
def test_future_with_time_current_as_time_local
with_env_tz 'US/Eastern' do
- Time.stubs(:current).returns(Time.local(2005,2,10,15,30,45))
- 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)')
- Time.stubs(:current).returns(twz)
- 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?
+ 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?
+ end
end
end
@@ -721,6 +733,13 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
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'
+ end
+
def test_at_with_datetime
assert_equal Time.utc(2000, 1, 1, 0, 0, 0), Time.at(DateTime.civil(2000, 1, 1, 0, 0, 0))
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 92c233d567..ccb7f02331 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -1,6 +1,8 @@
require 'abstract_unit'
require 'active_support/time'
require 'time_zone_test_helpers'
+require 'active_support/core_ext/string/strip'
+require 'yaml'
class TimeWithZoneTest < ActiveSupport::TestCase
include TimeZoneTestHelpers
@@ -123,11 +125,53 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_to_yaml
- assert_match(/^--- 2000-01-01 00:00:00(\.0+)?\s*Z\n/, @twz.to_yaml)
+ yaml = <<-EOF.strip_heredoc
+ --- !ruby/object:ActiveSupport::TimeWithZone
+ utc: 2000-01-01 00:00:00.000000000 Z
+ zone: !ruby/object:ActiveSupport::TimeZone
+ name: America/New_York
+ time: 1999-12-31 19:00:00.000000000 Z
+ EOF
+
+ assert_equal(yaml, @twz.to_yaml)
end
def test_ruby_to_yaml
- assert_match(/---\s*\n:twz: 2000-01-01 00:00:00(\.0+)?\s*Z\n/, {:twz => @twz}.to_yaml)
+ yaml = <<-EOF.strip_heredoc
+ ---
+ twz: !ruby/object:ActiveSupport::TimeWithZone
+ utc: 2000-01-01 00:00:00.000000000 Z
+ zone: !ruby/object:ActiveSupport::TimeZone
+ name: America/New_York
+ time: 1999-12-31 19:00:00.000000000 Z
+ EOF
+
+ assert_equal(yaml, { 'twz' => @twz }.to_yaml)
+ end
+
+ def test_yaml_load
+ yaml = <<-EOF.strip_heredoc
+ --- !ruby/object:ActiveSupport::TimeWithZone
+ utc: 2000-01-01 00:00:00.000000000 Z
+ zone: !ruby/object:ActiveSupport::TimeZone
+ name: America/New_York
+ time: 1999-12-31 19:00:00.000000000 Z
+ EOF
+
+ assert_equal(@twz, YAML.load(yaml))
+ end
+
+ def test_ruby_yaml_load
+ yaml = <<-EOF.strip_heredoc
+ ---
+ twz: !ruby/object:ActiveSupport::TimeWithZone
+ utc: 2000-01-01 00:00:00.000000000 Z
+ zone: !ruby/object:ActiveSupport::TimeZone
+ name: America/New_York
+ time: 1999-12-31 19:00:00.000000000 Z
+ EOF
+
+ assert_equal({ 'twz' => @twz }, YAML.load(yaml))
end
def test_httpdate
@@ -162,52 +206,61 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_today
- Date.stubs(:current).returns(Date.new(2000, 1, 1))
- 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?
+ 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?
+ end
end
def test_past_with_time_current_as_time_local
with_env_tz 'US/Eastern' do
- Time.stubs(:current).returns(Time.local(2005,2,10,15,30,45))
- 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) )
- Time.stubs(:current).returns(twz)
- 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, 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?
+ end
end
def test_future_with_time_current_as_time_local
with_env_tz 'US/Eastern' do
- Time.stubs(:current).returns(Time.local(2005,2,10,15,30,45))
- 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) )
- Time.stubs(:current).returns(twz)
- 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, 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?
+ 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) )
+
+ other_twz = ActiveSupport::TimeWithZone.new(DateTime.now.utc, @time_zone)
+ assert_equal true, other_twz.eql?(other_twz.dup)
end
def test_hash
@@ -434,22 +487,24 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_method_missing_with_non_time_return_value
- @twz.time.expects(:foo).returns('bar')
+ time = @twz.time
+ def time.foo; 'bar'; end
assert_equal 'bar', @twz.foo
end
def test_date_part_value_methods
twz = ActiveSupport::TimeWithZone.new(Time.utc(1999,12,31,19,18,17,500), @time_zone)
- twz.expects(:method_missing).never
- assert_equal 1999, twz.year
- assert_equal 12, twz.month
- assert_equal 31, twz.day
- assert_equal 14, twz.hour
- assert_equal 18, twz.min
- assert_equal 17, twz.sec
- assert_equal 500, twz.usec
- assert_equal 5, twz.wday
- assert_equal 365, twz.yday
+ assert_not_called(twz, :method_missing) do
+ assert_equal 1999, twz.year
+ assert_equal 12, twz.month
+ assert_equal 31, twz.day
+ assert_equal 14, twz.hour
+ assert_equal 18, twz.min
+ assert_equal 17, twz.sec
+ assert_equal 500, twz.usec
+ assert_equal 5, twz.wday
+ assert_equal 365, twz.yday
+ end
end
def test_usec_returns_0_when_datetime_is_wrapped
@@ -990,19 +1045,21 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < ActiveSupport::TestCase
def test_current_returns_time_now_when_zone_not_set
with_env_tz 'US/Eastern' do
- Time.stubs(:now).returns Time.local(2000)
- assert_equal false, Time.current.is_a?(ActiveSupport::TimeWithZone)
- assert_equal Time.local(2000), Time.current
+ Time.stub(:now, Time.local(2000)) do
+ assert_equal false, Time.current.is_a?(ActiveSupport::TimeWithZone)
+ assert_equal Time.local(2000), Time.current
+ end
end
end
def test_current_returns_time_zone_now_when_zone_set
Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
with_env_tz 'US/Eastern' do
- Time.stubs(:now).returns Time.local(2000)
- assert_equal true, Time.current.is_a?(ActiveSupport::TimeWithZone)
- assert_equal 'Eastern Time (US & Canada)', Time.current.time_zone.name
- assert_equal Time.utc(2000), Time.current.time
+ Time.stub(:now, Time.local(2000)) do
+ assert_equal true, Time.current.is_a?(ActiveSupport::TimeWithZone)
+ assert_equal 'Eastern Time (US & Canada)', Time.current.time_zone.name
+ assert_equal Time.utc(2000), Time.current.time
+ end
end
end
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 4a1d90bfd6..757e600646 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -187,7 +187,7 @@ class DependenciesTest < ActiveSupport::TestCase
assert_kind_of Module, A
assert_kind_of Class, A::B
assert_kind_of Class, A::C::D
- assert_kind_of Class, A::C::E::F
+ assert_kind_of Class, A::C::EM::F
end
end
@@ -552,24 +552,24 @@ class DependenciesTest < ActiveSupport::TestCase
def test_const_missing_in_anonymous_modules_loads_top_level_constants
with_autoloading_fixtures do
# class_eval STRING pushes the class to the nesting of the eval'ed code.
- klass = Class.new.class_eval "E"
- assert_equal E, klass
+ klass = Class.new.class_eval "EM"
+ assert_equal EM, klass
end
ensure
- remove_constants(:E)
+ remove_constants(:EM)
end
def test_const_missing_in_anonymous_modules_raises_if_the_constant_belongs_to_Object
with_autoloading_fixtures do
- require_dependency 'e'
+ require_dependency 'em'
mod = Module.new
- e = assert_raise(NameError) { mod::E }
- assert_equal 'E cannot be autoloaded from an anonymous class or module', e.message
- assert_equal :E, e.name
+ e = assert_raise(NameError) { mod::EM }
+ assert_equal 'EM cannot be autoloaded from an anonymous class or module', e.message
+ assert_equal :EM, e.name
end
ensure
- remove_constants(:E)
+ remove_constants(:EM)
end
def test_removal_from_tree_should_be_detected
@@ -664,19 +664,19 @@ class DependenciesTest < ActiveSupport::TestCase
def test_preexisting_constants_are_not_marked_as_autoloaded
with_autoloading_fixtures do
- require_dependency 'e'
- assert ActiveSupport::Dependencies.autoloaded?(:E)
+ require_dependency 'em'
+ assert ActiveSupport::Dependencies.autoloaded?(:EM)
ActiveSupport::Dependencies.clear
end
- Object.const_set :E, Class.new
+ Object.const_set :EM, Class.new
with_autoloading_fixtures do
- require_dependency 'e'
- assert ! ActiveSupport::Dependencies.autoloaded?(:E), "E shouldn't be marked autoloaded!"
+ require_dependency 'em'
+ assert ! ActiveSupport::Dependencies.autoloaded?(:EM), "EM shouldn't be marked autoloaded!"
ActiveSupport::Dependencies.clear
end
ensure
- remove_constants(:E)
+ remove_constants(:EM)
end
def test_constants_in_capitalized_nesting_marked_as_autoloaded
@@ -721,12 +721,13 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_unloadable_constants_should_receive_callback
- Object.const_set :C, Class.new
+ Object.const_set :C, Class.new { def self.before_remove_const; end }
C.unloadable
- C.expects(:before_remove_const).once
- assert C.respond_to?(:before_remove_const)
- ActiveSupport::Dependencies.clear
- assert !defined?(C)
+ assert_called(C, :before_remove_const, times: 1) do
+ assert C.respond_to?(:before_remove_const)
+ ActiveSupport::Dependencies.clear
+ assert !defined?(C)
+ end
ensure
remove_constants(:C)
end
diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb
index 20bd8ee5dd..7e8844b301 100644
--- a/activesupport/test/deprecation_test.rb
+++ b/activesupport/test/deprecation_test.rb
@@ -256,17 +256,17 @@ class DeprecationTest < ActiveSupport::TestCase
end
def test_deprecate_with_custom_deprecator
- custom_deprecator = mock('Deprecator') do
- expects(:deprecation_warning)
- end
+ custom_deprecator = Struct.new(:deprecation_warning).new
- klass = Class.new do
- def method
+ assert_called_with(custom_deprecator, :deprecation_warning, [:method, nil]) do
+ klass = Class.new do
+ def method
+ end
+ deprecate :method, deprecator: custom_deprecator
end
- deprecate :method, deprecator: custom_deprecator
- end
- klass.new.method
+ klass.new.method
+ end
end
def test_deprecated_constant_with_deprecator_given
diff --git a/activesupport/test/hash_with_indifferent_access_test.rb b/activesupport/test/hash_with_indifferent_access_test.rb
deleted file mode 100644
index 1facd691fa..0000000000
--- a/activesupport/test/hash_with_indifferent_access_test.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-require 'abstract_unit'
-require 'active_support/hash_with_indifferent_access'
-
-class HashWithIndifferentAccessTest < ActiveSupport::TestCase
- def test_reverse_merge
- hash = HashWithIndifferentAccess.new key: :old_value
- hash.reverse_merge! key: :new_value
- assert_equal :old_value, hash[:key]
- end
-
-end
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index be68bb2e2e..a0764f6d6b 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -8,6 +8,20 @@ class InflectorTest < ActiveSupport::TestCase
include InflectorTestCases
include ConstantizeTestCases
+ def setup
+ # Dups the singleton before each test, restoring the original inflections later.
+ #
+ # This helper is implemented by setting @__instance__ because in some tests
+ # there are module functions that access ActiveSupport::Inflector.inflections,
+ # so we need to replace the singleton itself.
+ @original_inflections = ActiveSupport::Inflector::Inflections.instance_variable_get(:@__instance__)[:en]
+ ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: @original_inflections.dup)
+ end
+
+ def teardown
+ ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: @original_inflections)
+ end
+
def test_pluralize_plurals
assert_equal "plurals", ActiveSupport::Inflector.pluralize("plurals")
assert_equal "Plurals", ActiveSupport::Inflector.pluralize("Plurals")
@@ -26,20 +40,18 @@ class InflectorTest < ActiveSupport::TestCase
end
def test_uncountable_word_is_not_greedy
- with_dup do
- uncountable_word = "ors"
- countable_word = "sponsor"
+ uncountable_word = "ors"
+ countable_word = "sponsor"
- ActiveSupport::Inflector.inflections.uncountable << uncountable_word
+ ActiveSupport::Inflector.inflections.uncountable << uncountable_word
- assert_equal uncountable_word, ActiveSupport::Inflector.singularize(uncountable_word)
- assert_equal uncountable_word, ActiveSupport::Inflector.pluralize(uncountable_word)
- assert_equal ActiveSupport::Inflector.pluralize(uncountable_word), ActiveSupport::Inflector.singularize(uncountable_word)
+ assert_equal uncountable_word, ActiveSupport::Inflector.singularize(uncountable_word)
+ assert_equal uncountable_word, ActiveSupport::Inflector.pluralize(uncountable_word)
+ assert_equal ActiveSupport::Inflector.pluralize(uncountable_word), ActiveSupport::Inflector.singularize(uncountable_word)
- assert_equal "sponsor", ActiveSupport::Inflector.singularize(countable_word)
- assert_equal "sponsors", ActiveSupport::Inflector.pluralize(countable_word)
- assert_equal "sponsor", ActiveSupport::Inflector.singularize(ActiveSupport::Inflector.pluralize(countable_word))
- end
+ assert_equal "sponsor", ActiveSupport::Inflector.singularize(countable_word)
+ assert_equal "sponsors", ActiveSupport::Inflector.pluralize(countable_word)
+ assert_equal "sponsor", ActiveSupport::Inflector.singularize(ActiveSupport::Inflector.pluralize(countable_word))
end
SingularToPlural.each do |singular, plural|
@@ -70,11 +82,9 @@ class InflectorTest < ActiveSupport::TestCase
def test_overwrite_previous_inflectors
- with_dup do
- assert_equal("series", ActiveSupport::Inflector.singularize("series"))
- ActiveSupport::Inflector.inflections.singular "series", "serie"
- assert_equal("serie", ActiveSupport::Inflector.singularize("series"))
- end
+ assert_equal("series", ActiveSupport::Inflector.singularize("series"))
+ ActiveSupport::Inflector.inflections.singular "series", "serie"
+ assert_equal("serie", ActiveSupport::Inflector.singularize("series"))
end
MixtureToTitleCase.each_with_index do |(before, titleized), index|
@@ -367,10 +377,8 @@ class InflectorTest < ActiveSupport::TestCase
%w{plurals singulars uncountables humans}.each do |inflection_type|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def test_clear_#{inflection_type}
- with_dup do
- ActiveSupport::Inflector.inflections.clear :#{inflection_type}
- assert ActiveSupport::Inflector.inflections.#{inflection_type}.empty?, \"#{inflection_type} inflections should be empty after clear :#{inflection_type}\"
- end
+ ActiveSupport::Inflector.inflections.clear :#{inflection_type}
+ assert ActiveSupport::Inflector.inflections.#{inflection_type}.empty?, \"#{inflection_type} inflections should be empty after clear :#{inflection_type}\"
end
RUBY
end
@@ -405,73 +413,63 @@ class InflectorTest < ActiveSupport::TestCase
end
def test_clear_all
- with_dup do
- ActiveSupport::Inflector.inflections do |inflect|
- # ensure any data is present
- inflect.plural(/(quiz)$/i, '\1zes')
- inflect.singular(/(database)s$/i, '\1')
- inflect.uncountable('series')
- inflect.human("col_rpted_bugs", "Reported bugs")
-
- inflect.clear :all
-
- assert inflect.plurals.empty?
- assert inflect.singulars.empty?
- assert inflect.uncountables.empty?
- assert inflect.humans.empty?
- end
+ ActiveSupport::Inflector.inflections do |inflect|
+ # ensure any data is present
+ inflect.plural(/(quiz)$/i, '\1zes')
+ inflect.singular(/(database)s$/i, '\1')
+ inflect.uncountable('series')
+ inflect.human("col_rpted_bugs", "Reported bugs")
+
+ inflect.clear :all
+
+ assert inflect.plurals.empty?
+ assert inflect.singulars.empty?
+ assert inflect.uncountables.empty?
+ assert inflect.humans.empty?
end
end
def test_clear_with_default
- with_dup do
- ActiveSupport::Inflector.inflections do |inflect|
- # ensure any data is present
- inflect.plural(/(quiz)$/i, '\1zes')
- inflect.singular(/(database)s$/i, '\1')
- inflect.uncountable('series')
- inflect.human("col_rpted_bugs", "Reported bugs")
-
- inflect.clear
-
- assert inflect.plurals.empty?
- assert inflect.singulars.empty?
- assert inflect.uncountables.empty?
- assert inflect.humans.empty?
- end
+ ActiveSupport::Inflector.inflections do |inflect|
+ # ensure any data is present
+ inflect.plural(/(quiz)$/i, '\1zes')
+ inflect.singular(/(database)s$/i, '\1')
+ inflect.uncountable('series')
+ inflect.human("col_rpted_bugs", "Reported bugs")
+
+ inflect.clear
+
+ assert inflect.plurals.empty?
+ assert inflect.singulars.empty?
+ assert inflect.uncountables.empty?
+ assert inflect.humans.empty?
end
end
Irregularities.each do |singular, plural|
define_method("test_irregularity_between_#{singular}_and_#{plural}") do
- with_dup do
- ActiveSupport::Inflector.inflections do |inflect|
- inflect.irregular(singular, plural)
- assert_equal singular, ActiveSupport::Inflector.singularize(plural)
- assert_equal plural, ActiveSupport::Inflector.pluralize(singular)
- end
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.irregular(singular, plural)
+ assert_equal singular, ActiveSupport::Inflector.singularize(plural)
+ assert_equal plural, ActiveSupport::Inflector.pluralize(singular)
end
end
end
Irregularities.each do |singular, plural|
define_method("test_pluralize_of_irregularity_#{plural}_should_be_the_same") do
- with_dup do
- ActiveSupport::Inflector.inflections do |inflect|
- inflect.irregular(singular, plural)
- assert_equal plural, ActiveSupport::Inflector.pluralize(plural)
- end
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.irregular(singular, plural)
+ assert_equal plural, ActiveSupport::Inflector.pluralize(plural)
end
end
end
Irregularities.each do |singular, plural|
define_method("test_singularize_of_irregularity_#{singular}_should_be_the_same") do
- with_dup do
- ActiveSupport::Inflector.inflections do |inflect|
- inflect.irregular(singular, plural)
- assert_equal singular, ActiveSupport::Inflector.singularize(singular)
- end
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.irregular(singular, plural)
+ assert_equal singular, ActiveSupport::Inflector.singularize(singular)
end
end
end
@@ -503,12 +501,10 @@ class InflectorTest < ActiveSupport::TestCase
%w(plurals singulars uncountables humans acronyms).each do |scope|
define_method("test_clear_inflections_with_#{scope}") do
- with_dup do
- # clear the inflections
- ActiveSupport::Inflector.inflections do |inflect|
- inflect.clear(scope)
- assert_equal [], inflect.send(scope)
- end
+ # clear the inflections
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.clear(scope)
+ assert_equal [], inflect.send(scope)
end
end
end
@@ -520,18 +516,4 @@ class InflectorTest < ActiveSupport::TestCase
assert_equal "HTTP", ActiveSupport::Inflector.pluralize("HTTP")
end
-
- # Dups the singleton and yields, restoring the original inflections later.
- # Use this in tests what modify the state of the singleton.
- #
- # This helper is implemented by setting @__instance__ because in some tests
- # there are module functions that access ActiveSupport::Inflector.inflections,
- # so we need to replace the singleton itself.
- def with_dup
- original = ActiveSupport::Inflector::Inflections.instance_variable_get(:@__instance__)[:en]
- ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original.dup)
- yield
- ensure
- ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original)
- end
end
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index 2f269a66f0..9f4b62fd8b 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -4,122 +4,24 @@ require 'active_support/core_ext/string/inflections'
require 'active_support/json'
require 'active_support/time'
require 'time_zone_test_helpers'
+require 'json/encoding_test_cases'
class TestJSONEncoding < ActiveSupport::TestCase
include TimeZoneTestHelpers
- class Foo
- def initialize(a, b)
- @a, @b = a, b
- end
- end
-
- class Hashlike
- def to_hash
- { :foo => "hello", :bar => "world" }
- end
- end
-
- class Custom
- def initialize(serialized)
- @serialized = serialized
- end
-
- def as_json(options = nil)
- @serialized
- end
- end
-
- class CustomWithOptions
- attr_accessor :foo, :bar
-
- def as_json(options={})
- options[:only] = %w(foo bar)
- super(options)
- end
- end
-
- class OptionsTest
- def as_json(options = :default)
- options
- end
- end
-
- class HashWithAsJson < Hash
- attr_accessor :as_json_called
-
- def initialize(*)
- super
- end
-
- def as_json(options={})
- @as_json_called = true
- super
- end
- end
-
- TrueTests = [[ true, %(true) ]]
- FalseTests = [[ false, %(false) ]]
- 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) ],
- [ BigDecimal('2.5'), %("#{BigDecimal('2.5')}") ]]
-
- StringTests = [[ 'this is the <string>', %("this is the \\u003cstring\\u003e")],
- [ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ],
- [ 'http://test.host/posts/1', %("http://test.host/posts/1")],
- [ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\u2028\u2029",
- %("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\\u2028\\u2029") ]]
-
- ArrayTests = [[ ['a', 'b', 'c'], %([\"a\",\"b\",\"c\"]) ],
- [ [1, 'a', :b, nil, false], %([1,\"a\",\"b\",null,false]) ]]
-
- RangeTests = [[ 1..2, %("1..2")],
- [ 1...2, %("1...2")],
- [ 1.5..2.5, %("1.5..2.5")]]
-
- SymbolTests = [[ :a, %("a") ],
- [ :this, %("this") ],
- [ :"a b", %("a b") ]]
-
- ObjectTests = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]]
- HashlikeTests = [[ Hashlike.new, %({\"bar\":\"world\",\"foo\":\"hello\"}) ]]
- CustomTests = [[ Custom.new("custom"), '"custom"' ],
- [ Custom.new(nil), 'null' ],
- [ Custom.new(:a), '"a"' ],
- [ Custom.new([ :foo, "bar" ]), '["foo","bar"]' ],
- [ Custom.new({ :foo => "hello", :bar => "world" }), '{"bar":"world","foo":"hello"}' ],
- [ Custom.new(Hashlike.new), '{"bar":"world","foo":"hello"}' ],
- [ Custom.new(Custom.new(Custom.new(:a))), '"a"' ]]
-
- RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']]
-
- 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") ]]
- StandardStringTests = [[ 'this is the <string>', %("this is the <string>")]]
-
def sorted_json(json)
return json unless json =~ /^\{.*\}$/
'{' + json[1..-2].split(',').sort.join(',') + '}'
end
- constants.grep(/Tests$/).each do |class_tests|
+ JSONTest::EncodingTestCases.constants.each do |class_tests|
define_method("test_#{class_tests[0..-6].underscore}") do
begin
prev = ActiveSupport.use_standard_json_time_format
ActiveSupport.escape_html_entities_in_json = class_tests !~ /^Standard/
ActiveSupport.use_standard_json_time_format = class_tests =~ /^Standard/
- self.class.const_get(class_tests).each do |pair|
+ JSONTest::EncodingTestCases.const_get(class_tests).each do |pair|
assert_equal pair.last, sorted_json(ActiveSupport::JSON.encode(pair.first))
end
ensure
@@ -147,6 +49,13 @@ class TestJSONEncoding < ActiveSupport::TestCase
assert_equal %({\"a\":\"b\",\"c\":\"d\"}), sorted_json(ActiveSupport::JSON.encode(:a => :b, :c => :d))
end
+ def test_hash_keys_encoding
+ ActiveSupport.escape_html_entities_in_json = true
+ assert_equal "{\"\\u003c\\u003e\":\"\\u003c\\u003e\"}", ActiveSupport::JSON.encode("<>" => "<>")
+ ensure
+ ActiveSupport.escape_html_entities_in_json = false
+ end
+
def test_utf8_string_encoded_properly
result = ActiveSupport::JSON.encode('€2.99')
assert_equal '"€2.99"', result
@@ -217,7 +126,7 @@ class TestJSONEncoding < ActiveSupport::TestCase
end
def test_hash_like_with_options
- h = Hashlike.new
+ h = JSONTest::Hashlike.new
json = h.to_json :only => [:foo]
assert_equal({"foo"=>"hello"}, JSON.parse(json))
@@ -338,6 +247,15 @@ class TestJSONEncoding < ActiveSupport::TestCase
assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
end
+ class CustomWithOptions
+ attr_accessor :foo, :bar
+
+ def as_json(options={})
+ options[:only] = %w(foo bar)
+ super(options)
+ end
+ end
+
def test_hash_to_json_should_not_keep_options_around
f = CustomWithOptions.new
f.foo = "hello"
@@ -358,6 +276,12 @@ class TestJSONEncoding < ActiveSupport::TestCase
{"foo"=>"other_foo","test"=>"other_test"}], ActiveSupport::JSON.decode(array.to_json))
end
+ class OptionsTest
+ def as_json(options = :default)
+ options
+ end
+ end
+
def test_hash_as_json_without_options
json = { foo: OptionsTest.new }.as_json
assert_equal({"foo" => :default}, json)
@@ -405,6 +329,19 @@ class TestJSONEncoding < ActiveSupport::TestCase
assert_equal false, false.as_json
end
+ class HashWithAsJson < Hash
+ attr_accessor :as_json_called
+
+ def initialize(*)
+ super
+ end
+
+ def as_json(options={})
+ @as_json_called = true
+ super
+ end
+ end
+
def test_json_gem_dump_by_passing_active_support_encoder
h = HashWithAsJson.new
h[:foo] = "hello"
diff --git a/activesupport/test/json/encoding_test_cases.rb b/activesupport/test/json/encoding_test_cases.rb
new file mode 100644
index 0000000000..0159ba8606
--- /dev/null
+++ b/activesupport/test/json/encoding_test_cases.rb
@@ -0,0 +1,88 @@
+require 'bigdecimal'
+
+module JSONTest
+ class Foo
+ def initialize(a, b)
+ @a, @b = a, b
+ end
+ end
+
+ class Hashlike
+ def to_hash
+ { :foo => "hello", :bar => "world" }
+ end
+ end
+
+ class Custom
+ def initialize(serialized)
+ @serialized = serialized
+ end
+
+ def as_json(options = nil)
+ @serialized
+ end
+ end
+
+ class MyStruct < Struct.new(:name, :value)
+ def initialize(*)
+ @unused = "unused instance variable"
+ super
+ end
+ end
+
+ module EncodingTestCases
+ TrueTests = [[ true, %(true) ]]
+ FalseTests = [[ false, %(false) ]]
+ 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) ],
+ [ BigDecimal('2.5'), %("#{BigDecimal('2.5')}") ]]
+
+ StringTests = [[ 'this is the <string>', %("this is the \\u003cstring\\u003e")],
+ [ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ],
+ [ 'http://test.host/posts/1', %("http://test.host/posts/1")],
+ [ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\u2028\u2029",
+ %("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\\u2028\\u2029") ]]
+
+ 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}) ]]
+
+ RangeTests = [[ 1..2, %("1..2")],
+ [ 1...2, %("1...2")],
+ [ 1.5..2.5, %("1.5..2.5")]]
+
+ SymbolTests = [[ :a, %("a") ],
+ [ :this, %("this") ],
+ [ :"a b", %("a b") ]]
+
+ ObjectTests = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]]
+ HashlikeTests = [[ Hashlike.new, %({\"bar\":\"world\",\"foo\":\"hello\"}) ]]
+ StructTests = [[ MyStruct.new(:foo, "bar"), %({\"name\":\"foo\",\"value\":\"bar\"}) ],
+ [ MyStruct.new(nil, nil), %({\"name\":null,\"value\":null}) ]]
+ CustomTests = [[ Custom.new("custom"), '"custom"' ],
+ [ Custom.new(nil), 'null' ],
+ [ Custom.new(:a), '"a"' ],
+ [ Custom.new([ :foo, "bar" ]), '["foo","bar"]' ],
+ [ Custom.new({ :foo => "hello", :bar => "world" }), '{"bar":"world","foo":"hello"}' ],
+ [ Custom.new(Hashlike.new), '{"bar":"world","foo":"hello"}' ],
+ [ Custom.new(Custom.new(Custom.new(:a))), '"a"' ]]
+
+ RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']]
+
+ 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") ]]
+ StandardStringTests = [[ 'this is the <string>', %("this is the <string>")]]
+ end
+end
diff --git a/activesupport/test/log_subscriber_test.rb b/activesupport/test/log_subscriber_test.rb
index 2a0e8d20ed..998a6887c5 100644
--- a/activesupport/test/log_subscriber_test.rb
+++ b/activesupport/test/log_subscriber_test.rb
@@ -82,10 +82,11 @@ class SyncLogSubscriberTest < ActiveSupport::TestCase
def test_does_not_send_the_event_if_logger_is_nil
ActiveSupport::LogSubscriber.logger = nil
- @log_subscriber.expects(:some_event).never
- ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber
- instrument "some_event.my_log_subscriber"
- wait
+ assert_not_called(@log_subscriber, :some_event) do
+ ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber
+ instrument "some_event.my_log_subscriber"
+ wait
+ end
end
def test_does_not_fail_with_non_namespaced_events
diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb
index 6c3519df9a..668d78492e 100644
--- a/activesupport/test/message_verifier_test.rb
+++ b/activesupport/test/message_verifier_test.rb
@@ -24,6 +24,7 @@ class MessageVerifierTest < ActiveSupport::TestCase
data, hash = @verifier.generate(@data).split("--")
assert !@verifier.valid_message?(nil)
assert !@verifier.valid_message?("")
+ assert !@verifier.valid_message?("\xff") # invalid encoding
assert !@verifier.valid_message?("#{data.reverse}--#{hash}")
assert !@verifier.valid_message?("#{data}--#{hash.reverse}")
assert !@verifier.valid_message?("purejunk")
diff --git a/activesupport/test/multibyte_unicode_database_test.rb b/activesupport/test/multibyte_unicode_database_test.rb
index 52ae266f01..dd33641ec2 100644
--- a/activesupport/test/multibyte_unicode_database_test.rb
+++ b/activesupport/test/multibyte_unicode_database_test.rb
@@ -11,8 +11,9 @@ class MultibyteUnicodeDatabaseTest < ActiveSupport::TestCase
UnicodeDatabase::ATTRIBUTES.each do |attribute|
define_method "test_lazy_loading_on_attribute_access_of_#{attribute}" do
- @ucd.expects(:load)
- @ucd.send(attribute)
+ assert_called(@ucd, :load) do
+ @ucd.send(attribute)
+ end
end
end
diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb
index 83efbffdfb..cb5230c4eb 100644
--- a/activesupport/test/number_helper_test.rb
+++ b/activesupport/test/number_helper_test.rb
@@ -234,15 +234,17 @@ module ActiveSupport
end
def test_number_to_human_size_with_si_prefix
- [@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_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)
+ end
end
end
diff --git a/activesupport/test/ordered_options_test.rb b/activesupport/test/ordered_options_test.rb
index fdc745b23b..18767a3536 100644
--- a/activesupport/test/ordered_options_test.rb
+++ b/activesupport/test/ordered_options_test.rb
@@ -85,4 +85,19 @@ class OrderedOptionsTest < ActiveSupport::TestCase
assert_equal 42, a.method(:blah=).call(42)
assert_equal 42, a.method(:blah).call
end
+
+ def test_raises_with_bang
+ a = ActiveSupport::OrderedOptions.new
+ a[:foo] = :bar
+ assert a.respond_to?(:foo!)
+
+ assert_nothing_raised { a.foo! }
+ assert_equal a.foo, a.foo!
+
+ assert_raises(KeyError) do
+ a.foo = nil
+ a.foo!
+ end
+ assert_raises(KeyError) { a.non_existing_key! }
+ end
end
diff --git a/activesupport/test/share_lock_test.rb b/activesupport/test/share_lock_test.rb
new file mode 100644
index 0000000000..ad41db608b
--- /dev/null
+++ b/activesupport/test/share_lock_test.rb
@@ -0,0 +1,333 @@
+require 'abstract_unit'
+require 'concurrent/atomics'
+require 'active_support/concurrency/share_lock'
+
+class ShareLockTest < ActiveSupport::TestCase
+ def setup
+ @lock = ActiveSupport::Concurrency::ShareLock.new
+ end
+
+ def test_reentrancy
+ thread = Thread.new do
+ @lock.sharing { @lock.sharing {} }
+ @lock.exclusive { @lock.exclusive {} }
+ end
+ assert_threads_not_stuck thread
+ end
+
+ def test_sharing_doesnt_block
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_latch|
+ assert_threads_not_stuck(Thread.new {@lock.sharing {} })
+ end
+ end
+
+ def test_sharing_blocks_exclusive
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ @lock.exclusive(no_wait: true) { flunk } # polling should fail
+ exclusive_thread = Thread.new { @lock.exclusive {} }
+ assert_threads_stuck_but_releasable_by_latch exclusive_thread, sharing_thread_release_latch
+ end
+ end
+
+ def test_exclusive_blocks_sharing
+ with_thread_waiting_in_lock_section(:exclusive) do |exclusive_thread_release_latch|
+ sharing_thread = Thread.new { @lock.sharing {} }
+ assert_threads_stuck_but_releasable_by_latch sharing_thread, exclusive_thread_release_latch
+ end
+ end
+
+ def test_multiple_exlusives_are_able_to_progress
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ exclusive_threads = (1..2).map do
+ Thread.new do
+ @lock.exclusive {}
+ end
+ end
+
+ assert_threads_stuck_but_releasable_by_latch exclusive_threads, sharing_thread_release_latch
+ end
+ end
+
+ def test_sharing_is_upgradeable_to_exclusive
+ upgrading_thread = Thread.new do
+ @lock.sharing do
+ @lock.exclusive {}
+ end
+ end
+ assert_threads_not_stuck upgrading_thread
+ end
+
+ def test_exclusive_upgrade_waits_for_other_sharers_to_leave
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ in_sharing = Concurrent::CountDownLatch.new
+
+ upgrading_thread = Thread.new do
+ @lock.sharing do
+ in_sharing.count_down
+ @lock.exclusive {}
+ end
+ end
+
+ in_sharing.wait
+ assert_threads_stuck_but_releasable_by_latch upgrading_thread, sharing_thread_release_latch
+ end
+ end
+
+ def test_exclusive_matching_purpose
+ [true, false].each do |use_upgrading|
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ exclusive_threads = (1..2).map do
+ Thread.new do
+ @lock.send(use_upgrading ? :sharing : :tap) do
+ @lock.exclusive(purpose: :load, compatible: [:load, :unload]) {}
+ end
+ end
+ end
+
+ assert_threads_stuck_but_releasable_by_latch exclusive_threads, sharing_thread_release_latch
+ end
+ end
+ end
+
+ def test_killed_thread_loses_lock
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ thread = Thread.new do
+ @lock.sharing do
+ @lock.exclusive {}
+ end
+ end
+
+ assert_threads_stuck thread
+ thread.kill
+
+ sharing_thread_release_latch.count_down
+
+ thread = Thread.new do
+ @lock.exclusive {}
+ end
+
+ assert_threads_not_stuck thread
+ end
+ end
+
+ def test_exclusive_conflicting_purpose
+ [true, false].each do |use_upgrading|
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ begin
+ conflicting_exclusive_threads = [
+ Thread.new do
+ @lock.send(use_upgrading ? :sharing : :tap) do
+ @lock.exclusive(purpose: :red, compatible: [:green, :purple]) {}
+ end
+ end,
+ Thread.new do
+ @lock.send(use_upgrading ? :sharing : :tap) do
+ @lock.exclusive(purpose: :blue, compatible: [:green]) {}
+ end
+ end
+ ]
+
+ assert_threads_stuck conflicting_exclusive_threads # wait for threads to get into their respective `exclusive {}` blocks
+
+ # This thread will be stuck as long as any other thread is in
+ # a sharing block. While it's blocked, it holds no lock, so it
+ # doesn't interfere with any other attempts.
+ no_purpose_thread = Thread.new do
+ @lock.exclusive {}
+ end
+ assert_threads_stuck no_purpose_thread
+
+ # This thread is compatible with both of the "primary"
+ # attempts above. It's initially stuck on the outer share
+ # lock, but as soon as that's released, it can run --
+ # regardless of whether those threads hold share locks.
+ compatible_thread = Thread.new do
+ @lock.exclusive(purpose: :green, compatible: []) {}
+ end
+ assert_threads_stuck compatible_thread
+
+ assert_threads_stuck conflicting_exclusive_threads
+
+ sharing_thread_release_latch.count_down
+
+ assert_threads_not_stuck compatible_thread # compatible thread is now able to squeak through
+
+ if use_upgrading
+ # The "primary" threads both each hold a share lock, and are
+ # mutually incompatible; they're still stuck.
+ assert_threads_stuck conflicting_exclusive_threads
+
+ # The thread without a specified purpose is also stuck; it's
+ # not compatible with anything.
+ assert_threads_stuck no_purpose_thread
+ else
+ # As the primaries didn't hold a share lock, as soon as the
+ # outer one was released, all the exclusive locks are free
+ # to be acquired in turn.
+
+ assert_threads_not_stuck conflicting_exclusive_threads
+ assert_threads_not_stuck no_purpose_thread
+ end
+ ensure
+ conflicting_exclusive_threads.each(&:kill)
+ no_purpose_thread.kill
+ end
+ end
+ end
+ end
+
+ def test_exclusive_ordering
+ scratch_pad = []
+ scratch_pad_mutex = Mutex.new
+
+ load_params = [:load, [:load]]
+ unload_params = [:unload, [:unload, :load]]
+
+ [load_params, load_params, unload_params, unload_params].permutation do |thread_params|
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ threads = thread_params.map do |purpose, compatible|
+ Thread.new do
+ @lock.sharing do
+ @lock.exclusive(purpose: purpose, compatible: compatible) do
+ scratch_pad_mutex.synchronize { scratch_pad << purpose }
+ end
+ end
+ end
+ end
+
+ sleep(0.01)
+ scratch_pad_mutex.synchronize { assert_empty scratch_pad }
+
+ sharing_thread_release_latch.count_down
+
+ assert_threads_not_stuck threads
+ scratch_pad_mutex.synchronize do
+ assert_equal [:load, :load, :unload, :unload], scratch_pad
+ scratch_pad.clear
+ end
+ end
+ end
+ end
+
+ def test_in_shared_section_incompatible_non_upgrading_threads_cannot_preempt_upgrading_threads
+ scratch_pad = []
+ scratch_pad_mutex = Mutex.new
+
+ upgrading_load_params = [:load, [:load], true]
+ non_upgrading_unload_params = [:unload, [:load, :unload], false]
+
+ [upgrading_load_params, non_upgrading_unload_params].permutation do |thread_params|
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ threads = thread_params.map do |purpose, compatible, use_upgrading|
+ Thread.new do
+ @lock.send(use_upgrading ? :sharing : :tap) do
+ @lock.exclusive(purpose: purpose, compatible: compatible) do
+ scratch_pad_mutex.synchronize { scratch_pad << purpose }
+ end
+ end
+ end
+ end
+
+ assert_threads_stuck threads
+ scratch_pad_mutex.synchronize { assert_empty scratch_pad }
+
+ sharing_thread_release_latch.count_down
+
+ assert_threads_not_stuck threads
+ scratch_pad_mutex.synchronize do
+ assert_equal [:load, :unload], scratch_pad
+ scratch_pad.clear
+ end
+ end
+ end
+ end
+
+ private
+
+ module CustomAssertions
+ SUFFICIENT_TIMEOUT = 0.2
+
+ private
+
+ def assert_threads_stuck_but_releasable_by_latch(threads, latch)
+ assert_threads_stuck threads
+ latch.count_down
+ assert_threads_not_stuck threads
+ end
+
+ def assert_threads_stuck(threads)
+ sleep(SUFFICIENT_TIMEOUT) # give threads time to do their business
+ assert(Array(threads).all? { |t| t.join(0.001).nil? })
+ end
+
+ def assert_threads_not_stuck(threads)
+ assert(Array(threads).all? { |t| t.join(SUFFICIENT_TIMEOUT) })
+ end
+ end
+
+ class CustomAssertionsTest < ActiveSupport::TestCase
+ include CustomAssertions
+
+ def setup
+ @latch = Concurrent::CountDownLatch.new
+ @thread = Thread.new { @latch.wait }
+ end
+
+ def teardown
+ @latch.count_down
+ @thread.join
+ end
+
+ def test_happy_path
+ assert_threads_stuck_but_releasable_by_latch @thread, @latch
+ end
+
+ def test_detects_stuck_thread
+ assert_raises(Minitest::Assertion) do
+ assert_threads_not_stuck @thread
+ end
+ end
+
+ def test_detects_free_thread
+ @latch.count_down
+ assert_raises(Minitest::Assertion) do
+ assert_threads_stuck @thread
+ end
+ end
+
+ def test_detects_already_released
+ @latch.count_down
+ assert_raises(Minitest::Assertion) do
+ assert_threads_stuck_but_releasable_by_latch @thread, @latch
+ end
+ end
+
+ def test_detects_remains_latched
+ another_latch = Concurrent::CountDownLatch.new
+ assert_raises(Minitest::Assertion) do
+ assert_threads_stuck_but_releasable_by_latch @thread, another_latch
+ end
+ end
+ end
+
+ include CustomAssertions
+
+ def with_thread_waiting_in_lock_section(lock_section)
+ in_section = Concurrent::CountDownLatch.new
+ section_release = Concurrent::CountDownLatch.new
+
+ stuck_thread = Thread.new do
+ @lock.send(lock_section) do
+ in_section.count_down
+ section_release.wait
+ end
+ end
+
+ in_section.wait
+
+ yield section_release
+ ensure
+ section_release.count_down
+ stuck_thread.join # clean up
+ end
+end
diff --git a/activesupport/test/testing/method_call_assertions_test.rb b/activesupport/test/testing/method_call_assertions_test.rb
new file mode 100644
index 0000000000..652cbda8da
--- /dev/null
+++ b/activesupport/test/testing/method_call_assertions_test.rb
@@ -0,0 +1,111 @@
+require 'abstract_unit'
+require 'active_support/testing/method_call_assertions'
+
+class MethodCallAssertionsTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::MethodCallAssertions
+
+ class Level
+ def increment; 1; end
+ def decrement; end
+ def <<(arg); end
+ end
+
+ setup do
+ @object = Level.new
+ end
+
+ def test_assert_called_with_defaults_to_expect_once
+ assert_called @object, :increment do
+ @object.increment
+ end
+ end
+
+ def test_assert_called_more_than_once
+ assert_called(@object, :increment, times: 2) do
+ @object.increment
+ @object.increment
+ end
+ end
+
+ def test_assert_called_failure
+ error = assert_raises(Minitest::Assertion) do
+ assert_called(@object, :increment) do
+ # Call nothing...
+ end
+ end
+
+ assert_equal "Expected increment to be called 1 times, but was called 0 times.\nExpected: 1\n Actual: 0", error.message
+ end
+
+ def test_assert_called_with_message
+ error = assert_raises(Minitest::Assertion) do
+ assert_called(@object, :increment, 'dang it') do
+ # Call nothing...
+ end
+ end
+
+ assert_match(/dang it.\nExpected increment/, error.message)
+ end
+
+ def test_assert_called_with
+ assert_called_with(@object, :increment) do
+ @object.increment
+ end
+ end
+
+ def test_assert_called_with_arguments
+ assert_called_with(@object, :<<, [ 2 ]) do
+ @object << 2
+ end
+ end
+
+ def test_assert_called_with_failure
+ assert_raises(MockExpectationError) do
+ assert_called_with(@object, :<<, [ 4567 ]) do
+ @object << 2
+ end
+ end
+ end
+
+ def test_assert_called_with_returns
+ assert_called_with(@object, :increment, returns: 1) do
+ @object.increment
+ end
+ end
+
+ def test_assert_called_with_multiple_expected_arguments
+ assert_called_with(@object, :<<, [ [ 1 ], [ 2 ] ]) do
+ @object << 1
+ @object << 2
+ end
+ end
+
+ def test_assert_not_called
+ assert_not_called(@object, :decrement) do
+ @object.increment
+ end
+ end
+
+ def test_assert_not_called_failure
+ error = assert_raises(Minitest::Assertion) do
+ assert_not_called(@object, :increment) do
+ @object.increment
+ end
+ end
+
+ assert_equal "Expected increment to be called 0 times, but was called 1 times.\nExpected: 0\n Actual: 1", error.message
+ end
+
+ def test_stub_any_instance
+ stub_any_instance(Level) do |instance|
+ assert_equal instance, Level.new
+ end
+ end
+
+ def test_stub_any_instance_with_instance
+ stub_any_instance(Level, instance: @object) do |instance|
+ assert_equal @object, instance
+ assert_equal instance, Level.new
+ end
+ end
+end
diff --git a/activesupport/test/time_travel_test.rb b/activesupport/test/time_travel_test.rb
index 676a143692..59c3e52c2f 100644
--- a/activesupport/test/time_travel_test.rb
+++ b/activesupport/test/time_travel_test.rb
@@ -1,83 +1,90 @@
require 'abstract_unit'
-require 'active_support/core_ext/date'
require 'active_support/core_ext/date_time'
require 'active_support/core_ext/numeric/time'
class TimeTravelTest < ActiveSupport::TestCase
- setup do
- Time.stubs now: Time.now
- end
-
teardown do
travel_back
end
def test_time_helper_travel
- expected_time = Time.now + 1.day
- travel 1.day
-
- assert_equal expected_time.to_s(:db), Time.now.to_s(:db)
- assert_equal expected_time.to_date, Date.today
- assert_equal expected_time.to_datetime.to_s(:db), DateTime.now.to_s(:db)
- end
-
- def test_time_helper_travel_with_block
- expected_time = Time.now + 1.day
+ Time.stub(:now, Time.now) do
+ expected_time = Time.now + 1.day
+ travel 1.day
- travel 1.day do
assert_equal expected_time.to_s(:db), Time.now.to_s(:db)
assert_equal expected_time.to_date, Date.today
assert_equal expected_time.to_datetime.to_s(:db), DateTime.now.to_s(:db)
end
-
- assert_not_equal expected_time.to_s(:db), Time.now.to_s(:db)
- assert_not_equal expected_time.to_date, Date.today
- assert_not_equal expected_time.to_datetime.to_s(:db), DateTime.now.to_s(:db)
end
- def test_time_helper_travel_to
- expected_time = Time.new(2004, 11, 24, 01, 04, 44)
- travel_to expected_time
-
- assert_equal expected_time, Time.now
- assert_equal Date.new(2004, 11, 24), Date.today
- assert_equal expected_time.to_datetime, DateTime.now
+ def test_time_helper_travel_with_block
+ Time.stub(:now, Time.now) do
+ expected_time = Time.now + 1.day
+
+ travel 1.day do
+ assert_equal expected_time.to_s(:db), Time.now.to_s(:db)
+ assert_equal expected_time.to_date, Date.today
+ assert_equal expected_time.to_datetime.to_s(:db), DateTime.now.to_s(:db)
+ end
+
+ assert_not_equal expected_time.to_s(:db), Time.now.to_s(:db)
+ assert_not_equal expected_time.to_date, Date.today
+ assert_not_equal expected_time.to_datetime.to_s(:db), DateTime.now.to_s(:db)
+ end
end
- def test_time_helper_travel_to_with_block
- expected_time = Time.new(2004, 11, 24, 01, 04, 44)
+ def test_time_helper_travel_to
+ Time.stub(:now, Time.now) do
+ expected_time = Time.new(2004, 11, 24, 01, 04, 44)
+ travel_to expected_time
- travel_to expected_time do
assert_equal expected_time, Time.now
assert_equal Date.new(2004, 11, 24), Date.today
assert_equal expected_time.to_datetime, DateTime.now
end
+ end
- assert_not_equal expected_time, Time.now
- assert_not_equal Date.new(2004, 11, 24), Date.today
- assert_not_equal expected_time.to_datetime, DateTime.now
+ def test_time_helper_travel_to_with_block
+ Time.stub(:now, Time.now) do
+ expected_time = Time.new(2004, 11, 24, 01, 04, 44)
+
+ travel_to expected_time do
+ assert_equal expected_time, Time.now
+ assert_equal Date.new(2004, 11, 24), Date.today
+ assert_equal expected_time.to_datetime, DateTime.now
+ end
+
+ assert_not_equal expected_time, Time.now
+ assert_not_equal Date.new(2004, 11, 24), Date.today
+ assert_not_equal expected_time.to_datetime, DateTime.now
+ end
end
def test_time_helper_travel_back
- expected_time = Time.new(2004, 11, 24, 01, 04, 44)
+ Time.stub(:now, Time.now) do
+ expected_time = Time.new(2004, 11, 24, 01, 04, 44)
- travel_to expected_time
- assert_equal expected_time, Time.now
- assert_equal Date.new(2004, 11, 24), Date.today
- assert_equal expected_time.to_datetime, DateTime.now
- travel_back
+ travel_to expected_time
+ assert_equal expected_time, Time.now
+ assert_equal Date.new(2004, 11, 24), Date.today
+ assert_equal expected_time.to_datetime, DateTime.now
+ travel_back
- assert_not_equal expected_time, Time.now
- assert_not_equal Date.new(2004, 11, 24), Date.today
- assert_not_equal expected_time.to_datetime, DateTime.now
+ assert_not_equal expected_time, Time.now
+ assert_not_equal Date.new(2004, 11, 24), Date.today
+ assert_not_equal expected_time.to_datetime, DateTime.now
+ end
end
def test_travel_to_will_reset_the_usec_to_avoid_mysql_rouding
- travel_to Time.utc(2014, 10, 10, 10, 10, 50, 999999) do
- assert_equal 50, Time.now.sec
- assert_equal 0, Time.now.usec
- assert_equal 50, DateTime.now.sec
- assert_equal 0, DateTime.now.usec
+ Time.stub(:now, Time.now) do
+ travel_to Time.utc(2014, 10, 10, 10, 10, 50, 999999) do
+ assert_equal 50, Time.now.sec
+ assert_equal 0, Time.now.usec
+ assert_equal 50, DateTime.now.sec
+ assert_equal 0, DateTime.now.usec
+ end
end
end
end
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index 7888b9919b..00d40c4497 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -1,6 +1,7 @@
require 'abstract_unit'
require 'active_support/time'
require 'time_zone_test_helpers'
+require 'yaml'
class TimeZoneTest < ActiveSupport::TestCase
include TimeZoneTestHelpers
@@ -252,9 +253,10 @@ class TimeZoneTest < ActiveSupport::TestCase
def test_parse_with_incomplete_date
zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
- zone.stubs(:now).returns zone.local(1999,12,31)
- twz = zone.parse('19:00:00')
- assert_equal Time.utc(1999,12,31,19), twz.time
+ 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
+ end
end
def test_parse_with_day_omitted
@@ -284,9 +286,10 @@ class TimeZoneTest < ActiveSupport::TestCase
def test_parse_with_missing_time_components
zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
- zone.stubs(:now).returns zone.local(1999, 12, 31, 12, 59, 59)
- twz = zone.parse('2012-12-01')
- assert_equal Time.utc(2012, 12, 1), twz.time
+ zone.stub(:now, zone.local(1999, 12, 31, 12, 59, 59)) do
+ twz = zone.parse('2012-12-01')
+ assert_equal Time.utc(2012, 12, 1), twz.time
+ end
end
def test_parse_with_javascript_date
@@ -311,6 +314,80 @@ class TimeZoneTest < ActiveSupport::TestCase
end
end
+ 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 zone, twz.time_zone
+ end
+
+ def test_strptime_with_nondefault_time_zone
+ 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 zone, twz.time_zone
+ end
+ end
+
+ 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 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 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 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 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 zone, twz.time_zone
+ end
+
+ def test_strptime_with_day_omitted
+ with_env_tz 'US/Eastern' do
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ assert_equal Time.local(2000, 2, 1), zone.strptime('Feb', '%b', Time.local(2000, 1, 1))
+ assert_equal Time.local(2005, 2, 1), zone.strptime('Feb 2005', '%b %Y', Time.local(2000, 1, 1))
+ assert_equal Time.local(2005, 2, 2), zone.strptime('2 Feb 2005', '%e %b %Y', Time.local(2000, 1, 1))
+ 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)
@@ -413,4 +490,13 @@ class TimeZoneTest < ActiveSupport::TestCase
assert ActiveSupport::TimeZone.us_zones.include?(ActiveSupport::TimeZone["Hawaii"])
assert !ActiveSupport::TimeZone.us_zones.include?(ActiveSupport::TimeZone["Kuala Lumpur"])
end
+
+ def test_to_yaml
+ assert_equal("--- !ruby/object:ActiveSupport::TimeZone\nname: Pacific/Honolulu\n", ActiveSupport::TimeZone["Hawaii"].to_yaml)
+ assert_equal("--- !ruby/object:ActiveSupport::TimeZone\nname: Europe/London\n", ActiveSupport::TimeZone["Europe/London"].to_yaml)
+ end
+
+ def test_yaml_load
+ assert_equal(ActiveSupport::TimeZone["Pacific/Honolulu"], YAML.load("--- !ruby/object:ActiveSupport::TimeZone\nname: Pacific/Honolulu\n"))
+ end
end
diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb
index bcd6997b06..55e8181b54 100644
--- a/activesupport/test/xml_mini_test.rb
+++ b/activesupport/test/xml_mini_test.rb
@@ -1,9 +1,9 @@
require 'abstract_unit'
require 'active_support/xml_mini'
require 'active_support/builder'
-require 'active_support/core_ext/array'
require 'active_support/core_ext/hash'
require 'active_support/core_ext/big_decimal'
+require 'yaml'
module XmlMiniTest
class RenameKeyTest < ActiveSupport::TestCase
diff --git a/ci/travis.rb b/ci/travis.rb
index b62f90a59e..52fef05fbf 100755
--- a/ci/travis.rb
+++ b/ci/travis.rb
@@ -16,13 +16,14 @@ end
class Build
MAP = {
'railties' => 'railties',
- 'ap' => 'actionpack',
- 'am' => 'actionmailer',
- 'amo' => 'activemodel',
- 'as' => 'activesupport',
- 'ar' => 'activerecord',
- 'av' => 'actionview',
- 'aj' => 'activejob'
+ 'ap' => 'actionpack',
+ 'am' => 'actionmailer',
+ 'amo' => 'activemodel',
+ 'as' => 'activesupport',
+ 'ar' => 'activerecord',
+ 'av' => 'actionview',
+ 'aj' => 'activejob',
+ 'guides' => 'guides'
}
attr_reader :component, :options
@@ -36,7 +37,11 @@ class Build
self.options.update(options)
Dir.chdir(dir) do
announce(heading)
- rake(*tasks)
+ if guides?
+ run_bug_report_templates
+ else
+ rake(*tasks)
+ end
end
end
@@ -71,6 +76,10 @@ class Build
gem == 'activerecord'
end
+ def guides?
+ gem == 'guides'
+ end
+
def isolated?
options[:isolated]
end
@@ -96,6 +105,12 @@ class Build
end
true
end
+
+ def run_bug_report_templates
+ Dir.glob('bug_report_templates/*.rb').all? do |file|
+ system(Gem.ruby, '-w', file)
+ end
+ end
end
if ENV['GEM']=='aj:integration'
@@ -110,6 +125,7 @@ ENV['GEM'].split(',').each do |gem|
next if ENV['TRAVIS_PULL_REQUEST'] && ENV['TRAVIS_PULL_REQUEST'] != 'false' && isolated
next if gem == 'railties' && isolated
next if gem == 'aj:integration' && isolated
+ next if gem == 'guides' && isolated
build = Build.new(gem, :isolated => isolated)
results[build.key] = build.run!
diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md
index dd5ca4b395..fd177b4238 100644
--- a/guides/CHANGELOG.md
+++ b/guides/CHANGELOG.md
@@ -1,3 +1,7 @@
+* New section in Configuring: Configuring Active Job
+
+ *Eliot Sykes*
+
* New section in Active Record Association Basics: Single Table Inheritance
*Andrey Nering*
diff --git a/guides/Rakefile b/guides/Rakefile
index 3c2099ac02..00577377d7 100644
--- a/guides/Rakefile
+++ b/guides/Rakefile
@@ -7,7 +7,7 @@ namespace :guides do
desc "Generate HTML guides"
task :html do
- ENV["WARN_BROKEN_LINKS"] = "1" # authors can't disable this
+ ENV["WARNINGS"] = "1" # authors can't disable this
ruby "rails_guides.rb"
end
diff --git a/guides/assets/images/getting_started/rails_welcome.png b/guides/assets/images/getting_started/rails_welcome.png
index 3e07c948a0..4d0cb417b7 100644
--- a/guides/assets/images/getting_started/rails_welcome.png
+++ b/guides/assets/images/getting_started/rails_welcome.png
Binary files differ
diff --git a/guides/bug_report_templates/action_controller_gem.rb b/guides/bug_report_templates/action_controller_gem.rb
index 032e6bfe11..58ba708a39 100644
--- a/guides/bug_report_templates/action_controller_gem.rb
+++ b/guides/bug_report_templates/action_controller_gem.rb
@@ -1,7 +1,16 @@
-# Activate the gem you are reporting the issue against.
-gem 'rails', '4.2.0'
+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 'rails', '4.2.0'
+end
-require 'rails'
require 'rack/test'
require 'action_controller/railtie'
@@ -23,7 +32,7 @@ class TestController < ActionController::Base
include Rails.application.routes.url_helpers
def index
- render text: 'Home'
+ render plain: 'Home'
end
end
diff --git a/guides/bug_report_templates/action_controller_master.rb b/guides/bug_report_templates/action_controller_master.rb
index 9be8130884..1a4b736348 100644
--- a/guides/bug_report_templates/action_controller_master.rb
+++ b/guides/bug_report_templates/action_controller_master.rb
@@ -1,17 +1,16 @@
-unless File.exist?('Gemfile')
- File.write('Gemfile', <<-GEMFILE)
- source 'https://rubygems.org'
- gem 'rails', github: 'rails/rails'
- gem 'arel', github: 'rails/arel'
- GEMFILE
-
- system 'bundle'
+begin
+ require 'bundler/inline'
+rescue LoadError => e
+ $stderr.puts 'Bundler version 1.10 or later is required. Please update your Bundler'
+ raise e
end
-require 'bundler'
-Bundler.setup(:default)
+gemfile(true) do
+ source 'https://rubygems.org'
+ gem 'rails', github: 'rails/rails'
+ gem 'arel', github: 'rails/arel'
+end
-require 'rails'
require 'action_controller/railtie'
class TestApp < Rails::Application
@@ -32,7 +31,7 @@ class TestController < ActionController::Base
include Rails.application.routes.url_helpers
def index
- render text: 'Home'
+ render plain: 'Home'
end
end
diff --git a/guides/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb
index b295d9d21f..09d6e7b331 100644
--- a/guides/bug_report_templates/active_record_gem.rb
+++ b/guides/bug_report_templates/active_record_gem.rb
@@ -1,5 +1,17 @@
-# Activate the gem you are reporting the issue against.
-gem 'activerecord', '4.2.0'
+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', '4.2.0'
+ gem 'sqlite3'
+end
+
require 'active_record'
require 'minitest/autorun'
require 'logger'
diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb
index 9557f0b7c5..270dbe7df7 100644
--- a/guides/bug_report_templates/active_record_master.rb
+++ b/guides/bug_report_templates/active_record_master.rb
@@ -1,16 +1,16 @@
-unless File.exist?('Gemfile')
- File.write('Gemfile', <<-GEMFILE)
- source 'https://rubygems.org'
- gem 'rails', github: 'rails/rails'
- gem 'arel', github: 'rails/arel'
- gem 'sqlite3'
- GEMFILE
-
- system 'bundle'
+begin
+ require 'bundler/inline'
+rescue LoadError => e
+ $stderr.puts 'Bundler version 1.10 or later is required. Please update your Bundler'
+ raise e
end
-require 'bundler'
-Bundler.setup(:default)
+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'
diff --git a/guides/bug_report_templates/generic_gem.rb b/guides/bug_report_templates/generic_gem.rb
new file mode 100644
index 0000000000..a4fe51156d
--- /dev/null
+++ b/guides/bug_report_templates/generic_gem.rb
@@ -0,0 +1,25 @@
+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 'activesupport', '4.2.0'
+end
+
+require 'active_support/core_ext/object/blank'
+require 'minitest/autorun'
+
+# Ensure backward compatibility with Minitest 4
+Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
+
+class BugTest < Minitest::Test
+ def test_stuff
+ assert "zomg".present?
+ refute "".present?
+ end
+end
diff --git a/guides/bug_report_templates/generic_master.rb b/guides/bug_report_templates/generic_master.rb
new file mode 100644
index 0000000000..b6b4562751
--- /dev/null
+++ b/guides/bug_report_templates/generic_master.rb
@@ -0,0 +1,26 @@
+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_support'
+require 'active_support/core_ext/object/blank'
+require 'minitest/autorun'
+
+# Ensure backward compatibility with Minitest 4
+Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
+
+class BugTest < Minitest::Test
+ def test_stuff
+ assert "zomg".present?
+ refute "".present?
+ end
+end
diff --git a/guides/rails_guides/levenshtein.rb b/guides/rails_guides/levenshtein.rb
index 36183fd321..049f633258 100644
--- a/guides/rails_guides/levenshtein.rb
+++ b/guides/rails_guides/levenshtein.rb
@@ -14,10 +14,13 @@ module RailsGuides
d = (0..m).to_a
x = nil
- str1.each_char.each_with_index do |char1,i|
+ # 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.each_char.each_with_index do |char2,j|
+ str2_codepoint_enumerable.with_index do |char2, j|
cost = (char1 == char2) ? 0 : 1
x = [
d[j+1] + 1, # insertion
diff --git a/guides/source/3_0_release_notes.md b/guides/source/3_0_release_notes.md
index 9ad32e8168..696493a3cf 100644
--- a/guides/source/3_0_release_notes.md
+++ b/guides/source/3_0_release_notes.md
@@ -88,7 +88,7 @@ $ cd myapp
Rails now uses a `Gemfile` in the application root to determine the gems you require for your application to start. This `Gemfile` is processed by the [Bundler](http://github.com/carlhuda/bundler,) which then installs all your dependencies. It can even install all the dependencies locally to your application so that it doesn't depend on the system gems.
-More information: - [bundler homepage](http://gembundler.com)
+More information: - [bundler homepage](http://bundler.io/)
### Living on the Edge
diff --git a/guides/source/3_1_release_notes.md b/guides/source/3_1_release_notes.md
index 537aa5a371..327495704a 100644
--- a/guides/source/3_1_release_notes.md
+++ b/guides/source/3_1_release_notes.md
@@ -151,7 +151,7 @@ $ cd myapp
Rails now uses a `Gemfile` in the application root to determine the gems you require for your application to start. This `Gemfile` is processed by the [Bundler](https://github.com/carlhuda/bundler) gem, which then installs all your dependencies. It can even install all the dependencies locally to your application so that it doesn't depend on the system gems.
-More information: - [bundler homepage](http://gembundler.com)
+More information: - [bundler homepage](http://bundler.io/)
### Living on the Edge
@@ -199,7 +199,7 @@ Railties
* jQuery is the new default JavaScript library.
-* jQuery and Prototype are no longer vendored and is provided from now on by the jquery-rails and prototype-rails gems.
+* jQuery and Prototype are no longer vendored and is provided from now on by the `jquery-rails` and `prototype-rails` gems.
* The application generator accepts an option `-j` which can be an arbitrary string. If passed "foo", the gem "foo-rails" is added to the `Gemfile`, and the application JavaScript manifest requires "foo" and "foo_ujs". Currently only "prototype-rails" and "jquery-rails" exist and provide those files via the asset pipeline.
diff --git a/guides/source/3_2_release_notes.md b/guides/source/3_2_release_notes.md
index 6ddf77d9c0..c52c39b705 100644
--- a/guides/source/3_2_release_notes.md
+++ b/guides/source/3_2_release_notes.md
@@ -81,7 +81,7 @@ $ cd myapp
Rails now uses a `Gemfile` in the application root to determine the gems you require for your application to start. This `Gemfile` is processed by the [Bundler](https://github.com/carlhuda/bundler) gem, which then installs all your dependencies. It can even install all the dependencies locally to your application so that it doesn't depend on the system gems.
-More information: [Bundler homepage](http://gembundler.com)
+More information: [Bundler homepage](http://bundler.io/)
### Living on the Edge
diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md
index 9feaff098a..b9444510ea 100644
--- a/guides/source/4_0_release_notes.md
+++ b/guides/source/4_0_release_notes.md
@@ -36,7 +36,7 @@ $ cd myapp
Rails now uses a `Gemfile` in the application root to determine the gems you require for your application to start. This `Gemfile` is processed by the [Bundler](https://github.com/carlhuda/bundler) gem, which then installs all your dependencies. It can even install all the dependencies locally to your application so that it doesn't depend on the system gems.
-More information: [Bundler homepage](http://gembundler.com)
+More information: [Bundler homepage](http://bundler.io)
### Living on the Edge
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index 8c1551f4a1..09fbdc0d32 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -185,7 +185,9 @@ end
These options will be used as a starting point when generating URLs, so it's possible they'll be overridden by the options passed to `url_for` calls.
-If you define `default_url_options` in `ApplicationController`, as in the example above, it will be used for all URL generation. The method can also be defined in a specific controller, in which case it only affects URLs generated there.
+If you define `default_url_options` in `ApplicationController`, as in the example above, these defaults will be used for all URL generation. The method can also be defined in a specific controller, in which case it only affects URLs generated there.
+
+In a given request, the method is not actually called for every single generated URL; for performance reasons, the returned hash is cached, there is at most one invocation per request.
### Strong Parameters
@@ -667,11 +669,11 @@ You may notice in the above code that we're using `render xml: @users`, not `ren
Filters
-------
-Filters are methods that are run before, after or "around" a controller action.
+Filters are methods that are run "before", "after" or "around" a controller action.
Filters are inherited, so if you set a filter on `ApplicationController`, it will be run on every controller in your application.
-"Before" filters may halt the request cycle. A common "before" filter is one which requires that a user is logged in for an action to be run. You can define the filter method this way:
+"before" filters may halt the request cycle. A common "before" filter is one which requires that a user is logged in for an action to be run. You can define the filter method this way:
```ruby
class ApplicationController < ActionController::Base
@@ -704,9 +706,9 @@ Now, the `LoginsController`'s `new` and `create` actions will work as before wit
In addition to "before" filters, you can also run filters after an action has been executed, or both before and after.
-"After" filters are similar to "before" filters, but because the action has already been run they have access to the response data that's about to be sent to the client. Obviously, "after" filters cannot stop the action from running.
+"after" filters are similar to "before" filters, but because the action has already been run they have access to the response data that's about to be sent to the client. Obviously, "after" filters cannot stop the action from running.
-"Around" filters are responsible for running their associated actions by yielding, similar to how Rack middlewares work.
+"around" filters are responsible for running their associated actions by yielding, similar to how Rack middlewares work.
For example, in a website where changes have an approval workflow an administrator could be able to preview them easily, just apply them within a transaction:
@@ -736,7 +738,7 @@ You can choose not to yield and build the response yourself, in which case the a
While the most common way to use filters is by creating private methods and using *_action to add them, there are two other ways to do the same thing.
-The first is to use a block directly with the *\_action methods. The block receives the controller as an argument, and the `require_login` filter from above could be rewritten to use a block:
+The first is to use a block directly with the *\_action methods. The block receives the controller as an argument. The `require_login` filter from above could be rewritten to use a block:
```ruby
class ApplicationController < ActionController::Base
@@ -749,7 +751,7 @@ class ApplicationController < ActionController::Base
end
```
-Note that the filter in this case uses `send` because the `logged_in?` method is private and the filter is not run in the scope of the controller. This is not the recommended way to implement this particular filter, but in more simple cases it might be useful.
+Note that the filter in this case uses `send` because the `logged_in?` method is private and the filter does not run in the scope of the controller. This is not the recommended way to implement this particular filter, but in more simple cases it might be useful.
The second way is to use a class (actually, any object that responds to the right methods will do) to handle the filtering. This is useful in cases that are more complex and cannot be implemented in a readable and reusable way using the two other methods. As an example, you could rewrite the login filter again to use a class:
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index 73b240ff2c..c39cd34e9a 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -326,7 +326,7 @@ key. The list of emails can be an array of email addresses or a single string
with the addresses separated by commas.
```ruby
-class AdminMailer < ActionMailer::Base
+class AdminMailer < ApplicationMailer
default to: Proc.new { Admin.pluck(:email) },
from: 'notification@example.com'
@@ -344,7 +344,7 @@ The same format can be used to set carbon copy (Cc:) and blind carbon copy
Sometimes you wish to show the name of the person instead of just their email
address when they receive the email. The trick to doing that is to format the
-email address in the format `"Full Name <email>"`.
+email address in the format `"Full Name" <email>`.
```ruby
def welcome_email(user)
@@ -503,7 +503,7 @@ You will need to use:
By using the full URL, your links will now work in your emails.
-#### generating URLs with `url_for`
+#### Generating URLs with `url_for`
`url_for` generate full URL by default in templates.
@@ -517,7 +517,7 @@ If you did not configure the `:host` option globally make sure to pass it to
action: 'greeting') %>
```
-#### generating URLs with named routes
+#### Generating URLs with Named Routes
Email clients have no web context and so paths have no base URL to form complete
web addresses. Thus, you should always use the "_url" variant of named route
@@ -530,6 +530,27 @@ url helper.
<%= user_url(@user, host: 'example.com') %>
```
+NOTE: non-`GET` links require [jQuery UJS](https://github.com/rails/jquery-ujs)
+and won't work in mailer templates. They will result in normal `GET` requests.
+
+### Adding images in Action Mailer Views
+
+Unlike controllers, the mailer instance doesn't have any context about the
+incoming request so you'll need to provide the `:asset_host` parameter yourself.
+
+As the `:asset_host` usually is consistent across the application you can
+configure it globally in config/application.rb:
+
+```ruby
+config.action_mailer.asset_host = 'http://example.com'
+```
+
+Now you can display an image inside your email.
+
+```ruby
+<%= image_tag 'image.jpg' %>
+```
+
### Sending Multipart Emails
Action Mailer will automatically send multipart emails if you have different
@@ -693,7 +714,7 @@ files (environment.rb, production.rb, etc...)
| Configuration | Description |
|---------------|-------------|
|`logger`|Generates information on the mailing run if available. Can be set to `nil` for no logging. Compatible with both Ruby's own `Logger` and `Log4r` loggers.|
-|`smtp_settings`|Allows detailed configuration for `:smtp` delivery method:<ul><li>`:address` - Allows you to use a remote mail server. Just change it from its default `"localhost"` setting.</li><li>`:port` - On the off chance that your mail server doesn't run on port 25, you can change it.</li><li>`:domain` - If you need to specify a HELO domain, you can do it here.</li><li>`:user_name` - If your mail server requires authentication, set the username in this setting.</li><li>`:password` - If your mail server requires authentication, set the password in this setting.</li><li>`:authentication` - If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of `:plain`, `:login`, `:cram_md5`.</li><li>`:enable_starttls_auto` - Set this to `false` if there is a problem with your server certificate that you cannot resolve.</li></ul>|
+|`smtp_settings`|Allows detailed configuration for `:smtp` delivery method:<ul><li>`:address` - Allows you to use a remote mail server. Just change it from its default `"localhost"` setting.</li><li>`:port` - On the off chance that your mail server doesn't run on port 25, you can change it.</li><li>`:domain` - If you need to specify a HELO domain, you can do it here.</li><li>`:user_name` - If your mail server requires authentication, set the username in this setting.</li><li>`:password` - If your mail server requires authentication, set the password in this setting.</li><li>`:authentication` - If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of `:plain` (will send the password in the clear), `:login` (will send password Base64 encoded) or `:cram_md5` (combines a Challenge/Response mechanism to exchange information and a cryptographic Message Digest 5 algorithm to hash important information)</li><li>`:enable_starttls_auto` - Detects if STARTTLS is enabled in your SMTP server and starts to use it. Defaults to `true`.</li><li>`:openssl_verify_mode` - When using TLS, you can set how OpenSSL checks the certificate. This is really useful if you need to validate a self-signed and/or a wildcard certificate. You can use the name of an OpenSSL verify constant ('none', 'peer', 'client_once', 'fail_if_no_peer_cert') or directly the constant (`OpenSSL::SSL::VERIFY_NONE`, `OpenSSL::SSL::VERIFY_PEER`, ...).</li></ul>|
|`sendmail_settings`|Allows you to override options for the `:sendmail` delivery method.<ul><li>`:location` - The location of the sendmail executable. Defaults to `/usr/sbin/sendmail`.</li><li>`:arguments` - The command line arguments to be passed to sendmail. Defaults to `-i -t`.</li></ul>|
|`raise_delivery_errors`|Whether or not errors should be raised if the email fails to be delivered. This only works if the external email server is configured for immediate delivery.|
|`delivery_method`|Defines a delivery method. Possible values are:<ul><li>`:smtp` (default), can be configured by using `config.action_mailer.smtp_settings`.</li><li>`:sendmail`, can be configured by using `config.action_mailer.sendmail_settings`.</li><li>`:file`: save emails to files; can be configured by using `config.action_mailer.file_settings`.</li><li>`:test`: save emails to `ActionMailer::Base.deliveries` array.</li></ul>See [API docs](http://api.rubyonrails.org/classes/ActionMailer/Base.html) for more info.|
@@ -738,6 +759,9 @@ config.action_mailer.smtp_settings = {
authentication: 'plain',
enable_starttls_auto: true }
```
+Note: As of July 15, 2014, Google increased [its security measures](https://support.google.com/accounts/answer/6010255) and now blocks attempts from apps it deems less secure.
+You can change your gmail settings [here](https://www.google.com/settings/security/lesssecureapps) to allow the attempts or
+use another ESP to send email by replacing 'smpt.gmail.com' above with the address of your provider.
Mailer Testing
--------------
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index 02ef32d66e..db7eeed19a 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -15,7 +15,7 @@ After reading this guide, you will know:
What is Action View?
--------------------
-Action View and Action Controller are the two major components of Action Pack. In Rails, web requests are handled by Action Pack, which splits the work into a controller part (performing the logic) and a view part (rendering a template). Typically, Action Controller will be concerned with communicating with the database and performing CRUD actions where necessary. Action View is then responsible for compiling the response.
+In Rails, web requests are handled by [Action Controller](action_controller_overview.html) and Action View. Typically, Action Controller will be concerned with communicating with the database and performing CRUD actions where necessary. Action View is then responsible for compiling the response.
Action View templates are written using embedded Ruby in tags mingled with HTML. To avoid cluttering the templates with boilerplate code, a number of helper classes provide common behavior for forms, dates, and strings. It's also easy to add new helpers to your application as it evolves.
@@ -214,7 +214,8 @@ By default `ActionView::Partials::PartialRenderer` has its object in a local var
<%= render partial: "product" %>
```
-within product we'll get `@product` in the local variable `product`, as if we had written:
+within `_product` partial we'll get `@product` in the local variable `product`,
+as if we had written:
```erb
<%= render partial: "product", locals: { product: @product } %>
@@ -317,26 +318,6 @@ The `box` layout simply wraps the `_article` partial in a `div`:
</div>
```
-The `_article` partial wraps the article's `body` in a `div` with the `id` of the article using the `div_for` helper:
-
-**articles/_article.html.erb**
-
-```html+erb
-<%= div_for(article) do %>
- <p><%= article.body %></p>
-<% end %>
-```
-
-this would output the following:
-
-```html
-<div class='box'>
- <div id='article_1'>
- <p>Partial Layouts are cool!</p>
- </div>
-</div>
-```
-
Note that the partial layout has access to the local `article` variable that was passed into the `render` call. However, unlike application-wide layouts, partial layouts still have the underscore prefix.
You can also render a block of code within a partial layout instead of calling `yield`. For example, if we didn't have the `_article` partial, we could do this instead:
@@ -345,9 +326,9 @@ You can also render a block of code within a partial layout instead of calling `
```html+erb
<% render(layout: 'box', locals: { article: @article }) do %>
- <%= div_for(article) do %>
+ <div>
<p><%= article.body %></p>
- <% end %>
+ </div>
<% end %>
```
@@ -356,50 +337,51 @@ Supposing we use the same `_box` partial from above, this would produce the same
View Paths
----------
-TODO...
+When rendering a response, the controller needs to resolve where the different
+views are located. By default it only looks inside the `app/views` directory.
-Overview of helpers provided by Action View
--------------------------------------------
+We can add other locations and give them a certain precedence when resolving
+paths using the `prepend_view_path` and `append_view_path` methods.
-WIP: Not all the helpers are listed here. For a full list see the [API documentation](http://api.rubyonrails.org/classes/ActionView/Helpers.html)
+### Prepend view path
-The following is only a brief overview summary of the helpers available in Action View. It's recommended that you review the [API Documentation](http://api.rubyonrails.org/classes/ActionView/Helpers.html), which covers all of the helpers in more detail, but this should serve as a good starting point.
+This can be helpful for example, when we want to put views inside a different
+directory for subdomains.
-### AssetTagHelper
+We can do this by using:
-This module provides methods for generating HTML that links views to assets such as images, JavaScript files, stylesheets, and feeds.
+```ruby
+prepend_view_path "app/views/#{request.subdomain}"
+```
-By default, Rails links to these assets on the current host in the public folder, but you can direct Rails to link to assets from a dedicated assets server by setting `config.action_controller.asset_host` in the application configuration, typically in `config/environments/production.rb`. For example, let's say your asset host is `assets.example.com`:
+Then Action View will look first in this directory when resolving views.
+
+### Append view path
+
+Similarly, we can append paths:
```ruby
-config.action_controller.asset_host = "assets.example.com"
-image_tag("rails.png") # => <img src="http://assets.example.com/images/rails.png" alt="Rails" />
+append_view_path "app/views/direct"
```
-#### register_javascript_expansion
+This will add `app/views/direct` to the end of the lookup paths.
+
+Overview of helpers provided by Action View
+-------------------------------------------
-Register one or more JavaScript files to be included when symbol is passed to javascript_include_tag. This method is typically intended to be called from plugin initialization to register JavaScript files that the plugin installed in `vendor/assets/javascripts`.
+WIP: Not all the helpers are listed here. For a full list see the [API documentation](http://api.rubyonrails.org/classes/ActionView/Helpers.html)
-```ruby
-ActionView::Helpers::AssetTagHelper.register_javascript_expansion monkey: ["head", "body", "tail"]
+The following is only a brief overview summary of the helpers available in Action View. It's recommended that you review the [API Documentation](http://api.rubyonrails.org/classes/ActionView/Helpers.html), which covers all of the helpers in more detail, but this should serve as a good starting point.
-javascript_include_tag :monkey # =>
- <script src="/assets/head.js"></script>
- <script src="/assets/body.js"></script>
- <script src="/assets/tail.js"></script>
-```
+### AssetTagHelper
-#### register_stylesheet_expansion
+This module provides methods for generating HTML that links views to assets such as images, JavaScript files, stylesheets, and feeds.
-Register one or more stylesheet files to be included when symbol is passed to `stylesheet_link_tag`. This method is typically intended to be called from plugin initialization to register stylesheet files that the plugin installed in `vendor/assets/stylesheets`.
+By default, Rails links to these assets on the current host in the public folder, but you can direct Rails to link to assets from a dedicated assets server by setting `config.action_controller.asset_host` in the application configuration, typically in `config/environments/production.rb`. For example, let's say your asset host is `assets.example.com`:
```ruby
-ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion monkey: ["head", "body", "tail"]
-
-stylesheet_link_tag :monkey # =>
- <link href="/assets/head.css" media="screen" rel="stylesheet" />
- <link href="/assets/body.css" media="screen" rel="stylesheet" />
- <link href="/assets/tail.css" media="screen" rel="stylesheet" />
+config.action_controller.asset_host = "assets.example.com"
+image_tag("rails.png") # => <img src="http://assets.example.com/images/rails.png" alt="Rails" />
```
#### auto_discovery_link_tag
@@ -492,7 +474,7 @@ Returns a stylesheet link tag for the sources specified as arguments. If you don
stylesheet_link_tag "application" # => <link href="/assets/application.css" media="screen" rel="stylesheet" />
```
-You can also include all styles in the stylesheet directory using :all as the source:
+You can also include all styles in the stylesheet directory using `:all` as the source:
```ruby
stylesheet_link_tag :all
@@ -507,7 +489,7 @@ stylesheet_link_tag :all, cache: true
#### stylesheet_path
-Computes the path to a stylesheet asset in the `app/assets/stylesheets` directory. If the source filename has no extension, .css will be appended. Full paths from the document root will be passed through. Used internally by stylesheet_link_tag to build the stylesheet path.
+Computes the path to a stylesheet asset in the `app/assets/stylesheets` directory. If the source filename has no extension, `.css` will be appended. Full paths from the document root will be passed through. Used internally by stylesheet_link_tag to build the stylesheet path.
```ruby
stylesheet_path "application" # => /assets/application.css
@@ -551,7 +533,7 @@ end
```ruby
atom_feed do |feed|
feed.title("Articles Index")
- feed.updated((@articles.first.created_at))
+ feed.updated(@articles.first.created_at)
@articles.each do |article|
feed.entry(article) do |entry|
@@ -725,7 +707,7 @@ Returns a select tag with options for each of the minutes 0 through 59 with the
```ruby
# Generates a select field for minutes that defaults to the minutes for the time provided.
-select_minute(Time.now + 6.hours)
+select_minute(Time.now + 10.minutes)
```
#### select_month
@@ -743,7 +725,7 @@ Returns a select tag with options for each of the seconds 0 through 59 with the
```ruby
# Generates a select field for seconds that defaults to the seconds for the time provided
-select_second(Time.now + 16.minutes)
+select_second(Time.now + 16.seconds)
```
#### select_time
@@ -808,9 +790,9 @@ third:
Form helpers are designed to make working with models much easier compared to using just standard HTML elements by providing a set of methods for creating forms based on your models. This helper generates the HTML for forms, providing a method for each sort of input (e.g., text, password, select, and so on). When the form is submitted (i.e., when the user hits the submit button or form.submit is called via JavaScript), the form inputs will be bundled into the params object and passed back to the controller.
-There are two types of form helpers: those that specifically work with model attributes and those that don't. This helper deals with those that work with model attributes; to see an example of form helpers that don't work with model attributes, check the ActionView::Helpers::FormTagHelper documentation.
+There are two types of form helpers: those that specifically work with model attributes and those that don't. This helper deals with those that work with model attributes; to see an example of form helpers that don't work with model attributes, check the `ActionView::Helpers::FormTagHelper` documentation.
-The core method of this helper, form_for, gives you the ability to create a form for a model instance; for example, let's say that you have a model Person and want to create a new instance of it:
+The core method of this helper, `form_for`, gives you the ability to create a form for a model instance; for example, let's say that you have a model Person and want to create a new instance of it:
```html+erb
# Note: a @person variable will have been created in the controller (e.g. @person = Person.new)
@@ -852,7 +834,7 @@ check_box("article", "validated")
#### fields_for
-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:
+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:
```html+erb
<%= form_for @person, url: { action: "update" } do |person_form| %>
@@ -1077,14 +1059,6 @@ If `@article.author_ids` is [1], this would return:
<input name="article[author_ids][]" type="hidden" value="" />
```
-#### country_options_for_select
-
-Returns a string of option tags for pretty much any country in the world.
-
-#### country_select
-
-Returns select and option tags for the given object and method, using country_options_for_select to generate the list of option tags.
-
#### option_groups_from_collection_for_select
Returns a string of `option` tags, like `options_from_collection_for_select`, but groups them by `optgroup` tags based on the object relationships of the arguments.
@@ -1146,7 +1120,7 @@ Returns a string of option tags that have been compiled by iterating over the `c
# options_from_collection_for_select(collection, value_method, text_method, selected = nil)
```
-For example, imagine a loop iterating over each person in @project.people to generate an input tag:
+For example, imagine a loop iterating over each person in `@project.people` to generate an input tag:
```ruby
options_from_collection_for_select(@project.people, "id", "name")
@@ -1171,8 +1145,8 @@ If `@article.person_id` is 1, this would become:
<select name="article[person_id]">
<option value=""></option>
<option value="1" selected="selected">David</option>
- <option value="2">Sam</option>
- <option value="3">Tobias</option>
+ <option value="2">Eileen</option>
+ <option value="3">Rafael</option>
</select>
```
@@ -1361,22 +1335,6 @@ date_field_tag "dob"
Provides functionality for working with JavaScript in your views.
-#### button_to_function
-
-Returns a button that'll trigger a JavaScript function using the onclick handler. Examples:
-
-```ruby
-button_to_function "Greeting", "alert('Hello world!')"
-button_to_function "Delete", "if (confirm('Really?')) do_delete()"
-button_to_function "Details" do |page|
- page[:details].visual_effect :toggle_slide
-end
-```
-
-#### define_javascript_functions
-
-Includes the Action Pack JavaScript libraries inside a single `script` tag.
-
#### escape_javascript
Escape carrier returns and single and double quotes for JavaScript segments.
@@ -1397,15 +1355,6 @@ alert('All is good')
</script>
```
-#### link_to_function
-
-Returns a link that will trigger a JavaScript function using the onclick handler and return false after the fact.
-
-```ruby
-link_to_function "Greeting", "alert('Hello world!')"
-# => <a onclick="alert('Hello world!'); return false;" href="#">Greeting</a>
-```
-
### NumberHelper
Provides methods for converting numbers into formatted strings. Methods are provided for phone numbers, currency, percentage, precision, positional notation, and file size.
@@ -1472,7 +1421,7 @@ This sanitize helper will HTML encode all tags and strip all attributes that are
sanitize @article.body
```
-If either the :attributes or :tags options are passed, only the mentioned tags and attributes are allowed and nothing else.
+If either the `:attributes` or `:tags` options are passed, only the mentioned attributes and tags are allowed and nothing else.
```ruby
sanitize @article.body, tags: %w(table tr td), attributes: %w(id class style)
diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md
index 953c29719d..e3502d7363 100644
--- a/guides/source/active_job_basics.md
+++ b/guides/source/active_job_basics.md
@@ -4,14 +4,14 @@ Active Job Basics
=================
This guide provides you with all you need to get started in creating,
-enqueueing and executing background jobs.
+enqueuing and executing background jobs.
After reading this guide, you will know:
* How to create jobs.
* How to enqueue jobs.
* How to run jobs in the background.
-* How to send emails from your application async.
+* How to send emails from your application asynchronously.
--------------------------------------------------------------------------------
@@ -20,7 +20,7 @@ Introduction
------------
Active Job is a framework for declaring jobs and making them run on a variety
-of queueing backends. These jobs can be everything from regularly scheduled
+of queuing backends. These jobs can be everything from regularly scheduled
clean-ups, to billing charges, to mailings. Anything that can be chopped up
into small units of work and run in parallel, really.
@@ -28,11 +28,14 @@ into small units of work and run in parallel, really.
The Purpose of Active Job
-----------------------------
The main point is to ensure that all Rails apps will have a job infrastructure
-in place, even if it's in the form of an "immediate runner". We can then have
-framework features and other gems build on top of that, without having to
-worry about API differences between various job runners such as Delayed Job
-and Resque. Picking your queuing backend becomes more of an operational concern,
-then. And you'll be able to switch between them without having to rewrite your jobs.
+in place. We can then have framework features and other gems build on top of that,
+without having to worry about API differences between various job runners such as
+Delayed Job and Resque. Picking your queuing backend becomes more of an operational
+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.
Creating a Job
@@ -67,49 +70,59 @@ Here's what a job looks like:
class GuestsCleanupJob < ActiveJob::Base
queue_as :default
- def perform(*args)
+ def perform(*guests)
# Do something later
end
end
```
+Note that you can define `perform` with as many arguments as you want.
+
### Enqueue the Job
Enqueue a job like so:
```ruby
-# Enqueue a job to be performed as soon the queueing system is
+# Enqueue a job to be performed as soon the queuing system is
# free.
-MyJob.perform_later record
+GuestsCleanupJob.perform_later guest
```
```ruby
# Enqueue a job to be performed tomorrow at noon.
-MyJob.set(wait_until: Date.tomorrow.noon).perform_later(record)
+GuestsCleanupJob.set(wait_until: Date.tomorrow.noon).perform_later(guest)
```
```ruby
# Enqueue a job to be performed 1 week from now.
-MyJob.set(wait: 1.week).perform_later(record)
+GuestsCleanupJob.set(wait: 1.week).perform_later(guest)
```
-That's it!
+```ruby
+# `perform_now` and `perform_later` will call `perform` under the hood so
+# you can pass as many arguments as defined in the latter.
+GuestsCleanupJob.perform_later(guest1, guest2, filter: 'some_filter')
+```
+That's it!
Job Execution
-------------
-If no adapter is set, the job is immediately executed.
+For enqueuing and executing jobs 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 does not provide a sophisticated queuing system and just executes the
+job immediately if no adapter is set.
### Backends
-Active Job has built-in adapters for multiple queueing backends (Sidekiq,
+Active Job has built-in adapters for multiple queuing backends (Sidekiq,
Resque, Delayed Job and others). To get an up-to-date list of the adapters
see the API Documentation for [ActiveJob::QueueAdapters](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html).
### Setting the Backend
-You can easily set your queueing backend:
+You can easily set your queuing backend:
```ruby
# config/application.rb
@@ -123,6 +136,10 @@ module YourApp
end
```
+NOTE: Since jobs run in parallel to your Rails application, most queuing libraries
+require that you start a library-specific queuing service (in addition to
+starting your Rails app) for the job processing to work. For information on
+how to do that refer to the documentation of your respective library.
Queues
------
@@ -212,7 +229,7 @@ end
ProcessVideoJob.perform_later(Video.last)
```
-NOTE: Make sure your queueing backend "listens" on your queue name. For some
+NOTE: Make sure your queuing backend "listens" on your queue name. For some
backends you need to specify the queues to listen to.
@@ -270,6 +287,19 @@ UserMailer.welcome(@user).deliver_later
```
+Internationalization
+--------------------
+
+Each job uses the `I18n.locale` set when the job was created. Useful if you send
+emails asynchronously:
+
+```ruby
+I18n.locale = :eo
+
+UserMailer.welcome(@user).deliver_later # Email will be localized to Esparanto.
+```
+
+
GlobalID
--------
@@ -319,3 +349,17 @@ class GuestsCleanupJob < ActiveJob::Base
end
end
```
+
+### Deserialization
+
+GlobalID allows serializing full Active Record objects passed to `#perform`.
+
+If a passed record is deleted after the job is enqueued but before the `#perform`
+method is called Active Job will raise an `ActiveJob::DeserializationError`
+exception.
+
+Job Testing
+--------------
+
+You can find detailed instructions on how to test your jobs in the
+[testing guide](testing.html#testing-jobs).
diff --git a/guides/source/active_model_basics.md b/guides/source/active_model_basics.md
index 4b2bfaee2f..81e2a69504 100644
--- a/guides/source/active_model_basics.md
+++ b/guides/source/active_model_basics.md
@@ -8,12 +8,12 @@ classes. Active Model allows for Action Pack helpers to interact with
plain Ruby objects. Active Model also helps build custom ORMs for use
outside of the Rails framework.
-After reading this guide, you will be able to add to plain Ruby objects:
+After reading this guide, you will know:
-* The ability to behave like an Active Record model.
-* Callbacks and validations like Active Record.
-* Serializers.
-* Integration with the Rails internationalization (i18n) framework.
+* How an Active Record model behaves.
+* How Callbacks and validations work.
+* How serializers work.
+* The Rails internationalization (i18n) framework.
--------------------------------------------------------------------------------
@@ -319,9 +319,8 @@ person.serializable_hash # => {"name"=>"Bob"}
#### ActiveModel::Serializers
-Rails provides two serializers `ActiveModel::Serializers::JSON` and
-`ActiveModel::Serializers::Xml`. Both of these modules automatically include
-the `ActiveModel::Serialization`.
+Rails provides a `ActiveModel::Serializers::JSON` serializer.
+This module automatically include the `ActiveModel::Serialization`.
##### ActiveModel::Serializers::JSON
@@ -379,62 +378,6 @@ person.from_json(json) # => #<Person:0x00000100c773f0 @name="Bob">
person.name # => "Bob"
```
-##### ActiveModel::Serializers::Xml
-
-To use the `ActiveModel::Serializers::Xml` you only need to change from
-`ActiveModel::Serialization` to `ActiveModel::Serializers::Xml`.
-
-```ruby
-class Person
- include ActiveModel::Serializers::Xml
-
- attr_accessor :name
-
- def attributes
- {'name' => nil}
- end
-end
-```
-
-With the `to_xml` you have an XML representing the model.
-
-```ruby
-person = Person.new
-person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<person>\n <name nil=\"true\"/>\n</person>\n"
-person.name = "Bob"
-person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<person>\n <name>Bob</name>\n</person>\n"
-```
-
-From an XML string you define the attributes of the model.
-You need to have the `attributes=` method defined on your class:
-
-```ruby
-class Person
- include ActiveModel::Serializers::Xml
-
- attr_accessor :name
-
- def attributes=(hash)
- hash.each do |key, value|
- send("#{key}=", value)
- end
- end
-
- def attributes
- {'name' => nil}
- end
-end
-```
-
-Now it is possible to create an instance of person and set the attributes using `from_xml`.
-
-```ruby
-xml = { name: 'Bob' }.to_xml
-person = Person.new
-person.from_xml(xml) # => #<Person:0x00000100c773f0 @name="Bob">
-person.name # => "Bob"
-```
-
### Translation
`ActiveModel::Translation` provides integration between your object and the Rails
diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md
index 6551ba0389..f4baf92228 100644
--- a/guides/source/active_record_basics.md
+++ b/guides/source/active_record_basics.md
@@ -74,8 +74,8 @@ By default, Active Record uses some naming conventions to find out how the
mapping between models and database tables should be created. Rails will
pluralize your class names to find the respective database table. So, for
a class `Book`, you should have a database table called **books**. The Rails
-pluralization mechanisms are very powerful, being capable to pluralize (and
-singularize) both regular and irregular words. When using class names composed
+pluralization mechanisms are very powerful, being capable of pluralizing (and
+singularizing) both regular and irregular words. When using class names composed
of two or more words, the model class name should follow the Ruby conventions,
using the CamelCase form, while the table name must contain the words separated
by underscores. Examples:
@@ -260,7 +260,7 @@ david = User.find_by(name: 'David')
```ruby
# find all users named David who are Code Artists and sort by created_at in reverse chronological order
-users = User.where(name: 'David', occupation: 'Code Artist').order('created_at DESC')
+users = User.where(name: 'David', occupation: 'Code Artist').order(created_at: :desc)
```
You can learn more about querying an Active Record model in the [Active Record
diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md
index 7a994cc5de..4e5902fb3d 100644
--- a/guides/source/active_record_migrations.md
+++ b/guides/source/active_record_migrations.md
@@ -357,8 +357,8 @@ will append `ENGINE=BLACKHOLE` to the SQL statement used to create the table
### Creating a Join Table
-Migration method `create_join_table` creates an HABTM join table. A typical use
-would be:
+The migration method `create_join_table` creates an HABTM (has and belongs to
+many) join table. A typical use would be:
```ruby
create_join_table :products, :categories
@@ -367,23 +367,21 @@ create_join_table :products, :categories
which creates a `categories_products` table with two columns called
`category_id` and `product_id`. These columns have the option `:null` set to
`false` by default. This can be overridden by specifying the `:column_options`
-option.
+option:
```ruby
-create_join_table :products, :categories, column_options: {null: true}
+create_join_table :products, :categories, column_options: { null: true }
```
-will create the `product_id` and `category_id` with the `:null` option as
-`true`.
-
-You can pass the option `:table_name` when you want to customize the table
-name. For example:
+By default, the name of the join table comes from the union of the first two
+arguments provided to create_join_table, in alphabetical order.
+To customize the name of the table, provide a `:table_name` option:
```ruby
create_join_table :products, :categories, table_name: :categorization
```
-will create a `categorization` table.
+creates a `categorization` table.
`create_join_table` also accepts a block, which you can use to add indices
(which are not created by default) or additional columns:
@@ -423,21 +421,23 @@ change_column :products, :part_number, :text
```
This changes the column `part_number` on products table to be a `:text` field.
+Note that `change_column` command is irreversible.
Besides `change_column`, the `change_column_null` and `change_column_default`
-methods are used specifically to change a not null constraint and default values of a
-column.
+methods are used specifically to change a not null constraint and default
+values of a column.
```ruby
change_column_null :products, :name, false
-change_column_default :products, :approved, false
+change_column_default :products, :approved, from: true, to: false
```
This sets `:name` field on products to a `NOT NULL` column and the default
-value of the `:approved` field to false.
+value of the `:approved` field from true to false.
-TIP: Unlike `change_column` (and `change_column_default`), `change_column_null`
-is reversible.
+Note: You could also write the above `change_column_default` migration as
+`change_column_default :products, :approved, false`, but unlike the previous
+example, this would make your migration irreversible.
### Column Modifiers
@@ -475,7 +475,8 @@ column names can not be derived from the table names, you can use the
`:column` and `:primary_key` options.
Rails will generate a name for every foreign key starting with
-`fk_rails_` followed by 10 random characters.
+`fk_rails_` followed by 10 characters which are deterministically
+generated from the `from_table` and `column`.
There is a `:name` option to specify a different name if needed.
NOTE: Active Record only supports single column foreign keys. `execute` and
@@ -554,7 +555,7 @@ or write the `up` and `down` methods instead of using the `change` method.
Complex migrations may require processing that Active Record doesn't know how
to reverse. You can use `reversible` to specify what to do when running a
-migration what else to do when reverting it. For example:
+migration and what else to do when reverting it. For example:
```ruby
class ExampleMigration < ActiveRecord::Migration
@@ -606,7 +607,7 @@ schema, and the `down` method of your migration should revert the
transformations done by the `up` method. In other words, the database schema
should be unchanged if you do an `up` followed by a `down`. For example, if you
create a table in the `up` method, you should drop it in the `down` method. It
-is wise to reverse the transformations in precisely the reverse order they were
+is wise to perform the transformations in precisely the reverse order they were
made in the `up` method. The example in the `reversible` section is equivalent to:
```ruby
@@ -1003,7 +1004,10 @@ such features, the `execute` method can be used to execute arbitrary SQL.
Migrations and Seed Data
------------------------
-Some people use migrations to add data to the 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
@@ -1019,9 +1023,11 @@ class AddInitialProducts < ActiveRecord::Migration
end
```
-However, Rails has a 'seeds' feature that should be used for seeding a database
-with initial data. It's a really simple feature: just fill up `db/seeds.rb`
-with some Ruby code, and run `rake db:seed`:
+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 `rake db:seed`:
```ruby
5.times do |i|
diff --git a/guides/source/active_record_postgresql.md b/guides/source/active_record_postgresql.md
index 4d9c1776f4..9d495dfacb 100644
--- a/guides/source/active_record_postgresql.md
+++ b/guides/source/active_record_postgresql.md
@@ -29,8 +29,8 @@ that are supported by the PostgreSQL adapter.
### Bytea
-* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-binary.html)
-* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-binarystring.html)
+* [type definition](http://www.postgresql.org/docs/current/static/datatype-binary.html)
+* [functions and operators](http://www.postgresql.org/docs/current/static/functions-binarystring.html)
```ruby
# db/migrate/20140207133952_create_documents.rb
@@ -49,8 +49,8 @@ Document.create payload: data
### Array
-* [type definition](http://www.postgresql.org/docs/9.3/static/arrays.html)
-* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-array.html)
+* [type definition](http://www.postgresql.org/docs/current/static/arrays.html)
+* [functions and operators](http://www.postgresql.org/docs/current/static/functions-array.html)
```ruby
# db/migrate/20140207133952_create_books.rb
@@ -83,7 +83,7 @@ Book.where("array_length(ratings, 1) >= 3")
### Hstore
-* [type definition](http://www.postgresql.org/docs/9.3/static/hstore.html)
+* [type definition](http://www.postgresql.org/docs/current/static/hstore.html)
NOTE: you need to enable the `hstore` extension to use hstore.
@@ -112,8 +112,8 @@ profile.save!
### JSON
-* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-json.html)
-* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-json.html)
+* [type definition](http://www.postgresql.org/docs/current/static/datatype-json.html)
+* [functions and operators](http://www.postgresql.org/docs/current/static/functions-json.html)
```ruby
# db/migrate/20131220144913_create_events.rb
@@ -138,10 +138,10 @@ Event.where("payload->>'kind' = ?", "user_renamed")
### Range Types
-* [type definition](http://www.postgresql.org/docs/9.3/static/rangetypes.html)
-* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-range.html)
+* [type definition](http://www.postgresql.org/docs/current/static/rangetypes.html)
+* [functions and operators](http://www.postgresql.org/docs/current/static/functions-range.html)
-This type is mapped to Ruby [`Range`](http://www.ruby-doc.org/core-2.1.1/Range.html) objects.
+This type is mapped to Ruby [`Range`](http://www.ruby-doc.org/core-2.2.2/Range.html) objects.
```ruby
# db/migrate/20130923065404_create_events.rb
@@ -173,7 +173,7 @@ event.ends_at # => Thu, 13 Feb 2014
### Composite Types
-* [type definition](http://www.postgresql.org/docs/9.3/static/rowtypes.html)
+* [type definition](http://www.postgresql.org/docs/current/static/rowtypes.html)
Currently there is no special support for composite types. They are mapped to
normal text columns:
@@ -213,7 +213,7 @@ contact.save!
### Enumerated Types
-* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-enum.html)
+* [type definition](http://www.postgresql.org/docs/current/static/datatype-enum.html)
Currently there is no special support for enumerated types. They are mapped as
normal text columns:
@@ -242,14 +242,17 @@ article.save!
### UUID
-* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-uuid.html)
-* [generator functions](http://www.postgresql.org/docs/9.3/static/uuid-ossp.html)
+* [type definition](http://www.postgresql.org/docs/current/static/datatype-uuid.html)
+* [pgcrypto generator function](http://www.postgresql.org/docs/current/static/pgcrypto.html#AEN159361)
+* [uuid-ossp generator functions](http://www.postgresql.org/docs/current/static/uuid-ossp.html)
+NOTE: you need to enable the `pgcrypto` (only PostgreSQL >= 9.4) or `uuid-ossp`
+extension to use uuid.
```ruby
# db/migrate/20131220144913_create_revisions.rb
create_table :revisions do |t|
- t.column :identifier, :uuid
+ t.uuid :identifier
end
# app/models/revision.rb
@@ -263,10 +266,35 @@ revision = Revision.first
revision.identifier # => "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"
```
+You can use `uuid` type to define references in migrations:
+
+```ruby
+# db/migrate/20150418012400_create_blog.rb
+enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto')
+create_table :posts, id: :uuid, default: 'gen_random_uuid()'
+
+create_table :comments, id: :uuid, default: 'gen_random_uuid()' do |t|
+ # t.belongs_to :post, type: :uuid
+ t.references :post, type: :uuid
+end
+
+# app/models/post.rb
+class Post < ActiveRecord::Base
+ has_many :comments
+end
+
+# app/models/comment.rb
+class Comment < ActiveRecord::Base
+ belongs_to :post
+end
+```
+
+See [this section](#uuid-primary-keys) for more details on using UUIDs as primary key.
+
### Bit String Types
-* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-bit.html)
-* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-bitstring.html)
+* [type definition](http://www.postgresql.org/docs/current/static/datatype-bit.html)
+* [functions and operators](http://www.postgresql.org/docs/current/static/functions-bitstring.html)
```ruby
# db/migrate/20131220144913_create_users.rb
@@ -289,10 +317,10 @@ user.save!
### Network Address Types
-* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-net-types.html)
+* [type definition](http://www.postgresql.org/docs/current/static/datatype-net-types.html)
The types `inet` and `cidr` are mapped to Ruby
-[`IPAddr`](http://www.ruby-doc.org/stdlib-2.1.1/libdoc/ipaddr/rdoc/IPAddr.html)
+[`IPAddr`](http://www.ruby-doc.org/stdlib-2.2.2/libdoc/ipaddr/rdoc/IPAddr.html)
objects. The `macaddr` type is mapped to normal text.
```ruby
@@ -324,7 +352,7 @@ macbook.address
### Geometric Types
-* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-geometric.html)
+* [type definition](http://www.postgresql.org/docs/current/static/datatype-geometric.html)
All geometric types, with the exception of `points` are mapped to normal text.
A point is casted to an array containing `x` and `y` coordinates.
@@ -333,12 +361,13 @@ A point is casted to an array containing `x` and `y` coordinates.
UUID Primary Keys
-----------------
-NOTE: you need to enable the `uuid-ossp` extension to generate UUIDs.
+NOTE: you need to enable the `pgcrypto` (only PostgreSQL >= 9.4) or `uuid-ossp`
+extension to generate random UUIDs.
```ruby
# db/migrate/20131220144913_create_devices.rb
-enable_extension 'uuid-ossp' unless extension_enabled?('uuid-ossp')
-create_table :devices, id: :uuid, default: 'uuid_generate_v4()' do |t|
+enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto')
+create_table :devices, id: :uuid, default: 'gen_random_uuid()' do |t|
t.string :kind
end
@@ -351,6 +380,9 @@ device = Device.create
device.id # => "814865cd-5a1d-4771-9306-4268f188fe9e"
```
+NOTE: `uuid_generate_v4()` (from `uuid-ossp`) is assumed if no `:default` option was
+passed to `create_table`.
+
Full Text Search
----------------
@@ -378,7 +410,7 @@ Document.where("to_tsvector('english', title || ' ' || body) @@ to_tsquery(?)",
Database Views
--------------
-* [view creation](http://www.postgresql.org/docs/9.3/static/sql-createview.html)
+* [view creation](http://www.postgresql.org/docs/current/static/sql-createview.html)
Imagine you need to work with a legacy database containing the following table:
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 98fb566222..4b4d70d3ce 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -317,7 +317,7 @@ end
The `find_each` method accepts most of the options allowed by the regular `find` method, except for `:order` and `:limit`, which are reserved for internal use by `find_each`.
-Two additional options, `:batch_size` and `:begin_at`, are available as well.
+Three additional options, `:batch_size`, `:begin_at` and `:end_at`, are available as well.
**`:batch_size`**
@@ -348,7 +348,7 @@ Another example would be if you wanted multiple workers handling the same proces
Similar to the `:begin_at` option, `:end_at` allows you to configure the last ID of the sequence whenever the highest ID is not the one you need.
This would be useful, for example, if you wanted to run a batch process, using a subset of records based on `:begin_at` and `:end_at`
-For example, to send newsletters only to users with the primary key starting from 2000 up to 10000 and to retrieve them in batches of 1000:
+For example, to send newsletters only to users with the primary key starting from 2000 up to 10000 and to retrieve them in batches of 5000:
```ruby
User.find_each(begin_at: 2000, end_at: 10000, batch_size: 5000) do |user|
@@ -529,7 +529,7 @@ Client.order("orders_count ASC, created_at DESC")
Client.order("orders_count ASC", "created_at DESC")
```
-If you want to call `order` multiple times e.g. in different context, new order will append previous one
+If you want to call `order` multiple times e.g. in different context, new order will append previous one:
```ruby
Client.order("orders_count ASC").order("created_at DESC")
@@ -898,7 +898,7 @@ For example:
Item.transaction do
i = Item.lock.first
i.name = 'Jones'
- i.save
+ i.save!
end
```
@@ -1266,6 +1266,18 @@ class Client < ActiveRecord::Base
end
```
+NOTE: The `default_scope` is also applied while creating/building a record.
+It is not applied while updating a record. E.g.:
+
+```ruby
+class Client < ActiveRecord::Base
+ default_scope { where(active: true) }
+end
+
+Client.new # => #<Client id: nil, active: true>
+Client.unscoped.new # => #<Client id: nil, active: nil>
+```
+
### Merging of scopes
Just like `where` clauses scopes are merged using `AND` conditions.
@@ -1787,8 +1799,9 @@ EXPLAIN for: SELECT `users`.* FROM `users` INNER JOIN `articles` ON `articles`.`
under MySQL.
-Active Record performs a pretty printing that emulates the one of the database
-shells. So, the same query running with the PostgreSQL adapter would yield instead
+Active Record performs a pretty printing that emulates that of the
+corresponding database shell. So, the same query running with the
+PostgreSQL adapter would yield instead
```
EXPLAIN for: SELECT "users".* FROM "users" INNER JOIN "articles" ON "articles"."user_id" = "users"."id" WHERE "users"."id" = 1
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index d251c5c0b1..71ca7a0f66 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -47,7 +47,7 @@ built-in helpers for common needs, and allows you to create your own validation
methods as well.
There are several other ways to validate data before it is saved into your
-database, including native database constraints, client-side validations,
+database, including native database constraints, client-side validations and
controller-level validations. Here's a summary of the pros and cons:
* Database constraints and/or stored procedures make the validation mechanisms
@@ -122,7 +122,7 @@ database only if the object is valid:
* `update!`
The bang versions (e.g. `save!`) raise an exception if the record is invalid.
-The non-bang versions don't, `save` and `update` return `false`,
+The non-bang versions don't: `save` and `update` return `false`, and
`create` just returns the object.
### Skipping Validations
@@ -143,7 +143,7 @@ database regardless of its validity. They should be used with caution.
* `update_counters`
Note that `save` also has the ability to skip validations if passed `validate:
-false` as argument. This technique should be used with caution.
+false` as an argument. This technique should be used with caution.
* `save(validate: false)`
@@ -272,7 +272,7 @@ available helpers.
This method validates that a checkbox on the user interface was checked when a
form was submitted. This is typically used when the user needs to agree to your
-application's terms of service, confirm reading some text, or any similar
+application's terms of service, confirm that some text is read, or any similar
concept. This validation is very specific to web applications and this
'acceptance' does not need to be recorded anywhere in your database (if you
don't have a field for it, the helper will just create a virtual attribute).
@@ -283,6 +283,7 @@ class Person < ActiveRecord::Base
end
```
+This check is performed only if `terms_of_service` is not `nil`.
The default error message for this helper is _"must be accepted"_.
It can receive an `:accept` option, which determines the value that will be
@@ -338,7 +339,7 @@ In your view template you could use something like
This check is performed only if `email_confirmation` is not `nil`. To require
confirmation, make sure to add a presence check for the confirmation attribute
-(we'll take a look at `presence` later on this guide):
+(we'll take a look at `presence` later on in this guide):
```ruby
class Person < ActiveRecord::Base
@@ -499,9 +500,9 @@ constraints to acceptable values:
default error message for this option is _"must be equal to %{count}"_.
* `:less_than` - Specifies the value must be less than the supplied value. The
default error message for this option is _"must be less than %{count}"_.
-* `:less_than_or_equal_to` - Specifies the value must be less than or equal the
- supplied value. The default error message for this option is _"must be less
- than or equal to %{count}"_.
+* `:less_than_or_equal_to` - Specifies the value must be less than or equal to
+ the supplied value. The default error message for this option is _"must be
+ less than or equal to %{count}"_.
* `:odd` - Specifies the value must be an odd number if set to true. The
default error message for this option is _"must be odd"_.
* `:even` - Specifies the value must be an even number if set to true. The
@@ -551,7 +552,6 @@ Since `false.blank?` is true, if you want to validate the presence of a boolean
field you should use one of the following validations:
```ruby
-validates :boolean_field_name, presence: true
validates :boolean_field_name, inclusion: { in: [true, false] }
validates :boolean_field_name, exclusion: { in: [nil] }
```
@@ -617,7 +617,7 @@ end
The validation happens by performing an SQL query into the model's table,
searching for an existing record with the same value in that attribute.
-There is a `:scope` option that you can use to specify other attributes that
+There is a `:scope` option that you can use to specify one or more attributes that
are used to limit the uniqueness check:
```ruby
@@ -626,7 +626,7 @@ class Holiday < ActiveRecord::Base
message: "should happen once per year" }
end
```
-Should you wish to create a database constraint to prevent possible violations of a uniqueness validation using the `:scope` option, you must create a unique index on both columns in your database. See [the MySQL manual](http://dev.mysql.com/doc/refman/5.6/en/multiple-column-indexes.html) for more details about multiple column indexes or [the PostgreSQL manual](http://www.postgresql.org/docs/9.4/static/ddl-constraints.html) for examples of unique constraints that refer to a group of columns.
+Should you wish to create a database constraint to prevent possible violations of a uniqueness validation using the `:scope` option, you must create a unique index on both columns in your database. See [the MySQL manual](http://dev.mysql.com/doc/refman/5.6/en/multiple-column-indexes.html) for more details about multiple column indexes or [the PostgreSQL manual](http://www.postgresql.org/docs/current/static/ddl-constraints.html) for examples of unique constraints that refer to a group of columns.
There is also a `:case_sensitive` option that you can use to define whether the
uniqueness constraint will be case sensitive or not. This option defaults to
@@ -813,7 +813,7 @@ end
Person.new.valid? # => ActiveModel::StrictValidationFailed: Name can't be blank
```
-There is also an ability to pass custom exception to `:strict` option.
+There is also the ability to pass a custom exception to the `:strict` option.
```ruby
class Person < ActiveRecord::Base
@@ -877,7 +877,7 @@ end
### Grouping Conditional validations
-Sometimes it is useful to have multiple validations use one condition, it can
+Sometimes it is useful to have multiple validations use one condition. It can
be easily achieved using `with_options`.
```ruby
@@ -889,8 +889,8 @@ class User < ActiveRecord::Base
end
```
-All validations inside of `with_options` block will have automatically passed
-the condition `if: :is_admin?`
+All validations inside of the `with_options` block will have automatically
+passed the condition `if: :is_admin?`
### Combining Validation Conditions
@@ -965,8 +965,9 @@ own custom validators.
You can also create methods that verify the state of your models and add
messages to the `errors` collection when they are invalid. You must then
-register these methods by using the `validate` class method, passing in the
-symbols for the validation methods' names.
+register these methods by using the `validate`
+([API](http://api.rubyonrails.org/classes/ActiveModel/Validations/ClassMethods.html#method-i-validate))
+class method, passing in the symbols for the validation methods' names.
You can pass more than one symbol for each class method and the respective
validations will be run in the same order as they were registered.
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index ff60f95a2c..01bf928407 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -1936,6 +1936,8 @@ as well as adding or subtracting their results from a Time object. For example:
(4.months + 5.years).from_now
```
+NOTE: Defined in `active_support/core_ext/numeric/time.rb`
+
### Formatting
Enables the formatting of numbers in a variety of ways.
@@ -2188,7 +2190,17 @@ The method `without` returns a copy of an enumerable with the specified elements
removed:
```ruby
-people.without("Aaron", "Todd")
+["David", "Rafael", "Aaron", "Todd"].without("Aaron", "Todd") # => ["David", "Rafael"]
+```
+
+NOTE: Defined in `active_support/core_ext/enumerable.rb`.
+
+### `pluck`
+
+The method `pluck` returns an array based on the given key:
+
+```ruby
+[{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name) # => ["David", "Rafael", "Aaron"]
```
NOTE: Defined in `active_support/core_ext/enumerable.rb`.
@@ -2201,14 +2213,14 @@ Extensions to `Array`
Active Support augments the API of arrays to ease certain ways of accessing them. For example, `to` returns the subarray of elements up to the one at the passed index:
```ruby
-%w(a b c d).to(2) # => %w(a b c)
+%w(a b c d).to(2) # => ["a", "b", "c"]
[].to(7) # => []
```
Similarly, `from` returns the tail from the element at the passed index to the end. If the index is greater than the length of the array, it returns an empty array.
```ruby
-%w(a b c d).from(2) # => %w(c d)
+%w(a b c d).from(2) # => ["c", "d"]
%w(a b c d).from(10) # => []
[].from(0) # => []
```
@@ -2216,7 +2228,7 @@ Similarly, `from` returns the tail from the element at the passed index to the e
The methods `second`, `third`, `fourth`, and `fifth` return the corresponding element (`first` is built-in). Thanks to social wisdom and positive constructiveness all around, `forty_two` is also available.
```ruby
-%w(a b c d).third # => c
+%w(a b c d).third # => "c"
%w(a b c d).fifth # => nil
```
@@ -2229,7 +2241,7 @@ NOTE: Defined in `active_support/core_ext/array/access.rb`.
This method is an alias of `Array#unshift`.
```ruby
-%w(a b c d).prepend('e') # => %w(e a b c d)
+%w(a b c d).prepend('e') # => ["e", "a", "b", "c", "d"]
[].prepend(10) # => [10]
```
@@ -2240,8 +2252,8 @@ NOTE: Defined in `active_support/core_ext/array/prepend_and_append.rb`.
This method is an alias of `Array#<<`.
```ruby
-%w(a b c d).append('e') # => %w(a b c d e)
-[].append([1,2]) # => [[1,2]]
+%w(a b c d).append('e') # => ["a", "b", "c", "d", "e"]
+[].append([1,2]) # => [[1, 2]]
```
NOTE: Defined in `active_support/core_ext/array/prepend_and_append.rb`.
@@ -2428,7 +2440,7 @@ The method `Array.wrap` wraps its argument in an array unless it is already an a
Specifically:
-* If the argument is `nil` an empty list is returned.
+* If the argument is `nil` an empty array is returned.
* Otherwise, if the argument responds to `to_ary` it is invoked, and if the value of `to_ary` is not `nil`, it is returned.
* Otherwise, an array with the argument as its single element is returned.
@@ -2440,9 +2452,9 @@ Array.wrap(0) # => [0]
This method is similar in purpose to `Kernel#Array`, but there are some differences:
-* If the argument responds to `to_ary` the method is invoked. `Kernel#Array` moves on to try `to_a` if the returned value is `nil`, but `Array.wrap` returns `nil` right away.
+* If the argument responds to `to_ary` the method is invoked. `Kernel#Array` moves on to try `to_a` if the returned value is `nil`, but `Array.wrap` returns an array with the argument as its single element right away.
* If the returned value from `to_ary` is neither `nil` nor an `Array` object, `Kernel#Array` raises an exception, while `Array.wrap` does not, it just returns the value.
-* It does not call `to_a` on the argument, though special-cases `nil` to return an empty array.
+* It does not call `to_a` on the argument, if the argument does not respond to +to_ary+ it returns an array with the argument as its single element.
The last point is particularly worth comparing for some enumerables:
@@ -2465,7 +2477,7 @@ NOTE: Defined in `active_support/core_ext/array/wrap.rb`.
### Duplicating
-The method `Array.deep_dup` duplicates itself and all objects inside
+The method `Array#deep_dup` duplicates itself and all objects inside
recursively with Active Support method `Object#deep_dup`. It works like `Array#map` with sending `deep_dup` method to each object inside.
```ruby
@@ -2687,7 +2699,7 @@ NOTE: Defined in `active_support/core_ext/hash/deep_merge.rb`.
### Deep duplicating
-The method `Hash.deep_dup` duplicates itself and all keys and values
+The method `Hash#deep_dup` duplicates itself and all keys and values
inside recursively with Active Support method `Object#deep_dup`. It works like `Enumerator#each_with_object` with sending `deep_dup` method to each pair inside.
```ruby
@@ -2883,7 +2895,7 @@ The method `transform_values` accepts a block and returns a hash that has applie
```
There's also the bang variant `transform_values!` that applies the block operations to values in the very receiver.
-NOTE: Defined in `active_support/core_text/hash/transform_values.rb`.
+NOTE: Defined in `active_support/core_ext/hash/transform_values.rb`.
### Slicing
diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md
index 1b14bedfbf..cd44c685ba 100644
--- a/guides/source/active_support_instrumentation.md
+++ b/guides/source/active_support_instrumentation.md
@@ -244,13 +244,19 @@ INFO. The adapters will add their own data as well.
}
```
-### identity.active_record
+### instantiation.active_record
| Key | Value |
| ---------------- | ----------------------------------------- |
-| `:line` | Primary Key of object in the identity map |
-| `:name` | Record's class |
-| `:connection_id` | `self.object_id` |
+| `:record_count` | Number of records that instantiated |
+| `:class_name` | Record's class |
+
+```ruby
+{
+ record_count: 1,
+ class_name: "User"
+}
+```
Action Mailer
-------------
@@ -307,17 +313,6 @@ Action Mailer
}
```
-Active Resource
---------------
-
-### request.active_resource
-
-| Key | Value |
-| -------------- | -------------------- |
-| `:method` | HTTP method |
-| `:request_uri` | Complete URI |
-| `:result` | HTTP response object |
-
Active Support
--------------
diff --git a/guides/source/api_app.md b/guides/source/api_app.md
new file mode 100644
index 0000000000..29ca872254
--- /dev/null
+++ b/guides/source/api_app.md
@@ -0,0 +1,408 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
+
+Using Rails for API-only Applications
+=====================================
+
+In this guide you will learn:
+
+* What Rails provides for API-only applications
+* How to configure Rails to start without any browser features
+* How to decide which middlewares you will want to include
+* How to decide which modules to use in your controller
+
+--------------------------------------------------------------------------------
+
+What is an API app?
+-------------------
+
+Traditionally, when people said that they used Rails as an "API", they meant
+providing a programmatically accessible API alongside their web application.
+For example, GitHub provides [an API](http://developer.github.com) that you
+can use from your own custom clients.
+
+With the advent of client-side frameworks, more developers are using Rails to
+build a back-end that is shared between their web application and other native
+applications.
+
+For example, Twitter uses its [public API](https://dev.twitter.com) in its web
+application, which is built as a static site that consumes JSON resources.
+
+Instead of using Rails to generate dynamic HTML that will communicate with the
+server through forms and links, many developers are treating their web application
+as just another client, delivered as static HTML, CSS and JavaScript consuming
+a simple JSON API.
+
+This guide covers building a Rails application that serves JSON resources to an
+API client **or** a client-side framework.
+
+Why use Rails for JSON APIs?
+----------------------------
+
+The first question a lot of people have when thinking about building a JSON API
+using Rails is: "isn't using Rails to spit out some JSON overkill? Shouldn't I
+just use something like Sinatra?".
+
+For very simple APIs, this may be true. However, even in very HTML-heavy
+applications, most of an application's logic is actually outside of the view
+layer.
+
+The reason most people use Rails is that it provides a set of defaults that
+allows us to get up and running quickly without having to make a lot of trivial
+decisions.
+
+Let's take a look at some of the things that Rails provides out of the box that are
+still applicable to API applications.
+
+Handled at the middleware layer:
+
+- Reloading: Rails applications support transparent reloading. This works even if
+ your application gets big and restarting the server for every request becomes
+ non-viable.
+- Development Mode: Rails applications come with smart defaults for development,
+ making development pleasant without compromising production-time performance.
+- Test Mode: Ditto development mode.
+- Logging: Rails applications log every request, with a level of verbosity
+ appropriate for the current mode. Rails logs in development include information
+ about the request environment, database queries, and basic performance
+ information.
+- Security: Rails detects and thwarts [IP spoofing
+ attacks](http://en.wikipedia.org/wiki/IP_address_spoofing) and handles
+ cryptographic signatures in a [timing
+ attack](http://en.wikipedia.org/wiki/Timing_attack) aware way. Don't know what
+ an IP spoofing attack or a timing attack is? Exactly.
+- Parameter Parsing: Want to specify your parameters as JSON instead of as a
+ URL-encoded String? No problem. Rails will decode the JSON for you and make
+ it available in `params`. Want to use nested URL-encoded parameters? That
+ works too.
+- Conditional GETs: Rails handles conditional `GET`, (`ETag` and `Last-Modified`),
+ processing request headers and returning the correct response headers and status
+ code. All you need to do is use the
+ [`stale?`](http://api.rubyonrails.org/classes/ActionController/ConditionalGet.html#method-i-stale-3F)
+ check in your controller, and Rails will handle all of the HTTP details for you.
+- Caching: If you use `dirty?` with public cache control, Rails will automatically
+ cache your responses. You can easily configure the cache store.
+- HEAD requests: Rails will transparently convert `HEAD` requests into `GET` ones,
+ and return just the headers on the way out. This makes `HEAD` work reliably in
+ all Rails APIs.
+
+While you could obviously build these up in terms of existing Rack middlewares,
+this list demonstrates that the default Rails middleware stack provides a lot
+of value, even if you're "just generating JSON".
+
+Handled at the Action Pack layer:
+
+- Resourceful Routing: If you're building a RESTful JSON API, you want to be
+ using the Rails router. Clean and conventional mapping from HTTP to controllers
+ means not having to spend time thinking about how to model your API in terms
+ of HTTP.
+- URL Generation: The flip side of routing is URL generation. A good API based
+ on HTTP includes URLs (see [the GitHub gist API](http://developer.github.com/v3/gists/)
+ for an example).
+- Header and Redirection Responses: `head :no_content` and
+ `redirect_to user_url(current_user)` come in handy. Sure, you could manually
+ add the response headers, but why?
+- Caching: Rails provides page, action and fragment caching. Fragment caching
+ is especially helpful when building up a nested JSON object.
+- Basic, Digest and Token Authentication: Rails comes with out-of-the-box support
+ for three kinds of HTTP authentication.
+- Instrumentation: Rails has an instrumentation API that will trigger registered
+ handlers for a variety of events, such as action processing, sending a file or
+ data, redirection, and database queries. The payload of each event comes with
+ relevant information (for the action processing event, the payload includes
+ the controller, action, parameters, request format, request method and the
+ request's full path).
+- Generators: This may be passé for advanced Rails users, but it can be nice to
+ generate a resource and get your model, controller, test stubs, and routes
+ created for you in a single command.
+- Plugins: Many third-party libraries come with support for Rails that reduce
+ or eliminate the cost of setting up and gluing together the library and the
+ web framework. This includes things like overriding default generators, adding
+ rake tasks, and honoring Rails choices (like the logger and cache back-end).
+
+Of course, the Rails boot process also glues together all registered components.
+For example, the Rails boot process is what uses your `config/database.yml` file
+when configuring Active Record.
+
+**The short version is**: you may not have thought about which parts of Rails
+are still applicable even if you remove the view layer, but the answer turns out
+to be "most of it".
+
+The Basic Configuration
+-----------------------
+
+If you're building a Rails application that will be an API server first and
+foremost, you can start with a more limited subset of Rails and add in features
+as needed.
+
+You can generate a new api Rails app:
+
+```bash
+$ rails new my_api --api
+```
+
+This will do three main things for you:
+
+- Configure your application to start with a more limited set of middlewares
+ than normal. Specifically, it will not include any middleware primarily useful
+ for browser applications (like cookies support) by default.
+- Make `ApplicationController` inherit from `ActionController::API` instead of
+ `ActionController::Base`. As with middlewares, this will leave out any Action
+ Controller modules that provide functionalities primarily used by browser
+ applications.
+- Configure the generators to skip generating views, helpers and assets when
+ you generate a new resource.
+
+If you want to take an existing application and make it an API one, read the
+following steps.
+
+In `config/application.rb` add the following line at the top of the `Application`
+class definition:
+
+```ruby
+config.api_only = true
+```
+
+Finally, inside `app/controllers/application_controller.rb`, instead of:
+
+```ruby
+class ApplicationController < ActionController::Base
+end
+```
+
+do:
+
+```ruby
+class ApplicationController < ActionController::API
+end
+```
+
+Choosing Middlewares
+--------------------
+
+An API application comes with the following middlewares by default:
+
+- `Rack::Sendfile`
+- `ActionDispatch::Static`
+- `Rack::Lock`
+- `ActiveSupport::Cache::Strategy::LocalCache::Middleware`
+- `ActionDispatch::RequestId`
+- `Rails::Rack::Logger`
+- `Rack::Runtime`
+- `ActionDispatch::ShowExceptions`
+- `ActionDispatch::DebugExceptions`
+- `ActionDispatch::RemoteIp`
+- `ActionDispatch::Reloader`
+- `ActionDispatch::Callbacks`
+- `ActionDispatch::ParamsParser`
+- `Rack::Head`
+- `Rack::ConditionalGet`
+- `Rack::ETag`
+
+See the [internal middlewares](rails_on_rack.html#internal-middleware-stack)
+section of the Rack guide for further information on them.
+
+Other plugins, including Active Record, may add additional middlewares. In
+general, these middlewares are agnostic to the type of application you are
+building, and make sense in an API-only Rails application.
+
+You can get a list of all middlewares in your application via:
+
+```bash
+$ rake middleware
+```
+
+### Using the Cache Middleware
+
+By default, Rails will add a middleware that provides a cache store based on
+the configuration of your application (memcache by default). This means that
+the built-in HTTP cache will rely on it.
+
+For instance, using the `stale?` method:
+
+```ruby
+def show
+ @post = Post.find(params[:id])
+
+ if stale?(last_modified: @post.updated_at)
+ render json: @post
+ end
+end
+```
+
+The call to `stale?` will compare the `If-Modified-Since` header in the request
+with `@post.updated_at`. If the header is newer than the last modified, this
+action will return a "304 Not Modified" response. Otherwise, it will render the
+response and include a `Last-Modified` header in it.
+
+Normally, this mechanism is used on a per-client basis. The cache middleware
+allows us to share this caching mechanism across clients. We can enable
+cross-client caching in the call to `stale?`:
+
+```ruby
+def show
+ @post = Post.find(params[:id])
+
+ if stale?(last_modified: @post.updated_at, public: true)
+ render json: @post
+ end
+end
+```
+
+This means that the cache middleware will store off the `Last-Modified` value
+for a URL in the Rails cache, and add an `If-Modified-Since` header to any
+subsequent inbound requests for the same URL.
+
+Think of it as page caching using HTTP semantics.
+
+NOTE: This middleware is always outside of the `Rack::Lock` mutex, even in
+single-threaded applications.
+
+### Using Rack::Sendfile
+
+When you use the `send_file` method inside a Rails controller, it sets the
+`X-Sendfile` header. `Rack::Sendfile` is responsible for actually sending the
+file.
+
+If your front-end server supports accelerated file sending, `Rack::Sendfile`
+will offload the actual file sending work to the front-end server.
+
+You can configure the name of the header that your front-end server uses for
+this purpose using `config.action_dispatch.x_sendfile_header` in the appropriate
+environment's configuration file.
+
+You can learn more about how to use `Rack::Sendfile` with popular
+front-ends in [the Rack::Sendfile
+documentation](http://rubydoc.info/github/rack/rack/master/Rack/Sendfile).
+
+Here are some values for popular servers, once they are configured, to support
+accelerated file sending:
+
+```ruby
+# Apache and lighttpd
+config.action_dispatch.x_sendfile_header = "X-Sendfile"
+
+# Nginx
+config.action_dispatch.x_sendfile_header = "X-Accel-Redirect"
+```
+
+Make sure to configure your server to support these options following the
+instructions in the `Rack::Sendfile` documentation.
+
+NOTE: The `Rack::Sendfile` middleware is always outside of the `Rack::Lock`
+mutex, even in single-threaded applications.
+
+### Using ActionDispatch::ParamsParser
+
+`ActionDispatch::ParamsParser` will take parameters from the client in the JSON
+format and make them available in your controller inside `params`.
+
+To use this, your client will need to make a request with JSON-encoded parameters
+and specify the `Content-Type` as `application/json`.
+
+Here's an example in jQuery:
+
+```javascript
+jQuery.ajax({
+ type: 'POST',
+ url: '/people',
+ dataType: 'json',
+ contentType: 'application/json',
+ data: JSON.stringify({ person: { firstName: "Yehuda", lastName: "Katz" } }),
+ success: function(json) { }
+});
+```
+
+`ActionDispatch::ParamsParser` will see the `Content-Type` and your parameters
+will be:
+
+```ruby
+{ :person => { :firstName => "Yehuda", :lastName => "Katz" } }
+```
+
+### Other Middlewares
+
+Rails ships with a number of other middlewares that you might want to use in an
+API application, especially if one of your API clients is the browser:
+
+- `Rack::MethodOverride`
+- `ActionDispatch::Cookies`
+- `ActionDispatch::Flash`
+- For sessions management
+ * `ActionDispatch::Session::CacheStore`
+ * `ActionDispatch::Session::CookieStore`
+ * `ActionDispatch::Session::MemCacheStore`
+
+Any of these middlewares can be added via:
+
+```ruby
+config.middleware.use Rack::MethodOverride
+```
+
+### Removing Middlewares
+
+If you don't want to use a middleware that is included by default in the API-only
+middleware set, you can remove it with:
+
+```ruby
+config.middleware.delete ::Rack::Sendfile
+```
+
+Keep in mind that removing these middlewares will remove support for certain
+features in Action Controller.
+
+Choosing Controller Modules
+---------------------------
+
+An API application (using `ActionController::API`) comes with the following
+controller modules by default:
+
+- `ActionController::UrlFor`: Makes `url_for` and friends available.
+- `ActionController::Redirecting`: Support for `redirect_to`.
+- `ActionController::Rendering`: Basic support for rendering.
+- `ActionController::Renderers::All`: Support for `render :json` and friends.
+- `ActionController::ConditionalGet`: Support for `stale?`.
+- `ActionController::ForceSSL`: Support for `force_ssl`.
+- `ActionController::RackDelegation`: Support for the `request` and `response`
+ methods returning `ActionDispatch::Request` and `ActionDispatch::Response`
+ objects.
+- `ActionController::DataStreaming`: Support for `send_file` and `send_data`.
+- `AbstractController::Callbacks`: Support for `before_filter` and friends.
+- `ActionController::Instrumentation`: Support for the instrumentation
+ hooks defined by Action Controller (see [the instrumentation
+ guide](active_support_instrumentation.html#action-controller)).
+- `ActionController::Rescue`: Support for `rescue_from`.
+- `ActionController::BasicImplicitRender`: Makes sure to return an empty response
+ if there's not an explicit one.
+- `ActionController::StrongParameters`: Support for parameters white-listing in
+ combination with Active Model mass assignment.
+- `ActionController::ParamsWrapper`: Wraps the parameters hash into a nested hash
+ so you don't have to specify root elements sending POST requests for instance.
+
+Other plugins may add additional modules. You can get a list of all modules
+included into `ActionController::API` in the rails console:
+
+```bash
+$ bin/rails c
+>> ActionController::API.ancestors - ActionController::Metal.ancestors
+```
+
+### Adding Other Modules
+
+All Action Controller modules know about their dependent modules, so you can feel
+free to include any modules into your controllers, and all dependencies will be
+included and set up as well.
+
+Some common modules you might want to add:
+
+- `AbstractController::Translation`: Support for the `l` and `t` localization
+ and translation methods.
+- `ActionController::HTTPAuthentication::Basic` (or `Digest` or `Token`): Support
+ for basic, digest or token HTTP authentication.
+- `AbstractController::Layouts`: Support for layouts when rendering.
+- `ActionController::MimeResponds`: Support for `respond_to`.
+- `ActionController::Cookies`: Support for `cookies`, which includes
+ support for signed and encrypted cookies. This requires the cookies middleware.
+
+The best place to add a module is in your `ApplicationController` but you can
+also add modules to individual controllers.
diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md
index b385bdbe83..46c9013087 100644
--- a/guides/source/api_documentation_guidelines.md
+++ b/guides/source/api_documentation_guidelines.md
@@ -16,7 +16,8 @@ RDoc
----
The [Rails API documentation](http://api.rubyonrails.org) is generated with
-[RDoc](http://docs.seattlerb.org/rdoc/).
+[RDoc](http://docs.seattlerb.org/rdoc/). To generate it, make sure you are
+in the rails root directory, run `bundle install` and execute:
```bash
bundle exec rake rdoc
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 9da0ef1eb3..7b8d2d3aef 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -403,13 +403,13 @@ When using the asset pipeline, paths to assets must be re-written and
underscored in Ruby) for the following asset classes: image, font, video, audio,
JavaScript and stylesheet.
-* `image-url("rails.png")` becomes `url(/assets/rails.png)`
-* `image-path("rails.png")` becomes `"/assets/rails.png"`.
+* `image-url("rails.png")` returns `url(/assets/rails.png)`
+* `image-path("rails.png")` returns `"/assets/rails.png"`
The more generic form can also be used:
-* `asset-url("rails.png")` becomes `url(/assets/rails.png)`
-* `asset-path("rails.png")` becomes `"/assets/rails.png"`
+* `asset-url("rails.png")` returns `url(/assets/rails.png)`
+* `asset-path("rails.png")` returns `"/assets/rails.png"`
#### JavaScript/CoffeeScript and ERB
@@ -643,7 +643,7 @@ above. By default Rails assumes assets have been precompiled and will be
served as static assets by your web server.
During the precompilation phase an MD5 is generated from the contents of the
-compiled files, and inserted into the filenames as they are written to disc.
+compiled files, and inserted into the filenames as they are written to disk.
These fingerprinted names are used by the Rails helpers in place of the manifest
name.
@@ -790,41 +790,6 @@ location ~ ^/assets/ {
}
```
-#### GZip Compression
-
-When files are precompiled, Sprockets also creates a
-[gzipped](http://en.wikipedia.org/wiki/Gzip) (.gz) version of your assets. Web
-servers are typically configured to use a moderate compression ratio as a
-compromise, but since precompilation happens once, Sprockets uses the maximum
-compression ratio, thus reducing the size of the data transfer to the minimum.
-On the other hand, web servers can be configured to serve compressed content
-directly from disk, rather than deflating non-compressed files themselves.
-
-NGINX is able to do this automatically enabling `gzip_static`:
-
-```nginx
-location ~ ^/(assets)/ {
- root /path/to/public;
- gzip_static on; # to serve pre-gzipped version
- expires max;
- add_header Cache-Control public;
-}
-```
-
-This directive is available if the core module that provides this feature was
-compiled with the web server. Ubuntu/Debian packages, even `nginx-light`, have
-the module compiled. Otherwise, you may need to perform a manual compilation:
-
-```bash
-./configure --with-http_gzip_static_module
-```
-
-If you're compiling NGINX with Phusion Passenger you'll need to pass that option
-when prompted.
-
-A robust configuration for Apache is possible but tricky; please Google around.
-(Or help update this Guide if you have a good configuration example for Apache.)
-
### Local Precompilation
There are several reasons why you might want to precompile your assets locally.
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index ec6017ff73..1191f5edfe 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -146,6 +146,17 @@ class CreateSuppliers < ActiveRecord::Migration
end
```
+Depending on the use case, you might also need to create a unique index and/or
+a foreign key constraint on the supplier column for the accounts table. In this
+case, the column definition might look like this:
+
+```ruby
+create_table :accounts do |t|
+ t.belongs_to :supplier, index: true, unique: true, foreign_key: true
+ # ...
+end
+```
+
### The `has_many` Association
A `has_many` association indicates a one-to-many connection with another model. You'll often find this association on the "other side" of a `belongs_to` association. This association indicates that each instance of the model has zero or more instances of another model. For example, in an application containing customers and orders, the customer model could be declared like this:
@@ -424,7 +435,7 @@ end
The simplest rule of thumb is that you should set up a `has_many :through` relationship if you need to work with the relationship model as an independent entity. If you don't need to do anything with the relationship model, it may be simpler to set up a `has_and_belongs_to_many` relationship (though you'll need to remember to create the joining table in the database).
-You should use `has_many :through` if you need validations, callbacks, or extra attributes on the join model.
+You should use `has_many :through` if you need validations, callbacks or extra attributes on the join model.
### Polymorphic Associations
@@ -579,7 +590,7 @@ If you create an association some time after you build the underlying model, you
If you create a `has_and_belongs_to_many` association, you need to explicitly create the joining table. Unless the name of the join table is explicitly specified by using the `:join_table` option, Active Record creates the name by using the lexical order of the class names. So a join between customer and order models will give the default join table name of "customers_orders" because "c" outranks "o" in lexical ordering.
-WARNING: The precedence between model names is calculated using the `<` operator for `String`. This means that if the strings are of different lengths, and the strings are equal when compared up to the shortest length, then the longer string is considered of higher lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers" to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes", but it in fact generates a join table name of "paper_boxes_papers" (because the underscore '_' is lexicographically _less_ than 's' in common encodings).
+WARNING: The precedence between model names is calculated using the `<=>` operator for `String`. This means that if the strings are of different lengths, and the strings are equal when compared up to the shortest length, then the longer string is considered of higher lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers" to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes", but it in fact generates a join table name of "paper_boxes_papers" (because the underscore '\_' is lexicographically _less_ than 's' in common encodings).
Whatever the name, you must manually generate the join table with an appropriate migration. For example, consider these associations:
@@ -609,7 +620,20 @@ class CreateAssembliesPartsJoinTable < ActiveRecord::Migration
end
```
-We pass `id: false` to `create_table` because that table does not represent a model. That's required for the association to work properly. If you observe any strange behavior in a `has_and_belongs_to_many` association like mangled models IDs, or exceptions about conflicting IDs, chances are you forgot that bit.
+We pass `id: false` to `create_table` because that table does not represent a model. That's required for the association to work properly. If you observe any strange behavior in a `has_and_belongs_to_many` association like mangled model IDs, or exceptions about conflicting IDs, chances are you forgot that bit.
+
+You can also use the method `create_join_table`
+
+```ruby
+class CreateAssembliesPartsJoinTable < ActiveRecord::Migration
+ def change
+ create_join_table :assemblies, :parts do |t|
+ t.index :assembly_id
+ t.index :part_id
+ end
+ end
+end
+```
### Controlling Association Scope
@@ -782,7 +806,7 @@ If the associated object has already been retrieved from the database for this o
##### `association=(associate)`
-The `association=` method assigns an associated object to this object. Behind the scenes, this means extracting the primary key from the associate object and setting this object's foreign key to the same value.
+The `association=` method assigns an associated object to this object. Behind the scenes, this means extracting the primary key from the associated object and setting this object's foreign key to the same value.
```ruby
@order.customer = @customer
@@ -829,6 +853,7 @@ The `belongs_to` association supports these options:
* `:counter_cache`
* `:dependent`
* `:foreign_key`
+* `:primary_key`
* `:inverse_of`
* `:polymorphic`
* `:touch`
@@ -875,18 +900,26 @@ end
With this declaration, Rails will keep the cache value up to date, and then return that value in response to the `size` method.
-Although the `:counter_cache` option is specified on the model that includes the `belongs_to` declaration, the actual column must be added to the _associated_ model. In the case above, you would need to add a column named `orders_count` to the `Customer` model. You can override the default column name if you need to:
+Although the `:counter_cache` option is specified on the model that includes
+the `belongs_to` declaration, the actual column must be added to the
+_associated_ (`has_many`) model. In the case above, you would need to add a
+column named `orders_count` to the `Customer` model.
+
+You can override the default column name by specifying a custom column name in
+the `counter_cache` declaration instead of `true`. For example, to use
+`count_of_orders` instead of `orders_count`:
```ruby
class Order < ActiveRecord::Base
belongs_to :customer, counter_cache: :count_of_orders
end
class Customer < ActiveRecord::Base
- has_many :orders, counter_cache: :count_of_orders
+ has_many :orders
end
```
-NOTE: You only need to specify the :counter_cache option on the "has_many side" of the association when using a custom name for the counter cache.
+NOTE: You only need to specify the :counter_cache option on the `belongs_to`
+side of the association.
Counter cache columns are added to the containing model's list of read-only attributes through `attr_readonly`.
@@ -913,6 +946,26 @@ end
TIP: In any case, Rails will not create foreign key columns for you. You need to explicitly define them as part of your migrations.
+##### `:primary_key`
+
+By convention, Rails assumes that the `id` column is used to hold the primary key
+of its tables. The `:primary_key` option allows you to specify a different column.
+
+For example, given we have a `users` table with `guid` as the primary key. If we want a separate `todos` table to hold the foreign key `user_id` in the `guid` column, then we can use `primary_key` to achieve this like so:
+
+```ruby
+class User < ActiveRecord::Base
+ self.primary_key = 'guid' # primary key is guid and not id
+end
+
+class Todo < ActiveRecord::Base
+ belongs_to :user, primary_key: 'guid'
+end
+```
+
+When we execute `@user.todos.create` then the `@todo` record will have its
+`user_id` value as the `guid` value of `@user`.
+
##### `:inverse_of`
The `:inverse_of` option specifies the name of the `has_many` or `has_one` association that is the inverse of this association. Does not work in combination with the `:polymorphic` options.
@@ -1098,7 +1151,7 @@ If the associated object has already been retrieved from the database for this o
##### `association=(associate)`
-The `association=` method assigns an associated object to this object. Behind the scenes, this means extracting the primary key from this object and setting the associate object's foreign key to the same value.
+The `association=` method assigns an associated object to this object. Behind the scenes, this means extracting the primary key from this object and setting the associated object's foreign key to the same value.
```ruby
@supplier.account = @account
@@ -1179,8 +1232,8 @@ Controls what happens to the associated object when its owner is destroyed:
It's necessary not to set or leave `:nullify` option for those associations
that have `NOT NULL` database constraints. If you don't set `dependent` to
destroy such associations you won't be able to change the associated object
-because initial associated object foreign key will be set to unallowed `NULL`
-value.
+because the initial associated object's foreign key will be set to the
+unallowed `NULL` value.
##### `:foreign_key`
@@ -1427,7 +1480,14 @@ The `collection_singular_ids=` method makes the collection contain only the obje
##### `collection.clear`
-The `collection.clear` method removes every object from the collection. This destroys the associated objects if they are associated with `dependent: :destroy`, deletes them directly from the database if `dependent: :delete_all`, and otherwise sets their foreign keys to `NULL`.
+The `collection.clear` method removes all objects from the collection according to the strategy specified by the `dependent` option. If no option is given, it follows the default strategy. The default strategy for `has_many :through` associations is `delete_all`, and for `has_many` associations is to set the foreign keys to `NULL`.
+
+```ruby
+@customer.orders.clear
+```
+
+WARNING: Objects will be deleted if they're associated with `dependent: :destroy`,
+just like `dependent: :delete_all`.
##### `collection.empty?`
@@ -1466,24 +1526,36 @@ The `collection.where` method finds objects within the collection based on the c
##### `collection.exists?(...)`
-The `collection.exists?` method checks whether an object meeting the supplied conditions exists in the collection. It uses the same syntax and options as `ActiveRecord::Base.exists?`.
+The `collection.exists?` method checks whether an object meeting the supplied
+conditions exists in the collection. It uses the same syntax and options as
+[`ActiveRecord::Base.exists?`](http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-exists-3F).
##### `collection.build(attributes = {}, ...)`
-The `collection.build` method returns one or more new objects of the associated type. These objects will be instantiated from the passed attributes, and the link through their foreign key will be created, but the associated objects will _not_ yet be saved.
+The `collection.build` method returns a single or array of new objects of the associated type. The object(s) will be instantiated from the passed attributes, and the link through their foreign key will be created, but the associated objects will _not_ yet be saved.
```ruby
@order = @customer.orders.build(order_date: Time.now,
order_number: "A12345")
+
+@orders = @customer.orders.build([
+ { order_date: Time.now, order_number: "A12346" },
+ { order_date: Time.now, order_number: "A12347" }
+])
```
##### `collection.create(attributes = {})`
-The `collection.create` method returns a new object of the associated type. This object will be instantiated from the passed attributes, the link through its foreign key will be created, and, once it passes all of the validations specified on the associated model, the associated object _will_ be saved.
+The `collection.create` method returns a single or array of new objects of the associated type. The object(s) will be instantiated from the passed attributes, the link through its foreign key will be created, and, once it passes all of the validations specified on the associated model, the associated object _will_ be saved.
```ruby
@order = @customer.orders.create(order_date: Time.now,
order_number: "A12345")
+
+@orders = @customer.orders.create([
+ { order_date: Time.now, order_number: "A12346" },
+ { order_date: Time.now, order_number: "A12347" }
+])
```
##### `collection.create!(attributes = {})`
@@ -1577,9 +1649,10 @@ end
By convention, Rails assumes that the column used to hold the primary key of the association is `id`. You can override this and explicitly specify the primary key with the `:primary_key` option.
-Let's say that `users` table has `id` as the primary_key but it also has
-`guid` column. And the requirement is that `todos` table should hold
-`guid` column value and not `id` value. This can be achieved like this
+Let's say the `users` table has `id` as the primary_key but it also
+has a `guid` column. The requirement is that the `todos` table should
+hold the `guid` column value as the foreign key and not `id`
+value. This can be achieved like this:
```ruby
class User < ActiveRecord::Base
@@ -1587,8 +1660,8 @@ class User < ActiveRecord::Base
end
```
-Now if we execute `@user.todos.create` then `@todo` record will have
-`user_id` value as the `guid` value of `@user`.
+Now if we execute `@todo = @user.todos.create` then the `@todo`
+record's `user_id` value will be the `guid` value of `@user`.
##### `:source`
@@ -1628,7 +1701,7 @@ You can use any of the standard [querying methods](active_record_querying.html)
* `order`
* `readonly`
* `select`
-* `uniq`
+* `distinct`
##### `where`
@@ -1964,7 +2037,9 @@ The `collection.where` method finds objects within the collection based on the c
##### `collection.exists?(...)`
-The `collection.exists?` method checks whether an object meeting the supplied conditions exists in the collection. It uses the same syntax and options as `ActiveRecord::Base.exists?`.
+The `collection.exists?` method checks whether an object meeting the supplied
+conditions exists in the collection. It uses the same syntax and options as
+[`ActiveRecord::Base.exists?`](http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-exists-3F).
##### `collection.build(attributes = {})`
@@ -2292,13 +2367,13 @@ associations, public methods, etc.
Creating a car will save it in the `vehicles` table with "Car" as the `type` field:
```ruby
-Car.create color: 'Red', price: 10000
+Car.create(color: 'Red', price: 10000)
```
will generate the following SQL:
```sql
-INSERT INTO "vehicles" ("type", "color", "price") VALUES ("Car", "Red", 10000)
+INSERT INTO "vehicles" ("type", "color", "price") VALUES ('Car', 'Red', 10000)
```
Querying car records will just search for vehicles that are cars:
diff --git a/guides/source/autoloading_and_reloading_constants.md b/guides/source/autoloading_and_reloading_constants.md
index c6149abcba..2b6d7e4044 100644
--- a/guides/source/autoloading_and_reloading_constants.md
+++ b/guides/source/autoloading_and_reloading_constants.md
@@ -466,9 +466,7 @@ by adding this to `config/application.rb`:
config.autoload_paths << "#{Rails.root}/lib"
```
-`config.autoload_paths` is accessible from environment-specific configuration
-files, but any changes made to it outside `config/application.rb` don't have any
-effect.
+`config.autoload_paths` is not changeable from environment-specific configuration files.
The value of `autoload_paths` can be inspected. In a just generated application
it is (edited):
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index 716beb9178..9a56233e4a 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -1,14 +1,26 @@
**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
-Caching with Rails: An overview
+Caching with Rails: An Overview
===============================
-This guide will teach you what you need to know about avoiding that expensive round-trip to your database and returning what you need to return to the web clients in the shortest time possible.
+This guide is an introduction to speeding up your Rails application with caching.
+
+Caching means to store content generated during the request-response cycle and
+to reuse it when responding to similar requests.
+
+Caching is often the most effective way to boost an application's performance.
+Through caching, web sites running on a single server with a single database
+can sustain a load of thousands of concurrent users.
+
+Rails provides a set of caching features out of the box. This guide will teach
+you the scope and purpose of each one of them. Master these techniques and your
+Rails applications can serve millions of views without exorbitant response times
+or server bills.
After reading this guide, you will know:
-* Page and action caching (moved to separate gems as of Rails 4).
-* Fragment caching.
+* Fragment and Russian doll caching.
+* How to manage the caching dependencies.
* Alternative cache stores.
* Conditional GET support.
@@ -18,19 +30,32 @@ Basic Caching
-------------
This is an introduction to three types of caching techniques: page, action and
-fragment caching. Rails provides by default fragment caching. In order to use
-page and action caching, you will need to add `actionpack-page_caching` and
+fragment caching. By default Rails provides fragment caching. In order to use
+page and action caching you will need to add `actionpack-page_caching` and
`actionpack-action_caching` to your Gemfile.
-To start playing with caching you'll want to ensure that `config.action_controller.perform_caching` is set to `true`, if you're running in development mode. This flag is normally set in the corresponding `config/environments/*.rb` and caching is disabled by default for development and test, and enabled for production.
+By default, caching is only enabled in your production environment. To play
+around with caching locally you'll want to enable caching in your local
+environment by setting `config.action_controller.perform_caching` to `true` in
+the relevant `config/environments/*.rb` file:
```ruby
config.action_controller.perform_caching = true
```
+NOTE: Changing the value of `config.action_controller.perform_caching` will
+only have an effect on the caching provided by the Action Controller component.
+For instance, it will not impact low-level caching, that we address
+[below](#low-level-caching).
+
### Page Caching
-Page caching is a Rails mechanism which allows the request for a generated page to be fulfilled by the webserver (i.e. Apache or NGINX), without ever having to go through the Rails stack at all. Obviously, this is super-fast. Unfortunately, it can't be applied to every situation (such as pages that need authentication) and since the webserver is literally just serving a file from the filesystem, cache expiration is an issue that needs to be dealt with.
+Page caching is a Rails mechanism which allows the request for a generated page
+to be fulfilled by the webserver (i.e. Apache or NGINX) without having to go
+through the entire Rails stack. While this is super fast it can't be applied to
+every situation (such as pages that need authentication). Also, because the
+webserver is serving a file directly from the filesystem you will need to
+implement cache expiration.
INFO: Page Caching has been removed from Rails 4. See the [actionpack-page_caching gem](https://github.com/rails/actionpack-page_caching).
@@ -42,109 +67,217 @@ INFO: Action Caching has been removed from Rails 4. See the [actionpack-action_c
### Fragment Caching
-Life would be perfect if we could get away with caching the entire contents of a page or action and serving it out to the world. Unfortunately, dynamic web applications usually build pages with a variety of components not all of which have the same caching characteristics. In order to address such a dynamically created page where different parts of the page need to be cached and expired differently, Rails provides a mechanism called Fragment Caching.
+Dynamic web applications usually build pages with a variety of components not
+all of which have the same caching characteristics. When different parts of the
+page need to be cached and expired separately you can use Fragment Caching.
Fragment Caching allows a fragment of view logic to be wrapped in a cache block and served out of the cache store when the next request comes in.
-As an example, if you wanted to show all the orders placed on your website in real time and didn't want to cache that part of the page, but did want to cache the part of the page which lists all products available, you could use this piece of code:
+For example, if you wanted to cache each product on a page, you could use this
+code:
```html+erb
-<% Order.find_recent.each do |o| %>
- <%= o.buyer.name %> bought <%= o.product.name %>
-<% end %>
-
-<% cache do %>
- All available products:
- <% Product.all.each do |p| %>
- <%= link_to p.name, product_url(p) %>
+<% @products.each do |product| %>
+ <% cache product do %>
+ <%= render product %>
<% end %>
<% end %>
```
-The cache block in our example will bind to the action that called it and is written out to the same place as the Action Cache, which means that if you want to cache multiple fragments per action, you should provide an `action_suffix` to the cache call:
+When your application receives its first request to this page, Rails will write
+a new cache entry with a unique key. A key looks something like this:
-```html+erb
-<% cache(action: 'recent', action_suffix: 'all_products') do %>
- All available products:
+```
+views/products/1-201505056193031061005000/bea67108094918eeba42cd4a6e786901
```
-and you can expire it using the `expire_fragment` method, like so:
+The number in the middle is the `product_id` followed by the timestamp value in
+the `updated_at` attribute of the product record. Rails uses the timestamp value
+to make sure it is not serving stale data. If the value of `updated_at` has
+changed, a new key will be generated. Then Rails will write a new cache to that
+key, and the old cache written to the old key will never be used again. This is
+called key-based expiration.
-```ruby
-expire_fragment(controller: 'products', action: 'recent', action_suffix: 'all_products')
-```
+Cache fragments will also be expired when the view fragment changes (e.g., the
+HTML in the view changes). The string of characters at the end of the key is a
+template tree digest. It is an md5 hash computed based on the contents of the
+view fragment you are caching. If you change the view fragment, the md5 hash
+will change, expiring the existing file.
-If you don't want the cache block to bind to the action that called it, you can also use globally keyed fragments by calling the `cache` method with a key:
+TIP: Cache stores like Memcached will automatically delete old cache files.
+
+If you want to cache a fragment under certain conditions, you can use
+`cache_if` or `cache_unless`:
```erb
-<% cache('all_available_products') do %>
- All available products:
+<% cache_if admin?, product do %>
+ <%= render product %>
<% end %>
```
-This fragment is then available to all actions in the `ProductsController` using the key and can be expired the same way:
+#### Collection caching
-```ruby
-expire_fragment('all_available_products')
-```
-If you want to avoid expiring the fragment manually, whenever an action updates a product, you can define a helper method:
+The `render` helper can also cache individual templates rendered for a collection.
+It can even one up the previous example with `each` by reading all cache
+templates at once instead of one by one. This is done automatically if the template
+rendered by the collection includes a `cache` call. Take a collection that renders
+a `products/_product.html.erb` partial for each element:
```ruby
-module ProductsHelper
- def cache_key_for_products
- count = Product.count
- max_updated_at = Product.maximum(:updated_at).try(:utc).try(:to_s, :number)
- "products/all-#{count}-#{max_updated_at}"
- end
-end
+render products
```
-This method generates a cache key that depends on all products and can be used in the view:
+If `products/_product.html.erb` starts with a `cache` call like so:
-```erb
-<% cache(cache_key_for_products) do %>
- All available products:
+```html+erb
+<% cache product do %>
+ <%= product.name %>
<% end %>
```
-If you want to cache a fragment under certain conditions, you can use `cache_if` or `cache_unless`
+All the cached templates from previous renders will be fetched at once with much
+greater speed. There's more info on how to make your templates [eligible for
+collection caching](http://api.rubyonrails.org/classes/ActionView/Template/Handlers/ERB.html#method-i-resource_cache_call_pattern).
+
+### Russian Doll Caching
+
+You may want to nest cached fragments inside other cached fragments. This is
+called Russian doll caching.
+
+The advantage of Russian doll caching is that if a single product is updated,
+all the other inner fragments can be reused when regenerating the outer
+fragment.
+
+As explained in the previous section, a cached file will expire if the value of
+`updated_at` changes for a record on which the cached file directly depends.
+However, this will not expire any cache the fragment is nested within.
+
+For example, take the following view:
```erb
-<% cache_if (condition, cache_key_for_products) do %>
- All available products:
+<% cache product do %>
+ <%= render product.games %>
<% end %>
```
-You can also use an Active Record model as the cache key:
+Which in turn renders this view:
```erb
-<% Product.all.each do |p| %>
- <% cache(p) do %>
- <%= link_to p.name, product_url(p) %>
- <% end %>
+<% cache game do %>
+ <%= render game %>
<% end %>
```
-Behind the scenes, a method called `cache_key` will be invoked on the model and it returns a string like `products/23-20130109142513`. The cache key includes the model name, the id and finally the updated_at timestamp. Thus it will automatically generate a new fragment when the product is updated because the key changes.
+If any attribute of game is changed, the `updated_at` value will be set to the
+current time, thereby expiring the cache. However, because `updated_at`
+will not be changed for the product object, that cache will not be expired and
+your app will serve stale data. To fix this, we tie the models together with
+the `touch` method:
+
+```ruby
+class Product < ActiveRecord::Base
+ has_many :games
+end
+
+class Game < ActiveRecord::Base
+ belongs_to :product, touch: true
+end
+```
+
+With `touch` set to true, any action which changes `updated_at` for a game
+record will also change it for the associated product, thereby expiring the
+cache.
-You can also combine the two schemes which is called "Russian Doll Caching":
+### Managing dependencies
-```erb
-<% cache(cache_key_for_products) do %>
- All available products:
- <% Product.all.each do |p| %>
- <% cache(p) do %>
- <%= link_to p.name, product_url(p) %>
- <% end %>
- <% end %>
+In order to correctly invalidate the cache, you need to properly define the
+caching dependencies. Rails is clever enough to handle common cases so you don't
+have to specify anything. However, sometimes, when you're dealing with custom
+helpers for instance, you need to explicitly define them.
+
+#### Implicit dependencies
+
+Most template dependencies can be derived from calls to `render` in the template
+itself. Here are some examples of render calls that `ActionView::Digestor` knows
+how to decode:
+
+```ruby
+render partial: "comments/comment", collection: commentable.comments
+render "comments/comments"
+render 'comments/comments'
+render('comments/comments')
+
+render "header" => render("comments/header")
+
+render(@topic) => render("topics/topic")
+render(topics) => render("topics/topic")
+render(message.topics) => render("topics/topic")
+```
+
+On the other hand, some calls need to be changed to make caching work properly.
+For instance, if you're passing a custom collection, you'll need to change:
+
+```ruby
+render @project.documents.where(published: true)
+```
+
+to:
+
+```ruby
+render partial: "documents/document", collection: @project.documents.where(published: true)
+```
+
+#### Explicit dependencies
+
+Sometimes you'll have template dependencies that can't be derived at all. This
+is typically the case when rendering happens in helpers. Here's an example:
+
+```html+erb
+<%= render_sortable_todolists @project.todolists %>
+```
+
+You'll need to use a special comment format to call those out:
+
+```html+erb
+<%# Template Dependency: todolists/todolist %>
+<%= render_sortable_todolists @project.todolists %>
+```
+
+In some cases, like a single table inheritance setup, you might have a bunch of
+explicit dependencies. Instead of writing every template out, you can use a
+wildcard to match any template in a directory:
+
+```html+erb
+<%# Template Dependency: events/* %>
+<%= render_categorizable_events @person.events %>
+```
+
+As for collection caching, if the partial template doesn't start with a clean
+cache call, you can still benefit from collection caching by adding a special
+comment format anywhere in the template, like:
+
+```html+erb
+<%# Template Collection: notification %>
+<% my_helper_that_calls_cache(some_arg, notification) do %>
+ <%= notification.name %>
<% end %>
```
-It's called "Russian Doll Caching" because it nests multiple fragments. The advantage is that if a single product is updated, all the other inner fragments can be reused when regenerating the outer fragment.
+#### External dependencies
+
+If you use a helper method, for example, inside a cached block and you then update
+that helper, you'll have to bump the cache as well. It doesn't really matter how
+you do it, but the md5 of the template file must change. One recommendation is to
+simply be explicit in a comment, like:
+
+```html+erb
+<%# Helper Dependency Updated: Jul 28, 2015 at 7pm %>
+<%= some_helper_method(person) %>
+```
### Low-Level Caching
-Sometimes you need to cache a particular value or query result, instead of caching view fragments. Rails caching mechanism works great for storing __any__ kind of information.
+Sometimes you need to cache a particular value or query result instead of caching view fragments. Rails' caching mechanism works great for storing __any__ kind of information.
The most efficient way to implement low-level caching is using the `Rails.cache.fetch` method. This method does both reading and writing to the cache. When passed only a single argument, the key is fetched and value from the cache is returned. If a block is passed, the result of the block will be cached to the given key and the result is returned.
@@ -160,11 +293,14 @@ class Product < ActiveRecord::Base
end
```
-NOTE: Notice that in this example we used `cache_key` method, so the resulting cache-key will be something like `products/233-20140225082222765838000/competing_price`. `cache_key` generates a string based on the model’s `id` and `updated_at` attributes. This is a common convention and has the benefit of invalidating the cache whenever the product is updated. In general, when you use low-level caching for instance level information, you need to generate a cache key.
+NOTE: Notice that in this example we used the `cache_key` method, so the resulting cache-key will be something like `products/233-20140225082222765838000/competing_price`. `cache_key` generates a string based on the model’s `id` and `updated_at` attributes. This is a common convention and has the benefit of invalidating the cache whenever the product is updated. In general, when you use low-level caching for instance level information, you need to generate a cache key.
### SQL Caching
-Query caching is a Rails feature that caches the result set returned by each query so that if Rails encounters the same query again for that request, it will use the cached result set as opposed to running the query against the database again.
+Query caching is a Rails feature that caches the result set returned by each
+query. If Rails encounters the same query again for that request, it will use
+the cached result set as opposed to running the query against the database
+again.
For example:
@@ -186,21 +322,25 @@ end
The second time the same query is run against the database, it's not actually going to hit the database. The first time the result is returned from the query it is stored in the query cache (in memory) and the second time it's pulled from memory.
-However, it's important to note that query caches are created at the start of an action and destroyed at the end of that action and thus persist only for the duration of the action. If you'd like to store query results in a more persistent fashion, you can in Rails by using low level caching.
+However, it's important to note that query caches are created at the start of
+an action and destroyed at the end of that action and thus persist only for the
+duration of the action. If you'd like to store query results in a more
+persistent fashion, you can with low level caching.
Cache Stores
------------
-Rails provides different stores for the cached data created by **action** and **fragment** caches.
-
-TIP: Page caches are always stored on disk.
+Rails provides different stores for the cached data (apart from SQL and page
+caching).
### Configuration
-You can set up your application's default cache store by calling `config.cache_store=` in the Application definition inside your `config/application.rb` file or in an Application.configure block in an environment specific configuration file (i.e. `config/environments/*.rb`). The first argument will be the cache store to use and the rest of the argument will be passed as arguments to the cache store constructor.
+You can set up your application's default cache store by setting the
+`config.cache_store` configuration option. Other parameters can be passed as
+arguments to the cache store's constructor:
```ruby
-config.cache_store = :memory_store
+config.cache_store = :memory_store, { size: 64.megabytes }
```
NOTE: Alternatively, you can call `ActionController::Base.cache_store` outside of a configuration block.
@@ -219,21 +359,42 @@ There are some common options used by all cache implementations. These can be pa
* `:compress` - This option can be used to indicate that compression should be used in the cache. This can be useful for transferring large cache entries over a slow network.
-* `:compress_threshold` - This options is used in conjunction with the `:compress` option to indicate a threshold under which cache entries should not be compressed. This defaults to 16 kilobytes.
+* `:compress_threshold` - This option is used in conjunction with the `:compress` option to indicate a threshold under which cache entries should not be compressed. This defaults to 16 kilobytes.
* `:expires_in` - This option sets an expiration time in seconds for the cache entry when it will be automatically removed from the cache.
* `:race_condition_ttl` - This option is used in conjunction with the `:expires_in` option. It will prevent race conditions when cache entries expire by preventing multiple processes from simultaneously regenerating the same entry (also known as the dog pile effect). This option sets the number of seconds that an expired entry can be reused while a new value is being regenerated. It's a good practice to set this value if you use the `:expires_in` option.
+#### Custom Cache Stores
+
+You can create your own custom cache store by simply extending
+`ActiveSupport::Cache::Store` and implementing the appropriate methods. This way,
+you can swap in any number of caching technologies into your Rails application.
+
+To use a custom cache store, simply set the cache store to a new instance of your
+custom class.
+
+```ruby
+config.cache_store = MyCacheStore.new
+```
+
### ActiveSupport::Cache::MemoryStore
-This cache store keeps entries in memory in the same Ruby process. The cache store has a bounded size specified by the `:size` options to the initializer (default is 32Mb). When the cache exceeds the allotted size, a cleanup will occur and the least recently used entries will be removed.
+This cache store keeps entries in memory in the same Ruby process. The cache
+store has a bounded size specified by sending the `:size` option to the
+initializer (default is 32Mb). When the cache exceeds the allotted size, a
+cleanup will occur and the least recently used entries will be removed.
```ruby
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 process instances won't be able to share cache data with each other. This cache store is not appropriate for large application deployments, but can work well for small, low traffic sites with only a couple of server processes or for development and test environments.
+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
+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,
+as well as development and test environments.
### ActiveSupport::Cache::FileStore
@@ -243,9 +404,13 @@ This cache store uses the file system to store entries. The path to the director
config.cache_store = :file_store, "/path/to/cache/directory"
```
-With this cache store, multiple server processes on the same host can share a cache. Servers processes running on different hosts could share a cache by using a shared file system, but that set up would not be ideal and is not recommended. The cache store is appropriate for low to medium traffic sites that are served off one or two hosts.
+With this cache store, multiple server processes on the same host can share a
+cache. The cache store is appropriate for low to medium traffic sites that are
+served off one or two hosts. Server processes running on different hosts could
+share a cache by using a shared file system, but that setup is not recommended.
-Note that the cache will grow until the disk is full unless you periodically clear out old entries.
+As the cache will grow until the disk is full, it is recommended to
+periodically clear out old entries.
This is the default cache store implementation.
@@ -253,65 +418,32 @@ This is the default cache store implementation.
This cache store uses Danga's `memcached` server to provide a centralized cache for your application. Rails uses the bundled `dalli` gem by default. This is currently the most popular cache store for production websites. It can be used to provide a single, shared cache cluster with very high performance and redundancy.
-When initializing the cache, you need to specify the addresses for all memcached servers in your cluster. If none is specified, it will assume memcached is running on the local host on the default port, but this is not an ideal set up for larger sites.
+When initializing the cache, you need to specify the addresses for all
+memcached servers in your cluster. If none are specified, it will assume
+memcached is running on localhost on the default port, but this is not an ideal
+setup for larger sites.
-The `write` and `fetch` methods on this cache accept two additional options that take advantage of features specific to memcached. You can specify `:raw` to send a value directly to the server with no serialization. The value must be a string or number. You can use memcached direct operation like `increment` and `decrement` only on raw values. You can also specify `:unless_exist` if you don't want memcached to overwrite an existing entry.
+The `write` and `fetch` methods on this cache accept two additional options that take advantage of features specific to memcached. You can specify `:raw` to send a value directly to the server with no serialization. The value must be a string or number. You can use memcached direct operations like `increment` and `decrement` only on raw values. You can also specify `:unless_exist` if you don't want memcached to overwrite an existing entry.
```ruby
config.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.com"
```
-### ActiveSupport::Cache::EhcacheStore
-
-If you are using JRuby you can use Terracotta's Ehcache as the cache store for your application. Ehcache is an open source Java cache that also offers an enterprise version with increased scalability, management, and commercial support. You must first install the jruby-ehcache-rails3 gem (version 1.1.0 or later) to use this cache store.
-
-```ruby
-config.cache_store = :ehcache_store
-```
-
-When initializing the cache, you may use the `:ehcache_config` option to specify the Ehcache config file to use (where the default is "ehcache.xml" in your Rails config directory), and the :cache_name option to provide a custom name for your cache (the default is rails_cache).
-
-In addition to the standard `:expires_in` option, the `write` method on this cache can also accept the additional `:unless_exist` option, which will cause the cache store to use Ehcache's `putIfAbsent` method instead of `put`, and therefore will not overwrite an existing entry. Additionally, the `write` method supports all of the properties exposed by the [Ehcache Element class](http://ehcache.org/apidocs/net/sf/ehcache/Element.html) , including:
-
-| Property | Argument Type | Description |
-| --------------------------- | ------------------- | ----------------------------------------------------------- |
-| elementEvictionData | ElementEvictionData | Sets this element's eviction data instance. |
-| eternal | boolean | Sets whether the element is eternal. |
-| timeToIdle, tti | int | Sets time to idle |
-| timeToLive, ttl, expires_in | int | Sets time to Live |
-| version | long | Sets the version attribute of the ElementAttributes object. |
-
-These options are passed to the `write` method as Hash options using either camelCase or underscore notation, as in the following examples:
-
-```ruby
-Rails.cache.write('key', 'value', time_to_idle: 60.seconds, timeToLive: 600.seconds)
-caches_action :index, expires_in: 60.seconds, unless_exist: true
-```
-
-For more information about Ehcache, see [http://ehcache.org/](http://ehcache.org/) .
-For more information about Ehcache for JRuby and Rails, see [http://ehcache.org/documentation/jruby.html](http://ehcache.org/documentation/jruby.html)
-
### ActiveSupport::Cache::NullStore
-This cache store implementation is meant to be used only in development or test environments and it never stores anything. This can be very useful in development when you have code that interacts directly with `Rails.cache`, but caching may interfere with being able to see the results of code changes. With this cache store, all `fetch` and `read` operations will result in a miss.
+This cache store implementation is meant to be used only in development or test environments and it never stores anything. This can be very useful in development when you have code that interacts directly with `Rails.cache` but caching may interfere with being able to see the results of code changes. With this cache store, all `fetch` and `read` operations will result in a miss.
```ruby
config.cache_store = :null_store
```
-### Custom Cache Stores
-
-You can create your own custom cache store by simply extending `ActiveSupport::Cache::Store` and implementing the appropriate methods. In this way, you can swap in any number of caching technologies into your Rails application.
-
-To use a custom cache store, simple set the cache store to a new instance of the class.
-
-```ruby
-config.cache_store = MyCacheStore.new
-```
-
-### Cache Keys
+Cache Keys
+----------
-The keys used in a cache can be any object that responds to either `:cache_key` or to `:to_param`. You can implement the `:cache_key` method on your classes if you need to generate custom keys. Active Record will generate keys based on the class name and record id.
+The keys used in a cache can be any object that responds to either `cache_key` or
+`to_param`. You can implement the `cache_key` method on your classes if you need
+to generate custom keys. Active Record will generate keys based on the class name
+and record id.
You can use Hashes and Arrays of values as cache keys.
@@ -320,7 +452,12 @@ You can use Hashes and Arrays of values as cache keys.
Rails.cache.read(site: "mysite", owners: [owner_1, owner_2])
```
-The keys you use on `Rails.cache` will not be the same as those actually used with the storage engine. They may be modified with a namespace or altered to fit technology backend constraints. This means, for instance, that you can't save values with `Rails.cache` and then try to pull them out with the `memcache-client` gem. However, you also don't need to worry about exceeding the memcached size limit or violating syntax rules.
+The keys you use on `Rails.cache` will not be the same as those actually used with
+the storage engine. They may be modified with a namespace or altered to fit
+technology backend constraints. This means, for instance, that you can't save
+values with `Rails.cache` and then try to pull them out with the `dalli` gem.
+However, you also don't need to worry about exceeding the memcached size limit or
+violating syntax rules.
Conditional GET support
-----------------------
@@ -353,7 +490,7 @@ class ProductsController < ApplicationController
end
```
-Instead of an options hash, you can also simply pass in a model, Rails will use the `updated_at` and `cache_key` methods for setting `last_modified` and `etag`:
+Instead of an options hash, you can also simply pass in a model. Rails will use the `updated_at` and `cache_key` methods for setting `last_modified` and `etag`:
```ruby
class ProductsController < ApplicationController
@@ -383,3 +520,9 @@ class ProductsController < ApplicationController
end
end
```
+
+References
+----------
+
+* [DHH's article on key-based expiration](https://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works)
+* [Ryan Bates' Railscast on cache digests](http://railscasts.com/episodes/387-cache-digests)
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index 3bd84b1ce6..cd265331d6 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -64,10 +64,10 @@ $ cd commandsapp
$ bin/rails server
=> Booting WEBrick
=> Rails 5.0.0 application starting in development on http://localhost:3000
-=> Call with -d to detach
+=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
[2013-08-07 02:00:01] INFO WEBrick 1.3.1
-[2013-08-07 02:00:01] INFO ruby 2.0.0 (2013-06-27) [x86_64-darwin11.2.0]
+[2013-08-07 02:00:01] INFO ruby 2.2.2 (2015-06-27) [x86_64-darwin11.2.0]
[2013-08-07 02:00:01] INFO WEBrick::HTTPServer#start: pid=69680 port=3000
```
@@ -260,7 +260,13 @@ $ bin/rake db:migrate
== CreateHighScores: migrated (0.0019s) ======================================
```
-INFO: Let's talk about unit tests. Unit tests are code that tests and makes assertions about code. In unit testing, we take a little part of code, say a method of a model, and test its inputs and outputs. Unit tests are your friend. The sooner you make peace with the fact that your quality of life will drastically increase when you unit test your code, the better. Seriously. We'll make one in a moment.
+INFO: Let's talk about unit tests. Unit tests are code that tests and makes assertions
+about code. In unit testing, we take a little part of code, say a method of a model,
+and test its inputs and outputs. Unit tests are your friend. The sooner you make
+peace with the fact that your quality of life will drastically increase when you unit
+test your code, the better. Seriously. Please visit
+[the testing guide](http://guides.rubyonrails.org/testing.html) for an in-depth
+look at unit testing.
Let's see the interface Rails created for us.
@@ -402,8 +408,8 @@ INFO: You can also use `rake -T` to get the list of tasks.
$ bin/rake about
About your application's environment
Rails version 5.0.0
-Ruby version 2.2.1 (x86_64-linux)
-RubyGems version 2.4.5
+Ruby version 2.2.2 (x86_64-linux)
+RubyGems version 2.4.6
Rack version 1.6
JavaScript Runtime Node.js (V8)
Middleware Rack::Sendfile, ActionDispatch::Static, Rack::Lock, #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007ffd131a7c88>, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, Rack::Head, Rack::ConditionalGet, Rack::ETag
@@ -423,7 +429,7 @@ If you want to clear `public/assets` completely, you can use `rake assets:clobbe
The most common tasks of the `db:` Rake namespace are `migrate` and `create`, and it will pay off to try out all of the migration rake tasks (`up`, `down`, `redo`, `reset`). `rake db:version` is useful when troubleshooting, telling you the current version of the database.
-More information about migrations can be found in the [Migrations](migrations.html) guide.
+More information about migrations can be found in the [Migrations](active_record_migrations.html) guide.
### `notes`
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index c6f6ef6dd5..47c275609e 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -88,8 +88,6 @@ application. Accepts a valid week day symbol (e.g. `:monday`).
end
```
-* `config.dependency_loading` is a flag that allows you to disable constant autoloading setting it to false. It only has effect if `config.cache_classes` is true, which it is by default in production mode.
-
* `config.eager_load` when true, eager loads all registered `config.eager_load_namespaces`. This includes your application, engines, Rails frameworks and any other registered namespace.
* `config.eager_load_namespaces` registers namespaces that are eager loaded when `config.eager_load` is true. All namespaces in the list must respond to the `eager_load!` method.
@@ -114,7 +112,7 @@ numbers. New applications filter out passwords by adding the following `config.f
defaults to `:debug` for all environments. The available log levels are: `:debug`,
`:info`, `:warn`, `:error`, `:fatal`, and `:unknown`.
-* `config.log_tags` accepts a list of methods that the `request` object responds to. This makes it easy to tag log lines with debug information like subdomain and request id - both very helpful in debugging multi-user production applications.
+* `config.log_tags` accepts a list of: methods that the `request` object responds to, a `Proc` that accepts the `request` object, or something that responds to `to_s`. This makes it easy to tag log lines with debug information like subdomain and request id - both very helpful in debugging multi-user production applications.
* `config.logger` accepts a logger conforming to the interface of Log4r or the default Ruby `Logger` class. Defaults to an instance of `ActiveSupport::Logger`.
@@ -163,8 +161,6 @@ pipeline is enabled. It is set to true by default.
* `config.assets.cache_store` defines the cache store that Sprockets will use. The default is the Rails file store.
-* `config.assets.version` is an option string that is used in MD5 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.
@@ -185,15 +181,17 @@ The full set of methods that can be used in this block are as follows:
* `assets` allows to create assets on generating a scaffold. Defaults to `true`.
* `force_plural` allows pluralized model names. Defaults to `false`.
* `helper` defines whether or not to generate helpers. Defaults to `true`.
-* `integration_tool` defines which integration tool to use. Defaults to `nil`.
+* `integration_tool` defines which integration tool to use to generate integration tests. Defaults to `:test_unit`.
* `javascripts` turns on the hook for JavaScript files in generators. Used in Rails for when the `scaffold` generator is run. Defaults to `true`.
-* `javascript_engine` configures the engine to be used (for eg. coffee) when generating assets. Defaults to `nil`.
+* `javascript_engine` configures the engine to be used (for eg. coffee) when generating assets. Defaults to `:js`.
* `orm` defines which orm to use. Defaults to `false` and will use Active Record by default.
* `resource_controller` defines which generator to use for generating a controller when using `rails generate resource`. Defaults to `:controller`.
+* `resource_route` defines whether a resource route definition should be generated
+ or not. Defaults to `true`.
* `scaffold_controller` different from `resource_controller`, defines which generator to use for generating a _scaffolded_ controller when using `rails generate scaffold`. Defaults to `:scaffold_controller`.
* `stylesheets` turns on the hook for stylesheets in generators. Used in Rails for when the `scaffold` generator is run, but this hook can be used in other generates as well. Defaults to `true`.
* `stylesheet_engine` configures the stylesheet engine (for eg. sass) to be used when generating assets. Defaults to `:css`.
-* `test_framework` defines which test framework to use. Defaults to `false` and will use Test::Unit by default.
+* `test_framework` defines which test framework to use. Defaults to `false` and will use Minitest by default.
* `template_engine` defines which template engine to use, such as ERB or Haml. Defaults to `:erb`.
### Configuring Middleware
@@ -201,7 +199,7 @@ The full set of methods that can be used in this block are as follows:
Every Rails application comes with a standard set of middleware which it uses in this order in the development environment:
* `ActionDispatch::SSL` forces every request to be under HTTPS protocol. Will be available if `config.force_ssl` is set to `true`. Options passed to this can be configured by using `config.ssl_options`.
-* `ActionDispatch::Static` is used to serve static assets. Disabled if `config.serve_static_files` is `false`.
+* `ActionDispatch::Static` is used to serve static assets. Disabled if `config.serve_static_files` is `false`. Set `config.static_index` if you need to serve a static directory index file that is not named `index`. For example, to serve `main.html` instead of `index.html` for directory requests, set `config.static_index` to `"main"`.
* `Rack::Lock` wraps the app in mutex so it can only be called by a single thread at a time. Only enabled when `config.cache_classes` is `false`.
* `ActiveSupport::Cache::Strategy::LocalCache` serves as a basic memory backed cache. This cache is not thread safe and is intended only for serving as a temporary memory cache for a single thread.
* `Rack::Runtime` sets an `X-Runtime` header, containing the time (in seconds) taken to execute the request.
@@ -328,7 +326,7 @@ The schema dumper adds one additional configuration option:
* `config.action_controller.asset_host` sets the host for the assets. Useful when CDNs are used for hosting assets rather than the application server itself.
-* `config.action_controller.perform_caching` configures whether the application should perform caching or not. Set to false in development mode, true in production.
+* `config.action_controller.perform_caching` configures whether the application should perform the caching features provided by the Action Controller component or not. Set to false in development mode, true in production.
* `config.action_controller.default_static_extension` configures the extension used for cached pages. Defaults to `.html`.
@@ -416,7 +414,7 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
`config.action_view` includes a small number of configuration settings:
-* `config.action_view.field_error_proc` provides an HTML generator for displaying errors that come from Active Record. The default is
+* `config.action_view.field_error_proc` provides an HTML generator for displaying errors that come from Active Model. The default is
```ruby
Proc.new do |html_tag, instance|
@@ -424,13 +422,23 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
end
```
-* `config.action_view.default_form_builder` tells Rails which form builder to use by default. The default is `ActionView::Helpers::FormBuilder`. If you want your form builder class to be loaded after initialization (so it's reloaded on each request in development), you can pass it as a `String`
+* `config.action_view.default_form_builder` tells Rails which form builder to
+ use by default. The default is `ActionView::Helpers::FormBuilder`. If you
+ want your form builder class to be loaded after initialization (so it's
+ reloaded on each request in development), you can pass it as a `String`.
* `config.action_view.logger` accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Action View. Set to `nil` to disable logging.
* `config.action_view.erb_trim_mode` gives the trim mode to be used by ERB. It defaults to `'-'`, which turns on trimming of tail spaces and newline when using `<%= -%>` or `<%= =%>`. See the [Erubis documentation](http://www.kuwata-lab.com/erubis/users-guide.06.html#topics-trimspaces) for more information.
-* `config.action_view.embed_authenticity_token_in_remote_forms` allows you to set the default behavior for `authenticity_token` in forms with `:remote => true`. By default it's set to false, which means that remote forms will not include `authenticity_token`, which is helpful when you're fragment-caching the form. Remote forms get the authenticity from the `meta` tag, so embedding is unnecessary unless you support browsers without JavaScript. In such case you can either pass `:authenticity_token => true` as a form option or set this config setting to `true`
+* `config.action_view.embed_authenticity_token_in_remote_forms` allows you to
+ set the default behavior for `authenticity_token` in forms with `remote:
+ true`. By default it's set to false, which means that remote forms will not
+ include `authenticity_token`, which is helpful when you're fragment-caching
+ the form. Remote forms get the authenticity from the `meta` tag, so embedding
+ is unnecessary unless you support browsers without JavaScript. In such case
+ you can either pass `authenticity_token: true` as a form option or set this
+ config setting to `true`.
* `config.action_view.prefix_partial_path_with_controller_namespace` determines whether or not partials are looked up from a subdirectory in templates rendered from namespaced controllers. For example, consider a controller named `Admin::ArticlesController` which renders this template:
@@ -440,7 +448,8 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
The default setting is `true`, which uses the partial at `/admin/articles/_article.erb`. Setting the value to `false` would render `/articles/_article.erb`, which is the same behavior as rendering from a non-namespaced controller such as `ArticlesController`.
-* `config.action_view.raise_on_missing_translations` determines whether an error should be raised for missing translations
+* `config.action_view.raise_on_missing_translations` determines whether an
+ error should be raised for missing translations.
### Configuring Action Mailer
@@ -507,15 +516,18 @@ There are a number of settings available on `config.action_mailer`:
config.action_mailer.show_previews = false
```
+* `config.action_mailer.deliver_later_queue_name` specifies the queue name for
+ mailers. By default this is `mailers`.
+
### Configuring Active Support
There are a few configuration options available in Active Support:
* `config.active_support.bare` enables or disables the loading of `active_support/all` when booting Rails. Defaults to `nil`, which means `active_support/all` is loaded.
-* `config.active_support.test_order` sets the order that test cases are executed. Possible values are `:sorted` and `:random`. Currently defaults to `:sorted`. In Rails 5.0, the default will be changed to `:random` instead.
+* `config.active_support.test_order` sets the order that test cases are executed. Possible values are `:random` and `:sorted`. This option is set to `:random` in `config/environments/test.rb` in newly-generated applications. If you have an application that does not specify a `test_order`, it will default to `:sorted`, *until* Rails 5.0, when the default will become `:random`.
-* `config.active_support.escape_html_entities_in_json` enables or disables the escaping of HTML entities in JSON serialization. Defaults to `false`.
+* `config.active_support.escape_html_entities_in_json` enables or disables the escaping of HTML entities in JSON serialization. Defaults to `true`.
* `config.active_support.use_standard_json_time_format` enables or disables serializing dates to ISO 8601 format. Defaults to `true`.
@@ -533,6 +545,58 @@ There are a few configuration options available in Active Support:
* `ActiveSupport::Deprecation.silenced` sets whether or not to display deprecation warnings.
+### Configuring Active Job
+
+`config.active_job` provides the following configuration options:
+
+* `config.active_job.queue_adapter` sets the adapter for the queueing backend. The default adapter is `:inline` which will perform jobs immediately. For an up-to-date list of built-in adapters see the [ActiveJob::QueueAdapters API documentation](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html).
+
+ ```ruby
+ # Be sure to have the adapter's gem in your Gemfile
+ # and follow the adapter's specific installation
+ # and deployment instructions.
+ config.active_job.queue_adapter = :sidekiq
+ ```
+
+* `config.active_job.default_queue_name` can be used to change the default queue name. By default this is `"default"`.
+
+ ```ruby
+ config.active_job.default_queue_name = :medium_priority
+ ```
+
+* `config.active_job.queue_name_prefix` allows you to set an optional, non-blank, queue name prefix for all jobs. By default it is blank and not used.
+
+ The following configuration would queue the given job on the `production_high_priority` queue when run in production:
+
+ ```ruby
+ config.active_job.queue_name_prefix = Rails.env
+ ```
+
+ ```ruby
+ class GuestsCleanupJob < ActiveJob::Base
+ queue_as :high_priority
+ #....
+ end
+ ```
+
+* `config.active_job.queue_name_delimiter` has a default value of `'_'`. If `queue_name_prefix` is set, then `queue_name_delimiter` joins the prefix and the non-prefixed queue name.
+
+ The following configuration would queue the provided job on the `video_server.low_priority` queue:
+
+ ```ruby
+ # prefix must be set for delimiter to be used
+ config.active_job.queue_name_prefix = 'video_server'
+ config.active_job.queue_name_delimiter = '.'
+ ```
+
+ ```ruby
+ class EncoderJob < ActiveJob::Base
+ queue_as :low_priority
+ #....
+ end
+ ```
+
+* `config.active_job.logger` accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Active Job. You can retrieve this logger by calling `logger` on either an Active Job class or an Active Job instance. Set to `nil` to disable logging.
### Configuring a Database
@@ -960,6 +1024,11 @@ Below is a comprehensive list of all the initializers found in Rails in the orde
* `active_record.set_dispatch_hooks` Resets all reloadable connections to the database if `config.cache_classes` is set to `false`.
+* `active_job.logger` Sets `ActiveJob::Base.logger` - if it's not already set -
+ to `Rails.logger`.
+
+* `active_job.set_configs` Sets up Active Job by using the settings in `config.active_job` by `send`'ing the method names as setters to `ActiveJob::Base` and passing the values through.
+
* `action_mailer.logger` Sets `ActionMailer::Base.logger` - if it's not already set - to `Rails.logger`.
* `action_mailer.set_configs` Sets up Action Mailer by using the settings in `config.action_mailer` by `send`'ing the method names as setters to `ActionMailer::Base` and passing the values through.
@@ -1015,22 +1084,22 @@ 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. Initially, the database connection pool is empty and it will create additional connections as the demand for them increases, until it reaches the connection pool limit.
+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.
-Any one request will check out a connection the first time it requires access to the database, after which it will check the connection back in, at the end of the request, meaning that the additional connection slot will be available again for the next request in the queue.
+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.
If you try to use more connections than are available, Active Record will block
-and wait for a connection from the pool. When it cannot get connection, a timeout
-error similar to given below will be thrown.
+you and wait for a connection from the pool. If it cannot get a connection, a
+timeout error similar to that given below will be thrown.
```ruby
ActiveRecord::ConnectionTimeoutError - could not obtain a database connection within 5 seconds. The max pool size is currently 5; consider increasing it:
```
-If you get the above error, you might want to increase the size of connection
-pool by incrementing the `pool` option in `database.yml`
+If you get the above error, you might want to increase the size of the
+connection pool by incrementing the `pool` option in `database.yml`
-NOTE. If you are running in a multi-threaded environment, there could be a chance that several threads may be accessing multiple connections simultaneously. So depending on your current request load, you could very well have multiple threads contending for a limited amount of connections.
+NOTE. If you are running in a multi-threaded environment, there could be a chance that several threads may be accessing multiple connections simultaneously. So depending on your current request load, you could very well have multiple threads contending for a limited number of connections.
Custom configuration
@@ -1058,7 +1127,7 @@ Search Engines Indexing
Sometimes, you may want to prevent some pages of your application to be visible
on search sites like Google, Bing, Yahoo or Duck Duck Go. The robots that index
-these sites will first analyse the `http://your-site.com/robots.txt` file to
+these sites will first analyze the `http://your-site.com/robots.txt` file to
know which pages it is allowed to index.
Rails creates this file for you inside the `/public` folder. By default, it allows
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index 018100c316..3b944f1274 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -28,19 +28,23 @@ NOTE: Bugs in the most recent released version of Ruby on Rails are likely to ge
If you've found a problem in Ruby on Rails which is not a security risk, do a search on GitHub under [Issues](https://github.com/rails/rails/issues) in case it has already been reported. If you are unable to find any open GitHub issues addressing the problem you found, your next step will be to [open a new one](https://github.com/rails/rails/issues/new). (See the next section for reporting security issues.)
-Your issue report should contain a title and a clear description of the issue at the bare minimum. You should include as much relevant information as possible and should at least post a code sample that demonstrates the issue. It would be even better if you could include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself - and others - to replicate the bug and figure out a fix.
+Your issue report should contain a title and a clear description of the issue at the bare minimum. You should include as much relevant information as possible and should at least post a code sample that demonstrates the issue. It would be even better if you could include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself - and others - to reproduce the bug and figure out a fix.
Then, don't get your hopes up! Unless you have a "Code Red, Mission Critical, the World is Coming to an End" kind of bug, you're creating this issue report in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the issue report will automatically see any activity or that others will jump to fix it. Creating an issue like this is mostly to help yourself start on the path of fixing the problem and for others to confirm it with an "I'm having this problem too" comment.
-### Create a Self-Contained gist for Active Record and Action Controller Issues
+### Create an Executable Test Case
-If you are filing a bug report, please use
-[Active Record template for gems](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_gem.rb) or
-[Action Controller template for gems](https://github.com/rails/rails/blob/master/guides/bug_report_templates/action_controller_gem.rb)
-if the bug is found in a published gem, and
-[Active Record template for master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_master.rb) or
-[Action Controller template for master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/action_controller_master.rb)
-if the bug happens in the master branch.
+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 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)
+* 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`).
+
+Simply copy the content of the appropriate template into a `.rb` file and make the necessary changes to demonstrate the issue. You can execute it by running `ruby the_file.rb` in your terminal. If all goes well, you should see your test case failing.
+
+You can then share your executable test case as a [gist](https://gist.github.com), or simply paste the content into the issue description.
### Special Treatment for Security Issues
@@ -57,12 +61,12 @@ can expect it to be marked "invalid" as soon as it's reviewed.
Sometimes, the line between 'bug' and 'feature' is a hard one to draw.
Generally, a feature is anything that adds new behavior, while a bug is
-anything that fixes already existing behavior that is misbehaving. Sometimes,
+anything that causes incorrect behavior. Sometimes,
the core team will have to make a judgement call. That said, the distinction
generally just affects which release your patch will get in to; we love feature
submissions! They just won't get backported to maintenance branches.
-If you'd like feedback on an idea for a feature before doing the work for make
+If you'd like feedback on an idea for a feature before doing the work to make
a patch, please send an email to the [rails-core mailing
list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core). You
might get no response, which means that everyone is indifferent. You might find
@@ -75,17 +79,17 @@ discussions new features require.
Helping to Resolve Existing Issues
----------------------------------
-As a next step beyond reporting issues, you can help the core team resolve existing issues. If you check the [Everyone's Issues](https://github.com/rails/rails/issues) list in GitHub Issues, you'll find lots of issues already requiring attention. What can you do for these? Quite a bit, actually:
+As a next step beyond reporting issues, you can help the core team resolve existing issues. If you check the [issues list](https://github.com/rails/rails/issues) in GitHub Issues, you'll find lots of issues already requiring attention. What can you do for these? Quite a bit, actually:
### Verifying Bug Reports
For starters, it helps just to verify bug reports. Can you reproduce the reported issue on your own computer? If so, you can add a comment to the issue saying that you're seeing the same thing.
-If something is very vague, can you help squash it down into something specific? Maybe you can provide additional information to help reproduce a bug, or help by eliminating needless steps that aren't required to demonstrate the problem.
+If an issue is very vague, can you help narrow it down to something more specific? Maybe you can provide additional information to help reproduce a bug, or help by eliminating needless steps that aren't required to demonstrate the problem.
If you find a bug report without a test, it's very useful to contribute a failing test. This is also a great way to get started exploring the source code: looking at the existing test files will teach you how to write more tests. New tests are best contributed in the form of a patch, as explained later on in the "Contributing to the Rails Code" section.
-Anything you can do to make bug reports more succinct or easier to reproduce is a help to folks trying to write code to fix those bugs - whether you end up writing the code yourself or not.
+Anything you can do to make bug reports more succinct or easier to reproduce helps folks trying to write code to fix those bugs - whether you end up writing the code yourself or not.
### Testing Patches
@@ -113,7 +117,7 @@ Once you're happy that the pull request contains a good change, comment on the G
>I like the way you've restructured that code in generate_finder_sql - much nicer. The tests look good too.
-If your comment simply says "+1", then odds are that other reviewers aren't going to take it too seriously. Show that you took the time to review the pull request.
+If your comment simply reads "+1", then odds are that other reviewers aren't going to take it too seriously. Show that you took the time to review the pull request.
Contributing to the Rails Documentation
---------------------------------------
@@ -291,7 +295,7 @@ You can run a single test through ruby. For instance:
```bash
$ cd actionmailer
-$ ruby -w -Itest test/mail_layout_test.rb -n test_explicit_class_layout
+$ bundle exec ruby -w -Itest test/mail_layout_test.rb -n test_explicit_class_layout
```
The `-n` option allows you to run a single method instead of the whole
@@ -330,7 +334,7 @@ will now run the four of them in turn.
You can also run any single test separately:
```bash
-$ ARCONN=sqlite3 ruby -Itest test/cases/associations/has_many_associations_test.rb
+$ ARCONN=sqlite3 bundle exec ruby -Itest test/cases/associations/has_many_associations_test.rb
```
To run a single test against all adapters, use:
@@ -355,9 +359,9 @@ $ RUBYOPT=-W0 bundle exec rake test
The CHANGELOG is an important part of every release. It keeps the list of changes for every Rails version.
-You should add an entry to the CHANGELOG of the framework that you modified if you're adding or removing a feature, committing a bug fix or adding deprecation notices. Refactorings and documentation changes generally should not go to the CHANGELOG.
+You should add an entry **to the top** of the CHANGELOG of the framework that you modified if you're adding or removing a feature, committing a bug fix or adding deprecation notices. Refactorings and documentation changes generally should not go to the CHANGELOG.
-A CHANGELOG entry should summarize what was changed and should end with author's name and it should go on top of a CHANGELOG. You can use multiple lines if you need more space and you can attach code examples indented with 4 spaces. If a change is related to a specific issue, you should attach the issue's number. Here is an example CHANGELOG entry:
+A CHANGELOG entry should summarize what was changed and should end with the author's name. You can use multiple lines if you need more space and you can attach code examples indented with 4 spaces. If a change is related to a specific issue, you should attach the issue's number. Here is an example CHANGELOG entry:
```
* Summary of a change that briefly describes what was changed. You can use multiple
@@ -374,7 +378,8 @@ A CHANGELOG entry should summarize what was changed and should end with author's
*Your Name*
```
-Your name can be added directly after the last word if you don't provide any code examples or don't need multiple paragraphs. Otherwise, it's best to make as a new paragraph.
+Your name can be added directly after the last word if there are no code
+examples or multiple paragraphs. Otherwise, it's best to make a new paragraph.
### Updating the Gemfile.lock
@@ -398,21 +403,27 @@ When you're happy with the code on your computer, you need to commit the changes
$ git commit -a
```
-At this point, your editor should be fired up and you can write a message for this commit. Well formatted and descriptive commit messages are extremely helpful for the others, especially when figuring out why given change was made, so please take the time to write it.
+This should fire up your editor to write a commit message. When you have
+finished, save and close to continue.
+
+A well-formatted and descriptive commit message is very helpful to others for
+understanding why the change was made, so please take the time to write it.
-Good commit message should be formatted according to the following example:
+A good commit message looks like this:
```
Short summary (ideally 50 characters or less)
-More detailed description, if necessary. It should be wrapped to 72
-characters. Try to be as descriptive as you can, even if you think that
-the commit content is obvious, it may not be obvious to others. You
-should add such description also if it's already present in bug tracker,
-it should not be necessary to visit a webpage to check the history.
+More detailed description, if necessary. It should be wrapped to
+72 characters. Try to be as descriptive as you can. Even if you
+think that the commit content is obvious, it may not be obvious
+to others. Add any description that is already present in the
+relevant issues; it should not be necessary to visit a webpage
+to check the history.
+
+The description section can have multiple paragraphs.
-Description can have multiple paragraphs and you can use code examples
-inside, just indent it with 4 spaces:
+Code examples can be embedded by indenting them with 4 spaces:
class ArticlesController
def index
@@ -422,13 +433,15 @@ inside, just indent it with 4 spaces:
You can also add bullet points:
-- you can use dashes or asterisks
+- make a bullet point by starting a line with either a dash (-)
+ or an asterisk (*)
-- also, try to indent next line of a point for readability, if it's too
- long to fit in 72 characters
+- wrap lines at 72 characters, and indent any additional lines
+ with 2 spaces for readability
```
-TIP. Please squash your commits into a single commit when appropriate. This simplifies future cherry picks, and also keeps the git log clean.
+TIP. Please squash your commits into a single commit when appropriate. This
+simplifies future cherry picks and keeps the git log clean.
### Update Your Branch
@@ -519,7 +532,7 @@ pull request". The Rails core team will be notified about your submission.
Most pull requests will go through a few iterations before they get merged.
Different contributors will sometimes have different opinions, and often
-patches will need revised before they can get merged.
+patches will need to be revised before they can get merged.
Some contributors to Rails have email notifications from GitHub turned on, but
others do not. Furthermore, (almost) everyone who works on Rails is a
@@ -566,8 +579,7 @@ following:
```bash
$ git fetch upstream
$ git checkout my_pull_request
-$ git rebase upstream/master
-$ git rebase -i
+$ git rebase -i upstream/master
< Choose 'squash' for all of your commits except the first one. >
< Edit the commit message to make sense, and describe all your changes. >
diff --git a/guides/source/credits.html.erb b/guides/source/credits.html.erb
index 61ea0b44ef..1d995581fa 100644
--- a/guides/source/credits.html.erb
+++ b/guides/source/credits.html.erb
@@ -28,7 +28,7 @@ Ruby on Rails Guides: Credits
<h3 class="section">Rails Guides Authors</h3>
<%= author('Ryan Bigg', 'radar', 'radar.png') do %>
- Ryan Bigg works as the Community Manager at <a href="http://spreecommerce.com">Spree Commerce</a> and has been working with Rails since 2006. He's the author of <a href="https://leanpub.com/multi-tenancy-rails">Multi Tenancy With Rails</a> and co-author of <a href="http://manning.com/bigg2">Rails 4 in Action</a>. He's written many gems which can be seen on <a href="https://github.com/radar">his GitHub page</a> and he also tweets prolifically as <a href="http://twitter.com/ryanbigg">@ryanbigg</a>.
+ Ryan Bigg works as a Rails developer at <a href="http://marketplacer.com">Marketplacer</a> and has been working with Rails since 2006. He's the author of <a href="https://leanpub.com/multi-tenancy-rails">Multi Tenancy With Rails</a> and co-author of <a href="http://manning.com/bigg2">Rails 4 in Action</a>. He's written many gems which can be seen on <a href="https://github.com/radar">his GitHub page</a> and he also tweets prolifically as <a href="http://twitter.com/ryanbigg">@ryanbigg</a>.
<% end %>
<%= author('Oscar Del Ben', 'oscardelben', 'oscardelben.jpg') do %>
diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md
index a9715fb837..44434c164b 100644
--- a/guides/source/debugging_rails_applications.md
+++ b/guides/source/debugging_rails_applications.md
@@ -17,7 +17,7 @@ After reading this guide, you will know:
View Helpers for Debugging
--------------------------
-One common task is to inspect the contents of a variable. In Rails, you can do this with three methods:
+One common task is to inspect the contents of a variable. Rails provides three different ways to do this:
* `debug`
* `to_yaml`
@@ -54,7 +54,7 @@ Title: Rails debugging guide
### `to_yaml`
-Displaying an instance variable, or any other object or method, in YAML format can be achieved this way:
+Alternatively, calling `to_yaml` on any object converts it to YAML. You can pass this converted object into the `simple_format` helper method to format the output. This is how `debug` does its magic.
```html+erb
<%= simple_format @article.to_yaml %>
@@ -64,9 +64,7 @@ Displaying an instance variable, or any other object or method, in YAML format c
</p>
```
-The `to_yaml` method converts the method to YAML format leaving it more readable, and then the `simple_format` helper is used to render each line as in the console. This is how `debug` method does its magic.
-
-As a result of this, you will have something like this in your view:
+The above code will render something like this:
```yaml
--- !ruby/object Article
@@ -94,7 +92,7 @@ Another useful method for displaying object values is `inspect`, especially when
</p>
```
-Will be rendered as follows:
+Will render:
```
[1, 2, 3, 4, 5]
@@ -109,9 +107,9 @@ It can also be useful to save information to log files at runtime. Rails maintai
### What is the Logger?
-Rails makes use of the `ActiveSupport::Logger` class to write log information. You can also substitute another logger such as `Log4r` if you wish.
+Rails makes use of the `ActiveSupport::Logger` class to write log information. Other loggers, such as `Log4r`, may also be substituted.
-You can specify an alternative logger in your `environment.rb` or any environment file:
+You can specify an alternative logger in `environment.rb` or any other environment file, for example:
```ruby
Rails.logger = Logger.new(STDOUT)
@@ -129,16 +127,21 @@ TIP: By default, each log is created under `Rails.root/log/` and the log file is
### Log Levels
-When something is logged it's printed into the corresponding log if the log level of the message is equal or higher than the configured log level. If you want to know the current log level you can call the `Rails.logger.level` method.
+When something is logged, it's printed into the corresponding log if the log
+level of the message is equal to or higher than the configured log level. If you
+want to know the current log level, you can call the `Rails.logger.level`
+method.
-The available log levels are: `:debug`, `:info`, `:warn`, `:error`, `:fatal`, and `:unknown`, corresponding to the log level numbers from 0 up to 5 respectively. To change the default log level, use
+The available log levels are: `:debug`, `:info`, `:warn`, `:error`, `:fatal`,
+and `:unknown`, corresponding to the log level numbers from 0 up to 5,
+respectively. To change the default log level, use
```ruby
config.log_level = :warn # In any environment initializer, or
Rails.logger.level = 0 # at any time
```
-This is useful when you want to log under development or staging, but you don't want to flood your production log with unnecessary information.
+This is useful when you want to log under development or staging without flooding your production log with unnecessary information.
TIP: The default Rails log level is `debug` in all environments.
@@ -202,7 +205,7 @@ Adding extra logging like this makes it easy to search for unexpected or unusual
When running multi-user, multi-account applications, it's often useful
to be able to filter the logs using some custom rules. `TaggedLogging`
-in Active Support helps in doing exactly that by stamping log lines with subdomains, request ids, and anything else to aid debugging such applications.
+in Active Support helps you do exactly that by stamping log lines with subdomains, request ids, and anything else to aid debugging such applications.
```ruby
logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
@@ -212,34 +215,33 @@ logger.tagged("BCX") { logger.tagged("Jason") { logger.info "Stuff" } } # Logs "
```
### Impact of Logs on Performance
-Logging will always have a small impact on performance of your rails app,
- particularly when logging to disk. However, there are a few subtleties:
+Logging will always have a small impact on the performance of your Rails app,
+ particularly when logging to disk. Additionally, there are a few subtleties:
Using the `:debug` level will have a greater performance penalty than `:fatal`,
as a far greater number of strings are being evaluated and written to the
log output (e.g. disk).
-Another potential pitfall is that if you have many calls to `Logger` like this
- in your code:
+Another potential pitfall is too many calls to `Logger` in your code:
```ruby
logger.debug "Person attributes hash: #{@person.attributes.inspect}"
```
-In the above example, There will be a performance impact even if the allowed
+In the above example, there will be a performance impact even if the allowed
output level doesn't include debug. The reason is that Ruby has to evaluate
these strings, which includes instantiating the somewhat heavy `String` object
-and interpolating the variables, and which takes time.
+and interpolating the variables.
Therefore, it's recommended to pass blocks to the logger methods, as these are
-only evaluated if the output level is the same or included in the allowed level
+only evaluated if the output level is the same as — or included in — the allowed level
(i.e. lazy loading). The same code rewritten would be:
```ruby
logger.debug {"Person attributes hash: #{@person.attributes.inspect}"}
```
-The contents of the block, and therefore the string interpolation, is only
-evaluated if debug is enabled. This performance savings is only really
+The contents of the block, and therefore the string interpolation, are only
+evaluated if debug is enabled. This performance savings are only really
noticeable with large amounts of logging, but it's a good practice to employ.
Debugging with the `byebug` gem
@@ -253,8 +255,7 @@ is your best companion.
The debugger can also help you if you want to learn about the Rails source code
but don't know where to start. Just debug any request to your application and
-use this guide to learn how to move from the code you have written deeper into
-Rails code.
+use this guide to learn how to move from the code you have written into the underlying Rails code.
### Setup
@@ -285,7 +286,7 @@ As soon as your application calls the `byebug` method, the debugger will be
started in a debugger shell inside the terminal window where you launched your
application server, and you will be placed at the debugger's prompt `(byebug)`.
Before the prompt, the code around the line that is about to be run will be
-displayed and the current line will be marked by '=>'. Like this:
+displayed and the current line will be marked by '=>', like this:
```
[1, 10] in /PathTo/project/app/controllers/articles_controller.rb
@@ -316,7 +317,7 @@ For example:
=> Notice: server is listening on all interfaces (0.0.0.0). Consider using 127.0.0.1 (--binding option)
=> Ctrl-C to shutdown server
[2014-04-11 13:11:47] INFO WEBrick 1.3.1
-[2014-04-11 13:11:47] INFO ruby 2.1.1 (2014-02-24) [i686-linux]
+[2014-04-11 13:11:47] INFO ruby 2.2.2 (2015-04-13) [i686-linux]
[2014-04-11 13:11:47] INFO WEBrick::HTTPServer#start: pid=6370 port=3000
@@ -339,7 +340,7 @@ Processing by ArticlesController#index as HTML
(byebug)
```
-Now it's time to explore and dig into your application. A good place to start is
+Now it's time to explore your application. A good place to start is
by asking the debugger for help. Type: `help`
```
@@ -360,9 +361,9 @@ continue edit frame kill pp reload skip undisplay
TIP: To view the help menu for any command use `help <command-name>` at the
debugger prompt. For example: _`help list`_. You can abbreviate any debugging
command by supplying just enough letters to distinguish them from other
-commands, so you can also use `l` for the `list` command, for example.
+commands. For example, you can use `l` for the `list` command.
-To see the previous ten lines you should type `list-` (or `l-`)
+To see the previous ten lines you should type `list-` (or `l-`).
```
(byebug) l-
@@ -381,7 +382,7 @@ To see the previous ten lines you should type `list-` (or `l-`)
```
-This way you can move inside the file, being able to see the code above and over
+This way you can move inside the file and see the code above
the line where you added the `byebug` call. Finally, to see where you are in
the code again you can type `list=`
@@ -411,8 +412,7 @@ contexts as you go through the different parts of the stack.
The debugger creates a context when a stopping point or an event is reached. The
context has information about the suspended program which enables the debugger
to inspect the frame stack, evaluate variables from the perspective of the
-debugged program, and contains information about the place where the debugged
-program is stopped.
+debugged program, and know the place where the debugged program is stopped.
At any time you can call the `backtrace` command (or its alias `where`) to print
the backtrace of the application. This can be very helpful to know how you got
@@ -476,9 +476,8 @@ character and the number indicates the current thread of execution.
* `thread resume _n_` resumes thread _n_.
* `thread switch _n_` switches the current thread context to _n_.
-This command is very helpful, among other occasions, when you are debugging
-concurrent threads and need to verify that there are no race conditions in your
-code.
+This command is very helpful when you are debugging concurrent threads and need
+to verify that there are no race conditions in your code.
### Inspecting Variables
@@ -503,7 +502,7 @@ current context:
(byebug) instance_variables
[:@_action_has_layout, :@_routes, :@_headers, :@_status, :@_request,
- :@_response, :@_env, :@_prefixes, :@_lookup_context, :@_action_name,
+ :@_response, :@_prefixes, :@_lookup_context, :@_action_name,
:@_response_body, :@marked_for_same_origin_verification, :@_config]
```
@@ -532,19 +531,22 @@ command later in this guide).
And then ask again for the instance_variables:
```
-(byebug) instance_variables.include? "@articles"
-true
+(byebug) instance_variables
+[:@_action_has_layout, :@_routes, :@_headers, :@_status, :@_request,
+ :@_response, :@_prefixes, :@_lookup_context, :@_action_name,
+ :@_response_body, :@marked_for_same_origin_verification, :@_config,
+ :@articles]
```
Now `@articles` is included in the instance variables, because the line defining it
was executed.
TIP: You can also step into **irb** mode with the command `irb` (of course!).
-This way an irb session will be started within the context you invoked it. But
+This will start an irb session within the context you invoked it. But
be warned: this is an experimental feature.
The `var` method is the most convenient way to show variables and their values.
-Let's let `byebug` help us with it.
+Let's have `byebug` help us with it.
```
(byebug) help var
@@ -556,7 +558,7 @@ v[ar] l[ocal] show local variables
```
This is a great way to inspect the values of the current context variables. For
-example, to check that we have no local variables currently defined.
+example, to check that we have no local variables currently defined:
```
(byebug) var local
@@ -587,14 +589,14 @@ tracking the values of a variable while the execution goes on.
1: @articles = nil
```
-The variables inside the displaying list will be printed with their values after
+The variables inside the displayed list will be printed with their values after
you move in the stack. To stop displaying a variable use `undisplay _n_` where
_n_ is the variable number (1 in the last example).
### Step by Step
Now you should know where you are in the running trace and be able to print the
-available variables. But lets continue and move on with the application
+available variables. But let's continue and move on with the application
execution.
Use `step` (abbreviated `s`) to continue running your program until the next
@@ -651,8 +653,8 @@ Next went up a frame because previous frame finished
(byebug)
```
-If we use `step` in the same situation, we will literally go the next ruby
-instruction to be executed. In this case, the activesupport's `week` method.
+If we use `step` in the same situation, we will literally go to the next Ruby
+instruction to be executed. In this case, Active Support's `week` method.
```
(byebug) step
@@ -672,8 +674,7 @@ instruction to be executed. In this case, the activesupport's `week` method.
(byebug)
```
-This is one of the best ways to find bugs in your code, or perhaps in Ruby on
-Rails.
+This is one of the best ways to find bugs in your code.
### Breakpoints
@@ -780,7 +781,7 @@ will be stopped and you will have to start it again.
### Settings
-`byebug` has a few available options to tweak its behaviour:
+`byebug` has a few available options to tweak its behavior:
* `set autoreload`: Reload source code when changed (defaults: true).
* `set autolist`: Execute `list` command on every breakpoint (defaults: true).
@@ -809,7 +810,7 @@ controller. The console would be rendered next to your HTML content.
### Console
-Inside any controller action or view, you can then invoke the console by
+Inside any controller action or view, you can invoke the console by
calling the `console` method.
For example, in a controller:
@@ -835,7 +836,7 @@ This will render a console inside your view. You don't need to care about the
location of the `console` call; it won't be rendered on the spot of its
invocation but next to your HTML content.
-The console executes pure Ruby code. You can define and instantiate
+The console executes pure Ruby code: You can define and instantiate
custom classes, create new models and inspect variables.
NOTE: Only one console can be rendered per request. Otherwise `web-console`
@@ -860,7 +861,7 @@ to use it in production.
Debugging Memory Leaks
----------------------
-A Ruby application (on Rails or not), can leak memory - either in the Ruby code
+A Ruby application (on Rails or not), can leak memory — either in the Ruby code
or at the C code level.
In this section, you will learn how to find and fix such leaks by using tool
@@ -891,7 +892,7 @@ footnotes that give request information and link back to your source via
TextMate.
* [Query Trace](https://github.com/ruckus/active-record-query-trace/tree/master) Adds query
origin tracing to your logs.
-* [Query Reviewer](https://github.com/nesquena/query_reviewer) This rails plugin
+* [Query Reviewer](https://github.com/nesquena/query_reviewer) This Rails plugin
not only runs "EXPLAIN" before each of your select queries in development, but
provides a small DIV in the rendered output of each page with the summary of
warnings for each query that it analyzed.
@@ -903,7 +904,7 @@ standard Rails error page with a new one containing more contextual information,
like source code and variable inspection.
* [RailsPanel](https://github.com/dejan/rails_panel) Chrome extension for Rails
development that will end your tailing of development.log. Have all information
-about your Rails app requests in the browser - in the Developer Tools panel.
+about your Rails app requests in the browser — in the Developer Tools panel.
Provides insight to db/rendering/total times, parameter list, rendered views and
more.
diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md
index 989b29956c..3c670a1221 100644
--- a/guides/source/development_dependencies_install.md
+++ b/guides/source/development_dependencies_install.md
@@ -9,7 +9,7 @@ After reading this guide, you will know:
* How to set up your machine for Rails development
* How to run specific groups of unit tests from the Rails test suite
-* How the ActiveRecord portion of the Rails test suite operates
+* How the Active Record portion of the Rails test suite operates
--------------------------------------------------------------------------------
@@ -21,14 +21,14 @@ The easiest and recommended way to get a development environment ready to hack i
The Hard Way
------------
-In case you can't use the Rails development box, see section above, these are the steps to manually build a development box for Ruby on Rails core development.
+In case you can't use the Rails development box, see section below, these are the steps to manually build a development box for Ruby on Rails core development.
### Install Git
Ruby on Rails uses Git for source code control. The [Git homepage](http://git-scm.com/) has installation instructions. There are a variety of resources on the net that will help you get familiar with Git:
* [Try Git course](http://try.github.io/) is an interactive course that will teach you the basics.
-* The [official Documentation](http://git-scm.com/documentation) is pretty comprehensive and also contains some videos with the basics of Git
+* The [official Documentation](http://git-scm.com/documentation) is pretty comprehensive and also contains some videos with the basics of Git.
* [Everyday Git](http://schacon.github.io/git/everyday.html) will teach you just enough about Git to get by.
* The [PeepCode screencast](https://peepcode.com/products/git) on Git is easier to follow.
* [GitHub](http://help.github.com) offers links to a variety of Git resources.
@@ -60,7 +60,7 @@ In Ubuntu you're done with just:
$ sudo apt-get install sqlite3 libsqlite3-dev
```
-And if you are on Fedora or CentOS, you're done with
+If you are on Fedora or CentOS, you're done with
```bash
$ sudo yum install sqlite3 sqlite3-devel
@@ -213,7 +213,7 @@ FreeBSD users will have to run the following:
```bash
# pkg install mysql56-client mysql56-server
-# pkg install postgresql93-client postgresql93-server
+# pkg install postgresql94-client postgresql94-server
```
Or install them through ports (they are located under the `databases` folder).
diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml
index 7ae3640937..4473eba478 100644
--- a/guides/source/documents.yaml
+++ b/guides/source/documents.yaml
@@ -11,7 +11,7 @@
-
name: Active Record Basics
url: active_record_basics.html
- description: This guide will get you started with models, persistence to database and the Active Record pattern and library.
+ description: This guide will get you started with models, persistence to database, and the Active Record pattern and library.
-
name: Active Record Migrations
url: active_record_migrations.html
@@ -19,7 +19,7 @@
-
name: Active Record Validations
url: active_record_validations.html
- description: This guide covers how you can use Active Record validations
+ description: This guide covers how you can use Active Record validations.
-
name: Active Record Callbacks
url: active_record_callbacks.html
@@ -74,7 +74,7 @@
-
name: Rails Internationalization API
url: i18n.html
- description: This guide covers how to add internationalization to your applications. Your application will be able to translate content to different languages, change pluralization rules, use correct date formats for each country and so on.
+ description: This guide covers how to add internationalization to your applications. Your application will be able to translate content to different languages, change pluralization rules, use correct date formats for each country, and so on.
-
name: Action Mailer Basics
url: action_mailer_basics.html
@@ -82,7 +82,7 @@
-
name: Active Job Basics
url: active_job_basics.html
- description: This guide provides you with all you need to get started in creating, enqueueing and executing background jobs.
+ description: This guide provides you with all you need to get started creating, enqueuing, and executing background jobs.
-
name: Testing Rails Applications
work_in_progress: true
@@ -116,12 +116,16 @@
name: The Rails Initialization Process
work_in_progress: true
url: initialization.html
- description: This guide explains the internals of the Rails initialization process as of Rails 4
+ description: This guide explains the internals of the Rails initialization process as of Rails 4.
-
name: Autoloading and Reloading Constants
url: autoloading_and_reloading_constants.html
description: This guide documents how autoloading and reloading constants work.
-
+ name: "Caching with Rails: An Overview"
+ url: caching_with_rails.html
+ description: This guide is an introduction to speeding up your Rails application with caching.
+ -
name: Active Support Instrumentation
work_in_progress: true
url: active_support_instrumentation.html
diff --git a/guides/source/engines.md b/guides/source/engines.md
index 84017d5e13..3b1588b75a 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -368,7 +368,7 @@ called `Blorgh::ArticlesController` (at
`app/controllers/blorgh/articles_controller.rb`) and its related views at
`app/views/blorgh/articles`. This generator also generates a test for the
controller (`test/controllers/blorgh/articles_controller_test.rb`) and a helper
-(`app/helpers/blorgh/articles_controller.rb`).
+(`app/helpers/blorgh/articles_helper.rb`).
Everything this generator has created is neatly namespaced. The controller's
class is defined within the `Blorgh` module:
@@ -402,15 +402,6 @@ Finally, the assets for this resource are generated in two files:
`app/assets/stylesheets/blorgh/articles.css`. You'll see how to use these a little
later.
-By default, the scaffold styling is not applied to the engine because the
-engine's layout file, `app/views/layouts/blorgh/application.html.erb`, doesn't
-load it. To make the scaffold styling apply, insert this line into the `<head>`
-tag of this layout:
-
-```erb
-<%= stylesheet_link_tag "scaffold" %>
-```
-
You can see what the engine has so far by running `rake db:migrate` at the root
of our engine to run the migration generated by the scaffold generator, and then
running `rails server` in `test/dummy`. When you open
@@ -591,7 +582,7 @@ the comments, however, is not quite right yet. If you were to create a comment
right now, you would see this error:
```
-Missing partial blorgh/comments/comment with {:handlers=>[:erb, :builder],
+Missing partial blorgh/comments/_comment with {:handlers=>[:erb, :builder],
:formats=>[:html], :locale=>[:en, :en]}. Searched in: *
"/Users/ryan/Sites/side_projects/blorgh/test/dummy/app/views" *
"/Users/ryan/Sites/side_projects/blorgh/app/views"
@@ -600,7 +591,7 @@ Missing partial blorgh/comments/comment with {:handlers=>[:erb, :builder],
The engine is unable to find the partial required for rendering the comments.
Rails looks first in the application's (`test/dummy`) `app/views` directory and
then in the engine's `app/views` directory. When it can't find it, it will throw
-this error. The engine knows to look for `blorgh/comments/comment` because the
+this error. The engine knows to look for `blorgh/comments/_comment` because the
model object it is receiving is from the `Blorgh::Comment` class.
This partial will be responsible for rendering just the comment text, for now.
@@ -698,8 +689,8 @@ haven't been copied over already. The first run for this command will output
something such as this:
```bash
-Copied migration [timestamp_1]_create_blorgh_articles.rb from blorgh
-Copied migration [timestamp_2]_create_blorgh_comments.rb from blorgh
+Copied migration [timestamp_1]_create_blorgh_articles.blorgh.rb from blorgh
+Copied migration [timestamp_2]_create_blorgh_comments.blorgh.rb from blorgh
```
The first timestamp (`[timestamp_1]`) will be the current time, and the second
@@ -831,11 +822,9 @@ Notice that only _one_ migration was copied over here. This is because the first
two migrations were copied over the first time this command was run.
```
-NOTE Migration [timestamp]_create_blorgh_articles.rb from blorgh has been
-skipped. Migration with the same name already exists. NOTE Migration
-[timestamp]_create_blorgh_comments.rb from blorgh has been skipped. Migration
-with the same name already exists. Copied migration
-[timestamp]_add_author_id_to_blorgh_articles.rb from blorgh
+NOTE Migration [timestamp]_create_blorgh_articles.blorgh.rb from blorgh has been skipped. Migration with the same name already exists.
+NOTE Migration [timestamp]_create_blorgh_comments.blorgh.rb from blorgh has been skipped. Migration with the same name already exists.
+Copied migration [timestamp]_add_author_id_to_blorgh_articles.blorgh.rb from blorgh
```
Run the migration using:
@@ -1201,7 +1190,7 @@ end
```
```ruby
-# Blorgh/lib/concerns/models/article
+# Blorgh/lib/concerns/models/article.rb
module Blorgh::Concerns::Models::Article
extend ActiveSupport::Concern
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index 853227e2a1..84a8d695cb 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -211,9 +211,8 @@ IMPORTANT: The search, telephone, date, time, color, datetime, datetime-local,
month, week, URL, email, number and range inputs are HTML5 controls.
If you require your app to have a consistent experience in older browsers,
you will need an HTML5 polyfill (provided by CSS and/or JavaScript).
-There is definitely [no shortage of solutions for this](https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills), although a couple of popular tools at the moment are
-[Modernizr](http://www.modernizr.com/) and [yepnope](http://yepnopejs.com/),
-which provide a simple way to add functionality based on the presence of
+There is definitely [no shortage of solutions for this](https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills), although a popular tool at the moment is
+[Modernizr](http://www.modernizr.com/), which provides a simple way to add functionality based on the presence of
detected HTML5 features.
TIP: If you're using password input fields (for any purpose), you might want to configure your application to prevent those parameters from being logged. You can learn about this in the [Security Guide](security.html#logging).
@@ -291,8 +290,8 @@ You can create a similar binding without actually creating `<form>` tags with th
```erb
<%= form_for @person, url: {action: "create"} do |person_form| %>
<%= person_form.text_field :name %>
- <%= fields_for @person.contact_detail do |contact_details_form| %>
- <%= contact_details_form.text_field :phone_number %>
+ <%= fields_for @person.contact_detail do |contact_detail_form| %>
+ <%= contact_detail_form.text_field :phone_number %>
<% end %>
<% end %>
```
@@ -441,7 +440,7 @@ Whenever Rails sees that the internal value of an option being generated matches
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.
+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 14f451cbc9..32bbdc554a 100644
--- a/guides/source/generators.md
+++ b/guides/source/generators.md
@@ -503,6 +503,14 @@ Adds a specified source to `Gemfile`:
add_source "http://gems.github.com"
```
+This method also takes a block:
+
+```ruby
+add_source "http://gems.github.com" do
+ gem "rspec-rails"
+end
+```
+
### `inject_into_file`
Injects a block of code into a defined position in your file.
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index db4e81e32e..ef66b75ec1 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -23,9 +23,12 @@ application from scratch. It does not assume that you have any prior experience
with Rails. However, to get the most out of it, you need to have some
prerequisites installed:
-* The [Ruby](https://www.ruby-lang.org/en/downloads) language version 1.9.3 or newer.
-* The [RubyGems](https://rubygems.org) packaging system, which is installed with Ruby
- versions 1.9 and later. To learn more about RubyGems, please read the [RubyGems Guides](http://guides.rubygems.org).
+* The [Ruby](https://www.ruby-lang.org/en/downloads) language version 2.2.2 or newer.
+* Right version of [Development Kit](http://rubyinstaller.org/downloads/), if you
+ are using Windows.
+* The [RubyGems](https://rubygems.org) packaging system, which is installed with
+ Ruby by default. To learn more about RubyGems, please read the
+ [RubyGems Guides](http://guides.rubygems.org).
* A working installation of the [SQLite3 Database](https://www.sqlite.org).
Rails is a web application framework running on the Ruby programming language.
@@ -34,7 +37,6 @@ curve diving straight into Rails. There are several curated lists of online reso
for learning Ruby:
* [Official Ruby Programming Language website](https://www.ruby-lang.org/en/documentation/)
-* [reSRC's List of Free Programming Books](http://resrc.io/list/10/list-of-free-programming-books/#ruby)
Be aware that some resources, while still excellent, cover versions of Ruby as old as
1.6, and commonly 1.8, and will not include some syntax that you will see in day-to-day
@@ -50,7 +52,7 @@ code while accomplishing more than many other languages and frameworks.
Experienced Rails developers also report that it makes web application
development more fun.
-Rails is opinionated software. It makes the assumption that there is the "best"
+Rails is opinionated software. It makes the assumption that there is a "best"
way to do things, and it's designed to encourage that way - and in some cases to
discourage alternatives. If you learn "The Rails Way" you'll probably discover a
tremendous increase in productivity. If you persist in bringing old habits from
@@ -89,17 +91,17 @@ Open up a command line prompt. On Mac OS X open Terminal.app, on Windows choose
dollar sign `$` should be run in the command line. Verify that you have a
current version of Ruby installed:
+```bash
+$ ruby -v
+ruby 2.2.2p95
+```
+
TIP: A number of tools exist to help you quickly install Ruby and Ruby
on Rails on your system. Windows users can use [Rails Installer](http://railsinstaller.org),
while Mac OS X users can use [Tokaido](https://github.com/tokaido/tokaidoapp).
For more installation methods for most Operating Systems take a look at
[ruby-lang.org](https://www.ruby-lang.org/en/documentation/installation/).
-```bash
-$ ruby -v
-ruby 2.0.0p353
-```
-
Many popular UNIX-like OSes ship with an acceptable version of SQLite3.
On Windows, if you installed Rails through Rails Installer, you
already have SQLite installed. Others can find installation instructions
@@ -163,7 +165,7 @@ of the files and folders that Rails created by default:
| File/Folder | Purpose |
| ----------- | ------- |
|app/|Contains the controllers, models, views, helpers, mailers and assets for your application. You'll focus on this folder for the remainder of this guide.|
-|bin/|Contains the rails script that starts your app and can contain other scripts you use to setup, deploy or run your application.|
+|bin/|Contains the rails script that starts your app and can contain other scripts you use to setup, update, deploy or run your application.|
|config/|Configure your application's routes, database, and more. This is covered in more detail in [Configuring Rails Applications](configuring.html).|
|config.ru|Rack configuration for Rack based servers used to start the application.|
|db/|Contains your current database schema, as well as the database migrations.|
@@ -298,6 +300,7 @@ Rails.application.routes.draw do
# The priority is based upon order of creation:
# first created -> highest priority.
+ # See how all your routes lay out with "rake routes".
#
# You can have the root of your site routed with "root"
# root 'welcome#index'
@@ -377,7 +380,7 @@ edit_article GET /articles/:id/edit(.:format) articles#edit
In the next section, you will add the ability to create new articles in your
application and be able to view them. This is the "C" and the "R" from CRUD:
-creation and reading. The form for doing this will look like this:
+create and read. The form for doing this will look like this:
![The new article form](images/getting_started/new_article.png)
@@ -400,7 +403,7 @@ a controller called `ArticlesController`. You can do this by running this
command:
```bash
-$ bin/rails g controller articles
+$ bin/rails generate controller articles
```
If you open up the newly generated `app/controllers/articles_controller.rb`
@@ -619,7 +622,7 @@ def create
end
```
-The `render` method here is taking a very simple hash with a key of `plain` and
+The `render` method here is taking a very simple hash with a key of `:plain` and
value of `params[:article].inspect`. The `params` method is the object which
represents the parameters (or fields) coming in from the form. The `params`
method returns an `ActiveSupport::HashWithIndifferentAccess` object, which
@@ -836,7 +839,7 @@ class ArticlesController < ApplicationController
def new
end
- # snipped for brevity
+ # snippet for brevity
```
A couple of things to note. We use `Article.find` to find the article we're
@@ -892,7 +895,7 @@ class ArticlesController < ApplicationController
def new
end
- # snipped for brevity
+ # snippet for brevity
```
And then finally, add the view for this action, located at
@@ -1240,10 +1243,9 @@ article we want to show the form back to the user.
We reuse the `article_params` method that we defined earlier for the create
action.
-TIP: You don't need to pass all attributes to `update`. For
-example, if you'd call `@article.update(title: 'A new title')`
-Rails would only update the `title` attribute, leaving all other
-attributes untouched.
+TIP: It is not necessary to pass all the attributes to `update`. For example,
+if `@article.update(title: 'A new title')` was called, Rails would only update
+the `title` attribute, leaving all other attributes untouched.
Finally, we want to show a link to the `edit` action in the list of all the
articles, so let's add that now to `app/views/articles/index.html.erb` to make
@@ -1491,7 +1493,7 @@ appear.
![Confirm Dialog](images/getting_started/confirm_dialog.png)
TIP: Learn more about jQuery Unobtrusive Adapter (jQuery UJS) on
-[Working With Javascript in Rails](working_with_javascript_in_rails.html) guide.
+[Working With JavaScript in Rails](working_with_javascript_in_rails.html) guide.
Congratulations, you can now create, show, list, update and destroy
articles.
@@ -1546,20 +1548,17 @@ class CreateComments < ActiveRecord::Migration
create_table :comments do |t|
t.string :commenter
t.text :body
-
- # this line adds an integer column called `article_id`.
- t.references :article, index: true
+ t.references :article, index: true, foreign_key: true
t.timestamps null: false
end
- add_foreign_key :comments, :articles
end
end
```
-The `t.references` line sets up a foreign key column for the association between
-the two models. An index for this association is also created on this column.
-Go ahead and run the migration:
+The `t.references` line creates an integer column called `article_id`, an index
+for it, and a foreign key constraint that points to the `id` column of the `articles`
+table. Go ahead and run the migration:
```bash
$ bin/rake db:migrate
@@ -1572,8 +1571,6 @@ run against the current database, so in this case you will just see:
== CreateComments: migrating =================================================
-- create_table(:comments)
-> 0.0115s
--- add_foreign_key(:comments, :articles)
- -> 0.0000s
== CreateComments: migrated (0.0119s) ========================================
```
@@ -2003,7 +2000,7 @@ class ArticlesController < ApplicationController
@articles = Article.all
end
- # snipped for brevity
+ # snippet for brevity
```
We also want to allow only authenticated users to delete comments, so in the
@@ -2019,7 +2016,7 @@ class CommentsController < ApplicationController
# ...
end
- # snipped for brevity
+ # snippet for brevity
```
Now if you try to create a new article, you will be greeted with a basic HTTP
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index 27f11ebbee..272a0e3623 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -40,7 +40,7 @@ Internationalization is a complex problem. Natural languages differ in so many w
* providing support for English and similar languages out of the box
* making it easy to customize and extend everything for other languages
-As part of this solution, **every static string in the Rails framework** - e.g. Active Record validation messages, time and date formats - **has been internationalized**, so _localization_ of a Rails application means "over-riding" these defaults.
+As part of this solution, **every static string in the Rails framework** - e.g. Active Record validation messages, time and date formats - **has been internationalized**. _Localization_ of a Rails application means defining translated values for these strings in desired languages.
### The Overall Architecture of the Library
@@ -84,13 +84,13 @@ So, let's internationalize a simple Rails application from the ground up in the
Setup the Rails Application for Internationalization
----------------------------------------------------
-There are just a few simple steps to get up and running with I18n support for your application.
+There are a few steps to get up and running with I18n support for a Rails application.
### Configure the I18n Module
-Following the _convention over configuration_ philosophy, Rails will set up your application with reasonable defaults. If you need different settings, you can overwrite them easily.
+Following the _convention over configuration_ philosophy, Rails I18n provides reasonable default translation strings. When different translation strings are needed, they can be overridden.
-Rails adds all `.rb` and `.yml` files from the `config/locales` directory to your **translations load path**, automatically.
+Rails adds all `.rb` and `.yml` files from the `config/locales` directory to the **translations load path**, automatically.
The default `en.yml` locale in this directory contains a sample pair of translation strings:
@@ -101,15 +101,15 @@ en:
This means, that in the `:en` locale, the key _hello_ will map to the _Hello world_ string. Every string inside Rails is internationalized in this way, see for instance Active Model validation messages in the [`activemodel/lib/active_model/locale/en.yml`](https://github.com/rails/rails/blob/master/activemodel/lib/active_model/locale/en.yml) file or time and date formats in the [`activesupport/lib/active_support/locale/en.yml`](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml) file. You can use YAML or standard Ruby Hashes to store translations in the default (Simple) backend.
-The I18n library will use **English** as a **default locale**, i.e. if you don't set a different locale, `:en` will be used for looking up translations.
+The I18n library will use **English** as a **default locale**, i.e. if a different locale is not set, `:en` will be used for looking up translations.
NOTE: The i18n library takes a **pragmatic approach** to locale keys (after [some discussion](http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en)), including only the _locale_ ("language") part, like `:en`, `:pl`, not the _region_ part, like `:en-US` or `:en-GB`, which are traditionally used for separating "languages" and "regional setting" or "dialects". Many international applications use only the "language" element of a locale such as `:cs`, `:th` or `:es` (for Czech, Thai and Spanish). However, there are also regional differences within different language groups that may be important. For instance, in the `:en-US` locale you would have $ as a currency symbol, while in `:en-GB`, you would have £. Nothing stops you from separating regional and other settings in this way: you just have to provide full "English - United Kingdom" locale in a `:en-GB` dictionary. Few gems such as [Globalize3](https://github.com/globalize/globalize) may help you implement it.
-The **translations load path** (`I18n.load_path`) is just a Ruby Array of paths to your translation files that will be loaded automatically and available in your application. You can pick whatever directory and translation file naming scheme makes sense for you.
+The **translations load path** (`I18n.load_path`) is an array of paths to files that will be loaded automatically. Configuring this path allows for customization of translations directory structure and file naming scheme.
-NOTE: The backend will lazy-load these translations when a translation is looked up for the first time. This makes it possible to just swap the backend with something else even after translations have already been announced.
+NOTE: The backend lazy-loads these translations when a translation is looked up for the first time. This backend can be swapped with something else even after translations have already been announced.
-The default `application.rb` file has instructions on how to add locales from another directory and how to set a different default locale. Just uncomment and edit the specific lines.
+The default `application.rb` file has instructions on how to add locales from another directory and how to set a different default locale.
```ruby
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
@@ -117,31 +117,25 @@ The default `application.rb` file has instructions on how to add locales from an
# config.i18n.default_locale = :de
```
-### Optional: Custom I18n Configuration Setup
-
-For the sake of completeness, let's mention that if you do not want to use the `application.rb` file for some reason, you can always wire up things manually, too.
-
-To tell the I18n library where it can find your custom translation files you can specify the load path anywhere in your application - just make sure it gets run before any translations are actually looked up. You might also want to change the default locale. The simplest thing possible is to put the following into an initializer:
+The load path must be specified before any translations are looked up. To change the default locale from an initializer instead of `application.rb`:
```ruby
-# in config/initializers/locale.rb
+# config/initializers/locale.rb
-# tell the I18n library where to find your translations
+# Where the I18n library should search for translation files
I18n.load_path += Dir[Rails.root.join('lib', 'locale', '*.{rb,yml}')]
-# set default locale to something other than :en
+# Set default locale to something other than :en
I18n.default_locale = :pt
```
-### Setting and Passing the Locale
-
-If you want to translate your Rails application to a **single language other than English** (the default locale), you can set I18n.default_locale to your locale in `application.rb` or an initializer as shown above, and it will persist through the requests.
+### Managing the Locale across Requests
-However, you would probably like to **provide support for more locales** in your application. In such case, you need to set and pass the locale between requests.
+The default locale is used for all translations unless `I18n.locale` is explicitly set.
-WARNING: You may be tempted to store the chosen locale in a _session_ or a *cookie*. However, **do not do this**. The locale should be transparent and a part of the URL. This way you won't break people's basic assumptions about the web itself: if you send a URL to a friend, they should see the same page and content as you. A fancy word for this would be that you're being [*RESTful*](http://en.wikipedia.org/wiki/Representational_State_Transfer). Read more about the RESTful approach in [Stefan Tilkov's articles](http://www.infoq.com/articles/rest-introduction). Sometimes there are exceptions to this rule and those are discussed below.
+A localized application will likely need to provide support for multiple locales. To accomplish this, the locale should be set at the beginning of each request so that all strings are translated using the desired locale during the lifetime of that request.
-The _setting part_ is easy. You can set the locale in a `before_action` in the `ApplicationController` like this:
+The locale can be set in a `before_action` in the `ApplicationController`:
```ruby
before_action :set_locale
@@ -151,11 +145,11 @@ def set_locale
end
```
-This requires you to pass the locale as a URL query parameter as in `http://example.com/books?locale=pt`. (This is, for example, Google's approach.) So `http://localhost:3000?locale=pt` will load the Portuguese localization, whereas `http://localhost:3000?locale=de` would load the German localization, and so on. You may skip the next section and head over to the **Internationalize your application** section, if you want to try things out by manually placing the locale in the URL and reloading the page.
+This example illustrates this using a URL query parameter to set the locale (e.g. `http://example.com/books?locale=pt`). With this approach, `http://localhost:3000?locale=pt` renders the Portuguese localization, while `http://localhost:3000?locale=de` loads a German localization.
-Of course, you probably don't want to manually include the locale in every URL all over your application, or want the URLs look differently, e.g. the usual `http://example.com/pt/books` versus `http://example.com/en/books`. Let's discuss the different options you have.
+The locale can be set using one of many different approaches.
-### Setting the Locale from the Domain Name
+#### Setting the Locale from the Domain Name
One option you have is to set the locale from the domain name where your application runs. For example, we want `www.example.com` to load the English (or default) locale, and `www.example.es` to load the Spanish locale. Thus the _top-level domain name_ is used for locale setting. This has several advantages:
@@ -208,7 +202,7 @@ assuming you would set `APP_CONFIG[:deutsch_website_url]` to some value like `ht
This solution has aforementioned advantages, however, you may not be able or may not want to provide different localizations ("language versions") on different domains. The most obvious solution would be to include locale code in the URL params (or request path).
-### Setting the Locale from the URL Params
+#### Setting the Locale from URL Params
The most usual way of setting (and passing) the locale would be to include it in URL params, as we did in the `I18n.locale = params[:locale]` _before_action_ in the first example. We would like to have URLs like `www.example.com/books?locale=ja` or `www.example.com/ja/books` in this case.
@@ -216,14 +210,14 @@ This approach has almost the same set of advantages as setting the locale from t
Getting the locale from `params` and setting it accordingly is not hard; including it in every URL and thus **passing it through the requests** is. To include an explicit option in every URL, e.g. `link_to(books_url(locale: I18n.locale))`, would be tedious and probably impossible, of course.
-Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its [`ApplicationController#default_url_options`](http://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Base.html#method-i-default_url_options), which is useful precisely in this scenario: it enables us to set "defaults" for [`url_for`](http://api.rubyonrails.org/classes/ActionDispatch/Routing/UrlFor.html#method-i-url_for) and helper methods dependent on it (by implementing/overriding this method).
+Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its [`ApplicationController#default_url_options`](http://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Base.html#method-i-default_url_options), which is useful precisely in this scenario: it enables us to set "defaults" for [`url_for`](http://api.rubyonrails.org/classes/ActionDispatch/Routing/UrlFor.html#method-i-url_for) and helper methods dependent on it (by implementing/overriding `default_url_options`).
We can include something like this in our `ApplicationController` then:
```ruby
# app/controllers/application_controller.rb
-def default_url_options(options = {})
- { locale: I18n.locale }.merge options
+def default_url_options
+ { locale: I18n.locale }
end
```
@@ -231,7 +225,7 @@ Every helper method dependent on `url_for` (e.g. helpers for named routes like `
You may be satisfied with this. It does impact the readability of URLs, though, when the locale "hangs" at the end of every URL in your application. Moreover, from the architectural standpoint, locale is usually hierarchically above the other parts of the application domain: and URLs should reflect this.
-You probably want URLs to look like this: `www.example.com/en/books` (which loads the English locale) and `www.example.com/nl/books` (which loads the Dutch locale). This is achievable with the "over-riding `default_url_options`" strategy from above: you just have to set up your routes with [`scoping`](http://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Scoping.html) option in this way:
+You probably want URLs to look like this: `http://www.example.com/en/books` (which loads the English locale) and `http://www.example.com/nl/books` (which loads the Dutch locale). This is achievable with the "over-riding `default_url_options`" strategy from above: you just have to set up your routes with [`scope`](http://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Scoping.html):
```ruby
# config/routes.rb
@@ -240,7 +234,9 @@ scope "/:locale" do
end
```
-Now, when you call the `books_path` method you should get `"/en/books"` (for the default locale). An URL like `http://localhost:3001/nl/books` should load the Dutch locale, then, and following calls to `books_path` should return `"/nl/books"` (because the locale changed).
+Now, when you call the `books_path` method you should get `"/en/books"` (for the default locale). A URL like `http://localhost:3001/nl/books` should load the Dutch locale, then, and following calls to `books_path` should return `"/nl/books"` (because the locale changed).
+
+WARNING. Since the return value of `default_url_options` is cached per request, the URLs in a locale selector cannot be generated invoking helpers in a loop that sets the corresponding `I18n.locale` in each iteration. Instead, leave `I18n.locale` untouched, and pass an explicit `:locale` option to the helper, or edit `request.original_fullpath`.
If you don't want to force the use of a locale in your routes you can use an optional path scope (denoted by the parentheses) like so:
@@ -266,14 +262,23 @@ Do take special care about the **order of your routes**, so this route declarati
NOTE: Have a look at various gems which simplify working with routes: [routing_filter](https://github.com/svenfuchs/routing-filter/tree/master), [rails-translate-routes](https://github.com/francesc/rails-translate-routes), [route_translator](https://github.com/enriclluelles/route_translator).
-### Setting the Locale from the Client Supplied Information
+#### Setting the Locale from User Preferences
+
+An application with authenticated users may allow users to set a locale preference through the application's interface. With this approach, a user's selected locale preference is persisted in the database and used to set the locale for authenticated requests by that user.
+
+```ruby
+def set_locale
+ I18n.locale = current_user.try(:locale) || I18n.default_locale
+end
+```
-In specific cases, it would make sense to set the locale from client-supplied information, i.e. not from the URL. This information may come for example from the users' preferred language (set in their browser), can be based on the users' geographical location inferred from their IP, or users can provide it simply by choosing the locale in your application interface and saving it to their profile. This approach is more suitable for web-based applications or services, not for websites - see the box about _sessions_, _cookies_ and RESTful architecture above.
+#### Choosing an Implied Locale
+When an explicit locale has not been set for a request (e.g. via one of the above methods), an application should attempt to infer the desired locale.
-#### Using `Accept-Language`
+##### Inferring Locale from the Language Header
-One source of client supplied information would be an `Accept-Language` HTTP header. People may [set this in their browser](http://www.w3.org/International/questions/qa-lang-priorities) or other clients (such as _curl_).
+The `Accept-Language` HTTP header indicates the preferred language for request's response. Browsers [set this header value based on the user's language preference settings](http://www.w3.org/International/questions/qa-lang-priorities), making it a good first choice when inferring a locale.
A trivial implementation of using an `Accept-Language` header would be:
@@ -290,24 +295,27 @@ private
end
```
-Of course, in a production environment you would need much more robust code, and could use a gem such as Iain Hecker's [http_accept_language](https://github.com/iain/http_accept_language/tree/master) or even Rack middleware such as Ryan Tomayko's [locale](https://github.com/rack/rack-contrib/blob/master/lib/rack/contrib/locale.rb).
-#### Using GeoIP (or Similar) Database
+In practice, more robust code is necessary to do this reliably. Iain Hecker's [http_accept_language](https://github.com/iain/http_accept_language/tree/master) library or Ryan Tomayko's [locale](https://github.com/rack/rack-contrib/blob/master/lib/rack/contrib/locale.rb) Rack middleware provide solutions to this problem.
-Another way of choosing the locale from client information would be to use a database for mapping the client IP to the region, such as [GeoIP Lite Country](http://www.maxmind.com/app/geolitecountry). The mechanics of the code would be very similar to the code above - you would need to query the database for the user's IP, and look up your preferred locale for the country/region/city returned.
+##### Inferring the Locale from IP Geolocation
-#### User Profile
+The IP address of the client making the request can be used to infer the client's region and thus their locale. Services such as [GeoIP Lite Country](http://www.maxmind.com/app/geolitecountry) or gems like [geocoder](https://github.com/alexreisner/geocoder) can be used to implement this approach.
-You can also provide users of your application with means to set (and possibly over-ride) the locale in your application interface, as well. Again, mechanics for this approach would be very similar to the code above - you'd probably let users choose a locale from a dropdown list and save it to their profile in the database. Then you'd set the locale to this value.
+In general, this approach is far less reliable than using the language header and is not recommended for most web applications.
-Internationalizing your Application
+#### Storing the Locale from the Session or Cookies
+
+WARNING: You may be tempted to store the chosen locale in a _session_ or a *cookie*. However, **do not do this**. The locale should be transparent and a part of the URL. This way you won't break people's basic assumptions about the web itself: if you send a URL to a friend, they should see the same page and content as you. A fancy word for this would be that you're being [*RESTful*](http://en.wikipedia.org/wiki/Representational_State_Transfer). Read more about the RESTful approach in [Stefan Tilkov's articles](http://www.infoq.com/articles/rest-introduction). Sometimes there are exceptions to this rule and those are discussed below.
+
+Internationalization and Localization
-----------------------------------
-OK! Now you've initialized I18n support for your Ruby on Rails application and told it which locale to use and how to preserve it between requests. With that in place, you're now ready for the really interesting stuff.
+OK! Now you've initialized I18n support for your Ruby on Rails application and told it which locale to use and how to preserve it between requests.
-Let's _internationalize_ our application, i.e. abstract every locale-specific parts, and then _localize_ it, i.e. provide necessary translations for these abstracts.
+Next we need to _internationalize_ our application by abstracting every locale-specific element. Finally, we need to _localize_ it by providing necessary translations for these abstracts.
-You most probably have something like this in one of your applications:
+Given the following example:
```ruby
# config/routes.rb
@@ -344,9 +352,9 @@ end
![rails i18n demo untranslated](images/i18n/demo_untranslated.png)
-### Adding Translations
+### Abstracting Localized Code
-Obviously there are **two strings that are localized to English**. In order to internationalize this code, **replace these strings** with calls to Rails' `#t` helper with a key that makes sense for the translation:
+There are two strings in our code that are in English and that users will be rendered in our response ("Hello Flash" and "Hello World"). In order to internationalize this code, these strings need to be replaced by calls to Rails' `#t` helper with an appropriate key for each string:
```ruby
# app/controllers/home_controller.rb
@@ -363,13 +371,15 @@ end
<p><%= flash[:notice] %></p>
```
-When you now render this view, it will show an error message which tells you that the translations for the keys `:hello_world` and `:hello_flash` are missing.
+Now, when this view is rendered, it will show an error message which tells you that the translations for the keys `:hello_world` and `:hello_flash` are missing.
![rails i18n demo translation missing](images/i18n/demo_translation_missing.png)
NOTE: Rails adds a `t` (`translate`) helper method to your views so that you do not need to spell out `I18n.t` all the time. Additionally this helper will catch missing translations and wrap the resulting error message into a `<span class="translation_missing">`.
-So let's add the missing translations into the dictionary files (i.e. do the "localization" part):
+### Providing Translations for Internationalized Strings
+
+Add the missing translations into the translation dictionary files:
```yaml
# config/locales/en.yml
@@ -383,11 +393,11 @@ pirate:
hello_flash: Ahoy Flash
```
-There you go. Because you haven't changed the default_locale, I18n will use English. Your application now shows:
+Because the `default_locale` hasn't changed, translations use the `:en` locale and the response renders the english strings:
![rails i18n demo translated to English](images/i18n/demo_translated_en.png)
-And when you change the URL to pass the pirate locale (`http://localhost:3000?locale=pirate`), you'll get:
+If the locale is set via the URL to the pirate locale (`http://localhost:3000?locale=pirate`), the response renders the pirate strings:
![rails i18n demo translated to pirate](images/i18n/demo_translated_pirate.png)
@@ -395,21 +405,64 @@ NOTE: You need to restart the server when you add new locale files.
You may use YAML (`.yml`) or plain Ruby (`.rb`) files for storing your translations in SimpleStore. YAML is the preferred option among Rails developers. However, it has one big disadvantage. YAML is very sensitive to whitespace and special characters, so the application may not load your dictionary properly. Ruby files will crash your application on first request, so you may easily find what's wrong. (If you encounter any "weird issues" with YAML dictionaries, try putting the relevant portion of your dictionary into a Ruby file.)
-### Passing variables to translations
+### Passing Variables to Translations
+
+One key consideration for successfully internationalizing an application is to
+avoid making incorrect assumptions about grammar rules when abstracting localized
+code. Grammar rules that seem fundamental in one locale may not hold true in
+another one.
-You can use variables in the translation messages and pass their values from the view.
+Improper abstraction is shown in the following example, where assumptions are
+made about the ordering of the different parts of the translation. Note that Rails
+provides a `number_to_currency` helper to handle the following case.
```erb
-# app/views/home/index.html.erb
-<%=t 'greet_username', user: "Bill", message: "Goodbye" %>
+# app/views/products/show.html.erb
+<%= "#{t('currency')}#{@product.price}" %>
```
```yaml
# config/locales/en.yml
en:
- greet_username: "%{message}, %{user}!"
+ currency: "$"
+
+# config/locales/es.yml
+es:
+ currency: "€"
```
+If the product's price is 10 then the proper translation for Spanish is "10 €"
+instead of "€10" but the abstraction cannot give it.
+
+To create proper abstraction, the I18n gem ships with a feature called variable
+interpolation that allows you to use variables in translation definitions and
+pass the values for these variables to the translation method.
+
+Proper abstraction is shown in the following example:
+
+```erb
+# app/views/products/show.html.erb
+<%= t('product_price', price: @product.price) %>
+```
+
+```yaml
+# config/locales/en.yml
+en:
+ product_price: "$%{price}"
+
+# config/locales/es.yml
+es:
+ product_price: "%{price} €"
+```
+
+All grammatical and punctuation decisions are made in the definition itself, so
+the abstraction can give a proper translation.
+
+NOTE: The `default` and `scope` keywords are reserved and can't be used as
+variable names. If used, an `I18n::ReservedInterpolationKey` exception is raised.
+If a translation expects an interpolation variable, but this has not been passed
+to `#translate`, an `I18n::MissingInterpolationArgument` exception is raised.
+
### Adding Date/Time Formats
OK! Now let's add a timestamp to the view, so we can demo the **date/time localization** feature as well. To localize the time format you pass the Time object to `I18n.l` or (preferably) use Rails' `#l` helper. You can pick a format by passing the `:format` option - by default the `:default` format is used.
@@ -449,7 +502,10 @@ You can make use of this feature, e.g. when working with a large amount of stati
### Organization of Locale Files
-When you are using the default SimpleStore shipped with the i18n library, dictionaries are stored in plain-text files on the disc. Putting translations for all parts of your application in one file per locale could be hard to manage. You can store these files in a hierarchy which makes sense to you.
+When you are using the default SimpleStore shipped with the i18n library,
+dictionaries are stored in plain-text files on the disk. Putting translations
+for all parts of your application in one file per locale could be hard to
+manage. You can store these files in a hierarchy which makes sense to you.
For example, your `config/locales` directory could look like this:
@@ -489,7 +545,9 @@ NOTE: The default locale loading mechanism in Rails does not load locale files i
Overview of the I18n API Features
---------------------------------
-You should have good understanding of using the i18n library now, knowing all necessary aspects of internationalizing a basic Rails application. In the following chapters, we'll cover it's features in more depth.
+You should have a good understanding of using the i18n library now and know how
+to internationalize a basic Rails application. In the following chapters, we'll
+cover its features in more depth.
These chapters will show examples using both the `I18n.translate` method as well as the [`translate` view helper method](http://api.rubyonrails.org/classes/ActionView/Helpers/TranslationHelper.html#method-i-translate) (noting the additional feature provide by the view helper method).
@@ -608,20 +666,6 @@ class BooksController < ApplicationController
end
```
-### Interpolation
-
-In many cases you want to abstract your translations so that **variables can be interpolated into the translation**. For this reason the I18n API provides an interpolation feature.
-
-All options besides `:default` and `:scope` that are passed to `#translate` will be interpolated to the translation:
-
-```ruby
-I18n.backend.store_translations :en, thanks: 'Thanks %{name}!'
-I18n.translate :thanks, name: 'Jeremy'
-# => 'Thanks Jeremy!'
-```
-
-If a translation uses `:default` or `:scope` as an interpolation variable, an `I18n::ReservedInterpolationKey` exception is raised. If a translation expects an interpolation variable, but this has not been passed to `#translate`, an `I18n::MissingInterpolationArgument` exception is raised.
-
### Pluralization
In English there are only one singular and one plural form for a given string, e.g. "1 message" and "2 messages". Other languages ([Arabic](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html#ar), [Japanese](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html#ja), [Russian](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html#ru) and many more) have different grammars that have additional or fewer [plural forms](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html). Thus, the I18n API provides a flexible pluralization feature.
diff --git a/guides/source/initialization.md b/guides/source/initialization.md
index c0c8b7d4b6..43083ebb86 100644
--- a/guides/source/initialization.md
+++ b/guides/source/initialization.md
@@ -34,7 +34,7 @@ Launch!
Let's start to boot and initialize the app. A Rails application is usually
started by running `rails console` or `rails server`.
-### `railties/bin/rails`
+### `railties/exe/rails`
The `rails` in the command `rails server` is a ruby executable in your load
path. This executable contains the following lines:
@@ -45,7 +45,7 @@ load Gem.bin_path('railties', 'rails', version)
```
If you try out this command in a Rails console, you would see that this loads
-`railties/bin/rails`. A part of the file `railties/bin/rails.rb` has the
+`railties/exe/rails`. A part of the file `railties/exe/rails.rb` has the
following code:
```ruby
@@ -53,11 +53,11 @@ require "rails/cli"
```
The file `railties/lib/rails/cli` in turn calls
-`Rails::AppRailsLoader.exec_app_rails`.
+`Rails::AppLoader.exec_app`.
-### `railties/lib/rails/app_rails_loader.rb`
+### `railties/lib/rails/app_loader.rb`
-The primary goal of the function `exec_app_rails` is to execute your app's
+The primary goal of the function `exec_app` is to execute your app's
`bin/rails`. If the current directory does not have a `bin/rails`, it will
navigate upwards until it finds a `bin/rails` executable. Thus one can invoke a
`rails` command from anywhere inside a rails application.
@@ -106,6 +106,7 @@ A standard Rails application depends on several gems, specifically:
* activemodel
* activerecord
* activesupport
+* activejob
* arel
* builder
* bundler
@@ -532,6 +533,7 @@ require "rails"
action_controller
action_view
action_mailer
+ active_job
rails/test_unit
sprockets
).each do |framework|
@@ -555,9 +557,8 @@ I18n and Rails configuration are all being defined here.
The rest of `config/application.rb` defines the configuration for the
`Rails::Application` which will be used once the application is fully
initialized. When `config/application.rb` has finished loading Rails and defined
-the application namespace, we go back to `config/environment.rb`,
-where the application is initialized. For example, if the application was called
-`Blog`, here we would find `Rails.application.initialize!`, which is
+the application namespace, we go back to `config/environment.rb`. Here, the
+application is initialized with `Rails.application.initialize!`, which is
defined in `rails/application.rb`.
### `railties/lib/rails/application.rb`
diff --git a/guides/source/kindle/welcome.html.erb b/guides/source/kindle/welcome.html.erb
index 610a71570f..ef3397f58f 100644
--- a/guides/source/kindle/welcome.html.erb
+++ b/guides/source/kindle/welcome.html.erb
@@ -2,4 +2,6 @@
<h3>Kindle Edition</h3>
-The Kindle Edition of the Rails Guides should be considered a work in progress. Feedback is really welcome. Please see the "Feedback" section at the end of each guide for instructions.
+<div>
+ The Kindle Edition of the Rails Guides should be considered a work in progress. Feedback is really welcome. Please see the "Feedback" section at the end of each guide for instructions.
+</div>
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index c57fa358d6..94cd7297e2 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -103,34 +103,6 @@ In most cases, the `ActionController::Base#render` method does the heavy lifting
TIP: If you want to see the exact results of a call to `render` without needing to inspect it in a browser, you can call `render_to_string`. This method takes exactly the same options as `render`, but it returns a string instead of sending a response back to the browser.
-#### Rendering Nothing
-
-Perhaps the simplest thing you can do with `render` is to render nothing at all:
-
-```ruby
-render nothing: true
-```
-
-If you look at the response for this using cURL, you will see the following:
-
-```bash
-$ curl -i 127.0.0.1:3000/books
-HTTP/1.1 200 OK
-Connection: close
-Date: Sun, 24 Jan 2010 09:25:18 GMT
-Transfer-Encoding: chunked
-Content-Type: */*; charset=utf-8
-X-Runtime: 0.014297
-Set-Cookie: _blog_session=...snip...; path=/; HttpOnly
-Cache-Control: no-cache
-
-$
-```
-
-We see there is an empty response (no data after the `Cache-Control` line), but the request was successful because Rails has set the response to 200 OK. You can set the `:status` option on render to change this response. Rendering nothing can be useful for Ajax requests where all you want to send back to the browser is an acknowledgment that the request was completed.
-
-TIP: You should probably be using the `head` method, discussed later in this guide, instead of `render :nothing`. This provides additional flexibility and makes it explicit that you're only generating HTTP headers.
-
#### Rendering an Action's View
If you want to render the view that corresponds to a different template within the same controller, you can use `render` with the name of the view:
@@ -1075,9 +1047,14 @@ One way to use partials is to treat them as the equivalent of subroutines: as a
<%= render "shared/footer" %>
```
-Here, the `_ad_banner.html.erb` and `_footer.html.erb` partials could contain content that is shared among many pages in your application. You don't need to see the details of these sections when you're concentrating on a particular page.
+Here, the `_ad_banner.html.erb` and `_footer.html.erb` partials could contain
+content that is shared by many pages in your application. You don't need to see
+the details of these sections when you're concentrating on a particular page.
-As you already could see from the previous sections of this guide, `yield` is a very powerful tool for cleaning up your layouts. Keep in mind that it's pure ruby, so you can use it almost everywhere. For example, we can use it to DRY form layout definition for several similar resources:
+As seen in the previous sections of this guide, `yield` is a very powerful tool
+for cleaning up your layouts. Keep in mind that it's pure Ruby, so you can use
+it almost everywhere. For example, we can use it to DRY up form layout
+definitions for several similar resources:
* `users/index.html.erb`
diff --git a/guides/source/nested_model_forms.md b/guides/source/nested_model_forms.md
index 1937369776..121cf2b185 100644
--- a/guides/source/nested_model_forms.md
+++ b/guides/source/nested_model_forms.md
@@ -106,7 +106,7 @@ Consider the following typical RESTful controller which will prepare a new Perso
class PeopleController < ApplicationController
def new
@person = Person.new
- @person.built_address
+ @person.build_address
2.times { @person.projects.build }
end
diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md
index b3e1874048..b7364536c3 100644
--- a/guides/source/rails_application_templates.md
+++ b/guides/source/rails_application_templates.md
@@ -215,7 +215,7 @@ CODE
### yes?(question) or no?(question)
-These methods let you ask questions from templates and decide the flow based on the user's answer. Let's say you want to freeze rails only if the user wants to:
+These methods let you ask questions from templates and decide the flow based on the user's answer. Let's say you want to Freeze Rails only if the user wants to:
```ruby
rake("rails:freeze:gems") if yes?("Freeze rails gems?")
diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md
index 993cd5ac44..1e2fe94010 100644
--- a/guides/source/rails_on_rack.md
+++ b/guides/source/rails_on_rack.md
@@ -58,23 +58,6 @@ class Server < ::Rack::Server
end
```
-Here's how it loads the middlewares:
-
-```ruby
-def middleware
- middlewares = []
- middlewares << [::Rack::ContentLength]
- Hash.new(middlewares)
-end
-```
-
-`Rails::Rack::Debugger` is primarily useful only in the development environment. The following table explains the usage of the loaded middlewares:
-
-| Middleware | Purpose |
-| ----------------------- | --------------------------------------------------------------------------------- |
-| `Rails::Rack::Debugger` | Starts Debugger |
-| `Rack::ContentLength` | Counts the number of bytes in the response and set the HTTP Content-Length header |
-
### `rackup`
To use `rackup` instead of Rails' `rails server`, you can put the following inside `config.ru` of your Rails application's root directory:
@@ -82,9 +65,6 @@ To use `rackup` instead of Rails' `rails server`, you can put the following insi
```ruby
# Rails.root/config.ru
require ::File.expand_path('../config/environment', __FILE__)
-
-use Rails::Rack::Debugger
-use Rack::ContentLength
run Rails.application
```
@@ -329,8 +309,6 @@ Resources
* [Official Rack Website](http://rack.github.io)
* [Introducing Rack](http://chneukirchen.org/blog/archive/2007/02/introducing-rack.html)
-* [Ruby on Rack #1 - Hello Rack!](http://m.onkey.org/ruby-on-rack-1-hello-rack)
-* [Ruby on Rack #2 - The Builder](http://m.onkey.org/ruby-on-rack-2-the-builder)
### Understanding Middlewares
diff --git a/guides/source/routing.md b/guides/source/routing.md
index 4ccc50a4d9..e4799d93fa 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -83,7 +83,9 @@ Rails would dispatch that request to the `destroy` method on the `photos` contro
### CRUD, Verbs, and Actions
-In Rails, a resourceful route provides a mapping between HTTP verbs and URLs to controller actions. By convention, each action also maps to particular CRUD operations in a database. A single entry in the routing file, such as:
+In Rails, a resourceful route provides a mapping between HTTP verbs and URLs to
+controller actions. By convention, each action also maps to a specific CRUD
+operation in a database. A single entry in the routing file, such as:
```ruby
resources :photos
@@ -177,6 +179,8 @@ WARNING: A [long-standing bug](https://github.com/rails/rails/issues/1769) preve
```ruby
form_for @geocoder, url: geocoder_path do |f|
+
+# snippet for brevity
```
### Controller Namespaces and Routing
@@ -613,6 +617,8 @@ get 'photos/:id', to: 'photos#show', defaults: { format: 'jpg' }
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
You can specify a name for any route using the `:as` option:
@@ -791,7 +797,11 @@ get '/stories/:name', to: redirect { |path_params, req| "/articles/#{path_params
get '/stories', to: redirect { |path_params, req| "/articles/#{req.subdomain}" }
```
-Please note that this redirection is a 301 "Moved Permanently" redirect. Keep in mind that some web browsers or proxy servers will cache this type of redirect, making the old page inaccessible.
+Please note that default redirection is a 301 "Moved Permanently" redirect. Keep in mind that some web browsers or proxy servers will cache this type of redirect, making the old page inaccessible. You can use the `:status` option to change the response status:
+
+```ruby
+get '/stories/:name', to: redirect('/articles/%{name}', status: 302)
+```
In all of these cases, if you don't provide the leading host (`http://www.example.com`), Rails will take those details from the current request.
@@ -807,13 +817,16 @@ As long as `Sprockets` responds to `call` and returns a `[status, headers, body]
NOTE: For the curious, `'articles#index'` actually expands out to `ArticlesController.action(:index)`, which returns a valid Rack application.
-If you specify a rack application as the endpoint for a matcher remember that the route will be unchanged in the receiving application. With the following route your rack application should expect the route to be '/admin':
+If you specify a Rack application as the endpoint for a matcher, remember that
+the route will be unchanged in the receiving application. With the following
+route your Rack application should expect the route to be '/admin':
```ruby
match '/admin', to: AdminApp, via: :all
```
-If you would prefer to have your rack application receive requests at the root path instead use mount:
+If you would prefer to have your Rack application receive requests at the root
+path instead, use mount:
```ruby
mount AdminApp, at: '/admin'
@@ -1082,6 +1095,20 @@ edit_videos GET /videos/:identifier/edit(.:format) videos#edit
Video.find_by(identifier: params[:identifier])
```
+You can override `ActiveRecord::Base#to_param` of a related model to construct
+an URL:
+
+```ruby
+class Video < ActiveRecord::Base
+ def to_param
+ identifier
+ end
+end
+
+video = Video.find_by(identifier: "Roman-Holiday")
+edit_videos_path(video) # => "/videos/Roman-Holiday"
+```
+
Inspecting and Testing Routes
-----------------------------
diff --git a/guides/source/ruby_on_rails_guides_guidelines.md b/guides/source/ruby_on_rails_guides_guidelines.md
index 1323742488..50866350f8 100644
--- a/guides/source/ruby_on_rails_guides_guidelines.md
+++ b/guides/source/ruby_on_rails_guides_guidelines.md
@@ -64,7 +64,9 @@ The guides and the API should be coherent and consistent where appropriate. In p
HTML Guides
-----------
-Before generating the guides, make sure that you have the latest version of Bundler installed on your system. As of this writing, you must install Bundler 1.3.5 on your device.
+Before generating the guides, make sure that you have the latest version of
+Bundler installed on your system. As of this writing, you must install Bundler
+1.3.5 or later on your device.
To install the latest version of Bundler, run `gem install bundler`.
@@ -82,6 +84,8 @@ or
bundle exec rake guides:generate:html
```
+Resulting HTML files can be found in the `./output` directory.
+
To process `my_guide.md` and nothing else use the `ONLY` environment variable:
```
diff --git a/guides/source/security.md b/guides/source/security.md
index 184af98d65..c701027479 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -93,9 +93,16 @@ Rails 2 introduced a new default session storage, CookieStore. CookieStore saves
* Cookies imply a strict size limit of 4kB. This is fine as you should not store large amounts of data in a session anyway, as described before. _Storing the current user's database id in a session is usually ok_.
-* The client can see everything you store in a session, because it is stored in clear-text (actually Base64-encoded, so not encrypted). So, of course, _you don't want to store any secrets here_. To prevent session hash tampering, a digest is calculated from the session with a server-side secret and inserted into the end of the cookie.
+* The client can see everything you store in a session, because it is stored in clear-text (actually Base64-encoded, so not encrypted). So, of course, _you don't want to store any secrets here_. To prevent session hash tampering, a digest is calculated from the session with a server-side secret (`secrets.secret_token`) and inserted into the end of the cookie.
-That means the security of this storage depends on this secret (and on the digest algorithm, which defaults to SHA1, for compatibility). So _don't use a trivial secret, i.e. a word from a dictionary, or one which is shorter than 30 characters_.
+However, since Rails 4, the default store is EncryptedCookieStore. With
+EncryptedCookieStore the session is encrypted before being stored in a cookie.
+This prevents the user from accessing and tampering the content of the cookie.
+Thus the session becomes a more secure place to store data. The encryption is
+done using a server-side secret key `secrets.secret_key_base` stored in
+`config/secrets.yml`.
+
+That means the security of this storage depends on this secret (and on the digest algorithm, which defaults to SHA1, for compatibility). So _don't use a trivial secret, i.e. a word from a dictionary, or one which is shorter than 30 characters, use `rake secret` instead_.
`secrets.secret_key_base` is used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `secrets.secret_key_base` initialized to a random key present in `config/secrets.yml`, e.g.:
@@ -449,14 +456,16 @@ Depending on your web application, there may be more ways to hijack the user's a
### CAPTCHAs
-INFO: _A CAPTCHA is a challenge-response test to determine that the response is not generated by a computer. It is often used to protect comment forms from automatic spam bots by asking the user to type the letters of a distorted image. The idea of a negative CAPTCHA is not for a user to prove that they are human, but reveal that a robot is a robot._
+INFO: _A CAPTCHA is a challenge-response test to determine that the response is not generated by a computer. It is often used to protect registration forms from attackers and comment forms from automatic spam bots by asking the user to type the letters of a distorted image. This is the positive CAPTCHA, but there is also the negative CAPTCHA. The idea of a negative CAPTCHA is not for a user to prove that they are human, but reveal that a robot is a robot._
-But not only spam robots (bots) are a problem, but also automatic login bots. A popular CAPTCHA API is [reCAPTCHA](http://recaptcha.net/) which displays two distorted images of words from old books. It also adds an angled line, rather than a distorted background and high levels of warping on the text as earlier CAPTCHAs did, because the latter were broken. As a bonus, using reCAPTCHA helps to digitize old books. [ReCAPTCHA](https://github.com/ambethia/recaptcha/) is also a Rails plug-in with the same name as the API.
+A popular positive CAPTCHA API is [reCAPTCHA](http://recaptcha.net/) which displays two distorted images of words from old books. It also adds an angled line, rather than a distorted background and high levels of warping on the text as earlier CAPTCHAs did, because the latter were broken. As a bonus, using reCAPTCHA helps to digitize old books. [ReCAPTCHA](https://github.com/ambethia/recaptcha/) is also a Rails plug-in with the same name as the API.
You will get two keys from the API, a public and a private key, which you have to put into your Rails environment. After that you can use the recaptcha_tags method in the view, and the verify_recaptcha method in the controller. Verify_recaptcha will return false if the validation fails.
-The problem with CAPTCHAs is, they are annoying. Additionally, some visually impaired users have found certain kinds of distorted CAPTCHAs difficult to read. The idea of negative CAPTCHAs is not to ask a user to proof that they are human, but reveal that a spam robot is a bot.
+The problem with CAPTCHAs is that they have a negative impact on the user experience. Additionally, some visually impaired users have found certain kinds of distorted CAPTCHAs difficult to read. Still, positive CAPTCHAs are one of the best methods to prevent all kinds of bots from submitting forms.
+
+Most bots are really dumb. They crawl the web and put their spam into every form's field they can find. Negative CAPTCHAs take advantage of that and include a "honeypot" field in the form which will be hidden from the human user by CSS or JavaScript.
-Most bots are really dumb, they crawl the web and put their spam into every form's field they can find. Negative CAPTCHAs take advantage of that and include a "honeypot" field in the form which will be hidden from the human user by CSS or JavaScript.
+Note that negative CAPTCHAs are only effective against dumb bots and won't suffice to protect critical applications from targeted bots. Still, the negative and positive CAPTCHAs can be combined to increase the performance, e.g., if the "honeypot" field is not empty (bot detected), you won't need to verify the positive CAPTCHA, which would require a HTTPS request to Google ReCaptcha before computing the response.
Here are some ideas how to hide honeypot fields by JavaScript and/or CSS:
@@ -570,7 +579,7 @@ NOTE: _When sanitizing, protecting or verifying something, prefer whitelists ove
A blacklist can be a list of bad e-mail addresses, non-public actions or bad HTML tags. This is opposed to a whitelist which lists the good e-mail addresses, public actions, good HTML tags and so on. Although sometimes it is not possible to create a whitelist (in a SPAM filter, for example), _prefer to use whitelist approaches_:
-* Use before_action only: [...] instead of except: [...]. This way you don't forget to turn it off for newly added actions.
+* Use before_action except: [...] instead of only: [...] for security-related actions. This way you don't forget to enable security checks for newly added actions.
* Allow &lt;strong&gt; instead of removing &lt;script&gt; against Cross-Site Scripting (XSS). See below for details.
* Don't try to correct user input by blacklists:
* This will make the attack work: "&lt;sc&lt;script&gt;ript&gt;".gsub("&lt;script&gt;", "")
@@ -710,7 +719,7 @@ The log files on www.attacker.com will read like this:
GET http://www.attacker.com/_app_session=836c1c25278e5b321d6bea4f19cb57e2
```
-You can mitigate these attacks (in the obvious way) by adding the **httpOnly** flag to cookies, so that document.cookie may not be read by JavaScript. Http only cookies can be used from IE v6.SP1, Firefox v2.0.0.5 and Opera 9.5. Safari is still considering, it ignores the option. But other, older browsers (such as WebTV and IE 5.5 on Mac) can actually cause the page to fail to load. Be warned that cookies [will still be visible using Ajax](http://ha.ckers.org/blog/20070719/firefox-implements-httponly-and-is-vulnerable-to-xmlhttprequest/), though.
+You can mitigate these attacks (in the obvious way) by adding the **httpOnly** flag to cookies, so that document.cookie may not be read by JavaScript. Http only cookies can be used from IE v6.SP1, Firefox v2.0.0.5 and Opera 9.5. Safari is still considering, it ignores the option. But other, older browsers (such as WebTV and IE 5.5 on Mac) can actually cause the page to fail to load. Be warned that cookies [will still be visible using Ajax](https://www.owasp.org/index.php/HTTPOnly#Browsers_Supporting_HttpOnly), though.
##### Defacement
@@ -923,7 +932,7 @@ HTTP/1.1 200 OK [Second New response created by attacker begins]
Content-Type: text/html
-&lt;html&gt;&lt;font color=red&gt;hey&lt;/font&gt;&lt;/html&gt; [Arbitary malicious input is
+&lt;html&gt;&lt;font color=red&gt;hey&lt;/font&gt;&lt;/html&gt; [Arbitrary malicious input is
Keep-Alive: timeout=15, max=100 shown as the redirected page]
Connection: Keep-Alive
Transfer-Encoding: chunked
@@ -969,7 +978,7 @@ request:
| `{ "person": [null, null, ...] }` | `{ :person => [] }` |
| `{ "person": ["foo", null] }` | `{ :person => ["foo"] }` |
-It is possible to return to old behaviour and disable `deep_munge` configuring
+It is possible to return to old behavior and disable `deep_munge` configuring
your application if you are aware of the risk and know how to handle it:
```ruby
@@ -1024,6 +1033,29 @@ Environmental Security
It is beyond the scope of this guide to inform you on how to secure your application code and environments. However, please secure your database configuration, e.g. `config/database.yml`, and your server-side secret, e.g. stored in `config/secrets.yml`. You may want to further restrict access, using environment-specific versions of these files and any others that may contain sensitive information.
+### Custom secrets
+
+Rails generates a `config/secrets.yml`. By default, this file contains the
+application's `secret_key_base`, but it could also be used to store other
+secrets such as access keys for external APIs.
+
+The secrets added to this file are accessible via `Rails.application.secrets`.
+For example, with the following `config/secrets.yml`:
+
+ development:
+ secret_key_base: 3b7cd727ee24e8444053437c36cc66c3
+ some_api_key: SOMEKEY
+
+`Rails.application.secrets.some_api_key` returns `SOMEKEY` in the development
+environment.
+
+If you want an exception to be raised when some key is blank, use the bang
+version:
+
+```ruby
+Rails.application.secrets.some_api_key! # => raises KeyError
+```
+
Additional Resources
--------------------
@@ -1031,4 +1063,5 @@ The security landscape shifts and it is important to keep up to date, because mi
* Subscribe to the Rails security [mailing list](http://groups.google.com/group/rubyonrails-security)
* [Keep up to date on the other application layers](http://secunia.com/) (they have a weekly newsletter, too)
-* A [good security blog](http://ha.ckers.org/blog/) including the [Cross-Site scripting Cheat Sheet](http://ha.ckers.org/xss.html)
+* A [good security blog](https://www.owasp.org) including the [Cross-Site scripting Cheat Sheet](https://www.owasp.org/index.php/DOM_based_XSS_Prevention_Cheat_Sheet)
+
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 752ef48b16..f40e765242 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -141,32 +141,30 @@ users(:david).id
email(david.partner.email, david.location_tonight)
```
-### Rake Tasks for Running your Tests
-
-Rails comes with a number of built-in rake tasks to help with testing. The
-table below lists the commands included in the default Rakefile when a Rails
-project is created.
-
-| Tasks | Description |
-| ----------------------- | ----------- |
-| `rake test` | Runs all tests in the `test` directory. You can also run `rake` and Rails will run all tests by default |
-| `rake test:controllers` | Runs all the controller tests from `test/controllers` |
-| `rake test:functionals` | Runs all the functional tests from `test/controllers`, `test/mailers`, and `test/functional` |
-| `rake test:helpers` | Runs all the helper tests from `test/helpers` |
-| `rake test:integration` | Runs all the integration tests from `test/integration` |
-| `rake test:jobs` | Runs all the job tests from `test/jobs` |
-| `rake test:mailers` | Runs all the mailer tests from `test/mailers` |
-| `rake test:models` | Runs all the model tests from `test/models` |
-| `rake test:units` | Runs all the unit tests from `test/models`, `test/helpers`, and `test/unit` |
-| `rake test:db` | Runs all tests in the `test` directory and resets the db |
+To get multiple fixtures at once, you can pass in a list of fixture names. For example:
+
+```ruby
+# this will return an array containing the fixtures david and steve
+users(:david, :steve)
+```
+
+### Console Tasks for Running your Tests
+
+Rails comes with a CLI command to run tests.
+Here are some examples of how to use it:
+
+```bash
+$ bin/rails test # run all tests in the `test` directory
+$ bin/rails test test/controllers # run all tests from specific directory
+$ bin/rails test test/models/post_test.rb # run specific test
+$ bin/rails test test/models/post_test.rb:44 # run specific test and line
+```
We will cover each of types Rails tests listed above in this guide.
Model Testing
------------------------
-In Rails, unit tests are what you write to test your models.
-
For this guide we will be using the application we built in the [Getting Started with Rails](getting_started.html) guide.
If you remember when you used the `rails generate scaffold` command from earlier. We created our first resource among other things it created a test stub in the `test/models` directory:
@@ -180,6 +178,14 @@ create test/fixtures/articles.yml
...
```
+You can also generate the test stub for a model using the following command:
+
+```bash
+$ bin/rails generate test_unit:model article title:string body:text
+create test/models/article_test.rb
+create test/fixtures/articles.yml
+```
+
The default test stub in `test/models/article_test.rb` looks like this:
```ruby
@@ -259,10 +265,10 @@ be rebuilt. This can be done by executing `bin/rake db:test:prepare`.
### Running Tests
-Running a test is as simple as invoking the file containing the test cases through `rake test` command.
+Running a test is as simple as invoking the file containing the test cases through `rails test` command.
```bash
-$ bin/rake test test/models/article_test.rb
+$ bin/rails test test/models/article_test.rb
.
Finished tests in 0.009262s, 107.9680 tests/s, 107.9680 assertions/s.
@@ -275,7 +281,7 @@ This will run all test methods from the test case.
You can also run a particular test method from the test case by running the test and providing the `test method name`.
```bash
-$ bin/rake test test/models/article_test.rb test_the_truth
+$ bin/rails test test/models/article_test.rb test_the_truth
.
Finished tests in 0.009064s, 110.3266 tests/s, 110.3266 assertions/s.
@@ -296,10 +302,10 @@ test "should not save article without title" do
end
```
-Let us run this newly added test.
+Let us run this newly added test (where `6` is the number of line where the test is defined).
```bash
-$ bin/rake test test/models/article_test.rb test_should_not_save_article_without_title
+$ bin/rails test test/models/article_test.rb:6
F
Finished tests in 0.044632s, 22.4054 tests/s, 22.4054 assertions/s.
@@ -339,7 +345,7 @@ end
Now the test should pass. Let us verify by running the test again:
```bash
-$ bin/rake test test/models/article_test.rb test_should_not_save_article_without_title
+$ bin/rails test test/models/article_test.rb:6
.
Finished tests in 0.047721s, 20.9551 tests/s, 20.9551 assertions/s.
@@ -368,7 +374,7 @@ end
Now you can see even more output in the console from running the tests:
```bash
-$ bin/rake test test/models/article_test.rb test_should_report_error
+$ bin/rails test test/models/article_test.rb
E
Finished tests in 0.030974s, 32.2851 tests/s, 0.0000 assertions/s.
@@ -386,18 +392,17 @@ Notice the 'E' in the output. It denotes a test with error.
NOTE: The execution of each test method stops as soon as any error or an
assertion failure is encountered, and the test suite continues with the next
method. All test methods are executed in random order. The
-[`config.active_support.test_order` option](http://edgeguides.rubyonrails.org/configuring.html#configuring-active-support)
+[`config.active_support.test_order` option](configuring.html#configuring-active-support)
can be used to configure test order.
When a test fails you are presented with the corresponding backtrace. By default
Rails filters that backtrace and will only print lines relevant to your
application. This eliminates the framework noise and helps to focus on your
code. However there are situations when you want to see the full
-backtrace. simply set the `BACKTRACE` environment variable to enable this
-behavior:
+backtrace. Simply set the `-b` (or `--backtrace`) argument to enable this behavior:
```bash
-$ BACKTRACE=1 bin/rake test test/models/article_test.rb
+$ bin/rails test -b test/models/article_test.rb
```
If we want this test to pass we can modify it to use `assert_raises` like so:
@@ -417,9 +422,49 @@ This test should now pass.
By now you've caught a glimpse of some of the assertions that are available. Assertions are the worker bees of testing. They are the ones that actually perform the checks to ensure that things are going as planned.
-There are a bunch of different types of assertions you can use that come with [`Minitest`](https://github.com/seattlerb/minitest), the default testing library used by Rails.
-
-For a list of all available assertions please check the [Minitest API documentation](http://docs.seattlerb.org/minitest/), specifically [`Minitest::Assertions`](http://docs.seattlerb.org/minitest/Minitest/Assertions.html)
+Here's an extract of the assertions you can use with
+[`Minitest`](https://github.com/seattlerb/minitest), the default testing library
+used by Rails. The `[msg]` parameter is an optional string message you can
+specify to make your test failure messages clearer. It's not required.
+
+| Assertion | Purpose |
+| ---------------------------------------------------------------- | ------- |
+| `assert( test, [msg] )` | Ensures that `test` is true.|
+| `assert_not( test, [msg] )` | Ensures that `test` is false.|
+| `assert_equal( expected, actual, [msg] )` | Ensures that `expected == actual` is true.|
+| `assert_not_equal( expected, actual, [msg] )` | Ensures that `expected != actual` is true.|
+| `assert_same( expected, actual, [msg] )` | Ensures that `expected.equal?(actual)` is true.|
+| `assert_not_same( expected, actual, [msg] )` | Ensures that `expected.equal?(actual)` is false.|
+| `assert_nil( obj, [msg] )` | Ensures that `obj.nil?` is true.|
+| `assert_not_nil( obj, [msg] )` | Ensures that `obj.nil?` is false.|
+| `assert_empty( obj, [msg] )` | Ensures that `obj` is `empty?`.|
+| `assert_not_empty( obj, [msg] )` | Ensures that `obj` is not `empty?`.|
+| `assert_match( regexp, string, [msg] )` | Ensures that a string matches the regular expression.|
+| `assert_no_match( regexp, string, [msg] )` | Ensures that a string doesn't match the regular expression.|
+| `assert_includes( collection, obj, [msg] )` | Ensures that `obj` is in `collection`.|
+| `assert_not_includes( collection, obj, [msg] )` | Ensures that `obj` is not in `collection`.|
+| `assert_in_delta( expecting, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are within `delta` of each other.|
+| `assert_not_in_delta( expecting, 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( exception1, exception2, ... ) { block }` | Ensures that the given block doesn't raise one of the given 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.|
+| `assert_not_kind_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class` and is not descending from it.|
+| `assert_respond_to( obj, symbol, [msg] )` | Ensures that `obj` responds to `symbol`.|
+| `assert_not_respond_to( obj, symbol, [msg] )` | Ensures that `obj` does not respond to `symbol`.|
+| `assert_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is true.|
+| `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. 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 &
+more up-to-date list, please check
+[Minitest API documentation](http://docs.seattlerb.org/minitest/), specifically
+[`Minitest::Assertions`](http://docs.seattlerb.org/minitest/Minitest/Assertions.html)
Because of the modular nature of the testing framework, it is possible to create your own assertions. In fact, that's exactly what Rails does. It includes some specialized assertions to make your life easier.
@@ -432,12 +477,11 @@ Rails adds some custom assertions of its own to the `minitest` framework:
| Assertion | Purpose |
| --------------------------------------------------------------------------------- | ------- |
| `assert_difference(expressions, difference = 1, message = nil) {...}` | 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, &amp;block)` | Asserts that the numeric result of evaluating an expression is not changed before and after invoking the passed in block.|
+| `assert_no_difference(expressions, message = nil, &block)` | Asserts that the numeric result of evaluating an expression is not changed before and after invoking the passed in block.|
| `assert_recognizes(expected_options, path, extras={}, message=nil)` | 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)` | 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)` | 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.|
| `assert_redirected_to(options = {}, message=nil)` | Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that `assert_redirected_to(controller: "weblog")` will also match the redirection of `redirect_to(controller: "weblog", action: "show")` and so on. You can also pass named routes such as `assert_redirected_to root_path` and Active Record objects such as `assert_redirected_to @article`.|
-| `assert_template(expected = nil, message=nil)` | Asserts that the request was rendered with the appropriate template file.|
You'll see the usage of some of these assertions in the next chapter.
@@ -454,7 +498,7 @@ All the basic assertions such as `assert_equal` defined in `Minitest::Assertions
Each of these classes include `Minitest::Assertions`, allowing us to use all of the basic assertions in our tests.
-NOTE: For more information on `Minitest`, refer to [Minitest](http://ruby-doc.org/stdlib-2.1.0/libdoc/minitest/rdoc/MiniTest.html)
+NOTE: For more information on `Minitest`, refer to [Minitest](http://docs.seattlerb.org/minitest)
Functional Tests for Your Controllers
-------------------------------------
@@ -473,19 +517,29 @@ You should test for things such as:
Now that we have used Rails scaffold generator for our `Article` resource, it has already created the controller code and tests. You can take look at the file `articles_controller_test.rb` in the `test/controllers` directory.
+The following command will generate a controller test case with a filled up
+test for each of the seven default actions.
+
+```bash
+$ bin/rails generate test_unit:scaffold article
+create test/controllers/articles_controller_test.rb
+```
+
Let me take you through one such test, `test_should_get_index` from the file `articles_controller_test.rb`.
```ruby
+# articles_controller_test.rb
class ArticlesControllerTest < ActionController::TestCase
test "should get index" do
get :index
assert_response :success
- assert_not_nil assigns(:articles)
+ assert_includes @response.body, 'Articles'
end
end
```
-In the `test_should_get_index` test, Rails simulates a request on the action called `index`, making sure the request was successful and also ensuring that it assigns a valid `articles` instance variable.
+In the `test_should_get_index` test, Rails simulates a request on the action called `index`, making sure the request was successful
+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 accepts 4 arguments:
@@ -504,13 +558,13 @@ All the keyword arguments are optional.
Example: Calling the `:show` action, passing an `id` of 12 as the `params` and setting a `user_id` of 5 in the session:
```ruby
-get(:show, params: { 'id' => "12" }, session: { 'user_id' => 5 })
+get(:show, params: { id: 12 }, session: { user_id: 5 })
```
Another example: Calling the `:view` action, passing an `id` of 12 as the `params`, this time with no session, but with a flash message.
```ruby
-get(:view, params: { 'id' => '12' }, flash: { 'message' => 'booya!' })
+get(:view, params: { id: 12 }, flash: { message: 'booya!' })
```
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.
@@ -523,7 +577,7 @@ test "should create article" do
post :create, params: { article: { title: 'Some title' } }
end
- assert_redirected_to article_path(assigns(:article))
+ assert_redirected_to article_path(Article.last)
end
```
@@ -550,11 +604,11 @@ To test AJAX requests, you can specify the `xhr: true` option to `get`, `post`,
`patch`, `put`, and `delete` methods:
```ruby
-test "ajax request responds with no layout" do
+test "ajax request" do
get :show, params: { id: articles(:first).id }, xhr: true
- assert_template :index
- assert_template layout: nil
+ assert_equal 'hello world', @response.body
+ assert_equal "text/javascript", @response.content_type
end
```
@@ -562,20 +616,16 @@ end
After a request has been made and processed, you will have 4 Hash objects ready for use:
-* `assigns` - Any objects that are stored as instance variables in actions for use in views.
* `cookies` - Any cookies that are set.
* `flash` - Any objects living in the flash.
* `session` - Any object living in session variables.
-As is the case with normal Hash objects, you can access the values by referencing the keys by string. You can also reference them by symbol name, except for `assigns`. For example:
+As is the case with normal Hash objects, you can access the values by referencing the keys by string. You can also reference them by symbol name. For example:
```ruby
flash["gordon"] flash[:gordon]
session["shmession"] session[:shmession]
cookies["are_good_for_u"] cookies[:are_good_for_u]
-
-# Because you can't use assigns[:something] for historical reasons:
-assigns["something"] assigns(:something)
```
### Instance Variables Available
@@ -603,46 +653,6 @@ get :index # simulate the request with custom header
post :create # simulate the request with custom env variable
```
-### Testing Templates and Layouts
-
-Eventually, you may want to test whether a specific layout is rendered in the view of a response.
-
-#### Asserting Templates
-
-If you want to make sure that the response rendered the correct template and layout, you can use the `assert_template`
-method:
-
-```ruby
-test "index should render correct template and layout" do
- get :index
- assert_template :index
- assert_template layout: "layouts/application"
-
- # You can also pass a regular expression.
- assert_template layout: /layouts\/application/
-end
-```
-
-NOTE: You cannot test for template and layout at the same time, with a single call to `assert_template`.
-
-WARNING: You must include the "layouts" directory name even if you save your layout file in this standard layout directory. Hence, `assert_template layout: "application"` will not work.
-
-#### Asserting Partials
-
-If your view renders any partial, when asserting for the layout, you can to assert for the partial at the same time.
-Otherwise, assertion will fail.
-
-Remember, we added the "_form" partial to our new Article view? Let's write an assertion for that in the `:new` action now:
-
-```ruby
-test "new should render correct layout" do
- get :new
- assert_template layout: "layouts/application", partial: "_form"
-end
-```
-
-This is the correct way to assert for when the view renders a partial with a given name. As identified by the `:partial` key passed to the `assert_template` call.
-
### Testing `flash` notices
If you remember from earlier one of the Four Hashes of the Apocalypse was `flash`.
@@ -658,7 +668,7 @@ test "should create article" do
post :create, params: { article: { title: 'Some title' } }
end
- assert_redirected_to article_path(assigns(:article))
+ assert_redirected_to article_path(Article.last)
assert_equal 'Article was successfully created.', flash[:notice]
end
```
@@ -666,7 +676,7 @@ end
If we run our test now, we should see a failure:
```bash
-$ bin/rake test test/controllers/articles_controller_test.rb test_should_create_article
+$ bin/rails test test/controllers/articles_controller_test.rb test_should_create_article
Run options: -n test_should_create_article --seed 32266
# Running:
@@ -704,7 +714,7 @@ end
Now if we run our tests, we should see it pass:
```bash
-$ bin/rake test test/controllers/articles_controller_test.rb test_should_create_article
+$ bin/rails test test/controllers/articles_controller_test.rb test_should_create_article
Run options: -n test_should_create_article --seed 18981
# Running:
@@ -751,7 +761,7 @@ We can also add a test for updating an existing Article.
test "should update article" do
article = articles(:one)
patch :update, params: { id: article.id, article: { title: "updated" } }
- assert_redirected_to article_path(assigns(:article))
+ assert_redirected_to article_path(article)
end
```
@@ -790,7 +800,7 @@ class ArticlesControllerTest < ActionController::TestCase
test "should update article" do
patch :update, params: { id: @article.id, article: { title: "updated" } }
- assert_redirected_to article_path(assigns(:article))
+ assert_redirected_to article_path(@article)
end
end
```
@@ -827,7 +837,6 @@ class ProfileControllerTest < ActionController::TestCase
get :show
assert_response :success
- assert_equal users(:david), assigns(:user)
end
end
```
@@ -852,7 +861,7 @@ end
I've added this file here `test/controllers/articles_routes_test.rb` and if we run the test we should see:
```bash
-$ bin/rake test test/controllers/articles_routes_test.rb
+$ bin/rails test test/controllers/articles_routes_test.rb
# Running:
@@ -868,7 +877,7 @@ For more information on routing assertions available in Rails, see the API docum
Testing Views
-------------
-Testing the response to your request by asserting the presence of key HTML elements and their content is a common way to test the views of your application. The `assert_select` method allows you to query HTML elements of the response by using a simple yet powerful syntax.
+Testing the response to your request by asserting the presence of key HTML elements and their content is a common way to test the views of your application. Like route tests, view tests reside in `test/controllers/` or are part of controller tests. The `assert_select` method allows you to query HTML elements of the response by using a simple yet powerful syntax.
There are two forms of `assert_select`:
@@ -1041,14 +1050,12 @@ How about testing our ability to create a new article in our blog and see the re
test "can create an article" do
get "/articles/new"
assert_response :success
- assert_template "articles/new", partial: "articles/_form"
post "/articles",
params: { article: { title: "can create", body: "article successfully." } }
assert_response :redirect
follow_redirect!
assert_response :success
- assert_template "articles/show"
assert_select "p", "Title:\n can create"
end
```
@@ -1074,7 +1081,7 @@ Finally we can assert that our response was successful, template was rendered, a
#### Taking it further
-We were able to successfully test a very small workflow for visiting our blog and creating a new article. If we wanted to take this further we could add tests for commenting, removing articles, or editting comments. Integration tests are a great place to experiment with all kinds of use-cases for our applications.
+We were able to successfully test a very small workflow for visiting our blog and creating a new article. If we wanted to take this further we could add tests for commenting, removing articles, or editing comments. Integration tests are a great place to experiment with all kinds of use-cases for our applications.
Testing Your Mailers
--------------------
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 7666601bd7..743241d7a0 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -20,7 +20,7 @@ The best way to be sure that your application still works after upgrading is to
Rails generally stays close to the latest released Ruby version when it's released:
-* Rails 5 requires Ruby 2.2.1 or newer.
+* Rails 5 requires Ruby 2.2.2 or newer.
* Rails 4 prefers Ruby 2.0 and requires 1.9.3 or newer.
* Rails 3.2.x is the last branch to support Ruby 1.8.7.
* Rails 3 and above require Ruby 1.8.7 or higher. Support for all of the previous Ruby versions has been dropped officially. You should upgrade as early as possible.
@@ -75,7 +75,7 @@ warning by adding the following configuration to your `config/application.rb`:
See [#17227](https://github.com/rails/rails/pull/17227) for more details.
-### ActiveJob jobs now inherent from ApplicationJob by default
+### ActiveJob jobs now inherit from ApplicationJob by default
In Rails 4.2 an ActiveJob inherits from `ActiveJob::Base`. In Rails 5.0 this
behavior has changed to now inherit from `ApplicationJob`.
@@ -298,7 +298,7 @@ end
The migration DSL has been expanded to support foreign key definitions. If
you've been using the Foreigner gem, you might want to consider removing it.
Note that the foreign key support of Rails is a subset of Foreigner. This means
-that not every Foreigner definition can be fully replaced by it's Rails
+that not every Foreigner definition can be fully replaced by its Rails
migration DSL counterpart.
The migration procedure is as follows:
@@ -460,7 +460,7 @@ If your application currently depend on MultiJSON directly, you have a few optio
WARNING: Do not simply replace `MultiJson.dump` and `MultiJson.load` with
`JSON.dump` and `JSON.load`. These JSON gem APIs are meant for serializing and
-deserializing arbitrary Ruby objects and are generally [unsafe](http://www.ruby-doc.org/stdlib-2.0.0/libdoc/json/rdoc/JSON.html#method-i-load).
+deserializing arbitrary Ruby objects and are generally [unsafe](http://www.ruby-doc.org/stdlib-2.2.2/libdoc/json/rdoc/JSON.html#method-i-load).
#### JSON gem compatibility
@@ -883,6 +883,20 @@ this gem such as `whitelist_attributes` or `mass_assignment_sanitizer` options.
* To re-enable the old finders, you can use the [activerecord-deprecated_finders gem](https://github.com/rails/activerecord-deprecated_finders).
+* Rails 4.0 has changed to default join table for `has_and_belongs_to_many` relations to strip the common prefix off the second table name. Any existing `has_and_belongs_to_many` relationship between models with a common prefix must be specified with the `join_table` option. For example:
+
+```ruby
+CatalogCategory < ActiveRecord::Base
+ has_and_belongs_to_many :catalog_products, join_table: 'catalog_categories_catalog_products'
+end
+
+CatalogProduct < ActiveRecord::Base
+ has_and_belongs_to_many :catalog_categories, join_table: 'catalog_categories_catalog_products'
+end
+```
+
+* Note that the the prefix takes scopes into account as well, so relations between `Catalog::Category` and `Catalog::Product` or `Catalog::Category` and `CatalogProduct` need to be updated similarly.
+
### Active Resource
Rails 4.0 extracted Active Resource to its own gem. If you still need the feature you can add the [Active Resource gem](https://github.com/rails/activeresource) in your Gemfile.
@@ -912,7 +926,7 @@ Rails 4.0 extracted Active Resource to its own gem. If you still need the featur
Please note that you should wait to set `secret_key_base` until you have 100% of your userbase on Rails 4.x and are reasonably sure you will not need to rollback to Rails 3.x. This is because cookies signed based on the new `secret_key_base` in Rails 4.x are not backwards compatible with Rails 3.x. You are free to leave your existing `secret_token` in place, not set the new `secret_key_base`, and ignore the deprecation warnings until you are reasonably sure that your upgrade is otherwise complete.
-If you are relying on the ability for external applications or Javascript to be able to read your Rails app's signed session cookies (or signed cookies in general) you should not set `secret_key_base` until you have decoupled these concerns.
+If you are relying on the ability for external applications or JavaScript to be able to read your Rails app's signed session cookies (or signed cookies in general) you should not set `secret_key_base` until you have decoupled these concerns.
* Rails 4.0 encrypts the contents of cookie-based sessions if `secret_key_base` has been set. Rails 3.x signed, but did not encrypt, the contents of cookie-based session. Signed cookies are "secure" in that they are verified to have been generated by your app and are tamper-proof. However, the contents can be viewed by end users, and encrypting the contents eliminates this caveat/concern without a significant performance penalty.
@@ -926,6 +940,8 @@ Please read [Pull Request #9978](https://github.com/rails/rails/pull/9978) for d
* Rails 4.0 has removed the XML parameters parser. You will need to add the `actionpack-xml_parser` gem if you require this feature.
+* Rails 4.0 changes the default `layout` lookup set using symbols or procs that return nil. To get the "no layout" behavior, return false instead of nil.
+
* Rails 4.0 changes the default memcached client from `memcache-client` to `dalli`. To upgrade, simply add `gem 'dalli'` to your `Gemfile`.
* Rails 4.0 deprecates the `dom_id` and `dom_class` methods in controllers (they are fine in views). You will need to include the `ActionView::RecordIdentifier` module in controllers requiring this feature.
diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md
index e3856a285a..1c42ff2914 100644
--- a/guides/source/working_with_javascript_in_rails.md
+++ b/guides/source/working_with_javascript_in_rails.md
@@ -191,34 +191,6 @@ $(document).ready ->
Obviously, you'll want to be a bit more sophisticated than that, but it's a
start. You can see more about the events [in the jquery-ujs wiki](https://github.com/rails/jquery-ujs/wiki/ajax).
-Another possibility is returning javascript directly from the server side on
-remote calls:
-
-```ruby
-# articles_controller
-def create
- respond_to do |format|
- if @article.save
- format.html { ... }
- format.js do
- render js: <<-endjs
- alert('Article saved successfully!');
- window.location = '#{article_path(@article)}';
- endjs
- end
- else
- format.html { ... }
- format.js do
- render js: "alert('There are empty fields in the form!');"
- end
- end
- end
-end
-```
-
-NOTE: If javascript is disabled in the user browser, `format.html { ... }`
-block should be executed as fallback.
-
### form_tag
[`form_tag`](http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-form_tag)
@@ -286,7 +258,7 @@ this generates
```html
<form action="/articles/1" class="button_to" data-remote="true" method="post">
- <div><input type="submit" value="An article"></div>
+ <input type="submit" value="An article" />
</form>
```
@@ -385,7 +357,7 @@ This gem uses Ajax to speed up page rendering in most applications.
Turbolinks attaches a click handler to all `<a>` on the page. If your browser
supports
-[PushState](https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Manipulating_the_browser_history#The_pushState()_method),
+[PushState](https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Manipulating_the_browser_history#The_pushState%28%29_method),
Turbolinks will make an Ajax request for the page, parse the response, and
replace the entire `<body>` of the page with the `<body>` of the response. It
will then use PushState to change the URL to the correct one, preserving
diff --git a/install.rb b/install.rb
index 96e4153165..933e5ee9a2 100644
--- a/install.rb
+++ b/install.rb
@@ -7,10 +7,10 @@ end
%w( activesupport activemodel activerecord actionpack actionview actionmailer railties activejob ).each do |framework|
puts "Installing #{framework}..."
- `cd #{framework} && gem build #{framework}.gemspec && gem install #{framework}-#{version}.gem --no-ri --no-rdoc && rm #{framework}-#{version}.gem`
+ `cd #{framework} && gem build #{framework}.gemspec && gem install #{framework}-#{version}.gem --no-document && rm #{framework}-#{version}.gem`
end
puts "Installing rails..."
`gem build rails.gemspec`
-`gem install rails-#{version}.gem --no-ri --no-rdoc `
+`gem install rails-#{version}.gem --no-document `
`rm rails-#{version}.gem`
diff --git a/rails.gemspec b/rails.gemspec
index 23404da9be..52112680b2 100644
--- a/rails.gemspec
+++ b/rails.gemspec
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
s.summary = 'Full-stack web application framework.'
s.description = 'Ruby on Rails is a full-stack web framework optimized for programmer happiness and sustainable productivity. It encourages beautiful code by favoring convention over configuration.'
- s.required_ruby_version = '>= 2.2.1'
+ s.required_ruby_version = '>= 2.2.2'
s.required_rubygems_version = '>= 1.8.11'
s.license = 'MIT'
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index e60feed15f..0f1aab9839 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,4 +1,100 @@
-* Print `bundle install` output in `rails new` as soon as it's available
+* Add `bin/update` script to update development environment automatically.
+
+ *Mehmet Emin İNAÇ*
+
+* Fix STATS_DIRECTORIES already defined warning when running rake from within
+ the top level directory of an engine that has a test app.
+
+ Fixes #20510
+
+ *Ersin Akinci*
+
+* Make enabling or disabling caching in development mode possible with
+ rake dev:cache.
+
+ Running rake dev:cache will create or remove tmp/caching-dev.txt. When this
+ file exists config.action_controller.perform_caching will be set to true in
+ config/environments/development.rb.
+
+ Additionally, a server can be started with either --dev-caching or
+ --no-dev-caching included to toggle caching on startup.
+
+ *Jussi Mertanen*, *Chuck Callebs*
+
+* Add a `--api` option in order to generate plugins that can be added
+ inside an API application.
+
+ *Robin Dupret*
+
+* Fix `NoMethodError` when generating a scaffold inside a full engine.
+
+ *Yuji Yaginuma*
+
+* Adding support for passing a block to the `add_source` action of a custom generator
+
+ *Mike Dalton*, *Hirofumi Wakasugi*
+
+* `assert_file` understands paths with special characters
+ (eg. `v0.1.4~alpha+nightly`).
+
+ *Diego Carrion*
+
+* Remove ContentLength middleware from the defaults. If you want it, just
+ add it as a middleware in your config.
+
+ *Egg McMuffin*
+
+* Make it possible to customize the executable inside rerun snippets.
+
+ *Yves Senn*
+
+* Add support for API only apps.
+ Middleware stack was slimmed down and it has only the needed
+ middleware for API apps & generators generates the right files,
+ folders and configurations.
+
+ *Santiago Pastorino & Jorge Bejar*
+
+* Make generated scaffold functional tests work inside engines.
+
+ *Yuji Yaginuma*
+
+* Generator a `.keep` file in the `tmp` folder by default as many scripts
+ assume the existence of this folder and most would fail if it is absent.
+
+ See #20299.
+
+ *Yoong Kang Lim*, *Sunny Juneja*
+
+* `config.static_index` configures directory `index.html` filename
+
+ Set `config.static_index` to serve a static directory index file not named
+ `index`. E.g. to serve `main.html` instead of `index.html` for directory
+ requests, set `config.static_index` to `"main"`.
+
+ *Eliot Sykes*
+
+* `bin/setup` uses built-in rake tasks (`log:clear`, `tmp:clear`).
+
+ *Mohnish Thallavajhula*
+
+* Fix mailer previews with attachments by using the mail gem's own API to
+ locate the first part of the correct mime type.
+
+ Fixes #14435.
+
+ *Andrew White*
+
+* Remove sqlite support from `rails dbconsole`.
+
+ *Andrew White*
+
+* Rename `railties/bin` to `railties/exe` to match the new Bundler executables
+ convention.
+
+ *Islam Wazery*
+
+* Print `bundle install` output in `rails new` as soon as it's available.
Running `rails new` will now print the output of `bundle install` as
it is available, instead of waiting until all gems finish installing.
@@ -18,7 +114,7 @@
*arthurnn*
-* Add `rake initializers`
+* Add `rake initializers`.
This task prints out all defined initializers in the order they are invoked
by Rails. This is helpful for debugging issues related to the initialization
@@ -33,7 +129,7 @@
*Hyonjee Joo*
-* Add `config/initializers/active_record_belongs_to_required_by_default.rb`
+* Add `config/initializers/active_record_belongs_to_required_by_default.rb`.
Newly generated Rails apps have a new initializer called
`active_record_belongs_to_required_by_default.rb` which sets the value of
@@ -52,7 +148,7 @@
'No such middleware' errors when `insert_before` or `insert_after` are added
after the `delete` operation for the middleware being deleted.
- Fixes: #16433.
+ Fixes #16433.
*Guo Xiang Tan*
@@ -64,11 +160,11 @@
*Xavier Noria*
-* Force generated routes to be inserted into routes.rb
+* Force generated routes to be inserted into `config/routes.rb`.
*Andrew White*
-* Don't remove all line endings from routes.rb when revoking scaffold.
+* Don't remove all line endings from `config/routes.rb` when revoking scaffold.
Fixes #15913.
@@ -78,11 +174,12 @@
*Melanie Gilman*
-* Add the `method_source` gem to the default Gemfile for apps
+* Add the `method_source` gem to the default Gemfile for apps.
*Sean Griffin*
-* Drop old test locations from `rake stats`
+* Drop old test locations from `rake stats`:
+
- test/functional
- test/unit
@@ -113,7 +210,7 @@
*Andrew Kozlov*
-* Add `config/initializers/callback_terminator.rb`
+* Add `config/initializers/callback_terminator.rb`.
Newly generated Rails apps have a new initializer called
`callback_terminator.rb` which sets the value of the configuration option
diff --git a/railties/Rakefile b/railties/Rakefile
index 9a377ce4ee..4393f45790 100644
--- a/railties/Rakefile
+++ b/railties/Rakefile
@@ -1,5 +1,4 @@
require 'rake/testtask'
-require 'rubygems/package_task'
task :default => :test
@@ -31,20 +30,3 @@ Rake::TestTask.new('test:regular') do |t|
t.verbose = true
t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
end
-
-# Generate GEM ----------------------------------------------------------------------------
-
-spec = eval(File.read('railties.gemspec'))
-
-Gem::PackageTask.new(spec) do |pkg|
- pkg.gem_spec = spec
-end
-
-# Publishing -------------------------------------------------------
-
-desc "Release to rubygems"
-task :release => :package do
- require 'rake/gemcutter'
- Rake::Gemcutter::Tasks.new(spec).define
- Rake::Task['gem:push'].invoke
-end
diff --git a/railties/bin/rails b/railties/exe/rails
index 82c17cabce..82c17cabce 100755
--- a/railties/bin/rails
+++ b/railties/exe/rails
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index b1f7c29b5a..fe789f3c2a 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -52,6 +52,11 @@ module Rails
end
end
+ # Returns a Pathname object of the current rails project,
+ # otherwise it returns nil if there is no project:
+ #
+ # Rails.root
+ # # => #<Pathname:/Users/someuser/some/path/project>
def root
application && application.config.root
end
@@ -94,6 +99,11 @@ module Rails
groups
end
+ # Returns a Pathname object of the public folder of the current
+ # rails project, otherwise it returns nil if there is no project:
+ #
+ # Rails.public_path
+ # # => #<Pathname:/Users/someuser/some/path/project/public>
def public_path
application && Pathname.new(application.paths["public"].first)
end
diff --git a/railties/lib/rails/app_rails_loader.rb b/railties/lib/rails/app_loader.rb
index 9a7c6c5f2d..a9fe21824e 100644
--- a/railties/lib/rails/app_rails_loader.rb
+++ b/railties/lib/rails/app_loader.rb
@@ -2,7 +2,7 @@ require 'pathname'
require 'rails/version'
module Rails
- module AppRailsLoader
+ module AppLoader # :nodoc:
extend self
RUBY = Gem.ruby
@@ -29,7 +29,7 @@ generate it and add it to source control:
EOS
- def exec_app_rails
+ def exec_app
original_cwd = Dir.pwd
loop do
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index e9683d4a95..8075068b3f 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -1,4 +1,5 @@
require 'fileutils'
+require 'yaml'
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/object/blank'
require 'active_support/key_generator'
@@ -6,8 +7,7 @@ require 'active_support/message_verifier'
require 'rails/engine'
module Rails
- # In Rails 3.0, a Rails::Application object was introduced which is nothing more than
- # an Engine but with the responsibility of coordinating the whole boot process.
+ # An Engine with the responsibility of coordinating the whole boot process.
#
# == Initialization
#
@@ -228,7 +228,6 @@ module Rails
yaml = Pathname.new("#{paths["config"].existent.first}/#{name}.yml")
if yaml.exist?
- require "yaml"
require "erb"
(YAML.load(ERB.new(yaml.read).result) || {})[Rails.env] || {}
else
@@ -511,7 +510,7 @@ module Rails
"Read the upgrade documentation to learn more about this new config option."
if secrets.secret_token.blank?
- raise "Missing `secret_token` and `secret_key_base` for '#{Rails.env}' environment, set these values in `config/secrets.yml`"
+ raise "Missing `secret_key_base` for '#{Rails.env}' environment, set this value in `config/secrets.yml`"
end
end
end
diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb
index 0f4d932749..9baf8aa742 100644
--- a/railties/lib/rails/application/bootstrap.rb
+++ b/railties/lib/rails/application/bootstrap.rb
@@ -63,7 +63,7 @@ INFO
Rails.cache = ActiveSupport::Cache.lookup_store(config.cache_store)
if Rails.cache.respond_to?(:middleware)
- config.middleware.insert_before("Rack::Runtime", Rails.cache.middleware)
+ config.middleware.insert_before(::Rack::Runtime, Rails.cache.middleware)
end
end
end
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index dc3ec4274b..4fc7a1db62 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -11,12 +11,12 @@ module Rails
:eager_load, :exceptions_app, :file_watcher, :filter_parameters,
:force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags,
:railties_order, :relative_url_root, :secret_key_base, :secret_token,
- :serve_static_files, :ssl_options, :static_cache_control, :session_options,
- :time_zone, :reload_classes_only_on_change,
+ :serve_static_files, :ssl_options, :static_cache_control, :static_index,
+ :session_options, :time_zone, :reload_classes_only_on_change,
:beginning_of_week, :filter_redirect, :x
attr_writer :log_level
- attr_reader :encoding
+ attr_reader :encoding, :api_only
def initialize(*)
super
@@ -28,6 +28,7 @@ module Rails
@helpers_paths = []
@serve_static_files = true
@static_cache_control = nil
+ @static_index = "index"
@force_ssl = false
@ssl_options = {}
@session_store = :cookie_store
@@ -48,6 +49,7 @@ module Rails
@eager_load = nil
@secret_token = nil
@secret_key_base = nil
+ @api_only = false
@x = Custom.new
end
@@ -59,6 +61,11 @@ module Rails
end
end
+ def api_only=(value)
+ @api_only = value
+ generators.api_only = value
+ end
+
def paths
@paths ||= begin
paths = super
diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb
index 02eea82b0c..88eade5c5a 100644
--- a/railties/lib/rails/application/default_middleware_stack.rb
+++ b/railties/lib/rails/application/default_middleware_stack.rb
@@ -18,7 +18,7 @@ module Rails
middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header
if config.serve_static_files
- middleware.use ::ActionDispatch::Static, paths["public"].first, config.static_cache_control
+ middleware.use ::ActionDispatch::Static, paths["public"].first, config.static_cache_control, index: config.static_index
end
if rack_cache = load_rack_cache
@@ -26,9 +26,29 @@ module Rails
middleware.use ::Rack::Cache, rack_cache
end
- middleware.use ::Rack::Lock unless allow_concurrency?
+ if config.allow_concurrency == false
+ # User has explicitly opted out of concurrent request
+ # handling: presumably their code is not threadsafe
+
+ middleware.use ::Rack::Lock
+
+ elsif config.allow_concurrency == :unsafe
+ # Do nothing, even if we know this is dangerous. This is the
+ # historical behaviour for true.
+
+ else
+ # Default concurrency setting: enabled, but safe
+
+ unless config.cache_classes && config.eager_load
+ # Without cache_classes + eager_load, the load interlock
+ # is required for proper operation
+
+ middleware.use ::ActionDispatch::LoadInterlock
+ end
+ end
+
middleware.use ::Rack::Runtime
- middleware.use ::Rack::MethodOverride
+ middleware.use ::Rack::MethodOverride unless config.api_only
middleware.use ::ActionDispatch::RequestId
# Must come after Rack::MethodOverride to properly log overridden methods
@@ -42,9 +62,9 @@ module Rails
end
middleware.use ::ActionDispatch::Callbacks
- middleware.use ::ActionDispatch::Cookies
+ middleware.use ::ActionDispatch::Cookies unless config.api_only
- if config.session_store
+ if !config.api_only && config.session_store
if config.force_ssl && !config.session_options.key?(:secure)
config.session_options[:secure] = true
end
@@ -65,14 +85,6 @@ module Rails
config.reload_classes_only_on_change != true || app.reloaders.map(&:updated?).any?
end
- def allow_concurrency?
- if config.allow_concurrency.nil?
- config.cache_classes && config.eager_load
- else
- config.allow_concurrency
- end
- end
-
def load_rack_cache
rack_cache = config.action_dispatch.rack_cache
return unless rack_cache
diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb
index 0599e988d9..404e3c3e23 100644
--- a/railties/lib/rails/application/finisher.rb
+++ b/railties/lib/rails/application/finisher.rb
@@ -86,8 +86,10 @@ module Rails
# added in the hook are taken into account.
initializer :set_clear_dependencies_hook, group: :all do
callback = lambda do
- ActiveSupport::DescendantsTracker.clear
- ActiveSupport::Dependencies.clear
+ ActiveSupport::Dependencies.interlock.attempt_unloading do
+ ActiveSupport::DescendantsTracker.clear
+ ActiveSupport::Dependencies.clear
+ end
end
if config.reload_classes_only_on_change
diff --git a/railties/lib/rails/application_controller.rb b/railties/lib/rails/application_controller.rb
index 9a29ec21cf..618a09a5b3 100644
--- a/railties/lib/rails/application_controller.rb
+++ b/railties/lib/rails/application_controller.rb
@@ -6,7 +6,7 @@ class Rails::ApplicationController < ActionController::Base # :nodoc:
def require_local!
unless local_request?
- render text: '<p>For security purposes, this information is only available to local requests.</p>', status: :forbidden
+ render html: '<p>For security purposes, this information is only available to local requests.</p>'.html_safe, status: :forbidden
end
end
diff --git a/railties/lib/rails/cli.rb b/railties/lib/rails/cli.rb
index dd70c272c6..a8794bc0de 100644
--- a/railties/lib/rails/cli.rb
+++ b/railties/lib/rails/cli.rb
@@ -1,8 +1,8 @@
-require 'rails/app_rails_loader'
+require 'rails/app_loader'
# If we are inside a Rails application this method performs an exec and thus
# the rest of this script is not run.
-Rails::AppRailsLoader.exec_app_rails
+Rails::AppLoader.exec_app
require 'rails/ruby_version_check'
Signal.trap("INT") { puts; exit(1) }
diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb
index 583d005d46..fd352dc9b7 100644
--- a/railties/lib/rails/code_statistics.rb
+++ b/railties/lib/rails/code_statistics.rb
@@ -41,11 +41,9 @@ class CodeStatistics #:nodoc:
if File.directory?(path) && (/^\./ !~ file_name)
stats.add(calculate_directory_statistics(path, pattern))
+ elsif file_name =~ pattern
+ stats.add_by_file_path(path)
end
-
- next unless file_name =~ pattern
-
- stats.add_by_file_path(path)
end
stats
diff --git a/railties/lib/rails/commands/commands_tasks.rb b/railties/lib/rails/commands/commands_tasks.rb
index d8d4080c3e..685d55eea8 100644
--- a/railties/lib/rails/commands/commands_tasks.rb
+++ b/railties/lib/rails/commands/commands_tasks.rb
@@ -151,6 +151,17 @@ EOT
puts HELP_MESSAGE
end
+ # Output an error message stating that the attempted command is not a valid rails command.
+ # Run the attempted command as a rake command with the --dry-run flag. If successful, suggest
+ # to the user that they possibly meant to run the given rails command as a rake command.
+ # Append the help message.
+ #
+ # Example:
+ # $ rails db:migrate
+ # Error: Command 'db:migrate' not recognized
+ # Did you mean: `$ rake db:migrate` ?
+ # (Help message output)
+ #
def write_error_message(command)
puts "Error: Command '#{command}' not recognized"
if %x{rake #{command} --dry-run 2>&1 } && $?.success?
diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb
index 5d37a2b699..ea5d20ea24 100644
--- a/railties/lib/rails/commands/console.rb
+++ b/railties/lib/rails/commands/console.rb
@@ -1,14 +1,13 @@
require 'optparse'
require 'irb'
require 'irb/completion'
+require 'rails/commands/console_helper'
module Rails
class Console
- class << self
- def start(*args)
- new(*args).start
- end
+ include ConsoleHelper
+ class << self
def parse_arguments(arguments)
options = {}
@@ -21,23 +20,8 @@ module Rails
opt.parse!(arguments)
end
- 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
+ set_options_env(arguments, options)
end
-
- private
-
- def available_environments
- Dir['config/environments/*.rb'].map { |fname| File.basename(fname, '.*') }
- end
end
attr_reader :options, :app, :console
@@ -57,12 +41,9 @@ module Rails
end
def environment
- options[:environment] ||= ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
- end
-
- def environment?
- environment
+ options[:environment] ||= super
end
+ alias_method :environment?, :environment
def set_environment!
Rails.env = environment
diff --git a/railties/lib/rails/commands/console_helper.rb b/railties/lib/rails/commands/console_helper.rb
new file mode 100644
index 0000000000..8ee0b60012
--- /dev/null
+++ b/railties/lib/rails/commands/console_helper.rb
@@ -0,0 +1,34 @@
+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 \ No newline at end of file
diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb
index 5175e31f14..dca60f948f 100644
--- a/railties/lib/rails/commands/dbconsole.rb
+++ b/railties/lib/rails/commands/dbconsole.rb
@@ -1,13 +1,49 @@
require 'erb'
require 'yaml'
require 'optparse'
+require 'rails/commands/console_helper'
module Rails
class DBConsole
+ include ConsoleHelper
+
attr_reader :arguments
- def self.start
- new.start
+ 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
end
def initialize(arguments = ARGV)
@@ -15,7 +51,7 @@ module Rails
end
def start
- options = parse_arguments(arguments)
+ options = self.class.parse_arguments(arguments)
ENV['RAILS_ENV'] = options[:environment] || environment
case config["adapter"]
@@ -50,9 +86,6 @@ module Rails
ENV['PGPASSWORD'] = config["password"].to_s if config["password"] && options['include_password']
find_cmd_and_exec('psql', config["database"])
- when "sqlite"
- find_cmd_and_exec('sqlite', config["database"])
-
when "sqlite3"
args = []
@@ -89,7 +122,7 @@ module Rails
find_cmd_and_exec("sqsh", *args)
else
- abort "Unknown command-line client for #{config['database']}. Submit a Rails patch to add support!"
+ abort "Unknown command-line client for #{config['database']}."
end
end
@@ -104,90 +137,37 @@ module Rails
end
def environment
- if Rails.respond_to?(:env)
- Rails.env
- else
- ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
- end
+ Rails.respond_to?(:env) ? Rails.env : super
end
protected
+ def configurations
+ require APP_PATH
+ ActiveRecord::Base.configurations = Rails.application.config.database_configuration
+ ActiveRecord::Base.configurations
+ end
- def configurations
- require APP_PATH
- ActiveRecord::Base.configurations = Rails.application.config.database_configuration
- ActiveRecord::Base.configurations
- end
-
- 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
+ def find_cmd_and_exec(commands, *args)
+ commands = Array(commands)
- opt.on("--header") do |h|
- options['header'] = h
+ dirs_on_path = ENV['PATH'].to_s.split(File::PATH_SEPARATOR)
+ unless (ext = RbConfig::CONFIG['EXEEXT']).empty?
+ commands = commands.map{|cmd| "#{cmd}#{ext}"}
end
- opt.on("-h", "--help", "Show this help message.") do
- puts opt
- exit
+ full_path_command = nil
+ found = commands.detect do |cmd|
+ dirs_on_path.detect do |path|
+ full_path_command = File.join(path, cmd)
+ File.file?(full_path_command) && File.executable?(full_path_command)
+ end
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
-
- if arguments.first && arguments.first[0] != '-'
- env = arguments.first
- if available_environments.include? env
- options[:environment] = env
+ if found
+ exec full_path_command, *args
else
- options[:environment] = %w(production development test).detect {|e| e =~ /^#{env}/} || env
+ abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.")
end
end
-
- options
- end
-
- def available_environments
- Dir['config/environments/*.rb'].map { |fname| File.basename(fname, '.*') }
- end
-
- def find_cmd_and_exec(commands, *args)
- 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}"}
- end
-
- full_path_command = nil
- found = commands.detect do |cmd|
- dirs_on_path.detect do |path|
- full_path_command = File.join(path, cmd)
- File.file?(full_path_command) && File.executable?(full_path_command)
- end
- end
-
- if found
- exec full_path_command, *args
- else
- abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.")
- end
- end
end
end
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index 546d3725d8..c8fb58ab05 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -34,6 +34,9 @@ module Rails
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 ""
@@ -67,6 +70,7 @@ module Rails
print_boot_information
trap(:INT) { exit }
create_tmp_directories
+ setup_dev_caching
log_to_stdout if options[:log_stdout]
super
@@ -77,18 +81,7 @@ module Rails
end
def middleware
- middlewares = []
- middlewares << [::Rack::ContentLength]
-
- # FIXME: add Rack::Lock in the case people are using webrick.
- # This is to remain backwards compatible for those who are
- # running webrick in production. We should consider removing this
- # in development.
- if server.name == 'Rack::Handler::WEBrick'
- middlewares << [::Rack::Lock]
- end
-
- Hash.new(middlewares)
+ Hash.new([])
end
def default_options
@@ -97,13 +90,23 @@ module Rails
DoNotReverseLookup: true,
environment: (ENV['RAILS_ENV'] || ENV['RACK_ENV'] || "development").dup,
daemonize: false,
- pid: File.expand_path("tmp/pids/server.pid"),
- config: File.expand_path("config.ru")
+ caching: false,
+ pid: File.expand_path("tmp/pids/server.pid")
})
end
private
+ def setup_dev_caching
+ return unless options[:environment] == "development"
+
+ if options[:caching] == false
+ delete_cache_file
+ elsif options[:caching]
+ create_cache_file
+ end
+ end
+
def print_boot_information
url = "#{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}"
puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}"
@@ -113,6 +116,14 @@ module Rails
puts "=> Ctrl-C to shutdown server" unless options[:daemonize]
end
+ def create_cache_file
+ FileUtils.touch("tmp/caching-dev.txt")
+ end
+
+ def delete_cache_file
+ FileUtils.rm("tmp/caching-dev.txt") if File.exist?("tmp/caching-dev.txt")
+ 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))
diff --git a/railties/lib/rails/commands/test.rb b/railties/lib/rails/commands/test.rb
index 598e224a6f..dd069f081f 100644
--- a/railties/lib/rails/commands/test.rb
+++ b/railties/lib/rails/commands/test.rb
@@ -1,5 +1,9 @@
-require "rails/test_unit/runner"
+require "rails/test_unit/minitest_plugin"
-$: << File.expand_path("../../test", APP_PATH)
+if defined?(ENGINE_ROOT)
+ $: << File.expand_path('test', ENGINE_ROOT)
+else
+ $: << File.expand_path('../../test', APP_PATH)
+end
-Rails::TestRunner.run(ARGV)
+exit Minitest.run(ARGV)
diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb
index 76364cea8f..d99d27a756 100644
--- a/railties/lib/rails/configuration.rb
+++ b/railties/lib/rails/configuration.rb
@@ -74,7 +74,7 @@ module Rails
end
class Generators #:nodoc:
- attr_accessor :aliases, :options, :templates, :fallbacks, :colorize_logging
+ attr_accessor :aliases, :options, :templates, :fallbacks, :colorize_logging, :api_only
attr_reader :hidden_namespaces
def initialize
@@ -83,6 +83,7 @@ module Rails
@fallbacks = {}
@templates = []
@colorize_logging = true
+ @api_only = false
@hidden_namespaces = []
end
diff --git a/railties/lib/rails/console/app.rb b/railties/lib/rails/console/app.rb
index 2a69c26deb..ac5836a588 100644
--- a/railties/lib/rails/console/app.rb
+++ b/railties/lib/rails/console/app.rb
@@ -18,6 +18,11 @@ module Rails
app = Rails.application
session = ActionDispatch::Integration::Session.new(app)
yield session if block_given?
+
+ # This makes app.url_for and app.foo_path available in the console
+ session.extend(app.routes.url_helpers)
+ session.extend(app.routes.mounted_helpers)
+
session
end
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index 83cee28fa3..e90d41cbec 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -6,7 +6,7 @@ require 'pathname'
module Rails
# <tt>Rails::Engine</tt> allows you to wrap a specific Rails application or subset of
# functionality and share it with other applications or within a larger packaged application.
- # Since Rails 3.0, every <tt>Rails::Application</tt> is just an engine, which allows for simple
+ # Every <tt>Rails::Application</tt> is just an engine, which allows for simple
# feature and application sharing.
#
# Any <tt>Rails::Engine</tt> is also a <tt>Rails::Railtie</tt>, so the same
@@ -15,10 +15,9 @@ module Rails
#
# == Creating an Engine
#
- # In Rails versions prior to 3.0, your gems automatically behaved as engines, however,
- # this coupled Rails to Rubygems. Since Rails 3.0, if you want a gem to automatically
- # behave as an engine, you have to specify an +Engine+ for it somewhere inside
- # your plugin's +lib+ folder (similar to how we specify a +Railtie+):
+ # If you want a gem to behave as an engine, you have to specify an +Engine+
+ # for it somewhere inside your plugin's +lib+ folder (similar to how we
+ # specify a +Railtie+):
#
# # lib/my_engine.rb
# module MyEngine
@@ -69,10 +68,9 @@ module Rails
#
# == Paths
#
- # Since Rails 3.0, applications and engines have more flexible path configuration (as
- # opposed to the previous hardcoded path configuration). This means that you are not
- # required to place your controllers at <tt>app/controllers</tt>, but in any place
- # which you find convenient.
+ # Applications and engines have flexible path configuration, meaning that you
+ # are not required to place your controllers at <tt>app/controllers</tt>, but
+ # in any place which you find convenient.
#
# For example, let's suppose you want to place your controllers in <tt>lib/controllers</tt>.
# You can set that as an option:
@@ -206,42 +204,51 @@ module Rails
# With such an engine, everything that is inside the +MyEngine+ module will be isolated from
# the application.
#
- # Consider such controller:
+ # Consider this controller:
#
# module MyEngine
# class FooController < ActionController::Base
# end
# end
#
- # If an engine is marked as isolated, +FooController+ has access only to helpers from +Engine+ and
- # <tt>url_helpers</tt> from <tt>MyEngine::Engine.routes</tt>.
+ # If the +MyEngine+ engine is marked as isolated, +FooController+ only has
+ # access to helpers from +MyEngine+, and <tt>url_helpers</tt> from
+ # <tt>MyEngine::Engine.routes</tt>.
#
- # The next thing that changes in isolated engines is the behavior of routes. Normally, when you namespace
- # your controllers, you also need to namespace all your routes. With an isolated engine,
- # the namespace is applied by default, so you can ignore it in routes:
+ # The next thing that changes in isolated engines is the behavior of routes.
+ # Normally, when you namespace your controllers, you also need to namespace
+ # the related routes. With an isolated engine, the engine's namespace is
+ # automatically applied, so you don't need to specify it explicity in your
+ # routes:
#
# MyEngine::Engine.routes.draw do
# resources :articles
# end
#
- # The routes above will automatically point to <tt>MyEngine::ArticlesController</tt>. Furthermore, you don't
- # need to use longer url helpers like <tt>my_engine_articles_path</tt>. Instead, you should simply use
- # <tt>articles_path</tt> as you would do with your application.
+ # If +MyEngine+ is isolated, The routes above will point to
+ # <tt>MyEngine::ArticlesController</tt>. You also don't need to use longer
+ # url helpers like +my_engine_articles_path+. Instead, you should simply use
+ # +articles_path+, like you would do with your main application.
#
- # To make that behavior consistent with other parts of the framework, an isolated engine also has influence on
- # <tt>ActiveModel::Naming</tt>. When you use a namespaced model, like <tt>MyEngine::Article</tt>, it will normally
- # use the prefix "my_engine". In an isolated engine, the prefix will be omitted in url helpers and
- # form fields for convenience.
+ # To make this behavior consistent with other parts of the framework,
+ # isolated engines also have an effect on <tt>ActiveModel::Naming</tt>. In a
+ # normal Rails app, when you use a namespaced model such as
+ # <tt>Namespace::Article</tt>, <tt>ActiveModel::Naming</tt> will generate
+ # names with the prefix "namespace". In an isolated engine, the prefix will
+ # be omitted in url helpers and form fields, for convenience.
#
- # polymorphic_url(MyEngine::Article.new) # => "articles_path"
+ # polymorphic_url(MyEngine::Article.new)
+ # # => "articles_path" # not "my_engine_articles_path"
#
# form_for(MyEngine::Article.new) do
# text_field :title # => <input type="text" name="article[title]" id="article_title" />
# end
#
- # Additionally, an isolated engine will set its name according to namespace, so
- # MyEngine::Engine.engine_name will be "my_engine". It will also set MyEngine.table_name_prefix
- # to "my_engine_", changing the MyEngine::Article model to use the my_engine_articles table.
+ # Additionally, an isolated engine will set its own name according to its
+ # namespace, so <tt>MyEngine::Engine.engine_name</tt> will return
+ # "my_engine". It will also set +MyEngine.table_name_prefix+ to "my_engine_",
+ # meaning for example that <tt>MyEngine::Article</tt> will use the
+ # +my_engine_articles+ database table by default.
#
# == Using Engine's routes outside Engine
#
@@ -405,7 +412,7 @@ module Rails
end
end
- # Finds engine with given path
+ # Finds engine with given path.
def find(path)
expanded_path = File.expand_path path
Rails::Engine.subclasses.each do |klass|
@@ -512,17 +519,15 @@ module Rails
# Define the Rack API for this engine.
def call(env)
env.merge!(env_config)
- if env['SCRIPT_NAME']
- env[routes.env_key] = env['SCRIPT_NAME'].dup
- end
+ req = ActionDispatch::Request.new env
+ req.routes = routes
+ req.engine_script_name = req.script_name
app.call(env)
end
# Defines additional Rack env configuration that is added on each call.
def env_config
- @env_config ||= {
- 'action_dispatch.routes' => routes
- }
+ @env_config ||= {}
end
# Defines the routes for this engine. If a block is given to
@@ -559,7 +564,7 @@ module Rails
# and the load_once paths.
#
# This needs to be an initializer, since it needs to run once
- # per engine and get the engine as a block parameter
+ # per engine and get the engine as a block parameter.
initializer :set_autoload_paths, before: :bootstrap_hook do
ActiveSupport::Dependencies.autoload_paths.unshift(*_all_autoload_paths)
ActiveSupport::Dependencies.autoload_once_paths.unshift(*_all_autoload_once_paths)
@@ -582,7 +587,7 @@ module Rails
# I18n load paths are a special case since the ones added
# later have higher priority.
initializer :add_locales do
- config.i18n.railties_load_path.concat(paths["config/locales"].existent)
+ config.i18n.railties_load_path << paths["config/locales"]
end
initializer :add_view_paths do
diff --git a/railties/lib/rails/engine/commands.rb b/railties/lib/rails/engine/commands.rb
index f39f926109..a6d87b78e4 100644
--- a/railties/lib/rails/engine/commands.rb
+++ b/railties/lib/rails/engine/commands.rb
@@ -2,7 +2,8 @@ ARGV << '--help' if ARGV.empty?
aliases = {
"g" => "generate",
- "d" => "destroy"
+ "d" => "destroy",
+ "t" => "test"
}
command = ARGV.shift
@@ -12,7 +13,7 @@ require ENGINE_PATH
engine = ::Rails::Engine.find(ENGINE_ROOT)
case command
-when 'generate', 'destroy'
+when 'generate', 'destroy', 'test'
require 'rails/generators'
Rails::Generators.namespace = engine.railtie_namespace
engine.load_generators
@@ -30,6 +31,7 @@ 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.
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 341291f08b..b430cf1909 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -33,6 +33,7 @@ module Rails
scaffold_controller: '-c',
stylesheets: '-y',
stylesheet_engine: '-se',
+ scaffold_stylesheet: '-ss',
template_engine: '-e',
test_framework: '-t'
},
@@ -44,6 +45,7 @@ module Rails
DEFAULT_OPTIONS = {
rails: {
+ api: false,
assets: true,
force_plural: false,
helper: true,
@@ -56,12 +58,14 @@ module Rails
scaffold_controller: :scaffold_controller,
stylesheets: true,
stylesheet_engine: :css,
+ scaffold_stylesheet: true,
test_framework: false,
template_engine: :erb
}
}
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
@@ -99,6 +103,21 @@ module Rails
@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 views, 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
+ )
+ end
+
# Remove the color from output.
def self.no_color!
Thor::Base.shell = Thor::Shell::Basic
@@ -267,10 +286,13 @@ module Rails
d = (0..m).to_a
x = nil
- str1.each_char.each_with_index do |char1,i|
+ # 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.each_char.each_with_index do |char2,j|
+ str2_codepoint_enumerable.with_index do |char2, j|
cost = (char1 == char2) ? 0 : 1
x = [
d[j+1] + 1, # insertion
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index 70a20801a0..b4356f71e0 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -63,12 +63,26 @@ module Rails
# Add the given source to +Gemfile+
#
+ # If block is given, gem entries in block are wrapped into the source group.
+ #
# add_source "http://gems.github.com/"
- def add_source(source, options={})
+ #
+ # add_source "http://gems.github.com/" do
+ # gem "rspec-rails"
+ # end
+ def add_source(source, options={}, &block)
log :source, source
in_root do
- prepend_file "Gemfile", "source #{quote(source)}\n", verbose: false
+ if block
+ append_file "Gemfile", "source #{quote(source)} do", force: true
+ @in_group = true
+ instance_eval(&block)
+ @in_group = false
+ append_file "Gemfile", "\nend\n", force: true
+ else
+ prepend_file "Gemfile", "source #{quote(source)}\n", verbose: false
+ end
end
end
@@ -78,11 +92,11 @@ module Rails
# file in <tt>config/environments</tt>.
#
# environment do
- # "config.autoload_paths += %W(#{config.root}/extras)"
+ # "config.action_controller.asset_host = 'cdn.provider.com'"
# end
#
# environment(nil, env: "development") do
- # "config.autoload_paths += %W(#{config.root}/extras)"
+ # "config.action_controller.asset_host = 'localhost:3000'"
# end
def environment(data=nil, options={})
sentinel = /class [a-z_:]+ < Rails::Application/i
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 10deeb0ba2..249fe96772 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -174,6 +174,10 @@ module Rails
options[value] ? '# ' : ''
end
+ def keeps?
+ !options[:skip_keeps]
+ end
+
def sqlite3?
!options[:skip_active_record] && options[:database] == 'sqlite3'
end
@@ -204,11 +208,13 @@ module Rails
if options.dev?
[
GemfileEntry.path('rails', Rails::Generators::RAILS_DEV_PATH),
+ GemfileEntry.github('sprockets-rails', 'rails/sprockets-rails'),
GemfileEntry.github('arel', 'rails/arel')
]
elsif options.edge?
[
GemfileEntry.github('rails', 'rails/rails'),
+ GemfileEntry.github('sprockets-rails', 'rails/sprockets-rails'),
GemfileEntry.github('arel', 'rails/arel')
]
else
@@ -260,6 +266,8 @@ module Rails
end
def jbuilder_gemfile_entry
+ return [] if options[:api]
+
comment = 'Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder'
GemfileEntry.version('jbuilder', '~> 2.0', comment)
end
@@ -336,7 +344,7 @@ module Rails
end
def spring_install?
- !options[:skip_spring] && Process.respond_to?(:fork) && !RUBY_PLATFORM.include?("cygwin")
+ !options[:skip_spring] && !options.dev? && Process.respond_to?(:fork) && !RUBY_PLATFORM.include?("cygwin")
end
def run_bundle
@@ -355,7 +363,7 @@ module Rails
end
def keep_file(destination)
- create_file("#{destination}/.keep") unless options[:skip_keeps]
+ create_file("#{destination}/.keep") if keeps?
end
end
end
diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb
index 813b8b629e..6fa413f8b0 100644
--- a/railties/lib/rails/generators/base.rb
+++ b/railties/lib/rails/generators/base.rb
@@ -103,12 +103,12 @@ module Rails
# hook_for :test_framework, as: :controller
# end
#
- # And now it will lookup at:
+ # And now it will look up at:
#
# "test_unit:controller", "test_unit"
#
- # Similarly, if you want it to also lookup in the rails namespace, you just
- # need to provide the :in value:
+ # Similarly, if you want it to also look up in the rails namespace, you
+ # just need to provide the :in value:
#
# class AwesomeGenerator < Rails::Generators::Base
# hook_for :test_framework, in: :rails, as: :controller
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
index d9713b0238..d99b27cb2d 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
@@ -17,6 +17,7 @@
<%%= f.label :password %><br>
<%%= f.password_field :password %>
</div>
+
<div class="field">
<%%= f.label :password_confirmation %><br>
<%%= f.password_field :password_confirmation %>
@@ -25,6 +26,7 @@
<%%= f.<%= attribute.field_type %> :<%= attribute.column_name %> %>
<% end -%>
</div>
+
<% end -%>
<div class="actions">
<%%= f.submit %>
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
index c3b8ef1181..5f4904fee1 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
@@ -28,4 +28,4 @@
<br>
-<%%= link_to 'New <%= human_name %>', new_<%= singular_table_name %>_path %>
+<%%= link_to 'New <%= singular_table_name.titleize %>', new_<%= singular_table_name %>_path %>
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index 01a8e2e9b4..243694f38e 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -18,8 +18,8 @@ module Rails
parse_attributes! if respond_to?(:attributes)
end
- # Defines the template that would be used for the migration file.
- # The arguments include the source template file, the migration filename etc.
+ # Overrides <tt>Thor::Actions#template</tt> so it can tell if
+ # a template is currently being created.
no_tasks do
def template(source, *args, &block)
inside_template do
@@ -183,6 +183,10 @@ module Rails
!defined?(ActiveRecord::Base) || ActiveRecord::Base.pluralize_table_names
end
+ def mountable_engine?
+ defined?(ENGINE_ROOT) && namespaced?
+ end
+
# Add a class collisions name to be checked on class initialization. You
# can supply a hash with a :prefix or :suffix to be tested.
#
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 899b33e529..b813b083f4 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -141,6 +141,7 @@ module Rails
end
def tmp
+ empty_directory_with_keep_file "tmp"
empty_directory "tmp/cache"
empty_directory "tmp/cache/assets"
end
@@ -174,6 +175,9 @@ module Rails
class_option :version, type: :boolean, aliases: "-v", group: :rails,
desc: "Show Rails version number and quit"
+ class_option :api, type: :boolean,
+ desc: "Preconfigure smaller stack for API only apps"
+
def initialize(*args)
super
@@ -184,6 +188,10 @@ module Rails
if !options[:skip_active_record] && !DATABASES.include?(options[:database])
raise Error, "Invalid value for --database option. Supported for preconfiguration are: #{DATABASES.join(", ")}."
end
+
+ # Force sprockets 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]
end
public_task :set_default_accessors!
@@ -251,6 +259,28 @@ module Rails
build(:vendor)
end
+ def delete_app_assets_if_api_option
+ if options[:api]
+ remove_dir 'app/assets'
+ remove_dir 'lib/assets'
+ remove_dir 'tmp/cache/assets'
+ remove_dir 'vendor/assets'
+ end
+ end
+
+ def delete_app_helpers_if_api_option
+ if options[:api]
+ remove_dir 'app/helpers'
+ remove_dir 'test/helpers'
+ end
+ end
+
+ def delete_app_views_if_api_option
+ if options[:api]
+ remove_dir 'app/views'
+ end
+ end
+
def delete_js_folder_skipping_javascript
if options[:skip_javascript]
remove_dir 'app/assets/javascripts'
@@ -269,6 +299,13 @@ module Rails
end
end
+ def delete_non_api_initializers_if_api_option
+ if options[:api]
+ remove_file 'config/initializers/session_store.rb'
+ remove_file 'config/initializers/cookies_serializer.rb'
+ end
+ end
+
def finish_template
build(:leftovers)
end
@@ -319,7 +356,9 @@ module Rails
if app_const =~ /^\d/
raise Error, "Invalid application name #{app_name}. Please give a name which does not start with numbers."
elsif RESERVED_NAMES.include?(app_name)
- raise Error, "Invalid application name #{app_name}. Please give a name which does not match one of the reserved rails words."
+ raise Error, "Invalid application name #{app_name}. Please give a " \
+ "name which does not match one of the reserved rails " \
+ "words: #{RESERVED_NAMES.join(", ")}"
elsif Object.const_defined?(app_const_base)
raise Error, "Invalid application name #{app_name}, constant #{app_const_base} is already in use. Please choose another application name."
end
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index c11bb58bfa..6a1c2faaab 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -21,23 +21,35 @@ source 'https://rubygems.org'
# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development
-group :development, :test do
+<%- if options.api? -%>
+# Use ActiveModelSerializers to serialize JSON responses
+gem 'active_model_serializers', '~> 0.10.0.rc2'
+
+# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
+# gem 'rack-cors'
+
+<%- end -%>
<% if RUBY_ENGINE == 'ruby' -%>
+group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug'
+end
+group :development do
+<%- unless options.api? -%>
# Access an IRB console on exception pages or by using <%%= console %> in views
<%- if options.dev? || options.edge? -%>
- gem 'web-console', github: "rails/web-console"
+ gem 'web-console', github: 'rails/web-console'
<%- else -%>
gem 'web-console', '~> 2.0'
<%- end -%>
-<%- if spring_install? %>
+<%- end -%>
+<% if spring_install? -%>
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring'
<% end -%>
-<% end -%>
end
+<% end -%>
<% if RUBY_PLATFORM.match(/bccwin|cygwin|emx|mingw|mswin|wince|java/) -%>
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
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 cb86978d4c..c88426ec06 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
@@ -5,7 +5,7 @@
// 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.
+// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
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 0cdd2788d0..0ebd7fe829 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
@@ -7,7 +7,8 @@
*
* 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
- * files in this directory. It is generally better to create a new file per style scope.
+ * files in this directory. Styles in this file should be added after the last require_* statement.
+ * It is generally better to create a new file per style scope.
*
*= require_tree .
*= require_self
diff --git a/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt b/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt
index d83690e1b9..f726fd6305 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt
@@ -1,5 +1,7 @@
-class ApplicationController < ActionController::Base
+class ApplicationController < ActionController::<%= options[:api] ? "API" : "Base" %>
+<%- unless options[:api] -%>
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
+<%- end -%>
end
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/setup b/railties/lib/rails/generators/rails/app/templates/bin/setup
index eee810be30..0c8b179827 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/setup
+++ b/railties/lib/rails/generators/rails/app/templates/bin/setup
@@ -5,13 +5,17 @@ include FileUtils
# path to your application root.
APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
+def system!(*args)
+ system(*args) || abort("\n== Command #{args} failed ==")
+end
+
chdir APP_ROOT do
# This script is a starting point to setup your application.
# Add necessary setup steps to this file.
puts '== Installing dependencies =='
- system 'gem install bundler --conservative'
- system('bundle check') or system('bundle install')
+ system! 'gem install bundler --conservative'
+ system('bundle check') or system!('bundle install')
# puts "\n== Copying sample files =="
# unless File.exist?('config/database.yml')
@@ -19,12 +23,11 @@ chdir APP_ROOT do
# end
puts "\n== Preparing database =="
- system 'ruby bin/rake db:setup'
+ system! 'ruby bin/rake db:setup'
puts "\n== Removing old logs and tempfiles =="
- rm_f Dir.glob('log/*')
- rm_rf 'tmp/cache'
+ system! 'ruby bin/rake log:clear tmp:clear'
puts "\n== Restarting application server =="
- touch 'tmp/restart.txt'
+ system! 'ruby bin/rake restart'
end
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/update b/railties/lib/rails/generators/rails/app/templates/bin/update
new file mode 100644
index 0000000000..9830e6b29a
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/bin/update
@@ -0,0 +1,28 @@
+require 'pathname'
+require 'fileutils'
+include FileUtils
+
+# path to your application root.
+APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
+
+def system!(*args)
+ system(*args) || abort("\n== Command #{args} failed ==")
+end
+
+chdir APP_ROOT do
+ # This script is a way to update your development environment automatically.
+ # Add necessary update steps to this file.
+
+ puts '== Installing dependencies =='
+ system! 'gem install bundler --conservative'
+ system 'bundle check' or system! 'bundle install'
+
+ puts "\n== Updating database =="
+ system! 'bin/rake db:migrate'
+
+ puts "\n== Removing old logs and tempfiles =="
+ system! 'bin/rake log:clear tmp:clear'
+
+ puts "\n== Restarting application server =="
+ system! 'bin/rake restart'
+end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb
index a2661bfb51..6b7d7abd0b 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -32,5 +32,12 @@ module <%= app_const_base %>
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de
+<%- if options[:api] -%>
+
+ # Only loads a smaller set of middleware suitable for API only apps.
+ # Middleware like session, flash, cookies can be added back manually.
+ # Skip views, helpers and assets when generating a new resource.
+ config.api_only = true
+<%- end -%>
end
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
index ecb5d4170f..e29f0bacaa 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
@@ -9,9 +9,19 @@ Rails.application.configure do
# Do not eager load code on boot.
config.eager_load = false
- # Show full error reports and disable caching.
+ # Show full error reports.
config.consider_all_requests_local = true
- config.action_controller.perform_caching = false
+
+ # Enable/disable caching. By default caching is disabled.
+ if Rails.root.join('tmp/caching-dev.txt').exist?
+ config.action_controller.perform_caching = true
+ config.static_cache_control = "public, max-age=172800"
+ config.cache_store = :memory_store
+ else
+ config.action_controller.perform_caching = false
+ config.cache_store = :null_store
+ end
+
<%- unless options.skip_action_mailer? -%>
# Don't care if the mailer can't send.
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb
index e63022da91..a70a1b9cde 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb
@@ -1,4 +1,4 @@
# Be sure to restart your server when you modify this file.
# Do not halt callback chains when a callback returns false.
-Rails.application.config.active_support.halt_callback_chains_on_return_false = false
+ActiveSupport.halt_callback_chains_on_return_false = false
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb
new file mode 100644
index 0000000000..45c44d24f8
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb
@@ -0,0 +1,14 @@
+# Avoid CORS issues when API is called from the frontend app
+# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests
+
+# Read more: https://github.com/cyu/rack-cors
+
+# Rails.application.config.middleware.insert_before 0, "Rack::Cors" do
+# allow do
+# origins 'example.com'
+#
+# resource '*',
+# headers: :any,
+# methods: [:get, :post, :put, :patch, :delete, :options, :head]
+# end
+# end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
index 94f612c3dd..cadc85cfac 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
@@ -5,7 +5,7 @@
# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
ActiveSupport.on_load(:action_controller) do
- wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
+ wrap_parameters format: [:json]
end
<%- unless options.skip_active_record? -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb
index 3f66539d54..787824f888 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb
@@ -1,56 +1,3 @@
Rails.application.routes.draw do
- # The priority is based upon order of creation: first created -> highest priority.
- # See how all your routes lay out with "rake routes".
-
- # You can have the root of your site routed with "root"
- # root 'welcome#index'
-
- # Example of regular route:
- # get 'products/:id' => 'catalog#view'
-
- # Example of named route that can be invoked with purchase_url(id: product.id)
- # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase
-
- # Example resource route (maps HTTP verbs to controller actions automatically):
- # resources :products
-
- # Example resource route with options:
- # resources :products do
- # member do
- # get 'short'
- # post 'toggle'
- # end
- #
- # collection do
- # get 'sold'
- # end
- # end
-
- # Example resource route with sub-resources:
- # resources :products do
- # resources :comments, :sales
- # resource :seller
- # end
-
- # Example resource route with more complex sub-resources:
- # resources :products do
- # resources :comments
- # resources :sales do
- # get 'recent', on: :collection
- # end
- # end
-
- # Example resource route with concerns:
- # concern :toggleable do
- # post 'toggle'
- # end
- # resources :posts, concerns: :toggleable
- # resources :photos, concerns: :toggleable
-
- # Example resource route within a namespace:
- # namespace :admin do
- # # Directs /admin/products/* to Admin::ProductsController
- # # (app/controllers/admin/products_controller.rb)
- # resources :products
- # end
+ # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
diff --git a/railties/lib/rails/generators/rails/app/templates/gitignore b/railties/lib/rails/generators/rails/app/templates/gitignore
index 7c6f2098b8..1b8cf8a9fa 100644
--- a/railties/lib/rails/generators/rails/app/templates/gitignore
+++ b/railties/lib/rails/generators/rails/app/templates/gitignore
@@ -15,5 +15,8 @@
<% end -%>
# Ignore all logfiles and tempfiles.
/log/*
+/tmp/*
+<% if keeps? -%>
!/log/.keep
-/tmp
+!/tmp/.keep
+<% end -%>
diff --git a/railties/lib/rails/generators/rails/controller/controller_generator.rb b/railties/lib/rails/generators/rails/controller/controller_generator.rb
index df615c88b5..0a4c509a31 100644
--- a/railties/lib/rails/generators/rails/controller/controller_generator.rb
+++ b/railties/lib/rails/generators/rails/controller/controller_generator.rb
@@ -13,12 +13,14 @@ module Rails
def add_routes
unless options[:skip_routes]
actions.reverse_each do |action|
- route generate_routing_code(action)
+ # route prepends two spaces onto the front of the string that is passed, this corrects that.
+ route generate_routing_code(action)[2..-1]
end
end
end
- hook_for :template_engine, :test_framework, :helper, :assets
+ hook_for :template_engine, :test_framework
+ hook_for :helper, :assets, hide: true
private
@@ -36,12 +38,12 @@ module Rails
# 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)
+ indent(" namespace :#{ns} do\n", i * 2)
end.join
# Create route
# get 'baz/index'
- route = indent(%{get '#{file_name}/#{action}'\n}, depth * 2)
+ route = indent(%{ get '#{file_name}/#{action}'\n}, depth * 2)
# Create `end` ladder
# end
diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
index 68c3829515..0e1326ce4f 100644
--- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
@@ -8,7 +8,7 @@ module Rails
# generator.
#
# This allows you to override entire operations, like the creation of the
- # Gemfile, README, or JavaScript files, without needing to know exactly
+ # Gemfile, \README, or JavaScript files, without needing to know exactly
# what those operations do so you can create another template action.
class PluginBuilder
def rakefile
@@ -17,15 +17,22 @@ module Rails
def app
if mountable?
- directory 'app'
- empty_directory_with_keep_file "app/assets/images/#{namespaced_name}"
+ if api?
+ directory 'app', exclude_pattern: %r{app/(views|helpers)}
+ else
+ directory 'app'
+ empty_directory_with_keep_file "app/assets/images/#{namespaced_name}"
+ end
elsif full?
empty_directory_with_keep_file 'app/models'
empty_directory_with_keep_file 'app/controllers'
- empty_directory_with_keep_file 'app/views'
- empty_directory_with_keep_file 'app/helpers'
empty_directory_with_keep_file 'app/mailers'
- empty_directory_with_keep_file "app/assets/images/#{namespaced_name}"
+
+ unless api?
+ empty_directory_with_keep_file "app/assets/images/#{namespaced_name}"
+ empty_directory_with_keep_file 'app/helpers'
+ empty_directory_with_keep_file 'app/views'
+ end
end
end
@@ -82,6 +89,7 @@ task default: :test
opts = (options || {}).slice(*PASSTHROUGH_OPTIONS)
opts[:force] = force
opts[:skip_bundle] = true
+ opts[:api] = options.api?
invoke Rails::Generators::AppGenerator,
[ File.expand_path(dummy_path, destination_root) ], opts
@@ -176,6 +184,9 @@ task default: :test
desc: "If creating plugin in application's directory " +
"skip adding entry to Gemfile"
+ class_option :api, type: :boolean, default: false,
+ desc: "Generate a smaller stack for API application plugins"
+
def initialize(*args)
@dummy_path = nil
super
@@ -210,15 +221,15 @@ task default: :test
end
def create_public_stylesheets_files
- build(:stylesheets)
+ build(:stylesheets) unless api?
end
def create_javascript_files
- build(:javascripts)
+ build(:javascripts) unless api?
end
def create_images_directory
- build(:images)
+ build(:images) unless api?
end
def create_bin_files
@@ -305,6 +316,10 @@ task default: :test
options[:skip_test].blank? || options[:dummy_path] != 'test/dummy'
end
+ def api?
+ options[:api]
+ end
+
def self.banner
"rails plugin new #{self.arguments.map(&:usage).join(' ')} [options]"
end
@@ -364,7 +379,9 @@ task default: :test
elsif camelized =~ /^\d/
raise Error, "Invalid plugin name #{original_name}. Please give a name which does not start with numbers."
elsif RESERVED_NAMES.include?(name)
- raise Error, "Invalid plugin name #{original_name}. Please give a name which does not match one of the reserved rails words."
+ raise Error, "Invalid plugin name #{original_name}. Please give a " \
+ "name which does not match one of the reserved rails " \
+ "words: #{RESERVED_NAMES.join(", ")}"
elsif Object.const_defined?(camelized)
raise Error, "Invalid plugin name #{original_name}, constant #{camelized} is already in use. Please choose another plugin name."
end
diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt
index 7157e48c42..7fe4e5034d 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt
+++ b/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt
@@ -1,5 +1,5 @@
<%= wrap_in_modules <<-rb.strip_heredoc
- class ApplicationController < ActionController::Base
+ class ApplicationController < ActionController::#{api? ? "API" : "Base"}
end
rb
%>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb
index 17afd52177..8938770fc4 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb
@@ -1,6 +1,7 @@
<%= wrap_in_modules <<-rb.strip_heredoc
class Engine < ::Rails::Engine
#{mountable? ? ' isolate_namespace ' + camelized_modules : ' '}
+ #{api? ? " config.generators.api_only = true" : ' '}
end
rb
%>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js b/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js
index 8913b40f69..e54c6461cc 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js
@@ -5,7 +5,7 @@
// 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.
+// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css b/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css
index 0cdd2788d0..0ebd7fe829 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css
@@ -7,7 +7,8 @@
*
* 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
- * files in this directory. It is generally better to create a new file per style scope.
+ * files in this directory. Styles in this file should be added after the last require_* statement.
+ * It is generally better to create a new file per style scope.
*
*= require_tree .
*= require_self
diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/integration/navigation_test.rb b/railties/lib/rails/generators/rails/plugin/templates/test/integration/navigation_test.rb
index 824caecb24..f5d1ec2046 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/test/integration/navigation_test.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/test/integration/navigation_test.rb
@@ -1,10 +1,6 @@
require 'test_helper'
class NavigationTest < ActionDispatch::IntegrationTest
-<% unless options[:skip_active_record] -%>
- fixtures :all
-<% end -%>
-
# test "the truth" do
# assert true
# end
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 0852ffce9a..b1b77629d4 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
@@ -20,6 +20,7 @@ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
# Load fixtures from the engine
if ActiveSupport::TestCase.respond_to?(:fixture_path=)
ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__)
- ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + "files"
+ ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path
+ ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + "/files"
ActiveSupport::TestCase.fixtures :all
end
diff --git a/railties/lib/rails/generators/rails/resource/resource_generator.rb b/railties/lib/rails/generators/rails/resource/resource_generator.rb
index 8014feb75f..3acf21df13 100644
--- a/railties/lib/rails/generators/rails/resource/resource_generator.rb
+++ b/railties/lib/rails/generators/rails/resource/resource_generator.rb
@@ -1,6 +1,5 @@
require 'rails/generators/resource_helpers'
require 'rails/generators/rails/model/model_generator'
-require 'active_support/core_ext/object/blank'
module Rails
module Generators
diff --git a/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb
index c986f95e67..42705107ae 100644
--- a/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb
+++ b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb
@@ -1,7 +1,6 @@
module Rails
module Generators
class ResourceRouteGenerator < NamedBase # :nodoc:
-
# Properly nests namespaces passed into a generator
#
# $ rails generate resource admin/users/products
diff --git a/railties/lib/rails/generators/rails/scaffold/USAGE b/railties/lib/rails/generators/rails/scaffold/USAGE
index 1b2a944103..d2e495758d 100644
--- a/railties/lib/rails/generators/rails/scaffold/USAGE
+++ b/railties/lib/rails/generators/rails/scaffold/USAGE
@@ -36,6 +36,6 @@ Description:
Examples:
`rails generate scaffold post`
- `rails generate scaffold post title body:text published:boolean`
+ `rails generate scaffold post title:string body:text published:boolean`
`rails generate scaffold purchase amount:decimal tracking_id:integer:uniq`
`rails generate scaffold user email:uniq password:digest`
diff --git a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
index e89789e72b..17c32bfdb3 100644
--- a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
@@ -10,10 +10,11 @@ module Rails
class_option :stylesheet_engine, desc: "Engine for Stylesheets"
class_option :assets, type: :boolean
class_option :resource_route, type: :boolean
+ class_option :scaffold_stylesheet, type: :boolean
def handle_skip
@options = @options.merge(stylesheets: false) unless options[:assets]
- @options = @options.merge(stylesheet_engine: false) unless options[:stylesheets]
+ @options = @options.merge(stylesheet_engine: false) unless options[:stylesheets] && options[:scaffold_stylesheet]
end
hook_for :scaffold_controller, required: true
diff --git a/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css b/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css
index 69af1e8307..b7818883d1 100644
--- a/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css
+++ b/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css
@@ -1,8 +1,11 @@
-body { background-color: #fff; color: #333; }
+body {
+ background-color: #fff;
+ color: #333;
+}
body, p, ol, ul, td {
font-family: verdana, arial, helvetica, sans-serif;
- font-size: 13px;
+ font-size: 13px;
line-height: 18px;
margin: 33px;
}
@@ -13,9 +16,18 @@ pre {
font-size: 11px;
}
-a { color: #000; }
-a:visited { color: #666; }
-a:hover { color: #fff; background-color:#000; }
+a {
+ color: #000;
+}
+
+a:visited {
+ color: #666;
+}
+
+a:hover {
+ color: #fff;
+ background-color: #000;
+}
th {
padding-bottom: 5px;
@@ -27,7 +39,8 @@ td {
padding-right: 5px;
}
-div.field, div.actions {
+div.field,
+div.actions {
margin-bottom: 10px;
}
@@ -56,7 +69,7 @@ div.field, div.actions {
padding: 5px 5px 5px 15px;
font-size: 12px;
margin: -7px;
- margin-bottom: 0px;
+ margin-bottom: 0;
background-color: #c00;
color: #fff;
}
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
index c01b82884d..d0b8cad896 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
@@ -10,11 +10,14 @@ module Rails
class_option :helper, type: :boolean
class_option :orm, banner: "NAME", type: :string, required: true,
desc: "ORM to generate the controller for"
+ class_option :api, type: :boolean,
+ desc: "Generates API controller"
argument :attributes, type: :array, default: [], banner: "field:type field:type"
def create_controller_files
- template "controller.rb", File.join('app/controllers', controller_class_path, "#{controller_file_name}_controller.rb")
+ template_file = options.api? ? "api_controller.rb" : "controller.rb"
+ template template_file, File.join('app/controllers', controller_class_path, "#{controller_file_name}_controller.rb")
end
hook_for :template_engine, :test_framework, as: :scaffold
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb
new file mode 100644
index 0000000000..bc3c9b3f6b
--- /dev/null
+++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb
@@ -0,0 +1,61 @@
+<% if namespaced? -%>
+require_dependency "<%= namespaced_file_path %>/application_controller"
+
+<% end -%>
+<% module_namespacing do -%>
+class <%= controller_class_name %>Controller < ApplicationController
+ before_action :set_<%= singular_table_name %>, only: [:show, :update, :destroy]
+
+ # GET <%= route_url %>
+ def index
+ @<%= plural_table_name %> = <%= orm_class.all(class_name) %>
+
+ render json: <%= "@#{plural_table_name}" %>
+ end
+
+ # GET <%= route_url %>/1
+ def show
+ render json: <%= "@#{singular_table_name}" %>
+ end
+
+ # POST <%= route_url %>
+ def create
+ @<%= singular_table_name %> = <%= orm_class.build(class_name, "#{singular_table_name}_params") %>
+
+ if @<%= orm_instance.save %>
+ render json: <%= "@#{singular_table_name}" %>, status: :created, location: <%= "@#{singular_table_name}" %>
+ else
+ render json: <%= "@#{orm_instance.errors}" %>, status: :unprocessable_entity
+ end
+ end
+
+ # PATCH/PUT <%= route_url %>/1
+ def update
+ if @<%= orm_instance.update("#{singular_table_name}_params") %>
+ render json: <%= "@#{singular_table_name}" %>
+ else
+ render json: <%= "@#{orm_instance.errors}" %>, status: :unprocessable_entity
+ end
+ end
+
+ # DELETE <%= route_url %>/1
+ def destroy
+ @<%= orm_instance.destroy %>
+ end
+
+ private
+ # Use callbacks to share common setup or constraints between actions.
+ def set_<%= singular_table_name %>
+ @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
+ end
+
+ # Only allow a trusted parameter "white list" through.
+ def <%= "#{singular_table_name}_params" %>
+ <%- if attributes_names.empty? -%>
+ params[:<%= singular_table_name %>]
+ <%- else -%>
+ params.require(:<%= singular_table_name %>).permit(<%= attributes_names.map { |name| ":#{name}" }.join(', ') %>)
+ <%- end -%>
+ end
+end
+<% end -%>
diff --git a/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb
index 509bd60564..5a8a3ca5e0 100644
--- a/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb
@@ -2,6 +2,12 @@ require 'test_helper'
<% module_namespacing do -%>
class <%= class_name %>ControllerTest < ActionController::TestCase
+<% if mountable_engine? -%>
+ setup do
+ @routes = Engine.routes
+ end
+
+<% end -%>
<% if actions.empty? -%>
# test "the truth" do
# assert true
diff --git a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
index 2e1f55f2a6..0171da7cc7 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
@@ -8,13 +8,26 @@ module TestUnit # :nodoc:
check_class_collision suffix: "ControllerTest"
+ class_option :api, type: :boolean,
+ desc: "Generates API functional tests"
+
argument :attributes, type: :array, default: [], banner: "field:type field:type"
def create_test_files
- template "functional_test.rb",
+ template_file = options.api? ? "api_functional_test.rb" : "functional_test.rb"
+ template template_file,
File.join("test/controllers", controller_class_path, "#{controller_file_name}_controller_test.rb")
end
+ def fixture_name
+ @fixture_name ||=
+ if mountable_engine?
+ "%s_%s" % [namespaced_path, table_name]
+ else
+ table_name
+ end
+ end
+
private
def attributes_hash
diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb
new file mode 100644
index 0000000000..f302cd6c3d
--- /dev/null
+++ b/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb
@@ -0,0 +1,43 @@
+require 'test_helper'
+
+<% module_namespacing do -%>
+class <%= controller_class_name %>ControllerTest < ActionController::TestCase
+ setup do
+ @<%= singular_table_name %> = <%= fixture_name %>(:one)
+<% if mountable_engine? -%>
+ @routes = Engine.routes
+<% end -%>
+ end
+
+ test "should get index" do
+ get :index
+ assert_response :success
+ end
+
+ test "should create <%= singular_table_name %>" do
+ assert_difference('<%= class_name %>.count') do
+ post :create, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> }
+ end
+
+ assert_response 201
+ end
+
+ test "should show <%= singular_table_name %>" do
+ get :show, params: { id: <%= "@#{singular_table_name}" %> }
+ assert_response :success
+ end
+
+ test "should update <%= singular_table_name %>" do
+ patch :update, params: { id: <%= "@#{singular_table_name}" %>, <%= "#{singular_table_name}: { #{attributes_hash} }" %> }
+ assert_response 200
+ end
+
+ test "should destroy <%= singular_table_name %>" do
+ assert_difference('<%= class_name %>.count', -1) do
+ delete :destroy, params: { id: <%= "@#{singular_table_name}" %> }
+ end
+
+ assert_response 204
+ end
+end
+<% end -%>
diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
index 8d825ae7b0..50b98b2631 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
@@ -3,13 +3,15 @@ require 'test_helper'
<% module_namespacing do -%>
class <%= controller_class_name %>ControllerTest < ActionController::TestCase
setup do
- @<%= singular_table_name %> = <%= table_name %>(:one)
+ @<%= singular_table_name %> = <%= fixture_name %>(:one)
+<% if mountable_engine? -%>
+ @routes = Engine.routes
+<% end -%>
end
test "should get index" do
get :index
assert_response :success
- assert_not_nil assigns(:<%= table_name %>)
end
test "should get new" do
@@ -22,7 +24,7 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase
post :create, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> }
end
- assert_redirected_to <%= singular_table_name %>_path(assigns(:<%= singular_table_name %>))
+ assert_redirected_to <%= singular_table_name %>_path(<%= class_name %>.last)
end
test "should show <%= singular_table_name %>" do
@@ -37,7 +39,7 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase
test "should update <%= singular_table_name %>" do
patch :update, params: { id: <%= "@#{singular_table_name}" %>, <%= "#{singular_table_name}: { #{attributes_hash} }" %> }
- assert_redirected_to <%= singular_table_name %>_path(assigns(:<%= singular_table_name %>))
+ assert_redirected_to <%= singular_table_name %>_path(<%= "@#{singular_table_name}" %>)
end
test "should destroy <%= singular_table_name %>" do
diff --git a/railties/lib/rails/generators/testing/assertions.rb b/railties/lib/rails/generators/testing/assertions.rb
index bd069e4bd0..17af6eddfa 100644
--- a/railties/lib/rails/generators/testing/assertions.rb
+++ b/railties/lib/rails/generators/testing/assertions.rb
@@ -23,7 +23,7 @@ module Rails
# end
# end
def assert_file(relative, *contents)
- absolute = File.expand_path(relative, destination_root).shellescape
+ absolute = File.expand_path(relative, destination_root)
assert File.exist?(absolute), "Expected file #{relative.inspect} to exist, but does not"
read = File.read(absolute) if block_given? || !contents.empty?
diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb
index 6e61cc3cb5..778105c5f7 100644
--- a/railties/lib/rails/info_controller.rb
+++ b/railties/lib/rails/info_controller.rb
@@ -18,7 +18,7 @@ class Rails::InfoController < Rails::ApplicationController # :nodoc:
def routes
if path = params[:path]
- path = URI.escape path
+ path = URI.parser.escape path
normalized_path = with_leading_slash path
render json: {
exact: match_route {|it| it.match normalized_path },
diff --git a/railties/lib/rails/mailers_controller.rb b/railties/lib/rails/mailers_controller.rb
index 32740d66da..78a857e0f1 100644
--- a/railties/lib/rails/mailers_controller.rb
+++ b/railties/lib/rails/mailers_controller.rb
@@ -16,26 +16,26 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc:
@page_title = "Mailer Previews for #{@preview.preview_name}"
render action: 'mailer'
else
- email = File.basename(params[:path])
+ @email_action = File.basename(params[:path])
- if @preview.email_exists?(email)
- @email = @preview.call(email)
+ if @preview.email_exists?(@email_action)
+ @email = @preview.call(@email_action)
if params[:part]
part_type = Mime::Type.lookup(params[:part])
if part = find_part(part_type)
response.content_type = part_type
- render text: part.respond_to?(:decoded) ? part.decoded : part
+ render plain: part.respond_to?(:decoded) ? part.decoded : part
else
- raise AbstractController::ActionNotFound, "Email part '#{part_type}' not found in #{@preview.name}##{email}"
+ raise AbstractController::ActionNotFound, "Email part '#{part_type}' not found in #{@preview.name}##{@email_action}"
end
else
@part = find_preferred_part(request.format, Mime::HTML, Mime::TEXT)
render action: 'email', layout: false, formats: %w[html]
end
else
- raise AbstractController::ActionNotFound, "Email '#{email}' not found in #{@preview.name}"
+ raise AbstractController::ActionNotFound, "Email '#{@email_action}' not found in #{@preview.name}"
end
end
end
@@ -54,18 +54,20 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc:
end
def find_preferred_part(*formats)
- if @email.multipart?
- formats.each do |format|
- return find_part(format) if @email.parts.any?{ |p| p.mime_type == format }
+ formats.each do |format|
+ if part = @email.find_first_mime_type(format)
+ return part
end
- else
+ end
+
+ if formats.any?{ |f| @email.mime_type == f }
@email
end
end
def find_part(format)
- if @email.multipart?
- @email.parts.find{ |p| p.mime_type == format }
+ if part = @email.find_first_mime_type(format)
+ part
elsif @email.mime_type == format
@email
end
diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb
index ebcaaaba46..8a7aa6f50a 100644
--- a/railties/lib/rails/paths.rb
+++ b/railties/lib/rails/paths.rb
@@ -123,6 +123,11 @@ module Rails
options[:load_path] ? load_path! : skip_load_path!
end
+ # :nodoc:
+ def absolute_current
+ File.expand_path(@current, @root.path)
+ end
+
def children
keys = @root.keys.find_all { |k|
k.start_with?(@current) && k != @current
@@ -175,6 +180,11 @@ module Rails
@paths
end
+ # :nodoc:
+ def extensions
+ $1.split(',') if @glob =~ /\{([\S]+)\}/
+ end
+
# Expands all paths against the root and return all unique values.
def expanded
raise "You need to set a path root" unless @root.path
diff --git a/railties/lib/rails/rack/logger.rb b/railties/lib/rails/rack/logger.rb
index 9962e6d943..12676b18bc 100644
--- a/railties/lib/rails/rack/logger.rb
+++ b/railties/lib/rails/rack/logger.rb
@@ -7,6 +7,10 @@ require 'rack/body_proxy'
module Rails
module Rack
# Sets log tags, logs the request, calls the app, and flushes the logs.
+ #
+ # Log tags (+taggers+) can be an Array containing: methods that the +request+
+ # object responds to, objects that respond to +to_s+ or Proc objects that accept
+ # an instance of the +request+ object.
class Logger < ActiveSupport::LogSubscriber
def initialize(app, taggers = nil)
@app = app
diff --git a/railties/lib/rails/ruby_version_check.rb b/railties/lib/rails/ruby_version_check.rb
index 9131c51e91..67a19d8a94 100644
--- a/railties/lib/rails/ruby_version_check.rb
+++ b/railties/lib/rails/ruby_version_check.rb
@@ -1,13 +1,13 @@
-if RUBY_VERSION < '2.2.1' && RUBY_ENGINE == 'ruby'
+if RUBY_VERSION < '2.2.2' && RUBY_ENGINE == 'ruby'
desc = defined?(RUBY_DESCRIPTION) ? RUBY_DESCRIPTION : "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})"
abort <<-end_message
- Rails 5 requires to run on Ruby 2.2.1 or newer.
+ Rails 5 requires Ruby 2.2.2 or newer.
You're running
#{desc}
- Please upgrade to Ruby 2.2.1 or newer to continue.
+ Please upgrade to Ruby 2.2.2 or newer to continue.
end_message
end
diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb
index 2c3d278eca..d3e33584d7 100644
--- a/railties/lib/rails/tasks.rb
+++ b/railties/lib/rails/tasks.rb
@@ -3,6 +3,7 @@ require 'rake'
# Load Rails Rakefile extensions
%w(
annotations
+ dev
framework
initializers
log
@@ -10,8 +11,9 @@ require 'rake'
misc
restart
routes
- statistics
tmp
-).each do |task|
+).tap { |arr|
+ arr << 'statistics' if Rake.application.current_scope.empty?
+}.each do |task|
load "rails/tasks/#{task}.rake"
end
diff --git a/railties/lib/rails/tasks/dev.rake b/railties/lib/rails/tasks/dev.rake
new file mode 100644
index 0000000000..e949172d3f
--- /dev/null
+++ b/railties/lib/rails/tasks/dev.rake
@@ -0,0 +1,15 @@
+namespace :dev do
+ task :cache do
+ desc 'Toggle development mode caching on/off'
+
+ if File.exist? 'tmp/caching-dev.txt'
+ File.delete 'tmp/caching-dev.txt'
+ puts 'Development mode is no longer being cached.'
+ else
+ FileUtils.touch 'tmp/caching-dev.txt'
+ puts 'Development mode is now being cached.'
+ end
+
+ FileUtils.touch 'tmp/restart.txt'
+ end
+end
diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake
index a1c805f8aa..904b9d9ad6 100644
--- a/railties/lib/rails/tasks/framework.rake
+++ b/railties/lib/rails/tasks/framework.rake
@@ -32,35 +32,37 @@ namespace :rails do
FileUtils.cp_r src_name, dst_name
end
end
- end
+ end
end
namespace :update do
- def invoke_from_app_generator(method)
- app_generator.send(method)
- end
+ class RailsUpdate
+ def self.invoke_from_app_generator(method)
+ app_generator.send(method)
+ end
- def app_generator
- @app_generator ||= begin
- require 'rails/generators'
- require 'rails/generators/rails/app/app_generator'
- gen = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true },
- destination_root: Rails.root
- File.exist?(Rails.root.join("config", "application.rb")) ?
- gen.send(:app_const) : gen.send(:valid_const?)
- gen
+ def self.app_generator
+ @app_generator ||= begin
+ require 'rails/generators'
+ require 'rails/generators/rails/app/app_generator'
+ gen = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true },
+ destination_root: Rails.root
+ File.exist?(Rails.root.join("config", "application.rb")) ?
+ gen.send(:app_const) : gen.send(:valid_const?)
+ gen
+ end
end
end
# desc "Update config/boot.rb from your current rails install"
task :configs do
- invoke_from_app_generator :create_boot_file
- invoke_from_app_generator :update_config_files
+ RailsUpdate.invoke_from_app_generator :create_boot_file
+ RailsUpdate.invoke_from_app_generator :update_config_files
end
# desc "Adds new executables to the application bin/ directory"
task :bin do
- invoke_from_app_generator :create_bin_files
+ RailsUpdate.invoke_from_app_generator :create_bin_files
end
end
end
diff --git a/railties/lib/rails/tasks/restart.rake b/railties/lib/rails/tasks/restart.rake
index 1e8940b675..f36c86d81b 100644
--- a/railties/lib/rails/tasks/restart.rake
+++ b/railties/lib/rails/tasks/restart.rake
@@ -1,4 +1,5 @@
desc "Restart app by touching tmp/restart.txt"
task :restart do
+ FileUtils.mkdir_p('tmp')
FileUtils.touch('tmp/restart.txt')
end
diff --git a/railties/lib/rails/templates/rails/mailers/email.html.erb b/railties/lib/rails/templates/rails/mailers/email.html.erb
index 0b08a01896..bb6e49966d 100644
--- a/railties/lib/rails/templates/rails/mailers/email.html.erb
+++ b/railties/lib/rails/templates/rails/mailers/email.html.erb
@@ -39,6 +39,10 @@
padding: 1px;
}
+ dd:empty:before {
+ content: "\00a0"; // &nbsp;
+ }
+
iframe {
border: 0;
width: 100%;
@@ -84,7 +88,7 @@
<% 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 }.inspect %>
+ <%= @email.attachments.map { |a| a.respond_to?(:original_filename) ? a.original_filename : a.filename }.join(', ') %>
</dd>
<% end %>
@@ -99,7 +103,14 @@
</dl>
</header>
-<iframe seamless name="messageBody" src="?part=<%= Rack::Utils.escape(@part.mime_type) %>"></iframe>
+<% if @part && @part.mime_type %>
+ <iframe seamless name="messageBody" src="?part=<%= Rack::Utils.escape(@part.mime_type) %>"></iframe>
+<% else %>
+ <p>
+ You are trying to preview an email that does not have any content.
+ This is probably because the <em>mail</em> method has not been called in <em><%= @preview.preview_name %>#<%= @email_action %></em>.
+ </p>
+<% end %>
</body>
</html>
diff --git a/railties/lib/rails/templates/rails/mailers/index.html.erb b/railties/lib/rails/templates/rails/mailers/index.html.erb
index c4c9757d57..000930c039 100644
--- a/railties/lib/rails/templates/rails/mailers/index.html.erb
+++ b/railties/lib/rails/templates/rails/mailers/index.html.erb
@@ -1,8 +1,8 @@
<% @previews.each do |preview| %>
-<h3><%= link_to preview.preview_name.titleize, "/rails/mailers/#{preview.preview_name}" %></h3>
+<h3><%= link_to preview.preview_name.titleize, url_for(controller: "rails/mailers", action: "preview", path: preview.preview_name) %></h3>
<ul>
<% preview.emails.each do |email| %>
-<li><%= link_to email, "/rails/mailers/#{preview.preview_name}/#{email}" %></li>
+<li><%= link_to email, url_for(controller: "rails/mailers", action: "preview", path: "#{preview.preview_name}/#{email}") %></li>
<% end %>
</ul>
<% end %>
diff --git a/railties/lib/rails/templates/rails/mailers/mailer.html.erb b/railties/lib/rails/templates/rails/mailers/mailer.html.erb
index 607c8d1677..c12ead0f90 100644
--- a/railties/lib/rails/templates/rails/mailers/mailer.html.erb
+++ b/railties/lib/rails/templates/rails/mailers/mailer.html.erb
@@ -1,6 +1,6 @@
<h3><%= @preview.preview_name.titleize %></h3>
<ul>
<% @preview.emails.each do |email| %>
-<li><%= link_to email, "/rails/mailers/#{@preview.preview_name}/#{email}" %></li>
+<li><%= link_to email, url_for(controller: "rails/mailers", action: "preview", path: "#{@preview.preview_name}/#{email}") %></li>
<% end %>
</ul>
diff --git a/railties/lib/rails/templates/rails/welcome/index.html.erb b/railties/lib/rails/templates/rails/welcome/index.html.erb
index 6726c23fc9..acf04af416 100644
--- a/railties/lib/rails/templates/rails/welcome/index.html.erb
+++ b/railties/lib/rails/templates/rails/welcome/index.html.erb
@@ -18,7 +18,9 @@
color: #000;
}
- a {color: #03c}
+ a {
+ color: #03c;
+ }
a:hover {
background-color: #03c;
@@ -64,7 +66,10 @@
height: 64px;
}
- #header h1, #header h2 {margin: 0}
+ #header h1,
+ #header h2 {
+ margin: 0;
+ }
#header h2 {
color: #888;
@@ -104,7 +109,9 @@
color: #555;
}
- #about-content td.value {color: #000}
+ #about-content td.value {
+ color: #000;
+ }
#about-content ul {
padding: 0;
diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb
index 5cf44e6331..5cc1b5b219 100644
--- a/railties/lib/rails/test_help.rb
+++ b/railties/lib/rails/test_help.rb
@@ -3,13 +3,14 @@
abort("Abort testing: Your Rails environment is running in production mode!") if Rails.env.production?
require "rails/test_unit/minitest_plugin"
-require 'active_support/testing/autorun'
require 'active_support/test_case'
require 'action_controller'
require 'action_controller/test_case'
require 'action_dispatch/testing/integration'
require 'rails/generators/test_case'
+require 'active_support/testing/autorun'
+
if defined?(ActiveRecord::Base)
ActiveRecord::Migration.maintain_test_schema!
@@ -27,13 +28,15 @@ if defined?(ActiveRecord::Base)
end
class ActionController::TestCase
- setup do
+ def before_setup # :nodoc:
@routes = Rails.application.routes
+ super
end
end
class ActionDispatch::IntegrationTest
- setup do
+ def before_setup # :nodoc:
@routes = Rails.application.routes
+ super
end
end
diff --git a/railties/lib/rails/test_unit/minitest_plugin.rb b/railties/lib/rails/test_unit/minitest_plugin.rb
index 70ce9d3360..5552779ef1 100644
--- a/railties/lib/rails/test_unit/minitest_plugin.rb
+++ b/railties/lib/rails/test_unit/minitest_plugin.rb
@@ -1,14 +1,52 @@
-require "minitest"
+require "active_support/core_ext/module/attribute_accessors"
require "rails/test_unit/reporter"
+require "rails/test_unit/test_requirer"
-def Minitest.plugin_rails_init(options)
- self.reporter << Rails::TestUnitReporter.new(options[:io], options)
- if $rails_test_runner && (method = $rails_test_runner.find_method)
- options[:filter] = method
+module Minitest
+ def self.plugin_rails_options(opts, options)
+ opts.separator ""
+ opts.separator "Usage: bin/rails test [options] [files or directories]"
+ opts.separator "You can run a single test by appending a line number to a filename:"
+ opts.separator ""
+ opts.separator " bin/rails test test/models/user_test.rb:27"
+ opts.separator ""
+ opts.separator "You can run multiple files and directories at the same time:"
+ opts.separator ""
+ opts.separator " bin/rails test test/controllers test/integration/login_test.rb"
+ opts.separator ""
+
+ opts.separator "Rails options:"
+ opts.on("-e", "--environment [ENV]",
+ "Run tests in the ENV environment") do |env|
+ options[:environment] = env.strip
+ end
+
+ opts.on("-b", "--backtrace",
+ "Show the complete backtrace") do
+ options[:full_backtrace] = true
+ end
+
+ options[:patterns] = opts.order!
end
- if !($rails_test_runner && $rails_test_runner.show_backtrace?)
- Minitest.backtrace_filter = Rails.backtrace_cleaner
+ 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
+
+ unless options[:full_backtrace] || ENV["BACKTRACE"]
+ # Plugin can run without Rails loaded, check before filtering.
+ Minitest.backtrace_filter = ::Rails.backtrace_cleaner if ::Rails.respond_to?(:backtrace_cleaner)
+ end
+
+ self.reporter << ::Rails::TestUnitReporter.new(options[:io], options)
end
+
+ mattr_accessor(:run_with_autorun) { false }
+ mattr_accessor(:run_with_rails_extension) { false }
end
+
+Minitest.load_plugins
Minitest.extensions << 'rails'
diff --git a/railties/lib/rails/test_unit/reporter.rb b/railties/lib/rails/test_unit/reporter.rb
index 64e99626eb..09b8675cf8 100644
--- a/railties/lib/rails/test_unit/reporter.rb
+++ b/railties/lib/rails/test_unit/reporter.rb
@@ -1,9 +1,13 @@
+require "active_support/core_ext/class/attribute"
require "minitest"
module Rails
class TestUnitReporter < Minitest::StatisticsReporter
+ class_attribute :executable
+ self.executable = "bin/rails test"
+
def report
- return if results.empty?
+ return if filtered_results.empty?
io.puts
io.puts "Failed tests:"
io.puts
@@ -11,12 +15,22 @@ module Rails
end
def aggregated_results # :nodoc:
- filtered_results = results.dup
- filtered_results.reject!(&:skipped?) unless options[:verbose]
filtered_results.map do |result|
location, line = result.method(result.name).source_location
- "bin/rails test #{location}:#{line}"
+ "#{self.executable} #{relative_path_for(location)}:#{line}"
end.join "\n"
end
+
+ def filtered_results
+ if options[:verbose]
+ results
+ else
+ results.reject(&:skipped?)
+ end
+ end
+
+ def relative_path_for(file)
+ file.sub(/^#{Rails.root}\/?/, '')
+ end
end
end
diff --git a/railties/lib/rails/test_unit/runner.rb b/railties/lib/rails/test_unit/runner.rb
deleted file mode 100644
index 5573fa6904..0000000000
--- a/railties/lib/rails/test_unit/runner.rb
+++ /dev/null
@@ -1,137 +0,0 @@
-require "optparse"
-require "rake/file_list"
-require "method_source"
-
-module Rails
- class TestRunner
- class Options
- def self.parse(args)
- options = { backtrace: !ENV["BACKTRACE"].nil?, name: nil, environment: "test" }
-
- opt_parser = ::OptionParser.new do |opts|
- opts.banner = "Usage: bin/rails test [options] [file or directory]"
-
- opts.separator ""
- opts.on("-e", "--environment [ENV]",
- "Run tests in the ENV environment") do |env|
- options[:environment] = env.strip
- end
- opts.separator ""
- opts.separator "Filter options:"
- opts.separator ""
- opts.separator <<-DESC
- You can run a single test by appending the line number to filename:
-
- bin/rails test test/models/user_test.rb:27
-
- DESC
-
- opts.on("-n", "--name [NAME]",
- "Only run tests matching NAME") do |name|
- options[:name] = name
- end
- opts.on("-p", "--pattern [PATTERN]",
- "Only run tests matching PATTERN") do |pattern|
- options[:name] = "/#{pattern}/"
- end
-
- opts.separator ""
- opts.separator "Output options:"
-
- opts.on("-b", "--backtrace",
- "Show the complete backtrace") do
- options[:backtrace] = true
- end
-
- opts.separator ""
- opts.separator "Common options:"
-
- opts.on_tail("-h", "--help", "Show this message") do
- puts opts
- exit
- end
- end
-
- opt_parser.order!(args)
-
- options[:patterns] = []
- while arg = args.shift
- if (file_and_line = arg.split(':')).size > 1
- options[:filename], options[:line] = file_and_line
- options[:filename] = File.expand_path options[:filename]
- options[:line] &&= options[:line].to_i
- else
- arg = arg.gsub(':', '')
- if Dir.exist?("#{arg}")
- options[:patterns] << File.expand_path("#{arg}/**/*_test.rb")
- elsif File.file?(arg)
- options[:patterns] << File.expand_path(arg)
- end
- end
- end
- options
- end
- end
-
- def initialize(options = {})
- @options = options
- end
-
- def self.run(arguments)
- options = Rails::TestRunner::Options.parse(arguments)
- Rails::TestRunner.new(options).run
- end
-
- def run
- $rails_test_runner = self
- ENV["RAILS_ENV"] = @options[:environment]
- run_tests
- end
-
- def find_method
- return @options[:name] if @options[:name]
- return unless @options[:line]
- method = test_methods.find do |location, test_method, start_line, end_line|
- location == @options[:filename] &&
- (start_line..end_line).include?(@options[:line].to_i)
- end
- method[1] if method
- end
-
- def show_backtrace?
- @options[:backtrace]
- end
-
- def test_files
- return [@options[:filename]] if @options[:filename]
- if @options[:patterns] && @options[:patterns].count > 0
- pattern = @options[:patterns]
- else
- pattern = "test/**/*_test.rb"
- end
- Rake::FileList[pattern]
- end
-
- private
- def run_tests
- test_files.to_a.each do |file|
- require File.expand_path file
- end
- end
-
- def test_methods
- methods_map = []
- suites = Minitest::Runnable.runnables.shuffle
- suites.each do |suite_class|
- suite_class.runnable_methods.each do |test_method|
- method = suite_class.instance_method(test_method)
- location = method.source_location
- start_line = location.last
- end_line = method.source.split("\n").size + start_line - 1
- methods_map << [File.expand_path(location.first), test_method, start_line, end_line]
- end
- end
- methods_map
- end
- end
-end
diff --git a/railties/lib/rails/test_unit/sub_test_task.rb b/railties/lib/rails/test_unit/sub_test_task.rb
deleted file mode 100644
index 6fa96d2ced..0000000000
--- a/railties/lib/rails/test_unit/sub_test_task.rb
+++ /dev/null
@@ -1,126 +0,0 @@
-require 'rake/testtask'
-
-module Rails
- class TestTask < Rake::TestTask # :nodoc: all
- # A utility class which is used primarily in "rails/test_unit/testing.rake"
- # to help define rake tasks corresponding to <tt>rake test</tt>.
- #
- # This class takes a TestInfo class and defines the appropriate rake task
- # based on the information, then invokes it.
- class TestCreator # :nodoc:
- def initialize(info)
- @info = info
- end
-
- def invoke_rake_task
- if @info.files.any?
- create_and_run_single_test
- reset_application_tasks
- else
- Rake::Task[ENV['TEST'] ? 'test:single' : 'test:run'].invoke
- end
- end
-
- private
-
- def create_and_run_single_test
- Rails::TestTask.new('test:single') { |t|
- t.test_files = @info.files
- }
- ENV['TESTOPTS'] ||= @info.opts
- Rake::Task['test:single'].invoke
- end
-
- def reset_application_tasks
- Rake.application.top_level_tasks.replace @info.tasks
- end
- end
-
- # This is a utility class used by the <tt>TestTask::TestCreator</tt> class.
- # This class takes a set of test tasks and checks to see if they correspond
- # to test files (or can be transformed into test files). Calling <tt>files</tt>
- # provides the set of test files and is used when initializing tests after
- # a call to <tt>rake test</tt>.
- class TestInfo # :nodoc:
- def initialize(tasks)
- @tasks = tasks
- @files = nil
- end
-
- def files
- @files ||= @tasks.map { |task|
- [task, translate(task)].find { |file| test_file?(file) }
- }.compact
- end
-
- def translate(file)
- if file =~ /^app\/(.*)$/
- "test/#{$1.sub(/\.rb$/, '')}_test.rb"
- else
- "test/#{file}_test.rb"
- end
- end
-
- def tasks
- @tasks - test_file_tasks - opt_names
- end
-
- def opts
- opts = opt_names
- if opts.any?
- "-n #{opts.join ' '}"
- end
- end
-
- private
-
- def test_file_tasks
- @tasks.find_all { |task|
- [task, translate(task)].any? { |file| test_file?(file) }
- }
- end
-
- def test_file?(file)
- file =~ /^test/ && File.file?(file) && !File.directory?(file)
- end
-
- def opt_names
- (@tasks - test_file_tasks).reject { |t| task_defined? t }
- end
-
- def task_defined?(task)
- Rake::Task.task_defined? task
- end
- end
-
- def self.test_creator(tasks)
- info = TestInfo.new(tasks)
- TestCreator.new(info)
- end
-
- def initialize(name = :test)
- super
- @libs << "test" # lib *and* test seem like a better default
- end
-
- def define
- task @name do
- if ENV['TESTOPTS']
- ARGV.replace Shellwords.split ENV['TESTOPTS']
- end
- libs = @libs - $LOAD_PATH
- $LOAD_PATH.unshift(*libs)
- file_list.each { |fl|
- FileList[fl].to_a.each { |f| require File.expand_path f }
- }
- end
- end
- end
-
- # Silence the default description to cut down on `rake -T` noise.
- class SubTestTask < Rake::TestTask # :nodoc:
- def desc(string)
- # Ignore the description.
- end
- end
-end
diff --git a/railties/lib/rails/test_unit/test_requirer.rb b/railties/lib/rails/test_unit/test_requirer.rb
new file mode 100644
index 0000000000..84c2256729
--- /dev/null
+++ b/railties/lib/rails/test_unit/test_requirer.rb
@@ -0,0 +1,28 @@
+require 'active_support/core_ext/object/blank'
+require 'rake/file_list'
+
+module Rails
+ class TestRequirer # :nodoc:
+ class << self
+ def require_files(patterns)
+ patterns = expand_patterns(patterns)
+
+ Rake::FileList[patterns.compact.presence || 'test/**/*_test.rb'].to_a.each do |file|
+ require File.expand_path(file)
+ end
+ end
+
+ private
+ def expand_patterns(patterns)
+ patterns.map do |arg|
+ arg = arg.gsub(/:(\d+)?$/, '')
+ if Dir.exist?(arg)
+ "#{arg}/**/*_test.rb"
+ elsif File.file?(arg)
+ arg
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake
index 0f26621b59..dda492f974 100644
--- a/railties/lib/rails/test_unit/testing.rake
+++ b/railties/lib/rails/test_unit/testing.rake
@@ -1,12 +1,13 @@
-require "rails/test_unit/runner"
+gem 'minitest'
+require 'minitest'
+require 'rails/test_unit/minitest_plugin'
task default: :test
desc "Runs all tests in test folder"
task :test do
$: << "test"
- args = ARGV[0] == "test" ? ARGV[1..-1] : []
- Rails::TestRunner.run(args)
+ Minitest.run(['test'])
end
namespace :test do
@@ -23,22 +24,22 @@ namespace :test do
["models", "helpers", "controllers", "mailers", "integration", "jobs"].each do |name|
task name => "test:prepare" do
$: << "test"
- Rails::TestRunner.run(["test/#{name}"])
+ Minitest.run(["test/#{name}"])
end
end
task :generators => "test:prepare" do
$: << "test"
- Rails::TestRunner.run(["test/lib/generators"])
+ Minitest.run(["test/lib/generators"])
end
task :units => "test:prepare" do
$: << "test"
- Rails::TestRunner.run(["test/models", "test/helpers", "test/unit"])
+ Minitest.run(["test/models", "test/helpers", "test/unit"])
end
task :functionals => "test:prepare" do
$: << "test"
- Rails::TestRunner.run(["test/controllers", "test/mailers", "test/functional"])
+ Minitest.run(["test/controllers", "test/mailers", "test/functional"])
end
end
diff --git a/railties/railties.gemspec b/railties/railties.gemspec
index 001882fdc6..afe1603448 100644
--- a/railties/railties.gemspec
+++ b/railties/railties.gemspec
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
s.summary = 'Tools for creating, working with, and running Rails applications.'
s.description = 'Rails internals: application bootup, plugins, generators, and rake tasks.'
- s.required_ruby_version = '>= 2.2.1'
+ s.required_ruby_version = '>= 2.2.2'
s.license = 'MIT'
@@ -15,10 +15,10 @@ Gem::Specification.new do |s|
s.email = 'david@loudthinking.com'
s.homepage = 'http://www.rubyonrails.org'
- s.files = Dir['CHANGELOG.md', 'README.rdoc', 'RDOC_MAIN.rdoc', 'bin/**/*', 'lib/**/{*,.[a-z]*}']
+ s.files = Dir['CHANGELOG.md', 'README.rdoc', 'RDOC_MAIN.rdoc', 'exe/**/*', 'lib/**/{*,.[a-z]*}']
s.require_path = 'lib'
- s.bindir = 'bin'
+ s.bindir = 'exe'
s.executables = ['rails']
s.rdoc_options << '--exclude' << '.'
diff --git a/railties/test/app_rails_loader_test.rb b/railties/test/app_loader_test.rb
index d4885447e6..5946c8fd4c 100644
--- a/railties/test/app_rails_loader_test.rb
+++ b/railties/test/app_loader_test.rb
@@ -1,11 +1,11 @@
require 'tmpdir'
require 'abstract_unit'
-require 'rails/app_rails_loader'
+require 'rails/app_loader'
-class AppRailsLoaderTest < ActiveSupport::TestCase
+class AppLoaderTest < ActiveSupport::TestCase
def loader
@loader ||= Class.new do
- extend Rails::AppRailsLoader
+ extend Rails::AppLoader
def self.exec_arguments
@exec_arguments
@@ -23,7 +23,7 @@ class AppRailsLoaderTest < ActiveSupport::TestCase
end
def expects_exec(exe)
- assert_equal [Rails::AppRailsLoader::RUBY, exe], loader.exec_arguments
+ assert_equal [Rails::AppLoader::RUBY, exe], loader.exec_arguments
end
setup do
@@ -38,20 +38,20 @@ class AppRailsLoaderTest < ActiveSupport::TestCase
test "is not in a Rails application if #{exe} is not found in the current or parent directories" do
def loader.find_executables; end
- assert !loader.exec_app_rails
+ assert !loader.exec_app
end
test "is not in a Rails application if #{exe} exists but is a folder" do
FileUtils.mkdir_p(exe)
- assert !loader.exec_app_rails
+ assert !loader.exec_app
end
['APP_PATH', 'ENGINE_PATH'].each do |keyword|
test "is in a Rails application if #{exe} exists and contains #{keyword}" do
write exe, keyword
- loader.exec_app_rails
+ loader.exec_app
expects_exec exe
end
@@ -59,7 +59,7 @@ class AppRailsLoaderTest < ActiveSupport::TestCase
test "is not in a Rails application if #{exe} exists but doesn't contain #{keyword}" do
write exe
- assert !loader.exec_app_rails
+ assert !loader.exec_app
end
test "is in a Rails application if parent directory has #{exe} containing #{keyword} and chdirs to the root directory" do
@@ -68,7 +68,7 @@ class AppRailsLoaderTest < ActiveSupport::TestCase
Dir.chdir('foo/bar')
- loader.exec_app_rails
+ loader.exec_app
expects_exec exe
diff --git a/railties/test/application/asset_debugging_test.rb b/railties/test/application/asset_debugging_test.rb
index acd387256c..36ab8109a7 100644
--- a/railties/test/application/asset_debugging_test.rb
+++ b/railties/test/application/asset_debugging_test.rb
@@ -36,7 +36,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){ `bundle exec rake assets:precompile --trace 2>&1` }
+ output = Dir.chdir(app_path){ `bin/rake assets:precompile --trace 2>&1` }
assert $?.success?, output
require "#{app_path}/config/environment"
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index f6b7d4c855..ba2c27d8e8 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -18,7 +18,7 @@ module ApplicationTests
def precompile!(env = nil)
quietly do
- precompile_task = "bundle exec rake assets:precompile #{env} --trace 2>&1"
+ precompile_task = "bin/rake assets:precompile #{env} --trace 2>&1"
output = Dir.chdir(app_path) { %x[ #{precompile_task} ] }
assert $?.success?, output
output
@@ -27,7 +27,7 @@ module ApplicationTests
def clean_assets!
quietly do
- assert Dir.chdir(app_path) { system('bundle exec rake assets:clobber') }
+ assert Dir.chdir(app_path) { system('bin/rake assets:clobber') }
end
end
@@ -60,6 +60,7 @@ module ApplicationTests
test "assets do not require compressors until it is used" do
app_file "app/assets/javascripts/demo.js.erb", "<%= :alert %>();"
add_to_env_config "production", "config.assets.compile = true"
+ add_to_env_config "production", "config.assets.precompile = []"
ENV["RAILS_ENV"] = "production"
require "#{app_path}/config/environment"
@@ -191,6 +192,7 @@ module ApplicationTests
test "asset pipeline should use a Sprockets::Index when config.assets.digest is true" do
add_to_config "config.action_controller.perform_caching = false"
+ add_to_env_config "production", "config.assets.compile = true"
ENV["RAILS_ENV"] = "production"
require "#{app_path}/config/environment"
@@ -464,17 +466,6 @@ module ApplicationTests
assert_match "src='/sub/uri/assets/rails.png'", File.read(Dir["#{app_path}/public/assets/app-*.js"].first)
end
- test "assets:cache:clean should clean cache" do
- ENV["RAILS_ENV"] = "production"
- precompile!
-
- quietly do
- Dir.chdir(app_path){ `bundle exec rake assets:clobber` }
- end
-
- assert !File.exist?("#{app_path}/tmp/cache/assets")
- end
-
private
def app_with_assets_in_view
diff --git a/railties/test/application/bin_setup_test.rb b/railties/test/application/bin_setup_test.rb
new file mode 100644
index 0000000000..1bdced02e9
--- /dev/null
+++ b/railties/test/application/bin_setup_test.rb
@@ -0,0 +1,54 @@
+require 'isolation/abstract_unit'
+
+module ApplicationTests
+ class BinSetupTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def setup
+ build_app
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ def test_bin_setup
+ Dir.chdir(app_path) do
+ app_file 'db/schema.rb', <<-RUBY
+ ActiveRecord::Schema.define(version: 20140423102712) do
+ create_table(:articles) {}
+ end
+ RUBY
+
+ list_tables = lambda { `bin/rails runner 'p ActiveRecord::Base.connection.tables'`.strip }
+ File.write("log/my.log", "zomg!")
+
+ assert_equal '[]', list_tables.call
+ assert_equal 5, File.size("log/my.log")
+ assert_not File.exist?("tmp/restart.txt")
+ `bin/setup 2>&1`
+ assert_equal 0, File.size("log/my.log")
+ assert_equal '["articles", "schema_migrations"]', list_tables.call
+ assert File.exist?("tmp/restart.txt")
+ end
+ end
+
+ def test_bin_setup_output
+ Dir.chdir(app_path) do
+ app_file 'db/schema.rb', ""
+
+ output = `bin/setup 2>&1`
+ assert_equal(<<-OUTPUT, output)
+== Installing dependencies ==
+The Gemfile's dependencies are satisfied
+
+== Preparing database ==
+
+== Removing old logs and tempfiles ==
+
+== Restarting application server ==
+ OUTPUT
+ end
+ end
+ end
+end
diff --git a/railties/test/application/configuration/base_test.rb b/railties/test/application/configuration/base_test.rb
deleted file mode 100644
index d6a82b139d..0000000000
--- a/railties/test/application/configuration/base_test.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-require 'isolation/abstract_unit'
-require 'rack/test'
-require 'env_helpers'
-
-module ApplicationTests
- module ConfigurationTests
- class BaseTest < ActiveSupport::TestCase
- def setup
- build_app
- boot_rails
- FileUtils.rm_rf("#{app_path}/config/environments")
- end
-
- def teardown
- teardown_app
- FileUtils.rm_rf(new_app) if File.directory?(new_app)
- end
-
- private
- def new_app
- File.expand_path("#{app_path}/../new_app")
- end
-
- def copy_app
- FileUtils.cp_r(app_path, new_app)
- end
-
- def app
- @app ||= Rails.application
- end
-
- def require_environment
- require "#{app_path}/config/environment"
- end
- end
- end
-end \ No newline at end of file
diff --git a/railties/test/application/configuration/custom_test.rb b/railties/test/application/configuration/custom_test.rb
index f8d22f6d97..28b3b2f2d6 100644
--- a/railties/test/application/configuration/custom_test.rb
+++ b/railties/test/application/configuration/custom_test.rb
@@ -1,22 +1,54 @@
-require 'application/configuration/base_test'
-
-class ApplicationTests::ConfigurationTests::CustomTest < ApplicationTests::ConfigurationTests::BaseTest
- test 'access custom configuration point' do
- add_to_config <<-RUBY
- config.x.payment_processing.schedule = :daily
- config.x.payment_processing.retries = 3
- config.x.super_debugger = true
- config.x.hyper_debugger = false
- config.x.nil_debugger = nil
- RUBY
- require_environment
-
- x = Rails.configuration.x
- assert_equal :daily, x.payment_processing.schedule
- 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.i_do_not_exist.zomg
+require 'isolation/abstract_unit'
+
+module ApplicationTests
+ module ConfigurationTests
+ class CustomTest < ActiveSupport::TestCase
+ def setup
+ build_app
+ boot_rails
+ FileUtils.rm_rf("#{app_path}/config/environments")
+ end
+
+ def teardown
+ teardown_app
+ FileUtils.rm_rf(new_app) if File.directory?(new_app)
+ end
+
+ test 'access custom configuration point' do
+ add_to_config <<-RUBY
+ config.x.payment_processing.schedule = :daily
+ config.x.payment_processing.retries = 3
+ config.x.super_debugger = true
+ config.x.hyper_debugger = false
+ config.x.nil_debugger = nil
+ RUBY
+ require_environment
+
+ x = Rails.configuration.x
+ assert_equal :daily, x.payment_processing.schedule
+ 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.i_do_not_exist.zomg
+ end
+
+ private
+ def new_app
+ File.expand_path("#{app_path}/../new_app")
+ end
+
+ def copy_app
+ FileUtils.cp_r(app_path, new_app)
+ end
+
+ def app
+ @app ||= Rails.application
+ end
+
+ def require_environment
+ require "#{app_path}/config/environment"
+ end
+ end
end
end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 8f5b2d0d68..f677a7c42a 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -690,7 +690,7 @@ module ApplicationTests
_ = ActionMailer::Base
- assert_equal [::MyPreviewMailInterceptor], ActionMailer::Base.preview_interceptors
+ assert_equal [ActionMailer::InlinePreviewInterceptor, ::MyPreviewMailInterceptor], ActionMailer::Base.preview_interceptors
end
test "registers multiple preview interceptors with ActionMailer" do
@@ -703,7 +703,20 @@ module ApplicationTests
_ = ActionMailer::Base
- assert_equal [MyPreviewMailInterceptor, MyOtherPreviewMailInterceptor], ActionMailer::Base.preview_interceptors
+ assert_equal [ActionMailer::InlinePreviewInterceptor, MyPreviewMailInterceptor, MyOtherPreviewMailInterceptor], ActionMailer::Base.preview_interceptors
+ end
+
+ test "default preview interceptor can be removed" do
+ app_file 'config/initializers/preview_interceptors.rb', <<-RUBY
+ ActionMailer::Base.preview_interceptors.delete(ActionMailer::InlinePreviewInterceptor)
+ RUBY
+
+ require "#{app_path}/config/environment"
+ require "mail"
+
+ _ = ActionMailer::Base
+
+ assert_equal [], ActionMailer::Base.preview_interceptors
end
test "registers observers with ActionMailer" do
@@ -732,6 +745,19 @@ module ApplicationTests
assert_equal [::MyMailObserver, ::MyOtherMailObserver], ::Mail.send(:class_variable_get, "@@delivery_notification_observers")
end
+ test "allows setting the queue name for the ActionMailer::DeliveryJob" do
+ add_to_config <<-RUBY
+ config.action_mailer.deliver_later_queue_name = 'test_default'
+ RUBY
+
+ require "#{app_path}/config/environment"
+ require "mail"
+
+ _ = ActionMailer::Base
+
+ assert_equal 'test_default', ActionMailer::Base.send(:class_variable_get, "@@deliver_later_queue_name")
+ end
+
test "valid timezone is setup correctly" do
add_to_config <<-RUBY
config.root = "#{app_path}"
diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb
index 31bc003dcb..7bf123d12b 100644
--- a/railties/test/application/console_test.rb
+++ b/railties/test/application/console_test.rb
@@ -29,6 +29,18 @@ class ConsoleTest < ActiveSupport::TestCase
assert_instance_of ActionDispatch::Integration::Session, console_session
end
+ def test_app_can_access_path_helper_method
+ app_file 'config/routes.rb', <<-RUBY
+ Rails.application.routes.draw do
+ get 'foo', to: 'foo#index'
+ end
+ RUBY
+
+ load_environment
+ console_session = irb_context.app
+ assert_equal '/foo', console_session.foo_path
+ end
+
def test_new_session_should_return_integration_session
load_environment
session = irb_context.new_session
diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb
index 78ada58ec8..84cc6e120b 100644
--- a/railties/test/application/generators_test.rb
+++ b/railties/test/application/generators_test.rb
@@ -125,5 +125,40 @@ module ApplicationTests
assert_equal expected, c.generators.options
end
end
+
+ test "api only generators hide assets, helper, js and css namespaces and set api option" do
+ add_to_config <<-RUBY
+ config.api_only = true
+ RUBY
+
+ # Initialize the application
+ 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 Rails::Generators.options[:rails][:api]
+ assert_equal false, Rails::Generators.options[:rails][:assets]
+ assert_equal false, Rails::Generators.options[:rails][:helper]
+ assert_nil Rails::Generators.options[:rails][:template_engine]
+ end
+
+ test "api only generators allow overriding generator options" do
+ add_to_config <<-RUBY
+ config.generators.helper = true
+ config.api_only = true
+ config.generators.template_engine = :my_template
+ RUBY
+
+ # Initialize the application
+ require "#{app_path}/config/environment"
+ Rails.application.load_generators
+
+ assert Rails::Generators.options[:rails][:api]
+ assert Rails::Generators.options[:rails][:helper]
+ assert_equal :my_template, Rails::Generators.options[:rails][:template_engine]
+ end
end
end
diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb
index 97b51911d9..7bba910b9e 100644
--- a/railties/test/application/initializers/frameworks_test.rb
+++ b/railties/test/application/initializers/frameworks_test.rb
@@ -129,6 +129,35 @@ module ApplicationTests
assert_equal "false", last_response.body
end
+ test "action_controller api executes using all the middleware stack" do
+ add_to_config "config.api_only = true"
+
+ app_file "app/controllers/application_controller.rb", <<-RUBY
+ class ApplicationController < ActionController::API
+ end
+ RUBY
+
+ app_file "app/controllers/omg_controller.rb", <<-RUBY
+ class OmgController < ApplicationController
+ def show
+ render json: { omg: 'omg' }
+ end
+ end
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ get "/:controller(/:action)"
+ end
+ RUBY
+
+ require 'rack/test'
+ extend Rack::Test::Methods
+
+ get 'omg/show'
+ assert_equal '{"omg":"omg"}', last_response.body
+ end
+
# AD
test "action_dispatch extensions are applied to ActionDispatch" do
add_to_config "config.action_dispatch.tld_length = 2"
@@ -177,7 +206,7 @@ module ApplicationTests
test "use schema cache dump" do
Dir.chdir(app_path) do
`rails generate model post title:string;
- bundle exec rake db:migrate db:schema:cache:dump`
+ bin/rake db:migrate db:schema:cache:dump`
end
require "#{app_path}/config/environment"
ActiveRecord::Base.connection.drop_table("posts") # force drop posts table for test.
@@ -187,7 +216,7 @@ module ApplicationTests
test "expire schema cache dump" do
Dir.chdir(app_path) do
`rails generate model post title:string;
- bundle exec rake db:migrate db:schema:cache:dump db:rollback`
+ bin/rake db:migrate db:schema:cache:dump db:rollback`
end
silence_warnings {
require "#{app_path}/config/environment"
diff --git a/railties/test/application/initializers/i18n_test.rb b/railties/test/application/initializers/i18n_test.rb
index 9ee54796a4..ab7f29b0f2 100644
--- a/railties/test/application/initializers/i18n_test.rb
+++ b/railties/test/application/initializers/i18n_test.rb
@@ -132,6 +132,79 @@ en:
assert_equal "2", last_response.body
end
+ test "new locale files are loaded" do
+ add_to_config <<-RUBY
+ config.cache_classes = false
+ RUBY
+
+ app_file "config/locales/en.yml", <<-YAML
+en:
+ foo: "1"
+ YAML
+
+ app_file 'config/routes.rb', <<-RUBY
+ Rails.application.routes.draw do
+ get '/i18n', :to => lambda { |env| [200, {}, [I18n.t(:foo)]] }
+ end
+ RUBY
+
+ require 'rack/test'
+ extend Rack::Test::Methods
+ load_app
+
+ get "/i18n"
+ assert_equal "1", last_response.body
+
+ # Wait a full second so we have time for changes to propagate
+ sleep(1)
+
+ remove_file "config/locales/en.yml"
+ app_file "config/locales/custom.en.yml", <<-YAML
+en:
+ foo: "2"
+ YAML
+
+ get "/i18n"
+ assert_equal "2", last_response.body
+ end
+
+ test "I18n.load_path is reloaded" do
+ add_to_config <<-RUBY
+ config.cache_classes = false
+ RUBY
+
+ app_file "config/locales/en.yml", <<-YAML
+en:
+ foo: "1"
+ YAML
+
+ app_file 'config/routes.rb', <<-RUBY
+ Rails.application.routes.draw do
+ get '/i18n', :to => lambda { |env| [200, {}, [I18n.load_path.inspect]] }
+ end
+ RUBY
+
+ require 'rack/test'
+ extend Rack::Test::Methods
+ load_app
+
+ get "/i18n"
+
+ assert_match "en.yml", last_response.body
+
+ # Wait a full second so we have time for changes to propagate
+ sleep(1)
+
+ app_file "config/locales/fr.yml", <<-YAML
+fr:
+ foo: "2"
+ YAML
+
+ get "/i18n"
+ assert_match "fr.yml", last_response.body
+ assert_match "en.yml", last_response.body
+ end
+
# Fallbacks
test "not using config.i18n.fallbacks does not initialize I18n.fallbacks" do
I18n.backend = Class.new(I18n::Backend::Simple).new
diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb
index 85066210f3..1027bca2c1 100644
--- a/railties/test/application/loading_test.rb
+++ b/railties/test/application/loading_test.rb
@@ -210,7 +210,7 @@ class LoadingTest < ActiveSupport::TestCase
app_file 'config/routes.rb', <<-RUBY
$counter ||= 0
Rails.application.routes.draw do
- get '/c', to: lambda { |env| User; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] }
+ get '/c', to: lambda { |env| User.name; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] }
end
RUBY
@@ -243,7 +243,7 @@ class LoadingTest < ActiveSupport::TestCase
$counter ||= 1
$counter *= 2
Rails.application.routes.draw do
- get '/c', to: lambda { |env| User; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] }
+ get '/c', to: lambda { |env| User.name; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] }
end
RUBY
diff --git a/railties/test/application/mailer_previews_test.rb b/railties/test/application/mailer_previews_test.rb
index 1752a9f3c6..e462d2c15e 100644
--- a/railties/test/application/mailer_previews_test.rb
+++ b/railties/test/application/mailer_previews_test.rb
@@ -1,5 +1,7 @@
require 'isolation/abstract_unit'
require 'rack/test'
+require 'base64'
+
module ApplicationTests
class MailerPreviewsTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
@@ -327,6 +329,32 @@ module ApplicationTests
assert_match "Email &#39;bar&#39; not found in NotifierPreview", last_response.body
end
+ test "mailer preview NullMail" do
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ # does not call +mail+
+ end
+ end
+ RUBY
+
+ mailer_preview 'notifier', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ app('development')
+
+ get "/rails/mailers/notifier/foo"
+ assert_match "You are trying to preview an email that does not have any content.", last_response.body
+ assert_match "notifier#foo", last_response.body
+ end
+
test "mailer preview email part not found" do
mailer 'notifier', <<-RUBY
class Notifier < ActionMailer::Base
@@ -428,6 +456,222 @@ module ApplicationTests
assert_match '<option selected value="?part=text%2Fplain">View as plain-text email</option>', last_response.body
end
+ test "mailer previews create correct links when loaded on a subdirectory" do
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ text_template 'notifier/foo', <<-RUBY
+ Hello, World!
+ RUBY
+
+ mailer_preview 'notifier', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ app('development')
+
+ get "/rails/mailers", {}, 'SCRIPT_NAME' => '/my_app'
+ assert_match '<h3><a href="/my_app/rails/mailers/notifier">Notifier</a></h3>', last_response.body
+ assert_match '<li><a href="/my_app/rails/mailers/notifier/foo">foo</a></li>', last_response.body
+ end
+
+ test "plain text mailer preview with attachment" do
+ image_file "pixel.png", "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEWzIioca/JlAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJgggo="
+
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ attachments['pixel.png'] = File.read("#{app_path}/public/images/pixel.png", mode: 'rb')
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ text_template 'notifier/foo', <<-RUBY
+ Hello, World!
+ RUBY
+
+ mailer_preview 'notifier', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ app('development')
+
+ get "/rails/mailers/notifier/foo"
+ assert_equal 200, last_response.status
+ assert_match %r[<iframe seamless name="messageBody"], last_response.body
+
+ get "/rails/mailers/notifier/foo?part=text/plain"
+ assert_equal 200, last_response.status
+ assert_match %r[Hello, World!], last_response.body
+ end
+
+ test "multipart mailer preview with attachment" do
+ image_file "pixel.png", "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEWzIioca/JlAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJgggo="
+
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ attachments['pixel.png'] = File.read("#{app_path}/public/images/pixel.png", mode: 'rb')
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ text_template 'notifier/foo', <<-RUBY
+ Hello, World!
+ RUBY
+
+ html_template 'notifier/foo', <<-RUBY
+ <p>Hello, World!</p>
+ RUBY
+
+ mailer_preview 'notifier', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ app('development')
+
+ get "/rails/mailers/notifier/foo"
+ assert_equal 200, last_response.status
+ assert_match %r[<iframe seamless name="messageBody"], last_response.body
+
+ get "/rails/mailers/notifier/foo?part=text/plain"
+ assert_equal 200, last_response.status
+ assert_match %r[Hello, World!], last_response.body
+
+ get "/rails/mailers/notifier/foo?part=text/html"
+ assert_equal 200, last_response.status
+ assert_match %r[<p>Hello, World!</p>], last_response.body
+ end
+
+ test "multipart mailer preview with inline attachment" do
+ image_file "pixel.png", "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEWzIioca/JlAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJgggo="
+
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ attachments['pixel.png'] = File.read("#{app_path}/public/images/pixel.png", mode: 'rb')
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ text_template 'notifier/foo', <<-RUBY
+ Hello, World!
+ RUBY
+
+ html_template 'notifier/foo', <<-RUBY
+ <p>Hello, World!</p>
+ <%= image_tag attachments['pixel.png'].url %>
+ RUBY
+
+ mailer_preview 'notifier', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ app('development')
+
+ get "/rails/mailers/notifier/foo"
+ assert_equal 200, last_response.status
+ assert_match %r[<iframe seamless name="messageBody"], last_response.body
+
+ get "/rails/mailers/notifier/foo?part=text/plain"
+ assert_equal 200, last_response.status
+ assert_match %r[Hello, World!], last_response.body
+
+ get "/rails/mailers/notifier/foo?part=text/html"
+ assert_equal 200, last_response.status
+ assert_match %r[<p>Hello, World!</p>], last_response.body
+ assert_match %r[src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEWzIioca/JlAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJgggo="], last_response.body
+ end
+
+ test "multipart mailer preview with attached email" do
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ message = ::Mail.new do
+ from 'foo@example.com'
+ to 'bar@example.com'
+ subject 'Important Message'
+
+ text_part do
+ body 'Goodbye, World!'
+ end
+
+ html_part do
+ body '<p>Goodbye, World!</p>'
+ end
+ end
+
+ attachments['message.eml'] = message.to_s
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ text_template 'notifier/foo', <<-RUBY
+ Hello, World!
+ RUBY
+
+ html_template 'notifier/foo', <<-RUBY
+ <p>Hello, World!</p>
+ RUBY
+
+ mailer_preview 'notifier', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ app('development')
+
+ get "/rails/mailers/notifier/foo"
+ assert_equal 200, last_response.status
+ assert_match %r[<iframe seamless name="messageBody"], last_response.body
+
+ get "/rails/mailers/notifier/foo?part=text/plain"
+ assert_equal 200, last_response.status
+ assert_match %r[Hello, World!], last_response.body
+
+ get "/rails/mailers/notifier/foo?part=text/html"
+ assert_equal 200, last_response.status
+ assert_match %r[<p>Hello, World!</p>], last_response.body
+ end
+
private
def build_app
super
@@ -449,5 +693,9 @@ module ApplicationTests
def text_template(name, contents)
app_file("app/views/#{name}.text.erb", contents)
end
+
+ def image_file(name, contents)
+ app_file("public/images/#{name}", Base64.strict_decode64(contents), 'wb')
+ end
end
end
diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb
index a8dc79d10a..25eadfc387 100644
--- a/railties/test/application/middleware/session_test.rb
+++ b/railties/test/application/middleware/session_test.rb
@@ -35,7 +35,7 @@ module ApplicationTests
flash[:notice] = "notice"
end
- render nothing: true
+ head :ok
end
end
@@ -60,7 +60,7 @@ module ApplicationTests
def write_session
session[:foo] = 1
- render nothing: true
+ head :ok
end
def read_session
@@ -101,7 +101,7 @@ module ApplicationTests
def write_cookie
cookies[:foo] = '1'
- render nothing: true
+ head :ok
end
def read_cookie
@@ -139,7 +139,7 @@ module ApplicationTests
class FooController < ActionController::Base
def write_session
session[:foo] = 1
- render nothing: true
+ head :ok
end
def read_session
@@ -184,7 +184,7 @@ module ApplicationTests
class FooController < ActionController::Base
def write_session
session[:foo] = 1
- render nothing: true
+ head :ok
end
def read_session
@@ -234,12 +234,12 @@ module ApplicationTests
def write_raw_session
# {"session_id"=>"1965d95720fffc123941bdfb7d2e6870", "foo"=>1}
cookies[:_myapp_session] = "BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJTE5NjVkOTU3MjBmZmZjMTIzOTQxYmRmYjdkMmU2ODcwBjsAVEkiCGZvbwY7AEZpBg==--315fb9931921a87ae7421aec96382f0294119749"
- render nothing: true
+ head :ok
end
def write_session
session[:foo] = session[:foo] + 1
- render nothing: true
+ head :ok
end
def read_session
@@ -293,12 +293,12 @@ module ApplicationTests
def write_raw_session
# {"session_id"=>"1965d95720fffc123941bdfb7d2e6870", "foo"=>1}
cookies[:_myapp_session] = "BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJTE5NjVkOTU3MjBmZmZjMTIzOTQxYmRmYjdkMmU2ODcwBjsAVEkiCGZvbwY7AEZpBg==--315fb9931921a87ae7421aec96382f0294119749"
- render nothing: true
+ head :ok
end
def write_session
session[:foo] = session[:foo] + 1
- render nothing: true
+ head :ok
end
def read_session
diff --git a/railties/test/application/middleware/static_test.rb b/railties/test/application/middleware/static_test.rb
index 121c5d3321..1a46cd3568 100644
--- a/railties/test/application/middleware/static_test.rb
+++ b/railties/test/application/middleware/static_test.rb
@@ -26,5 +26,26 @@ module ApplicationTests
assert_not last_response.headers.has_key?('Cache-Control'), "Cache-Control should not be set"
end
+
+ test "static_index defaults to 'index'" do
+ app_file "public/index.html", "/index.html"
+
+ require "#{app_path}/config/environment"
+
+ get '/'
+
+ assert_equal "/index.html\n", last_response.body
+ end
+
+ test "static_index configurable" do
+ app_file "public/other-index.html", "/other-index.html"
+ add_to_config "config.static_index = 'other-index'"
+
+ require "#{app_path}/config/environment"
+
+ get '/'
+
+ assert_equal "/other-index.html\n", last_response.body
+ end
end
end
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index 04bd19784a..d298e8d632 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -26,7 +26,7 @@ module ApplicationTests
assert_equal [
"Rack::Sendfile",
"ActionDispatch::Static",
- "Rack::Lock",
+ "ActionDispatch::LoadInterlock",
"ActiveSupport::Cache::Strategy::LocalCache",
"Rack::Runtime",
"Rack::MethodOverride",
@@ -50,6 +50,33 @@ module ApplicationTests
], middleware
end
+ test "api middleware stack" do
+ add_to_config "config.api_only = true"
+
+ boot!
+
+ assert_equal [
+ "Rack::Sendfile",
+ "ActionDispatch::Static",
+ "ActionDispatch::LoadInterlock",
+ "ActiveSupport::Cache::Strategy::LocalCache",
+ "Rack::Runtime",
+ "ActionDispatch::RequestId",
+ "Rails::Rack::Logger", # must come after Rack::MethodOverride to properly log overridden methods
+ "ActionDispatch::ShowExceptions",
+ "ActionDispatch::DebugExceptions",
+ "ActionDispatch::RemoteIp",
+ "ActionDispatch::Reloader",
+ "ActionDispatch::Callbacks",
+ "ActiveRecord::ConnectionAdapters::ConnectionManagement",
+ "ActiveRecord::QueryCache",
+ "ActionDispatch::ParamsParser",
+ "Rack::Head",
+ "Rack::ConditionalGet",
+ "Rack::ETag"
+ ], middleware
+ end
+
test "Rack::Cache is not included by default" do
boot!
@@ -94,23 +121,40 @@ module ApplicationTests
assert !middleware.include?("ActiveRecord::Migration::CheckPending")
end
- test "includes lock if cache_classes is set but eager_load is not" do
+ test "includes interlock if cache_classes is set but eager_load is not" do
add_to_config "config.cache_classes = true"
boot!
- assert middleware.include?("Rack::Lock")
+ assert_not_includes middleware, "Rack::Lock"
+ assert_includes middleware, "ActionDispatch::LoadInterlock"
+ end
+
+ test "includes interlock if cache_classes is off" do
+ add_to_config "config.cache_classes = false"
+ boot!
+ assert_not_includes middleware, "Rack::Lock"
+ assert_includes middleware, "ActionDispatch::LoadInterlock"
end
test "does not include lock if cache_classes is set and so is eager_load" do
add_to_config "config.cache_classes = true"
add_to_config "config.eager_load = true"
boot!
- assert !middleware.include?("Rack::Lock")
+ assert_not_includes middleware, "Rack::Lock"
+ assert_not_includes middleware, "ActionDispatch::LoadInterlock"
+ end
+
+ test "does not include lock if allow_concurrency is set to :unsafe" do
+ add_to_config "config.allow_concurrency = :unsafe"
+ boot!
+ assert_not_includes middleware, "Rack::Lock"
+ assert_not_includes middleware, "ActionDispatch::LoadInterlock"
end
- test "does not include lock if allow_concurrency is set" do
- add_to_config "config.allow_concurrency = true"
+ test "includes lock if allow_concurrency is disabled" do
+ add_to_config "config.allow_concurrency = false"
boot!
- assert !middleware.include?("Rack::Lock")
+ assert_includes middleware, "Rack::Lock"
+ assert_not_includes middleware, "ActionDispatch::LoadInterlock"
end
test "removes static asset server if serve_static_files is disabled" do
diff --git a/railties/test/application/multiple_applications_test.rb b/railties/test/application/multiple_applications_test.rb
index cddc79cc85..f2770a9cb4 100644
--- a/railties/test/application/multiple_applications_test.rb
+++ b/railties/test/application/multiple_applications_test.rb
@@ -118,7 +118,7 @@ module ApplicationTests
assert_equal 0, run_count, "Without loading the initializers, the count should be 0"
# Set config.eager_load to false so that an eager_load warning doesn't pop up
- AppTemplate::Application.new { config.eager_load = false }.initialize!
+ AppTemplate::Application.create { config.eager_load = false }.initialize!
assert_equal 3, run_count, "There should have been three initializers that incremented the count"
end
diff --git a/railties/test/application/per_request_digest_cache_test.rb b/railties/test/application/per_request_digest_cache_test.rb
new file mode 100644
index 0000000000..3198e12662
--- /dev/null
+++ b/railties/test/application/per_request_digest_cache_test.rb
@@ -0,0 +1,63 @@
+require 'isolation/abstract_unit'
+require 'rack/test'
+require 'minitest/mock'
+
+require 'action_view'
+require 'active_support/testing/method_call_assertions'
+
+class PerRequestDigestCacheTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+ include ActiveSupport::Testing::MethodCallAssertions
+ include Rack::Test::Methods
+
+ setup do
+ build_app
+ add_to_config 'config.consider_all_requests_local = true'
+
+ app_file 'app/models/customer.rb', <<-RUBY
+ class Customer < Struct.new(:name, :id)
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+ end
+ RUBY
+
+ app_file 'config/routes.rb', <<-RUBY
+ Rails.application.routes.draw do
+ resources :customers, only: :index
+ end
+ RUBY
+
+ app_file 'app/controllers/customers_controller.rb', <<-RUBY
+ class CustomersController < ApplicationController
+ def index
+ render [ Customer.new('david', 1), Customer.new('dingus', 2) ]
+ end
+ end
+ RUBY
+
+ app_file 'app/views/customers/_customer.html.erb', <<-RUBY
+ <% cache customer do %>
+ <%= customer.name %>
+ <% end %>
+ RUBY
+
+ require "#{app_path}/config/environment"
+ end
+
+ teardown :teardown_app
+
+ test "digests are reused when rendering the same template twice" do
+ get '/customers'
+ assert_equal 200, last_response.status
+
+ assert_equal [ '8ba099b7749542fe765ff34a6824d548' ], ActionView::Digestor.cache.values
+ assert_equal %w(david dingus), last_response.body.split.map(&:strip)
+ end
+
+ test "template digests are cleared before a request" do
+ assert_called(ActionView::Digestor.cache, :clear) do
+ get '/customers'
+ assert_equal 200, last_response.status
+ end
+ end
+end
diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb
index c414732f92..e7beab8b5e 100644
--- a/railties/test/application/rake/dbs_test.rb
+++ b/railties/test/application/rake/dbs_test.rb
@@ -28,11 +28,11 @@ module ApplicationTests
def db_create_and_drop(expected_database)
Dir.chdir(app_path) do
- output = `bundle exec rake db:create`
+ output = `bin/rake db:create`
assert_empty output
assert File.exist?(expected_database)
assert_equal expected_database, ActiveRecord::Base.connection_config[:database]
- output = `bundle exec rake db:drop`
+ output = `bin/rake db:drop`
assert_empty output
assert !File.exist?(expected_database)
end
@@ -51,9 +51,9 @@ module ApplicationTests
def db_migrate_and_status(expected_database)
Dir.chdir(app_path) do
- `rails generate model book title:string;
- bundle exec rake db:migrate`
- output = `bundle exec rake db:migrate:status`
+ `bin/rails generate model book title:string;
+ bin/rake db:migrate`
+ output = `bin/rake db:migrate:status`
assert_match(%r{database:\s+\S*#{Regexp.escape(expected_database)}}, output)
assert_match(/up\s+\d{14}\s+Create books/, output)
end
@@ -72,8 +72,8 @@ module ApplicationTests
def db_schema_dump
Dir.chdir(app_path) do
- `rails generate model book title:string;
- rake db:migrate db:schema:dump`
+ `bin/rails generate model book title:string;
+ bin/rake db:migrate db:schema:dump`
schema_dump = File.read("db/schema.rb")
assert_match(/create_table \"books\"/, schema_dump)
end
@@ -90,8 +90,8 @@ module ApplicationTests
def db_fixtures_load(expected_database)
Dir.chdir(app_path) do
- `rails generate model book title:string;
- bundle exec rake db:migrate db:fixtures:load`
+ `bin/rails generate model book title:string;
+ bin/rake db:migrate db:fixtures:load`
assert_match expected_database, ActiveRecord::Base.connection_config[:database]
require "#{app_path}/app/models/book"
assert_equal 2, Book.count
@@ -112,8 +112,8 @@ module ApplicationTests
test 'db:fixtures:load with namespaced fixture' do
require "#{app_path}/config/environment"
Dir.chdir(app_path) do
- `rails generate model admin::book title:string;
- bundle exec rake db:migrate db:fixtures:load`
+ `bin/rails generate model admin::book title:string;
+ bin/rake db:migrate db:fixtures:load`
require "#{app_path}/app/models/admin/book"
assert_equal 2, Admin::Book.count
end
@@ -121,11 +121,11 @@ module ApplicationTests
def db_structure_dump_and_load(expected_database)
Dir.chdir(app_path) do
- `rails generate model book title:string;
- bundle exec rake db:migrate db:structure:dump`
+ `bin/rails generate model book title:string;
+ bin/rake db:migrate db:structure:dump`
structure_dump = File.read("db/structure.sql")
assert_match(/CREATE TABLE \"books\"/, structure_dump)
- `bundle exec rake environment db:drop db:structure:load`
+ `bin/rake environment db:drop db:structure:load`
assert_match expected_database, ActiveRecord::Base.connection_config[:database]
require "#{app_path}/app/models/book"
#if structure is not loaded correctly, exception would be raised
@@ -147,9 +147,9 @@ module ApplicationTests
test 'db:structure:dump does not dump schema information when no migrations are used' do
Dir.chdir(app_path) do
# create table without migrations
- `bundle exec rails runner 'ActiveRecord::Base.connection.create_table(:posts) {|t| t.string :title }'`
+ `bin/rails runner 'ActiveRecord::Base.connection.create_table(:posts) {|t| t.string :title }'`
- stderr_output = capture(:stderr) { `bundle exec rake db:structure:dump` }
+ stderr_output = capture(:stderr) { `bin/rake db:structure:dump` }
assert_empty stderr_output
structure_dump = File.read("db/structure.sql")
assert_match(/CREATE TABLE \"posts\"/, structure_dump)
@@ -181,10 +181,38 @@ module ApplicationTests
end
end
+ test "db:schema:load with inflections" do
+ Dir.chdir(app_path) do
+ app_file 'config/initializers/inflection.rb', <<-RUBY
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.irregular 'goose', 'geese'
+ end
+ RUBY
+ app_file 'config/initializers/primary_key_table_name.rb', <<-RUBY
+ ActiveRecord::Base.primary_key_prefix_type = :table_name
+ RUBY
+ app_file 'db/schema.rb', <<-RUBY
+ ActiveRecord::Schema.define(version: 20140423102712) do
+ create_table("goose".pluralize) do |t|
+ t.string :name
+ end
+ end
+ RUBY
+
+ `bin/rake db:schema:load`
+
+ tables = `bin/rails runner 'p ActiveRecord::Base.connection.tables'`.strip
+ assert_match(/"geese"/, tables)
+
+ columns = `bin/rails runner 'p ActiveRecord::Base.connection.columns("geese").map(&:name)'`.strip
+ assert_equal columns, '["gooseid", "name"]'
+ end
+ end
+
def db_test_load_structure
Dir.chdir(app_path) do
- `rails generate model book title:string;
- bundle exec rake db:migrate db:structure:dump db:test:load_structure`
+ `bin/rails generate model book title:string;
+ bin/rake db:migrate db:structure:dump db:test:load_structure`
ActiveRecord::Base.configurations = Rails.application.config.database_configuration
ActiveRecord::Base.establish_connection :test
require "#{app_path}/app/models/book"
@@ -220,7 +248,7 @@ module ApplicationTests
RUBY
Dir.chdir(app_path) do
- database_path = `bundle exec rake db:setup`
+ database_path = `bin/rake db:setup`
assert_equal "development.sqlite3", File.basename(database_path.strip)
end
ensure
diff --git a/railties/test/application/rake/dev_test.rb b/railties/test/application/rake/dev_test.rb
new file mode 100644
index 0000000000..28d8b22a37
--- /dev/null
+++ b/railties/test/application/rake/dev_test.rb
@@ -0,0 +1,35 @@
+require 'isolation/abstract_unit'
+
+module ApplicationTests
+ module RakeTests
+ class RakeDevTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def setup
+ build_app
+ boot_rails
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ test 'dev:cache creates file and outputs message' do
+ Dir.chdir(app_path) do
+ output = `rake dev:cache`
+ assert File.exist?('tmp/caching-dev.txt')
+ assert_match(/Development mode is now being cached/, output)
+ end
+ end
+
+ test 'dev:cache deletes file and outputs message' do
+ Dir.chdir(app_path) do
+ output = `rake dev:cache`
+ output = `rake dev:cache`
+ assert_not File.exist?('tmp/caching-dev.txt')
+ assert_match(/Development mode is no longer being cached/, output)
+ end
+ end
+ end
+ end
+end
diff --git a/railties/test/application/rake/framework_test.rb b/railties/test/application/rake/framework_test.rb
new file mode 100644
index 0000000000..ec57af79f6
--- /dev/null
+++ b/railties/test/application/rake/framework_test.rb
@@ -0,0 +1,48 @@
+require "isolation/abstract_unit"
+require "active_support/core_ext/string/strip"
+
+module ApplicationTests
+ module RakeTests
+ class FrameworkTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def setup
+ build_app
+ boot_rails
+ FileUtils.rm_rf("#{app_path}/config/environments")
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ def load_tasks
+ require 'rake'
+ require 'rdoc/task'
+ require 'rake/testtask'
+
+ Rails.application.load_tasks
+ end
+
+ test 'requiring the rake task should not define method .app_generator on Object' do
+ require "#{app_path}/config/environment"
+
+ load_tasks
+
+ assert_raise NameError do
+ Object.method(:app_generator)
+ end
+ end
+
+ test 'requiring the rake task should not define method .invoke_from_app_generator on Object' do
+ require "#{app_path}/config/environment"
+
+ load_tasks
+
+ assert_raise NameError do
+ Object.method(:invoke_from_app_generator)
+ end
+ end
+ end
+ end
+end
diff --git a/railties/test/application/rake/migrations_test.rb b/railties/test/application/rake/migrations_test.rb
index a3819b93b2..2d8bd7c571 100644
--- a/railties/test/application/rake/migrations_test.rb
+++ b/railties/test/application/rake/migrations_test.rb
@@ -15,21 +15,21 @@ module ApplicationTests
test 'running migrations with given scope' do
Dir.chdir(app_path) do
- `rails generate model user username:string password:string`
+ `bin/rails generate model user username:string password:string`
app_file "db/migrate/01_a_migration.bukkits.rb", <<-MIGRATION
class AMigration < ActiveRecord::Migration
end
MIGRATION
- output = `rake db:migrate SCOPE=bukkits`
+ output = `bin/rake db:migrate SCOPE=bukkits`
assert_no_match(/create_table\(:users\)/, output)
assert_no_match(/CreateUsers/, output)
assert_no_match(/add_column\(:users, :email, :string\)/, output)
assert_match(/AMigration: migrated/, output)
- output = `rake db:migrate SCOPE=bukkits VERSION=0`
+ output = `bin/rake db:migrate SCOPE=bukkits VERSION=0`
assert_no_match(/drop_table\(:users\)/, output)
assert_no_match(/CreateUsers/, output)
assert_no_match(/remove_column\(:users, :email\)/, output)
@@ -40,16 +40,16 @@ module ApplicationTests
test 'model and migration generator with change syntax' do
Dir.chdir(app_path) do
- `rails generate model user username:string password:string;
- rails generate migration add_email_to_users email:string`
+ `bin/rails generate model user username:string password:string;
+ bin/rails generate migration add_email_to_users email:string`
- output = `rake db:migrate`
+ output = `bin/rake db:migrate`
assert_match(/create_table\(:users\)/, output)
assert_match(/CreateUsers: migrated/, output)
assert_match(/add_column\(:users, :email, :string\)/, output)
assert_match(/AddEmailToUsers: migrated/, output)
- output = `rake db:rollback STEP=2`
+ output = `bin/rake db:rollback STEP=2`
assert_match(/drop_table\(:users\)/, output)
assert_match(/CreateUsers: reverted/, output)
assert_match(/remove_column\(:users, :email, :string\)/, output)
@@ -58,23 +58,23 @@ module ApplicationTests
end
test 'migration status when schema migrations table is not present' do
- output = Dir.chdir(app_path){ `rake db:migrate:status 2>&1` }
+ output = Dir.chdir(app_path){ `bin/rake db:migrate:status 2>&1` }
assert_equal "Schema migrations table does not exist yet.\n", output
end
test 'test migration status' do
Dir.chdir(app_path) do
- `rails generate model user username:string password:string;
- rails generate migration add_email_to_users email:string;
- rake db:migrate`
+ `bin/rails generate model user username:string password:string;
+ bin/rails generate migration add_email_to_users email:string;
+ bin/rake db:migrate`
- output = `rake db:migrate:status`
+ output = `bin/rake db:migrate:status`
assert_match(/up\s+\d{14}\s+Create users/, output)
assert_match(/up\s+\d{14}\s+Add email to users/, output)
- `rake db:rollback STEP=1`
- output = `rake db:migrate:status`
+ `bin/rake db:rollback STEP=1`
+ output = `bin/rake db:migrate:status`
assert_match(/up\s+\d{14}\s+Create users/, output)
assert_match(/down\s+\d{14}\s+Add email to users/, output)
@@ -85,17 +85,17 @@ module ApplicationTests
add_to_config('config.active_record.timestamped_migrations = false')
Dir.chdir(app_path) do
- `rails generate model user username:string password:string;
- rails generate migration add_email_to_users email:string;
- rake db:migrate`
+ `bin/rails generate model user username:string password:string;
+ bin/rails generate migration add_email_to_users email:string;
+ bin/rake db:migrate`
- output = `rake db:migrate:status`
+ output = `bin/rake db:migrate:status`
assert_match(/up\s+\d{3,}\s+Create users/, output)
assert_match(/up\s+\d{3,}\s+Add email to users/, output)
- `rake db:rollback STEP=1`
- output = `rake db:migrate:status`
+ `bin/rake db:rollback STEP=1`
+ output = `bin/rake db:migrate:status`
assert_match(/up\s+\d{3,}\s+Create users/, output)
assert_match(/down\s+\d{3,}\s+Add email to users/, output)
@@ -104,23 +104,23 @@ module ApplicationTests
test 'test migration status after rollback and redo' do
Dir.chdir(app_path) do
- `rails generate model user username:string password:string;
- rails generate migration add_email_to_users email:string;
- rake db:migrate`
+ `bin/rails generate model user username:string password:string;
+ bin/rails generate migration add_email_to_users email:string;
+ bin/rake db:migrate`
- output = `rake db:migrate:status`
+ output = `bin/rake db:migrate:status`
assert_match(/up\s+\d{14}\s+Create users/, output)
assert_match(/up\s+\d{14}\s+Add email to users/, output)
- `rake db:rollback STEP=2`
- output = `rake db:migrate:status`
+ `bin/rake db:rollback STEP=2`
+ output = `bin/rake db:migrate:status`
assert_match(/down\s+\d{14}\s+Create users/, output)
assert_match(/down\s+\d{14}\s+Add email to users/, output)
- `rake db:migrate:redo`
- output = `rake db:migrate:status`
+ `bin/rake db:migrate:redo`
+ output = `bin/rake db:migrate:status`
assert_match(/up\s+\d{14}\s+Create users/, output)
assert_match(/up\s+\d{14}\s+Add email to users/, output)
@@ -131,23 +131,23 @@ module ApplicationTests
add_to_config('config.active_record.timestamped_migrations = false')
Dir.chdir(app_path) do
- `rails generate model user username:string password:string;
- rails generate migration add_email_to_users email:string;
- rake db:migrate`
+ `bin/rails generate model user username:string password:string;
+ bin/rails generate migration add_email_to_users email:string;
+ bin/rake db:migrate`
- output = `rake db:migrate:status`
+ output = `bin/rake db:migrate:status`
assert_match(/up\s+\d{3,}\s+Create users/, output)
assert_match(/up\s+\d{3,}\s+Add email to users/, output)
- `rake db:rollback STEP=2`
- output = `rake db:migrate:status`
+ `bin/rake db:rollback STEP=2`
+ output = `bin/rake db:migrate:status`
assert_match(/down\s+\d{3,}\s+Create users/, output)
assert_match(/down\s+\d{3,}\s+Add email to users/, output)
- `rake db:migrate:redo`
- output = `rake db:migrate:status`
+ `bin/rake db:migrate:redo`
+ output = `bin/rake db:migrate:status`
assert_match(/up\s+\d{3,}\s+Create users/, output)
assert_match(/up\s+\d{3,}\s+Add email to users/, output)
@@ -158,27 +158,29 @@ module ApplicationTests
add_to_config('config.active_record.dump_schema_after_migration = false')
Dir.chdir(app_path) do
- `rails generate model book title:string;
- bundle exec rake db:migrate`
+ `bin/rails generate model book title:string`
+ output = `bin/rails generate model author name:string`
+ version = output =~ %r{[^/]+db/migrate/(\d+)_create_authors\.rb} && $1
- assert !File.exist?("db/schema.rb")
+ `bin/rake db:migrate db:rollback db:forward db:migrate:up db:migrate:down VERSION=#{version}`
+ assert !File.exist?("db/schema.rb"), "should not dump schema when configured not to"
end
add_to_config('config.active_record.dump_schema_after_migration = true')
Dir.chdir(app_path) do
- `rails generate model author name:string;
- bundle exec rake db:migrate`
+ `bin/rails generate model reviews book_id:integer`
+ `bin/rake db:migrate`
structure_dump = File.read("db/schema.rb")
- assert_match(/create_table "authors"/, structure_dump)
+ assert_match(/create_table "reviews"/, structure_dump)
end
end
test 'default schema generation after migration' do
Dir.chdir(app_path) do
- `rails generate model book title:string;
- bundle exec rake db:migrate`
+ `bin/rails generate model book title:string;
+ bin/rake db:migrate`
structure_dump = File.read("db/schema.rb")
assert_match(/create_table "books"/, structure_dump)
@@ -187,12 +189,12 @@ module ApplicationTests
test 'test migration status migrated file is deleted' do
Dir.chdir(app_path) do
- `rails generate model user username:string password:string;
- rails generate migration add_email_to_users email:string;
- rake db:migrate
+ `bin/rails generate model user username:string password:string;
+ bin/rails generate migration add_email_to_users email:string;
+ bin/rake db:migrate
rm db/migrate/*email*.rb`
- output = `rake db:migrate:status`
+ output = `bin/rake db:migrate:status`
File.write('test.txt', output)
assert_match(/up\s+\d{14}\s+Create users/, output)
diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb
index 95087bf29f..c87515f00f 100644
--- a/railties/test/application/rake/notes_test.rb
+++ b/railties/test/application/rake/notes_test.rb
@@ -74,7 +74,7 @@ module ApplicationTests
app_file "some_other_dir/blah.rb", "# TODO: note in some_other directory"
- run_rake_notes "SOURCE_ANNOTATION_DIRECTORIES='some_other_dir' bundle exec rake notes" do |output, lines|
+ run_rake_notes "SOURCE_ANNOTATION_DIRECTORIES='some_other_dir' bin/rake notes" do |output, lines|
assert_match(/note in app directory/, output)
assert_match(/note in config directory/, output)
assert_match(/note in db directory/, output)
@@ -102,7 +102,7 @@ module ApplicationTests
end
EOS
- run_rake_notes "bundle exec rake notes_custom" do |output, lines|
+ run_rake_notes "bin/rake notes_custom" do |output, lines|
assert_match(/\[FIXME\] note in lib directory/, output)
assert_match(/\[TODO\] note in test directory/, output)
assert_no_match(/OPTIMIZE/, output)
@@ -114,6 +114,7 @@ module ApplicationTests
end
test 'register a new extension' do
+ add_to_config "config.assets.precompile = []"
add_to_config %q{ config.annotations.register_extensions("scss", "sass") { |annotation| /\/\/\s*(#{annotation}):?\s*(.*)$/ } }
app_file "app/assets/stylesheets/application.css.scss", "// TODO: note in scss"
app_file "app/assets/stylesheets/application.css.sass", "// TODO: note in sass"
@@ -127,7 +128,7 @@ module ApplicationTests
private
- def run_rake_notes(command = 'bundle exec rake notes')
+ def run_rake_notes(command = 'bin/rake notes')
boot_rails
load_tasks
diff --git a/railties/test/application/rake/restart_test.rb b/railties/test/application/rake/restart_test.rb
index 35099913fb..4cae199e6b 100644
--- a/railties/test/application/rake/restart_test.rb
+++ b/railties/test/application/rake/restart_test.rb
@@ -13,12 +13,12 @@ module ApplicationTests
def teardown
teardown_app
end
-
+
test 'rake restart touches tmp/restart.txt' do
Dir.chdir(app_path) do
`rake restart`
assert File.exist?("tmp/restart.txt")
-
+
prev_mtime = File.mtime("tmp/restart.txt")
sleep(1)
`rake restart`
@@ -26,6 +26,14 @@ module ApplicationTests
assert_not_equal prev_mtime, curr_mtime
end
end
+
+ test 'rake restart should work even if tmp folder does not exist' do
+ Dir.chdir(app_path) do
+ FileUtils.remove_dir('tmp')
+ `rake restart`
+ assert File.exist?('tmp/restart.txt')
+ end
+ end
end
end
end
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index 0648b11813..a040dd4cf6 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -36,7 +36,7 @@ module ApplicationTests
Rails.application.initialize!
RUBY
- assert_match("SuperMiddleware", Dir.chdir(app_path){ `rake middleware` })
+ assert_match("SuperMiddleware", Dir.chdir(app_path){ `bin/rake middleware` })
end
def test_initializers_are_executed_in_rake_tasks
@@ -51,7 +51,7 @@ module ApplicationTests
end
RUBY
- output = Dir.chdir(app_path){ `rake do_nothing` }
+ output = Dir.chdir(app_path){ `bin/rake do_nothing` }
assert_match "Doing something...", output
end
@@ -72,7 +72,7 @@ module ApplicationTests
end
RUBY
- output = Dir.chdir(app_path) { `rake do_nothing` }
+ output = Dir.chdir(app_path) { `bin/rake do_nothing` }
assert_match 'Hello world', output
end
@@ -93,14 +93,14 @@ module ApplicationTests
RUBY
Dir.chdir(app_path) do
- assert system('rake do_nothing RAILS_ENV=production'),
+ assert system('bin/rake do_nothing RAILS_ENV=production'),
'should not be pre-required for rake even eager_load=true'
end
end
def test_code_statistics_sanity
assert_match "Code LOC: 7 Test LOC: 0 Code to Test Ratio: 1:0.0",
- Dir.chdir(app_path){ `rake stats` }
+ Dir.chdir(app_path){ `bin/rake stats` }
end
def test_rake_routes_calls_the_route_inspector
@@ -110,7 +110,7 @@ module ApplicationTests
end
RUBY
- output = Dir.chdir(app_path){ `rake routes` }
+ output = Dir.chdir(app_path){ `bin/rake routes` }
assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
end
@@ -123,7 +123,7 @@ module ApplicationTests
RUBY
ENV['CONTROLLER'] = 'cart'
- output = Dir.chdir(app_path){ `rake routes` }
+ output = Dir.chdir(app_path){ `bin/rake routes` }
assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
end
@@ -133,7 +133,7 @@ module ApplicationTests
end
RUBY
- assert_equal <<-MESSAGE.strip_heredoc, Dir.chdir(app_path){ `rake routes` }
+ assert_equal <<-MESSAGE.strip_heredoc, Dir.chdir(app_path){ `bin/rake routes` }
You don't have any routes defined!
Please add some routes in config/routes.rb.
@@ -151,21 +151,21 @@ module ApplicationTests
end
RUBY
- output = Dir.chdir(app_path){ `rake log_something RAILS_ENV=production && cat log/production.log` }
+ output = Dir.chdir(app_path){ `bin/rake log_something RAILS_ENV=production && cat log/production.log` }
assert_match "Sample log message", output
end
def test_loading_specific_fixtures
Dir.chdir(app_path) do
- `rails generate model user username:string password:string;
- rails generate model product name:string;
- rake db:migrate`
+ `bin/rails generate model user username:string password:string;
+ bin/rails generate model product name:string;
+ bin/rake db:migrate`
end
require "#{rails_root}/config/environment"
# loading a specific fixture
- errormsg = Dir.chdir(app_path) { `rake db:fixtures:load FIXTURES=products` }
+ errormsg = Dir.chdir(app_path) { `bin/rake db:fixtures:load FIXTURES=products` }
assert $?.success?, errormsg
assert_equal 2, ::AppTemplate::Application::Product.count
@@ -174,23 +174,42 @@ module ApplicationTests
def test_loading_only_yml_fixtures
Dir.chdir(app_path) do
- `rake db:migrate`
+ `bin/rake db:migrate`
end
app_file "test/fixtures/products.csv", ""
require "#{rails_root}/config/environment"
- errormsg = Dir.chdir(app_path) { `rake db:fixtures:load` }
+ errormsg = Dir.chdir(app_path) { `bin/rake db:fixtures:load` }
assert $?.success?, errormsg
end
def test_scaffold_tests_pass_by_default
output = Dir.chdir(app_path) do
- `rails generate scaffold user username:string password:string;
- bundle exec rake db:migrate test`
+ `bin/rails generate scaffold user username:string password:string;
+ bin/rake db:migrate test`
end
- assert_match(/7 runs, 13 assertions, 0 failures, 0 errors/, output)
+ assert_match(/7 runs, 12 assertions, 0 failures, 0 errors/, output)
+ assert_no_match(/Errors running/, output)
+ end
+
+ def test_api_scaffold_tests_pass_by_default
+ add_to_config <<-RUBY
+ config.api_only = true
+ RUBY
+
+ app_file "app/controllers/application_controller.rb", <<-RUBY
+ class ApplicationController < ActionController::API
+ end
+ RUBY
+
+ output = Dir.chdir(app_path) do
+ `bin/rails generate scaffold user username:string password:string;
+ bin/rake db:migrate test`
+ end
+
+ assert_match(/5 runs, 7 assertions, 0 failures, 0 errors/, output)
assert_no_match(/Errors running/, output)
end
@@ -199,20 +218,20 @@ module ApplicationTests
"Rails.application.config.active_record.belongs_to_required_by_default = false"
output = Dir.chdir(app_path) do
- `rails generate scaffold LineItems product:references cart:belongs_to;
- bundle exec rake db:migrate test`
+ `bin/rails generate scaffold LineItems product:references cart:belongs_to;
+ bin/rake db:migrate test`
end
- assert_match(/7 runs, 13 assertions, 0 failures, 0 errors/, output)
+ assert_match(/7 runs, 12 assertions, 0 failures, 0 errors/, output)
assert_no_match(/Errors running/, output)
end
def test_db_test_clone_when_using_sql_format
add_to_config "config.active_record.schema_format = :sql"
output = Dir.chdir(app_path) do
- `rails generate scaffold user username:string;
- bundle exec rake db:migrate;
- bundle exec rake db:test:clone 2>&1 --trace`
+ `bin/rails generate scaffold user username:string;
+ bin/rake db:migrate;
+ bin/rake db:test:clone 2>&1 --trace`
end
assert_match(/Execute db:test:clone_structure/, output)
end
@@ -220,9 +239,9 @@ module ApplicationTests
def test_db_test_prepare_when_using_sql_format
add_to_config "config.active_record.schema_format = :sql"
output = Dir.chdir(app_path) do
- `rails generate scaffold user username:string;
- bundle exec rake db:migrate;
- bundle exec rake db:test:prepare 2>&1 --trace`
+ `bin/rails generate scaffold user username:string;
+ bin/rake db:migrate;
+ bin/rake db:test:prepare 2>&1 --trace`
end
assert_match(/Execute db:test:load_structure/, output)
end
@@ -230,7 +249,7 @@ module ApplicationTests
def test_rake_dump_structure_should_respect_db_structure_env_variable
Dir.chdir(app_path) do
# ensure we have a schema_migrations table to dump
- `bundle exec rake db:migrate db:structure:dump SCHEMA=db/my_structure.sql`
+ `bin/rake db:migrate db:structure:dump SCHEMA=db/my_structure.sql`
end
assert File.exist?(File.join(app_path, 'db', 'my_structure.sql'))
end
@@ -239,8 +258,8 @@ module ApplicationTests
add_to_config "config.active_record.schema_format = :sql"
output = Dir.chdir(app_path) do
- `rails g model post title:string;
- bundle exec rake db:migrate:redo 2>&1 --trace;`
+ `bin/rails g model post title:string;
+ bin/rake db:migrate:redo 2>&1 --trace;`
end
# expect only Invoke db:structure:dump (first_time)
@@ -249,23 +268,23 @@ module ApplicationTests
def test_rake_dump_schema_cache
Dir.chdir(app_path) do
- `rails generate model post title:string;
- rails generate model product name:string;
- bundle exec rake db:migrate db:schema:cache:dump`
+ `bin/rails generate model post title:string;
+ bin/rails generate model product name:string;
+ bin/rake db:migrate db:schema:cache:dump`
end
assert File.exist?(File.join(app_path, 'db', 'schema_cache.dump'))
end
def test_rake_clear_schema_cache
Dir.chdir(app_path) do
- `bundle exec rake db:schema:cache:dump db:schema:cache:clear`
+ `bin/rake db:schema:cache:dump db:schema:cache:clear`
end
assert !File.exist?(File.join(app_path, 'db', 'schema_cache.dump'))
end
def test_copy_templates
Dir.chdir(app_path) do
- `bundle exec rake rails:templates:copy`
+ `bin/rake rails:templates:copy`
%w(controller mailer scaffold).each do |dir|
assert File.exist?(File.join(app_path, 'lib', 'templates', 'erb', dir))
end
@@ -280,10 +299,17 @@ module ApplicationTests
app_file "template.rb", ""
output = Dir.chdir(app_path) do
- `bundle exec rake rails:template LOCATION=template.rb`
+ `bin/rake rails:template LOCATION=template.rb`
end
assert_match(/Hello, World!/, output)
end
+
+ def test_tmp_clear_should_work_if_folder_missing
+ FileUtils.remove_dir("#{app_path}/tmp")
+ errormsg = Dir.chdir(app_path) { `bin/rake tmp:clear` }
+ assert_predicate $?, :success?
+ assert_empty errormsg
+ end
end
end
diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb
index 6595c40f8b..0c180339b4 100644
--- a/railties/test/application/runner_test.rb
+++ b/railties/test/application/runner_test.rb
@@ -25,15 +25,15 @@ module ApplicationTests
end
def test_should_include_runner_in_shebang_line_in_help_without_option
- assert_match "/rails runner", Dir.chdir(app_path) { `bundle exec rails runner` }
+ assert_match "/rails runner", Dir.chdir(app_path) { `bin/rails runner` }
end
def test_should_include_runner_in_shebang_line_in_help
- assert_match "/rails runner", Dir.chdir(app_path) { `bundle exec rails runner --help` }
+ assert_match "/rails runner", Dir.chdir(app_path) { `bin/rails runner --help` }
end
def test_should_run_ruby_statement
- assert_match "42", Dir.chdir(app_path) { `bundle exec rails runner "puts User.count"` }
+ assert_match "42", Dir.chdir(app_path) { `bin/rails runner "puts User.count"` }
end
def test_should_run_file
@@ -41,7 +41,7 @@ module ApplicationTests
puts User.count
SCRIPT
- assert_match "42", Dir.chdir(app_path) { `bundle exec rails runner "bin/count_users.rb"` }
+ assert_match "42", Dir.chdir(app_path) { `bin/rails runner "bin/count_users.rb"` }
end
def test_should_set_dollar_0_to_file
@@ -49,7 +49,7 @@ module ApplicationTests
puts $0
SCRIPT
- assert_match "bin/dollar0.rb", Dir.chdir(app_path) { `bundle exec rails runner "bin/dollar0.rb"` }
+ assert_match "bin/dollar0.rb", Dir.chdir(app_path) { `bin/rails runner "bin/dollar0.rb"` }
end
def test_should_set_dollar_program_name_to_file
@@ -57,7 +57,7 @@ module ApplicationTests
puts $PROGRAM_NAME
SCRIPT
- assert_match "bin/program_name.rb", Dir.chdir(app_path) { `bundle exec rails runner "bin/program_name.rb"` }
+ assert_match "bin/program_name.rb", Dir.chdir(app_path) { `bin/rails runner "bin/program_name.rb"` }
end
def test_with_hook
@@ -67,22 +67,22 @@ module ApplicationTests
end
RUBY
- assert_match "true", Dir.chdir(app_path) { `bundle exec rails runner "puts Rails.application.config.ran"` }
+ assert_match "true", Dir.chdir(app_path) { `bin/rails runner "puts Rails.application.config.ran"` }
end
def test_default_environment
- assert_match "development", Dir.chdir(app_path) { `bundle exec rails runner "puts Rails.env"` }
+ assert_match "development", Dir.chdir(app_path) { `bin/rails runner "puts Rails.env"` }
end
def test_environment_with_rails_env
with_rails_env "production" do
- assert_match "production", Dir.chdir(app_path) { `bundle exec rails runner "puts Rails.env"` }
+ assert_match "production", Dir.chdir(app_path) { `bin/rails runner "puts Rails.env"` }
end
end
def test_environment_with_rack_env
with_rack_env "production" do
- assert_match "production", Dir.chdir(app_path) { `bundle exec rails runner "puts Rails.env"` }
+ assert_match "production", Dir.chdir(app_path) { `bin/rails runner "puts Rails.env"` }
end
end
end
diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb
index c122b315c0..494e6dd7bd 100644
--- a/railties/test/application/test_runner_test.rb
+++ b/railties/test/application/test_runner_test.rb
@@ -1,9 +1,10 @@
require 'isolation/abstract_unit'
require 'active_support/core_ext/string/strip'
+require 'env_helpers'
module ApplicationTests
class TestRunnerTest < ActiveSupport::TestCase
- include ActiveSupport::Testing::Isolation
+ include ActiveSupport::Testing::Isolation, EnvHelpers
def setup
build_app
@@ -14,20 +15,6 @@ module ApplicationTests
teardown_app
end
- def test_run_in_test_environment
- app_file 'test/unit/env_test.rb', <<-RUBY
- require 'test_helper'
-
- class EnvTest < ActiveSupport::TestCase
- def test_env
- puts "Current Environment: \#{Rails.env}"
- end
- end
- RUBY
-
- assert_match "Current Environment: test", run_test_command('test/unit/env_test.rb')
- end
-
def test_run_single_file
create_test_file :models, 'foo'
create_test_file :models, 'bar'
@@ -187,7 +174,7 @@ module ApplicationTests
end
RUBY
- run_test_command('-p rikka test/unit/chu_2_koi_test.rb').tap do |output|
+ run_test_command('-n /rikka/ test/unit/chu_2_koi_test.rb').tap do |output|
assert_match "Rikka", output
assert_no_match "Sanae", output
end
@@ -229,24 +216,128 @@ module ApplicationTests
assert_match "development", run_test_command('test/unit/env_test.rb')
end
+ def test_run_in_test_environment_by_default
+ create_env_test
+
+ assert_match "Current Environment: test", run_test_command('test/unit/env_test.rb')
+ end
+
def test_run_different_environment
- env = "development"
- app_file 'test/unit/env_test.rb', <<-RUBY
+ create_env_test
+
+ assert_match "Current Environment: development",
+ run_test_command("-e development test/unit/env_test.rb")
+ end
+
+ def test_generated_scaffold_works_with_rails_test
+ create_scaffold
+ assert_match "0 failures, 0 errors, 0 skips", run_test_command('')
+ end
+
+ def test_run_multiple_folders
+ create_test_file :models, 'account'
+ create_test_file :controllers, 'accounts_controller'
+
+ run_test_command('test/models test/controllers').tap do |output|
+ assert_match 'AccountTest', output
+ assert_match 'AccountsControllerTest', output
+ assert_match '2 runs, 2 assertions, 0 failures, 0 errors, 0 skips', output
+ end
+ end
+
+ def test_run_with_ruby_command
+ app_file 'test/models/post_test.rb', <<-RUBY
require 'test_helper'
- class EnvTest < ActiveSupport::TestCase
- def test_env
- puts Rails.env
+ class PostTest < ActiveSupport::TestCase
+ test 'declarative syntax works' do
+ puts 'PostTest'
+ assert true
end
end
RUBY
- assert_match env, run_test_command("-e #{env} test/unit/env_test.rb")
+ Dir.chdir(app_path) do
+ `ruby -Itest test/models/post_test.rb`.tap do |output|
+ assert_match 'PostTest', output
+ assert_no_match 'is already defined in', output
+ end
+ end
end
- def test_generated_scaffold_works_with_rails_test
- create_scaffold
- assert_match "0 failures, 0 errors, 0 skips", run_test_command('')
+ def test_mix_files_and_line_filters
+ create_test_file :models, 'account'
+ app_file 'test/models/post_test.rb', <<-RUBY
+ require 'test_helper'
+
+ class PostTest < ActiveSupport::TestCase
+ def test_post
+ puts 'PostTest'
+ assert true
+ end
+
+ def test_line_filter_does_not_run_this
+ assert true
+ end
+ end
+ RUBY
+
+ run_test_command('test/models/account_test.rb test/models/post_test.rb:4').tap do |output|
+ assert_match 'AccountTest', output
+ assert_match 'PostTest', output
+ assert_match '2 runs, 2 assertions', output
+ end
+ end
+
+ def test_multiple_line_filters
+ create_test_file :models, 'account'
+ create_test_file :models, 'post'
+
+ run_test_command('test/models/account_test.rb:4 test/models/post_test.rb:4').tap do |output|
+ assert_match 'AccountTest', output
+ assert_match 'PostTest', output
+ end
+ end
+
+ def test_line_filter_without_line_runs_all_tests
+ create_test_file :models, 'account'
+
+ run_test_command('test/models/account_test.rb:').tap do |output|
+ assert_match 'AccountTest', output
+ end
+ end
+
+ def test_shows_filtered_backtrace_by_default
+ create_backtrace_test
+
+ assert_match 'Rails::BacktraceCleaner', run_test_command('test/unit/backtrace_test.rb')
+ end
+
+ def test_backtrace_option
+ create_backtrace_test
+
+ assert_match 'Minitest::BacktraceFilter', run_test_command('test/unit/backtrace_test.rb -b')
+ assert_match 'Minitest::BacktraceFilter',
+ run_test_command('test/unit/backtrace_test.rb --backtrace')
+ end
+
+ def test_show_full_backtrace_using_backtrace_environment_variable
+ create_backtrace_test
+
+ switch_env 'BACKTRACE', 'true' do
+ assert_match 'Minitest::BacktraceFilter', run_test_command('test/unit/backtrace_test.rb')
+ end
+ end
+
+ def test_run_app_without_rails_loaded
+ # Simulate a real Rails app boot.
+ app_file 'config/boot.rb', <<-RUBY
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
+
+ require 'bundler/setup' # Set up gems listed in the Gemfile.
+ RUBY
+
+ assert_match '0 runs, 0 assertions', run_test_command('')
end
private
@@ -284,6 +375,18 @@ module ApplicationTests
RUBY
end
+ def create_backtrace_test
+ app_file 'test/unit/backtrace_test.rb', <<-RUBY
+ require 'test_helper'
+
+ class BacktraceTest < ActiveSupport::TestCase
+ def test_backtrace
+ puts Minitest.backtrace_filter
+ end
+ end
+ RUBY
+ end
+
def create_schema
app_file 'db/schema.rb', ''
end
@@ -301,6 +404,18 @@ module ApplicationTests
RUBY
end
+ def create_env_test
+ app_file 'test/unit/env_test.rb', <<-RUBY
+ require 'test_helper'
+
+ class EnvTest < ActiveSupport::TestCase
+ def test_env
+ puts "Current Environment: \#{Rails.env}"
+ end
+ end
+ RUBY
+ end
+
def create_scaffold
script 'generate scaffold user name:string'
Dir.chdir(app_path) { File.exist?('app/models/user.rb') }
@@ -308,7 +423,7 @@ module ApplicationTests
end
def run_migration
- Dir.chdir(app_path) { `bundle exec rake db:migrate` }
+ Dir.chdir(app_path) { `bin/rake db:migrate` }
end
end
end
diff --git a/railties/test/application/test_test.rb b/railties/test/application/test_test.rb
index 61652e5052..0e997f4ba7 100644
--- a/railties/test/application/test_test.rb
+++ b/railties/test/application/test_test.rb
@@ -44,7 +44,7 @@ module ApplicationTests
def test_index
get '/posts'
assert_response :success
- assert_template "index"
+ assert_includes @response.body, 'Posts#index'
end
end
RUBY
@@ -64,8 +64,8 @@ module ApplicationTests
RUBY
output = run_test_file('unit/failing_test.rb', env: { "BACKTRACE" => "1" })
- assert_match %r{/app/test/unit/failing_test\.rb}, output
- assert_match %r{/app/test/unit/failing_test\.rb:4}, output
+ assert_match %r{test/unit/failing_test\.rb}, output
+ assert_match %r{test/unit/failing_test\.rb:4}, output
end
test "ruby schema migrations" do
diff --git a/railties/test/code_statistics_test.rb b/railties/test/code_statistics_test.rb
new file mode 100644
index 0000000000..1b1ff80bc1
--- /dev/null
+++ b/railties/test/code_statistics_test.rb
@@ -0,0 +1,20 @@
+require 'abstract_unit'
+require 'rails/code_statistics'
+
+class CodeStatisticsTest < ActiveSupport::TestCase
+ def setup
+ @tmp_path = File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'tmp'))
+ @dir_js = File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'tmp', 'lib.js'))
+ FileUtils.mkdir_p(@dir_js)
+ end
+
+ def teardown
+ FileUtils.rm_rf(@tmp_path)
+ end
+
+ test 'ignores directories that happen to have source files extensions' do
+ assert_nothing_raised do
+ @code_statistics = CodeStatistics.new(['tmp dir', @tmp_path])
+ end
+ end
+end
diff --git a/railties/test/commands/dbconsole_test.rb b/railties/test/commands/dbconsole_test.rb
index a3cd1eb0ed..7950ed6aa7 100644
--- a/railties/test/commands/dbconsole_test.rb
+++ b/railties/test/commands/dbconsole_test.rb
@@ -99,19 +99,15 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
end
def test_rails_env_is_development_when_argument_is_dev
- dbconsole = Rails::DBConsole.new
-
- dbconsole.stub(:available_environments, ['development', 'test']) do
- options = dbconsole.send(:parse_arguments, ['dev'])
+ Rails::DBConsole.stub(:available_environments, ['development', 'test']) do
+ options = Rails::DBConsole.send(:parse_arguments, ['dev'])
assert_match('development', options[:environment])
end
end
def test_rails_env_is_dev_when_argument_is_dev_and_dev_env_is_present
- dbconsole = Rails::DBConsole.new
-
- dbconsole.stub(:available_environments, ['dev']) do
- options = dbconsole.send(:parse_arguments, ['dev'])
+ Rails::DBConsole.stub(:available_environments, ['dev']) do
+ options = Rails::DBConsole.send(:parse_arguments, ['dev'])
assert_match('dev', options[:environment])
end
end
@@ -158,12 +154,6 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
assert_equal 'q1w2e3', ENV['PGPASSWORD']
end
- def test_sqlite
- start(adapter: 'sqlite', database: 'db')
- assert !aborted
- assert_equal ['sqlite', 'db'], dbconsole.find_cmd_and_exec_args
- end
-
def test_sqlite3
start(adapter: 'sqlite3', database: 'db.sqlite3')
assert !aborted
diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb
index ba688f1e9e..7a063aeddf 100644
--- a/railties/test/commands/server_test.rb
+++ b/railties/test/commands/server_test.rb
@@ -44,6 +44,22 @@ class Rails::ServerTest < ActiveSupport::TestCase
end
end
+ def test_caching_without_option
+ args = []
+ options = Rails::Server::Options.new.parse!(args)
+ assert_equal nil, options[:caching]
+ end
+
+ def test_caching_with_option
+ args = ["--dev-caching"]
+ options = Rails::Server::Options.new.parse!(args)
+ assert_equal true, options[:caching]
+
+ args = ["--no-dev-caching"]
+ options = Rails::Server::Options.new.parse!(args)
+ assert_equal false, options[:caching]
+ end
+
def test_log_stdout
with_rack_env nil do
with_rails_env nil do
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index c0b88089b3..2857dae07e 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -1,7 +1,7 @@
require 'generators/generators_test_helper'
require 'rails/generators/rails/app/app_generator'
require 'env_helpers'
-require 'mocha/setup' # FIXME: stop using mocha
+require 'minitest/mock'
class ActionsTest < Rails::Generators::TestCase
include GeneratorsTestHelper
@@ -12,11 +12,13 @@ class ActionsTest < Rails::Generators::TestCase
def setup
Rails.application = TestApp::Application
+ @mock_generator = Minitest::Mock.new
super
end
def teardown
Rails.application = TestApp::Application.instance
+ @mock_generator.verify
end
def test_invoke_other_generator_with_shortcut
@@ -45,6 +47,14 @@ class ActionsTest < Rails::Generators::TestCase
assert_file 'Gemfile', /source 'http:\/\/gems\.github\.com'/
end
+ def test_add_source_with_block_adds_source_to_gemfile_with_gem
+ run_generator
+ action :add_source, 'http://gems.github.com' do
+ gem 'rspec-rails'
+ end
+ assert_file 'Gemfile', /source 'http:\/\/gems\.github\.com' do\n gem 'rspec-rails'\nend/
+ end
+
def test_gem_should_put_gem_dependency_in_gemfile
run_generator
action :gem, 'will-paginate'
@@ -140,13 +150,18 @@ class ActionsTest < Rails::Generators::TestCase
end
def test_git_with_symbol_should_run_command_using_git_scm
- generator.expects(:run).once.with('git init')
- action :git, :init
+ @mock_generator.expect(:call, nil, ['git init'])
+ generator.stub(:run, @mock_generator) do
+ action :git, :init
+ end
end
def test_git_with_hash_should_run_each_command_using_git_scm
- generator.expects(:run).times(2)
- action :git, rm: 'README', add: '.'
+ @mock_generator.expect(:call, nil, ["git rm README"])
+ @mock_generator.expect(:call, nil, ["git add ."])
+ generator.stub(:run, @mock_generator) do
+ action :git, rm: 'README', add: '.'
+ end
end
def test_vendor_should_write_data_to_file_in_vendor
@@ -170,46 +185,60 @@ class ActionsTest < Rails::Generators::TestCase
end
def test_generate_should_run_script_generate_with_argument_and_options
- generator.expects(:run_ruby_script).once.with('bin/rails generate model MyModel', verbose: false)
- action :generate, 'model', 'MyModel'
+ @mock_generator.expect(:call, nil, ['bin/rails generate model MyModel', verbose: false])
+ generator.stub(:run_ruby_script, @mock_generator) do
+ action :generate, 'model', 'MyModel'
+ end
end
def test_rake_should_run_rake_command_with_default_env
- generator.expects(:run).once.with("rake log:clear RAILS_ENV=development", verbose: false)
- with_rails_env nil do
- action :rake, 'log:clear'
+ @mock_generator.expect(:call, nil, ["rake log:clear RAILS_ENV=development", verbose: false])
+ generator.stub(:run, @mock_generator) do
+ with_rails_env nil do
+ action :rake, 'log:clear'
+ end
end
end
def test_rake_with_env_option_should_run_rake_command_in_env
- generator.expects(:run).once.with('rake log:clear RAILS_ENV=production', verbose: false)
- action :rake, 'log:clear', env: 'production'
+ @mock_generator.expect(:call, nil, ['rake log:clear RAILS_ENV=production', verbose: false])
+ generator.stub(:run, @mock_generator) do
+ action :rake, 'log:clear', env: 'production'
+ end
end
def test_rake_with_rails_env_variable_should_run_rake_command_in_env
- generator.expects(:run).once.with('rake log:clear RAILS_ENV=production', verbose: false)
- with_rails_env "production" do
- action :rake, 'log:clear'
+ @mock_generator.expect(:call, nil, ['rake log:clear RAILS_ENV=production', verbose: false])
+ generator.stub(:run, @mock_generator) do
+ with_rails_env "production" do
+ action :rake, 'log:clear'
+ end
end
end
def test_env_option_should_win_over_rails_env_variable_when_running_rake
- generator.expects(:run).once.with('rake log:clear RAILS_ENV=production', verbose: false)
- with_rails_env "staging" do
- action :rake, 'log:clear', env: 'production'
+ @mock_generator.expect(:call, nil, ['rake log:clear RAILS_ENV=production', verbose: false])
+ generator.stub(:run, @mock_generator) do
+ with_rails_env "staging" do
+ action :rake, 'log:clear', env: 'production'
+ end
end
end
def test_rake_with_sudo_option_should_run_rake_command_with_sudo
- generator.expects(:run).once.with("sudo rake log:clear RAILS_ENV=development", verbose: false)
- with_rails_env nil do
- action :rake, 'log:clear', sudo: true
+ @mock_generator.expect(:call, nil, ["sudo rake log:clear RAILS_ENV=development", verbose: false])
+ generator.stub(:run, @mock_generator) do
+ with_rails_env nil do
+ action :rake, 'log:clear', sudo: true
+ end
end
end
def test_capify_should_run_the_capify_command
- generator.expects(:run).once.with('capify .', verbose: false)
- action :capify!
+ @mock_generator.expect(:call, nil, ['capify .', verbose: false])
+ generator.stub(:run, @mock_generator) do
+ action :capify!
+ end
end
def test_route_should_add_data_to_the_routes_block_in_config_routes
@@ -245,15 +274,19 @@ F
def test_readme
run_generator
- Rails::Generators::AppGenerator.expects(:source_root).times(2).returns(destination_root)
- assert_match "application up and running", action(:readme, "README.md")
+ 2.times { @mock_generator.expect(:call, destination_root,[]) }
+ Rails::Generators::AppGenerator.stub(:source_root, @mock_generator) do
+ assert_match "application up and running", action(:readme, "README.md")
+ end
end
def test_readme_with_quiet
generator(default_arguments, quiet: true)
run_generator
- Rails::Generators::AppGenerator.expects(:source_root).times(2).returns(destination_root)
- assert_no_match "application up and running", action(:readme, "README.md")
+ 2.times { @mock_generator.expect(:call, destination_root,[]) }
+ Rails::Generators::AppGenerator.stub(:source_root, @mock_generator) do
+ assert_no_match "application up and running", action(:readme, "README.md")
+ end
end
def test_log
diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb
new file mode 100644
index 0000000000..998da3ef84
--- /dev/null
+++ b/railties/test/generators/api_app_generator_test.rb
@@ -0,0 +1,97 @@
+require 'generators/generators_test_helper'
+require 'rails/generators/rails/app/app_generator'
+
+class ApiAppGeneratorTest < Rails::Generators::TestCase
+ include GeneratorsTestHelper
+ tests Rails::Generators::AppGenerator
+
+ arguments [destination_root, '--api']
+
+ def setup
+ Rails.application = TestApp::Application
+ super
+
+ Kernel::silence_warnings do
+ Thor::Base.shell.send(:attr_accessor, :always_force)
+ @shell = Thor::Base.shell.new
+ @shell.send(:always_force=, true)
+ end
+ end
+
+ def teardown
+ super
+ Rails.application = TestApp::Application.instance
+ end
+
+ def test_skeleton_is_created
+ run_generator
+
+ default_files.each { |path| assert_file path }
+ skipped_files.each { |path| assert_no_file path }
+ end
+
+ def test_api_modified_files
+ run_generator
+
+ 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 'jbuilder'/, content)
+ assert_no_match(/gem 'web-console'/, content)
+ assert_match(/gem 'active_model_serializers'/, content)
+ end
+
+ assert_file "config/application.rb" do |content|
+ assert_match(/config.api_only = true/, content)
+ end
+
+ assert_file "config/initializers/cors.rb"
+
+ assert_file "config/initializers/wrap_parameters.rb"
+
+ assert_file "app/controllers/application_controller.rb", /ActionController::API/
+ end
+
+ private
+
+ def default_files
+ files = %W(
+ .gitignore
+ Gemfile
+ Rakefile
+ config.ru
+ app/controllers
+ app/mailers
+ app/models
+ config/environments
+ config/initializers
+ config/locales
+ db
+ lib
+ lib/tasks
+ log
+ test/fixtures
+ test/controllers
+ test/integration
+ test/models
+ tmp
+ vendor
+ )
+ files.concat %w(bin/bundle bin/rails bin/rake)
+ files
+ end
+
+ def skipped_files
+ %w(app/assets
+ app/helpers
+ app/views
+ config/initializers/assets.rb
+ config/initializers/cookies_serializer.rb
+ config/initializers/session_store.rb
+ lib/assets
+ vendor/assets
+ test/helpers
+ tmp/cache/assets)
+ end
+end
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 282e8cc4f9..daf362357c 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -44,6 +44,7 @@ DEFAULT_APP_FILES = %w(
vendor/assets
vendor/assets/stylesheets
vendor/assets/javascripts
+ tmp
tmp/cache
tmp/cache/assets
)
@@ -498,7 +499,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
def test_application_name_with_spaces
- path = File.join(destination_root, "foo bar".shellescape)
+ path = File.join(destination_root, "foo bar")
# This also applies to MySQL apps but not with SQLite
run_generator [path, "-d", 'postgresql']
@@ -516,7 +517,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
run_generator [destination_root, "--dev"]
assert_file "Gemfile" do |content|
- assert_match(/gem 'web-console',\s+github: "rails\/web-console"/, content)
+ assert_match(/gem 'web-console',\s+github: 'rails\/web-console'/, content)
assert_no_match(/gem 'web-console', '~> 2.0'/, content)
end
end
@@ -525,7 +526,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
run_generator [destination_root, "--edge"]
assert_file "Gemfile" do |content|
- assert_match(/gem 'web-console',\s+github: "rails\/web-console"/, content)
+ assert_match(/gem 'web-console',\s+github: 'rails\/web-console'/, content)
assert_no_match(/gem 'web-console', '~> 2.0'/, content)
end
end
@@ -560,6 +561,14 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_spring_with_dev_option
+ run_generator [destination_root, "--dev"]
+
+ assert_file "Gemfile" do |content|
+ assert_no_match(/spring/, content)
+ end
+ end
+
def test_generator_if_skip_turbolinks_is_given
run_generator [destination_root, "--skip-turbolinks"]
@@ -598,6 +607,32 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_create_keeps
+ run_generator
+ folders_with_keep = %w(
+ app/assets/images
+ app/mailers
+ app/models
+ app/controllers/concerns
+ app/models/concerns
+ lib/tasks
+ lib/assets
+ log
+ test/fixtures
+ test/fixtures/files
+ test/controllers
+ test/mailers
+ test/models
+ test/helpers
+ test/integration
+ tmp
+ vendor/assets/stylesheets
+ )
+ folders_with_keep.each do |folder|
+ assert_file("#{folder}/.keep")
+ end
+ end
+
def test_psych_gem
run_generator
gem_regex = /gem 'psych',\s+'~> 2.0',\s+platforms: :rbx/
diff --git a/railties/test/generators/controller_generator_test.rb b/railties/test/generators/controller_generator_test.rb
index a7d56dd352..1351151afb 100644
--- a/railties/test/generators/controller_generator_test.rb
+++ b/railties/test/generators/controller_generator_test.rb
@@ -96,6 +96,8 @@ class ControllerGeneratorTest < Rails::Generators::TestCase
def test_namespaced_routes_are_created_in_routes
run_generator ["admin/dashboard", "index"]
- assert_file "config/routes.rb", /namespace :admin do\n\s+get 'dashboard\/index'\n/
+ assert_file "config/routes.rb" do |route|
+ assert_match(/^ namespace :admin do\n get 'dashboard\/index'\n end$/, route)
+ end
end
end
diff --git a/railties/test/generators/named_base_test.rb b/railties/test/generators/named_base_test.rb
index 18a26fde05..1c32fc1bfd 100644
--- a/railties/test/generators/named_base_test.rb
+++ b/railties/test/generators/named_base_test.rb
@@ -1,6 +1,6 @@
require 'generators/generators_test_helper'
require 'rails/generators/rails/scaffold_controller/scaffold_controller_generator'
-require 'mocha/setup' # FIXME: stop using mocha
+require 'minitest/mock'
class NamedBaseTest < Rails::Generators::TestCase
include GeneratorsTestHelper
@@ -80,10 +80,13 @@ class NamedBaseTest < Rails::Generators::TestCase
def test_application_name
g = generator ['Admin::Foo']
- Rails.stubs(:application).returns(Object.new)
- assert_name g, "object", :application_name
- Rails.stubs(:application).returns(nil)
- assert_name g, "application", :application_name
+ Rails.stub(:application, Object.new) do
+ assert_name g, "object", :application_name
+ end
+
+ Rails.stub(:application, nil) do
+ assert_name g, "application", :application_name
+ end
end
def test_index_helper
@@ -103,11 +106,11 @@ class NamedBaseTest < Rails::Generators::TestCase
def test_hide_namespace
g = generator ['Hidden']
- g.class.stubs(:namespace).returns('hidden')
-
- assert !Rails::Generators.hidden_namespaces.include?('hidden')
- g.class.hide!
- assert Rails::Generators.hidden_namespaces.include?('hidden')
+ g.class.stub(:namespace, 'hidden') do
+ assert !Rails::Generators.hidden_namespaces.include?('hidden')
+ g.class.hide!
+ assert Rails::Generators.hidden_namespaces.include?('hidden')
+ end
end
def test_scaffold_plural_names_with_model_name_option
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index a0f244da28..1898691806 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -38,7 +38,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
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")] }
- assert_equal "Invalid plugin name plugin. Please give a name which does not match one of the reserved rails words.\n", content
+ 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")] }
assert_equal "Invalid plugin name Digest, constant Digest is already in use. Please choose another plugin name.\n", content
@@ -313,6 +313,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "test/test_helper.rb" do |content|
assert_match(/ActiveRecord::Migrator\.migrations_paths.+\.\.\/test\/dummy\/db\/migrate/, content)
assert_match(/ActiveRecord::Migrator\.migrations_paths.+<<.+\.\.\/db\/migrate/, content)
+ assert_match(/ActionDispatch::IntegrationTest\.fixture_path = ActiveSupport::TestCase\.fixture_pat/, content)
end
end
@@ -544,6 +545,60 @@ class PluginGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_skipping_useless_folders_generation_for_api_engines
+ ['--full', '--mountable'].each do |option|
+ run_generator [destination_root, option, '--api']
+
+ assert_no_directory "app/assets"
+ assert_no_directory "app/helpers"
+ assert_no_directory "app/views"
+
+ FileUtils.rm_rf destination_root
+ end
+ end
+
+ def test_application_controller_parent_for_mountable_api_plugins
+ run_generator [destination_root, '--mountable', '--api']
+
+ assert_file "app/controllers/bukkits/application_controller.rb" do |content|
+ assert_match "ApplicationController < ActionController::API", content
+ end
+ end
+
+ def test_dummy_api_application_for_api_plugins
+ run_generator [destination_root, '--api']
+
+ assert_file "test/dummy/config/application.rb" do |content|
+ assert_match "config.api_only = true", content
+ end
+ end
+
+
+ def test_api_generators_configuration_for_api_engines
+ run_generator [destination_root, '--full', '--api']
+
+ assert_file "lib/bukkits/engine.rb" do |content|
+ assert_match "config.generators.api_only = true", content
+ end
+ end
+
+ def test_scaffold_generator_for_mountable_api_plugins
+ run_generator [destination_root, '--mountable', '--api']
+
+ capture(:stdout) do
+ `#{destination_root}/bin/rails g scaffold article`
+ end
+
+ assert_file "app/models/bukkits/article.rb"
+ assert_file "app/controllers/bukkits/articles_controller.rb" do |content|
+ assert_match "only: [:show, :update, :destroy]", content
+ end
+
+ assert_no_directory "app/assets"
+ assert_no_directory "app/helpers"
+ assert_no_directory "app/views"
+ end
+
protected
def action(*args, &block)
silence(:stdout){ generator.send(*args, &block) }
diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb
index 34e752cea1..95ef853a11 100644
--- a/railties/test/generators/scaffold_controller_generator_test.rb
+++ b/railties/test/generators/scaffold_controller_generator_test.rb
@@ -174,4 +174,73 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
end
end
end
+
+ def test_controller_tests_pass_by_default_inside_mountable_engine
+ Dir.chdir(destination_root) { `bundle exec rails plugin new bukkits --mountable` }
+
+ engine_path = File.join(destination_root, "bukkits")
+
+ Dir.chdir(engine_path) do
+ quietly { `bin/rails g controller dashboard foo` }
+ assert_match(/2 runs, 2 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`)
+ end
+ end
+
+ def test_controller_tests_pass_by_default_inside_full_engine
+ Dir.chdir(destination_root) { `bundle exec rails plugin new bukkits --full` }
+
+ engine_path = File.join(destination_root, "bukkits")
+
+ Dir.chdir(engine_path) do
+ quietly { `bin/rails g controller dashboard foo` }
+ assert_match(/2 runs, 2 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`)
+ end
+ end
+
+ def test_api_only_generates_a_proper_api_controller
+ run_generator ["User", "--api"]
+
+ assert_file "app/controllers/users_controller.rb" do |content|
+ assert_match(/class UsersController < ApplicationController/, content)
+ assert_no_match(/respond_to/, content)
+
+ assert_match(/before_action :set_user, only: \[:show, :update, :destroy\]/, content)
+
+ assert_instance_method :index, content do |m|
+ assert_match(/@users = User\.all/, m)
+ assert_match(/render json: @users/, m)
+ end
+
+ assert_instance_method :show, content do |m|
+ assert_match(/render json: @user/, m)
+ end
+
+ assert_instance_method :create, content do |m|
+ assert_match(/@user = User\.new\(user_params\)/, m)
+ assert_match(/@user\.save/, m)
+ assert_match(/@user\.errors/, m)
+ end
+
+ assert_instance_method :update, content do |m|
+ assert_match(/@user\.update\(user_params\)/, m)
+ assert_match(/@user\.errors/, m)
+ end
+
+ assert_instance_method :destroy, content do |m|
+ assert_match(/@user\.destroy/, m)
+ end
+ end
+ end
+
+ def test_api_controller_tests
+ run_generator ["User", "name:string", "age:integer", "organization:references{polymorphic}", "--api"]
+
+ assert_file "test/controllers/users_controller_test.rb" do |content|
+ assert_match(/class UsersControllerTest < ActionController::TestCase/, content)
+ assert_match(/test "should get index"/, content)
+ assert_match(/post :create, params: \{ user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \} \}/, content)
+ assert_match(/patch :update, params: \{ id: @user, user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \} \}/, content)
+ assert_no_match(/assert_redirected_to/, content)
+ end
+ end
end
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index ee06802874..0c3808a9a0 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -87,6 +87,76 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_file "app/assets/stylesheets/product_lines.css"
end
+ def test_api_scaffold_on_invoke
+ run_generator %w(product_line title:string product:belongs_to user:references --api --no-template-engine --no-helper --no-assets)
+
+ # Model
+ assert_file "app/models/product_line.rb", /class ProductLine < ActiveRecord::Base/
+ assert_file "test/models/product_line_test.rb", /class ProductLineTest < ActiveSupport::TestCase/
+ assert_file "test/fixtures/product_lines.yml"
+ assert_migration "db/migrate/create_product_lines.rb", /belongs_to :product, index: true/
+ assert_migration "db/migrate/create_product_lines.rb", /references :user, index: true/
+
+ # Route
+ assert_file "config/routes.rb" do |route|
+ assert_match(/resources :product_lines$/, route)
+ end
+
+ # Controller
+ assert_file "app/controllers/product_lines_controller.rb" do |content|
+ assert_match(/class ProductLinesController < ApplicationController/, content)
+ assert_no_match(/respond_to/, content)
+
+ assert_match(/before_action :set_product_line, only: \[:show, :update, :destroy\]/, content)
+
+ assert_instance_method :index, content do |m|
+ assert_match(/@product_lines = ProductLine\.all/, m)
+ assert_match(/render json: @product_lines/, m)
+ end
+
+ assert_instance_method :show, content do |m|
+ assert_match(/render json: @product_line/, m)
+ end
+
+ assert_instance_method :create, content do |m|
+ assert_match(/@product_line = ProductLine\.new\(product_line_params\)/, m)
+ assert_match(/@product_line\.save/, m)
+ assert_match(/@product_line\.errors/, m)
+ end
+
+ assert_instance_method :update, content do |m|
+ assert_match(/@product_line\.update\(product_line_params\)/, m)
+ assert_match(/@product_line\.errors/, m)
+ end
+
+ assert_instance_method :destroy, content do |m|
+ assert_match(/@product_line\.destroy/, m)
+ end
+ end
+
+ assert_file "test/controllers/product_lines_controller_test.rb" do |test|
+ assert_match(/class ProductLinesControllerTest < ActionController::TestCase/, test)
+ assert_match(/post :create, params: \{ product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \} \}/, test)
+ assert_match(/patch :update, params: \{ id: @product_line, product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \} \}/, test)
+ assert_no_match(/assert_redirected_to/, test)
+ end
+
+ # Views
+ assert_no_file "app/views/layouts/product_lines.html.erb"
+
+ %w(index show new edit _form).each do |view|
+ assert_no_file "app/views/product_lines/#{view}.html.erb"
+ end
+
+ # Helpers
+ assert_no_file "app/helpers/product_lines_helper.rb"
+
+ # Assets
+ assert_no_file "app/assets/stylesheets/scaffold.css"
+ assert_no_file "app/assets/javascripts/product_lines.js"
+ assert_no_file "app/assets/stylesheets/product_lines.css"
+ end
+
def test_functional_tests_without_attributes
run_generator ["product_line"]
@@ -282,6 +352,20 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_no_file "app/assets/stylesheets/posts.css"
end
+ def test_scaffold_generator_no_scaffold_stylesheet_with_switch_no_scaffold_stylesheet
+ run_generator [ "posts", "--no-scaffold-stylesheet" ]
+ assert_no_file "app/assets/stylesheets/scaffold.css"
+ assert_file "app/assets/javascripts/posts.js"
+ assert_file "app/assets/stylesheets/posts.css"
+ end
+
+ def test_scaffold_generator_no_scaffold_stylesheet_with_switch_scaffold_stylesheet_false
+ run_generator [ "posts", "--scaffold-stylesheet=false" ]
+ assert_no_file "app/assets/stylesheets/scaffold.css"
+ assert_file "app/assets/javascripts/posts.js"
+ assert_file "app/assets/stylesheets/posts.css"
+ end
+
def test_scaffold_generator_with_switch_resource_route_false
run_generator [ "posts", "--resource-route=false" ]
assert_file "config/routes.rb" do |route|
@@ -393,4 +477,60 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_match(/password_digest: <%= BCrypt::Password.create\('secret'\) %>/, content)
end
end
+
+ def test_scaffold_tests_pass_by_default_inside_mountable_engine
+ Dir.chdir(destination_root) { `bundle exec rails plugin new bukkits --mountable` }
+
+ engine_path = File.join(destination_root, "bukkits")
+
+ Dir.chdir(engine_path) do
+ quietly do
+ `bin/rails g scaffold User name:string age:integer;
+ bundle exec rake db:migrate`
+ end
+ assert_match(/8 runs, 13 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`)
+ end
+ end
+
+ def test_scaffold_tests_pass_by_default_inside_full_engine
+ Dir.chdir(destination_root) { `bundle exec rails plugin new bukkits --full` }
+
+ engine_path = File.join(destination_root, "bukkits")
+
+ Dir.chdir(engine_path) do
+ quietly do
+ `bin/rails g scaffold User name:string age:integer;
+ bundle exec rake db:migrate`
+ end
+ assert_match(/8 runs, 13 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`)
+ end
+ end
+
+ def test_scaffold_tests_pass_by_default_inside_api_mountable_engine
+ Dir.chdir(destination_root) { `bundle exec rails plugin new bukkits --mountable --api` }
+
+ engine_path = File.join(destination_root, "bukkits")
+
+ Dir.chdir(engine_path) do
+ quietly do
+ `bin/rails g scaffold User name:string age:integer;
+ bundle exec rake db:migrate`
+ end
+ assert_match(/6 runs, 8 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`)
+ end
+ end
+
+ def test_scaffold_tests_pass_by_default_inside_api_full_engine
+ Dir.chdir(destination_root) { `bundle exec rails plugin new bukkits --full --api` }
+
+ engine_path = File.join(destination_root, "bukkits")
+
+ Dir.chdir(engine_path) do
+ quietly do
+ `bin/rails g scaffold User name:string age:integer;
+ bundle exec rake db:migrate`
+ end
+ assert_match(/6 runs, 8 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`)
+ end
+ end
end
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index 68f07f29d7..77372fb514 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -56,7 +56,7 @@ module SharedGeneratorTests
reserved_words = %w[application destroy plugin runner test]
reserved_words.each do |reserved|
content = capture(:stderr){ run_generator [File.join(destination_root, reserved)] }
- assert_match(/Invalid \w+ name #{reserved}. Please give a name which does not match one of the reserved rails words.\n/, content)
+ assert_match(/Invalid \w+ name #{reserved}. Please give a name which does not match one of the reserved rails words: application, destroy, plugin, runner, test\n/, content)
end
end
@@ -86,15 +86,6 @@ module SharedGeneratorTests
end
end
- def test_template_is_executed_when_supplied
- path = "https://gist.github.com/josevalim/103208/raw/"
- template = %{ say "It works!" }
- template.instance_eval "def read; self; end" # Make the string respond to read
-
- generator([destination_root], template: path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template)
- quietly { assert_match(/It works!/, capture(:stdout) { generator.invoke_all }) }
- end
-
def test_template_is_executed_when_supplied_an_https_path
path = "https://gist.github.com/josevalim/103208/raw/"
template = %{ say "It works!" }
@@ -138,7 +129,11 @@ module SharedGeneratorTests
def test_skip_keeps
run_generator [destination_root, '--skip-keeps', '--full']
- assert_file('.gitignore')
+
+ assert_file '.gitignore' do |content|
+ assert_no_match(/\.keep/, content)
+ end
+
assert_no_file('app/mailers/.keep')
end
end
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index 127e059bf1..31a575749a 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -1,7 +1,7 @@
require 'generators/generators_test_helper'
require 'rails/generators/rails/model/model_generator'
require 'rails/generators/test_unit/model/model_generator'
-require 'mocha/setup' # FIXME: stop using mocha
+require 'minitest/mock'
class GeneratorsTest < Rails::Generators::TestCase
include GeneratorsTestHelper
@@ -9,16 +9,20 @@ class GeneratorsTest < Rails::Generators::TestCase
def setup
@path = File.expand_path("lib", Rails.root)
$LOAD_PATH.unshift(@path)
+ @mock_generator = MiniTest::Mock.new
end
def teardown
$LOAD_PATH.delete(@path)
+ @mock_generator.verify
end
def test_simple_invoke
assert File.exist?(File.join(@path, 'generators', 'model_generator.rb'))
- TestUnit::Generators::ModelGenerator.expects(:start).with(["Account"], {})
- Rails::Generators.invoke("test_unit:model", ["Account"])
+ @mock_generator.expect(:call, nil, [["Account"],{}])
+ TestUnit::Generators::ModelGenerator.stub(:start, @mock_generator) do
+ Rails::Generators.invoke("test_unit:model", ["Account"])
+ end
end
def test_invoke_when_generator_is_not_found
@@ -34,6 +38,12 @@ class GeneratorsTest < Rails::Generators::TestCase
assert_match "Maybe you meant 'migration'", output
end
+ def test_generator_multiple_suggestions
+ name = :tas
+ 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, [] }
assert_match(/Description:/, output)
@@ -41,19 +51,25 @@ class GeneratorsTest < Rails::Generators::TestCase
def test_should_give_higher_preference_to_rails_generators
assert File.exist?(File.join(@path, 'generators', 'model_generator.rb'))
- Rails::Generators::ModelGenerator.expects(:start).with(["Account"], {})
- warnings = capture(:stderr){ Rails::Generators.invoke :model, ["Account"] }
- assert warnings.empty?
+ @mock_generator.expect(:call, nil, [["Account"],{}])
+ Rails::Generators::ModelGenerator.stub(:start, @mock_generator) do
+ warnings = capture(:stderr){ Rails::Generators.invoke :model, ["Account"] }
+ assert warnings.empty?
+ end
end
def test_invoke_with_default_values
- Rails::Generators::ModelGenerator.expects(:start).with(["Account"], {})
- Rails::Generators.invoke :model, ["Account"]
+ @mock_generator.expect(:call, nil, [["Account"],{}])
+ Rails::Generators::ModelGenerator.stub(:start, @mock_generator) do
+ Rails::Generators.invoke :model, ["Account"]
+ end
end
def test_invoke_with_config_values
- Rails::Generators::ModelGenerator.expects(:start).with(["Account"], behavior: :skip)
- Rails::Generators.invoke :model, ["Account"], behavior: :skip
+ @mock_generator.expect(:call, nil, [["Account"],{behavior: :skip}])
+ Rails::Generators::ModelGenerator.stub(:start, @mock_generator) do
+ Rails::Generators.invoke :model, ["Account"], behavior: :skip
+ end
end
def test_find_by_namespace
@@ -97,11 +113,13 @@ class GeneratorsTest < Rails::Generators::TestCase
end
def test_invoke_with_nested_namespaces
- model_generator = mock('ModelGenerator') do
- expects(:start).with(["Account"], {})
+ model_generator = Minitest::Mock.new
+ model_generator.expect(:start, nil, [["Account"], {}])
+ @mock_generator.expect(:call, model_generator, ['namespace', 'my:awesome'])
+ Rails::Generators.stub(:find_by_namespace, @mock_generator) do
+ Rails::Generators.invoke 'my:awesome:namespace', ["Account"]
end
- Rails::Generators.expects(:find_by_namespace).with('namespace', 'my:awesome').returns(model_generator)
- Rails::Generators.invoke 'my:awesome:namespace', ["Account"]
+ model_generator.verify
end
def test_rails_generators_help_with_builtin_information
@@ -167,8 +185,10 @@ class GeneratorsTest < Rails::Generators::TestCase
def test_fallbacks_for_generators_on_invoke
Rails::Generators.fallbacks[:shoulda] = :test_unit
- TestUnit::Generators::ModelGenerator.expects(:start).with(["Account"], {})
- Rails::Generators.invoke "shoulda:model", ["Account"]
+ @mock_generator.expect(:call, nil, [["Account"],{}])
+ TestUnit::Generators::ModelGenerator.stub(:start, @mock_generator) do
+ Rails::Generators.invoke "shoulda:model", ["Account"]
+ end
ensure
Rails::Generators.fallbacks.delete(:shoulda)
end
@@ -176,8 +196,10 @@ class GeneratorsTest < Rails::Generators::TestCase
def test_nested_fallbacks_for_generators
Rails::Generators.fallbacks[:shoulda] = :test_unit
Rails::Generators.fallbacks[:super_shoulda] = :shoulda
- TestUnit::Generators::ModelGenerator.expects(:start).with(["Account"], {})
- Rails::Generators.invoke "super_shoulda:model", ["Account"]
+ @mock_generator.expect(:call, nil, [["Account"],{}])
+ TestUnit::Generators::ModelGenerator.stub(:start, @mock_generator) do
+ Rails::Generators.invoke "super_shoulda:model", ["Account"]
+ end
ensure
Rails::Generators.fallbacks.delete(:shoulda)
Rails::Generators.fallbacks.delete(:super_shoulda)
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 63209559d7..65d8a55421 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -270,9 +270,9 @@ module TestHelpers
File.open(file, "w+") { |f| f.puts contents }
end
- def app_file(path, contents)
+ def app_file(path, contents, mode = 'w')
FileUtils.mkdir_p File.dirname("#{app_path}/#{path}")
- File.open("#{app_path}/#{path}", 'w') do |f|
+ File.open("#{app_path}/#{path}", mode) do |f|
f.puts contents
end
end
@@ -322,7 +322,7 @@ Module.new do
environment = File.expand_path('../../../../load_paths', __FILE__)
require_environment = "-r #{environment}"
- `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/railties/bin/rails new #{app_template_path} --skip-gemfile --no-rc`
+ `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/railties/exe/rails new #{app_template_path} --skip-gemfile --no-rc`
File.open("#{app_template_path}/config/boot.rb", 'w') do |f|
f.puts "require '#{environment}'"
f.puts "require 'rails/all'"
diff --git a/railties/test/path_generation_test.rb b/railties/test/path_generation_test.rb
index e3dfcdfb7d..27e64b97b7 100644
--- a/railties/test/path_generation_test.rb
+++ b/railties/test/path_generation_test.rb
@@ -1,21 +1,17 @@
require 'abstract_unit'
require 'active_support/core_ext/object/with_options'
require 'active_support/core_ext/object/json'
-require 'rails'
-require 'rails/application'
-
-ROUTING = ActionDispatch::Routing
class PathGenerationTest < ActiveSupport::TestCase
attr_reader :app
- class TestSet < ROUTING::RouteSet
+ class TestSet < ActionDispatch::Routing::RouteSet
def initialize(block)
@block = block
super()
end
- class Dispatcher < ROUTING::RouteSet::Dispatcher
+ class Dispatcher < ActionDispatch::Routing::RouteSet::Dispatcher
def initialize(defaults, set, block)
super(defaults)
@block = block
diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb
index 1aeb9ec339..96b54c7264 100644
--- a/railties/test/paths_test.rb
+++ b/railties/test/paths_test.rb
@@ -1,10 +1,9 @@
require 'abstract_unit'
require 'rails/paths'
-require 'mocha/setup' # FIXME: stop using mocha
+require 'minitest/mock'
class PathsTest < ActiveSupport::TestCase
def setup
- File.stubs(:exist?).returns(true)
@root = Rails::Paths::Root.new("/foo/bar")
end
@@ -63,6 +62,13 @@ class PathsTest < ActiveSupport::TestCase
assert_equal ["/foo/bar/baz"], @root["app/models"].to_a
end
+ test "absolute current path" do
+ @root.add "config"
+ @root.add "config/locales"
+
+ assert_equal "/foo/bar/config/locales", @root["config/locales"].absolute_current
+ end
+
test "adding multiple physical paths as an array" do
@root.add "app", with: ["/app", "/app2"]
assert_equal ["/app", "/app2"], @root["app"].to_a
@@ -93,10 +99,12 @@ class PathsTest < ActiveSupport::TestCase
end
test "it is possible to add a path that should be autoloaded only once" do
- @root.add "app", with: "/app"
- @root["app"].autoload_once!
- assert @root["app"].autoload_once?
- assert @root.autoload_once.include?(@root["app"].expanded.first)
+ File.stub(:exist?, true) do
+ @root.add "app", with: "/app"
+ @root["app"].autoload_once!
+ assert @root["app"].autoload_once?
+ assert @root.autoload_once.include?(@root["app"].expanded.first)
+ end
end
test "it is possible to remove a path that should be autoloaded only once" do
@@ -110,37 +118,47 @@ class PathsTest < ActiveSupport::TestCase
end
test "it is possible to add a path without assignment and specify it should be loaded only once" do
- @root.add "app", with: "/app", autoload_once: true
- assert @root["app"].autoload_once?
- assert @root.autoload_once.include?("/app")
+ File.stub(:exist?, true) do
+ @root.add "app", with: "/app", autoload_once: true
+ assert @root["app"].autoload_once?
+ assert @root.autoload_once.include?("/app")
+ end
end
test "it is possible to add multiple paths without assignment and specify it should be loaded only once" 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")
+ 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")
+ end
end
test "making a path autoload_once more than once only includes it once in @root.load_once" do
- @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
+ File.stub(:exist?, true) do
+ @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
+ end
end
test "paths added to a load_once path should be added to the autoload_once collection" do
- @root["app"] = "/app"
- @root["app"].autoload_once!
- @root["app"] << "/app2"
- assert_equal 2, @root.autoload_once.size
+ File.stub(:exist?, true) do
+ @root["app"] = "/app"
+ @root["app"].autoload_once!
+ @root["app"] << "/app2"
+ assert_equal 2, @root.autoload_once.size
+ end
end
test "it is possible to mark a path as eager loaded" do
- @root["app"] = "/app"
- @root["app"].eager_load!
- assert @root["app"].eager_load?
- assert @root.eager_load.include?(@root["app"].to_a.first)
+ File.stub(:exist?, true) do
+ @root["app"] = "/app"
+ @root["app"].eager_load!
+ assert @root["app"].eager_load?
+ assert @root.eager_load.include?(@root["app"].to_a.first)
+ end
end
test "it is possible to skip a path from eager loading" do
@@ -154,38 +172,48 @@ class PathsTest < ActiveSupport::TestCase
end
test "it is possible to add a path without assignment and mark it as eager" do
- @root.add "app", with: "/app", eager_load: true
- assert @root["app"].eager_load?
- assert @root.eager_load.include?("/app")
+ File.stub(:exist?, true) do
+ @root.add "app", with: "/app", eager_load: true
+ assert @root["app"].eager_load?
+ assert @root.eager_load.include?("/app")
+ end
end
test "it is possible to add multiple paths without assignment and mark them as eager" 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")
+ 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")
+ end
end
test "it is possible to create a path without assignment and mark it both as eager and load once" do
- @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")
+ File.stub(:exist?, true) do
+ @root.add "app", with: "/app", eager_load: true, autoload_once: true
+ assert @root["app"].eager_load?
+ assert @root["app"].autoload_once?
+ assert @root.eager_load.include?("/app")
+ assert @root.autoload_once.include?("/app")
+ end
end
test "making a path eager more than once only includes it once in @root.eager_paths" do
- @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
+ File.stub(:exist?, true) do
+ @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
+ end
end
test "paths added to an eager_load path should be added to the eager_load collection" do
- @root["app"] = "/app"
- @root["app"].eager_load!
- @root["app"] << "/app2"
- assert_equal 2, @root.eager_load.size
+ File.stub(:exist?, true) do
+ @root["app"] = "/app"
+ @root["app"].eager_load!
+ @root["app"] << "/app2"
+ assert_equal 2, @root.eager_load.size
+ end
end
test "it should be possible to add a path's default glob" do
@@ -194,6 +222,12 @@ class PathsTest < ActiveSupport::TestCase
assert_equal "*.rb", @root["app"].glob
end
+ test "it should be possible to get extensions by glob" do
+ @root["app"] = "/app"
+ @root["app"].glob = "*.{rb,yml}"
+ assert_equal ["rb", "yml"], @root["app"].extensions
+ end
+
test "it should be possible to override a path's default glob without assignment" do
@root.add "app", with: "/app", glob: "*.rb"
assert_equal "*.rb", @root["app"].glob
@@ -207,28 +241,36 @@ class PathsTest < ActiveSupport::TestCase
end
test "a path can be added to the load path" do
- @root["app"] = "app"
- @root["app"].load_path!
- @root["app/models"] = "app/models"
- assert_equal ["/foo/bar/app"], @root.load_paths
+ File.stub(:exist?, true) do
+ @root["app"] = "app"
+ @root["app"].load_path!
+ @root["app/models"] = "app/models"
+ assert_equal ["/foo/bar/app"], @root.load_paths
+ end
end
test "a path can be added to the load path on creation" do
- @root.add "app", with: "/app", load_path: true
- assert @root["app"].load_path?
- assert_equal ["/app"], @root.load_paths
+ File.stub(:exist?, true) do
+ @root.add "app", with: "/app", load_path: true
+ assert @root["app"].load_path?
+ assert_equal ["/app"], @root.load_paths
+ end
end
test "a path can be marked as autoload path" do
- @root["app"] = "app"
- @root["app"].autoload!
- @root["app/models"] = "app/models"
- assert_equal ["/foo/bar/app"], @root.autoload_paths
+ File.stub(:exist?, true) do
+ @root["app"] = "app"
+ @root["app"].autoload!
+ @root["app/models"] = "app/models"
+ assert_equal ["/foo/bar/app"], @root.autoload_paths
+ end
end
test "a path can be marked as autoload on creation" do
- @root.add "app", with: "/app", autoload: true
- assert @root["app"].autoload?
- assert_equal ["/app"], @root.autoload_paths
+ File.stub(:exist?, true) do
+ @root.add "app", with: "/app", autoload: true
+ assert @root["app"].autoload?
+ assert_equal ["/app"], @root.autoload_paths
+ end
end
end
diff --git a/railties/test/railties/generators_test.rb b/railties/test/railties/generators_test.rb
index 7348d70c56..5f4171d44b 100644
--- a/railties/test/railties/generators_test.rb
+++ b/railties/test/railties/generators_test.rb
@@ -30,7 +30,7 @@ module RailtiesTests
if File.exist?("#{environment}.rb")
require_environment = "-r #{environment}"
end
- `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/railties/bin/rails #{cmd}`
+ `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/railties/exe/rails #{cmd}`
end
def build_engine(is_mountable=false)
@@ -122,5 +122,13 @@ module RailtiesTests
assert_no_file "app/helpers/foo_bar/topics_helper.rb"
end
end
+
+ def test_assert_file_with_special_characters
+ path = "#{app_path}/tmp"
+ file_name = "#{path}/v0.1.4~alpha+nightly"
+ FileUtils.mkdir_p path
+ FileUtils.touch file_name
+ assert_file file_name
+ end
end
end
diff --git a/railties/test/test_info_test.rb b/railties/test/test_info_test.rb
deleted file mode 100644
index b9c3a9c0c7..0000000000
--- a/railties/test/test_info_test.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-require 'abstract_unit'
-require 'rails/test_unit/sub_test_task'
-
-module Rails
- class TestInfoTest < ActiveSupport::TestCase
- def test_test_files
- info = new_test_info ['test']
- assert_predicate info.files, :empty?
- assert_nil info.opts
- assert_equal ['test'], info.tasks
- end
-
- def test_with_file
- info = new_test_info ['test', __FILE__]
- assert_equal [__FILE__], info.files
- assert_nil info.opts
- assert_equal ['test'], info.tasks
- end
-
- def test_with_opts
- info = new_test_info ['test', __FILE__, '/foo/']
- assert_equal [__FILE__], info.files
- assert_equal '-n /foo/', info.opts
- assert_equal ['test'], info.tasks
- end
-
- def test_with_model_shorthand
- info = new_test_info ['test', 'models/foo', '/foo/']
-
- def info.test_file?(file)
- file == "test/models/foo_test.rb" || super
- end
-
- assert_equal ['test/models/foo_test.rb'], info.files
- assert_equal '-n /foo/', info.opts
- assert_equal ['test'], info.tasks
- end
-
- def test_with_model_path
- info = new_test_info ['test', 'app/models/foo.rb', '/foo/']
-
- def info.test_file?(file)
- file == "test/models/foo_test.rb" || super
- end
-
- assert_equal ['test/models/foo_test.rb'], info.files
- assert_equal '-n /foo/', info.opts
- assert_equal ['test'], info.tasks
- end
-
- private
- def new_test_info(tasks)
- Class.new(TestTask::TestInfo) {
- def task_defined?(task)
- task == "test"
- end
- }.new tasks
- end
- end
-end
diff --git a/railties/test/test_unit/reporter_test.rb b/railties/test/test_unit/reporter_test.rb
index 77883612f5..3066ba82d6 100644
--- a/railties/test/test_unit/reporter_test.rb
+++ b/railties/test/test_unit/reporter_test.rb
@@ -32,6 +32,7 @@ class TestUnitReporterTest < ActiveSupport::TestCase
@reporter.record(passing_test)
@reporter.record(skipped_test)
@reporter.report
+ assert_no_match 'Failed tests:', @output.string
assert_rerun_snippet_count 0
end
@@ -43,6 +44,19 @@ class TestUnitReporterTest < ActiveSupport::TestCase
assert_rerun_snippet_count 1
end
+ test "allows to customize the executable in the rerun snippet" do
+ original_executable = Rails::TestUnitReporter.executable
+ begin
+ Rails::TestUnitReporter.executable = "bin/test"
+ @reporter.record(failed_test)
+ @reporter.report
+
+ assert_match %r{^bin/test .*test/test_unit/reporter_test.rb:6$}, @output.string
+ ensure
+ Rails::TestUnitReporter.executable = original_executable
+ end
+ end
+
private
def assert_rerun_snippet_count(snippet_count)
assert_equal snippet_count, @output.string.scan(%r{^bin/rails test }).size
diff --git a/railties/test/test_unit/runner_test.rb b/railties/test/test_unit/runner_test.rb
deleted file mode 100644
index 9ea8b2c114..0000000000
--- a/railties/test/test_unit/runner_test.rb
+++ /dev/null
@@ -1,111 +0,0 @@
-require 'abstract_unit'
-require 'env_helpers'
-require 'rails/test_unit/runner'
-
-class TestUnitTestRunnerTest < ActiveSupport::TestCase
- include EnvHelpers
-
- setup do
- @options = Rails::TestRunner::Options
- end
-
- test "shows the filtered backtrace by default" do
- options = @options.parse([])
- assert_not options[:backtrace]
- end
-
- test "has --backtrace (-b) option to show the full backtrace" do
- options = @options.parse(["-b"])
- assert options[:backtrace]
-
- options = @options.parse(["--backtrace"])
- assert options[:backtrace]
- end
-
- test "show full backtrace using BACKTRACE environment variable" do
- switch_env "BACKTRACE", "true" do
- options = @options.parse([])
- assert options[:backtrace]
- end
- end
-
- test "tests run in the test environment by default" do
- options = @options.parse([])
- assert_equal "test", options[:environment]
- end
-
- test "can run in a specific environment" do
- options = @options.parse(["-e development"])
- assert_equal "development", options[:environment]
- end
-
- test "parse the filename and line" do
- file = "test/test_unit/runner_test.rb"
- absolute_file = File.expand_path __FILE__
- options = @options.parse(["#{file}:20"])
- assert_equal absolute_file, options[:filename]
- assert_equal 20, options[:line]
-
- options = @options.parse(["#{file}:"])
- assert_equal [absolute_file], options[:patterns]
- assert_nil options[:line]
-
- options = @options.parse([file])
- assert_equal [absolute_file], options[:patterns]
- assert_nil options[:line]
- end
-
- test "find_method on same file" do
- options = @options.parse(["#{__FILE__}:#{__LINE__}"])
- runner = Rails::TestRunner.new(options)
- assert_equal "test_find_method_on_same_file", runner.find_method
- end
-
- test "find_method on a different file" do
- options = @options.parse(["foobar.rb:#{__LINE__}"])
- runner = Rails::TestRunner.new(options)
- assert_nil runner.find_method
- end
-
- test "run all tests in a directory" do
- options = @options.parse([__dir__])
-
- assert_equal ["#{__dir__}/**/*_test.rb"], options[:patterns]
- assert_nil options[:filename]
- assert_nil options[:line]
- end
-
- test "run multiple folders" do
- application_dir = File.expand_path("#{__dir__}/../application")
-
- options = @options.parse([__dir__, application_dir])
-
- assert_equal ["#{__dir__}/**/*_test.rb", "#{application_dir}/**/*_test.rb"], options[:patterns]
- assert_nil options[:filename]
- assert_nil options[:line]
-
- runner = Rails::TestRunner.new(options)
- assert runner.test_files.size > 0
- end
-
- test "run multiple files and run one file by line" do
- line = __LINE__
- absolute_file = File.expand_path(__FILE__)
- options = @options.parse([__dir__, "#{__FILE__}:#{line}"])
-
- assert_equal ["#{__dir__}/**/*_test.rb"], options[:patterns]
- assert_equal absolute_file, options[:filename]
- assert_equal line, options[:line]
-
- runner = Rails::TestRunner.new(options)
- assert_equal [absolute_file], runner.test_files, 'Only returns the file that running by line'
- end
-
- test "running multiple files passing line number" do
- line = __LINE__
- options = @options.parse(["foobar.rb:8", "#{__FILE__}:#{line}"])
-
- assert_equal File.expand_path(__FILE__), options[:filename], 'Returns the last file'
- assert_equal line, options[:line]
- end
-end
diff --git a/tools/profile b/tools/profile
index eb7fc7792b..191e73b3dd 100755
--- a/tools/profile
+++ b/tools/profile
@@ -117,9 +117,9 @@ rescue LoadError
def camelize(uppercase_first_letter = true)
string = self
if uppercase_first_letter
- string = string.sub(/^[a-z\d]*/) { $&.capitalize }
+ string = string.sub(/^[a-z\d]*/) { |match| match.capitalize }
else
- string = string.sub(/^(?:(?=\b|[A-Z_])|\w)/) { $&.downcase }
+ string = string.sub(/^(?:(?=\b|[A-Z_])|\w)/) { |match| match.downcase }
end
string.gsub(/(?:_|(\/))([a-z\d]*)/) { "#{$1}#{$2.capitalize}" }.gsub('/', '::')
end
diff --git a/tools/test.rb b/tools/test.rb
new file mode 100644
index 0000000000..70f295b554
--- /dev/null
+++ b/tools/test.rb
@@ -0,0 +1,12 @@
+$: << File.expand_path("test", COMPONENT_ROOT)
+require File.expand_path("../../load_paths", __FILE__)
+require "rails/test_unit/minitest_plugin"
+
+module Rails
+ # Necessary to get rerun-snippts working.
+ def self.root
+ @root ||= Pathname.new(COMPONENT_ROOT)
+ end
+end
+
+Rails::TestUnitReporter.executable = "bin/test"