aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.travis.yml26
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--Gemfile32
-rw-r--r--Gemfile.lock297
-rw-r--r--RAILS_VERSION2
-rw-r--r--README.md2
-rw-r--r--RELEASING_RAILS.rdoc14
-rw-r--r--actionmailer/CHANGELOG.md68
-rw-r--r--actionmailer/MIT-LICENSE2
-rw-r--r--actionmailer/actionmailer.gemspec4
-rw-r--r--actionmailer/lib/action_mailer.rb2
-rw-r--r--actionmailer/lib/action_mailer/base.rb57
-rw-r--r--actionmailer/lib/action_mailer/delivery_job.rb4
-rw-r--r--actionmailer/lib/action_mailer/gem_version.rb6
-rw-r--r--actionmailer/lib/action_mailer/mail_helper.rb14
-rw-r--r--actionmailer/lib/action_mailer/message_delivery.rb21
-rw-r--r--actionmailer/lib/action_mailer/railtie.rb2
-rw-r--r--actionmailer/lib/action_mailer/test_helper.rb51
-rw-r--r--actionmailer/lib/rails/generators/mailer/USAGE6
-rw-r--r--actionmailer/lib/rails/generators/mailer/mailer_generator.rb13
-rw-r--r--actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb4
-rw-r--r--actionmailer/lib/rails/generators/mailer/templates/mailer.rb5
-rw-r--r--actionmailer/test/abstract_unit.rb7
-rw-r--r--actionmailer/test/base_test.rb30
-rw-r--r--actionmailer/test/fixtures/base_mailer/email_custom_layout.text.html.erb1
-rw-r--r--actionmailer/test/fixtures/base_mailer/implicit_with_locale.de-AT.text.erb1
-rw-r--r--actionmailer/test/fixtures/base_mailer/implicit_with_locale.de.html.erb1
-rw-r--r--actionmailer/test/fixtures/first_mailer/share.erb1
-rw-r--r--actionmailer/test/fixtures/path.with.dots/funky_path_mailer/multipart_with_template_path_with_dots.erb1
-rw-r--r--actionmailer/test/fixtures/second_mailer/share.erb1
-rw-r--r--actionmailer/test/fixtures/test_mailer/_subtemplate.text.erb1
-rw-r--r--actionmailer/test/fixtures/test_mailer/custom_templating_extension.html.haml6
-rw-r--r--actionmailer/test/fixtures/test_mailer/custom_templating_extension.text.haml6
-rw-r--r--actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.html.erb10
-rw-r--r--actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.html.erb~10
-rw-r--r--actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.ignored.erb1
-rw-r--r--actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.rhtml.bak1
-rw-r--r--actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.erb2
-rw-r--r--actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.yaml.erb1
-rw-r--r--actionmailer/test/fixtures/test_mailer/included_subtemplate.text.erb1
-rw-r--r--actionmailer/test/fixtures/test_mailer/multipart_alternative.html.erb1
-rw-r--r--actionmailer/test/fixtures/test_mailer/multipart_alternative.plain.erb1
-rw-r--r--actionmailer/test/fixtures/test_mailer/rxml_template.rxml2
-rw-r--r--actionmailer/test/fixtures/test_mailer/signed_up.html.erb3
-rw-r--r--actionmailer/test/fixtures/url_test_mailer/exercise_url_for.erb1
-rw-r--r--actionmailer/test/message_delivery_test.rb20
-rw-r--r--actionmailer/test/test_helper_test.rb59
-rw-r--r--actionmailer/test/url_test.rb60
-rw-r--r--actionpack/CHANGELOG.md473
-rw-r--r--actionpack/MIT-LICENSE2
-rw-r--r--actionpack/Rakefile5
-rw-r--r--actionpack/actionpack.gemspec10
-rw-r--r--actionpack/lib/abstract_controller/base.rb37
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb58
-rw-r--r--actionpack/lib/abstract_controller/helpers.rb2
-rw-r--r--actionpack/lib/abstract_controller/railties/routes_helpers.rb4
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb15
-rw-r--r--actionpack/lib/abstract_controller/translation.rb15
-rw-r--r--actionpack/lib/action_controller.rb2
-rw-r--r--actionpack/lib/action_controller/base.rb3
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb10
-rw-r--r--actionpack/lib/action_controller/metal.rb13
-rw-r--r--actionpack/lib/action_controller/metal/conditional_get.rb84
-rw-r--r--actionpack/lib/action_controller/metal/etag_with_template_digest.rb4
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb2
-rw-r--r--actionpack/lib/action_controller/metal/head.rb8
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb4
-rw-r--r--actionpack/lib/action_controller/metal/hide_actions.rb40
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb33
-rw-r--r--actionpack/lib/action_controller/metal/implicit_render.rb7
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb13
-rw-r--r--actionpack/lib/action_controller/metal/live.rb8
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb54
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb5
-rw-r--r--actionpack/lib/action_controller/metal/rack_delegation.rb10
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb3
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb11
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb28
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb17
-rw-r--r--actionpack/lib/action_controller/metal/testing.rb2
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb11
-rw-r--r--actionpack/lib/action_controller/model_naming.rb12
-rw-r--r--actionpack/lib/action_controller/renderer.rb100
-rw-r--r--actionpack/lib/action_controller/test_case.rb151
-rw-r--r--actionpack/lib/action_dispatch.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb7
-rw-r--r--actionpack/lib/action_dispatch/http/filter_redirect.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb17
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/parameter_filter.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb8
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb31
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb24
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb117
-rw-r--r--actionpack/lib/action_dispatch/journey/formatter.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/transition_table.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/transition_table.rb47
-rw-r--r--actionpack/lib/action_dispatch/journey/path/pattern.rb9
-rw-r--r--actionpack/lib/action_dispatch/journey/route.rb8
-rw-r--r--actionpack/lib/action_dispatch/journey/router.rb6
-rw-r--r--actionpack/lib/action_dispatch/journey/routes.rb19
-rw-r--r--actionpack/lib/action_dispatch/journey/visualizer/fsm.css4
-rw-r--r--actionpack/lib/action_dispatch/middleware/callbacks.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb24
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb36
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb41
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb39
-rw-r--r--actionpack/lib/action_dispatch/middleware/params_parser.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/public_exceptions.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/request_id.rb14
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cache_store.rb9
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb10
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb10
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb16
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb40
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb8
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb124
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb3
-rw-r--r--actionpack/lib/action_dispatch/request/utils.rb4
-rw-r--r--actionpack/lib/action_dispatch/routing/inspector.rb30
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb60
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb31
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb192
-rw-r--r--actionpack/lib/action_dispatch/routing/routes_proxy.rb9
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions.rb4
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/dom.rb3
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb30
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/selector.rb3
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/tag.rb3
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb315
-rw-r--r--actionpack/lib/action_dispatch/testing/test_request.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/test_response.rb6
-rw-r--r--actionpack/lib/action_pack.rb2
-rw-r--r--actionpack/lib/action_pack/gem_version.rb6
-rw-r--r--actionpack/test/abstract/callbacks_test.rb8
-rw-r--r--actionpack/test/abstract/translation_test.rb33
-rw-r--r--actionpack/test/abstract_unit.rb50
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb11
-rw-r--r--actionpack/test/controller/base_test.rb66
-rw-r--r--actionpack/test/controller/caching_test.rb66
-rw-r--r--actionpack/test/controller/default_url_options_with_before_action_test.rb3
-rw-r--r--actionpack/test/controller/filters_test.rb95
-rw-r--r--actionpack/test/controller/flash_hash_test.rb34
-rw-r--r--actionpack/test/controller/flash_test.rb16
-rw-r--r--actionpack/test/controller/force_ssl_test.rb31
-rw-r--r--actionpack/test/controller/helper_test.rb6
-rw-r--r--actionpack/test/controller/http_basic_authentication_test.rb7
-rw-r--r--actionpack/test/controller/http_token_authentication_test.rb23
-rw-r--r--actionpack/test/controller/integration_test.rb449
-rw-r--r--actionpack/test/controller/live_stream_test.rb2
-rw-r--r--actionpack/test/controller/localized_templates_test.rb6
-rw-r--r--actionpack/test/controller/log_subscriber_test.rb32
-rw-r--r--actionpack/test/controller/mime/accept_format_test.rb4
-rw-r--r--actionpack/test/controller/mime/respond_to_test.rb71
-rw-r--r--actionpack/test/controller/mime/responders_test.rb32
-rw-r--r--actionpack/test/controller/new_base/bare_metal_test.rb9
-rw-r--r--actionpack/test/controller/new_base/content_negotiation_test.rb4
-rw-r--r--actionpack/test/controller/new_base/content_type_test.rb4
-rw-r--r--actionpack/test/controller/new_base/metal_test.rb6
-rw-r--r--actionpack/test/controller/new_base/middleware_test.rb2
-rw-r--r--actionpack/test/controller/new_base/render_action_test.rb4
-rw-r--r--actionpack/test/controller/new_base/render_layout_test.rb6
-rw-r--r--actionpack/test/controller/new_base/render_streaming_test.rb2
-rw-r--r--actionpack/test/controller/new_base/render_template_test.rb10
-rw-r--r--actionpack/test/controller/new_base/render_test.rb6
-rw-r--r--actionpack/test/controller/parameters/always_permitted_parameters_test.rb9
-rw-r--r--actionpack/test/controller/parameters/parameters_permit_test.rb6
-rw-r--r--actionpack/test/controller/params_wrapper_test.rb48
-rw-r--r--actionpack/test/controller/permitted_params_test.rb4
-rw-r--r--actionpack/test/controller/redirect_test.rb4
-rw-r--r--actionpack/test/controller/render_js_test.rb4
-rw-r--r--actionpack/test/controller/render_json_test.rb4
-rw-r--r--actionpack/test/controller/render_test.rb193
-rw-r--r--actionpack/test/controller/render_xml_test.rb4
-rw-r--r--actionpack/test/controller/renderer_test.rb103
-rw-r--r--actionpack/test/controller/request/test_request_test.rb8
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb99
-rw-r--r--actionpack/test/controller/required_params_test.rb8
-rw-r--r--actionpack/test/controller/resources_test.rb30
-rw-r--r--actionpack/test/controller/routing_test.rb14
-rw-r--r--actionpack/test/controller/send_file_test.rb1
-rw-r--r--actionpack/test/controller/show_exceptions_test.rb16
-rw-r--r--actionpack/test/controller/test_case_test.rb501
-rw-r--r--actionpack/test/controller/url_for_integration_test.rb1
-rw-r--r--actionpack/test/controller/url_for_test.rb44
-rw-r--r--actionpack/test/controller/webservice_test.rb31
-rw-r--r--actionpack/test/dispatch/cookies_test.rb32
-rw-r--r--actionpack/test/dispatch/debug_exceptions_test.rb112
-rw-r--r--actionpack/test/dispatch/exception_wrapper_test.rb114
-rw-r--r--actionpack/test/dispatch/mime_type_test.rb4
-rw-r--r--actionpack/test/dispatch/mount_test.rb2
-rw-r--r--actionpack/test/dispatch/request/json_params_parsing_test.rb12
-rw-r--r--actionpack/test/dispatch/request/multipart_params_parsing_test.rb15
-rw-r--r--actionpack/test/dispatch/request/query_string_parsing_test.rb9
-rw-r--r--actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb4
-rw-r--r--actionpack/test/dispatch/request_id_test.rb14
-rw-r--r--actionpack/test/dispatch/request_test.rb52
-rw-r--r--actionpack/test/dispatch/response_test.rb22
-rw-r--r--actionpack/test/dispatch/routing/inspector_test.rb52
-rw-r--r--actionpack/test/dispatch/routing/route_set_test.rb56
-rw-r--r--actionpack/test/dispatch/routing_test.rb169
-rw-r--r--actionpack/test/dispatch/session/cache_store_test.rb2
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb14
-rw-r--r--actionpack/test/dispatch/session/mem_cache_store_test.rb4
-rw-r--r--actionpack/test/dispatch/show_exceptions_test.rb24
-rw-r--r--actionpack/test/dispatch/ssl_test.rb2
-rw-r--r--actionpack/test/dispatch/static_test.rb34
-rw-r--r--actionpack/test/dispatch/template_assertions_test.rb14
-rw-r--r--actionpack/test/dispatch/test_request_test.rb2
-rw-r--r--actionpack/test/dispatch/url_generation_test.rb4
-rw-r--r--actionpack/test/fixtures/collection_cache/index.html.erb1
-rw-r--r--actionpack/test/fixtures/customers/_commented_customer.html.erb4
-rw-r--r--actionpack/test/fixtures/customers/_customer.html.erb3
-rw-r--r--actionpack/test/fixtures/localized/hello_world.de.html2
-rw-r--r--actionpack/test/fixtures/symlink_parent/symlinked_layout.erb5
-rw-r--r--actionpack/test/journey/path/pattern_test.rb2
-rw-r--r--actionpack/test/journey/router/utils_test.rb1
-rw-r--r--actionpack/test/journey/router_test.rb29
-rw-r--r--actionpack/test/journey/routes_test.rb22
-rw-r--r--actionpack/test/routing/helper_test.rb14
-rw-r--r--actionview/CHANGELOG.md220
-rw-r--r--actionview/MIT-LICENSE2
-rw-r--r--actionview/Rakefile2
-rw-r--r--actionview/actionview.gemspec6
-rw-r--r--actionview/lib/action_view.rb2
-rw-r--r--actionview/lib/action_view/base.rb12
-rw-r--r--actionview/lib/action_view/dependency_tracker.rb19
-rw-r--r--actionview/lib/action_view/gem_version.rb6
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb3
-rw-r--r--actionview/lib/action_view/helpers/atom_feed_helper.rb5
-rw-r--r--actionview/lib/action_view/helpers/cache_helper.rb35
-rw-r--r--actionview/lib/action_view/helpers/capture_helper.rb7
-rw-r--r--actionview/lib/action_view/helpers/date_helper.rb6
-rw-r--r--actionview/lib/action_view/helpers/debug_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb61
-rw-r--r--actionview/lib/action_view/helpers/form_options_helper.rb44
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb11
-rw-r--r--actionview/lib/action_view/helpers/javascript_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/number_helper.rb17
-rw-r--r--actionview/lib/action_view/helpers/record_tag_helper.rb111
-rw-r--r--actionview/lib/action_view/helpers/rendering_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/sanitize_helper.rb130
-rw-r--r--actionview/lib/action_view/helpers/tag_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags.rb1
-rw-r--r--actionview/lib/action_view/helpers/tags/base.rb60
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_check_boxes.rb9
-rw-r--r--actionview/lib/action_view/helpers/tags/file_field.rb15
-rw-r--r--actionview/lib/action_view/helpers/tags/label.rb16
-rw-r--r--actionview/lib/action_view/helpers/tags/placeholderable.rb18
-rw-r--r--actionview/lib/action_view/helpers/tags/search_field.rb1
-rw-r--r--actionview/lib/action_view/helpers/tags/translator.rb40
-rw-r--r--actionview/lib/action_view/helpers/text_helper.rb9
-rw-r--r--actionview/lib/action_view/helpers/translation_helper.rb44
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb51
-rw-r--r--actionview/lib/action_view/layouts.rb29
-rw-r--r--actionview/lib/action_view/lookup_context.rb11
-rw-r--r--actionview/lib/action_view/model_naming.rb2
-rw-r--r--actionview/lib/action_view/railtie.rb8
-rw-r--r--actionview/lib/action_view/record_identifier.rb63
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer.rb53
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb70
-rw-r--r--actionview/lib/action_view/renderer/renderer.rb2
-rw-r--r--actionview/lib/action_view/renderer/template_renderer.rb2
-rw-r--r--actionview/lib/action_view/rendering.rb11
-rw-r--r--actionview/lib/action_view/routing_url_for.rb39
-rw-r--r--actionview/lib/action_view/tasks/dependencies.rake16
-rw-r--r--actionview/lib/action_view/template.rb29
-rw-r--r--actionview/lib/action_view/template/error.rb2
-rw-r--r--actionview/lib/action_view/template/handlers.rb8
-rw-r--r--actionview/lib/action_view/template/handlers/erb.rb20
-rw-r--r--actionview/lib/action_view/template/handlers/raw.rb2
-rw-r--r--actionview/lib/action_view/template/resolver.rb33
-rw-r--r--actionview/lib/action_view/template/types.rb2
-rw-r--r--actionview/lib/action_view/test_case.rb5
-rw-r--r--actionview/lib/action_view/view_paths.rb22
-rw-r--r--actionview/test/abstract_unit.rb66
-rw-r--r--actionview/test/actionpack/abstract/abstract_controller_test.rb18
-rw-r--r--actionview/test/actionpack/abstract/views/abstract_controller/testing/me5/index.erb1
-rw-r--r--actionview/test/actionpack/controller/layout_test.rb15
-rw-r--r--actionview/test/actionpack/controller/render_test.rb27
-rw-r--r--actionview/test/actionpack/controller/view_paths_test.rb2
-rw-r--r--actionview/test/active_record_unit.rb2
-rw-r--r--actionview/test/activerecord/controller_runtime_test.rb2
-rw-r--r--actionview/test/activerecord/debug_helper_test.rb (renamed from actionview/test/template/debug_helper_test.rb)6
-rw-r--r--actionview/test/activerecord/polymorphic_routes_test.rb24
-rw-r--r--actionview/test/fixtures/blog_public/.gitignore1
-rw-r--r--actionview/test/fixtures/blog_public/blog.html1
-rw-r--r--actionview/test/fixtures/blog_public/index.html1
-rw-r--r--actionview/test/fixtures/blog_public/subdir/index.html1
-rw-r--r--actionview/test/fixtures/functional_caching/fragment_cached_without_digest.html.erb3
-rw-r--r--actionview/test/fixtures/happy_path/render_action/hello_world.erb1
-rw-r--r--actionview/test/fixtures/layouts/streaming_with_capture.erb6
-rw-r--r--actionview/test/fixtures/multipart/bracketed_utf8_param5
-rw-r--r--actionview/test/fixtures/multipart/single_utf8_param5
-rw-r--r--actionview/test/fixtures/scope/test/modgreet.erb1
-rw-r--r--actionview/test/fixtures/test/_FooBar.html.erb1
-rw-r--r--actionview/test/fixtures/test/_a-in.html.erb0
-rw-r--r--actionview/test/fixtures/test/_cached_customer.erb3
-rw-r--r--actionview/test/fixtures/test/_cached_customer_as.erb3
-rw-r--r--actionview/test/fixtures/test/_label_with_block.erb2
-rw-r--r--actionview/test/fixtures/test/_partial_shortcut_with_block_content.html.erb3
-rw-r--r--actionview/test/lib/controller/fake_models.rb41
-rw-r--r--actionview/test/template/asset_tag_helper_test.rb3
-rw-r--r--actionview/test/template/atom_feed_helper_test.rb56
-rw-r--r--actionview/test/template/date_helper_test.rb4
-rw-r--r--actionview/test/template/dependency_tracker_test.rb2
-rw-r--r--actionview/test/template/erb_util_test.rb2
-rw-r--r--actionview/test/template/form_helper_test.rb165
-rw-r--r--actionview/test/template/form_tag_helper_test.rb14
-rw-r--r--actionview/test/template/javascript_helper_test.rb9
-rw-r--r--actionview/test/template/number_helper_test.rb4
-rw-r--r--actionview/test/template/record_identifier_test.rb44
-rw-r--r--actionview/test/template/record_tag_helper_test.rb90
-rw-r--r--actionview/test/template/render_test.rb82
-rw-r--r--actionview/test/template/sanitize_helper_test.rb4
-rw-r--r--actionview/test/template/streaming_render_test.rb5
-rw-r--r--actionview/test/template/tag_helper_test.rb10
-rw-r--r--actionview/test/template/template_test.rb3
-rw-r--r--actionview/test/template/test_case_test.rb3
-rw-r--r--actionview/test/template/text_helper_test.rb1
-rw-r--r--actionview/test/template/translation_helper_test.rb45
-rw-r--r--actionview/test/template/url_helper_test.rb28
-rw-r--r--activejob/CHANGELOG.md85
-rw-r--r--activejob/MIT-LICENSE2
-rw-r--r--activejob/README.md13
-rw-r--r--activejob/Rakefile6
-rw-r--r--activejob/activejob.gemspec2
-rw-r--r--activejob/lib/active_job.rb2
-rw-r--r--activejob/lib/active_job/arguments.rb49
-rw-r--r--activejob/lib/active_job/base.rb2
-rw-r--r--activejob/lib/active_job/callbacks.rb4
-rw-r--r--activejob/lib/active_job/core.rb32
-rw-r--r--activejob/lib/active_job/gem_version.rb6
-rw-r--r--activejob/lib/active_job/logging.rb9
-rw-r--r--activejob/lib/active_job/queue_adapter.rb54
-rw-r--r--activejob/lib/active_job/queue_adapters.rb72
-rw-r--r--activejob/lib/active_job/queue_adapters/backburner_adapter.rb14
-rw-r--r--activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb12
-rw-r--r--activejob/lib/active_job/queue_adapters/inline_adapter.rb12
-rw-r--r--activejob/lib/active_job/queue_adapters/qu_adapter.rb16
-rw-r--r--activejob/lib/active_job/queue_adapters/que_adapter.rb12
-rw-r--r--activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb38
-rw-r--r--activejob/lib/active_job/queue_adapters/resque_adapter.rb18
-rw-r--r--activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb30
-rw-r--r--activejob/lib/active_job/queue_adapters/sneakers_adapter.rb20
-rw-r--r--activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb12
-rw-r--r--activejob/lib/active_job/queue_adapters/test_adapter.rb38
-rw-r--r--activejob/lib/active_job/test_helper.rb168
-rw-r--r--activejob/lib/rails/generators/job/job_generator.rb1
-rw-r--r--activejob/lib/rails/generators/job/templates/job.rb2
-rw-r--r--activejob/test/adapters/test.rb3
-rw-r--r--activejob/test/cases/adapter_test.rb5
-rw-r--r--activejob/test/cases/argument_serialization_test.rb39
-rw-r--r--activejob/test/cases/logging_test.rb11
-rw-r--r--activejob/test/cases/queue_adapter_test.rb56
-rw-r--r--activejob/test/cases/test_case_test.rb11
-rw-r--r--activejob/test/cases/test_helper_test.rb218
-rw-r--r--activejob/test/helper.rb13
-rw-r--r--activejob/test/integration/queuing_test.rb13
-rw-r--r--activejob/test/jobs/kwargs_job.rb7
-rw-r--r--activejob/test/support/delayed_job/delayed/backend/test.rb4
-rw-r--r--activejob/test/support/integration/adapters/qu.rb2
-rw-r--r--activejob/test/support/integration/adapters/queue_classic.rb4
-rw-r--r--activejob/test/support/integration/adapters/resque.rb2
-rw-r--r--activejob/test/support/integration/adapters/sidekiq.rb3
-rw-r--r--activejob/test/support/integration/dummy_app_template.rb2
-rw-r--r--activejob/test/support/integration/helper.rb4
-rw-r--r--activejob/test/support/integration/jobs_manager.rb2
-rw-r--r--activejob/test/support/integration/test_case_helpers.rb6
-rw-r--r--activemodel/CHANGELOG.md96
-rw-r--r--activemodel/MIT-LICENSE2
-rw-r--r--activemodel/README.rdoc8
-rw-r--r--activemodel/Rakefile2
-rw-r--r--activemodel/activemodel.gemspec2
-rw-r--r--activemodel/lib/active_model.rb4
-rw-r--r--activemodel/lib/active_model/attribute_assignment.rb52
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb10
-rw-r--r--activemodel/lib/active_model/callbacks.rb5
-rw-r--r--activemodel/lib/active_model/conversion.rb6
-rw-r--r--activemodel/lib/active_model/dirty.rb77
-rw-r--r--activemodel/lib/active_model/errors.rb158
-rw-r--r--activemodel/lib/active_model/gem_version.rb8
-rw-r--r--activemodel/lib/active_model/lint.rb60
-rw-r--r--activemodel/lib/active_model/locale/en.yml3
-rw-r--r--activemodel/lib/active_model/model.rb7
-rw-r--r--activemodel/lib/active_model/naming.rb10
-rw-r--r--activemodel/lib/active_model/secure_password.rb11
-rw-r--r--activemodel/lib/active_model/serializers/json.rb8
-rw-r--r--activemodel/lib/active_model/serializers/xml.rb2
-rw-r--r--activemodel/lib/active_model/validations.rb37
-rw-r--r--activemodel/lib/active_model/validations/absence.rb2
-rw-r--r--activemodel/lib/active_model/validations/acceptance.rb8
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb7
-rw-r--r--activemodel/lib/active_model/validations/format.rb2
-rw-r--r--activemodel/lib/active_model/validations/length.rb50
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb11
-rw-r--r--activemodel/lib/active_model/validator.rb8
-rw-r--r--activemodel/lib/active_model/version.rb2
-rw-r--r--activemodel/test/cases/attribute_assignment_test.rb109
-rw-r--r--activemodel/test/cases/callbacks_test.rb29
-rw-r--r--activemodel/test/cases/dirty_test.rb21
-rw-r--r--activemodel/test/cases/errors_test.rb118
-rw-r--r--activemodel/test/cases/helper.rb12
-rw-r--r--activemodel/test/cases/model_test.rb4
-rw-r--r--activemodel/test/cases/serializers/json_serialization_test.rb4
-rw-r--r--activemodel/test/cases/validations/absence_validation_test.rb1
-rw-r--r--activemodel/test/cases/validations/acceptance_validation_test.rb7
-rw-r--r--activemodel/test/cases/validations/callbacks_test.rb58
-rw-r--r--activemodel/test/cases/validations/conditional_validation_test.rb1
-rw-r--r--activemodel/test/cases/validations/confirmation_validation_test.rb1
-rw-r--r--activemodel/test/cases/validations/exclusion_validation_test.rb1
-rw-r--r--activemodel/test/cases/validations/format_validation_test.rb1
-rw-r--r--activemodel/test/cases/validations/i18n_generate_message_validation_test.rb2
-rw-r--r--activemodel/test/cases/validations/i18n_validation_test.rb2
-rw-r--r--activemodel/test/cases/validations/inclusion_validation_test.rb1
-rw-r--r--activemodel/test/cases/validations/length_validation_test.rb30
-rw-r--r--activemodel/test/cases/validations/numericality_validation_test.rb5
-rw-r--r--activemodel/test/cases/validations/presence_validation_test.rb1
-rw-r--r--activemodel/test/cases/validations/validates_test.rb1
-rw-r--r--activemodel/test/cases/validations/validations_context_test.rb1
-rw-r--r--activemodel/test/cases/validations/with_validation_test.rb1
-rw-r--r--activemodel/test/cases/validations_test.rb59
-rw-r--r--activemodel/test/models/topic.rb4
-rw-r--r--activerecord/CHANGELOG.md1364
-rw-r--r--activerecord/MIT-LICENSE2
-rw-r--r--activerecord/RUNNING_UNIT_TESTS.rdoc16
-rw-r--r--activerecord/Rakefile2
-rw-r--r--activerecord/activerecord.gemspec4
-rw-r--r--activerecord/examples/performance.rb4
-rw-r--r--activerecord/lib/active_record.rb7
-rw-r--r--activerecord/lib/active_record/aggregations.rb33
-rw-r--r--activerecord/lib/active_record/association_relation.rb17
-rw-r--r--activerecord/lib/active_record/associations.rb111
-rw-r--r--activerecord/lib/active_record/associations/alias_tracker.rb43
-rw-r--r--activerecord/lib/active_record/associations/association.rb6
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb176
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb14
-rw-r--r--activerecord/lib/active_record/associations/builder/association.rb60
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb39
-rw-r--r--activerecord/lib/active_record/associations/builder/collection_association.rb24
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb16
-rw-r--r--activerecord/lib/active_record/associations/builder/has_many.rb6
-rw-r--r--activerecord/lib/active_record/associations/builder/has_one.rb15
-rw-r--r--activerecord/lib/active_record/associations/builder/singular_association.rb9
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb77
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb16
-rw-r--r--activerecord/lib/active_record/associations/foreign_association.rb11
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb39
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb43
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb3
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb23
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb21
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb3
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb22
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_many_through.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader/through_association.rb5
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb15
-rw-r--r--activerecord/lib/active_record/attribute.rb53
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb164
-rw-r--r--activerecord/lib/active_record/attribute_decorators.rb11
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb61
-rw-r--r--activerecord/lib/active_record/attribute_methods/before_type_cast.rb5
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb26
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb3
-rw-r--r--activerecord/lib/active_record/attribute_methods/query.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb74
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb15
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb55
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb35
-rw-r--r--activerecord/lib/active_record/attribute_set.rb34
-rw-r--r--activerecord/lib/active_record/attribute_set/builder.rb87
-rw-r--r--activerecord/lib/active_record/attributes.rb250
-rw-r--r--activerecord/lib/active_record/autosave_association.rb45
-rw-r--r--activerecord/lib/active_record/base.rb8
-rw-r--r--activerecord/lib/active_record/callbacks.rb19
-rw-r--r--activerecord/lib/active_record/coders/yaml_column.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb41
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb52
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb67
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb43
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb315
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb25
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb132
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb73
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb78
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb290
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb58
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb23
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb23
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb26
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb93
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/column.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb75
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/date.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/infinity.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/integer.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb39
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/time.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb48
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/vector.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/xml.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb43
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb39
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb162
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb129
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb35
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/utils.rb19
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb217
-rw-r--r--activerecord/lib/active_record/connection_adapters/schema_cache.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb32
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb104
-rw-r--r--activerecord/lib/active_record/connection_handling.rb4
-rw-r--r--activerecord/lib/active_record/core.rb191
-rw-r--r--activerecord/lib/active_record/counter_cache.rb19
-rw-r--r--activerecord/lib/active_record/dynamic_matchers.rb21
-rw-r--r--activerecord/lib/active_record/enum.rb98
-rw-r--r--activerecord/lib/active_record/errors.rb33
-rw-r--r--activerecord/lib/active_record/fixtures.rb75
-rw-r--r--activerecord/lib/active_record/gem_version.rb6
-rw-r--r--activerecord/lib/active_record/inheritance.rb14
-rw-r--r--activerecord/lib/active_record/legacy_yaml_adapter.rb46
-rw-r--r--activerecord/lib/active_record/locale/en.yml1
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb46
-rw-r--r--activerecord/lib/active_record/locking/pessimistic.rb2
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb23
-rw-r--r--activerecord/lib/active_record/migration.rb19
-rw-r--r--activerecord/lib/active_record/model_schema.rb93
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb43
-rw-r--r--activerecord/lib/active_record/no_touching.rb2
-rw-r--r--activerecord/lib/active_record/null_relation.rb20
-rw-r--r--activerecord/lib/active_record/persistence.rb99
-rw-r--r--activerecord/lib/active_record/querying.rb9
-rw-r--r--activerecord/lib/active_record/railtie.rb10
-rw-r--r--activerecord/lib/active_record/railties/controller_runtime.rb2
-rw-r--r--activerecord/lib/active_record/railties/databases.rake26
-rw-r--r--activerecord/lib/active_record/readonly_attributes.rb2
-rw-r--r--activerecord/lib/active_record/reflection.rb163
-rw-r--r--activerecord/lib/active_record/relation.rb124
-rw-r--r--activerecord/lib/active_record/relation/batches.rb66
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb65
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb1
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb27
-rw-r--r--activerecord/lib/active_record/relation/from_clause.rb32
-rw-r--r--activerecord/lib/active_record/relation/merger.rb65
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb179
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/array_handler.rb28
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb78
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/base_handler.rb17
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb17
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/class_handler.rb27
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/range_handler.rb17
-rw-r--r--activerecord/lib/active_record/relation/query_attribute.rb19
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb333
-rw-r--r--activerecord/lib/active_record/relation/record_fetch_warning.rb49
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb7
-rw-r--r--activerecord/lib/active_record/relation/where_clause.rb173
-rw-r--r--activerecord/lib/active_record/relation/where_clause_factory.rb34
-rw-r--r--activerecord/lib/active_record/result.rb2
-rw-r--r--activerecord/lib/active_record/sanitization.rb44
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb28
-rw-r--r--activerecord/lib/active_record/scoping.rb15
-rw-r--r--activerecord/lib/active_record/scoping/default.rb5
-rw-r--r--activerecord/lib/active_record/scoping/named.rb36
-rw-r--r--activerecord/lib/active_record/secure_token.rb38
-rw-r--r--activerecord/lib/active_record/serialization.rb2
-rw-r--r--activerecord/lib/active_record/serializers/xml_serializer.rb4
-rw-r--r--activerecord/lib/active_record/statement_cache.rb51
-rw-r--r--activerecord/lib/active_record/store.rb6
-rw-r--r--activerecord/lib/active_record/suppressor.rb55
-rw-r--r--activerecord/lib/active_record/table_metadata.rb62
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb43
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb1
-rw-r--r--activerecord/lib/active_record/tasks/postgresql_database_tasks.rb12
-rw-r--r--activerecord/lib/active_record/timestamp.rb6
-rw-r--r--activerecord/lib/active_record/touch_later.rb50
-rw-r--r--activerecord/lib/active_record/transactions.rb143
-rw-r--r--activerecord/lib/active_record/type.rb52
-rw-r--r--activerecord/lib/active_record/type/adapter_specific_registry.rb142
-rw-r--r--activerecord/lib/active_record/type/binary.rb6
-rw-r--r--activerecord/lib/active_record/type/boolean.rb13
-rw-r--r--activerecord/lib/active_record/type/date.rb11
-rw-r--r--activerecord/lib/active_record/type/date_time.rb23
-rw-r--r--activerecord/lib/active_record/type/decimal.rb12
-rw-r--r--activerecord/lib/active_record/type/decorator.rb14
-rw-r--r--activerecord/lib/active_record/type/float.rb12
-rw-r--r--activerecord/lib/active_record/type/hash_lookup_type_map.rb18
-rw-r--r--activerecord/lib/active_record/type/helpers.rb4
-rw-r--r--activerecord/lib/active_record/type/helpers/accepts_multiparameter_time.rb30
-rw-r--r--activerecord/lib/active_record/type/helpers/mutable.rb18
-rw-r--r--activerecord/lib/active_record/type/helpers/numeric.rb34
-rw-r--r--activerecord/lib/active_record/type/helpers/time_value.rb58
-rw-r--r--activerecord/lib/active_record/type/integer.rb33
-rw-r--r--activerecord/lib/active_record/type/mutable.rb16
-rw-r--r--activerecord/lib/active_record/type/numeric.rb36
-rw-r--r--activerecord/lib/active_record/type/serialized.rb27
-rw-r--r--activerecord/lib/active_record/type/string.rb10
-rw-r--r--activerecord/lib/active_record/type/time.rb18
-rw-r--r--activerecord/lib/active_record/type/time_value.rb38
-rw-r--r--activerecord/lib/active_record/type/type_map.rb30
-rw-r--r--activerecord/lib/active_record/type/unsigned_integer.rb15
-rw-r--r--activerecord/lib/active_record/type/value.rb93
-rw-r--r--activerecord/lib/active_record/type_caster.rb7
-rw-r--r--activerecord/lib/active_record/type_caster/connection.rb29
-rw-r--r--activerecord/lib/active_record/type_caster/map.rb19
-rw-r--r--activerecord/lib/active_record/validations.rb24
-rw-r--r--activerecord/lib/active_record/validations/length.rb33
-rw-r--r--activerecord/lib/active_record/validations/presence.rb3
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb19
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb7
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/templates/migration.rb3
-rw-r--r--activerecord/lib/rails/generators/active_record/model/templates/model.rb3
-rw-r--r--activerecord/test/active_record/connection_adapters/fake_adapter.rb9
-rw-r--r--activerecord/test/cases/adapter_test.rb16
-rw-r--r--activerecord/test/cases/adapters/mysql/active_schema_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql/charset_collation_test.rb54
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb14
-rw-r--r--activerecord/test/cases/adapters/mysql/consistency_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb17
-rw-r--r--activerecord/test/cases/adapters/mysql/quoting_test.rb8
-rw-r--r--activerecord/test/cases/adapters/mysql/reserved_word_test.rb8
-rw-r--r--activerecord/test/cases/adapters/mysql/schema_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql/unsigned_type_test.rb30
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql2/boolean_test.rb8
-rw-r--r--activerecord/test/cases/adapters/mysql2/charset_collation_test.rb54
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb13
-rw-r--r--activerecord/test/cases/adapters/mysql2/explain_test.rb3
-rw-r--r--activerecord/test/cases/adapters/mysql2/reserved_word_test.rb8
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb22
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb30
-rw-r--r--activerecord/test/cases/adapters/postgresql/array_test.rb73
-rw-r--r--activerecord/test/cases/adapters/postgresql/bit_string_test.rb19
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb19
-rw-r--r--activerecord/test/cases/adapters/postgresql/change_schema_test.rb38
-rw-r--r--activerecord/test/cases/adapters/postgresql/cidr_test.rb25
-rw-r--r--activerecord/test/cases/adapters/postgresql/citext_test.rb17
-rw-r--r--activerecord/test/cases/adapters/postgresql/composite_test.rb23
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb12
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb45
-rw-r--r--activerecord/test/cases/adapters/postgresql/domain_test.rb10
-rw-r--r--activerecord/test/cases/adapters/postgresql/enum_test.rb20
-rw-r--r--activerecord/test/cases/adapters/postgresql/explain_test.rb3
-rw-r--r--activerecord/test/cases/adapters/postgresql/extension_migration_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/full_text_test.rb34
-rw-r--r--activerecord/test/cases/adapters/postgresql/geometric_test.rb89
-rw-r--r--activerecord/test/cases/adapters/postgresql/hstore_test.rb98
-rw-r--r--activerecord/test/cases/adapters/postgresql/infinity_test.rb27
-rw-r--r--activerecord/test/cases/adapters/postgresql/integer_test.rb25
-rw-r--r--activerecord/test/cases/adapters/postgresql/json_test.rb48
-rw-r--r--activerecord/test/cases/adapters/postgresql/ltree_test.rb17
-rw-r--r--activerecord/test/cases/adapters/postgresql/money_test.rb28
-rw-r--r--activerecord/test/cases/adapters/postgresql/network_test.rb45
-rw-r--r--activerecord/test/cases/adapters/postgresql/numbers_test.rb49
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb50
-rw-r--r--activerecord/test/cases/adapters/postgresql/quoting_test.rb48
-rw-r--r--activerecord/test/cases/adapters/postgresql/range_test.rb36
-rw-r--r--activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb111
-rw-r--r--activerecord/test/cases/adapters/postgresql/rename_table_test.rb34
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb9
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb76
-rw-r--r--activerecord/test/cases/adapters/postgresql/serial_test.rb60
-rw-r--r--activerecord/test/cases/adapters/postgresql/sql_types_test.rb18
-rw-r--r--activerecord/test/cases/adapters/postgresql/timestamp_test.rb66
-rw-r--r--activerecord/test/cases/adapters/postgresql/type_lookup_test.rb18
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb78
-rw-r--r--activerecord/test/cases/adapters/postgresql/xml_test.rb10
-rw-r--r--activerecord/test/cases/adapters/sqlite3/explain_test.rb3
-rw-r--r--activerecord/test/cases/adapters/sqlite3/quoting_test.rb51
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb33
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb1
-rw-r--r--activerecord/test/cases/ar_schema_test.rb66
-rw-r--r--activerecord/test/cases/associations/association_scope_test.rb7
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb102
-rw-r--r--activerecord/test/cases/associations/callbacks_test.rb1
-rw-r--r--activerecord/test/cases/associations/deprecated_counter_cache_on_has_many_through_test.rb26
-rw-r--r--activerecord/test/cases/associations/eager_load_nested_include_test.rb4
-rw-r--r--activerecord/test/cases/associations/eager_test.rb100
-rw-r--r--activerecord/test/cases/associations/extension_test.rb4
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb23
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb386
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb27
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb49
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb1
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb4
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb12
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb10
-rw-r--r--activerecord/test/cases/associations/required_test.rb30
-rw-r--r--activerecord/test/cases/associations_test.rb32
-rw-r--r--activerecord/test/cases/attribute_decorators_test.rb16
-rw-r--r--activerecord/test/cases/attribute_methods/read_test.rb10
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb88
-rw-r--r--activerecord/test/cases/attribute_set_test.rb52
-rw-r--r--activerecord/test/cases/attribute_test.rb52
-rw-r--r--activerecord/test/cases/attributes_test.rb85
-rw-r--r--activerecord/test/cases/autosave_association_test.rb64
-rw-r--r--activerecord/test/cases/base_test.rb159
-rw-r--r--activerecord/test/cases/batches_test.rb49
-rw-r--r--activerecord/test/cases/binary_test.rb1
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb38
-rw-r--r--activerecord/test/cases/calculations_test.rb44
-rw-r--r--activerecord/test/cases/callbacks_test.rb221
-rw-r--r--activerecord/test/cases/column_definition_test.rb79
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb4
-rw-r--r--activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb38
-rw-r--r--activerecord/test/cases/connection_adapters/type_lookup_test.rb7
-rw-r--r--activerecord/test/cases/core_test.rb11
-rw-r--r--activerecord/test/cases/counter_cache_test.rb18
-rw-r--r--activerecord/test/cases/date_time_precision_test.rb111
-rw-r--r--activerecord/test/cases/date_time_test.rb18
-rw-r--r--activerecord/test/cases/defaults_test.rb131
-rw-r--r--activerecord/test/cases/dirty_test.rb63
-rw-r--r--activerecord/test/cases/disconnected_test.rb2
-rw-r--r--activerecord/test/cases/enum_test.rb80
-rw-r--r--activerecord/test/cases/explain_test.rb2
-rw-r--r--activerecord/test/cases/finder_test.rb67
-rw-r--r--activerecord/test/cases/fixtures_test.rb82
-rw-r--r--activerecord/test/cases/helper.rb17
-rw-r--r--activerecord/test/cases/hot_compatibility_test.rb2
-rw-r--r--activerecord/test/cases/inheritance_test.rb14
-rw-r--r--activerecord/test/cases/integration_test.rb2
-rw-r--r--activerecord/test/cases/invalid_connection_test.rb2
-rw-r--r--activerecord/test/cases/locking_test.rb14
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb15
-rw-r--r--activerecord/test/cases/migration/change_schema_test.rb67
-rw-r--r--activerecord/test/cases/migration/change_table_test.rb45
-rw-r--r--activerecord/test/cases/migration/column_attributes_test.rb2
-rw-r--r--activerecord/test/cases/migration/column_positioning_test.rb14
-rw-r--r--activerecord/test/cases/migration/columns_test.rb10
-rw-r--r--activerecord/test/cases/migration/command_recorder_test.rb9
-rw-r--r--activerecord/test/cases/migration/create_join_table_test.rb2
-rw-r--r--activerecord/test/cases/migration/foreign_key_test.rb43
-rw-r--r--activerecord/test/cases/migration/index_test.rb14
-rw-r--r--activerecord/test/cases/migration/logger_test.rb2
-rw-r--r--activerecord/test/cases/migration/references_foreign_key_test.rb133
-rw-r--r--activerecord/test/cases/migration/references_statements_test.rb2
-rw-r--r--activerecord/test/cases/migration/rename_table_test.rb44
-rw-r--r--activerecord/test/cases/migration/table_and_index_test.rb4
-rw-r--r--activerecord/test/cases/migration_test.rb37
-rw-r--r--activerecord/test/cases/migrator_test.rb8
-rw-r--r--activerecord/test/cases/mixin_test.rb2
-rw-r--r--activerecord/test/cases/modules_test.rb4
-rw-r--r--activerecord/test/cases/multiparameter_attributes_test.rb12
-rw-r--r--activerecord/test/cases/multiple_db_test.rb9
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb33
-rw-r--r--activerecord/test/cases/persistence_test.rb59
-rw-r--r--activerecord/test/cases/pooled_connections_test.rb34
-rw-r--r--activerecord/test/cases/primary_keys_test.rb79
-rw-r--r--activerecord/test/cases/query_cache_test.rb34
-rw-r--r--activerecord/test/cases/quoting_test.rb23
-rw-r--r--activerecord/test/cases/readonly_test.rb13
-rw-r--r--activerecord/test/cases/reaper_test.rb2
-rw-r--r--activerecord/test/cases/reflection_test.rb44
-rw-r--r--activerecord/test/cases/relation/merging_test.rb16
-rw-r--r--activerecord/test/cases/relation/mutation_test.rb16
-rw-r--r--activerecord/test/cases/relation/or_test.rb84
-rw-r--r--activerecord/test/cases/relation/predicate_builder_test.rb6
-rw-r--r--activerecord/test/cases/relation/record_fetch_warning_test.rb28
-rw-r--r--activerecord/test/cases/relation/where_chain_test.rb122
-rw-r--r--activerecord/test/cases/relation/where_clause_test.rb182
-rw-r--r--activerecord/test/cases/relation/where_test.rb82
-rw-r--r--activerecord/test/cases/relation_test.rb85
-rw-r--r--activerecord/test/cases/relations_test.rb144
-rw-r--r--activerecord/test/cases/sanitize_test.rb11
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb138
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb85
-rw-r--r--activerecord/test/cases/scoping/named_scoping_test.rb11
-rw-r--r--activerecord/test/cases/scoping/relation_scoping_test.rb9
-rw-r--r--activerecord/test/cases/secure_token_test.rb32
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb36
-rw-r--r--activerecord/test/cases/suppressor_test.rb21
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb16
-rw-r--r--activerecord/test/cases/tasks/postgresql_rake_test.rb49
-rw-r--r--activerecord/test/cases/test_case.rb22
-rw-r--r--activerecord/test/cases/test_fixtures_test.rb36
-rw-r--r--activerecord/test/cases/time_precision_test.rb108
-rw-r--r--activerecord/test/cases/timestamp_test.rb24
-rw-r--r--activerecord/test/cases/touch_later_test.rb93
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb154
-rw-r--r--activerecord/test/cases/transaction_isolation_test.rb4
-rw-r--r--activerecord/test/cases/transactions_test.rb120
-rw-r--r--activerecord/test/cases/type/adapter_specific_registry_test.rb133
-rw-r--r--activerecord/test/cases/type/decimal_test.rb27
-rw-r--r--activerecord/test/cases/type/integer_test.rb81
-rw-r--r--activerecord/test/cases/type/string_test.rb10
-rw-r--r--activerecord/test/cases/type/type_map_test.rb47
-rw-r--r--activerecord/test/cases/type/unsigned_integer_test.rb17
-rw-r--r--activerecord/test/cases/type_test.rb39
-rw-r--r--activerecord/test/cases/types_test.rb115
-rw-r--r--activerecord/test/cases/unconnected_test.rb2
-rw-r--r--activerecord/test/cases/validations/association_validation_test.rb3
-rw-r--r--activerecord/test/cases/validations/length_validation_test.rb91
-rw-r--r--activerecord/test/cases/validations/presence_validation_test.rb17
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb62
-rw-r--r--activerecord/test/cases/validations_repair_helper.rb8
-rw-r--r--activerecord/test/cases/validations_test.rb15
-rw-r--r--activerecord/test/cases/xml_serialization_test.rb2
-rw-r--r--activerecord/test/cases/yaml_serialization_test.rb35
-rw-r--r--activerecord/test/config.example.yml6
-rw-r--r--activerecord/test/fixtures/bulbs.yml5
-rw-r--r--activerecord/test/fixtures/computers.yml5
-rw-r--r--activerecord/test/fixtures/dead_parrots.yml5
-rw-r--r--activerecord/test/fixtures/developers.yml1
-rw-r--r--activerecord/test/fixtures/live_parrots.yml4
-rw-r--r--activerecord/test/fixtures/naked/csv/accounts.csv1
-rw-r--r--activerecord/test/fixtures/pirates.yml3
-rw-r--r--activerecord/test/migrations/missing/1000_people_have_middle_names.rb2
-rw-r--r--activerecord/test/migrations/missing/1_people_have_last_names.rb2
-rw-r--r--activerecord/test/migrations/missing/3_we_need_reminders.rb2
-rw-r--r--activerecord/test/migrations/missing/4_innocent_jointable.rb2
-rw-r--r--activerecord/test/migrations/rename/1_we_need_things.rb2
-rw-r--r--activerecord/test/migrations/rename/2_rename_things.rb2
-rw-r--r--activerecord/test/migrations/valid/2_we_need_reminders.rb2
-rw-r--r--activerecord/test/migrations/valid/3_innocent_jointable.rb2
-rw-r--r--activerecord/test/migrations/valid_with_subdirectories/sub/2_we_need_reminders.rb2
-rw-r--r--activerecord/test/migrations/valid_with_subdirectories/sub1/3_innocent_jointable.rb2
-rw-r--r--activerecord/test/models/admin.rb2
-rw-r--r--activerecord/test/models/admin/account.rb2
-rw-r--r--activerecord/test/models/admin/randomly_named_c1.rb8
-rw-r--r--activerecord/test/models/author.rb6
-rw-r--r--activerecord/test/models/binary.rb2
-rw-r--r--activerecord/test/models/bird.rb2
-rw-r--r--activerecord/test/models/bulb.rb2
-rw-r--r--activerecord/test/models/car.rb2
-rw-r--r--activerecord/test/models/chef.rb1
-rw-r--r--activerecord/test/models/company.rb3
-rw-r--r--activerecord/test/models/company_in_module.rb2
-rw-r--r--activerecord/test/models/customer.rb2
-rw-r--r--activerecord/test/models/developer.rb2
-rw-r--r--activerecord/test/models/event.rb2
-rw-r--r--activerecord/test/models/guid.rb2
-rw-r--r--activerecord/test/models/hotel.rb1
-rw-r--r--activerecord/test/models/image.rb3
-rw-r--r--activerecord/test/models/notification.rb2
-rw-r--r--activerecord/test/models/organization.rb2
-rw-r--r--activerecord/test/models/owner.rb2
-rw-r--r--activerecord/test/models/parrot.rb2
-rw-r--r--activerecord/test/models/pirate.rb8
-rw-r--r--activerecord/test/models/post.rb13
-rw-r--r--activerecord/test/models/randomly_named_c1.rb2
-rw-r--r--activerecord/test/models/recipe.rb3
-rw-r--r--activerecord/test/models/ship.rb5
-rw-r--r--activerecord/test/models/ship_part.rb3
-rw-r--r--activerecord/test/models/treasure.rb1
-rw-r--r--activerecord/test/models/tyre.rb8
-rw-r--r--activerecord/test/models/user.rb8
-rw-r--r--activerecord/test/schema/mysql2_specific_schema.rb20
-rw-r--r--activerecord/test/schema/mysql_specific_schema.rb20
-rw-r--r--activerecord/test/schema/oracle_specific_schema.rb5
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb112
-rw-r--r--activerecord/test/schema/schema.rb42
-rw-r--r--activerecord/test/support/yaml_compatibility_fixtures/rails_4_1.yml22
-rw-r--r--activerecord/test/support/yaml_compatibility_fixtures/rails_4_2_0.yml182
-rw-r--r--activesupport/CHANGELOG.md441
-rw-r--r--activesupport/MIT-LICENSE2
-rw-r--r--activesupport/activesupport.gemspec6
-rwxr-xr-xactivesupport/bin/generate_tables2
-rw-r--r--activesupport/lib/active_support.rb13
-rw-r--r--activesupport/lib/active_support/array_inquirer.rb38
-rw-r--r--activesupport/lib/active_support/cache.rb90
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb1
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb17
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache.rb2
-rw-r--r--activesupport/lib/active_support/callbacks.rb298
-rw-r--r--activesupport/lib/active_support/concern.rb2
-rw-r--r--activesupport/lib/active_support/configurable.rb1
-rw-r--r--activesupport/lib/active_support/core_ext.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/array.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/array/access.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/array/conversions.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/array/inquiry.rb15
-rw-r--r--activesupport/lib/active_support/core_ext/big_decimal/conversions.rb13
-rw-r--r--activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/calculations.rb74
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/file/atomic.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/hash/compact.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/hash/deep_merge.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/except.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb22
-rw-r--r--activesupport/lib/active_support/core_ext/hash/slice.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/integer/time.rb15
-rw-r--r--activesupport/lib/active_support/core_ext/kernel.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/debugger.rb13
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/reporting.rb81
-rw-r--r--activesupport/lib/active_support/core_ext/load_error.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/marshal.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/module/aliasing.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/module/anonymous.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/module/attr_internal.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb40
-rw-r--r--activesupport/lib/active_support/core_ext/module/method_transplanting.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/module/remove_method.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/name_error.rb17
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/bytes.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/conversions.rb28
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/time.rb35
-rw-r--r--activesupport/lib/active_support/core_ext/object.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/object/deep_dup.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/duplicable.rb15
-rw-r--r--activesupport/lib/active_support/core_ext/object/instance_variables.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/itself.rb15
-rw-r--r--activesupport/lib/active_support/core_ext/object/json.rb16
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_query.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/try.rb16
-rw-r--r--activesupport/lib/active_support/core_ext/range/conversions.rb19
-rw-r--r--activesupport/lib/active_support/core_ext/range/each.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/range/include_range.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/securerandom.rb23
-rw-r--r--activesupport/lib/active_support/core_ext/string/behavior.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/filters.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/multibyte.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb26
-rw-r--r--activesupport/lib/active_support/core_ext/struct.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/thread.rb86
-rw-r--r--activesupport/lib/active_support/core_ext/time.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/time/marshal.rb31
-rw-r--r--activesupport/lib/active_support/core_ext/time/zones.rb20
-rw-r--r--activesupport/lib/active_support/dependencies.rb39
-rw-r--r--activesupport/lib/active_support/deprecation/behaviors.rb2
-rw-r--r--activesupport/lib/active_support/deprecation/method_wrappers.rb8
-rw-r--r--activesupport/lib/active_support/duration.rb38
-rw-r--r--activesupport/lib/active_support/gem_version.rb6
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb2
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb10
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb142
-rw-r--r--activesupport/lib/active_support/inflector/transliterate.rb14
-rw-r--r--activesupport/lib/active_support/json/encoding.rb49
-rw-r--r--activesupport/lib/active_support/message_verifier.rb80
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb5
-rw-r--r--activesupport/lib/active_support/notifications.rb2
-rw-r--r--activesupport/lib/active_support/notifications/instrumenter.rb12
-rw-r--r--activesupport/lib/active_support/number_helper.rb22
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_currency_converter.rb6
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_human_converter.rb4
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb53
-rw-r--r--activesupport/lib/active_support/railtie.rb5
-rw-r--r--activesupport/lib/active_support/rescuable.rb6
-rw-r--r--activesupport/lib/active_support/string_inquirer.rb2
-rw-r--r--activesupport/lib/active_support/subscriber.rb11
-rw-r--r--activesupport/lib/active_support/tagged_logging.rb4
-rw-r--r--activesupport/lib/active_support/test_case.rb38
-rw-r--r--activesupport/lib/active_support/testing/assertions.rb2
-rw-r--r--activesupport/lib/active_support/testing/file_fixtures.rb34
-rw-r--r--activesupport/lib/active_support/testing/isolation.rb4
-rw-r--r--activesupport/lib/active_support/testing/stream.rb42
-rw-r--r--activesupport/lib/active_support/testing/time_helpers.rb23
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb76
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb24
-rw-r--r--activesupport/lib/active_support/values/unicode_tables.datbin904640 -> 1001806 bytes
-rw-r--r--activesupport/lib/active_support/xml_mini/jdom.rb2
-rw-r--r--activesupport/lib/active_support/xml_mini/libxml.rb4
-rw-r--r--activesupport/lib/active_support/xml_mini/nokogiri.rb4
-rw-r--r--activesupport/test/abstract_unit.rb5
-rw-r--r--activesupport/test/array_inquirer_test.rb36
-rw-r--r--activesupport/test/caching_test.rb69
-rw-r--r--activesupport/test/callbacks_test.rb161
-rw-r--r--activesupport/test/concern_test.rb10
-rw-r--r--activesupport/test/configurable_test.rb8
-rw-r--r--activesupport/test/core_ext/array/access_test.rb4
-rw-r--r--activesupport/test/core_ext/array/conversions_test.rb6
-rw-r--r--activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb11
-rw-r--r--activesupport/test/core_ext/date_and_time_behavior.rb70
-rw-r--r--activesupport/test/core_ext/duration_test.rb22
-rw-r--r--activesupport/test/core_ext/enumerable_test.rb11
-rw-r--r--activesupport/test/core_ext/file_test.rb10
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb10
-rw-r--r--activesupport/test/core_ext/kernel_test.rb71
-rw-r--r--activesupport/test/core_ext/load_error_test.rb23
-rw-r--r--activesupport/test/core_ext/marshal_test.rb4
-rw-r--r--activesupport/test/core_ext/module_test.rb222
-rw-r--r--activesupport/test/core_ext/object/blank_test.rb1
-rw-r--r--activesupport/test/core_ext/object/duplicable_test.rb14
-rw-r--r--activesupport/test/core_ext/object/itself_test.rb9
-rw-r--r--activesupport/test/core_ext/object/try_test.rb4
-rw-r--r--activesupport/test/core_ext/range_ext_test.rb4
-rw-r--r--activesupport/test/core_ext/secure_random_test.rb20
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb38
-rw-r--r--activesupport/test/core_ext/struct_test.rb10
-rw-r--r--activesupport/test/core_ext/thread_test.rb75
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb2
-rw-r--r--activesupport/test/core_ext/uri_ext_test.rb1
-rw-r--r--activesupport/test/dependencies_test.rb247
-rw-r--r--activesupport/test/deprecation_test.rb19
-rw-r--r--activesupport/test/file_fixtures/sample.txt1
-rw-r--r--activesupport/test/hash_with_indifferent_access_test.rb3
-rw-r--r--activesupport/test/inflector_test_cases.rb1
-rw-r--r--activesupport/test/json/decoding_test.rb1
-rw-r--r--activesupport/test/json/encoding_test.rb43
-rw-r--r--activesupport/test/message_encryptor_test.rb11
-rw-r--r--activesupport/test/message_verifier_test.rb47
-rw-r--r--activesupport/test/multibyte_chars_test.rb3
-rw-r--r--activesupport/test/multibyte_conformance_test.rb3
-rw-r--r--activesupport/test/multibyte_proxy_test.rb1
-rw-r--r--activesupport/test/multibyte_test_helpers.rb1
-rw-r--r--activesupport/test/multibyte_unicode_database_test.rb1
-rw-r--r--activesupport/test/number_helper_test.rb8
-rw-r--r--activesupport/test/rescuable_test.rb33
-rw-r--r--activesupport/test/safe_buffer_test.rb12
-rw-r--r--activesupport/test/tagged_logging_test.rb13
-rw-r--r--activesupport/test/test_case_test.rb29
-rw-r--r--activesupport/test/testing/file_fixtures_test.rb28
-rw-r--r--activesupport/test/time_travel_test.rb11
-rw-r--r--activesupport/test/time_zone_test.rb16
-rw-r--r--activesupport/test/transliterate_test.rb1
-rw-r--r--activesupport/test/xml_mini/nokogiri_engine_test.rb20
-rw-r--r--activesupport/test/xml_mini/nokogirisax_engine_test.rb20
-rw-r--r--activesupport/test/xml_mini/rexml_engine_test.rb14
-rw-r--r--activesupport/test/xml_mini_test.rb2
-rwxr-xr-xci/travis.rb2
-rw-r--r--guides/CHANGELOG.md28
-rw-r--r--guides/Rakefile8
-rw-r--r--guides/assets/images/favicon.icobin1150 -> 5430 bytes
-rw-r--r--guides/assets/images/getting_started/article_with_comments.pngbin15190 -> 22560 bytes
-rw-r--r--guides/assets/stylesheets/main.css2
-rw-r--r--guides/bug_report_templates/action_controller_gem.rb8
-rw-r--r--guides/bug_report_templates/action_controller_master.rb6
-rw-r--r--guides/bug_report_templates/active_record_gem.rb6
-rw-r--r--guides/bug_report_templates/active_record_master.rb6
-rw-r--r--guides/rails_guides.rb46
-rw-r--r--guides/rails_guides/generator.rb1
-rw-r--r--guides/rails_guides/kindle.rb2
-rw-r--r--guides/rails_guides/levenshtein.rb2
-rw-r--r--guides/rails_guides/markdown/renderer.rb5
-rw-r--r--guides/source/2_2_release_notes.md2
-rw-r--r--guides/source/2_3_release_notes.md4
-rw-r--r--guides/source/3_0_release_notes.md6
-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.md47
-rw-r--r--guides/source/4_1_release_notes.md22
-rw-r--r--guides/source/4_2_release_notes.md525
-rw-r--r--guides/source/_welcome.html.erb9
-rw-r--r--guides/source/action_controller_overview.md167
-rw-r--r--guides/source/action_mailer_basics.md90
-rw-r--r--guides/source/action_view_overview.md126
-rw-r--r--guides/source/active_job_basics.md55
-rw-r--r--guides/source/active_model_basics.md6
-rw-r--r--guides/source/active_record_basics.md22
-rw-r--r--guides/source/active_record_callbacks.md4
-rw-r--r--guides/source/active_record_migrations.md41
-rw-r--r--guides/source/active_record_postgresql.md14
-rw-r--r--guides/source/active_record_querying.md107
-rw-r--r--guides/source/active_record_validations.md82
-rw-r--r--guides/source/active_support_core_extensions.md164
-rw-r--r--guides/source/active_support_instrumentation.md8
-rw-r--r--guides/source/api_documentation_guidelines.md2
-rw-r--r--guides/source/asset_pipeline.md63
-rw-r--r--guides/source/association_basics.md144
-rw-r--r--guides/source/autoloading_and_reloading_constants.md1316
-rw-r--r--guides/source/caching_with_rails.md10
-rw-r--r--guides/source/command_line.md74
-rw-r--r--guides/source/configuring.md125
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md33
-rw-r--r--guides/source/debugging_rails_applications.md82
-rw-r--r--guides/source/development_dependencies_install.md2
-rw-r--r--guides/source/documents.yaml32
-rw-r--r--guides/source/engines.md47
-rw-r--r--guides/source/form_helpers.md31
-rw-r--r--guides/source/generators.md10
-rw-r--r--guides/source/getting_started.md169
-rw-r--r--guides/source/i18n.md44
-rw-r--r--guides/source/initialization.md19
-rw-r--r--guides/source/layouts_and_rendering.md127
-rw-r--r--guides/source/maintenance_policy.md2
-rw-r--r--guides/source/nested_model_forms.md2
-rw-r--r--guides/source/plugins.md10
-rw-r--r--guides/source/profiling.md16
-rw-r--r--guides/source/rails_application_templates.md2
-rw-r--r--guides/source/rails_on_rack.md11
-rw-r--r--guides/source/routing.md20
-rw-r--r--guides/source/ruby_on_rails_guides_guidelines.md2
-rw-r--r--guides/source/security.md26
-rw-r--r--guides/source/testing.md837
-rw-r--r--guides/source/upgrading_ruby_on_rails.md136
-rw-r--r--guides/source/working_with_javascript_in_rails.md32
-rw-r--r--rails.gemspec6
-rw-r--r--railties/CHANGELOG.md197
-rw-r--r--railties/MIT-LICENSE2
-rw-r--r--railties/lib/rails.rb10
-rw-r--r--railties/lib/rails/api/task.rb14
-rw-r--r--railties/lib/rails/app_rails_loader.rb3
-rw-r--r--railties/lib/rails/application.rb65
-rw-r--r--railties/lib/rails/application/configuration.rb23
-rw-r--r--railties/lib/rails/application/default_middleware_stack.rb2
-rw-r--r--railties/lib/rails/application/finisher.rb7
-rw-r--r--railties/lib/rails/application/routes_reloader.rb4
-rw-r--r--railties/lib/rails/code_statistics.rb19
-rw-r--r--railties/lib/rails/code_statistics_calculator.rb8
-rw-r--r--railties/lib/rails/commands.rb3
-rw-r--r--railties/lib/rails/commands/commands_tasks.rb7
-rw-r--r--railties/lib/rails/commands/console.rb28
-rw-r--r--railties/lib/rails/commands/dbconsole.rb24
-rw-r--r--railties/lib/rails/commands/destroy.rb2
-rw-r--r--railties/lib/rails/commands/generate.rb2
-rw-r--r--railties/lib/rails/commands/plugin.rb2
-rw-r--r--railties/lib/rails/commands/runner.rb1
-rw-r--r--railties/lib/rails/commands/server.rb18
-rw-r--r--railties/lib/rails/commands/test.rb5
-rw-r--r--railties/lib/rails/configuration.rb10
-rw-r--r--railties/lib/rails/deprecation.rb19
-rw-r--r--railties/lib/rails/engine.rb32
-rw-r--r--railties/lib/rails/engine/configuration.rb5
-rw-r--r--railties/lib/rails/gem_version.rb6
-rw-r--r--railties/lib/rails/generators.rb12
-rw-r--r--railties/lib/rails/generators/actions.rb7
-rw-r--r--railties/lib/rails/generators/app_base.rb74
-rw-r--r--railties/lib/rails/generators/base.rb2
-rw-r--r--railties/lib/rails/generators/erb/mailer/mailer_generator.rb31
-rw-r--r--railties/lib/rails/generators/erb/mailer/templates/layout.html.erb5
-rw-r--r--railties/lib/rails/generators/erb/mailer/templates/layout.text.erb1
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb8
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb2
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/index.html.erb2
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/new.html.erb2
-rw-r--r--railties/lib/rails/generators/generated_attribute.rb10
-rw-r--r--railties/lib/rails/generators/migration.rb4
-rw-r--r--railties/lib/rails/generators/named_base.rb10
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb27
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile15
-rw-r--r--railties/lib/rails/generators/rails/app/templates/README.md (renamed from railties/lib/rails/generators/rails/app/templates/README.rdoc)6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/jobs/application_job.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/rails2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/setup26
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config.ru2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb10
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt28
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt8
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb4
-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/gitignore3
-rw-r--r--railties/lib/rails/generators/rails/migration/migration_generator.rb2
-rw-r--r--railties/lib/rails/generators/rails/model/USAGE9
-rw-r--r--railties/lib/rails/generators/rails/model/model_generator.rb2
-rw-r--r--railties/lib/rails/generators/rails/plugin/plugin_generator.rb64
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec10
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/Gemfile10
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/README.rdoc2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/Rakefile2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/app/controllers/%name%/application_controller.rb.tt)5
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/app/helpers/%name%/application_helper.rb.tt4
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/app/helpers/%namespaced_name%/application_helper.rb.tt5
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/app/views/layouts/%name%/application.html.erb.tt14
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/app/views/layouts/%namespaced_name%/application.html.erb.tt14
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/config/routes.rb2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/gitignore4
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/%name%.rb6
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/%name%/engine.rb7
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/%name%/version.rb3
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb5
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb6
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb1
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%namespaced_name%_tasks.rake (renamed from railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%name%_tasks.rake)2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/application.rb6
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js4
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css7
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/test/%name%_test.rb7
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb7
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb6
-rw-r--r--railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb6
-rw-r--r--railties/lib/rails/generators/rails/scaffold/templates/scaffold.css11
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb1
-rw-r--r--railties/lib/rails/generators/resource_helpers.rb6
-rw-r--r--railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb11
-rw-r--r--railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb4
-rw-r--r--railties/lib/rails/generators/test_unit/mailer/templates/preview.rb8
-rw-r--r--railties/lib/rails/generators/test_unit/model/model_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/model/templates/fixtures.yml2
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb10
-rw-r--r--railties/lib/rails/generators/testing/behaviour.rb18
-rw-r--r--railties/lib/rails/info.rb7
-rw-r--r--railties/lib/rails/info_controller.rb25
-rw-r--r--railties/lib/rails/paths.rb16
-rw-r--r--railties/lib/rails/rack.rb4
-rw-r--r--railties/lib/rails/rack/debugger.rb25
-rw-r--r--railties/lib/rails/rack/log_tailer.rb38
-rw-r--r--railties/lib/rails/railtie.rb2
-rw-r--r--railties/lib/rails/ruby_version_check.rb6
-rw-r--r--railties/lib/rails/source_annotation_extractor.rb5
-rw-r--r--railties/lib/rails/tasks.rb5
-rw-r--r--railties/lib/rails/tasks/documentation.rake70
-rw-r--r--railties/lib/rails/tasks/initializers.rake6
-rw-r--r--railties/lib/rails/tasks/restart.rake4
-rw-r--r--railties/lib/rails/tasks/statistics.rake11
-rw-r--r--railties/lib/rails/tasks/tmp.rake16
-rw-r--r--railties/lib/rails/templates/rails/welcome/index.html.erb2
-rw-r--r--railties/lib/rails/test_help.rb8
-rw-r--r--railties/lib/rails/test_unit/minitest_plugin.rb14
-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/testing.rake48
-rw-r--r--railties/railties.gemspec3
-rw-r--r--railties/test/abstract_unit.rb24
-rw-r--r--railties/test/application/asset_debugging_test.rb4
-rw-r--r--railties/test/application/assets_test.rb17
-rw-r--r--railties/test/application/build_original_fullpath_test.rb27
-rw-r--r--railties/test/application/configuration_test.rb240
-rw-r--r--railties/test/application/initializers/frameworks_test.rb1
-rw-r--r--railties/test/application/loading_test.rb29
-rw-r--r--railties/test/application/mailer_previews_test.rb63
-rw-r--r--railties/test/application/middleware/exceptions_test.rb1
-rw-r--r--railties/test/application/middleware/sendfile_test.rb2
-rw-r--r--railties/test/application/middleware/session_test.rb9
-rw-r--r--railties/test/application/middleware/static_test.rb1
-rw-r--r--railties/test/application/middleware_test.rb20
-rw-r--r--railties/test/application/multiple_applications_test.rb14
-rw-r--r--railties/test/application/rake/dbs_test.rb34
-rw-r--r--railties/test/application/rake/restart_test.rb31
-rw-r--r--railties/test/application/rake_test.rb9
-rw-r--r--railties/test/application/routing_test.rb20
-rw-r--r--railties/test/application/test_runner_test.rb55
-rw-r--r--railties/test/application/test_test.rb111
-rw-r--r--railties/test/application/url_generation_test.rb15
-rw-r--r--railties/test/code_statistics_calculator_test.rb74
-rw-r--r--railties/test/code_statistics_test.rb20
-rw-r--r--railties/test/commands/console_test.rb22
-rw-r--r--railties/test/generators/actions_test.rb30
-rw-r--r--railties/test/generators/app_generator_test.rb138
-rw-r--r--railties/test/generators/generators_test_helper.rb10
-rw-r--r--railties/test/generators/job_generator_test.rb29
-rw-r--r--railties/test/generators/mailer_generator_test.rb135
-rw-r--r--railties/test/generators/migration_generator_test.rb52
-rw-r--r--railties/test/generators/model_generator_test.rb64
-rw-r--r--railties/test/generators/named_base_test.rb14
-rw-r--r--railties/test/generators/namespaced_generators_test.rb26
-rw-r--r--railties/test/generators/plugin_generator_test.rb156
-rw-r--r--railties/test/generators/scaffold_controller_generator_test.rb8
-rw-r--r--railties/test/generators/scaffold_generator_test.rb61
-rw-r--r--railties/test/generators/shared_generator_tests.rb4
-rw-r--r--railties/test/isolation/abstract_unit.rb40
-rw-r--r--railties/test/path_generation_test.rb1
-rw-r--r--railties/test/rails_info_controller_test.rb36
-rw-r--r--railties/test/railties/engine_test.rb27
-rw-r--r--railties/test/test_unit/reporter_test.rb74
-rw-r--r--railties/test/test_unit/runner_test.rb111
-rw-r--r--tasks/release.rb4
-rw-r--r--tools/line_statistics2
-rw-r--r--version.rb6
1275 files changed, 26597 insertions, 14814 deletions
diff --git a/.gitignore b/.gitignore
index bc96284375..c3cb009140 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,7 +5,6 @@ debug.log
.Gemfile
/.bundle
/.ruby-version
-/Gemfile.lock
pkg
/dist
/doc/rdoc
diff --git a/.travis.yml b/.travis.yml
index 2823a5456f..6fa8c491a5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,12 @@
language: ruby
+sudo: false
script: 'ci/travis.rb'
before_install:
- - travis_retry gem install bundler
- - "rvm current | grep 'jruby' && export AR_JDBC=true || echo"
+ - gem install bundler
+ - "rm ${BUNDLE_GEMFILE}.lock"
+before_script:
+ - bundle update
+cache: bundler
env:
global:
- JRUBY_OPTS='-J-Xmx1024M'
@@ -16,22 +20,17 @@ env:
- "GEM=ar:postgresql"
- "GEM=aj:integration"
rvm:
- - 1.9.3
- - 2.0.0
- - 2.1
+ - 2.2.1
- ruby-head
- rbx-2
- - jruby
+ - jruby-head
matrix:
allow_failures:
- - rvm: 1.9.3
- env: "GEM=ar:mysql"
- - rvm: 2.0.0
- env: "GEM=ar:mysql"
+ - env: "GEM=ar:mysql"
+ - env: "GEM=aj:integration"
- rvm: ruby-head
- env: "GEM=ar:mysql"
- rvm: rbx-2
- - rvm: jruby
+ - rvm: jruby-head
fast_finish: true
notifications:
email: false
@@ -45,11 +44,10 @@ notifications:
on_failure: always
rooms:
- secure: "YA1alef1ESHWGFNVwvmVGCkMe4cUy4j+UcNvMUESraceiAfVyRMAovlQBGs6\n9kBRm7DHYBUXYC2ABQoJbQRLDr/1B5JPf/M8+Qd7BKu8tcDC03U01SMHFLpO\naOs/HLXcDxtnnpL07tGVsm0zhMc5N8tq4/L3SHxK7Vi+TacwQzI="
-bundler_args: --path vendor/bundle --without test
+bundler_args: --without test --jobs 3 --retry 3
services:
- memcached
- redis
- rabbitmq
addons:
postgresql: "9.3"
-
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9ba2e53ef2..421e7088cb 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -6,7 +6,7 @@ Ruby on Rails is a volunteer effort. We encourage you to pitch in. [Join the tea
* If you want to contribute to Rails documentation, please read the [Contributing to the Rails Documentation](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation) section of the aforementioned guide.
-*We only accept bug reports and pull requests in GitHub*.
+*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).
diff --git a/Gemfile b/Gemfile
index 931c8e85bc..ddaaacdc59 100644
--- a/Gemfile
+++ b/Gemfile
@@ -11,15 +11,18 @@ gem 'rake', '>= 10.3'
gem 'mocha', '~> 0.14', require: false
gem 'rack-cache', '~> 1.2'
-gem 'jquery-rails', '~> 4.0.0.beta2'
+gem 'jquery-rails', github: 'rails/jquery-rails', branch: 'master'
gem 'coffee-rails', '~> 4.1.0'
gem 'turbolinks'
-gem 'arel', github: 'rails/arel'
+gem 'arel', github: 'rails/arel', branch: 'master'
+gem 'mail', github: 'mikel/mail'
+
+gem 'sprockets', '~> 3.0.0.rc.1'
# 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
@@ -27,12 +30,12 @@ gem 'uglifier', '>= 1.3.0', require: false
group :doc do
gem 'sdoc', '~> 0.4.0'
- gem 'redcarpet', '~> 3.1.2', platforms: :ruby
+ gem 'redcarpet', '~> 3.2.3', platforms: :ruby
gem 'w3c_validators'
- gem 'kindlerb'
+ gem 'kindlerb', '0.1.1'
end
-# AS
+# ActiveSupport
gem 'dalli', '>= 2.2.1'
# ActiveJob
@@ -42,7 +45,7 @@ group :job do
gem 'sidekiq', require: false
gem 'sucker_punch', require: false
gem 'delayed_job', require: false
- gem 'queue_classic', "< 3.0.0", require: false, platforms: :ruby
+ gem 'queue_classic', require: false, platforms: :ruby
gem 'sneakers', '0.1.1.pre', require: false
gem 'que', require: false
gem 'backburner', require: false
@@ -60,14 +63,11 @@ group :test do
# FIX: Our test suite isn't ready to run in random order yet
gem 'minitest', '< 5.3.4'
- platforms :mri_19 do
- gem 'ruby-prof', '~> 0.11.2'
+ platforms :mri do
+ gem 'stackprof'
+ # gem 'byebug'
end
- # platforms :mri_19, :mri_20 do
- # gem 'debugger'
- # end
-
gem 'benchmark-ips'
end
@@ -77,13 +77,13 @@ platforms :ruby do
# Needed for compiling the ActionDispatch::Journey parser
gem 'racc', '>=1.4.6', require: false
- # AR
+ # ActiveRecord
gem 'sqlite3', '~> 1.3.6'
group :db do
- gem 'pg', '>= 0.11.0'
+ gem 'pg', '>= 0.18.0'
gem 'mysql', '>= 2.9.0'
- gem 'mysql2', '>= 0.3.13'
+ gem 'mysql2', '>= 0.3.18'
end
end
diff --git a/Gemfile.lock b/Gemfile.lock
new file mode 100644
index 0000000000..543cfaf3da
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,297 @@
+GIT
+ remote: git://github.com/bkeepers/qu.git
+ revision: d098e2657c92e89a6413bebd9c033930759c061f
+ branch: master
+ specs:
+ qu (0.2.0)
+ qu-rails (0.2.0)
+ qu (= 0.2.0)
+ railties (>= 3.2, < 5)
+ qu-redis (0.2.0)
+ qu (= 0.2.0)
+ redis-namespace
+
+GIT
+ remote: git://github.com/mikel/mail.git
+ revision: b159e0a542962fdd5e292a48cfffa560d7cf412e
+ specs:
+ mail (2.6.3.edge)
+ mime-types (>= 1.16, < 3)
+
+GIT
+ remote: git://github.com/rails/arel.git
+ revision: aac9da257f291ad8d2d4f914528881c240848bb2
+ branch: master
+ specs:
+ arel (7.0.0.alpha)
+
+GIT
+ remote: git://github.com/rails/jquery-rails.git
+ revision: 272abdd319bb3381b23182b928b25320590096b0
+ branch: master
+ specs:
+ jquery-rails (4.0.3)
+ rails-dom-testing (~> 1.0)
+ railties (>= 4.2.0)
+ thor (>= 0.14, < 2.0)
+
+PATH
+ remote: .
+ specs:
+ actionmailer (5.0.0.alpha)
+ actionpack (= 5.0.0.alpha)
+ actionview (= 5.0.0.alpha)
+ activejob (= 5.0.0.alpha)
+ mail (~> 2.5, >= 2.5.4)
+ rails-dom-testing (~> 1.0, >= 1.0.5)
+ actionpack (5.0.0.alpha)
+ actionview (= 5.0.0.alpha)
+ activesupport (= 5.0.0.alpha)
+ rack (~> 1.6)
+ rack-test (~> 0.6.3)
+ rails-dom-testing (~> 1.0, >= 1.0.5)
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
+ actionview (5.0.0.alpha)
+ activesupport (= 5.0.0.alpha)
+ builder (~> 3.1)
+ erubis (~> 2.7.0)
+ rails-dom-testing (~> 1.0, >= 1.0.5)
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
+ activejob (5.0.0.alpha)
+ activesupport (= 5.0.0.alpha)
+ globalid (>= 0.3.0)
+ activemodel (5.0.0.alpha)
+ activesupport (= 5.0.0.alpha)
+ builder (~> 3.1)
+ activerecord (5.0.0.alpha)
+ activemodel (= 5.0.0.alpha)
+ activesupport (= 5.0.0.alpha)
+ arel (= 7.0.0.alpha)
+ activesupport (5.0.0.alpha)
+ i18n (~> 0.7)
+ json (~> 1.7, >= 1.7.7)
+ minitest (~> 5.1)
+ thread_safe (~> 0.3, >= 0.3.4)
+ tzinfo (~> 1.1)
+ rails (5.0.0.alpha)
+ actionmailer (= 5.0.0.alpha)
+ actionpack (= 5.0.0.alpha)
+ actionview (= 5.0.0.alpha)
+ activejob (= 5.0.0.alpha)
+ activemodel (= 5.0.0.alpha)
+ activerecord (= 5.0.0.alpha)
+ activesupport (= 5.0.0.alpha)
+ bundler (>= 1.3.0, < 2.0)
+ railties (= 5.0.0.alpha)
+ sprockets-rails
+ railties (5.0.0.alpha)
+ actionpack (= 5.0.0.alpha)
+ activesupport (= 5.0.0.alpha)
+ method_source
+ rake (>= 0.8.7)
+ thor (>= 0.18.1, < 2.0)
+
+GEM
+ remote: https://rubygems.org/
+ specs:
+ amq-protocol (1.9.2)
+ backburner (0.4.6)
+ beaneater (~> 0.3.1)
+ dante (~> 0.1.5)
+ bcrypt (3.1.10)
+ bcrypt (3.1.10-x64-mingw32)
+ bcrypt (3.1.10-x86-mingw32)
+ beaneater (0.3.3)
+ benchmark-ips (2.1.1)
+ builder (3.2.2)
+ bunny (1.1.9)
+ amq-protocol (>= 1.9.2)
+ celluloid (0.16.0)
+ 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-source
+ execjs
+ coffee-script-source (1.9.0)
+ connection_pool (2.1.1)
+ dalli (2.7.2)
+ dante (0.1.5)
+ 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)
+ hitimes (1.2.2)
+ hitimes (1.2.2-x86-mingw32)
+ i18n (0.7.0)
+ json (1.8.2)
+ kindlerb (0.1.1)
+ mustache
+ nokogiri
+ loofah (2.0.1)
+ nokogiri (>= 1.5.9)
+ metaclass (0.0.4)
+ method_source (0.8.2)
+ mime-types (2.4.3)
+ 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)
+ mysql (2.9.1)
+ mysql2 (0.3.18)
+ nokogiri (1.6.6.2)
+ mini_portile (~> 0.6.0)
+ nokogiri (1.6.6.2-x64-mingw32)
+ mini_portile (~> 0.6.0)
+ nokogiri (1.6.6.2-x86-mingw32)
+ mini_portile (~> 0.6.0)
+ pg (0.18.1)
+ psych (2.0.13)
+ que (0.9.2)
+ queue_classic (3.1.0)
+ pg (>= 0.17, < 0.19)
+ racc (1.4.12)
+ rack (1.6.0)
+ rack-cache (1.2)
+ rack (>= 0.4)
+ rack-protection (1.5.3)
+ rack
+ rack-test (0.6.3)
+ rack (>= 1.0)
+ rails-deprecated_sanitizer (1.0.3)
+ activesupport (>= 4.2.0.alpha)
+ rails-dom-testing (1.0.5)
+ activesupport (>= 4.2.0.beta, < 5.0)
+ nokogiri (~> 1.6.0)
+ rails-deprecated_sanitizer (>= 1.0.1)
+ rails-html-sanitizer (1.0.2)
+ loofah (~> 2.0)
+ rake (10.4.2)
+ rdoc (4.2.0)
+ redcarpet (3.2.3)
+ redis (3.2.1)
+ redis-namespace (1.5.1)
+ redis (~> 3.0, >= 3.0.4)
+ resque (1.25.2)
+ mono_logger (~> 1.0)
+ multi_json (~> 1.0)
+ redis-namespace (~> 1.3)
+ sinatra (>= 0.9.2)
+ vegas (~> 0.1.2)
+ resque-scheduler (4.0.0)
+ mono_logger (~> 1.0)
+ redis (~> 3.0)
+ resque (~> 1.25)
+ rufus-scheduler (~> 3.0)
+ rufus-scheduler (3.0.9)
+ tzinfo
+ sdoc (0.4.1)
+ json (~> 1.7, >= 1.7.7)
+ rdoc (~> 4.0)
+ sequel (4.19.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)
+ rack (~> 1.4)
+ rack-protection (~> 1.4)
+ tilt (~> 1.3, >= 1.3.4)
+ sneakers (0.1.1.pre)
+ bunny (~> 1.1.3)
+ serverengine
+ thor
+ thread
+ sprockets (3.0.0.rc.1)
+ 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)
+ celluloid (~> 0.16.0)
+ thor (0.19.1)
+ thread (0.1.5)
+ thread_safe (0.3.4)
+ tilt (1.4.1)
+ timers (4.0.1)
+ hitimes
+ turbolinks (2.5.3)
+ coffee-rails
+ tzinfo (1.2.2)
+ thread_safe (~> 0.1)
+ uglifier (2.7.0)
+ execjs (>= 0.3.0)
+ json (>= 1.8.0)
+ vegas (0.1.11)
+ rack (>= 1.0.0)
+ w3c_validators (1.2)
+ json
+ nokogiri
+
+PLATFORMS
+ ruby
+ x64-mingw32
+ x86-mingw32
+
+DEPENDENCIES
+ activerecord-jdbcmysql-adapter (>= 1.3.0)
+ activerecord-jdbcpostgresql-adapter (>= 1.3.0)
+ activerecord-jdbcsqlite3-adapter (>= 1.3.0)
+ arel!
+ backburner
+ bcrypt (~> 3.1.10)
+ benchmark-ips
+ coffee-rails (~> 4.1.0)
+ dalli (>= 2.2.1)
+ delayed_job
+ delayed_job_active_record
+ jquery-rails!
+ json
+ kindlerb (= 0.1.1)
+ mail!
+ minitest (< 5.3.4)
+ mocha (~> 0.14)
+ mysql (>= 2.9.0)
+ mysql2 (>= 0.3.18)
+ nokogiri (>= 1.4.5)
+ pg (>= 0.18.0)
+ psych (~> 2.0)
+ qu-rails!
+ qu-redis
+ que
+ queue_classic
+ racc (>= 1.4.6)
+ rack-cache (~> 1.2)
+ rails!
+ rake (>= 10.3)
+ redcarpet (~> 3.2.3)
+ resque
+ resque-scheduler
+ sdoc (~> 0.4.0)
+ sequel
+ sidekiq
+ sneakers (= 0.1.1.pre)
+ sprockets (~> 3.0.0.rc.1)
+ sqlite3 (~> 1.3.6)
+ stackprof
+ sucker_punch
+ turbolinks
+ uglifier (>= 1.3.0)
+ w3c_validators
diff --git a/RAILS_VERSION b/RAILS_VERSION
index b86dc8c0c6..2b915d7d5c 100644
--- a/RAILS_VERSION
+++ b/RAILS_VERSION
@@ -1 +1 @@
-4.2.0.beta4
+5.0.0.alpha
diff --git a/README.md b/README.md
index 7e1bcd02ea..bbe3a6731d 100644
--- a/README.md
+++ b/README.md
@@ -78,7 +78,7 @@ We encourage you to contribute to Ruby on Rails! Please check out the
## Code Status
-* [![Build Status](https://travis-ci.org/rails/rails.svg?branch=master)](https://travis-ci.org/rails/rails)
+[![Build Status](https://travis-ci.org/rails/rails.svg?branch=master)](https://travis-ci.org/rails/rails)
## License
diff --git a/RELEASING_RAILS.rdoc b/RELEASING_RAILS.rdoc
index f8c40f0b02..0e09234b82 100644
--- a/RELEASING_RAILS.rdoc
+++ b/RELEASING_RAILS.rdoc
@@ -25,18 +25,6 @@ for Rails. You can check the status of his tests here:
Do not release with Red AWDwR tests.
-=== Are the supported plugins working? If not, make it work.
-
-Some Rails plugins are important and need to be supported until Rails 5.
-As these plugins are outside the Rails repository it is easy to break then without knowing
-after some refactoring or bug fix, so it is important to check if the following plugins
-are working with the versions that will be released:
-
-* https://github.com/rails/protected_attributes
-* https://github.com/rails/activerecord-deprecated_finders
-
-Do not release red plugins tests.
-
=== Do we have any Git dependencies? If so, contact those authors.
Having Git dependencies indicates that we depend on unreleased code.
@@ -54,7 +42,7 @@ addressed, and that can impact your release date.
Ruby implementors have high stakes in making sure Rails works. Be kind and
give them a heads up that Rails will be released soonish.
-This only needs done for major and minor releases, bugfix releases aren't a
+This is only required for major and minor releases, bugfix releases aren't a
big enough deal, and are supposed to be backwards compatible.
Send an email just giving a heads up about the upcoming release to these
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index 5685871ac9..86ecb3ee88 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,50 +1,56 @@
-* Attachments can be added while rendering the mail template.
+* Add `assert_enqueued_emails` and `assert_no_enqueued_emails`.
- Fixes #16974.
+ Example:
- *Christian Felder*
+ def test_emails
+ assert_enqueued_emails 2 do
+ ContactMailer.welcome.deliver_later
+ ContactMailer.welcome.deliver_later
+ end
+ end
-* Added `#deliver_later`, `#deliver_now` and deprecate `#deliver` in favour of
- `#deliver_now`. `#deliver_later` will enqueue a job to render and deliver
- the mail instead of delivering it right at that moment. The job is enqueued
- using the new Active Job framework in Rails, and will use whatever queue is
- configured for Rails.
+ def test_no_emails
+ assert_no_enqueued_emails do
+ # No emails enqueued here
+ end
+ end
- *DHH*, *Abdelkader Boudih*, *Cristian Bica*
+ *George Claghorn*
-* Make `ActionMailer::Previews` methods class methods. Previously they were
- instance methods and `ActionMailer` tries to render a message when they
- are called.
+* Add `_mailer` suffix to mailers created via generator, following the same
+ naming convention used in controllers and jobs.
- *Cristian Bica*
+ *Carlos Souza*
-* Deprecate `*_path` helpers in email views. When used they generate
- non-working links and are not the intention of most developers. Instead
- we recommend to use `*_url` helper.
+* Remove deprecate `*_path` helpers in email views.
- *Richard Schneeman*
+ *Rafael Mendonça França*
-* Raise an exception when attachments are added after `mail` was called.
- This is a safeguard to prevent invalid emails.
+* Remove deprecated `deliver` and `deliver!` methods.
- Fixes #16163.
+ *claudiob*
- *Yves Senn*
+* Template lookup now respects default locale and I18n fallbacks.
-* Add `config.action_mailer.show_previews` configuration option.
+ Given the following templates:
- This config option can be used to enable the mail preview in environments
- other than development (such as staging).
+ mailer/demo.html.erb
+ mailer/demo.en.html.erb
+ mailer/demo.pt.html.erb
- Defaults to `true` in development and false elsewhere.
+ Before this change, for a locale that doesn't have its associated file, the
+ `mailer/demo.html.erb` would be rendered even if `en` was the default locale.
- *Leonard Garvey*
+ Now `mailer/demo.en.html.erb` has precedence over the file without locale.
-* Allow preview interceptors to be registered through
- `config.action_mailer.preview_interceptors`.
+ Also, it is possible to give a fallback.
- See #15739.
+ mailer/demo.pt.html.erb
+ mailer/demo.pt-BR.html.erb
- *Yves Senn*
+ So if the locale is `pt-PT`, `mailer/demo.pt.html.erb` will be rendered given
+ the right I18n fallback configuration.
-Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/actionmailer/CHANGELOG.md) for previous changes.
+ *Rafael Mendonça França*
+
+Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/actionmailer/CHANGELOG.md) for previous changes.
diff --git a/actionmailer/MIT-LICENSE b/actionmailer/MIT-LICENSE
index d58dd9ed9b..3ec7a617cf 100644
--- a/actionmailer/MIT-LICENSE
+++ b/actionmailer/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2014 David Heinemeier Hansson
+Copyright (c) 2004-2015 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec
index abc047d339..41913899ff 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 = '>= 1.9.3'
+ s.required_ruby_version = '>= 2.2.1'
s.license = 'MIT'
@@ -24,5 +24,5 @@ Gem::Specification.new do |s|
s.add_dependency 'activejob', version
s.add_dependency 'mail', ['~> 2.5', '>= 2.5.4']
- s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.4'
+ s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.5'
end
diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb
index b994ef3182..17d8dcc208 100644
--- a/actionmailer/lib/action_mailer.rb
+++ b/actionmailer/lib/action_mailer.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2014 David Heinemeier Hansson
+# Copyright (c) 2004-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index a70bf1544a..c7f09ed192 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -15,11 +15,17 @@ module ActionMailer
#
# $ rails generate mailer Notifier
#
- # The generated model inherits from <tt>ActionMailer::Base</tt>. A mailer model defines methods
+ # The generated model inherits from <tt>ApplicationMailer</tt> which in turn
+ # inherits from <tt>ActionMailer::Base</tt>. A mailer model defines methods
# used to generate an email message. In these methods, you can setup variables to be used in
# the mailer views, options on the mail itself such as the <tt>:from</tt> address, and attachments.
#
- # class Notifier < ActionMailer::Base
+ # class ApplicationMailer < ActionMailer::Base
+ # default from: 'from@example.com'
+ # layout 'mailer'
+ # end
+ #
+ # class NotifierMailer < ApplicationMailer
# default from: 'no-reply@example.com',
# return_path: 'system@example.com'
#
@@ -52,7 +58,7 @@ module ActionMailer
#
# The mail method, if not passed a block, will inspect your views and send all the views with
# the same name as the method, so the above action would send the +welcome.text.erb+ view
- # file as well as the +welcome.text.html.erb+ view file in a +multipart/alternative+ email.
+ # file as well as the +welcome.html.erb+ view file in a +multipart/alternative+ email.
#
# If you want to explicitly render only certain templates, pass a block:
#
@@ -82,9 +88,9 @@ module ActionMailer
#
# To define a template to be used with a mailing, create an <tt>.erb</tt> file with the same
# name as the method in your mailer model. For example, in the mailer defined above, the template at
- # <tt>app/views/notifier/welcome.text.erb</tt> would be used to generate the email.
+ # <tt>app/views/notifier_mailer/welcome.text.erb</tt> would be used to generate the email.
#
- # Variables defined in the methods of your mailer model are accessible as instance variables in their
+ # Variables defined in the methods of your mailer model are accessible as instance variables in their
# corresponding view.
#
# Emails by default are sent in plain text, so a sample view for our model example might look like this:
@@ -126,30 +132,25 @@ module ActionMailer
#
# config.action_mailer.default_url_options = { host: "example.com" }
#
- # When you decide to set a default <tt>:host</tt> for your mailers, then you need to make sure to use the
- # <tt>only_path: false</tt> option when using <tt>url_for</tt>. Since the <tt>url_for</tt> view helper
- # will generate relative URLs by default when a <tt>:host</tt> option isn't explicitly provided, passing
- # <tt>only_path: false</tt> will ensure that absolute URLs are generated.
- #
# = Sending mail
#
# Once a mailer action and template are defined, you can deliver your message or create it and save it
# for delivery later:
#
- # Notifier.welcome(User.first).deliver_now # sends the email
- # mail = Notifier.welcome(User.first) # => an ActionMailer::MessageDelivery object
+ # NotifierMailer.welcome(User.first).deliver_now # sends the email
+ # mail = NotifierMailer.welcome(User.first) # => an ActionMailer::MessageDelivery object
# mail.deliver_now # sends the email
#
# 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.
#
- # Notifier.welcome(User.first).message # => a Mail::Message 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):
#
- # Notifier.welcome(User.first).deliver_later # enqueue the email sending to Active Job
+ # NotifierMailer.welcome(User.first).deliver_later # enqueue the email sending to Active Job
#
# You never instantiate your mailer class. Rather, you just call the method you defined on the class itself.
#
@@ -178,7 +179,7 @@ module ActionMailer
#
# Sending attachment in emails is easy:
#
- # class ApplicationMailer < ActionMailer::Base
+ # class NotifierMailer < ApplicationMailer
# def welcome(recipient)
# attachments['free_book.pdf'] = File.read('path/to/file.pdf')
# mail(to: recipient, subject: "New account information")
@@ -194,7 +195,7 @@ module ActionMailer
# If you need to send attachments with no content, you need to create an empty view for it,
# or add an empty body parameter like this:
#
- # class ApplicationMailer < ActionMailer::Base
+ # class NotifierMailer < ApplicationMailer
# def welcome(recipient)
# attachments['free_book.pdf'] = File.read('path/to/file.pdf')
# mail(to: recipient, subject: "New account information", body: "")
@@ -206,7 +207,7 @@ module ActionMailer
# You can also specify that a file should be displayed inline with other HTML. This is useful
# if you want to display a corporate logo or a photo.
#
- # class ApplicationMailer < ActionMailer::Base
+ # class NotifierMailer < ApplicationMailer
# def welcome(recipient)
# attachments.inline['photo.png'] = File.read('path/to/photo.png')
# mail(to: recipient, subject: "Here is what we look like")
@@ -245,7 +246,7 @@ module ActionMailer
# Action Mailer provides some intelligent defaults for your emails, these are usually specified in a
# default method inside the class definition:
#
- # class Notifier < ActionMailer::Base
+ # class NotifierMailer < ApplicationMailer
# default sender: 'system@example.com'
# end
#
@@ -263,7 +264,7 @@ module ActionMailer
# As you can pass in any header, you need to either quote the header as a string, or pass it in as
# an underscored symbol, so the following will work:
#
- # class Notifier < ActionMailer::Base
+ # class NotifierMailer < ApplicationMailer
# default 'Content-Transfer-Encoding' => '7bit',
# content_description: 'This is a description'
# end
@@ -271,7 +272,7 @@ module ActionMailer
# Finally, Action Mailer also supports passing <tt>Proc</tt> objects into the default hash, so you
# can define methods that evaluate as the message is being generated:
#
- # class Notifier < ActionMailer::Base
+ # class NotifierMailer < ApplicationMailer
# default 'X-Special-Header' => Proc.new { my_method }
#
# private
@@ -296,7 +297,7 @@ module ActionMailer
# This may be useful, for example, when you want to add default inline attachments for all
# messages sent out by a certain mailer class:
#
- # class Notifier < ActionMailer::Base
+ # class NotifierMailer < ApplicationMailer
# before_action :add_inline_attachment!
#
# def welcome
@@ -324,9 +325,9 @@ module ActionMailer
# <tt>ActionMailer::Base.preview_path</tt>. Since most emails do something interesting
# with database data, you'll need to write some scenarios to load messages with fake data:
#
- # class NotifierPreview < ActionMailer::Preview
+ # class NotifierMailerPreview < ActionMailer::Preview
# def welcome
- # Notifier.welcome(User.first)
+ # NotifierMailer.welcome(User.first)
# end
# end
#
@@ -492,7 +493,7 @@ module ActionMailer
# Sets the defaults through app configuration:
#
- # config.action_mailer.default { from: "no-reply@example.org" }
+ # config.action_mailer.default(from: "no-reply@example.org")
#
# Aliased by ::default_options=
def default(value = nil)
@@ -585,8 +586,6 @@ module ActionMailer
}
ActiveSupport::Notifications.instrument("process.action_mailer", payload) do
- lookup_context.skip_default_locale!
-
super
@_message = NullMail.new unless @_mail_was_called
end
@@ -703,7 +702,7 @@ module ActionMailer
# The main method that creates the message and renders the email templates. There are
# two ways to call this method, with a block, or without a block.
#
- # It accepts a headers hash. This hash allows you to specify
+ # It accepts a headers hash. This hash allows you to specify
# the most used headers in an email message, these are:
#
# * +:subject+ - The subject of the message, if this is omitted, Action Mailer will
@@ -853,7 +852,7 @@ module ActionMailer
when user_content_type.present?
user_content_type
when m.has_attachments?
- if m.attachments.detect { |a| a.inline? }
+ if m.attachments.detect(&:inline?)
["multipart", "related", params]
else
["multipart", "mixed", params]
@@ -908,7 +907,7 @@ module ActionMailer
if templates.empty?
raise ActionView::MissingTemplate.new(paths, name, paths, false, 'mailer')
else
- templates.uniq { |t| t.formats }.each(&block)
+ templates.uniq(&:formats).each(&block)
end
end
diff --git a/actionmailer/lib/action_mailer/delivery_job.rb b/actionmailer/lib/action_mailer/delivery_job.rb
index 95231411fb..e864ab7a4d 100644
--- a/actionmailer/lib/action_mailer/delivery_job.rb
+++ b/actionmailer/lib/action_mailer/delivery_job.rb
@@ -3,10 +3,10 @@ require 'active_job'
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:
+ class DeliveryJob < ActiveJob::Base # :nodoc:
queue_as :mailers
- def perform(mailer, mail_method, delivery_method, *args) #:nodoc#
+ def perform(mailer, mail_method, delivery_method, *args) #:nodoc:
mailer.constantize.public_send(mail_method, *args).send(delivery_method)
end
end
diff --git a/actionmailer/lib/action_mailer/gem_version.rb b/actionmailer/lib/action_mailer/gem_version.rb
index e568b8f6f2..ac79788cf0 100644
--- a/actionmailer/lib/action_mailer/gem_version.rb
+++ b/actionmailer/lib/action_mailer/gem_version.rb
@@ -5,10 +5,10 @@ module ActionMailer
end
module VERSION
- MAJOR = 4
- MINOR = 2
+ MAJOR = 5
+ MINOR = 0
TINY = 0
- PRE = "beta4"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionmailer/lib/action_mailer/mail_helper.rb b/actionmailer/lib/action_mailer/mail_helper.rb
index cc7935a7e0..239974e7b1 100644
--- a/actionmailer/lib/action_mailer/mail_helper.rb
+++ b/actionmailer/lib/action_mailer/mail_helper.rb
@@ -4,7 +4,17 @@ module ActionMailer
# attachments list.
module MailHelper
# Take the text and format it, indented two spaces for each line, and
- # wrapped at 72 columns.
+ # wrapped at 72 columns:
+ #
+ # text = <<-TEXT
+ # This is
+ # the paragraph.
+ #
+ # * item1 * item2
+ # TEXT
+ #
+ # block_format text
+ # # => " This is the paragraph.\n\n * item1\n * item2\n"
def block_format(text)
formatted = text.split(/\n\r?\n/).collect { |paragraph|
format_paragraph(paragraph)
@@ -33,6 +43,8 @@ module ActionMailer
end
# Returns +text+ wrapped at +len+ columns and indented +indent+ spaces.
+ # By default column length +len+ equals 72 characters and indent
+ # +indent+ equal two spaces.
#
# my_text = 'Here is a sample text with more than 40 characters'
#
diff --git a/actionmailer/lib/action_mailer/message_delivery.rb b/actionmailer/lib/action_mailer/message_delivery.rb
index b5dc2d7497..ff2cb0fd01 100644
--- a/actionmailer/lib/action_mailer/message_delivery.rb
+++ b/actionmailer/lib/action_mailer/message_delivery.rb
@@ -1,5 +1,4 @@
require 'delegate'
-require 'active_support/core_ext/string/filters'
module ActionMailer
@@ -85,26 +84,6 @@ module ActionMailer
message.deliver
end
- def deliver! #:nodoc:
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- `#deliver!` is deprecated and will be removed in Rails 5. Use
- `#deliver_now!` to deliver immediately or `#deliver_later!` to
- deliver through Active Job.
- MSG
-
- deliver_now!
- end
-
- def deliver #:nodoc:
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- `#deliver` is deprecated and will be removed in Rails 5. Use
- `#deliver_now` to deliver immediately or `#deliver_later` to
- deliver through Active Job.
- MSG
-
- deliver_now
- end
-
private
def enqueue_delivery(delivery_method, options={})
diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb
index 05a3ac6a21..bebcf4de01 100644
--- a/actionmailer/lib/action_mailer/railtie.rb
+++ b/actionmailer/lib/action_mailer/railtie.rb
@@ -41,7 +41,7 @@ module ActionMailer
options.each { |k,v| send("#{k}=", v) }
if options.show_previews
- app.routes.append do
+ app.routes.prepend do
get '/rails/mailers' => "rails/mailers#index"
get '/rails/mailers/*path' => "rails/mailers#preview"
end
diff --git a/actionmailer/lib/action_mailer/test_helper.rb b/actionmailer/lib/action_mailer/test_helper.rb
index 6ddacf7b79..524e6e3af1 100644
--- a/actionmailer/lib/action_mailer/test_helper.rb
+++ b/actionmailer/lib/action_mailer/test_helper.rb
@@ -1,7 +1,11 @@
+require 'active_job'
+
module ActionMailer
# Provides helper methods for testing Action Mailer, including #assert_emails
# and #assert_no_emails
module TestHelper
+ include ActiveJob::TestHelper
+
# Asserts that the number of emails sent matches the given number.
#
# def test_emails
@@ -58,5 +62,52 @@ module ActionMailer
def assert_no_emails(&block)
assert_emails 0, &block
end
+
+ # Asserts that the number of emails enqueued for later delivery matches
+ # the given number.
+ #
+ # def test_emails
+ # assert_enqueued_emails 0
+ # ContactMailer.welcome.deliver_later
+ # assert_enqueued_emails 1
+ # ContactMailer.welcome.deliver_later
+ # assert_enqueued_emails 2
+ # end
+ #
+ # If a block is passed, that block should cause the specified number of
+ # emails to be enqueued.
+ #
+ # def test_emails_again
+ # assert_enqueued_emails 1 do
+ # ContactMailer.welcome.deliver_later
+ # end
+ #
+ # assert_enqueued_emails 2 do
+ # ContactMailer.welcome.deliver_later
+ # ContactMailer.welcome.deliver_later
+ # end
+ # end
+ def assert_enqueued_emails(number, &block)
+ assert_enqueued_jobs number, only: ActionMailer::DeliveryJob, &block
+ end
+
+ # Asserts that no emails are enqueued for later delivery.
+ #
+ # def test_no_emails
+ # assert_no_enqueued_emails
+ # ContactMailer.welcome.deliver_later
+ # assert_enqueued_emails 1
+ # end
+ #
+ # If a block is provided, it should not cause any emails to be enqueued.
+ #
+ # def test_no_emails
+ # assert_no_enqueued_emails do
+ # # No emails should be enqueued from this block
+ # end
+ # end
+ def assert_no_enqueued_emails(&block)
+ assert_no_enqueued_jobs only: ActionMailer::DeliveryJob, &block
+ end
end
end
diff --git a/actionmailer/lib/rails/generators/mailer/USAGE b/actionmailer/lib/rails/generators/mailer/USAGE
index 323bb8a87f..2b0a078109 100644
--- a/actionmailer/lib/rails/generators/mailer/USAGE
+++ b/actionmailer/lib/rails/generators/mailer/USAGE
@@ -11,7 +11,7 @@ Example:
rails generate mailer Notifications signup forgot_password invoice
creates a Notifications mailer class, views, and test:
- Mailer: app/mailers/notifications.rb
- Views: app/views/notifications/signup.text.erb [...]
- Test: test/mailers/notifications_test.rb
+ Mailer: app/mailers/notifications_mailer.rb
+ Views: app/views/notifications_mailer/signup.text.erb [...]
+ Test: test/mailers/notifications_mailer_test.rb
diff --git a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb
index d5bf864595..3ec7d3d896 100644
--- a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb
+++ b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb
@@ -4,13 +4,22 @@ module Rails
source_root File.expand_path("../templates", __FILE__)
argument :actions, type: :array, default: [], banner: "method method"
- check_class_collision
+
+ check_class_collision suffix: "Mailer"
def create_mailer_file
- template "mailer.rb", File.join('app/mailers', class_path, "#{file_name}.rb")
+ template "mailer.rb", File.join('app/mailers', class_path, "#{file_name}_mailer.rb")
+ if self.behavior == :invoke
+ template "application_mailer.rb", 'app/mailers/application_mailer.rb'
+ end
end
hook_for :template_engine, :test_framework
+
+ protected
+ def file_name
+ @_file_name ||= super.gsub(/\_mailer/i, '')
+ end
end
end
end
diff --git a/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb b/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb
new file mode 100644
index 0000000000..d25d8892dd
--- /dev/null
+++ b/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb
@@ -0,0 +1,4 @@
+class ApplicationMailer < ActionMailer::Base
+ default from: "from@example.com"
+ layout 'mailer'
+end
diff --git a/actionmailer/lib/rails/generators/mailer/templates/mailer.rb b/actionmailer/lib/rails/generators/mailer/templates/mailer.rb
index edcfb4233d..348d314758 100644
--- a/actionmailer/lib/rails/generators/mailer/templates/mailer.rb
+++ b/actionmailer/lib/rails/generators/mailer/templates/mailer.rb
@@ -1,12 +1,11 @@
<% module_namespacing do -%>
-class <%= class_name %> < ActionMailer::Base
- default from: "from@example.com"
+class <%= class_name %>Mailer < ApplicationMailer
<% actions.each do |action| -%>
# Subject can be set in your I18n file at config/locales/en.yml
# with the following lookup:
#
- # en.<%= file_path.tr("/",".") %>.<%= action %>.subject
+ # en.<%= file_path.tr("/",".") %>_mailer.<%= action %>.subject
#
def <%= action %>
@greeting = "Hi"
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index 7681679dc7..ae91903c95 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -15,7 +15,7 @@ require 'mail'
# Emulate AV railtie
require 'action_view'
-ActionMailer::Base.send(:include, ActionView::Layouts)
+ActionMailer::Base.include(ActionView::Layouts)
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
@@ -42,8 +42,3 @@ def jruby_skip(message = '')
end
require 'mocha/setup' # FIXME: stop using mocha
-
-# FIXME: we have tests that depend on run order, we should fix that and
-# remove this method call.
-require 'active_support/test_case'
-ActiveSupport::TestCase.test_order = :sorted
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index 396d0a95b5..59c5638f96 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'set'
@@ -287,7 +286,7 @@ class BaseTest < ActiveSupport::TestCase
end
end
- assert_nothing_raised { LateAttachmentAccessorMailer.welcome }
+ assert_nothing_raised { LateAttachmentAccessorMailer.welcome.message }
end
# Implicit multipart
@@ -353,10 +352,35 @@ class BaseTest < ActiveSupport::TestCase
assert_equal("text/plain", email.parts[0].mime_type)
assert_equal("Implicit with locale PL TEXT", email.parts[0].body.encoded)
assert_equal("text/html", email.parts[1].mime_type)
- assert_equal("Implicit with locale HTML", email.parts[1].body.encoded)
+ assert_equal("Implicit with locale EN HTML", email.parts[1].body.encoded)
end
end
+ test "implicit multipart with fallback locale" do
+ fallback_backend = Class.new(I18n::Backend::Simple) do
+ include I18n::Backend::Fallbacks
+ end
+
+ begin
+ backend = I18n.backend
+ I18n.backend = fallback_backend.new
+ I18n.fallbacks[:"de-AT"] = [:de]
+
+ swap I18n, locale: 'de-AT' do
+ email = BaseMailer.implicit_with_locale
+ assert_equal(2, email.parts.size)
+ assert_equal("multipart/alternative", email.mime_type)
+ assert_equal("text/plain", email.parts[0].mime_type)
+ assert_equal("Implicit with locale DE-AT TEXT", email.parts[0].body.encoded)
+ assert_equal("text/html", email.parts[1].mime_type)
+ assert_equal("Implicit with locale DE HTML", email.parts[1].body.encoded)
+ end
+ ensure
+ I18n.backend = backend
+ end
+ end
+
+
test "implicit multipart with several view paths uses the first one with template" do
old = BaseMailer.view_paths
begin
diff --git a/actionmailer/test/fixtures/base_mailer/email_custom_layout.text.html.erb b/actionmailer/test/fixtures/base_mailer/email_custom_layout.text.html.erb
deleted file mode 100644
index a2187308b6..0000000000
--- a/actionmailer/test/fixtures/base_mailer/email_custom_layout.text.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-body_text \ No newline at end of file
diff --git a/actionmailer/test/fixtures/base_mailer/implicit_with_locale.de-AT.text.erb b/actionmailer/test/fixtures/base_mailer/implicit_with_locale.de-AT.text.erb
new file mode 100644
index 0000000000..e97505fad9
--- /dev/null
+++ b/actionmailer/test/fixtures/base_mailer/implicit_with_locale.de-AT.text.erb
@@ -0,0 +1 @@
+Implicit with locale DE-AT TEXT \ No newline at end of file
diff --git a/actionmailer/test/fixtures/base_mailer/implicit_with_locale.de.html.erb b/actionmailer/test/fixtures/base_mailer/implicit_with_locale.de.html.erb
new file mode 100644
index 0000000000..0536b5d3e2
--- /dev/null
+++ b/actionmailer/test/fixtures/base_mailer/implicit_with_locale.de.html.erb
@@ -0,0 +1 @@
+Implicit with locale DE HTML \ No newline at end of file
diff --git a/actionmailer/test/fixtures/first_mailer/share.erb b/actionmailer/test/fixtures/first_mailer/share.erb
deleted file mode 100644
index da43638ceb..0000000000
--- a/actionmailer/test/fixtures/first_mailer/share.erb
+++ /dev/null
@@ -1 +0,0 @@
-first mail
diff --git a/actionmailer/test/fixtures/path.with.dots/funky_path_mailer/multipart_with_template_path_with_dots.erb b/actionmailer/test/fixtures/path.with.dots/funky_path_mailer/multipart_with_template_path_with_dots.erb
deleted file mode 100644
index 2d0cd5c124..0000000000
--- a/actionmailer/test/fixtures/path.with.dots/funky_path_mailer/multipart_with_template_path_with_dots.erb
+++ /dev/null
@@ -1 +0,0 @@
-Have some dots. Enjoy! \ No newline at end of file
diff --git a/actionmailer/test/fixtures/second_mailer/share.erb b/actionmailer/test/fixtures/second_mailer/share.erb
deleted file mode 100644
index 9a54010672..0000000000
--- a/actionmailer/test/fixtures/second_mailer/share.erb
+++ /dev/null
@@ -1 +0,0 @@
-second mail
diff --git a/actionmailer/test/fixtures/test_mailer/_subtemplate.text.erb b/actionmailer/test/fixtures/test_mailer/_subtemplate.text.erb
deleted file mode 100644
index 3b4ba35f20..0000000000
--- a/actionmailer/test/fixtures/test_mailer/_subtemplate.text.erb
+++ /dev/null
@@ -1 +0,0 @@
-let's go! \ No newline at end of file
diff --git a/actionmailer/test/fixtures/test_mailer/custom_templating_extension.html.haml b/actionmailer/test/fixtures/test_mailer/custom_templating_extension.html.haml
deleted file mode 100644
index 8dcf9746cc..0000000000
--- a/actionmailer/test/fixtures/test_mailer/custom_templating_extension.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-%p Hello there,
-
-%p
- Mr.
- = @recipient
- from haml \ No newline at end of file
diff --git a/actionmailer/test/fixtures/test_mailer/custom_templating_extension.text.haml b/actionmailer/test/fixtures/test_mailer/custom_templating_extension.text.haml
deleted file mode 100644
index 8dcf9746cc..0000000000
--- a/actionmailer/test/fixtures/test_mailer/custom_templating_extension.text.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-%p Hello there,
-
-%p
- Mr.
- = @recipient
- from haml \ No newline at end of file
diff --git a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.html.erb b/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.html.erb
deleted file mode 100644
index 946d99ede5..0000000000
--- a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.html.erb
+++ /dev/null
@@ -1,10 +0,0 @@
-<html>
- <body>
- HTML formatted message to <strong><%= @recipient %></strong>.
- </body>
-</html>
-<html>
- <body>
- HTML formatted message to <strong><%= @recipient %></strong>.
- </body>
-</html>
diff --git a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.html.erb~ b/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.html.erb~
deleted file mode 100644
index 946d99ede5..0000000000
--- a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.html.erb~
+++ /dev/null
@@ -1,10 +0,0 @@
-<html>
- <body>
- HTML formatted message to <strong><%= @recipient %></strong>.
- </body>
-</html>
-<html>
- <body>
- HTML formatted message to <strong><%= @recipient %></strong>.
- </body>
-</html>
diff --git a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.ignored.erb b/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.ignored.erb
deleted file mode 100644
index 6940419d47..0000000000
--- a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.ignored.erb
+++ /dev/null
@@ -1 +0,0 @@
-Ignored when searching for implicitly multipart parts.
diff --git a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.rhtml.bak b/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.rhtml.bak
deleted file mode 100644
index 6940419d47..0000000000
--- a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.rhtml.bak
+++ /dev/null
@@ -1 +0,0 @@
-Ignored when searching for implicitly multipart parts.
diff --git a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.erb b/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.erb
deleted file mode 100644
index a6c8d54cf9..0000000000
--- a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.erb
+++ /dev/null
@@ -1,2 +0,0 @@
-Plain text to <%= @recipient %>.
-Plain text to <%= @recipient %>.
diff --git a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.yaml.erb b/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.yaml.erb
deleted file mode 100644
index c14348c770..0000000000
--- a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.yaml.erb
+++ /dev/null
@@ -1 +0,0 @@
-yaml to: <%= @recipient %> \ No newline at end of file
diff --git a/actionmailer/test/fixtures/test_mailer/included_subtemplate.text.erb b/actionmailer/test/fixtures/test_mailer/included_subtemplate.text.erb
deleted file mode 100644
index ae3cfa77e7..0000000000
--- a/actionmailer/test/fixtures/test_mailer/included_subtemplate.text.erb
+++ /dev/null
@@ -1 +0,0 @@
-Hey Ho, <%= render partial: "subtemplate" %> \ No newline at end of file
diff --git a/actionmailer/test/fixtures/test_mailer/multipart_alternative.html.erb b/actionmailer/test/fixtures/test_mailer/multipart_alternative.html.erb
deleted file mode 100644
index 73ea14f82f..0000000000
--- a/actionmailer/test/fixtures/test_mailer/multipart_alternative.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<strong>foo</strong> <%= @foo %> \ No newline at end of file
diff --git a/actionmailer/test/fixtures/test_mailer/multipart_alternative.plain.erb b/actionmailer/test/fixtures/test_mailer/multipart_alternative.plain.erb
deleted file mode 100644
index 779fe4c1ea..0000000000
--- a/actionmailer/test/fixtures/test_mailer/multipart_alternative.plain.erb
+++ /dev/null
@@ -1 +0,0 @@
-foo: <%= @foo %> \ No newline at end of file
diff --git a/actionmailer/test/fixtures/test_mailer/rxml_template.rxml b/actionmailer/test/fixtures/test_mailer/rxml_template.rxml
deleted file mode 100644
index d566bd8d7c..0000000000
--- a/actionmailer/test/fixtures/test_mailer/rxml_template.rxml
+++ /dev/null
@@ -1,2 +0,0 @@
-xml.instruct!
-xml.test \ No newline at end of file
diff --git a/actionmailer/test/fixtures/test_mailer/signed_up.html.erb b/actionmailer/test/fixtures/test_mailer/signed_up.html.erb
deleted file mode 100644
index 7afe1f651c..0000000000
--- a/actionmailer/test/fixtures/test_mailer/signed_up.html.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-Hello there,
-
-Mr. <%= @recipient %> \ No newline at end of file
diff --git a/actionmailer/test/fixtures/url_test_mailer/exercise_url_for.erb b/actionmailer/test/fixtures/url_test_mailer/exercise_url_for.erb
new file mode 100644
index 0000000000..0322c1191e
--- /dev/null
+++ b/actionmailer/test/fixtures/url_test_mailer/exercise_url_for.erb
@@ -0,0 +1 @@
+<%= url_for(@options) %> <%= @url %>
diff --git a/actionmailer/test/message_delivery_test.rb b/actionmailer/test/message_delivery_test.rb
index 9abf8b225c..e4dd269494 100644
--- a/actionmailer/test/message_delivery_test.rb
+++ b/actionmailer/test/message_delivery_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'active_job'
require 'minitest/mock'
@@ -32,25 +31,6 @@ class MessageDeliveryTest < ActiveSupport::TestCase
assert_equal Mail::Message , @mail.message.class
end
- test 'should respond to .deliver' do
- assert_respond_to @mail, :deliver
- end
-
- test 'should respond to .deliver!' do
- assert_respond_to @mail, :deliver!
- end
-
- test '.deliver is deprecated' do
- assert_deprecated do
- @mail.deliver
- end
- end
- test '.deliver! is deprecated' do
- assert_deprecated do
- @mail.deliver!
- end
- end
-
test 'should respond to .deliver_later' do
assert_respond_to @mail, :deliver_later
end
diff --git a/actionmailer/test/test_helper_test.rb b/actionmailer/test/test_helper_test.rb
index 96b75ff2e0..089933e245 100644
--- a/actionmailer/test/test_helper_test.rb
+++ b/actionmailer/test/test_helper_test.rb
@@ -1,5 +1,5 @@
-# encoding: utf-8
require 'abstract_unit'
+require 'active_support/testing/stream'
class TestHelperMailer < ActionMailer::Base
def test
@@ -11,6 +11,8 @@ class TestHelperMailer < ActionMailer::Base
end
class TestHelperMailerTest < ActionMailer::TestCase
+ include ActiveSupport::Testing::Stream
+
def test_setup_sets_right_action_mailer_options
assert_equal :test, ActionMailer::Base.delivery_method
assert ActionMailer::Base.perform_deliveries
@@ -119,6 +121,61 @@ class TestHelperMailerTest < ActionMailer::TestCase
assert_match(/0 .* but 1/, error.message)
end
+
+ def test_assert_enqueued_emails
+ assert_nothing_raised do
+ assert_enqueued_emails 1 do
+ silence_stream($stdout) do
+ TestHelperMailer.test.deliver_later
+ end
+ end
+ end
+ end
+
+ def test_assert_enqueued_emails_too_few_sent
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_enqueued_emails 2 do
+ silence_stream($stdout) do
+ TestHelperMailer.test.deliver_later
+ end
+ end
+ end
+
+ assert_match(/2 .* but 1/, error.message)
+ end
+
+ def test_assert_enqueued_emails_too_many_sent
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_enqueued_emails 1 do
+ silence_stream($stdout) do
+ TestHelperMailer.test.deliver_later
+ TestHelperMailer.test.deliver_later
+ end
+ end
+ end
+
+ assert_match(/1 .* but 2/, error.message)
+ end
+
+ def test_assert_no_enqueued_emails
+ assert_nothing_raised do
+ assert_no_enqueued_emails do
+ TestHelperMailer.test.deliver_now
+ end
+ end
+ end
+
+ def test_assert_no_enqueued_emails_failure
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_no_enqueued_emails do
+ silence_stream($stdout) do
+ TestHelperMailer.test.deliver_later
+ end
+ end
+ end
+
+ assert_match(/0 .* but 1/, error.message)
+ end
end
class AnotherTestHelperMailerTest < ActionMailer::TestCase
diff --git a/actionmailer/test/url_test.rb b/actionmailer/test/url_test.rb
index be7532d42f..7928fe9542 100644
--- a/actionmailer/test/url_test.rb
+++ b/actionmailer/test/url_test.rb
@@ -23,9 +23,32 @@ class UrlTestMailer < ActionMailer::Base
mail(to: recipient, subject: "[Signed up] Welcome #{recipient}",
from: "system@loudthinking.com", date: Time.local(2004, 12, 12))
end
+
+ def exercise_url_for(options)
+ @options = options
+ @url = url_for(@options)
+ mail(from: 'from@example.com', to: 'to@example.com', subject: 'subject')
+ end
end
class ActionMailerUrlTest < ActionMailer::TestCase
+ class DummyModel
+ def self.model_name
+ OpenStruct.new(route_key: 'dummy_model')
+ end
+
+ def persisted?
+ false
+ end
+
+ def model_name
+ self.class.model_name
+ end
+
+ def to_model
+ self
+ end
+ end
def encode( text, charset="UTF-8" )
quoted_printable( text, charset )
@@ -40,10 +63,47 @@ class ActionMailerUrlTest < ActionMailer::TestCase
mail
end
+ def assert_url_for(expected, options, relative = false)
+ expected = "http://www.basecamphq.com#{expected}" if expected.start_with?('/') && !relative
+ urls = UrlTestMailer.exercise_url_for(options).body.to_s.chomp.split
+
+ assert_equal expected, urls.first
+ assert_equal expected, urls.second
+ end
+
def setup
@recipient = 'test@localhost'
end
+ def test_url_for
+ UrlTestMailer.delivery_method = :test
+
+ AppRoutes.draw do
+ get ':controller(/:action(/:id))'
+ get '/welcome' => 'foo#bar', as: 'welcome'
+ get '/dummy_model' => 'foo#baz', as: 'dummy_model'
+ end
+
+ # string
+ assert_url_for 'http://foo/', 'http://foo/'
+
+ # symbol
+ assert_url_for '/welcome', :welcome
+
+ # hash
+ assert_url_for '/a/b/c', controller: 'a', action: 'b', id: 'c'
+ assert_url_for '/a/b/c', {controller: 'a', action: 'b', id: 'c', only_path: true}, true
+
+ # model
+ assert_url_for '/dummy_model', DummyModel.new
+
+ # class
+ assert_url_for '/dummy_model', DummyModel
+
+ # array
+ assert_url_for '/dummy_model' , [DummyModel]
+ end
+
def test_signed_up_with_url
UrlTestMailer.delivery_method = :test
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 158b22c0cc..4ab0857a66 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,396 +1,319 @@
-* Deprecate the `only_path` option on `*_path` helpers.
+* 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.
- In cases where this option is set to `true`, the option is redundant and can
- be safely removed; otherwise, the corresponding `*_url` helper should be
- used instead.
+ See #19036.
- Fixes #17294.
+ *Stephen Bussey*
- *Dan Olson*, *Godfrey Chan*
+* Provide friendlier access to request variants.
-* Improve Journey compliance to RFC 3986.
+ request.variant = :phone
+ request.variant.phone? # true
+ request.variant.tablet? # false
- The scanner in Journey failed to recognize routes that use literals
- from the sub-delims section of RFC 3986. It's now able to parse those
- authorized delimiters and route as expected.
+ request.variant = [:phone, :tablet]
+ request.variant.phone? # true
+ request.variant.desktop? # false
+ request.variant.any?(:phone, :desktop) # true
+ request.variant.any?(:desktop, :watch) # false
- Fixes #17212.
+ *George Claghorn*
- *Nicolas Cavigneaux*
+* Fix regression where a gzip file response would have a Content-type,
+ even when it was a 304 status code.
-* Deprecate implicit Array conversion for Response objects. It was added
- (using `#to_ary`) so we could conveniently use implicit splatting:
+ See #19271.
- status, headers, body = response
+ *Kohei Suzuki*
- But it also means `response + response` works and `[response].flatten`
- cascades down to the Rack body. Nonsense behavior. Instead, rely on
- explicit conversion and splatting with `#to_a`:
+* Fix handling of empty X_FORWARDED_HOST header in raw_host_with_port
- status, header, body = *response
+ 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.
- *Jeremy Kemper*
+ *Adam Forsyth*
-* Don't rescue `IPAddr::InvalidAddressError`.
+* Drop request class from RouteSet constructor.
- `IPAddr::InvalidAddressError` does not exist in Ruby 1.9.3
- and fails for JRuby in 1.9 mode.
+ If you would like to use a custom request class, please subclass and implement
+ the `request_class` method.
- *Peter Suschlik*
+ *tenderlove@ruby-lang.org*
-* Fix bug where the router would ignore any constraints added to redirect
- routes.
+* Fallback to `ENV['RAILS_RELATIVE_URL_ROOT']` in `url_for`.
- Fixes #16605.
+ Fixed an issue where the `RAILS_RELATIVE_URL_ROOT` environment variable is not
+ prepended to the path when `url_for` is called. If `SCRIPT_NAME` (used by Rack)
+ is set, it takes precedence.
- *Agis Anastasopoulos*
+ Fixes #5122.
-* Allow `config.action_dispatch.trusted_proxies` to accept an IPAddr object.
+ *Yasyf Mohamedali*
- Example:
-
- # config/environments/production.rb
- config.action_dispatch.trusted_proxies = IPAddr.new('4.8.15.0/16')
-
- *Sam Aarons*
-
-* Avoid duplicating routes for HEAD requests.
-
- Instead of duplicating the routes, we will first match the HEAD request to
- HEAD routes. If no match is found, we will then map the HEAD request to
- GET routes.
-
- *Guo Xiang Tan*, *Andrew White*
-
-* Requests that hit `ActionDispatch::Static` can now take advantage
- of gzipped assets on disk. By default a gzip asset will be served if
- the client supports gzip and a compressed file is on disk.
-
- *Richard Schneeman*
-
-* `ActionController::Parameters` will stop inheriting from `Hash` and
- `HashWithIndifferentAccess` in the next major release. If you use any method
- that is not available on `ActionController::Parameters` you should consider
- calling `#to_h` to convert it to a `Hash` first before calling that method.
-
- *Prem Sichanugrist*
-
-* `ActionController::Parameters#to_h` now returns a `Hash` with unpermitted
- keys removed. This change is to reflect on a security concern where some
- method performed on an `ActionController::Parameters` may yield a `Hash`
- object which does not maintain `permitted?` status. If you would like to
- get a `Hash` with all the keys intact, duplicate and mark it as permitted
- before calling `#to_h`.
-
- params = ActionController::Parameters.new({
- name: 'Senjougahara Hitagi',
- oddity: 'Heavy stone crab'
- })
- params.to_h
- # => {}
-
- unsafe_params = params.dup.permit!
- unsafe_params.to_h
- # => {"name"=>"Senjougahara Hitagi", "oddity"=>"Heavy stone crab"}
-
- safe_params = params.permit(:name)
- safe_params.to_h
- # => {"name"=>"Senjougahara Hitagi"}
-
- This change is consider a stopgap as we cannot change the code to stop
- `ActionController::Parameters` to inherit from `HashWithIndifferentAccess`
- in the next minor release.
-
- *Prem Sichanugrist*
-
-* Deprecated `TagAssertions`.
-
- *Kasper Timm Hansen*
-
-* Use the Active Support JSON encoder for cookie jars using the `:json` or
- `:hybrid` serializer. This allows you to serialize custom Ruby objects into
- cookies by defining the `#as_json` hook on such objects.
-
- Fixes #16520.
-
- *Godfrey Chan*
-
-* Add `config.action_dispatch.cookies_digest` option for setting custom
- digest. The default remains the same - 'SHA1'.
-
- *Łukasz Strzałkowski*
-
-* Move `respond_with` (and the class-level `respond_to`) to
- the `responders` gem.
+* Partitioning of routes is now done when the routes are being drawn. This
+ helps to decrease the time spent filtering the routes during the first request.
- *José Valim*
-
-* When your templates change, browser caches bust automatically.
-
- New default: the template digest is automatically included in your ETags.
- When you call `fresh_when @post`, the digest for `posts/show.html.erb`
- is mixed in so future changes to the HTML will blow HTTP caches for you.
- This makes it easy to HTTP-cache many more of your actions.
-
- If you render a different template, you can now pass the `:template`
- option to include its digest instead:
-
- fresh_when @post, template: 'widgets/show'
-
- Pass `template: false` to skip the lookup. To turn this off entirely, set:
-
- config.action_controller.etag_with_template_digest = false
-
- *Jeremy Kemper*
+ *Guo Xiang Tan*
-* Remove deprecated `AbstractController::Helpers::ClassMethods::MissingHelperError`
- in favor of `AbstractController::Helpers::MissingHelperError`.
+* Fix regression in functional tests. Responses should have default headers
+ assigned.
- *Yves Senn*
+ See #18423.
-* Fix `assert_template` not being able to assert that no files were rendered.
+ *Jeremy Kemper*, *Yves Senn*
- *Guo Xiang Tan*
+* Deprecate AbstractController#skip_action_callback in favor of individual skip_callback methods
+ (which can be made to raise an error if no callback was removed).
-* Extract source code for the entire exception stack trace for
- better debugging and diagnosis.
+ *Iain Beeston*
- *Ryan Dao*
+* Alias the `ActionDispatch::Request#uuid` method to `ActionDispatch::Request#request_id`.
+ Due to implementation, `config.log_tags = [:request_id]` also works in substitute
+ for `config.log_tags = [:uuid]`.
-* Allows ActionDispatch::Request::LOCALHOST to match any IPv4 127.0.0.0/8
- loopback address.
+ *David Ilizarov*
- *Earl St Sauver*, *Sven Riedel*
+* Change filter on /rails/info/routes to use an actual path regexp from rails
+ and not approximate javascript version. Oniguruma supports much more
+ extensive list of features than javascript regexp engine.
-* Preserve original path in `ShowExceptions` middleware by stashing it as
- `env["action_dispatch.original_path"]`
+ Fixes #18402.
- `ActionDispatch::ShowExceptions` overwrites `PATH_INFO` with the status code
- for the exception defined in `ExceptionWrapper`, so the path
- the user was visiting when an exception occurred was not previously
- available to any custom exceptions_app. The original `PATH_INFO` is now
- stashed in `env["action_dispatch.original_path"]`.
+ *Ravil Bayramgalin*
- *Grey Baker*
+* Non-string authenticity tokens do not raise NoMethodError when decoding
+ the masked token.
-* Use `String#bytesize` instead of `String#size` when checking for cookie
- overflow.
+ *Ville Lautanala*
- *Agis Anastasopoulos*
+* Add `http_cache_forever` to Action Controller, so we can cache a response
+ that never gets expired.
-* `render nothing: true` or rendering a `nil` body no longer add a single
- space to the response body.
+ *arthurnn*
- The old behavior was added as a workaround for a bug in an early version of
- Safari, where the HTTP headers are not returned correctly if the response
- body has a 0-length. This is been fixed since and the workaround is no
- longer necessary.
+* `ActionController#translate` supports symbols as shortcuts.
+ When shortcut is given it also lookups without action name.
- Use `render body: ' '` if the old behavior is desired.
+ *Max Melentiev*
- See #14883 for details.
+* Expand `ActionController::ConditionalGet#fresh_when` and `stale?` to also
+ accept a collection of records as the first argument, so that the
+ following code can be written in a shorter form.
- *Godfrey Chan*
+ # Before
+ def index
+ @articles = Article.all
+ fresh_when(etag: @articles, last_modified: @articles.maximum(:updated_at))
+ end
-* Prepend a JS comment to JSONP callbacks. Addresses CVE-2014-4671
- ("Rosetta Flash").
+ # After
+ def index
+ @articles = Article.all
+ fresh_when(@articles)
+ end
- *Greg Campbell*
+ *claudiob*
-* Because URI paths may contain non US-ASCII characters we need to force
- the encoding of any unescaped URIs to UTF-8 if they are US-ASCII.
- This essentially replicates the functionality of the monkey patch to
- URI.parser.unescape in active_support/core_ext/uri.rb.
+* Explicitly ignored wildcard verbs when searching for HEAD routes before fallback
- Fixes #16104.
+ Fixes an issue where a mounted rack app at root would intercept the HEAD
+ request causing an incorrect behavior during the fall back to GET requests.
- *Karl Entwistle*
+ Example:
-* Generate shallow paths for all children of shallow resources.
+ draw do
+ get '/home' => 'test#index'
+ mount rack_app, at: '/'
+ end
+ head '/home'
+ assert_response :success
- Fixes #15783.
+ In this case, a HEAD request runs through the routes the first time and fails
+ to match anything. Then, it runs through the list with the fallback and matches
+ `get '/home'`. The original behavior would match the rack app in the first pass.
- *Seb Jacobs*
+ *Terence Sun*
-* JSONP responses are now rendered with the `text/javascript` content type
- when rendering through a `respond_to` block.
+* Migrating xhr methods to keyword arguments syntax
+ in `ActionController::TestCase` and `ActionDispatch::Integration`
- Fixes #15081.
+ Old syntax:
- *Lucas Mazza*
+ xhr :get, :create, params: { id: 1 }
-* Add `config.action_controller.always_permitted_parameters` to configure which
- parameters are permitted globally. The default value of this configuration is
- `['controller', 'action']`.
+ New syntax example:
- *Gary S. Weaver*, *Rafael Chacon*
+ get :create, params: { id: 1 }, xhr: true
-* Fix env['PATH_INFO'] missing leading slash when a rack app mounted at '/'.
+ *Kir Shatrov*
- Fixes #15511.
+* Migrating to keyword arguments syntax in `ActionController::TestCase` and
+ `ActionDispatch::Integration` HTTP request methods.
- *Larry Lv*
+ Example:
-* ActionController::Parameters#require now accepts `false` values.
+ post :create, params: { y: x }, session: { a: 'b' }
+ get :view, params: { id: 1 }
+ get :view, params: { id: 1 }, format: :json
- Fixes #15685.
+ *Kir Shatrov*
- *Sergio Romano*
+* Preserve default url options when generating URLs.
-* With authorization header `Authorization: Token token=`, `authenticate` now
- recognize token as nil, instead of "token".
+ 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.
- Fixes #14846.
+ *Tekin Suleyman*
- *Larry Lv*
+* Deprecate *_via_redirect integration test methods.
-* Ensure the controller is always notified as soon as the client disconnects
- during live streaming, even when the controller is blocked on a write.
+ Use `follow_redirect!` manually after the request call for the same behavior.
- *Nicholas Jakobsen*, *Matthew Draper*
+ *Aditya Kapoor*
-* Routes specifying 'to:' must be a string that contains a "#" or a rack
- application. Use of a symbol should be replaced with `action: symbol`.
- Use of a string without a "#" should be replaced with `controller: string`.
+* Add `ActionController::Renderer` to render arbitrary templates
+ outside controller actions.
- *Aaron Patterson*
+ Its functionality is accessible through class methods `render` and
+ `renderer` of `ActionController::Base`.
-* Fix URL generation with `:trailing_slash` such that it does not add
- a trailing slash after `.:format`
+ *Ravil Bayramgalin*
- *Dan Langevin*
+* Support `:assigns` option when rendering with controllers/mailers.
-* Build full URI as string when processing path in integration tests for
- performance reasons.
+ *Ravil Bayramgalin*
- *Guo Xiang Tan*
+* Default headers, removed in controller actions, are no longer reapplied on
+ the test response.
-* Fix `'Stack level too deep'` when rendering `head :ok` in an action method
- called 'status' in a controller.
+ *Jonas Baumann*
- Fixes #13905.
+* Deprecate all *_filter callbacks in favor of *_action callbacks.
- *Christiaan Van den Poel*
+ *Rafael Mendonça França*
-* Add MKCALENDAR HTTP method (RFC 4791).
+* 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.
- *Sergey Karpesh*
+ Example:
-* Instrument fragment cache metrics.
+ class ApplicationController < ActionController::Base
+ before_action :authenticate
+ protect_from_forgery prepend: false, unless: -> { @authenticated_by.oauth? }
- Adds `:controller`: and `:action` keys to the instrumentation payload
- for the `*_fragment.action_controller` notifications. This allows tracking
- e.g. the fragment cache hit rates for each controller action.
+ private
+ def authenticate
+ if oauth_request?
+ # authenticate with oauth
+ @authenticated_by = 'oauth'.inquiry
+ else
+ # authenticate with cookies
+ @authenticated_by = 'cookie'.inquiry
+ end
+ end
+ end
- *Daniel Schierbeck*
+ *Josef Šimánek*
-* Always use the provided port if the protocol is relative.
+* Remove `ActionController::HideActions`.
- Fixes #15043.
+ *Ravil Bayramgalin*
- *Guilherme Cavalcanti*, *Andrew White*
+* Remove `respond_to`/`respond_with` placeholder methods, this functionality
+ has been extracted to the `responders` gem.
-* Moved `params[request_forgery_protection_token]` into its own method
- and improved tests.
+ *Carlos Antonio da Silva*
- Fixes #11316.
+* Remove deprecated assertion files.
- *Tom Kadwill*
+ *Rafael Mendonça França*
-* Added verification of route constraints given as a Proc or an object responding
- to `:matches?`. Previously, when given an non-complying object, it would just
- silently fail to enforce the constraint. It will now raise an `ArgumentError`
- when setting up the routes.
+* Remove deprecated usage of string keys in URL helpers.
- *Xavier Defrang*
+ *Rafael Mendonça França*
-* Properly treat the entire IPv6 User Local Address space as private for
- purposes of remote IP detection. Also handle uppercase private IPv6
- addresses.
+* Remove deprecated `only_path` option on `*_path` helpers.
- Fixes #12638.
+ *Rafael Mendonça França*
- *Caleb Spare*
+* Remove deprecated `NamedRouteCollection#helpers`.
-* Fixed an issue with migrating legacy json cookies.
+ *Rafael Mendonça França*
- Previously, the `VerifyAndUpgradeLegacySignedMessage` assumes all incoming
- cookies are marshal-encoded. This is not the case when `secret_token` is
- used in conjunction with the `:json` or `:hybrid` serializer.
+* Remove deprecated support to define routes with `:to` option that doesn't contain `#`.
- In those case, when upgrading to use `secret_key_base`, this would cause a
- `TypeError: incompatible marshal file format` and a 500 error for the user.
+ *Rafael Mendonça França*
- Fixes #14774.
+* Remove deprecated `ActionDispatch::Response#to_ary`.
- *Godfrey Chan*
+ *Rafael Mendonça França*
-* Make URL escaping more consistent:
+* Remove deprecated `ActionDispatch::Request#deep_munge`.
- 1. Escape '%' characters in URLs - only unescaped data should be passed to URL helpers
- 2. Add an `escape_segment` helper to `Router::Utils` that escapes '/' characters
- 3. Use `escape_segment` rather than `escape_fragment` in optimized URL generation
- 4. Use `escape_segment` rather than `escape_path` in URL generation
+ *Rafael Mendonça França*
- For point 4 there are two exceptions. Firstly, when a route uses wildcard segments
- (e.g. `*foo`) then we use `escape_path` as the value may contain '/' characters. This
- means that wildcard routes can't be optimized. Secondly, if a `:controller` segment
- is used in the path then this uses `escape_path` as the controller may be namespaced.
+* Remove deprecated `ActionDispatch::Http::Parameters#symbolized_path_parameters`.
- Fixes #14629, #14636 and #14070.
+ *Rafael Mendonça França*
- *Andrew White*, *Edho Arief*
+* Remove deprecated option `use_route` in controller tests.
-* Add alias `ActionDispatch::Http::UploadedFile#to_io` to
- `ActionDispatch::Http::UploadedFile#tempfile`.
+ *Rafael Mendonça França*
- *Tim Linquist*
+* Ensure `append_info_to_payload` is called even if an exception is raised.
-* Returns null type format when format is not know and controller is using `any`
- format block.
+ Fixes an issue where when an exception is raised in the request the additonal
+ payload data is not available.
- Fixes #14462.
+ See:
+ * #14903
+ * https://github.com/roidrage/lograge/issues/37
- *Rafael Mendonça França*
+ *Dieter Komendera*, *Margus Pärt*
-* Improve routing error page with fuzzy matching search.
+* Correctly rely on the response's status code to handle calls to `head`.
- *Winston*
+ *Robin Dupret*
-* Only make deeply nested routes shallow when parent is shallow.
+* Using `head` method returns empty response_body instead
+ of returning a single space " ".
- Fixes #14684.
+ The old behavior was added as a workaround for a bug in an early
+ version of Safari, where the HTTP headers are not returned correctly
+ if the response body has a 0-length. This is been fixed since and
+ the workaround is no longer necessary.
- *Andrew White*, *James Coglan*
+ Fixes #18253.
-* Append link to bad code to backtrace when exception is `SyntaxError`.
+ *Prathamesh Sonpatki*
- *Boris Kuznetsov*
+* Fix how polymorphic routes works with objects that implement `to_model`.
-* Swapped the parameters of assert_equal in `assert_select` so that the
- proper values were printed correctly.
+ *Travis Grathwell*
- Fixes #14422.
+* Stop converting empty arrays in `params` to `nil`.
- *Vishal Lal*
+ This behaviour was introduced in response to CVE-2012-2660, CVE-2012-2694
+ and CVE-2013-0155
-* The method `shallow?` returns false if the parent resource is a singleton so
- we need to check if we're not inside a nested scope before copying the :path
- and :as options to their shallow equivalents.
+ ActiveRecord now issues a safe query when passing an empty array into
+ a where clause, so there is no longer a need to defend against this type
+ of input (any nils are still stripped from the array).
- Fixes #14388.
+ *Chris Sinjakli*
- *Andrew White*
+* Fixed usage of optional scopes in url helpers.
-* Make logging of CSRF failures optional (but on by default) with the
- `log_warning_on_csrf_failure` configuration setting in
- `ActionController::RequestForgeryProtection`.
+ *Alex Robbin*
- *John Barton*
+* Fixed handling of positional url helper arguments when `format: false`.
-* Fix URL generation in controller tests with request-dependent
- `default_url_options` methods.
+ Fixes #17819.
- *Tony Wooster*
+ *Andrew White*, *Tatiana Soukiassian*
-Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/actionpack/CHANGELOG.md) for previous changes.
+Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/actionpack/CHANGELOG.md) for previous changes.
diff --git a/actionpack/MIT-LICENSE b/actionpack/MIT-LICENSE
index d58dd9ed9b..3ec7a617cf 100644
--- a/actionpack/MIT-LICENSE
+++ b/actionpack/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2014 David Heinemeier Hansson
+Copyright (c) 2004-2015 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index 4b60b77759..3bd27f8d64 100644
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -9,10 +9,7 @@ task :default => :test
# Run the unit tests
Rake::TestTask.new do |t|
t.libs << 'test'
-
- # make sure we include the tests in alphabetical order as on some systems
- # this will not happen automatically and the tests (as a whole) will error
- t.test_files = test_files.sort
+ t.test_files = test_files
t.warning = true
t.verbose = true
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 5196e67e55..b6b70a027c 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 = '>= 1.9.3'
+ s.required_ruby_version = '>= 2.2.1'
s.license = 'MIT'
@@ -21,10 +21,10 @@ Gem::Specification.new do |s|
s.add_dependency 'activesupport', version
- s.add_dependency 'rack', '~> 1.6.0.beta'
- s.add_dependency 'rack-test', '~> 0.6.2'
- s.add_dependency 'rails-html-sanitizer', '~> 1.0', '>= 1.0.1'
- s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.4'
+ s.add_dependency 'rack', '~> 1.6'
+ s.add_dependency 'rack-test', '~> 0.6.3'
+ s.add_dependency 'rails-html-sanitizer', '~> 1.0', '>= 1.0.2'
+ s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.5'
s.add_dependency 'actionview', version
s.add_development_dependency 'activemodel', version
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index 4026dab2ce..c95b9a4097 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -12,7 +12,7 @@ module AbstractController
class ActionNotFound < StandardError
end
- # <tt>AbstractController::Base</tt> is a low-level API. Nobody should be
+ # AbstractController::Base is a low-level API. Nobody should be
# using it directly, and subclasses (like ActionController::Base) are
# expected to provide their own +render+ method, since rendering means
# different things depending on the context.
@@ -57,21 +57,11 @@ module AbstractController
controller.public_instance_methods(true)
end
- # The list of hidden actions. Defaults to an empty array.
- # This can be modified by other modules or subclasses
- # to specify particular actions as hidden.
- #
- # ==== Returns
- # * <tt>Array</tt> - An array of method names that should not be considered actions.
- def hidden_actions
- []
- end
-
# A list of method names that should be considered actions. This
# includes all public instance methods on a controller, less
- # any internal methods (see #internal_methods), adding back in
+ # any internal methods (see internal_methods), adding back in
# any methods that are internal, but still exist on the class
- # itself. Finally, #hidden_actions are removed.
+ # itself.
#
# ==== Returns
# * <tt>Set</tt> - A set of all methods that should be considered actions.
@@ -82,25 +72,26 @@ module AbstractController
# Except for public instance methods of Base and its ancestors
internal_methods +
# Be sure to include shadowed public instance methods of this class
- public_instance_methods(false)).uniq.map { |x| x.to_s } -
- # And always exclude explicitly hidden actions
- hidden_actions.to_a
+ public_instance_methods(false)).uniq.map(&:to_s)
- # Clear out AS callback method pollution
- Set.new(methods.reject { |method| method =~ /_one_time_conditions/ })
+ methods.to_set
end
end
# action_methods are cached and there is sometimes need to refresh
- # them. clear_action_methods! allows you to do that, so next time
+ # them. ::clear_action_methods! allows you to do that, so next time
# you run action_methods, they will be recalculated
def clear_action_methods!
@action_methods = nil
end
# Returns the full controller name, underscored, without the ending Controller.
- # For instance, MyApp::MyPostsController would return "my_app/my_posts" for
- # controller_path.
+ #
+ # class MyApp::MyPostsController < AbstractController::Base
+ # end
+ # end
+ #
+ # MyApp::MyPostsController.controller_path # => "my_app/my_posts"
#
# ==== Returns
# * <tt>String</tt>
@@ -137,12 +128,12 @@ module AbstractController
process_action(action_name, *args)
end
- # Delegates to the class' #controller_path
+ # Delegates to the class' ::controller_path
def controller_path
self.class.controller_path
end
- # Delegates to the class' #action_methods
+ # Delegates to the class' ::action_methods
def action_methods
self.class.action_methods
end
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index ca5c80cd71..13795f0dd8 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -9,7 +9,7 @@ module AbstractController
included do
define_callbacks :process_action,
- terminator: ->(controller,_) { controller.response_body },
+ terminator: ->(controller, result_lambda) { result_lambda.call if result_lambda.is_a?(Proc); controller.response_body },
skip_after_callbacks_if_terminated: true
end
@@ -22,10 +22,21 @@ module AbstractController
end
module ClassMethods
- # If :only or :except are used, convert the options into the
- # :unless and :if options of ActiveSupport::Callbacks.
- # The basic idea is that :only => :index gets converted to
- # :if => proc {|c| c.action_name == "index" }.
+ # If +:only+ or +:except+ are used, convert the options into the
+ # +:if+ and +:unless+ options of ActiveSupport::Callbacks.
+ #
+ # The basic idea is that <tt>:only => :index</tt> gets converted to
+ # <tt>:if => proc {|c| c.action_name == "index" }</tt>.
+ #
+ # Note that <tt>:only</tt> has priority over <tt>:if</tt> in case they
+ # are used together.
+ #
+ # only: :index, if: -> { true } # the :if option will be ignored.
+ #
+ # Note that <tt>:if</tt> has priority over <tt>:except</tt> in case they
+ # are used together.
+ #
+ # except: :index, if: -> { true } # the :except option will be ignored.
#
# ==== Options
# * <tt>only</tt> - The callback should be run only for this action
@@ -50,11 +61,16 @@ module AbstractController
# impossible to skip a callback defined using an anonymous proc
# using #skip_action_callback
def skip_action_callback(*names)
- skip_before_action(*names)
- skip_after_action(*names)
- skip_around_action(*names)
+ ActiveSupport::Deprecation.warn('`skip_action_callback` is deprecated and will be removed in the next major version of Rails. Please use skip_before_action, skip_after_action or skip_around_action instead.')
+ skip_before_action(*names, raise: false)
+ skip_after_action(*names, raise: false)
+ skip_around_action(*names, raise: false)
+ end
+
+ def skip_filter(*names)
+ ActiveSupport::Deprecation.warn("`skip_filter` is deprecated and will be removed in Rails 5.1. Use skip_before_action, skip_after_action or skip_around_action instead.")
+ skip_action_callback(*names)
end
- alias_method :skip_filter, :skip_action_callback
# Take callback names and an optional callback proc, normalize them,
# then call the block with each callback. This allows us to abstract
@@ -169,14 +185,22 @@ module AbstractController
set_callback(:process_action, callback, name, options)
end
end
- alias_method :"#{callback}_filter", :"#{callback}_action"
+
+ define_method "#{callback}_filter" do |*names, &blk|
+ ActiveSupport::Deprecation.warn("#{callback}_filter is deprecated and will be removed in Rails 5.1. Use #{callback}_action instead.")
+ send("#{callback}_action", *names, &blk)
+ end
define_method "prepend_#{callback}_action" do |*names, &blk|
_insert_callbacks(names, blk) do |name, options|
set_callback(:process_action, callback, name, options.merge(:prepend => true))
end
end
- alias_method :"prepend_#{callback}_filter", :"prepend_#{callback}_action"
+
+ define_method "prepend_#{callback}_filter" do |*names, &blk|
+ ActiveSupport::Deprecation.warn("prepend_#{callback}_filter is deprecated and will be removed in Rails 5.1. Use prepend_#{callback}_action instead.")
+ send("prepend_#{callback}_action", *names, &blk)
+ end
# Skip a before, after or around callback. See _insert_callbacks
# for details on the allowed parameters.
@@ -185,11 +209,19 @@ module AbstractController
skip_callback(:process_action, callback, name, options)
end
end
- alias_method :"skip_#{callback}_filter", :"skip_#{callback}_action"
+
+ define_method "skip_#{callback}_filter" do |*names, &blk|
+ ActiveSupport::Deprecation.warn("skip_#{callback}_filter is deprecated and will be removed in Rails 5.1. Use skip_#{callback}_action instead.")
+ send("skip_#{callback}_action", *names, &blk)
+ end
# *_action is the same as append_*_action
alias_method :"append_#{callback}_action", :"#{callback}_action"
- alias_method :"append_#{callback}_filter", :"#{callback}_action"
+
+ define_method "append_#{callback}_filter" do |*names, &blk|
+ ActiveSupport::Deprecation.warn("append_#{callback}_filter is deprecated and will be removed in Rails 5.1. Use append_#{callback}_action instead.")
+ send("append_#{callback}_action", *names, &blk)
+ end
end
end
end
diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb
index df7382f02d..109eff10eb 100644
--- a/actionpack/lib/abstract_controller/helpers.rb
+++ b/actionpack/lib/abstract_controller/helpers.rb
@@ -184,7 +184,7 @@ module AbstractController
module_name = name.sub(/Controller$/, '')
module_path = module_name.underscore
helper module_path
- rescue MissingSourceFile => e
+ rescue LoadError => e
raise e unless e.is_missing? "helpers/#{module_path}_helper"
rescue NameError => e
raise e unless e.missing_name? "#{module_name}Helper"
diff --git a/actionpack/lib/abstract_controller/railties/routes_helpers.rb b/actionpack/lib/abstract_controller/railties/routes_helpers.rb
index 568c47e43a..14b574e322 100644
--- a/actionpack/lib/abstract_controller/railties/routes_helpers.rb
+++ b/actionpack/lib/abstract_controller/railties/routes_helpers.rb
@@ -6,9 +6,9 @@ module AbstractController
define_method(:inherited) do |klass|
super(klass)
if namespace = klass.parents.detect { |m| m.respond_to?(:railtie_routes_url_helpers) }
- klass.send(:include, namespace.railtie_routes_url_helpers(include_path_helpers))
+ klass.include(namespace.railtie_routes_url_helpers(include_path_helpers))
else
- klass.send(:include, routes.url_helpers(include_path_helpers))
+ klass.include(routes.url_helpers(include_path_helpers))
end
end
end
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index 9d10140ed2..5514213ad8 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -17,8 +17,8 @@ module AbstractController
extend ActiveSupport::Concern
include ActionView::ViewPaths
- # Normalize arguments, options and then delegates render_to_body and
- # sticks the result in self.response_body.
+ # Normalizes arguments, options and then delegates render_to_body and
+ # sticks the result in <tt>self.response_body</tt>.
# :api: public
def render(*args, &block)
options = _normalize_render(*args, &block)
@@ -30,11 +30,11 @@ module AbstractController
# Raw rendering of a template to a string.
#
# It is similar to render, except that it does not
- # set the response_body and it should be guaranteed
+ # set the +response_body+ and it should be guaranteed
# to always return a string.
#
- # If a component extends the semantics of response_body
- # (as Action Controller extends it to be anything that
+ # If a component extends the semantics of +response_body+
+ # (as ActionController extends it to be anything that
# responds to the method each), this method needs to be
# overridden in order to still return a string.
# :api: plugin
@@ -73,8 +73,9 @@ module AbstractController
}
end
- # Normalize args by converting render "foo" to render :action => "foo" and
- # render "foo/bar" to render :file => "foo/bar".
+ # Normalize args by converting <tt>render "foo"</tt> to
+ # <tt>render :action => "foo"</tt> and <tt>render "foo/bar"</tt> to
+ # <tt>render :file => "foo/bar"</tt>.
# :api: plugin
def _normalize_args(action=nil, options={})
if action.is_a? Hash
diff --git a/actionpack/lib/abstract_controller/translation.rb b/actionpack/lib/abstract_controller/translation.rb
index 02028d8e05..56b8ce895e 100644
--- a/actionpack/lib/abstract_controller/translation.rb
+++ b/actionpack/lib/abstract_controller/translation.rb
@@ -8,14 +8,15 @@ module AbstractController
# <tt>I18n.translate("people.index.foo")</tt>. This makes it less repetitive
# to translate many keys within the same controller / action and gives you a
# simple framework for scoping them consistently.
- def translate(*args)
- key = args.first
- if key.is_a?(String) && (key[0] == '.')
- key = "#{ controller_path.tr('/', '.') }.#{ action_name }#{ key }"
- args[0] = key
+ def translate(key, options = {})
+ if key.to_s.first == '.'
+ path = controller_path.tr('/', '.')
+ defaults = [:"#{path}#{key}"]
+ defaults << options[:default] if options[:default]
+ options[:default] = defaults
+ key = "#{path}.#{action_name}#{key}"
end
-
- I18n.translate(*args)
+ I18n.translate(key, options)
end
alias :t :translate
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index 91ac7eef01..7667e469d3 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -11,6 +11,7 @@ module ActionController
autoload :Caching
autoload :Metal
autoload :Middleware
+ autoload :Renderer
autoload_under "metal" do
autoload :Compatibility
@@ -22,7 +23,6 @@ module ActionController
autoload :ForceSSL
autoload :Head
autoload :Helpers
- autoload :HideActions
autoload :HttpAuthentication
autoload :ImplicitRender
autoload :Instrumentation
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 7bbf938987..e6038396f9 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -44,7 +44,7 @@ module ActionController
# The full request object is available via the request accessor and is primarily used to query for HTTP headers:
#
# def server_ip
- # location = request.env["SERVER_ADDR"]
+ # location = request.env["REMOTE_ADDR"]
# render plain: "This server hosted at #{location}"
# end
#
@@ -206,7 +206,6 @@ module ActionController
AbstractController::AssetPaths,
Helpers,
- HideActions,
UrlFor,
Redirecting,
ActionView::Layouts,
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index 89fa75f025..87609d8aa7 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -1,4 +1,3 @@
-
module ActionController
class LogSubscriber < ActiveSupport::LogSubscriber
INTERNAL_PARAMS = %w(controller action format _method only_path)
@@ -54,15 +53,6 @@ module ActionController
end
end
- def deep_munge(event)
- debug do
- "Value for params[:#{event.payload[:keys].join('][:')}] was set "\
- "to nil, because it was one of [], [null] or [null, null, ...]. "\
- "Go to http://guides.rubyonrails.org/security.html#unsafe-query-generation "\
- "for more information."\
- end
- end
-
%w(write_fragment read_fragment exist_fragment?
expire_fragment expire_page write_page).each do |method|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 6dd213b2f7..ae111e4951 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -173,6 +173,7 @@ module ActionController
def status
@_status
end
+ alias :response_code :status # :nodoc:
def status=(status)
@_status = Rack::Utils.status_code(status)
@@ -189,11 +190,15 @@ module ActionController
end
def dispatch(name, request) #:nodoc:
+ set_request!(request)
+ process(name)
+ to_a
+ end
+
+ def set_request!(request) #:nodoc:
@_request = request
@_env = request.env
@_env['action_controller.instance'] = self
- process(name)
- to_a
end
def to_a #:nodoc:
@@ -236,9 +241,5 @@ module ActionController
lambda { |env| new.dispatch(name, klass.new(env)) }
end
end
-
- def _status_code #:nodoc:
- @_status
- end
end
end
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index b210ee3423..47bcfdb1e9 100644
--- a/actionpack/lib/action_controller/metal/conditional_get.rb
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -15,7 +15,7 @@ module ActionController
module ClassMethods
# Allows you to consider additional controller-wide information when generating an ETag.
# For example, if you serve pages tailored depending on who's logged in at the moment, you
- # may want to add the current user id to be part of the ETag to prevent authorized displaying
+ # may want to add the current user id to be part of the ETag to prevent unauthorized displaying
# of cached pages.
#
# class InvoicesController < ApplicationController
@@ -51,21 +51,31 @@ module ActionController
#
# def show
# @article = Article.find(params[:id])
- # fresh_when(etag: @article, last_modified: @article.created_at, public: true)
+ # fresh_when(etag: @article, last_modified: @article.updated_at, public: true)
# end
#
# This will render the show template if the request isn't sending a matching ETag or
# If-Modified-Since header and just a <tt>304 Not Modified</tt> response if there's a match.
#
- # You can also just pass a record where +last_modified+ will be set by calling
- # +updated_at+ and the +etag+ by passing the object itself.
+ # You can also just pass a record. In this case +last_modified+ will be set
+ # by calling +updated_at+ and +etag+ by passing the object itself.
#
# def show
# @article = Article.find(params[:id])
# fresh_when(@article)
# end
#
- # When passing a record, you can still set whether the public header:
+ # You can also pass an object that responds to +maximum+, such as a
+ # collection of active records. In this case +last_modified+ will be set by
+ # calling +maximum(:updated_at)+ on the collection (the timestamp of the
+ # most recently updated record) and the +etag+ by passing the object itself.
+ #
+ # def index
+ # @articles = Article.all
+ # fresh_when(@articles)
+ # end
+ #
+ # When passing a record or a collection, you can still set the public header:
#
# def show
# @article = Article.find(params[:id])
@@ -77,18 +87,16 @@ module ActionController
#
# before_action { fresh_when @article, template: 'widgets/show' }
#
- def fresh_when(record_or_options, additional_options = {})
- if record_or_options.is_a? Hash
- options = record_or_options
- options.assert_valid_keys(:etag, :last_modified, :public, :template)
- else
- record = record_or_options
- options = { etag: record, last_modified: record.try(:updated_at) }.merge!(additional_options)
+ def fresh_when(object = nil, etag: object, last_modified: nil, public: false, template: nil)
+ last_modified ||= object.try(:updated_at) || object.try(:maximum, :updated_at)
+
+ if etag || template
+ response.etag = combine_etags(etag: etag, last_modified: last_modified,
+ public: public, template: template)
end
- response.etag = combine_etags(options) if options[:etag] || options[:template]
- response.last_modified = options[:last_modified] if options[:last_modified]
- response.cache_control[:public] = true if options[:public]
+ response.last_modified = last_modified if last_modified
+ response.cache_control[:public] = true if public
head :not_modified if request.fresh?(response)
end
@@ -115,7 +123,7 @@ module ActionController
# def show
# @article = Article.find(params[:id])
#
- # if stale?(etag: @article, last_modified: @article.created_at)
+ # if stale?(etag: @article, last_modified: @article.updated_at)
# @statistics = @article.really_expensive_call
# respond_to do |format|
# # all the supported formats
@@ -123,8 +131,8 @@ module ActionController
# end
# end
#
- # You can also just pass a record where +last_modified+ will be set by calling
- # +updated_at+ and the +etag+ by passing the object itself.
+ # You can also just pass a record. In this case +last_modified+ will be set
+ # by calling +updated_at+ and +etag+ by passing the object itself.
#
# def show
# @article = Article.find(params[:id])
@@ -137,7 +145,23 @@ module ActionController
# end
# end
#
- # When passing a record, you can still set whether the public header:
+ # You can also pass an object that responds to +maximum+, such as a
+ # collection of active records. In this case +last_modified+ will be set by
+ # calling +maximum(:updated_at)+ on the collection (the timestamp of the
+ # most recently updated record) and the +etag+ by passing the object itself.
+ #
+ # def index
+ # @articles = Article.all
+ #
+ # if stale?(@articles)
+ # @statistics = @articles.really_expensive_call
+ # respond_to do |format|
+ # # all the supported formats
+ # end
+ # end
+ # end
+ #
+ # When passing a record or a collection, you can still set the public header:
#
# def show
# @article = Article.find(params[:id])
@@ -157,8 +181,8 @@ module ActionController
# super if stale? @article, template: 'widgets/show'
# end
#
- def stale?(record_or_options, additional_options = {})
- fresh_when(record_or_options, additional_options)
+ def stale?(object = nil, etag: object, last_modified: nil, public: nil, template: nil)
+ fresh_when(object, etag: etag, last_modified: last_modified, public: public, template: template)
!request.fresh?(response)
end
@@ -191,6 +215,24 @@ module ActionController
response.cache_control.replace(:no_cache => true)
end
+ # Cache or yield the block. The cache is supposed to never expire.
+ #
+ # You can use this method when you have a HTTP response that never changes,
+ # and the browser and proxies should cache it indefinitely.
+ #
+ # * +public+: By default, HTTP responses are private, cached only on the
+ # user's web browser. To allow proxies to cache the response, set +true+ to
+ # indicate that they can serve the cached response to all users.
+ #
+ # * +version+: the version passed as a key for the cache.
+ def http_cache_forever(public: false, version: 'v1')
+ expires_in 100.years, public: public
+
+ yield if stale?(etag: "#{version}-#{request.fullpath}",
+ last_modified: Time.parse('2011-01-01').utc,
+ public: public)
+ end
+
private
def combine_etags(options)
etags = etaggers.map { |etagger| instance_exec(options, &etagger) }.compact
diff --git a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb
index 3ca0c6837a..f9303efe6c 100644
--- a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb
+++ b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb
@@ -7,8 +7,8 @@ module ActionController
#
# config.action_controller.etag_with_template_digest = false
#
- # Override the template to digest by passing `:template` to `fresh_when`
- # and `stale?` calls. For example:
+ # Override the template to digest by passing +:template+ to +fresh_when+
+ # and +stale?+ calls. For example:
#
# # We're going to render widgets/show, not posts/show
# fresh_when @post, template: 'widgets/show'
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index d920668184..5a8c7db162 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -89,7 +89,7 @@ module ActionController
end
secure_url = ActionDispatch::Http::URL.url_for(options.slice(*URL_OPTIONS))
- flash.keep if respond_to?(:flash)
+ flash.keep if request.respond_to?(:flash)
redirect_to secure_url, options.slice(*REDIRECT_OPTIONS)
end
end
diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb
index 3d2badf9c2..70f42bf565 100644
--- a/actionpack/lib/action_controller/metal/head.rb
+++ b/actionpack/lib/action_controller/metal/head.rb
@@ -29,15 +29,17 @@ module ActionController
self.status = status
self.location = url_for(location) if location
- if include_content?(self._status_code)
+ self.response_body = ""
+
+ if include_content?(self.response_code)
self.content_type = content_type || (Mime[formats.first] if formats)
self.response.charset = false if self.response
- self.response_body = " "
else
headers.delete('Content-Type')
headers.delete('Content-Length')
- self.response_body = ""
end
+
+ true
end
private
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index a9c3e438fb..4038101fe0 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -93,6 +93,10 @@ module ActionController
super(args)
end
+ # Returns a list of helper names in a given path.
+ #
+ # ActionController::Base.all_helpers_from_path 'app/helpers'
+ # # => ["application", "chart", "rubygems"]
def all_helpers_from_path(path)
helpers = Array(path).flat_map do |_path|
extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/
diff --git a/actionpack/lib/action_controller/metal/hide_actions.rb b/actionpack/lib/action_controller/metal/hide_actions.rb
deleted file mode 100644
index af36ffa240..0000000000
--- a/actionpack/lib/action_controller/metal/hide_actions.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-
-module ActionController
- # Adds the ability to prevent public methods on a controller to be called as actions.
- module HideActions
- extend ActiveSupport::Concern
-
- included do
- class_attribute :hidden_actions
- self.hidden_actions = Set.new.freeze
- end
-
- private
-
- # Overrides AbstractController::Base#action_method? to return false if the
- # action name is in the list of hidden actions.
- def method_for_action(action_name)
- self.class.visible_action?(action_name) && super
- end
-
- module ClassMethods
- # Sets all of the actions passed in as hidden actions.
- #
- # ==== Parameters
- # * <tt>args</tt> - A list of actions
- def hide_action(*args)
- self.hidden_actions = hidden_actions.dup.merge(args.map(&:to_s)).freeze
- end
-
- def visible_action?(action_name)
- not hidden_actions.include?(action_name)
- end
-
- # Overrides AbstractController::Base#action_methods to remove any methods
- # that are listed as hidden methods.
- def action_methods
- @action_methods ||= Set.new(super.reject { |name| hidden_actions.include?(name) }).freeze
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 25c123edf7..c492b7fb64 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -53,10 +53,8 @@ module ActionController
# In your integration tests, you can do something like this:
#
# def test_access_granted_from_xml
- # get(
- # "/notes/1.xml", nil,
- # 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
- # )
+ # @request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
+ # get "/notes/1.xml"
#
# assert_equal 200, status
# end
@@ -108,11 +106,11 @@ module ActionController
end
def auth_scheme(request)
- request.authorization.split(' ', 2).first
+ request.authorization.to_s.split(' ', 2).first
end
def auth_param(request)
- request.authorization.split(' ', 2).second
+ request.authorization.to_s.split(' ', 2).second
end
def encode_credentials(user_name, password)
@@ -120,7 +118,7 @@ module ActionController
end
def authentication_request(controller, realm)
- controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.gsub(/"/, "")}")
+ controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.gsub('"'.freeze, "".freeze)}")
controller.status = 401
controller.response_body = "HTTP Basic: Access denied.\n"
end
@@ -316,7 +314,7 @@ module ActionController
nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout
end
- # Opaque based on random generation - but changing each request?
+ # Opaque based on digest of secret key
def opaque(secret_key)
::Digest::MD5.hexdigest(secret_key)
end
@@ -397,6 +395,7 @@ module ActionController
#
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
module Token
+ TOKEN_KEY = 'token='
TOKEN_REGEX = /^Token /
AUTHN_PAIR_DELIMITERS = /(?:,|;|\t+)/
extend self
@@ -462,16 +461,22 @@ module ActionController
raw_params.map { |param| param.split %r/=(.+)?/ }
end
- # This removes the `"` characters wrapping the value.
+ # This removes the <tt>"</tt> characters wrapping the value.
def rewrite_param_values(array_params)
array_params.each { |param| (param[1] || "").gsub! %r/^"|"$/, '' }
end
# This method takes an authorization body and splits up the key-value
- # pairs by the standardized `:`, `;`, or `\t` delimiters defined in
- # `AUTHN_PAIR_DELIMITERS`.
+ # pairs by the standardized <tt>:</tt>, <tt>;</tt>, or <tt>\t</tt>
+ # delimiters defined in +AUTHN_PAIR_DELIMITERS+.
def raw_params(auth)
- auth.sub(TOKEN_REGEX, '').split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
+ _raw_params = auth.sub(TOKEN_REGEX, '').split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
+
+ if !(_raw_params.first =~ %r{\A#{TOKEN_KEY}})
+ _raw_params[0] = "#{TOKEN_KEY}#{_raw_params.first}"
+ end
+
+ _raw_params
end
# Encodes the given token and options into an Authorization header value.
@@ -481,7 +486,7 @@ module ActionController
#
# Returns String.
def encode_credentials(token, options = {})
- values = ["token=#{token.to_s.inspect}"] + options.map do |key, value|
+ values = ["#{TOKEN_KEY}#{token.to_s.inspect}"] + options.map do |key, value|
"#{key}=#{value.to_s.inspect}"
end
"Token #{values * ", "}"
@@ -494,7 +499,7 @@ module ActionController
#
# Returns nothing.
def authentication_request(controller, realm)
- controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.gsub(/"/, "")}")
+ controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.gsub('"'.freeze, "".freeze)}")
controller.__send__ :render, :text => "HTTP Token: Access denied.\n", :status => :unauthorized
end
end
diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb
index ae04b53825..1573ea7099 100644
--- a/actionpack/lib/action_controller/metal/implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/implicit_render.rb
@@ -7,7 +7,12 @@ module ActionController
end
def default_render(*args)
- 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
+ end
end
def method_for_action(action_name)
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index b0e164bc57..a3e1a71b0a 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -21,17 +21,20 @@ module ActionController
:action => self.action_name,
:params => request.filtered_parameters,
:format => request.format.try(:ref),
- :method => request.method,
+ :method => request.request_method,
:path => (request.fullpath rescue "unknown")
}
ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
- result = super
- payload[:status] = response.status
- append_info_to_payload(payload)
- result
+ begin
+ result = super
+ payload[:status] = response.status
+ result
+ ensure
+ append_info_to_payload(payload)
+ end
end
end
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index 1e13b3761f..58150cd9a9 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -102,7 +102,7 @@ module ActionController
end
end
- message = json.gsub(/\n/, "\ndata: ")
+ message = json.gsub("\n".freeze, "\ndata: ".freeze)
@stream.write "data: #{message}\n\n"
end
end
@@ -189,12 +189,6 @@ module ActionController
!@aborted
end
- def await_close
- synchronize do
- @cv.wait_until { @closed }
- end
- end
-
def on_error(&block)
@error_callback = block
end
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 591f881a53..fab1be3459 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -1,28 +1,7 @@
-require 'active_support/core_ext/array/extract_options'
require 'abstract_controller/collector'
module ActionController #:nodoc:
module MimeResponds
- extend ActiveSupport::Concern
-
- module ClassMethods
- def respond_to(*)
- raise NoMethodError, "The controller-level `respond_to' feature has " \
- "been extracted to the `responders` gem. Add it to your Gemfile to " \
- "continue using this feature:\n" \
- " gem 'responders', '~> 2.0'\n" \
- "Consult the Rails upgrade guide for details."
- end
- end
-
- def respond_with(*)
- raise NoMethodError, "The `respond_with' feature has been extracted " \
- "to the `responders` gem. Add it to your Gemfile to continue using " \
- "this feature:\n" \
- " gem 'responders', '~> 2.0'\n" \
- "Consult the Rails upgrade guide for details."
- end
-
# Without web-service support, an action which collects the data for displaying a list of people
# might look something like this:
#
@@ -135,18 +114,6 @@ module ActionController #:nodoc:
#
# render json: @people
#
- # Since this is a common pattern, you can use the class method respond_to
- # with the respond_with method to have the same results:
- #
- # class PeopleController < ApplicationController
- # respond_to :html, :xml, :json
- #
- # def index
- # @people = Person.all
- # respond_with(@people)
- # end
- # end
- #
# Formats can have different variants.
#
# The request variant is a specialization of the request format, like <tt>:tablet</tt>,
@@ -214,8 +181,8 @@ module ActionController #:nodoc:
# format.html.phone # this gets rendered
# end
#
- # Be sure to check the documentation of +respond_with+ and
- # <tt>ActionController::MimeResponds.respond_to</tt> for more examples.
+ # Be sure to check the documentation of <tt>ActionController::MimeResponds.respond_to</tt>
+ # for more examples.
def respond_to(*mimes)
raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?
@@ -234,8 +201,8 @@ module ActionController #:nodoc:
# A container for responses available from the current controller for
# requests for different mime-types sent to a particular action.
#
- # The public controller methods +respond_with+ and +respond_to+ may be called
- # with a block that is used to define responses to different mime-types, e.g.
+ # The public controller methods +respond_to+ may be called with a block
+ # that is used to define responses to different mime-types, e.g.
# for +respond_to+ :
#
# respond_to do |format|
@@ -321,16 +288,17 @@ module ActionController #:nodoc:
end
def variant
- if @variant.nil?
+ if @variant.empty?
@variants[:none] || @variants[:any]
- elsif (@variants.keys & @variant).any?
- @variant.each do |v|
- return @variants[v] if @variants.key?(v)
- end
else
- @variants[:any]
+ @variants[variant_key]
end
end
+
+ private
+ def variant_key
+ @variant.find { |variant| @variants.key?(variant) } || :any
+ end
end
end
end
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index 09c7a6f946..0a04848eba 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -1,7 +1,6 @@
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/module/anonymous'
-require 'active_support/core_ext/struct'
require 'action_dispatch/http/mime_type'
module ActionController
@@ -86,7 +85,7 @@ module ActionController
new name, format, include, exclude, nil, nil
end
- def initialize(name, format, include, exclude, klass, model) # nodoc
+ def initialize(name, format, include, exclude, klass, model) # :nodoc:
super
@include_set = include
@name_set = name
@@ -132,7 +131,7 @@ module ActionController
private
# Determine the wrapper model from the controller's name. By convention,
# this could be done by trying to find the defined model that has the
- # same singularize name as the controller. For example, +UsersController+
+ # same singular name as the controller. For example, +UsersController+
# will try to find if the +User+ model exists.
#
# This method also does namespace lookup. Foo::Bar::UsersController will
diff --git a/actionpack/lib/action_controller/metal/rack_delegation.rb b/actionpack/lib/action_controller/metal/rack_delegation.rb
index 545d4a7e6e..ae9d89cc8c 100644
--- a/actionpack/lib/action_controller/metal/rack_delegation.rb
+++ b/actionpack/lib/action_controller/metal/rack_delegation.rb
@@ -8,9 +8,15 @@ module ActionController
delegate :headers, :status=, :location=, :content_type=,
:status, :location, :content_type, :response_code, :to => "@_response"
- def dispatch(action, request)
+ module ClassMethods
+ def build_with_env(env = {}) #:nodoc:
+ new.tap { |c| c.set_request! ActionDispatch::Request.new(env) }
+ end
+ end
+
+ def set_request!(request) #:nodoc:
+ super
set_response!(request)
- super(action, request)
end
def response_body=(body)
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index bc94536c8c..45d3962494 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -86,8 +86,7 @@ module ActionController
# end
# end
# To use renderers and their mime types in more concise ways, see
- # <tt>ActionController::MimeResponds::ClassMethods.respond_to</tt> and
- # <tt>ActionController::MimeResponds#respond_with</tt>
+ # <tt>ActionController::MimeResponds::ClassMethods.respond_to</tt>
def self.add(key, &block)
define_method(_render_with_renderer_method_name(key), &block)
RENDERERS << key.to_sym
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index 7bbff0450a..2d15c39d88 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -4,6 +4,17 @@ module ActionController
RENDER_FORMATS_IN_PRIORITY = [:body, :text, :plain, :html]
+ module ClassMethods
+ # Documentation at ActionController::Renderer#render
+ delegate :render, to: :renderer
+
+ # Returns a renderer class (inherited from ActionController::Renderer)
+ # for the controller.
+ def renderer
+ @renderer ||= Renderer.for(self)
+ end
+ end
+
# Before processing, set the request formats in current controller formats.
def process_action(*) #:nodoc:
self.formats = request.formats.map(&:ref).compact
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index fd20682f8f..367b736035 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -29,14 +29,7 @@ module ActionController #:nodoc:
# you're building an API you'll need something like:
#
# class ApplicationController < ActionController::Base
- # protect_from_forgery
- # skip_before_action :verify_authenticity_token, if: :json_request?
- #
- # protected
- #
- # def json_request?
- # request.format.json?
- # end
+ # protect_from_forgery unless: -> { request.format.json? }
# end
#
# CSRF protection is turned on with the <tt>protect_from_forgery</tt> method,
@@ -87,12 +80,18 @@ module ActionController #:nodoc:
# class FooController < ApplicationController
# protect_from_forgery except: :index
#
- # You can disable CSRF protection on controller by skipping the verification before_action:
+ # You can disable forgery protection on controller by skipping the verification before_action:
# skip_before_action :verify_authenticity_token
#
# Valid Options:
#
- # * <tt>:only/:except</tt> - Passed to the <tt>before_action</tt> call. Set which actions are verified.
+ # * <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>: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
+ # 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.
#
# Valid unverified request handling methods are:
@@ -100,9 +99,11 @@ module ActionController #:nodoc:
# * <tt>:reset_session</tt> - Resets the session.
# * <tt>:null_session</tt> - Provides an empty session during request but doesn't reset it completely. Used as default if <tt>:with</tt> option is not specified.
def protect_from_forgery(options = {})
+ options = options.reverse_merge(prepend: true)
+
self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session)
self.request_forgery_protection_token ||= :authenticity_token
- prepend_before_action :verify_authenticity_token, options
+ before_action :verify_authenticity_token, options
append_after_action :verify_same_origin_request
end
@@ -209,6 +210,7 @@ module ActionController #:nodoc:
forgery_protection_strategy.new(self).handle_unverified_request
end
+ #:nodoc:
CROSS_ORIGIN_JAVASCRIPT_WARNING = "Security warning: an embedded " \
"<script> tag on another site requested protected JavaScript. " \
"If you know what you're doing, go ahead and disable forgery " \
@@ -273,7 +275,9 @@ module ActionController #:nodoc:
# session token. Essentially the inverse of
# +masked_authenticity_token+.
def valid_authenticity_token?(session, encoded_masked_token)
- return false if encoded_masked_token.nil? || encoded_masked_token.empty?
+ if encoded_masked_token.nil? || encoded_masked_token.empty? || !encoded_masked_token.is_a?(String)
+ return false
+ end
begin
masked_token = Base64.strict_decode64(encoded_masked_token)
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index a5ee1e2159..c98e937423 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -1,7 +1,6 @@
require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/string/filters'
-require 'active_support/deprecation'
require 'active_support/rescuable'
require 'action_dispatch/http/upload'
require 'stringio'
@@ -92,7 +91,11 @@ module ActionController
# params.permit(:c)
# # => ActionController::UnpermittedParameters: found unpermitted keys: a, b
#
- # <tt>ActionController::Parameters</tt> is inherited from
+ # Please note that these options *are not thread-safe*. In a multi-threaded
+ # environment they should only be set once at boot-time and never mutated at
+ # runtime.
+ #
+ # <tt>ActionController::Parameters</tt> inherits from
# <tt>ActiveSupport::HashWithIndifferentAccess</tt>, this means
# that you can fetch values using either <tt>:key</tt> or <tt>"key"</tt>.
#
@@ -114,7 +117,7 @@ module ActionController
self.always_permitted_parameters = %w( controller action )
def self.const_missing(const_name)
- super unless const_name == :NEVER_UNPERMITTED_PARAMS
+ return super unless const_name == :NEVER_UNPERMITTED_PARAMS
ActiveSupport::Deprecation.warn(<<-MSG.squish)
`ActionController::Parameters::NEVER_UNPERMITTED_PARAMS` has been deprecated.
Use `ActionController::Parameters.always_permitted_parameters` instead.
@@ -163,6 +166,12 @@ module ActionController
end
end
+ # Returns an unsafe, unfiltered +Hash+ representation of this parameter.
+ def to_unsafe_h
+ to_hash
+ 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)
@@ -259,7 +268,7 @@ module ActionController
#
# params.permit(:name)
#
- # +:name+ passes it is a key of +params+ whose associated value is of type
+ # +:name+ passes if it is a key of +params+ whose associated value is of type
# +String+, +Symbol+, +NilClass+, +Numeric+, +TrueClass+, +FalseClass+,
# +Date+, +Time+, +DateTime+, +StringIO+, +IO+,
# +ActionDispatch::Http::UploadedFile+ or +Rack::Test::UploadedFile+.
diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb
index dd8da4b5dc..d01927b7cb 100644
--- a/actionpack/lib/action_controller/metal/testing.rb
+++ b/actionpack/lib/action_controller/metal/testing.rb
@@ -24,7 +24,7 @@ module ActionController
module ClassMethods
def before_filters
- _process_action_callbacks.find_all{|x| x.kind == :before}.map{|x| x.name}
+ _process_action_callbacks.find_all{|x| x.kind == :before}.map(&:name)
end
end
end
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index 0f2fa5fb08..5a0e5c62e4 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -4,7 +4,10 @@ module ActionController
#
# In addition to <tt>AbstractController::UrlFor</tt>, this module accesses the HTTP layer to define
# url options like the +host+. In order to do so, this module requires the host class
- # to implement +env+ and +request+, which need to be a Rack-compatible.
+ # to implement +env+ which needs to be Rack-compatible and +request+
+ # which is either an instance of +ActionDispatch::Request+ or an object
+ # that responds to the +host+, +optional_port+, +protocol+ and
+ # +symbolized_path_parameter+ methods.
#
# class RootUrl
# include ActionController::UrlFor
@@ -30,9 +33,9 @@ module ActionController
:_recall => request.path_parameters
}.merge!(super).freeze
- if (same_origin = _routes.equal?(env["action_dispatch.routes".freeze])) ||
- (script_name = env["ROUTES_#{_routes.object_id}_SCRIPT_NAME"]) ||
- (original_script_name = env['ORIGINAL_SCRIPT_NAME'.freeze])
+ if (same_origin = _routes.equal?(request.routes)) ||
+ (script_name = request.engine_script_name(_routes)) ||
+ (original_script_name = request.original_script_name)
options = @_url_options.dup
if original_script_name
diff --git a/actionpack/lib/action_controller/model_naming.rb b/actionpack/lib/action_controller/model_naming.rb
deleted file mode 100644
index 2b33f67263..0000000000
--- a/actionpack/lib/action_controller/model_naming.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-module ActionController
- module ModelNaming
- # Converts the given object to an ActiveModel compliant one.
- def convert_to_model(object)
- object.respond_to?(:to_model) ? object.to_model : object
- end
-
- def model_name_from_record_or_class(record_or_class)
- convert_to_model(record_or_class).model_name
- end
- end
-end
diff --git a/actionpack/lib/action_controller/renderer.rb b/actionpack/lib/action_controller/renderer.rb
new file mode 100644
index 0000000000..e8b29c5b5e
--- /dev/null
+++ b/actionpack/lib/action_controller/renderer.rb
@@ -0,0 +1,100 @@
+require 'active_support/core_ext/hash/keys'
+
+module ActionController
+ # ActionController::Renderer allows to render arbitrary templates
+ # without requirement of being in controller actions.
+ #
+ # You get a concrete renderer class by invoking ActionController::Base#renderer.
+ # For example,
+ #
+ # ApplicationController.renderer
+ #
+ # It allows you to call method #render directly.
+ #
+ # ApplicationController.renderer.render template: '...'
+ #
+ # You can use a shortcut on controller to replace previous example with:
+ #
+ # ApplicationController.render template: '...'
+ #
+ # #render method allows you to use any options as when rendering in controller.
+ # For example,
+ #
+ # FooController.render :action, locals: { ... }, assigns: { ... }
+ #
+ # The template will be rendered in a Rack environment which is accessible through
+ # ActionController::Renderer#env. You can set it up in two ways:
+ #
+ # * by changing renderer defaults, like
+ #
+ # ApplicationController.renderer.defaults # => hash with default Rack environment
+ #
+ # * by initializing an instance of renderer by passing it a custom environment.
+ #
+ # ApplicationController.renderer.new(method: 'post', https: true)
+ #
+ class Renderer
+ class_attribute :controller, :defaults
+ # Rack environment to render templates in.
+ attr_reader :env
+
+ class << self
+ delegate :render, to: :new
+
+ # Create a new renderer class for a specific controller class.
+ def for(controller)
+ Class.new self do
+ self.controller = controller
+ self.defaults = {
+ http_host: 'example.org',
+ https: false,
+ method: 'get',
+ script_name: '',
+ 'rack.input' => ''
+ }
+ end
+ end
+ end
+
+ # Accepts a custom Rack environment to render templates in.
+ # It will be merged with ActionController::Renderer.defaults
+ def initialize(env = {})
+ @env = normalize_keys(defaults).merge normalize_keys(env)
+ @env['action_dispatch.routes'] = controller._routes
+ end
+
+ # Render templates with any options from ActionController::Base#render_to_string.
+ def render(*args)
+ raise 'missing controller' unless controller?
+
+ instance = controller.build_with_env(env)
+ instance.render_to_string(*args)
+ end
+
+ private
+ def normalize_keys(env)
+ http_header_format(env).tap do |new_env|
+ handle_method_key! new_env
+ handle_https_key! new_env
+ end
+ end
+
+ def http_header_format(env)
+ env.transform_keys do |key|
+ key.is_a?(Symbol) ? key.to_s.upcase : key
+ end
+ end
+
+ def handle_method_key!(env)
+ if method = env.delete('METHOD')
+ env['REQUEST_METHOD'] = method.upcase
+ end
+ end
+
+ def handle_https_key!(env)
+ if env.has_key? 'HTTPS'
+ env['HTTPS'] = env['HTTPS'] ? 'on' : 'off'
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 41d33d4396..6ffd7a7d2b 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -66,7 +66,10 @@ module ActionController
def reset_template_assertion
RENDER_TEMPLATE_INSTANCE_VARIABLES.each do |instance_variable|
- instance_variable_get("@_#{instance_variable}").clear
+ ivar_name = "@_#{instance_variable}"
+ if instance_variable_defined?(ivar_name)
+ instance_variable_get(ivar_name).clear
+ end
end
end
@@ -144,6 +147,8 @@ module ActionController
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
@@ -196,7 +201,7 @@ module ActionController
super
self.session = TestSession.new
- self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => SecureRandom.hex(16))
+ self.session_options = TestSession::DEFAULT_OPTIONS
end
def assign_parameters(routes, controller_path, action, parameters = {})
@@ -489,55 +494,66 @@ module ActionController
# Simulate a GET request with the given parameters.
#
# - +action+: The controller action to call.
- # - +parameters+: The HTTP parameters that you want to pass. This may
- # be +nil+, a hash, or a string that is appropriately encoded
+ # - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+.
+ # - +body+: The request body with a string that is appropriately encoded
# (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
# - +session+: A hash of parameters to store in the session. This may be +nil+.
# - +flash+: A hash of parameters to store in the flash. This may be +nil+.
#
# You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with
# +post+, +patch+, +put+, +delete+, and +head+.
+ # Example sending parameters, session and setting a flash message:
+ #
+ # get :show,
+ # params: { id: 7 },
+ # session: { user_id: 1 },
+ # flash: { notice: 'This is flash message' }
#
# Note that the request method is not verified. The different methods are
# available to make the tests more expressive.
def get(action, *args)
- process(action, "GET", *args)
+ process_with_kwargs("GET", action, *args)
end
# Simulate a POST request with the given parameters and set/volley the response.
# See +get+ for more details.
def post(action, *args)
- process(action, "POST", *args)
+ process_with_kwargs("POST", action, *args)
end
# Simulate a PATCH request with the given parameters and set/volley the response.
# See +get+ for more details.
def patch(action, *args)
- process(action, "PATCH", *args)
+ process_with_kwargs("PATCH", action, *args)
end
# Simulate a PUT request with the given parameters and set/volley the response.
# See +get+ for more details.
def put(action, *args)
- process(action, "PUT", *args)
+ process_with_kwargs("PUT", action, *args)
end
# Simulate a DELETE request with the given parameters and set/volley the response.
# See +get+ for more details.
def delete(action, *args)
- process(action, "DELETE", *args)
+ process_with_kwargs("DELETE", action, *args)
end
# Simulate a HEAD request with the given parameters and set/volley the response.
# See +get+ for more details.
def head(action, *args)
- process(action, "HEAD", *args)
+ process_with_kwargs("HEAD", action, *args)
end
- def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
+ def xml_http_request(*args)
+ ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc)
+ xhr and xml_http_request methods are deprecated in favor of
+ `get :index, xhr: true` and `post :create, xhr: true`
+ MSG
+
@request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
- @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
- __send__(request_method, action, parameters, session, flash).tap do
+ @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
+ __send__(*args).tap do
@request.env.delete 'HTTP_X_REQUESTED_WITH'
@request.env.delete 'HTTP_ACCEPT'
end
@@ -561,41 +577,69 @@ module ActionController
# parameters and set/volley the response.
#
# - +action+: The controller action to call.
- # - +http_method+: Request method used to send the http request. Possible values
- # are +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, +HEAD+. Defaults to +GET+.
- # - +parameters+: The HTTP parameters. This may be +nil+, a hash, or a
- # string that is appropriately encoded (+application/x-www-form-urlencoded+
- # or +multipart/form-data+).
+ # - +method+: Request method used to send the HTTP request. Possible values
+ # are +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, +HEAD+. Defaults to +GET+. Can be a symbol.
+ # - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+.
+ # - +body+: The request body with a string that is appropriately encoded
+ # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
# - +session+: A hash of parameters to store in the session. This may be +nil+.
# - +flash+: A hash of parameters to store in the flash. This may be +nil+.
+ # - +format+: Request format. Defaults to +nil+. Can be string or symbol.
#
# Example calling +create+ action and sending two params:
#
- # process :create, 'POST', user: { name: 'Gaurish Sharma', email: 'user@example.com' }
- #
- # Example sending parameters, +nil+ session and setting a flash message:
- #
- # process :view, 'GET', { id: 7 }, nil, { notice: 'This is flash message' }
+ # process :create,
+ # method: 'POST',
+ # params: {
+ # user: { name: 'Gaurish Sharma', email: 'user@example.com' }
+ # },
+ # session: { user_id: 1 },
+ # flash: { notice: 'This is flash message' }
#
# To simulate +GET+, +POST+, +PATCH+, +PUT+, +DELETE+ and +HEAD+ requests
# prefer using #get, #post, #patch, #put, #delete and #head methods
# respectively which will make tests more expressive.
#
# Note that the request method is not verified.
- def process(action, http_method = 'GET', *args)
+ def process(action, *args)
check_required_ivars
- if args.first.is_a?(String) && http_method != 'HEAD'
- @request.env['RAW_POST_DATA'] = args.shift
+ if kwarg_request?(*args)
+ parameters, session, body, flash, http_method, format, xhr = args[0].values_at(:params, :session, :body, :flash, :method, :format, :xhr)
+ else
+ http_method, parameters, session, flash = args
+ format = nil
+
+ if parameters.is_a?(String) && http_method != 'HEAD'
+ body = parameters
+ parameters = nil
+ end
+
+ if parameters.present? || session.present? || flash.present?
+ non_kwarg_request_warning
+ end
+ end
+
+ if body.present?
+ @request.env['RAW_POST_DATA'] = body
+ end
+
+ if http_method.present?
+ http_method = http_method.to_s.upcase
+ else
+ http_method = "GET"
end
- parameters, session, flash = args
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
+
@html_document = nil
unless @controller.respond_to?(:recycle!)
@@ -615,7 +659,14 @@ module ActionController
@request.assign_parameters(@routes, controller_class_name, action.to_s, parameters)
@request.session.update(session) if session
- @request.flash.update(flash || {})
+
+ is_request_flash_enabled = @request.respond_to?(:flash)
+ @request.flash.update(flash || {}) if is_request_flash_enabled
+
+ if xhr
+ @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
+ @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
+ end
@controller.request = @request
@controller.response = @response
@@ -636,8 +687,16 @@ module ActionController
@assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {}
- if flash_value = @request.flash.to_session_value
+ flash_value = is_request_flash_enabled ? @request.flash.to_session_value : nil
+ if flash_value
@request.session['flash'] = flash_value
+ else
+ @request.session.delete('flash')
+ end
+
+ if xhr
+ @request.env.delete 'HTTP_X_REQUESTED_WITH'
+ @request.env.delete 'HTTP_ACCEPT'
end
@response
@@ -688,8 +747,40 @@ module ActionController
private
+ def process_with_kwargs(http_method, action, *args)
+ if kwarg_request?(*args)
+ args.first.merge!(method: http_method)
+ process(action, *args)
+ else
+ non_kwarg_request_warning if args.present?
+
+ args = args.unshift(http_method)
+ process(action, *args)
+ end
+ end
+
+ REQUEST_KWARGS = %i(params session flash method body xhr)
+ def kwarg_request?(*args)
+ args[0].respond_to?(:keys) && (
+ (args[0].key?(:format) && args[0].keys.size == 1) ||
+ args[0].keys.any? { |k| REQUEST_KWARGS.include?(k) }
+ )
+ end
+
+ def non_kwarg_request_warning
+ ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc)
+ ActionController::TestCase HTTP request methods will accept only
+ keyword arguments in future Rails versions.
+
+ Examples:
+
+ get :show, params: { id: 1 }, session: { user_id: 1 }
+ process :update, method: :post, params: { id: 1 }
+ MSG
+ end
+
def document_root_element
- html_document
+ html_document.root
end
def check_required_ivars
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 11b5e6be33..dcd3ee0644 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2014 David Heinemeier Hansson
+# Copyright (c) 2004-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index 63a3cbc90b..747d295261 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -69,17 +69,17 @@ module ActionDispatch
end
def date
- if date_header = headers['Date']
+ if date_header = headers[DATE]
Time.httpdate(date_header)
end
end
def date?
- headers.include?('Date')
+ headers.include?(DATE)
end
def date=(utc_time)
- headers['Date'] = utc_time.httpdate
+ headers[DATE] = utc_time.httpdate
end
def etag=(etag)
@@ -89,6 +89,7 @@ module ActionDispatch
private
+ DATE = 'Date'.freeze
LAST_MODIFIED = "Last-Modified".freeze
ETAG = "ETag".freeze
CACHE_CONTROL = "Cache-Control".freeze
diff --git a/actionpack/lib/action_dispatch/http/filter_redirect.rb b/actionpack/lib/action_dispatch/http/filter_redirect.rb
index cd603649c3..bf79963351 100644
--- a/actionpack/lib/action_dispatch/http/filter_redirect.rb
+++ b/actionpack/lib/action_dispatch/http/filter_redirect.rb
@@ -4,7 +4,7 @@ module ActionDispatch
FILTERED = '[FILTERED]'.freeze # :nodoc:
- def filtered_location
+ def filtered_location # :nodoc:
filters = location_filter
if !filters.empty? && location_filter_match?(filters)
FILTERED
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index 9c8f65deac..ff336b7354 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -10,8 +10,6 @@ module ActionDispatch
self.ignore_accept_header = false
end
- attr_reader :variant
-
# The MIME type of the HTTP request, such as Mime::XML.
#
# For backward compatibility, the post \format is extracted from the
@@ -72,20 +70,25 @@ module ActionDispatch
end
end
end
+
# Sets the \variant for template.
def variant=(variant)
- if variant.is_a?(Symbol)
- @variant = [variant]
- elsif variant.is_a?(Array) && variant.any? && variant.all?{ |v| v.is_a?(Symbol) }
- @variant = variant
+ variant = Array(variant)
+
+ if variant.all? { |v| v.is_a?(Symbol) }
+ @variant = ActiveSupport::ArrayInquirer.new(variant)
else
- raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols, not a #{variant.class}. " \
+ raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols. " \
"For security reasons, never directly set the variant to a user-provided value, " \
"like params[:variant].to_sym. Check user-provided value against a whitelist first, " \
"then set the variant: request.variant = :tablet if params[:variant] == 'tablet'"
end
end
+ def variant
+ @variant ||= ActiveSupport::ArrayInquirer.new
+ end
+
# Sets the \format by string extension, which can be used to force custom formats
# that are not controlled by the extension.
#
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index b9d5009683..7e585aa244 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -6,7 +6,7 @@ require 'active_support/core_ext/string/starts_ends_with'
module Mime
class Mimes < Array
def symbols
- @symbols ||= map { |m| m.to_sym }
+ @symbols ||= map(&:to_sym)
end
%w(<< concat shift unshift push pop []= clear compact! collect!
@@ -45,7 +45,7 @@ module Mime
#
# respond_to do |format|
# format.html
- # format.ics { render text: @post.to_ics, mime_type: Mime::Type["text/calendar"] }
+ # format.ics { render text: @post.to_ics, mime_type: Mime::Type.lookup("text/calendar") }
# format.xml { render xml: @post }
# end
# end
diff --git a/actionpack/lib/action_dispatch/http/parameter_filter.rb b/actionpack/lib/action_dispatch/http/parameter_filter.rb
index b655a54865..df4b073a17 100644
--- a/actionpack/lib/action_dispatch/http/parameter_filter.rb
+++ b/actionpack/lib/action_dispatch/http/parameter_filter.rb
@@ -56,7 +56,7 @@ module ActionDispatch
elsif value.is_a?(Array)
value = value.map { |v| v.is_a?(Hash) ? call(v) : v }
elsif blocks.any?
- key = key.dup
+ key = key.dup if key.duplicable?
value = value.dup if value.duplicable?
blocks.each { |b| b.call(key, value) }
end
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index a5cd26a3c1..c2f05ecc86 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -1,6 +1,5 @@
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/hash/indifferent_access'
-require 'active_support/deprecation'
module ActionDispatch
module Http
@@ -25,13 +24,6 @@ module ActionDispatch
@env[PARAMETERS_KEY] = parameters
end
- def symbolized_path_parameters
- ActiveSupport::Deprecation.warn(
- '`symbolized_path_parameters` is deprecated. Please use `path_parameters`.'
- )
- path_parameters
- end
-
# Returns a hash with the \parameters used to form the \path of the request.
# Returned hash keys are strings:
#
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 2a7bb374a5..a1f84e5ace 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -50,7 +50,7 @@ module ActionDispatch
@original_fullpath = nil
@fullpath = nil
@ip = nil
- @uuid = nil
+ @request_id = nil
end
def check_path_parameters!
@@ -105,6 +105,18 @@ module ActionDispatch
@request_method ||= check_method(env["REQUEST_METHOD"])
end
+ def routes # :nodoc:
+ env["action_dispatch.routes".freeze]
+ end
+
+ def original_script_name # :nodoc:
+ env['ORIGINAL_SCRIPT_NAME'.freeze]
+ end
+
+ def engine_script_name(_routes) # :nodoc:
+ env[_routes.env_key]
+ end
+
def request_method=(request_method) #:nodoc:
if check_method(request_method)
@request_method = env["REQUEST_METHOD"] = request_method
@@ -237,10 +249,12 @@ 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 uuid
- @uuid ||= env["action_dispatch.request_id"]
+ def request_id
+ @request_id ||= env["action_dispatch.request_id"]
end
+ alias_method :uuid, :request_id
+
# Returns the lowercase name of the HTTP server software.
def server_software
(@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
@@ -325,17 +339,8 @@ module ActionDispatch
LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip
end
- # Extracted into ActionDispatch::Request::Utils.deep_munge, but kept here for backwards compatibility.
- def deep_munge(hash)
- ActiveSupport::Deprecation.warn(
- 'This method has been extracted into `ActionDispatch::Request::Utils.deep_munge`. Please start using that instead.'
- )
-
- Utils.deep_munge(hash)
- end
-
protected
- def parse_query(qs)
+ def parse_query(*)
Utils.deep_munge(super)
end
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 33de2f8b5f..a895d1ab18 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -1,6 +1,4 @@
require 'active_support/core_ext/module/attribute_accessors'
-require 'active_support/core_ext/string/filters'
-require 'active_support/deprecation'
require 'action_dispatch/http/filter_redirect'
require 'monitor'
@@ -115,10 +113,10 @@ module ActionDispatch # :nodoc:
# The underlying body, as a streamable object.
attr_reader :stream
- def initialize(status = 200, header = {}, body = [])
+ def initialize(status = 200, header = {}, body = [], default_headers: self.class.default_headers)
super()
- header = merge_default_headers(header, self.class.default_headers)
+ header = merge_default_headers(header, default_headers)
self.body, self.header, self.status = body, header, status
@@ -284,20 +282,6 @@ module ActionDispatch # :nodoc:
end
alias prepare! to_a
- # Be super clear that a response object is not an Array. Defining this
- # would make implicit splatting work, but it also makes adding responses
- # as arrays work, and "flattening" responses, cascading to the rack body!
- # Not sensible behavior.
- def to_ary
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- `ActionDispatch::Response#to_ary` no longer performs implicit conversion
- to an array. Please use `response.to_a` instead, or a splat like `status,
- headers, body = *response`.
- MSG
-
- to_a
- end
-
# Returns the response cookies, converted to a Hash of (name => value) pairs
#
# assert_equal 'AuthorOfNewPage', r.cookies['author']
@@ -324,9 +308,7 @@ module ActionDispatch # :nodoc:
end
def merge_default_headers(original, default)
- return original unless default.respond_to?(:merge)
-
- default.merge(original)
+ default.respond_to?(:merge) ? default.merge(original) : original
end
def build_buffer(response, body)
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 6b8dcaf497..f5b709ccd6 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -12,10 +12,22 @@ module ActionDispatch
self.tld_length = 1
class << self
+ # Returns the domain part of a host given the domain level.
+ #
+ # # Top-level domain example
+ # extract_domain('www.example.com', 1) # => "example.com"
+ # # Second-level domain example
+ # extract_domain('dev.www.example.co.uk', 2) # => "example.co.uk"
def extract_domain(host, tld_length)
extract_domain_from(host, tld_length) if named_host?(host)
end
+ # Returns the subdomains of a host as an Array given the domain level.
+ #
+ # # Top-level domain example
+ # extract_subdomains('www.example.com', 1) # => ["www"]
+ # # Second-level domain example
+ # extract_subdomains('dev.www.example.co.uk', 2) # => ["dev", "www"]
def extract_subdomains(host, tld_length)
if named_host?(host)
extract_subdomains_from(host, tld_length)
@@ -24,6 +36,12 @@ module ActionDispatch
end
end
+ # Returns the subdomains of a host as a String given the domain level.
+ #
+ # # Top-level domain example
+ # extract_subdomain('www.example.com', 1) # => "www"
+ # # Second-level domain example
+ # extract_subdomain('dev.www.example.co.uk', 2) # => "dev.www"
def extract_subdomain(host, tld_length)
extract_subdomains(host, tld_length).join('.')
end
@@ -49,7 +67,7 @@ module ActionDispatch
end
def path_for(options)
- path = options[:script_name].to_s.chomp("/")
+ path = options[:script_name].to_s.chomp("/".freeze)
path << options[:path] if options.key?(:path)
add_trailing_slash(path) if options[:trailing_slash]
@@ -68,7 +86,9 @@ module ActionDispatch
end
def add_anchor(path, anchor)
- path << "##{Journey::Router::Utils.escape_fragment(anchor.to_param.to_s)}"
+ if anchor
+ path << "##{Journey::Router::Utils.escape_fragment(anchor.to_param)}"
+ end
end
def extract_domain_from(host, tld_length)
@@ -171,18 +191,45 @@ module ActionDispatch
end
# Returns the complete URL used for this request.
+ #
+ # class Request < Rack::Request
+ # include ActionDispatch::Http::URL
+ # end
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com'
+ # req.url # => "http://example.com"
def url
protocol + host_with_port + fullpath
end
# Returns 'https://' if this is an SSL request and 'http://' otherwise.
+ #
+ # class Request < Rack::Request
+ # include ActionDispatch::Http::URL
+ # end
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com'
+ # req.protocol # => "http://"
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com', 'HTTPS' => 'on'
+ # req.protocol # => "https://"
def protocol
@protocol ||= ssl? ? 'https://' : 'http://'
end
# Returns the \host for this request, such as "example.com".
+ #
+ # class Request < Rack::Request
+ # include ActionDispatch::Http::URL
+ # end
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com'
+ # req.raw_host_with_port # => "example.com"
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com:8080'
+ # req.raw_host_with_port # => "example.com:8080"
def raw_host_with_port
- if forwarded = env["HTTP_X_FORWARDED_HOST"]
+ if forwarded = env["HTTP_X_FORWARDED_HOST"].presence
forwarded.split(/,\s?/).last
else
env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
@@ -190,17 +237,44 @@ module ActionDispatch
end
# Returns the host for this request, such as example.com.
+ #
+ # class Request < Rack::Request
+ # include ActionDispatch::Http::URL
+ # end
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com:8080'
+ # req.host # => "example.com"
def host
raw_host_with_port.sub(/:\d+$/, '')
end
# Returns a \host:\port string for this request, such as "example.com" or
# "example.com:8080".
+ #
+ # class Request < Rack::Request
+ # include ActionDispatch::Http::URL
+ # end
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com:80'
+ # req.host_with_port # => "example.com"
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com:8080'
+ # req.host_with_port # => "example.com:8080"
def host_with_port
"#{host}#{port_string}"
end
# Returns the port number of this request as an integer.
+ #
+ # class Request < Rack::Request
+ # include ActionDispatch::Http::URL
+ # end
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com'
+ # req.port # => 80
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com:8080'
+ # req.port # => 8080
def port
@port ||= begin
if raw_host_with_port =~ /:(\d+)$/
@@ -212,6 +286,13 @@ module ActionDispatch
end
# Returns the standard \port number for this request's protocol.
+ #
+ # class Request < Rack::Request
+ # include ActionDispatch::Http::URL
+ # end
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com:8080'
+ # req.standard_port # => 80
def standard_port
case protocol
when 'https://' then 443
@@ -220,18 +301,48 @@ module ActionDispatch
end
# Returns whether this request is using the standard port
+ #
+ # class Request < Rack::Request
+ # include ActionDispatch::Http::URL
+ # end
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com:80'
+ # req.standard_port? # => true
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com:8080'
+ # req.standard_port? # => false
def standard_port?
port == standard_port
end
# Returns a number \port suffix like 8080 if the \port number of this request
# is not the default HTTP \port 80 or HTTPS \port 443.
+ #
+ # class Request < Rack::Request
+ # include ActionDispatch::Http::URL
+ # end
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com:80'
+ # req.optional_port # => nil
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com:8080'
+ # req.optional_port # => 8080
def optional_port
standard_port? ? nil : port
end
# Returns a string \port suffix, including colon, like ":8080" if the \port
# number of this request is not the default HTTP \port 80 or HTTPS \port 443.
+ #
+ # class Request < Rack::Request
+ # include ActionDispatch::Http::URL
+ # end
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com:80'
+ # req.port_string # => ""
+ #
+ # req = Request.new 'HTTP_HOST' => 'example.com:8080'
+ # req.port_string # => ":8080"
def port_string
standard_port? ? '' : ":#{port}"
end
diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb
index 992c1a9efe..c0566c6fc9 100644
--- a/actionpack/lib/action_dispatch/journey/formatter.rb
+++ b/actionpack/lib/action_dispatch/journey/formatter.rb
@@ -39,7 +39,7 @@ module ActionDispatch
return [route.format(parameterized_parts), params]
end
- message = "No route matches #{Hash[constraints.sort].inspect}"
+ message = "No route matches #{Hash[constraints.sort_by{|k,v| k.to_s}].inspect}"
message << " missing required keys: #{missing_keys.sort.inspect}" unless missing_keys.empty?
raise ActionController::UrlGenerationError, message
diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
index 1b914f0637..d7ce6042c2 100644
--- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
+++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
@@ -109,7 +109,7 @@ module ActionDispatch
svg = to_svg
javascripts = [states, fsm_js]
- # Annoying hack for 1.9 warnings
+ # Annoying hack warnings
fun_routes = fun_routes
stylesheets = stylesheets
svg = svg
diff --git a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb
index 66e414213a..0ccab21801 100644
--- a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb
+++ b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb
@@ -45,51 +45,6 @@ module ActionDispatch
(@table.keys + @table.values.flat_map(&:keys)).uniq
end
- # Returns a generalized transition graph with reduced states. The states
- # are reduced like a DFA, but the table must be simulated like an NFA.
- #
- # Edges of the GTG are regular expressions.
- def generalized_table
- gt = GTG::TransitionTable.new
- marked = {}
- state_id = Hash.new { |h,k| h[k] = h.length }
- alphabet = self.alphabet
-
- stack = [eclosure(0)]
-
- until stack.empty?
- state = stack.pop
- next if marked[state] || state.empty?
-
- marked[state] = true
-
- alphabet.each do |alpha|
- next_state = eclosure(following_states(state, alpha))
- next if next_state.empty?
-
- gt[state_id[state], state_id[next_state]] = alpha
- stack << next_state
- end
- end
-
- final_groups = state_id.keys.find_all { |s|
- s.sort.last == accepting
- }
-
- final_groups.each do |states|
- id = state_id[states]
-
- gt.add_accepting(id)
- save = states.find { |s|
- @memos.key?(s) && eclosure(s).sort.last == accepting
- }
-
- gt.add_memo(id, memo(save))
- end
-
- gt
- end
-
# Returns set of NFA states to which there is a transition on ast symbol
# +a+ from some state +s+ in +t+.
def following_states(t, a)
@@ -107,7 +62,7 @@ module ActionDispatch
end
def alphabet
- inverted.values.flat_map(&:keys).compact.uniq.sort_by { |x| x.to_s }
+ inverted.values.flat_map(&:keys).compact.uniq.sort_by(&:to_s)
end
# Returns a set of NFA states reachable from some NFA state +s+ in set
diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb
index 3af940a02f..64b48ca45f 100644
--- a/actionpack/lib/action_dispatch/journey/path/pattern.rb
+++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb
@@ -42,7 +42,7 @@ module ActionDispatch
end
def names
- @names ||= spec.grep(Nodes::Symbol).map { |n| n.name }
+ @names ||= spec.grep(Nodes::Symbol).map(&:name)
end
def required_names
@@ -52,7 +52,7 @@ module ActionDispatch
def optional_names
@optional_names ||= spec.grep(Nodes::Group).flat_map { |group|
group.grep(Nodes::Symbol)
- }.map { |n| n.name }.uniq
+ }.map(&:name).uniq
end
class RegexpOffsets < Journey::Visitors::Visitor # :nodoc:
@@ -122,6 +122,11 @@ module ActionDispatch
re = @matchers[node.left.to_sym] || '.+'
"(#{re})"
end
+
+ def visit_OR(node)
+ children = node.children.map { |n| visit n }
+ "(?:#{children.join(?|)})"
+ end
end
class UnanchoredRegexp < AnchoredRegexp # :nodoc:
diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb
index 9f0a3af902..4d5c18984a 100644
--- a/actionpack/lib/action_dispatch/journey/route.rb
+++ b/actionpack/lib/action_dispatch/journey/route.rb
@@ -60,7 +60,7 @@ module ActionDispatch
end
def parts
- @parts ||= segments.map { |n| n.to_sym }
+ @parts ||= segments.map(&:to_sym)
end
alias :segment_keys :parts
@@ -68,12 +68,8 @@ module ActionDispatch
@path_formatter.evaluate path_options
end
- def optional_parts
- path.optional_names.map { |n| n.to_sym }
- end
-
def required_parts
- @required_parts ||= path.required_names.map { |n| n.to_sym }
+ @required_parts ||= path.required_names.map(&:to_sym)
end
def required_default?(key)
diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb
index 9131b65380..cc4bd6105d 100644
--- a/actionpack/lib/action_dispatch/journey/router.rb
+++ b/actionpack/lib/action_dispatch/journey/router.rb
@@ -68,8 +68,8 @@ module ActionDispatch
def visualizer
tt = GTG::Builder.new(ast).transition_table
- groups = partitioned_routes.first.map(&:ast).group_by { |a| a.to_s }
- asts = groups.values.map { |v| v.first }
+ groups = partitioned_routes.first.map(&:ast).group_by(&:to_s)
+ asts = groups.values.map(&:first)
tt.visualizer(asts)
end
@@ -88,7 +88,7 @@ module ActionDispatch
end
def custom_routes
- partitioned_routes.last
+ routes.custom_routes
end
def filter_routes(path)
diff --git a/actionpack/lib/action_dispatch/journey/routes.rb b/actionpack/lib/action_dispatch/journey/routes.rb
index 80e3818ccd..a6d1980db2 100644
--- a/actionpack/lib/action_dispatch/journey/routes.rb
+++ b/actionpack/lib/action_dispatch/journey/routes.rb
@@ -5,13 +5,14 @@ module ActionDispatch
class Routes # :nodoc:
include Enumerable
- attr_reader :routes, :named_routes
+ attr_reader :routes, :named_routes, :custom_routes, :anchored_routes
def initialize
@routes = []
@named_routes = {}
@ast = nil
- @partitioned_routes = nil
+ @anchored_routes = []
+ @custom_routes = []
@simulator = nil
end
@@ -30,18 +31,22 @@ module ActionDispatch
def clear
routes.clear
+ anchored_routes.clear
+ custom_routes.clear
named_routes.clear
end
- def partitioned_routes
- @partitioned_routes ||= routes.partition do |r|
- r.path.anchored && r.ast.grep(Nodes::Symbol).all?(&:default_regexp?)
+ def partition_route(route)
+ if route.path.anchored && route.ast.grep(Nodes::Symbol).all?(&:default_regexp?)
+ anchored_routes << route
+ else
+ custom_routes << route
end
end
def ast
@ast ||= begin
- asts = partitioned_routes.first.map(&:ast)
+ asts = anchored_routes.map(&:ast)
Nodes::Or.new(asts) unless asts.empty?
end
end
@@ -60,6 +65,7 @@ module ActionDispatch
route.precedence = routes.length
routes << route
named_routes[name] = route if name && !named_routes[name]
+ partition_route(route)
clear_cache!
route
end
@@ -68,7 +74,6 @@ module ActionDispatch
def clear_cache!
@ast = nil
- @partitioned_routes = nil
@simulator = nil
end
end
diff --git a/actionpack/lib/action_dispatch/journey/visualizer/fsm.css b/actionpack/lib/action_dispatch/journey/visualizer/fsm.css
index 50caebaa18..403e16a7bb 100644
--- a/actionpack/lib/action_dispatch/journey/visualizer/fsm.css
+++ b/actionpack/lib/action_dispatch/journey/visualizer/fsm.css
@@ -16,10 +16,6 @@ h2 {
font-size: 0.5em;
}
-div#chart-2 {
- height: 350px;
-}
-
.clearfix {display: inline-block; }
.input { overflow: show;}
.instruction { color: #666; padding: 0 30px 20px; font-size: 0.9em}
diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb
index baf9d5779e..f80df78582 100644
--- a/actionpack/lib/action_dispatch/middleware/callbacks.rb
+++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb
@@ -1,6 +1,6 @@
module ActionDispatch
- # Provide callbacks to be executed before and after the request dispatch.
+ # Provides callbacks to be executed before and after dispatching the request.
class Callbacks
include ActiveSupport::Callbacks
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 83ac62a83d..b7687ca100 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -71,11 +71,13 @@ module ActionDispatch
# restrict to the domain level. If you use a schema like www.example.com
# and want to share session with user.example.com set <tt>:domain</tt>
# to <tt>:all</tt>. Make sure to specify the <tt>:domain</tt> option with
- # <tt>:all</tt> again when deleting cookies.
+ # <tt>:all</tt> or <tt>Array</tt> again when deleting cookies.
#
- # domain: nil # Does not sets cookie domain. (default)
+ # domain: nil # Does not set cookie domain. (default)
# domain: :all # Allow the cookie for the top most level
# # domain and subdomains.
+ # domain: %w(.example.com .example.org) # Allow the cookie
+ # # for concrete domain names.
#
# * <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.
@@ -120,7 +122,7 @@ module ActionDispatch
# the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
# cookie was tampered with by the user (or a 3rd party), nil will be returned.
#
- # If +secrets.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
+ # If +secrets.secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
# legacy cookies signed with the old key generator will be transparently upgraded.
#
# This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+.
@@ -143,7 +145,7 @@ module ActionDispatch
# Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
# If the cookie was tampered with by the user (or a 3rd party), nil will be returned.
#
- # If +secrets.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
+ # If +secrets.secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
# legacy cookies signed with the old key generator will be transparently upgraded.
#
# This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+.
@@ -281,7 +283,7 @@ module ActionDispatch
def handle_options(options) #:nodoc:
options[:path] ||= "/"
- if options[:domain] == :all
+ if options[:domain] == :all || options[:domain] == 'all'
# if there is a provided tld length then we use it otherwise default domain regexp
domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP
@@ -408,7 +410,7 @@ module ActionDispatch
@options[:serializer] == :hybrid && value.start_with?(MARSHAL_SIGNATURE)
end
- def serialize(name, value)
+ def serialize(value)
serializer.dump(value)
end
@@ -461,9 +463,9 @@ module ActionDispatch
def []=(name, options)
if options.is_a?(Hash)
options.symbolize_keys!
- options[:value] = @verifier.generate(serialize(name, options[:value]))
+ options[:value] = @verifier.generate(serialize(options[:value]))
else
- options = { :value => @verifier.generate(serialize(name, options)) }
+ options = { :value => @verifier.generate(serialize(options)) }
end
raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
@@ -479,7 +481,7 @@ module ActionDispatch
end
# UpgradeLegacySignedCookieJar is used instead of SignedCookieJar if
- # config.secret_token and secrets.secret_key_base are both set. It reads
+ # 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.
class UpgradeLegacySignedCookieJar < SignedCookieJar #:nodoc:
@@ -522,7 +524,7 @@ module ActionDispatch
options = { :value => options }
end
- options[:value] = @encryptor.encrypt_and_sign(serialize(name, options[:value]))
+ options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]))
raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
@parent_jar[name] = options
@@ -537,7 +539,7 @@ module ActionDispatch
end
# UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore
- # instead of EncryptedCookieJar if config.secret_token and secrets.secret_key_base
+ # instead of EncryptedCookieJar if secrets.secret_token and secrets.secret_key_base
# are both set. It reads legacy cookies signed with the old dummy key generator and
# encrypts and re-saves them using the new key generator to provide a smooth upgrade path.
class UpgradeLegacyEncryptedCookieJar < EncryptedCookieJar #:nodoc:
diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
index 798c087d64..9082aac271 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -1,6 +1,10 @@
require 'action_dispatch/http/request'
require 'action_dispatch/middleware/exception_wrapper'
require 'action_dispatch/routing/inspector'
+require 'action_view'
+require 'action_view/base'
+
+require 'pp'
module ActionDispatch
# This middleware is responsible for logging exceptions and
@@ -8,6 +12,32 @@ module ActionDispatch
class DebugExceptions
RESCUES_TEMPLATE_PATH = File.expand_path('../templates', __FILE__)
+ class DebugView < ActionView::Base
+ def debug_params(params)
+ clean_params = params.clone
+ clean_params.delete("action")
+ clean_params.delete("controller")
+
+ if clean_params.empty?
+ 'None'
+ else
+ PP.pp(clean_params, "", 200)
+ end
+ end
+
+ def debug_headers(headers)
+ if headers.present?
+ headers.inspect.gsub(',', ",\n")
+ else
+ 'None'
+ end
+ end
+
+ def debug_hash(object)
+ object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
+ end
+ end
+
def initialize(app, routes_app = nil)
@app = app
@routes_app = routes_app
@@ -38,7 +68,7 @@ module ActionDispatch
traces = wrapper.traces
trace_to_show = 'Application Trace'
- if traces[trace_to_show].empty?
+ if traces[trace_to_show].empty? && wrapper.rescue_template != 'routing_error'
trace_to_show = 'Full Trace'
end
@@ -46,14 +76,14 @@ module ActionDispatch
source_to_show_id = source_to_show[:id]
end
- template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
+ template = DebugView.new([RESCUES_TEMPLATE_PATH],
request: request,
exception: wrapper.exception,
traces: traces,
show_source_idx: source_to_show_id,
trace_to_show: trace_to_show,
routes_inspector: routes_inspector(exception),
- source_extract: wrapper.source_extract,
+ source_extracts: wrapper.source_extracts,
line_number: wrapper.line_number,
file: wrapper.file
)
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index e0140b0692..d176a73633 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -1,5 +1,6 @@
require 'action_controller/metal/exceptions'
require 'active_support/core_ext/module/attribute_accessors'
+require 'rack/utils'
module ActionDispatch
class ExceptionWrapper
@@ -62,14 +63,16 @@ module ActionDispatch
framework_trace_with_ids = []
full_trace_with_ids = []
- if full_trace
- full_trace.each_with_index do |trace, idx|
- trace_with_id = { id: idx, trace: trace }
+ full_trace.each_with_index do |trace, idx|
+ trace_with_id = { id: idx, trace: trace }
- appplication_trace_with_ids << trace_with_id if application_trace.include?(trace)
- framework_trace_with_ids << trace_with_id if framework_trace.include?(trace)
- full_trace_with_ids << trace_with_id
+ if application_trace.include?(trace)
+ appplication_trace_with_ids << trace_with_id
+ else
+ framework_trace_with_ids << trace_with_id
end
+
+ full_trace_with_ids << trace_with_id
end
{
@@ -83,20 +86,23 @@ module ActionDispatch
Rack::Utils.status_code(@@rescue_responses[class_name])
end
- def source_extract
- exception.backtrace.map do |trace|
- file, line = trace.split(":")
- line_number = line.to_i
+ def source_extracts
+ backtrace.map do |trace|
+ file, line_number = extract_file_and_line_number(trace)
+
{
code: source_fragment(file, line_number),
- file: file,
line_number: line_number
}
- end if exception.backtrace
+ end
end
private
+ def backtrace
+ Array(@exception.backtrace)
+ end
+
def original_exception(exception)
if registered_original_exception?(exception)
exception.original_exception
@@ -111,9 +117,9 @@ module ActionDispatch
def clean_backtrace(*args)
if backtrace_cleaner
- backtrace_cleaner.clean(@exception.backtrace, *args)
+ backtrace_cleaner.clean(backtrace, *args)
else
- @exception.backtrace
+ backtrace
end
end
@@ -133,6 +139,13 @@ module ActionDispatch
end
end
+ def extract_file_and_line_number(trace)
+ # Split by the first colon followed by some digits, which works for both
+ # Windows and Unix path styles.
+ file, line = trace.match(/^(.+?):(\d+).*$/, &:captures) || trace
+ [file, line.to_i]
+ end
+
def expand_backtrace
@exception.backtrace.unshift(
@exception.to_s.split("\n")
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index e90f8b9ce6..59639a010e 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -79,22 +79,31 @@ module ActionDispatch
class FlashHash
include Enumerable
- def self.from_session_value(value)
- flash = case value
- when FlashHash # Rails 3.1, 3.2
- new(value.instance_variable_get(:@flashes), value.instance_variable_get(:@used))
- when Hash # Rails 4.0
- new(value['flashes'], value['discard'])
- else
- new
- end
-
- flash.tap(&:sweep)
+ def self.from_session_value(value) #:nodoc:
+ case value
+ when FlashHash # Rails 3.1, 3.2
+ flashes = value.instance_variable_get(:@flashes)
+ if discard = value.instance_variable_get(:@used)
+ flashes.except!(*discard)
+ end
+ new(flashes, flashes.keys)
+ when Hash # Rails 4.0
+ flashes = value['flashes']
+ if discard = value['discard']
+ flashes.except!(*discard)
+ end
+ new(flashes, flashes.keys)
+ else
+ new
+ end
end
- def to_session_value
- return nil if empty?
- {'discard' => @discard.to_a, 'flashes' => @flashes}
+ # Builds a hash containing the flashes to keep for the next request.
+ # If there are none to keep, returns nil.
+ def to_session_value #:nodoc:
+ flashes_to_keep = @flashes.except(*@discard)
+ return nil if flashes_to_keep.empty?
+ {'flashes' => flashes_to_keep}
end
def initialize(flashes = {}, discard = []) #:nodoc:
@@ -132,7 +141,7 @@ module ActionDispatch
end
def key?(name)
- @flashes.key? name
+ @flashes.key? name.to_s
end
def delete(key)
diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb
index b426183488..29d43faeed 100644
--- a/actionpack/lib/action_dispatch/middleware/params_parser.rb
+++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb
@@ -47,7 +47,7 @@ module ActionDispatch
else
false
end
- rescue Exception => e # JSON or Ruby code block errors
+ rescue => e # JSON or Ruby code block errors
logger(env).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
raise ParseError.new(e.message, e)
diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
index 040cb215b7..7cde76b30e 100644
--- a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
@@ -17,10 +17,10 @@ module ActionDispatch
end
def call(env)
- status = env["PATH_INFO"][1..-1]
+ status = env["PATH_INFO"][1..-1].to_i
request = ActionDispatch::Request.new(env)
content_type = request.formats.first
- body = { :status => status, :error => Rack::Utils::HTTP_STATUS_CODES.fetch(status.to_i, Rack::Utils::HTTP_STATUS_CODES[500]) }
+ body = { :status => status, :error => Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500]) }
render(status, content_type, body)
end
diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb
index 25658bac3d..b9ca524309 100644
--- a/actionpack/lib/action_dispatch/middleware/request_id.rb
+++ b/actionpack/lib/action_dispatch/middleware/request_id.rb
@@ -3,7 +3,7 @@ require 'active_support/core_ext/string/access'
module ActionDispatch
# Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through
- # ActionDispatch::Request#uuid) and sends the same id to the client via the X-Request-Id header.
+ # ActionDispatch::Request#uuid or the alias ActionDispatch::Request#request_id) and sends the same id to the client via the X-Request-Id header.
#
# The unique request id is either based on the X-Request-Id header in the request, which would typically be generated
# by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the
@@ -12,19 +12,23 @@ module ActionDispatch
# The unique request id can be used to trace a request end-to-end and would typically end up being part of log files
# from multiple pieces of the stack.
class RequestId
+ X_REQUEST_ID = "X-Request-Id".freeze # :nodoc:
+ 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"] }
+ 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] }
end
private
def external_request_id(env)
- if request_id = env["HTTP_X_REQUEST_ID"].presence
- request_id.gsub(/[^\w\-]/, "").first(255)
+ if request_id = env[HTTP_X_REQUEST_ID].presence
+ request_id.gsub(/[^\w\-]/, "".freeze).first(255)
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
index 625050dc4b..857e49a682 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
@@ -2,12 +2,15 @@ require 'action_dispatch/middleware/session/abstract_store'
module ActionDispatch
module Session
- # Session store that uses an ActiveSupport::Cache::Store to store the sessions. This store is most useful
+ # A session store that uses an ActiveSupport::Cache::Store to store the sessions. This store is most useful
# if you don't store critical data in your sessions and you don't need them to live for extended periods
# of time.
+ #
+ # ==== Options
+ # * <tt>cache</tt> - The cache to use. If it is not specified, <tt>Rails.cache</tt> will be used.
+ # * <tt>expire_after</tt> - The length of time a session will be stored before automatically expiring.
+ # By default, the <tt>:expires_in</tt> option of the cache is used.
class CacheStore < AbstractStore
- # Create a new store. The cache to use can be passed in the <tt>:cache</tt> option. If it is
- # not specified, <tt>Rails.cache</tt> will be used.
def initialize(app, options = {})
@cache = options[:cache] || Rails.cache
options[:expire_after] ||= @cache.options[:expires_in]
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index ed25c67ae5..d8f9614904 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -52,6 +52,16 @@ module ActionDispatch
# JavaScript before upgrading.
#
# Note that changing the secret key will invalidate all existing sessions!
+ #
+ # Because CookieStore extends Rack::Session::Abstract::ID, many of the
+ # options described there can be used to customize the session cookie that
+ # is generated. For example:
+ #
+ # Rails.application.config.session_store :cookie_store, expire_after: 14.days
+ #
+ # would set the session cookie to expire automatically 14 days after creation.
+ # Other useful options include <tt>:key</tt>, <tt>:secure</tt> and
+ # <tt>:httponly</tt>.
class CookieStore < Rack::Session::Abstract::ID
include Compatibility
include StaleSessionCheck
diff --git a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
index b4d6629c35..cb19786f0b 100644
--- a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
@@ -8,6 +8,10 @@ end
module ActionDispatch
module Session
+ # A session store that uses MemCache to implement storage.
+ #
+ # ==== Options
+ # * <tt>expire_after</tt> - The length of time a session will be stored before automatically expiring.
class MemCacheStore < Rack::Session::Dalli
include Compatibility
include StaleSessionCheck
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index 002bf8b11a..9a92b690c7 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -23,13 +23,12 @@ module ActionDispatch
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}"].map { |v|
- Rack::Utils.clean_path_info v
- }
+ 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'))
begin
File.file?(path) && File.readable?(path)
rescue SystemCallError
@@ -48,6 +47,9 @@ module ActionDispatch
if gzip_path && gzip_encoding_accepted?(env)
env['PATH_INFO'] = gzip_path
status, headers, body = @file_server.call(env)
+ if status == 304
+ return [status, headers, body]
+ end
headers['Content-Encoding'] = 'gzip'
headers['Content-Type'] = content_type(path)
else
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb
index db219c8fa9..49b1e83551 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb
@@ -5,20 +5,8 @@
<pre id="blame_trace" <%='style="display:none"' if hide %>><code><%= @exception.describe_blame %></code></pre>
<% end %>
-<%
- clean_params = @request.filtered_parameters.clone
- clean_params.delete("action")
- clean_params.delete("controller")
-
- request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n")
-
- def debug_hash(object)
- object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
- end unless self.class.method_defined?(:debug_hash)
-%>
-
<h2 style="margin-top: 30px">Request</h2>
-<p><b>Parameters</b>:</p> <pre><%= request_dump %></pre>
+<p><b>Parameters</b>:</p> <pre><%= debug_params(@request.filtered_parameters) %></pre>
<div class="details">
<div class="summary"><a href="#" onclick="return toggleSessionDump()">Toggle session dump</a></div>
@@ -31,4 +19,4 @@
</div>
<h2 style="margin-top: 30px">Response</h2>
-<p><b>Headers</b>:</p> <pre><%= defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %></pre>
+<p><b>Headers</b>:</p> <pre><%= debug_headers(defined?(@response) ? @response.headers : {}) %></pre>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb
index eabac3a9d2..e7b913bbe4 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb
@@ -1,29 +1,27 @@
-<% if @source_extract %>
- <% @source_extract.each_with_index do |extract_source, index| %>
- <% if extract_source[:code] %>
- <div class="source <%="hidden" if @show_source_idx != index%>" id="frame-source-<%=index%>">
- <div class="info">
- Extracted source (around line <strong>#<%= extract_source[:line_number] %></strong>):
- </div>
- <div class="data">
- <table cellpadding="0" cellspacing="0" class="lines">
- <tr>
- <td>
- <pre class="line_numbers">
- <% extract_source[:code].each_key do |line_number| %>
+<% @source_extracts.each_with_index do |source_extract, index| %>
+ <% if source_extract[:code] %>
+ <div class="source <%="hidden" if @show_source_idx != index%>" id="frame-source-<%=index%>">
+ <div class="info">
+ Extracted source (around line <strong>#<%= source_extract[:line_number] %></strong>):
+ </div>
+ <div class="data">
+ <table cellpadding="0" cellspacing="0" class="lines">
+ <tr>
+ <td>
+ <pre class="line_numbers">
+ <% source_extract[:code].each_key do |line_number| %>
<span><%= line_number -%></span>
- <% end %>
- </pre>
- </td>
+ <% end %>
+ </pre>
+ </td>
<td width="100%">
<pre>
-<% extract_source[:code].each do |line, source| -%><div class="line<%= " active" if line == extract_source[:line_number] -%>"><%= source -%></div><% end -%>
+<% source_extract[:code].each do |line, source| -%><div class="line<%= " active" if line == source_extract[:line_number] -%>"><%= source -%></div><% end -%>
</pre>
</td>
- </tr>
- </table>
- </div>
+ </tr>
+ </table>
</div>
- <% end %>
+ </div>
<% end %>
<% end %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb
index 5c016e544e..2a65fd06ad 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb
@@ -4,4 +4,8 @@
<div id="container">
<h2><%= h @exception.message %></h2>
+
+ <%= render template: "rescues/_source" %>
+ <%= render template: "rescues/_trace" %>
+ <%= render template: "rescues/_request_and_response" %>
</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb
index 7e9cedb95e..55dd5ddc7b 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb
@@ -27,4 +27,6 @@
<%= @routes_inspector.format(ActionDispatch::Routing::HtmlTableFormatter.new(self)) %>
<% end %>
+
+ <%= render template: "rescues/_request_and_response" %>
</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb
index 24e44f31ac..6e995c85c1 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb
@@ -4,13 +4,13 @@
<%= route[:name] %><span class='helper'>_path</span>
<% end %>
</td>
- <td data-route-verb='<%= route[:verb] %>'>
+ <td>
<%= route[:verb] %>
</td>
- <td data-route-path='<%= route[:path] %>' data-regexp='<%= route[:regexp] %>'>
+ <td data-route-path='<%= route[:path] %>'>
<%= route[:path] %>
</td>
- <td data-route-reqs='<%= route[:reqs] %>'>
- <%= route[:reqs] %>
+ <td>
+ <%=simple_format route[:reqs] %>
</td>
</tr>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
index 6ffa242da4..429ea7057c 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
@@ -1,6 +1,6 @@
<% content_for :style do %>
#route_table {
- margin: 0 auto 0;
+ margin: 0;
border-collapse: collapse;
}
@@ -81,92 +81,87 @@
</table>
<script type='text/javascript'>
- // Iterates each element through a function
- function each(elems, func) {
- if (!elems instanceof Array) { elems = [elems]; }
- for (var i = 0, len = elems.length; i < len; i++) {
- func(elems[i]);
- }
- }
-
- // Sets innerHTML for an element
- function setContent(elem, text) {
- elem.innerHTML = text;
- }
+ // support forEarch iterator on NodeList
+ NodeList.prototype.forEach = Array.prototype.forEach;
// Enables path search functionality
function setupMatchPaths() {
- // Check if the user input (sanitized as a path) matches the regexp data attribute
- function checkExactMatch(section, elem, value) {
- var string = sanitizePath(value),
- regexp = elem.getAttribute("data-regexp");
-
- showMatch(string, regexp, section, elem);
+ // Check if there are any matched results in a section
+ function checkNoMatch(section, noMatchText) {
+ if (section.children.length <= 1) {
+ section.innerHTML += noMatchText;
+ }
}
- // Check if the route path data attribute contains the user input
- function checkFuzzyMatch(section, elem, value) {
- var string = elem.getAttribute("data-route-path"),
- regexp = value;
-
- showMatch(string, regexp, section, elem);
+ // get JSON from url and invoke callback with result
+ function getJSON(url, success) {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', url);
+ xhr.onload = function() {
+ if (this.status == 200)
+ success(JSON.parse(this.response));
+ };
+ xhr.send();
}
- // Display the parent <tr> element in the appropriate section when there's a match
- function showMatch(string, regexp, section, elem) {
- if(string.match(RegExp(regexp))) {
- section.appendChild(elem.parentNode.cloneNode(true));
+ function delayedKeyup(input, callback) {
+ var timeout;
+ input.onkeyup = function(){
+ if (timeout) clearTimeout(timeout);
+ timeout = setTimeout(callback, 300);
}
}
- // Check if there are any matched results in a section
- function checkNoMatch(section, defaultText, noMatchText) {
- if (section.innerHTML === defaultText) {
- setContent(section, defaultText + noMatchText);
- }
- }
-
- // Ensure path always starts with a slash "/" and remove params or fragments
+ // remove params or fragments
function sanitizePath(path) {
- var path = path.charAt(0) == '/' ? path : "/" + path;
- return path.replace(/\#.*|\?.*/, '');
+ return path.replace(/[#?].*/, '');
}
- var regexpElems = document.querySelectorAll('#route_table [data-regexp]'),
- searchElem = document.querySelector('#search'),
- exactMatches = document.querySelector('#exact_matches'),
- fuzzyMatches = document.querySelector('#fuzzy_matches');
+ var pathElements = document.querySelectorAll('#route_table [data-route-path]'),
+ searchElem = document.querySelector('#search'),
+ exactSection = document.querySelector('#exact_matches'),
+ fuzzySection = document.querySelector('#fuzzy_matches');
// Remove matches when no search value is present
searchElem.onblur = function(e) {
if (searchElem.value === "") {
- setContent(exactMatches, "");
- setContent(fuzzyMatches, "");
+ exactSection.innerHTML = "";
+ fuzzySection.innerHTML = "";
}
}
// On key press perform a search for matching paths
- searchElem.onkeyup = function(e){
- var userInput = searchElem.value,
- defaultExactMatch = '<tr><th colspan="4">Paths Matching (' + escape(sanitizePath(userInput)) +'):</th></tr>',
- defaultFuzzyMatch = '<tr><th colspan="4">Paths Containing (' + escape(userInput) +'):</th></tr>',
+ delayedKeyup(searchElem, function() {
+ var path = sanitizePath(searchElem.value),
+ defaultExactMatch = '<tr><th colspan="4">Paths Matching (' + path +'):</th></tr>',
+ defaultFuzzyMatch = '<tr><th colspan="4">Paths Containing (' + path +'):</th></tr>',
noExactMatch = '<tr><th colspan="4">No Exact Matches Found</th></tr>',
noFuzzyMatch = '<tr><th colspan="4">No Fuzzy Matches Found</th></tr>';
- // Clear out results section
- setContent(exactMatches, defaultExactMatch);
- setContent(fuzzyMatches, defaultFuzzyMatch);
+ if (!path)
+ return searchElem.onblur();
- // Display exact matches and fuzzy matches
- each(regexpElems, function(elem) {
- checkExactMatch(exactMatches, elem, userInput);
- checkFuzzyMatch(fuzzyMatches, elem, userInput);
- })
+ getJSON('/rails/info/routes?path=' + path, function(matches){
+ // Clear out results section
+ exactSection.innerHTML = defaultExactMatch;
+ fuzzySection.innerHTML = defaultFuzzyMatch;
- // Display 'No Matches' message when no matches are found
- checkNoMatch(exactMatches, defaultExactMatch, noExactMatch);
- checkNoMatch(fuzzyMatches, defaultFuzzyMatch, noFuzzyMatch);
- }
+ // Display exact matches and fuzzy matches
+ pathElements.forEach(function(elem) {
+ var elemPath = elem.getAttribute('data-route-path');
+
+ if (matches['exact'].indexOf(elemPath) != -1)
+ exactSection.appendChild(elem.parentNode.cloneNode(true));
+
+ if (matches['fuzzy'].indexOf(elemPath) != -1)
+ fuzzySection.appendChild(elem.parentNode.cloneNode(true));
+ })
+
+ // Display 'No Matches' message when no matches are found
+ checkNoMatch(exactSection, noExactMatch);
+ checkNoMatch(fuzzySection, noFuzzyMatch);
+ })
+ })
}
// Enables functionality to toggle between `_path` and `_url` helper suffixes
@@ -174,19 +169,20 @@
// Sets content for each element
function setValOn(elems, val) {
- each(elems, function(elem) {
- setContent(elem, val);
+ elems.forEach(function(elem) {
+ elem.innerHTML = val;
});
}
// Sets onClick event for each element
function onClick(elems, func) {
- each(elems, function(elem) {
+ elems.forEach(function(elem) {
elem.onclick = func;
});
}
var toggleLinks = document.querySelectorAll('#route_table [data-route-helper]');
+
onClick(toggleLinks, function(){
var helperTxt = this.getAttribute("data-route-helper"),
helperElems = document.querySelectorAll('[data-route-name] span.helper');
diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb
index 973627f106..9a1a05e971 100644
--- a/actionpack/lib/action_dispatch/request/session.rb
+++ b/actionpack/lib/action_dispatch/request/session.rb
@@ -9,7 +9,8 @@ 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)
diff --git a/actionpack/lib/action_dispatch/request/utils.rb b/actionpack/lib/action_dispatch/request/utils.rb
index 9d4f1aa3c5..1c9371d89c 100644
--- a/actionpack/lib/action_dispatch/request/utils.rb
+++ b/actionpack/lib/action_dispatch/request/utils.rb
@@ -16,10 +16,6 @@ module ActionDispatch
when Array
v.grep(Hash) { |x| deep_munge(x, keys) }
v.compact!
- if v.empty?
- hash[k] = nil
- ActiveSupport::Notifications.instrument("deep_munge.action_controller", keys: keys)
- end
when Hash
deep_munge(v, keys)
end
diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb
index cfe2237512..c513737fc2 100644
--- a/actionpack/lib/action_dispatch/routing/inspector.rb
+++ b/actionpack/lib/action_dispatch/routing/inspector.rb
@@ -28,23 +28,6 @@ module ActionDispatch
super.to_s
end
- def regexp
- __getobj__.path.to_regexp
- end
-
- def json_regexp
- str = regexp.inspect.
- sub('\\A' , '^').
- sub('\\Z' , '$').
- sub('\\z' , '$').
- sub(/^\// , '').
- sub(/\/[a-z]*$/ , '').
- gsub(/\(\?#.+\)/ , '').
- gsub(/\(\?-\w+:/ , '(').
- gsub(/\s/ , '')
- Regexp.new(str).source
- end
-
def reqs
@reqs ||= begin
reqs = endpoint
@@ -114,16 +97,13 @@ module ActionDispatch
def collect_routes(routes)
routes.collect do |route|
RouteWrapper.new(route)
- end.reject do |route|
- route.internal?
- end.collect do |route|
+ end.reject(&:internal?).collect do |route|
collect_engine_routes(route)
- { name: route.name,
- verb: route.verb,
- path: route.path,
- reqs: route.reqs,
- regexp: route.json_regexp }
+ { name: route.name,
+ verb: route.verb,
+ path: route.path,
+ reqs: route.reqs }
end
end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index ac03ecb2c8..49009a45cc 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -4,11 +4,9 @@ require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/enumerable'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/module/remove_method'
-require 'active_support/core_ext/string/filters'
require 'active_support/inflector'
require 'action_dispatch/routing/redirection'
require 'action_dispatch/routing/endpoint'
-require 'active_support/deprecation'
module ActionDispatch
module Routing
@@ -244,12 +242,10 @@ module ActionDispatch
def app(blocks)
if to.respond_to?(:call)
Constraints.new(to, blocks, false)
+ elsif blocks.any?
+ Constraints.new(dispatcher(defaults), blocks, true)
else
- if blocks.any?
- Constraints.new(dispatcher(defaults), blocks, true)
- else
- dispatcher(defaults)
- end
+ dispatcher(defaults)
end
end
@@ -281,22 +277,8 @@ module ActionDispatch
end
def split_to(to)
- case to
- when Symbol
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Defining a route where `to` is a symbol is deprecated.
- Please change `to: :#{to}` to `action: :#{to}`.
- MSG
-
- [nil, to.to_s]
- when /#/ then to.split('#')
- when String
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Defining a route where `to` is a controller without an action is deprecated.
- Please change `to: :#{to}` to `controller: :#{to}`.
- MSG
-
- [to, nil]
+ if to =~ /#/
+ to.split('#')
else
[]
end
@@ -391,7 +373,7 @@ module ActionDispatch
# Matches a url pattern to one or more routes.
#
- # You should not use the `match` method in your router
+ # You should not use the +match+ method in your router
# without specifying an HTTP method.
#
# If you want to expose your action to both GET and POST, use:
@@ -402,7 +384,7 @@ module ActionDispatch
# Note that +:controller+, +:action+ and +:id+ are interpreted as url
# query parameters and thus available through +params+ in an action.
#
- # If you want to expose your action to GET, use `get` in the router:
+ # If you want to expose your action to GET, use +get+ in the router:
#
# Instead of:
#
@@ -457,7 +439,7 @@ module ActionDispatch
# The route's action.
#
# [:param]
- # Overrides the default resource identifier `:id` (name of the
+ # Overrides the default resource identifier +:id+ (name of the
# dynamic segment used to generate the routes).
# You can access that segment from your controller using
# <tt>params[<:param>]</tt>.
@@ -582,13 +564,7 @@ module ActionDispatch
raise "A rack application must be specified" unless path
rails_app = rails_app? app
-
- if rails_app
- options[:as] ||= app.railtie_name
- else
- # non rails apps can't have an :as
- options[:as] = nil
- end
+ options[:as] ||= app_name(app, rails_app)
target_as = name_for_action(options[:as], path)
options[:via] ||= :all
@@ -620,6 +596,15 @@ module ActionDispatch
app.is_a?(Class) && app < Rails::Railtie
end
+ def app_name(app, rails_app)
+ if rails_app
+ app.railtie_name
+ elsif app.is_a?(Class)
+ class_name = app.name
+ ActiveSupport::Inflector.underscore(class_name).tr("/", "_")
+ end
+ end
+
def define_generate_prefix(app, name)
_route = @set.named_routes.get name
_routes = @set
@@ -1519,7 +1504,7 @@ module ActionDispatch
end
def using_match_shorthand?(path, options)
- path && (options[:to] || options[:action]).nil? && path =~ %r{/[\w/]+$}
+ path && (options[:to] || options[:action]).nil? && path =~ %r{^/?[-\w]+/[-\w/]+$}
end
def decomposed_match(path, options) # :nodoc:
@@ -1693,7 +1678,7 @@ module ActionDispatch
end
def shallow_nesting_depth #:nodoc:
- @nesting.select(&:shallow?).size
+ @nesting.count(&:shallow?)
end
def param_constraint? #:nodoc:
@@ -1754,9 +1739,10 @@ module ActionDispatch
member_name = parent_resource.member_name
end
- name = @scope.action_name(name_prefix, prefix, collection_name, member_name)
+ action_name = @scope.action_name(name_prefix, prefix, collection_name, member_name)
+ candidate = action_name.select(&:present?).join('_')
- if candidate = name.compact.join("_").presence
+ unless candidate.empty?
# If a name was not explicitly given, we check if it is valid
# and return nil in case it isn't. Otherwise, we pass the invalid name
# forward so the underlying router engine treats it and raises an exception.
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index 0847842fa2..9934f5547a 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -1,5 +1,3 @@
-require 'action_controller/model_naming'
-
module ActionDispatch
module Routing
# Polymorphic URL helpers are methods for smart resolution to a named route call when
@@ -55,8 +53,6 @@ module ActionDispatch
# form_for([blog, @post]) # => "/blog/posts/1"
#
module PolymorphicRoutes
- include ActionController::ModelNaming
-
# Constructs a call to a named RESTful route for the given record and returns the
# resulting URL string. For example:
#
@@ -251,14 +247,12 @@ module ActionDispatch
args = []
model = record.to_model
- name = if record.persisted?
- args << model
- model.model_name.singular_route_key
- else
- @key_strategy.call model.model_name
- end
-
- named_route = prefix + "#{name}_#{suffix}"
+ named_route = if model.persisted?
+ args << model
+ get_method_for_string model.model_name.singular_route_key
+ else
+ get_method_for_class model
+ end
[named_route, args]
end
@@ -294,11 +288,12 @@ module ActionDispatch
when Class
@key_strategy.call record.model_name
else
- if record.persisted?
- args << record.to_model
- record.to_model.model_name.singular_route_key
+ model = record.to_model
+ if model.persisted?
+ args << model
+ model.model_name.singular_route_key
else
- @key_strategy.call record.to_model.model_name
+ @key_strategy.call model.model_name
end
end
@@ -312,11 +307,11 @@ module ActionDispatch
def get_method_for_class(klass)
name = @key_strategy.call klass.model_name
- prefix + "#{name}_#{suffix}"
+ get_method_for_string name
end
def get_method_for_string(str)
- prefix + "#{str}_#{suffix}"
+ "#{prefix}#{str}_#{suffix}"
end
[nil, 'new', 'edit'].each do |action|
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index a641ea3ea9..d0d8ded515 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -6,21 +6,21 @@ require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/module/remove_method'
require 'active_support/core_ext/array/extract_options'
-require 'active_support/core_ext/string/filters'
require 'action_controller/metal/exceptions'
require 'action_dispatch/http/request'
require 'action_dispatch/routing/endpoint'
module ActionDispatch
module Routing
- class RouteSet #:nodoc:
+ # :stopdoc:
+ class RouteSet
# Since the router holds references to many parts of the system
# like engines, controllers and the application itself, inspecting
# the route set can actually be really slow, therefore we default
# alias inspect to to_s.
alias inspect to_s
- class Dispatcher < Routing::Endpoint #:nodoc:
+ class Dispatcher < Routing::Endpoint
def initialize(defaults)
@defaults = defaults
@controller_class_names = ThreadSafe::Cache.new
@@ -85,9 +85,9 @@ module ActionDispatch
# A NamedRouteCollection instance is a collection of named routes, and also
# maintains an anonymous module that can be used to install helpers for the
# named routes.
- class NamedRouteCollection #:nodoc:
+ class NamedRouteCollection
include Enumerable
- attr_reader :routes, :url_helpers_module
+ attr_reader :routes, :url_helpers_module, :path_helpers_module
def initialize
@routes = {}
@@ -102,14 +102,6 @@ module ActionDispatch
@path_helpers.include?(key) || @url_helpers.include?(key)
end
- def helpers
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- `named_routes.helpers` is deprecated, please use `route_defined?(route_name)`
- to see if a named route was defined.
- MSG
- @path_helpers + @url_helpers
- end
-
def helper_names
@path_helpers.map(&:to_s) + @url_helpers.map(&:to_s)
end
@@ -138,7 +130,7 @@ module ActionDispatch
@url_helpers_module.send :undef_method, url_name
end
routes[key] = route
- define_url_helper @path_helpers_module, route, path_name, route.defaults, name, LEGACY
+ define_url_helper @path_helpers_module, route, path_name, route.defaults, name, PATH
define_url_helper @url_helpers_module, route, url_name, route.defaults, name, UNKNOWN
@path_helpers << path_name
@@ -170,26 +162,7 @@ module ActionDispatch
routes.length
end
- def path_helpers_module(warn = false)
- if warn
- mod = @path_helpers_module
- helpers = @path_helpers
- Module.new do
- include mod
-
- helpers.each do |meth|
- define_method(meth) do |*args, &block|
- ActiveSupport::Deprecation.warn("The method `#{meth}` cannot be used here as a full URL is required. Use `#{meth.to_s.sub(/_path$/, '_url')}` instead")
- super(*args, &block)
- end
- end
- end
- else
- @path_helpers_module
- end
- end
-
- class UrlHelper # :nodoc:
+ class UrlHelper
def self.create(route, options, route_name, url_strategy)
if optimize_helper?(route)
OptimizedUrlHelper.new(route, options, route_name, url_strategy)
@@ -204,7 +177,7 @@ module ActionDispatch
attr_reader :url_strategy, :route_name
- class OptimizedUrlHelper < UrlHelper # :nodoc:
+ class OptimizedUrlHelper < UrlHelper
attr_reader :arg_size
def initialize(route, options, route_name, url_strategy)
@@ -226,12 +199,9 @@ module ActionDispatch
private
def optimized_helper(args)
- params = parameterize_args(args)
- missing_keys = missing_keys(params)
-
- unless missing_keys.empty?
- raise_generation_error(params, missing_keys)
- end
+ params = parameterize_args(args) { |k|
+ raise_generation_error(args)
+ }
@route.format params
end
@@ -242,16 +212,21 @@ module ActionDispatch
def parameterize_args(args)
params = {}
- @required_parts.zip(args.map(&:to_param)) { |k,v| params[k] = v }
+ @arg_size.times { |i|
+ key = @required_parts[i]
+ value = args[i].to_param
+ yield key if value.nil? || value.empty?
+ params[key] = value
+ }
params
end
- def missing_keys(args)
- args.select{ |part, arg| arg.nil? || arg.empty? }.keys
- end
-
- def raise_generation_error(args, missing_keys)
- constraints = Hash[@route.requirements.merge(args).sort]
+ def raise_generation_error(args)
+ missing_keys = []
+ params = parameterize_args(args) { |missing_key|
+ missing_keys << missing_key
+ }
+ 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}"
@@ -280,15 +255,22 @@ module ActionDispatch
end
def handle_positional_args(controller_options, inner_options, args, result, path_params)
-
if args.size > 0
- if args.size < path_params.size - 1 # take format into account
+ # take format into account
+ if path_params.include?(:format)
+ path_params_size = path_params.size - 1
+ else
+ path_params_size = path_params.size
+ end
+
+ if args.size < path_params_size
path_params -= controller_options.keys
path_params -= result.keys
end
- path_params.each { |param|
- result[param] = inner_options[param] || args.shift
- }
+ path_params -= inner_options.keys
+ path_params.take(args.size).each do |param|
+ result[param] = args.shift
+ end
end
result.merge!(inner_options)
@@ -321,42 +303,14 @@ module ActionDispatch
end
end
- # :stopdoc:
# strategy for building urls to send to the client
PATH = ->(options) { ActionDispatch::Http::URL.path_for(options) }
- FULL = ->(options) { ActionDispatch::Http::URL.full_url_for(options) }
UNKNOWN = ->(options) { ActionDispatch::Http::URL.url_for(options) }
- LEGACY = ->(options) {
- if options.key?(:only_path)
- if options[:only_path]
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- You are calling a `*_path` helper with the `only_path` option
- explicitly set to `true`. This option will stop working on
- path helpers in Rails 5. Simply remove the `only_path: true`
- argument from your call as it is redundant when applied to a
- path helper.
- MSG
-
- PATH.call(options)
- else
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- You are calling a `*_path` helper with the `only_path` option
- explicitly set to `false`. This option will stop working on
- path helpers in Rails 5. Use the corresponding `*_url` helper
- instead.
- MSG
-
- FULL.call(options)
- end
- else
- PATH.call(options)
- end
- }
- # :startdoc:
attr_accessor :formatter, :set, :named_routes, :default_scope, :router
attr_accessor :disable_clear_and_finalize, :resources_path_names
- attr_accessor :default_url_options, :request_class
+ attr_accessor :default_url_options
+ attr_reader :env_key
alias :routes :set
@@ -364,22 +318,44 @@ module ActionDispatch
{ :new => 'new', :edit => 'edit' }
end
- def initialize(request_class = ActionDispatch::Request)
+ def self.new_with_config(config)
+ if config.respond_to? :relative_url_root
+ new Config.new config.relative_url_root
+ else
+ # engines apparently don't have this set
+ new
+ end
+ end
+
+ Config = Struct.new :relative_url_root
+
+ DEFAULT_CONFIG = Config.new(nil)
+
+ def initialize(config = DEFAULT_CONFIG)
self.named_routes = NamedRouteCollection.new
self.resources_path_names = self.class.default_resources_path_names
self.default_url_options = {}
- self.request_class = request_class
+ @config = config
@append = []
@prepend = []
@disable_clear_and_finalize = false
@finalized = false
+ @env_key = "ROUTES_#{object_id}_SCRIPT_NAME".freeze
@set = Journey::Routes.new
@router = Journey::Router.new @set
@formatter = Journey::Formatter.new @set
end
+ def relative_url_root
+ @config.relative_url_root
+ end
+
+ def request_class
+ ActionDispatch::Request
+ end
+
def draw(&block)
clear! unless @disable_clear_and_finalize
eval_block(block)
@@ -427,7 +403,7 @@ module ActionDispatch
Routing::RouteSet::Dispatcher.new(defaults)
end
- module MountedHelpers #:nodoc:
+ module MountedHelpers
extend ActiveSupport::Concern
include UrlFor
end
@@ -444,9 +420,11 @@ module ActionDispatch
return if MountedHelpers.method_defined?(name)
routes = self
+ helpers = routes.url_helpers
+
MountedHelpers.class_eval do
define_method "_#{name}" do
- RoutesProxy.new(routes, _routes_context)
+ RoutesProxy.new(routes, _routes_context, helpers)
end
end
@@ -457,7 +435,7 @@ module ActionDispatch
RUBY
end
- def url_helpers(include_path_helpers = true)
+ def url_helpers(supports_path = true)
routes = self
Module.new do
@@ -468,7 +446,14 @@ module ActionDispatch
# Rails.application.routes.url_helpers.url_for(args)
@_routes = routes
class << self
- delegate :url_for, :optimize_routes_generation?, to: '@_routes'
+ def url_for(options)
+ @_routes.url_for(options)
+ end
+
+ def optimize_routes_generation?
+ @_routes.optimize_routes_generation?
+ end
+
attr_reader :_routes
def url_options; {}; end
end
@@ -484,14 +469,12 @@ module ActionDispatch
# named routes...
include url_helpers
- if include_path_helpers
+ if supports_path
path_helpers = routes.named_routes.path_helpers_module
- else
- path_helpers = routes.named_routes.path_helpers_module(true)
- end
- include path_helpers
- extend path_helpers
+ include path_helpers
+ extend path_helpers
+ end
# plus a singleton class method called _routes ...
included do
@@ -502,6 +485,12 @@ module ActionDispatch
# UrlFor (included in this module) add extra
# conveniences for working with @_routes.
define_method(:_routes) { @_routes || routes }
+
+ define_method(:_generate_paths_by_default) do
+ supports_path
+ end
+
+ private :_generate_paths_by_default
end
end
@@ -523,7 +512,7 @@ module ActionDispatch
path = conditions.delete :path_info
ast = conditions.delete :parsed_path_info
path = build_path(path, ast, requirements, anchor)
- conditions = build_conditions(conditions, path.names.map { |x| x.to_sym })
+ conditions = build_conditions(conditions, path.names.map(&:to_sym))
route = @set.add_route(app, path, conditions, defaults, name)
named_routes[name] = route if name
@@ -575,17 +564,17 @@ module ActionDispatch
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) || path_values.include?(k)
end
end
private :build_conditions
- class Generator #:nodoc:
+ class Generator
PARAMETERIZE = lambda do |name, value|
if name == :controller
value
elsif value.is_a?(Array)
- value.map { |v| v.to_param }.join('/')
+ value.map(&:to_param).join('/')
elsif param = value.to_param
param
end
@@ -729,10 +718,10 @@ module ActionDispatch
end
def find_script_name(options)
- options.delete(:script_name) { '' }
+ options.delete(:script_name) || relative_url_root || ''
end
- def path_for(options, route_name = nil) # :nodoc:
+ def path_for(options, route_name = nil)
url_for(options, route_name, PATH)
end
@@ -818,5 +807,6 @@ module ActionDispatch
raise ActionController::RoutingError, "No route matches #{path.inspect}"
end
end
+ # :startdoc:
end
end
diff --git a/actionpack/lib/action_dispatch/routing/routes_proxy.rb b/actionpack/lib/action_dispatch/routing/routes_proxy.rb
index e2393d3799..040ea04046 100644
--- a/actionpack/lib/action_dispatch/routing/routes_proxy.rb
+++ b/actionpack/lib/action_dispatch/routing/routes_proxy.rb
@@ -8,8 +8,9 @@ module ActionDispatch
attr_accessor :scope, :routes
alias :_routes :routes
- def initialize(routes, scope)
+ def initialize(routes, scope, helpers)
@routes, @scope = routes, scope
+ @helpers = helpers
end
def url_options
@@ -19,16 +20,16 @@ module ActionDispatch
end
def respond_to?(method, include_private = false)
- super || routes.url_helpers.respond_to?(method)
+ super || @helpers.respond_to?(method)
end
def method_missing(method, *args)
- if routes.url_helpers.respond_to?(method)
+ if @helpers.respond_to?(method)
self.class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{method}(*args)
options = args.extract_options!
args << url_options.merge((options || {}).symbolize_keys)
- routes.url_helpers.#{method}(*args)
+ @helpers.#{method}(*args)
end
RUBY
send(method, *args)
diff --git a/actionpack/lib/action_dispatch/testing/assertions.rb b/actionpack/lib/action_dispatch/testing/assertions.rb
index 41d00b5e2b..21b3b89d22 100644
--- a/actionpack/lib/action_dispatch/testing/assertions.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions.rb
@@ -12,10 +12,10 @@ module ActionDispatch
include Rails::Dom::Testing::Assertions
def html_document
- @html_document ||= if @response.content_type =~ /xml$/
+ @html_document ||= if @response.content_type === Mime::XML
Nokogiri::XML::Document.parse(@response.body)
else
- Nokogiri::HTML::DocumentFragment.parse(@response.body)
+ Nokogiri::HTML::Document.parse(@response.body)
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/dom.rb b/actionpack/lib/action_dispatch/testing/assertions/dom.rb
deleted file mode 100644
index fb579b52fe..0000000000
--- a/actionpack/lib/action_dispatch/testing/assertions/dom.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-require 'active_support/deprecation'
-
-ActiveSupport::Deprecation.warn("ActionDispatch::Assertions::DomAssertions has been extracted to the rails-dom-testing gem.") \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index e06f7037c6..c94eea9134 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -38,18 +38,24 @@ module ActionDispatch
# # Test a custom route
# assert_recognizes({controller: 'items', action: 'show', id: '1'}, 'view/item1')
def assert_recognizes(expected_options, path, extras={}, msg=nil)
- request = recognized_request_for(path, extras, msg)
+ if path.is_a?(Hash) && path[:method].to_s == "all"
+ [:get, :post, :put, :delete].each do |method|
+ assert_recognizes(expected_options, path.merge(method: method), extras, msg)
+ end
+ else
+ request = recognized_request_for(path, extras, msg)
- expected_options = expected_options.clone
+ expected_options = expected_options.clone
- expected_options.stringify_keys!
+ expected_options.stringify_keys!
- msg = message(msg, "") {
- sprintf("The recognized options <%s> did not match <%s>, difference:",
- request.path_parameters, expected_options)
- }
+ msg = message(msg, "") {
+ sprintf("The recognized options <%s> did not match <%s>, difference:",
+ request.path_parameters, expected_options)
+ }
- assert_equal(expected_options, request.path_parameters, msg)
+ assert_equal(expected_options, request.path_parameters, msg)
+ end
end
# Asserts that the provided options can be used to generate the provided path. This is the inverse of +assert_recognizes+.
@@ -144,13 +150,7 @@ module ActionDispatch
old_controller, @controller = @controller, @controller.clone
_routes = @routes
- # Unfortunately, there is currently an abstraction leak between AC::Base
- # and AV::Base which requires having the URL helpers in both AC and AV.
- # To do this safely at runtime for tests, we need to bump up the helper serial
- # to that the old AV subclass isn't cached.
- #
- # TODO: Make this unnecessary
- @controller.singleton_class.send(:include, _routes.url_helpers)
+ @controller.singleton_class.include(_routes.url_helpers)
@controller.view_context_class = Class.new(@controller.view_context_class) do
include _routes.url_helpers
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
deleted file mode 100644
index 7361e6c44b..0000000000
--- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-require 'active_support/deprecation'
-
-ActiveSupport::Deprecation.warn("ActionDispatch::Assertions::SelectorAssertions has been extracted to the rails-dom-testing gem.")
diff --git a/actionpack/lib/action_dispatch/testing/assertions/tag.rb b/actionpack/lib/action_dispatch/testing/assertions/tag.rb
deleted file mode 100644
index da98b1d6ce..0000000000
--- a/actionpack/lib/action_dispatch/testing/assertions/tag.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-require 'active_support/deprecation'
-
-ActiveSupport::Deprecation.warn('`ActionDispatch::Assertions::TagAssertions` has been extracted to the rails-dom-testing gem.')
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index c300a4ea0d..9390e2937a 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -12,12 +12,14 @@ module ActionDispatch
#
# - +path+: The URI (as a String) on which you want to perform a GET
# request.
- # - +parameters+: The HTTP parameters that you want to pass. This may
+ # - +params+: The HTTP parameters that you want to pass. This may
# be +nil+,
# a Hash, or a String that is appropriately encoded
# (<tt>application/x-www-form-urlencoded</tt> or
# <tt>multipart/form-data</tt>).
- # - +headers_or_env+: Additional headers to pass, as a Hash. The headers will be
+ # - +headers+: Additional headers to pass, as a Hash. The headers will be
+ # merged into the Rack env hash.
+ # - +env+: Additional env to pass, as a Hash. The headers will be
# merged into the Rack env hash.
#
# This method returns a Response object, which one can use to
@@ -28,38 +30,43 @@ module ActionDispatch
#
# You can also perform POST, PATCH, PUT, DELETE, and HEAD requests with
# +#post+, +#patch+, +#put+, +#delete+, and +#head+.
- def get(path, parameters = nil, headers_or_env = nil)
- process :get, path, parameters, headers_or_env
+ #
+ # Example:
+ #
+ # get '/feed', params: { since: 201501011400 }
+ # post '/profile', headers: { "X-Test-Header" => "testvalue" }
+ def get(path, *args)
+ process_with_kwargs(:get, path, *args)
end
# Performs a POST request with the given parameters. See +#get+ for more
# details.
- def post(path, parameters = nil, headers_or_env = nil)
- process :post, path, parameters, headers_or_env
+ def post(path, *args)
+ process_with_kwargs(:post, path, *args)
end
# Performs a PATCH request with the given parameters. See +#get+ for more
# details.
- def patch(path, parameters = nil, headers_or_env = nil)
- process :patch, path, parameters, headers_or_env
+ def patch(path, *args)
+ process_with_kwargs(:patch, path, *args)
end
# Performs a PUT request with the given parameters. See +#get+ for more
# details.
- def put(path, parameters = nil, headers_or_env = nil)
- process :put, path, parameters, headers_or_env
+ def put(path, *args)
+ process_with_kwargs(:put, path, *args)
end
# Performs a DELETE request with the given parameters. See +#get+ for
# more details.
- def delete(path, parameters = nil, headers_or_env = nil)
- process :delete, path, parameters, headers_or_env
+ def delete(path, *args)
+ process_with_kwargs(:delete, path, *args)
end
# Performs a HEAD request with the given parameters. See +#get+ for more
# details.
- def head(path, parameters = nil, headers_or_env = nil)
- process :head, path, parameters, headers_or_env
+ def head(path, *args)
+ process_with_kwargs(:head, path, *args)
end
# Performs an XMLHttpRequest request with the given parameters, mirroring
@@ -68,11 +75,29 @@ module ActionDispatch
# The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or
# +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart
# string; the headers are a hash.
- def xml_http_request(request_method, path, parameters = nil, headers_or_env = nil)
- headers_or_env ||= {}
- headers_or_env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
- headers_or_env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
- process(request_method, path, parameters, headers_or_env)
+ #
+ # Example:
+ #
+ # xhr :get, '/feed', params: { since: 201501011400 }
+ def xml_http_request(request_method, path, *args)
+ if kwarg_request?(*args)
+ params, headers, env = args.first.values_at(:params, :headers, :env)
+ else
+ params = args[0]
+ headers = args[1]
+ env = {}
+
+ if params.present? || headers.present?
+ non_kwarg_request_warning
+ end
+ end
+
+ ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc)
+ xhr and xml_http_request methods are deprecated in favor of
+ `get "/posts", xhr: true` and `post "/posts/1", xhr: true`
+ MSG
+
+ process(request_method, path, params: params, headers: headers, xhr: true)
end
alias xhr :xml_http_request
@@ -89,40 +114,52 @@ module ActionDispatch
# redirect. Note that the redirects are followed until the response is
# not a redirect--this means you may run into an infinite loop if your
# redirect loops back to itself.
- def request_via_redirect(http_method, path, parameters = nil, headers_or_env = nil)
- process(http_method, path, parameters, headers_or_env)
+ #
+ # Example:
+ #
+ # request_via_redirect :post, '/welcome',
+ # params: { ref_id: 14 },
+ # headers: { "X-Test-Header" => "testvalue" }
+ def request_via_redirect(http_method, path, *args)
+ process_with_kwargs(http_method, path, *args)
+
follow_redirect! while redirect?
status
end
# Performs a GET request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
- def get_via_redirect(path, parameters = nil, headers_or_env = nil)
- request_via_redirect(:get, path, parameters, headers_or_env)
+ def get_via_redirect(path, *args)
+ ActiveSupport::Deprecation.warn('`get_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.')
+ request_via_redirect(:get, path, *args)
end
# Performs a POST request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
- def post_via_redirect(path, parameters = nil, headers_or_env = nil)
- request_via_redirect(:post, path, parameters, headers_or_env)
+ def post_via_redirect(path, *args)
+ ActiveSupport::Deprecation.warn('`post_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.')
+ request_via_redirect(:post, path, *args)
end
# Performs a PATCH request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
- def patch_via_redirect(path, parameters = nil, headers_or_env = nil)
- request_via_redirect(:patch, path, parameters, headers_or_env)
+ def patch_via_redirect(path, *args)
+ ActiveSupport::Deprecation.warn('`patch_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.')
+ request_via_redirect(:patch, path, *args)
end
# Performs a PUT request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
- def put_via_redirect(path, parameters = nil, headers_or_env = nil)
- request_via_redirect(:put, path, parameters, headers_or_env)
+ def put_via_redirect(path, *args)
+ ActiveSupport::Deprecation.warn('`put_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.')
+ request_via_redirect(:put, path, *args)
end
# Performs a DELETE request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
- def delete_via_redirect(path, parameters = nil, headers_or_env = nil)
- request_via_redirect(:delete, path, parameters, headers_or_env)
+ def delete_via_redirect(path, *args)
+ ActiveSupport::Deprecation.warn('`delete_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.')
+ request_via_redirect(:delete, path, *args)
end
end
@@ -185,15 +222,6 @@ module ActionDispatch
super()
@app = app
- # If the app is a Rails app, make url_helpers available on the session
- # This makes app.url_for and app.foo_path available in the console
- if app.respond_to?(:routes)
- singleton_class.class_eval do
- include app.routes.url_helpers
- include app.routes.mounted_helpers
- end
- end
-
reset!
end
@@ -261,8 +289,38 @@ module ActionDispatch
@_mock_session ||= Rack::MockSession.new(@app, host)
end
+ def process_with_kwargs(http_method, path, *args)
+ if kwarg_request?(*args)
+ process(http_method, path, *args)
+ else
+ non_kwarg_request_warning if args.present?
+ process(http_method, path, { params: args[0], headers: args[1] })
+ end
+ end
+
+ REQUEST_KWARGS = %i(params headers env xhr)
+ def kwarg_request?(*args)
+ args[0].respond_to?(:keys) && args[0].keys.any? { |k| REQUEST_KWARGS.include?(k) }
+ end
+
+ def non_kwarg_request_warning
+ ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc)
+ ActionDispatch::IntegrationTest HTTP request methods will accept only
+ the following keyword arguments in future Rails versions:
+ #{REQUEST_KWARGS.join(', ')}
+
+ Examples:
+
+ get '/profile',
+ params: { id: 1 },
+ headers: { 'X-Extra-Header' => '123' },
+ env: { 'action_dispatch.custom' => 'custom' },
+ xhr: true
+ MSG
+ end
+
# Performs the actual request.
- def process(method, path, parameters = nil, headers_or_env = nil)
+ def process(method, path, params: nil, headers: nil, env: nil, xhr: false)
if path =~ %r{://}
location = URI.parse(path)
https! URI::HTTPS === location if location.scheme
@@ -272,9 +330,9 @@ module ActionDispatch
hostname, port = host.split(':')
- env = {
+ request_env = {
:method => method,
- :params => parameters,
+ :params => params,
"SERVER_NAME" => hostname,
"SERVER_PORT" => port || (https? ? "443" : "80"),
@@ -287,25 +345,37 @@ module ActionDispatch
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
"HTTP_ACCEPT" => accept
}
- # this modifies the passed env directly
- Http::Headers.new(env).merge!(headers_or_env || {})
+
+ if xhr
+ headers ||= {}
+ headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
+ headers['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
+ end
+
+ # this modifies the passed request_env directly
+ if headers.present?
+ Http::Headers.new(request_env).merge!(headers)
+ end
+ if env.present?
+ Http::Headers.new(request_env).merge!(env)
+ end
session = Rack::Test::Session.new(_mock_session)
# NOTE: rack-test v0.5 doesn't build a default uri correctly
# Make sure requested path is always a full uri
- session.request(build_full_uri(path, env), env)
+ session.request(build_full_uri(path, request_env), request_env)
@request_count += 1
@request = ActionDispatch::Request.new(session.last_request.env)
response = _mock_session.last_response
- @response = ActionDispatch::TestResponse.new(response.status, response.headers, response.body)
+ @response = ActionDispatch::TestResponse.from_response(response)
@html_document = nil
@url_options = nil
@controller = session.last_request.env['action_controller.instance']
- return response.status
+ response.status
end
def build_full_uri(path, env)
@@ -316,23 +386,51 @@ module ActionDispatch
module Runner
include ActionDispatch::Assertions
- def app
- @app ||= nil
+ APP_SESSIONS = {}
+
+ attr_reader :app
+
+ def before_setup
+ @app = nil
+ @integration_session = nil
+ super
+ end
+
+ def integration_session
+ @integration_session ||= create_session(app)
end
# Reset the current session. This is useful for testing multiple sessions
# in a single test case.
def reset!
- @integration_session = Integration::Session.new(app)
+ @integration_session = create_session(app)
+ end
+
+ def create_session(app)
+ klass = APP_SESSIONS[app] ||= Class.new(Integration::Session) {
+ # If the app is a Rails app, make url_helpers available on the session
+ # This makes app.url_for and app.foo_path available in the console
+ if app.respond_to?(:routes)
+ include app.routes.url_helpers
+ include app.routes.mounted_helpers
+ end
+ }
+ klass.new(app)
+ end
+
+ def remove! # :nodoc:
+ @integration_session = nil
end
%w(get post patch put head delete cookies assigns
xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
define_method(method) do |*args|
- reset! unless integration_session
- reset_template_assertion
- # reset the html_document variable, but only for new get/post calls
- @html_document = nil unless method == 'cookies' || method == 'assigns'
+ # 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
copy_session_variables!
end
@@ -358,19 +456,16 @@ module ActionDispatch
# Copy the instance variables from the current session instance into the
# test instance.
def copy_session_variables! #:nodoc:
- return unless integration_session
- %w(controller response request).each do |var|
- instance_variable_set("@#{var}", @integration_session.__send__(var))
- end
+ @controller = @integration_session.controller
+ @response = @integration_session.response
+ @request = @integration_session.request
end
def default_url_options
- reset! unless integration_session
integration_session.default_url_options
end
def default_url_options=(options)
- reset! unless integration_session
integration_session.default_url_options = options
end
@@ -380,7 +475,6 @@ module ActionDispatch
# Delegate unhandled messages to the current session instance.
def method_missing(sym, *args, &block)
- reset! unless integration_session
if integration_session.respond_to?(sym)
integration_session.__send__(sym, *args, &block).tap do
copy_session_variables!
@@ -389,11 +483,6 @@ module ActionDispatch
super
end
end
-
- private
- def integration_session
- @integration_session ||= nil
- end
end
end
@@ -416,8 +505,8 @@ module ActionDispatch
# assert_equal 200, status
#
# # post the login and follow through to the home page
- # post "/login", username: people(:jamis).username,
- # password: people(:jamis).password
+ # post "/login", params: { username: people(:jamis).username,
+ # password: people(:jamis).password }
# follow_redirect!
# assert_equal 200, status
# assert_equal "/home", path
@@ -456,7 +545,7 @@ module ActionDispatch
# end
#
# def speak(room, message)
- # xml_http_request "/say/#{room.id}", message: message
+ # post "/say/#{room.id}", xhr: true, params: { message: message }
# assert(...)
# ...
# end
@@ -466,12 +555,91 @@ module ActionDispatch
# open_session do |sess|
# sess.extend(CustomAssertions)
# who = people(who)
- # sess.post "/login", username: who.username,
- # password: who.password
+ # sess.post "/login", params: { username: who.username,
+ # password: who.password }
# assert(...)
# end
# end
# end
+ #
+ # Another longer example would be:
+ #
+ # A simple integration test that exercises multiple controllers:
+ #
+ # require 'test_helper'
+ #
+ # class UserFlowsTest < ActionDispatch::IntegrationTest
+ # test "login and browse site" do
+ # # login via https
+ # https!
+ # get "/login"
+ # assert_response :success
+ #
+ # post "/login", params: { username: users(:david).username, password: users(:david).password }
+ # follow_redirect!
+ # assert_equal '/welcome', path
+ # assert_equal 'Welcome david!', flash[:notice]
+ #
+ # https!(false)
+ # get "/articles/all"
+ # assert_response :success
+ # assert assigns(:articles)
+ # end
+ # end
+ #
+ # As you can see the integration test involves multiple controllers and
+ # exercises the entire stack from database to dispatcher. In addition you can
+ # have multiple session instances open simultaneously in a test and extend
+ # those instances with assertion methods to create a very powerful testing
+ # DSL (domain-specific language) just for your application.
+ #
+ # Here's an example of multiple sessions and custom DSL in an integration test
+ #
+ # require 'test_helper'
+ #
+ # class UserFlowsTest < ActionDispatch::IntegrationTest
+ # test "login and browse site" do
+ # # User david logs in
+ # david = login(:david)
+ # # User guest logs in
+ # guest = login(:guest)
+ #
+ # # Both are now available in different sessions
+ # assert_equal 'Welcome david!', david.flash[:notice]
+ # assert_equal 'Welcome guest!', guest.flash[:notice]
+ #
+ # # User david can browse site
+ # david.browses_site
+ # # User guest can browse site as well
+ # guest.browses_site
+ #
+ # # Continue with other assertions
+ # end
+ #
+ # private
+ #
+ # module CustomDsl
+ # def browses_site
+ # get "/products/all"
+ # assert_response :success
+ # assert assigns(:products)
+ # end
+ # end
+ #
+ # def login(user)
+ # open_session do |sess|
+ # sess.extend(CustomDsl)
+ # u = users(user)
+ # sess.https!
+ # sess.post "/login", params: { username: u.username, password: u.password }
+ # assert_equal '/welcome', sess.path
+ # sess.https!(false)
+ # end
+ # end
+ # end
+ #
+ # Consult the Rails Testing Guide for more.
+
class IntegrationTest < ActiveSupport::TestCase
include Integration::Runner
include ActionController::TemplateAssertions
@@ -492,12 +660,11 @@ module ActionDispatch
end
def url_options
- reset! unless integration_session
integration_session.url_options
end
def document_root_element
- html_document
+ html_document.root
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb
index de3dc5f924..4b9a088265 100644
--- a/actionpack/lib/action_dispatch/testing/test_request.rb
+++ b/actionpack/lib/action_dispatch/testing/test_request.rb
@@ -60,7 +60,7 @@ module ActionDispatch
def accept=(mime_types)
@env.delete('action_dispatch.request.accepts')
- @env['HTTP_ACCEPT'] = Array(mime_types).collect { |mime_type| mime_type.to_s }.join(",")
+ @env['HTTP_ACCEPT'] = Array(mime_types).collect(&:to_s).join(",")
end
alias :rack_cookies :cookies
diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb
index 82039e72e7..a9b88ac5fd 100644
--- a/actionpack/lib/action_dispatch/testing/test_response.rb
+++ b/actionpack/lib/action_dispatch/testing/test_response.rb
@@ -7,11 +7,7 @@ module ActionDispatch
# See Response for more information on controller response objects.
class TestResponse < Response
def self.from_response(response)
- new.tap do |resp|
- resp.status = response.status
- resp.headers = response.headers
- resp.body = response.body
- end
+ new response.status, response.headers, response.body, default_headers: nil
end
# Was the response successful?
diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb
index 77f656d6f1..f664dab620 100644
--- a/actionpack/lib/action_pack.rb
+++ b/actionpack/lib/action_pack.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2014 David Heinemeier Hansson
+# Copyright (c) 2004-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_pack/gem_version.rb b/actionpack/lib/action_pack/gem_version.rb
index 9b3ea30f69..255ac9f4ed 100644
--- a/actionpack/lib/action_pack/gem_version.rb
+++ b/actionpack/lib/action_pack/gem_version.rb
@@ -5,10 +5,10 @@ module ActionPack
end
module VERSION
- MAJOR = 4
- MINOR = 2
+ MAJOR = 5
+ MINOR = 0
TINY = 0
- PRE = "beta4"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionpack/test/abstract/callbacks_test.rb b/actionpack/test/abstract/callbacks_test.rb
index 8cba049485..07571602e4 100644
--- a/actionpack/test/abstract/callbacks_test.rb
+++ b/actionpack/test/abstract/callbacks_test.rb
@@ -267,9 +267,11 @@ module AbstractController
end
class AliasedCallbacks < ControllerWithCallbacks
- before_filter :first
- after_filter :second
- around_filter :aroundz
+ ActiveSupport::Deprecation.silence do
+ before_filter :first
+ after_filter :second
+ around_filter :aroundz
+ end
def first
@text = "Hello world"
diff --git a/actionpack/test/abstract/translation_test.rb b/actionpack/test/abstract/translation_test.rb
index 4fdc480b43..8289252dfc 100644
--- a/actionpack/test/abstract/translation_test.rb
+++ b/actionpack/test/abstract/translation_test.rb
@@ -9,6 +9,22 @@ module AbstractController
class TranslationControllerTest < ActiveSupport::TestCase
def setup
@controller = TranslationController.new
+ I18n.backend.store_translations(:en, {
+ one: {
+ two: 'bar',
+ },
+ abstract_controller: {
+ testing: {
+ translation: {
+ index: {
+ foo: 'bar',
+ },
+ no_action: 'no_action_tr',
+ },
+ },
+ },
+ })
+ @controller.stubs(action_name: :index)
end
def test_action_controller_base_responds_to_translate
@@ -28,16 +44,19 @@ module AbstractController
end
def test_lazy_lookup
- expected = 'bar'
- @controller.stubs(action_name: :index)
- I18n.stubs(:translate).with('abstract_controller.testing.translation.index.foo').returns(expected)
- assert_equal expected, @controller.t('.foo')
+ assert_equal 'bar', @controller.t('.foo')
+ end
+
+ def test_lazy_lookup_with_symbol
+ assert_equal 'bar', @controller.t(:'.foo')
+ end
+
+ def test_lazy_lookup_fallback
+ assert_equal 'no_action_tr', @controller.t(:'.no_action')
end
def test_default_translation
- key, expected = 'one.two', 'bar'
- I18n.stubs(:translate).with(key).returns(expected)
- assert_equal expected, @controller.t(key)
+ assert_equal 'bar', @controller.t('one.two')
end
def test_localize
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 69312e4c22..c1be2c9afe 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -14,13 +14,18 @@ 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
require 'active_support/testing/autorun'
require 'abstract_controller'
+require 'abstract_controller/railties/routes_helpers'
require 'action_controller'
require 'action_view'
require 'action_view/testing/resolvers'
@@ -53,23 +58,8 @@ I18n.enforce_available_locales = false
# Register danish language for testing
I18n.backend.store_translations 'da', {}
I18n.backend.store_translations 'pt-BR', {}
-ORIGINAL_LOCALES = I18n.available_locales.map {|locale| locale.to_s }.sort
FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
-FIXTURES = Pathname.new(FIXTURE_LOAD_PATH)
-
-module RackTestUtils
- def body_to_string(body)
- if body.respond_to?(:each)
- str = ""
- body.each {|s| str << s }
- str
- else
- body
- end
- end
- extend self
-end
SharedTestRoutes = ActionDispatch::Routing::RouteSet.new
@@ -109,7 +99,7 @@ end
module ActiveSupport
class TestCase
include ActionDispatch::DrawOnce
- if ActiveSupport::Testing::Isolation.forking_env? && PROCESS_COUNT > 0
+ if RUBY_ENGINE == "ruby" && PROCESS_COUNT > 0
parallelize_me!
end
end
@@ -128,22 +118,6 @@ class RoutedRackApp
end
end
-class BasicController
- attr_accessor :request
-
- def config
- @config ||= ActiveSupport::InheritableOptions.new(ActionController::Base.config).tap do |config|
- # VIEW TODO: View tests should not require a controller
- public_dir = File.expand_path("../fixtures/public", __FILE__)
- config.assets_dir = public_dir
- config.javascripts_dir = "#{public_dir}/javascripts"
- config.stylesheets_dir = "#{public_dir}/stylesheets"
- config.assets = ActiveSupport::InheritableOptions.new({ :prefix => "assets" })
- config
- end
- end
-end
-
class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
include ActionDispatch::SharedRoutes
@@ -194,6 +168,7 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
yield temporary_routes
ensure
self.class.app = old_app
+ self.remove!
silence_warnings { Object.const_set(:SharedTestRoutes, old_routes) }
end
@@ -259,7 +234,7 @@ end
module ActionController
class Base
# This stub emulates the Railtie including the URL helpers from a Rails application
- include SharedTestRoutes.url_helpers
+ extend AbstractController::Railties::RoutesHelpers.with(SharedTestRoutes)
include SharedTestRoutes.mounted_helpers
self.view_paths = FIXTURE_LOAD_PATH
@@ -508,12 +483,7 @@ class ForkingExecutor
end
end
-if ActiveSupport::Testing::Isolation.forking_env? && PROCESS_COUNT > 0
+if RUBY_ENGINE == "ruby" && PROCESS_COUNT > 0
# Use N processes (N defaults to 4)
Minitest.parallel_executor = ForkingExecutor.new(PROCESS_COUNT)
end
-
-# FIXME: we have tests that depend on run order, we should fix that and
-# remove this method call.
-require 'active_support/test_case'
-ActiveSupport::TestCase.test_order = :sorted
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index f2a4503f13..21586e2193 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -378,7 +378,9 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
end
def test_render_based_on_parameters
- process :render_based_on_parameters, "GET", "name" => "David"
+ process :render_based_on_parameters,
+ method: "GET",
+ params: { name: "David" }
assert_equal "Mr. David", @response.body
end
@@ -575,6 +577,13 @@ class AssertTemplateTest < ActionController::TestCase
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"
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
index 950788743e..f7ad8e5158 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -1,31 +1,11 @@
require 'abstract_unit'
require 'active_support/logger'
require 'controller/fake_models'
-require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late
# Provide some controller to run the tests on.
module Submodule
class ContainedEmptyController < ActionController::Base
end
-
- class ContainedNonEmptyController < ActionController::Base
- def public_action
- render :nothing => true
- end
-
- hide_action :hidden_action
- def hidden_action
- raise "Noooo!"
- end
-
- def another_hidden_action
- end
- hide_action :another_hidden_action
- end
-
- class SubclassedController < ContainedNonEmptyController
- hide_action :public_action # Hiding it here should not affect the superclass.
- end
end
class EmptyController < ActionController::Base
@@ -35,10 +15,6 @@ class NonEmptyController < ActionController::Base
def public_action
render :nothing => true
end
-
- hide_action :hidden_action
- def hidden_action
- end
end
class DefaultUrlOptionsController < ActionController::Base
@@ -51,6 +27,16 @@ class DefaultUrlOptionsController < ActionController::Base
end
end
+class OptionalDefaultUrlOptionsController < ActionController::Base
+ def show
+ render nothing: true
+ end
+
+ def default_url_options
+ { format: 'atom', id: 'default-id' }
+ end
+end
+
class UrlOptionsController < ActionController::Base
def from_view
render :inline => "<%= #{params[:route]} %>"
@@ -108,10 +94,7 @@ class ControllerInstanceTests < ActiveSupport::TestCase
def setup
@empty = EmptyController.new
@contained = Submodule::ContainedEmptyController.new
- @empty_controllers = [@empty, @contained, Submodule::SubclassedController.new]
-
- @non_empty_controllers = [NonEmptyController.new,
- Submodule::ContainedNonEmptyController.new]
+ @empty_controllers = [@empty, @contained]
end
def test_performed?
@@ -124,10 +107,6 @@ class ControllerInstanceTests < ActiveSupport::TestCase
@empty_controllers.each do |c|
assert_equal Set.new, c.class.action_methods, "#{c.controller_path} should be empty!"
end
-
- @non_empty_controllers.each do |c|
- assert_equal Set.new(%w(public_action)), c.class.action_methods, "#{c.controller_path} should not be empty!"
- end
end
def test_temporary_anonymous_controllers
@@ -161,12 +140,6 @@ class PerformActionTest < ActionController::TestCase
assert_equal "The action 'non_existent' could not be found for EmptyController", exception.message
end
- def test_get_on_hidden_should_fail
- use_controller NonEmptyController
- assert_raise(AbstractController::ActionNotFound) { get :hidden_action }
- assert_raise(AbstractController::ActionNotFound) { get :another_hidden_action }
- end
-
def test_action_missing_should_work
use_controller ActionMissingController
get :arbitrary_action
@@ -205,7 +178,7 @@ class UrlOptionsTest < ActionController::TestCase
get ':controller/:action'
end
- get :from_view, :route => "from_view_url"
+ get :from_view, params: { route: "from_view_url" }
assert_equal 'http://www.override.com/from_view', @response.body
assert_equal 'http://www.override.com/from_view', @controller.send(:from_view_url)
@@ -239,7 +212,7 @@ class DefaultUrlOptionsTest < ActionController::TestCase
get ':controller/:action'
end
- get :from_view, :route => "from_view_url"
+ get :from_view, params: { route: "from_view_url" }
assert_equal 'http://www.override.com/from_view?locale=en', @response.body
assert_equal 'http://www.override.com/from_view?locale=en', @controller.send(:from_view_url)
@@ -256,7 +229,7 @@ class DefaultUrlOptionsTest < ActionController::TestCase
get ':controller/:action'
end
- get :from_view, :route => "description_path(1)"
+ get :from_view, params: { route: "description_path(1)" }
assert_equal '/en/descriptions/1', @response.body
assert_equal '/en/descriptions', @controller.send(:descriptions_path)
@@ -271,7 +244,18 @@ class DefaultUrlOptionsTest < ActionController::TestCase
assert_equal '/en/descriptions/1.xml', @controller.send(:description_path, 1, :format => "xml")
end
end
+end
+class OptionalDefaultUrlOptionsControllerTest < ActionController::TestCase
+ def test_default_url_options_override_missing_positional_arguments
+ with_routing do |set|
+ set.draw do
+ get "/things/:id(.:format)" => 'things#show', :as => :thing
+ end
+ assert_equal '/things/1.atom', thing_path('1')
+ assert_equal '/things/default-id.atom', thing_path
+ end
+ end
end
class EmptyUrlOptionsTest < ActionController::TestCase
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index c0e6a2ebd1..2d6607041d 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -1,5 +1,6 @@
require 'fileutils'
require 'abstract_unit'
+require 'lib/controller/fake_models'
CACHE_DIR = 'test_cache'
# Don't change '/../temp/' cavalierly or you might hose something you don't want hosed
@@ -210,7 +211,7 @@ CACHED
end
def test_skipping_fragment_cache_digesting
- get :fragment_cached_without_digest, :format => "html"
+ get :fragment_cached_without_digest, format: "html"
assert_response :success
expected_body = "<body>\n<p>ERB</p>\n</body>\n"
@@ -244,7 +245,7 @@ CACHED
end
def test_html_formatted_fragment_caching
- get :formatted_fragment_cached, :format => "html"
+ get :formatted_fragment_cached, format: "html"
assert_response :success
expected_body = "<body>\n<p>ERB</p>\n</body>\n"
@@ -255,7 +256,7 @@ CACHED
end
def test_xml_formatted_fragment_caching
- get :formatted_fragment_cached, :format => "xml"
+ get :formatted_fragment_cached, format: "xml"
assert_response :success
expected_body = "<body>\n <p>Builder</p>\n</body>\n"
@@ -269,7 +270,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"
assert_response :success
expected_body = "<body>\n<p>PHONE</p>\n</body>\n"
@@ -349,3 +350,60 @@ class ViewCacheDependencyTest < ActionController::TestCase
assert_equal %w(trombone flute), HasDependenciesController.new.view_cache_dependencies
end
end
+
+class CollectionCacheController < ActionController::Base
+ def index
+ @customers = [Customer.new('david', params[:id] || 1)]
+ end
+
+ def index_ordered
+ @customers = [Customer.new('david', 1), Customer.new('david', 2), Customer.new('david', 3)]
+ render 'index'
+ end
+
+ def index_explicit_render
+ @customers = [Customer.new('david', 1)]
+ render partial: 'customers/customer', collection: @customers
+ end
+
+ def index_with_comment
+ @customers = [Customer.new('david', 1)]
+ render partial: 'customers/commented_customer', collection: @customers, as: :customer
+ end
+end
+
+class AutomaticCollectionCacheTest < ActionController::TestCase
+ def setup
+ super
+ @controller = CollectionCacheController.new
+ @controller.perform_caching = true
+ @controller.cache_store = ActiveSupport::Cache::MemoryStore.new
+ end
+
+ def test_collection_fetches_cached_views
+ get :index
+
+ ActionView::PartialRenderer.expects(:collection_with_template).never
+ get :index
+ end
+
+ def test_preserves_order_when_reading_from_cache_plus_rendering
+ get :index, params: { id: 2 }
+ get :index_ordered
+
+ assert_select ':root', "david, 1\n david, 2\n david, 3"
+ end
+
+ def test_explicit_render_call_with_options
+ get :index_explicit_render
+
+ assert_select ':root', "david, 1"
+ end
+
+ def test_caching_works_with_beginning_comment
+ get :index_with_comment
+
+ ActionView::PartialRenderer.expects(:collection_with_template).never
+ get :index_with_comment
+ end
+end
diff --git a/actionpack/test/controller/default_url_options_with_before_action_test.rb b/actionpack/test/controller/default_url_options_with_before_action_test.rb
index 656fd0431e..230f40d7ad 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
@@ -1,6 +1,5 @@
require 'abstract_unit'
-
class ControllerWithBeforeActionAndDefaultUrlOptions < ActionController::Base
before_action { I18n.locale = params[:locale] }
@@ -23,7 +22,7 @@ class ControllerWithBeforeActionAndDefaultUrlOptionsTest < ActionController::Tes
# This test has its roots in issue #1872
test "should redirect with correct locale :de" do
- get :redirect, :locale => "de"
+ get :redirect, params: { locale: "de" }
assert_redirected_to "/controller_with_before_action_and_default_url_options/target?locale=de"
end
end
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
index b2b01b3fa9..a1ce12a13e 100644
--- a/actionpack/test/controller/filters_test.rb
+++ b/actionpack/test/controller/filters_test.rb
@@ -10,7 +10,7 @@ class ActionController::Base
def before_actions
filters = _process_action_callbacks.select { |c| c.kind == :before }
- filters.map! { |c| c.raw_filter }
+ filters.map!(&:raw_filter)
end
end
@@ -26,7 +26,6 @@ class ActionController::Base
end
class FilterTest < ActionController::TestCase
-
class TestController < ActionController::Base
before_action :ensure_login
after_action :clean_up
@@ -225,6 +224,30 @@ class FilterTest < ActionController::TestCase
skip_before_action :clean_up_tmp, if: -> { true }
end
+ class SkipFilterUsingOnlyAndIf < ConditionalFilterController
+ before_action :clean_up_tmp
+ before_action :ensure_login
+
+ skip_before_action :ensure_login, only: :login, if: -> { false }
+ skip_before_action :clean_up_tmp, only: :login, if: -> { true }
+
+ def login
+ render text: 'ok'
+ end
+ end
+
+ class SkipFilterUsingIfAndExcept < ConditionalFilterController
+ before_action :clean_up_tmp
+ before_action :ensure_login
+
+ skip_before_action :ensure_login, if: -> { false }, except: :login
+ skip_before_action :clean_up_tmp, if: -> { true }, except: :login
+
+ def login
+ render text: 'ok'
+ end
+ end
+
class ClassController < ConditionalFilterController
before_action ConditionalClassFilter
end
@@ -504,7 +527,6 @@ class FilterTest < ActionController::TestCase
def non_yielding_action
@filters << "it didn't yield"
- @filter_return_value
end
def action_three
@@ -528,32 +550,15 @@ class FilterTest < ActionController::TestCase
end
end
- def test_non_yielding_around_actions_not_returning_false_do_not_raise
- controller = NonYieldingAroundFilterController.new
- controller.instance_variable_set "@filter_return_value", true
- assert_nothing_raised do
- test_process(controller, "index")
- end
- end
-
- def test_non_yielding_around_actions_returning_false_do_not_raise
+ def test_non_yielding_around_actions_do_not_raise
controller = NonYieldingAroundFilterController.new
- controller.instance_variable_set "@filter_return_value", false
assert_nothing_raised do
test_process(controller, "index")
end
end
- def test_after_actions_are_not_run_if_around_action_returns_false
- controller = NonYieldingAroundFilterController.new
- controller.instance_variable_set "@filter_return_value", false
- test_process(controller, "index")
- assert_equal ["filter_one", "it didn't yield"], controller.assigns['filters']
- end
-
def test_after_actions_are_not_run_if_around_action_does_not_yield
controller = NonYieldingAroundFilterController.new
- controller.instance_variable_set "@filter_return_value", true
test_process(controller, "index")
assert_equal ["filter_one", "it didn't yield"], controller.assigns['filters']
end
@@ -614,6 +619,16 @@ class FilterTest < ActionController::TestCase
assert_equal %w( ensure_login ), assigns["ran_filter"]
end
+ def test_if_is_ignored_when_used_with_only
+ test_process(SkipFilterUsingOnlyAndIf, 'login')
+ assert_nil assigns['ran_filter']
+ end
+
+ def test_except_is_ignored_when_used_with_if
+ test_process(SkipFilterUsingIfAndExcept, 'login')
+ assert_equal %w(ensure_login), assigns["ran_filter"]
+ end
+
def test_skipping_class_actions
test_process(ClassController)
assert_equal true, assigns["ran_class_action"]
@@ -951,8 +966,15 @@ class ControllerWithAllTypesOfFilters < PostsController
end
class ControllerWithTwoLessFilters < ControllerWithAllTypesOfFilters
- skip_action_callback :around_again
- skip_action_callback :after
+ skip_around_action :around_again
+ skip_after_action :after
+end
+
+class SkipFilterUsingSkipActionCallback < ControllerWithAllTypesOfFilters
+ ActiveSupport::Deprecation.silence do
+ skip_action_callback :around_again
+ skip_action_callback :after
+ end
end
class YieldingAroundFiltersTest < ActionController::TestCase
@@ -1021,24 +1043,45 @@ class YieldingAroundFiltersTest < ActionController::TestCase
def test_first_action_in_multiple_before_action_chain_halts
controller = ::FilterTest::TestMultipleFiltersController.new
response = test_process(controller, 'fail_1')
- assert_equal ' ', response.body
+ assert_equal '', response.body
assert_equal 1, controller.instance_variable_get(:@try)
end
def test_second_action_in_multiple_before_action_chain_halts
controller = ::FilterTest::TestMultipleFiltersController.new
response = test_process(controller, 'fail_2')
- assert_equal ' ', response.body
+ assert_equal '', response.body
assert_equal 2, controller.instance_variable_get(:@try)
end
def test_last_action_in_multiple_before_action_chain_halts
controller = ::FilterTest::TestMultipleFiltersController.new
response = test_process(controller, 'fail_3')
- assert_equal ' ', response.body
+ assert_equal '', response.body
assert_equal 3, controller.instance_variable_get(:@try)
end
+ def test_skipping_with_skip_action_callback
+ test_process(SkipFilterUsingSkipActionCallback,'no_raise')
+ assert_equal 'before around (before yield) around (after yield)', assigns['ran_filter'].join(' ')
+ end
+
+ def test_deprecated_skip_action_callback
+ assert_deprecated do
+ Class.new(PostsController) do
+ skip_action_callback :clean_up
+ end
+ end
+ end
+
+ def test_deprecated_skip_filter
+ assert_deprecated do
+ Class.new(PostsController) do
+ skip_filter :clean_up
+ end
+ end
+ end
+
protected
def test_process(controller, action = "show")
@controller = controller.is_a?(Class) ? controller.new : controller
diff --git a/actionpack/test/controller/flash_hash_test.rb b/actionpack/test/controller/flash_hash_test.rb
index 50b36a0567..081288ef21 100644
--- a/actionpack/test/controller/flash_hash_test.rb
+++ b/actionpack/test/controller/flash_hash_test.rb
@@ -29,6 +29,15 @@ module ActionDispatch
assert_equal 'world', @hash['hello']
end
+ def test_key
+ @hash['foo'] = 'bar'
+
+ assert @hash.key?('foo')
+ assert @hash.key?(:foo)
+ assert_not @hash.key?('bar')
+ assert_not @hash.key?(:bar)
+ end
+
def test_delete
@hash['foo'] = 'bar'
@hash.delete 'foo'
@@ -48,33 +57,36 @@ module ActionDispatch
def test_to_session_value
@hash['foo'] = 'bar'
- assert_equal({'flashes' => {'foo' => 'bar'}, 'discard' => []}, @hash.to_session_value)
-
- @hash.discard('foo')
- assert_equal({'flashes' => {'foo' => 'bar'}, 'discard' => %w[foo]}, @hash.to_session_value)
+ assert_equal({'flashes' => {'foo' => 'bar'}}, @hash.to_session_value)
@hash.now['qux'] = 1
- assert_equal({'flashes' => {'foo' => 'bar', 'qux' => 1}, 'discard' => %w[foo qux]}, @hash.to_session_value)
+ assert_equal({'flashes' => {'foo' => 'bar'}}, @hash.to_session_value)
+
+ @hash.discard('foo')
+ assert_equal(nil, @hash.to_session_value)
@hash.sweep
assert_equal(nil, @hash.to_session_value)
end
def test_from_session_value
- rails_3_2_cookie = 'BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJWY4ZTFiODE1MmJhNzYwOWMyOGJiYjE3ZWM5MjYzYmE3BjsAVEkiCmZsYXNoBjsARm86JUFjdGlvbkRpc3BhdGNoOjpGbGFzaDo6Rmxhc2hIYXNoCToKQHVzZWRvOghTZXQGOgpAaGFzaHsAOgxAY2xvc2VkRjoNQGZsYXNoZXN7BkkiDG1lc3NhZ2UGOwBGSSIKSGVsbG8GOwBGOglAbm93MA=='
+ # {"session_id"=>"f8e1b8152ba7609c28bbb17ec9263ba7", "flash"=>#<ActionDispatch::Flash::FlashHash:0x00000000000000 @used=#<Set: {"farewell"}>, @closed=false, @flashes={"greeting"=>"Hello", "farewell"=>"Goodbye"}, @now=nil>}
+ rails_3_2_cookie = 'BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJWY4ZTFiODE1MmJhNzYwOWMyOGJiYjE3ZWM5MjYzYmE3BjsAVEkiCmZsYXNoBjsARm86JUFjdGlvbkRpc3BhdGNoOjpGbGFzaDo6Rmxhc2hIYXNoCToKQHVzZWRvOghTZXQGOgpAaGFzaHsGSSINZmFyZXdlbGwGOwBUVDoMQGNsb3NlZEY6DUBmbGFzaGVzewdJIg1ncmVldGluZwY7AFRJIgpIZWxsbwY7AFRJIg1mYXJld2VsbAY7AFRJIgxHb29kYnllBjsAVDoJQG5vdzA='
session = Marshal.load(Base64.decode64(rails_3_2_cookie))
hash = Flash::FlashHash.from_session_value(session['flash'])
- assert_equal({'flashes' => {'message' => 'Hello'}, 'discard' => %w[message]}, hash.to_session_value)
+ assert_equal({'greeting' => 'Hello'}, hash.to_hash)
+ assert_equal(nil, hash.to_session_value)
end
def test_from_session_value_on_json_serializer
- decrypted_data = "{ \"session_id\":\"d98bdf6d129618fc2548c354c161cfb5\", \"flash\":{\"discard\":[], \"flashes\":{\"message\":\"hey you\"}} }"
+ decrypted_data = "{ \"session_id\":\"d98bdf6d129618fc2548c354c161cfb5\", \"flash\":{\"discard\":[\"farewell\"], \"flashes\":{\"greeting\":\"Hello\",\"farewell\":\"Goodbye\"}} }"
session = ActionDispatch::Cookies::JsonSerializer.load(decrypted_data)
hash = Flash::FlashHash.from_session_value(session['flash'])
- assert_equal({'discard' => %w[message], 'flashes' => { 'message' => 'hey you'}}, hash.to_session_value)
- assert_equal "hey you", hash[:message]
- assert_equal "hey you", hash["message"]
+ assert_equal({'greeting' => 'Hello'}, hash.to_hash)
+ assert_equal(nil, hash.to_session_value)
+ assert_equal "Hello", hash[:greeting]
+ assert_equal "Hello", hash["greeting"]
end
def test_empty?
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index 3720a920d0..0ff0a1ef61 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -288,16 +288,16 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest
def test_setting_flash_does_not_raise_in_following_requests
with_test_route_set do
env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new }
- get '/set_flash', nil, env
- get '/set_flash', nil, env
+ get '/set_flash', env: env
+ get '/set_flash', env: env
end
end
def test_setting_flash_now_does_not_raise_in_following_requests
with_test_route_set do
env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new }
- get '/set_flash_now', nil, env
- get '/set_flash_now', nil, env
+ get '/set_flash_now', env: env
+ get '/set_flash_now', env: env
end
end
@@ -312,9 +312,11 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest
private
# Overwrite get to send SessionSecret in env hash
- def get(path, parameters = nil, env = {})
- env["action_dispatch.key_generator"] ||= Generator
- super
+ def get(path, *args)
+ args[0] ||= {}
+ args[0][:env] ||= {}
+ args[0][:env]["action_dispatch.key_generator"] ||= Generator
+ super(path, *args)
end
def with_test_route_set
diff --git a/actionpack/test/controller/force_ssl_test.rb b/actionpack/test/controller/force_ssl_test.rb
index 00d4612ac9..a1bb1cee58 100644
--- a/actionpack/test/controller/force_ssl_test.rb
+++ b/actionpack/test/controller/force_ssl_test.rb
@@ -100,7 +100,7 @@ class ForceSSLControllerLevelTest < ActionController::TestCase
end
def test_banana_redirects_to_https_with_extra_params
- get :banana, :token => "secret"
+ get :banana, params: { token: "secret" }
assert_response 301
assert_equal "https://test.host/force_ssl_controller_level/banana?token=secret", redirect_to_url
end
@@ -273,7 +273,7 @@ class ForceSSLFormatTest < ActionController::TestCase
get '/foo', :to => 'force_ssl_controller_level#banana'
end
- get :banana, :format => :json
+ get :banana, format: :json
assert_response 301
assert_equal 'https://test.host/foo.json', redirect_to_url
end
@@ -294,7 +294,7 @@ class ForceSSLOptionalSegmentsTest < ActionController::TestCase
end
@request.env['PATH_INFO'] = '/en/foo'
- get :banana, :locale => 'en'
+ get :banana, params: { locale: 'en' }
assert_equal 'en', @controller.params[:locale]
assert_response 301
assert_equal 'https://test.host/en/foo', redirect_to_url
@@ -321,4 +321,29 @@ class RedirectToSSLTest < ActionController::TestCase
assert_response 200
assert_equal 'ihaz', response.body
end
+
+ def test_banana_redirects_to_https_if_not_https_and_flash_middleware_is_disabled
+ disable_flash
+ get :banana
+ assert_response 301
+ assert_equal 'https://test.host/redirect_to_ssl/banana', redirect_to_url
+ ensure
+ enable_flash
+ end
+
+ private
+
+ def disable_flash
+ ActionDispatch::TestRequest.class_eval do
+ alias_method :flash_origin, :flash
+ undef_method :flash
+ end
+ end
+
+ def enable_flash
+ ActionDispatch::TestRequest.class_eval do
+ alias_method :flash, :flash_origin
+ undef_method :flash_origin
+ end
+ end
end
diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb
index 936b8c2450..e263ed341f 100644
--- a/actionpack/test/controller/helper_test.rb
+++ b/actionpack/test/controller/helper_test.rb
@@ -29,7 +29,7 @@ module ImpressiveLibrary
def useful_function() end
end
-ActionController::Base.send :include, ImpressiveLibrary
+ActionController::Base.include(ImpressiveLibrary)
class JustMeController < ActionController::Base
clear_helpers
@@ -223,10 +223,10 @@ class HelperTest < ActiveSupport::TestCase
# fun/pdf_helper.rb
assert methods.include?(:foobar)
end
-
+
def test_helper_proxy_config
AllHelpersController.config.my_var = 'smth'
-
+
assert_equal 'smth', AllHelpersController.helpers.config.my_var
end
diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb
index 9052fc6962..20962a90cb 100644
--- a/actionpack/test/controller/http_basic_authentication_test.rb
+++ b/actionpack/test/controller/http_basic_authentication_test.rb
@@ -83,6 +83,13 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
assert_response :unauthorized
assert_equal "HTTP Basic: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header} and long credentials"
end
+
+ test "unsuccessful authentication with #{header.downcase} and no credentials" do
+ get :show
+
+ assert_response :unauthorized
+ assert_equal "HTTP Basic: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header} and no credentials"
+ end
end
def test_encode_credentials_has_no_newline
diff --git a/actionpack/test/controller/http_token_authentication_test.rb b/actionpack/test/controller/http_token_authentication_test.rb
index 8c6c8a0aa7..a758df2ec6 100644
--- a/actionpack/test/controller/http_token_authentication_test.rb
+++ b/actionpack/test/controller/http_token_authentication_test.rb
@@ -162,17 +162,36 @@ class HttpTokenAuthenticationTest < ActionController::TestCase
assert_equal(expected, actual)
end
+ test "token_and_options returns right token when token key is not specified in header" do
+ token = "rcHu+HzSFw89Ypyhn/896A="
+
+ actual = ActionController::HttpAuthentication::Token.token_and_options(
+ sample_request_without_token_key(token)
+ ).first
+
+ expected = token
+ assert_equal(expected, actual)
+ end
+
private
def sample_request(token, options = {nonce: "def"})
authorization = options.inject([%{Token token="#{token}"}]) do |arr, (k, v)|
arr << "#{k}=\"#{v}\""
end.join(", ")
- @sample_request ||= OpenStruct.new authorization: authorization
+ mock_authorization_request(authorization)
end
def malformed_request
- @malformed_request ||= OpenStruct.new authorization: %{Token token=}
+ mock_authorization_request(%{Token token=})
+ end
+
+ def sample_request_without_token_key(token)
+ mock_authorization_request(%{Token #{token}})
+ end
+
+ def mock_authorization_request(authorization)
+ OpenStruct.new(authorization: authorization)
end
def encode_credentials(token, options = {})
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index d91a1657b3..a87059bee4 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -32,158 +32,275 @@ class SessionTest < ActiveSupport::TestCase
def test_request_via_redirect_uses_given_method
path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue"}
- @session.expects(:process).with(:put, path, args, headers)
+ @session.expects(:process).with(:put, path, params: args, headers: headers)
@session.stubs(:redirect?).returns(false)
- @session.request_via_redirect(:put, path, args, headers)
+ @session.request_via_redirect(:put, path, params: args, headers: headers)
+ end
+
+ def test_deprecated_request_via_redirect_uses_given_method
+ path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" }
+ @session.expects(:process).with(:put, path, params: args, headers: headers)
+ @session.stubs(:redirect?).returns(false)
+ assert_deprecated { @session.request_via_redirect(:put, path, args, headers) }
end
def test_request_via_redirect_follows_redirects
path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue"}
@session.stubs(:redirect?).returns(true, true, false)
@session.expects(:follow_redirect!).times(2)
- @session.request_via_redirect(:get, path, args, headers)
+ @session.request_via_redirect(:get, path, params: args, headers: headers)
end
def test_request_via_redirect_returns_status
path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue"}
@session.stubs(:redirect?).returns(false)
@session.stubs(:status).returns(200)
- assert_equal 200, @session.request_via_redirect(:get, path, args, headers)
+ assert_equal 200, @session.request_via_redirect(:get, path, params: args, headers: headers)
end
- def test_get_via_redirect
- path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
+ def test_deprecated_get_via_redirect
+ path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" }
@session.expects(:request_via_redirect).with(:get, path, args, headers)
- @session.get_via_redirect(path, args, headers)
+
+ assert_deprecated do
+ @session.get_via_redirect(path, args, headers)
+ end
end
- def test_post_via_redirect
- path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
+ def test_deprecated_post_via_redirect
+ path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" }
@session.expects(:request_via_redirect).with(:post, path, args, headers)
- @session.post_via_redirect(path, args, headers)
+
+ assert_deprecated do
+ @session.post_via_redirect(path, args, headers)
+ end
end
- def test_patch_via_redirect
- path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
+ def test_deprecated_patch_via_redirect
+ path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" }
@session.expects(:request_via_redirect).with(:patch, path, args, headers)
- @session.patch_via_redirect(path, args, headers)
+
+ assert_deprecated do
+ @session.patch_via_redirect(path, args, headers)
+ end
end
- def test_put_via_redirect
- path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
+ def test_deprecated_put_via_redirect
+ path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" }
@session.expects(:request_via_redirect).with(:put, path, args, headers)
- @session.put_via_redirect(path, args, headers)
+
+ assert_deprecated do
+ @session.put_via_redirect(path, args, headers)
+ end
end
- def test_delete_via_redirect
- path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
+ def test_deprecated_delete_via_redirect
+ path = "/somepath"; args = { id: '1' }; headers = { "X-Test-Header" => "testvalue" }
@session.expects(:request_via_redirect).with(:delete, path, args, headers)
- @session.delete_via_redirect(path, args, headers)
+
+ assert_deprecated do
+ @session.delete_via_redirect(path, args, headers)
+ end
end
def test_get
- path = "/index"; params = "blah"; headers = {:location => 'blah'}
- @session.expects(:process).with(:get,path,params,headers)
- @session.get(path,params,headers)
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:get, path, params: params, headers: headers)
+ @session.get(path, params: params, headers: headers)
+ end
+
+ def test_get_with_env_and_headers
+ path = "/index"; params = "blah"; headers = { location: 'blah' }; env = { 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest' }
+ @session.expects(:process).with(:get, path, params: params, headers: headers, env: env)
+ @session.get(path, params: params, headers: headers, env: env)
+ end
+
+ def test_deprecated_get
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:get, path, params: params, headers: headers)
+ assert_deprecated {
+ @session.get(path, params, headers)
+ }
end
def test_post
- path = "/index"; params = "blah"; headers = {:location => 'blah'}
- @session.expects(:process).with(:post,path,params,headers)
- @session.post(path,params,headers)
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:post, path, params: params, headers: headers)
+ assert_deprecated {
+ @session.post(path, params, headers)
+ }
+ end
+
+ def test_deprecated_post
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:post, path, params: params, headers: headers)
+ @session.post(path, params: params, headers: headers)
end
def test_patch
- path = "/index"; params = "blah"; headers = {:location => 'blah'}
- @session.expects(:process).with(:patch,path,params,headers)
- @session.patch(path,params,headers)
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:patch, path, params: params, headers: headers)
+ @session.patch(path, params: params, headers: headers)
+ end
+
+ def test_deprecated_patch
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:patch, path, params: params, headers: headers)
+ assert_deprecated {
+ @session.patch(path, params, headers)
+ }
end
def test_put
- path = "/index"; params = "blah"; headers = {:location => 'blah'}
- @session.expects(:process).with(:put,path,params,headers)
- @session.put(path,params,headers)
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:put, path, params: params, headers: headers)
+ @session.put(path, params: params, headers: headers)
+ end
+
+ def test_deprecated_put
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:put, path, params: params, headers: headers)
+ assert_deprecated {
+ @session.put(path, params, headers)
+ }
end
def test_delete
- path = "/index"; params = "blah"; headers = {:location => 'blah'}
- @session.expects(:process).with(:delete,path,params,headers)
- @session.delete(path,params,headers)
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:delete, path, params: params, headers: headers)
+ assert_deprecated {
+ @session.delete(path,params,headers)
+ }
+ end
+
+ def test_deprecated_delete
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:delete, path, params: params, headers: headers)
+ @session.delete(path, params: params, headers: headers)
end
def test_head
- path = "/index"; params = "blah"; headers = {:location => 'blah'}
- @session.expects(:process).with(:head,path,params,headers)
- @session.head(path,params,headers)
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:head, path, params: params, headers: headers)
+ @session.head(path, params: params, headers: headers)
+ end
+
+ def deprecated_test_head
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:head, path, params: params, headers: headers)
+ assert_deprecated {
+ @session.head(path, params, headers)
+ }
end
def test_xml_http_request_get
- path = "/index"; params = "blah"; headers = {:location => 'blah'}
- headers_after_xhr = headers.merge(
- "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
- "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
- )
- @session.expects(:process).with(:get,path,params,headers_after_xhr)
- @session.xml_http_request(:get,path,params,headers)
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:get, path, params: params, headers: headers, xhr: true)
+ @session.get(path, params: params, headers: headers, xhr: true)
+ end
+
+ def test_deprecated_xml_http_request_get
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:get, path, params: params, headers: headers, xhr: true)
+ @session.get(path, params: params, headers: headers, xhr: true)
+ end
+
+ def test_deprecated_args_xml_http_request_get
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:get, path, params: params, headers: headers, xhr: true)
+ assert_deprecated(/xml_http_request/) {
+ @session.xml_http_request(:get, path, params, headers)
+ }
end
def test_xml_http_request_post
- path = "/index"; params = "blah"; headers = {:location => 'blah'}
- headers_after_xhr = headers.merge(
- "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
- "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
- )
- @session.expects(:process).with(:post,path,params,headers_after_xhr)
- @session.xml_http_request(:post,path,params,headers)
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:post, path, params: params, headers: headers, xhr: true)
+ @session.post(path, params: params, headers: headers, xhr: true)
+ end
+
+ def test_deprecated_xml_http_request_post
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:post, path, params: params, headers: headers, xhr: true)
+ @session.post(path, params: params, headers: headers, xhr: true)
+ end
+
+ def test_deprecated_args_xml_http_request_post
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:post, path, params: params, headers: headers, xhr: true)
+ assert_deprecated(/xml_http_request/) { @session.xml_http_request(:post,path,params,headers) }
end
def test_xml_http_request_patch
- path = "/index"; params = "blah"; headers = {:location => 'blah'}
- headers_after_xhr = headers.merge(
- "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
- "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
- )
- @session.expects(:process).with(:patch,path,params,headers_after_xhr)
- @session.xml_http_request(:patch,path,params,headers)
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:patch, path, params: params, headers: headers, xhr: true)
+ @session.patch(path, params: params, headers: headers, xhr: true)
+ end
+
+ def test_deprecated_xml_http_request_patch
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:patch, path, params: params, headers: headers, xhr: true)
+ @session.patch(path, params: params, headers: headers, xhr: true)
+ end
+
+ def test_deprecated_args_xml_http_request_patch
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:patch, path, params: params, headers: headers, xhr: true)
+ assert_deprecated(/xml_http_request/) { @session.xml_http_request(:patch,path,params,headers) }
end
def test_xml_http_request_put
- path = "/index"; params = "blah"; headers = {:location => 'blah'}
- headers_after_xhr = headers.merge(
- "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
- "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
- )
- @session.expects(:process).with(:put,path,params,headers_after_xhr)
- @session.xml_http_request(:put,path,params,headers)
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:put, path, params: params, headers: headers, xhr: true)
+ @session.put(path, params: params, headers: headers, xhr: true)
+ end
+
+ def test_deprecated_xml_http_request_put
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:put, path, params: params, headers: headers, xhr: true)
+ @session.put(path, params: params, headers: headers, xhr: true)
+ end
+
+ def test_deprecated_args_xml_http_request_put
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:put, path, params: params, headers: headers, xhr: true)
+ assert_deprecated(/xml_http_request/) { @session.xml_http_request(:put, path, params, headers) }
end
def test_xml_http_request_delete
- path = "/index"; params = "blah"; headers = {:location => 'blah'}
- headers_after_xhr = headers.merge(
- "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
- "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
- )
- @session.expects(:process).with(:delete,path,params,headers_after_xhr)
- @session.xml_http_request(:delete,path,params,headers)
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:delete, path, params: params, headers: headers, xhr: true)
+ @session.delete(path, params: params, headers: headers, xhr: true)
+ end
+
+ def test_deprecated_xml_http_request_delete
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:delete, path, params: params, headers: headers, xhr: true)
+ assert_deprecated { @session.xml_http_request(:delete, path, params: params, headers: headers) }
+ end
+
+ def test_deprecated_args_xml_http_request_delete
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:delete, path, params: params, headers: headers, xhr: true)
+ assert_deprecated(/xml_http_request/) { @session.xml_http_request(:delete, path, params, headers) }
end
def test_xml_http_request_head
- path = "/index"; params = "blah"; headers = {:location => 'blah'}
- headers_after_xhr = headers.merge(
- "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
- "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
- )
- @session.expects(:process).with(:head,path,params,headers_after_xhr)
- @session.xml_http_request(:head,path,params,headers)
- end
-
- def test_xml_http_request_override_accept
- path = "/index"; params = "blah"; headers = {:location => 'blah', "HTTP_ACCEPT" => "application/xml"}
- headers_after_xhr = headers.merge(
- "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest"
- )
- @session.expects(:process).with(:post,path,params,headers_after_xhr)
- @session.xml_http_request(:post,path,params,headers)
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:head, path, params: params, headers: headers, xhr: true)
+ @session.head(path, params: params, headers: headers, xhr: true)
+ end
+
+ def test_deprecated_xml_http_request_head
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:head, path, params: params, headers: headers, xhr: true)
+ assert_deprecated(/xml_http_request/) { @session.xml_http_request(:head, path, params: params, headers: headers) }
+ end
+
+ def test_deprecated_args_xml_http_request_head
+ path = "/index"; params = "blah"; headers = { location: 'blah' }
+ @session.expects(:process).with(:head, path, params: params, headers: headers, xhr: true)
+ assert_deprecated { @session.xml_http_request(:head, path, params, headers) }
end
end
@@ -246,6 +363,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
respond_to do |format|
format.html { render :text => "OK", :status => 200 }
format.js { render :text => "JS OK", :status => 200 }
+ format.xml { render :xml => "<root></root>", :status => 200 }
end
end
@@ -279,6 +397,11 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
def redirect
redirect_to action_url('get')
end
+
+ def remove_header
+ response.headers.delete params[:header]
+ head :ok, 'c' => '3'
+ end
end
def test_get
@@ -292,7 +415,23 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
assert_equal({}, cookies.to_hash)
assert_equal "OK", body
assert_equal "OK", response.body
- assert_kind_of Nokogiri::HTML::DocumentFragment, html_document
+ assert_kind_of Nokogiri::HTML::Document, html_document
+ assert_equal 1, request_count
+ end
+ end
+
+ def test_get_xml
+ with_test_route_set do
+ get "/get", params: {}, headers: {"HTTP_ACCEPT" => "application/xml"}
+ assert_equal 200, status
+ assert_equal "OK", status_message
+ assert_response 200
+ assert_response :success
+ assert_response :ok
+ assert_equal({}, cookies.to_hash)
+ assert_equal "<root></root>", body
+ assert_equal "<root></root>", response.body
+ assert_instance_of Nokogiri::XML::Document, html_document
assert_equal 1, request_count
end
end
@@ -308,7 +447,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
assert_equal({}, cookies.to_hash)
assert_equal "Created", body
assert_equal "Created", response.body
- assert_kind_of Nokogiri::HTML::DocumentFragment, html_document
+ assert_kind_of Nokogiri::HTML::Document, html_document
assert_equal 1, request_count
end
end
@@ -368,7 +507,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
assert_response :redirect
assert_response :found
assert_equal "<html><body>You are being <a href=\"http://www.example.com/get\">redirected</a>.</body></html>", response.body
- assert_kind_of Nokogiri::HTML::DocumentFragment, html_document
+ assert_kind_of Nokogiri::HTML::Document, html_document
assert_equal 1, request_count
follow_redirect!
@@ -383,7 +522,19 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
def test_xml_http_request_get
with_test_route_set do
- xhr :get, '/get'
+ get '/get', xhr: true
+ assert_equal 200, status
+ assert_equal "OK", status_message
+ assert_response 200
+ assert_response :success
+ assert_response :ok
+ assert_equal "JS OK", response.body
+ end
+ end
+
+ def test_deprecated_xml_http_request_get
+ with_test_route_set do
+ assert_deprecated { xhr :get, '/get' }
assert_equal 200, status
assert_equal "OK", status_message
assert_response 200
@@ -395,7 +546,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
def test_request_with_bad_format
with_test_route_set do
- xhr :get, '/get.php'
+ get '/get.php', xhr: true
assert_equal 406, status
assert_response 406
assert_response :not_acceptable
@@ -418,7 +569,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
def test_get_with_parameters
with_test_route_set do
- get '/get_with_params', :foo => "bar"
+ get '/get_with_params', params: { foo: "bar" }
assert_equal '/get_with_params', request.env["PATH_INFO"]
assert_equal '/get_with_params', request.path_info
assert_equal 'foo=bar', request.env["QUERY_STRING"]
@@ -506,7 +657,27 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
end
end
+ def test_respect_removal_of_default_headers_by_a_controller_action
+ with_test_route_set do
+ with_default_headers 'a' => '1', 'b' => '2' do
+ get '/remove_header', params: { header: 'a' }
+ end
+ end
+
+ assert_not_includes @response.headers, 'a', 'Response should not include default header removed by the controller action'
+ assert_includes @response.headers, 'b'
+ assert_includes @response.headers, 'c'
+ end
+
private
+ def with_default_headers(headers)
+ original = ActionDispatch::Response.default_headers
+ ActionDispatch::Response.default_headers = headers
+ yield
+ ensure
+ ActionDispatch::Response.default_headers = original
+ end
+
def with_test_route_set
with_routing do |set|
controller = ::IntegrationProcessTest::IntegrationController.clone
@@ -521,7 +692,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
get 'get/:action', :to => controller, :as => :get_action
end
- self.singleton_class.send(:include, set.url_helpers)
+ self.singleton_class.include(set.url_helpers)
yield
end
@@ -565,14 +736,22 @@ class MetalIntegrationTest < ActionDispatch::IntegrationTest
end
def test_pass_headers
- get "/success", {}, "Referer" => "http://www.example.com/foo", "Host" => "http://nohost.com"
+ get "/success", headers: {"Referer" => "http://www.example.com/foo", "Host" => "http://nohost.com"}
assert_equal "http://nohost.com", @request.env["HTTP_HOST"]
assert_equal "http://www.example.com/foo", @request.env["HTTP_REFERER"]
end
+ def test_pass_headers_and_env
+ get "/success", headers: { "X-Test-Header" => "value" }, env: { "HTTP_REFERER" => "http://test.com/", "HTTP_HOST" => "http://test.com" }
+
+ assert_equal "http://test.com", @request.env["HTTP_HOST"]
+ assert_equal "http://test.com/", @request.env["HTTP_REFERER"]
+ assert_equal "value", @request.env["HTTP_X_TEST_HEADER"]
+ end
+
def test_pass_env
- get "/success", {}, "HTTP_REFERER" => "http://test.com/", "HTTP_HOST" => "http://test.com"
+ get "/success", env: { "HTTP_REFERER" => "http://test.com/", "HTTP_HOST" => "http://test.com" }
assert_equal "http://test.com", @request.env["HTTP_HOST"]
assert_equal "http://test.com/", @request.env["HTTP_REFERER"]
@@ -662,7 +841,7 @@ class ApplicationIntegrationTest < ActionDispatch::IntegrationTest
test "process do not modify the env passed as argument" do
env = { :SERVER_NAME => 'server', 'action_dispatch.custom' => 'custom' }
old_env = env.dup
- get '/foo', nil, env
+ get '/foo', env: env
assert_equal old_env, env
end
end
@@ -692,7 +871,7 @@ class EnvironmentFilterIntegrationTest < ActionDispatch::IntegrationTest
end
test "filters rack request form vars" do
- post "/post", :username => 'cjolly', :password => 'secret'
+ post "/post", params: { username: 'cjolly', password: 'secret' }
assert_equal 'cjolly', request.filtered_parameters['username']
assert_equal '[FILTERED]', request.filtered_parameters['password']
@@ -814,3 +993,75 @@ class HeadWithStatusActionIntegrationTest < ActionDispatch::IntegrationTest
assert_response :ok
end
end
+
+class IntegrationWithRoutingTest < ActionDispatch::IntegrationTest
+ class FooController < ActionController::Base
+ def index
+ render plain: 'ok'
+ end
+ end
+
+ def test_with_routing_resets_session
+ klass_namespace = self.class.name.underscore
+
+ with_routing do |routes|
+ routes.draw do
+ namespace klass_namespace do
+ resources :foo, path: '/with'
+ end
+ end
+
+ get '/integration_with_routing_test/with'
+ assert_response 200
+ assert_equal 'ok', response.body
+ end
+
+ with_routing do |routes|
+ routes.draw do
+ namespace klass_namespace do
+ resources :foo, path: '/routing'
+ end
+ end
+
+ get '/integration_with_routing_test/routing'
+ assert_response 200
+ assert_equal 'ok', response.body
+ end
+ end
+end
+
+# to work in contexts like rspec before(:all)
+class IntegrationRequestsWithoutSetup < ActionDispatch::IntegrationTest
+ self._setup_callbacks = []
+ self._teardown_callbacks = []
+
+ class FooController < ActionController::Base
+ def ok
+ cookies[:key] = 'ok'
+ render plain: 'ok'
+ end
+ end
+
+ def test_request
+ with_routing do |routes|
+ routes.draw { get ':action' => FooController }
+ get '/ok'
+
+ assert_response 200
+ assert_equal 'ok', response.body
+ assert_equal 'ok', cookies['key']
+ end
+ end
+end
+
+# to ensure that session requirements in setup are persisted in the tests
+class IntegrationRequestsWithSessionSetup < ActionDispatch::IntegrationTest
+ setup do
+ cookies['user_name'] = 'david'
+ end
+
+ def test_cookies_set_in_setup_are_persisted_through_the_session
+ get "/foo"
+ assert_equal({"user_name"=>"david"}, cookies.to_hash)
+ end
+end
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
index 7fd1276e98..0c65270ec1 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -276,6 +276,8 @@ module ActionController
end
def test_async_stream
+ rubinius_skip "https://github.com/rubinius/rubinius/issues/2934"
+
@controller.latch = ActiveSupport::Concurrency::Latch.new
parts = ['hello', 'world']
diff --git a/actionpack/test/controller/localized_templates_test.rb b/actionpack/test/controller/localized_templates_test.rb
index 27871ef351..3576015513 100644
--- a/actionpack/test/controller/localized_templates_test.rb
+++ b/actionpack/test/controller/localized_templates_test.rb
@@ -19,7 +19,7 @@ class LocalizedTemplatesTest < ActionController::TestCase
def test_localized_template_is_used
I18n.locale = :de
get :hello_world
- assert_equal "Gutten Tag", @response.body
+ assert_equal "Guten Tag", @response.body
end
def test_default_locale_template_is_used_when_locale_is_missing
@@ -30,11 +30,11 @@ class LocalizedTemplatesTest < ActionController::TestCase
def test_use_fallback_locales
I18n.locale = :"de-AT"
- I18n.backend.class.send(:include, I18n::Backend::Fallbacks)
+ I18n.backend.class.include(I18n::Backend::Fallbacks)
I18n.fallbacks[:"de-AT"] = [:de]
get :hello_world
- assert_equal "Gutten Tag", @response.body
+ assert_equal "Guten Tag", @response.body
end
def test_localized_template_has_correct_header_with_no_format_in_template_name
diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb
index 49be7caf38..03a4ad7823 100644
--- a/actionpack/test/controller/log_subscriber_test.rb
+++ b/actionpack/test/controller/log_subscriber_test.rb
@@ -73,6 +73,16 @@ module Another
def with_action_not_found
raise AbstractController::ActionNotFound
end
+
+ def append_info_to_payload(payload)
+ super
+ payload[:test_key] = "test_value"
+ @last_payload = payload
+ end
+
+ def last_payload
+ @last_payload
+ end
end
end
@@ -130,7 +140,7 @@ class ACLogSubscriberTest < ActionController::TestCase
end
def test_process_action_with_parameters
- get :show, :id => '10'
+ get :show, params: { id: '10' }
wait
assert_equal 3, logs.size
@@ -138,8 +148,8 @@ class ACLogSubscriberTest < ActionController::TestCase
end
def test_multiple_process_with_parameters
- get :show, :id => '10'
- get :show, :id => '20'
+ get :show, params: { id: '10' }
+ get :show, params: { id: '20' }
wait
@@ -150,7 +160,7 @@ class ACLogSubscriberTest < ActionController::TestCase
def test_process_action_with_wrapped_parameters
@request.env['CONTENT_TYPE'] = 'application/json'
- post :show, :id => '10', :name => 'jose'
+ post :show, params: { id: '10', name: 'jose' }
wait
assert_equal 3, logs.size
@@ -163,10 +173,22 @@ class ACLogSubscriberTest < ActionController::TestCase
assert_match(/\(Views: [\d.]+ms\)/, logs[1])
end
+ def test_append_info_to_payload_is_called_even_with_exception
+ begin
+ get :with_exception
+ wait
+ rescue Exception
+ end
+
+ assert_equal "test_value", @controller.last_payload[:test_key]
+ end
+
def test_process_action_with_filter_parameters
@request.env["action_dispatch.parameter_filter"] = [:lifo, :amount]
- get :show, :lifo => 'Pratik', :amount => '420', :step => '1'
+ get :show, params: {
+ lifo: 'Pratik', amount: '420', step: '1'
+ }
wait
params = logs[1]
diff --git a/actionpack/test/controller/mime/accept_format_test.rb b/actionpack/test/controller/mime/accept_format_test.rb
index 811c507af2..e20c08da4e 100644
--- a/actionpack/test/controller/mime/accept_format_test.rb
+++ b/actionpack/test/controller/mime/accept_format_test.rb
@@ -11,7 +11,7 @@ end
class StarStarMimeControllerTest < ActionController::TestCase
def test_javascript_with_format
@request.accept = "text/javascript"
- get :index, :format => 'js'
+ get :index, format: 'js'
assert_match "function addition(a,b){ return a+b; }", @response.body
end
@@ -86,7 +86,7 @@ class MimeControllerLayoutsTest < ActionController::TestCase
end
def test_non_navigational_format_with_no_template_fallbacks_to_html_template_with_no_layout
- get :index, :format => :js
+ get :index, format: :js
assert_equal "Hello Firefox", @response.body
end
end
diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb
index 66d2fd7716..7aef8a50ce 100644
--- a/actionpack/test/controller/mime/respond_to_test.rb
+++ b/actionpack/test/controller/mime/respond_to_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require "active_support/log_subscriber/test_helper"
class RespondToController < ActionController::Base
layout :set_layout
@@ -310,17 +311,17 @@ class RespondToControllerTest < ActionController::TestCase
def test_js_or_html
@request.accept = "text/javascript, text/html"
- xhr :get, :js_or_html
+ get :js_or_html, xhr: true
assert_equal 'JS', @response.body
@request.accept = "text/javascript, text/html"
- xhr :get, :html_or_xml
+ get :html_or_xml, xhr: true
assert_equal 'HTML', @response.body
@request.accept = "text/javascript, text/html"
assert_raises(ActionController::UnknownFormat) do
- xhr :get, :just_xml
+ get :just_xml, xhr: true
end
end
@@ -335,13 +336,13 @@ class RespondToControllerTest < ActionController::TestCase
end
def test_json_or_yaml
- xhr :get, :json_or_yaml
+ get :json_or_yaml, xhr: true
assert_equal 'JSON', @response.body
- get :json_or_yaml, :format => 'json'
+ get :json_or_yaml, format: 'json'
assert_equal 'JSON', @response.body
- get :json_or_yaml, :format => 'yaml'
+ get :json_or_yaml, format: 'yaml'
assert_equal 'YAML', @response.body
{ 'YAML' => %w(text/yaml),
@@ -357,13 +358,13 @@ class RespondToControllerTest < ActionController::TestCase
def test_js_or_anything
@request.accept = "text/javascript, */*"
- xhr :get, :js_or_html
+ get :js_or_html, xhr: true
assert_equal 'JS', @response.body
- xhr :get, :html_or_xml
+ get :html_or_xml, xhr: true
assert_equal 'HTML', @response.body
- xhr :get, :just_xml
+ get :just_xml, xhr: true
assert_equal 'XML', @response.body
end
@@ -408,14 +409,14 @@ class RespondToControllerTest < ActionController::TestCase
def test_with_atom_content_type
@request.accept = ""
@request.env["CONTENT_TYPE"] = "application/atom+xml"
- xhr :get, :made_for_content_type
+ get :made_for_content_type, xhr: true
assert_equal "ATOM", @response.body
end
def test_with_rss_content_type
@request.accept = ""
@request.env["CONTENT_TYPE"] = "application/rss+xml"
- xhr :get, :made_for_content_type
+ get :made_for_content_type, xhr: true
assert_equal "RSS", @response.body
end
@@ -474,7 +475,7 @@ class RespondToControllerTest < ActionController::TestCase
end
def test_handle_any_any_parameter_format
- get :handle_any_any, {:format=>'html'}
+ get :handle_any_any, format: 'html'
assert_equal 'HTML', @response.body
end
@@ -497,7 +498,7 @@ class RespondToControllerTest < ActionController::TestCase
end
def test_handle_any_any_unkown_format
- get :handle_any_any, { format: 'php' }
+ get :handle_any_any, format: 'php'
assert_equal 'Whatever you ask for, I got it', @response.body
end
@@ -525,18 +526,18 @@ class RespondToControllerTest < ActionController::TestCase
end
def test_xhr
- xhr :get, :js_or_html
+ get :js_or_html, xhr: true
assert_equal 'JS', @response.body
end
def test_custom_constant
- get :custom_constant_handling, :format => "mobile"
+ get :custom_constant_handling, format: "mobile"
assert_equal "text/x-mobile", @response.content_type
assert_equal "Mobile", @response.body
end
def test_custom_constant_handling_without_block
- get :custom_constant_handling_without_block, :format => "mobile"
+ get :custom_constant_handling_without_block, format: "mobile"
assert_equal "text/x-mobile", @response.content_type
assert_equal "Mobile", @response.body
end
@@ -545,13 +546,13 @@ class RespondToControllerTest < ActionController::TestCase
get :html_xml_or_rss
assert_equal "HTML", @response.body
- get :html_xml_or_rss, :format => "html"
+ get :html_xml_or_rss, format: "html"
assert_equal "HTML", @response.body
- get :html_xml_or_rss, :format => "xml"
+ get :html_xml_or_rss, format: "xml"
assert_equal "XML", @response.body
- get :html_xml_or_rss, :format => "rss"
+ get :html_xml_or_rss, format: "rss"
assert_equal "RSS", @response.body
end
@@ -559,12 +560,12 @@ class RespondToControllerTest < ActionController::TestCase
get :forced_xml
assert_equal "XML", @response.body
- get :forced_xml, :format => "html"
+ get :forced_xml, format: "html"
assert_equal "XML", @response.body
end
def test_extension_synonyms
- get :html_xml_or_rss, :format => "xhtml"
+ get :html_xml_or_rss, format: "xhtml"
assert_equal "HTML", @response.body
end
@@ -581,7 +582,7 @@ class RespondToControllerTest < ActionController::TestCase
get :using_defaults
assert_equal "using_defaults - #{[:html]}", @response.body
- get :using_defaults, :format => "xml"
+ get :using_defaults, format: "xml"
assert_equal "using_defaults - #{[:xml]}", @response.body
end
@@ -589,7 +590,7 @@ class RespondToControllerTest < ActionController::TestCase
get :iphone_with_html_response_type
assert_equal '<html><div id="html">Hello future from Firefox!</div></html>', @response.body
- get :iphone_with_html_response_type, :format => "iphone"
+ get :iphone_with_html_response_type, format: "iphone"
assert_equal "text/html", @response.content_type
assert_equal '<html><div id="iphone">Hello iPhone future from iPhone!</div></html>', @response.body
end
@@ -603,24 +604,34 @@ class RespondToControllerTest < ActionController::TestCase
def test_invalid_format
assert_raises(ActionController::UnknownFormat) do
- get :using_defaults, :format => "invalidformat"
+ get :using_defaults, format: "invalidformat"
end
end
def test_invalid_variant
+ logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
+ old_logger, ActionController::Base.logger = ActionController::Base.logger, logger
+
@request.variant = :invalid
- assert_raises(ActionView::MissingTemplate) do
- get :variant_with_implicit_rendering
- end
+ get :variant_with_implicit_rendering
+ assert_response :no_content
+ assert_equal 1, logger.logged(:info).select{ |s| s =~ /No template found/ }.size, "Implicit head :no_content not logged"
+ ensure
+ ActionController::Base.logger = old_logger
end
def test_variant_not_set_regular_template_missing
- assert_raises(ActionView::MissingTemplate) do
- get :variant_with_implicit_rendering
- end
+ get :variant_with_implicit_rendering
+ assert_response :no_content
end
def test_variant_with_implicit_rendering
+ @request.variant = :implicit
+ get :variant_with_implicit_rendering
+ assert_response :no_content
+ end
+
+ def test_variant_with_implicit_template_rendering
@request.variant = :mobile
get :variant_with_implicit_rendering
assert_equal "text/html", @response.content_type
diff --git a/actionpack/test/controller/mime/responders_test.rb b/actionpack/test/controller/mime/responders_test.rb
deleted file mode 100644
index 032b4c0ab1..0000000000
--- a/actionpack/test/controller/mime/responders_test.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-require 'abstract_unit'
-require 'controller/fake_models'
-
-class ResponderTest < ActionController::TestCase
- def test_class_level_respond_to
- e = assert_raises(NoMethodError) do
- Class.new(ActionController::Base) do
- respond_to :json
- end
- end
-
- assert_includes e.message, '`responders` gem'
- assert_includes e.message, '~> 2.0'
- end
-
- def test_respond_with
- klass = Class.new(ActionController::Base) do
- def index
- respond_with Customer.new("david", 13)
- end
- end
-
- @controller = klass.new
-
- e = assert_raises(NoMethodError) do
- get :index
- end
-
- assert_includes e.message, '`responders` gem'
- assert_includes e.message, '~> 2.0'
- end
-end
diff --git a/actionpack/test/controller/new_base/bare_metal_test.rb b/actionpack/test/controller/new_base/bare_metal_test.rb
index 246ba099af..710c428dcc 100644
--- a/actionpack/test/controller/new_base/bare_metal_test.rb
+++ b/actionpack/test/controller/new_base/bare_metal_test.rb
@@ -31,6 +31,15 @@ module BareMetalTest
controller.index
assert_equal ["Hello world"], controller.response_body
end
+
+ test "connect a request to controller instance without dispatch" do
+ env = {}
+ controller = BareController.new
+ controller.set_request! ActionDispatch::Request.new(env)
+ assert controller.request
+ assert controller.response
+ assert env['action_controller.instance']
+ end
end
class HeadController < ActionController::Metal
diff --git a/actionpack/test/controller/new_base/content_negotiation_test.rb b/actionpack/test/controller/new_base/content_negotiation_test.rb
index 5fd5946619..c8166280fc 100644
--- a/actionpack/test/controller/new_base/content_negotiation_test.rb
+++ b/actionpack/test/controller/new_base/content_negotiation_test.rb
@@ -15,12 +15,12 @@ module ContentNegotiation
class TestContentNegotiation < Rack::TestCase
test "A */* Accept header will return HTML" do
- get "/content_negotiation/basic/hello", {}, "HTTP_ACCEPT" => "*/*"
+ get "/content_negotiation/basic/hello", headers: { "HTTP_ACCEPT" => "*/*" }
assert_body "Hello world */*!"
end
test "Not all mimes are converted to symbol" do
- get "/content_negotiation/basic/all", {}, "HTTP_ACCEPT" => "text/plain, mime/another"
+ get "/content_negotiation/basic/all", headers: { "HTTP_ACCEPT" => "text/plain, mime/another" }
assert_body '[:text, "mime/another"]'
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 9b57641e75..88453988dd 100644
--- a/actionpack/test/controller/new_base/content_type_test.rb
+++ b/actionpack/test/controller/new_base/content_type_test.rb
@@ -76,7 +76,7 @@ module ContentType
end
test "sets Content-Type as application/xml when rendering *.xml.erb" do
- get "/content_type/implied/i_am_xml_erb", "format" => "xml"
+ get "/content_type/implied/i_am_xml_erb", params: { "format" => "xml" }
assert_header "Content-Type", "application/xml; charset=utf-8"
end
@@ -88,7 +88,7 @@ module ContentType
end
test "sets Content-Type as application/xml when rendering *.xml.builder" do
- get "/content_type/implied/i_am_xml_builder", "format" => "xml"
+ get "/content_type/implied/i_am_xml_builder", params: { "format" => "xml" }
assert_header "Content-Type", "application/xml; charset=utf-8"
end
diff --git a/actionpack/test/controller/new_base/metal_test.rb b/actionpack/test/controller/new_base/metal_test.rb
index 45a6619eb4..537b93387a 100644
--- a/actionpack/test/controller/new_base/metal_test.rb
+++ b/actionpack/test/controller/new_base/metal_test.rb
@@ -18,8 +18,6 @@ module MetalTest
end
class TestMiddleware < ActiveSupport::TestCase
- include RackTestUtils
-
def setup
@app = Rack::Builder.new do
use MetalTest::MetalMiddleware
@@ -31,14 +29,14 @@ module MetalTest
env = Rack::MockRequest.env_for("/authed")
response = @app.call(env)
- assert_equal "Hello World", body_to_string(response[2])
+ 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!", body_to_string(response[2])
+ assert_equal ["Not authed!"], response[2]
assert_equal 401, response[0]
end
end
diff --git a/actionpack/test/controller/new_base/middleware_test.rb b/actionpack/test/controller/new_base/middleware_test.rb
index 6b7b5e10e3..a30e937bb3 100644
--- a/actionpack/test/controller/new_base/middleware_test.rb
+++ b/actionpack/test/controller/new_base/middleware_test.rb
@@ -75,7 +75,7 @@ module MiddlewareTest
test "middleware that is 'use'd is called as part of the Rack application" do
result = @app.call(env_for("/"))
- assert_equal "Hello World", RackTestUtils.body_to_string(result[2])
+ assert_equal ["Hello World"], result[2]
assert_equal "Success", result[1]["Middleware-Test"]
end
diff --git a/actionpack/test/controller/new_base/render_action_test.rb b/actionpack/test/controller/new_base/render_action_test.rb
index 475bf9d3c9..3bf1dd0ede 100644
--- a/actionpack/test/controller/new_base/render_action_test.rb
+++ b/actionpack/test/controller/new_base/render_action_test.rb
@@ -88,7 +88,7 @@ module RenderAction
test "rendering with layout => true" do
assert_raise(ArgumentError) do
- get "/render_action/basic/hello_world_with_layout", {}, "action_dispatch.show_exceptions" => false
+ get "/render_action/basic/hello_world_with_layout", headers: { "action_dispatch.show_exceptions" => false }
end
end
@@ -108,7 +108,7 @@ module RenderAction
test "rendering with layout => 'greetings'" do
assert_raise(ActionView::MissingTemplate) do
- get "/render_action/basic/hello_world_with_custom_layout", {}, "action_dispatch.show_exceptions" => false
+ get "/render_action/basic/hello_world_with_custom_layout", headers: { "action_dispatch.show_exceptions" => false }
end
end
end
diff --git a/actionpack/test/controller/new_base/render_layout_test.rb b/actionpack/test/controller/new_base/render_layout_test.rb
index 4ac40ca405..7ab3777026 100644
--- a/actionpack/test/controller/new_base/render_layout_test.rb
+++ b/actionpack/test/controller/new_base/render_layout_test.rb
@@ -86,7 +86,7 @@ module ControllerLayouts
XML_INSTRUCT = %Q(<?xml version="1.0" encoding="UTF-8"?>\n)
test "if XML is selected, an HTML template is not also selected" do
- get :index, :format => "xml"
+ get :index, params: { format: "xml" }
assert_response XML_INSTRUCT
end
@@ -96,7 +96,7 @@ module ControllerLayouts
end
test "a layout for JS is ignored even if explicitly provided for HTML" do
- get :explicit, { :format => "js" }
+ get :explicit, params: { format: "js" }
assert_response "alert('foo');"
end
end
@@ -120,7 +120,7 @@ module ControllerLayouts
testing ControllerLayouts::FalseLayoutMethodController
test "access false layout returned by a method/proc" do
- get :index, :format => "js"
+ get :index, params: { format: "js" }
assert_response "alert('foo');"
end
end
diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb
index 4c9126ca8c..9ea056194a 100644
--- a/actionpack/test/controller/new_base/render_streaming_test.rb
+++ b/actionpack/test/controller/new_base/render_streaming_test.rb
@@ -97,7 +97,7 @@ module RenderStreaming
end
test "do not stream on HTTP/1.0" do
- get "/render_streaming/basic/hello_world", nil, "HTTP_VERSION" => "HTTP/1.0"
+ get "/render_streaming/basic/hello_world", headers: { "HTTP_VERSION" => "HTTP/1.0" }
assert_body "Hello world, I'm here!"
assert_status 200
assert_equal "22", headers["Content-Length"]
diff --git a/actionpack/test/controller/new_base/render_template_test.rb b/actionpack/test/controller/new_base/render_template_test.rb
index 42a86b1d0d..b06ce5db40 100644
--- a/actionpack/test/controller/new_base/render_template_test.rb
+++ b/actionpack/test/controller/new_base/render_template_test.rb
@@ -111,7 +111,7 @@ module RenderTemplate
end
test "rendering a builder template" do
- get :builder_template, "format" => "xml"
+ get :builder_template, params: { "format" => "xml" }
assert_response "<html>\n <p>Hello</p>\n</html>\n"
end
@@ -126,7 +126,7 @@ module RenderTemplate
assert_body "Hello <strong>this is also raw</strong> in an html template"
assert_status 200
- get :with_implicit_raw, format: 'text'
+ get :with_implicit_raw, params: { format: 'text' }
assert_body "Hello <strong>this is also raw</strong> in a text template"
assert_status 200
@@ -186,21 +186,21 @@ module RenderTemplate
end
end
- test "rendering with layout => :true" do
+ test "rendering with layout => true" do
get "/render_template/with_layout/with_layout"
assert_body "Hello from basic.html.erb, I'm here!"
assert_status 200
end
- test "rendering with layout => :false" do
+ test "rendering with layout => false" do
get "/render_template/with_layout/with_layout_false"
assert_body "Hello from basic.html.erb"
assert_status 200
end
- test "rendering with layout => :nil" do
+ test "rendering with layout => nil" do
get "/render_template/with_layout/with_layout_nil"
assert_body "Hello from basic.html.erb"
diff --git a/actionpack/test/controller/new_base/render_test.rb b/actionpack/test/controller/new_base/render_test.rb
index 5635e16234..11a19ab783 100644
--- a/actionpack/test/controller/new_base/render_test.rb
+++ b/actionpack/test/controller/new_base/render_test.rb
@@ -74,7 +74,7 @@ module Render
end
assert_raises(AbstractController::DoubleRenderError) do
- get "/render/double_render", {}, "action_dispatch.show_exceptions" => false
+ get "/render/double_render", headers: { "action_dispatch.show_exceptions" => false }
end
end
end
@@ -84,13 +84,13 @@ module Render
# Only public methods on actual controllers are callable actions
test "raises an exception when a method of Object is called" do
assert_raises(AbstractController::ActionNotFound) do
- get "/render/blank_render/clone", {}, "action_dispatch.show_exceptions" => false
+ get "/render/blank_render/clone", headers: { "action_dispatch.show_exceptions" => false }
end
end
test "raises an exception when a private method is called" do
assert_raises(AbstractController::ActionNotFound) do
- get "/render/blank_render/secretz", {}, "action_dispatch.show_exceptions" => false
+ get "/render/blank_render/secretz", headers: { "action_dispatch.show_exceptions" => false }
end
end
end
diff --git a/actionpack/test/controller/parameters/always_permitted_parameters_test.rb b/actionpack/test/controller/parameters/always_permitted_parameters_test.rb
index 059f310d49..59be08db54 100644
--- a/actionpack/test/controller/parameters/always_permitted_parameters_test.rb
+++ b/actionpack/test/controller/parameters/always_permitted_parameters_test.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'action_controller/metal/strong_parameters'
+require 'minitest/mock'
class AlwaysPermittedParametersTest < ActiveSupport::TestCase
def setup
@@ -14,7 +15,13 @@ class AlwaysPermittedParametersTest < ActiveSupport::TestCase
test "shows deprecations warning on NEVER_UNPERMITTED_PARAMS" do
assert_deprecated do
- ActionController::Parameters::NEVER_UNPERMITTED_PARAMS
+ ActionController::Parameters::NEVER_UNPERMITTED_PARAMS
+ end
+ end
+
+ test "returns super on missing constant other than NEVER_UNPERMITTED_PARAMS" do
+ ActionController::Parameters.superclass.stub :const_missing, "super" do
+ assert_equal "super", ActionController::Parameters::NON_EXISTING_CONSTANT
end
end
diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb
index ba98ad7605..2ed486516d 100644
--- a/actionpack/test/controller/parameters/parameters_permit_test.rb
+++ b/actionpack/test/controller/parameters/parameters_permit_test.rb
@@ -280,4 +280,10 @@ class ParametersPermitTest < ActiveSupport::TestCase
assert_equal({ "controller" => "users", "action" => "create" }, params.to_h)
end
+
+ 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/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb
index 645ecae220..8bf016d060 100644
--- a/actionpack/test/controller/params_wrapper_test.rb
+++ b/actionpack/test/controller/params_wrapper_test.rb
@@ -40,7 +40,7 @@ class ParamsWrapperTest < ActionController::TestCase
def test_filtered_parameters
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu' }
+ post :parse, params: { 'username' => 'sikachu' }
assert_equal @request.filtered_parameters, { 'controller' => 'params_wrapper_test/users', 'action' => 'parse', 'username' => 'sikachu', 'user' => { 'username' => 'sikachu' } }
end
end
@@ -48,7 +48,7 @@ class ParamsWrapperTest < ActionController::TestCase
def test_derived_name_from_controller
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu' }
+ post :parse, params: { 'username' => 'sikachu' }
assert_parameters({ 'username' => 'sikachu', 'user' => { 'username' => 'sikachu' }})
end
end
@@ -58,7 +58,7 @@ class ParamsWrapperTest < ActionController::TestCase
UsersController.wrap_parameters :person
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu' }
+ post :parse, params: { 'username' => 'sikachu' }
assert_parameters({ 'username' => 'sikachu', 'person' => { 'username' => 'sikachu' }})
end
end
@@ -68,7 +68,7 @@ class ParamsWrapperTest < ActionController::TestCase
UsersController.wrap_parameters Person
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu' }
+ post :parse, params: { 'username' => 'sikachu' }
assert_parameters({ 'username' => 'sikachu', 'person' => { 'username' => 'sikachu' }})
end
end
@@ -78,7 +78,7 @@ class ParamsWrapperTest < ActionController::TestCase
UsersController.wrap_parameters :include => :username
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' }
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }})
end
end
@@ -88,7 +88,7 @@ class ParamsWrapperTest < ActionController::TestCase
UsersController.wrap_parameters :exclude => :title
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' }
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }})
end
end
@@ -98,7 +98,7 @@ class ParamsWrapperTest < ActionController::TestCase
UsersController.wrap_parameters :person, :include => :username
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' }
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }})
end
end
@@ -106,7 +106,7 @@ class ParamsWrapperTest < ActionController::TestCase
def test_not_enabled_format
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/xml'
- post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' }
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer' })
end
end
@@ -115,7 +115,7 @@ class ParamsWrapperTest < ActionController::TestCase
with_default_wrapper_options do
UsersController.wrap_parameters false
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' }
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer' })
end
end
@@ -125,7 +125,7 @@ class ParamsWrapperTest < ActionController::TestCase
UsersController.wrap_parameters :format => :xml
@request.env['CONTENT_TYPE'] = 'application/xml'
- post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' }
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu', 'title' => 'Developer' }})
end
end
@@ -133,7 +133,7 @@ class ParamsWrapperTest < ActionController::TestCase
def test_not_wrap_reserved_parameters
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'authenticity_token' => 'pwned', '_method' => 'put', 'utf8' => '&#9731;', 'username' => 'sikachu' }
+ post :parse, params: { 'authenticity_token' => 'pwned', '_method' => 'put', 'utf8' => '&#9731;', 'username' => 'sikachu' }
assert_parameters({ 'authenticity_token' => 'pwned', '_method' => 'put', 'utf8' => '&#9731;', 'username' => 'sikachu', 'user' => { 'username' => 'sikachu' }})
end
end
@@ -141,7 +141,7 @@ class ParamsWrapperTest < ActionController::TestCase
def test_no_double_wrap_if_key_exists
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'user' => { 'username' => 'sikachu' }}
+ post :parse, params: { 'user' => { 'username' => 'sikachu' }}
assert_parameters({ 'user' => { 'username' => 'sikachu' }})
end
end
@@ -149,7 +149,7 @@ class ParamsWrapperTest < ActionController::TestCase
def test_nested_params
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'person' => { 'username' => 'sikachu' }}
+ post :parse, params: { 'person' => { 'username' => 'sikachu' }}
assert_parameters({ 'person' => { 'username' => 'sikachu' }, 'user' => {'person' => { 'username' => 'sikachu' }}})
end
end
@@ -160,7 +160,7 @@ class ParamsWrapperTest < ActionController::TestCase
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' }
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }})
end
end
@@ -173,7 +173,7 @@ class ParamsWrapperTest < ActionController::TestCase
UsersController.wrap_parameters Person
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' }
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }})
end
end
@@ -184,7 +184,7 @@ class ParamsWrapperTest < ActionController::TestCase
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' }
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu', 'title' => 'Developer' }})
end
end
@@ -192,7 +192,7 @@ class ParamsWrapperTest < ActionController::TestCase
def test_preserves_query_string_params
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- get :parse, { 'user' => { 'username' => 'nixon' } }
+ get :parse, params: { 'user' => { 'username' => 'nixon' } }
assert_parameters(
{'user' => { 'username' => 'nixon' } }
)
@@ -202,7 +202,7 @@ class ParamsWrapperTest < ActionController::TestCase
def test_empty_parameter_set
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, {}
+ post :parse, params: {}
assert_parameters(
{'user' => { } }
)
@@ -249,7 +249,7 @@ class NamespacedParamsWrapperTest < ActionController::TestCase
def test_derived_name_from_controller
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu' }
+ post :parse, params: { 'username' => 'sikachu' }
assert_parameters({'username' => 'sikachu', 'user' => { 'username' => 'sikachu' }})
end
end
@@ -259,7 +259,7 @@ class NamespacedParamsWrapperTest < ActionController::TestCase
begin
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' }
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }})
end
ensure
@@ -272,7 +272,7 @@ class NamespacedParamsWrapperTest < ActionController::TestCase
begin
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ post :parse, params: { 'username' => 'sikachu', 'title' => 'Developer' }
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'title' => 'Developer' }})
end
ensure
@@ -299,7 +299,7 @@ class AnonymousControllerParamsWrapperTest < ActionController::TestCase
def test_does_not_implicitly_wrap_params
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu' }
+ post :parse, params: { 'username' => 'sikachu' }
assert_parameters({ 'username' => 'sikachu' })
end
end
@@ -308,7 +308,7 @@ class AnonymousControllerParamsWrapperTest < ActionController::TestCase
with_default_wrapper_options do
@controller.class.wrap_parameters(:name => "guest")
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu' }
+ post :parse, params: { 'username' => 'sikachu' }
assert_parameters({ 'username' => 'sikachu', 'guest' => { 'username' => 'sikachu' }})
end
end
@@ -344,7 +344,7 @@ class IrregularInflectionParamsWrapperTest < ActionController::TestCase
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu', 'test_attr' => 'test_value' }
+ post :parse, params: { 'username' => 'sikachu', 'test_attr' => 'test_value' }
assert_parameters({ 'username' => 'sikachu', 'test_attr' => 'test_value', 'paramswrappernews_item' => { 'test_attr' => 'test_value' }})
end
end
diff --git a/actionpack/test/controller/permitted_params_test.rb b/actionpack/test/controller/permitted_params_test.rb
index f46249d712..7136fafae5 100644
--- a/actionpack/test/controller/permitted_params_test.rb
+++ b/actionpack/test/controller/permitted_params_test.rb
@@ -14,12 +14,12 @@ class ActionControllerPermittedParamsTest < ActionController::TestCase
tests PeopleController
test "parameters are forbidden" do
- post :create, { person: { name: "Mjallo!" } }
+ post :create, params: { person: { name: "Mjallo!" } }
assert_equal "forbidden", response.body
end
test "parameters can be permitted and are then not forbidden" do
- post :create_with_permit, { person: { name: "Mjallo!" } }
+ post :create_with_permit, params: { person: { name: "Mjallo!" } }
assert_equal "permitted", response.body
end
end
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index 103ca9c776..efd790de63 100644
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -63,7 +63,7 @@ class RedirectController < ActionController::Base
end
def redirect_to_url_with_unescaped_query_string
- redirect_to "http://dev.rubyonrails.org/query?status=new"
+ redirect_to "http://example.com/query?status=new"
end
def redirect_to_url_with_complex_scheme
@@ -233,7 +233,7 @@ class RedirectTest < ActionController::TestCase
def test_redirect_to_url_with_unescaped_query_string
get :redirect_to_url_with_unescaped_query_string
assert_response :redirect
- assert_redirected_to "http://dev.rubyonrails.org/query?status=new"
+ assert_redirected_to "http://example.com/query?status=new"
end
def test_redirect_to_url_with_complex_scheme
diff --git a/actionpack/test/controller/render_js_test.rb b/actionpack/test/controller/render_js_test.rb
index d550422a2f..6b661de064 100644
--- a/actionpack/test/controller/render_js_test.rb
+++ b/actionpack/test/controller/render_js_test.rb
@@ -22,13 +22,13 @@ class RenderJSTest < ActionController::TestCase
tests TestController
def test_render_vanilla_js
- xhr :get, :render_vanilla_js_hello
+ get :render_vanilla_js_hello, xhr: true
assert_equal "alert('hello')", @response.body
assert_equal "text/javascript", @response.content_type
end
def test_should_render_js_partial
- xhr :get, :show_partial, :format => 'js'
+ get :show_partial, format: 'js', xhr: true
assert_equal 'partial js', @response.body
end
end
diff --git a/actionpack/test/controller/render_json_test.rb b/actionpack/test/controller/render_json_test.rb
index ada978aa11..b1ad16bc55 100644
--- a/actionpack/test/controller/render_json_test.rb
+++ b/actionpack/test/controller/render_json_test.rb
@@ -100,13 +100,13 @@ class RenderJsonTest < ActionController::TestCase
end
def test_render_json_with_callback
- xhr :get, :render_json_hello_world_with_callback
+ get :render_json_hello_world_with_callback, xhr: true
assert_equal '/**/alert({"hello":"world"})', @response.body
assert_equal 'text/javascript', @response.content_type
end
def test_render_json_with_custom_content_type
- xhr :get, :render_json_with_custom_content_type
+ get :render_json_with_custom_content_type, xhr: true
assert_equal '{"hello":"world"}', @response.body
assert_equal 'text/javascript', @response.content_type
end
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index b036b6c08e..79e2104789 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -58,24 +58,24 @@ class TestController < ActionController::Base
end
end
- def conditional_hello_with_public_header
- if stale?(:last_modified => Time.now.utc.beginning_of_day, :etag => [:foo, 123], :public => true)
- render :action => 'hello_world'
+ class Collection
+ def initialize(records)
+ @records = records
end
- end
-
- def conditional_hello_with_public_header_with_record
- record = Struct.new(:updated_at, :cache_key).new(Time.now.utc.beginning_of_day, "foo/123")
- if stale?(record, :public => true)
- render :action => 'hello_world'
+ def maximum(attribute)
+ @records.max_by(&attribute).public_send(attribute)
end
end
- def conditional_hello_with_public_header_and_expires_at
- expires_in 1.minute
- if stale?(:last_modified => Time.now.utc.beginning_of_day, :etag => [:foo, 123], :public => true)
- render :action => 'hello_world'
+ def conditional_hello_with_collection_of_records
+ ts = Time.now.utc.beginning_of_day
+
+ record = Struct.new(:updated_at, :cache_key).new(ts, "foo/123")
+ old_record = Struct.new(:updated_at, :cache_key).new(ts - 1.day, "bar/123")
+
+ if stale?(Collection.new([record, old_record]))
+ render action: 'hello_world'
end
end
@@ -129,50 +129,6 @@ class TestController < ActionController::Base
fresh_when(:last_modified => Time.now.utc.beginning_of_day, :etag => [ :foo, 123 ])
end
- def heading
- head :ok
- end
-
- # :ported:
- def double_render
- render :text => "hello"
- render :text => "world"
- end
-
- def double_redirect
- redirect_to :action => "double_render"
- redirect_to :action => "double_render"
- end
-
- def render_and_redirect
- render :text => "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}"
- end
-
- def render_to_string_with_inline_and_render
- render_to_string :inline => "<%= 'dlrow olleh'.reverse %>"
- render :template => "test/hello_world"
- end
-
- def rendering_with_conflicting_local_vars
- @name = "David"
- render :action => "potential_conflicts"
- end
-
- def hello_world_from_rxml_using_action
- render :action => "hello_world_from_rxml", :handlers => [:builder]
- end
-
- # :deprecated:
- def hello_world_from_rxml_using_template
- render :template => "test/hello_world_from_rxml", :handlers => [:builder]
- end
-
def head_created
head :created
end
@@ -217,6 +173,20 @@ class TestController < ActionController::Base
head :forbidden, :x_custom_header => "something"
end
+ def head_and_return
+ head :ok and return
+ raise 'should not reach this line'
+ end
+
+ def head_with_no_content
+ # Fill in the headers with dummy data to make
+ # sure they get removed during the testing
+ response.headers["Content-Type"] = "dummy"
+ response.headers["Content-Length"] = 42
+
+ head 204
+ end
+
private
def set_variable_for_layout
@@ -344,7 +314,6 @@ class LastModifiedRenderTest < ActionController::TestCase
assert_equal @last_modified, @response.headers['Last-Modified']
end
-
def test_responds_with_last_modified_with_record
get :conditional_hello_with_record
assert_equal @last_modified, @response.headers['Last-Modified']
@@ -355,6 +324,7 @@ class LastModifiedRenderTest < ActionController::TestCase
get :conditional_hello_with_record
assert_equal 304, @response.status.to_i
assert @response.body.blank?
+ assert_not_nil @response.etag
assert_equal @last_modified, @response.headers['Last-Modified']
end
@@ -373,6 +343,34 @@ class LastModifiedRenderTest < ActionController::TestCase
assert_equal @last_modified, @response.headers['Last-Modified']
end
+ def test_responds_with_last_modified_with_collection_of_records
+ get :conditional_hello_with_collection_of_records
+ assert_equal @last_modified, @response.headers['Last-Modified']
+ end
+
+ def test_request_not_modified_with_collection_of_records
+ @request.if_modified_since = @last_modified
+ get :conditional_hello_with_collection_of_records
+ assert_equal 304, @response.status.to_i
+ assert @response.body.blank?
+ assert_equal @last_modified, @response.headers['Last-Modified']
+ end
+
+ def test_request_not_modified_but_etag_differs_with_collection_of_records
+ @request.if_modified_since = @last_modified
+ @request.if_none_match = "234"
+ get :conditional_hello_with_collection_of_records
+ assert_response :success
+ end
+
+ def test_request_modified_with_collection_of_records
+ @request.if_modified_since = 'Thu, 16 Jul 2008 00:00:00 GMT'
+ get :conditional_hello_with_collection_of_records
+ assert_equal 200, @response.status.to_i
+ assert @response.body.present?
+ assert_equal @last_modified, @response.headers['Last-Modified']
+ end
+
def test_request_with_bang_gets_last_modified
get :conditional_hello_with_bangs
assert_equal @last_modified, @response.headers['Last-Modified']
@@ -518,21 +516,21 @@ class HeadRenderTest < ActionController::TestCase
end
def test_head_with_symbolic_status
- get :head_with_symbolic_status, :status => "ok"
+ get :head_with_symbolic_status, params: { status: "ok" }
assert_equal 200, @response.status
assert_response :ok
- get :head_with_symbolic_status, :status => "not_found"
+ get :head_with_symbolic_status, params: { status: "not_found" }
assert_equal 404, @response.status
assert_response :not_found
- get :head_with_symbolic_status, :status => "no_content"
+ get :head_with_symbolic_status, params: { status: "no_content" }
assert_equal 204, @response.status
assert !@response.headers.include?('Content-Length')
assert_response :no_content
Rack::Utils::SYMBOL_TO_STATUS_CODE.each do |status, code|
- get :head_with_symbolic_status, :status => status.to_s
+ get :head_with_symbolic_status, params: { status: status.to_s }
assert_equal code, @response.response_code
assert_response status
end
@@ -540,13 +538,21 @@ class HeadRenderTest < ActionController::TestCase
def test_head_with_integer_status
Rack::Utils::HTTP_STATUS_CODES.each do |code, message|
- get :head_with_integer_status, :status => code.to_s
+ get :head_with_integer_status, params: { status: code.to_s }
assert_equal message, @response.message
end
end
+ def test_head_with_no_content
+ get :head_with_no_content
+
+ assert_equal 204, @response.status
+ assert_nil @response.headers["Content-Type"]
+ assert_nil @response.headers["Content-Length"]
+ end
+
def test_head_with_string_status
- get :head_with_string_status, :status => "404 Eat Dirt"
+ get :head_with_string_status, params: { status: "404 Eat Dirt" }
assert_equal 404, @response.response_code
assert_equal "Not Found", @response.message
assert_response :not_found
@@ -559,4 +565,63 @@ class HeadRenderTest < ActionController::TestCase
assert_equal "something", @response.headers["X-Custom-Header"]
assert_response :forbidden
end
+
+ def test_head_returns_truthy_value
+ assert_nothing_raised do
+ get :head_and_return
+ end
+ end
+end
+
+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'
+ end
+ end
+ end
+
+ tests HttpCacheForeverController
+
+ def test_cache_with_public
+ get :cache_me_forever, params: {public: true}
+ assert_equal "max-age=#{100.years.to_i}, public", @response.headers["Cache-Control"]
+ assert_not_nil @response.etag
+ end
+
+ def test_cache_with_private
+ get :cache_me_forever
+ assert_equal "max-age=#{100.years.to_i}, private", @response.headers["Cache-Control"]
+ assert_not_nil @response.etag
+ assert_response :success
+ end
+
+ def test_cache_response_code_with_if_modified_since
+ get :cache_me_forever
+ assert_response :success
+ @request.if_modified_since = @response.headers['Last-Modified']
+ get :cache_me_forever
+ assert_response :not_modified
+ end
+
+ def test_cache_response_code_with_etag
+ get :cache_me_forever
+ assert_response :success
+ @request.if_modified_since = @response.headers['Last-Modified']
+ @request.if_none_match = @response.etag
+
+ get :cache_me_forever
+ assert_response :not_modified
+ @request.if_modified_since = @response.headers['Last-Modified']
+ @request.if_none_match = @response.etag
+
+ get :cache_me_forever, params: {version: 'v2'}
+ assert_response :success
+ @request.if_modified_since = @response.headers['Last-Modified']
+ @request.if_none_match = @response.etag
+
+ get :cache_me_forever, params: {version: 'v2'}
+ assert_response :not_modified
+ end
end
diff --git a/actionpack/test/controller/render_xml_test.rb b/actionpack/test/controller/render_xml_test.rb
index 4f280c4bec..7a91577b17 100644
--- a/actionpack/test/controller/render_xml_test.rb
+++ b/actionpack/test/controller/render_xml_test.rb
@@ -81,7 +81,7 @@ class RenderXmlTest < ActionController::TestCase
end
def test_should_render_formatted_xml_erb_template
- get :formatted_xml_erb, :format => :xml
+ get :formatted_xml_erb, format: :xml
assert_equal '<test>passed formatted xml erb</test>', @response.body
end
@@ -91,7 +91,7 @@ class RenderXmlTest < ActionController::TestCase
end
def test_should_use_implicit_content_type
- get :implicit_content_type, :format => 'atom'
+ get :implicit_content_type, format: 'atom'
assert_equal Mime::ATOM, @response.content_type
end
end
diff --git a/actionpack/test/controller/renderer_test.rb b/actionpack/test/controller/renderer_test.rb
new file mode 100644
index 0000000000..b55a25430b
--- /dev/null
+++ b/actionpack/test/controller/renderer_test.rb
@@ -0,0 +1,103 @@
+require 'abstract_unit'
+
+class RendererTest < ActiveSupport::TestCase
+ test 'creating with a controller' do
+ controller = CommentsController
+ renderer = ActionController::Renderer.for controller
+
+ assert_equal controller, renderer.controller
+ end
+
+ test 'creating from a controller' do
+ controller = AccountsController
+ renderer = controller.renderer
+
+ assert_equal controller, renderer.controller
+ end
+
+ test 'rendering with a class renderer' do
+ renderer = ApplicationController.renderer
+ content = renderer.render template: 'ruby_template'
+
+ assert_equal 'Hello from Ruby code', content
+ end
+
+ test 'rendering with an instance renderer' do
+ renderer = ApplicationController.renderer.new
+ content = renderer.render file: 'test/hello_world'
+
+ assert_equal 'Hello world!', content
+ end
+
+ test 'rendering with a controller class' do
+ assert_equal 'Hello world!', ApplicationController.render('test/hello_world')
+ end
+
+ test 'rendering with locals' do
+ renderer = ApplicationController.renderer
+ content = renderer.render template: 'test/render_file_with_locals',
+ locals: { secret: 'bar' }
+
+ assert_equal "The secret is bar\n", content
+ end
+
+ test 'rendering with assigns' do
+ renderer = ApplicationController.renderer
+ content = renderer.render template: 'test/render_file_with_ivar',
+ assigns: { secret: 'foo' }
+
+ assert_equal "The secret is foo\n", content
+ end
+
+ test 'rendering with custom env' do
+ renderer = ApplicationController.renderer.new method: 'post'
+ content = renderer.render inline: '<%= request.post? %>'
+
+ assert_equal 'true', content
+ end
+
+ test 'rendering with defaults' do
+ renderer = ApplicationController.renderer
+ renderer.defaults[:https] = true
+ content = renderer.render inline: '<%= request.ssl? %>'
+
+ assert_equal 'true', content
+ end
+
+ test 'same defaults from the same controller' do
+ renderer_defaults = ->(controller) { controller.renderer.defaults }
+
+ assert renderer_defaults[AccountsController].equal? renderer_defaults[AccountsController]
+ assert_not renderer_defaults[AccountsController].equal? renderer_defaults[CommentsController]
+ end
+
+ test 'rendering with different formats' do
+ html = 'Hello world!'
+ xml = "<p>Hello world!</p>\n"
+
+ assert_equal html, render['respond_to/using_defaults']
+ assert_equal xml, render['respond_to/using_defaults.xml.builder']
+ assert_equal xml, render['respond_to/using_defaults', formats: :xml]
+ end
+
+ test 'rendering with helpers' do
+ assert_equal "<p>1\n<br />2</p>", render[inline: '<%= simple_format "1\n2" %>']
+ end
+
+ test 'rendering from inherited renderer' do
+ inherited = Class.new ApplicationController.renderer do
+ defaults[:script_name] = 'script'
+ def render(options)
+ super options.merge(locals: { param: :value })
+ end
+ end
+
+ template = '<%= url_for controller: :foo, action: :bar, param: param %>'
+ assert_equal 'script/foo/bar?param=value', inherited.render(inline: template)
+ end
+
+ private
+ def render
+ @render ||= ApplicationController.renderer.method(:render)
+ end
+end
diff --git a/actionpack/test/controller/request/test_request_test.rb b/actionpack/test/controller/request/test_request_test.rb
index e624f11773..77a2f68b1c 100644
--- a/actionpack/test/controller/request/test_request_test.rb
+++ b/actionpack/test/controller/request/test_request_test.rb
@@ -24,12 +24,4 @@ class ActionController::TestRequestTest < ActiveSupport::TestCase
end
end
- def test_session_id_exists_by_default
- assert_not_nil(@request.session_options[:id])
- end
-
- def test_session_id_different_on_each_call
- assert_not_equal(@request.session_options[:id], ActionController::TestRequest.new.session_options[:id])
- end
-
end
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index 3e0bfe8d14..8887f291cf 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -103,6 +103,31 @@ class RequestForgeryProtectionControllerUsingNullSession < ActionController::Bas
end
end
+class PrependProtectForgeryBaseController < ActionController::Base
+ before_action :custom_action
+ attr_accessor :called_callbacks
+
+ def index
+ render inline: 'OK'
+ end
+
+ protected
+
+ def add_called_callback(name)
+ @called_callbacks ||= []
+ @called_callbacks << name
+ end
+
+
+ def custom_action
+ add_called_callback("custom_action")
+ end
+
+ def verify_authenticity_token
+ add_called_callback("verify_authenticity_token")
+ end
+end
+
class FreeCookieController < RequestForgeryProtectionControllerUsingResetSession
self.allow_forgery_protection = false
@@ -237,23 +262,23 @@ module RequestForgeryProtectionTests
end
def test_should_not_allow_xhr_post_without_token
- assert_blocked { xhr :post, :index }
+ assert_blocked { post :index, xhr: true }
end
def test_should_allow_post_with_token
- assert_not_blocked { post :index, :custom_authenticity_token => @token }
+ assert_not_blocked { post :index, params: { custom_authenticity_token: @token } }
end
def test_should_allow_patch_with_token
- assert_not_blocked { patch :index, :custom_authenticity_token => @token }
+ assert_not_blocked { patch :index, params: { custom_authenticity_token: @token } }
end
def test_should_allow_put_with_token
- assert_not_blocked { put :index, :custom_authenticity_token => @token }
+ assert_not_blocked { put :index, params: { custom_authenticity_token: @token } }
end
def test_should_allow_delete_with_token
- assert_not_blocked { delete :index, :custom_authenticity_token => @token }
+ assert_not_blocked { delete :index, params: { custom_authenticity_token: @token } }
end
def test_should_allow_post_with_token_in_header
@@ -315,21 +340,21 @@ module RequestForgeryProtectionTests
get :negotiate_same_origin
end
- assert_cross_origin_not_blocked { xhr :get, :same_origin_js }
- assert_cross_origin_not_blocked { xhr :get, :same_origin_js, format: 'js' }
+ assert_cross_origin_not_blocked { get :same_origin_js, xhr: true }
+ assert_cross_origin_not_blocked { get :same_origin_js, xhr: true, format: 'js'}
assert_cross_origin_not_blocked do
@request.accept = 'text/javascript'
- xhr :get, :negotiate_same_origin
+ get :negotiate_same_origin, xhr: true
end
end
# Allow non-GET requests since GET is all a remote <script> tag can muster.
def test_should_allow_non_get_js_without_xhr_header
- assert_cross_origin_not_blocked { post :same_origin_js, custom_authenticity_token: @token }
- assert_cross_origin_not_blocked { post :same_origin_js, format: 'js', custom_authenticity_token: @token }
+ assert_cross_origin_not_blocked { post :same_origin_js, params: { custom_authenticity_token: @token } }
+ assert_cross_origin_not_blocked { post :same_origin_js, params: { format: 'js', custom_authenticity_token: @token } }
assert_cross_origin_not_blocked do
@request.accept = 'text/javascript'
- post :negotiate_same_origin, custom_authenticity_token: @token
+ post :negotiate_same_origin, params: { custom_authenticity_token: @token}
end
end
@@ -341,11 +366,18 @@ module RequestForgeryProtectionTests
get :negotiate_cross_origin
end
- assert_cross_origin_not_blocked { xhr :get, :cross_origin_js }
- assert_cross_origin_not_blocked { xhr :get, :cross_origin_js, format: 'js' }
+ assert_cross_origin_not_blocked { get :cross_origin_js, xhr: true }
+ assert_cross_origin_not_blocked { get :cross_origin_js, xhr: true, format: 'js' }
assert_cross_origin_not_blocked do
@request.accept = 'text/javascript'
- xhr :get, :negotiate_cross_origin
+ get :negotiate_cross_origin, xhr: true
+ end
+ end
+
+ def test_should_not_raise_error_if_token_is_not_a_string
+ @controller.unstub(:valid_authenticity_token?)
+ assert_blocked do
+ patch :index, params: { custom_authenticity_token: { foo: 'bar' } }
end
end
@@ -431,6 +463,41 @@ class RequestForgeryProtectionControllerUsingExceptionTest < ActionController::T
end
end
+class PrependProtectForgeryBaseControllerTest < ActionController::TestCase
+ PrependTrueController = Class.new(PrependProtectForgeryBaseController) do
+ protect_from_forgery prepend: true
+ end
+
+ PrependFalseController = Class.new(PrependProtectForgeryBaseController) do
+ protect_from_forgery prepend: false
+ end
+
+ PrependDefaultController = Class.new(PrependProtectForgeryBaseController) do
+ protect_from_forgery
+ end
+
+ def test_verify_authenticity_token_is_prepended
+ @controller = PrependTrueController.new
+ get :index
+ expected_callback_order = ["verify_authenticity_token", "custom_action"]
+ assert_equal(expected_callback_order, @controller.called_callbacks)
+ end
+
+ def test_verify_authenticity_token_is_not_prepended
+ @controller = PrependFalseController.new
+ get :index
+ expected_callback_order = ["custom_action", "verify_authenticity_token"]
+ assert_equal(expected_callback_order, @controller.called_callbacks)
+ end
+
+ def test_verify_authenticity_token_is_prepended_by_default
+ @controller = PrependDefaultController.new
+ get :index
+ expected_callback_order = ["verify_authenticity_token", "custom_action"]
+ assert_equal(expected_callback_order, @controller.called_callbacks)
+ end
+end
+
class FreeCookieControllerTest < ActionController::TestCase
def setup
@controller = FreeCookieController.new
@@ -483,7 +550,7 @@ class CustomAuthenticityParamControllerTest < ActionController::TestCase
@controller.stubs(:valid_authenticity_token?).returns(:true)
begin
- post :index, :custom_token_name => 'foobar'
+ post :index, params: { custom_token_name: 'foobar' }
assert_equal 0, @logger.logged(:warn).size
ensure
ActionController::Base.logger = @old_logger
@@ -494,7 +561,7 @@ class CustomAuthenticityParamControllerTest < ActionController::TestCase
ActionController::Base.logger = @logger
begin
- post :index, :custom_token_name => 'bazqux'
+ post :index, params: { custom_token_name: 'bazqux' }
assert_equal 1, @logger.logged(:warn).size
ensure
ActionController::Base.logger = @old_logger
diff --git a/actionpack/test/controller/required_params_test.rb b/actionpack/test/controller/required_params_test.rb
index 6803dbbb62..a901e56332 100644
--- a/actionpack/test/controller/required_params_test.rb
+++ b/actionpack/test/controller/required_params_test.rb
@@ -12,21 +12,21 @@ class ActionControllerRequiredParamsTest < ActionController::TestCase
test "missing required parameters will raise exception" do
assert_raise ActionController::ParameterMissing do
- post :create, { magazine: { name: "Mjallo!" } }
+ post :create, params: { magazine: { name: "Mjallo!" } }
end
assert_raise ActionController::ParameterMissing do
- post :create, { book: { title: "Mjallo!" } }
+ post :create, params: { book: { title: "Mjallo!" } }
end
end
test "required parameters that are present will not raise" do
- post :create, { book: { name: "Mjallo!" } }
+ post :create, params: { book: { name: "Mjallo!" } }
assert_response :ok
end
test "required parameters with false value will not raise" do
- post :create, { book: { name: false } }
+ post :create, params: { book: { name: false } }
assert_response :ok
end
end
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index 0e15883f43..f3da2df3ef 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -1047,6 +1047,28 @@ class ResourcesTest < ActionController::TestCase
end
end
+ def test_assert_routing_accepts_all_as_a_valid_method
+ with_routing do |set|
+ set.draw do
+ match "/products", to: "products#show", via: :all
+ end
+
+ assert_routing({ method: "all", path: "/products" }, { controller: "products", action: "show" })
+ end
+ end
+
+ def test_assert_routing_fails_when_not_all_http_methods_are_recognized
+ with_routing do |set|
+ set.draw do
+ match "/products", to: "products#show", via: [:get, :post, :put]
+ end
+
+ assert_raises(Minitest::Assertion) do
+ assert_routing({ method: "all", path: "/products" }, { controller: "products", action: "show" })
+ end
+ end
+ end
+
def test_singleton_resource_name_is_not_singularized
with_singleton_resources(:preferences) do
assert_singleton_restful_for :preferences
@@ -1184,10 +1206,10 @@ class ResourcesTest < ActionController::TestCase
end
@controller = "#{options[:options][:controller].camelize}Controller".constantize.new
- @controller.singleton_class.send(:include, @routes.url_helpers)
+ @controller.singleton_class.include(@routes.url_helpers)
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
- get :index, options[:options]
+ get :index, params: options[:options]
options[:options].delete :action
path = "#{options[:as] || controller_name}"
@@ -1254,10 +1276,10 @@ class ResourcesTest < ActionController::TestCase
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
- @controller.singleton_class.send(:include, @routes.url_helpers)
+ @controller.singleton_class.include(@routes.url_helpers)
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
- get :show, options[:options]
+ get :show, params: options[:options]
options[:options].delete :action
full_path = "/#{options[:path_prefix]}#{options[:as] || singleton_name}"
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index c18914cc8e..2d08987ca6 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'controller/fake_controllers'
require 'active_support/core_ext/object/with_options'
@@ -884,13 +883,13 @@ class RouteSetTest < ActiveSupport::TestCase
set.draw { get ':controller/(:action(/:id))' }
path, extras = set.generate_extras(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
assert_equal "/foo/bar/15", path
- assert_equal %w(that this), extras.map { |e| e.to_s }.sort
+ assert_equal %w(that this), extras.map(&:to_s).sort
end
def test_extra_keys
set.draw { get ':controller/:action/:id' }
extras = set.extra_keys(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
- assert_equal %w(that this), extras.map { |e| e.to_s }.sort
+ assert_equal %w(that this), extras.map(&:to_s).sort
end
def test_generate_extras_not_first
@@ -900,7 +899,7 @@ class RouteSetTest < ActiveSupport::TestCase
end
path, extras = set.generate_extras(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
assert_equal "/foo/bar/15", path
- assert_equal %w(that this), extras.map { |e| e.to_s }.sort
+ assert_equal %w(that this), extras.map(&:to_s).sort
end
def test_generate_not_first
@@ -918,7 +917,7 @@ class RouteSetTest < ActiveSupport::TestCase
get ':controller/:action/:id'
end
extras = set.extra_keys(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
- assert_equal %w(that this), extras.map { |e| e.to_s }.sort
+ assert_equal %w(that this), extras.map(&:to_s).sort
end
def test_draw
@@ -1001,6 +1000,9 @@ class RouteSetTest < ActiveSupport::TestCase
assert_equal "http://test.host/people?baz=bar#location",
controller.send(:index_url, :baz => "bar", :anchor => 'location')
+
+ assert_equal "http://test.host/people", controller.send(:index_url, anchor: nil)
+ assert_equal "http://test.host/people", controller.send(:index_url, anchor: false)
end
def test_named_route_url_method_with_port
@@ -1383,7 +1385,7 @@ class RouteSetTest < ActiveSupport::TestCase
url = controller.url_for({ :controller => "connection", :only_path => true })
assert_equal "/connection/connection", url
- url = controller.url_for({ :use_route => :family_connection,
+ url = controller.url_for({ :use_route => "family_connection",
:controller => "connection", :only_path => true })
assert_equal "/connection", url
end
diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb
index c002cf4d8f..36c57ec9b2 100644
--- a/actionpack/test/controller/send_file_test.rb
+++ b/actionpack/test/controller/send_file_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
module TestFileUtils
diff --git a/actionpack/test/controller/show_exceptions_test.rb b/actionpack/test/controller/show_exceptions_test.rb
index f7eba1ef43..786dc15444 100644
--- a/actionpack/test/controller/show_exceptions_test.rb
+++ b/actionpack/test/controller/show_exceptions_test.rb
@@ -58,13 +58,13 @@ module ShowExceptions
class ShowExceptionsOverriddenTest < ActionDispatch::IntegrationTest
test 'show error page' do
@app = ShowExceptionsOverriddenController.action(:boom)
- get '/', {'detailed' => '0'}
+ get '/', params: { 'detailed' => '0' }
assert_equal "500 error fixture\n", body
end
test 'show diagnostics message' do
@app = ShowExceptionsOverriddenController.action(:boom)
- get '/', {'detailed' => '1'}
+ get '/', params: { 'detailed' => '1' }
assert_match(/boom/, body)
end
end
@@ -72,23 +72,23 @@ module ShowExceptions
class ShowExceptionsFormatsTest < ActionDispatch::IntegrationTest
def test_render_json_exception
@app = ShowExceptionsOverriddenController.action(:boom)
- get "/", {}, 'HTTP_ACCEPT' => 'application/json'
+ get "/", headers: { 'HTTP_ACCEPT' => 'application/json' }
assert_response :internal_server_error
assert_equal 'application/json', response.content_type.to_s
- assert_equal({ :status => '500', :error => 'Internal Server Error' }.to_json, response.body)
+ assert_equal({ :status => 500, :error => 'Internal Server Error' }.to_json, response.body)
end
def test_render_xml_exception
@app = ShowExceptionsOverriddenController.action(:boom)
- get "/", {}, 'HTTP_ACCEPT' => 'application/xml'
+ get "/", headers: { 'HTTP_ACCEPT' => 'application/xml' }
assert_response :internal_server_error
assert_equal 'application/xml', response.content_type.to_s
- assert_equal({ :status => '500', :error => 'Internal Server Error' }.to_xml, response.body)
+ assert_equal({ :status => 500, :error => 'Internal Server Error' }.to_xml, response.body)
end
def test_render_fallback_exception
@app = ShowExceptionsOverriddenController.action(:boom)
- get "/", {}, 'HTTP_ACCEPT' => 'text/csv'
+ get "/", headers: { 'HTTP_ACCEPT' => 'text/csv' }
assert_response :internal_server_error
assert_equal 'text/html', response.content_type.to_s
end
@@ -101,7 +101,7 @@ module ShowExceptions
@app.instance_variable_set(:@exceptions_app, nil)
$stderr = StringIO.new
- get '/', {}, 'HTTP_ACCEPT' => 'text/json'
+ get '/', headers: { 'HTTP_ACCEPT' => 'text/json' }
assert_response :internal_server_error
assert_equal 'text/plain', response.content_type.to_s
ensure
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index 9d7abd5e94..e348749f78 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -1,61 +1,67 @@
require 'abstract_unit'
require 'controller/fake_controllers'
require 'active_support/json/decoding'
+require 'rails/engine'
class TestCaseTest < ActionController::TestCase
class TestController < ActionController::Base
def no_op
- render :text => 'dummy'
+ render text: 'dummy'
end
def set_flash
flash["test"] = ">#{flash["test"]}<"
+ render text: 'ignore me'
+ end
+
+ def delete_flash
+ flash.delete("test")
render :text => 'ignore me'
end
def set_flash_now
flash.now["test_now"] = ">#{flash["test_now"]}<"
- render :text => 'ignore me'
+ render text: 'ignore me'
end
def set_session
session['string'] = 'A wonder'
session[:symbol] = 'it works'
- render :text => 'Success'
+ render text: 'Success'
end
def reset_the_session
reset_session
- render :text => 'ignore me'
+ render text: '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 text: request.raw_post
end
def render_body
- render :text => request.body.read
+ render text: request.body.read
end
def test_params
- render :text => params.inspect
+ render text: params.inspect
end
def test_uri
- render :text => request.fullpath
+ render text: request.fullpath
end
def test_format
- render :text => request.format
+ render text: request.format
end
def test_query_string
- render :text => request.query_string
+ render text: request.query_string
end
def test_protocol
- render :text => request.protocol
+ render text: request.protocol
end
def test_headers
@@ -63,7 +69,7 @@ class TestCaseTest < ActionController::TestCase
end
def test_html_output
- render :text => <<HTML
+ render text: <<HTML
<html>
<body>
<a href="/"><img src="/images/button.png" /></a>
@@ -85,7 +91,7 @@ HTML
def test_xml_output
response.content_type = "application/xml"
- render :text => <<XML
+ render text: <<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>
@@ -94,15 +100,15 @@ XML
end
def test_only_one_param
- render :text => (params[:left] && params[:right]) ? "EEP, Both here!" : "OK"
+ render text: (params[:left] && params[:right]) ? "EEP, Both here!" : "OK"
end
def test_remote_addr
- render :text => (request.remote_addr || "not specified")
+ render text: (request.remote_addr || "not specified")
end
def test_file_upload
- render :text => params[:file].size
+ render text: params[:file].size
end
def test_send_file
@@ -110,32 +116,40 @@ XML
end
def redirect_to_same_controller
- redirect_to :controller => 'test', :action => 'test_uri', :id => 5
+ redirect_to controller: 'test', action: 'test_uri', id: 5
end
def redirect_to_different_controller
- redirect_to :controller => 'fail', :id => 5
+ redirect_to controller: 'fail', id: 5
end
def create
- head :created, :location => 'created resource'
+ head :created, location: 'created resource'
end
def delete_cookie
cookies.delete("foo")
- render :nothing => true
+ render nothing: true
end
def test_assigns
@foo = "foo"
- @foo_hash = {:foo => :bar}
- render :nothing => true
+ @foo_hash = { foo: :bar }
+ render nothing: true
+ end
+
+ def test_without_body
+ render html: '<div class="foo"></div>'.html_safe
+ end
+
+ def test_with_body
+ render html: '<body class="foo"></body>'.html_safe
end
private
def generate_url(opts)
- url_for(opts.merge(:action => "test_uri"))
+ url_for(opts.merge(action: "test_uri"))
end
end
@@ -155,7 +169,7 @@ XML
class ViewAssignsController < ActionController::Base
def test_assigns
@foo = "foo"
- render :nothing => true
+ render nothing: true
end
def view_assigns
@@ -179,6 +193,19 @@ XML
end
end
+ def test_assert_select_without_body
+ get :test_without_body
+
+ assert_select 'body', 0
+ assert_select 'div.foo'
+ end
+
+ def test_assert_select_with_body
+ get :test_with_body
+
+ assert_select 'body.foo'
+ end
+
def test_url_options_reset
@controller = DefaultUrlOptionsCachingController.new
get :test_url_options_reset
@@ -187,32 +214,50 @@ XML
end
def test_raw_post_handling
- params = Hash[:page, {:name => 'page name'}, 'some key', 123]
- post :render_raw_post, params.dup
+ params = Hash[:page, { name: 'page name' }, 'some key', 123]
+ post :render_raw_post, params: params.dup
assert_equal params.to_query, @response.body
end
def test_body_stream
- params = Hash[:page, { :name => 'page name' }, 'some key', 123]
+ params = Hash[:page, { name: 'page name' }, 'some key', 123]
- post :render_body, params.dup
+ post :render_body, params: params.dup
+
+ assert_equal params.to_query, @response.body
+ end
+
+ def test_deprecated_body_stream
+ params = Hash[:page, { name: 'page name' }, 'some key', 123]
+
+ assert_deprecated { post :render_body, params.dup }
assert_equal params.to_query, @response.body
end
def test_document_body_and_params_with_post
- post :test_params, :id => 1
- assert_equal("{\"id\"=>\"1\", \"controller\"=>\"test_case_test/test\", \"action\"=>\"test_params\"}", @response.body)
+ post :test_params, params: { id: 1 }
+ assert_equal(%({"id"=>"1", "controller"=>"test_case_test/test", "action"=>"test_params"}), @response.body)
end
def test_document_body_with_post
- post :render_body, "document body"
+ post :render_body, body: "document body"
+ assert_equal "document body", @response.body
+ end
+
+ def test_deprecated_document_body_with_post
+ assert_deprecated { post :render_body, "document body" }
assert_equal "document body", @response.body
end
def test_document_body_with_put
- put :render_body, "document body"
+ put :render_body, body: "document body"
+ assert_equal "document body", @response.body
+ end
+
+ def test_deprecated_document_body_with_put
+ assert_deprecated { put :render_body, "document body" }
assert_equal "document body", @response.body
end
@@ -221,25 +266,42 @@ XML
assert_equal 200, @response.status
end
- def test_head_params_as_string
- assert_raise(NoMethodError) { head :test_params, "document body", :id => 10 }
- end
-
def test_process_without_flash
process :set_flash
assert_equal '><', flash['test']
end
+ def test_deprecated_process_with_flash
+ assert_deprecated { process :set_flash, "GET", nil, nil, { "test" => "value" } }
+ assert_equal '>value<', flash['test']
+ end
+
def test_process_with_flash
- process :set_flash, "GET", nil, nil, { "test" => "value" }
+ process :set_flash,
+ method: "GET",
+ flash: { "test" => "value" }
assert_equal '>value<', flash['test']
end
+ def test_deprecated_process_with_flash_now
+ assert_deprecated { process :set_flash_now, "GET", nil, nil, { "test_now" => "value_now" } }
+ assert_equal '>value_now<', flash['test_now']
+ end
+
def test_process_with_flash_now
- process :set_flash_now, "GET", nil, nil, { "test_now" => "value_now" }
+ process :set_flash_now,
+ method: "GET",
+ flash: { "test_now" => "value_now" }
assert_equal '>value_now<', flash['test_now']
end
+ def test_process_delete_flash
+ process :set_flash
+ process :delete_flash
+ assert_empty flash
+ assert_empty session
+ end
+
def test_process_with_session
process :set_session
assert_equal 'A wonder', session['string'], "A value stored in the session should be available by string key"
@@ -249,22 +311,48 @@ XML
end
def test_process_with_session_arg
- process :no_op, "GET", nil, { 'string' => 'value1', :symbol => 'value2' }
+ assert_deprecated { process :no_op, "GET", nil, { 'string' => 'value1', symbol: 'value2' } }
assert_equal 'value1', session['string']
assert_equal 'value1', session[:string]
assert_equal 'value2', session['symbol']
assert_equal 'value2', session[:symbol]
end
+ def test_process_with_session_kwarg
+ process :no_op, method: "GET", session: { 'string' => 'value1', symbol: 'value2' }
+ assert_equal 'value1', session['string']
+ assert_equal 'value1', session[:string]
+ assert_equal 'value2', session['symbol']
+ assert_equal 'value2', session[:symbol]
+ end
+
+ def test_deprecated_process_merges_session_arg
+ session[:foo] = 'bar'
+ assert_deprecated {
+ get :no_op, nil, { bar: 'baz' }
+ }
+ assert_equal 'bar', session[:foo]
+ assert_equal 'baz', session[:bar]
+ end
+
def test_process_merges_session_arg
session[:foo] = 'bar'
- get :no_op, nil, { :bar => 'baz' }
+ get :no_op, session: { bar: 'baz' }
assert_equal 'bar', session[:foo]
assert_equal 'baz', session[:bar]
end
+ def test_deprecated_merged_session_arg_is_retained_across_requests
+ assert_deprecated {
+ get :no_op, nil, { foo: 'bar' }
+ }
+ assert_equal 'bar', session[:foo]
+ get :no_op
+ assert_equal 'bar', session[:foo]
+ end
+
def test_merged_session_arg_is_retained_across_requests
- get :no_op, nil, { :foo => 'bar' }
+ get :no_op, session: { foo: 'bar' }
assert_equal 'bar', session[:foo]
get :no_op
assert_equal 'bar', session[:foo]
@@ -272,7 +360,7 @@ XML
def test_process_overwrites_existing_session_arg
session[:foo] = 'bar'
- get :no_op, nil, { :foo => 'baz' }
+ get :no_op, session: { foo: 'baz' }
assert_equal 'baz', session[:foo]
end
@@ -299,19 +387,40 @@ XML
assert_equal "/test_case_test/test/test_uri", @response.body
end
+ def test_process_with_symbol_method
+ process :test_uri, method: :get
+ assert_equal "/test_case_test/test/test_uri", @response.body
+ end
+
+ def test_deprecated_process_with_request_uri_with_params
+ assert_deprecated { process :test_uri, "GET", id: 7 }
+ assert_equal "/test_case_test/test/test_uri/7", @response.body
+ end
+
def test_process_with_request_uri_with_params
- process :test_uri, "GET", :id => 7
+ process :test_uri,
+ method: "GET",
+ params: { id: 7 }
+
assert_equal "/test_case_test/test/test_uri/7", @response.body
end
+ def test_deprecated_process_with_request_uri_with_params_with_explicit_uri
+ @request.env['PATH_INFO'] = "/explicit/uri"
+ assert_deprecated { process :test_uri, "GET", id: 7 }
+ assert_equal "/explicit/uri", @response.body
+ end
+
def test_process_with_request_uri_with_params_with_explicit_uri
@request.env['PATH_INFO'] = "/explicit/uri"
- process :test_uri, "GET", :id => 7
+ process :test_uri, method: "GET", params: { id: 7 }
assert_equal "/explicit/uri", @response.body
end
def test_process_with_query_string
- process :test_query_string, "GET", :q => 'test'
+ process :test_query_string,
+ method: "GET",
+ params: { q: 'test' }
assert_equal "q=test", @response.body
end
@@ -323,9 +432,9 @@ XML
end
def test_multiple_calls
- process :test_only_one_param, "GET", :left => true
+ process :test_only_one_param, method: "GET", params: { left: true }
assert_equal "OK", @response.body
- process :test_only_one_param, "GET", :right => true
+ process :test_only_one_param, method: "GET", params: { right: true }
assert_equal "OK", @response.body
end
@@ -340,7 +449,7 @@ XML
assert_equal "foo", assigns["foo"]
# but the assigned variable should not have its own keys stringified
- expected_hash = { :foo => :bar }
+ expected_hash = { foo: :bar }
assert_equal expected_hash, assigns(:foo_hash)
end
@@ -368,21 +477,21 @@ XML
end
def test_assert_generates
- assert_generates 'controller/action/5', :controller => 'controller', :action => 'action', :id => '5'
- assert_generates 'controller/action/7', {:id => "7"}, {:controller => "controller", :action => "action"}
- assert_generates 'controller/action/5', {:controller => "controller", :action => "action", :id => "5", :name => "bob"}, {}, {:name => "bob"}
- assert_generates 'controller/action/7', {:id => "7", :name => "bob"}, {:controller => "controller", :action => "action"}, {:name => "bob"}
- assert_generates 'controller/action/7', {:id => "7"}, {:controller => "controller", :action => "action", :name => "bob"}, {}
+ assert_generates 'controller/action/5', controller: 'controller', action: 'action', id: '5'
+ assert_generates 'controller/action/7', { id: "7" }, { controller: "controller", action: "action" }
+ assert_generates 'controller/action/5', { controller: "controller", action: "action", id: "5", name: "bob" }, {}, { name: "bob" }
+ assert_generates 'controller/action/7', { id: "7", name: "bob" }, { controller: "controller", action: "action" }, { name: "bob" }
+ assert_generates 'controller/action/7', { id: "7" }, { controller: "controller", action: "action", name: "bob" }, {}
end
def test_assert_routing
- assert_routing 'content', :controller => 'content', :action => 'index'
+ assert_routing 'content', controller: 'content', action: 'index'
end
def test_assert_routing_with_method
with_routing do |set|
set.draw { resources(:content) }
- assert_routing({ :method => 'post', :path => 'content' }, { :controller => 'content', :action => 'create' })
+ assert_routing({ method: 'post', path: 'content' }, { controller: 'content', action: 'create' })
end
end
@@ -394,29 +503,75 @@ XML
end
end
- assert_routing 'admin/user', :controller => 'admin/user', :action => 'index'
+ assert_routing 'admin/user', controller: 'admin/user', action: 'index'
end
end
def test_assert_routing_with_glob
with_routing do |set|
set.draw { get('*path' => "pages#show") }
- assert_routing('/company/about', { :controller => 'pages', :action => 'show', :path => 'company/about' })
+ assert_routing('/company/about', { controller: 'pages', action: 'show', path: 'company/about' })
end
end
+ def test_deprecated_params_passing
+ assert_deprecated {
+ get :test_params, page: { name: "Page name", month: '4', year: '2004', day: '6' }
+ }
+ parsed_params = eval(@response.body)
+ assert_equal(
+ {
+ 'controller' => 'test_case_test/test', 'action' => 'test_params',
+ 'page' => { 'name' => "Page name", 'month' => '4', 'year' => '2004', 'day' => '6' }
+ },
+ parsed_params
+ )
+ end
+
def test_params_passing
- get :test_params, :page => {:name => "Page name", :month => '4', :year => '2004', :day => '6'}
+ get :test_params, params: {
+ page: {
+ name: "Page name",
+ month: '4',
+ year: '2004',
+ day: '6'
+ }
+ }
+ parsed_params = eval(@response.body)
+ assert_equal(
+ {
+ 'controller' => 'test_case_test/test', 'action' => 'test_params',
+ 'page' => { 'name' => "Page name", 'month' => '4', 'year' => '2004', 'day' => '6' }
+ },
+ parsed_params
+ )
+ end
+
+ def test_kwarg_params_passing_with_session_and_flash
+ get :test_params, params: {
+ page: {
+ name: "Page name",
+ month: '4',
+ year: '2004',
+ day: '6'
+ }
+ }, session: { 'foo' => 'bar' }, flash: { notice: 'created' }
+
parsed_params = eval(@response.body)
assert_equal(
{'controller' => 'test_case_test/test', 'action' => 'test_params',
'page' => {'name' => "Page name", 'month' => '4', 'year' => '2004', 'day' => '6'}},
parsed_params
)
+
+ assert_equal 'bar', session[:foo]
+ assert_equal 'created', flash[:notice]
end
def test_params_passing_with_fixnums
- get :test_params, :page => {:name => "Page name", :month => 4, :year => 2004, :day => 6}
+ get :test_params, params: {
+ page: { name: "Page name", month: 4, year: 2004, day: 6 }
+ }
parsed_params = eval(@response.body)
assert_equal(
{'controller' => 'test_case_test/test', 'action' => 'test_params',
@@ -426,7 +581,7 @@ XML
end
def test_params_passing_with_fixnums_when_not_html_request
- get :test_params, :format => 'json', :count => 999
+ get :test_params, params: { format: 'json', count: 999 }
parsed_params = eval(@response.body)
assert_equal(
{'controller' => 'test_case_test/test', 'action' => 'test_params',
@@ -436,7 +591,17 @@ XML
end
def test_params_passing_path_parameter_is_string_when_not_html_request
- get :test_params, :format => 'json', :id => 1
+ get :test_params, params: { format: 'json', id: 1 }
+ parsed_params = eval(@response.body)
+ assert_equal(
+ {'controller' => 'test_case_test/test', 'action' => 'test_params',
+ 'format' => 'json', 'id' => '1' },
+ parsed_params
+ )
+ end
+
+ def test_deprecated_params_passing_path_parameter_is_string_when_not_html_request
+ assert_deprecated { get :test_params, format: 'json', id: 1 }
parsed_params = eval(@response.body)
assert_equal(
{'controller' => 'test_case_test/test', 'action' => 'test_params',
@@ -447,7 +612,9 @@ XML
def test_params_passing_with_frozen_values
assert_nothing_raised do
- get :test_params, :frozen => 'icy'.freeze, :frozens => ['icy'.freeze].freeze, :deepfreeze => { :frozen => 'icy'.freeze }.freeze
+ get :test_params, params: {
+ frozen: 'icy'.freeze, frozens: ['icy'.freeze].freeze, deepfreeze: { frozen: 'icy'.freeze }.freeze
+ }
end
parsed_params = eval(@response.body)
assert_equal(
@@ -458,8 +625,8 @@ XML
end
def test_params_passing_doesnt_modify_in_place
- page = {:name => "Page name", :month => 4, :year => 2004, :day => 6}
- get :test_params, :page => page
+ page = { name: "Page name", month: 4, year: 2004, day: 6 }
+ get :test_params, params: { page: page }
assert_equal 2004, page[:year]
end
@@ -482,25 +649,32 @@ XML
end
def test_id_converted_to_string
- get :test_params, :id => 20, :foo => Object.new
+ get :test_params, params: {
+ id: 20, foo: Object.new
+ }
+ assert_kind_of String, @request.path_parameters[:id]
+ end
+
+ def test_deprecared_id_converted_to_string
+ assert_deprecated { get :test_params, id: 20, foo: Object.new}
assert_kind_of String, @request.path_parameters[:id]
end
def test_array_path_parameter_handled_properly
with_routing do |set|
set.draw do
- get 'file/*path', :to => 'test_case_test/test#test_params'
+ get 'file/*path', to: 'test_case_test/test#test_params'
get ':controller/:action'
end
- get :test_params, :path => ['hello', 'world']
+ get :test_params, params: { path: ['hello', 'world'] }
assert_equal ['hello', 'world'], @request.path_parameters[:path]
assert_equal 'hello/world', @request.path_parameters[:path].to_param
end
end
def test_assert_realistic_path_parameters
- get :test_params, :id => 20, :foo => Object.new
+ get :test_params, params: { id: 20, foo: Object.new }
# All elements of path_parameters should use Symbol keys
@request.path_parameters.each_key do |key|
@@ -532,19 +706,57 @@ XML
end
def test_header_properly_reset_after_remote_http_request
- xhr :get, :test_params
+ get :test_params, xhr: true
assert_nil @request.env['HTTP_X_REQUESTED_WITH']
assert_nil @request.env['HTTP_ACCEPT']
end
+ def test_deprecated_xhr_with_params
+ assert_deprecated { xhr :get, :test_params, params: { id: 1 } }
+
+ assert_equal(%({"id"=>"1", "controller"=>"test_case_test/test", "action"=>"test_params"}), @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)
+ end
+
+ def test_xhr_with_session
+ get :set_session, xhr: true
+
+ assert_equal 'A wonder', session['string'], "A value stored in the session should be available by string key"
+ assert_equal 'A wonder', session[:string], "Test session hash should allow indifferent access"
+ assert_equal 'it works', session['symbol'], "Test session hash should allow indifferent access"
+ assert_equal 'it works', session[:symbol], "Test session hash should allow indifferent access"
+ end
+
+ def test_deprecated_xhr_with_session
+ assert_deprecated { xhr :get, :set_session }
+
+ assert_equal 'A wonder', session['string'], "A value stored in the session should be available by string key"
+ assert_equal 'A wonder', session[:string], "Test session hash should allow indifferent access"
+ assert_equal 'it works', session['symbol'], "Test session hash should allow indifferent access"
+ assert_equal 'it works', session[:symbol], "Test session hash should allow indifferent access"
+ end
+
def test_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]
+
+ post :no_op
+ assert @request.params[:foo].blank?
+ end
+
def test_params_reset_between_post_requests
- post :no_op, :foo => "bar"
+ post :no_op, params: { foo: "bar" }
assert_equal "bar", @request.params[:foo]
post :no_op
@@ -552,15 +764,15 @@ XML
end
def test_filtered_parameters_reset_between_requests
- get :no_op, :foo => "bar"
+ get :no_op, params: { foo: "bar" }
assert_equal "bar", @request.filtered_parameters[:foo]
- get :no_op, :foo => "baz"
+ get :no_op, params: { foo: "baz" }
assert_equal "baz", @request.filtered_parameters[:foo]
end
def test_path_params_reset_between_request
- get :test_params, :id => "foo"
+ get :test_params, params: { id: "foo" }
assert_equal "foo", @request.path_parameters[:id]
get :test_params
@@ -581,19 +793,38 @@ XML
end
def test_request_format
- get :test_format, :format => 'html'
+ get :test_format, params: { format: 'html' }
+ assert_equal 'text/html', @response.body
+
+ get :test_format, params: { format: 'json' }
+ assert_equal 'application/json', @response.body
+
+ get :test_format, params: { format: 'xml' }
+ assert_equal 'application/xml', @response.body
+
+ get :test_format
+ assert_equal 'text/html', @response.body
+ end
+
+ def test_request_format_kwarg
+ get :test_format, format: 'html'
assert_equal 'text/html', @response.body
- get :test_format, :format => 'json'
+ get :test_format, format: 'json'
assert_equal 'application/json', @response.body
- get :test_format, :format => 'xml'
+ get :test_format, format: 'xml'
assert_equal 'application/xml', @response.body
get :test_format
assert_equal 'text/html', @response.body
end
+ def test_request_format_kwarg_overrides_params
+ get :test_format, format: 'json', params: { format: 'html' }
+ assert_equal 'application/json', @response.body
+ end
+
def test_should_have_knowledge_of_client_side_cookie_state_even_if_they_are_not_set
cookies['foo'] = 'bar'
get :no_op
@@ -678,7 +909,10 @@ XML
end
def test_fixture_file_upload
- post :test_file_upload, :file => fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg")
+ post :test_file_upload,
+ params: {
+ file: fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg")
+ }
assert_equal '159528', @response.body
end
@@ -694,10 +928,21 @@ XML
assert_equal File.open("#{FILES_DIR}/mona_lisa.jpg", READ_PLAIN).read, uploaded_file.read
end
+ def test_deprecated_action_dispatch_uploaded_file_upload
+ 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))
+ }
+ assert_equal '159528', @response.body
+ end
+
def test_action_dispatch_uploaded_file_upload
filename = 'mona_lisa.jpg'
path = "#{FILES_DIR}/#{filename}"
- post :test_file_upload, :file => ActionDispatch::Http::UploadedFile.new(:filename => path, :type => "image/jpg", :tempfile => File.open(path))
+ post :test_file_upload, params: {
+ file: ActionDispatch::Http::UploadedFile.new(filename: path, type: "image/jpg", tempfile: File.open(path))
+ }
assert_equal '159528', @response.body
end
@@ -720,6 +965,90 @@ XML
end
end
+class ResponseDefaultHeadersTest < ActionController::TestCase
+ class TestController < ActionController::Base
+ def remove_header
+ headers.delete params[:header]
+ head :ok, 'C' => '3'
+ end
+ end
+
+ setup do
+ @original = ActionDispatch::Response.default_headers
+ @defaults = { 'A' => '1', 'B' => '2' }
+ ActionDispatch::Response.default_headers = @defaults
+ end
+
+ teardown do
+ ActionDispatch::Response.default_headers = @original
+ end
+
+ 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
+ get ':controller(/:action(/:id))'
+ end
+ end
+ end
+
+ test "response contains default headers" do
+ # Response headers start out with the defaults
+ assert_equal @defaults, response.headers
+
+ get :remove_header, params: { header: 'A' }
+ assert_response :ok
+
+ # After a request, the response in the test case doesn't have the
+ # defaults merged on top again.
+ assert_not_includes response.headers, 'A'
+ assert_includes response.headers, 'B'
+ assert_includes response.headers, 'C'
+ end
+end
+
+module EngineControllerTests
+ class Engine < ::Rails::Engine
+ isolate_namespace EngineControllerTests
+
+ routes.draw do
+ get '/' => 'bar#index'
+ end
+ end
+
+ class BarController < ActionController::Base
+ def index
+ render text: 'bar'
+ end
+ end
+
+ class BarControllerTest < ActionController::TestCase
+ tests BarController
+
+ def test_engine_controller_route
+ get :index
+ assert_equal @response.body, 'bar'
+ end
+ end
+
+ class BarControllerTestWithExplicitRouteSet < ActionController::TestCase
+ tests BarController
+
+ def setup
+ @routes = Engine.routes
+ end
+
+ def test_engine_controller_route
+ get :index
+ assert_equal @response.body, 'bar'
+ end
+ end
+end
+
class InferringClassNameTest < ActionController::TestCase
def test_determine_controller_class
assert_equal ContentController, determine_class("ContentControllerTest")
@@ -770,7 +1099,7 @@ class NamedRoutesControllerTest < ActionController::TestCase
with_routing do |set|
set.draw { resources :contents }
assert_equal 'http://test.host/contents/new', new_content_url
- assert_equal 'http://test.host/contents/1', content_url(:id => 1)
+ assert_equal 'http://test.host/contents/1', content_url(id: 1)
end
end
end
@@ -779,7 +1108,7 @@ class AnonymousControllerTest < ActionController::TestCase
def setup
@controller = Class.new(ActionController::Base) do
def index
- render :text => params[:controller]
+ render text: params[:controller]
end
end.new
@@ -800,29 +1129,29 @@ class RoutingDefaultsTest < ActionController::TestCase
def setup
@controller = Class.new(ActionController::Base) do
def post
- render :text => request.fullpath
+ render text: request.fullpath
end
def project
- render :text => request.fullpath
+ render text: request.fullpath
end
end.new
@routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
r.draw do
- get '/posts/:id', :to => 'anonymous#post', :bucket_type => 'post'
- get '/projects/:id', :to => 'anonymous#project', :defaults => { :bucket_type => 'project' }
+ get '/posts/:id', to: 'anonymous#post', bucket_type: 'post'
+ get '/projects/:id', to: 'anonymous#project', defaults: { bucket_type: 'project' }
end
end
end
def test_route_option_can_be_passed_via_process
- get :post, :id => 1, :bucket_type => 'post'
+ get :post, params: { id: 1, bucket_type: 'post'}
assert_equal '/posts/1', @response.body
end
def test_route_default_is_not_required_for_building_request_uri
- get :project, :id => 2
+ get :project, params: { id: 2 }
assert_equal '/projects/2', @response.body
end
end
diff --git a/actionpack/test/controller/url_for_integration_test.rb b/actionpack/test/controller/url_for_integration_test.rb
index 24a09222b1..0e4c2b7c32 100644
--- a/actionpack/test/controller/url_for_integration_test.rb
+++ b/actionpack/test/controller/url_for_integration_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'controller/fake_controllers'
require 'active_support/core_ext/object/with_options'
diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb
index 969129d9ba..31677f202d 100644
--- a/actionpack/test/controller/url_for_test.rb
+++ b/actionpack/test/controller/url_for_test.rb
@@ -25,14 +25,13 @@ module AbstractController
path = klass.new.fun_path({:controller => :articles,
:baz => "baz",
- :zot => "zot",
- :only_path => true })
+ :zot => "zot"})
# :bar key isn't provided
assert_equal '/foo/zot', path
end
- def add_host!
- W.default_url_options[:host] = 'www.basecamphq.com'
+ def add_host!(app = W)
+ app.default_url_options[:host] = 'www.basecamphq.com'
end
def add_port!
@@ -55,6 +54,20 @@ module AbstractController
)
end
+ def test_nil_anchor
+ assert_equal(
+ '/c/a',
+ W.new.url_for(only_path: true, controller: 'c', action: 'a', anchor: nil)
+ )
+ end
+
+ def test_false_anchor
+ assert_equal(
+ '/c/a',
+ W.new.url_for(only_path: true, controller: 'c', action: 'a', anchor: false)
+ )
+ end
+
def test_anchor_should_call_to_param
assert_equal('/c/a#anchor',
W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anchor'))
@@ -242,6 +255,20 @@ module AbstractController
)
end
+ def test_relative_url_root_is_respected_with_environment_variable
+ # `config.relative_url_root` is set by ENV['RAILS_RELATIVE_URL_ROOT']
+ w = Class.new {
+ config = ActionDispatch::Routing::RouteSet::Config.new '/subdir'
+ r = ActionDispatch::Routing::RouteSet.new(config)
+ r.draw { get ':controller(/:action(/:id(.:format)))' }
+ include r.url_helpers
+ }
+ add_host!(w)
+ assert_equal('https://www.basecamphq.com/subdir/c/a/i',
+ w.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https')
+ )
+ end
+
def test_named_routes
with_routing do |set|
set.draw do
@@ -277,6 +304,13 @@ module AbstractController
end
end
+ def test_using_nil_script_name_properly_concats_with_original_script_name
+ add_host!
+ assert_equal('https://www.basecamphq.com/subdir/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https', :script_name => nil, :original_script_name => '/subdir')
+ )
+ end
+
def test_only_path
with_routing do |set|
set.draw do
@@ -291,7 +325,7 @@ module AbstractController
assert_equal '/brave/new/world',
controller.url_for(:controller => 'brave', :action => 'new', :id => 'world', :only_path => true)
- assert_equal("/home/sweet/home/alabama", controller.home_path(:user => 'alabama', :host => 'unused', :only_path => true))
+ assert_equal("/home/sweet/home/alabama", controller.home_path(:user => 'alabama', :host => 'unused'))
assert_equal("/home/sweet/home/alabama", controller.home_path('alabama'))
end
end
diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb
index d80b0e2da0..21fa670bb6 100644
--- a/actionpack/test/controller/webservice_test.rb
+++ b/actionpack/test/controller/webservice_test.rb
@@ -35,7 +35,9 @@ class WebServiceTest < ActionDispatch::IntegrationTest
def test_post_json
with_test_route_set do
- post "/", '{"entry":{"summary":"content..."}}', 'CONTENT_TYPE' => 'application/json'
+ post "/",
+ params: '{"entry":{"summary":"content..."}}',
+ headers: { 'CONTENT_TYPE' => 'application/json' }
assert_equal 'entry', @controller.response.body
assert @controller.params.has_key?(:entry)
@@ -45,7 +47,9 @@ class WebServiceTest < ActionDispatch::IntegrationTest
def test_put_json
with_test_route_set do
- put "/", '{"entry":{"summary":"content..."}}', 'CONTENT_TYPE' => 'application/json'
+ put "/",
+ params: '{"entry":{"summary":"content..."}}',
+ headers: { 'CONTENT_TYPE' => 'application/json' }
assert_equal 'entry', @controller.response.body
assert @controller.params.has_key?(:entry)
@@ -56,8 +60,9 @@ class WebServiceTest < ActionDispatch::IntegrationTest
def test_register_and_use_json_simple
with_test_route_set do
with_params_parsers Mime::JSON => Proc.new { |data| ActiveSupport::JSON.decode(data)['request'].with_indifferent_access } do
- post "/", '{"request":{"summary":"content...","title":"JSON"}}',
- 'CONTENT_TYPE' => 'application/json'
+ post "/",
+ params: '{"request":{"summary":"content...","title":"JSON"}}',
+ headers: { 'CONTENT_TYPE' => 'application/json' }
assert_equal 'summary, title', @controller.response.body
assert @controller.params.has_key?(:summary)
@@ -70,19 +75,33 @@ class WebServiceTest < ActionDispatch::IntegrationTest
def test_use_json_with_empty_request
with_test_route_set do
- assert_nothing_raised { post "/", "", 'CONTENT_TYPE' => 'application/json' }
+ assert_nothing_raised { post "/", headers: { 'CONTENT_TYPE' => 'application/json' } }
assert_equal '', @controller.response.body
end
end
def test_dasherized_keys_as_json
with_test_route_set do
- post "/?full=1", '{"first-key":{"sub-key":"..."}}', 'CONTENT_TYPE' => 'application/json'
+ post "/?full=1",
+ params: '{"first-key":{"sub-key":"..."}}',
+ headers: { 'CONTENT_TYPE' => 'application/json' }
assert_equal 'action, controller, first-key(sub-key), full', @controller.response.body
assert_equal "...", @controller.params['first-key']['sub-key']
end
end
+ def test_parsing_json_doesnot_rescue_exception
+ with_test_route_set do
+ with_params_parsers Mime::JSON => Proc.new { |data| raise Interrupt } do
+ assert_raises(Interrupt) do
+ post "/",
+ params: '{"title":"JSON"}}',
+ headers: { 'CONTENT_TYPE' => 'application/json' }
+ end
+ end
+ end
+ end
+
private
def with_params_parsers(parsers = {})
old_session = @integration_session
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 7dc6c37522..6223a52a76 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -1,12 +1,5 @@
require 'abstract_unit'
-
-begin
- require 'openssl'
- OpenSSL::PKCS5
-rescue LoadError, NameError
- $stderr.puts "Skipping KeyGenerator test: broken OpenSSL install"
-else
-
+require 'openssl'
require 'active_support/key_generator'
require 'active_support/message_verifier'
@@ -152,11 +145,21 @@ class CookiesTest < ActionController::TestCase
head :ok
end
+ def set_cookie_with_domain_all_as_string
+ cookies[:user_name] = {:value => "rizwanreza", :domain => 'all'}
+ head :ok
+ end
+
def delete_cookie_with_domain
cookies.delete(:user_name, :domain => :all)
head :ok
end
+ def delete_cookie_with_domain_all_as_string
+ cookies.delete(:user_name, :domain => 'all')
+ head :ok
+ end
+
def set_cookie_with_domain_and_tld
cookies[:user_name] = {:value => "rizwanreza", :domain => :all, :tld_length => 2}
head :ok
@@ -494,7 +497,7 @@ class CookiesTest < ActionController::TestCase
assert_nil @response.cookies["user_id"]
end
- def test_accessing_nonexistant_signed_cookie_should_not_raise_an_invalid_signature
+ def test_accessing_nonexistent_signed_cookie_should_not_raise_an_invalid_signature
get :set_signed_cookie
assert_nil @controller.send(:cookies).signed[:non_existant_attribute]
end
@@ -612,7 +615,7 @@ class CookiesTest < ActionController::TestCase
assert_nil @response.cookies["foo"]
end
- def test_accessing_nonexistant_encrypted_cookie_should_not_raise_invalid_message
+ def test_accessing_nonexistent_encrypted_cookie_should_not_raise_invalid_message
get :set_encrypted_cookie
assert_nil @controller.send(:cookies).encrypted[:non_existant_attribute]
end
@@ -984,6 +987,13 @@ class CookiesTest < ActionController::TestCase
assert_cookie_header "user_name=rizwanreza; domain=.nextangle.local; path=/"
end
+ def test_cookie_with_all_domain_option_using_a_non_standard_2_letter_tld
+ @request.host = "admin.lvh.me"
+ get :set_cookie_with_domain_and_tld
+ assert_response :success
+ assert_cookie_header "user_name=rizwanreza; domain=.lvh.me; path=/"
+ end
+
def test_cookie_with_all_domain_option_using_host_with_port_and_tld_length
@request.host = "nextangle.local:3000"
get :set_cookie_with_domain_and_tld
@@ -1155,5 +1165,3 @@ class CookiesTest < ActionController::TestCase
end
end
end
-
-end
diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb
index f8851f0152..a867aee7ec 100644
--- a/actionpack/test/dispatch/debug_exceptions_test.rb
+++ b/actionpack/test/dispatch/debug_exceptions_test.rb
@@ -43,6 +43,8 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
raise ActionController::InvalidAuthenticityToken
when "/not_found_original_exception"
raise ActionView::Template::Error.new('template', AbstractController::ActionNotFound.new)
+ when "/missing_template"
+ raise ActionView::MissingTemplate.new(%w(foo), 'foo/index', %w(foo), false, 'mailer')
when "/bad_request"
raise ActionController::BadRequest
when "/missing_keys"
@@ -84,21 +86,21 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
test 'skip diagnosis if not showing detailed exceptions' do
@app = ProductionApp
assert_raise RuntimeError do
- get "/", {}, {'action_dispatch.show_exceptions' => true}
+ get "/", headers: { 'action_dispatch.show_exceptions' => true }
end
end
test 'skip diagnosis if not showing exceptions' do
@app = DevelopmentApp
assert_raise RuntimeError do
- get "/", {}, {'action_dispatch.show_exceptions' => false}
+ get "/", headers: { 'action_dispatch.show_exceptions' => false }
end
end
test 'raise an exception on cascade pass' do
@app = ProductionApp
assert_raise ActionController::RoutingError do
- get "/pass", {}, {'action_dispatch.show_exceptions' => true}
+ get "/pass", headers: { 'action_dispatch.show_exceptions' => true }
end
end
@@ -106,44 +108,53 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
boomer = Boomer.new(false)
@app = ActionDispatch::DebugExceptions.new(boomer)
assert_raise ActionController::RoutingError do
- get "/pass", {}, {'action_dispatch.show_exceptions' => true}
+ get "/pass", headers: { 'action_dispatch.show_exceptions' => true }
end
assert boomer.closed, "Expected to close the response body"
end
test 'displays routes in a table when a RoutingError occurs' do
@app = DevelopmentApp
- get "/pass", {}, {'action_dispatch.show_exceptions' => true}
+ get "/pass", headers: { 'action_dispatch.show_exceptions' => true }
routing_table = body[/route_table.*<.table>/m]
assert_match '/:controller(/:action)(.:format)', routing_table
assert_match ':controller#:action', routing_table
assert_no_match '&lt;|&gt;', routing_table, "there should not be escaped html in the output"
end
+ test 'displays request and response info when a RoutingError occurs' do
+ @app = DevelopmentApp
+
+ get "/pass", headers: { 'action_dispatch.show_exceptions' => true }
+
+ assert_select 'h2', /Request/
+ assert_select 'h2', /Response/
+ end
+
test "rescue with diagnostics message" do
@app = DevelopmentApp
- get "/", {}, {'action_dispatch.show_exceptions' => true}
+ get "/", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 500
assert_match(/puke/, body)
- get "/not_found", {}, {'action_dispatch.show_exceptions' => true}
+ get "/not_found", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 404
assert_match(/#{AbstractController::ActionNotFound.name}/, body)
- get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true}
+ get "/method_not_allowed", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 405
assert_match(/ActionController::MethodNotAllowed/, body)
- get "/unknown_http_method", {}, {'action_dispatch.show_exceptions' => true}
+ get "/unknown_http_method", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 405
assert_match(/ActionController::UnknownHttpMethod/, body)
- get "/bad_request", {}, {'action_dispatch.show_exceptions' => true}
+ get "/bad_request", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 400
assert_match(/ActionController::BadRequest/, body)
- get "/parameter_missing", {}, {'action_dispatch.show_exceptions' => true}
+ get "/parameter_missing", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 400
assert_match(/ActionController::ParameterMissing/, body)
end
@@ -152,38 +163,38 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
@app = DevelopmentApp
xhr_request_env = {'action_dispatch.show_exceptions' => true, 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest'}
- get "/", {}, xhr_request_env
+ get "/", headers: xhr_request_env
assert_response 500
assert_no_match(/<header>/, body)
assert_no_match(/<body>/, body)
assert_equal "text/plain", response.content_type
assert_match(/RuntimeError\npuke/, body)
- get "/not_found", {}, xhr_request_env
+ get "/not_found", headers: xhr_request_env
assert_response 404
assert_no_match(/<body>/, body)
assert_equal "text/plain", response.content_type
assert_match(/#{AbstractController::ActionNotFound.name}/, body)
- get "/method_not_allowed", {}, xhr_request_env
+ get "/method_not_allowed", headers: xhr_request_env
assert_response 405
assert_no_match(/<body>/, body)
assert_equal "text/plain", response.content_type
assert_match(/ActionController::MethodNotAllowed/, body)
- get "/unknown_http_method", {}, xhr_request_env
+ get "/unknown_http_method", headers: xhr_request_env
assert_response 405
assert_no_match(/<body>/, body)
assert_equal "text/plain", response.content_type
assert_match(/ActionController::UnknownHttpMethod/, body)
- get "/bad_request", {}, xhr_request_env
+ get "/bad_request", headers: xhr_request_env
assert_response 400
assert_no_match(/<body>/, body)
assert_equal "text/plain", response.content_type
assert_match(/ActionController::BadRequest/, body)
- get "/parameter_missing", {}, xhr_request_env
+ get "/parameter_missing", headers: xhr_request_env
assert_response 400
assert_no_match(/<body>/, body)
assert_equal "text/plain", response.content_type
@@ -193,8 +204,8 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
test "does not show filtered parameters" do
@app = DevelopmentApp
- get "/", {"foo"=>"bar"}, {'action_dispatch.show_exceptions' => true,
- 'action_dispatch.parameter_filter' => [:foo]}
+ get "/", params: { "foo"=>"bar" }, headers: { 'action_dispatch.show_exceptions' => true,
+ 'action_dispatch.parameter_filter' => [:foo] }
assert_response 500
assert_match("&quot;foo&quot;=&gt;&quot;[FILTERED]&quot;", body)
end
@@ -202,7 +213,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
test "show registered original exception for wrapped exceptions" do
@app = DevelopmentApp
- get "/not_found_original_exception", {}, {'action_dispatch.show_exceptions' => true}
+ get "/not_found_original_exception", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 404
assert_match(/AbstractController::ActionNotFound/, body)
end
@@ -210,7 +221,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
test "named urls missing keys raise 500 level error" do
@app = DevelopmentApp
- get "/missing_keys", {}, {'action_dispatch.show_exceptions' => true}
+ get "/missing_keys", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 500
assert_match(/ActionController::UrlGenerationError/, body)
@@ -218,7 +229,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
test "show the controller name in the diagnostics template when controller name is present" do
@app = DevelopmentApp
- get("/runtime_error", {}, {
+ get("/runtime_error", headers: {
'action_dispatch.show_exceptions' => true,
'action_dispatch.request.parameters' => {
'action' => 'show',
@@ -230,24 +241,47 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
assert_match(/RuntimeError\n\s+in FeaturedTileController/, body)
end
+ test "show formatted params" do
+ @app = DevelopmentApp
+
+ params = {
+ 'id' => 'unknown',
+ 'someparam' => {
+ 'foo' => 'bar',
+ 'abc' => 'goo'
+ }
+ }
+
+ get("/runtime_error", headers: {
+ 'action_dispatch.show_exceptions' => true,
+ 'action_dispatch.request.parameters' => {
+ 'action' => 'show',
+ 'controller' => 'featured_tile'
+ }.merge(params)
+ })
+ assert_response 500
+
+ assert_includes(body, CGI.escapeHTML(PP.pp(params, "", 200)))
+ end
+
test "sets the HTTP charset parameter" do
@app = DevelopmentApp
- get "/", {}, {'action_dispatch.show_exceptions' => true}
+ get "/", headers: { 'action_dispatch.show_exceptions' => true }
assert_equal "text/html; charset=utf-8", response.headers["Content-Type"]
end
test 'uses logger from env' do
@app = DevelopmentApp
output = StringIO.new
- get "/", {}, {'action_dispatch.show_exceptions' => true, 'action_dispatch.logger' => Logger.new(output)}
+ get "/", headers: { 'action_dispatch.show_exceptions' => true, 'action_dispatch.logger' => Logger.new(output) }
assert_match(/puke/, output.rewind && output.read)
end
test 'uses backtrace cleaner from env' do
@app = DevelopmentApp
cleaner = stub(:clean => ['passed backtrace cleaner'])
- get "/", {}, {'action_dispatch.show_exceptions' => true, 'action_dispatch.backtrace_cleaner' => cleaner}
+ get "/", headers: { 'action_dispatch.show_exceptions' => true, 'action_dispatch.backtrace_cleaner' => cleaner }
assert_match(/passed backtrace cleaner/, body)
end
@@ -260,29 +294,45 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
'action_dispatch.logger' => Logger.new(output),
'action_dispatch.backtrace_cleaner' => backtrace_cleaner}
- get "/", {}, env
+ get "/", headers: env
assert_operator((output.rewind && output.read).lines.count, :>, 10)
end
test 'display backtrace when error type is SyntaxError' do
@app = DevelopmentApp
- get '/original_syntax_error', {}, {'action_dispatch.backtrace_cleaner' => ActiveSupport::BacktraceCleaner.new}
+ get '/original_syntax_error', headers: { 'action_dispatch.backtrace_cleaner' => ActiveSupport::BacktraceCleaner.new }
assert_response 500
assert_select '#Application-Trace' do
- assert_select 'pre code', /\(eval\):1: syntax error, unexpected/
+ assert_select 'pre code', /syntax error, unexpected/
end
end
+ test 'display backtrace on template missing errors' do
+ @app = DevelopmentApp
+
+ get "/missing_template"
+
+ assert_select "header h1", /Template is missing/
+
+ assert_select "#container h2", /^Missing template/
+
+ assert_select '#Application-Trace'
+ assert_select '#Framework-Trace'
+ assert_select '#Full-Trace'
+
+ assert_select 'h2', /Request/
+ end
+
test 'display backtrace when error type is SyntaxError wrapped by ActionView::Template::Error' do
@app = DevelopmentApp
- get '/syntax_error_into_view', {}, {'action_dispatch.backtrace_cleaner' => ActiveSupport::BacktraceCleaner.new}
+ get '/syntax_error_into_view', headers: { 'action_dispatch.backtrace_cleaner' => ActiveSupport::BacktraceCleaner.new }
assert_response 500
assert_select '#Application-Trace' do
- assert_select 'pre code', /\(eval\):1: syntax error, unexpected/
+ assert_select 'pre code', /syntax error, unexpected/
end
end
@@ -294,7 +344,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
bc.add_silencer { |line| line !~ %r{test/dispatch/debug_exceptions_test.rb} }
end
- get '/framework_raises', {}, {'action_dispatch.backtrace_cleaner' => cleaner}
+ get '/framework_raises', headers: { 'action_dispatch.backtrace_cleaner' => cleaner }
# Assert correct error
assert_response 500
diff --git a/actionpack/test/dispatch/exception_wrapper_test.rb b/actionpack/test/dispatch/exception_wrapper_test.rb
new file mode 100644
index 0000000000..7a29a7ff97
--- /dev/null
+++ b/actionpack/test/dispatch/exception_wrapper_test.rb
@@ -0,0 +1,114 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ class ExceptionWrapperTest < ActionDispatch::IntegrationTest
+ class TestError < StandardError
+ attr_reader :backtrace
+
+ def initialize(*backtrace)
+ @backtrace = backtrace.flatten
+ end
+ end
+
+ class BadlyDefinedError < StandardError
+ def backtrace
+ nil
+ end
+ end
+
+ setup do
+ Rails.stubs(:root).returns(Pathname.new('.'))
+
+ cleaner = ActiveSupport::BacktraceCleaner.new
+ cleaner.add_silencer { |line| line !~ /^lib/ }
+
+ @environment = { 'action_dispatch.backtrace_cleaner' => cleaner }
+ 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.expects(:source_fragment).with('lib/file.rb', 42).returns('foo')
+
+ assert_equal [ code: 'foo', line_number: 42 ], wrapper.source_extracts
+ end
+
+ 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.expects(:source_fragment).with('c:/path/to/rails/app/controller.rb', 27).returns('nothing')
+
+ assert_equal [ code: 'nothing', line_number: 27 ], wrapper.source_extracts
+ end
+
+ test '#source_extracts works with non standard backtrace' do
+ exc = TestError.new('invalid')
+
+ wrapper = ExceptionWrapper.new({}, exc)
+ wrapper.expects(:source_fragment).with('invalid', 0).returns('nothing')
+
+ assert_equal [ code: 'nothing', line_number: 0 ], wrapper.source_extracts
+ end
+
+ 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)
+
+ 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)
+
+ assert_equal [], nil_backtrace_wrapper.application_trace
+ assert_equal [], nil_cleaner_wrapper.application_trace
+ end
+
+ 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)
+
+ 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)
+
+ assert_equal [], nil_backtrace_wrapper.framework_trace
+ assert_equal [], nil_cleaner_wrapper.framework_trace
+ end
+
+ 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)
+
+ 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)
+
+ assert_equal [], nil_backtrace_wrapper.full_trace
+ assert_equal [], nil_cleaner_wrapper.full_trace
+ end
+
+ 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)
+
+ assert_equal({
+ 'Application Trace' => [ id: 0, trace: "lib/file.rb:42:in `index'" ],
+ 'Framework Trace' => [ id: 1, trace: "/gems/rack.rb:43:in `index'" ],
+ 'Full Trace' => [
+ { id: 0, trace: "lib/file.rb:42:in `index'" },
+ { id: 1, trace: "/gems/rack.rb:43:in `index'" }
+ ]
+ }, wrapper.traces)
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb
index ad6335f132..3017a9c2d6 100644
--- a/actionpack/test/dispatch/mime_type_test.rb
+++ b/actionpack/test/dispatch/mime_type_test.rb
@@ -83,7 +83,7 @@ class MimeTypeTest < ActiveSupport::TestCase
test "parse broken acceptlines" do
accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/*,,*/*;q=0.5"
expect = [Mime::HTML, Mime::XML, "image/*", Mime::TEXT, Mime::ALL]
- assert_equal expect, Mime::Type.parse(accept).collect { |c| c.to_s }
+ assert_equal expect, Mime::Type.parse(accept).collect(&:to_s)
end
# Accept header send with user HTTP_USER_AGENT: Mozilla/4.0
@@ -91,7 +91,7 @@ class MimeTypeTest < ActiveSupport::TestCase
test "parse other broken acceptlines" do
accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, , pronto/1.00.00, sslvpn/1.00.00.00, */*"
expect = ['image/gif', 'image/x-xbitmap', 'image/jpeg','image/pjpeg', 'application/x-shockwave-flash', 'application/vnd.ms-excel', 'application/vnd.ms-powerpoint', 'application/msword', 'pronto/1.00.00', 'sslvpn/1.00.00.00', Mime::ALL]
- assert_equal expect, Mime::Type.parse(accept).collect { |c| c.to_s }
+ assert_equal expect, Mime::Type.parse(accept).collect(&:to_s)
end
test "custom type" do
diff --git a/actionpack/test/dispatch/mount_test.rb b/actionpack/test/dispatch/mount_test.rb
index d5a4d8ee11..6a439be2b5 100644
--- a/actionpack/test/dispatch/mount_test.rb
+++ b/actionpack/test/dispatch/mount_test.rb
@@ -64,7 +64,7 @@ class TestRoutingMount < ActionDispatch::IntegrationTest
end
def test_mounting_works_with_nested_script_name
- get "/foo/sprockets/omg", {}, 'SCRIPT_NAME' => '/foo', 'PATH_INFO' => '/sprockets/omg'
+ get "/foo/sprockets/omg", headers: { 'SCRIPT_NAME' => '/foo', 'PATH_INFO' => '/sprockets/omg' }
assert_equal "/foo/sprockets -- /omg", response.body
end
diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb
index c609075e6b..d77341bc64 100644
--- a/actionpack/test/dispatch/request/json_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb
@@ -39,7 +39,7 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
test "nils are stripped from collections" do
assert_parses(
- {"person" => nil},
+ {"person" => []},
"{\"person\":[null]}", { 'CONTENT_TYPE' => 'application/json' }
)
assert_parses(
@@ -47,7 +47,7 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
"{\"person\":[\"foo\",null]}", { 'CONTENT_TYPE' => 'application/json' }
)
assert_parses(
- {"person" => nil},
+ {"person" => []},
"{\"person\":[null, null]}", { 'CONTENT_TYPE' => 'application/json' }
)
end
@@ -56,7 +56,7 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
with_test_routing do
output = StringIO.new
json = "[\"person]\": {\"name\": \"David\"}}"
- post "/parse", json, {'CONTENT_TYPE' => 'application/json', 'action_dispatch.show_exceptions' => true, 'action_dispatch.logger' => ActiveSupport::Logger.new(output)}
+ post "/parse", params: json, headers: { 'CONTENT_TYPE' => 'application/json', 'action_dispatch.show_exceptions' => true, 'action_dispatch.logger' => ActiveSupport::Logger.new(output) }
assert_response :bad_request
output.rewind && err = output.read
assert err =~ /Error occurred while parsing request parameters/
@@ -79,7 +79,7 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
test 'raw_post is not empty for JSON request' do
with_test_routing do
- post '/parse', '{"posts": [{"title": "Post Title"}]}', 'CONTENT_TYPE' => 'application/json'
+ post '/parse', params: '{"posts": [{"title": "Post Title"}]}', headers: { 'CONTENT_TYPE' => 'application/json' }
assert_equal '{"posts": [{"title": "Post Title"}]}', request.raw_post
end
end
@@ -87,7 +87,7 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
private
def assert_parses(expected, actual, headers = {})
with_test_routing do
- post "/parse", actual, headers
+ post "/parse", params: actual, headers: headers
assert_response :ok
assert_equal(expected, TestController.last_request_parameters)
end
@@ -146,7 +146,7 @@ class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest
private
def assert_parses(expected, actual, headers = {})
with_test_routing(UsersController) do
- post "/parse", actual, headers
+ post "/parse", params: actual, headers: headers
assert_response :ok
assert_equal(expected, UsersController.last_request_parameters)
assert_equal(expected.merge({"action" => "parse"}), UsersController.last_parameters)
diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
index 926472163e..50f69c53cb 100644
--- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
@@ -37,7 +36,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
end
test "parse single utf8 parameter" do
- assert_equal({ 'Iñtërnâtiônàlizætiøn_name' => 'Iñtërnâtiônàlizætiøn_value'},
+ assert_equal({ 'Iñtërnâtiônàlizætiøn_name' => 'Iñtërnâtiônàlizætiøn_value'},
parse_multipart('single_utf8_param'), "request.request_parameters")
assert_equal(
'Iñtërnâtiônàlizætiøn_value',
@@ -45,8 +44,8 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
end
test "parse bracketed utf8 parameter" do
- assert_equal({ 'Iñtërnâtiônàlizætiøn_name' => {
- 'Iñtërnâtiônàlizætiøn_nested_name' => 'Iñtërnâtiônàlizætiøn_value'} },
+ assert_equal({ 'Iñtërnâtiônàlizætiøn_name' => {
+ 'Iñtërnâtiônàlizætiøn_nested_name' => 'Iñtërnâtiônàlizætiøn_value'} },
parse_multipart('bracketed_utf8_param'), "request.request_parameters")
assert_equal(
{'Iñtërnâtiônàlizætiøn_nested_name' => 'Iñtërnâtiônàlizætiøn_value'},
@@ -134,13 +133,13 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
with_test_routing do
fixture = FIXTURE_PATH + "/mona_lisa.jpg"
params = { :uploaded_data => fixture_file_upload(fixture, "image/jpg") }
- post '/read', params
+ post '/read', params: params
end
end
test "uploads and reads file" do
with_test_routing do
- post '/read', :uploaded_data => fixture_file_upload(FIXTURE_PATH + "/hello.txt", "text/plain")
+ post '/read', params: { uploaded_data: fixture_file_upload(FIXTURE_PATH + "/hello.txt", "text/plain") }
assert_equal "File: Hello", response.body
end
end
@@ -152,7 +151,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
get ':action', controller: 'multipart_params_parsing_test/test'
end
headers = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x" }
- get "/parse", {}, headers
+ get "/parse", headers: headers
assert_response :ok
end
end
@@ -169,7 +168,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
def parse_multipart(name)
with_test_routing do
headers = fixture(name)
- post "/parse", headers.delete("rack.input"), headers
+ post "/parse", params: headers.delete("rack.input"), headers: headers
assert_response :ok
TestController.last_request_parameters
end
diff --git a/actionpack/test/dispatch/request/query_string_parsing_test.rb b/actionpack/test/dispatch/request/query_string_parsing_test.rb
index 4e99c26e03..bc6716525e 100644
--- a/actionpack/test/dispatch/request/query_string_parsing_test.rb
+++ b/actionpack/test/dispatch/request/query_string_parsing_test.rb
@@ -95,8 +95,8 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest
assert_parses({"action" => nil}, "action")
assert_parses({"action" => {"foo" => nil}}, "action[foo]")
assert_parses({"action" => {"foo" => { "bar" => nil }}}, "action[foo][bar]")
- assert_parses({"action" => {"foo" => { "bar" => nil }}}, "action[foo][bar][]")
- assert_parses({"action" => {"foo" => nil }}, "action[foo][]")
+ assert_parses({"action" => {"foo" => { "bar" => [] }}}, "action[foo][bar][]")
+ assert_parses({"action" => {"foo" => [] }}, "action[foo][]")
assert_parses({"action"=>{"foo"=>[{"bar"=>nil}]}}, "action[foo][][bar]")
end
@@ -147,7 +147,7 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest
get ':action', :to => ::QueryStringParsingTest::TestController
end
- get "/parse", nil, "QUERY_STRING" => "foo[]=bar&foo[4]=bar"
+ get "/parse", headers: { "QUERY_STRING" => "foo[]=bar&foo[4]=bar" }
assert_response :bad_request
end
end
@@ -162,8 +162,7 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest
middleware.use(EarlyParse)
end
-
- get "/parse", actual
+ get "/parse", params: actual
assert_response :ok
assert_equal(expected, ::QueryStringParsingTest::TestController.last_query_parameters)
end
diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
index 1de05cbf09..365edf849a 100644
--- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
@@ -131,7 +131,7 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest
test "ambiguous params returns a bad request" do
with_test_routing do
- post "/parse", "foo[]=bar&foo[4]=bar"
+ post "/parse", params: "foo[]=bar&foo[4]=bar"
assert_response :bad_request
end
end
@@ -148,7 +148,7 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest
def assert_parses(expected, actual)
with_test_routing do
- post "/parse", actual
+ post "/parse", params: actual
assert_response :ok
assert_equal expected, TestController.last_request_parameters
assert_utf8 TestController.last_request_parameters
diff --git a/actionpack/test/dispatch/request_id_test.rb b/actionpack/test/dispatch/request_id_test.rb
index a8050b4fab..00d8caf8f4 100644
--- a/actionpack/test/dispatch/request_id_test.rb
+++ b/actionpack/test/dispatch/request_id_test.rb
@@ -2,19 +2,23 @@ require 'abstract_unit'
class RequestIdTest < ActiveSupport::TestCase
test "passing on the request id from the outside" do
- assert_equal "external-uu-rid", stub_request('HTTP_X_REQUEST_ID' => 'external-uu-rid').uuid
+ assert_equal "external-uu-rid", stub_request('HTTP_X_REQUEST_ID' => 'external-uu-rid').request_id
end
test "ensure that only alphanumeric uurids are accepted" do
- assert_equal "X-Hacked-HeaderStuff", stub_request('HTTP_X_REQUEST_ID' => '; X-Hacked-Header: Stuff').uuid
+ assert_equal "X-Hacked-HeaderStuff", stub_request('HTTP_X_REQUEST_ID' => '; X-Hacked-Header: Stuff').request_id
end
test "ensure that 255 char limit on the request id is being enforced" do
- assert_equal "X" * 255, stub_request('HTTP_X_REQUEST_ID' => 'X' * 500).uuid
+ assert_equal "X" * 255, stub_request('HTTP_X_REQUEST_ID' => 'X' * 500).request_id
end
test "generating a request id when none is supplied" do
- assert_match(/\w+-\w+-\w+-\w+-\w+/, stub_request.uuid)
+ assert_match(/\w+-\w+-\w+-\w+-\w+/, stub_request.request_id)
+ end
+
+ test "uuid alias" do
+ assert_equal "external-uu-rid", stub_request('HTTP_X_REQUEST_ID' => 'external-uu-rid').uuid
end
private
@@ -41,7 +45,7 @@ class RequestIdResponseTest < ActionDispatch::IntegrationTest
test "request id given on request is passed all the way to the response" do
with_test_route_set do
- get '/', {}, 'HTTP_X_REQUEST_ID' => 'X' * 500
+ get '/', headers: { 'HTTP_X_REQUEST_ID' => 'X' * 500 }
assert_equal "X" * 255, @response.headers["X-Request-Id"]
end
end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 940ebc0224..f208cfda89 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -435,6 +435,9 @@ class RequestHost < BaseRequestTest
request = stub_request 'HTTP_X_FORWARDED_HOST' => "www.firsthost.org, www.secondhost.org"
assert_equal "www.secondhost.org", request.host
+
+ request = stub_request 'HTTP_X_FORWARDED_HOST' => "", 'HTTP_HOST' => "rubyonrails.org"
+ assert_equal "rubyonrails.org", request.host
end
test "http host with default port overrides server port" do
@@ -997,8 +1000,8 @@ class RequestParameterFilter < BaseRequestTest
}
parameter_filter = ActionDispatch::Http::ParameterFilter.new(filter_words)
- before_filter['barg'] = {'bargain'=>'gain', 'blah'=>'bar', 'bar'=>{'bargain'=>{'blah'=>'foo'}}}
- after_filter['barg'] = {'bargain'=>'niag', 'blah'=>'[FILTERED]', 'bar'=>{'bargain'=>{'blah'=>'[FILTERED]'}}}
+ before_filter['barg'] = {:bargain=>'gain', 'blah'=>'bar', 'bar'=>{'bargain'=>{'blah'=>'foo'}}}
+ after_filter['barg'] = {:bargain=>'niag', 'blah'=>'[FILTERED]', 'bar'=>{'bargain'=>{'blah'=>'[FILTERED]'}}}
assert_equal after_filter, parameter_filter.filter(before_filter)
end
@@ -1125,28 +1128,47 @@ class RequestEtag < BaseRequestTest
end
class RequestVariant < BaseRequestTest
- test "setting variant" do
- request = stub_request
+ def setup
+ super
+ @request = stub_request
+ end
- request.variant = :mobile
- assert_equal [:mobile], request.variant
+ test 'setting variant to a symbol' do
+ @request.variant = :phone
- request.variant = [:phone, :tablet]
- assert_equal [:phone, :tablet], request.variant
+ assert @request.variant.phone?
+ assert_not @request.variant.tablet?
+ assert @request.variant.any?(:phone, :tablet)
+ assert_not @request.variant.any?(:tablet, :desktop)
+ end
- assert_raise ArgumentError do
- request.variant = [:phone, "tablet"]
- end
+ test 'setting variant to an array of symbols' do
+ @request.variant = [:phone, :tablet]
+
+ assert @request.variant.phone?
+ assert @request.variant.tablet?
+ assert_not @request.variant.desktop?
+ assert @request.variant.any?(:tablet, :desktop)
+ assert_not @request.variant.any?(:desktop, :watch)
+ end
+
+ test 'clearing variant' do
+ @request.variant = nil
+ assert @request.variant.empty?
+ assert_not @request.variant.phone?
+ assert_not @request.variant.any?(:phone, :tablet)
+ end
+
+ test 'setting variant to a non-symbol value' do
assert_raise ArgumentError do
- request.variant = "yolo"
+ @request.variant = 'phone'
end
end
- test "setting variant with non symbol value" do
- request = stub_request
+ test 'setting variant to an array containing a non-symbol value' do
assert_raise ArgumentError do
- request.variant = "mobile"
+ @request.variant = [:phone, 'tablet']
end
end
end
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index 48342e252a..5fbd19acdf 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -231,9 +231,9 @@ class ResponseTest < ActiveSupport::TestCase
assert_equal ['Not Found'], body.each.to_a
end
- test "[response].flatten does not recurse infinitely" do
+ test "[response.to_a].flatten does not recurse infinitely" do
Timeout.timeout(1) do # use a timeout to prevent it stalling indefinitely
- status, headers, body = assert_deprecated { [@response].flatten }
+ status, headers, body = [@response.to_a].flatten
assert_equal @response.status, status
assert_equal @response.headers, headers
assert_equal @response.body, body.each.to_a.join
@@ -251,27 +251,9 @@ class ResponseTest < ActiveSupport::TestCase
status, headers, body = Rack::ContentLength.new(app).call(env)
assert_equal '5', headers['Content-Length']
end
-
- test "implicit destructuring and Array conversion is deprecated" do
- response = ActionDispatch::Response.new(404, { 'Content-Type' => 'text/plain' }, ['Not Found'])
-
- assert_deprecated do
- status, headers, body = response
-
- assert_equal 404, status
- assert_equal({ 'Content-Type' => 'text/plain' }, headers)
- assert_equal ['Not Found'], body.each.to_a
- end
-
- assert_deprecated { response.to_ary }
- end
end
class ResponseIntegrationTest < ActionDispatch::IntegrationTest
- def app
- @app
- end
-
test "response cache control from railsish app" do
@app = lambda { |env|
ActionDispatch::Response.new.tap { |resp|
diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb
index ff33dd5652..3df022c64b 100644
--- a/actionpack/test/dispatch/routing/inspector_test.rb
+++ b/actionpack/test/dispatch/routing/inspector_test.rb
@@ -2,6 +2,11 @@ require 'abstract_unit'
require 'rails/engine'
require 'action_dispatch/routing/inspector'
+class MountedRackApp
+ def self.call(env)
+ end
+end
+
module ActionDispatch
module Routing
class RoutesInspectorTest < ActiveSupport::TestCase
@@ -21,14 +26,6 @@ module ActionDispatch
inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, options[:filter]).split("\n")
end
- def test_json_regexp_converter
- @set.draw do
- get '/cart', :to => 'cart#show'
- end
- route = ActionDispatch::Routing::RouteWrapper.new(@set.routes.first)
- assert_equal "^\\/cart(?:\\.([^\\/.?]+))?$", route.json_regexp
- end
-
def test_displaying_routes_for_engines
engine = Class.new(Rails::Engine) do
def self.inspect
@@ -204,19 +201,36 @@ module ActionDispatch
], output
end
- class RackApp
- def self.call(env)
+ def test_rake_routes_shows_route_with_rack_app
+ output = draw do
+ get 'foo/:id' => MountedRackApp, :id => /[A-Z]\d{5}/
+ end
+
+ assert_equal [
+ "Prefix Verb URI Pattern Controller#Action",
+ " GET /foo/:id(.:format) MountedRackApp {:id=>/[A-Z]\\d{5}/}"
+ ], output
+ end
+
+ def test_rake_routes_shows_named_route_with_mounted_rack_app
+ output = draw do
+ mount MountedRackApp => '/foo'
end
+
+ assert_equal [
+ " Prefix Verb URI Pattern Controller#Action",
+ "mounted_rack_app /foo MountedRackApp"
+ ], output
end
- def test_rake_routes_shows_route_with_rack_app
+ def test_rake_routes_shows_overridden_named_route_with_mounted_rack_app_with_name
output = draw do
- get 'foo/:id' => RackApp, :id => /[A-Z]\d{5}/
+ mount MountedRackApp => '/foo', as: 'blog'
end
assert_equal [
- "Prefix Verb URI Pattern Controller#Action",
- " GET /foo/:id(.:format) #{RackApp.name} {:id=>/[A-Z]\\d{5}/}"
+ "Prefix Verb URI Pattern Controller#Action",
+ " blog /foo MountedRackApp"
], output
end
@@ -229,21 +243,21 @@ module ActionDispatch
output = draw do
scope :constraint => constraint.new do
- mount RackApp => '/foo'
+ mount MountedRackApp => '/foo'
end
end
assert_equal [
- "Prefix Verb URI Pattern Controller#Action",
- " /foo #{RackApp.name} {:constraint=>( my custom constraint )}"
+ " Prefix Verb URI Pattern Controller#Action",
+ "mounted_rack_app /foo MountedRackApp {:constraint=>( my custom constraint )}"
], output
end
def test_rake_routes_dont_show_app_mounted_in_assets_prefix
output = draw do
- get '/sprockets' => RackApp
+ get '/sprockets' => MountedRackApp
end
- assert_no_match(/RackApp/, output.first)
+ assert_no_match(/MountedRackApp/, output.first)
assert_no_match(/\/sprockets/, output.first)
end
diff --git a/actionpack/test/dispatch/routing/route_set_test.rb b/actionpack/test/dispatch/routing/route_set_test.rb
index a7acc0de41..fe52c50336 100644
--- a/actionpack/test/dispatch/routing/route_set_test.rb
+++ b/actionpack/test/dispatch/routing/route_set_test.rb
@@ -105,50 +105,6 @@ module ActionDispatch
assert_equal 'http://example.com/foo', url_helpers.foo_url(only_path: false)
end
- test "only_path: true with *_path" do
- draw do
- get 'foo', to: SimpleApp.new('foo#index')
- end
-
- assert_deprecated do
- assert_equal '/foo', url_helpers.foo_path(only_path: true)
- end
- end
-
- test "only_path: false with *_path with global :host option" do
- @set.default_url_options = { host: 'example.com' }
-
- draw do
- get 'foo', to: SimpleApp.new('foo#index')
- end
-
- assert_deprecated do
- assert_equal 'http://example.com/foo', url_helpers.foo_path(only_path: false)
- end
- end
-
- test "only_path: false with *_path with local :host option" do
- draw do
- get 'foo', to: SimpleApp.new('foo#index')
- end
-
- assert_deprecated do
- assert_equal 'http://example.com/foo', url_helpers.foo_path(only_path: false, host: 'example.com')
- end
- end
-
- test "only_path: false with *_path with no :host option" do
- draw do
- get 'foo', to: SimpleApp.new('foo#index')
- end
-
- assert_deprecated do
- assert_raises ArgumentError do
- assert_equal 'http://example.com/foo', url_helpers.foo_path(only_path: false)
- end
- end
- end
-
test "explicit keys win over implicit keys" do
draw do
resources :foo do
@@ -160,6 +116,18 @@ module ActionDispatch
assert_equal '/foo/1/bar/2', url_helpers.foo_bar_path(2, foo_id: 1)
end
+ test "having an optional scope with resources" do
+ draw do
+ scope "(/:foo)" do
+ resources :users
+ end
+ end
+
+ assert_equal '/users/1', url_helpers.user_path(1)
+ assert_equal '/users/1', url_helpers.user_path(1, foo: nil)
+ assert_equal '/a/users/1', url_helpers.user_path(1, foo: 'a')
+ end
+
private
def draw(&block)
@set.draw(&block)
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index d0a624784a..62c99a2edc 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -362,22 +362,22 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get 'admin/passwords' => "queenbee#passwords", :constraints => ::TestRoutingMapper::IpRestrictor
end
- get '/admin', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ get '/admin', headers: { 'REMOTE_ADDR' => '192.168.1.100' }
assert_equal 'queenbee#index', @response.body
- get '/admin', {}, {'REMOTE_ADDR' => '10.0.0.100'}
+ get '/admin', headers: { 'REMOTE_ADDR' => '10.0.0.100' }
assert_equal 'pass', @response.headers['X-Cascade']
- get '/admin/accounts', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ get '/admin/accounts', headers: { 'REMOTE_ADDR' => '192.168.1.100' }
assert_equal 'queenbee#accounts', @response.body
- get '/admin/accounts', {}, {'REMOTE_ADDR' => '10.0.0.100'}
+ get '/admin/accounts', headers: { 'REMOTE_ADDR' => '10.0.0.100' }
assert_equal 'pass', @response.headers['X-Cascade']
- get '/admin/passwords', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ get '/admin/passwords', headers: { 'REMOTE_ADDR' => '192.168.1.100' }
assert_equal 'queenbee#passwords', @response.body
- get '/admin/passwords', {}, {'REMOTE_ADDR' => '10.0.0.100'}
+ get '/admin/passwords', headers: { 'REMOTE_ADDR' => '10.0.0.100' }
assert_equal 'pass', @response.headers['X-Cascade']
end
@@ -1430,6 +1430,15 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 'api/v3/products#list', @response.body
end
+ def test_not_matching_shorthand_with_dynamic_parameters
+ draw do
+ get ':controller/:action/admin'
+ end
+
+ get '/finances/overview/admin'
+ assert_equal 'finances#overview', @response.body
+ end
+
def test_controller_option_with_nesting_and_leading_slash
draw do
scope '/job', controller: 'job' do
@@ -1683,9 +1692,9 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get '/products/0001/images/0001'
assert_equal 'images#show', @response.body
- get '/dashboard', {}, {'REMOTE_ADDR' => '10.0.0.100'}
+ get '/dashboard', headers: { 'REMOTE_ADDR' => '10.0.0.100' }
assert_equal 'pass', @response.headers['X-Cascade']
- get '/dashboard', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ get '/dashboard', headers: { 'REMOTE_ADDR' => '192.168.1.100' }
assert_equal 'dashboards#show', @response.body
end
@@ -3331,30 +3340,6 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 'comments#index', @response.body
end
- def test_mix_symbol_to_controller_action
- assert_deprecated do
- draw do
- get '/projects', controller: 'project_files',
- action: 'index',
- to: :show
- end
- end
- get '/projects'
- assert_equal 'project_files#show', @response.body
- end
-
- def test_mix_string_to_controller_action_no_hash
- assert_deprecated do
- draw do
- get '/projects', controller: 'project_files',
- action: 'index',
- to: 'show'
- end
- end
- get '/projects'
- assert_equal 'show#index', @response.body
- end
-
def test_shallow_path_and_prefix_are_not_added_to_non_shallow_routes
draw do
scope shallow_path: 'projects', shallow_prefix: 'project' do
@@ -3463,6 +3448,63 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal '/bar/comments/1', comment_path('1')
end
+ def test_resource_where_as_is_empty
+ draw do
+ resource :post, as: ''
+
+ scope 'post', as: 'post' do
+ resource :comment, as: ''
+ end
+ end
+
+ assert_equal '/post/new', new_path
+ assert_equal '/post/comment/new', new_post_path
+ end
+
+ def test_resources_where_as_is_empty
+ draw do
+ resources :posts, as: ''
+
+ scope 'posts', as: 'posts' do
+ resources :comments, as: ''
+ end
+ end
+
+ assert_equal '/posts/new', new_path
+ assert_equal '/posts/comments/new', new_posts_path
+ end
+
+ def test_scope_where_as_is_empty
+ draw do
+ scope 'post', as: '' do
+ resource :user
+ resources :comments
+ end
+ end
+
+ assert_equal '/post/user/new', new_user_path
+ assert_equal '/post/comments/new', new_comment_path
+ end
+
+ def test_head_fetch_with_mount_on_root
+ draw do
+ get '/home' => 'test#index'
+ mount lambda { |env| [200, {}, [env['REQUEST_METHOD']]] }, at: '/'
+ end
+
+ # TODO: 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
+
+ # But the Rack app can still respond to its own HEAD requests.
+ head '/foobar'
+ assert_response :ok
+ assert_equal 'HEAD', @response.body
+ end
+
private
def draw(&block)
@@ -3541,7 +3583,11 @@ class TestAltApp < ActionDispatch::IntegrationTest
end
end
- AltRoutes = ActionDispatch::Routing::RouteSet.new(AltRequest)
+ AltRoutes = Class.new(ActionDispatch::Routing::RouteSet) {
+ def request_class
+ AltRequest
+ end
+ }.new
AltRoutes.draw do
get "/" => TestAltApp::XHeader.new, :constraints => {:x_header => /HEADER/}
get "/" => TestAltApp::AltApp.new
@@ -3559,12 +3605,12 @@ class TestAltApp < ActionDispatch::IntegrationTest
end
def test_alt_request_with_matched_header
- get "/", {}, "HTTP_X_HEADER" => "HEADER"
+ get "/", headers: { "HTTP_X_HEADER" => "HEADER" }
assert_equal "XHeader", @response.body
end
def test_alt_request_with_unmatched_header
- get "/", {}, "HTTP_X_HEADER" => "NON_MATCH"
+ get "/", headers: { "HTTP_X_HEADER" => "NON_MATCH" }
assert_equal "Alternative App", @response.body
end
end
@@ -3629,15 +3675,13 @@ class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest
assert_match(/Missing :controller/, ex.message)
end
- def test_missing_action
+ def test_missing_controller_with_to
ex = assert_raises(ArgumentError) {
- assert_deprecated do
- draw do
- get '/foo/bar', :to => 'foo'
- end
+ draw do
+ get '/foo/bar', :to => 'foo'
end
}
- assert_match(/Missing :action/, ex.message)
+ assert_match(/Missing :controller/, ex.message)
end
def test_missing_action_on_hash
@@ -3761,7 +3805,7 @@ class TestHttpMethods < ActionDispatch::IntegrationTest
(RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC4791 + RFC5789).each do |method|
test "request method #{method.underscore} can be matched" do
- get '/', nil, 'REQUEST_METHOD' => method
+ get '/', headers: { 'REQUEST_METHOD' => method }
assert_equal method, @response.body
end
end
@@ -4432,4 +4476,45 @@ 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"
+ end
+ end
+
+ Routes = ActionDispatch::Routing::RouteSet.new
+ Routes.draw do
+ default_url_options locale: 'en'
+ scope ':locale', format: false do
+ get '/posts/:year/:month/:day', to: 'posts#archive', as: 'archived_posts'
+ end
+ end
+
+ APP = build_app Routes
+
+ def app
+ APP
+ end
+
+ include Routes.url_helpers
+
+ def test_positional_args_with_format_false
+ assert_equal '/en/posts/2014/12/13', archived_posts_path(2014, 12, 13)
+ end
end
diff --git a/actionpack/test/dispatch/session/cache_store_test.rb b/actionpack/test/dispatch/session/cache_store_test.rb
index 9f810cad01..22a46b0930 100644
--- a/actionpack/test/dispatch/session/cache_store_test.rb
+++ b/actionpack/test/dispatch/session/cache_store_test.rb
@@ -22,7 +22,7 @@ class CacheStoreTest < ActionDispatch::IntegrationTest
end
def get_session_id
- render :text => "#{request.session_options[:id]}"
+ render :text => "#{request.session.id}"
end
def call_reset_session
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index c5cd24d06e..e7f4235de8 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -29,7 +29,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
end
def get_session_id
- render :text => "id: #{request.session_options[:id]}"
+ render :text => "id: #{request.session.id}"
end
def get_class_after_reset_session
@@ -53,7 +53,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
end
def change_session_id
- request.session_options[:id] = nil
+ request.session.options[:id] = nil
get_session_id
end
@@ -125,7 +125,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
def test_does_set_secure_cookies_over_https
with_test_route_set(:secure => true) do
- get '/set_session_value', nil, 'HTTPS' => 'on'
+ get '/set_session_value', headers: { 'HTTPS' => 'on' }
assert_response :success
assert_equal "_myapp_session=#{response.body}; path=/; secure; HttpOnly",
headers['Set-Cookie']
@@ -331,9 +331,11 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
private
# Overwrite get to send SessionSecret in env hash
- def get(path, parameters = nil, env = {})
- env["action_dispatch.key_generator"] ||= Generator
- super
+ def get(path, *args)
+ args[0] ||= {}
+ args[0][:headers] ||= {}
+ args[0][:headers]["action_dispatch.key_generator"] ||= Generator
+ super(path, *args)
end
def with_test_route_set(options = {})
diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb
index f7a06cfed4..9a5d5131c0 100644
--- a/actionpack/test/dispatch/session/mem_cache_store_test.rb
+++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb
@@ -23,7 +23,7 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
end
def get_session_id
- render :text => "#{request.session_options[:id]}"
+ render :text => "#{request.session.id}"
end
def call_reset_session
@@ -172,7 +172,7 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
reset!
- get '/set_session_value', :_session_id => session_id
+ get '/set_session_value', params: { _session_id: session_id }
assert_response :success
assert_not_equal session_id, cookies['_session_id']
end
diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb
index 323fbc285e..72eaa916bc 100644
--- a/actionpack/test/dispatch/show_exceptions_test.rb
+++ b/actionpack/test/dispatch/show_exceptions_test.rb
@@ -27,30 +27,30 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
test "skip exceptions app if not showing exceptions" do
@app = ProductionApp
assert_raise RuntimeError do
- get "/", {}, {'action_dispatch.show_exceptions' => false}
+ get "/", headers: { 'action_dispatch.show_exceptions' => false }
end
end
test "rescue with error page" do
@app = ProductionApp
- get "/", {}, {'action_dispatch.show_exceptions' => true}
+ get "/", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 500
assert_equal "500 error fixture\n", body
- get "/bad_params", {}, {'action_dispatch.show_exceptions' => true}
+ get "/bad_params", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 400
assert_equal "400 error fixture\n", body
- get "/not_found", {}, {'action_dispatch.show_exceptions' => true}
+ get "/not_found", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 404
assert_equal "404 error fixture\n", body
- get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true}
+ get "/method_not_allowed", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 405
assert_equal "", body
- get "/unknown_http_method", {}, {'action_dispatch.show_exceptions' => true}
+ get "/unknown_http_method", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 405
assert_equal "", body
end
@@ -61,11 +61,11 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
begin
@app = ProductionApp
- get "/", {}, {'action_dispatch.show_exceptions' => true}
+ get "/", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 500
assert_equal "500 localized error fixture\n", body
- get "/not_found", {}, {'action_dispatch.show_exceptions' => true}
+ get "/not_found", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 404
assert_equal "404 error fixture\n", body
ensure
@@ -76,14 +76,14 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
test "sets the HTTP charset parameter" do
@app = ProductionApp
- get "/", {}, {'action_dispatch.show_exceptions' => true}
+ get "/", headers: { 'action_dispatch.show_exceptions' => true }
assert_equal "text/html; charset=utf-8", response.headers["Content-Type"]
end
test "show registered original exception for wrapped exceptions" do
@app = ProductionApp
- get "/not_found_original_exception", {}, {'action_dispatch.show_exceptions' => true}
+ get "/not_found_original_exception", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 404
assert_match(/404 error/, body)
end
@@ -97,7 +97,7 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
end
@app = ActionDispatch::ShowExceptions.new(Boomer.new, exceptions_app)
- get "/not_found_original_exception", {}, {'action_dispatch.show_exceptions' => true}
+ get "/not_found_original_exception", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 404
assert_equal "YOU FAILED BRO", body
end
@@ -108,7 +108,7 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
end
@app = ActionDispatch::ShowExceptions.new(Boomer.new, exceptions_app)
- get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true}
+ get "/method_not_allowed", headers: { 'action_dispatch.show_exceptions' => true }
assert_response 405
assert_equal "", body
end
diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb
index c3598c5e8e..7ced41bc2e 100644
--- a/actionpack/test/dispatch/ssl_test.rb
+++ b/actionpack/test/dispatch/ssl_test.rb
@@ -20,7 +20,7 @@ class SSLTest < ActionDispatch::IntegrationTest
end
def test_allows_https_proxy_header_url
- get "http://example.org/", {}, 'HTTP_X_FORWARDED_PROTO' => "https"
+ get "http://example.org/", headers: { 'HTTP_X_FORWARDED_PROTO' => "https" }
assert_response :success
end
diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb
index 7f1207eaed..f153030675 100644
--- a/actionpack/test/dispatch/static_test.rb
+++ b/actionpack/test/dispatch/static_test.rb
@@ -1,9 +1,17 @@
-# encoding: utf-8
require 'abstract_unit'
-require 'rbconfig'
require 'zlib'
module StaticTests
+ def setup
+ @default_internal_encoding = Encoding.default_internal
+ @default_external_encoding = Encoding.default_external
+ end
+
+ def teardown
+ Encoding.default_internal = @default_internal_encoding
+ Encoding.default_external = @default_external_encoding
+ end
+
def test_serves_dynamic_content
assert_equal "Hello, World!", get("/nofile").body
end
@@ -12,6 +20,16 @@ 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
+ Encoding.default_internal = "Windows-31J"
+ Encoding.default_external = "Windows-31J"
+ 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
@@ -145,6 +163,16 @@ module StaticTests
assert_equal default_response.headers['Content-Type'], response.headers['Content-Type']
end
+ def test_serves_gzip_files_with_not_modified
+ file_name = "/gzip/application-a71b3024f80aea3181c09774ca17e712.js"
+ last_modified = File.mtime(File.join(@root, "#{file_name}.gz"))
+ response = get(file_name, 'HTTP_ACCEPT_ENCODING' => 'gzip', 'HTTP_IF_MODIFIED_SINCE' => last_modified.httpdate)
+ assert_equal 304, response.status
+ assert_equal nil, response.headers['Content-Type']
+ assert_equal nil, response.headers['Content-Encoding']
+ assert_equal nil, response.headers['Vary']
+ end
+
# Windows doesn't allow \ / : * ? " < > | in filenames
unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
def test_serves_static_file_with_colon
@@ -200,6 +228,7 @@ class StaticTest < ActiveSupport::TestCase
}
def setup
+ super
@root = "#{FIXTURE_LOAD_PATH}/public"
@app = ActionDispatch::Static.new(DummyApp, @root, "public, max-age=60")
end
@@ -229,6 +258,7 @@ 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
index 3c393f937b..7278754b49 100644
--- a/actionpack/test/dispatch/template_assertions_test.rb
+++ b/actionpack/test/dispatch/template_assertions_test.rb
@@ -10,7 +10,7 @@ class AssertTemplateController < ActionController::Base
end
def render_with_layout
- @variable_for_layout = nil
+ @variable_for_layout = 'hello'
render 'test/hello_world', layout: "layouts/standard"
end
@@ -95,4 +95,16 @@ class AssertTemplateControllerTest < ActionDispatch::IntegrationTest
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 65ad8677f3..cc35d4594e 100644
--- a/actionpack/test/dispatch/test_request_test.rb
+++ b/actionpack/test/dispatch/test_request_test.rb
@@ -18,7 +18,7 @@ class TestRequestTest < ActiveSupport::TestCase
assert_equal "0.0.0.0", env.delete("REMOTE_ADDR")
assert_equal "Rails Testing", env.delete("HTTP_USER_AGENT")
- assert_equal [1, 2], env.delete("rack.version")
+ assert_equal [1, 3], env.delete("rack.version")
assert_equal "", env.delete("rack.input").string
assert_kind_of StringIO, env.delete("rack.errors")
assert_equal true, env.delete("rack.multithread")
diff --git a/actionpack/test/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb
index 8f79e7bf9a..ce1e1d0a6a 100644
--- a/actionpack/test/dispatch/url_generation_test.rb
+++ b/actionpack/test/dispatch/url_generation_test.rb
@@ -39,12 +39,12 @@ module TestUrlGeneration
end
test "the request's SCRIPT_NAME takes precedence over the route" do
- get "/foo", {}, 'SCRIPT_NAME' => "/new", 'action_dispatch.routes' => Routes
+ get "/foo", headers: { 'SCRIPT_NAME' => "/new", 'action_dispatch.routes' => Routes }
assert_equal "/new/foo", response.body
end
test "the request's SCRIPT_NAME wraps the mounted app's" do
- get '/new/bar/foo', {}, 'SCRIPT_NAME' => '/new', 'PATH_INFO' => '/bar/foo', 'action_dispatch.routes' => Routes
+ get '/new/bar/foo', headers: { 'SCRIPT_NAME' => '/new', 'PATH_INFO' => '/bar/foo', 'action_dispatch.routes' => Routes }
assert_equal "/new/bar/foo", response.body
end
diff --git a/actionpack/test/fixtures/collection_cache/index.html.erb b/actionpack/test/fixtures/collection_cache/index.html.erb
new file mode 100644
index 0000000000..521b1450df
--- /dev/null
+++ b/actionpack/test/fixtures/collection_cache/index.html.erb
@@ -0,0 +1 @@
+<%= render @customers %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/customers/_commented_customer.html.erb b/actionpack/test/fixtures/customers/_commented_customer.html.erb
new file mode 100644
index 0000000000..d5f6e3b491
--- /dev/null
+++ b/actionpack/test/fixtures/customers/_commented_customer.html.erb
@@ -0,0 +1,4 @@
+<%# I'm a comment %>
+<% cache customer do %>
+ <%= 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
new file mode 100644
index 0000000000..67e9f6d411
--- /dev/null
+++ b/actionpack/test/fixtures/customers/_customer.html.erb
@@ -0,0 +1,3 @@
+<% cache customer do %>
+ <%= customer.name %>, <%= customer.id %>
+<% end %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/localized/hello_world.de.html b/actionpack/test/fixtures/localized/hello_world.de.html
index 4727d7a7e0..a8fc612c60 100644
--- a/actionpack/test/fixtures/localized/hello_world.de.html
+++ b/actionpack/test/fixtures/localized/hello_world.de.html
@@ -1 +1 @@
-Gutten Tag \ No newline at end of file
+Guten Tag \ No newline at end of file
diff --git a/actionpack/test/fixtures/symlink_parent/symlinked_layout.erb b/actionpack/test/fixtures/symlink_parent/symlinked_layout.erb
deleted file mode 100644
index bda57d0fae..0000000000
--- a/actionpack/test/fixtures/symlink_parent/symlinked_layout.erb
+++ /dev/null
@@ -1,5 +0,0 @@
-This is my layout
-
-<%= yield %>
-
-End.
diff --git a/actionpack/test/journey/path/pattern_test.rb b/actionpack/test/journey/path/pattern_test.rb
index 9dfdfc23ed..6939b426f6 100644
--- a/actionpack/test/journey/path/pattern_test.rb
+++ b/actionpack/test/journey/path/pattern_test.rb
@@ -16,6 +16,7 @@ module ActionDispatch
'/:controller(.:format)' => %r{\A/(#{x})(?:\.([^/.?]+))?\Z},
'/:controller/*foo' => %r{\A/(#{x})/(.+)\Z},
'/:controller/*foo/bar' => %r{\A/(#{x})/(.+)/bar\Z},
+ '/:foo|*bar' => %r{\A/(?:([^/.?]+)|(.+))\Z},
}.each do |path, expected|
define_method(:"test_to_regexp_#{path}") do
strexp = Router::Strexp.build(
@@ -39,6 +40,7 @@ module ActionDispatch
'/:controller(.:format)' => %r{\A/(#{x})(?:\.([^/.?]+))?},
'/:controller/*foo' => %r{\A/(#{x})/(.+)},
'/:controller/*foo/bar' => %r{\A/(#{x})/(.+)/bar},
+ '/:foo|*bar' => %r{\A/(?:([^/.?]+)|(.+))},
}.each do |path, expected|
define_method(:"test_to_non_anchored_regexp_#{path}") do
strexp = Router::Strexp.build(
diff --git a/actionpack/test/journey/router/utils_test.rb b/actionpack/test/journey/router/utils_test.rb
index 9b2b85ec73..2b505f081e 100644
--- a/actionpack/test/journey/router/utils_test.rb
+++ b/actionpack/test/journey/router/utils_test.rb
@@ -1,4 +1,3 @@
-# coding: utf-8
require 'abstract_unit'
module ActionDispatch
diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb
index fbac86e8ca..a134e343cc 100644
--- a/actionpack/test/journey/router_test.rb
+++ b/actionpack/test/journey/router_test.rb
@@ -401,6 +401,33 @@ 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, {}, {}, {}
@@ -415,7 +442,7 @@ module ActionDispatch
def test_generate_with_name
path = Path::Pattern.from_string '/:controller(/:action)'
- @router.routes.add_route @app, path, {}, {}, {}
+ @router.routes.add_route @app, path, {}, {}, "tasks"
path, params = @formatter.generate(
"tasks",
diff --git a/actionpack/test/journey/routes_test.rb b/actionpack/test/journey/routes_test.rb
index a4efc82b8c..b54d961f66 100644
--- a/actionpack/test/journey/routes_test.rb
+++ b/actionpack/test/journey/routes_test.rb
@@ -3,6 +3,10 @@ require 'abstract_unit'
module ActionDispatch
module Journey
class TestRoutes < ActiveSupport::TestCase
+ setup do
+ @routes = Routes.new
+ end
+
def test_clear
routes = Routes.new
exp = Router::Strexp.build '/foo(/:id)', {}, ['/.?']
@@ -36,8 +40,24 @@ module ActionDispatch
assert_not_equal sim, routes.simulator
end
+ def test_partition_route
+ path = Path::Pattern.from_string '/hello'
+
+ anchored_route = @routes.add_route nil, path, {}, {}, {}
+ assert_equal [anchored_route], @routes.anchored_routes
+ assert_equal [], @routes.custom_routes
+
+ strexp = Router::Strexp.build(
+ "/hello/:who", { who: /\d/ }, ['/', '.', '?']
+ )
+ path = Path::Pattern.new strexp
+
+ custom_route = @routes.add_route nil, path, {}, {}, {}
+ assert_equal [custom_route], @routes.custom_routes
+ assert_equal [anchored_route], @routes.anchored_routes
+ end
+
def test_first_name_wins
- #def add_route app, path, conditions, defaults, name = nil
routes = Routes.new
one = Path::Pattern.from_string '/hello'
diff --git a/actionpack/test/routing/helper_test.rb b/actionpack/test/routing/helper_test.rb
index 09ca7ff73b..0028aaa629 100644
--- a/actionpack/test/routing/helper_test.rb
+++ b/actionpack/test/routing/helper_test.rb
@@ -26,20 +26,6 @@ module ActionDispatch
x.new.pond_duck_path Duck.new
end
end
-
- def test_path_deprecation
- rs = ::ActionDispatch::Routing::RouteSet.new
- rs.draw do
- resources :ducks
- end
-
- x = Class.new {
- include rs.url_helpers(false)
- }
- assert_deprecated do
- assert_equal '/ducks', x.new.ducks_path
- end
- end
end
end
end
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index e388e6ecd3..80aacf7234 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,199 +1,111 @@
-* Update `select_tag` to work correctly with `:include_blank` option passing a string.
+* Accept lambda as `child_index` option in `fields_for` method.
- Fixes #16483.
+ *Karol Galanciak*
- *Frank Groeneveld*
+* `translate` allows `default: [[]]` again for a default value of `[]`.
-* Changed the meaning of `render "foo/bar"`.
+ Fixes #19640.
- Previously, calling `render "foo/bar"` in a controller action is equivalent
- to `render file: "foo/bar"`. In Rails 4.2, this has been changed to mean
- `render template: "foo/bar"` instead. If you need to render a file, please
- change your code to use the explicit form (`render file: "foo/bar"`) instead.
+ *Adam Prescott*
- *Jeremy Jackson*
+* `translate` should accept nils as members of the `:default`
+ parameter without raising a translation missing error. Fixes a
+ regression introduced 362557e.
-* Add support for ARIA attributes in tags.
+ Fixes #19419
- Example:
+ *Justin Coyne*
- <%= f.text_field :name, aria: { required: "true", hidden: "false" } %>
-
- now generates:
-
- <input aria-hidden="false" aria-required="true" id="user_name" name="user[name]" type="text">
-
- *Paola Garcia Casadiego*
-
-* Provide a `builder` object when using the `label` form helper in block form.
-
- The new `builder` object responds to `translation`, allowing I18n fallback support
- when you want to customize how a particular label is presented.
-
- *Alex Robbin*
-
-* Add I18n support for input/textarea placeholder text.
+* `number_to_percentage` does not crash with `Float::NAN` or `Float::INFINITY`
+ as input when `precision: 0` is used.
- Placeholder I18n follows the same convention as `label` I18n.
+ Fixes #19227.
- *Alex Robbin*
+ *Yves Senn*
-* Fix that render layout: 'messages/layout' should also be added to the dependency tracker tree.
+* Fixed the translation helper method to accept different default values types
+ besides String.
- *DHH*
+ *Ulisses Almeida*
-* Add `PartialIteration` object used when rendering collections.
+* Collection rendering automatically caches and fetches multiple partials.
- The iteration object is available as the local variable
- `#{template_name}_iteration` when rendering partials with collections.
+ Collections rendered as:
- It gives access to the `size` of the collection being iterated over,
- the current `index` and two convenience methods `first?` and `last?`.
+ ```ruby
+ <%= render @notifications %>
+ <%= render partial: 'notifications/notification', collection: @notifications, as: :notification %>
+ ```
- *Joel Junström*, *Lucas Uyezu*
+ will now read several partials from cache at once, if the template starts with a cache call:
-* Return an absolute instead of relative path from an asset url in the case
- of the `asset_host` proc returning nil.
+ ```ruby
+ # notifications/_notification.html.erb
+ <% cache notification do %>
+ <%# ... %>
+ <% end %>
+ ```
- *Jolyon Pawlyn*
+ *Kasper Timm Hansen*
-* Fix `html_escape_once` to properly handle hex escape sequences (e.g. &#x1a2b;).
+* Fixed a dependency tracker bug that caused template dependencies not
+ count layouts as dependencies for partials.
- *John F. Douthat*
+ *Juho Leinonen*
-* Added String support for min and max properties for date field helpers.
+* Extracted `ActionView::Helpers::RecordTagHelper` to external gem
+ (`record_tag_helper`) and added removal notices.
*Todd Bealmear*
-* The `highlight` helper now accepts a block to be used instead of the `highlighter`
- option.
-
- *Lucas Mazza*
-
-* The `except` and `highlight` helpers now accept regular expressions.
-
- *Jan Szumiec*
-
-* Flatten the array parameter in `safe_join`, so it behaves consistently with
- `Array#join`.
-
- *Paul Grayson*
-
-* Honor `html_safe` on array elements in tag values, as we do for plain string
- values.
-
- *Paul Grayson*
-
-* Add `ActionView::Template::Handler.unregister_template_handler`.
-
- It performs the opposite of `ActionView::Template::Handler.register_template_handler`.
-
- *Zuhao Wan*
-
-* Bring `cache_digest` rake tasks up-to-date with the latest API changes.
-
- *Jiri Pospisil*
-
-* Allow custom `:host` option to be passed to `asset_url` helper that
- overwrites `config.action_controller.asset_host` for particular asset.
+* Allow to pass a string value to `size` option in `image_tag` and `video_tag`.
- *Hubert Łępicki*
+ This makes the behavior more consistent with `width` or `height` options.
-* Deprecate `AbstractController::Base.parent_prefixes`.
- Override `AbstractController::Base.local_prefixes` when you want to change
- where to find views.
+ *Mehdi Lahmam*
- *Nick Sutterer*
+* Partial template name does no more have to be a valid Ruby identifier.
-* Take label values into account when doing I18n lookups for model attributes.
+ There used to be a naming rule that the partial name should start with
+ underscore, and should be followed by any combination of letters, numbers
+ and underscores.
+ But now we can give our partials any name starting with underscore, such as
+ _🍔.html.erb.
- The following:
+ *Akira Matsuda*
- # form.html.erb
- <%= form_for @post do |f| %>
- <%= f.label :type, value: "long" %>
- <% end %>
+* Change the default template handler from `ERB` to `Raw`.
- # en.yml
- en:
- activerecord:
- attributes:
- post/long: "Long-form Post"
+ Files without a template handler in their extension will be rendered using the raw
+ handler instead of ERB.
- Used to simply return "long", but now it will return "Long-form
- Post".
+ *Rafael Mendonça França*
- *Joshua Cody*
+* Remove deprecated `AbstractController::Base::parent_prefixes`.
-* Change `asset_path` to use File.join to create proper paths:
+ *Rafael Mendonça França*
- Before:
+* Default translations that have a lower precedence than a html safe default,
+ but are not themselves safe, should not be marked as html_safe.
- https://some.host.com//assets/some.js
-
- After:
-
- https://some.host.com/assets/some.js
-
- *Peter Schröder*
-
-* Change `favicon_link_tag` default mimetype from `image/vnd.microsoft.icon` to
- `image/x-icon`.
-
- Before:
-
- # => favicon_link_tag 'myicon.ico'
- <link href="/assets/myicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
-
- After:
-
- # => favicon_link_tag 'myicon.ico'
- <link href="/assets/myicon.ico" rel="shortcut icon" type="image/x-icon" />
-
- *Geoffroy Lorieux*
-
-* Remove wrapping div with inline styles for hidden form fields.
-
- We are dropping HTML 4.01 and XHTML strict compliance since input tags directly
- inside a form are valid HTML5, and the absence of inline styles help in validating
- for Content Security Policy.
-
- *Joost Baaij*
-
-* `collection_check_boxes` respects `:index` option for the hidden field name.
-
- Fixes #14147.
-
- *Vasiliy Ermolovich*
-
-* `date_select` helper with option `with_css_classes: true` does not overwrite other classes.
-
- *Izumi Wong-Horiuchi*
-
-* `number_to_percentage` does not crash with `Float::NAN` or `Float::INFINITY`
- as input.
-
- Fixes #14405.
-
- *Yves Senn*
+ *Justin Coyne*
-* Add `include_hidden` option to `collection_check_boxes` helper.
+* Make possible to use blocks with short version of `render "partial"` helper.
- *Vasiliy Ermolovich*
+ *Nikolay Shebanov*
-* Fixed a problem where the default options for the `button_tag` helper are not
- applied correctly.
+* Add a `hidden_field` on the `file_field` to avoid raise a error when the only
+ input on the form is the `file_field`.
- Fixes #14254.
+ *Mauro George*
- *Sergey Prikhodko*
+* Add an explicit error message, in `ActionView::PartialRenderer` for partial
+ `rendering`, when the value of option `as` has invalid characters.
-* Take variants into account when calculating template digests in ActionView::Digestor.
+ *Angelo Capilleri*
- The arguments to ActionView::Digestor#digest are now being passed as a hash
- to support variants and allow more flexibility in the future. The support for
- regular (required) arguments is deprecated and will be removed in Rails 5.0 or later.
+* Allow entries without a link tag in AtomFeedHelper.
- *Piotr Chmolowski, Łukasz Strzałkowski*
+ *Daniel Gomez de Souza*
-Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/actionview/CHANGELOG.md) for previous changes.
+Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/actionview/CHANGELOG.md) for previous changes.
diff --git a/actionview/MIT-LICENSE b/actionview/MIT-LICENSE
index d58dd9ed9b..3ec7a617cf 100644
--- a/actionview/MIT-LICENSE
+++ b/actionview/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2014 David Heinemeier Hansson
+Copyright (c) 2004-2015 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionview/Rakefile b/actionview/Rakefile
index 1b71435948..2b752b83df 100644
--- a/actionview/Rakefile
+++ b/actionview/Rakefile
@@ -18,7 +18,7 @@ namespace :test do
Rake::TestTask.new(:template) do |t|
t.libs << 'test'
- t.test_files = Dir.glob('test/template/**/*_test.rb').sort
+ t.test_files = Dir.glob('test/template/**/*_test.rb')
t.warning = true
t.verbose = true
t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec
index 70efe7e8c2..d8ea9d562c 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 = '>= 1.9.3'
+ s.required_ruby_version = '>= 2.2.1'
s.license = 'MIT'
@@ -23,8 +23,8 @@ Gem::Specification.new do |s|
s.add_dependency 'builder', '~> 3.1'
s.add_dependency 'erubis', '~> 2.7.0'
- s.add_dependency 'rails-html-sanitizer', '~> 1.0', '>= 1.0.1'
- s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.4'
+ s.add_dependency 'rails-html-sanitizer', '~> 1.0', '>= 1.0.2'
+ s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.5'
s.add_development_dependency 'actionpack', version
s.add_development_dependency 'activemodel', version
diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb
index 6a1837c6e2..c3bbac27fd 100644
--- a/actionview/lib/action_view.rb
+++ b/actionview/lib/action_view.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2014 David Heinemeier Hansson
+# Copyright (c) 2004-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb
index 3071a13655..43124bb904 100644
--- a/actionview/lib/action_view/base.rb
+++ b/actionview/lib/action_view/base.rb
@@ -70,6 +70,14 @@ module ActionView #:nodoc:
# Headline: <%= headline %>
# First name: <%= person.first_name %>
#
+ # The local variables passed to sub templates can be accessed as a hash using the <tt>local_assigns</tt> hash. This lets you access the
+ # variables as:
+ #
+ # Headline: <%= local_assigns[:headline] %>
+ #
+ # This is useful in cases where you aren't sure if the local variable has been assigned. Alternately, you could also use
+ # <tt>defined? headline</tt> to first check if the variable has been assigned before using it.
+ #
# === Template caching
#
# By default, Rails will compile each template to a method in order to render it. When you alter a template,
@@ -126,8 +134,8 @@ module ActionView #:nodoc:
# end
# end
#
- # For more information on Builder please consult the [source
- # code](https://github.com/jimweirich/builder).
+ # For more information on Builder please consult the {source
+ # code}[https://github.com/jimweirich/builder].
class Base
include Helpers, ::ERB::Util, Context
diff --git a/actionview/lib/action_view/dependency_tracker.rb b/actionview/lib/action_view/dependency_tracker.rb
index e34bdd4a46..7a7e116dbb 100644
--- a/actionview/lib/action_view/dependency_tracker.rb
+++ b/actionview/lib/action_view/dependency_tracker.rb
@@ -76,6 +76,12 @@ module ActionView
(?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
/xm
+ LAYOUT_DEPENDENCY = /\A
+ (?:\s*\(?\s*) # optional opening paren surrounded by spaces
+ (?:.*?#{LAYOUT_HASH_KEY}) # check if the line has layout key declaration
+ (?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
+ /xm
+
def self.call(name, template)
new(name, template).dependencies
end
@@ -106,15 +112,20 @@ module ActionView
render_calls = source.split(/\brender\b/).drop(1)
render_calls.each do |arguments|
- arguments.scan(RENDER_ARGUMENTS) do
- add_dynamic_dependency(render_dependencies, Regexp.last_match[:dynamic])
- add_static_dependency(render_dependencies, Regexp.last_match[:static])
- end
+ add_dependencies(render_dependencies, arguments, LAYOUT_DEPENDENCY)
+ add_dependencies(render_dependencies, arguments, RENDER_ARGUMENTS)
end
render_dependencies.uniq
end
+ def add_dependencies(render_dependencies, arguments, pattern)
+ arguments.scan(pattern) do
+ add_dynamic_dependency(render_dependencies, Regexp.last_match[:dynamic])
+ add_static_dependency(render_dependencies, Regexp.last_match[:static])
+ end
+ end
+
def add_dynamic_dependency(dependencies, dependency)
if dependency
dependencies << "#{dependency.pluralize}/#{dependency.singularize}"
diff --git a/actionview/lib/action_view/gem_version.rb b/actionview/lib/action_view/gem_version.rb
index 752eafee3c..4f45f5b8c8 100644
--- a/actionview/lib/action_view/gem_version.rb
+++ b/actionview/lib/action_view/gem_version.rb
@@ -5,10 +5,10 @@ module ActionView
end
module VERSION
- MAJOR = 4
- MINOR = 2
+ MAJOR = 5
+ MINOR = 0
TINY = 0
- PRE = "beta4"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index b7fdc16a9d..60fc9ee1a2 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -127,7 +127,7 @@ module ActionView
# auto_discovery_link_tag(:rss, {controller: "news", action: "feed"})
# # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/news/feed" />
# auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {title: "Example RSS"})
- # # => <link rel="alternate" type="application/rss+xml" title="Example RSS" href="http://www.example.com/feed" />
+ # # => <link rel="alternate" type="application/rss+xml" title="Example RSS" href="http://www.example.com/feed.rss" />
def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {})
if !(type == :rss || type == :atom) && tag_options[:type].blank?
raise ArgumentError.new("You should pass :type tag_option key explicitly, because you have passed #{type} type other than :rss or :atom.")
@@ -318,6 +318,7 @@ module ActionView
end
def extract_dimensions(size)
+ size = size.to_s
if size =~ %r{\A\d+x\d+\z}
size.split('x')
elsif size =~ %r{\A\d+\z}
diff --git a/actionview/lib/action_view/helpers/atom_feed_helper.rb b/actionview/lib/action_view/helpers/atom_feed_helper.rb
index 227ad4cdfa..d8be4e5678 100644
--- a/actionview/lib/action_view/helpers/atom_feed_helper.rb
+++ b/actionview/lib/action_view/helpers/atom_feed_helper.rb
@@ -174,7 +174,7 @@ module ActionView
#
# * <tt>:published</tt>: Time first published. Defaults to the created_at attribute on the record if one such exists.
# * <tt>:updated</tt>: Time of update. Defaults to the updated_at attribute on the record if one such exists.
- # * <tt>:url</tt>: The URL for this entry. Defaults to the polymorphic_url for the record.
+ # * <tt>:url</tt>: The URL for this entry or false or nil for not having a link tag. Defaults to the polymorphic_url for the record.
# * <tt>:id</tt>: The ID for this entry. Defaults to "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}"
# * <tt>:type</tt>: The TYPE for this entry. Defaults to "text/html".
def entry(record, options = {})
@@ -191,7 +191,8 @@ module ActionView
type = options.fetch(:type, 'text/html')
- @xml.link(:rel => 'alternate', :type => type, :href => options[:url] || @view.polymorphic_url(record))
+ url = options.fetch(:url) { @view.polymorphic_url(record) }
+ @xml.link(:rel => 'alternate', :type => type, :href => url) if url
yield AtomBuilder.new(@xml)
end
diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb
index 4db8930a26..0e2a5f90f4 100644
--- a/actionview/lib/action_view/helpers/cache_helper.rb
+++ b/actionview/lib/action_view/helpers/cache_helper.rb
@@ -110,6 +110,29 @@ module ActionView
# <%= some_helper_method(person) %>
#
# Now all you'll have to do is change that timestamp when the helper method changes.
+ #
+ # === Automatic Collection Caching
+ #
+ # When rendering collections such as:
+ #
+ # <%= render @notifications %>
+ # <%= render partial: 'notifications/notification', collection: @notifications %>
+ #
+ # If the notifications/_notification partial starts with a cache call like so:
+ #
+ # <% cache notification do %>
+ # <%= notification.name %>
+ # <% end %>
+ #
+ # 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.
+ #
+ # 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
safe_concat(fragment_for(cache_fragment_name(name, options), options, &block))
@@ -122,7 +145,7 @@ module ActionView
# Cache fragments of a view if +condition+ is true
#
- # <%= cache_if admin?, project do %>
+ # <% cache_if admin?, project do %>
# <b>All the topics on this project</b>
# <%= render project.topics %>
# <% end %>
@@ -138,7 +161,7 @@ module ActionView
# Cache fragments of a view unless +condition+ is true
#
- # <%= cache_unless admin?, project do %>
+ # <% cache_unless admin?, project do %>
# <b>All the topics on this project</b>
# <%= render project.topics %>
# <% end %>
@@ -161,6 +184,14 @@ module ActionView
end
end
+ # Given a key (as described in ActionController::Caching::Fragments.expire_fragment),
+ # returns a key suitable for use in reading, writing, or expiring a
+ # cached fragment. All keys are prefixed with <tt>views/</tt> and uses
+ # ActiveSupport::Cache.expand_cache_key for the expansion.
+ def fragment_cache_key(key)
+ ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views)
+ end
+
private
def fragment_name_with_digest(name) #:nodoc:
diff --git a/actionview/lib/action_view/helpers/capture_helper.rb b/actionview/lib/action_view/helpers/capture_helper.rb
index 75d1634b2e..a67ba580f1 100644
--- a/actionview/lib/action_view/helpers/capture_helper.rb
+++ b/actionview/lib/action_view/helpers/capture_helper.rb
@@ -31,7 +31,8 @@ module ActionView
# <head><title><%= @greeting %></title></head>
# <body>
# <b><%= @greeting %></b>
- # </body></html>
+ # </body>
+ # </html>
#
def capture(*args)
value = nil
@@ -194,7 +195,9 @@ module ActionView
def with_output_buffer(buf = nil) #:nodoc:
unless buf
buf = ActionView::OutputBuffer.new
- buf.force_encoding(output_buffer.encoding) if output_buffer
+ if output_buffer && output_buffer.respond_to?(:encoding)
+ buf.force_encoding(output_buffer.encoding)
+ end
end
self.output_buffer, old_buffer = buf, output_buffer
yield
diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb
index 01a9747035..46ce7cf0be 100644
--- a/actionview/lib/action_view/helpers/date_helper.rb
+++ b/actionview/lib/action_view/helpers/date_helper.rb
@@ -177,7 +177,9 @@ module ActionView
# and +:name+ (string). A format string would be something like "%{name} (%<number>02d)" for example.
# See <tt>Kernel.sprintf</tt> for documentation on format sequences.
# * <tt>:date_separator</tt> - Specifies a string to separate the date fields. Default is "" (i.e. nothing).
- # * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Date.today.year - 5</tt>if
+ # * <tt>:time_separator</tt> - Specifies a string to separate the time fields. Default is "" (i.e. nothing).
+ # * <tt>:datetime_separator</tt>- Specifies a string to separate the date and time fields. Default is "" (i.e. nothing).
+ # * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Date.today.year - 5</tt> if
# you are creating new record. While editing existing record, <tt>:start_year</tt> defaults to
# the current selected year minus 5.
# * <tt>:end_year</tt> - Set the end year for the year select. Default is <tt>Date.today.year + 5</tt> if
@@ -898,7 +900,7 @@ module ActionView
def translated_date_order
date_order = I18n.translate(:'date.order', :locale => @options[:locale], :default => [])
- date_order = date_order.map { |element| element.to_sym }
+ date_order = date_order.map(&:to_sym)
forbidden_elements = date_order - [:year, :month, :day]
if forbidden_elements.any?
diff --git a/actionview/lib/action_view/helpers/debug_helper.rb b/actionview/lib/action_view/helpers/debug_helper.rb
index ba47eee9ba..e9dccbad1c 100644
--- a/actionview/lib/action_view/helpers/debug_helper.rb
+++ b/actionview/lib/action_view/helpers/debug_helper.rb
@@ -26,7 +26,7 @@ module ActionView
Marshal::dump(object)
object = ERB::Util.html_escape(object.to_yaml)
content_tag(:pre, object, :class => "debug_dump")
- rescue Exception # errors from Marshal or YAML
+ rescue # errors from Marshal or YAML
# Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback
content_tag(:code, object.inspect, :class => "debug_dump")
end
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 03f80ff360..ece117b547 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -4,6 +4,7 @@ require 'action_view/helpers/tag_helper'
require 'action_view/helpers/form_tag_helper'
require 'action_view/helpers/active_model_helper'
require 'action_view/model_naming'
+require 'action_view/record_identifier'
require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/string/output_safety'
@@ -66,9 +67,10 @@ module ActionView
#
# In particular, thanks to the conventions followed in the generated field names, the
# controller gets a nested hash <tt>params[:person]</tt> with the person attributes
- # set in the form. That hash is ready to be passed to <tt>Person.create</tt>:
+ # set in the form. That hash is ready to be passed to <tt>Person.new</tt>:
#
- # if @person = Person.create(params[:person])
+ # @person = Person.new(params[:person])
+ # if @person.save
# # success
# else
# # error handling
@@ -110,6 +112,7 @@ module ActionView
include FormTagHelper
include UrlHelper
include ModelNaming
+ include RecordIdentifier
# Creates a form that allows the user to create or update the attributes
# of a specific model object.
@@ -138,6 +141,7 @@ module ActionView
# will get expanded to
#
# <%= text_field :person, :first_name %>
+ #
# which results in an HTML <tt><input></tt> tag whose +name+ attribute is
# <tt>person[first_name]</tt>. This means that when the form is submitted,
# the value entered by the user will be available in the controller as
@@ -164,6 +168,23 @@ module ActionView
# * <tt>:namespace</tt> - A namespace for your form to ensure uniqueness of
# id attributes on form elements. The namespace attribute will be prefixed
# with underscore on the generated HTML id.
+ # * <tt>:method</tt> - The method to use when submitting the form, usually
+ # either "get" or "post". If "patch", "put", "delete", or another verb
+ # is used, a hidden input with name <tt>_method</tt> is added to
+ # simulate the verb over post.
+ # * <tt>:authenticity_token</tt> - Authenticity token to use in the form.
+ # Use only if you need to pass custom authenticity token string, or to
+ # not add authenticity_token field at all (by passing <tt>false</tt>).
+ # Remote forms may omit the embedded authenticity token by setting
+ # <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>.
+ # This is helpful when you're fragment-caching the form. Remote forms
+ # get the authenticity token from the <tt>meta</tt> tag, so embedding is
+ # unnecessary unless you support browsers without JavaScript.
+ # * <tt>:remote</tt> - If set to true, will allow the Unobtrusive
+ # JavaScript drivers to control the submit behavior. By default this
+ # behavior is an ajax submit.
+ # * <tt>:enforce_utf8</tt> - If set to false, a hidden input with name
+ # utf8 is not output.
# * <tt>:html</tt> - Optional HTML attributes for the form tag.
#
# Also note that +form_for+ doesn't create an exclusive scope. It's still
@@ -420,6 +441,7 @@ module ActionView
html_options[:data] = options.delete(:data) if options.has_key?(:data)
html_options[:remote] = options.delete(:remote) if options.has_key?(:remote)
html_options[:method] = options.delete(:method) if options.has_key?(:method)
+ html_options[:enforce_utf8] = options.delete(:enforce_utf8) if options.has_key?(:enforce_utf8)
html_options[:authenticity_token] = options.delete(:authenticity_token)
builder = instantiate_builder(object_name, object, options)
@@ -836,6 +858,24 @@ module ActionView
#
# file_field(:attachment, :file, class: 'file_input')
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
+ #
+ # ==== Gotcha
+ #
+ # The HTML specification says that when a file field is empty, web browsers
+ # do not send any value to the server. Unfortunately this introduces a
+ # gotcha: if a +User+ model has an +avatar+ field, and no file is selected,
+ # then the +avatar+ parameter is empty. Thus, any mass-assignment idiom like
+ #
+ # @user.update(params[:user])
+ #
+ # wouldn't update the +avatar+ field.
+ #
+ # To prevent this, the helper generates an auxiliary hidden field before
+ # every file field. The hidden field has the same name as the file one and
+ # a blank value.
+ #
+ # In case you don't want the helper to generate this hidden field you can
+ # specify the <tt>include_hidden: false</tt> option.
def file_field(object_name, method, options = {})
Tags::FileField.new(object_name, method, self, options).render
end
@@ -1188,11 +1228,11 @@ module ActionView
object_name = model_name_from_record_or_class(object).param_key
end
- builder = options[:builder] || default_form_builder
+ builder = options[:builder] || default_form_builder_class
builder.new(object_name, object, self, options)
end
- def default_form_builder
+ def default_form_builder_class
builder = ActionView::Base.default_form_builder
builder.respond_to?(:constantize) ? builder.constantize : builder
end
@@ -1208,7 +1248,7 @@ module ActionView
# Admin: <%= person_form.check_box :admin %>
# <% end %>
#
- # In the above block, the a +FormBuilder+ object is yielded as the
+ # In the above block, a +FormBuilder+ object is yielded as the
# +person_form+ variable. This allows you to generate the +text_field+
# and +check_box+ fields by specifying their eponymous methods, which
# modify the underlying template and associates the +@person+ model object
@@ -1229,6 +1269,7 @@ module ActionView
# )
# )
# end
+ # end
#
# The above code creates a new method +div_radio_button+ which wraps a div
# around the new radio button. Note that when options are passed in, you
@@ -1887,7 +1928,11 @@ module ActionView
explicit_child_index = options[:child_index]
output = ActiveSupport::SafeBuffer.new
association.each do |child|
- options[:child_index] = nested_child_index(name) unless explicit_child_index
+ if explicit_child_index
+ options[:child_index] = explicit_child_index.call if explicit_child_index.respond_to?(:call)
+ else
+ options[:child_index] = nested_child_index(name)
+ end
output << fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block)
end
output
@@ -1917,6 +1962,8 @@ module ActionView
end
ActiveSupport.on_load(:action_view) do
- cattr_accessor(:default_form_builder) { ::ActionView::Helpers::FormBuilder }
+ cattr_accessor(:default_form_builder, instance_writer: false, instance_reader: false) do
+ ::ActionView::Helpers::FormBuilder
+ end
end
end
diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb
index 83b07a00d4..8a5928477f 100644
--- a/actionview/lib/action_view/helpers/form_options_helper.rb
+++ b/actionview/lib/action_view/helpers/form_options_helper.rb
@@ -18,10 +18,10 @@ module ActionView
#
# could become:
#
- # <select name="post[category]">
- # <option></option>
- # <option>joke</option>
- # <option>poem</option>
+ # <select name="post[category]" id="post_category">
+ # <option value=""></option>
+ # <option value="joke">joke</option>
+ # <option value="poem">poem</option>
# </select>
#
# Another common case is a select tag for a <tt>belongs_to</tt>-associated object.
@@ -32,7 +32,7 @@ module ActionView
#
# could become:
#
- # <select name="post[person_id]">
+ # <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>
@@ -45,7 +45,7 @@ module ActionView
#
# could become:
#
- # <select name="post[person_id]">
+ # <select name="post[person_id]" id="post_person_id">
# <option value="">Select Person</option>
# <option value="1">David</option>
# <option value="2">Sam</option>
@@ -71,11 +71,11 @@ module ActionView
#
# could become:
#
- # <select name="post[category]">
- # <option></option>
- # <option>joke</option>
- # <option>poem</option>
- # <option disabled="disabled">restricted</option>
+ # <select name="post[category]" id="post_category">
+ # <option value=""></option>
+ # <option value="joke">joke</option>
+ # <option value="poem">poem</option>
+ # <option disabled="disabled" value="restricted">restricted</option>
# </select>
#
# 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.
@@ -83,7 +83,7 @@ module ActionView
# collection_select(:post, :category_id, Category.all, :id, :name, {disabled: lambda{|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]">
+ # <select name="post[category_id]" id="post_category_id">
# <option value="1" disabled="disabled">2008 stuff</option>
# <option value="2" disabled="disabled">Christmas</option>
# <option value="3">Jokes</option>
@@ -109,7 +109,7 @@ module ActionView
#
# would become:
#
- # <select name="post[person_id]">
+ # <select name="post[person_id]" id="post_person_id">
# <option value=""></option>
# <option value="1" selected="selected">David</option>
# <option value="2">Sam</option>
@@ -192,7 +192,7 @@ module ActionView
# collection_select(:post, :author_id, Author.all, :id, :name_with_initial, prompt: true)
#
# If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return:
- # <select name="post[author_id]">
+ # <select name="post[author_id]" id="post_author_id">
# <option value="">Please select</option>
# <option value="1" selected="selected">D. Heinemeier Hansson</option>
# <option value="2">D. Thomas</option>
@@ -243,7 +243,7 @@ module ActionView
#
# Possible output:
#
- # <select name="city[country_id]">
+ # <select name="city[country_id]" id="city_country_id">
# <optgroup label="Africa">
# <option value="1">South Africa</option>
# <option value="3">Somalia</option>
@@ -302,17 +302,17 @@ module ActionView
# # => <option value="DKK">Kroner</option>
#
# options_for_select([ "VISA", "MasterCard" ], "MasterCard")
- # # => <option>VISA</option>
- # # => <option selected="selected">MasterCard</option>
+ # # => <option value="VISA">VISA</option>
+ # # => <option selected="selected" value="MasterCard">MasterCard</option>
#
# options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40")
# # => <option value="$20">Basic</option>
# # => <option value="$40" selected="selected">Plus</option>
#
# options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"])
- # # => <option selected="selected">VISA</option>
- # # => <option>MasterCard</option>
- # # => <option selected="selected">Discover</option>
+ # # => <option selected="selected" value="VISA">VISA</option>
+ # # => <option value="MasterCard">MasterCard</option>
+ # # => <option selected="selected" value="Discover">Discover</option>
#
# You can optionally provide HTML attributes as the last element of the array.
#
@@ -351,12 +351,12 @@ module ActionView
return container if String === container
selected, disabled = extract_selected_and_disabled(selected).map do |r|
- Array(r).map { |item| item.to_s }
+ Array(r).map(&:to_s)
end
container.map do |element|
html_attributes = option_html_attributes(element)
- text, value = option_text_and_value(element).map { |item| item.to_s }
+ text, value = option_text_and_value(element).map(&:to_s)
html_attributes[:selected] ||= option_value_selected?(value, selected)
html_attributes[:disabled] ||= disabled && option_value_selected?(value, disabled)
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index c0218fd55d..65a0548ffb 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -84,14 +84,13 @@ module ActionView
# * <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.
- # * <tt>:selected</tt> - Provide a default selected value. It should be of the exact type as the provided options.
# * Any other key creates standard HTML attributes for the tag.
#
# ==== Examples
# select_tag "people", options_from_collection_for_select(@people, "id", "name")
# # <select id="people" name="people"><option value="1">David</option></select>
#
- # select_tag "people", options_from_collection_for_select(@people, "id", "name"), selected: ["1", "David"]
+ # select_tag "people", options_from_collection_for_select(@people, "id", "name", "1")
# # <select id="people" name="people"><option value="1" selected="selected">David</option></select>
#
# select_tag "people", "<option>David</option>".html_safe
@@ -140,7 +139,9 @@ module ActionView
include_blank = ''
end
- option_tags = content_tag(:option, include_blank, value: '').safe_concat(option_tags)
+ if include_blank
+ option_tags = content_tag(:option, include_blank, value: '').safe_concat(option_tags)
+ end
end
if prompt = options.delete(:prompt)
@@ -775,10 +776,10 @@ module ActionView
# # => <input id="quantity" name="quantity" min="1" max="9" type="number" />
#
# number_field_tag 'quantity', nil, min: 1, max: 10
- # # => <input id="quantity" name="quantity" min="1" max="9" type="number" />
+ # # => <input id="quantity" name="quantity" min="1" max="10" type="number" />
#
# number_field_tag 'quantity', nil, min: 1, max: 10, step: 2
- # # => <input id="quantity" name="quantity" min="1" max="9" step="2" type="number" />
+ # # => <input id="quantity" name="quantity" min="1" max="10" step="2" type="number" />
#
# number_field_tag 'quantity', '1', class: 'special_input', disabled: true
# # => <input disabled="disabled" class="special_input" id="quantity" name="quantity" type="number" value="1" />
diff --git a/actionview/lib/action_view/helpers/javascript_helper.rb b/actionview/lib/action_view/helpers/javascript_helper.rb
index 629c447f3f..e237a32cb7 100644
--- a/actionview/lib/action_view/helpers/javascript_helper.rb
+++ b/actionview/lib/action_view/helpers/javascript_helper.rb
@@ -21,7 +21,7 @@ module ActionView
# Also available through the alias j(). This is particularly helpful in JavaScript
# responses, like:
#
- # $('some_element').replaceWith('<%=j render 'some/element_template' %>');
+ # $('some_element').replaceWith('<%= j render 'some/element_template' %>');
def escape_javascript(javascript)
if javascript
result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) {|match| JS_ESCAPE_MAP[match] }
diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb
index f66dbfe7d3..ca8d30e4ef 100644
--- a/actionview/lib/action_view/helpers/number_helper.rb
+++ b/actionview/lib/action_view/helpers/number_helper.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/string/output_safety'
@@ -117,8 +116,8 @@ module ActionView
# (defaults to current locale).
# * <tt>:precision</tt> - Sets the precision of the number
# (defaults to 3).
- # * <tt>:significant</tt> - If +true+, precision will be the #
- # of significant_digits. If +false+, the # of fractional
+ # * <tt>:significant</tt> - If +true+, precision will be the number
+ # of significant_digits. If +false+, the number of fractional
# digits (defaults to +false+).
# * <tt>:separator</tt> - Sets the separator between the
# fractional and integer digits (defaults to ".").
@@ -192,8 +191,8 @@ module ActionView
# (defaults to current locale).
# * <tt>:precision</tt> - Sets the precision of the number
# (defaults to 3).
- # * <tt>:significant</tt> - If +true+, precision will be the #
- # of significant_digits. If +false+, the # of fractional
+ # * <tt>:significant</tt> - If +true+, precision will be the number
+ # of significant_digits. If +false+, the number of fractional
# digits (defaults to +false+).
# * <tt>:separator</tt> - Sets the separator between the
# fractional and integer digits (defaults to ".").
@@ -240,8 +239,8 @@ module ActionView
# (defaults to current locale).
# * <tt>:precision</tt> - Sets the precision of the number
# (defaults to 3).
- # * <tt>:significant</tt> - If +true+, precision will be the #
- # of significant_digits. If +false+, the # of fractional
+ # * <tt>:significant</tt> - If +true+, precision will be the number
+ # of significant_digits. If +false+, the number of fractional
# digits (defaults to +true+)
# * <tt>:separator</tt> - Sets the separator between the
# fractional and integer digits (defaults to ".").
@@ -292,8 +291,8 @@ module ActionView
# (defaults to current locale).
# * <tt>:precision</tt> - Sets the precision of the number
# (defaults to 3).
- # * <tt>:significant</tt> - If +true+, precision will be the #
- # of significant_digits. If +false+, the # of fractional
+ # * <tt>:significant</tt> - If +true+, precision will be the number
+ # of significant_digits. If +false+, the number of fractional
# digits (defaults to +true+)
# * <tt>:separator</tt> - Sets the separator between the
# fractional and integer digits (defaults to ".").
diff --git a/actionview/lib/action_view/helpers/record_tag_helper.rb b/actionview/lib/action_view/helpers/record_tag_helper.rb
index 77c3e6d394..f7ee573035 100644
--- a/actionview/lib/action_view/helpers/record_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/record_tag_helper.rb
@@ -1,108 +1,21 @@
-require 'action_view/record_identifier'
-
module ActionView
- # = Action View Record Tag Helpers
module Helpers
module RecordTagHelper
- include ActionView::RecordIdentifier
-
- # Produces a wrapper DIV element with id and class parameters that
- # relate to the specified Active Record object. Usage example:
- #
- # <%= div_for(@person, class: "foo") do %>
- # <%= @person.name %>
- # <% end %>
- #
- # produces:
- #
- # <div id="person_123" class="person foo"> Joe Bloggs </div>
- #
- # You can also pass an array of Active Record objects, which will then
- # get iterated over and yield each record as an argument for the block.
- # For example:
- #
- # <%= div_for(@people, class: "foo") do |person| %>
- # <%= person.name %>
- # <% end %>
- #
- # produces:
- #
- # <div id="person_123" class="person foo"> Joe Bloggs </div>
- # <div id="person_124" class="person foo"> Jane Bloggs </div>
- #
- def div_for(record, *args, &block)
- content_tag_for(:div, record, *args, &block)
+ def div_for(*)
+ raise NoMethodError, "The `div_for` method has been removed from " \
+ "Rails. To continue using it, add the `record_tag_helper` gem to " \
+ "your Gemfile:\n" \
+ " gem 'record_tag_helper', '~> 1.0'\n" \
+ "Consult the Rails upgrade guide for details."
end
- # content_tag_for creates an HTML element with id and class parameters
- # that relate to the specified Active Record object. For example:
- #
- # <%= content_tag_for(:tr, @person) do %>
- # <td><%= @person.first_name %></td>
- # <td><%= @person.last_name %></td>
- # <% end %>
- #
- # would produce the following HTML (assuming @person is an instance of
- # a Person object, with an id value of 123):
- #
- # <tr id="person_123" class="person">....</tr>
- #
- # If you require the HTML id attribute to have a prefix, you can specify it:
- #
- # <%= content_tag_for(:tr, @person, :foo) do %> ...
- #
- # produces:
- #
- # <tr id="foo_person_123" class="person">...
- #
- # You can also pass an array of objects which this method will loop through
- # and yield the current object to the supplied block, reducing the need for
- # having to iterate through the object (using <tt>each</tt>) beforehand.
- # For example (assuming @people is an array of Person objects):
- #
- # <%= content_tag_for(:tr, @people) do |person| %>
- # <td><%= person.first_name %></td>
- # <td><%= person.last_name %></td>
- # <% end %>
- #
- # produces:
- #
- # <tr id="person_123" class="person">...</tr>
- # <tr id="person_124" class="person">...</tr>
- #
- # content_tag_for also accepts a hash of options, which will be converted to
- # additional HTML attributes. If you specify a <tt>:class</tt> value, it will be combined
- # with the default class name for your object. For example:
- #
- # <%= content_tag_for(:li, @person, class: "bar") %>...
- #
- # produces:
- #
- # <li id="person_123" class="person bar">...
- #
- def content_tag_for(tag_name, single_or_multiple_records, prefix = nil, options = nil, &block)
- options, prefix = prefix, nil if prefix.is_a?(Hash)
-
- Array(single_or_multiple_records).map do |single_record|
- content_tag_for_single_record(tag_name, single_record, prefix, options, &block)
- end.join("\n").html_safe
+ def content_tag_for(*)
+ raise NoMethodError, "The `content_tag_for` method has been removed from " \
+ "Rails. To continue using it, add the `record_tag_helper` gem to " \
+ "your Gemfile:\n" \
+ " gem 'record_tag_helper', '~> 1.0'\n" \
+ "Consult the Rails upgrade guide for details."
end
-
- private
-
- # Called by <tt>content_tag_for</tt> internally to render a content tag
- # for each record.
- def content_tag_for_single_record(tag_name, record, prefix, options, &block)
- options = options ? options.dup : {}
- options[:class] = [ dom_class(record, prefix), options[:class] ].compact
- options[:id] = dom_id(record, prefix)
-
- if block_given?
- content_tag(tag_name, capture(record, &block), options)
- else
- content_tag(tag_name, "", options)
- end
- end
end
end
end
diff --git a/actionview/lib/action_view/helpers/rendering_helper.rb b/actionview/lib/action_view/helpers/rendering_helper.rb
index e11670e00d..827932d8e2 100644
--- a/actionview/lib/action_view/helpers/rendering_helper.rb
+++ b/actionview/lib/action_view/helpers/rendering_helper.rb
@@ -32,7 +32,7 @@ module ActionView
view_renderer.render(self, options)
end
else
- view_renderer.render_partial(self, :partial => options, :locals => locals)
+ view_renderer.render_partial(self, :partial => options, :locals => locals, &block)
end
end
diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb
index 7cb55cc214..a2e9f37453 100644
--- a/actionview/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionview/lib/action_view/helpers/sanitize_helper.rb
@@ -1,5 +1,4 @@
require 'active_support/core_ext/object/try'
-require 'active_support/deprecation'
require 'rails-html-sanitizer'
module ActionView
@@ -9,76 +8,77 @@ module ActionView
# These helper methods extend Action View making them callable within your template files.
module SanitizeHelper
extend ActiveSupport::Concern
- # This +sanitize+ helper will HTML encode all tags and strip all attributes that
- # aren't specifically allowed.
+ # Sanitizes HTML input, stripping all tags and attributes that aren't whitelisted.
#
- # It also strips href/src tags with invalid protocols, like javascript: especially.
- # It does its best to counter any tricks that hackers may use, like throwing in
- # unicode/ascii/hex values to get past the javascript: filters. Check out
- # the extensive test suite.
+ # It also strips href/src attributes with unsafe protocols like
+ # <tt>javascript:</tt>, while also protecting against attempts to use Unicode,
+ # ASCII, and hex character references to work around these protocol filters.
#
- # <%= sanitize @article.body %>
+ # The default sanitizer is Rails::Html::WhiteListSanitizer. See {Rails HTML
+ # Sanitizers}[https://github.com/rails/rails-html-sanitizer] for more information.
#
- # You can add or remove tags/attributes if you want to customize it a bit.
- # See ActionView::Base for full docs on the available options. You can add
- # tags/attributes for single uses of +sanitize+ by passing either the
- # <tt>:attributes</tt> or <tt>:tags</tt> options:
+ # Custom sanitization rules can also be provided.
#
- # Normal Use
- #
- # <%= sanitize @article.body %>
+ # Please note that sanitizing user-provided text does not guarantee that the
+ # resulting markup is valid or even well-formed. For example, the output may still
+ # contain unescaped characters like <tt><</tt>, <tt>></tt>, or <tt>&</tt>.
#
- # Custom Use - Custom Scrubber
- # (supply a Loofah::Scrubber that does the sanitization)
+ # ==== Options
#
- # scrubber can either wrap a block:
- # scrubber = Loofah::Scrubber.new do |node|
- # node.text = "dawn of cats"
- # end
+ # * <tt>:tags</tt> - An array of allowed tags.
+ # * <tt>:attributes</tt> - An array of allowed attributes.
+ # * <tt>:scrubber</tt> - A {Rails::Html scrubber}[https://github.com/rails/rails-html-sanitizer]
+ # or {Loofah::Scrubber}[https://github.com/flavorjones/loofah] object that
+ # defines custom sanitization rules. A custom scrubber takes precedence over
+ # custom tags and attributes.
#
- # or be a subclass of Loofah::Scrubber which responds to scrub:
- # class KittyApocalypse < Loofah::Scrubber
- # def scrub(node)
- # node.text = "dawn of cats"
- # end
- # end
- # scrubber = KittyApocalypse.new
+ # ==== Examples
#
- # <%= sanitize @article.body, scrubber: scrubber %>
+ # Normal use:
#
- # A custom scrubber takes precedence over custom tags and attributes
- # Learn more about scrubbers here: https://github.com/flavorjones/loofah
+ # <%= sanitize @comment.body %>
#
- # Custom Use - tags and attributes
- # (only the mentioned tags and attributes are allowed, nothing else)
+ # Providing custom whitelisted tags and attributes:
#
- # <%= sanitize @article.body, tags: %w(table tr td), attributes: %w(id class style) %>
+ # <%= sanitize @comment.body, tags: %w(strong em a), attributes: %w(href) %>
#
- # Add table tags to the default allowed tags
+ # Providing a custom Rails::Html scrubber:
#
- # class Application < Rails::Application
- # config.action_view.sanitized_allowed_tags = ['table', 'tr', 'td']
- # end
+ # class CommentScrubber < Rails::Html::PermitScrubber
+ # def allowed_node?(node)
+ # !%w(form script comment blockquote).include?(node.name)
+ # end
#
- # Remove tags to the default allowed tags
+ # def skip_node?(node)
+ # node.text?
+ # end
#
- # class Application < Rails::Application
- # config.after_initialize do
- # ActionView::Base.sanitized_allowed_tags.delete 'div'
+ # def scrub_attribute?(name)
+ # name == 'style'
# end
# end
#
- # Change allowed default attributes
+ # <%= sanitize @comment.body, scrubber: CommentScrubber.new %>
+ #
+ # See {Rails HTML Sanitizer}[https://github.com/rails/rails-html-sanitizer] for
+ # documentation about Rails::Html scrubbers.
#
- # class Application < Rails::Application
- # config.action_view.sanitized_allowed_attributes = ['id', 'class', 'style']
+ # Providing a custom Loofah::Scrubber:
+ #
+ # scrubber = Loofah::Scrubber.new do |node|
+ # node.remove if node.name == 'script'
# end
#
- # Please note that sanitizing user-provided text does not guarantee that the
- # resulting markup is valid (conforming to a document type) or even well-formed.
- # The output may still contain e.g. unescaped '<', '>', '&' characters and
- # confuse browsers.
+ # <%= sanitize @comment.body, scrubber: scrubber %>
+ #
+ # See {Loofah's documentation}[https://github.com/flavorjones/loofah] for more
+ # information about defining custom Loofah::Scrubber objects.
#
+ # To set the default allowed tags or attributes across your application:
+ #
+ # # In config/application.rb
+ # config.action_view.sanitized_allowed_tags = ['strong', 'em', 'a']
+ # config.action_view.sanitized_allowed_attributes = ['href', 'title']
def sanitize(html, options = {})
self.class.white_list_sanitizer.sanitize(html, options).try(:html_safe)
end
@@ -88,9 +88,7 @@ module ActionView
self.class.white_list_sanitizer.sanitize_css(style)
end
- # Strips all HTML tags from the +html+, including comments. This uses
- # Nokogiri for tokenization (via Loofah) and so its HTML parsing ability
- # is limited by that of Nokogiri.
+ # Strips all HTML tags from +html+, including comments.
#
# strip_tags("Strip <i>these</i> tags!")
# # => Strip these tags!
@@ -101,10 +99,10 @@ module ActionView
# strip_tags("<div id='top-bar'>Welcome to my website!</div>")
# # => Welcome to my website!
def strip_tags(html)
- self.class.full_sanitizer.sanitize(html)
+ self.class.full_sanitizer.sanitize(html, encode_special_chars: false)
end
- # Strips all link tags from +text+ leaving just the link text.
+ # Strips all link tags from +html+ leaving just the link text.
#
# strip_links('<a href="http://www.rubyonrails.org">Ruby on Rails</a>')
# # => Ruby on Rails
@@ -167,30 +165,6 @@ module ActionView
def white_list_sanitizer
@white_list_sanitizer ||= sanitizer_vendor.white_list_sanitizer.new
end
-
- ##
- # :method: sanitized_allowed_tags=
- #
- # :call-seq: sanitized_allowed_tags=(tags)
- #
- # Replaces the allowed tags for the +sanitize+ helper.
- #
- # class Application < Rails::Application
- # config.action_view.sanitized_allowed_tags = ['table', 'tr', 'td']
- # end
- #
-
- ##
- # :method: sanitized_allowed_attributes=
- #
- # :call-seq: sanitized_allowed_attributes=(attributes)
- #
- # Replaces the allowed HTML attributes for the +sanitize+ helper.
- #
- # class Application < Rails::Application
- # config.action_view.sanitized_allowed_attributes = ['onclick', 'longdesc']
- # end
- #
end
end
end
diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb
index b2038576a2..a87c223a71 100644
--- a/actionview/lib/action_view/helpers/tag_helper.rb
+++ b/actionview/lib/action_view/helpers/tag_helper.rb
@@ -18,7 +18,7 @@ module ActionView
itemscope allowfullscreen default inert sortable
truespeed typemustmatch).to_set
- BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map {|attribute| attribute.to_sym })
+ BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym))
TAG_PREFIXES = ['aria', 'data', :aria, :data].to_set
diff --git a/actionview/lib/action_view/helpers/tags.rb b/actionview/lib/action_view/helpers/tags.rb
index 45c75d10c0..a4f6eb0150 100644
--- a/actionview/lib/action_view/helpers/tags.rb
+++ b/actionview/lib/action_view/helpers/tags.rb
@@ -5,6 +5,7 @@ module ActionView
eager_autoload do
autoload :Base
+ autoload :Translator
autoload :CheckBox
autoload :CollectionCheckBoxes
autoload :CollectionRadioButtons
diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb
index f8abb19698..acc6443a96 100644
--- a/actionview/lib/action_view/helpers/tags/base.rb
+++ b/actionview/lib/action_view/helpers/tags/base.rb
@@ -14,7 +14,7 @@ module ActionView
@object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
@object = retrieve_object(options.delete(:object))
@options = options
- @auto_index = retrieve_autoindex(Regexp.last_match.pre_match) if Regexp.last_match
+ @auto_index = Regexp.last_match ? retrieve_autoindex(Regexp.last_match.pre_match) : nil
end
# This is what child classes implement.
@@ -32,12 +32,19 @@ module ActionView
unless object.nil?
method_before_type_cast = @method_name + "_before_type_cast"
- object.respond_to?(method_before_type_cast) ?
- object.send(method_before_type_cast) :
+ if value_came_from_user?(object) && object.respond_to?(method_before_type_cast)
+ object.public_send(method_before_type_cast)
+ else
value(object)
+ end
end
end
+ def value_came_from_user?(object)
+ method_name = "#{@method_name}_came_from_user?"
+ !object.respond_to?(method_name) || object.public_send(method_name)
+ end
+
def retrieve_object(object)
if object
object
@@ -72,35 +79,30 @@ module ActionView
end
def add_default_name_and_id(options)
- if options.has_key?("index")
- options["name"] ||= options.fetch("name"){ tag_name_with_index(options["index"], options["multiple"]) }
- options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) }
- options.delete("index")
- elsif defined?(@auto_index)
- options["name"] ||= options.fetch("name"){ tag_name_with_index(@auto_index, options["multiple"]) }
- options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) }
- else
- options["name"] ||= options.fetch("name"){ tag_name(options["multiple"]) }
- options["id"] = options.fetch("id"){ tag_id }
+ index = name_and_id_index(options)
+ options["name"] = options.fetch("name"){ tag_name(options["multiple"], index) }
+ options["id"] = options.fetch("id"){ tag_id(index) }
+ if namespace = options.delete("namespace")
+ options['id'] = options['id'] ? "#{namespace}_#{options['id']}" : namespace
end
-
- options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence
- end
-
- def tag_name(multiple = false)
- "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}"
end
- def tag_name_with_index(index, multiple = false)
- "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}"
- end
-
- def tag_id
- "#{sanitized_object_name}_#{sanitized_method_name}"
+ def tag_name(multiple = false, index = nil)
+ # a little duplication to construct less strings
+ if index
+ "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}"
+ else
+ "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}"
+ end
end
- def tag_id_with_index(index)
- "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
+ def tag_id(index = nil)
+ # a little duplication to construct less strings
+ if index
+ "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
+ else
+ "#{sanitized_object_name}_#{sanitized_method_name}"
+ end
end
def sanitized_object_name
@@ -142,6 +144,10 @@ module ActionView
end
option_tags
end
+
+ def name_and_id_index(options)
+ options.key?("index") ? options.delete("index") || "" : @auto_index
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
index 6242a2a085..1765fa6558 100644
--- a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
@@ -41,14 +41,7 @@ module ActionView
end
def hidden_field
- hidden_name = @html_options[:name]
-
- hidden_name ||= if @options.has_key?(:index)
- "#{tag_name_with_index(@options[:index])}[]"
- else
- "#{tag_name}[]"
- end
-
+ hidden_name = @html_options[:name] || "#{tag_name(false, @options[:index])}[]"
@template_object.hidden_field_tag(hidden_name, "", id: nil)
end
end
diff --git a/actionview/lib/action_view/helpers/tags/file_field.rb b/actionview/lib/action_view/helpers/tags/file_field.rb
index 476b820d84..e6a1d9c62d 100644
--- a/actionview/lib/action_view/helpers/tags/file_field.rb
+++ b/actionview/lib/action_view/helpers/tags/file_field.rb
@@ -2,6 +2,21 @@ module ActionView
module Helpers
module Tags # :nodoc:
class FileField < TextField # :nodoc:
+
+ def render
+ options = @options.stringify_keys
+
+ if options.fetch("include_hidden", true)
+ add_default_name_and_id(options)
+ options[:type] = "file"
+ tag("input", name: options["name"], type: "hidden", value: "") + tag("input", options)
+ else
+ options.delete("include_hidden")
+ @options = options
+
+ super
+ end
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/label.rb b/actionview/lib/action_view/helpers/tags/label.rb
index 08a23e497e..b31d5fda66 100644
--- a/actionview/lib/action_view/helpers/tags/label.rb
+++ b/actionview/lib/action_view/helpers/tags/label.rb
@@ -15,20 +15,10 @@ module ActionView
def translation
method_and_value = @tag_value.present? ? "#{@method_name}.#{@tag_value}" : @method_name
- @object_name.gsub!(/\[(.*)_attributes\]\[\d+\]/, '.\1')
-
- if object.respond_to?(:to_model)
- key = object.model_name.i18n_key
- i18n_default = ["#{key}.#{method_and_value}".to_sym, ""]
- end
-
- i18n_default ||= ""
- content = I18n.t("#{@object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence
-
- content ||= if object && object.class.respond_to?(:human_attribute_name)
- object.class.human_attribute_name(method_and_value)
- end
+ content ||= Translator
+ .new(object, @object_name, method_and_value, scope: "helpers.label")
+ .translate
content ||= @method_name.humanize
content
diff --git a/actionview/lib/action_view/helpers/tags/placeholderable.rb b/actionview/lib/action_view/helpers/tags/placeholderable.rb
index ae67bc13af..cf7b117614 100644
--- a/actionview/lib/action_view/helpers/tags/placeholderable.rb
+++ b/actionview/lib/action_view/helpers/tags/placeholderable.rb
@@ -7,24 +7,12 @@ module ActionView
if tag_value = @options[:placeholder]
placeholder = tag_value if tag_value.is_a?(String)
-
- object_name = @object_name.gsub(/\[(.*)_attributes\]\[\d+\]/, '.\1')
method_and_value = tag_value.is_a?(TrueClass) ? @method_name : "#{@method_name}.#{tag_value}"
- if object.respond_to?(:to_model)
- key = object.class.model_name.i18n_key
- i18n_default = ["#{key}.#{method_and_value}".to_sym, ""]
- end
-
- i18n_default ||= ""
- placeholder ||= I18n.t("#{object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.placeholder").presence
-
- placeholder ||= if object && object.class.respond_to?(:human_attribute_name)
- object.class.human_attribute_name(method_and_value)
- end
-
+ placeholder ||= Tags::Translator
+ .new(object, @object_name, method_and_value, scope: "helpers.placeholder")
+ .translate
placeholder ||= @method_name.humanize
-
@options[:placeholder] = placeholder
end
end
diff --git a/actionview/lib/action_view/helpers/tags/search_field.rb b/actionview/lib/action_view/helpers/tags/search_field.rb
index c09e2f1be7..a848aeabfa 100644
--- a/actionview/lib/action_view/helpers/tags/search_field.rb
+++ b/actionview/lib/action_view/helpers/tags/search_field.rb
@@ -16,6 +16,7 @@ module ActionView
options["incremental"] = true unless options.has_key?("incremental")
end
+ @options = options
super
end
end
diff --git a/actionview/lib/action_view/helpers/tags/translator.rb b/actionview/lib/action_view/helpers/tags/translator.rb
new file mode 100644
index 0000000000..8b6655481d
--- /dev/null
+++ b/actionview/lib/action_view/helpers/tags/translator.rb
@@ -0,0 +1,40 @@
+module ActionView
+ module Helpers
+ module Tags # :nodoc:
+ class Translator # :nodoc:
+ def initialize(object, object_name, method_and_value, scope:)
+ @object_name = object_name.gsub(/\[(.*)_attributes\]\[\d+\]/, '.\1')
+ @method_and_value = method_and_value
+ @scope = scope
+ @model = object.respond_to?(:to_model) ? object.to_model : nil
+ end
+
+ def translate
+ translated_attribute = I18n.t("#{object_name}.#{method_and_value}", default: i18n_default, scope: scope).presence
+ translated_attribute || human_attribute_name
+ end
+
+ protected
+
+ attr_reader :object_name, :method_and_value, :scope, :model
+
+ private
+
+ def i18n_default
+ if model
+ key = model.model_name.i18n_key
+ ["#{key}.#{method_and_value}".to_sym, ""]
+ else
+ ""
+ end
+ end
+
+ def human_attribute_name
+ if model && model.class.respond_to?(:human_attribute_name)
+ model.class.human_attribute_name(method_and_value)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb
index a9f1631586..c216d4401f 100644
--- a/actionview/lib/action_view/helpers/text_helper.rb
+++ b/actionview/lib/action_view/helpers/text_helper.rb
@@ -103,7 +103,9 @@ module ActionView
# Highlights one or more +phrases+ everywhere in +text+ by inserting it into
# a <tt>:highlighter</tt> string. The highlighter can be specialized by passing <tt>:highlighter</tt>
# as a single-quoted string with <tt>\1</tt> where the phrase is to be inserted (defaults to
- # '<mark>\1</mark>') or passing a block that receives each matched term.
+ # '<mark>\1</mark>') or passing a block that receives each matched term. By default +text+
+ # is sanitized to prevent possible XSS attacks. If the input is trustworthy, passing false
+ # for <tt>:sanitize</tt> will turn sanitizing off.
#
# highlight('You searched for: rails', 'rails')
# # => You searched for: <mark>rails</mark>
@@ -122,6 +124,9 @@ module ActionView
#
# highlight('You searched for: rails', 'rails') { |match| link_to(search_path(q: match, match)) }
# # => You searched for: <a href="search?q=rails">rails</a>
+ #
+ # highlight('<a href="javascript:alert(\'no!\')">ruby</a> on rails', 'rails', sanitize: false)
+ # # => "<a>ruby</a> on <mark>rails</mark>"
def highlight(text, phrases, options = {})
text = sanitize(text) if options.fetch(:sanitize, true)
@@ -309,7 +314,7 @@ module ActionView
# <table>
# <% @items.each do |item| %>
# <tr class="<%= cycle("odd", "even") -%>">
- # <td>item</td>
+ # <td><%= item %></td>
# </tr>
# <% end %>
# </table>
diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb
index c2fda42396..9d7390f1fd 100644
--- a/actionview/lib/action_view/helpers/translation_helper.rb
+++ b/actionview/lib/action_view/helpers/translation_helper.rb
@@ -37,14 +37,21 @@ module ActionView
# you know what kind of output to expect when you call translate in a template.
def translate(key, options = {})
options = options.dup
- options[:default] = wrap_translate_defaults(options[:default]) if options[:default]
+ has_default = options.has_key?(:default)
+ remaining_defaults = Array(options.delete(:default)).compact
- # If the user has specified rescue_format then pass it all through, otherwise use
- # raise and do the work ourselves
- options[:raise] ||= ActionView::Base.raise_on_missing_translations
+ if has_default && !remaining_defaults.first.kind_of?(Symbol)
+ options[:default] = remaining_defaults
+ end
- raise_error = options[:raise] || options.key?(:rescue_format)
- unless raise_error
+ # 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?)
+ raise_error = false
+ options[:raise] = false
+ else
+ raise_error = options[:raise] || options[:rescue_format] || ActionView::Base.raise_on_missing_translations
options[:raise] = true
end
@@ -62,10 +69,14 @@ module ActionView
I18n.translate(scope_key_by_partial(key), options)
end
rescue I18n::MissingTranslationData => e
- raise e if raise_error
+ if remaining_defaults.present?
+ translate remaining_defaults.shift, options.merge(default: remaining_defaults)
+ else
+ raise e if raise_error
- keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
- content_tag('span', keys.last.to_s.titleize, :class => 'translation_missing', :title => "translation missing: #{keys.join('.')}")
+ keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
+ content_tag('span', keys.last.to_s.titleize, :class => 'translation_missing', :title => "translation missing: #{keys.join('.')}")
+ end
end
alias :t :translate
@@ -94,21 +105,6 @@ module ActionView
def html_safe_translation_key?(key)
key.to_s =~ /(\b|_|\.)html$/
end
-
- def wrap_translate_defaults(defaults)
- new_defaults = []
- defaults = Array(defaults)
- while key = defaults.shift
- if key.is_a?(Symbol)
- new_defaults << lambda { |_, options| translate key, options.merge(:default => defaults) }
- break
- else
- new_defaults << key
- end
- end
-
- new_defaults
- end
end
end
end
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index 364414da05..8843a89362 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -46,9 +46,9 @@ module ActionView
end
protected :_back_url
- # Creates a link tag of the given +name+ using a URL created by the set of +options+.
+ # Creates an anchor element of the given +name+ using a URL created by the set of +options+.
# See the valid options in the documentation for +url_for+. It's also possible to
- # pass a String instead of an options hash, which generates a link tag that uses the
+ # pass a String instead of an options hash, which generates an anchor element that uses the
# value of the String as the href for the link. Using a <tt>:back</tt> Symbol instead
# of an options hash will generate a link to the referrer (a JavaScript back link
# will be used in place of a referrer if none exists). If +nil+ is passed as the name
@@ -172,6 +172,11 @@ module ActionView
#
# link_to "Visit Other Site", "http://www.rubyonrails.org/", data: { confirm: "Are you sure?" }
# # => <a href="http://www.rubyonrails.org/" data-confirm="Are you sure?">Visit Other Site</a>
+ #
+ # Also you can set any link attributes such as <tt>target</tt>, <tt>rel</tt>, <tt>type</tt>:
+ #
+ # link_to "External link", "http://www.rubyonrails.org/", target: "_blank", rel: "nofollow"
+ # # => <a href="http://www.rubyonrails.org/" target="_blank" rel="nofollow">External link</a>
def link_to(name = nil, options = nil, html_options = nil, &block)
html_options, options, name = options, name, block if block_given?
options ||= {}
@@ -280,9 +285,7 @@ module ActionView
html_options, options = options, name if block_given?
options ||= {}
html_options ||= {}
-
html_options = html_options.stringify_keys
- convert_boolean_attributes!(html_options, %w(disabled))
url = options.is_a?(String) ? options : url_for(options)
remote = html_options.delete('remote')
@@ -294,8 +297,9 @@ module ActionView
form_method = method == 'get' ? 'get' : 'post'
form_options = html_options.delete('form') || {}
form_options[:class] ||= html_options.delete('form_class') || 'button_to'
- form_options.merge!(method: form_method, action: url)
- form_options.merge!("data-remote" => "true") if remote
+ form_options[:method] = form_method
+ form_options[:action] = url
+ form_options[:'data-remote'] = true if remote
request_token_tag = form_method == 'post' ? token_tag : ''
@@ -428,6 +432,7 @@ module ActionView
# * <tt>:body</tt> - Preset the body of the email.
# * <tt>:cc</tt> - Carbon Copy additional recipients on the email.
# * <tt>:bcc</tt> - Blind Carbon Copy additional recipients on the email.
+ # * <tt>:reply_to</tt> - Preset the Reply-To field of the email.
#
# ==== Obfuscation
# Prior to Rails 4.0, +mail_to+ provided options for encoding the address
@@ -457,9 +462,9 @@ module ActionView
html_options, name = name, nil if block_given?
html_options = (html_options || {}).stringify_keys
- extras = %w{ cc bcc body subject }.map! { |item|
- option = html_options.delete(item) || next
- "#{item}=#{Rack::Utils.escape_path(option)}"
+ extras = %w{ cc bcc body subject reply_to }.map! { |item|
+ option = html_options.delete(item).presence || next
+ "#{item.dasherize}=#{Rack::Utils.escape_path(option)}"
}.compact
extras = extras.empty? ? '' : '?' + extras.join('&')
@@ -575,34 +580,6 @@ module ActionView
html_options["data-method"] = method
end
- # Processes the +html_options+ hash, converting the boolean
- # attributes from true/false form into the form required by
- # HTML/XHTML. (An attribute is considered to be boolean if
- # its name is listed in the given +bool_attrs+ array.)
- #
- # More specifically, for each boolean attribute in +html_options+
- # given as:
- #
- # "attr" => bool_value
- #
- # if the associated +bool_value+ evaluates to true, it is
- # replaced with the attribute's name; otherwise the attribute is
- # removed from the +html_options+ hash. (See the XHTML 1.0 spec,
- # section 4.5 "Attribute Minimization" for more:
- # http://www.w3.org/TR/xhtml1/#h-4.5)
- #
- # Returns the updated +html_options+ hash, which is also modified
- # in place.
- #
- # Example:
- #
- # convert_boolean_attributes!( html_options,
- # %w( checked disabled readonly ) )
- def convert_boolean_attributes!(html_options, bool_attrs)
- bool_attrs.each { |x| html_options[x] = x if html_options.delete(x) }
- html_options
- end
-
def token_tag(token=nil)
if token != false && protect_against_forgery?
token ||= form_authenticity_token
diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb
index 9ee05bd816..1fc609f2cd 100644
--- a/actionview/lib/action_view/layouts.rb
+++ b/actionview/lib/action_view/layouts.rb
@@ -228,7 +228,7 @@ module ActionView
# set by the <tt>layout</tt> method.
#
# ==== Returns
- # * <tt> Boolean</tt> - True if the action has a layout definition, false otherwise.
+ # * <tt>Boolean</tt> - True if the action has a layout definition, false otherwise.
def _conditional_layout?
return unless super
@@ -262,7 +262,7 @@ module ActionView
def layout(layout, conditions = {})
include LayoutConditions unless conditions.empty?
- conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} }
+ conditions.each {|k, v| conditions[k] = Array(v).map(&:to_s) }
self._layout_conditions = conditions
self._layout = layout
@@ -315,16 +315,25 @@ module ActionView
name_clause
end
- self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
- def _layout
- if _conditional_layout?
+ if self._layout_conditions.empty?
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def _layout
#{layout_definition}
- else
- #{name_clause}
end
- end
- private :_layout
- RUBY
+ private :_layout
+ RUBY
+ else
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def _layout
+ if _conditional_layout?
+ #{layout_definition}
+ else
+ #{name_clause}
+ end
+ end
+ private :_layout
+ RUBY
+ end
end
private
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index ea687d9cca..4452dcfed5 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -126,7 +126,7 @@ module ActionView
@view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys, options))
end
- def exists?(name, prefixes = [], partial = false, keys = [], options = {})
+ def exists?(name, prefixes = [], partial = false, keys = [], **options)
@view_paths.exists?(*args_for_lookup(name, prefixes, partial, keys, options))
end
alias :template_exists? :exists?
@@ -191,7 +191,6 @@ module ActionView
def initialize(view_paths, details = {}, prefixes = [])
@details, @details_key = {}, nil
- @skip_default_locale = false
@cache = true
@prefixes = prefixes
@rendered_format = nil
@@ -213,12 +212,6 @@ module ActionView
super(values)
end
- # Do not use the default locale on template lookup.
- def skip_default_locale!
- @skip_default_locale = true
- self.locale = nil
- end
-
# Override locale to return a symbol instead of array.
def locale
@details[:locale].first
@@ -233,7 +226,7 @@ module ActionView
config.locale = value
end
- super(@skip_default_locale ? I18n.locale : default_locale)
+ super(default_locale)
end
# Uses the first format in the formats array for layout lookup.
diff --git a/actionview/lib/action_view/model_naming.rb b/actionview/lib/action_view/model_naming.rb
index d42e436b17..b6ed13424e 100644
--- a/actionview/lib/action_view/model_naming.rb
+++ b/actionview/lib/action_view/model_naming.rb
@@ -1,5 +1,5 @@
module ActionView
- module ModelNaming
+ module ModelNaming #:nodoc:
# Converts the given object to an ActiveModel compliant one.
def convert_to_model(object)
object.respond_to?(:to_model) ? object.to_model : object
diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb
index 81f9c40b85..9a26cba574 100644
--- a/actionview/lib/action_view/railtie.rb
+++ b/actionview/lib/action_view/railtie.rb
@@ -36,9 +36,15 @@ module ActionView
end
end
+ initializer "action_view.collection_caching" do |app|
+ ActiveSupport.on_load(:action_controller) do
+ PartialRenderer.collection_cache = app.config.action_controller.cache_store
+ end
+ end
+
initializer "action_view.setup_action_pack" do |app|
ActiveSupport.on_load(:action_controller) do
- ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor)
+ ActionView::RoutingUrlFor.include(ActionDispatch::Routing::UrlFor)
end
end
diff --git a/actionview/lib/action_view/record_identifier.rb b/actionview/lib/action_view/record_identifier.rb
index 63f645431a..6c6e69101b 100644
--- a/actionview/lib/action_view/record_identifier.rb
+++ b/actionview/lib/action_view/record_identifier.rb
@@ -2,29 +2,54 @@ require 'active_support/core_ext/module'
require 'action_view/model_naming'
module ActionView
- # The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or
- # pretty much any other model type that has an id. These patterns are then used to try elevate the view actions to
- # a higher logical level.
+ # RecordIdentifier encapsulates methods used by various ActionView helpers
+ # to associate records with DOM elements.
#
- # # routes
- # resources :posts
+ # Consider for example the following code that displays the body of a post:
#
- # # view
- # <%= div_for(post) do %> <div id="post_45" class="post">
- # <%= post.body %> What a wonderful world!
- # <% end %> </div>
+ # <%= div_for(post) do %>
+ # <%= post.body %>
+ # <% end %>
#
- # # controller
- # def update
- # post = Post.find(params[:id])
- # post.update(params[:post])
+ # When +post+ is a new, unsaved ActiveRecord::Base intance, the resulting HTML
+ # is:
#
- # redirect_to(post) # Calls polymorphic_url(post) which in turn calls post_url(post)
- # end
+ # <div id="new_post" class="post">
+ # </div>
+ #
+ # When +post+ is a persisted ActiveRecord::Base instance, the resulting HTML
+ # is:
+ #
+ # <div id="post_42" class="post">
+ # What a wonderful world!
+ # </div>
+ #
+ # In both cases, the +id+ and +class+ of the wrapping DOM element are
+ # automatically generated, following naming conventions encapsulated by the
+ # RecordIdentifier methods #dom_id and #dom_class:
+ #
+ # dom_id(Post.new) # => "new_post"
+ # dom_class(Post.new) # => "post"
+ # dom_id(Post.find 42) # => "post_42"
+ # dom_class(Post.find 42) # => "post"
#
- # As the example above shows, you can stop caring to a large extent what the actual id of the post is.
- # You just know that one is being assigned and that the subsequent calls in redirect_to expect that
- # same naming convention and allows you to write less code if you follow it.
+ # Note that these methods do not strictly require +Post+ to be a subclass of
+ # ActiveRecord::Base.
+ # Any +Post+ class will work as long as its instances respond to +to_key+
+ # and +model_name+, given that +model_name+ responds to +param_key+.
+ # For instance:
+ #
+ # class Post
+ # attr_accessor :to_key
+ #
+ # def model_name
+ # OpenStruct.new param_key: 'post'
+ # end
+ #
+ # def self.find(id)
+ # new.tap { |post| post.to_key = [id] }
+ # end
+ # end
module RecordIdentifier
extend self
extend ModelNaming
@@ -78,7 +103,7 @@ module ActionView
# make sure yourself that your dom ids are valid, in case you overwrite this method.
def record_key_for_dom_id(record)
key = convert_to_model(record).to_key
- key ? key.join('_') : key
+ key ? key.join(JOIN) : key
end
end
end
diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb
index 0407632435..cd151c0189 100644
--- a/actionview/lib/action_view/renderer/partial_renderer.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer.rb
@@ -1,3 +1,4 @@
+require 'action_view/renderer/partial_renderer/collection_caching'
require 'thread_safe'
module ActionView
@@ -73,7 +74,7 @@ module ActionView
#
# <%= render partial: "account", locals: { user: @buyer } %>
#
- # == Rendering a collection of partials
+ # == \Rendering a collection of partials
#
# The example of partial use describes a familiar pattern where a template needs to iterate over an array and
# render a sub template for each of the elements. This pattern has been implemented as a single method that
@@ -105,7 +106,7 @@ module ActionView
# NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also
# just keep domain objects, like Active Records, in there.
#
- # == Rendering shared partials
+ # == \Rendering shared partials
#
# Two controllers can share a set of partials and render them like this:
#
@@ -113,7 +114,7 @@ module ActionView
#
# This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from.
#
- # == Rendering objects that respond to `to_partial_path`
+ # == \Rendering objects that respond to `to_partial_path`
#
# Instead of explicitly naming the location of a partial, you can also let PartialRenderer do the work
# and pick the proper path by checking `to_partial_path` method.
@@ -127,7 +128,7 @@ module ActionView
# # <%= render partial: "posts/post", collection: @posts %>
# <%= render partial: @posts %>
#
- # == Rendering the default case
+ # == \Rendering the default case
#
# If you're not going to be using any of the options like collections or layouts, you can also use the short-hand
# defaults of render to render partials. Examples:
@@ -147,29 +148,29 @@ module ActionView
# # <%= render partial: "posts/post", collection: @posts %>
# <%= render @posts %>
#
- # == Rendering partials with layouts
+ # == \Rendering partials with layouts
#
# Partials can have their own layouts applied to them. These layouts are different than the ones that are
# specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types
# of users:
#
- # <%# app/views/users/index.html.erb &>
+ # <%# app/views/users/index.html.erb %>
# Here's the administrator:
# <%= render partial: "user", layout: "administrator", locals: { user: administrator } %>
#
# Here's the editor:
# <%= render partial: "user", layout: "editor", locals: { user: editor } %>
#
- # <%# app/views/users/_user.html.erb &>
+ # <%# app/views/users/_user.html.erb %>
# Name: <%= user.name %>
#
- # <%# app/views/users/_administrator.html.erb &>
+ # <%# app/views/users/_administrator.html.erb %>
# <div id="administrator">
# Budget: $<%= user.budget %>
# <%= yield %>
# </div>
#
- # <%# app/views/users/_editor.html.erb &>
+ # <%# app/views/users/_editor.html.erb %>
# <div id="editor">
# Deadline: <%= user.deadline %>
# <%= yield %>
@@ -232,7 +233,7 @@ module ActionView
#
# You can also apply a layout to a block within any template:
#
- # <%# app/views/users/_chief.html.erb &>
+ # <%# app/views/users/_chief.html.erb %>
# <%= render(layout: "administrator", locals: { user: chief }) do %>
# Title: <%= chief.title %>
# <% end %>
@@ -249,13 +250,13 @@ module ActionView
# If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass
# an array to layout and treat it as an enumerable.
#
- # <%# app/views/users/_user.html.erb &>
+ # <%# app/views/users/_user.html.erb %>
# <div class="user">
# Budget: $<%= user.budget %>
# <%= yield user %>
# </div>
#
- # <%# app/views/users/index.html.erb &>
+ # <%# app/views/users/index.html.erb %>
# <%= render layout: @users do |user| %>
# Title: <%= user.title %>
# <% end %>
@@ -264,14 +265,14 @@ module ActionView
#
# You can also yield multiple times in one layout and use block arguments to differentiate the sections.
#
- # <%# app/views/users/_user.html.erb &>
+ # <%# app/views/users/_user.html.erb %>
# <div class="user">
# <%= yield user, :header %>
# Budget: $<%= user.budget %>
# <%= yield user, :footer %>
# </div>
#
- # <%# app/views/users/index.html.erb &>
+ # <%# app/views/users/index.html.erb %>
# <%= render layout: @users do |user, section| %>
# <%- case section when :header -%>
# Title: <%= user.title %>
@@ -280,6 +281,8 @@ module ActionView
# <%- end -%>
# <% end %>
class PartialRenderer < AbstractRenderer
+ include CollectionCaching
+
PREFIXED_PARTIAL_NAMES = ThreadSafe::Cache.new do |h, k|
h[k] = ThreadSafe::Cache.new
end
@@ -321,8 +324,9 @@ module ActionView
spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals)
end
- result = @template ? collection_with_template : collection_without_template
- result.join(spacer).html_safe
+ cache_collection_render do
+ @template ? collection_with_template : collection_without_template
+ end.join(spacer).html_safe
end
def render_partial
@@ -366,10 +370,12 @@ module ActionView
partial = options[:partial]
if String === partial
+ @has_object = options.key?(:object)
@object = options[:object]
@collection = collection_from_options
@path = partial
else
+ @has_object = true
@object = partial
@collection = collection_from_object || collection_from_options
@@ -382,7 +388,7 @@ module ActionView
end
if as = options[:as]
- raise_invalid_identifier(as) unless as.to_s =~ /\A[a-z_]\w*\z/
+ raise_invalid_option_as(as) unless as.to_s =~ /\A[a-z_]\w*\z/
as = as.to_sym
end
@@ -506,7 +512,7 @@ module ActionView
def retrieve_template_keys
keys = @locals.keys
- keys << @variable if @object || @collection
+ keys << @variable if @has_object || @collection
if @collection
keys << @variable_counter
keys << @variable_iteration
@@ -517,7 +523,7 @@ module ActionView
def retrieve_variable(path, as)
variable = as || begin
base = path[-1] == "/" ? "" : File.basename(path)
- raise_invalid_identifier(path) unless base =~ /\A_?([a-z]\w*)(\.\w+)*\z/
+ raise_invalid_identifier(path) unless base =~ /\A_?(.*)(?:\.\w+)*\z/
$1.to_sym
end
if @collection
@@ -528,11 +534,18 @@ module ActionView
end
IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " +
- "make sure your partial name starts with a lowercase letter or underscore, " +
+ "make sure your partial name starts with underscore."
+
+ OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " +
+ "make sure it starts with lowercase letter, " +
"and is followed by any combination of letters, numbers and underscores."
def raise_invalid_identifier(path)
raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path))
end
+
+ def raise_invalid_option_as(as)
+ raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as))
+ end
end
end
diff --git a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
new file mode 100644
index 0000000000..b77c884e66
--- /dev/null
+++ b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
@@ -0,0 +1,70 @@
+require 'active_support/core_ext/object/try'
+
+module ActionView
+ module CollectionCaching # :nodoc:
+ extend ActiveSupport::Concern
+
+ included do
+ # Fallback cache store if Action View is used without Rails.
+ # Otherwise overriden in Railtie to use Rails.cache.
+ mattr_accessor(:collection_cache) { ActiveSupport::Cache::MemoryStore.new }
+ end
+
+ private
+ def cache_collection_render
+ return yield unless cache_collection?
+
+ keyed_collection = collection_by_cache_keys
+ partial_cache = collection_cache.read_multi(*keyed_collection.keys)
+
+ @collection = keyed_collection.reject { |key, _| partial_cache.key?(key) }.values
+ rendered_partials = @collection.any? ? yield.dup : []
+
+ fetch_or_cache_partial(partial_cache, order_by: keyed_collection.each_key) do
+ rendered_partials.shift
+ end
+ end
+
+ def cache_collection?
+ @options.fetch(:cache, automatic_cache_eligible?)
+ end
+
+ def automatic_cache_eligible?
+ single_template_render? && !callable_cache_key? &&
+ @template.eligible_for_collection_caching?(as: @options[:as])
+ end
+
+ def single_template_render?
+ @template # Template is only set when a collection renders one template.
+ end
+
+ def callable_cache_key?
+ @options[:cache].respond_to?(:call)
+ end
+
+ def collection_by_cache_keys
+ seed = callable_cache_key? ? @options[:cache] : ->(i) { i }
+
+ @collection.each_with_object({}) do |item, hash|
+ hash[expanded_cache_key(seed.call(item))] = item
+ end
+ end
+
+ def expanded_cache_key(key)
+ key = @view.fragment_cache_key(@view.cache_fragment_name(key))
+ key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0.
+ end
+
+ def fetch_or_cache_partial(cached_partials, order_by:)
+ cache_options = @options[:cache_options] || @locals[:cache_options] || {}
+
+ order_by.map do |key|
+ cached_partials.fetch(key) do
+ yield.tap do |rendered_partial|
+ collection_cache.write(key, rendered_partial, cache_options)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionview/lib/action_view/renderer/renderer.rb b/actionview/lib/action_view/renderer/renderer.rb
index 964b18337e..1bee35d80d 100644
--- a/actionview/lib/action_view/renderer/renderer.rb
+++ b/actionview/lib/action_view/renderer/renderer.rb
@@ -37,7 +37,7 @@ module ActionView
end
end
- # Direct accessor to template rendering.
+ # Direct access to template rendering.
def render_template(context, options) #:nodoc:
TemplateRenderer.new(@lookup_context).render(context, options)
end
diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb
index cd21d7ab47..dbb4855e39 100644
--- a/actionview/lib/action_view/renderer/template_renderer.rb
+++ b/actionview/lib/action_view/renderer/template_renderer.rb
@@ -40,7 +40,7 @@ module ActionView
find_template(options[:template], options[:prefixes], false, keys, @details)
end
else
- raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :text or :body option."
+ raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :html, :text or :body option."
end
end
diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb
index 5cbdfdf6c0..1e8e7415d1 100644
--- a/actionview/lib/action_view/rendering.rb
+++ b/actionview/lib/action_view/rendering.rb
@@ -35,13 +35,13 @@ module ActionView
module ClassMethods
def view_context_class
@view_context_class ||= begin
- include_path_helpers = supports_path?
+ supports_path = supports_path?
routes = respond_to?(:_routes) && _routes
helpers = respond_to?(:_helpers) && _helpers
Class.new(ActionView::Base) do
if routes
- include routes.url_helpers(include_path_helpers)
+ include routes.url_helpers(supports_path)
include routes.mounted_helpers
end
@@ -92,12 +92,15 @@ module ActionView
# Find and render a template based on the options given.
# :api: private
def _render_template(options) #:nodoc:
- variant = options[:variant]
+ variant = options.delete(:variant)
+ assigns = options.delete(:assigns)
+ context = view_context
+ context.assign assigns if assigns
lookup_context.rendered_format = nil if options[:formats]
lookup_context.variants = variant if variant
- view_renderer.render(view_context, options)
+ view_renderer.render(context, options)
end
# Assign the rendered format to lookup context.
diff --git a/actionview/lib/action_view/routing_url_for.rb b/actionview/lib/action_view/routing_url_for.rb
index 75febb8652..0371db07dc 100644
--- a/actionview/lib/action_view/routing_url_for.rb
+++ b/actionview/lib/action_view/routing_url_for.rb
@@ -80,21 +80,38 @@ module ActionView
when String
options
when nil
- super({:only_path => true})
+ super(only_path: _generate_paths_by_default)
when Hash
options = options.symbolize_keys
- options[:only_path] = options[:host].nil? unless options.key?(:only_path)
+ 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 Symbol
- ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_string_call self, options
when Array
- polymorphic_path(options, options.extract_options!)
- when Class
- ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_class_call self, options
+ if _generate_paths_by_default
+ polymorphic_path(options, options.extract_options!)
+ else
+ polymorphic_url(options, options.extract_options!)
+ end
else
- ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_model_call self, options
+ method = _generate_paths_by_default ? :path : :url
+ builder = ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.send(method)
+
+ case options
+ when Symbol
+ builder.handle_string_call(self, options)
+ when Class
+ builder.handle_class_call(self, options)
+ else
+ builder.handle_model_call(self, options)
+ end
end
end
@@ -113,5 +130,11 @@ module ActionView
controller.optimize_routes_generation? : super
end
protected :optimize_routes_generation?
+
+ private
+
+ def _generate_paths_by_default
+ true
+ end
end
end
diff --git a/actionview/lib/action_view/tasks/dependencies.rake b/actionview/lib/action_view/tasks/dependencies.rake
index b39f7d583b..f394c319c1 100644
--- a/actionview/lib/action_view/tasks/dependencies.rake
+++ b/actionview/lib/action_view/tasks/dependencies.rake
@@ -2,20 +2,22 @@ namespace :cache_digests do
desc 'Lookup nested dependencies for TEMPLATE (like messages/show or comments/_comment.html)'
task :nested_dependencies => :environment do
abort 'You must provide TEMPLATE for the task to run' unless ENV['TEMPLATE'].present?
- puts JSON.pretty_generate ActionView::Digestor.new(name: template_name, finder: finder).nested_dependencies
+ puts JSON.pretty_generate ActionView::Digestor.new(name: CacheDigests.template_name, finder: CacheDigests.finder).nested_dependencies
end
desc 'Lookup first-level dependencies for TEMPLATE (like messages/show or comments/_comment.html)'
task :dependencies => :environment do
abort 'You must provide TEMPLATE for the task to run' unless ENV['TEMPLATE'].present?
- puts JSON.pretty_generate ActionView::Digestor.new(name: template_name, finder: finder).dependencies
+ puts JSON.pretty_generate ActionView::Digestor.new(name: CacheDigests.template_name, finder: CacheDigests.finder).dependencies
end
- def template_name
- ENV['TEMPLATE'].split('.', 2).first
- end
+ class CacheDigests
+ def self.template_name
+ ENV['TEMPLATE'].split('.', 2).first
+ end
- def finder
- ApplicationController.new.lookup_context
+ def self.finder
+ ApplicationController.new.lookup_context
+ end
end
end
diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb
index 6b61378a1f..377ceb534a 100644
--- a/actionview/lib/action_view/template.rb
+++ b/actionview/lib/action_view/template.rb
@@ -87,6 +87,19 @@ module ActionView
# expected_encoding
# )
+ ##
+ # :method: local_assigns
+ #
+ # Returns a hash with the defined local variables.
+ #
+ # Given this sub template rendering:
+ #
+ # <%= render "shared/header", { headline: "Welcome", person: person } %>
+ #
+ # You can use +local_assigns+ in the sub templates to access the local variables:
+ #
+ # local_assigns[:headline] # => "Welcome"
+
eager_autoload do
autoload :Error
autoload :Handlers
@@ -103,7 +116,7 @@ module ActionView
# This finalizer is needed (and exactly with a proc inside another proc)
# otherwise templates leak in development.
- Finalizer = proc do |method_name, mod|
+ Finalizer = proc do |method_name, mod| # :nodoc:
proc do
mod.module_eval do
remove_possible_method method_name
@@ -117,6 +130,7 @@ module ActionView
@source = source
@identifier = identifier
@handler = handler
+ @cache_name = extract_resource_cache_call_name
@compiled = false
@original_encoding = nil
@locals = details[:locals] || []
@@ -152,6 +166,10 @@ module ActionView
@type ||= Types[@formats.first] if @formats.first
end
+ def eligible_for_collection_caching?(as: nil)
+ @cache_name == (as || inferred_cache_name).to_s
+ end
+
# Receives a view object and return a template similar to self by using @virtual_path.
#
# This method is useful if you have a template object but it does not contain its source
@@ -332,5 +350,14 @@ module ActionView
payload = { virtual_path: @virtual_path, identifier: @identifier }
ActiveSupport::Notifications.instrument("#{action}.action_view", payload, &block)
end
+
+ def extract_resource_cache_call_name
+ $1 if @handler.respond_to?(:resource_cache_call_pattern) &&
+ @source =~ @handler.resource_cache_call_pattern
+ end
+
+ def inferred_cache_name
+ @inferred_cache_name ||= @virtual_path.split('/').last.sub('_', '')
+ end
end
end
diff --git a/actionview/lib/action_view/template/error.rb b/actionview/lib/action_view/template/error.rb
index 743ef6de0a..390bce98a2 100644
--- a/actionview/lib/action_view/template/error.rb
+++ b/actionview/lib/action_view/template/error.rb
@@ -75,7 +75,7 @@ module ActionView
def sub_template_message
if @sub_templates
"Trace of template inclusion: " +
- @sub_templates.collect { |template| template.inspect }.join(", ")
+ @sub_templates.collect(&:inspect).join(", ")
else
""
end
diff --git a/actionview/lib/action_view/template/handlers.rb b/actionview/lib/action_view/template/handlers.rb
index 33bfcb458c..0105e88a49 100644
--- a/actionview/lib/action_view/template/handlers.rb
+++ b/actionview/lib/action_view/template/handlers.rb
@@ -7,9 +7,9 @@ module ActionView #:nodoc:
autoload :Raw, 'action_view/template/handlers/raw'
def self.extended(base)
- base.register_default_template_handler :erb, ERB.new
+ base.register_default_template_handler :raw, Raw.new
+ base.register_template_handler :erb, ERB.new
base.register_template_handler :builder, Builder.new
- base.register_template_handler :raw, Raw.new
base.register_template_handler :ruby, :source.to_proc
end
@@ -22,7 +22,7 @@ module ActionView #:nodoc:
# Register an object that knows how to handle template files with the given
# extensions. This can be used to implement new template types.
- # The handler must respond to `:call`, which will be passed the template
+ # The handler must respond to +:call+, which will be passed the template
# and should return the rendered template as a String.
def register_template_handler(*extensions, handler)
raise(ArgumentError, "Extension is required") if extensions.empty?
@@ -42,7 +42,7 @@ module ActionView #:nodoc:
end
def template_handler_extensions
- @@template_handlers.keys.map {|key| key.to_s }.sort
+ @@template_handlers.keys.map(&:to_s).sort
end
def registered_template_handler(extension)
diff --git a/actionview/lib/action_view/template/handlers/erb.rb b/actionview/lib/action_view/template/handlers/erb.rb
index 3c2224fbf5..88a8570706 100644
--- a/actionview/lib/action_view/template/handlers/erb.rb
+++ b/actionview/lib/action_view/template/handlers/erb.rb
@@ -35,7 +35,7 @@ module ActionView
end
end
- BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
+ BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
def add_expr_literal(src, code)
flush_newline_if_pending(src)
@@ -123,6 +123,24 @@ module ActionView
).src
end
+ # 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.
+ #
+ # <% cache notification do %> # => notification
+ #
+ # The pattern should support templates with a beginning comment:
+ #
+ # <%# Still extractable even though there's a comment %>
+ # <% cache notification do %> # => notification
+ #
+ # But fail to extract a name if a resource association is cached.
+ #
+ # <% cache notification.event do %> # => nil
+ def resource_cache_call_pattern
+ /\A(?:<%#.*%>\n?)?<% cache\(?\s*(\w+\.?)/
+ end
+
private
def valid_encoding(string, encoding)
diff --git a/actionview/lib/action_view/template/handlers/raw.rb b/actionview/lib/action_view/template/handlers/raw.rb
index 397c86014a..b08fb0870f 100644
--- a/actionview/lib/action_view/template/handlers/raw.rb
+++ b/actionview/lib/action_view/template/handlers/raw.rb
@@ -2,7 +2,7 @@ module ActionView
module Template::Handlers
class Raw
def call(template)
- escaped = template.source.gsub(/:/, '\:')
+ escaped = template.source.gsub(':'.freeze, '\:'.freeze)
'%q:' + escaped + ':;'
end
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index 8af1821f3e..955118a554 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -1,7 +1,6 @@
require "pathname"
require "active_support/core_ext/class"
require "active_support/core_ext/module/attribute_accessors"
-require 'active_support/core_ext/string/filters'
require "action_view/template"
require "thread"
require "thread_safe"
@@ -139,7 +138,7 @@ module ActionView
# resolver is fresher before returning it.
def cached(key, path_info, details, locals) #:nodoc:
name, prefix, partial = path_info
- locals = locals.map { |x| x.to_s }.sort!
+ locals = locals.map(&:to_s).sort!
if key
@cache.cache(key, name, prefix, partial, locals) do
@@ -197,24 +196,12 @@ module ActionView
}
end
- if RUBY_VERSION >= '2.2.0'
- def find_template_paths(query)
- Dir[query].reject { |filename|
- File.directory?(filename) ||
- # deals with case-insensitive file systems.
- !File.fnmatch(query, filename, File::FNM_EXTGLOB)
- }
- end
- else
- def find_template_paths(query)
- # deals with case-insensitive file systems.
- sanitizer = Hash.new { |h,dir| h[dir] = Dir["#{dir}/*"] }
-
- Dir[query].reject { |filename|
- File.directory?(filename) ||
- !sanitizer[File.dirname(filename)].include?(filename)
- }
- end
+ def find_template_paths(query)
+ Dir[query].reject { |filename|
+ File.directory?(filename) ||
+ # deals with case-insensitive file systems.
+ !File.fnmatch(query, filename, File::FNM_EXTGLOB)
+ }
end
# Helper for building query glob string based on resolver's pattern.
@@ -251,12 +238,6 @@ module ActionView
pieces.shift
extension = pieces.pop
- unless extension
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- The file #{path} did not specify a template handler. The default is
- currently ERB, but will change to RAW in the future.
- MSG
- end
handler = Template.handler_for_extension(extension)
format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last
diff --git a/actionview/lib/action_view/template/types.rb b/actionview/lib/action_view/template/types.rb
index b84e0281ae..be45fcf742 100644
--- a/actionview/lib/action_view/template/types.rb
+++ b/actionview/lib/action_view/template/types.rb
@@ -9,7 +9,7 @@ module ActionView
self.types = Set.new
def self.register(*t)
- types.merge(t.map { |type| type.to_s })
+ types.merge(t.map(&:to_s))
end
register :html, :text, :js, :css, :xml, :json
diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb
index fec980462d..06810ad14d 100644
--- a/actionview/lib/action_view/test_case.rb
+++ b/actionview/lib/action_view/test_case.rb
@@ -125,6 +125,7 @@ module ActionView
@_rendered_views ||= RenderedViewsCollection.new
end
+ # Need to experiment if this priority is the best one: rendered => output_buffer
class RenderedViewsCollection
def initialize
@rendered_views ||= Hash.new { |hash, key| hash[key] = [] }
@@ -158,7 +159,7 @@ module ActionView
# Need to experiment if this priority is the best one: rendered => output_buffer
def document_root_element
- Nokogiri::HTML::DocumentFragment.parse(@rendered.blank? ? @output_buffer : @rendered)
+ Nokogiri::HTML::Document.parse(@rendered.blank? ? @output_buffer : @rendered).root
end
def say_no_to_protect_against_forgery!
@@ -203,7 +204,7 @@ module ActionView
def view
@view ||= begin
view = @controller.view_context
- view.singleton_class.send :include, _helpers
+ view.singleton_class.include(_helpers)
view.extend(Locals)
view.rendered_views = self.rendered_views
view.output_buffer = self.output_buffer
diff --git a/actionview/lib/action_view/view_paths.rb b/actionview/lib/action_view/view_paths.rb
index 2e203a7590..492f67f45d 100644
--- a/actionview/lib/action_view/view_paths.rb
+++ b/actionview/lib/action_view/view_paths.rb
@@ -16,14 +16,9 @@ module ActionView
module ClassMethods
def _prefixes # :nodoc:
@_prefixes ||= begin
- deprecated_prefixes = handle_deprecated_parent_prefixes
- if deprecated_prefixes
- deprecated_prefixes
- else
- return local_prefixes if superclass.abstract?
-
- local_prefixes + superclass._prefixes
- end
+ return local_prefixes if superclass.abstract?
+
+ local_prefixes + superclass._prefixes
end
end
@@ -34,17 +29,6 @@ module ActionView
def local_prefixes
[controller_path]
end
-
- def handle_deprecated_parent_prefixes # TODO: remove in 4.3/5.0.
- return unless respond_to?(:parent_prefixes)
-
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Overriding `ActionController::Base::parent_prefixes` is deprecated,
- override `.local_prefixes` instead.
- MSG
-
- local_prefixes + parent_prefixes
- end
end
# The prefixes used in render "foo" shortcuts.
diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb
index 7096d588b6..4635c645d0 100644
--- a/actionview/test/abstract_unit.rb
+++ b/actionview/test/abstract_unit.rb
@@ -46,23 +46,9 @@ I18n.enforce_available_locales = false
# Register danish language for testing
I18n.backend.store_translations 'da', {}
I18n.backend.store_translations 'pt-BR', {}
-ORIGINAL_LOCALES = I18n.available_locales.map {|locale| locale.to_s }.sort
+ORIGINAL_LOCALES = I18n.available_locales.map(&:to_s).sort
FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
-FIXTURES = Pathname.new(FIXTURE_LOAD_PATH)
-
-module RackTestUtils
- def body_to_string(body)
- if body.respond_to?(:each)
- str = ""
- body.each {|s| str << s }
- str
- else
- body
- end
- end
- extend self
-end
module RenderERBUtils
def view
@@ -225,50 +211,7 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
end
end
-# Temporary base class
-class Rack::TestCase < ActionDispatch::IntegrationTest
- def self.testing(klass = nil)
- if klass
- @testing = "/#{klass.name.underscore}".sub!(/_controller$/, '')
- else
- @testing
- end
- end
-
- def get(thing, *args)
- if thing.is_a?(Symbol)
- super("#{self.class.testing}/#{thing}", *args)
- else
- super
- end
- end
-
- def assert_body(body)
- assert_equal body, Array(response.body).join
- end
-
- def assert_status(code)
- assert_equal code, response.status
- end
-
- def assert_response(body, status = 200, headers = {})
- assert_body body
- assert_status status
- headers.each do |header, value|
- assert_header header, value
- end
- end
-
- def assert_content_type(type)
- assert_equal type, response.headers["Content-Type"]
- end
-
- def assert_header(name, value)
- assert_equal value, response.headers[name]
- end
-end
-
-ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor)
+ActionView::RoutingUrlFor.include(ActionDispatch::Routing::UrlFor)
module ActionController
class Base
@@ -338,8 +281,3 @@ def jruby_skip(message = '')
end
require 'mocha/setup' # FIXME: stop using mocha
-
-# FIXME: we have tests that depend on run order, we should fix that and
-# remove this method call.
-require 'active_support/test_case'
-ActiveSupport::TestCase.test_order = :sorted
diff --git a/actionview/test/actionpack/abstract/abstract_controller_test.rb b/actionview/test/actionpack/abstract/abstract_controller_test.rb
index e653b12d32..490932fef0 100644
--- a/actionview/test/actionpack/abstract/abstract_controller_test.rb
+++ b/actionview/test/actionpack/abstract/abstract_controller_test.rb
@@ -168,7 +168,7 @@ module AbstractController
end
end
- class OverridingLocalPrefixesTest < ActiveSupport::TestCase # TODO: remove me in 5.0/4.3.
+ class OverridingLocalPrefixesTest < ActiveSupport::TestCase
test "overriding .local_prefixes adds prefix" do
@controller = OverridingLocalPrefixes.new
@controller.process(:index)
@@ -182,22 +182,6 @@ module AbstractController
end
end
- class DeprecatedParentPrefixes < OverridingLocalPrefixes
- def self.parent_prefixes
- ["abstract_controller/testing/me3"]
- end
- end
-
- class DeprecatedParentPrefixesTest < ActiveSupport::TestCase # TODO: remove me in 5.0/4.3.
- test "overriding .parent_prefixes is deprecated" do
- @controller = DeprecatedParentPrefixes.new
- assert_deprecated do
- @controller.process(:index)
- end
- assert_equal "Hello from me3/index.erb", @controller.response_body
- end
- end
-
# Test rendering with layouts
# ====
# self._layout is used when defined
diff --git a/actionview/test/actionpack/abstract/views/abstract_controller/testing/me5/index.erb b/actionview/test/actionpack/abstract/views/abstract_controller/testing/me5/index.erb
deleted file mode 100644
index 84d0b7417e..0000000000
--- a/actionview/test/actionpack/abstract/views/abstract_controller/testing/me5/index.erb
+++ /dev/null
@@ -1 +0,0 @@
-Hello from me5/index.erb \ No newline at end of file
diff --git a/actionview/test/actionpack/controller/layout_test.rb b/actionview/test/actionpack/controller/layout_test.rb
index bd345fe873..64ab125637 100644
--- a/actionview/test/actionpack/controller/layout_test.rb
+++ b/actionview/test/actionpack/controller/layout_test.rb
@@ -1,5 +1,4 @@
require 'abstract_unit'
-require 'rbconfig'
require 'active_support/core_ext/array/extract_options'
# The view_paths array must be set on Base and not LayoutTest so that LayoutTest's inherited
@@ -123,6 +122,14 @@ class PrependsViewPathController < LayoutTest
end
end
+class ParentController < LayoutTest
+ layout 'item'
+end
+
+class ChildController < ParentController
+ layout 'layout_test', only: :hello
+end
+
class OnlyLayoutController < LayoutTest
layout 'item', :only => "hello"
end
@@ -226,6 +233,12 @@ class LayoutSetInResponseTest < ActionController::TestCase
get :hello
assert_equal "layout_test.erb hello.erb", @response.body.strip
end
+
+ def test_respect_to_parent_layout
+ @controller = ChildController.new
+ get :goodbye
+ assert_template :layout => "layouts/item"
+ end
end
class SetsNonExistentLayoutFile < LayoutTest
diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb
index 563caee8a2..8b47536a18 100644
--- a/actionview/test/actionpack/controller/render_test.rb
+++ b/actionview/test/actionpack/controller/render_test.rb
@@ -31,6 +31,10 @@ class Customer < Struct.new(:name, :id)
def persisted?
id.present?
end
+
+ def cache_key
+ name.to_s
+ end
end
module Quiz
@@ -453,6 +457,10 @@ class TestController < ApplicationController
render :text => "foo"
end
+ def render_with_assigns_option
+ render inline: '<%= @hello %>', assigns: { hello: "world" }
+ end
+
def yield_content_for
render :action => "content_for", :layout => "yield"
end
@@ -857,12 +865,12 @@ class RenderTest < ActionController::TestCase
# :ported:
def test_attempt_to_access_object_method
- assert_raise(AbstractController::ActionNotFound, "No action responded to [clone]") { get :clone }
+ assert_raise(AbstractController::ActionNotFound) { get :clone }
end
# :ported:
def test_private_methods
- assert_raise(AbstractController::ActionNotFound, "No action responded to [determine_layout]") { get :determine_layout }
+ assert_raise(AbstractController::ActionNotFound) { get :determine_layout }
end
# :ported:
@@ -953,23 +961,23 @@ class RenderTest < ActionController::TestCase
end
def test_accessing_params_in_template
- get :accessing_params_in_template, :name => "David"
+ get :accessing_params_in_template, params: { name: "David" }
assert_equal "Hello: David", @response.body
end
def test_accessing_local_assigns_in_inline_template
- get :accessing_local_assigns_in_inline_template, :local_name => "Local David"
+ get :accessing_local_assigns_in_inline_template, params: { local_name: "Local David" }
assert_equal "Goodbye, Local David", @response.body
assert_equal "text/html", @response.content_type
end
def test_should_implicitly_render_html_template_from_xhr_request
- xhr :get, :render_implicit_html_template_from_xhr_request
+ get :render_implicit_html_template_from_xhr_request, xhr: true
assert_equal "XHR!\nHello HTML!", @response.body
end
def test_should_implicitly_render_js_template_without_layout
- xhr :get, :render_implicit_js_template_without_layout, :format => :js
+ get :render_implicit_js_template_without_layout, format: :js, xhr: true
assert_no_match %r{<html>}, @response.body
end
@@ -1042,7 +1050,7 @@ class RenderTest < ActionController::TestCase
end
def test_accessing_params_in_template_with_layout
- get :accessing_params_in_template_with_layout, :name => "David"
+ get :accessing_params_in_template_with_layout, params: { name: "David" }
assert_equal "<html>Hello: David</html>", @response.body
end
@@ -1102,6 +1110,11 @@ class RenderTest < ActionController::TestCase
assert_equal "world", assigns["hello"]
end
+ def test_render_text_with_assigns_option
+ get :render_with_assigns_option
+ assert_equal 'world', response.body
+ end
+
# :ported:
def test_template_with_locals
get :render_with_explicit_template_with_locals
diff --git a/actionview/test/actionpack/controller/view_paths_test.rb b/actionview/test/actionpack/controller/view_paths_test.rb
index c6e7a523b9..7fba9ff8ff 100644
--- a/actionview/test/actionpack/controller/view_paths_test.rb
+++ b/actionview/test/actionpack/controller/view_paths_test.rb
@@ -39,7 +39,7 @@ class ViewLoadPathsTest < ActionController::TestCase
def assert_paths(*paths)
controller = paths.first.is_a?(Class) ? paths.shift : @controller
- assert_equal expand(paths), controller.view_paths.map { |p| p.to_s }
+ assert_equal expand(paths), controller.view_paths.map(&:to_s)
end
def test_template_load_path_was_set_correctly
diff --git a/actionview/test/active_record_unit.rb b/actionview/test/active_record_unit.rb
index cca55c9af4..f9e94413b5 100644
--- a/actionview/test/active_record_unit.rb
+++ b/actionview/test/active_record_unit.rb
@@ -76,7 +76,7 @@ class ActiveRecordTestCase < ActionController::TestCase
# Set our fixture path
if ActiveRecordTestConnector.able_to_connect
self.fixture_path = [FIXTURE_LOAD_PATH]
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
end
def self.fixtures(*args)
diff --git a/actionview/test/activerecord/controller_runtime_test.rb b/actionview/test/activerecord/controller_runtime_test.rb
index 469adff39a..af91348d76 100644
--- a/actionview/test/activerecord/controller_runtime_test.rb
+++ b/actionview/test/activerecord/controller_runtime_test.rb
@@ -4,7 +4,7 @@ require 'fixtures/project'
require 'active_support/log_subscriber/test_helper'
require 'action_controller/log_subscriber'
-ActionController::Base.send :include, ActiveRecord::Railties::ControllerRuntime
+ActionController::Base.include(ActiveRecord::Railties::ControllerRuntime)
class ControllerRuntimeLogSubscriberTest < ActionController::TestCase
class LogSubscriberController < ActionController::Base
diff --git a/actionview/test/template/debug_helper_test.rb b/actionview/test/activerecord/debug_helper_test.rb
index 5609694cd5..03cb1d5a91 100644
--- a/actionview/test/template/debug_helper_test.rb
+++ b/actionview/test/activerecord/debug_helper_test.rb
@@ -1,8 +1,14 @@
require 'active_record_unit'
+require 'nokogiri'
class DebugHelperTest < ActionView::TestCase
def test_debug
company = Company.new(name: "firebase")
assert_match "name: firebase", debug(company)
end
+
+ def test_debug_with_marshal_error
+ obj = -> { }
+ assert_match obj.inspect, Nokogiri.XML(debug(obj)).content
+ end
end
diff --git a/actionview/test/activerecord/polymorphic_routes_test.rb b/actionview/test/activerecord/polymorphic_routes_test.rb
index 5842b775bb..34b2698c7f 100644
--- a/actionview/test/activerecord/polymorphic_routes_test.rb
+++ b/actionview/test/activerecord/polymorphic_routes_test.rb
@@ -25,15 +25,17 @@ class Series < ActiveRecord::Base
self.table_name = 'projects'
end
-class ModelDelegator < ActiveRecord::Base
- self.table_name = 'projects'
-
+class ModelDelegator
def to_model
ModelDelegate.new
end
end
class ModelDelegate
+ def persisted?
+ true
+ end
+
def model_name
ActiveModel::Name.new(self.class)
end
@@ -111,7 +113,7 @@ class PolymorphicRoutesTest < ActionController::TestCase
def test_passing_routes_proxy
with_namespaced_routes(:blog) do
- proxy = ActionDispatch::Routing::RoutesProxy.new(_routes, self)
+ proxy = ActionDispatch::Routing::RoutesProxy.new(_routes, self, _routes.url_helpers)
@blog_post.save
assert_url "http://example.com/posts/#{@blog_post.id}", [proxy, @blog_post]
end
@@ -206,7 +208,7 @@ class PolymorphicRoutesTest < ActionController::TestCase
@series.save
polymorphic_url([nil, @series])
end
- assert_match(/undefined method `series_url' for/, exception.message)
+ assert_match(/undefined method `series_url'/, exception.message)
end
end
@@ -605,13 +607,18 @@ class PolymorphicRoutesTest < ActionController::TestCase
end
end
- def test_routing_a_to_model_delegate
+ def test_routing_to_a_model_delegate
with_test_routes do
- @delegator.save
assert_url "http://example.com/model_delegates/overridden", @delegator
end
end
+ def test_nested_routing_to_a_model_delegate
+ with_test_routes do
+ assert_url "http://example.com/foo/model_delegates/overridden", [:foo, @delegator]
+ end
+ end
+
def with_namespaced_routes(name)
with_routing do |set|
set.draw do
@@ -645,6 +652,9 @@ class PolymorphicRoutesTest < ActionController::TestCase
end
resources :series
resources :model_delegates
+ namespace :foo do
+ resources :model_delegates
+ end
end
extend @routes.url_helpers
diff --git a/actionview/test/fixtures/blog_public/.gitignore b/actionview/test/fixtures/blog_public/.gitignore
deleted file mode 100644
index 312e635ee6..0000000000
--- a/actionview/test/fixtures/blog_public/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-absolute/*
diff --git a/actionview/test/fixtures/blog_public/blog.html b/actionview/test/fixtures/blog_public/blog.html
deleted file mode 100644
index 79ad44c010..0000000000
--- a/actionview/test/fixtures/blog_public/blog.html
+++ /dev/null
@@ -1 +0,0 @@
-/blog/blog.html \ No newline at end of file
diff --git a/actionview/test/fixtures/blog_public/index.html b/actionview/test/fixtures/blog_public/index.html
deleted file mode 100644
index 2de3825481..0000000000
--- a/actionview/test/fixtures/blog_public/index.html
+++ /dev/null
@@ -1 +0,0 @@
-/blog/index.html \ No newline at end of file
diff --git a/actionview/test/fixtures/blog_public/subdir/index.html b/actionview/test/fixtures/blog_public/subdir/index.html
deleted file mode 100644
index 517bded335..0000000000
--- a/actionview/test/fixtures/blog_public/subdir/index.html
+++ /dev/null
@@ -1 +0,0 @@
-/blog/subdir/index.html \ No newline at end of file
diff --git a/actionview/test/fixtures/functional_caching/fragment_cached_without_digest.html.erb b/actionview/test/fixtures/functional_caching/fragment_cached_without_digest.html.erb
deleted file mode 100644
index 3125583a28..0000000000
--- a/actionview/test/fixtures/functional_caching/fragment_cached_without_digest.html.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-<body>
-<%= cache 'nodigest', skip_digest: true do %><p>ERB</p><% end %>
-</body>
diff --git a/actionview/test/fixtures/happy_path/render_action/hello_world.erb b/actionview/test/fixtures/happy_path/render_action/hello_world.erb
deleted file mode 100644
index 6769dd60bd..0000000000
--- a/actionview/test/fixtures/happy_path/render_action/hello_world.erb
+++ /dev/null
@@ -1 +0,0 @@
-Hello world! \ No newline at end of file
diff --git a/actionview/test/fixtures/layouts/streaming_with_capture.erb b/actionview/test/fixtures/layouts/streaming_with_capture.erb
new file mode 100644
index 0000000000..538c19ce3a
--- /dev/null
+++ b/actionview/test/fixtures/layouts/streaming_with_capture.erb
@@ -0,0 +1,6 @@
+<%= yield :header -%>
+<%= capture do %>
+ this works
+<% end %>
+<%= yield :footer -%>
+<%= yield(:unknown).presence || "." -%>
diff --git a/actionview/test/fixtures/multipart/bracketed_utf8_param b/actionview/test/fixtures/multipart/bracketed_utf8_param
deleted file mode 100644
index df9cecea08..0000000000
--- a/actionview/test/fixtures/multipart/bracketed_utf8_param
+++ /dev/null
@@ -1,5 +0,0 @@
---AaB03x
-Content-Disposition: form-data; name="Iñtërnâtiônàlizætiøn_name[Iñtërnâtiônàlizætiøn_nested_name]"
-
-Iñtërnâtiônàlizætiøn_value
---AaB03x--
diff --git a/actionview/test/fixtures/multipart/single_utf8_param b/actionview/test/fixtures/multipart/single_utf8_param
deleted file mode 100644
index 1d9fae7b17..0000000000
--- a/actionview/test/fixtures/multipart/single_utf8_param
+++ /dev/null
@@ -1,5 +0,0 @@
---AaB03x
-Content-Disposition: form-data; name="Iñtërnâtiônàlizætiøn_name"
-
-Iñtërnâtiônàlizætiøn_value
---AaB03x--
diff --git a/actionview/test/fixtures/scope/test/modgreet.erb b/actionview/test/fixtures/scope/test/modgreet.erb
deleted file mode 100644
index 8947726e89..0000000000
--- a/actionview/test/fixtures/scope/test/modgreet.erb
+++ /dev/null
@@ -1 +0,0 @@
-<p>Beautiful modules!</p> \ No newline at end of file
diff --git a/actionview/test/fixtures/test/_FooBar.html.erb b/actionview/test/fixtures/test/_FooBar.html.erb
new file mode 100644
index 0000000000..4bbe59410a
--- /dev/null
+++ b/actionview/test/fixtures/test/_FooBar.html.erb
@@ -0,0 +1 @@
+🍣
diff --git a/actionview/test/fixtures/test/_a-in.html.erb b/actionview/test/fixtures/test/_a-in.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionview/test/fixtures/test/_a-in.html.erb
diff --git a/actionview/test/fixtures/test/_cached_customer.erb b/actionview/test/fixtures/test/_cached_customer.erb
new file mode 100644
index 0000000000..52f35a3497
--- /dev/null
+++ b/actionview/test/fixtures/test/_cached_customer.erb
@@ -0,0 +1,3 @@
+<% cache cached_customer do %>
+ Hello: <%= cached_customer.name %>
+<% end %> \ No newline at end of file
diff --git a/actionview/test/fixtures/test/_cached_customer_as.erb b/actionview/test/fixtures/test/_cached_customer_as.erb
new file mode 100644
index 0000000000..fca8d19e34
--- /dev/null
+++ b/actionview/test/fixtures/test/_cached_customer_as.erb
@@ -0,0 +1,3 @@
+<% cache buyer do %>
+ <%= greeting %>: <%= customer.name %>
+<% end %> \ No newline at end of file
diff --git a/actionview/test/fixtures/test/_label_with_block.erb b/actionview/test/fixtures/test/_label_with_block.erb
index 40117e594e..94089ea93d 100644
--- a/actionview/test/fixtures/test/_label_with_block.erb
+++ b/actionview/test/fixtures/test/_label_with_block.erb
@@ -1,4 +1,4 @@
-<%= label 'post', 'message' do %>
+<%= label('post', 'message')do %>
Message
<%= text_field 'post', 'message' %>
<% end %>
diff --git a/actionview/test/fixtures/test/_partial_shortcut_with_block_content.html.erb b/actionview/test/fixtures/test/_partial_shortcut_with_block_content.html.erb
new file mode 100644
index 0000000000..352128f3ba
--- /dev/null
+++ b/actionview/test/fixtures/test/_partial_shortcut_with_block_content.html.erb
@@ -0,0 +1,3 @@
+<%= render "test/layout_for_block_with_args" do |arg_1, arg_2| %>
+ Yielded: <%= arg_1 %>/<%= arg_2 %>
+<% end %>
diff --git a/actionview/test/lib/controller/fake_models.rb b/actionview/test/lib/controller/fake_models.rb
index a463a08bb6..65c68fc34a 100644
--- a/actionview/test/lib/controller/fake_models.rb
+++ b/actionview/test/lib/controller/fake_models.rb
@@ -54,6 +54,22 @@ class Post < Struct.new(:title, :author_name, :body, :secret, :persisted, :writt
def tags_attributes=(attributes); end
end
+class PostDelegator < Post
+ def to_model
+ PostDelegate.new
+ end
+end
+
+class PostDelegate < Post
+ def self.human_attribute_name(attribute)
+ "Delegate #{super}"
+ end
+
+ def model_name
+ ActiveModel::Name.new(self.class)
+ end
+end
+
class Comment
extend ActiveModel::Naming
include ActiveModel::Conversion
@@ -111,19 +127,6 @@ class CommentRelevance
end
end
-class Sheep
- extend ActiveModel::Naming
- include ActiveModel::Conversion
-
- attr_reader :id
- def to_key; id ? [id] : nil end
- def save; @id = 1 end
- def new_record?; @id.nil? end
- def name
- @id.nil? ? 'new sheep' : "sheep ##{@id}"
- end
-end
-
class TagRelevance
extend ActiveModel::Naming
include ActiveModel::Conversion
@@ -183,3 +186,15 @@ end
class Car < Struct.new(:color)
end
+
+class Plane
+ attr_reader :to_key
+
+ def model_name
+ OpenStruct.new param_key: 'airplane'
+ end
+
+ def save
+ @to_key = [1]
+ end
+end
diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb
index dac1c7024d..02dce71496 100644
--- a/actionview/test/template/asset_tag_helper_test.rb
+++ b/actionview/test/template/asset_tag_helper_test.rb
@@ -1,4 +1,3 @@
-require 'zlib'
require 'abstract_unit'
require 'active_support/ordered_options'
@@ -180,6 +179,7 @@ class AssetTagHelperTest < ActionView::TestCase
%(image_tag("xml.png")) => %(<img alt="Xml" src="/images/xml.png" />),
%(image_tag("rss.gif", :alt => "rss syndication")) => %(<img alt="rss syndication" src="/images/rss.gif" />),
%(image_tag("gold.png", :size => "20")) => %(<img alt="Gold" height="20" src="/images/gold.png" width="20" />),
+ %(image_tag("gold.png", :size => 20)) => %(<img alt="Gold" height="20" src="/images/gold.png" width="20" />),
%(image_tag("gold.png", :size => "45x70")) => %(<img alt="Gold" height="70" src="/images/gold.png" width="45" />),
%(image_tag("gold.png", "size" => "45x70")) => %(<img alt="Gold" height="70" src="/images/gold.png" width="45" />),
%(image_tag("error.png", "size" => "45 x 70")) => %(<img alt="Error" src="/images/error.png" />),
@@ -238,6 +238,7 @@ class AssetTagHelperTest < ActionView::TestCase
%(video_tag("gold.m4v", "size" => "320x240")) => %(<video height="240" src="/videos/gold.m4v" width="320"></video>),
%(video_tag("trailer.ogg", :poster => "screenshot.png")) => %(<video poster="/images/screenshot.png" src="/videos/trailer.ogg"></video>),
%(video_tag("error.avi", "size" => "100")) => %(<video height="100" src="/videos/error.avi" width="100"></video>),
+ %(video_tag("error.avi", "size" => 100)) => %(<video height="100" src="/videos/error.avi" width="100"></video>),
%(video_tag("error.avi", "size" => "100 x 100")) => %(<video src="/videos/error.avi"></video>),
%(video_tag("error.avi", "size" => "x")) => %(<video src="/videos/error.avi"></video>),
%(video_tag("http://media.rubyonrails.org/video/rails_blog_2.mov")) => %(<video src="http://media.rubyonrails.org/video/rails_blog_2.mov"></video>),
diff --git a/actionview/test/template/atom_feed_helper_test.rb b/actionview/test/template/atom_feed_helper_test.rb
index 68b44c4f0d..525d99750d 100644
--- a/actionview/test/template/atom_feed_helper_test.rb
+++ b/actionview/test/template/atom_feed_helper_test.rb
@@ -62,6 +62,23 @@ class ScrollsController < ActionController::Base
end
end
EOT
+ FEEDS["entry_url_false_option"] = <<-EOT
+ atom_feed do |feed|
+ feed.title("My great blog!")
+ feed.updated((@scrolls.first.created_at))
+
+ @scrolls.each do |scroll|
+ feed.entry(scroll, :url => false) do |entry|
+ entry.title(scroll.title)
+ entry.content(scroll.body, :type => 'html')
+
+ entry.author do |author|
+ author.name("DHH")
+ end
+ end
+ end
+ end
+ EOT
FEEDS["xml_block"] = <<-EOT
atom_feed do |feed|
feed.title("My great blog!")
@@ -214,28 +231,28 @@ class AtomFeedTest < ActionController::TestCase
def test_feed_should_use_default_language_if_none_is_given
with_restful_routing(:scrolls) do
- get :index, :id => "defaults"
+ get :index, params: { id: "defaults" }
assert_match(%r{xml:lang="en-US"}, @response.body)
end
end
def test_feed_should_include_two_entries
with_restful_routing(:scrolls) do
- get :index, :id => "defaults"
+ get :index, params: { id: "defaults" }
assert_select "entry", 2
end
end
def test_entry_should_only_use_published_if_created_at_is_present
with_restful_routing(:scrolls) do
- get :index, :id => "defaults"
+ get :index, params: { id: "defaults" }
assert_select "published", 1
end
end
def test_providing_builder_to_atom_feed
with_restful_routing(:scrolls) do
- get :index, :id=>"provide_builder"
+ get :index, params: { id: "provide_builder" }
# because we pass in the non-default builder, the content generated by the
# helper should go 'nowhere'. Leaving the response body blank.
assert @response.body.blank?
@@ -244,7 +261,7 @@ class AtomFeedTest < ActionController::TestCase
def test_entry_with_prefilled_options_should_use_those_instead_of_querying_the_record
with_restful_routing(:scrolls) do
- get :index, :id => "entry_options"
+ get :index, params: { id: "entry_options" }
assert_select "updated", Time.utc(2007, 1, 1).xmlschema
assert_select "updated", Time.utc(2007, 1, 2).xmlschema
@@ -253,21 +270,21 @@ class AtomFeedTest < ActionController::TestCase
def test_self_url_should_default_to_current_request_url
with_restful_routing(:scrolls) do
- get :index, :id => "defaults"
+ get :index, params: { id: "defaults" }
assert_select "link[rel=self][href=\"http://www.nextangle.com/scrolls?id=defaults\"]"
end
end
def test_feed_id_should_be_a_valid_tag
with_restful_routing(:scrolls) do
- get :index, :id => "defaults"
+ get :index, params: { id: "defaults" }
assert_select "id", :text => "tag:www.nextangle.com,2008:/scrolls?id=defaults"
end
end
def test_entry_id_should_be_a_valid_tag
with_restful_routing(:scrolls) do
- get :index, :id => "defaults"
+ get :index, params: { id: "defaults" }
assert_select "entry id", :text => "tag:www.nextangle.com,2008:Scroll/1"
assert_select "entry id", :text => "tag:www.nextangle.com,2008:Scroll/2"
end
@@ -275,14 +292,14 @@ class AtomFeedTest < ActionController::TestCase
def test_feed_should_allow_nested_xml_blocks
with_restful_routing(:scrolls) do
- get :index, :id => "xml_block"
+ get :index, params: { id: "xml_block" }
assert_select "author name", :text => "DHH"
end
end
def test_feed_should_include_atomPub_namespace
with_restful_routing(:scrolls) do
- get :index, :id => "feed_with_atomPub_namespace"
+ get :index, params: { id: "feed_with_atomPub_namespace" }
assert_match %r{xml:lang="en-US"}, @response.body
assert_match %r{xmlns="http://www.w3.org/2005/Atom"}, @response.body
assert_match %r{xmlns:app="http://www.w3.org/2007/app"}, @response.body
@@ -291,7 +308,7 @@ class AtomFeedTest < ActionController::TestCase
def test_feed_should_allow_overriding_ids
with_restful_routing(:scrolls) do
- get :index, :id => "feed_with_overridden_ids"
+ get :index, params: { id: "feed_with_overridden_ids" }
assert_select "id", :text => "tag:test.rubyonrails.org,2008:test/"
assert_select "entry id", :text => "tag:test.rubyonrails.org,2008:1"
assert_select "entry id", :text => "tag:test.rubyonrails.org,2008:2"
@@ -300,7 +317,7 @@ class AtomFeedTest < ActionController::TestCase
def test_feed_xml_processing_instructions
with_restful_routing(:scrolls) do
- get :index, :id => 'feed_with_xml_processing_instructions'
+ get :index, params: { id: 'feed_with_xml_processing_instructions' }
assert_match %r{<\?xml-stylesheet [^\?]*type="text/css"}, @response.body
assert_match %r{<\?xml-stylesheet [^\?]*href="t.css"}, @response.body
end
@@ -308,7 +325,7 @@ class AtomFeedTest < ActionController::TestCase
def test_feed_xml_processing_instructions_duplicate_targets
with_restful_routing(:scrolls) do
- get :index, :id => 'feed_with_xml_processing_instructions_duplicate_targets'
+ get :index, params: { id: 'feed_with_xml_processing_instructions_duplicate_targets' }
assert_match %r{<\?target1 (a="1" b="2"|b="2" a="1")\?>}, @response.body
assert_match %r{<\?target1 (c="3" d="4"|d="4" c="3")\?>}, @response.body
end
@@ -316,7 +333,7 @@ class AtomFeedTest < ActionController::TestCase
def test_feed_xhtml
with_restful_routing(:scrolls) do
- get :index, :id => "feed_with_xhtml_content"
+ get :index, params: { id: "feed_with_xhtml_content" }
assert_match %r{xmlns="http://www.w3.org/1999/xhtml"}, @response.body
assert_select "summary", :text => /Something Boring/
assert_select "summary", :text => /after 2/
@@ -325,18 +342,25 @@ class AtomFeedTest < ActionController::TestCase
def test_feed_entry_type_option_default_to_text_html
with_restful_routing(:scrolls) do
- get :index, :id => 'defaults'
+ get :index, params: { id: 'defaults' }
assert_select "entry link[rel=alternate][type=\"text/html\"]"
end
end
def test_feed_entry_type_option_specified
with_restful_routing(:scrolls) do
- get :index, :id => 'entry_type_options'
+ get :index, params: { id: 'entry_type_options' }
assert_select "entry link[rel=alternate][type=\"text/xml\"]"
end
end
+ def test_feed_entry_url_false_option_adds_no_link
+ with_restful_routing(:scrolls) do
+ get :index, params: { id: 'entry_url_false_option' }
+ assert_select "entry link", false
+ end
+ end
+
private
def with_restful_routing(resources)
with_routing do |set|
diff --git a/actionview/test/template/date_helper_test.rb b/actionview/test/template/date_helper_test.rb
index 0cdb130710..bfb073680e 100644
--- a/actionview/test/template/date_helper_test.rb
+++ b/actionview/test/template/date_helper_test.rb
@@ -1504,7 +1504,7 @@ class DateHelperTest < ActionView::TestCase
expected << %(<option value="">Choose seconds</option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
expected << "</select>\n"
- assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :prompt => true, :include_seconds => true,
+ assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :include_seconds => true,
:prompt => {:hour => 'Choose hour', :minute => 'Choose minute', :second => 'Choose seconds'})
end
@@ -2330,7 +2330,7 @@ class DateHelperTest < ActionView::TestCase
# The love zone is UTC+0
mytz = Class.new(ActiveSupport::TimeZone) {
attr_accessor :now
- }.create('tenderlove', 0)
+ }.create('tenderlove', 0, ActiveSupport::TimeZone.find_tzinfo('UTC'))
now = Time.mktime(2004, 6, 15, 16, 35, 0)
mytz.now = now
diff --git a/actionview/test/template/dependency_tracker_test.rb b/actionview/test/template/dependency_tracker_test.rb
index bb375076c6..672b4747ec 100644
--- a/actionview/test/template/dependency_tracker_test.rb
+++ b/actionview/test/template/dependency_tracker_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'action_view/dependency_tracker'
@@ -61,7 +60,6 @@ class ERBTrackerTest < Minitest::Test
end
def test_dependency_of_template_partial_with_layout
- skip # FIXME: Needs to be fixed properly, right now we can only match one dependency per line. Need multiple!
template = FakeTemplate.new("<%# render partial: 'messages/show', layout: 'messages/layout' %>", :erb)
tracker = make_tracker("multiple/_dependencies", template)
diff --git a/actionview/test/template/erb_util_test.rb b/actionview/test/template/erb_util_test.rb
index 3bb84cbc50..3e72be31de 100644
--- a/actionview/test/template/erb_util_test.rb
+++ b/actionview/test/template/erb_util_test.rb
@@ -84,7 +84,7 @@ class ErbUtilTest < ActiveSupport::TestCase
end
def test_rest_in_ascii
- (0..127).to_a.map {|int| int.chr }.each do |chr|
+ (0..127).to_a.map(&:chr).each do |chr|
next if %('"&<>).include?(chr)
assert_equal chr, html_escape(chr)
end
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index 4bbbdf4fb1..5c55b154d3 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -40,6 +40,9 @@ class FormHelperTest < ActionView::TestCase
},
tag: {
value: "Tag"
+ },
+ post_delegate: {
+ title: 'Delegate model_name title'
}
}
}
@@ -81,6 +84,9 @@ class FormHelperTest < ActionView::TestCase
body: "Write body here"
}
},
+ post_delegate: {
+ title: 'Delegate model_name title'
+ },
tag: {
value: "Tag"
}
@@ -99,7 +105,9 @@ class FormHelperTest < ActionView::TestCase
}.new
end
def @post.to_key; [123]; end
- def @post.id_before_type_cast; 123; end
+ def @post.id; 0; end
+ def @post.id_before_type_cast; "omg"; end
+ def @post.id_came_from_user?; true; end
def @post.to_param; '123'; end
@post.persisted = true
@@ -115,6 +123,10 @@ class FormHelperTest < ActionView::TestCase
@post.tags = []
@post.tags << Tag.new
+ @post_delegator = PostDelegator.new
+
+ @post_delegator.title = 'Hello World'
+
@car = Car.new("#000FFF")
end
@@ -247,6 +259,18 @@ class FormHelperTest < ActionView::TestCase
end
end
+ def test_label_with_non_active_record_object
+ form_for(OpenStruct.new(name:'ok'), as: 'person', url: 'an_url', html: { id: 'create-person' }) do |f|
+ f.label(:name)
+ end
+
+ expected = whole_form("an_url", "create-person", "new_person", method: "post") do
+ '<label for="person_name">Name</label>'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_label_with_for_attribute_as_symbol
assert_dom_equal('<label for="my_for">Title</label>', label(:post, :title, nil, for: "my_for"))
end
@@ -335,6 +359,22 @@ class FormHelperTest < ActionView::TestCase
)
end
+ def test_label_with_to_model
+ assert_dom_equal(
+ %{<label for="post_delegator_title">Delegate Title</label>},
+ label(:post_delegator, :title)
+ )
+ end
+
+ def test_label_with_to_model_and_overriden_model_name
+ with_locale :label do
+ assert_dom_equal(
+ %{<label for="post_delegator_title">Delegate model_name title</label>},
+ label(:post_delegator, :title)
+ )
+ end
+ end
+
def test_text_field_placeholder_without_locales
with_locale :placeholder do
assert_dom_equal('<input id="post_body" name="post[body]" placeholder="Body" type="text" value="Back to the hill and over it again!" />', text_field(:post, :body, placeholder: true))
@@ -347,12 +387,28 @@ class FormHelperTest < ActionView::TestCase
end
end
+ def test_text_field_placeholder_with_locales_and_to_model
+ with_locale :placeholder do
+ assert_dom_equal(
+ '<input id="post_delegator_title" name="post_delegator[title]" placeholder="Delegate model_name title" type="text" value="Hello World" />',
+ text_field(:post_delegator, :title, placeholder: true)
+ )
+ end
+ end
+
def test_text_field_placeholder_with_human_attribute_name
with_locale :placeholder do
assert_dom_equal('<input id="post_cost" name="post[cost]" placeholder="Total cost" type="text" />', text_field(:post, :cost, placeholder: true))
end
end
+ def test_text_field_placeholder_with_human_attribute_name_and_to_model
+ assert_dom_equal(
+ '<input id="post_delegator_title" name="post_delegator[title]" placeholder="Delegate Title" type="text" value="Hello World" />',
+ text_field(:post_delegator, :title, placeholder: true)
+ )
+ end
+
def test_text_field_placeholder_with_string_value
with_locale :placeholder do
assert_dom_equal('<input id="post_cost" name="post[cost]" placeholder="HOW MUCH?" type="text" />', text_field(:post, :cost, placeholder: "HOW MUCH?"))
@@ -472,18 +528,33 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, text_field(object_name, "title")
end
- def test_file_field_has_no_size
+ def test_file_field_does_generate_a_hidden_field
+ expected = '<input name="user[avatar]" type="hidden" value="" /><input id="user_avatar" name="user[avatar]" type="file" />'
+ assert_dom_equal expected, file_field("user", "avatar")
+ end
+
+ def test_file_field_does_not_generate_a_hidden_field_if_included_hidden_option_is_false
+ expected = '<input id="user_avatar" name="user[avatar]" type="file" />'
+ assert_dom_equal expected, file_field("user", "avatar", include_hidden: false)
+ end
+
+ def test_file_field_does_not_generate_a_hidden_field_if_included_hidden_option_is_false_with_key_as_string
expected = '<input id="user_avatar" name="user[avatar]" type="file" />'
+ assert_dom_equal expected, file_field("user", "avatar", "include_hidden" => false)
+ end
+
+ def test_file_field_has_no_size
+ expected = '<input name="user[avatar]" type="hidden" value="" /><input id="user_avatar" name="user[avatar]" type="file" />'
assert_dom_equal expected, file_field("user", "avatar")
end
def test_file_field_with_multiple_behavior
- expected = '<input id="import_file" multiple="multiple" name="import[file][]" type="file" />'
+ expected = '<input name="import[file][]" type="hidden" value="" /><input id="import_file" multiple="multiple" name="import[file][]" type="file" />'
assert_dom_equal expected, file_field("import", "file", :multiple => true)
end
def test_file_field_with_multiple_behavior_and_explicit_name
- expected = '<input id="import_file" multiple="multiple" name="custom" type="file" />'
+ expected = '<input name="custom" type="hidden" value="" /><input id="import_file" multiple="multiple" name="custom" type="file" />'
assert_dom_equal expected, file_field("import", "file", :multiple => true, :name => "custom")
end
@@ -885,9 +956,29 @@ class FormHelperTest < ActionView::TestCase
)
end
- def test_text_area_with_value_before_type_cast
+ def test_inputs_use_before_type_cast_to_retain_information_from_validations_like_numericality
+ assert_dom_equal(
+ %{<textarea id="post_id" name="post[id]">\nomg</textarea>},
+ text_area("post", "id")
+ )
+ end
+
+ def test_inputs_dont_use_before_type_cast_when_value_did_not_come_from_user
+ class << @post
+ undef id_came_from_user?
+ def id_came_from_user?; false; end
+ end
+
assert_dom_equal(
- %{<textarea id="post_id" name="post[id]">\n123</textarea>},
+ %{<textarea id="post_id" name="post[id]">\n0</textarea>},
+ text_area("post", "id")
+ )
+ end
+
+ def test_inputs_use_before_typecast_when_object_doesnt_respond_to_came_from_user
+ class << @post; undef id_came_from_user?; end
+ assert_dom_equal(
+ %{<textarea id="post_id" name="post[id]">\nomg</textarea>},
text_area("post", "id")
)
end
@@ -928,6 +1019,11 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal(expected, search_field("contact", "notes_query"))
end
+ def test_search_field_with_onsearch_value
+ expected = %{<input onsearch="true" type="search" name="contact[notes_query]" id="contact_notes_query" incremental="true" />}
+ assert_dom_equal(expected, search_field("contact", "notes_query", onsearch: true))
+ end
+
def test_telephone_field
expected = %{<input id="user_cell" name="user[cell]" type="tel" />}
assert_dom_equal(expected, telephone_field("user", "cell"))
@@ -1714,7 +1810,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "create-post", "edit_post", method: "patch", multipart: true) do
- "<input name='post[file]' type='file' id='post_file' />"
+ "<input name='post[file]' type='hidden' value='' /><input name='post[file]' type='file' id='post_file' />"
end
assert_dom_equal expected, output_buffer
@@ -1730,7 +1826,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch", multipart: true) do
- "<input name='post[comment][file]' type='file' id='post_comment_file' />"
+ "<input name='post[comment][file]' type='hidden' value='' /><input name='post[comment][file]' type='file' id='post_comment_file' />"
end
assert_dom_equal expected, output_buffer
@@ -1864,6 +1960,30 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_form_for_enforce_utf8_true
+ form_for(:post, enforce_utf8: true) do |f|
+ concat f.text_field(:title)
+ end
+
+ expected = whole_form("/", nil, nil, enforce_utf8: true) do
+ "<input name='post[title]' type='text' id='post_title' value='Hello World' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_for_enforce_utf8_false
+ form_for(:post, enforce_utf8: false) do |f|
+ concat f.text_field(:title)
+ end
+
+ expected = whole_form("/", nil, nil, enforce_utf8: false) do
+ "<input name='post[title]' type='text' id='post_title' value='Hello World' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_form_for_with_remote_in_html
form_for(@post, url: '/', html: { remote: true, id: 'create-post', method: :patch }) do |f|
concat f.text_field(:title)
@@ -2758,6 +2878,23 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_nested_fields_for_with_child_index_as_lambda_option_override_on_a_nested_attributes_collection_association
+ @post.comments = []
+
+ form_for(@post) do |f|
+ concat f.fields_for(:comments, Comment.new(321), child_index: -> { 'abc' } ) { |cf|
+ concat cf.text_field(:name)
+ }
+ end
+
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', method: 'patch') do
+ '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' +
+ '<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
class FakeAssociationProxy
def to_ary
[1, 2, 3]
@@ -3313,8 +3450,14 @@ class FormHelperTest < ActionView::TestCase
protected
- def hidden_fields(method = nil)
- txt = %{<input name="utf8" type="hidden" value="&#x2713;" />}
+ def hidden_fields(options = {})
+ method = options[:method]
+
+ if options.fetch(:enforce_utf8, true)
+ txt = %{<input name="utf8" type="hidden" value="&#x2713;" />}
+ else
+ txt = ''
+ end
if method && !%w(get post).include?(method.to_s)
txt << %{<input name="_method" type="hidden" value="#{method}" />}
@@ -3338,7 +3481,7 @@ class FormHelperTest < ActionView::TestCase
method, remote, multipart = options.values_at(:method, :remote, :multipart)
- form_text(action, id, html_class, remote, multipart, method) + hidden_fields(method) + contents + "</form>"
+ form_text(action, id, html_class, remote, multipart, method) + hidden_fields(options.slice :method, :enforce_utf8) + contents + "</form>"
end
def protect_against_forgery?
diff --git a/actionview/test/template/form_tag_helper_test.rb b/actionview/test/template/form_tag_helper_test.rb
index f8fd642809..cad1c82309 100644
--- a/actionview/test/template/form_tag_helper_test.rb
+++ b/actionview/test/template/form_tag_helper_test.rb
@@ -210,13 +210,13 @@ class FormTagHelperTest < ActionView::TestCase
end
def test_select_tag_with_multiple
- actual = select_tag "colors", "<option>Red</option><option>Blue</option><option>Green</option>".html_safe, :multiple => :true
- expected = %(<select id="colors" multiple="multiple" name="colors"><option>Red</option><option>Blue</option><option>Green</option></select>)
+ actual = select_tag "colors", "<option>Red</option><option>Blue</option><option>Green</option>".html_safe, multiple: true
+ expected = %(<select id="colors" multiple="multiple" name="colors[]"><option>Red</option><option>Blue</option><option>Green</option></select>)
assert_dom_equal expected, actual
end
def test_select_tag_disabled
- actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, :disabled => :true
+ actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, disabled: true
expected = %(<select id="places" disabled="disabled" name="places"><option>Home</option><option>Work</option><option>Pub</option></select>)
assert_dom_equal expected, actual
end
@@ -232,6 +232,12 @@ class FormTagHelperTest < ActionView::TestCase
assert_dom_equal expected, actual
end
+ def test_select_tag_with_include_blank_false
+ actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, include_blank: false
+ expected = %(<select id="places" name="places"><option>Home</option><option>Work</option><option>Pub</option></select>)
+ assert_dom_equal expected, actual
+ end
+
def test_select_tag_with_include_blank_string
actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, include_blank: 'Choose'
expected = %(<select id="places" name="places"><option value="">Choose</option><option>Home</option><option>Work</option><option>Pub</option></select>)
@@ -346,7 +352,7 @@ class FormTagHelperTest < ActionView::TestCase
end
def test_text_field_disabled
- actual = text_field_tag "title", "Hello!", :disabled => :true
+ actual = text_field_tag "title", "Hello!", disabled: true
expected = %(<input id="title" name="title" disabled="disabled" type="text" value="Hello!" />)
assert_dom_equal expected, actual
end
diff --git a/actionview/test/template/javascript_helper_test.rb b/actionview/test/template/javascript_helper_test.rb
index 9ba7f64ad1..9f1535ef53 100644
--- a/actionview/test/template/javascript_helper_test.rb
+++ b/actionview/test/template/javascript_helper_test.rb
@@ -3,14 +3,7 @@ require 'abstract_unit'
class JavaScriptHelperTest < ActionView::TestCase
tests ActionView::Helpers::JavaScriptHelper
- def _evaluate_assigns_and_ivars() end
-
- attr_accessor :formats, :output_buffer
-
- def update_details(details)
- @details = details
- yield if block_given?
- end
+ attr_accessor :output_buffer
setup do
@old_escape_html_entities_in_json = ActiveSupport.escape_html_entities_in_json
diff --git a/actionview/test/template/number_helper_test.rb b/actionview/test/template/number_helper_test.rb
index b59883b760..b70b750869 100644
--- a/actionview/test/template/number_helper_test.rb
+++ b/actionview/test/template/number_helper_test.rb
@@ -35,6 +35,10 @@ class NumberHelperTest < ActionView::TestCase
assert_equal "98a%", number_to_percentage("98a")
assert_equal "NaN%", number_to_percentage(Float::NAN)
assert_equal "Inf%", number_to_percentage(Float::INFINITY)
+ assert_equal "NaN%", number_to_percentage(Float::NAN, precision: 0)
+ assert_equal "Inf%", number_to_percentage(Float::INFINITY, precision: 0)
+ assert_equal "NaN%", number_to_percentage(Float::NAN, precision: 1)
+ assert_equal "Inf%", number_to_percentage(Float::INFINITY, precision: 1)
end
def test_number_with_delimiter
diff --git a/actionview/test/template/record_identifier_test.rb b/actionview/test/template/record_identifier_test.rb
index 22038110a5..04898c0b0e 100644
--- a/actionview/test/template/record_identifier_test.rb
+++ b/actionview/test/template/record_identifier_test.rb
@@ -9,7 +9,6 @@ class RecordIdentifierTest < ActiveSupport::TestCase
@record = @klass.new
@singular = 'comment'
@plural = 'comments'
- @uncountable = Sheep
end
def test_dom_id_with_new_record
@@ -47,3 +46,46 @@ class RecordIdentifierTest < ActiveSupport::TestCase
assert_equal @singular, ActionView::RecordIdentifier.dom_class(@record)
end
end
+
+class RecordIdentifierWithoutActiveModelTest < ActiveSupport::TestCase
+ include ActionView::RecordIdentifier
+
+ def setup
+ @record = Plane.new
+ end
+
+ def test_dom_id_with_new_record
+ assert_equal "new_airplane", dom_id(@record)
+ end
+
+ def test_dom_id_with_new_record_and_prefix
+ assert_equal "custom_prefix_airplane", dom_id(@record, :custom_prefix)
+ end
+
+ def test_dom_id_with_saved_record
+ @record.save
+ assert_equal "airplane_1", dom_id(@record)
+ end
+
+ def test_dom_id_with_prefix
+ @record.save
+ assert_equal "edit_airplane_1", dom_id(@record, :edit)
+ end
+
+ def test_dom_class
+ assert_equal 'airplane', dom_class(@record)
+ end
+
+ def test_dom_class_with_prefix
+ assert_equal "custom_prefix_airplane", dom_class(@record, :custom_prefix)
+ end
+
+ def test_dom_id_as_singleton_method
+ @record.save
+ assert_equal "airplane_1", ActionView::RecordIdentifier.dom_id(@record)
+ end
+
+ def test_dom_class_as_singleton_method
+ assert_equal 'airplane', ActionView::RecordIdentifier.dom_class(@record)
+ end
+end
diff --git a/actionview/test/template/record_tag_helper_test.rb b/actionview/test/template/record_tag_helper_test.rb
index ab84bccb56..4b7b653916 100644
--- a/actionview/test/template/record_tag_helper_test.rb
+++ b/actionview/test/template/record_tag_helper_test.rb
@@ -24,94 +24,10 @@ class RecordTagHelperTest < ActionView::TestCase
end
def test_content_tag_for
- expected = %(<li class="record_tag_post" id="record_tag_post_45"></li>)
- actual = content_tag_for(:li, @post)
- assert_dom_equal expected, actual
+ assert_raises(NoMethodError) { content_tag_for(:li, @post) }
end
- def test_content_tag_for_prefix
- expected = %(<ul class="archived_record_tag_post" id="archived_record_tag_post_45"></ul>)
- actual = content_tag_for(:ul, @post, :archived)
- assert_dom_equal expected, actual
- end
-
- def test_content_tag_for_with_extra_html_options
- expected = %(<tr class="record_tag_post special" id="record_tag_post_45" style='background-color: #f0f0f0'></tr>)
- actual = content_tag_for(:tr, @post, class: "special", style: "background-color: #f0f0f0")
- assert_dom_equal expected, actual
- end
-
- def test_content_tag_for_with_array_css_class
- expected = %(<tr class="record_tag_post special odd" id="record_tag_post_45"></tr>)
- actual = content_tag_for(:tr, @post, class: ["special", "odd"])
- assert_dom_equal expected, actual
- end
-
- def test_content_tag_for_with_prefix_and_extra_html_options
- expected = %(<tr class="archived_record_tag_post special" id="archived_record_tag_post_45" style='background-color: #f0f0f0'></tr>)
- actual = content_tag_for(:tr, @post, :archived, class: "special", style: "background-color: #f0f0f0")
- assert_dom_equal expected, actual
- end
-
- def test_block_not_in_erb_multiple_calls
- expected = %(<div class="record_tag_post special" id="record_tag_post_45">What a wonderful world!</div>)
- actual = div_for(@post, class: "special") { @post.body }
- assert_dom_equal expected, actual
- actual = div_for(@post, class: "special") { @post.body }
- assert_dom_equal expected, actual
- end
-
- def test_block_works_with_content_tag_for_in_erb
- expected = %(<tr class="record_tag_post" id="record_tag_post_45">What a wonderful world!</tr>)
- actual = render_erb("<%= content_tag_for(:tr, @post) do %><%= @post.body %><% end %>")
- assert_dom_equal expected, actual
- end
-
- def test_div_for_in_erb
- expected = %(<div class="record_tag_post special" id="record_tag_post_45">What a wonderful world!</div>)
- actual = render_erb("<%= div_for(@post, class: 'special') do %><%= @post.body %><% end %>")
- assert_dom_equal expected, actual
- end
-
- def test_content_tag_for_collection
- post_1 = RecordTagPost.new { |post| post.id = 101; post.body = "Hello!" }
- post_2 = RecordTagPost.new { |post| post.id = 102; post.body = "World!" }
- expected = %(<li class="record_tag_post" id="record_tag_post_101">Hello!</li>\n<li class="record_tag_post" id="record_tag_post_102">World!</li>)
- actual = content_tag_for(:li, [post_1, post_2]) { |post| post.body }
- assert_dom_equal expected, actual
- end
-
- def test_content_tag_for_collection_without_given_block
- post_1 = RecordTagPost.new.tap { |post| post.id = 101; post.body = "Hello!" }
- post_2 = RecordTagPost.new.tap { |post| post.id = 102; post.body = "World!" }
- expected = %(<li class="record_tag_post" id="record_tag_post_101"></li>\n<li class="record_tag_post" id="record_tag_post_102"></li>)
- actual = content_tag_for(:li, [post_1, post_2])
- assert_dom_equal expected, actual
- end
-
- def test_div_for_collection
- post_1 = RecordTagPost.new { |post| post.id = 101; post.body = "Hello!" }
- post_2 = RecordTagPost.new { |post| post.id = 102; post.body = "World!" }
- expected = %(<div class="record_tag_post" id="record_tag_post_101">Hello!</div>\n<div class="record_tag_post" id="record_tag_post_102">World!</div>)
- actual = div_for([post_1, post_2]) { |post| post.body }
- assert_dom_equal expected, actual
- end
-
- def test_content_tag_for_single_record_is_html_safe
- result = div_for(@post, class: "special") { @post.body }
- assert result.html_safe?
- end
-
- def test_content_tag_for_collection_is_html_safe
- post_1 = RecordTagPost.new { |post| post.id = 101; post.body = "Hello!" }
- post_2 = RecordTagPost.new { |post| post.id = 102; post.body = "World!" }
- result = content_tag_for(:li, [post_1, post_2]) { |post| post.body }
- assert result.html_safe?
- end
-
- def test_content_tag_for_does_not_change_options_hash
- options = { class: "important" }
- content_tag_for(:li, @post, options)
- assert_equal({ class: "important" }, options)
+ def test_div_for
+ assert_raises(NoMethodError) { div_for(@post, class: "special") }
end
end
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index 85817119ba..22665b6844 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'controller/fake_models'
@@ -16,7 +15,7 @@ module RenderTestCases
I18n.backend.store_translations 'pt-BR', {}
# Ensure original are still the same since we are reindexing view paths
- assert_equal ORIGINAL_LOCALES, I18n.available_locales.map {|l| l.to_s }.sort
+ assert_equal ORIGINAL_LOCALES, I18n.available_locales.map(&:to_s).sort
end
def test_render_without_options
@@ -62,9 +61,10 @@ module RenderTestCases
def test_render_template_with_a_missing_partial_of_another_format
@view.lookup_context.formats = [:html]
- assert_raise ActionView::Template::Error, "Missing partial /_missing with {:locale=>[:en], :formats=>[:json], :handlers=>[:erb, :builder]}" do
+ e = assert_raise ActionView::Template::Error do
@view.render(:template => "with_format", :formats => [:json])
end
+ assert_includes(e.message, "Missing partial /_missing with {:locale=>[:en], :formats=>[:json], :variants=>[], :handlers=>[:raw, :erb, :builder, :ruby]}.")
end
def test_render_file_with_locale
@@ -172,18 +172,12 @@ module RenderTestCases
assert_equal "only partial", @view.render("test/partial_only", :counter_counter => 5)
end
- def test_render_partial_with_invalid_name
- e = assert_raises(ArgumentError) { @view.render(:partial => "test/200") }
- assert_equal "The partial name (test/200) is not a valid Ruby identifier; " +
- "make sure your partial name starts with a lowercase letter or underscore, " +
- "and is followed by any combination of letters, numbers and underscores.", e.message
+ def test_render_partial_with_number
+ assert_nothing_raised { @view.render(:partial => "test/200") }
end
def test_render_partial_with_missing_filename
- e = assert_raises(ArgumentError) { @view.render(:partial => "test/") }
- assert_equal "The partial name (test/) is not a valid Ruby identifier; " +
- "make sure your partial name starts with a lowercase letter or underscore, " +
- "and is followed by any combination of letters, numbers and underscores.", e.message
+ assert_raises(ActionView::MissingTemplate) { @view.render(:partial => "test/") }
end
def test_render_partial_with_incompatible_object
@@ -191,10 +185,25 @@ module RenderTestCases
assert_equal "'#{nil.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.", e.message
end
+ def test_render_partial_starting_with_a_capital
+ assert_nothing_raised { @view.render(:partial => 'test/FooBar') }
+ end
+
def test_render_partial_with_hyphen
- e = assert_raises(ArgumentError) { @view.render(:partial => "test/a-in") }
- assert_equal "The partial name (test/a-in) is not a valid Ruby identifier; " +
- "make sure your partial name starts with a lowercase letter or underscore, " +
+ assert_nothing_raised { @view.render(:partial => "test/a-in") }
+ end
+
+ def test_render_partial_with_invalid_option_as
+ e = assert_raises(ArgumentError) { @view.render(:partial => "test/partial_only", :as => 'a-in') }
+ assert_equal "The value (a-in) of the option `as` is not a valid Ruby identifier; " +
+ "make sure it starts with lowercase letter, " +
+ "and is followed by any combination of letters, numbers and underscores.", e.message
+ end
+
+ def test_render_partial_with_hyphen_and_invalid_option_as
+ e = assert_raises(ArgumentError) { @view.render(:partial => "test/a-in", :as => 'a-in') }
+ assert_equal "The value (a-in) of the option `as` is not a valid Ruby identifier; " +
+ "make sure it starts with lowercase letter, " +
"and is followed by any combination of letters, numbers and underscores.", e.message
end
@@ -466,6 +475,11 @@ module RenderTestCases
@view.render(:partial => 'test/partial_with_partial', :layout => 'test/layout_for_partial', :locals => { :name => 'Foo!'})
end
+ def test_render_partial_shortcut_with_block_content
+ assert_equal %(Before (shortcut test)\nBefore\n\n Yielded: arg1/arg2\n\nAfter\nAfter),
+ @view.render(partial: "test/partial_shortcut_with_block_content", layout: "test/layout_for_partial", locals: { name: "shortcut test" })
+ end
+
def test_render_layout_with_a_nested_render_layout_call
assert_equal %(Before (Foo!)\nBefore (Bar!)\npartial html\nAfter\npartial with layout\n\nAfter),
@view.render(:partial => 'test/partial_with_layout', :layout => 'test/layout_for_partial', :locals => { :name => 'Foo!'})
@@ -584,3 +598,41 @@ class LazyViewRenderTest < ActiveSupport::TestCase
silence_warnings { Encoding.default_external = old }
end
end
+
+class CachedCollectionViewRenderTest < CachedViewRenderTest
+ class CachedCustomer < Customer; end
+
+ teardown do
+ ActionView::PartialRenderer.collection_cache.clear
+ end
+
+ test "with custom key" do
+ customer = Customer.new("david")
+ key = ActionController::Base.new.fragment_cache_key([customer, 'key'])
+
+ ActionView::PartialRenderer.collection_cache.write(key, 'Hello')
+
+ assert_equal "Hello",
+ @view.render(partial: "test/customer", collection: [customer], cache: ->(item) { [item, 'key'] })
+ end
+
+ test "automatic caching with inferred cache name" do
+ customer = CachedCustomer.new("david")
+ key = ActionController::Base.new.fragment_cache_key(customer)
+
+ ActionView::PartialRenderer.collection_cache.write(key, 'Cached')
+
+ assert_equal "Cached",
+ @view.render(partial: "test/cached_customer", collection: [customer])
+ end
+
+ test "automatic caching with as name" do
+ customer = CachedCustomer.new("david")
+ key = ActionController::Base.new.fragment_cache_key(customer)
+
+ ActionView::PartialRenderer.collection_cache.write(key, 'Cached')
+
+ assert_equal "Cached",
+ @view.render(partial: "test/cached_customer_as", collection: [customer], as: :buyer)
+ end
+end
diff --git a/actionview/test/template/sanitize_helper_test.rb b/actionview/test/template/sanitize_helper_test.rb
index e4be21be2c..efe846a7eb 100644
--- a/actionview/test/template/sanitize_helper_test.rb
+++ b/actionview/test/template/sanitize_helper_test.rb
@@ -29,6 +29,10 @@ class SanitizeHelperTest < ActionView::TestCase
assert_equal "", strip_tags("<script>")
end
+ def test_strip_tags_will_not_encode_special_characters
+ assert_equal "test\r\n\r\ntest", strip_tags("test\r\n\r\ntest")
+ end
+
def test_sanitize_is_marked_safe
assert sanitize("<html><script></script></html>").html_safe?
end
diff --git a/actionview/test/template/streaming_render_test.rb b/actionview/test/template/streaming_render_test.rb
index 8a24d78e74..d06ba4ceb0 100644
--- a/actionview/test/template/streaming_render_test.rb
+++ b/actionview/test/template/streaming_render_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
class TestController < ActionController::Base
@@ -105,4 +104,8 @@ class FiberedTest < ActiveSupport::TestCase
buffered_render(:template => "test/nested_streaming", :layout => "layouts/streaming")
end
+ def test_render_with_streaming_and_capture
+ assert_equal "Yes, \n this works\n like a charm.",
+ buffered_render(template: "test/streaming", layout: "layouts/streaming_with_capture")
+ end
end
diff --git a/actionview/test/template/tag_helper_test.rb b/actionview/test/template/tag_helper_test.rb
index ce89d5728e..d037447567 100644
--- a/actionview/test/template/tag_helper_test.rb
+++ b/actionview/test/template/tag_helper_test.rb
@@ -50,6 +50,11 @@ class TagHelperTest < ActionView::TestCase
assert_dom_equal "<div>Hello world!</div>", buffer
end
+ def test_content_tag_with_block_in_erb_containing_non_displayed_erb
+ buffer = render_erb("<%= content_tag(:p) do %><% 1 %><% end %>")
+ assert_dom_equal "<p></p>", buffer
+ end
+
def test_content_tag_with_block_and_options_in_erb
buffer = render_erb("<%= content_tag(:div, :class => 'green') do %>Hello world!<% end %>")
assert_dom_equal %(<div class="green">Hello world!</div>), buffer
@@ -64,6 +69,11 @@ class TagHelperTest < ActionView::TestCase
content_tag("a", "href" => "create") { "Create" }
end
+ def test_content_tag_with_block_and_non_string_outside_out_of_erb
+ assert_equal content_tag("p"),
+ content_tag("p") { 3.times { "do_something" } }
+ end
+
def test_content_tag_nested_in_content_tag_out_of_erb
assert_equal content_tag("p", content_tag("b", "Hello")),
content_tag("p") { content_tag("b", "Hello") },
diff --git a/actionview/test/template/template_test.rb b/actionview/test/template/template_test.rb
index c94508d678..aae6a9aa09 100644
--- a/actionview/test/template/template_test.rb
+++ b/actionview/test/template/template_test.rb
@@ -183,10 +183,11 @@ class TestERBTemplate < ActiveSupport::TestCase
end
def test_error_when_template_isnt_valid_utf8
- assert_raises(ActionView::Template::Error, /\xFC/) do
+ e = assert_raises ActionView::Template::Error do
@template = new_template("hello \xFCmlat", :virtual_path => nil)
render
end
+ assert_match(/\xFC/, e.message)
end
def with_external_encoding(encoding)
diff --git a/actionview/test/template/test_case_test.rb b/actionview/test/template/test_case_test.rb
index 5ad1938b61..c6cc47fb4f 100644
--- a/actionview/test/template/test_case_test.rb
+++ b/actionview/test/template/test_case_test.rb
@@ -328,9 +328,10 @@ module ActionView
test "supports specifying locals (failing)" do
controller.controller_path = "test"
render(:template => "test/calling_partial_with_layout")
- assert_raise ActiveSupport::TestCase::Assertion, /Somebody else.*David/m do
+ 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
diff --git a/actionview/test/template/text_helper_test.rb b/actionview/test/template/text_helper_test.rb
index f05b845e46..f1b84c4786 100644
--- a/actionview/test/template/text_helper_test.rb
+++ b/actionview/test/template/text_helper_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
class TextHelperTest < ActionView::TestCase
diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb
index 362f05ea70..df096b3c3a 100644
--- a/actionview/test/template/translation_helper_test.rb
+++ b/actionview/test/template/translation_helper_test.rb
@@ -1,5 +1,13 @@
require 'abstract_unit'
+module I18n
+ class CustomExceptionHandler
+ def self.call(exception, locale, key, options)
+ 'from CustomExceptionHandler'
+ end
+ end
+end
+
class TranslationHelperTest < ActiveSupport::TestCase
include ActionView::Helpers::TranslationHelper
@@ -72,6 +80,22 @@ class TranslationHelperTest < ActiveSupport::TestCase
end
end
+ def test_uses_custom_exception_handler_when_specified
+ old_exception_handler = I18n.exception_handler
+ I18n.exception_handler = I18n::CustomExceptionHandler
+ assert_equal 'from CustomExceptionHandler', translate(:"translations.missing", raise: false)
+ ensure
+ I18n.exception_handler = old_exception_handler
+ end
+
+ def test_uses_custom_exception_handler_when_specified_for_html
+ old_exception_handler = I18n.exception_handler
+ I18n.exception_handler = I18n::CustomExceptionHandler
+ assert_equal 'from CustomExceptionHandler', translate(:"translations.missing_html", raise: false)
+ ensure
+ 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")
@@ -145,16 +169,37 @@ class TranslationHelperTest < ActiveSupport::TestCase
assert_equal true, translation.html_safe?
end
+ def test_translate_with_last_default_not_named_html
+ translation = translate(:'translations.missing', :default => [:'translations.missing_html', :'translations.foo'])
+ assert_equal 'Foo', translation
+ assert_equal false, translation.html_safe?
+ end
+
def test_translate_with_string_default
translation = translate(:'translations.missing', default: 'A Generic String')
assert_equal 'A Generic String', translation
end
+ def test_translate_with_object_default
+ translation = translate(:'translations.missing', default: 123)
+ assert_equal 123, translation
+ end
+
def test_translate_with_array_of_string_defaults
translation = translate(:'translations.missing', default: ['A Generic String', 'Second generic string'])
assert_equal 'A Generic String', translation
end
+ def test_translate_with_array_of_defaults_with_nil
+ translation = translate(:'translations.missing', default: [:'also_missing', nil, 'A Generic String'])
+ assert_equal 'A Generic String', translation
+ end
+
+ def test_translate_with_array_of_array_default
+ translation = translate(:'translations.missing', default: [[]])
+ assert_equal [], translation
+ end
+
def test_translate_does_not_change_options
options = {}
translate(:'translations.missing', options)
diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb
index e0678ae1f7..ef4df0407a 100644
--- a/actionview/test/template/url_helper_test.rb
+++ b/actionview/test/template/url_helper_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'minitest/mock'
@@ -493,8 +492,13 @@ class UrlHelperTest < ActiveSupport::TestCase
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">My email</a>},
- mail_to("me@example.com", "My email", cc: "ccaddress@example.com", bcc: "bccaddress@example.com", subject: "This is an example email", body: "This is the body of the message.")
+ %{<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>},
+ mail_to("me@example.com", "My email", cc: "ccaddress@example.com", bcc: "bccaddress@example.com", subject: "This is an example email", body: "This is the body of the message.", reply_to: "foo@bar.com")
+ )
+
+ assert_dom_equal(
+ %{<a href="mailto:me@example.com?body=This%20is%20the%20body%20of%20the%20message.&amp;subject=This%20is%20an%20example%20email">My email</a>},
+ mail_to("me@example.com", "My email", cc: '', bcc: '', subject: "This is an example email", body: "This is the body of the message.")
)
end
@@ -624,13 +628,13 @@ class UrlHelperControllerTest < ActionController::TestCase
end
def test_named_route_url_shows_host_and_path
- get :show_named_route, kind: 'url'
+ get :show_named_route, params: { kind: 'url' }
assert_equal 'http://test.host/url_helper_controller_test/url_helper/show_named_route',
@response.body
end
def test_named_route_path_shows_only_path
- get :show_named_route, kind: 'path'
+ get :show_named_route, params: { kind: 'path' }
assert_equal '/url_helper_controller_test/url_helper/show_named_route', @response.body
end
@@ -646,7 +650,7 @@ class UrlHelperControllerTest < ActionController::TestCase
end
end
- get :show_named_route, kind: 'url'
+ get :show_named_route, params: { kind: 'url' }
assert_equal 'http://testtwo.host/url_helper_controller_test/url_helper/show_named_route', @response.body
end
@@ -661,11 +665,11 @@ class UrlHelperControllerTest < ActionController::TestCase
end
def test_recall_params_should_normalize_id
- get :show, id: '123'
+ get :show, params: { id: '123' }
assert_equal 302, @response.status
assert_equal 'http://test.host/url_helper_controller_test/url_helper/profile/123', @response.location
- get :show, name: '123'
+ get :show, params: { name: '123' }
assert_equal 'ok', @response.body
end
@@ -704,7 +708,7 @@ class LinkToUnlessCurrentWithControllerTest < ActionController::TestCase
end
def test_link_to_unless_current_shows_link
- get :show, id: 1
+ get :show, params: { id: 1 }
assert_equal %{<a href="/tasks">tasks</a>\n} +
%{<a href="#{@request.protocol}#{@request.host_with_port}/tasks">tasks</a>},
@response.body
@@ -778,21 +782,21 @@ class PolymorphicControllerTest < ActionController::TestCase
def test_existing_resource
@controller = WorkshopsController.new
- get :show, id: 1
+ get :show, params: { id: 1 }
assert_equal %{/workshops/1\n<a href="/workshops/1">Workshop</a>}, @response.body
end
def test_new_nested_resource
@controller = SessionsController.new
- get :index, workshop_id: 1
+ get :index, params: { workshop_id: 1 }
assert_equal %{/workshops/1/sessions\n<a href="/workshops/1/sessions">Session</a>}, @response.body
end
def test_existing_nested_resource
@controller = SessionsController.new
- get :show, workshop_id: 1, id: 1
+ get :show, params: { workshop_id: 1, id: 1 }
assert_equal %{/workshops/1/sessions/1\n<a href="/workshops/1/sessions/1">Session</a>}, @response.body
end
end
diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md
index b04883413e..85a437a1dd 100644
--- a/activejob/CHANGELOG.md
+++ b/activejob/CHANGELOG.md
@@ -1 +1,84 @@
-* Started project. \ No newline at end of file
+* A generated job now inherents from `app/jobs/application_job.rb` by default.
+
+ *Jeroen van Baarsen*
+
+* Add an `:only` option to `perform_enqueued_jobs` to filter jobs based on
+ type.
+
+ This allows specific jobs to be tested, while preventing others from
+ being performed unnecessarily.
+
+ Example:
+
+ def test_hello_job
+ assert_performed_jobs 1, only: HelloJob do
+ HelloJob.perform_later('jeremy')
+ LoggingJob.perform_later
+ end
+ end
+
+ An array may also be specified, to support testing multiple jobs.
+
+ Example:
+
+ def test_hello_and_logging_jobs
+ assert_nothing_raised do
+ assert_performed_jobs 2, only: [HelloJob, LoggingJob] do
+ HelloJob.perform_later('jeremy')
+ LoggingJob.perform_later('stewie')
+ RescueJob.perform_later('david')
+ end
+ end
+ end
+
+ Fixes #18802.
+
+ *Michael Ryan*
+
+* Allow keyword arguments to be used with Active Job.
+
+ Fixes #18741.
+
+ *Sean Griffin*
+
+* Add `:only` option to `assert_enqueued_jobs`, to check the number of times
+ a specific kind of job is enqueued.
+
+ Example:
+
+ def test_logging_job
+ assert_enqueued_jobs 1, only: LoggingJob do
+ LoggingJob.perform_later
+ HelloJob.perform_later('jeremy')
+ end
+ end
+
+ *George Claghorn*
+
+* `ActiveJob::Base.deserialize` delegates to the job class.
+
+ Since `ActiveJob::Base#deserialize` can be overridden by subclasses (like
+ `ActiveJob::Base#serialize`) this allows jobs to attach arbitrary metadata
+ when they get serialized and read it back when they get performed.
+
+ Example:
+
+ class DeliverWebhookJob < ActiveJob::Base
+ def serialize
+ super.merge('attempt_number' => (@attempt_number || 0) + 1)
+ end
+
+ def deserialize(job_data)
+ super
+ @attempt_number = job_data['attempt_number']
+ end
+
+ rescue_from(TimeoutError) do |exception|
+ raise exception if @attempt_number > 5
+ retry_job(wait: 10)
+ end
+ end
+
+ *Isaac Seymour*
+
+Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activejob/CHANGELOG.md) for previous changes.
diff --git a/activejob/MIT-LICENSE b/activejob/MIT-LICENSE
index 8b1e97b776..0cef8cdda0 100644
--- a/activejob/MIT-LICENSE
+++ b/activejob/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2014 David Heinemeier Hansson
+Copyright (c) 2014-2015 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activejob/README.md b/activejob/README.md
index b5d27272b1..5170ebee6e 100644
--- a/activejob/README.md
+++ b/activejob/README.md
@@ -5,9 +5,9 @@ of queueing 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.
-It also serves as the backend for ActionMailer's #deliver_later functionality
+It also serves as the backend for Action Mailer's #deliver_later functionality
that makes it easy to turn any mailing into a job for running later. That's
-one of the most common jobs in a modern web application: Sending emails outside
+one of the most common jobs in a modern web application: sending emails outside
of the request-response cycle, so the user doesn't have to wait on it.
The main point is to ensure that all Rails apps will have a job infrastructure
@@ -26,7 +26,8 @@ Set the queue adapter for Active Job:
ActiveJob::Base.queue_adapter = :inline # default queue adapter
```
Note: To learn how to use your preferred queueing backend see its adapter
-documentation at ActiveJob::QueueAdapters.
+documentation at
+[ActiveJob::QueueAdapters](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html).
Declare a job like so:
@@ -110,14 +111,14 @@ Source code can be downloaded as part of the Rails project on GitHub
## License
-ActiveJob is released under the MIT license:
+Active Job is released under the MIT license:
* http://www.opensource.org/licenses/MIT
## Support
-API documentation is at
+API documentation is at:
* http://api.rubyonrails.org
@@ -128,5 +129,3 @@ Bug reports can be filed for the Ruby on Rails project here:
Feature requests should be discussed on the rails-core mailing list here:
* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
-
-
diff --git a/activejob/Rakefile b/activejob/Rakefile
index 7e66860b36..6c7e6aae6e 100644
--- a/activejob/Rakefile
+++ b/activejob/Rakefile
@@ -1,7 +1,7 @@
require 'rake/testtask'
require 'rubygems/package_task'
-ACTIVEJOB_ADAPTERS = %w(inline delayed_job qu que queue_classic resque sidekiq sneakers sucker_punch backburner)
+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)
task default: :test
@@ -20,7 +20,7 @@ namespace :test do
desc 'Run integration tests for all adapters'
task :integration do
- run_without_aborting ACTIVEJOB_ADAPTERS.map { |a| "test:integration:#{a}" }
+ run_without_aborting (ACTIVEJOB_ADAPTERS - ['test']).map { |a| "test:integration:#{a}" }
end
task 'env:integration' do
@@ -28,7 +28,7 @@ namespace :test do
end
ACTIVEJOB_ADAPTERS.each do |adapter|
- task("env:#{adapter}") { ENV['AJADAPTER'] = adapter }
+ task("env:#{adapter}") { ENV['AJ_ADAPTER'] = adapter }
Rake::TestTask.new(adapter => "test:env:#{adapter}") do |t|
t.description = "Run adapter tests for #{adapter}"
diff --git a/activejob/activejob.gemspec b/activejob/activejob.gemspec
index a9be2a8f00..ef8db3bcd3 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 = '>= 1.9.3'
+ s.required_ruby_version = '>= 2.2.1'
s.license = 'MIT'
diff --git a/activejob/lib/active_job.rb b/activejob/lib/active_job.rb
index 1b582f5877..3d4f63b261 100644
--- a/activejob/lib/active_job.rb
+++ b/activejob/lib/active_job.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2014 David Heinemeier Hansson
+# Copyright (c) 2014-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb
index e2c076eb3f..622c37098e 100644
--- a/activejob/lib/active_job/arguments.rb
+++ b/activejob/lib/active_job/arguments.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/hash'
+
module ActiveJob
# Raised when an exception is raised during job arguments deserialization.
#
@@ -42,7 +44,9 @@ module ActiveJob
private
GLOBALID_KEY = '_aj_globalid'.freeze
- private_constant :GLOBALID_KEY
+ SYMBOL_KEYS_KEY = '_aj_symbol_keys'.freeze
+ WITH_INDIFFERENT_ACCESS_KEY = '_aj_hash_with_indifferent_access'.freeze
+ private_constant :GLOBALID_KEY, :SYMBOL_KEYS_KEY, :WITH_INDIFFERENT_ACCESS_KEY
def serialize_argument(argument)
case argument
@@ -52,10 +56,15 @@ module ActiveJob
{ GLOBALID_KEY => argument.to_global_id.to_s }
when Array
argument.map { |arg| serialize_argument(arg) }
+ when ActiveSupport::HashWithIndifferentAccess
+ result = serialize_hash(argument)
+ result[WITH_INDIFFERENT_ACCESS_KEY] = serialize_argument(true)
+ result
when Hash
- argument.each_with_object({}) do |(key, value), hash|
- hash[serialize_hash_key(key)] = serialize_argument(value)
- end
+ symbol_keys = argument.each_key.grep(Symbol).map(&:to_s)
+ result = serialize_hash(argument)
+ result[SYMBOL_KEYS_KEY] = symbol_keys
+ result
else
raise SerializationError.new("Unsupported argument type: #{argument.class.name}")
end
@@ -73,7 +82,7 @@ module ActiveJob
if serialized_global_id?(argument)
deserialize_global_id argument
else
- deserialize_hash argument
+ deserialize_hash(argument)
end
else
raise ArgumentError, "Can only deserialize primitive arguments: #{argument.inspect}"
@@ -88,13 +97,27 @@ module ActiveJob
GlobalID::Locator.locate hash[GLOBALID_KEY]
end
+ def serialize_hash(argument)
+ argument.each_with_object({}) do |(key, value), hash|
+ hash[serialize_hash_key(key)] = serialize_argument(value)
+ end
+ end
+
def deserialize_hash(serialized_hash)
- serialized_hash.each_with_object({}.with_indifferent_access) do |(key, value), hash|
- hash[key] = deserialize_argument(value)
+ result = serialized_hash.transform_values { |v| deserialize_argument(v) }
+ if result.delete(WITH_INDIFFERENT_ACCESS_KEY)
+ result = result.with_indifferent_access
+ elsif symbol_keys = result.delete(SYMBOL_KEYS_KEY)
+ result = transform_symbol_keys(result, symbol_keys)
end
+ result
end
- RESERVED_KEYS = [GLOBALID_KEY, GLOBALID_KEY.to_sym]
+ RESERVED_KEYS = [
+ GLOBALID_KEY, GLOBALID_KEY.to_sym,
+ SYMBOL_KEYS_KEY, SYMBOL_KEYS_KEY.to_sym,
+ WITH_INDIFFERENT_ACCESS_KEY, WITH_INDIFFERENT_ACCESS_KEY.to_sym,
+ ]
private_constant :RESERVED_KEYS
def serialize_hash_key(key)
@@ -107,5 +130,15 @@ module ActiveJob
raise SerializationError.new("Only string and symbol hash keys may be serialized as job arguments, but #{key.inspect} is a #{key.class}")
end
end
+
+ def transform_symbol_keys(hash, symbol_keys)
+ hash.transform_keys do |key|
+ if symbol_keys.include?(key)
+ key.to_sym
+ else
+ key
+ end
+ end
+ end
end
end
diff --git a/activejob/lib/active_job/base.rb b/activejob/lib/active_job/base.rb
index 0c4a29090e..fd49b3fda5 100644
--- a/activejob/lib/active_job/base.rb
+++ b/activejob/lib/active_job/base.rb
@@ -32,7 +32,7 @@ module ActiveJob #:nodoc:
# end
#
# Records that are passed in are serialized/deserialized using Global
- # Id. More information can be found in Arguments.
+ # ID. More information can be found in Arguments.
#
# To enqueue a job to be performed as soon the queueing system is free:
#
diff --git a/activejob/lib/active_job/callbacks.rb b/activejob/lib/active_job/callbacks.rb
index c4ceb484cc..2b6149e84e 100644
--- a/activejob/lib/active_job/callbacks.rb
+++ b/activejob/lib/active_job/callbacks.rb
@@ -3,8 +3,8 @@ require 'active_support/callbacks'
module ActiveJob
# = Active Job Callbacks
#
- # Active Job provides hooks during the lifecycle of a job. Callbacks allow you
- # to trigger logic during the lifecycle of a job. Available callbacks are:
+ # Active Job provides hooks during the life cycle of a job. Callbacks allow you
+ # to trigger logic during the life cycle of a job. Available callbacks are:
#
# * <tt>before_enqueue</tt>
# * <tt>around_enqueue</tt>
diff --git a/activejob/lib/active_job/core.rb b/activejob/lib/active_job/core.rb
index a0e55a0028..ddd7d1361c 100644
--- a/activejob/lib/active_job/core.rb
+++ b/activejob/lib/active_job/core.rb
@@ -22,10 +22,8 @@ module ActiveJob
module ClassMethods
# Creates a new job instance from a hash created with +serialize+
def deserialize(job_data)
- job = job_data['job_class'].constantize.new
- job.job_id = job_data['job_id']
- job.queue_name = job_data['queue_name']
- job.serialized_arguments = job_data['arguments']
+ job = job_data['job_class'].constantize.new
+ job.deserialize(job_data)
job
end
@@ -69,6 +67,32 @@ module ActiveJob
}
end
+ # Attaches the stored job data to the current instance. Receives a hash
+ # returned from +serialize+
+ #
+ # ==== Examples
+ #
+ # class DeliverWebhookJob < ActiveJob::Base
+ # def serialize
+ # super.merge('attempt_number' => (@attempt_number || 0) + 1)
+ # end
+ #
+ # def deserialize(job_data)
+ # super
+ # @attempt_number = job_data['attempt_number']
+ # end
+ #
+ # rescue_from(TimeoutError) do |exception|
+ # raise exception if @attempt_number > 5
+ # retry_job(wait: 10)
+ # end
+ # end
+ def deserialize(job_data)
+ self.job_id = job_data['job_id']
+ self.queue_name = job_data['queue_name']
+ self.serialized_arguments = job_data['arguments']
+ end
+
private
def deserialize_arguments_if_needed
if defined?(@serialized_arguments) && @serialized_arguments.present?
diff --git a/activejob/lib/active_job/gem_version.rb b/activejob/lib/active_job/gem_version.rb
index ac364f77d1..27a5de93f4 100644
--- a/activejob/lib/active_job/gem_version.rb
+++ b/activejob/lib/active_job/gem_version.rb
@@ -5,10 +5,10 @@ module ActiveJob
end
module VERSION
- MAJOR = 4
- MINOR = 2
+ MAJOR = 5
+ MINOR = 0
TINY = 0
- PRE = "beta4"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activejob/lib/active_job/logging.rb b/activejob/lib/active_job/logging.rb
index 21d2fda3ff..54774db601 100644
--- a/activejob/lib/active_job/logging.rb
+++ b/activejob/lib/active_job/logging.rb
@@ -81,11 +81,16 @@ module ActiveJob
private
def queue_name(event)
- event.payload[:adapter].name.demodulize.remove('Adapter') + "(#{event.payload[:job].queue_name})"
+ event.payload[:adapter].class.name.demodulize.remove('Adapter') + "(#{event.payload[:job].queue_name})"
end
def args_info(job)
- job.arguments.any? ? " with arguments: #{job.arguments.map(&:inspect).join(", ")}" : ""
+ if job.arguments.any?
+ ' with arguments: ' +
+ job.arguments.map { |arg| arg.try(:to_global_id).try(:to_s) || arg.inspect }.join(', ')
+ else
+ ''
+ end
end
def scheduled_at(event)
diff --git a/activejob/lib/active_job/queue_adapter.rb b/activejob/lib/active_job/queue_adapter.rb
index 85d7c44bb8..9c4519432d 100644
--- a/activejob/lib/active_job/queue_adapter.rb
+++ b/activejob/lib/active_job/queue_adapter.rb
@@ -1,35 +1,61 @@
require 'active_job/queue_adapters/inline_adapter'
+require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/string/inflections'
module ActiveJob
- # The <tt>ActionJob::QueueAdapter</tt> module is used to load the
+ # The <tt>ActiveJob::QueueAdapter</tt> module is used to load the
# correct adapter. The default queue adapter is the :inline queue.
module QueueAdapter #:nodoc:
extend ActiveSupport::Concern
+ included do
+ class_attribute :_queue_adapter, instance_accessor: false, instance_predicate: false
+ self.queue_adapter = :inline
+ end
+
# Includes the setter method for changing the active queue adapter.
module ClassMethods
- mattr_reader(:queue_adapter) { ActiveJob::QueueAdapters::InlineAdapter }
+ def queue_adapter
+ _queue_adapter
+ end
# Specify the backend queue provider. The default queue adapter
# is the :inline queue. See QueueAdapters for more
# information.
- def queue_adapter=(name_or_adapter)
- @@queue_adapter = \
- case name_or_adapter
- when :test
- ActiveJob::QueueAdapters::TestAdapter.new
- when Symbol, String
- load_adapter(name_or_adapter)
- when Class
- name_or_adapter
- end
+ def queue_adapter=(name_or_adapter_or_class)
+ self._queue_adapter = interpret_adapter(name_or_adapter_or_class)
end
private
- def load_adapter(name)
- "ActiveJob::QueueAdapters::#{name.to_s.camelize}Adapter".constantize
+
+ def interpret_adapter(name_or_adapter_or_class)
+ case name_or_adapter_or_class
+ when Symbol, String
+ ActiveJob::QueueAdapters.lookup(name_or_adapter_or_class).new
+ else
+ if queue_adapter?(name_or_adapter_or_class)
+ name_or_adapter_or_class
+ elsif queue_adapter_class?(name_or_adapter_or_class)
+ ActiveSupport::Deprecation.warn "Passing an adapter class is deprecated " \
+ "and will be removed in Rails 5.1. Please pass an adapter name " \
+ "(.queue_adapter = :#{name_or_adapter_or_class.name.demodulize.remove('Adapter').underscore}) " \
+ "or an instance (.queue_adapter = #{name_or_adapter_or_class.name}.new) instead."
+ name_or_adapter_or_class.new
+ else
+ raise ArgumentError
+ end
end
+ end
+
+ QUEUE_ADAPTER_METHODS = [:enqueue, :enqueue_at].freeze
+
+ def queue_adapter?(object)
+ QUEUE_ADAPTER_METHODS.all? { |meth| object.respond_to?(meth) }
+ end
+
+ def queue_adapter_class?(object)
+ object.is_a?(Class) && QUEUE_ADAPTER_METHODS.all? { |meth| object.public_method_defined?(meth) }
+ end
end
end
end
diff --git a/activejob/lib/active_job/queue_adapters.rb b/activejob/lib/active_job/queue_adapters.rb
index f22b0502dc..46caa5c6a5 100644
--- a/activejob/lib/active_job/queue_adapters.rb
+++ b/activejob/lib/active_job/queue_adapters.rb
@@ -19,6 +19,7 @@ module ActiveJob
# |-------------------|-------|--------|-----------|------------|---------|---------|
# | 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 |
@@ -28,11 +29,67 @@ module ActiveJob
# | Active Job Inline | No | Yes | N/A | N/A | N/A | N/A |
# | Active Job | Yes | Yes | Yes | No | No | No |
#
+ # ==== 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 does not support job scheduling.
+ # However, you can use the queue_classic-later gem.
+ # See the documentation for ActiveJob::QueueAdapters::QueueClassicAdapter.
+ #
+ # ==== 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
@@ -47,5 +104,14 @@ module ActiveJob
autoload :SneakersAdapter
autoload :SuckerPunchAdapter
autoload :TestAdapter
+
+ ADAPTER = 'Adapter'.freeze
+ private_constant :ADAPTER
+
+ class << self
+ def lookup(name)
+ const_get(name.to_s.camelize << ADAPTER)
+ end
+ end
end
end
diff --git a/activejob/lib/active_job/queue_adapters/backburner_adapter.rb b/activejob/lib/active_job/queue_adapters/backburner_adapter.rb
index 2453d065de..17703e3e41 100644
--- a/activejob/lib/active_job/queue_adapters/backburner_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/backburner_adapter.rb
@@ -13,15 +13,13 @@ module ActiveJob
#
# Rails.application.config.active_job.queue_adapter = :backburner
class BackburnerAdapter
- class << self
- def enqueue(job) #:nodoc:
- Backburner::Worker.enqueue JobWrapper, [ job.serialize ], queue: job.queue_name
- end
+ def enqueue(job) #:nodoc:
+ Backburner::Worker.enqueue JobWrapper, [ job.serialize ], queue: job.queue_name
+ end
- def enqueue_at(job, timestamp) #:nodoc:
- delay = timestamp - Time.current.to_f
- Backburner::Worker.enqueue JobWrapper, [ job.serialize ], queue: job.queue_name, delay: delay
- end
+ def enqueue_at(job, timestamp) #:nodoc:
+ delay = timestamp - Time.current.to_f
+ Backburner::Worker.enqueue JobWrapper, [ job.serialize ], queue: job.queue_name, delay: delay
end
class JobWrapper #:nodoc:
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 69d9e70de3..852a6ee326 100644
--- a/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb
@@ -13,14 +13,12 @@ module ActiveJob
#
# Rails.application.config.active_job.queue_adapter = :delayed_job
class DelayedJobAdapter
- class << self
- def enqueue(job) #:nodoc:
- Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name)
- end
+ def enqueue(job) #:nodoc:
+ Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name)
+ end
- def enqueue_at(job, timestamp) #:nodoc:
- Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name, run_at: Time.at(timestamp))
- end
+ def enqueue_at(job, timestamp) #:nodoc:
+ Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name, run_at: Time.at(timestamp))
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 08e26b7418..1d06324c18 100644
--- a/activejob/lib/active_job/queue_adapters/inline_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/inline_adapter.rb
@@ -9,14 +9,12 @@ module ActiveJob
#
# Rails.application.config.active_job.queue_adapter = :inline
class InlineAdapter
- class << self
- def enqueue(job) #:nodoc:
- Base.execute(job.serialize)
- end
+ def enqueue(job) #:nodoc:
+ Base.execute(job.serialize)
+ 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/v4.2.0/active_job_basics.html")
- 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")
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 30aa5a4670..94584ef9d8 100644
--- a/activejob/lib/active_job/queue_adapters/qu_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/qu_adapter.rb
@@ -16,16 +16,14 @@ module ActiveJob
#
# Rails.application.config.active_job.queue_adapter = :qu
class QuAdapter
- class << self
- def enqueue(job, *args) #:nodoc:
- Qu::Payload.new(klass: JobWrapper, args: [job.serialize]).tap do |payload|
- payload.instance_variable_set(:@queue, job.queue_name)
- end.push
- end
+ def enqueue(job, *args) #:nodoc:
+ Qu::Payload.new(klass: JobWrapper, args: [job.serialize]).tap do |payload|
+ payload.instance_variable_set(:@queue, job.queue_name)
+ end.push
+ end
- def enqueue_at(job, timestamp, *args) #:nodoc:
- raise NotImplementedError
- end
+ def enqueue_at(job, timestamp, *args) #:nodoc:
+ raise NotImplementedError
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 e501fe0368..84cc2845b0 100644
--- a/activejob/lib/active_job/queue_adapters/que_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/que_adapter.rb
@@ -15,14 +15,12 @@ module ActiveJob
#
# Rails.application.config.active_job.queue_adapter = :que
class QueAdapter
- class << self
- def enqueue(job) #:nodoc:
- JobWrapper.enqueue job.serialize, queue: job.queue_name
- end
+ def enqueue(job) #:nodoc:
+ JobWrapper.enqueue job.serialize, queue: job.queue_name
+ end
- def enqueue_at(job, timestamp) #:nodoc:
- JobWrapper.enqueue job.serialize, queue: job.queue_name, run_at: Time.at(timestamp)
- end
+ def enqueue_at(job, timestamp) #:nodoc:
+ JobWrapper.enqueue job.serialize, queue: job.queue_name, run_at: Time.at(timestamp)
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 34c11a68b2..059754a87f 100644
--- a/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/queue_classic_adapter.rb
@@ -17,29 +17,27 @@ module ActiveJob
#
# Rails.application.config.active_job.queue_adapter = :queue_classic
class QueueClassicAdapter
- class << self
- def enqueue(job) #:nodoc:
- build_queue(job.queue_name).enqueue("#{JobWrapper.name}.perform", job.serialize)
- end
+ def enqueue(job) #:nodoc:
+ build_queue(job.queue_name).enqueue("#{JobWrapper.name}.perform", job.serialize)
+ end
- def enqueue_at(job, timestamp) #:nodoc:
- queue = build_queue(job.queue_name)
- unless queue.respond_to?(:enqueue_at)
- raise NotImplementedError, 'To be able to schedule jobs with queue_classic ' \
- '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)
+ def enqueue_at(job, timestamp) #:nodoc:
+ queue = build_queue(job.queue_name)
+ unless queue.respond_to?(:enqueue_at)
+ raise NotImplementedError, 'To be able to schedule jobs with queue_classic ' \
+ '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)
+ end
- # Builds a <tt>QC::Queue</tt> object to schedule jobs on.
- #
- # If you have a custom <tt>QC::Queue</tt> subclass you'll need to subclass
- # <tt>ActiveJob::QueueAdapters::QueueClassicAdapter</tt> and override the
- # <tt>build_queue</tt> method.
- def build_queue(queue_name)
- QC::Queue.new(queue_name)
- end
+ # Builds a <tt>QC::Queue</tt> object to schedule jobs on.
+ #
+ # If you have a custom <tt>QC::Queue</tt> subclass you'll need to subclass
+ # <tt>ActiveJob::QueueAdapters::QueueClassicAdapter</tt> and override the
+ # <tt>build_queue</tt> method.
+ def build_queue(queue_name)
+ QC::Queue.new(queue_name)
end
class JobWrapper #:nodoc:
diff --git a/activejob/lib/active_job/queue_adapters/resque_adapter.rb b/activejob/lib/active_job/queue_adapters/resque_adapter.rb
index 88c6b48fef..417854afd8 100644
--- a/activejob/lib/active_job/queue_adapters/resque_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/resque_adapter.rb
@@ -26,18 +26,16 @@ module ActiveJob
#
# Rails.application.config.active_job.queue_adapter = :resque
class ResqueAdapter
- class << self
- def enqueue(job) #:nodoc:
- Resque.enqueue_to job.queue_name, JobWrapper, job.serialize
- end
+ def enqueue(job) #:nodoc:
+ Resque.enqueue_to job.queue_name, JobWrapper, job.serialize
+ end
- def enqueue_at(job, timestamp) #:nodoc:
- unless Resque.respond_to?(:enqueue_at_with_queue)
- raise NotImplementedError, "To be able to schedule jobs with Resque you need the " \
- "resque-scheduler gem. Please add it to your Gemfile and run bundle install"
- end
- Resque.enqueue_at_with_queue job.queue_name, timestamp, JobWrapper, job.serialize
+ def enqueue_at(job, timestamp) #:nodoc:
+ unless Resque.respond_to?(:enqueue_at_with_queue)
+ raise NotImplementedError, "To be able to schedule jobs with Resque you need the " \
+ "resque-scheduler gem. Please add it to your Gemfile and run bundle install"
end
+ Resque.enqueue_at_with_queue job.queue_name, timestamp, JobWrapper, job.serialize
end
class JobWrapper #:nodoc:
diff --git a/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb b/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb
index 21005fc728..743d5ea333 100644
--- a/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb
@@ -15,22 +15,22 @@ module ActiveJob
#
# Rails.application.config.active_job.queue_adapter = :sidekiq
class SidekiqAdapter
- class << self
- def enqueue(job) #:nodoc:
- #Sidekiq::Client does not support symbols as keys
- Sidekiq::Client.push \
- 'class' => JobWrapper,
- 'queue' => job.queue_name,
- 'args' => [ job.serialize ]
- end
+ def enqueue(job) #:nodoc:
+ #Sidekiq::Client does not support symbols as keys
+ Sidekiq::Client.push \
+ 'class' => JobWrapper,
+ 'wrapped' => job.class.to_s,
+ 'queue' => job.queue_name,
+ 'args' => [ job.serialize ]
+ end
- def enqueue_at(job, timestamp) #:nodoc:
- Sidekiq::Client.push \
- 'class' => JobWrapper,
- 'queue' => job.queue_name,
- 'args' => [ job.serialize ],
- 'at' => timestamp
- end
+ def enqueue_at(job, timestamp) #:nodoc:
+ Sidekiq::Client.push \
+ 'class' => JobWrapper,
+ 'wrapped' => job.class.to_s,
+ 'queue' => job.queue_name,
+ 'args' => [ job.serialize ],
+ 'at' => timestamp
end
class JobWrapper #:nodoc:
diff --git a/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb b/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb
index 6d60a2f303..f5737487ca 100644
--- a/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/sneakers_adapter.rb
@@ -16,19 +16,19 @@ module ActiveJob
#
# Rails.application.config.active_job.queue_adapter = :sneakers
class SneakersAdapter
- @monitor = Monitor.new
+ def initialize
+ @monitor = Monitor.new
+ end
- class << self
- def enqueue(job) #:nodoc:
- @monitor.synchronize do
- JobWrapper.from_queue job.queue_name
- JobWrapper.enqueue ActiveSupport::JSON.encode(job.serialize)
- end
+ def enqueue(job) #:nodoc:
+ @monitor.synchronize do
+ JobWrapper.from_queue job.queue_name
+ JobWrapper.enqueue ActiveSupport::JSON.encode(job.serialize)
end
+ end
- def enqueue_at(job, timestamp) #:nodoc:
- raise NotImplementedError
- end
+ def enqueue_at(job, timestamp) #:nodoc:
+ raise NotImplementedError
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 be9e7fd03a..64c93e8198 100644
--- a/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb
@@ -18,14 +18,12 @@ module ActiveJob
#
# Rails.application.config.active_job.queue_adapter = :sucker_punch
class SuckerPunchAdapter
- class << self
- def enqueue(job) #:nodoc:
- JobWrapper.new.async.perform job.serialize
- end
+ def enqueue(job) #:nodoc:
+ JobWrapper.new.async.perform job.serialize
+ end
- def enqueue_at(job, timestamp) #:nodoc:
- raise NotImplementedError
- end
+ def enqueue_at(job, timestamp) #:nodoc:
+ raise NotImplementedError
end
class JobWrapper #:nodoc:
diff --git a/activejob/lib/active_job/queue_adapters/test_adapter.rb b/activejob/lib/active_job/queue_adapters/test_adapter.rb
index e4fdf60008..9b7b7139f4 100644
--- a/activejob/lib/active_job/queue_adapters/test_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/test_adapter.rb
@@ -10,8 +10,7 @@ module ActiveJob
#
# Rails.application.config.active_job.queue_adapter = :test
class TestAdapter
- delegate :name, to: :class
- attr_accessor(:perform_enqueued_jobs, :perform_enqueued_at_jobs)
+ attr_accessor(:perform_enqueued_jobs, :perform_enqueued_at_jobs, :filter)
attr_writer(:enqueued_jobs, :performed_jobs)
# Provides a store of all the enqueued jobs with the TestAdapter so you can check them.
@@ -25,22 +24,37 @@ module ActiveJob
end
def enqueue(job) #:nodoc:
- if perform_enqueued_jobs
- performed_jobs << {job: job.class, args: job.arguments, queue: job.queue_name}
- job.perform_now
- else
- enqueued_jobs << {job: job.class, args: job.arguments, queue: job.queue_name}
- end
+ return if filtered?(job)
+
+ job_data = job_to_hash(job)
+ enqueue_or_perform(perform_enqueued_jobs, job, job_data)
end
def enqueue_at(job, timestamp) #:nodoc:
- if perform_enqueued_at_jobs
- performed_jobs << {job: job.class, args: job.arguments, queue: job.queue_name, at: timestamp}
- job.perform_now
+ return if filtered?(job)
+
+ job_data = job_to_hash(job, at: timestamp)
+ enqueue_or_perform(perform_enqueued_at_jobs, job, job_data)
+ end
+
+ private
+
+ def job_to_hash(job, extras = {})
+ { job: job.class, args: job.serialize.fetch('arguments'), queue: job.queue_name }.merge!(extras)
+ end
+
+ def enqueue_or_perform(perform, job, job_data)
+ if perform
+ performed_jobs << job_data
+ Base.execute job.serialize
else
- enqueued_jobs << {job: job.class, args: job.arguments, queue: job.queue_name, at: timestamp}
+ enqueued_jobs << job_data
end
end
+
+ def filtered?(job)
+ filter && !Array(filter).include?(job.class)
+ end
end
end
end
diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb
index bb20d93947..4efb4b72d2 100644
--- a/activejob/lib/active_job/test_helper.rb
+++ b/activejob/lib/active_job/test_helper.rb
@@ -1,3 +1,6 @@
+require 'active_support/core_ext/class/subclasses'
+require 'active_support/core_ext/hash/keys'
+
module ActiveJob
# Provides helper methods for testing Active Job
module TestHelper
@@ -5,8 +8,17 @@ module ActiveJob
included do
def before_setup
- @old_queue_adapter = queue_adapter
- ActiveJob::Base.queue_adapter = :test
+ test_adapter = ActiveJob::QueueAdapters::TestAdapter.new
+
+ @old_queue_adapters = (ActiveJob::Base.subclasses << ActiveJob::Base).select do |klass|
+ # only override explicitly set adapters, a quirk of `class_attribute`
+ klass.singleton_class.public_instance_methods(false).include?(:_queue_adapter)
+ end.map do |klass|
+ [klass, klass.queue_adapter].tap do
+ klass.queue_adapter = test_adapter
+ end
+ end
+
clear_enqueued_jobs
clear_performed_jobs
super
@@ -14,7 +26,9 @@ module ActiveJob
def after_teardown
super
- ActiveJob::Base.queue_adapter = @old_queue_adapter
+ @old_queue_adapters.each do |(klass, adapter)|
+ klass.queue_adapter = adapter
+ end
end
# Asserts that the number of enqueued jobs matches the given number.
@@ -40,16 +54,24 @@ module ActiveJob
# HelloJob.perform_later('rafael')
# end
# end
- def assert_enqueued_jobs(number)
+ #
+ # The number of times a specific job is enqueued can be asserted.
+ #
+ # def test_logging_job
+ # assert_enqueued_jobs 2, only: LoggingJob do
+ # LoggingJob.perform_later
+ # HelloJob.perform_later('jeremy')
+ # end
+ # end
+ def assert_enqueued_jobs(number, only: nil)
if block_given?
- original_count = enqueued_jobs.size
+ original_count = enqueued_jobs_size(only: only)
yield
- new_count = enqueued_jobs.size
- assert_equal original_count + number, new_count,
- "#{number} jobs expected, but #{new_count - original_count} were enqueued"
+ new_count = enqueued_jobs_size(only: only)
+ assert_equal original_count + number, new_count, "#{number} jobs expected, but #{new_count - original_count} were enqueued"
else
- enqueued_jobs_size = enqueued_jobs.size
- assert_equal number, enqueued_jobs_size, "#{number} jobs expected, but #{enqueued_jobs_size} were enqueued"
+ actual_count = enqueued_jobs_size(only: only)
+ assert_equal number, actual_count, "#{number} jobs expected, but #{actual_count} were enqueued"
end
end
@@ -69,21 +91,37 @@ module ActiveJob
# end
# end
#
+ # It can be asserted that no jobs of a specific kind are enqueued:
+ #
+ # def test_no_logging
+ # assert_no_enqueued_jobs only: LoggingJob do
+ # HelloJob.perform_later('jeremy')
+ # end
+ # end
+ #
# Note: This assertion is simply a shortcut for:
#
# assert_enqueued_jobs 0, &block
- def assert_no_enqueued_jobs(&block)
- assert_enqueued_jobs 0, &block
+ def assert_no_enqueued_jobs(only: nil, &block)
+ assert_enqueued_jobs 0, only: only, &block
end
# Asserts that the number of performed jobs matches the given number.
+ # If no block is passed, <tt>perform_enqueued_jobs</tt>
+ # must be called around the job call.
#
# def test_jobs
# assert_performed_jobs 0
- # HelloJob.perform_later('xavier')
+ #
+ # perform_enqueued_jobs do
+ # HelloJob.perform_later('xavier')
+ # end
# assert_performed_jobs 1
- # HelloJob.perform_later('yves')
- # assert_performed_jobs 2
+ #
+ # perform_enqueued_jobs do
+ # HelloJob.perform_later('yves')
+ # assert_performed_jobs 2
+ # end
# end
#
# If a block is passed, that block should cause the specified number of
@@ -99,10 +137,32 @@ module ActiveJob
# HelloJob.perform_later('sean')
# end
# end
- def assert_performed_jobs(number)
+ #
+ # The block form supports filtering. If the :only option is specified,
+ # then only the listed job(s) will be performed.
+ #
+ # def test_hello_job
+ # assert_performed_jobs 1, only: HelloJob do
+ # HelloJob.perform_later('jeremy')
+ # LoggingJob.perform_later
+ # end
+ # end
+ #
+ # An array may also be specified, to support testing multiple jobs.
+ #
+ # def test_hello_and_logging_jobs
+ # assert_nothing_raised do
+ # assert_performed_jobs 2, only: [HelloJob, LoggingJob] do
+ # HelloJob.perform_later('jeremy')
+ # LoggingJob.perform_later('stewie')
+ # RescueJob.perform_later('david')
+ # end
+ # end
+ # end
+ def assert_performed_jobs(number, only: nil)
if block_given?
original_count = performed_jobs.size
- yield
+ perform_enqueued_jobs(only: only) { yield }
new_count = performed_jobs.size
assert_equal original_count + number, new_count,
"#{number} jobs expected, but #{new_count - original_count} were performed"
@@ -116,8 +176,11 @@ module ActiveJob
#
# def test_jobs
# assert_no_performed_jobs
- # HelloJob.perform_later('matthew')
- # assert_performed_jobs 1
+ #
+ # perform_enqueued_jobs do
+ # HelloJob.perform_later('matthew')
+ # assert_performed_jobs 1
+ # end
# end
#
# If a block is passed, that block should not cause any job to be performed.
@@ -128,11 +191,33 @@ module ActiveJob
# end
# end
#
+ # The block form supports filtering. If the :only option is specified,
+ # then only the listed job(s) will be performed.
+ #
+ # def test_hello_job
+ # assert_performed_jobs 1, only: HelloJob do
+ # HelloJob.perform_later('jeremy')
+ # LoggingJob.perform_later
+ # end
+ # end
+ #
+ # An array may also be specified, to support testing multiple jobs.
+ #
+ # def test_hello_and_logging_jobs
+ # assert_nothing_raised do
+ # assert_performed_jobs 2, only: [HelloJob, LoggingJob] do
+ # HelloJob.perform_later('jeremy')
+ # LoggingJob.perform_later('stewie')
+ # RescueJob.perform_later('david')
+ # end
+ # end
+ # end
+ #
# Note: This assertion is simply a shortcut for:
#
# assert_performed_jobs 0, &block
- def assert_no_performed_jobs(&block)
- assert_performed_jobs 0, &block
+ def assert_no_performed_jobs(only: nil, &block)
+ assert_performed_jobs 0, only: only, &block
end
# Asserts that the job passed in the block has been enqueued with the given arguments.
@@ -146,9 +231,10 @@ module ActiveJob
original_enqueued_jobs = enqueued_jobs.dup
clear_enqueued_jobs
args.assert_valid_keys(:job, :args, :at, :queue)
+ serialized_args = serialize_args_for_assertion(args)
yield
matching_job = enqueued_jobs.any? do |job|
- args.all? { |key, value| value == job[key] }
+ serialized_args.all? { |key, value| value == job[key] }
end
assert matching_job, "No enqueued job found with #{args}"
ensure
@@ -166,15 +252,33 @@ module ActiveJob
original_performed_jobs = performed_jobs.dup
clear_performed_jobs
args.assert_valid_keys(:job, :args, :at, :queue)
- yield
+ serialized_args = serialize_args_for_assertion(args)
+ perform_enqueued_jobs { yield }
matching_job = performed_jobs.any? do |job|
- args.all? { |key, value| value == job[key] }
+ serialized_args.all? { |key, value| value == job[key] }
end
assert matching_job, "No performed job found with #{args}"
ensure
queue_adapter.performed_jobs = original_performed_jobs + performed_jobs
end
+ def perform_enqueued_jobs(only: nil)
+ old_perform_enqueued_jobs = queue_adapter.perform_enqueued_jobs
+ old_perform_enqueued_at_jobs = queue_adapter.perform_enqueued_at_jobs
+ old_filter = queue_adapter.filter
+
+ begin
+ queue_adapter.perform_enqueued_jobs = true
+ queue_adapter.perform_enqueued_at_jobs = true
+ queue_adapter.filter = only
+ yield
+ ensure
+ queue_adapter.perform_enqueued_jobs = old_perform_enqueued_jobs
+ queue_adapter.perform_enqueued_at_jobs = old_perform_enqueued_at_jobs
+ queue_adapter.filter = old_filter
+ end
+ end
+
def queue_adapter
ActiveJob::Base.queue_adapter
end
@@ -191,6 +295,22 @@ module ActiveJob
def clear_performed_jobs
performed_jobs.clear
end
+
+ def enqueued_jobs_size(only: nil)
+ if only
+ enqueued_jobs.select { |job| job.fetch(:job) == only }.size
+ else
+ enqueued_jobs.size
+ end
+ end
+
+ def serialize_args_for_assertion(args)
+ serialized_args = args.dup
+ if job_args = serialized_args.delete(:args)
+ serialized_args[:args] = ActiveJob::Arguments.serialize(job_args)
+ end
+ serialized_args
+ 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 979ffcb748..86e4c5266c 100644
--- a/activejob/lib/rails/generators/job/job_generator.rb
+++ b/activejob/lib/rails/generators/job/job_generator.rb
@@ -18,7 +18,6 @@ module Rails
def create_job_file
template 'job.rb', File.join('app/jobs', class_path, "#{file_name}_job.rb")
end
-
end
end
end
diff --git a/activejob/lib/rails/generators/job/templates/job.rb b/activejob/lib/rails/generators/job/templates/job.rb
index 462c71d917..4ad2914a45 100644
--- a/activejob/lib/rails/generators/job/templates/job.rb
+++ b/activejob/lib/rails/generators/job/templates/job.rb
@@ -1,5 +1,5 @@
<% module_namespacing do -%>
-class <%= class_name %>Job < ActiveJob::Base
+class <%= class_name %>Job < ApplicationJob
queue_as :<%= options[:queue] %>
def perform(*args)
diff --git a/activejob/test/adapters/test.rb b/activejob/test/adapters/test.rb
new file mode 100644
index 0000000000..7180b38a57
--- /dev/null
+++ b/activejob/test/adapters/test.rb
@@ -0,0 +1,3 @@
+ActiveJob::Base.queue_adapter = :test
+ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
+ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true
diff --git a/activejob/test/cases/adapter_test.rb b/activejob/test/cases/adapter_test.rb
index 4fc235ae40..6d75ae9a7c 100644
--- a/activejob/test/cases/adapter_test.rb
+++ b/activejob/test/cases/adapter_test.rb
@@ -1,8 +1,7 @@
require 'helper'
class AdapterTest < ActiveSupport::TestCase
- test "should load #{ENV['AJADAPTER']} adapter" do
- ActiveJob::Base.queue_adapter = ENV['AJADAPTER'].to_sym
- assert_equal ActiveJob::Base.queue_adapter, "active_job/queue_adapters/#{ENV['AJADAPTER']}_adapter".classify.constantize
+ test "should load #{ENV['AJ_ADAPTER']} adapter" do
+ assert_equal "active_job/queue_adapters/#{ENV['AJ_ADAPTER']}_adapter".classify, ActiveJob::Base.queue_adapter.class.name
end
end
diff --git a/activejob/test/cases/argument_serialization_test.rb b/activejob/test/cases/argument_serialization_test.rb
index dbe36fc572..8b9b62190f 100644
--- a/activejob/test/cases/argument_serialization_test.rb
+++ b/activejob/test/cases/argument_serialization_test.rb
@@ -2,6 +2,7 @@ require 'helper'
require 'active_job/arguments'
require 'models/person'
require 'active_support/core_ext/hash/indifferent_access'
+require 'jobs/kwargs_job'
class ArgumentSerializationTest < ActiveSupport::TestCase
setup do
@@ -31,16 +32,26 @@ class ArgumentSerializationTest < ActiveSupport::TestCase
end
test 'should convert records to Global IDs' do
- assert_arguments_roundtrip [@person], ['_aj_globalid' => @person.to_gid.to_s]
+ assert_arguments_roundtrip [@person]
end
test 'should dive deep into arrays and hashes' do
- assert_arguments_roundtrip [3, [@person]], [3, ['_aj_globalid' => @person.to_gid.to_s]]
- assert_arguments_roundtrip [{ 'a' => @person }], [{ 'a' => { '_aj_globalid' => @person.to_gid.to_s }}.with_indifferent_access]
+ assert_arguments_roundtrip [3, [@person]]
+ assert_arguments_roundtrip [{ 'a' => @person }]
end
- test 'should stringify symbol hash keys' do
- assert_equal [ 'a' => 1 ], ActiveJob::Arguments.serialize([ a: 1 ])
+ test 'should maintain string and symbol keys' do
+ assert_arguments_roundtrip([a: 1, "b" => 2])
+ end
+
+ test 'should maintain hash with indifferent access' do
+ symbol_key = { a: 1 }
+ string_key = { 'a' => 1 }
+ indifferent_access = { a: 1 }.with_indifferent_access
+
+ assert_not_instance_of ActiveSupport::HashWithIndifferentAccess, perform_round_trip([symbol_key]).first
+ assert_not_instance_of ActiveSupport::HashWithIndifferentAccess, perform_round_trip([string_key]).first
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, perform_round_trip([indifferent_access]).first
end
test 'should disallow non-string/symbol hash keys' do
@@ -71,14 +82,22 @@ class ArgumentSerializationTest < ActiveSupport::TestCase
end
end
+ test 'allows for keyword arguments' do
+ KwargsJob.perform_later(argument: 2)
+
+ assert_equal "Job with argument: 2", JobBuffer.last_value
+ end
+
private
def assert_arguments_unchanged(*args)
- assert_arguments_roundtrip args, args
+ assert_arguments_roundtrip args
+ end
+
+ def assert_arguments_roundtrip(args)
+ assert_equal args, perform_round_trip(args)
end
- def assert_arguments_roundtrip(args, expected_serialized_args)
- serialized = ActiveJob::Arguments.serialize(args)
- assert_equal expected_serialized_args, serialized
- assert_equal args, ActiveJob::Arguments.deserialize(serialized)
+ def perform_round_trip(args)
+ ActiveJob::Arguments.deserialize(ActiveJob::Arguments.serialize(args))
end
end
diff --git a/activejob/test/cases/logging_test.rb b/activejob/test/cases/logging_test.rb
index 3d4e561117..b18be553ec 100644
--- a/activejob/test/cases/logging_test.rb
+++ b/activejob/test/cases/logging_test.rb
@@ -4,8 +4,9 @@ require 'active_support/core_ext/numeric/time'
require 'jobs/hello_job'
require 'jobs/logging_job'
require 'jobs/nested_job'
+require 'models/person'
-class AdapterTest < ActiveSupport::TestCase
+class LoggingTest < ActiveSupport::TestCase
include ActiveSupport::LogSubscriber::TestHelper
include ActiveSupport::Logger::Severity
@@ -65,6 +66,14 @@ class AdapterTest < ActiveSupport::TestCase
LoggingJob.queue_name = original_queue_name
end
+ def test_globalid_parameter_logging
+ person = Person.new(123)
+ LoggingJob.perform_later person
+ assert_match(%r{Enqueued.*gid://aj/Person/123}, @logger.messages)
+ assert_match(%r{Dummy, here is it: #<Person:.*>}, @logger.messages)
+ assert_match(%r{Performing.*gid://aj/Person/123}, @logger.messages)
+ end
+
def test_enqueue_job_logging
HelloJob.perform_later "Cristian"
assert_match(/Enqueued HelloJob \(Job ID: .*?\) to .*?:.*Cristian/, @logger.messages)
diff --git a/activejob/test/cases/queue_adapter_test.rb b/activejob/test/cases/queue_adapter_test.rb
new file mode 100644
index 0000000000..fb3fdc392f
--- /dev/null
+++ b/activejob/test/cases/queue_adapter_test.rb
@@ -0,0 +1,56 @@
+require 'helper'
+
+module ActiveJob
+ module QueueAdapters
+ class StubOneAdapter
+ def enqueue(*); end
+ def enqueue_at(*); end
+ end
+
+ class StubTwoAdapter
+ def enqueue(*); end
+ def enqueue_at(*); end
+ end
+ end
+end
+
+class QueueAdapterTest < ActiveJob::TestCase
+ test 'should forbid nonsense arguments' do
+ assert_raises(ArgumentError) { ActiveJob::Base.queue_adapter = Mutex }
+ assert_raises(ArgumentError) { ActiveJob::Base.queue_adapter = Mutex.new }
+ end
+
+ test 'should warn on passing an adapter class' do
+ klass = Class.new do
+ def self.name
+ 'fake'
+ end
+
+ def enqueue(*); end
+ def enqueue_at(*); end
+ end
+
+ assert_deprecated { ActiveJob::Base.queue_adapter = klass }
+ end
+
+ test 'should allow overriding the queue_adapter at the child class level without affecting the parent or its sibling' do
+ base_queue_adapter = ActiveJob::Base.queue_adapter
+
+ child_job_one = Class.new(ActiveJob::Base)
+ child_job_one.queue_adapter = :stub_one
+
+ assert_not_equal ActiveJob::Base.queue_adapter, child_job_one.queue_adapter
+ assert_kind_of ActiveJob::QueueAdapters::StubOneAdapter, child_job_one.queue_adapter
+
+ child_job_two = Class.new(ActiveJob::Base)
+ child_job_two.queue_adapter = :stub_two
+
+ assert_kind_of ActiveJob::QueueAdapters::StubTwoAdapter, child_job_two.queue_adapter
+ assert_kind_of ActiveJob::QueueAdapters::StubOneAdapter, child_job_one.queue_adapter, "child_job_one's queue adapter should remain unchanged"
+ assert_equal base_queue_adapter, ActiveJob::Base.queue_adapter, "ActiveJob::Base's queue adapter should remain unchanged"
+
+ child_job_three = Class.new(ActiveJob::Base)
+
+ assert_not_nil child_job_three.queue_adapter
+ end
+end
diff --git a/activejob/test/cases/test_case_test.rb b/activejob/test/cases/test_case_test.rb
index 1d0fdbd22d..0a3a20d5a0 100644
--- a/activejob/test/cases/test_case_test.rb
+++ b/activejob/test/cases/test_case_test.rb
@@ -4,11 +4,20 @@ require 'jobs/logging_job'
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
+ def self.queue_adapter=(*)
+ raise 'Attemping to break `class_attribute` inheritence, bad!'
+ end
+ end
+
def test_include_helper
assert_includes self.class.ancestors, ActiveJob::TestHelper
end
def test_set_test_adapter
- assert_instance_of ActiveJob::QueueAdapters::TestAdapter, self.queue_adapter
+ assert_kind_of ActiveJob::QueueAdapters::TestAdapter, self.queue_adapter
end
end
diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb
index 71c505a65f..19a2820a6e 100644
--- a/activejob/test/cases/test_helper_test.rb
+++ b/activejob/test/cases/test_helper_test.rb
@@ -4,10 +4,10 @@ require 'active_support/core_ext/date'
require 'jobs/hello_job'
require 'jobs/logging_job'
require 'jobs/nested_job'
+require 'jobs/rescue_job'
+require 'models/person'
class EnqueuedJobsTest < ActiveJob::TestCase
- setup { queue_adapter.perform_enqueued_at_jobs = true }
-
def test_assert_enqueued_jobs
assert_nothing_raised do
assert_enqueued_jobs 1 do
@@ -44,11 +44,16 @@ class EnqueuedJobsTest < ActiveJob::TestCase
end
end
+ def test_assert_no_enqueued_jobs_with_no_block
+ assert_nothing_raised do
+ assert_no_enqueued_jobs
+ end
+ end
+
def test_assert_no_enqueued_jobs
assert_nothing_raised do
assert_no_enqueued_jobs do
- # Scheduled jobs are being performed in this context
- HelloJob.set(wait_until: Date.tomorrow.noon).perform_later('godfrey')
+ HelloJob.perform_now
end
end
end
@@ -84,9 +89,68 @@ class EnqueuedJobsTest < ActiveJob::TestCase
assert_match(/0 .* but 1/, error.message)
end
+ def test_assert_enqueued_jobs_with_only_option
+ assert_nothing_raised do
+ assert_enqueued_jobs 1, only: HelloJob do
+ HelloJob.perform_later('jeremy')
+ LoggingJob.perform_later
+ end
+ end
+ end
+
+ def test_assert_enqueued_jobs_with_only_option_and_none_sent
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_enqueued_jobs 1, only: HelloJob do
+ LoggingJob.perform_later
+ end
+ end
+
+ assert_match(/1 .* but 0/, error.message)
+ end
+
+ def test_assert_enqueued_jobs_with_only_option_and_too_few_sent
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_enqueued_jobs 5, only: HelloJob do
+ HelloJob.perform_later('jeremy')
+ 4.times { LoggingJob.perform_later }
+ end
+ end
+
+ assert_match(/5 .* but 1/, error.message)
+ end
+
+ def test_assert_enqueued_jobs_with_only_option_and_too_many_sent
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_enqueued_jobs 1, only: HelloJob do
+ 2.times { HelloJob.perform_later('jeremy') }
+ end
+ end
+
+ assert_match(/1 .* but 2/, error.message)
+ end
+
+ def test_assert_no_enqueued_jobs_with_only_option
+ assert_nothing_raised do
+ assert_no_enqueued_jobs only: HelloJob do
+ LoggingJob.perform_later
+ end
+ end
+ end
+
+ def test_assert_no_enqueued_jobs_with_only_option_failure
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_no_enqueued_jobs only: HelloJob do
+ HelloJob.perform_later('jeremy')
+ LoggingJob.perform_later
+ end
+ end
+
+ assert_match(/0 .* but 1/, error.message)
+ end
+
def test_assert_enqueued_job
assert_enqueued_with(job: LoggingJob, queue: 'default') do
- NestedJob.set(wait_until: Date.tomorrow.noon).perform_later
+ LoggingJob.set(wait_until: Date.tomorrow.noon).perform_later
end
end
@@ -113,10 +177,35 @@ class EnqueuedJobsTest < ActiveJob::TestCase
end
end
end
+
+ def test_assert_enqueued_job_with_global_id_args
+ ricardo = Person.new(9)
+ assert_enqueued_with(job: HelloJob, args: [ricardo]) do
+ HelloJob.perform_later(ricardo)
+ end
+ end
+
+ def test_assert_enqueued_job_failure_with_global_id_args
+ ricardo = Person.new(9)
+ wilma = Person.new(11)
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_enqueued_with(job: HelloJob, args: [wilma]) do
+ HelloJob.perform_later(ricardo)
+ end
+ end
+
+ assert_equal "No enqueued job found with {:job=>HelloJob, :args=>[#{wilma.inspect}]}", error.message
+ end
end
class PerformedJobsTest < ActiveJob::TestCase
- setup { queue_adapter.perform_enqueued_jobs = true }
+ def test_performed_enqueue_jobs_with_only_option_doesnt_leak_outside_the_block
+ assert_equal nil, queue_adapter.filter
+ perform_enqueued_jobs only: HelloJob do
+ assert_equal HelloJob, queue_adapter.filter
+ end
+ assert_equal nil, queue_adapter.filter
+ end
def test_assert_performed_jobs
assert_nothing_raised do
@@ -143,22 +232,31 @@ class PerformedJobsTest < ActiveJob::TestCase
def test_assert_performed_jobs_with_no_block
assert_nothing_raised do
- HelloJob.perform_later('rafael')
+ perform_enqueued_jobs do
+ HelloJob.perform_later('rafael')
+ end
assert_performed_jobs 1
end
assert_nothing_raised do
- HelloJob.perform_later('aaron')
- HelloJob.perform_later('matthew')
- assert_performed_jobs 3
+ perform_enqueued_jobs do
+ HelloJob.perform_later('aaron')
+ HelloJob.perform_later('matthew')
+ assert_performed_jobs 3
+ end
+ end
+ end
+
+ def test_assert_no_performed_jobs_with_no_block
+ assert_nothing_raised do
+ assert_no_performed_jobs
end
end
def test_assert_no_performed_jobs
assert_nothing_raised do
assert_no_performed_jobs do
- # Scheduled jobs are being enqueued in this context
- HelloJob.set(wait_until: Date.tomorrow.noon).perform_later('godfrey')
+ # empty block won't perform jobs
end
end
end
@@ -194,6 +292,83 @@ class PerformedJobsTest < ActiveJob::TestCase
assert_match(/0 .* but 1/, error.message)
end
+ def test_assert_performed_jobs_with_only_option
+ assert_nothing_raised do
+ assert_performed_jobs 1, only: HelloJob do
+ HelloJob.perform_later('jeremy')
+ LoggingJob.perform_later
+ end
+ end
+ end
+
+ def test_assert_performed_jobs_with_only_option_as_array
+ assert_nothing_raised do
+ assert_performed_jobs 2, only: [HelloJob, LoggingJob] do
+ HelloJob.perform_later('jeremy')
+ LoggingJob.perform_later('stewie')
+ RescueJob.perform_later('david')
+ end
+ end
+ end
+
+ def test_assert_performed_jobs_with_only_option_and_none_sent
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_performed_jobs 1, only: HelloJob do
+ LoggingJob.perform_later
+ end
+ end
+
+ assert_match(/1 .* but 0/, error.message)
+ end
+
+ def test_assert_performed_jobs_with_only_option_and_too_few_sent
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_performed_jobs 5, only: HelloJob do
+ HelloJob.perform_later('jeremy')
+ 4.times { LoggingJob.perform_later }
+ end
+ end
+
+ assert_match(/5 .* but 1/, error.message)
+ end
+
+ def test_assert_performed_jobs_with_only_option_and_too_many_sent
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_performed_jobs 1, only: HelloJob do
+ 2.times { HelloJob.perform_later('jeremy') }
+ end
+ end
+
+ assert_match(/1 .* but 2/, error.message)
+ end
+
+ def test_assert_no_performed_jobs_with_only_option
+ assert_nothing_raised do
+ assert_no_performed_jobs only: HelloJob do
+ LoggingJob.perform_later
+ end
+ end
+ end
+
+ def test_assert_no_performed_jobs_with_only_option_as_array
+ assert_nothing_raised do
+ assert_no_performed_jobs only: [HelloJob, RescueJob] do
+ LoggingJob.perform_later
+ end
+ end
+ end
+
+ def test_assert_no_performed_jobs_with_only_option_failure
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_no_performed_jobs only: HelloJob do
+ HelloJob.perform_later('jeremy')
+ LoggingJob.perform_later
+ end
+ end
+
+ assert_match(/0 .* but 1/, error.message)
+ end
+
def test_assert_performed_job
assert_performed_with(job: NestedJob, queue: 'default') do
NestedJob.perform_later
@@ -213,4 +388,23 @@ class PerformedJobsTest < ActiveJob::TestCase
end
end
end
+
+ def test_assert_performed_job_with_global_id_args
+ ricardo = Person.new(9)
+ assert_performed_with(job: HelloJob, args: [ricardo]) do
+ HelloJob.perform_later(ricardo)
+ end
+ end
+
+ def test_assert_performed_job_failure_with_global_id_args
+ ricardo = Person.new(9)
+ wilma = Person.new(11)
+ error = assert_raise ActiveSupport::TestCase::Assertion do
+ assert_performed_with(job: HelloJob, args: [wilma]) do
+ HelloJob.perform_later(ricardo)
+ end
+ end
+
+ assert_equal "No performed job found with {:job=>HelloJob, :args=>[#{wilma.inspect}]}", error.message
+ end
end
diff --git a/activejob/test/helper.rb b/activejob/test/helper.rb
index ec0c8a8ede..72ec2b8904 100644
--- a/activejob/test/helper.rb
+++ b/activejob/test/helper.rb
@@ -5,18 +5,7 @@ require 'support/job_buffer'
GlobalID.app = 'aj'
-@adapter = ENV['AJADAPTER'] || 'inline'
-
-def sidekiq?
- @adapter == 'sidekiq'
-end
-
-def ruby_193?
- RUBY_VERSION == '1.9.3' && RUBY_ENGINE != 'java'
-end
-
-# Sidekiq doesn't work with MRI 1.9.3
-exit if sidekiq? && ruby_193?
+@adapter = ENV['AJ_ADAPTER'] || 'inline'
if ENV['AJ_INTEGRATION_TESTS']
require 'support/integration/helper'
diff --git a/activejob/test/integration/queuing_test.rb b/activejob/test/integration/queuing_test.rb
index 38874b51a8..af19a92118 100644
--- a/activejob/test/integration/queuing_test.rb
+++ b/activejob/test/integration/queuing_test.rb
@@ -1,5 +1,6 @@
require 'helper'
require 'jobs/logging_job'
+require 'jobs/hello_job'
require 'active_support/core_ext/numeric/time'
class QueuingTest < ActiveSupport::TestCase
@@ -23,6 +24,18 @@ class QueuingTest < ActiveSupport::TestCase
end
end
+ 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
+ assert_equal "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper", hash['class']
+ assert_equal "HelloJob", hash['wrapped']
+ end
+ end
+
test 'should not run job enqueued in the future' do
begin
TestJob.set(wait: 10.minutes).perform_later @id
diff --git a/activejob/test/jobs/kwargs_job.rb b/activejob/test/jobs/kwargs_job.rb
new file mode 100644
index 0000000000..2df17d15ae
--- /dev/null
+++ b/activejob/test/jobs/kwargs_job.rb
@@ -0,0 +1,7 @@
+require_relative '../support/job_buffer'
+
+class KwargsJob < ActiveJob::Base
+ def perform(argument: 1)
+ JobBuffer.add("Job with argument: #{argument}")
+ end
+end
diff --git a/activejob/test/support/delayed_job/delayed/backend/test.rb b/activejob/test/support/delayed_job/delayed/backend/test.rb
index b50ed36fc2..f80ec3a5a6 100644
--- a/activejob/test/support/delayed_job/delayed/backend/test.rb
+++ b/activejob/test/support/delayed_job/delayed/backend/test.rb
@@ -43,9 +43,7 @@ module Delayed
end
def self.create(attrs = {})
- new(attrs).tap do |o|
- o.save
- end
+ new(attrs).tap(&:save)
end
def self.create!(*args); create(*args); end
diff --git a/activejob/test/support/integration/adapters/qu.rb b/activejob/test/support/integration/adapters/qu.rb
index 3a5b66a057..256ddb3cf3 100644
--- a/activejob/test/support/integration/adapters/qu.rb
+++ b/activejob/test/support/integration/adapters/qu.rb
@@ -3,7 +3,7 @@ module QuJobsManager
require 'qu-rails'
require 'qu-redis'
ActiveJob::Base.queue_adapter = :qu
- ENV['REDISTOGO_URL'] = "tcp://127.0.0.1:6379/12"
+ ENV['REDISTOGO_URL'] = "redis://127.0.0.1:6379/12"
backend = Qu::Backend::Redis.new
backend.namespace = "active_jobs_int_test"
Qu.backend = backend
diff --git a/activejob/test/support/integration/adapters/queue_classic.rb b/activejob/test/support/integration/adapters/queue_classic.rb
index 038473ccdc..f522b2711f 100644
--- a/activejob/test/support/integration/adapters/queue_classic.rb
+++ b/activejob/test/support/integration/adapters/queue_classic.rb
@@ -1,6 +1,7 @@
module QueueClassicJobsManager
def setup
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']
@@ -20,7 +21,8 @@ module QueueClassicJobsManager
end
def start_workers
- QC::Conn.disconnect
+ QC.default_conn_adapter.disconnect
+ QC.default_conn_adapter = nil
@pid = fork do
worker = QC::Worker.new(q_name: 'integration_tests')
worker.start
diff --git a/activejob/test/support/integration/adapters/resque.rb b/activejob/test/support/integration/adapters/resque.rb
index 82acb17b2b..912f4bc387 100644
--- a/activejob/test/support/integration/adapters/resque.rb
+++ b/activejob/test/support/integration/adapters/resque.rb
@@ -1,7 +1,7 @@
module ResqueJobsManager
def setup
ActiveJob::Base.queue_adapter = :resque
- Resque.redis = Redis::Namespace.new 'active_jobs_int_test', redis: Redis.connect(url: "tcp://127.0.0.1:6379/12", :thread_safe => true)
+ Resque.redis = Redis::Namespace.new 'active_jobs_int_test', redis: Redis.connect(url: "redis://127.0.0.1:6379/12", :thread_safe => true)
Resque.logger = Rails.logger
unless can_run?
puts "Cannot run integration tests for resque. To be able to run integration tests for resque you need to install and start redis.\n"
diff --git a/activejob/test/support/integration/adapters/sidekiq.rb b/activejob/test/support/integration/adapters/sidekiq.rb
index 3cc9a34993..6ff18fb56a 100644
--- a/activejob/test/support/integration/adapters/sidekiq.rb
+++ b/activejob/test/support/integration/adapters/sidekiq.rb
@@ -48,7 +48,8 @@ module SidekiqJobsManager
def can_run?
begin
- Sidekiq.redis { |conn| conn.connect }
+ Sidekiq.redis(&:info)
+ Sidekiq.logger = nil
rescue
return false
end
diff --git a/activejob/test/support/integration/dummy_app_template.rb b/activejob/test/support/integration/dummy_app_template.rb
index 65994d6a1c..09a68738ad 100644
--- a/activejob/test/support/integration/dummy_app_template.rb
+++ b/activejob/test/support/integration/dummy_app_template.rb
@@ -1,4 +1,4 @@
-if ENV['AJADAPTER'] == 'delayed_job'
+if ENV['AJ_ADAPTER'] == 'delayed_job'
generate "delayed_job:active_record", "--quiet"
rake("db:migrate")
end
diff --git a/activejob/test/support/integration/helper.rb b/activejob/test/support/integration/helper.rb
index 9bd45e09e8..8c2e5a86c2 100644
--- a/activejob/test/support/integration/helper.rb
+++ b/activejob/test/support/integration/helper.rb
@@ -1,4 +1,4 @@
-puts "*** rake aj:integration:#{ENV['AJADAPTER']} ***\n"
+puts "*** rake aj:integration:#{ENV['AJ_ADAPTER']} ***\n"
ENV["RAILS_ENV"] = "test"
ActiveJob::Base.queue_name_prefix = nil
@@ -20,7 +20,7 @@ require 'rails/test_help'
Rails.backtrace_cleaner.remove_silencers!
require_relative 'test_case_helpers'
-ActiveSupport::TestCase.send(:include, TestCaseHelpers)
+ActiveSupport::TestCase.include(TestCaseHelpers)
JobsManager.current_manager.start_workers
diff --git a/activejob/test/support/integration/jobs_manager.rb b/activejob/test/support/integration/jobs_manager.rb
index 4df34aaeb1..78d48e8d9a 100644
--- a/activejob/test/support/integration/jobs_manager.rb
+++ b/activejob/test/support/integration/jobs_manager.rb
@@ -3,7 +3,7 @@ class JobsManager
attr :adapter_name
def self.current_manager
- @@managers[ENV['AJADAPTER']] ||= new(ENV['AJADAPTER'])
+ @@managers[ENV['AJ_ADAPTER']] ||= new(ENV['AJ_ADAPTER'])
end
def initialize(adapter_name)
diff --git a/activejob/test/support/integration/test_case_helpers.rb b/activejob/test/support/integration/test_case_helpers.rb
index ee2f6aebea..bed28b2900 100644
--- a/activejob/test/support/integration/test_case_helpers.rb
+++ b/activejob/test/support/integration/test_case_helpers.rb
@@ -5,7 +5,7 @@ module TestCaseHelpers
extend ActiveSupport::Concern
included do
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
setup do
clear_jobs
@@ -27,8 +27,8 @@ module TestCaseHelpers
jobs_manager.clear_jobs
end
- def adapter_is?(adapter)
- ActiveJob::Base.queue_adapter.name.split("::").last.gsub(/Adapter$/, '').underscore==adapter.to_s
+ def adapter_is?(adapter_class_symbol)
+ ActiveJob::Base.queue_adapter.class.name.split("::").last.gsub(/Adapter$/, '').underscore == adapter_class_symbol.to_s
end
def wait_for_jobs_to_finish_for(seconds=60)
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 5588699d9b..32a2cb4517 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,61 +1,89 @@
-* Passwords with spaces only allowed in `ActiveModel::SecurePassword`.
+* Deprecate the `:tokenizer` option for `validates_length_of`, in favor of
+ plain Ruby.
- Presence validation can be used to restore old behavior.
+ *Sean Griffin*
- *Yevhene Shemet*
+* Deprecate `ActiveModel::Errors#add_on_empty` and `ActiveModel::Errors#add_on_blank`
+ with no replacement.
-* Validate options passed to `ActiveModel::Validations.validate`.
+ *Wojciech Wnętrzak*
- Preventing, in many cases, the simple mistake of using `validate` instead of `validates`.
+* Deprecate `ActiveModel::Errors#get`, `ActiveModel::Errors#set` and
+ `ActiveModel::Errors#[]=` methods that have inconsistent behaviour.
- *Sonny Michaud*
+ *Wojciech Wnętrzak*
-* Deprecate `reset_#{attribute}` in favor of `restore_#{attribute}`.
+* Allow symbol as values for `tokenize` of `LengthValidator`
- These methods may cause confusion with the `reset_changes`, which has
- different behaviour.
+ *Kensuke Naito*
- *Rafael Mendonça França*
+* Assigning an unknown attribute key to an `ActiveModel` instance during initialization
+ will now raise `ActiveModel::AttributeAssignment::UnknownAttributeError` instead of
+ `NoMethodError`.
-* Deprecate `ActiveModel::Dirty#reset_changes` in favor of `#clear_changes_information`.
+ Example:
- Method's name is causing confusion with the `reset_#{attribute}` methods.
- While `reset_name` sets the value of the name attribute to previous value
- `reset_changes` only discards the changes.
+ User.new(foo: 'some value')
+ # => ActiveModel::AttributeAssignment::UnknownAttributeError: unknown attribute 'foo' for User.
- *Rafael Mendonça França*
+ *Eugene Gilburg*
+
+* Extracted `ActiveRecord::AttributeAssignment` to `ActiveModel::AttributeAssignment`
+ allowing to use it for any object as an includable module.
-* Added `restore_attributes` method to `ActiveModel::Dirty` API which restores
- the value of changed attributes to previous value.
+ Example:
- *Igor G.*
+ class Cat
+ include ActiveModel::AttributeAssignment
+ attr_accessor :name, :status
+ end
-* Allow proc and symbol as values for `only_integer` of `NumericalityValidator`
+ cat = Cat.new
+ cat.assign_attributes(name: "Gorby", status: "yawning")
+ cat.name # => 'Gorby'
+ cat.status # => 'yawning'
+ cat.assign_attributes(status: "sleeping")
+ cat.name # => 'Gorby'
+ cat.status # => 'sleeping'
- *Robin Mehner*
+ *Bogdan Gusiev*
-* `has_secure_password` now verifies that the given password is less than 72
- characters if validations are enabled.
+* Add `ActiveModel::Errors#details`
- Fixes #14591.
+ To be able to return type of used validator, one can now call `details`
+ on errors instance.
- *Akshay Vishnoi*
+ Example:
-* Remove deprecated `Validator#setup` without replacement.
+ class User < ActiveRecord::Base
+ validates :name, presence: true
+ end
- See #10716.
+ user = User.new; user.valid?; user.errors.details
+ => {name: [{error: :blank}]}
- *Kuldeep Aggarwal*
+ *Wojciech Wnętrzak*
-* Add plural and singular form for length validator's default messages.
+* Change validates_acceptance_of to accept true by default.
- *Abd ar-Rahman Hamid*
+ The default for validates_acceptance_of is now "1" and true.
+ In the past, only "1" was the default and you were required to add
+ accept: true.
+
+* Remove deprecated `ActiveModel::Dirty#reset_#{attribute}` and
+ `ActiveModel::Dirty#reset_changes`.
+
+ *Rafael Mendonça França*
-* Introduce `validate` as an alias for `valid?`.
+* Change the way in which callback chains can be halted.
- This is more intuitive when you want to run validations but don't care about
- the return value.
+ The preferred method to halt a callback chain from now on is to explicitly
+ `throw(:abort)`.
+ In the past, returning `false` in an ActiveModel or ActiveModel::Validations
+ `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
+ either not work at all or display a deprecation warning.
- *Henrik Nyh*
-Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/activemodel/CHANGELOG.md) for previous changes.
+Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activemodel/CHANGELOG.md) for previous changes.
diff --git a/activemodel/MIT-LICENSE b/activemodel/MIT-LICENSE
index d58dd9ed9b..3ec7a617cf 100644
--- a/activemodel/MIT-LICENSE
+++ b/activemodel/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2014 David Heinemeier Hansson
+Copyright (c) 2004-2015 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc
index f6beff14e1..4920666f27 100644
--- a/activemodel/README.rdoc
+++ b/activemodel/README.rdoc
@@ -49,7 +49,7 @@ behavior out of the box:
send("#{attr}=", nil)
end
end
-
+
person = Person.new
person.clear_name
person.clear_age
@@ -132,7 +132,7 @@ behavior out of the box:
"Name"
end
end
-
+
person = Person.new
person.name = nil
person.validate!
@@ -216,10 +216,10 @@ behavior out of the box:
{Learn more}[link:classes/ActiveModel/Validations.html]
* Custom validators
-
+
class HasNameValidator < ActiveModel::Validator
def validate(record)
- record.errors[:name] = "must exist" if record.name.blank?
+ record.errors.add(:name, "must exist") if record.name.blank?
end
end
diff --git a/activemodel/Rakefile b/activemodel/Rakefile
index c30a559ef5..7256285a41 100644
--- a/activemodel/Rakefile
+++ b/activemodel/Rakefile
@@ -6,7 +6,7 @@ task :default => :test
Rake::TestTask.new do |t|
t.libs << "test"
- t.test_files = Dir.glob("#{dir}/test/cases/**/*_test.rb").sort
+ t.test_files = Dir.glob("#{dir}/test/cases/**/*_test.rb")
t.warning = true
t.verbose = true
t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec
index 36e565f692..1a16f2a1ed 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 = '>= 1.9.3'
+ s.required_ruby_version = '>= 2.2.1'
s.license = 'MIT'
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index feb3d9371d..8aa1b6f664 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2014 David Heinemeier Hansson
+# Copyright (c) 2004-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -28,6 +28,7 @@ require 'active_model/version'
module ActiveModel
extend ActiveSupport::Autoload
+ autoload :AttributeAssignment
autoload :AttributeMethods
autoload :BlockValidator, 'active_model/validator'
autoload :Callbacks
@@ -49,6 +50,7 @@ module ActiveModel
eager_autoload do
autoload :Errors
autoload :StrictValidationFailed, 'active_model/errors'
+ autoload :UnknownAttributeError, 'active_model/errors'
end
module Serializers
diff --git a/activemodel/lib/active_model/attribute_assignment.rb b/activemodel/lib/active_model/attribute_assignment.rb
new file mode 100644
index 0000000000..087d11f708
--- /dev/null
+++ b/activemodel/lib/active_model/attribute_assignment.rb
@@ -0,0 +1,52 @@
+require 'active_support/core_ext/hash/keys'
+
+module ActiveModel
+ module AttributeAssignment
+ include ActiveModel::ForbiddenAttributesProtection
+
+ # Allows you to set all the attributes by passing in a hash of attributes with
+ # keys matching the attribute names.
+ #
+ # If the passed hash responds to <tt>permitted?</tt> method and the return value
+ # of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
+ # exception is raised.
+ #
+ # class Cat
+ # include ActiveModel::AttributeAssignment
+ # attr_accessor :name, :status
+ # end
+ #
+ # cat = Cat.new
+ # cat.assign_attributes(name: "Gorby", status: "yawning")
+ # cat.name # => 'Gorby'
+ # cat.status => 'yawning'
+ # cat.assign_attributes(status: "sleeping")
+ # cat.name # => 'Gorby'
+ # cat.status => 'sleeping'
+ def assign_attributes(new_attributes)
+ if !new_attributes.respond_to?(:stringify_keys)
+ raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
+ end
+ return if new_attributes.blank?
+
+ attributes = new_attributes.stringify_keys
+ _assign_attributes(sanitize_for_mass_assignment(attributes))
+ end
+
+ private
+
+ def _assign_attributes(attributes)
+ attributes.each do |k, v|
+ _assign_attribute(k, v)
+ end
+ end
+
+ def _assign_attribute(k, v)
+ if respond_to?("#{k}=")
+ public_send("#{k}=", v)
+ else
+ raise UnknownAttributeError.new(self, k)
+ end
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index ea07c5c039..96be551264 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -353,14 +353,12 @@ module ActiveModel
@attribute_method_matchers_cache ||= ThreadSafe::Cache.new(initial_capacity: 4)
end
- def attribute_method_matcher(method_name) #:nodoc:
+ def attribute_method_matchers_matching(method_name) #:nodoc:
attribute_method_matchers_cache.compute_if_absent(method_name) do
# Must try to match prefixes/suffixes first, or else the matcher with no prefix/suffix
# will match every time.
matchers = attribute_method_matchers.partition(&:plain?).reverse.flatten(1)
- match = nil
- matchers.detect { |method| match = method.match(method_name) }
- match
+ matchers.map { |method| method.match(method_name) }.compact
end
end
@@ -469,8 +467,8 @@ module ActiveModel
# Returns a struct representing the matching attribute method.
# The struct's attributes are prefix, base and suffix.
def match_attribute_method?(method_name)
- match = self.class.send(:attribute_method_matcher, method_name)
- match if match && attribute_method?(match.attr_name)
+ matches = self.class.send(:attribute_method_matchers_matching, method_name)
+ matches.detect { |match| attribute_method?(match.attr_name) }
end
def missing_attribute(attr_name, stack)
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index b3d70dc515..2cf39b68fb 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -6,7 +6,7 @@ module ActiveModel
# Provides an interface for any class to have Active Record like callbacks.
#
# Like the Active Record methods, the callback chain is aborted as soon as
- # one of the methods in the chain returns +false+.
+ # one of the methods throws +:abort+.
#
# First, extend ActiveModel::Callbacks from the class you are creating:
#
@@ -49,7 +49,7 @@ module ActiveModel
# puts 'block successfully called.'
# end
#
- # You can choose not to have all three callbacks by passing a hash to the
+ # You can choose to have only specific callbacks by passing a hash to the
# +define_model_callbacks+ method.
#
# define_model_callbacks :create, only: [:after, :before]
@@ -103,7 +103,6 @@ module ActiveModel
def define_model_callbacks(*callbacks)
options = callbacks.extract_options!
options = {
- terminator: ->(_,result) { result == false },
skip_after_callbacks_if_terminated: true,
scope: [:kind, :name],
only: [:before, :around, :after]
diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb
index 9c9b6f4a77..9de6ea65be 100644
--- a/activemodel/lib/active_model/conversion.rb
+++ b/activemodel/lib/active_model/conversion.rb
@@ -22,7 +22,7 @@ module ActiveModel
module Conversion
extend ActiveSupport::Concern
- # If your object is already designed to implement all of the Active Model
+ # If your object is already designed to implement all of the \Active \Model
# you can use the default <tt>:to_model</tt> implementation, which simply
# returns +self+.
#
@@ -33,9 +33,9 @@ module ActiveModel
# person = Person.new
# person.to_model == person # => true
#
- # If your model does not act like an Active Model object, then you should
+ # If your model does not act like an \Active \Model object, then you should
# define <tt>:to_model</tt> yourself returning a proxy object that wraps
- # your object with Active Model compliant methods.
+ # your object with \Active \Model compliant methods.
def to_model
self
end
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index ae185694ca..c03e5fac79 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -1,6 +1,5 @@
require 'active_support/hash_with_indifferent_access'
require 'active_support/core_ext/object/duplicable'
-require 'active_support/core_ext/string/filters'
module ActiveModel
# == Active \Model \Dirty
@@ -13,7 +12,7 @@ module ActiveModel
# * <tt>include ActiveModel::Dirty</tt> in your object.
# * Call <tt>define_attribute_methods</tt> passing each method you want to
# track.
- # * Call <tt>attr_name_will_change!</tt> before each change to the tracked
+ # * Call <tt>[attr_name]_will_change!</tt> before each change to the tracked
# attribute.
# * Call <tt>changes_applied</tt> after the changes are persisted.
# * Call <tt>clear_changes_information</tt> when you want to reset the changes
@@ -53,10 +52,10 @@ module ActiveModel
# end
# end
#
- # A newly instantiated object is unchanged:
+ # A newly instantiated +Person+ object is unchanged:
#
- # person = Person.find_by(name: 'Uncle Bob')
- # person.changed? # => false
+ # person = Person.new
+ # person.changed? # => false
#
# Change the name:
#
@@ -72,8 +71,8 @@ module ActiveModel
# Save the changes:
#
# person.save
- # person.changed? # => false
- # person.name_changed? # => false
+ # person.changed? # => false
+ # person.name_changed? # => false
#
# Reset the changes:
#
@@ -85,42 +84,41 @@ module ActiveModel
#
# person.name = "Uncle Bob"
# person.rollback!
- # person.name # => "Bill"
- # person.name_changed? # => false
+ # person.name # => "Bill"
+ # person.name_changed? # => false
#
# Assigning the same value leaves the attribute unchanged:
#
# person.name = 'Bill'
- # person.name_changed? # => false
- # person.name_change # => nil
+ # person.name_changed? # => false
+ # person.name_change # => nil
#
# Which attributes have changed?
#
# person.name = 'Bob'
- # person.changed # => ["name"]
- # person.changes # => {"name" => ["Bill", "Bob"]}
+ # person.changed # => ["name"]
+ # 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.
- # Otherwise Active Model can't track changes to in-place attributes. Note
+ # 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.
#
# person.name_will_change!
- # person.name_change # => ["Bill", "Bill"]
+ # person.name_change # => ["Bill", "Bill"]
# person.name << 'y'
- # person.name_change # => ["Bill", "Billy"]
+ # person.name_change # => ["Bill", "Billy"]
module Dirty
extend ActiveSupport::Concern
include ActiveModel::AttributeMethods
included do
attribute_method_suffix '_changed?', '_change', '_will_change!', '_was'
- attribute_method_affix prefix: 'reset_', suffix: '!'
attribute_method_affix prefix: 'restore_', suffix: '!'
end
- # Returns +true+ if any attribute have unsaved changes, +false+ otherwise.
+ # Returns +true+ if any of the attributes have unsaved changes, +false+ otherwise.
#
# person.changed? # => false
# person.name = 'bob'
@@ -168,15 +166,15 @@ module ActiveModel
@changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
end
- # Handle <tt>*_changed?</tt> for +method_missing+.
+ # Handles <tt>*_changed?</tt> for +method_missing+.
def attribute_changed?(attr, options = {}) #:nodoc:
- result = changed_attributes.include?(attr)
+ result = changes_include?(attr)
result &&= options[:to] == __send__(attr) if options.key?(:to)
result &&= options[:from] == changed_attributes[attr] if options.key?(:from)
result
end
- # Handle <tt>*_was</tt> for +method_missing+.
+ # Handles <tt>*_was</tt> for +method_missing+.
def attribute_was(attr) # :nodoc:
attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
end
@@ -188,33 +186,30 @@ module ActiveModel
private
+ # Returns +true+ if attr_name is changed, +false+ otherwise.
+ def changes_include?(attr_name)
+ attributes_changed_by_setter.include?(attr_name)
+ end
+ alias attribute_changed_by_setter? changes_include?
+
# Removes current changes and makes them accessible through +previous_changes+.
def changes_applied # :doc:
@previously_changed = changes
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
end
- # Clear all dirty data: current changes and previous changes.
+ # Clears all dirty data: current changes and previous changes.
def clear_changes_information # :doc:
@previously_changed = ActiveSupport::HashWithIndifferentAccess.new
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
end
- def reset_changes
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- `#reset_changes` is deprecated and will be removed on Rails 5.
- Please use `#clear_changes_information` instead.
- MSG
-
- clear_changes_information
- end
-
- # Handle <tt>*_change</tt> for +method_missing+.
+ # Handles <tt>*_change</tt> for +method_missing+.
def attribute_change(attr)
[changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
end
- # Handle <tt>*_will_change!</tt> for +method_missing+.
+ # Handles <tt>*_will_change!</tt> for +method_missing+.
def attribute_will_change!(attr)
return if attribute_changed?(attr)
@@ -227,17 +222,7 @@ module ActiveModel
set_attribute_was(attr, value)
end
- # Handle <tt>reset_*!</tt> for +method_missing+.
- def reset_attribute!(attr)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- `#reset_#{attr}!` is deprecated and will be removed on Rails 5.
- Please use `#restore_#{attr}!` instead.
- MSG
-
- restore_attribute!(attr)
- end
-
- # Handle <tt>restore_*!</tt> for +method_missing+.
+ # Handles <tt>restore_*!</tt> for +method_missing+.
def restore_attribute!(attr)
if attribute_changed?(attr)
__send__("#{attr}=", changed_attributes[attr])
@@ -246,7 +231,7 @@ module ActiveModel
end
# This is necessary because `changed_attributes` might be overridden in
- # other implemntations (e.g. in `ActiveRecord`)
+ # other implementations (e.g. in `ActiveRecord`)
alias_method :attributes_changed_by_setter, :changed_attributes # :nodoc:
# Force an attribute to have a particular "before" value
@@ -255,7 +240,7 @@ module ActiveModel
end
# Remove changes information for the provided attributes.
- def clear_attribute_changes(attributes)
+ def clear_attribute_changes(attributes) # :doc:
attributes_changed_by_setter.except!(*attributes)
end
end
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 9105ef5dd6..f843b279ce 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -2,6 +2,8 @@
require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/string/inflections'
+require 'active_support/core_ext/object/deep_dup'
+require 'active_support/core_ext/string/filters'
module ActiveModel
# == Active \Model \Errors
@@ -23,7 +25,7 @@ module ActiveModel
# attr_reader :errors
#
# def validate!
- # errors.add(:name, "cannot be nil") if name.nil?
+ # errors.add(:name, :blank, message: "cannot be nil") if name.nil?
# end
#
# # The following methods are needed to be minimally implemented
@@ -32,11 +34,11 @@ module ActiveModel
# send(attr)
# end
#
- # def Person.human_attribute_name(attr, options = {})
+ # def self.human_attribute_name(attr, options = {})
# attr
# end
#
- # def Person.lookup_ancestors
+ # def self.lookup_ancestors
# [self]
# end
# end
@@ -58,8 +60,9 @@ module ActiveModel
include Enumerable
CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict]
+ MESSAGE_OPTIONS = [:message]
- attr_reader :messages
+ attr_reader :messages, :details
# Pass in the instance of the object that is using the errors object.
#
@@ -70,11 +73,13 @@ module ActiveModel
# end
def initialize(base)
@base = base
- @messages = {}
+ @messages = Hash.new { |messages, attribute| messages[attribute] = [] }
+ @details = Hash.new { |details, attribute| details[attribute] = [] }
end
def initialize_dup(other) # :nodoc:
@messages = other.messages.dup
+ @details = other.details.deep_dup
super
end
@@ -85,6 +90,7 @@ module ActiveModel
# person.errors.full_messages # => []
def clear
messages.clear
+ details.clear
end
# Returns +true+ if the error messages include an error for the given key
@@ -96,35 +102,46 @@ module ActiveModel
def include?(attribute)
messages[attribute].present?
end
- # aliases include?
alias :has_key? :include?
- # aliases include?
alias :key? :include?
# Get messages for +key+.
#
# person.errors.messages # => {:name=>["cannot be nil"]}
# person.errors.get(:name) # => ["cannot be nil"]
- # person.errors.get(:age) # => nil
+ # person.errors.get(:age) # => []
def get(key)
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ ActiveModel::Errors#get is deprecated and will be removed in Rails 5.1.
+
+ To achieve the same use model.errors[:#{key}].
+ MESSAGE
+
messages[key]
end
# Set messages for +key+ to +value+.
#
- # person.errors.get(:name) # => ["cannot be nil"]
+ # person.errors[:name] # => ["cannot be nil"]
# person.errors.set(:name, ["can't be nil"])
- # person.errors.get(:name) # => ["can't be nil"]
+ # person.errors[:name] # => ["can't be nil"]
def set(key, value)
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ ActiveModel::Errors#set is deprecated and will be removed in Rails 5.1.
+
+ Use model.errors.add(:#{key}, #{value.inspect}) instead.
+ MESSAGE
+
messages[key] = value
end
# Delete messages for +key+. Returns the deleted messages.
#
- # person.errors.get(:name) # => ["cannot be nil"]
+ # person.errors[:name] # => ["cannot be nil"]
# person.errors.delete(:name) # => ["cannot be nil"]
- # person.errors.get(:name) # => nil
+ # person.errors[:name] # => []
def delete(key)
+ details.delete(key)
messages.delete(key)
end
@@ -134,7 +151,7 @@ module ActiveModel
# person.errors[:name] # => ["cannot be nil"]
# person.errors['name'] # => ["cannot be nil"]
def [](attribute)
- get(attribute.to_sym) || set(attribute.to_sym, [])
+ messages[attribute.to_sym]
end
# Adds to the supplied attribute the supplied error message.
@@ -142,38 +159,45 @@ module ActiveModel
# person.errors[:name] = "must be set"
# person.errors[:name] # => ['must be set']
def []=(attribute, error)
- self[attribute] << error
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ ActiveModel::Errors#[]= is deprecated and will be removed in Rails 5.1.
+
+ Use model.errors.add(:#{attribute}, #{error.inspect}) instead.
+ MESSAGE
+
+ messages[attribute.to_sym] << error
end
# Iterates through each error key, value pair in the error messages hash.
# Yields the attribute and the error for that attribute. If the attribute
# has more than one error message, yields once for each error message.
#
- # person.errors.add(:name, "can't be blank")
+ # person.errors.add(:name, :blank, message: "can't be blank")
# person.errors.each do |attribute, error|
# # Will yield :name and "can't be blank"
# end
#
- # person.errors.add(:name, "must be specified")
+ # person.errors.add(:name, :not_specified, message: "must be specified")
# person.errors.each do |attribute, error|
# # Will yield :name and "can't be blank"
# # then yield :name and "must be specified"
# end
def each
messages.each_key do |attribute|
- self[attribute].each { |error| yield attribute, error }
+ messages[attribute].each { |error| yield attribute, error }
end
end
# Returns the number of error messages.
#
- # person.errors.add(:name, "can't be blank")
+ # person.errors.add(:name, :blank, message: "can't be blank")
# person.errors.size # => 1
- # person.errors.add(:name, "must be specified")
+ # person.errors.add(:name, :not_specified, message: "must be specified")
# person.errors.size # => 2
def size
values.flatten.size
end
+ alias :count :size
# Returns all message values.
#
@@ -191,40 +215,20 @@ module ActiveModel
messages.keys
end
- # Returns an array of error messages, with the attribute name included.
- #
- # person.errors.add(:name, "can't be blank")
- # person.errors.add(:name, "must be specified")
- # person.errors.to_a # => ["name can't be blank", "name must be specified"]
- def to_a
- full_messages
- end
-
- # Returns the number of error messages.
- #
- # person.errors.add(:name, "can't be blank")
- # person.errors.count # => 1
- # person.errors.add(:name, "must be specified")
- # person.errors.count # => 2
- def count
- to_a.size
- end
-
# Returns +true+ if no errors are found, +false+ otherwise.
# If the error message is a string it can be empty.
#
# person.errors.full_messages # => ["name cannot be nil"]
# person.errors.empty? # => false
def empty?
- all? { |k, v| v && v.empty? && !v.is_a?(String) }
+ size.zero?
end
- # aliases empty?
- alias_method :blank?, :empty?
+ alias :blank? :empty?
# Returns an xml formatted representation of the Errors hash.
#
- # person.errors.add(:name, "can't be blank")
- # person.errors.add(:name, "must be specified")
+ # person.errors.add(:name, :blank, message: "can't be blank")
+ # person.errors.add(:name, :not_specified, message: "must be specified")
# person.errors.to_xml
# # =>
# # <?xml version=\"1.0\" encoding=\"UTF-8\"?>
@@ -261,17 +265,20 @@ module ActiveModel
end
end
- # Adds +message+ to the error messages on +attribute+. More than one error
- # can be added to the same +attribute+. If no +message+ is supplied,
- # <tt>:invalid</tt> is assumed.
+ # Adds +message+ to the error messages and used validator type to +details+ on +attribute+.
+ # More than one error can be added to the same +attribute+.
+ # If no +message+ is supplied, <tt>:invalid</tt> is assumed.
#
# person.errors.add(:name)
# # => ["is invalid"]
- # person.errors.add(:name, 'must be implemented')
+ # person.errors.add(:name, :not_implemented, message: "must be implemented")
# # => ["is invalid", "must be implemented"]
#
# person.errors.messages
- # # => {:name=>["must be implemented", "is invalid"]}
+ # # => {:name=>["is invalid", "must be implemented"]}
+ #
+ # person.errors.details
+ # # => {:name=>[{error: :not_implemented}, {error: :invalid}]}
#
# If +message+ is a symbol, it will be translated using the appropriate
# scope (see +generate_message+).
@@ -283,9 +290,9 @@ module ActiveModel
# ActiveModel::StrictValidationFailed instead of adding the error.
# <tt>:strict</tt> option can also be set to any other exception.
#
- # person.errors.add(:name, nil, strict: true)
+ # person.errors.add(:name, :invalid, strict: true)
# # => ActiveModel::StrictValidationFailed: name is invalid
- # person.errors.add(:name, nil, strict: NameIsInvalid)
+ # person.errors.add(:name, :invalid, strict: NameIsInvalid)
# # => NameIsInvalid: name is invalid
#
# person.errors.messages # => {}
@@ -293,17 +300,23 @@ module ActiveModel
# +attribute+ should be set to <tt>:base</tt> if the error is not
# directly associated with a single attribute.
#
- # person.errors.add(:base, "either name or email must be present")
+ # person.errors.add(:base, :name_or_email_blank,
+ # message: "either name or email must be present")
# person.errors.messages
# # => {:base=>["either name or email must be present"]}
+ # person.errors.details
+ # # => {:base=>[{error: :name_or_email_blank}]}
def add(attribute, message = :invalid, options = {})
+ message = message.call if message.respond_to?(:call)
+ detail = normalize_detail(attribute, message, options)
message = normalize_message(attribute, message, options)
if exception = options[:strict]
exception = ActiveModel::StrictValidationFailed if exception == true
raise exception, full_message(attribute, message)
end
- self[attribute] << message
+ details[attribute.to_sym] << detail
+ messages[attribute.to_sym] << message
end
# Will add an error message to each of the attributes in +attributes+
@@ -313,6 +326,14 @@ module ActiveModel
# person.errors.messages
# # => {:name=>["can't be empty"]}
def add_on_empty(attributes, options = {})
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ ActiveModel::Errors#add_on_empty is deprecated and will be removed in Rails 5.1
+
+ To achieve the same use:
+
+ errors.add(attribute, :empty, options) if value.nil? || value.empty?
+ MESSAGE
+
Array(attributes).each do |attribute|
value = @base.send(:read_attribute_for_validation, attribute)
is_empty = value.respond_to?(:empty?) ? value.empty? : false
@@ -327,6 +348,14 @@ module ActiveModel
# person.errors.messages
# # => {:name=>["can't be blank"]}
def add_on_blank(attributes, options = {})
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ ActiveModel::Errors#add_on_blank is deprecated and will be removed in Rails 5.1
+
+ To achieve the same use:
+
+ errors.add(attribute, :empty, options) if value.blank?
+ MESSAGE
+
Array(attributes).each do |attribute|
value = @base.send(:read_attribute_for_validation, attribute)
add(attribute, :blank, options) if value.blank?
@@ -339,6 +368,7 @@ module ActiveModel
# person.errors.add :name, :blank
# person.errors.added? :name, :blank # => true
def added?(attribute, message = :invalid, options = {})
+ message = message.call if message.respond_to?(:call)
message = normalize_message(attribute, message, options)
self[attribute].include? message
end
@@ -356,6 +386,7 @@ module ActiveModel
def full_messages
map { |attribute, message| full_message(attribute, message) }
end
+ alias :to_a :full_messages
# Returns all the full error messages for a given attribute in an array.
#
@@ -368,7 +399,7 @@ module ActiveModel
# person.errors.full_messages_for(:name)
# # => ["Name is too short (minimum is 5 characters)", "Name can't be blank"]
def full_messages_for(attribute)
- (get(attribute) || []).map { |message| full_message(attribute, message) }
+ messages[attribute].map { |message| full_message(attribute, message) }
end
# Returns a full message for a given attribute.
@@ -388,8 +419,8 @@ module ActiveModel
# Translates an error message in its default scope
# (<tt>activemodel.errors.messages</tt>).
#
- # Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>,
- # if it's not there, it's looked up in <tt>models.MODEL.MESSAGE</tt> and if
+ # Error messages are first looked up in <tt>activemodel.errors.models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>,
+ # if it's not there, it's looked up in <tt>activemodel.errors.models.MODEL.MESSAGE</tt> and if
# that is not there also, it returns the translation of the default message
# (e.g. <tt>activemodel.errors.messages.MESSAGE</tt>). The translated model
# name, translated attribute name and the value are available for
@@ -447,12 +478,14 @@ module ActiveModel
case message
when Symbol
generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
- when Proc
- message.call
else
message
end
end
+
+ def normalize_detail(attribute, message, options)
+ { error: message }.merge(options.except(*CALLBACKS_OPTIONS + MESSAGE_OPTIONS))
+ end
end
# Raised when a validation cannot be corrected by end users and are considered
@@ -472,4 +505,15 @@ module ActiveModel
# # => ActiveModel::StrictValidationFailed: Name can't be blank
class StrictValidationFailed < StandardError
end
+
+ # Raised when unknown attributes are supplied via mass assignment.
+ class UnknownAttributeError < NoMethodError
+ attr_reader :record, :attribute
+
+ def initialize(record, attribute)
+ @record = record
+ @attribute = attribute
+ super("unknown attribute '#{attribute}' for #{@record.class}.")
+ end
+ end
end
diff --git a/activemodel/lib/active_model/gem_version.rb b/activemodel/lib/active_model/gem_version.rb
index 932fe3e5a9..762f4fe939 100644
--- a/activemodel/lib/active_model/gem_version.rb
+++ b/activemodel/lib/active_model/gem_version.rb
@@ -1,14 +1,14 @@
module ActiveModel
- # Returns the version of the currently loaded Active Model as a <tt>Gem::Version</tt>
+ # Returns the version of the currently loaded \Active \Model as a <tt>Gem::Version</tt>
def self.gem_version
Gem::Version.new VERSION::STRING
end
module VERSION
- MAJOR = 4
- MINOR = 2
+ MAJOR = 5
+ MINOR = 0
TINY = 0
- PRE = "beta4"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb
index 38087521a2..010eaeb170 100644
--- a/activemodel/lib/active_model/lint.rb
+++ b/activemodel/lib/active_model/lint.rb
@@ -21,28 +21,27 @@ module ActiveModel
# +self+.
module Tests
- # == Responds to <tt>to_key</tt>
+ # Passes if the object's model responds to <tt>to_key</tt> and if calling
+ # this method returns +nil+ when the object is not persisted.
+ # Fails otherwise.
#
- # Returns an Enumerable of all (primary) key attributes
- # or nil if <tt>model.persisted?</tt> is false. This is used by
- # <tt>dom_id</tt> to generate unique ids for the object.
+ # <tt>to_key</tt> returns an Enumerable of all (primary) key attributes
+ # of the model, and is used to a generate unique DOM id for the object.
def test_to_key
assert model.respond_to?(:to_key), "The model should respond to to_key"
def model.persisted?() false end
assert model.to_key.nil?, "to_key should return nil when `persisted?` returns false"
end
- # == Responds to <tt>to_param</tt>
- #
- # Returns a string representing the object's key suitable for use in URLs
- # or +nil+ if <tt>model.persisted?</tt> is +false+.
+ # Passes if the object's model responds to <tt>to_param</tt> and if
+ # calling this method returns +nil+ when the object is not persisted.
+ # Fails otherwise.
#
+ # <tt>to_param</tt> is used to represent the object's key in URLs.
# Implementers can decide to either raise an exception or provide a
# default in case the record uses a composite primary key. There are no
# tests for this behavior in lint because it doesn't make sense to force
# any of the possible implementation strategies on the implementer.
- # However, if the resource is not persisted?, then <tt>to_param</tt>
- # should always return +nil+.
def test_to_param
assert model.respond_to?(:to_param), "The model should respond to to_param"
def model.to_key() [1] end
@@ -50,32 +49,34 @@ module ActiveModel
assert model.to_param.nil?, "to_param should return nil when `persisted?` returns false"
end
- # == Responds to <tt>to_partial_path</tt>
+ # Passes if the object's model responds to <tt>to_partial_path</tt> and if
+ # calling this method returns a string. Fails otherwise.
#
- # Returns a string giving a relative path. This is used for looking up
- # partials. For example, a BlogPost model might return "blog_posts/blog_post"
+ # <tt>to_partial_path</tt> is used for looking up partials. For example,
+ # a BlogPost model might return "blog_posts/blog_post".
def test_to_partial_path
assert model.respond_to?(:to_partial_path), "The model should respond to to_partial_path"
assert_kind_of String, model.to_partial_path
end
- # == Responds to <tt>persisted?</tt>
+ # Passes if the object's model responds to <tt>persisted?</tt> and if
+ # calling this method returns either +true+ or +false+. Fails otherwise.
#
- # Returns a boolean that specifies whether the object has been persisted
- # yet. This is used when calculating the URL for an object. If the object
- # is not persisted, a form for that object, for instance, will route to
- # the create action. If it is persisted, a form for the object will routes
- # to the update action.
+ # <tt>persisted?</tt> is used when calculating the URL for an object.
+ # If the object is not persisted, a form for that object, for instance,
+ # will route to the create action. If it is persisted, a form for the
+ # object will route to the update action.
def test_persisted?
assert model.respond_to?(:persisted?), "The model should respond to persisted?"
assert_boolean model.persisted?, "persisted?"
end
- # == \Naming
+ # Passes if the object's model responds to <tt>model_name</tt> both as
+ # an instance method and as a class method, and if calling this method
+ # returns a string with some convenience methods: <tt>:human</tt>,
+ # <tt>:singular</tt> and <tt>:plural</tt>.
#
- # Model.model_name and Model#model_name must return a string with some
- # convenience methods: # <tt>:human</tt>, <tt>:singular</tt> and
- # <tt>:plural</tt>. Check ActiveModel::Naming for more information.
+ # Check ActiveModel::Naming for more information.
def test_model_naming
assert model.class.respond_to?(:model_name), "The model class should respond to model_name"
model_name = model.class.model_name
@@ -88,12 +89,15 @@ module ActiveModel
assert_equal model.model_name, model.class.model_name
end
- # == \Errors Testing
+ # Passes if the object's model responds to <tt>errors</tt> and if calling
+ # <tt>[](attribute)</tt> on the result of this method returns an array.
+ # Fails otherwise.
#
- # Returns an object that implements [](attribute) defined which returns an
- # Array of Strings that are the errors for the attribute in question.
- # If localization is used, the Strings should be localized for the current
- # locale. If no error is present, this method should return an empty Array.
+ # <tt>errors[attribute]</tt> is used to retrieve the errors of a model
+ # for a given attribute. If errors are present, the method should return
+ # an array of strings that are the errors for the attribute in question.
+ # If localization is used, the strings should be localized for the current
+ # locale. If no error is present, the method should return an empty array.
def test_errors_aref
assert model.respond_to?(:errors), "The model should respond to errors"
assert model.errors[:hello].is_a?(Array), "errors#[] should return an Array"
diff --git a/activemodel/lib/active_model/locale/en.yml b/activemodel/lib/active_model/locale/en.yml
index bf07945fe1..061e35dd1e 100644
--- a/activemodel/lib/active_model/locale/en.yml
+++ b/activemodel/lib/active_model/locale/en.yml
@@ -6,6 +6,7 @@ en:
# The values :model, :attribute and :value are always available for interpolation
# The value :count is available when applicable. Can be used for pluralization.
messages:
+ model_invalid: "Validation failed: %{errors}"
inclusion: "is not included in the list"
exclusion: "is reserved"
invalid: "is invalid"
@@ -16,7 +17,7 @@ en:
present: "must be blank"
too_long:
one: "is too long (maximum is 1 character)"
- other: "is too long (maximum is %{count} characters)"
+ other: "is too long (maximum is %{count} characters)"
too_short:
one: "is too short (minimum is 1 character)"
other: "is too short (minimum is %{count} characters)"
diff --git a/activemodel/lib/active_model/model.rb b/activemodel/lib/active_model/model.rb
index d51d6ddcc9..dac8d549a7 100644
--- a/activemodel/lib/active_model/model.rb
+++ b/activemodel/lib/active_model/model.rb
@@ -57,6 +57,7 @@ module ActiveModel
# (see below).
module Model
extend ActiveSupport::Concern
+ include ActiveModel::AttributeAssignment
include ActiveModel::Validations
include ActiveModel::Conversion
@@ -75,10 +76,8 @@ module ActiveModel
# person = Person.new(name: 'bob', age: '18')
# person.name # => "bob"
# person.age # => "18"
- def initialize(params={})
- params.each do |attr, value|
- self.public_send("#{attr}=", value)
- end if params
+ def initialize(attributes={})
+ assign_attributes(attributes) if attributes
super()
end
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index 4e6b02c246..1f1749af4e 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -1,5 +1,7 @@
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/module/introspection'
+require 'active_support/core_ext/module/remove_method'
+require 'active_support/core_ext/module/delegation'
module ActiveModel
class Name
@@ -129,7 +131,7 @@ module ActiveModel
#
# Equivalent to +to_s+.
delegate :==, :===, :<=>, :=~, :"!~", :eql?, :to_s,
- :to_str, to: :name
+ :to_str, :as_json, to: :name
# Returns a new ActiveModel::Name instance. By default, the +namespace+
# and +name+ option will take the namespace and name of the given class
@@ -189,8 +191,8 @@ module ActiveModel
private
- def _singularize(string, replacement='_')
- ActiveSupport::Inflector.underscore(string).tr('/', replacement)
+ def _singularize(string)
+ ActiveSupport::Inflector.underscore(string).tr('/', '_')
end
end
@@ -211,7 +213,7 @@ module ActiveModel
# BookModule::BookCover.model_name.i18n_key # => :"book_module/book_cover"
#
# Providing the functionality that ActiveModel::Naming provides in your object
- # is required to pass the Active Model Lint test. So either extending the
+ # is required to pass the \Active \Model Lint test. So either extending the
# provided method below, or rolling your own is required.
module Naming
def self.extended(base) #:nodoc:
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index 8f2a069ba3..89da74efa8 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -26,7 +26,7 @@ module ActiveModel
# it). When this attribute has a +nil+ value, the validation will not be
# triggered.
#
- # For further customizability, it is possible to supress the default
+ # For further customizability, it is possible to suppress the default
# validations by passing <tt>validations: false</tt> as an argument.
#
# Add bcrypt (~> 3.1.7) to Gemfile to use #has_secure_password:
@@ -77,13 +77,6 @@ module ActiveModel
validates_length_of :password, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED
validates_confirmation_of :password, allow_blank: true
end
-
- # This code is necessary as long as the protected_attributes gem is supported.
- if respond_to?(:attributes_protected_by_default)
- def self.attributes_protected_by_default #:nodoc:
- super + ['password_digest']
- end
- end
end
end
@@ -99,7 +92,7 @@ module ActiveModel
# user.authenticate('notright') # => false
# user.authenticate('mUc3m00RsqyRe') # => user
def authenticate(unencrypted_password)
- BCrypt::Password.new(password_digest) == unencrypted_password && self
+ BCrypt::Password.new(password_digest).is_password?(unencrypted_password) && self
end
attr_reader :password
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index 77f2a64b11..b66dbf1afe 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -130,10 +130,10 @@ module ActiveModel
#
# json = { person: { name: 'bob', age: 22, awesome:true } }.to_json
# person = Person.new
- # person.from_json(json) # => #<Person:0x007fec5e7a0088 @age=22, @awesome=true, @name="bob">
- # person.name # => "bob"
- # person.age # => 22
- # person.awesome # => true
+ # person.from_json(json, true) # => #<Person:0x007fec5e7a0088 @age=22, @awesome=true, @name="bob">
+ # person.name # => "bob"
+ # person.age # => 22
+ # person.awesome # => true
def from_json(json, include_root=include_root_in_json)
hash = ActiveSupport::JSON.decode(json)
hash = hash.values.first if include_root
diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb
index 3ad3bf30ad..e33c766627 100644
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ b/activemodel/lib/active_model/serializers/xml.rb
@@ -6,7 +6,7 @@ require 'active_support/core_ext/time/acts_like'
module ActiveModel
module Serializers
- # == Active Model XML Serializer
+ # == \Active \Model XML Serializer
module Xml
extend ActiveSupport::Concern
include ActiveModel::Serialization
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index c1e344b215..74d60327d6 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].freeze
+ VALID_OPTIONS_FOR_VALIDATE = [:on, :if, :unless, :prepend].freeze
# Adds a validation method or block to the class. This is useful when
# overriding the +validate+ instance method becomes too unwieldy and
@@ -371,6 +371,15 @@ module ActiveModel
!valid?(context)
end
+ # Runs all the validations within the specified context. Returns +true+ if
+ # no errors are found, raises +ValidationError+ otherwise.
+ #
+ # Validations with no <tt>:on</tt> option will run no matter the context. Validations with
+ # some <tt>:on</tt> option will only run in the specified context.
+ def validate!(context = nil)
+ valid?(context) || raise_validation_error
+ end
+
# Hook method defining how an attribute value should be retrieved. By default
# this is assumed to be an instance named after the attribute. Override this
# method in subclasses should you need to retrieve the value for a given
@@ -392,9 +401,33 @@ module ActiveModel
protected
def run_validations! #:nodoc:
- _run_validate_callbacks
+ run_callbacks :validate
errors.empty?
end
+
+ def raise_validation_error
+ raise(ValidationError.new(self))
+ end
+ end
+
+ # = Active Model ValidationError
+ #
+ # Raised by <tt>validate!</tt> when the model is invalid. Use the
+ # +model+ method to retrieve the record which did not validate.
+ #
+ # begin
+ # complex_operation_that_internally_calls_validate!
+ # rescue ActiveModel::ValidationError => invalid
+ # puts invalid.model.errors
+ # end
+ class ValidationError < StandardError
+ attr_reader :model
+
+ def initialize(model)
+ @model = model
+ errors = @model.errors.full_messages.join(", ")
+ super(I18n.t(:"#{@model.class.i18n_scope}.errors.messages.model_invalid", errors: errors, default: :"errors.messages.model_invalid"))
+ end
end
end
diff --git a/activemodel/lib/active_model/validations/absence.rb b/activemodel/lib/active_model/validations/absence.rb
index 9b5416fb1d..75bf655578 100644
--- a/activemodel/lib/active_model/validations/absence.rb
+++ b/activemodel/lib/active_model/validations/absence.rb
@@ -1,6 +1,6 @@
module ActiveModel
module Validations
- # == Active Model Absence Validator
+ # == \Active \Model Absence Validator
class AbsenceValidator < EachValidator #:nodoc:
def validate_each(record, attr_name, value)
record.errors.add(attr_name, :present, options) if value.present?
diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb
index ac5e79859b..ee160fb483 100644
--- a/activemodel/lib/active_model/validations/acceptance.rb
+++ b/activemodel/lib/active_model/validations/acceptance.rb
@@ -3,12 +3,12 @@ module ActiveModel
module Validations
class AcceptanceValidator < EachValidator # :nodoc:
def initialize(options)
- super({ allow_nil: true, accept: "1" }.merge!(options))
+ super({ allow_nil: true, accept: ["1", true] }.merge!(options))
setup!(options[:class])
end
def validate_each(record, attribute, value)
- unless value == options[:accept]
+ unless acceptable_option?(value)
record.errors.add(attribute, :accepted, options.except(:accept, :allow_nil))
end
end
@@ -20,6 +20,10 @@ module ActiveModel
klass.send(:attr_reader, *attr_readers)
klass.send(:attr_writer, *attr_writers)
end
+
+ def acceptable_option?(value)
+ Array(options[:accept]).include?(value)
+ end
end
module HelperMethods
diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb
index 25ccabd66b..b4301c23e4 100644
--- a/activemodel/lib/active_model/validations/callbacks.rb
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -15,15 +15,14 @@ module ActiveModel
# after_validation :do_stuff_after_validation
# end
#
- # Like other <tt>before_*</tt> callbacks if +before_validation+ returns
- # +false+ then <tt>valid?</tt> will not be called.
+ # Like other <tt>before_*</tt> callbacks if +before_validation+ throws
+ # +:abort+ then <tt>valid?</tt> will not be called.
module Callbacks
extend ActiveSupport::Concern
included do
include ActiveSupport::Callbacks
define_callbacks :validation,
- terminator: ->(_,result) { result == false },
skip_after_callbacks_if_terminated: true,
scope: [:kind, :name]
end
@@ -110,7 +109,7 @@ module ActiveModel
# Overwrite run validations to include callbacks.
def run_validations! #:nodoc:
- _run_validation_callbacks { super }
+ run_callbacks(:validation) { super }
end
end
end
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index 02478dd5b6..46a2e54fba 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -77,7 +77,7 @@ module ActiveModel
# with: ->(person) { person.admin? ? /\A[a-z0-9][a-z0-9_\-]*\z/i : /\A[a-z][a-z0-9_\-]*\z/i }
# end
#
- # Note: use <tt>\A</tt> and <tt>\Z</tt> to match the start and end of the
+ # Note: use <tt>\A</tt> and <tt>\z</tt> to match the start and end of the
# string, <tt>^</tt> and <tt>$</tt> match the start/end of a line.
#
# Due to frequent misuse of <tt>^</tt> and <tt>$</tt>, you need to pass
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index a96b30cadd..c22a58f9e1 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -1,3 +1,5 @@
+require "active_support/core_ext/string/strip"
+
module ActiveModel
# == Active \Model Length Validator
@@ -18,6 +20,27 @@ module ActiveModel
options[:minimum] = 1
end
+ if options[:tokenizer]
+ ActiveSupport::Deprecation.warn(<<-EOS.strip_heredoc)
+ The `:tokenizer` option is deprecated, and will be removed in Rails 5.1.
+ You can achieve the same functionality by defining an instance method
+ with the value that you want to validate the length of. For example,
+
+ validates_length_of :essay, minimum: 100,
+ tokenizer: ->(str) { str.scan(/\w+/) }
+
+ should be written as
+
+ validates_length_of :words_in_essay, minimum: 100
+
+ private
+
+ def words_in_essay
+ essay.scan(/\w+/)
+ end
+ EOS
+ end
+
super
end
@@ -38,7 +61,7 @@ module ActiveModel
end
def validate_each(record, attribute, value)
- value = tokenize(value)
+ value = tokenize(record, value)
value_length = value.respond_to?(:length) ? value.length : value.to_s.length
errors_options = options.except(*RESERVED_OPTIONS)
@@ -59,10 +82,14 @@ module ActiveModel
end
private
-
- def tokenize(value)
- if options[:tokenizer] && value.kind_of?(String)
- options[:tokenizer].call(value)
+ def tokenize(record, value)
+ tokenizer = options[:tokenizer]
+ if tokenizer && value.kind_of?(String)
+ if tokenizer.kind_of?(Proc)
+ tokenizer.call(value)
+ elsif record.respond_to?(tokenizer)
+ record.send(tokenizer, value)
+ end
end || value
end
@@ -84,8 +111,13 @@ module ActiveModel
# validates_length_of :user_name, within: 6..20, too_long: 'pick a shorter name', too_short: 'pick a longer name'
# validates_length_of :zip_code, minimum: 5, too_short: 'please enter at least 5 characters'
# validates_length_of :smurf_leader, is: 4, message: "papa is spelled with 4 characters... don't play me."
- # validates_length_of :essay, minimum: 100, too_short: 'Your essay must be at least 100 words.',
- # tokenizer: ->(str) { str.scan(/\w+/) }
+ # validates_length_of :words_in_essay, minimum: 100, too_short: 'Your essay must be at least 100 words.'
+ #
+ # private
+ #
+ # def words_in_essay
+ # essay.scan(/\w+/)
+ # end
# end
#
# Configuration options:
@@ -108,10 +140,6 @@ module ActiveModel
# * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>,
# <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate
# <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
- # * <tt>:tokenizer</tt> - Specifies how to split up the attribute string.
- # (e.g. <tt>tokenizer: ->(str) { str.scan(/\w+/) }</tt> to count words
- # as in above example). Defaults to <tt>->(value) { value.split(//) }</tt>
- # which counts individual characters.
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+ and +:strict+.
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index 13d6a966c0..4ba4e3e8f7 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -23,6 +23,10 @@ module ActiveModel
raw_value = record.send(before_type_cast) if record.respond_to?(before_type_cast)
raw_value ||= value
+ if record_attribute_changed_in_place?(record, attr_name)
+ raw_value = value
+ end
+
return if options[:allow_nil] && raw_value.nil?
unless value = parse_raw_value_as_a_number(raw_value)
@@ -86,6 +90,13 @@ module ActiveModel
options[:only_integer]
end
end
+
+ private
+
+ def record_attribute_changed_in_place?(record, attr_name)
+ record.respond_to?(:attribute_changed_in_place?) &&
+ record.attribute_changed_in_place?(attr_name.to_s)
+ end
end
module HelperMethods
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index 0116de68ab..1d2888a818 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -15,7 +15,7 @@ module ActiveModel
# class MyValidator < ActiveModel::Validator
# def validate(record)
# if some_complex_logic
- # record.errors[:base] = "This record is invalid"
+ # record.errors.add(:base, "This record is invalid")
# end
# end
#
@@ -127,7 +127,7 @@ module ActiveModel
# in the options hash invoking the <tt>validate_each</tt> method passing in the
# record, attribute and value.
#
- # All Active Model validations are built on top of this validator.
+ # All \Active \Model validations are built on top of this validator.
class EachValidator < Validator #:nodoc:
attr_reader :attributes
@@ -163,6 +163,10 @@ module ActiveModel
# +ArgumentError+ when invalid options are supplied.
def check_validity!
end
+
+ def should_validate?(record) # :nodoc:
+ !record.persisted? || record.changed? || record.marked_for_destruction?
+ end
end
# +BlockValidator+ is a special +EachValidator+ which receives a block on initialization
diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb
index b1f9082ea7..6da3b4117b 100644
--- a/activemodel/lib/active_model/version.rb
+++ b/activemodel/lib/active_model/version.rb
@@ -1,7 +1,7 @@
require_relative 'gem_version'
module ActiveModel
- # Returns the version of the currently loaded ActiveModel as a <tt>Gem::Version</tt>
+ # Returns the version of the currently loaded \Active \Model as a <tt>Gem::Version</tt>
def self.version
gem_version
end
diff --git a/activemodel/test/cases/attribute_assignment_test.rb b/activemodel/test/cases/attribute_assignment_test.rb
new file mode 100644
index 0000000000..3b01644dd1
--- /dev/null
+++ b/activemodel/test/cases/attribute_assignment_test.rb
@@ -0,0 +1,109 @@
+require "cases/helper"
+require "active_support/hash_with_indifferent_access"
+
+class AttributeAssignmentTest < ActiveModel::TestCase
+ class Model
+ include ActiveModel::AttributeAssignment
+
+ attr_accessor :name, :description
+
+ def initialize(attributes = {})
+ assign_attributes(attributes)
+ end
+
+ def broken_attribute=(value)
+ raise ErrorFromAttributeWriter
+ end
+
+ protected
+
+ attr_writer :metadata
+ end
+
+ class ErrorFromAttributeWriter < StandardError
+ end
+
+ class ProtectedParams < ActiveSupport::HashWithIndifferentAccess
+ def permit!
+ @permitted = true
+ end
+
+ def permitted?
+ @permitted ||= false
+ end
+
+ def dup
+ super.tap do |duplicate|
+ duplicate.instance_variable_set :@permitted, permitted?
+ end
+ end
+ end
+
+ test "simple assignment" do
+ model = Model.new
+
+ model.assign_attributes(name: "hello", description: "world")
+ assert_equal "hello", model.name
+ assert_equal "world", model.description
+ end
+
+ test "assign non-existing attribute" do
+ model = Model.new
+ error = assert_raises(ActiveModel::UnknownAttributeError) do
+ model.assign_attributes(hz: 1)
+ end
+
+ assert_equal model, error.record
+ assert_equal "hz", error.attribute
+ 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 })
+ end
+ end
+
+ test "does not swallow errors raised in an attribute writer" do
+ assert_raises(ErrorFromAttributeWriter) do
+ Model.new(broken_attribute: 1)
+ end
+ end
+
+ test "an ArgumentError is raised if a non-hash-like obejct is passed" do
+ assert_raises(ArgumentError) do
+ Model.new(1)
+ end
+ end
+
+ test "forbidden attributes cannot be used for mass assignment" do
+ params = ProtectedParams.new(name: "Guille", description: "m")
+
+ assert_raises(ActiveModel::ForbiddenAttributesError) do
+ Model.new(params)
+ end
+ end
+
+ test "permitted attributes can be used for mass assignment" do
+ params = ProtectedParams.new(name: "Guille", description: "desc")
+ params.permit!
+ model = Model.new(params)
+
+ assert_equal "Guille", model.name
+ assert_equal "desc", model.description
+ end
+
+ test "regular hash should still be used for mass assignment" do
+ model = Model.new(name: "Guille", description: "m")
+
+ assert_equal "Guille", model.name
+ assert_equal "m", model.description
+ end
+
+ test "assigning no attributes should not raise, even if the hash is un-permitted" do
+ model = Model.new
+ assert_nil model.assign_attributes(ProtectedParams.new({}))
+ end
+end
diff --git a/activemodel/test/cases/callbacks_test.rb b/activemodel/test/cases/callbacks_test.rb
index 5fede098d1..85455c112c 100644
--- a/activemodel/test/cases/callbacks_test.rb
+++ b/activemodel/test/cases/callbacks_test.rb
@@ -7,6 +7,7 @@ class CallbacksTest < ActiveModel::TestCase
model.callbacks << :before_around_create
yield
model.callbacks << :after_around_create
+ false
end
end
@@ -24,16 +25,22 @@ class CallbacksTest < ActiveModel::TestCase
after_create do |model|
model.callbacks << :after_create
+ false
end
after_create "@callbacks << :final_callback"
- def initialize(valid=true)
- @callbacks, @valid = [], valid
+ def initialize(options = {})
+ @callbacks = []
+ @valid = options[:valid]
+ @before_create_returns = options.fetch(:before_create_returns, true)
+ @before_create_throws = options[:before_create_throws]
end
def before_create
@callbacks << :before_create
+ throw(@before_create_throws) if @before_create_throws
+ @before_create_returns
end
def create
@@ -51,14 +58,28 @@ class CallbacksTest < ActiveModel::TestCase
:after_around_create, :after_create, :final_callback]
end
- test "after callbacks are always appended" do
+ test "the callback chain is not halted when around or after callbacks return false" do
model = ModelCallbacks.new
model.create
assert_equal model.callbacks.last, :final_callback
end
+ test "the callback chain is halted when a before callback returns false (deprecated)" do
+ model = ModelCallbacks.new(before_create_returns: false)
+ assert_deprecated do
+ model.create
+ assert_equal model.callbacks.last, :before_create
+ end
+ end
+
+ test "the callback chain is halted when a callback throws :abort" do
+ model = ModelCallbacks.new(before_create_throws: :abort)
+ model.create
+ assert_equal model.callbacks, [:before_create]
+ end
+
test "after callbacks are not executed if the block returns false" do
- model = ModelCallbacks.new(false)
+ model = ModelCallbacks.new(valid: false)
model.create
assert_equal model.callbacks, [ :before_create, :before_around_create,
:create, :after_around_create]
diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb
index db2cd885e2..66ed8a350a 100644
--- a/activemodel/test/cases/dirty_test.rb
+++ b/activemodel/test/cases/dirty_test.rb
@@ -45,10 +45,6 @@ class DirtyTest < ActiveModel::TestCase
def reload
clear_changes_information
end
-
- def deprecated_reload
- reset_changes
- end
end
setup do
@@ -181,23 +177,6 @@ class DirtyTest < ActiveModel::TestCase
assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.changed_attributes
end
- test "reset_changes is deprecated" do
- @model.name = 'Dmitry'
- @model.name_changed?
- @model.save
- @model.name = 'Bob'
-
- assert_equal [nil, 'Dmitry'], @model.previous_changes['name']
- assert_equal 'Dmitry', @model.changed_attributes['name']
-
- assert_deprecated do
- @model.deprecated_reload
- end
-
- assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.previous_changes
- assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.changed_attributes
- end
-
test "restore_attributes should restore all previous data" do
@model.name = 'Dmitry'
@model.color = 'Red'
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index efedd9055f..f781a0017f 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -29,28 +29,28 @@ class ErrorsTest < ActiveModel::TestCase
def test_delete
errors = ActiveModel::Errors.new(self)
- errors[:foo] = 'omg'
+ errors[:foo] << 'omg'
errors.delete(:foo)
assert_empty errors[:foo]
end
def test_include?
errors = ActiveModel::Errors.new(self)
- errors[:foo] = 'omg'
+ errors[:foo] << 'omg'
assert errors.include?(:foo), 'errors should include :foo'
end
def test_dup
errors = ActiveModel::Errors.new(self)
- errors[:foo] = 'bar'
+ errors[:foo] << 'bar'
errors_dup = errors.dup
- errors_dup[:bar] = 'omg'
+ errors_dup[:bar] << 'omg'
assert_not_same errors_dup.messages, errors.messages
end
def test_has_key?
errors = ActiveModel::Errors.new(self)
- errors[:foo] = 'omg'
+ errors[:foo] << 'omg'
assert_equal true, errors.has_key?(:foo), 'errors should have key :foo'
end
@@ -61,7 +61,7 @@ class ErrorsTest < ActiveModel::TestCase
def test_key?
errors = ActiveModel::Errors.new(self)
- errors[:foo] = 'omg'
+ errors[:foo] << 'omg'
assert_equal true, errors.key?(:foo), 'errors should have key :foo'
end
@@ -81,37 +81,41 @@ class ErrorsTest < ActiveModel::TestCase
test "get returns the errors for the provided key" do
errors = ActiveModel::Errors.new(self)
- errors[:foo] = "omg"
+ errors[:foo] << "omg"
- assert_equal ["omg"], errors.get(:foo)
+ assert_deprecated do
+ assert_equal ["omg"], errors.get(:foo)
+ end
end
test "sets the error with the provided key" do
errors = ActiveModel::Errors.new(self)
- errors.set(:foo, "omg")
+ assert_deprecated do
+ errors.set(:foo, "omg")
+ end
assert_equal({ foo: "omg" }, errors.messages)
end
test "error access is indifferent" do
errors = ActiveModel::Errors.new(self)
- errors[:foo] = "omg"
+ errors[:foo] << "omg"
assert_equal ["omg"], errors["foo"]
end
test "values returns an array of messages" do
errors = ActiveModel::Errors.new(self)
- errors.set(:foo, "omg")
- errors.set(:baz, "zomg")
+ errors.messages[:foo] = "omg"
+ errors.messages[:baz] = "zomg"
assert_equal ["omg", "zomg"], errors.values
end
test "keys returns the error keys" do
errors = ActiveModel::Errors.new(self)
- errors.set(:foo, "omg")
- errors.set(:baz, "zomg")
+ errors.messages[:foo] << "omg"
+ errors.messages[:baz] << "zomg"
assert_equal [:foo, :baz], errors.keys
end
@@ -133,7 +137,9 @@ class ErrorsTest < ActiveModel::TestCase
test "assign error" do
person = Person.new
- person.errors[:name] = 'should not be nil'
+ assert_deprecated do
+ person.errors[:name] = 'should not be nil'
+ end
assert_equal ["should not be nil"], person.errors[:name]
end
@@ -206,6 +212,12 @@ class ErrorsTest < ActiveModel::TestCase
assert_equal 1, person.errors.size
end
+ test "count calculates the number of error messages" do
+ person = Person.new
+ person.errors.add(:name, "cannot be blank")
+ assert_equal 1, person.errors.count
+ end
+
test "to_a returns the list of errors with complete messages containing the attribute names" do
person = Person.new
person.errors.add(:name, "cannot be blank")
@@ -282,45 +294,107 @@ class ErrorsTest < ActiveModel::TestCase
test "add_on_empty generates message" do
person = Person.new
person.errors.expects(:generate_message).with(:name, :empty, {})
- person.errors.add_on_empty :name
+ assert_deprecated do
+ person.errors.add_on_empty :name
+ 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, {})
- person.errors.add_on_empty [:name, :age]
+ assert_deprecated do
+ person.errors.add_on_empty [:name, :age]
+ 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' })
- person.errors.add_on_empty :name, message: 'custom'
+ assert_deprecated do
+ person.errors.add_on_empty :name, message: 'custom'
+ 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, {})
- person.errors.add_on_empty :name
+ assert_deprecated do
+ person.errors.add_on_empty :name
+ end
end
test "add_on_blank generates message" do
person = Person.new
person.errors.expects(:generate_message).with(:name, :blank, {})
- person.errors.add_on_blank :name
+ assert_deprecated do
+ person.errors.add_on_blank :name
+ 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, {})
- person.errors.add_on_blank [:name, :age]
+ assert_deprecated do
+ person.errors.add_on_blank [:name, :age]
+ 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' })
- person.errors.add_on_blank :name, message: 'custom'
+ assert_deprecated do
+ person.errors.add_on_blank :name, message: 'custom'
+ end
+ end
+
+ test "details returns added error detail" do
+ person = Person.new
+ person.errors.add(:name, :invalid)
+ assert_equal({ name: [{ error: :invalid }] }, person.errors.details)
+ end
+
+ test "details returns added error detail with custom option" do
+ person = Person.new
+ person.errors.add(:name, :greater_than, count: 5)
+ assert_equal({ name: [{ error: :greater_than, count: 5 }] }, person.errors.details)
+ end
+
+ test "details do not include message option" do
+ person = Person.new
+ person.errors.add(:name, :invalid, message: "is bad")
+ assert_equal({ name: [{ error: :invalid }] }, person.errors.details)
+ end
+
+ test "dup duplicates details" do
+ errors = ActiveModel::Errors.new(Person.new)
+ errors.add(:name, :invalid)
+ errors_dup = errors.dup
+ errors_dup.add(:name, :taken)
+ assert_not_equal errors_dup.details, errors.details
+ end
+
+ test "delete removes details on given attribute" do
+ errors = ActiveModel::Errors.new(Person.new)
+ errors.add(:name, :invalid)
+ errors.delete(:name)
+ assert_empty errors.details[:name]
+ end
+
+ test "delete returns the deleted messages" do
+ errors = ActiveModel::Errors.new(Person.new)
+ errors.add(:name, :invalid)
+ assert_equal ["is invalid"], errors.delete(:name)
+ end
+
+ test "clear removes details" do
+ person = Person.new
+ person.errors.add(:name, :invalid)
+
+ assert_equal 1, person.errors.details.count
+ person.errors.clear
+ assert person.errors.details.empty?
end
end
diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb
index 4ce6103593..2b9de5e5d2 100644
--- a/activemodel/test/cases/helper.rb
+++ b/activemodel/test/cases/helper.rb
@@ -14,7 +14,11 @@ require 'active_support/testing/autorun'
require 'mocha/setup' # FIXME: stop using mocha
-# FIXME: we have tests that depend on run order, we should fix that and
-# remove this method call.
-require 'active_support/test_case'
-ActiveSupport::TestCase.test_order = :sorted
+# Skips the current run on Rubinius using Minitest::Assertions#skip
+def rubinius_skip(message = '')
+ skip message if RUBY_ENGINE == 'rbx'
+end
+# Skips the current run on JRuby using Minitest::Assertions#skip
+def jruby_skip(message = '')
+ skip message if defined?(JRUBY_VERSION)
+end
diff --git a/activemodel/test/cases/model_test.rb b/activemodel/test/cases/model_test.rb
index ee0fa26546..3017f3541b 100644
--- a/activemodel/test/cases/model_test.rb
+++ b/activemodel/test/cases/model_test.rb
@@ -70,6 +70,8 @@ class ModelTest < ActiveModel::TestCase
end
def test_mixin_initializer_when_args_dont_exist
- assert_raises(NoMethodError) { SimpleModel.new(hello: 'world') }
+ assert_raises(ActiveModel::UnknownAttributeError) do
+ SimpleModel.new(hello: 'world')
+ end
end
end
diff --git a/activemodel/test/cases/serializers/json_serialization_test.rb b/activemodel/test/cases/serializers/json_serialization_test.rb
index e2eb91eeb0..d765a47636 100644
--- a/activemodel/test/cases/serializers/json_serialization_test.rb
+++ b/activemodel/test/cases/serializers/json_serialization_test.rb
@@ -195,4 +195,8 @@ class JsonSerializationTest < ActiveModel::TestCase
assert_no_match %r{"awesome":}, json
assert_no_match %r{"preferences":}, json
end
+
+ test "Class.model_name should be json encodable" do
+ assert_match %r{"Contact"}, Contact.model_name.to_json
+ end
end
diff --git a/activemodel/test/cases/validations/absence_validation_test.rb b/activemodel/test/cases/validations/absence_validation_test.rb
index ebfe1cf4e4..9cbc77dfb5 100644
--- a/activemodel/test/cases/validations/absence_validation_test.rb
+++ b/activemodel/test/cases/validations/absence_validation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/topic'
require 'models/person'
diff --git a/activemodel/test/cases/validations/acceptance_validation_test.rb b/activemodel/test/cases/validations/acceptance_validation_test.rb
index e78aa1adaf..9c2114d83d 100644
--- a/activemodel/test/cases/validations/acceptance_validation_test.rb
+++ b/activemodel/test/cases/validations/acceptance_validation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/topic'
@@ -65,4 +64,10 @@ class AcceptanceValidationTest < ActiveModel::TestCase
ensure
Person.clear_validators!
end
+
+ def test_validates_acceptance_of_true
+ Topic.validates_acceptance_of(:terms_of_service)
+
+ assert Topic.new(terms_of_service: true).valid?
+ end
end
diff --git a/activemodel/test/cases/validations/callbacks_test.rb b/activemodel/test/cases/validations/callbacks_test.rb
index 6cd0f4ed4d..75eb18e795 100644
--- a/activemodel/test/cases/validations/callbacks_test.rb
+++ b/activemodel/test/cases/validations/callbacks_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
class Dog
@@ -30,16 +29,34 @@ class DogWithTwoValidators < Dog
before_validation { self.history << 'before_validation_marker2' }
end
-class DogValidatorReturningFalse < Dog
+class DogDeprecatedBeforeValidatorReturningFalse < Dog
before_validation { false }
before_validation { self.history << 'before_validation_marker2' }
end
+class DogBeforeValidatorThrowingAbort < Dog
+ before_validation { throw :abort }
+ before_validation { self.history << 'before_validation_marker2' }
+end
+
+class DogAfterValidatorReturningFalse < Dog
+ after_validation { false }
+ after_validation { self.history << 'after_validation_marker' }
+end
+
class DogWithMissingName < Dog
before_validation { self.history << 'before_validation_marker' }
validates_presence_of :name
end
+class DogValidatorWithOnCondition < Dog
+ before_validation :set_before_validation_marker, on: :create
+ after_validation :set_after_validation_marker, on: :create
+
+ def set_before_validation_marker; self.history << 'before_validation_marker'; end
+ def set_after_validation_marker; self.history << 'after_validation_marker' ; end
+end
+
class DogValidatorWithIfCondition < Dog
before_validation :set_before_validation_marker1, if: -> { true }
before_validation :set_before_validation_marker2, if: -> { false }
@@ -63,6 +80,24 @@ class CallbacksWithMethodNamesShouldBeCalled < ActiveModel::TestCase
assert_equal ["before_validation_marker1", "after_validation_marker1"], d.history
end
+ def test_on_condition_is_respected_for_validation_with_matching_context
+ d = DogValidatorWithOnCondition.new
+ d.valid?(:create)
+ assert_equal ["before_validation_marker", "after_validation_marker"], d.history
+ end
+
+ def test_on_condition_is_respected_for_validation_without_matching_context
+ d = DogValidatorWithOnCondition.new
+ d.valid?(:save)
+ assert_equal [], d.history
+ end
+
+ def test_on_condition_is_respected_for_validation_without_context
+ d = DogValidatorWithOnCondition.new
+ d.valid?
+ assert_equal [], d.history
+ end
+
def test_before_validation_and_after_validation_callbacks_should_be_called
d = DogWithMethodCallbacks.new
d.valid?
@@ -81,13 +116,28 @@ class CallbacksWithMethodNamesShouldBeCalled < ActiveModel::TestCase
assert_equal ['before_validation_marker1', 'before_validation_marker2'], d.history
end
- def test_further_callbacks_should_not_be_called_if_before_validation_returns_false
- d = DogValidatorReturningFalse.new
+ def test_further_callbacks_should_not_be_called_if_before_validation_throws_abort
+ d = DogBeforeValidatorThrowingAbort.new
output = d.valid?
assert_equal [], d.history
assert_equal false, output
end
+ def test_deprecated_further_callbacks_should_not_be_called_if_before_validation_returns_false
+ d = DogDeprecatedBeforeValidatorReturningFalse.new
+ assert_deprecated do
+ output = d.valid?
+ assert_equal [], d.history
+ assert_equal false, output
+ end
+ end
+
+ def test_further_callbacks_should_be_called_if_after_validation_returns_false
+ d = DogAfterValidatorReturningFalse.new
+ d.valid?
+ assert_equal ['after_validation_marker'], d.history
+ end
+
def test_validation_test_should_be_done
d = DogWithMissingName.new
output = d.valid?
diff --git a/activemodel/test/cases/validations/conditional_validation_test.rb b/activemodel/test/cases/validations/conditional_validation_test.rb
index 1261937b56..296d3b4407 100644
--- a/activemodel/test/cases/validations/conditional_validation_test.rb
+++ b/activemodel/test/cases/validations/conditional_validation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/topic'
diff --git a/activemodel/test/cases/validations/confirmation_validation_test.rb b/activemodel/test/cases/validations/confirmation_validation_test.rb
index 65a2a1eb49..c1431548f7 100644
--- a/activemodel/test/cases/validations/confirmation_validation_test.rb
+++ b/activemodel/test/cases/validations/confirmation_validation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/topic'
diff --git a/activemodel/test/cases/validations/exclusion_validation_test.rb b/activemodel/test/cases/validations/exclusion_validation_test.rb
index 1ce41f9bc9..b269c3691a 100644
--- a/activemodel/test/cases/validations/exclusion_validation_test.rb
+++ b/activemodel/test/cases/validations/exclusion_validation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/topic'
diff --git a/activemodel/test/cases/validations/format_validation_test.rb b/activemodel/test/cases/validations/format_validation_test.rb
index 0f91b73cd7..86bbbe6ebe 100644
--- a/activemodel/test/cases/validations/format_validation_test.rb
+++ b/activemodel/test/cases/validations/format_validation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/topic'
diff --git a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
index 3eeb80a48b..da63df9152 100644
--- a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
@@ -62,7 +62,7 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase
assert_equal 'custom message', @person.errors.generate_message(:title, :empty, message: 'custom message')
end
- # add_on_blank: generate_message(attr, :blank, message: custom_message)
+ # validates_presence_of: generate_message(attr, :blank, message: custom_message)
def test_generate_message_blank_with_default_message
assert_equal "can't be blank", @person.errors.generate_message(:title, :blank)
end
diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb
index 96084a32ba..70ee7afecc 100644
--- a/activemodel/test/cases/validations/i18n_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_validation_test.rb
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
require "cases/helper"
require 'models/person'
diff --git a/activemodel/test/cases/validations/inclusion_validation_test.rb b/activemodel/test/cases/validations/inclusion_validation_test.rb
index 3a8f3080e1..55d1fb4dcb 100644
--- a/activemodel/test/cases/validations/inclusion_validation_test.rb
+++ b/activemodel/test/cases/validations/inclusion_validation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'active_support/all'
diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb
index 046ffcb16f..ee901b75fb 100644
--- a/activemodel/test/cases/validations/length_validation_test.rb
+++ b/activemodel/test/cases/validations/length_validation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/topic'
@@ -320,8 +319,33 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_with_block
- Topic.validates_length_of :content, minimum: 5, too_short: "Your essay must be at least %{count} words.",
- tokenizer: lambda {|str| str.scan(/\w+/) }
+ assert_deprecated do
+ Topic.validates_length_of(
+ :content,
+ minimum: 5,
+ too_short: "Your essay must be at least %{count} words.",
+ tokenizer: lambda {|str| str.scan(/\w+/) },
+ )
+ end
+ t = Topic.new(content: "this content should be long enough")
+ assert t.valid?
+
+ t.content = "not long enough"
+ assert t.invalid?
+ assert t.errors[:content].any?
+ assert_equal ["Your essay must be at least 5 words."], t.errors[:content]
+ end
+
+
+ def test_validates_length_of_with_symbol
+ assert_deprecated do
+ Topic.validates_length_of(
+ :content,
+ minimum: 5,
+ too_short: "Your essay must be at least %{count} words.",
+ tokenizer: :my_word_tokenizer,
+ )
+ end
t = Topic.new(content: "this content should be long enough")
assert t.valid?
diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb
index 3834d327ea..05432abaff 100644
--- a/activemodel/test/cases/validations/numericality_validation_test.rb
+++ b/activemodel/test/cases/validations/numericality_validation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/topic'
@@ -59,7 +58,7 @@ class NumericalityValidationTest < ActiveModel::TestCase
def test_validates_numericality_of_with_integer_only_and_proc_as_value
Topic.send(:define_method, :allow_only_integers?, lambda { false })
- Topic.validates_numericality_of :approved, only_integer: Proc.new {|topic| topic.allow_only_integers? }
+ Topic.validates_numericality_of :approved, only_integer: Proc.new(&:allow_only_integers?)
invalid!(NIL + BLANK + JUNK)
valid!(FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
@@ -130,7 +129,7 @@ class NumericalityValidationTest < ActiveModel::TestCase
def test_validates_numericality_with_proc
Topic.send(:define_method, :min_approved, lambda { 5 })
- Topic.validates_numericality_of :approved, greater_than_or_equal_to: Proc.new {|topic| topic.min_approved }
+ Topic.validates_numericality_of :approved, greater_than_or_equal_to: Proc.new(&:min_approved)
invalid!([3, 4])
valid!([5, 6])
diff --git a/activemodel/test/cases/validations/presence_validation_test.rb b/activemodel/test/cases/validations/presence_validation_test.rb
index ecf16d1e16..59b9db0795 100644
--- a/activemodel/test/cases/validations/presence_validation_test.rb
+++ b/activemodel/test/cases/validations/presence_validation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/topic'
diff --git a/activemodel/test/cases/validations/validates_test.rb b/activemodel/test/cases/validations/validates_test.rb
index 8d4b74ee49..04101f3545 100644
--- a/activemodel/test/cases/validations/validates_test.rb
+++ b/activemodel/test/cases/validations/validates_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/person'
require 'models/topic'
diff --git a/activemodel/test/cases/validations/validations_context_test.rb b/activemodel/test/cases/validations/validations_context_test.rb
index 005bf118c6..150dce379f 100644
--- a/activemodel/test/cases/validations/validations_context_test.rb
+++ b/activemodel/test/cases/validations/validations_context_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/topic'
diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb
index 736c2deea8..01804032f0 100644
--- a/activemodel/test/cases/validations/with_validation_test.rb
+++ b/activemodel/test/cases/validations/with_validation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/topic'
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index de71bb6f42..f0317ad219 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/topic'
@@ -171,10 +170,45 @@ class ValidationsTest < ActiveModel::TestCase
# A common mistake -- we meant to call 'validates'
Topic.validate :title, presence: true
end
- message = 'Unknown key: :presence. Valid keys are: :on, :if, :unless. Perhaps you meant to call `validates` instead of `validate`?'
+ message = 'Unknown key: :presence. Valid keys are: :on, :if, :unless, :prepend. Perhaps you meant to call `validates` instead of `validate`?'
assert_equal message, error.message
end
+ def test_callback_options_to_validate
+ klass = Class.new(Topic) do
+ attr_reader :call_sequence
+
+ def initialize(*)
+ super
+ @call_sequence = []
+ end
+
+ private
+ def validator_a
+ @call_sequence << :a
+ end
+
+ def validator_b
+ @call_sequence << :b
+ end
+
+ def validator_c
+ @call_sequence << :c
+ end
+ end
+
+ assert_nothing_raised do
+ klass.validate :validator_a, if: ->{ true }
+ klass.validate :validator_b, prepend: true
+ klass.validate :validator_c, unless: ->{ true }
+ end
+
+ t = klass.new
+
+ assert_predicate t, :valid?
+ assert_equal [:b, :a], t.call_sequence
+ end
+
def test_errors_conversions
Topic.validates_presence_of %w(title content)
t = Topic.new
@@ -282,7 +316,7 @@ class ValidationsTest < ActiveModel::TestCase
ActiveModel::Validations::FormatValidator,
ActiveModel::Validations::LengthValidator,
ActiveModel::Validations::PresenceValidator
- ], validators.map { |v| v.class }.sort_by { |c| c.to_s }
+ ], validators.map(&:class).sort_by(&:to_s)
end
def test_list_of_validators_will_be_empty_when_empty
@@ -317,6 +351,25 @@ class ValidationsTest < ActiveModel::TestCase
assert_not_empty topic.errors
end
+ def test_validate_with_bang
+ Topic.validates :title, presence: true
+
+ assert_raise(ActiveModel::ValidationError) do
+ Topic.new.validate!
+ end
+ end
+
+ def test_validate_with_bang_and_context
+ Topic.validates :title, presence: true, on: :context
+
+ assert_raise(ActiveModel::ValidationError) do
+ Topic.new.validate!(:context)
+ end
+
+ t = Topic.new(title: "Valid title")
+ assert t.validate!(:context)
+ end
+
def test_strict_validation_in_validates
Topic.validates :title, strict: true, presence: true
assert_raises ActiveModel::StrictValidationFailed do
diff --git a/activemodel/test/models/topic.rb b/activemodel/test/models/topic.rb
index 1411a093e9..fed50bc361 100644
--- a/activemodel/test/models/topic.rb
+++ b/activemodel/test/models/topic.rb
@@ -37,4 +37,8 @@ class Topic
errors.add attr, "is missing" unless send(attr)
end
+ def my_word_tokenizer(str)
+ str.scan(/\w+/)
+ end
+
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 1961ba93c6..904ac5c26a 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,1277 +1,763 @@
-* Added SchemaDumper support for tables with jsonb columns.
+* 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.
- *Ted O'Meara*
+ *Andrey Voronkov*
-* Deprecate `sanitize_sql_hash_for_conditions` without replacement. Using a
- `Relation` for performing queries and updates is the prefered API.
+* MySQL: `:charset` and `:collation` support for string and text columns.
- *Sean Griffin*
-
-* Queries now properly type cast values that are part of a join statement,
- even when using type decorators such as `serialize`.
-
- *Melanie Gilman & Sean Griffin*
-
-* MySQL enum type lookups, with values matching another type, no longer result
- in an endless loop.
-
- Fixes #17402.
-
- *Yves Senn*
-
-* Raise `ArgumentError` when the body of a scope is not callable.
-
- *Mauro George*
-
-* Use type column first in multi-column indexes created with `add-reference`.
-
- *Derek Prior*
-
-* Fix `Relation.rewhere` to work with Range values.
-
- *Dan Olson*
-
-* `AR::UnknownAttributeError` now includes the class name of a record.
-
- User.new(name: "Yuki Nishijima", project_attributes: {name: "kaminari"})
- # => ActiveRecord::UnknownAttributeError: unknown attribute 'name' for User.
-
- *Yuki Nishijima*
-
-* Fix regression causing `after_create` callbacks to run before associated
- records are autosaved.
-
- Fixes #17209.
-
- *Agis Anastasopoulos*
-
-* Honor overridden `rack.test` in Rack environment for the connection
- management middleware.
-
- *Simon Eskildsen*
-
-* Add a truncate method to the connection.
-
- *Aaron Patterson*
-
-* Don't autosave unchanged has_one through records.
-
- *Alan Kennedy*, *Steve Parrington*
-
-* Do not dump foreign keys for ignored tables.
-
- *Yves Senn*
-
-* PostgreSQL adapter correctly dumps foreign keys targeting tables
- outside the schema search path.
-
- Fixes #16907.
-
- *Matthew Draper*, *Yves Senn*
-
-* When a thread is killed, rollback the active transaction, instead of
- committing it during the stack unwind. Previously, we could commit half-
- completed work. This fix only works for Ruby 2.0+; on 1.9, we can't
- distinguish a thread kill from an ordinary non-local (block) return, so must
- default to committing.
-
- *Chris Hanks*
-
-* A `NullRelation` should represent nothing. This fixes a bug where
- `Comment.where(post_id: Post.none)` returned a non-empty result.
+ Example:
- Fixes #15176.
+ create_table :foos do |t|
+ t.string :string_utf8_bin, charset: 'utf8', collation: 'utf8_bin'
+ t.text :text_ascii, charset: 'ascii'
+ end
- *Matthew Draper*, *Yves Senn*
+ *Ryuta Kamizono*
-* Include default column limits in schema.rb. Allows defaults to be changed
- in the future without affecting old migrations that assumed old defaults.
+* Foreign key related methods in the migration DSL respect
+ `ActiveRecord::Base.pluralize_table_names = false`.
- *Jeremy Kemper*
+ Fixes #19643.
-* MySQL: schema.rb now includes TEXT and BLOB column limits.
+ *Mehmet Emin İNAÇ*
- *Jeremy Kemper*
+* Reduce memory usage from loading types on pg.
-* MySQL: correct LONGTEXT and LONGBLOB limits from 2GB to their true 4GB.
+ Fixes #19578.
- *Jeremy Kemper*
+ *Sean Griffin*
-* SQLite3Adapter now checks for views in `table_exists?`. Fixes #14041.
+* Add `config.active_record.warn_on_records_fetched_greater_than` option
- *Girish Sonawane*
+ 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
-* Introduce `connection.supports_views?` to check whether the current adapter
- has support for SQL views. Connection adapters should define this method.
+ *Jason Nochlin*
- *Yves Senn*
+* Ignore psqlrc when loading database structure.
-* Allow included modules to override association methods.
+ *Jason Weathered*
- Fixes #16684.
+* Fix referencing wrong table aliases while joining tables of has many through
+ association (only when calling calculation methods).
- *Yves Senn*
+ Fixes #19276.
-* Schema loading rake tasks (like `db:schema:load` and `db:setup`) maintain
- the database connection to the current environment.
+ *pinglamb*
- Fixes #16757.
+* Correctly persist a serialized attribute that has been returned to
+ its default value by an in-place modification.
- *Joshua Cody*, *Yves Senn*
+ Fixes #19467.
-* MySQL: set the connection collation along with the charset.
-
- Sets the connection collation to the database collation configured in
- database.yml. Otherwise, `SET NAMES utf8mb4` will use the default
- collation for that charset (utf8mb4_general_ci) when you may have chosen
- a different collation, like utf8mb4_unicode_ci.
+ *Matthew Draper*
- This only applies to literal string comparisons, not column values, so it
- is unlikely to affect you.
+* Fix generating the schema file when using PostgreSQL `BigInt[]` data type.
+ Previously the `limit: 8` was not coming through, and this caused it to
+ become `Int[]` data type after rebuilding from the schema.
- *Jeremy Kemper*
+ Fixes #19420.
-* `default_sequence_name` from the PostgreSQL adapter returns a `String`.
+ *Jake Waller*
- *Yves Senn*
+* Reuse the `CollectionAssociation#reader` cache when the foreign key is
+ available prior to save.
-* Fixed a regression where whitespaces were stripped from DISTINCT queries in
- PostgreSQL.
+ *Ben Woosley*
- *Agis Anastasopoulos*
+* Add `config.active_record.dump_schemas` to fix `db:structure:dump`
+ when using schema_search_path and PostgreSQL extensions.
- Fixes #16623.
+ Fixes #17157.
-* Fix has_many :through relation merging failing when dynamic conditions are
- passed as a lambda with an arity of one.
+ *Ryan Wallace*
- Fixes #16128.
+* Renaming `use_transactional_fixtures` to `use_transactional_tests` for clarity.
- *Agis Anastasopoulos*
+ Fixes #18864.
-* Fixed `Relation#exists?` to work with polymorphic associations.
+ *Brandon Weiss*
- Fixes #15821.
+* Increase pg gem version requirement to `~> 0.18`. Earlier versions of the
+ pg gem are known to have problems with Ruby 2.2.
- *Kassio Borges*
+ *Matt Brictson*
-* Currently, Active Record rescues any errors raised within
- `after_rollback`/`after_create` callbacks and prints them to the logs.
- Future versions of Rails will not rescue these errors anymore and
- just bubble them up like the other callbacks.
+* Correctly dump `serial` and `bigserial`.
- This commit adds an opt-in flag to enable not rescuing the errors.
+ *Ryuta Kamizono*
- Example:
+* Fix default `format` value in `ActiveRecord::Tasks::DatabaseTasks#schema_file`.
- # Do not swallow errors in after_commit/after_rollback callbacks.
- config.active_record.raise_in_transactional_callbacks = true
+ *James Cox*
- Fixes #13460.
+* Don't enroll records in the transaction if they don't have commit callbacks.
+ This was causing a memory leak when creating many records inside a transaction.
- *arthurnn*
+ Fixes #15549.
-* Fixed an issue where custom accessor methods (such as those generated by
- `enum`) with the same name as a global method are incorrectly overridden
- when subclassing.
+ *Will Bryant*, *Aaron Patterson*
- Fixes #16288.
+* Correctly create through records when created on a has many through
+ association when using `where`.
- *Godfrey Chan*
-
-* `*_was` and `changes` now work correctly for in-place attribute changes as
- well.
+ Fixes #19073.
*Sean Griffin*
-* Fix regression on `after_commit` that did not fire with nested transactions.
-
- Fixes #16425.
-
- *arthurnn*
-
-* Do not try to write timestamps when a table has no timestamps columns.
-
- Fixes #8813.
-
- *Sergey Potapov*
-
-* `index_exists?` with `:name` option does verify specified columns.
-
- Example:
-
- add_index :articles, :title, name: "idx_title"
-
- # Before:
- index_exists? :articles, :title, name: "idx_title" # => `true`
- index_exists? :articles, :body, name: "idx_title" # => `true`
-
- # After:
- index_exists? :articles, :title, name: "idx_title" # => `true`
- index_exists? :articles, :body, name: "idx_title" # => `false`
-
- *Yves Senn*, *Matthew Draper*
-
-* When calling `update_columns` on a record that is not persisted, the error
- message now reflects whether that object is a new record or has been
- destroyed.
-
- *Lachlan Sylvester*
+* Add `SchemaMigration.create_table` support for any unicode charsets with MySQL.
-* Define `id_was` to get the previous value of the primary key.
-
- Currently when we call `id_was` and we have a custom primary key name,
- Active Record will return the current value of the primary key. This
- makes it impossible to correctly do an update operation if you change the
- id.
+ *Ryuta Kamizono*
- Fixes #16413.
+* PostgreSQL no longer disables user triggers if system triggers can't be
+ disabled. Disabling user triggers does not fulfill what the method promises.
+ Rails currently requires superuser privileges for this method.
- *Rafael Mendonça França*
-
-* Deprecate `DatabaseTasks.load_schema` to act on the current connection.
- Use `.load_schema_current` instead. In the future `load_schema` will
- require the `configuration` to act on as an argument.
+ If you absolutely rely on this behavior, consider patching
+ `disable_referential_integrity`.
*Yves Senn*
-* Fixed automatic maintaining test schema to properly handle sql structure
- schema format.
-
- Fixes #15394.
-
- *Wojciech Wnętrzak*
-
-* Fix type casting to Decimal from Float with large precision.
-
- *Tomohiro Hashidate*
+* Restore aborted transaction state when `disable_referential_integrity` fails
+ due to missing permissions.
-* Deprecate `Reflection#source_macro`
+ *Toby Ovod-Everett*, *Yves Senn*
- `Reflection#source_macro` is no longer needed in Active Record
- source so it has been deprecated. Code that used `source_macro`
- was removed in #16353.
+* In PostgreSQL, print a warning message if `disable_referential_integrity`
+ fails due to missing permissions.
- *Eileen M. Uchtitelle*, *Aaron Patterson*
+ *Andrey Nering*, *Yves Senn*
-* No verbose backtrace by `db:drop` when database does not exist.
-
- Fixes #16295.
-
- *Kenn Ejima*
-
-* Add support for PostgreSQL JSONB.
+* Allow a `:limit` option for MySQL bigint primary key support.
Example:
- create_table :posts do |t|
- t.jsonb :meta_data
+ create_table :foos, id: :primary_key, limit: 8 do |t|
end
- *Philippe Creux*, *Chris Teague*
-
-* `db:purge` with MySQL respects `Rails.env`.
-
- *Yves Senn*
-
-* `change_column_default :table, :column, nil` with PostgreSQL will issue a
- `DROP DEFAULT` instead of a `DEFAULT NULL` query.
-
- Fixes #16261.
-
- *Matthew Draper*, *Yves Senn*
-
-* Allow to specify a type for the foreign key column in `references`
- and `add_reference`.
-
- Example:
+ # or
- change_table :vehicle do |t|
- t.references :station, type: :uuid
+ create_table :foos, id: false do |t|
+ t.primary_key :id, limit: 8
end
- *Andrey Novikov*, *Łukasz Sarnacki*
+ *Ryuta Kamizono*
-* `create_join_table` removes a common prefix when generating the join table.
- This matches the existing behavior of HABTM associations.
+* `belongs_to` will now trigger a validation error by default if the association is not present.
+ You can turn this off on a per-association basis with `optional: true`.
+ (Note this new default only applies to new Rails apps that will be generated with
+ `config.active_record.belongs_to_required_by_default = true` in initializer.)
- Fixes #13683.
+ *Josef Šimánek*
- *Stefan Kanev*
+* Fixed ActiveRecord::Relation#becomes! and changed_attributes issues for type
+ columns.
-* Do not swallow errors on `compute_type` when having a bad `alias_method` on
- a class.
+ Fixes #17139.
- *arthurnn*
+ *Miklos Fazekas*
-* PostgreSQL invalid `uuid` are convert to nil.
+* Format the time string according to the precision of the time column.
- *Abdelkader Boudih*
+ *Ryuta Kamizono*
-* Restore 4.0 behavior for using serialize attributes with `JSON` as coder.
+* Allow a `:precision` option for time type columns.
- With 4.1.x, `serialize` started returning a string when `JSON` was passed as
- the second attribute. It will now return a hash as per previous versions.
+ *Ryuta Kamizono*
- Example:
+* Add `ActiveRecord::Base.suppress` to prevent the receiver from being saved
+ during the given block.
- class Post < ActiveRecord::Base
- serialize :comment, JSON
- end
+ For example, here's a pattern of creating notifications when new comments
+ are posted. (The notification may in turn trigger an email, a push
+ notification, or just appear in the UI somewhere):
- class Comment
- include ActiveModel::Model
- attr_accessor :category, :text
+ class Comment < ActiveRecord::Base
+ belongs_to :commentable, polymorphic: true
+ after_create -> { Notification.create! comment: self,
+ recipients: commentable.recipients }
end
- post = Post.create!
- post.comment = Comment.new(category: "Animals", text: "This is a comment about squirrels.")
- post.save!
-
- # 4.0
- post.comment # => {"category"=>"Animals", "text"=>"This is a comment about squirrels."}
+ That's what you want the bulk of the time. A new comment creates a new
+ Notification. There may be edge cases where you don't want that, like
+ when copying a commentable and its comments, in which case write a
+ concern with something like this:
+
+ module Copyable
+ def copy_to(destination)
+ Notification.suppress do
+ # Copy logic that creates new comments that we do not want triggering
+ # notifications.
+ end
+ end
+ end
- # 4.1 before
- post.comment # => "#<Comment:0x007f80ab48ff98>"
+ *Michael Ryan*
- # 4.1 after
- post.comment # => {"category"=>"Animals", "text"=>"This is a comment about squirrels."}
+* `:time` option added for `#touch`.
- When using `JSON` as the coder in `serialize`, Active Record will use the
- new `ActiveRecord::Coders::JSON` coder which delegates its `dump/load` to
- `ActiveSupport::JSON.encode/decode`. This ensures special objects are dumped
- correctly using the `#as_json` hook.
+ Fixes #18905.
- To keep the previous behaviour, supply a custom coder instead
- ([example](https://gist.github.com/jenncoop/8c4142bbe59da77daa63)).
+ *Hyonjee Joo*
- Fixes #15594.
+* Deprecate passing of `start` value to `find_in_batches` and `find_each`
+ in favour of `begin_at` value.
- *Jenn Cooper*
+ *Vipul A M*
-* Do not use `RENAME INDEX` syntax for MariaDB 10.0.
+* Add `foreign_key_exists?` method.
- Fixes #15931.
+ *Tõnis Simo*
- *Jeff Browning*
+* Use SQL COUNT and LIMIT 1 queries for `none?` and `one?` methods
+ if no block or limit is given, instead of loading the entire
+ collection into memory. This applies to relations (e.g. `User.all`)
+ as well as associations (e.g. `account.users`)
-* Calling `#empty?` on a `has_many` association would use the value from the
- counter cache if one exists.
+ # Before:
- *David Verhasselt*
+ users.none?
+ # SELECT "users".* FROM "users"
-* Fix the schema dump generated for tables without constraints and with
- primary key with default value of custom PostgreSQL function result.
+ users.one?
+ # SELECT "users".* FROM "users"
- Fixes #16111.
+ # After:
- *Andrey Novikov*
+ users.none?
+ # SELECT 1 AS one FROM "users" LIMIT 1
-* Fix the SQL generated when a `delete_all` is run on an association to not
- produce an `IN` statements.
+ users.one?
+ # SELECT COUNT(*) FROM "users"
- Before:
+ *Eugene Gilburg*
- UPDATE "categorizations" SET "category_id" = NULL WHERE
- "categorizations"."category_id" = 1 AND "categorizations"."id" IN (1, 2)
+* Have `enum` perform type casting consistently with the rest of Active
+ Record, such as `where`.
- After:
+ *Sean Griffin*
- UPDATE "categorizations" SET "category_id" = NULL WHERE
- "categorizations"."category_id" = 1
+* `scoping` no longer pollutes the current scope of sibling classes when using
+ STI. e.x.
- *Eileen M. Uchitelle, Aaron Patterson*
-
-* Avoid type casting boolean and `ActiveSupport::Duration` values to numeric
- values for string columns. Otherwise, in some database, the string column
- values will be coerced to a numeric allowing false or 0.seconds match any
- string starting with a non-digit.
+ StiOne.none.scoping do
+ StiTwo.all
+ end
- Example:
+ Fixes #18806.
- App.where(apikey: false) # => SELECT * FROM users WHERE apikey = '0'
+ *Sean Griffin*
- *Dylan Thacker-Smith*
+* `remove_reference` with `foreign_key: true` removes the foreign key before
+ removing the column. This fixes a bug where it was not possible to remove
+ the column on MySQL.
-* Add a `:required` option to singular associations, providing a nicer
- API for presence validations on associations.
+ Fixes #18664.
- *Sean Griffin*
+ *Yves Senn*
-* Fixed error in `reset_counters` when associations have `select` scope.
- (Call to `count` generates invalid SQL.)
+* `find_in_batches` now accepts an `:end_at` parameter that complements the `:start`
+ parameter to specify where to stop batch processing.
- *Cade Truitt*
+ *Vipul A M*
-* After a successful `reload`, `new_record?` is always false.
+* Fix a rounding problem for PostgreSQL timestamp columns.
- Fixes #12101.
+ If a timestamp column has a precision specified, it needs to
+ format according to that.
- *Matthew Draper*
+ *Ryuta Kamizono*
-* PostgreSQL renaming table doesn't attempt to rename non existent sequences.
+* Respect the database default charset for `schema_migrations` table.
- *Abdelkader Boudih*
+ The charset of `version` column in `schema_migrations` table depends
+ on the database default charset and collation rather than the encoding
+ of the connection.
-* Move 'dependent: :destroy' handling for `belongs_to`
- from `before_destroy` to `after_destroy` callback chain
+ *Ryuta Kamizono*
- Fixes #12380.
+* Raise `ArgumentError` when passing `nil` or `false` to `Relation#merge`.
- *Ivan Antropov*
+ These are not valid values to merge in a relation, so it should warn users
+ early.
-* Detect in-place modifications on String attributes.
+ *Rafael Mendonça França*
- Before this change, an attribute modified in-place had to be marked as
- changed in order for it to be persisted in the database. Now it is no longer
- required.
+* Use `SCHEMA` instead of `DB_STRUCTURE` for specifying a structure file.
- Before:
+ This makes the db:structure tasks consistent with test:load_structure.
- user = User.first
- user.name << ' Griffin'
- user.name_will_change!
- user.save
- user.reload.name # => "Sean Griffin"
+ *Dieter Komendera*
- After:
+* Respect custom primary keys for associations when calling `Relation#where`
- user = User.first
- user.name << ' Griffin'
- user.save
- user.reload.name # => "Sean Griffin"
+ Fixes #18813.
*Sean Griffin*
-* Add `ActiveRecord::Base#validate!` that raises `RecordInvalid` if the record
- is invalid.
+* Fix several edge cases which could result in a counter cache updating
+ twice or not updating at all for `has_many` and `has_many :through`.
- *Bogdan Gusiev*, *Marc Schütz*
+ Fixes #10865.
-* Support for adding and removing foreign keys. Foreign keys are now
- a part of `schema.rb`. This is supported by Mysql2Adapter, MysqlAdapter
- and PostgreSQLAdapter.
+ *Sean Griffin*
- Many thanks to *Matthew Higgins* for laying the foundation with his work on
- [foreigner](https://github.com/matthuhiggins/foreigner).
+* Foreign keys added by migrations were given random, generated names. This
+ meant a different `structure.sql` would be generated every time a developer
+ ran migrations on their machine.
- Example:
+ The generated part of foreign key names is now a hash of the table name and
+ column name, which is consistent every time you run the migration.
- # within your migrations:
- add_foreign_key :articles, :authors
- remove_foreign_key :articles, :authors
+ *Chris Sinjakli*
- *Yves Senn*
+* Validation errors would be raised for parent records when an association
+ was saved when the parent had `validate: false`. It should not be the
+ responsibility of the model to validate an associated object unless the
+ object was created or modified by the parent.
-* Fix subtle bugs regarding attribute assignment on models with no primary
- key. `'id'` will no longer be part of the attributes hash.
+ This fixes the issue by skipping validations if the parent record is
+ persisted, not changed, and not marked for destruction.
- *Sean Griffin*
+ Fixes #17621.
-* Deprecate automatic counter caches on `has_many :through`. The behavior was
- broken and inconsistent.
+ *Eileen M. Uchitelle, Aaron Patterson*
- *Sean Griffin*
+* Fix n+1 query problem when eager loading nil associations (fixes #18312)
-* `preload` preserves readonly flag for associations.
+ *Sammy Larbi*
- See #15853.
+* Change the default error message from `can't be blank` to `must exist` for
+ the presence validator of the `:required` option on `belongs_to`/`has_one`
+ associations.
- *Yves Senn*
+ *Henrik Nygren*
-* Assume numeric types have changed if they were assigned to a value that
- would fail numericality validation, regardless of the old value. Previously
- this would only occur if the old value was 0.
+* Fixed ActiveRecord::Relation#group method when an argument is an SQL
+ reserved key word:
Example:
- model = Model.create!(number: 5)
- model.number = '5wibble'
- model.number_changed? # => true
+ SplitTest.group(:key).count
+ Property.group(:value).count
- Fixes #14731.
+ *Bogdan Gusiev*
- *Sean Griffin*
+* Added the `#or` method on ActiveRecord::Relation, allowing use of the OR
+ operator to combine WHERE or HAVING clauses.
-* `reload` no longer merges with the existing attributes.
- The attribute hash is fully replaced. The record is put into the same state
- as it would be with `Model.find(model.id)`.
+ Example:
- *Sean Griffin*
+ Post.where('id = 1').or(Post.where('id = 2'))
+ # => SELECT * FROM posts WHERE (id = 1) OR (id = 2)
-* The object returned from `select_all` must respond to `column_types`.
- If this is not the case a `NoMethodError` is raised.
+ *Sean Griffin*, *Matthew Draper*, *Gael Muller*, *Olivier El Mekki*
- *Sean Griffin*
+* Don't define autosave association callbacks twice from
+ `accepts_nested_attributes_for`.
-* Detect in-place modifications of PG array types
+ Fixes #18704.
*Sean Griffin*
-* Add `bin/rake db:purge` task to empty the current database.
-
- *Yves Senn*
+* Integer types will no longer raise a `RangeError` when assigning an
+ attribute, but will instead raise when going to the database.
-* Deprecate `serialized_attributes` without replacement.
+ Fixes several vague issues which were never reported directly. See the
+ commit message from the commit which added this line for some examples.
*Sean Griffin*
-* Correctly extract IPv6 addresses from `DATABASE_URI`: the square brackets
- are part of the URI structure, not the actual host.
+* Values which would error while being sent to the database (such as an
+ ASCII-8BIT string with invalid UTF-8 bytes on SQLite3), no longer error on
+ assignment. They will still error when sent to the database, but you are
+ given the ability to re-assign it to a valid value.
- Fixes #15705.
-
- *Andy Bakun*, *Aaron Stone*
-
-* Ensure both parent IDs are set on join records when both sides of a
- through association are new.
+ Fixes #18580.
*Sean Griffin*
-* `ActiveRecord::Dirty` now detects in-place changes to mutable values.
- Serialized attributes on ActiveRecord models will no longer save when
- unchanged.
+* Don't remove join dependencies in `Relation#exists?`
- Fixes #8328.
+ Fixes #18632.
*Sean Griffin*
-* `Pluck` now works when selecting columns from different tables with the same
- name.
+* Invalid values assigned to a JSON column are assumed to be `nil`.
- Fixes #15649.
+ Fixes #18629.
*Sean Griffin*
-* Remove `cache_attributes` and friends. All attributes are cached.
+* Add `ActiveRecord::Base#accessed_fields`, which can be used to quickly
+ discover which fields were read from a model when you are looking to only
+ select the data you need from the database.
*Sean Griffin*
-* Remove deprecated method `ActiveRecord::Base.quoted_locking_column`.
-
- *Akshay Vishnoi*
-
-* `ActiveRecord::FinderMethods.find` with block can handle proc parameter as
- `Enumerable#find` does.
-
- Fixes #15382.
-
- *James Yang*
-
-* Make timezone aware attributes work with PostgreSQL array columns.
-
- Fixes #13402.
-
- *Kuldeep Aggarwal*, *Sean Griffin*
-
-* `ActiveRecord::SchemaMigration` has no primary key regardless of the
- `primary_key_prefix_type` configuration.
-
- Fixes #15051.
+* Introduce the `:if_exists` option for `drop_table`.
- *JoseLuis Torres*, *Yves Senn*
-
-* `rake db:migrate:status` works with legacy migration numbers like `00018_xyz.rb`.
+ Example:
- Fixes #15538.
+ drop_table(:posts, if_exists: true)
- *Yves Senn*
+ That would execute:
-* Baseclass becomes! subclass.
+ DROP TABLE IF EXISTS posts
- Before this change, a record which changed its STI type, could not be
- updated.
+ If the table doesn't exist, `if_exists: false` (the default) raises an
+ exception whereas `if_exists: true` does nothing.
- Fixes #14785.
+ *Cody Cutrer*, *Stefan Kanev*, *Ryuta Kamizono*
- *Matthew Draper*, *Earl St Sauver*, *Edo Balvers*
+* Don't run SQL if attribute value is not changed for update_attribute method.
-* Remove deprecated `ActiveRecord::Migrator.proper_table_name`. Use the
- `proper_table_name` instance method on `ActiveRecord::Migration` instead.
+ *Prathamesh Sonpatki*
- *Akshay Vishnoi*
+* `time` columns can now get affected by `time_zone_aware_attributes`. If you have
+ set `config.time_zone` to a value other than `'UTC'`, they will be treated
+ as in that time zone by default in Rails 5.1. If this is not the desired
+ behavior, you can set
-* Fix regression on eager loading association based on SQL query rather than
- existing column.
+ ActiveRecord::Base.time_zone_aware_types = [:datetime]
- Fixes #15480.
+ A deprecation warning will be emitted if you have a `:time` column, and have
+ not explicitly opted out.
- *Lauro Caetano*, *Carlos Antonio da Silva*
-
-* Deprecate returning `nil` from `column_for_attribute` when no column exists.
- It will return a null object in Rails 5.0
+ Fixes #3145.
*Sean Griffin*
-* Implemented `ActiveRecord::Base#pretty_print` to work with PP.
-
- *Ethan*
+* Tests now run after_commit callbacks. You no longer have to declare
+ `uses_transaction ‘test name’` to test the results of an after_commit.
-* Preserve type when dumping PostgreSQL point, bit, bit varying and money
- columns.
+ after_commit callbacks run after committing a transaction whose parent
+ is not `joinable?`: un-nested transactions, transactions within test cases,
+ and transactions in `console --sandbox`.
- *Yves Senn*
+ *arthurnn*, *Ravil Bayramgalin*, *Matthew Draper*
-* New records remain new after YAML serialization.
+* `nil` as a value for a binary column in a query no longer logs as
+ "<NULL binary data>", and instead logs as just "nil".
*Sean Griffin*
-* PostgreSQL support default values for enum types. Fixes #7814.
-
- *Yves Senn*
-
-* PostgreSQL `default_sequence_name` respects schema. Fixes #7516.
-
- *Yves Senn*
-
-* Fixed `columns_for_distinct` of postgresql adapter to work correctly
- with orders without sort direction modifiers.
-
- *Nikolay Kondratyev*
-
-* PostgreSQL `reset_pk_sequence!` respects schemas. Fixes #14719.
-
- *Yves Senn*
-
-* Keep PostgreSQL `hstore` and `json` attributes as `Hash` in `@attributes`.
- Fixes duplication in combination with `store_accessor`.
-
- Fixes #15369.
-
- *Yves Senn*
-
-* `rake railties:install:migrations` respects the order of railties.
-
- *Arun Agrawal*
-
-* Fix redefine a `has_and_belongs_to_many` inside inherited class
- Fixing regression case, where redefining the same `has_and_belongs_to_many`
- definition into a subclass would raise.
-
- Fixes #14983.
-
- *arthurnn*
-
-* Fix `has_and_belongs_to_many` public reflection.
- When defining a `has_and_belongs_to_many`, internally we convert that to two has_many.
- But as `reflections` is a public API, people expect to see the right macro.
-
- Fixes #14682.
-
- *arthurnn*
+* `attribute_will_change!` will no longer cause non-persistable attributes to
+ be sent to the database.
-* Fixed serialization for records with an attribute named `format`.
-
- Fixes #15188.
-
- *Godfrey Chan*
-
-* When a `group` is set, `sum`, `size`, `average`, `minimum` and `maximum`
- on a NullRelation should return a Hash.
-
- *Kuldeep Aggarwal*
-
-* Fixed serialized fields returning serialized data after being updated with
- `update_column`.
-
- *Simon Hørup Eskildsen*
-
-* Fixed polymorphic eager loading when using a String as foreign key.
-
- Fixes #14734.
-
- *Lauro Caetano*
-
-* Change belongs_to touch to be consistent with timestamp updates
-
- If a model is set up with a belongs_to: touch relationship the parent
- record will only be touched if the record was modified. This makes it
- consistent with timestamp updating on the record itself.
-
- *Brock Trappitt*
-
-* Fixed the inferred table name of a `has_and_belongs_to_many` auxiliar
- table inside a schema.
-
- Fixes #14824.
-
- *Eric Chahin*
-
-* Remove unused `:timestamp` type. Transparently alias it to `:datetime`
- in all cases. Fixes inconsistencies when column types are sent outside of
- `ActiveRecord`, such as for XML Serialization.
+ Fixes #18407.
*Sean Griffin*
-* Fix bug that added `table_name_prefix` and `table_name_suffix` to
- extension names in PostgreSQL when migrating.
-
- *Joao Carlos*
-
-* The `:index` option in migrations, which previously was only available for
- `references`, now works with any column types.
-
- *Marc Schütz*
-
-* Add support for counter name to be passed as parameter on `CounterCache::ClassMethods#reset_counters`.
-
- *jnormore*
-
-* Restrict deletion of record when using `delete_all` with `uniq`, `group`, `having`
- or `offset`.
+* Remove support for the `protected_attributes` gem.
- In these cases the generated query ignored them and that caused unintended
- records to be deleted.
+ *Carlos Antonio da Silva*, *Roberto Miranda*
- Fixes #11985.
+* Fix accessing of fixtures having non-string labels like Fixnum.
- *Leandro Facchinetti*
+ *Prathamesh Sonpatki*
-* Floats with limit >= 25 that get turned into doubles in MySQL no longer have
- their limit dropped from the schema.
-
- Fixes #14135.
-
- *Aaron Nelson*
-
-* Fix how to calculate associated class name when using namespaced `has_and_belongs_to_many`
- association.
-
- Fixes #14709.
-
- *Kassio Borges*
-
-* `ActiveRecord::Relation::Merger#filter_binds` now compares equivalent symbols and
- strings in column names as equal.
-
- This fixes a rare case in which more bind values are passed than there are
- placeholders for them in the generated SQL statement, which can make PostgreSQL
- throw a `StatementInvalid` exception.
-
- *Nat Budin*
-
-* Fix `stored_attributes` to correctly merge the details of stored
- attributes defined in parent classes.
-
- Fixes #14672.
-
- *Brad Bennett*, *Jessica Yao*, *Lakshmi Parthasarathy*
-
-* `change_column_default` allows `[]` as argument to `change_column_default`.
-
- Fixes #11586.
+* Remove deprecated support to preload instance-dependent associations.
*Yves Senn*
-* Handle `name` and `"char"` column types in the PostgreSQL adapter.
-
- `name` and `"char"` are special character types used internally by
- PostgreSQL and are used by internal system catalogs. These field types
- can sometimes show up in structure-sniffing queries that feature internal system
- structures or with certain PostgreSQL extensions.
-
- *J Smith*, *Yves Senn*
-
-* Fix `PostgreSQLAdapter::OID::Float#type_cast` to convert Infinity and
- NaN PostgreSQL values into a native Ruby `Float::INFINITY` and `Float::NAN`
-
- Before:
-
- Point.create(value: 1.0/0)
- Point.last.value # => 0.0
-
- After:
-
- Point.create(value: 1.0/0)
- Point.last.value # => Infinity
-
- *Innokenty Mikhailov*
-
-* Allow the PostgreSQL adapter to handle bigserial primary key types again.
-
- Fixes #10410.
-
- *Patrick Robertson*
-
-* Deprecate joining, eager loading and preloading of instance dependent
- associations without replacement. These operations happen before instances
- are created. The current behavior is unexpected and can result in broken
- behavior.
-
- Fixes #15024.
+* Remove deprecated support for PostgreSQL ranges with exclusive lower bounds.
*Yves Senn*
-* Fixed has_and_belongs_to_many's CollectionAssociation size calculation.
-
- `has_and_belongs_to_many` should fall back to using the normal CollectionAssociation's
- size calculation if the collection is not cached or loaded.
-
- Fixes #14913, #14914.
-
- *Fred Wu*
-
-* Return a non zero status when running `rake db:migrate:status` and migration table does
- not exist.
-
- *Paul B.*
-
-* Add support for module-level `table_name_suffix` in models.
-
- This makes `table_name_suffix` work the same way as `table_name_prefix` when
- using namespaced models.
-
- *Jenner LaFave*
-
-* Revert the behaviour of `ActiveRecord::Relation#join` changed through 4.0 => 4.1 to 4.0.
-
- In 4.1.0 `Relation#join` is delegated to `Arel#SelectManager`.
- In 4.0 series it is delegated to `Array#join`.
-
- *Bogdan Gusiev*
-
-* Log nil binary column values correctly.
-
- When an object with a binary column is updated with a nil value
- in that column, the SQL logger would throw an exception when trying
- to log that nil value. This only occurs when updating a record
- that already has a non-nil value in that column since an initial nil
- value isn't included in the SQL anyway (at least, when dirty checking
- is enabled.) The column's new value will now be logged as `<NULL binary data>`
- to parallel the existing `<N bytes of binary data>` for non-nil values.
-
- *James Coleman*
-
-* Rails will now pass a custom validation context through to autosave associations
- in order to validate child associations with the same context.
-
- Fixes #13854.
-
- *Eric Chahin*, *Aaron Nelson*, *Kevin Casey*
-
-* Stringify all variables keys of MySQL connection configuration.
-
- When `sql_mode` variable for MySQL adapters set in configuration as `String`
- was ignored and overwritten by strict mode option.
-
- Fixes #14895.
-
- *Paul Nikitochkin*
-
-* Ensure SQLite3 statements are closed on errors.
-
- Fixes #13631.
-
- *Timur Alperovich*
-
-* Give `ActiveRecord::PredicateBuilder` private methods the privacy they deserve.
-
- *Hector Satre*
-
-* When using a custom `join_table` name on a `habtm`, rails was not saving it
- on Reflections. This causes a problem when rails loads fixtures, because it
- uses the reflections to set database with fixtures.
-
- Fixes #14845.
-
- *Kassio Borges*
-
-* Reset the cache when modifying a Relation with cached Arel.
- Additionally display a warning message to make the user aware.
+* Remove deprecation when modifying a relation with cached Arel.
+ This raises an `ImmutableRelation` error instead.
*Yves Senn*
-* PostgreSQL should internally use `:datetime` consistently for TimeStamp. Assures
- different spellings of timestamps are treated the same.
+* Added `ActiveRecord::SecureToken` in order to encapsulate generation of
+ unique tokens for attributes in a model using `SecureRandom`.
- Example:
+ *Roberto Miranda*
- mytimestamp.simplified_type('timestamp without time zone')
- # => :datetime
- mytimestamp.simplified_type('timestamp(6) without time zone')
- # => also :datetime (previously would be :timestamp)
+* Change the behavior of boolean columns to be closer to Ruby's semantics.
- See #14513.
+ Before this change we had a small set of "truthy", and all others are "falsy".
- *Jefferson Lai*
+ Now, we have a small set of "falsy" values and all others are "truthy" matching
+ Ruby's semantics.
-* `ActiveRecord::Base.no_touching` no longer triggers callbacks or start empty transactions.
+ *Rafael Mendonça França*
- Fixes #14841.
+* Deprecate `ActiveRecord::Base.errors_in_transactional_callbacks=`.
- *Lucas Mazza*
+ *Rafael Mendonça França*
-* Fix name collision with `Array#select!` with `Relation#select!`.
+* Change transaction callbacks to not swallow errors.
- Fixes #14752.
+ Before this change any errors raised inside a transaction callback
+ were getting rescued and printed in the logs.
- *Earl St Sauver*
+ Now these errors are not rescued anymore and just bubble up, as the other callbacks.
-* Fixed unexpected behavior for `has_many :through` associations going through a scoped `has_many`.
+ *Rafael Mendonça França*
- If a `has_many` association is adjusted using a scope, and another `has_many :through`
- uses this association, then the scope adjustment is unexpectedly neglected.
+* Remove deprecated `sanitize_sql_hash_for_conditions`.
- Fixes #14537.
+ *Rafael Mendonça França*
- *Jan Habermann*
+* Remove deprecated `Reflection#source_macro`.
-* `@destroyed` should always be set to `false` when an object is duped.
+ *Rafael Mendonça França*
- *Kuldeep Aggarwal*
+* Remove deprecated `symbolized_base_class` and `symbolized_sti_name`.
-* Fixed `has_many` association to make it support irregular inflections.
+ *Rafael Mendonça França*
- Fixes #8928.
+* Remove deprecated `ActiveRecord::Base.disable_implicit_join_references=`.
- *arthurnn*, *Javier Goizueta*
+ *Rafael Mendonça França*
-* Fixed a problem where count used with a grouping was not returning a Hash.
+* Remove deprecated access to connection specification using a string accessor.
- Fixes #14721.
+ Now all strings will be handled as a URL.
- *Eric Chahin*
+ *Rafael Mendonça França*
-* `sanitize_sql_like` helper method to escape a string for safe use in an SQL
- LIKE statement.
+* Change the default `null` value for `timestamps` to `false`.
- Example:
+ *Rafael Mendonça França*
- class Article
- def self.search(term)
- where("title LIKE ?", sanitize_sql_like(term))
- end
- end
+* Return an array of pools from `connection_pools`.
- Article.search("20% _reduction_")
- # => Query looks like "... title LIKE '20\% \_reduction\_' ..."
+ *Rafael Mendonça França*
- *Rob Gilson*, *Yves Senn*
+* Return a null column from `column_for_attribute` when no column exists.
-* Do not quote uuid default value on `change_column`.
+ *Rafael Mendonça França*
- Fixes #14604.
+* Remove deprecated `serialized_attributes`.
- *Eric Chahin*
+ *Rafael Mendonça França*
-* The comparison between `Relation` and `CollectionProxy` should be consistent.
+* Remove deprecated automatic counter caches on `has_many :through`.
- Example:
+ *Rafael Mendonça França*
- author.posts == Post.where(author_id: author.id)
- # => true
- Post.where(author_id: author.id) == author.posts
- # => true
+* Change the way in which callback chains can be halted.
- Fixes #13506.
+ 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
+ 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
+ either not work at all or display a deprecation warning.
- *Lauro Caetano*
+ *claudiob*
-* Calling `delete_all` on an unloaded `CollectionProxy` no longer
- generates an SQL statement containing each id of the collection:
+* Clear query cache on rollback.
- Before:
+ *Florian Weingarten*
- DELETE FROM `model` WHERE `model`.`parent_id` = 1
- AND `model`.`id` IN (1, 2, 3...)
+* Fix setting of foreign_key for through associations when building a new record.
- After:
+ Fixes #12698.
- DELETE FROM `model` WHERE `model`.`parent_id` = 1
+ *Ivan Antropov*
- *Eileen M. Uchitelle*, *Aaron Patterson*
+* Improve dumping of the primary key. If it is not a default primary key,
+ correctly dump the type and options.
-* Fixed error for aggregate methods (`empty?`, `any?`, `count`) with `select`
- which created invalid SQL.
+ Fixes #14169, #16599.
- Fixes #13648.
+ *Ryuta Kamizono*
- *Simon Woker*
+* Format the datetime string according to the precision of the datetime field.
-* PostgreSQL adapter only warns once for every missing OID per connection.
+ Incompatible to rounding behavior between MySQL 5.6 and earlier.
- Fixes #14275.
+ In 5.5, when you insert `2014-08-17 12:30:00.999999` the fractional part
+ is ignored. In 5.6, it's rounded to `2014-08-17 12:30:01`:
- *Matthew Draper*, *Yves Senn*
+ http://bugs.mysql.com/bug.php?id=68760
-* PostgreSQL adapter automatically reloads it's type map when encountering
- unknown OIDs.
+ *Ryuta Kamizono*
- Fixes #14678.
+* Allow a precision option for MySQL datetimes.
- *Matthew Draper*, *Yves Senn*
+ *Ryuta Kamizono*
-* Fix insertion of records via `has_many :through` association with scope.
+* Fixed automatic `inverse_of` for models nested in a module.
- Fixes #3548.
+ *Andrew McCloud*
- *Ivan Antropov*
+* Change `ActiveRecord::Relation#update` behavior so that it can
+ be called without passing ids of the records to be updated.
-* Auto-generate stable fixture UUIDs on PostgreSQL.
+ This change allows updating multiple records returned by
+ `ActiveRecord::Relation` with callbacks and validations.
- Fixes #11524.
+ # Before
+ # ArgumentError: wrong number of arguments (1 for 2)
+ Comment.where(group: 'expert').update(body: "Group of Rails Experts")
- *Roderick van Domburg*
+ # After
+ # Comments with group expert updated with body "Group of Rails Experts"
+ Comment.where(group: 'expert').update(body: "Group of Rails Experts")
-* Fixed a problem where an enum would overwrite values of another enum
- with the same name in an unrelated class.
+ *Prathamesh Sonpatki*
- Fixes #14607.
+* Fix `reaping_frequency` option when the value is a string.
- *Evan Whalen*
+ This usually happens when it is configured using `DATABASE_URL`.
-* PostgreSQL and SQLite string columns no longer have a default limit of 255.
+ *korbin*
- Fixes #13435, #9153.
+* Fix error message when trying to create an associated record and the foreign
+ key is missing.
- *Vladimir Sazhin*, *Toms Mikoss*, *Yves Senn*
+ Before this fix the following exception was being raised:
-* Make possible to have an association called `records`.
+ NoMethodError: undefined method `val' for #<Arel::Nodes::BindParam:0x007fc64d19c218>
- Fixes #11645.
+ Now the message is:
- *prathamesh-sonpatki*
+ ActiveRecord::UnknownAttributeError: unknown attribute 'foreign_key' for Model.
-* `to_sql` on an association now matches the query that is actually executed, where it
- could previously have incorrectly accrued additional conditions (e.g. as a result of
- a previous query). `CollectionProxy` now always defers to the association scope's
- `arel` method so the (incorrect) inherited one should be entirely concealed.
+ *Rafael Mendonça França*
- Fixes #14003.
+* Fix change detection problem for PostgreSQL bytea type and
+ `ArgumentError: string contains null byte` exception with pg-0.18.
- *Jefferson Lai*
+ Fixes #17680.
-* Block a few default Class methods as scope name.
+ *Lars Kanis*
- For instance, this will raise:
+* When a table has a composite primary key, the `primary_key` method for
+ SQLite3 and PostgreSQL adapters was only returning the first field of the key.
+ Ensures that it will return nil instead, as Active Record doesn't support
+ composite primary keys.
- scope :public, -> { where(status: 1) }
+ Fixes #18070.
*arthurnn*
-* Fixed error when using `with_options` with lambda.
-
- Fixes #9805.
+* `validates_size_of` / `validates_length_of` do not count records
+ which are `marked_for_destruction?`.
- *Lauro Caetano*
-
-* Switch `sqlite3:///` URLs (which were temporarily
- deprecated in 4.1) from relative to absolute.
-
- If you still want the previous interpretation, you should replace
- `sqlite3:///my/path` with `sqlite3:my/path`.
-
- *Matthew Draper*
-
-* Treat blank UUID values as `nil`.
-
- Example:
-
- Sample.new(uuid_field: '') #=> <Sample id: nil, uuid_field: nil>
-
- *Dmitry Lavrov*
-
-* Enable support for materialized views on PostgreSQL >= 9.3.
-
- *Dave Lee*
-
-* The PostgreSQL adapter supports custom domains. Fixes #14305.
+ Fixes #7247.
*Yves Senn*
-* PostgreSQL `Column#type` is now determined through the corresponding OID.
- The column types stay the same except for enum columns. They no longer have
- `nil` as type but `enum`.
-
- See #7814.
+* Ensure `first!` and friends work on loaded associations.
- *Yves Senn*
+ Fixes #18237.
-* Fixed error when specifying a non-empty default value on a PostgreSQL array column.
-
- Fixes #10613.
+ *Sean Griffin*
- *Luke Steensen*
+* `eager_load` preserves readonly flag for associations.
-* Fixed error where .persisted? throws SystemStackError for an unsaved model with a
- custom primary key that didn't save due to validation error.
+ Closes #15853.
- Fixes #14393.
+ *Takashi Kokubun*
- *Chris Finne*
+* Provide `:touch` option to `save()` to accommodate saving without updating
+ timestamps.
-* Introduce `validate` as an alias for `valid?`.
+ Fixes #18202.
- This is more intuitive when you want to run validations but don't care about the return value.
+ *Dan Olson*
- *Henrik Nyh*
+* Provide a more helpful error message when an unsupported class is passed to
+ `serialize`.
-* Create indexes inline in CREATE TABLE for MySQL.
+ Fixes #18224.
- This is important, because adding an index on a temporary table after it has been created
- would commit the transaction.
+ *Sean Griffin*
- It also allows creating and dropping indexed tables with fewer queries and fewer permissions
- required.
+* Add bigint primary key support for MySQL.
Example:
- create_table :temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query" do |t|
- t.index :zip
+ create_table :foos, id: :bigint do |t|
end
- # => CREATE TEMPORARY TABLE temp (INDEX (zip)) AS SELECT id, name, zip FROM a_really_complicated_query
-
- *Cody Cutrer*, *Steve Rice*, *Rafael Mendonça Franca*
-
-* Use singular table name in generated migrations when
- `ActiveRecord::Base.pluralize_table_names` is `false`.
-
- Fixes #13426.
- *Kuldeep Aggarwal*
-
-* `touch` accepts many attributes to be touched at once.
-
- Example:
+ *Ryuta Kamizono*
- # touches :signed_at, :sealed_at, and :updated_at/on attributes.
- Photo.last.touch(:signed_at, :sealed_at)
+* Support for any type of primary key.
- *James Pinto*
+ Fixes #14194.
-* `rake db:structure:dump` only dumps schema information if the schema
- migration table exists.
+ *Ryuta Kamizono*
- Fixes #14217.
+* Dump the default `nil` for PostgreSQL UUID primary key.
- *Yves Senn*
-
-* Reap connections that were checked out by now-dead threads, instead
- of waiting until they disconnect by themselves. Before this change,
- a suitably constructed series of short-lived threads could starve
- the connection pool, without ever having more than a couple alive at
- the same time.
+ *Ryuta Kamizono*
- *Matthew Draper*
+* Add a `:foreign_key` option to `references` and associated migration
+ methods. The model and migration generators now use this option, rather than
+ the `add_foreign_key` form.
-* `pk_and_sequence_for` now ensures that only the pg_depend entries
- pointing to pg_class, and thus only sequence objects, are considered.
+ *Sean Griffin*
- *Josh Williams*
+* Don't raise when writing an attribute with an out-of-range datetime passed
+ by the user.
-* `where.not` adds `references` for `includes` like normal `where` calls do.
+ *Grey Baker*
- Fixes #14406.
+* Replace deprecated `ActiveRecord::Tasks::DatabaseTasks#load_schema` with
+ `ActiveRecord::Tasks::DatabaseTasks#load_schema_for`.
*Yves Senn*
-* Extend fixture `$LABEL` replacement to allow string interpolation.
+* Fix bug with 'ActiveRecord::Type::Numeric' that caused negative values to
+ be marked as having changed when set to the same negative value.
- Example:
+ Closes #18161.
- martin:
- email: $LABEL@email.com
+ *Daniel Fox*
- users(:martin).email # => martin@email.com
+* Introduce `force: :cascade` option for `create_table`. Using this option
+ will recreate tables even if they have dependent objects (like foreign keys).
+ `db/schema.rb` now uses `force: :cascade`. This makes it possible to
+ reload the schema when foreign keys are in place.
- *Eric Steele*
-
-* Add support for `Relation` be passed as parameter on `QueryCache#select_all`.
+ *Matthew Draper*, *Yves Senn*
- Fixes #14361.
+* `db:schema:load` and `db:structure:load` no longer purge the database
+ before loading the schema. This is left for the user to do.
+ `db:test:prepare` will still purge the database.
- *arthurnn*
+ Closes #17945.
-* Passing an Active Record object to `find` or `exists?` is now deprecated.
- Call `.id` on the object first.
-
- *Aaron Patterson*
+ *Yves Senn*
-* Only use BINARY for MySQL case sensitive uniqueness check when column has a case insensitive collation.
+* Fix undesirable RangeError by `Type::Integer`. Add `Type::UnsignedInteger`.
*Ryuta Kamizono*
-* Support for MySQL 5.6 fractional seconds.
-
- *arthurnn*, *Tatsuhiko Miyagawa*
-
-* Support for Postgres `citext` data type enabling case-insensitive where
- values without needing to wrap in UPPER/LOWER sql functions.
-
- *Troy Kruthoff*, *Lachlan Sylvester*
-
-* Only save has_one associations if record has changes.
- Previously after save related callbacks, such as `#after_commit`, were triggered when the has_one
- object did not get saved to the db.
-
- *Alan Kennedy*
+* Add `foreign_type` option to `has_one` and `has_many` association macros.
-* Allow strings to specify the `#order` value.
+ This option enables to define the column name of associated object's type for polymorphic associations.
- Example:
-
- Model.order(id: 'asc').to_sql == Model.order(id: :asc).to_sql
-
- *Marcelo Casiraghi*, *Robin Dupret*
-
-* Dynamically register PostgreSQL enum OIDs. This prevents "unknown OID"
- warnings on enum columns.
-
- *Dieter Komendera*
-
-* `includes` is able to detect the right preloading strategy when string
- joins are involved.
-
- Fixes #14109.
-
- *Aaron Patterson*, *Yves Senn*
-
-* Fixed error with validation with enum fields for records where the
- value for any enum attribute is always evaluated as 0 during
- uniqueness validation.
-
- Fixes #14172.
+ *Ulisses Almeida*, *Kassio Borges*
- *Vilius Luneckas* *Ahmed AbouElhamayed*
+* Remove deprecated behavior allowing nested arrays to be passed as query
+ values.
-* `before_add` callbacks are fired before the record is saved on
- `has_and_belongs_to_many` associations *and* on `has_many :through`
- associations. Before this change, `before_add` callbacks would be fired
- before the record was saved on `has_and_belongs_to_many` associations, but
- *not* on `has_many :through` associations.
+ *Melanie Gilman*
- Fixes #14144.
+* Deprecate passing a class as a value in a query. Users should pass strings
+ instead.
-* Fixed STI classes not defining an attribute method if there is a
- conflicting private method defined on its ancestors.
+ *Melanie Gilman*
- Fixes #11569.
+* `add_timestamps` and `remove_timestamps` now properly reversible with
+ options.
- *Godfrey Chan*
-
-* Coerce strings when reading attributes. Fixes #10485.
-
- Example:
-
- book = Book.new(title: 12345)
- book.save!
- book.title # => "12345"
-
- *Yves Senn*
-
-* Deprecate half-baked support for PostgreSQL range values with excluding beginnings.
- We currently map PostgreSQL ranges to Ruby ranges. This conversion is not fully
- possible because the Ruby range does not support excluded beginnings.
-
- The current solution of incrementing the beginning is not correct and is now
- deprecated. For subtypes where we don't know how to increment (e.g. `#succ`
- is not defined) it will raise an `ArgumentException` for ranges with excluding
- beginnings.
-
- *Yves Senn*
+ *Noam Gagliardi-Rabinovich*
-* Support for user created range types in PostgreSQL.
+* `ActiveRecord::ConnectionAdapters::ColumnDumper#column_spec` and
+ `ActiveRecord::ConnectionAdapters::ColumnDumper#prepare_column_options` no
+ longer have a `types` argument. They should access
+ `connection#native_database_types` directly.
*Yves Senn*
-Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/activerecord/CHANGELOG.md) for previous changes.
+Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activerecord/CHANGELOG.md) for previous changes.
diff --git a/activerecord/MIT-LICENSE b/activerecord/MIT-LICENSE
index 2950f05b11..7c2197229d 100644
--- a/activerecord/MIT-LICENSE
+++ b/activerecord/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2014 David Heinemeier Hansson
+Copyright (c) 2004-2015 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activerecord/RUNNING_UNIT_TESTS.rdoc b/activerecord/RUNNING_UNIT_TESTS.rdoc
index 569685bd45..bae40604b1 100644
--- a/activerecord/RUNNING_UNIT_TESTS.rdoc
+++ b/activerecord/RUNNING_UNIT_TESTS.rdoc
@@ -16,15 +16,19 @@ To run a set of tests:
You can also run tests that depend upon a specific database backend. For
example:
- $ bundle exec rake test_sqlite3
+ $ bundle exec rake test:sqlite3
Simply executing <tt>bundle exec rake test</tt> is equivalent to the following:
- $ bundle exec rake test_mysql
- $ bundle exec rake test_mysql2
- $ bundle exec rake test_postgresql
- $ bundle exec rake test_sqlite3
- $ bundle exec rake test_sqlite3_mem
+ $ bundle exec rake test:mysql
+ $ bundle exec rake test:mysql2
+ $ bundle exec rake test:postgresql
+ $ bundle exec rake test:sqlite3
+
+Using the SQLite3 adapter with an in-memory database is the fastest way
+to run the tests:
+
+ $ bundle exec rake test:sqlite3_mem
There should be tests available for each database backend listed in the {Config
File}[rdoc-label:label-Config+File]. (the exact set of available tests is
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 976b559da9..f1facac21b 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -51,7 +51,7 @@ end
t.libs << 'test'
t.test_files = (Dir.glob( "test/cases/**/*_test.rb" ).reject {
|x| x =~ /\/adapters\//
- } + Dir.glob("test/cases/adapters/#{adapter_short}/**/*_test.rb")).sort
+ } + Dir.glob("test/cases/adapters/#{adapter_short}/**/*_test.rb"))
t.warning = true
t.verbose = true
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index bc10e96244..c88b9e8718 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 = '>= 1.9.3'
+ s.required_ruby_version = '>= 2.2.1'
s.license = 'MIT'
@@ -24,5 +24,5 @@ Gem::Specification.new do |s|
s.add_dependency 'activesupport', version
s.add_dependency 'activemodel', version
- s.add_dependency 'arel', '>= 6.0.0.beta2', '< 6.1'
+ s.add_dependency 'arel', '7.0.0.alpha'
end
diff --git a/activerecord/examples/performance.rb b/activerecord/examples/performance.rb
index d3546ce948..a5a1f284a0 100644
--- a/activerecord/examples/performance.rb
+++ b/activerecord/examples/performance.rb
@@ -39,8 +39,8 @@ class Exhibit < ActiveRecord::Base
where("notes IS NOT NULL")
end
- def self.look(exhibits) exhibits.each { |e| e.look } end
- def self.feel(exhibits) exhibits.each { |e| e.feel } end
+ def self.look(exhibits) exhibits.each(&:look) end
+ def self.feel(exhibits) exhibits.each(&:feel) end
end
def progress_bar(int); print "." if (int%100).zero? ; end
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 9028970a3d..1844b29ccb 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2014 David Heinemeier Hansson
+# Copyright (c) 2004-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -43,11 +43,13 @@ module ActiveRecord
autoload :Explain
autoload :Inheritance
autoload :Integration
+ autoload :LegacyYamlAdapter
autoload :Migration
autoload :Migrator, 'active_record/migration'
autoload :ModelSchema
autoload :NestedAttributes
autoload :NoTouching
+ autoload :TouchLater
autoload :Persistence
autoload :QueryCache
autoload :Querying
@@ -62,10 +64,13 @@ module ActiveRecord
autoload :Serialization
autoload :StatementCache
autoload :Store
+ autoload :Suppressor
+ autoload :TableMetadata
autoload :Timestamp
autoload :Transactions
autoload :Translation
autoload :Validations
+ autoload :SecureToken
eager_autoload do
autoload :ActiveRecordError, 'active_record/errors'
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index e576ec4d40..3d497a30fb 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -3,10 +3,27 @@ module ActiveRecord
module Aggregations # :nodoc:
extend ActiveSupport::Concern
- def clear_aggregation_cache #:nodoc:
- @aggregation_cache.clear if persisted?
+ def initialize_dup(*) # :nodoc:
+ @aggregation_cache = {}
+ super
end
+ def reload(*) # :nodoc:
+ clear_aggregation_cache
+ super
+ end
+
+ private
+
+ def clear_aggregation_cache # :nodoc:
+ @aggregation_cache.clear if persisted?
+ end
+
+ def init_internals # :nodoc:
+ @aggregation_cache = {}
+ super
+ end
+
# Active Record implements aggregation through a macro-like class method called +composed_of+
# for representing attributes as value objects. It expresses relationships like "Account [is]
# composed of Money [among other things]" or "Person [is] composed of [an] address". Each call
@@ -87,11 +104,6 @@ module ActiveRecord
# customer.address_city = "Copenhagen"
# customer.address # => Address.new("Hyancintvej", "Copenhagen")
#
- # customer.address_street = "Vesterbrogade"
- # customer.address # => Address.new("Hyancintvej", "Copenhagen")
- # customer.clear_aggregation_cache
- # customer.address # => Address.new("Vesterbrogade", "Copenhagen")
- #
# customer.address = Address.new("May Street", "Chicago")
# customer.address_street # => "May Street"
# customer.address_city # => "Chicago"
@@ -230,8 +242,8 @@ module ActiveRecord
private
def reader_method(name, class_name, mapping, allow_nil, constructor)
define_method(name) do
- if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|key, _| !read_attribute(key).nil? })
- attrs = mapping.collect {|key, _| read_attribute(key)}
+ if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|key, _| !_read_attribute(key).nil? })
+ attrs = mapping.collect {|key, _| _read_attribute(key)}
object = constructor.respond_to?(:call) ?
constructor.call(*attrs) :
class_name.constantize.send(constructor, *attrs)
@@ -245,7 +257,8 @@ module ActiveRecord
define_method("#{name}=") do |part|
klass = class_name.constantize
if part.is_a?(Hash)
- part = klass.new(*part.values)
+ raise ArgumentError unless part.size == part.keys.max
+ part = klass.new(*part.sort.map(&:last))
end
unless part.is_a?(klass) || converter.nil? || part.nil?
diff --git a/activerecord/lib/active_record/association_relation.rb b/activerecord/lib/active_record/association_relation.rb
index 5a84792f45..ee0bb8fafe 100644
--- a/activerecord/lib/active_record/association_relation.rb
+++ b/activerecord/lib/active_record/association_relation.rb
@@ -1,7 +1,7 @@
module ActiveRecord
class AssociationRelation < Relation
- def initialize(klass, table, association)
- super(klass, table)
+ def initialize(klass, table, predicate_builder, association)
+ super(klass, table, predicate_builder)
@association = association
end
@@ -13,6 +13,19 @@ module ActiveRecord
other == to_a
end
+ def build(*args, &block)
+ scoping { @association.build(*args, &block) }
+ end
+ alias new build
+
+ def create(*args, &block)
+ scoping { @association.create(*args, &block) }
+ end
+
+ def create!(*args, &block)
+ scoping { @association.create!(*args, &block) }
+ end
+
private
def exec_queries
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 8911506694..deecd1b7b7 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -57,7 +57,7 @@ module ActiveRecord
through_reflection = reflection.through_reflection
source_reflection_names = reflection.source_reflection_names
source_associations = reflection.through_reflection.klass._reflections.keys
- super("Could not find the source association(s) #{source_reflection_names.collect{ |a| a.inspect }.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)}?")
+ super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)}?")
end
end
@@ -113,18 +113,19 @@ module ActiveRecord
# These classes will be loaded when associations are created.
# So there is no need to eager load them.
- autoload :Association, 'active_record/associations/association'
- autoload :SingularAssociation, 'active_record/associations/singular_association'
- autoload :CollectionAssociation, 'active_record/associations/collection_association'
- autoload :CollectionProxy, 'active_record/associations/collection_proxy'
+ autoload :Association
+ autoload :SingularAssociation
+ autoload :CollectionAssociation
+ autoload :ForeignAssociation
+ autoload :CollectionProxy
- autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association'
- autoload :BelongsToPolymorphicAssociation, 'active_record/associations/belongs_to_polymorphic_association'
- autoload :HasManyAssociation, 'active_record/associations/has_many_association'
- autoload :HasManyThroughAssociation, 'active_record/associations/has_many_through_association'
- autoload :HasOneAssociation, 'active_record/associations/has_one_association'
- autoload :HasOneThroughAssociation, 'active_record/associations/has_one_through_association'
- autoload :ThroughAssociation, 'active_record/associations/through_association'
+ autoload :BelongsToAssociation
+ autoload :BelongsToPolymorphicAssociation
+ autoload :HasManyAssociation
+ autoload :HasManyThroughAssociation
+ autoload :HasOneAssociation
+ autoload :HasOneThroughAssociation
+ autoload :ThroughAssociation
module Builder #:nodoc:
autoload :Association, 'active_record/associations/builder/association'
@@ -138,26 +139,20 @@ module ActiveRecord
end
eager_autoload do
- autoload :Preloader, 'active_record/associations/preloader'
- autoload :JoinDependency, 'active_record/associations/join_dependency'
- autoload :AssociationScope, 'active_record/associations/association_scope'
- autoload :AliasTracker, 'active_record/associations/alias_tracker'
+ autoload :Preloader
+ autoload :JoinDependency
+ autoload :AssociationScope
+ autoload :AliasTracker
end
- # Clears out the association cache.
- def clear_association_cache #:nodoc:
- @association_cache.clear if persisted?
- end
-
- # :nodoc:
- attr_reader :association_cache
-
# Returns the association instance for the given name, instantiating it if it doesn't already exist
def association(name) #:nodoc:
association = association_instance_get(name)
if association.nil?
- raise AssociationNotFoundError.new(self, name) unless reflection = self.class._reflect_on_association(name)
+ unless reflection = self.class._reflect_on_association(name)
+ raise AssociationNotFoundError.new(self, name)
+ end
association = reflection.association_class.new(self, reflection)
association_instance_set(name, association)
end
@@ -165,7 +160,31 @@ module ActiveRecord
association
end
+ def association_cached?(name) # :nodoc
+ @association_cache.key?(name)
+ end
+
+ def initialize_dup(*) # :nodoc:
+ @association_cache = {}
+ super
+ end
+
+ def reload(*) # :nodoc:
+ clear_association_cache
+ super
+ end
+
private
+ # Clears out the association cache.
+ def clear_association_cache # :nodoc:
+ @association_cache.clear if persisted?
+ end
+
+ def init_internals # :nodoc:
+ @association_cache = {}
+ super
+ end
+
# Returns the specified association instance if it responds to :loaded?, nil otherwise.
def association_instance_get(name)
@association_cache[name]
@@ -999,6 +1018,8 @@ module ActiveRecord
# callbacks declared either before or after the <tt>:dependent</tt> option
# can affect what it does.
#
+ # Note that <tt>:dependent</tt> option is ignored for +has_one+ <tt>:through</tt> associations.
+ #
# === Delete or destroy?
#
# +has_many+ and +has_and_belongs_to_many+ associations have the methods <tt>destroy</tt>,
@@ -1011,7 +1032,7 @@ module ActiveRecord
# record(s) being removed so that callbacks are run. However <tt>delete</tt> and <tt>delete_all</tt> will either
# do the deletion according to the strategy specified by the <tt>:dependent</tt> option, or
# if no <tt>:dependent</tt> option is given, then it will follow the default strategy.
- # The default strategy is <tt>:nullify</tt> (set the foreign keys to <tt>nil</tt>), except for
+ # The default strategy is to do nothing (leave the foreign keys with the parent ids set), except for
# +has_many+ <tt>:through</tt>, where the default strategy is <tt>delete_all</tt> (delete
# the join records, without running their callbacks).
#
@@ -1176,6 +1197,12 @@ module ActiveRecord
# Specify the foreign key used for the association. By default this is guessed to be the name
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_many+
# association will use "person_id" as the default <tt>:foreign_key</tt>.
+ # [:foreign_type]
+ # Specify the column used to store the associated object's type, if this is a polymorphic
+ # association. By default this is guessed to be the name of the polymorphic association
+ # specified on "as" option with a "_type" suffix. So a class that defines a
+ # <tt>has_many :tags, as: :taggable</tt> association will use "taggable_type" as the
+ # default <tt>:foreign_type</tt>.
# [:primary_key]
# Specify the name of the column to use as the primary key for the association. By default this is +id+.
# [:dependent]
@@ -1238,6 +1265,10 @@ module ActiveRecord
# that is the inverse of this <tt>has_many</tt> association. Does not work in combination
# with <tt>:through</tt> or <tt>:as</tt> options.
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
+ # [:extend]
+ # Specifies a module or array of modules that will be extended into the association object returned.
+ # Useful for defining methods on associations, especially when they should be shared between multiple
+ # association objects.
#
# Option examples:
# has_many :comments, -> { order "posted_on" }
@@ -1319,10 +1350,18 @@ module ActiveRecord
# * <tt>:nullify</tt> causes the foreign key to be set to +NULL+. Callbacks are not executed.
# * <tt>:restrict_with_exception</tt> causes an exception to be raised if there is an associated record
# * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there is an associated object
+ #
+ # Note that <tt>:dependent</tt> option is ignored when using <tt>:through</tt> option.
# [:foreign_key]
# Specify the foreign key used for the association. By default this is guessed to be the name
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association
# will use "person_id" as the default <tt>:foreign_key</tt>.
+ # [:foreign_type]
+ # Specify the column used to store the associated object's type, if this is a polymorphic
+ # association. By default this is guessed to be the name of the polymorphic association
+ # specified on "as" option with a "_type" suffix. So a class that defines a
+ # <tt>has_one :tag, as: :taggable</tt> association will use "taggable_type" as the
+ # default <tt>:foreign_type</tt>.
# [:primary_key]
# Specify the method that returns the primary key used for the association. By default this is +id+.
# [:as]
@@ -1365,7 +1404,7 @@ module ActiveRecord
# has_one :last_comment, -> { order 'posted_on' }, class_name: "Comment"
# has_one :project_manager, -> { where role: 'project_manager' }, class_name: "Person"
# has_one :attachment, as: :attachable
- # has_one :boss, readonly: :true
+ # has_one :boss, -> { readonly }
# has_one :club, through: :membership
# has_one :primary_address, -> { where primary: true }, through: :addressables, source: :addressable
# has_one :credit_card, required: true
@@ -1417,7 +1456,7 @@ module ActiveRecord
# when you access the associated object.
#
# Scope examples:
- # belongs_to :user, -> { where(id: 2) }
+ # belongs_to :firm, -> { where(id: 2) }
# belongs_to :user, -> { joins(:friends) }
# belongs_to :level, ->(level) { where("game_level > ?", level.current) }
#
@@ -1481,10 +1520,14 @@ module ActiveRecord
# object that is the inverse of this <tt>belongs_to</tt> association. Does not work in
# combination with the <tt>:polymorphic</tt> options.
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
+ # [:optional]
+ # When set to +true+, the association will not have its presence validated.
# [:required]
# When set to +true+, the association will also have its presence validated.
# This will validate the association itself, not the id. You can use
# +:inverse_of+ to avoid an extra query during validation.
+ # NOTE: <tt>required</tt> is set to <tt>true</tt> by default and is deprecated. If
+ # you don't want to have association presence validated, use <tt>optional: true</tt>.
#
# Option examples:
# belongs_to :firm, foreign_key: "client_of"
@@ -1493,11 +1536,11 @@ module ActiveRecord
# belongs_to :valid_coupon, ->(o) { where "discounts > ?", o.payments_count },
# class_name: "Coupon", foreign_key: "coupon_id"
# belongs_to :attachable, polymorphic: true
- # belongs_to :project, readonly: true
+ # belongs_to :project, -> { readonly }
# belongs_to :post, counter_cache: true
- # belongs_to :company, touch: true
+ # belongs_to :comment, touch: true
# belongs_to :company, touch: :employees_last_updated_at
- # belongs_to :company, required: true
+ # belongs_to :user, optional: true
def belongs_to(name, scope = nil, options = {})
reflection = Builder::BelongsTo.build(self, name, scope, options)
Reflection.add_reflection self, name, reflection
@@ -1675,10 +1718,8 @@ 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
@@ -1700,7 +1741,7 @@ 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].each do |k|
+ [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name].each do |k|
hm_options[k] = options[k] if options.key? k
end
diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb
index a6a1947148..2b7e4f28c5 100644
--- a/activerecord/lib/active_record/associations/alias_tracker.rb
+++ b/activerecord/lib/active_record/associations/alias_tracker.rb
@@ -5,20 +5,23 @@ module ActiveRecord
# Keeps track of table aliases for ActiveRecord::Associations::ClassMethods::JoinDependency and
# ActiveRecord::Associations::ThroughAssociationScope
class AliasTracker # :nodoc:
- attr_reader :aliases, :connection
+ attr_reader :aliases
- def self.empty(connection)
- new connection, Hash.new(0)
+ def self.create(connection, initial_table, type_caster)
+ aliases = Hash.new(0)
+ aliases[initial_table] = 1
+ new connection, aliases, type_caster
end
- def self.create(connection, table_joins)
- if table_joins.empty?
- empty connection
+ def self.create_with_joins(connection, initial_table, joins, type_caster)
+ if joins.empty?
+ create(connection, initial_table, type_caster)
else
- aliases = Hash.new { |h,k|
- h[k] = initial_count_for(connection, k, table_joins)
+ aliases = Hash.new { |h, k|
+ h[k] = initial_count_for(connection, k, joins)
}
- new connection, aliases
+ aliases[initial_table] = 1
+ new connection, aliases, type_caster
end
end
@@ -51,45 +54,37 @@ module ActiveRecord
end
# table_joins is an array of arel joins which might conflict with the aliases we assign here
- def initialize(connection, aliases)
+ def initialize(connection, aliases, type_caster)
@aliases = aliases
@connection = connection
+ @type_caster = type_caster
end
def aliased_table_for(table_name, aliased_name)
- table_alias = aliased_name_for(table_name, aliased_name)
-
- if table_alias == table_name
- Arel::Table.new(table_name)
- else
- Arel::Table.new(table_name).alias(table_alias)
- end
- end
-
- def aliased_name_for(table_name, aliased_name)
if aliases[table_name].zero?
# If it's zero, we can have our table_name
aliases[table_name] = 1
- table_name
+ Arel::Table.new(table_name, type_caster: @type_caster)
else
# Otherwise, we need to use an alias
- aliased_name = connection.table_alias_for(aliased_name)
+ aliased_name = @connection.table_alias_for(aliased_name)
# Update the count
aliases[aliased_name] += 1
- if aliases[aliased_name] > 1
+ table_alias = if aliases[aliased_name] > 1
"#{truncate(aliased_name)}_#{aliases[aliased_name]}"
else
aliased_name
end
+ Arel::Table.new(table_name, type_caster: @type_caster).alias(table_alias)
end
end
private
def truncate(name)
- name.slice(0, connection.table_alias_length - 2)
+ name.slice(0, @connection.table_alias_length - 2)
end
end
end
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index f1c36cd047..930f678ae8 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -8,12 +8,12 @@ module ActiveRecord
#
# Association
# SingularAssociation
- # HasOneAssociation
+ # HasOneAssociation + ForeignAssociation
# HasOneThroughAssociation + ThroughAssociation
# BelongsToAssociation
# BelongsToPolymorphicAssociation
# CollectionAssociation
- # HasManyAssociation
+ # HasManyAssociation + ForeignAssociation
# HasManyThroughAssociation + ThroughAssociation
class Association #:nodoc:
attr_reader :owner, :target, :reflection
@@ -121,7 +121,7 @@ module ActiveRecord
# Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
# through association's scope)
def target_scope
- AssociationRelation.create(klass, klass.arel_table, self).merge!(klass.all)
+ AssociationRelation.create(klass, klass.arel_table, klass.predicate_builder, self).merge!(klass.all)
end
# Loads the \target if needed and returns it.
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index b965230e60..2416167834 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -2,42 +2,30 @@ module ActiveRecord
module Associations
class AssociationScope #:nodoc:
def self.scope(association, connection)
- INSTANCE.scope association, connection
- end
-
- class BindSubstitution
- def initialize(block)
- @block = block
- end
-
- def bind_value(scope, column, value, alias_tracker)
- substitute = alias_tracker.connection.substitute_at(
- column, scope.bind_values.length)
- scope.bind_values += [[column, @block.call(value)]]
- substitute
- end
+ INSTANCE.scope(association, connection)
end
def self.create(&block)
- block = block ? block : lambda { |val| val }
- new BindSubstitution.new(block)
+ block ||= lambda { |val| val }
+ new(block)
end
- def initialize(bind_substitution)
- @bind_substitution = bind_substitution
+ def initialize(value_transformation)
+ @value_transformation = value_transformation
end
INSTANCE = create
def scope(association, connection)
- klass = association.klass
- reflection = association.reflection
- scope = klass.unscoped
- owner = association.owner
- alias_tracker = AliasTracker.empty connection
+ klass = association.klass
+ reflection = association.reflection
+ scope = klass.unscoped
+ owner = association.owner
+ alias_tracker = AliasTracker.create connection, association.klass.table_name, klass.type_caster
+ chain_head, chain_tail = get_chain(reflection, association, alias_tracker)
scope.extending! Array(reflection.options[:extend])
- add_constraints(scope, owner, klass, reflection, alias_tracker)
+ add_constraints(scope, owner, klass, reflection, chain_head, chain_tail)
end
def join_type
@@ -61,132 +49,114 @@ module ActiveRecord
binds
end
- private
+ protected
- def construct_tables(chain, klass, refl, alias_tracker)
- chain.map do |reflection|
- alias_tracker.aliased_table_for(
- table_name_for(reflection, klass, refl),
- table_alias_for(reflection, refl, reflection != refl)
- )
- end
- end
-
- def table_alias_for(reflection, refl, join = false)
- name = "#{reflection.plural_name}_#{alias_suffix(refl)}"
- name << "_join" if join
- name
- end
+ attr_reader :value_transformation
+ private
def join(table, constraint)
table.create_join(table, table.create_on(constraint), join_type)
end
- def column_for(table_name, column_name, alias_tracker)
- columns = alias_tracker.connection.schema_cache.columns_hash(table_name)
- columns[column_name]
- end
-
- def bind_value(scope, column, value, alias_tracker)
- @bind_substitution.bind_value scope, column, value, alias_tracker
- end
-
- def bind(scope, table_name, column_name, value, tracker)
- column = column_for table_name, column_name, tracker
- bind_value scope, column, value, tracker
- end
-
- def last_chain_scope(scope, table, reflection, owner, tracker, assoc_klass)
- join_keys = reflection.join_keys(assoc_klass)
+ def last_chain_scope(scope, table, reflection, owner, association_klass)
+ join_keys = reflection.join_keys(association_klass)
key = join_keys.key
foreign_key = join_keys.foreign_key
- bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], tracker
- scope = scope.where(table[key].eq(bind_val))
+ value = transform_value(owner[foreign_key])
+ scope = scope.where(table.name => { key => value })
if reflection.type
- value = owner.class.base_class.name
- bind_val = bind scope, table.table_name, reflection.type, value, tracker
- scope = scope.where(table[reflection.type].eq(bind_val))
- else
- scope
+ polymorphic_type = transform_value(owner.class.base_class.name)
+ scope = scope.where(table.name => { reflection.type => polymorphic_type })
end
+
+ scope
end
- def next_chain_scope(scope, table, reflection, tracker, assoc_klass, foreign_table, next_reflection)
- join_keys = reflection.join_keys(assoc_klass)
+ def transform_value(value)
+ value_transformation.call(value)
+ end
+
+ def next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection)
+ join_keys = reflection.join_keys(association_klass)
key = join_keys.key
foreign_key = join_keys.foreign_key
constraint = table[key].eq(foreign_table[foreign_key])
if reflection.type
- value = next_reflection.klass.base_class.name
- bind_val = bind scope, table.table_name, reflection.type, value, tracker
- scope = scope.where(table[reflection.type].eq(bind_val))
+ value = transform_value(next_reflection.klass.base_class.name)
+ scope = scope.where(table.name => { reflection.type => value })
end
scope = scope.joins(join(foreign_table, constraint))
end
- def add_constraints(scope, owner, assoc_klass, refl, tracker)
- chain = refl.chain
- scope_chain = refl.scope_chain
+ class ReflectionProxy < SimpleDelegator # :nodoc:
+ attr_accessor :next
+ attr_reader :alias_name
- tables = construct_tables(chain, assoc_klass, refl, tracker)
+ def initialize(reflection, alias_name)
+ super(reflection)
+ @alias_name = alias_name
+ end
- owner_reflection = chain.last
- table = tables.last
- scope = last_chain_scope(scope, table, owner_reflection, owner, tracker, assoc_klass)
+ def all_includes; nil; end
+ end
- chain.each_with_index do |reflection, i|
- table, foreign_table = tables.shift, tables.first
+ def get_chain(reflection, association, tracker)
+ name = reflection.name
+ runtime_reflection = Reflection::RuntimeReflection.new(reflection, association)
+ previous_reflection = runtime_reflection
+ reflection.chain.drop(1).each do |refl|
+ alias_name = tracker.aliased_table_for(refl.table_name, refl.alias_candidate(name))
+ proxy = ReflectionProxy.new(refl, alias_name)
+ previous_reflection.next = proxy
+ previous_reflection = proxy
+ end
+ [runtime_reflection, previous_reflection]
+ end
- unless reflection == chain.last
- next_reflection = chain[i + 1]
- scope = next_chain_scope(scope, table, reflection, tracker, assoc_klass, foreign_table, next_reflection)
- end
+ def add_constraints(scope, owner, association_klass, refl, chain_head, chain_tail)
+ owner_reflection = chain_tail
+ table = owner_reflection.alias_name
+ scope = last_chain_scope(scope, table, owner_reflection, owner, association_klass)
- is_first_chain = i == 0
- klass = is_first_chain ? assoc_klass : reflection.klass
+ reflection = chain_head
+ loop do
+ break unless reflection
+ table = reflection.alias_name
+
+ unless reflection == chain_tail
+ next_reflection = reflection.next
+ foreign_table = next_reflection.alias_name
+ scope = next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection)
+ end
# Exclude the scope of the association itself, because that
# was already merged in the #scope method.
- scope_chain[i].each do |scope_chain_item|
- item = eval_scope(klass, scope_chain_item, owner)
+ reflection.constraints.each do |scope_chain_item|
+ item = eval_scope(reflection.klass, scope_chain_item, owner)
if scope_chain_item == refl.scope
- scope.merge! item.except(:where, :includes, :bind)
+ scope.merge! item.except(:where, :includes)
end
- if is_first_chain
+ reflection.all_includes do
scope.includes! item.includes_values
end
- scope.where_values += item.where_values
- scope.bind_values += item.bind_values
+ scope.where_clause += item.where_clause
scope.order_values |= item.order_values
end
+
+ reflection = reflection.next
end
scope
end
- def alias_suffix(refl)
- refl.name
- end
-
- def table_name_for(reflection, klass, refl)
- if reflection == refl
- # If this is a polymorphic belongs_to, we want to get the klass from the
- # association because it depends on the polymorphic_type attribute of
- # the owner
- klass.table_name
- else
- reflection.table_name
- end
- end
-
def eval_scope(klass, scope, owner)
klass.unscoped.instance_exec(owner, &scope)
end
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index 81fdd681de..265a65c4c1 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -68,16 +68,19 @@ module ActiveRecord
def increment_counter(counter_cache_name)
if foreign_key_present?
klass.increment_counter(counter_cache_name, target_id)
+ if target && !stale_target?
+ target.increment(counter_cache_name)
+ end
end
end
# Checks whether record is different to the current target, without loading it
def different_target?(record)
- record.id != owner[reflection.foreign_key]
+ record.id != owner._read_attribute(reflection.foreign_key)
end
def replace_keys(record)
- owner[reflection.foreign_key] = record[reflection.association_primary_key(record.class)]
+ owner[reflection.foreign_key] = record._read_attribute(reflection.association_primary_key(record.class))
end
def remove_keys
@@ -85,7 +88,7 @@ module ActiveRecord
end
def foreign_key_present?
- owner[reflection.foreign_key]
+ owner._read_attribute(reflection.foreign_key)
end
# NOTE - for now, we're only supporting inverse setting from belongs_to back onto
@@ -99,12 +102,13 @@ module ActiveRecord
if options[:primary_key]
owner.send(reflection.name).try(:id)
else
- owner[reflection.foreign_key]
+ owner._read_attribute(reflection.foreign_key)
end
end
def stale_state
- owner[reflection.foreign_key] && owner[reflection.foreign_key].to_s
+ result = owner._read_attribute(reflection.foreign_key)
+ result && result.to_s
end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb
index 947d61ee7b..88406740d8 100644
--- a/activerecord/lib/active_record/associations/builder/association.rb
+++ b/activerecord/lib/active_record/associations/builder/association.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/module/attribute_accessors'
-
# This is the parent Association class which defines the variables
# used by all associations.
#
@@ -15,15 +13,10 @@ module ActiveRecord::Associations::Builder
class Association #:nodoc:
class << self
attr_accessor :extensions
- # TODO: This class accessor is needed to make activerecord-deprecated_finders work.
- # We can move it to a constant in 5.0.
- attr_accessor :valid_options
end
self.extensions = []
- self.valid_options = [:class_name, :class, :foreign_key, :validate]
-
- attr_reader :name, :scope, :options
+ VALID_OPTIONS = [:class_name, :class, :foreign_key, :validate] # :nodoc:
def self.build(model, name, scope, options, &block)
if model.dangerous_attribute_method?(name)
@@ -32,57 +25,60 @@ module ActiveRecord::Associations::Builder
"Please choose a different association name."
end
- builder = create_builder model, name, scope, options, &block
- reflection = builder.build(model)
+ extension = define_extensions model, name, &block
+ reflection = create_reflection model, name, scope, options, extension
define_accessors model, reflection
define_callbacks model, reflection
define_validations model, reflection
- builder.define_extensions model
reflection
end
- def self.create_builder(model, name, scope, options, &block)
+ def self.create_reflection(model, name, scope, options, extension = nil)
raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
- new(model, name, scope, options, &block)
- end
-
- def initialize(model, name, scope, options)
- # TODO: Move this to create_builder as soon we drop support to activerecord-deprecated_finders.
if scope.is_a?(Hash)
options = scope
scope = nil
end
- # TODO: Remove this model argument as soon we drop support to activerecord-deprecated_finders.
- @name = name
- @scope = scope
- @options = options
+ validate_options(options)
- validate_options
+ scope = build_scope(scope, extension)
+
+ ActiveRecord::Reflection.create(macro, name, scope, options, model)
+ end
+
+ def self.build_scope(scope, extension)
+ new_scope = scope
if scope && scope.arity == 0
- @scope = proc { instance_exec(&scope) }
+ new_scope = proc { instance_exec(&scope) }
+ end
+
+ if extension
+ new_scope = wrap_scope new_scope, extension
end
+
+ new_scope
end
- def build(model)
- ActiveRecord::Reflection.create(macro, name, scope, options, model)
+ def self.wrap_scope(scope, extension)
+ scope
end
- def macro
+ def self.macro
raise NotImplementedError
end
- def valid_options
- Association.valid_options + Association.extensions.flat_map(&:valid_options)
+ def self.valid_options(options)
+ VALID_OPTIONS + Association.extensions.flat_map(&:valid_options)
end
- def validate_options
- options.assert_valid_keys(valid_options)
+ def self.validate_options(options)
+ options.assert_valid_keys(valid_options(options))
end
- def define_extensions(model)
+ def self.define_extensions(model, name)
end
def self.define_callbacks(model, reflection)
@@ -133,8 +129,6 @@ module ActiveRecord::Associations::Builder
raise NotImplementedError
end
- private
-
def self.check_dependent_options(dependent)
unless valid_dependent_options.include? dependent
raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}"
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index 954ea3878a..97eb007f62 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -1,11 +1,11 @@
module ActiveRecord::Associations::Builder
class BelongsTo < SingularAssociation #:nodoc:
- def macro
+ def self.macro
:belongs_to
end
- def valid_options
- super + [:foreign_type, :polymorphic, :touch, :counter_cache]
+ def self.valid_options(options)
+ super + [:foreign_type, :polymorphic, :touch, :counter_cache, :optional]
end
def self.valid_dependent_options
@@ -23,8 +23,6 @@ module ActiveRecord::Associations::Builder
add_counter_cache_methods mixin
end
- private
-
def self.add_counter_cache_methods(mixin)
return if mixin.method_defined? :belongs_to_counter_cache_after_update
@@ -62,7 +60,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
@@ -77,9 +75,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
@@ -87,9 +85,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
@@ -100,7 +98,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?
@@ -112,5 +111,23 @@ module ActiveRecord::Associations::Builder
name = reflection.name
model.after_destroy lambda { |o| o.association(name).handle_dependency }
end
+
+ def self.define_validations(model, reflection)
+ if reflection.options.key?(:required)
+ reflection.options[:optional] = !reflection.options.delete(:required)
+ end
+
+ if reflection.options[:optional].nil?
+ required = model.belongs_to_required_by_default
+ else
+ required = !reflection.options[:optional]
+ end
+
+ super
+
+ if required
+ model.validates_presence_of reflection.name, message: :required
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb
index bc15a49996..2ff67f904d 100644
--- a/activerecord/lib/active_record/associations/builder/collection_association.rb
+++ b/activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -7,22 +7,11 @@ module ActiveRecord::Associations::Builder
CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
- def valid_options
+ def self.valid_options(options)
super + [:table_name, :before_add,
:after_add, :before_remove, :after_remove, :extend]
end
- attr_reader :block_extension
-
- def initialize(model, name, scope, options)
- super
- @mod = nil
- if block_given?
- @mod = Module.new(&Proc.new)
- @scope = wrap_scope @scope, @mod
- end
- end
-
def self.define_callbacks(model, reflection)
super
name = reflection.name
@@ -32,10 +21,11 @@ module ActiveRecord::Associations::Builder
}
end
- def define_extensions(model)
- if @mod
+ def self.define_extensions(model, name)
+ if block_given?
extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
- model.parent.const_set(extension_module_name, @mod)
+ extension = Module.new(&Proc.new)
+ model.parent.const_set(extension_module_name, extension)
end
end
@@ -78,9 +68,7 @@ module ActiveRecord::Associations::Builder
CODE
end
- private
-
- def wrap_scope(scope, mod)
+ def self.wrap_scope(scope, mod)
if scope
proc { |owner| instance_exec(owner, &scope).extending(mod) }
else
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 815e8eb97f..97b57a6a55 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
@@ -85,20 +85,20 @@ module ActiveRecord::Associations::Builder
def middle_reflection(join_model)
middle_name = [lhs_model.name.downcase.pluralize,
- association_name].join('_').gsub(/::/, '_').to_sym
+ association_name].join('_'.freeze).gsub('::'.freeze, '_'.freeze).to_sym
middle_options = middle_options join_model
- hm_builder = HasMany.create_builder(lhs_model,
- middle_name,
- nil,
- middle_options)
- hm_builder.build lhs_model
+
+ HasMany.create_reflection(lhs_model,
+ middle_name,
+ nil,
+ middle_options)
end
private
def middle_options(join_model)
middle_options = {}
- middle_options[:class] = join_model
+ middle_options[:class_name] = "#{lhs_model.name}::#{join_model.name}"
middle_options[:source] = join_model.left_reflection.name
if options.key? :foreign_key
middle_options[:foreign_key] = options[:foreign_key]
@@ -110,7 +110,7 @@ module ActiveRecord::Associations::Builder
rhs_options = {}
if options.key? :class_name
- rhs_options[:foreign_key] = options[:class_name].foreign_key
+ rhs_options[:foreign_key] = options[:class_name].to_s.foreign_key
rhs_options[:class_name] = options[:class_name]
end
diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb
index 4c8c826f76..1c1b47bd56 100644
--- a/activerecord/lib/active_record/associations/builder/has_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_many.rb
@@ -1,11 +1,11 @@
module ActiveRecord::Associations::Builder
class HasMany < CollectionAssociation #:nodoc:
- def macro
+ def self.macro
:has_many
end
- def valid_options
- super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table]
+ def self.valid_options(options)
+ super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table, :foreign_type]
end
def self.valid_dependent_options
diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb
index c194c8ae9a..a272d3c781 100644
--- a/activerecord/lib/active_record/associations/builder/has_one.rb
+++ b/activerecord/lib/active_record/associations/builder/has_one.rb
@@ -1,11 +1,11 @@
module ActiveRecord::Associations::Builder
class HasOne < SingularAssociation #:nodoc:
- def macro
+ def self.macro
:has_one
end
- def valid_options
- valid = super + [:as]
+ def self.valid_options(options)
+ valid = super + [:as, :foreign_type]
valid += [:through, :source, :source_type] if options[:through]
valid
end
@@ -14,10 +14,15 @@ module ActiveRecord::Associations::Builder
[:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception]
end
- private
-
def self.add_destroy_callbacks(model, reflection)
super unless reflection.options[:through]
end
+
+ def self.define_validations(model, reflection)
+ super
+ if reflection.options[:required]
+ model.validates_presence_of reflection.name, message: :required
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb
index 6e6dd7204c..42542f188e 100644
--- a/activerecord/lib/active_record/associations/builder/singular_association.rb
+++ b/activerecord/lib/active_record/associations/builder/singular_association.rb
@@ -2,7 +2,7 @@
module ActiveRecord::Associations::Builder
class SingularAssociation < Association #:nodoc:
- def valid_options
+ def self.valid_options(options)
super + [:dependent, :primary_key, :inverse_of, :required]
end
@@ -27,12 +27,5 @@ module ActiveRecord::Associations::Builder
end
CODE
end
-
- def self.define_validations(model, reflection)
- super
- if reflection.options[:required]
- model.validates_presence_of reflection.name
- end
- end
end
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index bdfd569be2..6caadb4ce8 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -33,7 +33,13 @@ module ActiveRecord
reload
end
- @proxy ||= CollectionProxy.create(klass, self)
+ if null_scope?
+ # Cache the proxy separately before the owner has an id
+ # or else a post-save proxy will still lack the id
+ @null_proxy ||= CollectionProxy.create(klass, self)
+ else
+ @proxy ||= CollectionProxy.create(klass, self)
+ end
end
# Implements the writer method, e.g. foo.items= for Foo.has_many :items
@@ -56,9 +62,9 @@ module ActiveRecord
# Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
def ids_writer(ids)
pk_type = reflection.primary_key_type
- ids = Array(ids).reject { |id| id.blank? }
- ids.map! { |i| pk_type.type_cast_from_user(i) }
- replace(klass.find(ids).index_by { |r| r.id }.values_at(*ids))
+ ids = Array(ids).reject(&:blank?)
+ ids.map! { |i| pk_type.cast(i) }
+ replace(klass.find(ids).index_by(&:id).values_at(*ids))
end
def reset
@@ -123,6 +129,16 @@ module ActiveRecord
first_nth_or_last(:last, *args)
end
+ def take(n = nil)
+ if loaded?
+ n ? target.take(n) : target.first
+ else
+ scope.take(n).tap do |record|
+ set_inverse_instance record if record.is_a? ActiveRecord::Base
+ end
+ end
+ end
+
def build(attributes = {}, &block)
if attributes.is_a?(Array)
attributes.collect { |attr| build(attr, &block) }
@@ -145,6 +161,7 @@ module ActiveRecord
# be chained. Since << flattens its argument list and inserts each record,
# +push+ and +concat+ behave identically.
def concat(*records)
+ records = records.flatten
if owner.new_record?
load_target
concat_records(records)
@@ -169,8 +186,8 @@ module ActiveRecord
end
# Removes all records from the association without calling callbacks
- # on the associated records. It honors the `:dependent` option. However
- # if the `:dependent` value is `:destroy` then in that case the `:delete_all`
+ # on the associated records. It honors the +:dependent+ option. However
+ # if the +:dependent+ value is +:destroy+ then in that case the +:delete_all+
# deletion strategy for the association is applied.
#
# You can force a particular deletion strategy by passing a parameter.
@@ -212,11 +229,7 @@ module ActiveRecord
# Count all records using SQL. Construct options and pass them with
# scope to the target class's +count+.
- def count(column_name = nil, count_options = {})
- # TODO: Remove count_options argument as soon we remove support to
- # activerecord-deprecated_finders.
- column_name, count_options = nil, column_name if column_name.is_a?(Hash)
-
+ def count(column_name = nil)
relation = scope
if association_scope.distinct_value
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
@@ -283,7 +296,7 @@ module ActiveRecord
elsif !loaded? && !association_scope.group_values.empty?
load_target.size
elsif !loaded? && !association_scope.distinct_value && target.is_a?(Array)
- unsaved_records = target.select { |r| r.new_record? }
+ unsaved_records = target.select(&:new_record?)
unsaved_records.size + count_records
else
count_records
@@ -316,7 +329,8 @@ module ActiveRecord
end
# Returns true if the collections is not empty.
- # Equivalent to +!collection.empty?+.
+ # If block given, loads all records and checks for one or more matches.
+ # Otherwise, equivalent to +!collection.empty?+.
def any?
if block_given?
load_target.any? { |*block_args| yield(*block_args) }
@@ -326,7 +340,8 @@ module ActiveRecord
end
# Returns true if the collection has more than 1 record.
- # Equivalent to +collection.size > 1+.
+ # If block given, loads all records and checks for two or more matches.
+ # Otherwise, equivalent to +collection.size > 1+.
def many?
if block_given?
load_target.many? { |*block_args| yield(*block_args) }
@@ -352,8 +367,11 @@ module ActiveRecord
if owner.new_record?
replace_records(other_array, original_target)
else
+ replace_common_records_in_memory(other_array, original_target)
if other_array != original_target
transaction { replace_records(other_array, original_target) }
+ else
+ other_array
end
end
end
@@ -379,11 +397,18 @@ module ActiveRecord
target
end
- def add_to_target(record, skip_callbacks = false)
+ def add_to_target(record, skip_callbacks = false, &block)
+ if association_scope.distinct_value
+ index = @target.index(record)
+ end
+ replace_on_target(record, index, skip_callbacks, &block)
+ end
+
+ def replace_on_target(record, index, skip_callbacks)
callback(:before_add, record) unless skip_callbacks
yield(record) if block_given?
- if association_scope.distinct_value && index = @target.index(record)
+ if index
@target[index] = record
else
@target << record
@@ -409,7 +434,7 @@ module ActiveRecord
def get_records
if reflection.scope_chain.any?(&:any?) ||
scope.eager_loading? ||
- klass.current_scope
+ klass.scope_attributes?
return scope.to_a
end
@@ -491,7 +516,7 @@ module ActiveRecord
def delete_or_destroy(records, method)
records = records.flatten
records.each { |record| raise_on_type_mismatch!(record) }
- existing_records = records.reject { |r| r.new_record? }
+ existing_records = records.reject(&:new_record?)
if existing_records.empty?
remove_records(existing_records, records, method)
@@ -527,10 +552,18 @@ module ActiveRecord
target
end
+ def replace_common_records_in_memory(new_target, original_target)
+ common_records = new_target & original_target
+ common_records.each do |record|
+ skip_callbacks = true
+ replace_on_target(record, @target.index(record), skip_callbacks)
+ end
+ end
+
def concat_records(records, should_raise = false)
result = true
- records.flatten.each do |record|
+ records.each do |record|
raise_on_type_mismatch!(record)
add_to_target(record) do |rec|
result &&= insert_record(rec, true, should_raise) unless owner.new_record?
@@ -574,8 +607,8 @@ module ActiveRecord
if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
assoc = owner.association(reflection.through_reflection.name)
assoc.reader.any? { |source|
- target = source.send(reflection.source_reflection.name)
- target.respond_to?(:include?) ? target.include?(record) : target == record
+ target_reflection = source.send(reflection.source_reflection.name)
+ target_reflection.respond_to?(:include?) ? target_reflection.include?(record) : target_reflection == record
} || target.include?(record)
else
target.include?(record)
@@ -586,7 +619,7 @@ module ActiveRecord
# specified, then #find scans the entire collection.
def find_by_scan(*args)
expects_array = args.first.kind_of?(Array)
- ids = args.flatten.compact.map{ |arg| arg.to_s }.uniq
+ ids = args.flatten.compact.map(&:to_s).uniq
if ids.size == 1
id = ids.first
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 060b2278d9..685c3a5f17 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -29,10 +29,11 @@ module ActiveRecord
# instantiation of the actual post records.
class CollectionProxy < Relation
delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope)
+ delegate :find_nth, to: :scope
def initialize(klass, association) #:nodoc:
@association = association
- super klass, klass.arel_table
+ super klass, klass.arel_table, klass.predicate_builder
merge! association.scope(nullify: false)
end
@@ -226,6 +227,10 @@ module ActiveRecord
@association.last(*args)
end
+ def take(n = nil)
+ @association.take(n)
+ end
+
# Returns a new object of the collection type that has been instantiated
# with +attributes+ and linked to this object, but have not yet been saved.
# You can pass an array of attributes hashes, this will return an array
@@ -687,10 +692,8 @@ module ActiveRecord
# # #<Pet id: 2, name: "Spook", person_id: 1>,
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
# # ]
- def count(column_name = nil, options = {})
- # TODO: Remove options argument as soon we remove support to
- # activerecord-deprecated_finders.
- @association.count(column_name, options)
+ def count(column_name = nil)
+ @association.count(column_name)
end
# Returns the size of the collection. If the collection hasn't been loaded,
@@ -973,6 +976,9 @@ module ActiveRecord
# Equivalent to +delete_all+. The difference is that returns +self+, instead
# of an array with the deleted objects, so methods can be chained. See
# +delete_all+ for more information.
+ # Note that because +delete_all+ removes records by directly
+ # running an SQL query into the database, the +updated_at+ column of
+ # the object is not changed.
def clear
delete_all
self
diff --git a/activerecord/lib/active_record/associations/foreign_association.rb b/activerecord/lib/active_record/associations/foreign_association.rb
new file mode 100644
index 0000000000..fe48ecec29
--- /dev/null
+++ b/activerecord/lib/active_record/associations/foreign_association.rb
@@ -0,0 +1,11 @@
+module ActiveRecord::Associations
+ module ForeignAssociation
+ def foreign_key_present?
+ if reflection.klass.primary_key
+ owner.attribute_present?(reflection.active_record_primary_key)
+ else
+ false
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 1413efaf7f..ca27c9fdde 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -6,6 +6,7 @@ module ActiveRecord
# If the association has a <tt>:through</tt> option further specialization
# is provided by its child HasManyThroughAssociation.
class HasManyAssociation < CollectionAssociation #:nodoc:
+ include ForeignAssociation
def handle_dependency
case options[:dependent]
@@ -16,7 +17,7 @@ module ActiveRecord
unless empty?
record = klass.human_attribute_name(reflection.name).downcase
owner.errors.add(:base, :"restrict_dependent_destroy.many", record: record)
- false
+ throw(:abort)
end
else
@@ -66,7 +67,7 @@ module ActiveRecord
# the loaded flag is set to true as well.
def count_records
count = if has_cached_counter?
- owner.read_attribute cached_counter_attribute_name
+ owner._read_attribute cached_counter_attribute_name
else
scope.count
end
@@ -84,7 +85,11 @@ module ActiveRecord
end
def cached_counter_attribute_name(reflection = reflection())
- options[:counter_cache] || "#{reflection.name}_count"
+ if reflection.options[:counter_cache]
+ reflection.options[:counter_cache].to_s
+ else
+ "#{reflection.name}_count"
+ end
end
def update_counter(difference, reflection = reflection())
@@ -100,7 +105,7 @@ module ActiveRecord
end
def update_counter_in_memory(difference, reflection = reflection())
- if has_cached_counter?(reflection)
+ if counter_must_be_updated_by_has_many?(reflection)
counter = cached_counter_attribute_name(reflection)
owner[counter] += difference
owner.send(:clear_attribute_changes, counter) # eww
@@ -117,18 +122,28 @@ module ActiveRecord
# it will be decremented twice.
#
# Hence this method.
- def inverse_updates_counter_cache?(reflection = reflection())
+ def inverse_which_updates_counter_cache(reflection = reflection())
counter_name = cached_counter_attribute_name(reflection)
- inverse_updates_counter_named?(counter_name, reflection)
+ inverse_which_updates_counter_named(counter_name, reflection)
end
+ alias inverse_updates_counter_cache? inverse_which_updates_counter_cache
- def inverse_updates_counter_named?(counter_name, reflection = reflection())
- reflection.klass._reflections.values.any? { |inverse_reflection|
+ def inverse_which_updates_counter_named(counter_name, reflection)
+ reflection.klass._reflections.values.find { |inverse_reflection|
inverse_reflection.belongs_to? &&
inverse_reflection.counter_cache_column == counter_name
}
end
+ def inverse_updates_counter_in_memory?(reflection)
+ inverse = inverse_which_updates_counter_cache(reflection)
+ inverse && inverse == reflection.inverse_of
+ end
+
+ def counter_must_be_updated_by_has_many?(reflection)
+ !inverse_updates_counter_in_memory?(reflection) && has_cached_counter?(reflection)
+ end
+
def delete_count(method, scope)
if method == :delete_all
scope.delete_all
@@ -153,14 +168,6 @@ module ActiveRecord
end
end
- def foreign_key_present?
- if reflection.klass.primary_key
- owner.attribute_present?(reflection.association_primary_key)
- else
- false
- end
- end
-
def concat_records(records, *)
update_counter_if_success(super, records.length)
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 bde23fc116..29e8a0edc1 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/string/filters'
-
module ActiveRecord
# = Active Record Has Many Through Association
module Associations
@@ -13,21 +11,6 @@ module ActiveRecord
@through_association = nil
end
- # Returns the size of the collection by executing a SELECT COUNT(*) query
- # if the collection hasn't been loaded, and by calling collection.size if
- # it has. If the collection will likely have a size greater than zero,
- # and if fetching the collection will be needed afterwards, one less
- # SELECT query will be generated by using #length instead.
- def size
- if has_cached_counter?
- owner.read_attribute cached_counter_attribute_name(reflection)
- elsif loaded?
- target.size
- else
- super
- end
- end
-
def concat(*records)
unless owner.new_record?
records.flatten.each do |record|
@@ -64,16 +47,7 @@ module ActiveRecord
end
save_through_record(record)
- if has_cached_counter? && !through_reflection_updates_counter_cache?
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Automatic updating of counter caches on through associations has been
- deprecated, and will be removed in Rails 5. Instead, please set the
- appropriate `counter_cache` options on the `has_many` and `belongs_to`
- for your associations to #{through_reflection.name}.
- MSG
- update_counter_in_database(1)
- end
record
end
@@ -161,17 +135,15 @@ module ActiveRecord
if scope.klass.primary_key
count = scope.destroy_all.length
else
- scope.each do |record|
- record._run_destroy_callbacks
- end
+ scope.each { |record| record.run_callbacks :destroy }
arel = scope.arel
- stmt = Arel::DeleteManager.new arel.engine
+ stmt = Arel::DeleteManager.new
stmt.from scope.klass.arel_table
stmt.wheres = arel.constraints
- count = scope.klass.connection.delete(stmt, 'SQL', scope.bind_values)
+ count = scope.klass.connection.delete(stmt, 'SQL', scope.bound_attributes)
end
when :nullify
count = scope.update_all(source_reflection.foreign_key => nil)
@@ -188,9 +160,9 @@ module ActiveRecord
if through_reflection.collection? && update_through_counter?(method)
update_counter(-count, through_reflection)
+ else
+ update_counter(-count)
end
-
- update_counter(-count)
end
def through_records_for(record)
@@ -228,11 +200,6 @@ module ActiveRecord
def invertible_for?(record)
false
end
-
- def through_reflection_updates_counter_cache?
- counter_name = cached_counter_attribute_name
- inverse_updates_counter_named?(counter_name, through_reflection)
- end
end
end
end
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index e6095d84dc..41a75b820e 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -2,6 +2,7 @@ module ActiveRecord
# = Active Record Belongs To Has One Association
module Associations
class HasOneAssociation < SingularAssociation #:nodoc:
+ include ForeignAssociation
def handle_dependency
case options[:dependent]
@@ -12,7 +13,7 @@ module ActiveRecord
if load_target
record = klass.human_attribute_name(reflection.name).downcase
owner.errors.add(:base, :"restrict_dependent_destroy.one", record: record)
- false
+ throw(:abort)
end
else
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index c5c4edd090..81eb5136a1 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -20,7 +20,7 @@ module ActiveRecord
end
def columns
- @tables.flat_map { |t| t.column_aliases }
+ @tables.flat_map(&:column_aliases)
end
# An array of [column_name, alias] pairs for the table
@@ -93,8 +93,7 @@ module ActiveRecord
# joins # => []
#
def initialize(base, associations, joins)
- @alias_tracker = AliasTracker.create(base.connection, joins)
- @alias_tracker.aliased_name_for(base.table_name, base.table_name) # Updates the count for base.table_name to 1
+ @alias_tracker = AliasTracker.create_with_joins(base.connection, base.table_name, joins, base.type_caster)
tree = self.class.make_tree associations
@join_root = JoinBase.new base, build(tree, base)
@join_root.children.each { |child| construct_tables! @join_root, child }
@@ -233,23 +232,26 @@ module ActiveRecord
end
def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
+ return if ar_parent.nil?
primary_id = ar_parent.id
parent.children.each do |node|
if node.reflection.collection?
other = ar_parent.association(node.reflection.name)
other.loaded!
- else
- if ar_parent.association_cache.key?(node.reflection.name)
- model = ar_parent.association(node.reflection.name).target
- construct(model, node, row, rs, seen, model_cache, aliases)
- next
- end
+ elsif ar_parent.association_cached?(node.reflection.name)
+ model = ar_parent.association(node.reflection.name).target
+ construct(model, node, row, rs, seen, model_cache, aliases)
+ next
end
key = aliases.column_alias(node, node.primary_key)
id = row[key]
- next if id.nil?
+ if id.nil?
+ nil_association = ar_parent.association(node.reflection.name)
+ nil_association.loaded!
+ next
+ end
model = seen[parent.base_klass][primary_id][node.base_klass][id]
@@ -257,6 +259,7 @@ module ActiveRecord
construct(model, node, row, rs, seen, model_cache, aliases)
else
model = construct_model(ar_parent, node, row, model_cache, id, aliases)
+ model.readonly!
seen[parent.base_klass][primary_id][node.base_klass][id] = model
construct(model, node, row, rs, seen, model_cache, aliases)
end
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index e7d3c9ba40..a6ad09a38a 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -25,7 +25,7 @@ module ActiveRecord
def join_constraints(foreign_table, foreign_klass, node, join_type, tables, scope_chain, chain)
joins = []
- bind_values = []
+ binds = []
tables = tables.reverse
scope_chain_index = 0
@@ -43,23 +43,30 @@ module ActiveRecord
constraint = build_constraint(klass, table, key, foreign_table, foreign_key)
+ predicate_builder = PredicateBuilder.new(TableMetadata.new(klass, table))
scope_chain_items = scope_chain[scope_chain_index].map do |item|
if item.is_a?(Relation)
item
else
- ActiveRecord::Relation.create(klass, table).instance_exec(node, &item)
+ ActiveRecord::Relation.create(klass, table, predicate_builder)
+ .instance_exec(node, &item)
end
end
scope_chain_index += 1
- scope_chain_items.concat [klass.send(:build_default_scope, ActiveRecord::Relation.create(klass, table))].compact
+ relation = ActiveRecord::Relation.create(
+ klass,
+ table,
+ predicate_builder,
+ )
+ scope_chain_items.concat [klass.send(:build_default_scope, relation)].compact
rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right|
left.merge right
end
if rel && !rel.arel.constraints.empty?
- bind_values.concat rel.bind_values
+ binds += rel.bound_attributes
constraint = constraint.and rel.arel.constraints
end
@@ -67,8 +74,8 @@ module ActiveRecord
value = foreign_klass.base_class.name
column = klass.columns_hash[reflection.type.to_s]
- substitute = klass.connection.substitute_at(column, bind_values.length)
- bind_values.push [column, value]
+ substitute = klass.connection.substitute_at(column)
+ binds << Relation::QueryAttribute.new(column.name, value, klass.type_for_attribute(column.name))
constraint = constraint.and table[reflection.type].eq substitute
end
@@ -78,7 +85,7 @@ module ActiveRecord
foreign_table, foreign_klass = table, klass
end
- JoinInformation.new joins, bind_values
+ JoinInformation.new joins, binds
end
# Builds equality condition.
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index 46bccbf15a..97f4bd3811 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -89,7 +89,7 @@ module ActiveRecord
# { author: :avatar }
# [ :books, { author: :avatar } ]
- NULL_RELATION = Struct.new(:values, :bind_values).new({}, [])
+ NULL_RELATION = Struct.new(:values, :where_clause, :joins_values).new({}, Relation::WhereClause.empty, [])
def preload(records, associations, preload_scope = nil)
records = Array.wrap(records).compact.uniq
@@ -178,6 +178,7 @@ module ActiveRecord
class NullPreloader
def self.new(klass, owners, reflection, preload_scope); self; end
def self.run(preloader); end
+ def self.preloaded_records; []; end
end
def preloader_for(reflection, owners, rhs_klass)
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index c0639742be..1dc8bff193 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -33,7 +33,7 @@ module ActiveRecord
end
def query_scope(ids)
- scope.where(association_key.in(ids))
+ scope.where(association_key_name => ids)
end
def table
@@ -131,25 +131,20 @@ module ActiveRecord
def build_scope
scope = klass.unscoped
- values = reflection_scope.values
- reflection_binds = reflection_scope.bind_values
+ values = reflection_scope.values
preload_values = preload_scope.values
- preload_binds = preload_scope.bind_values
- scope.where_values = Array(values[:where]) + Array(preload_values[:where])
+ scope.where_clause = reflection_scope.where_clause + preload_scope.where_clause
scope.references_values = Array(values[:references]) + Array(preload_values[:references])
- scope.bind_values = (reflection_binds + preload_binds)
- scope._select! preload_values[:select] || values[:select] || table[Arel.star]
+ scope._select! preload_values[:select] || values[:select] || table[Arel.star]
scope.includes! preload_values[:includes] || values[:includes]
-
- if preload_values.key? :order
- scope.order! preload_values[:order]
+ if preload_scope.joins_values.any?
+ scope.joins!(preload_scope.joins_values)
else
- if values.key? :order
- scope.order! values[:order]
- end
+ scope.joins!(reflection_scope.joins_values)
end
+ scope.order! preload_values[:order] || values[:order]
if preload_values[:readonly] || values[:readonly]
scope.readonly!
@@ -159,6 +154,7 @@ module ActiveRecord
scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
end
+ scope.unscope_values = Array(values[:unscope])
klass.default_scoped.merge(scope)
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/has_many_through.rb b/activerecord/lib/active_record/associations/preloader/has_many_through.rb
index 7b37b5942d..2029871f39 100644
--- a/activerecord/lib/active_record/associations/preloader/has_many_through.rb
+++ b/activerecord/lib/active_record/associations/preloader/has_many_through.rb
@@ -8,7 +8,7 @@ module ActiveRecord
records_by_owner = super
if reflection_scope.distinct_value
- records_by_owner.each_value { |records| records.uniq! }
+ records_by_owner.each_value(&:uniq!)
end
records_by_owner
diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb
index 12bf3ef138..56aa23b173 100644
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
@@ -78,10 +78,9 @@ module ActiveRecord
if options[:source_type]
scope.where! reflection.foreign_type => options[:source_type]
else
- unless reflection_scope.where_values.empty?
+ unless reflection_scope.where_clause.empty?
scope.includes_values = Array(reflection_scope.values[:includes] || options[:source])
- scope.where_values = reflection_scope.values[:where]
- scope.bind_values = reflection_scope.bind_values
+ scope.where_clause = reflection_scope.where_clause
end
scope.references! reflection_scope.values[:references]
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index c360ef1b2c..58d0f7d65d 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -41,7 +41,7 @@ module ActiveRecord
def get_records
if reflection.scope_chain.any?(&:any?) ||
scope.eager_loading? ||
- klass.current_scope
+ klass.scope_attributes?
return scope.limit(1).to_a
end
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index e47e81aa0f..af1bce523c 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -18,7 +18,7 @@ module ActiveRecord
reflection_scope = reflection.scope
if reflection_scope && reflection_scope.arity.zero?
- relation.merge!(reflection_scope)
+ relation = relation.merge(reflection_scope)
end
scope.merge!(
@@ -33,7 +33,7 @@ module ActiveRecord
# Construct attributes for :through pointing to owner and associate. This is used by the
# methods which create and delete records on the association.
#
- # We only support indirectly modifying through associations which has a belongs_to source.
+ # We only support indirectly modifying through associations which have a belongs_to source.
# This is the "has_many :tags, through: :taggings" situation, where the join model
# typically has a belongs_to on both side. In other words, associations which could also
# be represented as has_and_belongs_to_many associations.
@@ -91,6 +91,17 @@ module ActiveRecord
raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection)
end
end
+
+ def build_record(attributes)
+ inverse = source_reflection.inverse_of
+ target = through_association.target
+
+ if inverse && target && !target.is_a?(Array)
+ attributes[inverse.foreign_key] = target.id
+ end
+
+ super(attributes)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb
index 8cc1904575..73dd3fa041 100644
--- a/activerecord/lib/active_record/attribute.rb
+++ b/activerecord/lib/active_record/attribute.rb
@@ -9,6 +9,10 @@ module ActiveRecord
FromUser.new(name, value, type)
end
+ def with_cast_value(name, value, type)
+ WithCastValue.new(name, value, type)
+ end
+
def null(name)
Null.new(name)
end
@@ -39,7 +43,7 @@ module ActiveRecord
end
def value_for_database
- type.type_cast_for_database(value)
+ type.serialize(value)
end
def changed_from?(old_value)
@@ -47,7 +51,7 @@ module ActiveRecord
end
def changed_in_place_from?(old_value)
- type.changed_in_place?(old_value, value)
+ has_been_read? && type.changed_in_place?(old_value, value)
end
def with_value_from_user(value)
@@ -58,6 +62,14 @@ module ActiveRecord
self.class.from_database(name, value, type)
end
+ def with_cast_value(value)
+ self.class.with_cast_value(name, value, type)
+ end
+
+ def with_type(type)
+ self.class.new(name, value_before_type_cast, type)
+ end
+
def type_cast(*)
raise NotImplementedError
end
@@ -66,12 +78,25 @@ module ActiveRecord
true
end
+ def came_from_user?
+ false
+ end
+
+ def has_been_read?
+ defined?(@value)
+ end
+
def ==(other)
self.class == other.class &&
name == other.name &&
value_before_type_cast == other.value_before_type_cast &&
type == other.type
end
+ alias eql? ==
+
+ def hash
+ [self.class, name, value_before_type_cast, type].hash
+ end
protected
@@ -83,13 +108,27 @@ module ActiveRecord
class FromDatabase < Attribute # :nodoc:
def type_cast(value)
- type.type_cast_from_database(value)
+ type.deserialize(value)
end
end
class FromUser < Attribute # :nodoc:
def type_cast(value)
- type.type_cast_from_user(value)
+ type.cast(value)
+ end
+
+ def came_from_user?
+ true
+ end
+ end
+
+ class WithCastValue < Attribute # :nodoc:
+ def type_cast(value)
+ value
+ end
+
+ def changed_in_place_from?(old_value)
+ false
end
end
@@ -102,6 +141,10 @@ module ActiveRecord
nil
end
+ def with_type(type)
+ self.class.with_cast_value(name, nil, type)
+ end
+
def with_value_from_database(value)
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`"
end
@@ -126,6 +169,6 @@ module ActiveRecord
false
end
end
- private_constant :FromDatabase, :FromUser, :Null, :Uninitialized
+ private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue
end
end
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index 2887db3bf7..cc265e2af6 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -3,61 +3,37 @@ require 'active_model/forbidden_attributes_protection'
module ActiveRecord
module AttributeAssignment
extend ActiveSupport::Concern
- include ActiveModel::ForbiddenAttributesProtection
-
- # Allows you to set all the attributes by passing in a hash of attributes with
- # keys matching the attribute names (which again matches the column names).
- #
- # If the passed hash responds to <tt>permitted?</tt> method and the return value
- # of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
- # exception is raised.
- #
- # cat = Cat.new(name: "Gorby", status: "yawning")
- # cat.attributes # => { "name" => "Gorby", "status" => "yawning", "created_at" => nil, "updated_at" => nil}
- # cat.assign_attributes(status: "sleeping")
- # cat.attributes # => { "name" => "Gorby", "status" => "sleeping", "created_at" => nil, "updated_at" => nil }
- #
- # New attributes will be persisted in the database when the object is saved.
- #
- # Aliased to <tt>attributes=</tt>.
- def assign_attributes(new_attributes)
- if !new_attributes.respond_to?(:stringify_keys)
- raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
- end
- return if new_attributes.blank?
+ include ActiveModel::AttributeAssignment
+
+ # Alias for `assign_attributes`. See +ActiveModel::AttributeAssignment+.
+ def attributes=(attributes)
+ assign_attributes(attributes)
+ end
- attributes = new_attributes.stringify_keys
- multi_parameter_attributes = []
- nested_parameter_attributes = []
+ private
- attributes = sanitize_for_mass_assignment(attributes)
+ def _assign_attributes(attributes) # :nodoc:
+ multi_parameter_attributes = {}
+ nested_parameter_attributes = {}
attributes.each do |k, v|
if k.include?("(")
- multi_parameter_attributes << [ k, v ]
+ multi_parameter_attributes[k] = attributes.delete(k)
elsif v.is_a?(Hash)
- nested_parameter_attributes << [ k, v ]
- else
- _assign_attribute(k, v)
+ nested_parameter_attributes[k] = attributes.delete(k)
end
end
+ super(attributes)
assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
end
- alias attributes= assign_attributes
-
- private
-
- def _assign_attribute(k, v)
- public_send("#{k}=", v)
- rescue NoMethodError
- if respond_to?("#{k}=")
- raise
- else
- raise UnknownAttributeError.new(self, k)
- end
+ # Re-raise with the ActiveRecord constant in case of an error
+ def _assign_attribute(k, v) # :nodoc:
+ super
+ rescue ActiveModel::UnknownAttributeError
+ raise UnknownAttributeError.new(self, k)
end
# Assign any deferred nested attributes after the base attributes have been set.
@@ -81,13 +57,18 @@ module ActiveRecord
errors = []
callstack.each do |name, values_with_empty_parameters|
begin
- send("#{name}=", MultiparameterAttribute.new(self, name, values_with_empty_parameters).read_value)
+ if values_with_empty_parameters.each_value.all?(&:nil?)
+ values = nil
+ else
+ values = values_with_empty_parameters
+ end
+ send("#{name}=", values)
rescue => ex
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
end
end
unless errors.empty?
- error_descriptions = errors.map { |ex| ex.message }.join(",")
+ error_descriptions = errors.map(&:message).join(",")
raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
end
end
@@ -113,100 +94,5 @@ module ActiveRecord
def find_parameter_position(multiparameter_name)
multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
end
-
- class MultiparameterAttribute #:nodoc:
- attr_reader :object, :name, :values, :cast_type
-
- def initialize(object, name, values)
- @object = object
- @name = name
- @values = values
- end
-
- def read_value
- return if values.values.compact.empty?
-
- @cast_type = object.type_for_attribute(name)
- klass = cast_type.klass
-
- if klass == Time
- read_time
- elsif klass == Date
- read_date
- else
- read_other
- end
- end
-
- private
-
- def instantiate_time_object(set_values)
- if object.class.send(:create_time_zone_conversion_attribute?, name, cast_type)
- Time.zone.local(*set_values)
- else
- Time.send(object.class.default_timezone, *set_values)
- end
- end
-
- def read_time
- # If column is a :time (and not :date or :datetime) there is no need to validate if
- # there are year/month/day fields
- if cast_type.type == :time
- # if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil
- { 1 => 1970, 2 => 1, 3 => 1 }.each do |key,value|
- values[key] ||= value
- end
- else
- # else column is a timestamp, so if Date bits were not provided, error
- validate_required_parameters!([1,2,3])
-
- # If Date bits were provided but blank, then return nil
- return if blank_date_parameter?
- end
-
- max_position = extract_max_param(6)
- set_values = values.values_at(*(1..max_position))
- # If Time bits are not there, then default to 0
- (3..5).each { |i| set_values[i] = set_values[i].presence || 0 }
- instantiate_time_object(set_values)
- end
-
- def read_date
- return if blank_date_parameter?
- set_values = values.values_at(1,2,3)
- begin
- Date.new(*set_values)
- rescue ArgumentError # if Date.new raises an exception on an invalid date
- instantiate_time_object(set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
- end
- end
-
- def read_other
- max_position = extract_max_param
- positions = (1..max_position)
- validate_required_parameters!(positions)
-
- values.slice(*positions)
- end
-
- # Checks whether some blank date parameter exists. Note that this is different
- # than the validate_required_parameters! method, since it just checks for blank
- # positions instead of missing ones, and does not raise in case one blank position
- # exists. The caller is responsible to handle the case of this returning true.
- def blank_date_parameter?
- (1..3).any? { |position| values[position].blank? }
- end
-
- # If some position is not provided, it errors out a missing parameter exception.
- def validate_required_parameters!(positions)
- if missing_parameter = positions.detect { |position| !values.key?(position) }
- raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter})")
- end
- end
-
- def extract_max_param(upper_cap = 100)
- [values.keys.max, upper_cap].min
- end
- end
end
end
diff --git a/activerecord/lib/active_record/attribute_decorators.rb b/activerecord/lib/active_record/attribute_decorators.rb
index 5b96623b6e..7d0ae32411 100644
--- a/activerecord/lib/active_record/attribute_decorators.rb
+++ b/activerecord/lib/active_record/attribute_decorators.rb
@@ -15,7 +15,7 @@ module ActiveRecord
end
def decorate_matching_attribute_types(matcher, decorator_name, &block)
- clear_caches_calculated_from_columns
+ reload_schema_from_cache
decorator_name = decorator_name.to_s
# Create new hashes so we don't modify parent classes
@@ -24,10 +24,11 @@ module ActiveRecord
private
- def add_user_provided_columns(*)
- super.map do |column|
- decorated_type = attribute_type_decorations.apply(column.name, column.cast_type)
- column.with_type(decorated_type)
+ def load_schema!
+ super
+ attribute_types.each do |name, type|
+ decorated_type = attribute_type_decorations.apply(name, type)
+ define_attribute(name, decorated_type)
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 34ec397aee..9d58a19304 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -83,7 +83,7 @@ module ActiveRecord
generated_attribute_methods.synchronize do
return false if @attribute_methods_generated
superclass.define_attribute_methods unless self == base_class
- super(column_names)
+ super(attribute_names)
@attribute_methods_generated = true
end
true
@@ -150,7 +150,7 @@ module ActiveRecord
BLACKLISTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Base)
end
- def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc
+ def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
if klass.respond_to?(name, true)
if superklass.respond_to?(name, true)
klass.method(name).owner != superklass.method(name).owner
@@ -185,14 +185,15 @@ module ActiveRecord
# # => ["id", "created_at", "updated_at", "name", "age"]
def attribute_names
@attribute_names ||= if !abstract_class? && table_exists?
- column_names
+ attribute_types.keys
else
[]
end
end
# Returns the column object for the named attribute.
- # Returns nil if the named attribute does not exist.
+ # Returns a +ActiveRecord::ConnectionAdapters::NullColumn+ if the
+ # named attribute does not exist.
#
# class Person < ActiveRecord::Base
# end
@@ -202,23 +203,18 @@ module ActiveRecord
# # => #<ActiveRecord::ConnectionAdapters::Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
#
# person.column_for_attribute(:nothing)
- # # => nil
+ # # => #<ActiveRecord::ConnectionAdapters::NullColumn:0xXXX @name=nil, @sql_type=nil, @cast_type=#<Type::Value>, ...>
def column_for_attribute(name)
- column = columns_hash[name.to_s]
- if column.nil?
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- `#column_for_attribute` will return a null object for non-existent
- columns in Rails 5. Use `#has_attribute?` if you need to check for
- an attribute's existence.
- MSG
+ name = name.to_s
+ columns_hash.fetch(name) do
+ ConnectionAdapters::NullColumn.new(name)
end
- column
end
end
# A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
# <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
- # which will all return +true+. It also define the attribute methods if they have
+ # which will all return +true+. It also defines the attribute methods if they have
# not been generated.
#
# class Person < ActiveRecord::Base
@@ -332,7 +328,7 @@ module ActiveRecord
# task.attribute_present?(:title) # => true
# task.attribute_present?(:is_done) # => true
def attribute_present?(attribute)
- value = read_attribute(attribute)
+ value = _read_attribute(attribute)
!value.nil? && !(value.respond_to?(:empty?) && value.empty?)
end
@@ -373,6 +369,39 @@ module ActiveRecord
write_attribute(attr_name, value)
end
+ # Returns the name of all database fields which have been read from this
+ # model. This can be useful in development mode to determine which fields
+ # need to be selected. For performance critical pages, selecting only the
+ # required fields can be an easy performance win (assuming you aren't using
+ # all of the fields on the model).
+ #
+ # For example:
+ #
+ # class PostsController < ActionController::Base
+ # after_action :print_accessed_fields, only: :index
+ #
+ # def index
+ # @posts = Post.all
+ # end
+ #
+ # private
+ #
+ # def print_accessed_fields
+ # p @posts.first.accessed_fields
+ # 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)
+ # end
+ # end
+ def accessed_fields
+ @attributes.accessed
+ end
+
protected
def clone_attribute_value(reader_method, attribute_name) # :nodoc:
@@ -433,7 +462,7 @@ module ActiveRecord
end
def typecasted_attribute_value(name)
- read_attribute(name)
+ _read_attribute(name)
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
index fd61febd57..56c1898551 100644
--- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
+++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
@@ -28,6 +28,7 @@ module ActiveRecord
included do
attribute_method_suffix "_before_type_cast"
+ attribute_method_suffix "_came_from_user?"
end
# Returns the value of the attribute identified by +attr_name+ before
@@ -66,6 +67,10 @@ module ActiveRecord
def attribute_before_type_cast(attribute_name)
read_attribute_before_type_cast(attribute_name)
end
+
+ def attribute_came_from_user?(attribute_name)
+ @attributes[attribute_name].came_from_user?
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 2f02738f6d..7ba907f786 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -69,8 +69,17 @@ module ActiveRecord
end
end
+ def attribute_changed_in_place?(attr_name)
+ old_value = original_raw_attribute(attr_name)
+ @attributes[attr_name].changed_in_place_from?(old_value)
+ end
+
private
+ def changes_include?(attr_name)
+ super || attribute_changed_in_place?(attr_name)
+ end
+
def calculate_changes_from_defaults
@changed_attributes = nil
self.class.column_defaults.each do |attr, orig_value|
@@ -99,7 +108,7 @@ module ActiveRecord
end
def save_changed_attribute(attr, old_value)
- if attribute_changed?(attr)
+ if attribute_changed_by_setter?(attr)
clear_attribute_changes(attr) unless _field_changed?(attr, old_value)
else
set_attribute_was(attr, old_value) if _field_changed?(attr, old_value)
@@ -110,7 +119,7 @@ module ActiveRecord
if attribute_changed?(attr)
changed_attributes[attr]
else
- clone_attribute_value(:read_attribute, attr)
+ clone_attribute_value(:_read_attribute, attr)
end
end
@@ -122,10 +131,8 @@ module ActiveRecord
partial_writes? ? super(keys_for_partial_write) : super
end
- # Serialized attributes should always be written in case they've been
- # changed in place.
def keys_for_partial_write
- changed
+ changed & self.class.column_names
end
def _field_changed?(attr, old_value)
@@ -141,15 +148,10 @@ module ActiveRecord
def changed_in_place
self.class.attribute_names.select do |attr_name|
- changed_in_place?(attr_name)
+ attribute_changed_in_place?(attr_name)
end
end
- def changed_in_place?(attr_name)
- old_value = original_raw_attribute(attr_name)
- @attributes[attr_name].changed_in_place_from?(old_value)
- end
-
def original_raw_attribute(attr_name)
original_raw_attributes.fetch(attr_name) do
read_attribute_before_type_cast(attr_name)
@@ -161,7 +163,7 @@ module ActiveRecord
end
def store_original_raw_attribute(attr_name)
- original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database
+ original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database rescue nil
end
def store_original_raw_attributes
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index 9bd333bbac..c28374e4ab 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -17,7 +17,7 @@ module ActiveRecord
def id
if pk = self.class.primary_key
sync_with_transaction_state
- read_attribute(pk)
+ _read_attribute(pk)
end
end
@@ -120,6 +120,7 @@ module ActiveRecord
def primary_key=(value)
@primary_key = value && value.to_s
@quoted_primary_key = nil
+ @attributes_builder = nil
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/query.rb b/activerecord/lib/active_record/attribute_methods/query.rb
index dc689f399a..553122a5fc 100644
--- a/activerecord/lib/active_record/attribute_methods/query.rb
+++ b/activerecord/lib/active_record/attribute_methods/query.rb
@@ -22,7 +22,7 @@ module ActiveRecord
return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
!value.blank?
end
- elsif column.number?
+ elsif value.respond_to?(:zero?)
!value.zero?
else
!value.blank?
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index bf2a084a00..0d989c2eca 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/module/method_transplanting'
-
module ActiveRecord
module AttributeMethods
module Read
@@ -27,7 +25,7 @@ module ActiveRecord
<<-EOMETHOD
def #{method_name}
name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name}
- read_attribute(name) { |n| missing_attribute(n, caller) }
+ _read_attribute(name) { |n| missing_attribute(n, caller) }
end
EOMETHOD
end
@@ -36,60 +34,56 @@ module ActiveRecord
extend ActiveSupport::Concern
module ClassMethods
- [:cache_attributes, :cached_attributes, :cache_attribute?].each do |method_name|
- define_method method_name do |*|
- cached_attributes_deprecation_warning(method_name)
- true
- end
- end
-
protected
- def cached_attributes_deprecation_warning(method_name)
- ActiveSupport::Deprecation.warn "Calling `#{method_name}` is no longer necessary. All attributes are cached."
- end
-
- if Module.methods_transplantable?
- def define_method_attribute(name)
- method = ReaderMethodCache[name]
- generated_attribute_methods.module_eval { define_method name, method }
- end
- else
- def define_method_attribute(name)
- safe_name = name.unpack('h*').first
- temp_method = "__temp__#{safe_name}"
-
- ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
+ def define_method_attribute(name)
+ safe_name = name.unpack('h*').first
+ temp_method = "__temp__#{safe_name}"
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
- def #{temp_method}
- name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
- read_attribute(name) { |n| missing_attribute(n, caller) }
- end
- STR
+ ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
- generated_attribute_methods.module_eval do
- alias_method name, temp_method
- undef_method temp_method
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
+ def #{temp_method}
+ name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
+ _read_attribute(name) { |n| missing_attribute(n, caller) }
end
+ STR
+
+ generated_attribute_methods.module_eval do
+ alias_method name, temp_method
+ undef_method temp_method
end
end
end
+ ID = 'id'.freeze
+
# Returns the value of the attribute identified by <tt>attr_name</tt> after
# it has been typecast (for example, "2004-12-12" in a date column is cast
# to a date object, like Date.new(2004, 12, 12)).
def read_attribute(attr_name, &block)
name = attr_name.to_s
- name = self.class.primary_key if name == 'id'
- @attributes.fetch_value(name, &block)
+ name = self.class.primary_key if name == ID
+ _read_attribute(name, &block)
end
- private
-
- def attribute(attribute_name)
- read_attribute(attribute_name)
+ # This method exists to avoid the expensive primary_key check internally, without
+ # breaking compatibility with the read_attribute API
+ if defined?(JRUBY_VERSION)
+ # This form is significantly faster on JRuby, and this is one of our biggest hotspots.
+ # https://github.com/jruby/jruby/pull/2562
+ def _read_attribute(attr_name, &block) # :nodoc
+ @attributes.fetch_value(attr_name.to_s, &block)
+ end
+ else
+ def _read_attribute(attr_name) # :nodoc:
+ @attributes.fetch_value(attr_name.to_s) { |n| yield n if block_given? }
+ end
end
+
+ alias :attribute :_read_attribute
+ private :attribute
+
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index e5ec5ddca5..d0d8a968c5 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/string/filters'
-
module ActiveRecord
module AttributeMethods
module Serialization
@@ -51,19 +49,6 @@ module ActiveRecord
Type::Serialized.new(type, coder)
end
end
-
- def serialized_attributes
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- `serialized_attributes` is deprecated without replacement, and will
- be removed in Rails 5.0.
- MSG
-
- @serialized_attributes ||= Hash[
- columns.select { |t| t.cast_type.is_a?(Type::Serialized) }.map { |c|
- [c.name, c.cast_type.coder]
- }
- ]
- end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index b7fe079ef5..f9beb43e4b 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -1,21 +1,27 @@
module ActiveRecord
module AttributeMethods
module TimeZoneConversion
- class TimeZoneConverter < SimpleDelegator # :nodoc:
- include Type::Decorator
-
- def type_cast_from_database(value)
+ class TimeZoneConverter < DelegateClass(Type::Value) # :nodoc:
+ def deserialize(value)
convert_time_to_time_zone(super)
end
- def type_cast_from_user(value)
+ def cast(value)
if value.is_a?(Array)
- value.map { |v| type_cast_from_user(v) }
+ value.map { |v| cast(v) }
+ elsif value.is_a?(Hash)
+ set_time_zone_without_conversion(super)
elsif value.respond_to?(:in_time_zone)
- value.in_time_zone
+ begin
+ user_input_in_time_zone(value) || super
+ rescue ArgumentError
+ nil
+ end
end
end
+ private
+
def convert_time_to_time_zone(value)
if value.is_a?(Array)
value.map { |v| convert_time_to_time_zone(v) }
@@ -25,6 +31,10 @@ module ActiveRecord
value
end
end
+
+ def set_time_zone_without_conversion(value)
+ ::Time.zone.local_to_utc(value).in_time_zone
+ end
end
extend ActiveSupport::Concern
@@ -35,6 +45,9 @@ module ActiveRecord
class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false
self.skip_time_zone_conversion_for_attributes = []
+
+ class_attribute :time_zone_aware_types, instance_writer: false
+ self.time_zone_aware_types = [:datetime, :not_explicitly_configured]
end
module ClassMethods
@@ -55,9 +68,31 @@ module ActiveRecord
end
def create_time_zone_conversion_attribute?(name, cast_type)
- time_zone_aware_attributes &&
- !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) &&
- (:datetime == cast_type.type)
+ enabled_for_column = time_zone_aware_attributes &&
+ !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym)
+ result = enabled_for_column &&
+ time_zone_aware_types.include?(cast_type.type)
+
+ if enabled_for_column &&
+ !result &&
+ cast_type.type == :time &&
+ time_zone_aware_types.include?(:not_explicitly_configured)
+ ActiveSupport::Deprecation.warn(<<-MESSAGE)
+ Time columns will become time zone aware in Rails 5.1. This
+ still causes `String`s to be parsed as if they were in `Time.zone`,
+ and `Time`s to be converted to `Time.zone`.
+
+ To keep the old behavior, you must add the following to your initializer:
+
+ config.active_record.time_zone_aware_types = [:datetime]
+
+ To silence this deprecation warning, add the following:
+
+ config.active_record.time_zone_aware_types << :time
+ MESSAGE
+ end
+
+ result
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index b3c8209a74..ab017c7b54 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/module/method_transplanting'
-
module ActiveRecord
module AttributeMethods
module Write
@@ -25,27 +23,18 @@ module ActiveRecord
module ClassMethods
protected
- if Module.methods_transplantable?
- def define_method_attribute=(name)
- method = WriterMethodCache[name]
- generated_attribute_methods.module_eval {
- define_method "#{name}=", method
- }
- end
- else
- def define_method_attribute=(name)
- safe_name = name.unpack('h*').first
- ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
+ def define_method_attribute=(name)
+ safe_name = name.unpack('h*').first
+ ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
- def __temp__#{safe_name}=(value)
- name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
- write_attribute(name, value)
- end
- alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
- undef_method :__temp__#{safe_name}=
- STR
- end
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
+ def __temp__#{safe_name}=(value)
+ name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
+ write_attribute(name, value)
+ end
+ alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
+ undef_method :__temp__#{safe_name}=
+ STR
end
end
@@ -73,7 +62,7 @@ module ActiveRecord
if should_type_cast
@attributes.write_from_user(attr_name, value)
else
- @attributes.write_from_database(attr_name, value)
+ @attributes.write_cast_value(attr_name, value)
end
value
diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb
index 98ac63c7e1..013a7d0e01 100644
--- a/activerecord/lib/active_record/attribute_set.rb
+++ b/activerecord/lib/active_record/attribute_set.rb
@@ -2,8 +2,6 @@ require 'active_record/attribute_set/builder'
module ActiveRecord
class AttributeSet # :nodoc:
- delegate :keys, to: :initialized_attributes
-
def initialize(attributes)
@attributes = attributes
end
@@ -12,6 +10,10 @@ module ActiveRecord
attributes[name] || Attribute.null(name)
end
+ def []=(name, value)
+ attributes[name] = value
+ end
+
def values_before_type_cast
attributes.transform_values(&:value_before_type_cast)
end
@@ -25,8 +27,20 @@ module ActiveRecord
attributes.key?(name) && self[name].initialized?
end
- def fetch_value(name, &block)
- self[name].value(&block)
+ def keys
+ attributes.each_key.select { |name| self[name].initialized? }
+ end
+
+ if defined?(JRUBY_VERSION)
+ # This form is significantly faster on JRuby, and this is one of our biggest hotspots.
+ # https://github.com/jruby/jruby/pull/2562
+ def fetch_value(name, &block)
+ self[name].value(&block)
+ end
+ else
+ def fetch_value(name)
+ self[name].value { |n| yield n if block_given? }
+ end
end
def write_from_database(name, value)
@@ -37,13 +51,17 @@ module ActiveRecord
attributes[name] = self[name].with_value_from_user(value)
end
+ def write_cast_value(name, value)
+ attributes[name] = self[name].with_cast_value(value)
+ end
+
def freeze
@attributes.freeze
super
end
def initialize_dup(_)
- @attributes = attributes.transform_values(&:dup)
+ @attributes = attributes.deep_dup
super
end
@@ -58,10 +76,8 @@ module ActiveRecord
end
end
- def ensure_initialized(key)
- unless self[key].initialized?
- write_from_database(key, nil)
- end
+ def accessed
+ attributes.select { |_, attr| attr.has_been_read? }.keys
end
protected
diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb
index d4a787f2fe..e85777c335 100644
--- a/activerecord/lib/active_record/attribute_set/builder.rb
+++ b/activerecord/lib/active_record/attribute_set/builder.rb
@@ -1,35 +1,92 @@
module ActiveRecord
class AttributeSet # :nodoc:
class Builder # :nodoc:
- attr_reader :types
+ attr_reader :types, :always_initialized
- def initialize(types)
+ def initialize(types, always_initialized = nil)
@types = types
+ @always_initialized = always_initialized
end
def build_from_database(values = {}, additional_types = {})
- attributes = build_attributes_from_values(values, additional_types)
- add_uninitialized_attributes(attributes)
+ if always_initialized && !values.key?(always_initialized)
+ values[always_initialized] = nil
+ end
+
+ attributes = LazyAttributeHash.new(types, values, additional_types)
AttributeSet.new(attributes)
end
+ end
+ end
+
+ class LazyAttributeHash # :nodoc:
+ delegate :transform_values, :each_key, to: :materialize
+
+ def initialize(types, values, additional_types)
+ @types = types
+ @values = values
+ @additional_types = additional_types
+ @materialized = false
+ @delegate_hash = {}
+ end
+
+ def key?(key)
+ delegate_hash.key?(key) || values.key?(key) || types.key?(key)
+ end
- private
+ def [](key)
+ delegate_hash[key] || assign_default_value(key)
+ end
- def build_attributes_from_values(values, additional_types)
- values.each_with_object({}) do |(name, value), hash|
- type = additional_types.fetch(name, types[name])
- hash[name] = Attribute.from_database(name, value, type)
+ def []=(key, value)
+ if frozen?
+ raise RuntimeError, "Can't modify frozen hash"
+ end
+ delegate_hash[key] = value
+ end
+
+ def initialize_dup(_)
+ @delegate_hash = delegate_hash.transform_values(&:dup)
+ super
+ end
+
+ def select
+ keys = types.keys | values.keys | delegate_hash.keys
+ keys.each_with_object({}) do |key, hash|
+ attribute = self[key]
+ if yield(key, attribute)
+ hash[key] = attribute
end
end
+ end
+
+ protected
+
+ attr_reader :types, :values, :additional_types, :delegate_hash
+
+ private
+
+ def assign_default_value(name)
+ type = additional_types.fetch(name, types[name])
+ value_present = true
+ value = values.fetch(name) { value_present = false }
+
+ if value_present
+ delegate_hash[name] = Attribute.from_database(name, value, type)
+ elsif types.key?(name)
+ delegate_hash[name] = Attribute.uninitialized(name, type)
+ end
+ end
- def add_uninitialized_attributes(attributes)
- types.each_key do |name|
- next if attributes.key? name
- type = types[name]
- attributes[name] =
- Attribute.uninitialized(name, type)
+ def materialize
+ unless @materialized
+ values.each_key { |key| self[key] }
+ types.each_key { |key| self[key] }
+ unless frozen?
+ @materialized = true
end
end
+ delegate_hash
end
end
end
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
index 3288108a6a..50339b6f69 100644
--- a/activerecord/lib/active_record/attributes.rb
+++ b/activerecord/lib/active_record/attributes.rb
@@ -1,31 +1,45 @@
module ActiveRecord
- module Attributes # :nodoc:
+ # See ActiveRecord::Attributes::ClassMethods for documentation
+ module Attributes
extend ActiveSupport::Concern
+ # :nodoc:
Type = ActiveRecord::Type
included do
- class_attribute :user_provided_columns, instance_accessor: false # :internal:
- class_attribute :user_provided_defaults, instance_accessor: false # :internal:
- self.user_provided_columns = {}
- self.user_provided_defaults = {}
+ class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false # :internal:
+ self.attributes_to_define_after_schema_loads = {}
end
- module ClassMethods # :nodoc:
- # Defines or overrides a attribute on this model. This allows customization of
- # Active Record's type casting behavior, as well as adding support for user defined
- # types.
+ module ClassMethods
+ # Defines an attribute with a type on this model. It will override the
+ # type of existing attributes if needed. This allows control over how
+ # values are converted to and from SQL when assigned to a model. It also
+ # changes the behavior of values passed to
+ # ActiveRecord::QueryMethods#where. This will let you use
+ # your domain objects across much of Active Record, without having to
+ # rely on implementation details or monkey patching.
+ #
+ # +name+ The name of the methods to define attribute methods for, and the
+ # column which this will persist to.
+ #
+ # +cast_type+ A symbol such as +:string+ or +:integer+, or a type object
+ # to be used for this attribute. See the examples below for more
+ # information about providing custom type objects.
#
- # +name+ The name of the methods to define attribute methods for, and the column which
- # this will persist to.
+ # ==== Options
#
- # +cast_type+ A type object that contains information about how to type cast the value.
- # See the examples section for more information.
+ # The following options are accepted:
#
- # ==== Options
- # The options hash accepts the following options:
+ # +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+.
+ #
+ # +array+ (PG only) specifies that the type should be an array (see the
+ # examples below).
#
- # +default+ is the default value that the column should use on a new record.
+ # +range+ (PG only) specifies that the type should be a range (see the
+ # examples below).
#
# ==== Examples
#
@@ -46,93 +60,187 @@ module ActiveRecord
# store_listing.price_in_cents # => BigDecimal.new(10.1)
#
# class StoreListing < ActiveRecord::Base
- # attribute :price_in_cents, Type::Integer.new
+ # attribute :price_in_cents, :integer
# end
#
# # after
# store_listing.price_in_cents # => 10
#
- # Users may also define their own custom types, as long as they respond to the methods
- # defined on the value type. The `type_cast` method on your type object will be called
- # with values both from the database, and from your controllers. See
- # `ActiveRecord::Attributes::Type::Value` for the expected API. It is recommended that your
- # type objects inherit from an existing type, or the base value type.
+ # A default can also be provided.
+ #
+ # create_table :store_listings, force: true do |t|
+ # t.string :my_string, default: "original default"
+ # end
+ #
+ # StoreListing.new.my_string # => "original default"
+ #
+ # class StoreListing < ActiveRecord::Base
+ # attribute :my_string, :string, default: "new default"
+ # end
+ #
+ # StoreListing.new.my_string # => "new default"
+ #
+ # Attributes do not need to be backed by a database column.
+ #
+ # class MyModel < ActiveRecord::Base
+ # attribute :my_string, :string
+ # attribute :my_int_array, :integer, array: true
+ # attribute :my_float_range, :float, range: true
+ # end
+ #
+ # model = MyModel.new(
+ # my_string: "string",
+ # my_int_array: ["1", "2", "3"],
+ # my_float_range: "[1,3.5]",
+ # )
+ # model.attributes
+ # # =>
+ # {
+ # my_string: "string",
+ # my_int_array: [1, 2, 3],
+ # my_float_range: 1.0..3.5
+ # }
+ #
+ # ==== Creating Custom Types
+ #
+ # Users may also define their own custom types, as long as they respond
+ # to the methods defined on the value type. The method +deserialize+ or
+ # +cast+ will be called on your type object, with raw input from the
+ # database or from your controllers. See ActiveRecord::Type::Value for the
+ # expected API. It is recommended that your type objects inherit from an
+ # existing type, or from ActiveRecord::Type::Value
#
# class MoneyType < ActiveRecord::Type::Integer
- # def type_cast(value)
+ # def cast(value)
# if value.include?('$')
# price_in_dollars = value.gsub(/\$/, '').to_f
- # price_in_dollars * 100
+ # super(price_in_dollars * 100)
# else
- # value.to_i
+ # super
# end
# end
# end
#
+ # # config/initializers/types.rb
+ # ActiveRecord::Type.register(:money, MoneyType)
+ #
+ # # /app/models/store_listing.rb
# class StoreListing < ActiveRecord::Base
- # attribute :price_in_cents, MoneyType.new
+ # attribute :price_in_cents, :money
# end
#
# store_listing = StoreListing.new(price_in_cents: '$10.00')
# store_listing.price_in_cents # => 1000
- def attribute(name, cast_type, options = {})
+ #
+ # For more details on creating custom types, see the documentation for
+ # ActiveRecord::Type::Value. For more details on registering your types
+ # to be referenced by a symbol, see ActiveRecord::Type.register. You can
+ # also pass a type object directly, in place of a symbol.
+ #
+ # ==== Querying
+ #
+ # When ActiveRecord::QueryMethods#where is called, it will
+ # use the type defined by the model class to convert the value to SQL,
+ # calling +serialize+ on your type object. For example:
+ #
+ # class Money < Struct.new(:amount, :currency)
+ # end
+ #
+ # class MoneyType < Type::Value
+ # def initialize(currency_converter)
+ # @currency_converter = currency_converter
+ # end
+ #
+ # # value will be the result of +deserialize+ or
+ # # +cast+. Assumed to be an instance of +Money+ in
+ # # this case.
+ # def serialize(value)
+ # value_in_bitcoins = @currency_converter.convert_to_bitcoins(value)
+ # value_in_bitcoins.amount
+ # end
+ # end
+ #
+ # ActiveRecord::Type.register(:money, MoneyType)
+ #
+ # class Product < ActiveRecord::Base
+ # currency_converter = ConversionRatesFromTheInternet.new
+ # attribute :price_in_bitcoins, :money, currency_converter
+ # end
+ #
+ # Product.where(price_in_bitcoins: Money.new(5, "USD"))
+ # # => SELECT * FROM products WHERE price_in_bitcoins = 0.02230
+ #
+ # Product.where(price_in_bitcoins: Money.new(5, "GBP"))
+ # # => SELECT * FROM products WHERE price_in_bitcoins = 0.03412
+ #
+ # ==== Dirty Tracking
+ #
+ # The type of an attribute is given the opportunity to change how dirty
+ # tracking is performed. The methods +changed?+ and +changed_in_place?+
+ # will be called from ActiveModel::Dirty. See the documentation for those
+ # methods in ActiveRecord::Type::Value for more details.
+ def attribute(name, cast_type, **options)
name = name.to_s
- clear_caches_calculated_from_columns
- # Assign a new hash to ensure that subclasses do not share a hash
- self.user_provided_columns = user_provided_columns.merge(name => cast_type)
+ reload_schema_from_cache
- if options.key?(:default)
- self.user_provided_defaults = user_provided_defaults.merge(name => options[:default])
- end
+ self.attributes_to_define_after_schema_loads =
+ attributes_to_define_after_schema_loads.merge(
+ name => [cast_type, options]
+ )
end
- # Returns an array of column objects for the table associated with this class.
- def columns
- @columns ||= add_user_provided_columns(connection.schema_cache.columns(table_name))
+ # This is the low level API which sits beneath +attribute+. It only
+ # accepts type objects, and will do its work immediately instead of
+ # waiting for the schema to load. Automatic schema detection and
+ # ClassMethods#attribute both call this under the hood. While this method
+ # is provided so it can be used by plugin authors, application code
+ # should probably use ClassMethods#attribute.
+ #
+ # +name+ The name of the attribute being defined. Expected to be a +String+.
+ #
+ # +cast_type+ The type object to use for this attribute.
+ #
+ # +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+.
+ #
+ # +user_provided_default+ Whether the default value should be cast using
+ # +cast+ or +deserialize+.
+ def define_attribute(
+ name,
+ cast_type,
+ default: NO_DEFAULT_PROVIDED,
+ user_provided_default: true
+ )
+ attribute_types[name] = cast_type
+ define_default_attribute(name, default, cast_type, from_user: user_provided_default)
end
- # Returns a hash of column objects for the table associated with this class.
- def columns_hash
- @columns_hash ||= Hash[columns.map { |c| [c.name, c] }]
- end
-
- def reset_column_information # :nodoc:
+ def load_schema! # :nodoc:
super
- clear_caches_calculated_from_columns
- end
-
- private
-
- def add_user_provided_columns(schema_columns)
- existing_columns = schema_columns.map do |column|
- new_type = user_provided_columns[column.name]
- if new_type
- column.with_type(new_type)
- else
- column
+ attributes_to_define_after_schema_loads.each do |name, (type, options)|
+ if type.is_a?(Symbol)
+ type = ActiveRecord::Type.lookup(type, **options.except(:default))
end
- end
- existing_column_names = existing_columns.map(&:name)
- new_columns = user_provided_columns.except(*existing_column_names).map do |(name, type)|
- connection.new_column(name, nil, type)
+ define_attribute(name, type, **options.slice(:default))
end
-
- existing_columns + new_columns
end
- def clear_caches_calculated_from_columns
- @attributes_builder = nil
- @column_names = nil
- @column_types = nil
- @columns = nil
- @columns_hash = nil
- @content_columns = nil
- @default_attributes = nil
- end
+ private
- def raw_default_values
- super.merge(user_provided_defaults)
+ NO_DEFAULT_PROVIDED = Object.new # :nodoc:
+ private_constant :NO_DEFAULT_PROVIDED
+
+ def define_default_attribute(name, value, type, from_user:)
+ if value == NO_DEFAULT_PROVIDED
+ default_attribute = _default_attributes[name].with_type(type)
+ elsif from_user
+ default_attribute = Attribute.from_user(name, value, type)
+ else
+ default_attribute = Attribute.from_database(name, value, type)
+ end
+ _default_attributes[name] = default_attribute
end
end
end
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index a0d70435fa..0792d19c3e 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -177,10 +177,8 @@ module ActiveRecord
# before actually defining them.
def add_autosave_association_callbacks(reflection)
save_method = :"autosave_associated_records_for_#{reflection.name}"
- validation_method = :"validate_associated_records_for_#{reflection.name}"
- collection = reflection.collection?
- if collection
+ if reflection.collection?
before_save :before_save_collection_association
define_non_cyclic_method(save_method) { save_collection_association(reflection) }
@@ -200,13 +198,29 @@ module ActiveRecord
after_create save_method
after_update save_method
else
- define_non_cyclic_method(save_method) { save_belongs_to_association(reflection) }
+ define_non_cyclic_method(save_method) { throw(:abort) if save_belongs_to_association(reflection) == false }
before_save save_method
end
+ define_autosave_validation_callbacks(reflection)
+ end
+
+ def define_autosave_validation_callbacks(reflection)
+ validation_method = :"validate_associated_records_for_#{reflection.name}"
if reflection.validate? && !method_defined?(validation_method)
- method = (collection ? :validate_collection_association : :validate_single_association)
- define_non_cyclic_method(validation_method) { send(method, reflection) }
+ if reflection.collection?
+ method = :validate_collection_association
+ else
+ method = :validate_single_association
+ end
+
+ define_non_cyclic_method(validation_method) do
+ send(method, reflection)
+ # TODO: remove the following line as soon as the return value of
+ # callbacks is ignored, that is, returning `false` does not
+ # display a deprecation warning or halts the callback chain.
+ true
+ end
validate validation_method
end
end
@@ -263,20 +277,27 @@ module ActiveRecord
if new_record
association && association.target
elsif autosave
- association.target.find_all { |record| record.changed_for_autosave? }
+ association.target.find_all(&:changed_for_autosave?)
else
- association.target.find_all { |record| record.new_record? }
+ association.target.find_all(&:new_record?)
end
end
# go through nested autosave associations that are loaded in memory (without loading
# any new ones), and return true if is changed for autosave
def nested_records_changed_for_autosave?
- self.class._reflections.values.any? do |reflection|
- if reflection.options[:autosave]
- association = association_instance_get(reflection.name)
- association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? }
+ @_nested_records_changed_for_autosave_already_called ||= false
+ return false if @_nested_records_changed_for_autosave_already_called
+ begin
+ @_nested_records_changed_for_autosave_already_called = true
+ self.class._reflections.values.any? do |reflection|
+ if reflection.options[:autosave]
+ association = association_instance_get(reflection.name)
+ association && Array.wrap(association.target).any?(&:changed_for_autosave?)
+ end
end
+ ensure
+ @_nested_records_changed_for_autosave_already_called = false
end
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index f978fbd0a4..67490ecd97 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -22,6 +22,7 @@ require 'active_record/log_subscriber'
require 'active_record/explain_subscriber'
require 'active_record/relation/delegation'
require 'active_record/attributes'
+require 'active_record/type_caster'
module ActiveRecord #:nodoc:
# = Active Record
@@ -141,7 +142,7 @@ module ActiveRecord #:nodoc:
#
# In addition to the basic accessors, query methods are also automatically available on the Active Record object.
# Query methods allow you to test whether an attribute value is present.
- # For numeric values, present is defined as non-zero.
+ # Additionally, when dealing with numeric values, a query method will return false if the value is zero.
#
# For example, an Active Record User with the <tt>name</tt> attribute has a <tt>name?</tt> method that you can call
# to determine whether the user has a name:
@@ -257,7 +258,7 @@ module ActiveRecord #:nodoc:
# <tt>attributes=</tt> method. The +errors+ property of this exception contains an array of
# AttributeAssignmentError
# objects that should be inspected to determine which attributes triggered the errors.
- # * RecordInvalid - raised by save! and create! when the record is invalid.
+ # * RecordInvalid - raised by <tt>save!</tt> and <tt>create!</tt> when the record is invalid.
# * RecordNotFound - No record responded to the +find+ method. Either the row with the given ID doesn't exist
# or the row didn't meet the additional restrictions. Some +find+ calls do not raise this exception to signal
# nothing was found, please check its documentation for further details.
@@ -308,9 +309,12 @@ module ActiveRecord #:nodoc:
include Aggregations
include Transactions
include NoTouching
+ include TouchLater
include Reflection
include Serialization
include Store
+ include SecureToken
+ include Suppressor
end
ActiveSupport.run_load_hooks(:active_record, Base)
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 523d492a48..2fcba8e309 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -192,14 +192,14 @@ module ActiveRecord
#
# == <tt>before_validation*</tt> returning statements
#
- # If the returning value of a +before_validation+ callback can be evaluated to +false+, the process will be
+ # 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.
#
# == Canceling callbacks
#
- # If a <tt>before_*</tt> callback returns +false+, all the later callbacks and the associated action are
- # cancelled. If an <tt>after_*</tt> callback returns +false+, all the later callbacks are cancelled.
+ # If a <tt>before_*</tt> callback throws +:abort+, all the later callbacks and
+ # the associated action are cancelled.
# Callbacks are generally run in the order they are defined, with the exception of callbacks defined as
# methods on the model, which are called last.
#
@@ -289,25 +289,24 @@ module ActiveRecord
end
def destroy #:nodoc:
- _run_destroy_callbacks { super }
+ run_callbacks(:destroy) { super }
end
def touch(*) #:nodoc:
- _run_touch_callbacks { super }
+ run_callbacks(:touch) { super }
end
private
-
- def create_or_update #:nodoc:
- _run_save_callbacks { super }
+ def create_or_update(*) #:nodoc:
+ run_callbacks(:save) { super }
end
def _create_record #:nodoc:
- _run_create_callbacks { super }
+ run_callbacks(:create) { super }
end
def _update_record(*) #:nodoc:
- _run_update_callbacks { super }
+ run_callbacks(:update) { super }
end
end
end
diff --git a/activerecord/lib/active_record/coders/yaml_column.rb b/activerecord/lib/active_record/coders/yaml_column.rb
index d3d7396c91..9ea22ed798 100644
--- a/activerecord/lib/active_record/coders/yaml_column.rb
+++ b/activerecord/lib/active_record/coders/yaml_column.rb
@@ -8,6 +8,7 @@ module ActiveRecord
def initialize(object_class = Object)
@object_class = object_class
+ check_arity_of_constructor
end
def dump(obj)
@@ -33,6 +34,16 @@ module ActiveRecord
obj
end
+
+ private
+
+ def check_arity_of_constructor
+ begin
+ load(nil)
+ rescue ArgumentError
+ raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor."
+ end
+ 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 46812b75bb..8c50f3d1a3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -2,7 +2,6 @@ require 'thread'
require 'thread_safe'
require 'monitor'
require 'set'
-require 'active_support/core_ext/string/filters'
module ActiveRecord
# Raised when a connection could not be obtained within the connection
@@ -122,13 +121,13 @@ module ActiveRecord
# greater than the number of threads currently waiting (that
# is, don't jump ahead in line). Otherwise, return nil.
#
- # If +timeout+ is given, block if it there is no element
+ # If +timeout+ is given, block if there is no element
# available, waiting up to +timeout+ seconds for an element to
# become available.
#
# Raises:
# - ConnectionTimeoutError if +timeout+ is given and no element
- # becomes available after +timeout+ seconds,
+ # becomes available within +timeout+ seconds,
def poll(timeout = nil)
synchronize do
if timeout
@@ -151,7 +150,7 @@ module ActiveRecord
end
# A thread can remove an element from the queue without
- # waiting if an only if the number of currently available
+ # waiting if and only if the number of currently available
# connections is strictly greater than the number of waiting
# threads.
def can_remove_no_wait?
@@ -236,7 +235,7 @@ module ActiveRecord
@spec = spec
@checkout_timeout = (spec.config[:checkout_timeout] && spec.config[:checkout_timeout].to_f) || 5
- @reaper = Reaper.new self, spec.config[:reaping_frequency]
+ @reaper = Reaper.new(self, (spec.config[:reaping_frequency] && spec.config[:reaping_frequency].to_f))
@reaper.run
# default max pool size to 5
@@ -320,9 +319,7 @@ module ActiveRecord
checkin conn
conn.disconnect! if conn.requires_reloading?
end
- @connections.delete_if do |conn|
- conn.requires_reloading?
- end
+ @connections.delete_if(&:requires_reloading?)
@available.clear
@connections.each do |conn|
@available.add conn
@@ -361,11 +358,11 @@ module ActiveRecord
synchronize do
owner = conn.owner
- conn._run_checkin_callbacks do
+ conn.run_callbacks :checkin do
conn.expire
end
- release owner
+ release conn, owner
@available.add conn
end
@@ -378,7 +375,7 @@ module ActiveRecord
@connections.delete conn
@available.delete conn
- release conn.owner
+ release conn, conn.owner
@available.add checkout_new_connection if @available.any_waiting?
end
@@ -426,10 +423,12 @@ module ActiveRecord
end
end
- def release(owner)
+ def release(conn, owner)
thread_id = owner.object_id
- @reserved_connections.delete thread_id
+ if @reserved_connections[thread_id] == conn
+ @reserved_connections.delete thread_id
+ end
end
def new_connection
@@ -450,10 +449,14 @@ module ActiveRecord
end
def checkout_and_verify(c)
- c._run_checkout_callbacks do
+ c.run_callbacks :checkout do
c.verify!
end
c
+ rescue
+ remove c
+ c.disconnect!
+ raise
end
end
@@ -517,15 +520,7 @@ module ActiveRecord
def connection_pool_list
owner_to_pool.values.compact
end
-
- def connection_pools
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- In the next release, this will return the same as `#connection_pool_list`.
- (An array of pools, rather than a hash mapping specs to pools.)
- MSG
-
- Hash[connection_pool_list.map { |pool| [pool.spec, pool] }]
- end
+ alias :connection_pools :connection_pool_list
def establish_connection(owner, spec)
@class_to_pool.clear
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 1a3ed28d66..42c794c828 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -136,7 +136,7 @@ module ActiveRecord
#
# In order to get around this problem, #transaction will emulate the effect
# of nested transactions, by using savepoints:
- # http://dev.mysql.com/doc/refman/5.0/en/savepoint.html
+ # http://dev.mysql.com/doc/refman/5.6/en/savepoint.html
# Savepoints are supported by MySQL and PostgreSQL. SQLite3 version >= '3.6.8'
# supports savepoints.
#
@@ -189,7 +189,7 @@ module ActiveRecord
# semantics of these different levels:
#
# * http://www.postgresql.org/docs/9.1/static/transaction-iso.html
- # * https://dev.mysql.com/doc/refman/5.0/en/set-transaction.html
+ # * https://dev.mysql.com/doc/refman/5.6/en/set-transaction.html
#
# An <tt>ActiveRecord::TransactionIsolationError</tt> will be raised if:
#
@@ -201,16 +201,14 @@ module ActiveRecord
# isolation level. However, support is disabled for MySQL versions below 5,
# because they are affected by a bug[http://bugs.mysql.com/bug.php?id=39170]
# which means the isolation level gets persisted outside the transaction.
- def transaction(options = {})
- options.assert_valid_keys :requires_new, :joinable, :isolation
-
- if !options[:requires_new] && current_transaction.joinable?
- if options[:isolation]
+ def transaction(requires_new: nil, isolation: nil, joinable: true)
+ if !requires_new && current_transaction.joinable?
+ if isolation
raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
end
yield
else
- transaction_manager.within_new_transaction(options) { yield }
+ transaction_manager.within_new_transaction(isolation: isolation, joinable: joinable) { yield }
end
rescue ActiveRecord::Rollback
# rollbacks are silently swallowed
@@ -234,6 +232,10 @@ module ActiveRecord
current_transaction.add_record(record)
end
+ def transaction_state
+ current_transaction.state
+ end
+
# Begins the transaction (and turns off auto-committing).
def begin_db_transaction() end
@@ -258,7 +260,18 @@ module ActiveRecord
# Rolls back the transaction (and turns on auto-committing). Must be
# done if the transaction block raises an exception or returns false.
- def rollback_db_transaction() end
+ def rollback_db_transaction
+ exec_rollback_db_transaction
+ end
+
+ def exec_rollback_db_transaction() end #:nodoc:
+
+ def rollback_to_savepoint(name = nil)
+ exec_rollback_to_savepoint(name)
+ end
+
+ def exec_rollback_to_savepoint(name = nil) #:nodoc:
+ end
def default_sequence_name(table, column)
nil
@@ -274,10 +287,17 @@ module ActiveRecord
def insert_fixture(fixture, table_name)
columns = schema_cache.columns_hash(table_name)
- key_list = []
- value_list = fixture.map do |name, value|
- key_list << quote_column_name(name)
- quote(value, columns[name])
+ binds = fixture.map do |name, value|
+ type = lookup_cast_type_from_column(columns[name])
+ Relation::QueryAttribute.new(name, value, type)
+ end
+ key_list = fixture.keys.map { |name| quote_column_name(name) }
+ value_list = prepare_binds_for_database(binds).map do |value|
+ begin
+ quote(value)
+ rescue TypeError
+ quote(YAML.dump(value))
+ end
end
execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", 'Fixture Insert'
@@ -287,10 +307,6 @@ module ActiveRecord
"DEFAULT VALUES"
end
- def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
- "WHERE #{quoted_primary_key} IN (SELECT #{quoted_primary_key} FROM #{quoted_table_name} #{where_sql})"
- end
-
# Sanitizes the given LIMIT parameter in order to prevent SQL injection.
#
# The +limit+ may be anything that can evaluate to a string via #to_s. It
@@ -368,7 +384,7 @@ module ActiveRecord
def binds_from_relation(relation, binds)
if relation.is_a?(Relation) && binds.empty?
- relation, binds = relation.arel, relation.bind_values
+ relation, binds = relation.arel, relation.bound_attributes
end
[relation, binds]
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 4a4506c7f5..5e27cfe507 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -3,7 +3,7 @@ module ActiveRecord
module QueryCache
class << self
def included(base) #:nodoc:
- dirties_query_cache base, :insert, :update, :delete
+ dirties_query_cache base, :insert, :update, :delete, :rollback_to_savepoint, :rollback_db_transaction
end
def dirties_query_cache(base, *method_names)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 679878d860..91c7298983 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -10,7 +10,13 @@ module ActiveRecord
return value.quoted_id if value.respond_to?(:quoted_id)
if column
- value = column.cast_type.type_cast_for_database(value)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing a column to `quote` has been deprecated. It is only used
+ for type casting, which should be handled elsewhere. See
+ https://github.com/rails/arel/commit/6160bfbda1d1781c3b08a33ec4955f170e95be11
+ for more information.
+ MSG
+ value = type_cast_from_column(column, value)
end
_quote(value)
@@ -19,13 +25,13 @@ module ActiveRecord
# Cast a +value+ to a type that the database understands. For example,
# SQLite does not understand dates, so this method will convert a Date
# to a String.
- def type_cast(value, column)
+ def type_cast(value, column = nil)
if value.respond_to?(:quoted_id) && value.respond_to?(:id)
return value.id
end
if column
- value = column.cast_type.type_cast_for_database(value)
+ value = type_cast_from_column(column, value)
end
_type_cast(value)
@@ -34,10 +40,44 @@ module ActiveRecord
raise TypeError, "can't cast #{value.class}#{to_type}"
end
+ # If you are having to call this function, you are likely doing something
+ # wrong. The column does not have sufficient type information if the user
+ # provided a custom type on the class level either explicitly (via
+ # `attribute`) or implicitly (via `serialize`,
+ # `time_zone_aware_attributes`). In almost all cases, the sql type should
+ # only be used to change quoting behavior, when the primitive to
+ # represent the type doesn't sufficiently reflect the differences
+ # (varchar vs binary) for example. The type used to get this primitive
+ # should have been provided before reaching the connection adapter.
+ def type_cast_from_column(column, value) # :nodoc:
+ if column
+ type = lookup_cast_type_from_column(column)
+ type.serialize(value)
+ else
+ value
+ end
+ end
+
+ # See docs for +type_cast_from_column+
+ def lookup_cast_type_from_column(column) # :nodoc:
+ lookup_cast_type(column.sql_type)
+ end
+
+ def fetch_type_metadata(sql_type)
+ cast_type = lookup_cast_type(sql_type)
+ SqlTypeMetadata.new(
+ sql_type: sql_type,
+ type: cast_type.type,
+ limit: cast_type.limit,
+ precision: cast_type.precision,
+ scale: cast_type.scale,
+ )
+ end
+
# Quotes a string, escaping any ' (single quote) and \ (backslash)
# characters.
def quote_string(s)
- s.gsub(/\\/, '\&\&').gsub(/'/, "''") # ' (for ruby-mode)
+ s.gsub('\\'.freeze, '\&\&'.freeze).gsub("'".freeze, "''".freeze) # ' (for ruby-mode)
end
# Quotes the column name. Defaults to no quoting.
@@ -62,6 +102,11 @@ module ActiveRecord
quote_table_name("#{table}.#{attr}")
end
+ def quote_default_expression(value, column) #:nodoc:
+ value = lookup_cast_type(column.sql_type).serialize(value)
+ quote(value)
+ end
+
def quoted_true
"'t'"
end
@@ -87,7 +132,16 @@ module ActiveRecord
end
end
- value.to_s(:db)
+ result = value.to_s(:db)
+ if value.respond_to?(:usec) && value.usec > 0
+ "#{result}.#{sprintf("%06d", value.usec)}"
+ else
+ result
+ end
+ end
+
+ def prepare_binds_for_database(binds) # :nodoc:
+ binds.map(&:value_for_database)
end
private
@@ -109,8 +163,7 @@ module ActiveRecord
when Date, Time then "'#{quoted_date(value)}'"
when Symbol then "'#{quote_string(value.to_s)}'"
when Class then "'#{value}'"
- else
- "'#{quote_string(YAML.dump(value))}'"
+ else raise TypeError, "can't quote #{value.class.name}"
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb b/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb
index 25c17ce971..c0662f8473 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb
@@ -9,7 +9,7 @@ module ActiveRecord
execute("SAVEPOINT #{name}")
end
- def rollback_to_savepoint(name = current_savepoint_name)
+ def exec_rollback_to_savepoint(name = current_savepoint_name)
execute("ROLLBACK TO SAVEPOINT #{name}")
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
index 6bab260f5a..f754df93b6 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -15,11 +15,12 @@ module ActiveRecord
end
def visit_AddColumn(o)
- sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale)
- sql = "ADD #{quote_column_name(o.name)} #{sql_type}"
- add_column_options!(sql, column_options(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
+
private
def visit_AlterTable(o)
@@ -30,9 +31,9 @@ module ActiveRecord
end
def visit_ColumnDefinition(o)
- sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale)
- column_sql = "#{quote_column_name(o.name)} #{sql_type}"
- add_column_options!(column_sql, column_options(o)) unless o.primary_key?
+ o.sql_type ||= type_to_sql(o.type, o.limit, o.precision, o.scale)
+ column_sql = "#{quote_column_name(o.name)} #{o.sql_type}"
+ add_column_options!(column_sql, column_options(o)) unless o.type == :primary_key
column_sql
end
@@ -67,23 +68,13 @@ module ActiveRecord
column_options[:column] = o
column_options[:first] = o.first
column_options[:after] = o.after
+ column_options[:auto_increment] = o.auto_increment
+ column_options[:primary_key] = o.primary_key
column_options
end
- def quote_column_name(name)
- @conn.quote_column_name name
- end
-
- def quote_table_name(name)
- @conn.quote_table_name name
- end
-
- def type_to_sql(type, limit, precision, scale)
- @conn.type_to_sql type.to_sym, limit, precision, scale
- end
-
def add_column_options!(sql, options)
- sql << " DEFAULT #{quote_value(options[:default], options[:column])}" if options_include_default?(options)
+ sql << " DEFAULT #{quote_default_expression(options[:default], options[:column])}" if options_include_default?(options)
# must explicitly check for :null to allow change_column to work on migrations
if options[:null] == false
sql << " NOT NULL"
@@ -91,16 +82,12 @@ module ActiveRecord
if options[:auto_increment] == true
sql << " AUTO_INCREMENT"
end
+ if options[:primary_key] == true
+ sql << " PRIMARY KEY"
+ end
sql
end
- def quote_value(value, column)
- column.sql_type ||= type_to_sql(column.type, column.limit, column.precision, column.scale)
- column.cast_type ||= type_for_column(column)
-
- @conn.quote(value, column)
- end
-
def options_include_default?(options)
options.include?(:default) && !(options[:null] == false && options[:default].nil?)
end
@@ -117,10 +104,6 @@ module ActiveRecord
MSG
end
end
-
- def type_for_column(column)
- @conn.lookup_cast_type(column.sql_type)
- end
end
end
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 f8b6daea5a..cb83d0022c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -15,14 +15,14 @@ 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, :primary_key, :sql_type, :cast_type) #:nodoc:
+ class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :sql_type) #:nodoc:
def primary_key?
primary_key || type.to_sym == :primary_key
end
end
- class ChangeColumnDefinition < Struct.new(:column, :type, :options) #:nodoc:
+ class ChangeColumnDefinition < Struct.new(:column, :name) #:nodoc:
end
class ForeignKeyDefinition < Struct.new(:from_table, :to_table, :options) #:nodoc:
@@ -50,21 +50,130 @@ module ActiveRecord
options[:primary_key] != default_primary_key
end
+ def defined_for?(options_or_to_table = {})
+ if options_or_to_table.is_a?(Hash)
+ options_or_to_table.all? {|key, value| options[key].to_s == value.to_s }
+ else
+ to_table == options_or_to_table.to_s
+ end
+ end
+
private
def default_primary_key
"id"
end
end
- module TimestampDefaultDeprecation # :nodoc:
- def emit_warning_if_null_unspecified(options)
- return if options.key?(:null)
+ class ReferenceDefinition # :nodoc:
+ def initialize(
+ name,
+ polymorphic: false,
+ index: false,
+ foreign_key: false,
+ type: :integer,
+ **options
+ )
+ @name = name
+ @polymorphic = polymorphic
+ @index = index
+ @foreign_key = foreign_key
+ @type = type
+ @options = options
+
+ if polymorphic && foreign_key
+ raise ArgumentError, "Cannot add a foreign key to a polymorphic relation"
+ end
+ end
+
+ def add_to(table)
+ columns.each do |column_options|
+ table.column(*column_options)
+ end
+
+ if index
+ table.index(column_names, index_options)
+ end
+
+ if foreign_key
+ table.foreign_key(foreign_table_name, foreign_key_options)
+ end
+ end
+
+ protected
+
+ attr_reader :name, :polymorphic, :index, :foreign_key, :type, :options
+
+ private
+
+ def as_options(value, default = {})
+ if value.is_a?(Hash)
+ value
+ else
+ default
+ end
+ end
+
+ def polymorphic_options
+ as_options(polymorphic, options)
+ end
+
+ def index_options
+ as_options(index)
+ end
+
+ def foreign_key_options
+ as_options(foreign_key)
+ end
+
+ def columns
+ result = [["#{name}_id", type, options]]
+ if polymorphic
+ result.unshift(["#{name}_type", :string, polymorphic_options])
+ end
+ result
+ end
+
+ def column_names
+ columns.map(&:first)
+ end
+
+ def foreign_table_name
+ Base.pluralize_table_names ? name.to_s.pluralize : name
+ end
+ end
+
+ module ColumnMethods
+ # Appends a primary key definition to the table definition.
+ # Can be called multiple times, but this is probably not a good idea.
+ def primary_key(name, type = :primary_key, **options)
+ column(name, type, options.merge(primary_key: true))
+ end
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- `#timestamp` was called without specifying an option for `null`. In Rails 5,
- this behavior will change to `null: false`. You should manually specify
- `null: true` to prevent the behavior of your existing migrations from changing.
- MSG
+ # Appends a column or columns of a specified type.
+ #
+ # t.string(:goat)
+ # t.string(:goat, :sheep)
+ #
+ # See TableDefinition#column
+ [
+ :bigint,
+ :binary,
+ :boolean,
+ :date,
+ :datetime,
+ :decimal,
+ :float,
+ :integer,
+ :string,
+ :text,
+ :time,
+ :timestamp,
+ ].each do |column_type|
+ module_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{column_type}(*args, **options)
+ args.each { |name| column(name, :#{column_type}, options) }
+ end
+ CODE
end
end
@@ -89,16 +198,17 @@ module ActiveRecord
# The table definitions
# The Columns are stored as a ColumnDefinition in the +columns+ attribute.
class TableDefinition
- include TimestampDefaultDeprecation
+ include ColumnMethods
# An array of ColumnDefinition objects, representing the column changes
# that have been defined.
attr_accessor :indexes
- attr_reader :name, :temporary, :options, :as
+ attr_reader :name, :temporary, :options, :as, :foreign_keys
def initialize(types, name, temporary, options, as = nil)
@columns_hash = {}
@indexes = {}
+ @foreign_keys = {}
@native = types
@temporary = temporary
@options = options
@@ -108,12 +218,6 @@ module ActiveRecord
def columns; @columns_hash.values; end
- # Appends a primary key definition to the table definition.
- # Can be called multiple times, but this is probably not a good idea.
- def primary_key(name, type = :primary_key, options = {})
- column(name, type, options.merge(:primary_key => true))
- end
-
# Returns a ColumnDefinition for the column with name +name+.
def [](name)
@columns_hash[name.to_s]
@@ -124,8 +228,8 @@ module ActiveRecord
# 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>:datetime</tt>, <tt>:timestamp</tt>, <tt>:time</tt>,
- # <tt>:date</tt>, <tt>:binary</tt>, <tt>:boolean</tt>.
+ # <tt>:datetime</tt>, <tt>:time</tt>, <tt>:date</tt>,
+ # <tt>:binary</tt>, <tt>:boolean</tt>.
#
# You may use a type not in this list as long as it is supported by your
# database (for example, "polygon" in MySQL), but this will not be database
@@ -226,7 +330,7 @@ module ActiveRecord
# t.integer :shop_id, :creator_id
# t.string :item_number, index: true
# t.string :name, :value, default: "Untitled"
- # t.timestamps
+ # t.timestamps null: false
# end
#
# There's a short-hand method for each of the type values declared at the top. And then there's
@@ -270,14 +374,6 @@ module ActiveRecord
@columns_hash.delete name.to_s
end
- [:string, :text, :integer, :bigint, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type|
- define_method column_type do |*args|
- options = args.extract_options!
- column_names = args
- column_names.each { |name| column(name, column_type, options) }
- end
- end
-
# Adds index options to the indexes hash, keyed by column name
# This is primarily used to track indexes that need to be created after the table
#
@@ -286,33 +382,37 @@ module ActiveRecord
indexes[column_name] = options
end
+ def foreign_key(table_name, options = {}) # :nodoc:
+ foreign_keys[table_name] = options
+ end
+
# Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
- # <tt>:updated_at</tt> to the table.
+ # <tt>:updated_at</tt> to the table. See SchemaStatements#add_timestamps
+ #
+ # t.timestamps null: false
def timestamps(*args)
options = args.extract_options!
- emit_warning_if_null_unspecified(options)
+
+ options[:null] = false if options[:null].nil?
+
column(:created_at, :datetime, options)
column(:updated_at, :datetime, options)
end
- # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
- # <tt>references</tt> and <tt>belongs_to</tt> are acceptable. The reference column will be an +integer+
- # by default, the <tt>:type</tt> option can be used to specify a different type.
+ # 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.
#
# t.references(:user)
# t.references(:user, type: "string")
# t.belongs_to(:supplier, polymorphic: true)
#
# See SchemaStatements#add_reference
- def references(*args)
- options = args.extract_options!
- polymorphic = options.delete(:polymorphic)
- index_options = options.delete(:index)
- type = options.delete(:type) || :integer
+ def references(*args, **options)
args.each do |col|
- column("#{col}_id", type, options)
- column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
- index(polymorphic ? %w(type id).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
+ ReferenceDefinition.new(col, **options).add_to(self)
end
end
alias :belongs_to :references
@@ -331,6 +431,7 @@ module ActiveRecord
column.null = options[:null]
column.first = options[:first]
column.after = options[:after]
+ column.auto_increment = options[:auto_increment]
column.primary_key = type == :primary_key || options[:primary_key]
column
end
@@ -384,6 +485,7 @@ module ActiveRecord
# Available transformations are:
#
# change_table :table do |t|
+ # t.primary_key
# t.column
# t.index
# t.rename_index
@@ -396,6 +498,7 @@ module ActiveRecord
# t.string
# t.text
# t.integer
+ # t.bigint
# t.float
# t.decimal
# t.datetime
@@ -412,132 +515,151 @@ module ActiveRecord
# end
#
class Table
- include TimestampDefaultDeprecation
+ include ColumnMethods
+
+ attr_reader :name
def initialize(table_name, base)
- @table_name = table_name
+ @name = table_name
@base = base
end
# Adds a new column to the named table.
- # See TableDefinition#column for details of the options you can use.
#
- # ====== Creating a simple column
# t.column(:name, :string)
+ #
+ # See TableDefinition#column for details of the options you can use.
def column(column_name, type, options = {})
- @base.add_column(@table_name, column_name, type, options)
+ @base.add_column(name, column_name, type, options)
end
- # Checks to see if a column exists. See SchemaStatements#column_exists?
+ # Checks to see if a column exists.
+ #
+ # t.string(:name) unless t.column_exists?(:name, :string)
+ #
+ # See SchemaStatements#column_exists?
def column_exists?(column_name, type = nil, options = {})
- @base.column_exists?(@table_name, column_name, type, options)
+ @base.column_exists?(name, column_name, type, options)
end
# Adds a new index to the table. +column_name+ can be a single Symbol, or
- # an Array of Symbols. See SchemaStatements#add_index
+ # an Array of Symbols.
#
- # ====== Creating a simple index
# t.index(:name)
- # ====== Creating a unique index
# t.index([:branch_id, :party_id], unique: true)
- # ====== Creating a named index
# t.index([:branch_id, :party_id], unique: true, name: 'by_branch_party')
+ #
+ # See SchemaStatements#add_index for details of the options you can use.
def index(column_name, options = {})
- @base.add_index(@table_name, column_name, options)
+ @base.add_index(name, column_name, options)
end
- # Checks to see if an index exists. See SchemaStatements#index_exists?
+ # Checks to see if an index exists.
+ #
+ # unless t.index_exists?(:branch_id)
+ # t.index(:branch_id)
+ # end
+ #
+ # See SchemaStatements#index_exists?
def index_exists?(column_name, options = {})
- @base.index_exists?(@table_name, column_name, options)
+ @base.index_exists?(name, column_name, options)
end
# Renames the given index on the table.
#
# t.rename_index(:user_id, :account_id)
+ #
+ # See SchemaStatements#rename_index
def rename_index(index_name, new_index_name)
- @base.rename_index(@table_name, index_name, new_index_name)
+ @base.rename_index(name, index_name, new_index_name)
end
- # Adds timestamps (+created_at+ and +updated_at+) columns to the table. See SchemaStatements#add_timestamps
+ # Adds timestamps (+created_at+ and +updated_at+) columns to the table.
+ #
+ # t.timestamps(null: false)
#
- # t.timestamps
+ # See SchemaStatements#add_timestamps
def timestamps(options = {})
- emit_warning_if_null_unspecified(options)
- @base.add_timestamps(@table_name, options)
+ @base.add_timestamps(name, options)
end
# Changes the column's definition according to the new options.
- # See TableDefinition#column for details of the options you can use.
#
# t.change(:name, :string, limit: 80)
# t.change(:description, :text)
+ #
+ # See TableDefinition#column for details of the options you can use.
def change(column_name, type, options = {})
- @base.change_column(@table_name, column_name, type, options)
+ @base.change_column(name, column_name, type, options)
end
- # Sets a new default value for a column. See SchemaStatements#change_column_default
+ # Sets a new default value for a column.
#
# t.change_default(:qualification, 'new')
# t.change_default(:authorized, 1)
+ #
+ # See SchemaStatements#change_column_default
def change_default(column_name, default)
- @base.change_column_default(@table_name, column_name, default)
+ @base.change_column_default(name, column_name, default)
end
# Removes the column(s) from the table definition.
#
# t.remove(:qualification)
# t.remove(:qualification, :experience)
+ #
+ # See SchemaStatements#remove_columns
def remove(*column_names)
- @base.remove_columns(@table_name, *column_names)
+ @base.remove_columns(name, *column_names)
end
# Removes the given index from the table.
#
- # ====== Remove the index_table_name_on_column in the table_name table
- # t.remove_index :column
- # ====== Remove the index named index_table_name_on_branch_id in the table_name table
- # t.remove_index column: :branch_id
- # ====== Remove the index named index_table_name_on_branch_id_and_party_id in the table_name table
- # t.remove_index column: [:branch_id, :party_id]
- # ====== Remove the index named by_branch_party in the table_name table
- # t.remove_index name: :by_branch_party
+ # t.remove_index(:branch_id)
+ # t.remove_index(column: [:branch_id, :party_id])
+ # t.remove_index(name: :by_branch_party)
+ #
+ # See SchemaStatements#remove_index
def remove_index(options = {})
- @base.remove_index(@table_name, options)
+ @base.remove_index(name, options)
end
# Removes the timestamp columns (+created_at+ and +updated_at+) from the table.
#
# t.remove_timestamps
- def remove_timestamps
- @base.remove_timestamps(@table_name)
+ #
+ # See SchemaStatements#remove_timestamps
+ def remove_timestamps(options = {})
+ @base.remove_timestamps(name, options)
end
# Renames a column.
#
# t.rename(:description, :name)
+ #
+ # See SchemaStatements#rename_column
def rename(column_name, new_column_name)
- @base.rename_column(@table_name, column_name, new_column_name)
+ @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.
- # <tt>references</tt> and <tt>belongs_to</tt> are acceptable. The reference column will be an +integer+
- # by default, the <tt>:type</tt> option can be used to specify a different type.
+ # Adds a reference. Optionally adds a +type+ column, if
+ # <tt>:polymorphic</tt> option is provided.
#
# 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
def references(*args)
options = args.extract_options!
args.each do |ref_name|
- @base.add_reference(@table_name, ref_name, options)
+ @base.add_reference(name, ref_name, options)
end
end
alias :belongs_to :references
# Removes a reference. Optionally removes a +type+ column.
- # <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable.
#
# t.remove_references(:user)
# t.remove_belongs_to(:supplier, polymorphic: true)
@@ -546,22 +668,27 @@ module ActiveRecord
def remove_references(*args)
options = args.extract_options!
args.each do |ref_name|
- @base.remove_reference(@table_name, ref_name, options)
+ @base.remove_reference(name, ref_name, options)
end
end
alias :remove_belongs_to :remove_references
- # Adds a column or columns of a specified type
+ # Adds a foreign key.
#
- # t.string(:goat)
- # t.string(:goat, :sheep)
- [:string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type|
- define_method column_type do |*args|
- options = args.extract_options!
- args.each do |name|
- @base.add_column(@table_name, name, column_type, options)
- end
- end
+ # t.foreign_key(:authors)
+ #
+ # See SchemaStatements#add_foreign_key
+ def foreign_key(*args) # :nodoc:
+ @base.add_foreign_key(name, *args)
+ end
+
+ # Checks to see if a foreign key exists.
+ #
+ # t.foreign_key(:authors) unless t.foreign_key_exists?(:authors)
+ #
+ # See SchemaStatements#foreign_key_exists?
+ def foreign_key_exists?(*args) # :nodoc:
+ @base.foreign_key_exists?(name, *args)
end
private
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 6eab11b88b..999cb0ec5a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
@@ -6,22 +6,28 @@ module ActiveRecord
# We can then redefine how certain data types may be handled in the schema dumper on the
# Adapter level by over-writing this code inside the database specific adapters
module ColumnDumper
- def column_spec(column, types)
- spec = prepare_column_options(column, types)
+ def column_spec(column)
+ spec = prepare_column_options(column)
(spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k}: ")}
spec
end
+ def column_spec_for_primary_key(column)
+ return if column.type == :integer
+ spec = { id: column.type.inspect }
+ spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type].include?(key) })
+ end
+
# This can be overridden on a Adapter level basis to support other
# extended datatypes (Example: Adding an array option in the
# PostgreSQLAdapter)
- def prepare_column_options(column, types)
+ def prepare_column_options(column)
spec = {}
spec[:name] = column.name.inspect
- spec[:type] = column.type.to_s
+ spec[:type] = schema_type(column)
spec[:null] = 'false' unless column.null
- limit = column.limit || types[column.type][:limit]
+ 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
@@ -39,10 +45,15 @@ module ActiveRecord
private
+ def schema_type(column)
+ column.type.to_s
+ end
+
def schema_default(column)
- default = column.type_cast_from_database(column.default)
+ type = lookup_cast_type_from_column(column)
+ default = type.deserialize(column.default)
unless default.nil?
- column.type_cast_for_schema(default)
+ type.type_cast_for_schema(default)
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 cbf87df356..f0909aabb5 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -1,4 +1,6 @@
require 'active_record/migration/join_table'
+require 'active_support/core_ext/string/access'
+require 'digest'
module ActiveRecord
module ConnectionAdapters # :nodoc:
@@ -132,6 +134,7 @@ module ActiveRecord
# Make a temporary table.
# [<tt>:force</tt>]
# Set to true to drop the table before creating it.
+ # Set to +:cascade+ to drop dependent objects as well.
# Defaults to false.
# [<tt>:as</tt>]
# SQL to use to generate the table. When this option is used, the block is
@@ -203,7 +206,17 @@ module ActiveRecord
end
result = execute schema_creation.accept td
- td.indexes.each_pair { |c, o| add_index(table_name, c, o) } unless supports_indexes_in_create?
+
+ unless supports_indexes_in_create?
+ td.indexes.each_pair do |column_name, index_options|
+ add_index(table_name, column_name, index_options)
+ end
+ end
+
+ td.foreign_keys.each_pair do |other_table_name, foreign_key_options|
+ add_foreign_key(table_name, other_table_name, foreign_key_options)
+ end
+
result
end
@@ -361,11 +374,18 @@ module ActiveRecord
# Drops a table from the database.
#
- # Although this command ignores +options+ and the block if one is given, it can be helpful
- # to provide these in a migration's +change+ method so it can be reverted.
+ # [<tt>:force</tt>]
+ # Set to +:cascade+ to drop dependent objects as well.
+ # Defaults to false.
+ # [<tt>:if_exists</tt>]
+ # Set to +true+ to only drop the table if it exists.
+ # Defaults to false.
+ #
+ # Although this command ignores most +options+ and the block if one is given,
+ # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
# In that case, +options+ and the block will be used by create_table.
def drop_table(table_name, options = {})
- execute "DROP TABLE #{quote_table_name(table_name)}"
+ execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
end
# Adds a new column to the named table.
@@ -571,6 +591,8 @@ module ActiveRecord
# rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
#
def rename_index(table_name, old_name, new_name)
+ validate_index_length!(table_name, new_name)
+
# this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance)
old_index_def = indexes(table_name).detect { |i| i.name == old_name }
return unless old_index_def
@@ -619,22 +641,21 @@ module ActiveRecord
#
# add_belongs_to(:products, :supplier, polymorphic: true)
#
- # ====== Create a supplier_id, supplier_type columns and appropriate index
+ # ====== Create supplier_id, supplier_type columns and appropriate index
#
# add_reference(:products, :supplier, polymorphic: true, index: true)
#
- def add_reference(table_name, ref_name, options = {})
- polymorphic = options.delete(:polymorphic)
- index_options = options.delete(:index)
- type = options.delete(:type) || :integer
- add_column(table_name, "#{ref_name}_id", type, options)
- add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
- add_index(table_name, polymorphic ? %w[type id].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
+ # ====== Create a supplier_id column and appropriate foreign key
+ #
+ # add_reference(:products, :supplier, foreign_key: true)
+ #
+ def add_reference(table_name, *args)
+ ReferenceDefinition.new(*args).add_to(update_table_definition(table_name, self))
end
alias :add_belongs_to :add_reference
# Removes the reference(s). Also removes a +type+ column if one exists.
- # <tt>remove_reference</tt>, <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable.
+ # <tt>remove_reference</tt> and <tt>remove_belongs_to</tt> are acceptable.
#
# ====== Remove the reference
#
@@ -644,7 +665,16 @@ module ActiveRecord
#
# remove_reference(:products, :supplier, polymorphic: true)
#
+ # ====== Remove the reference with a foreign key
+ #
+ # remove_reference(:products, :user, index: true, foreign_key: true)
+ #
def remove_reference(table_name, ref_name, options = {})
+ if options[:foreign_key]
+ reference_name = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
+ remove_foreign_key(table_name, reference_name)
+ end
+
remove_column(table_name, "#{ref_name}_id")
remove_column(table_name, "#{ref_name}_type") if options[:polymorphic]
end
@@ -660,8 +690,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
#
@@ -733,21 +763,7 @@ module ActiveRecord
def remove_foreign_key(from_table, options_or_to_table = {})
return unless supports_foreign_keys?
- if options_or_to_table.is_a?(Hash)
- options = options_or_to_table
- else
- options = { column: foreign_key_column_for(options_or_to_table) }
- end
-
- fk_name_to_delete = options.fetch(:name) do
- fk_to_delete = foreign_keys(from_table).detect {|fk| fk.column == options[:column] }
-
- if fk_to_delete
- fk_to_delete.name
- else
- raise ArgumentError, "Table '#{from_table}' has no foreign key on column '#{options[:column]}'"
- end
- end
+ fk_name_to_delete = foreign_key_for!(from_table, options_or_to_table).name
at = create_alter_table from_table
at.drop_foreign_key fk_name_to_delete
@@ -755,6 +771,31 @@ module ActiveRecord
execute schema_creation.accept(at)
end
+ # Checks to see if a foreign key exists on a table for a given foreign key definition.
+ #
+ # # Check a foreign key exists
+ # foreign_key_exists?(:accounts, :branches)
+ #
+ # # Check a foreign key on a specified column exists
+ # foreign_key_exists?(:accounts, column: :owner_id)
+ #
+ # # Check a foreign key with a custom name exists
+ # foreign_key_exists?(:accounts, name: "special_fk_name")
+ #
+ def foreign_key_exists?(from_table, options_or_to_table = {})
+ foreign_key_for(from_table, options_or_to_table).present?
+ end
+
+ def foreign_key_for(from_table, options_or_to_table = {}) # :nodoc:
+ return unless supports_foreign_keys?
+ foreign_keys(from_table).detect {|fk| fk.defined_for? options_or_to_table }
+ end
+
+ def foreign_key_for!(from_table, options_or_to_table = {}) # :nodoc:
+ foreign_key_for(from_table, options_or_to_table) or \
+ raise ArgumentError, "Table '#{from_table}' has no foreign key for #{options_or_to_table}"
+ end
+
def foreign_key_column_for(table_name) # :nodoc:
"#{table_name.to_s.singularize}_id"
end
@@ -778,7 +819,7 @@ module ActiveRecord
version = version.to_i
sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
- migrated = select_values("SELECT version FROM #{sm_table}").map { |v| v.to_i }
+ migrated = select_values("SELECT version FROM #{sm_table}").map(&:to_i)
paths = migrations_paths.map {|p| "#{p}/[0-9]*_*.rb" }
versions = Dir[*paths].map do |filename|
filename.split('/').last.split('_').first.to_i
@@ -816,6 +857,12 @@ module ActiveRecord
raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified"
end
+ elsif [:datetime, :time].include?(type) && precision ||= native[:precision]
+ if (0..6) === precision
+ column_type_sql << "(#{precision})"
+ else
+ raise(ActiveRecordError, "No #{native[:name]} type has precision of #{precision}. The allowed range of precision is from 0 to 6")
+ end
elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
column_type_sql << "(#{limit})"
end
@@ -835,11 +882,14 @@ module ActiveRecord
columns
end
- # Adds timestamps (+created_at+ and +updated_at+) columns to the named table.
+ # Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+.
+ # Additional options (like <tt>null: false</tt>) are forwarded to #add_column.
#
- # add_timestamps(:suppliers)
+ # add_timestamps(:suppliers, null: false)
#
def add_timestamps(table_name, options = {})
+ options[:null] = false if options[:null].nil?
+
add_column table_name, :created_at, :datetime, options
add_column table_name, :updated_at, :datetime, options
end
@@ -848,7 +898,7 @@ module ActiveRecord
#
# remove_timestamps(:suppliers)
#
- def remove_timestamps(table_name)
+ def remove_timestamps(table_name, options = {})
remove_column table_name, :updated_at
remove_column table_name, :created_at
end
@@ -962,17 +1012,25 @@ module ActiveRecord
end
private
- def create_table_definition(name, temporary, options, as = nil)
+ def create_table_definition(name, temporary = false, options = nil, as = nil)
TableDefinition.new native_database_types, name, temporary, options, as
end
def create_alter_table(name)
- AlterTable.new create_table_definition(name, false, {})
+ AlterTable.new create_table_definition(name)
end
def foreign_key_name(table_name, options) # :nodoc:
+ identifier = "#{table_name}_#{options.fetch(:column)}_fk"
+ hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
options.fetch(:name) do
- "fk_rails_#{SecureRandom.hex(5)}"
+ "fk_rails_#{hashed_identifier}"
+ end
+ end
+
+ def validate_index_length!(table_name, new_name)
+ 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
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index fd666c8c39..295a7bed87 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -1,13 +1,10 @@
module ActiveRecord
module ConnectionAdapters
class TransactionState
- attr_reader :parent
-
VALID_STATES = Set.new([:committed, :rolledback, nil])
def initialize(state = nil)
@state = state
- @parent = nil
end
def finalized?
@@ -27,7 +24,7 @@ module ActiveRecord
end
def set_state(state)
- if !VALID_STATES.include?(state)
+ unless VALID_STATES.include?(state)
raise ArgumentError, "Invalid transaction state: #{state}"
end
@state = state
@@ -47,19 +44,16 @@ module ActiveRecord
attr_reader :connection, :state, :records, :savepoint_name
attr_writer :joinable
- def initialize(connection, options)
+ def initialize(connection, options, run_commit_callbacks: false)
@connection = connection
@state = TransactionState.new
@records = []
@joinable = options.fetch(:joinable, true)
+ @run_commit_callbacks = run_commit_callbacks
end
def add_record(record)
- if record.has_transactional_callbacks?
- records << record
- else
- record.set_transaction_state(@state)
- end
+ records << record
end
def rollback
@@ -69,16 +63,11 @@ module ActiveRecord
def rollback_records
ite = records.uniq
while record = ite.shift
- begin
- record.rolledback! full_rollback?
- rescue => e
- raise if ActiveRecord::Base.raise_in_transactional_callbacks
- record.logger.error(e) if record.respond_to?(:logger) && record.logger
- end
+ record.rolledback!(force_restore_state: full_rollback?)
end
ensure
ite.each do |i|
- i.rolledback!(full_rollback?, false)
+ i.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: false)
end
end
@@ -86,20 +75,22 @@ module ActiveRecord
@state.set_state(:committed)
end
+ def before_commit_records
+ records.uniq.each(&:before_committed!) if @run_commit_callbacks
+ end
+
def commit_records
ite = records.uniq
while record = ite.shift
- begin
+ if @run_commit_callbacks
record.committed!
- rescue => e
- raise if ActiveRecord::Base.raise_in_transactional_callbacks
- record.logger.error(e) if record.respond_to?(:logger) && record.logger
+ else
+ # if not running callbacks, only adds the record to the parent transaction
+ record.add_to_transaction
end
end
ensure
- ite.each do |i|
- i.committed!(false)
- end
+ ite.each { |i| i.committed!(should_run_callbacks: false) }
end
def full_rollback?; true; end
@@ -110,8 +101,8 @@ module ActiveRecord
class SavepointTransaction < Transaction
- def initialize(connection, savepoint_name, options)
- super(connection, options)
+ def initialize(connection, savepoint_name, options, *args)
+ super(connection, options, *args)
if options[:isolation]
raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
end
@@ -121,14 +112,11 @@ module ActiveRecord
def rollback
connection.rollback_to_savepoint(savepoint_name)
super
- rollback_records
end
def commit
connection.release_savepoint(savepoint_name)
super
- parent = connection.transaction_manager.current_transaction
- records.each { |r| parent.add_record(r) }
end
def full_rollback?; false; end
@@ -136,7 +124,7 @@ module ActiveRecord
class RealTransaction < Transaction
- def initialize(connection, options)
+ def initialize(connection, options, *args)
super
if options[:isolation]
connection.begin_isolated_db_transaction(options[:isolation])
@@ -148,13 +136,11 @@ module ActiveRecord
def rollback
connection.rollback_db_transaction
super
- rollback_records
end
def commit
connection.commit_db_transaction
super
- commit_records
end
end
@@ -165,22 +151,31 @@ module ActiveRecord
end
def begin_transaction(options = {})
+ run_commit_callbacks = !current_transaction.joinable?
transaction =
if @stack.empty?
- RealTransaction.new(@connection, options)
+ RealTransaction.new(@connection, options, run_commit_callbacks: run_commit_callbacks)
else
- SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options)
+ SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options,
+ run_commit_callbacks: run_commit_callbacks)
end
+
@stack.push(transaction)
transaction
end
def commit_transaction
- @stack.pop.commit
+ transaction = @stack.last
+ transaction.before_commit_records
+ @stack.pop
+ transaction.commit
+ transaction.commit_records
end
- def rollback_transaction
- @stack.pop.rollback
+ def rollback_transaction(transaction = nil)
+ transaction ||= @stack.pop
+ transaction.rollback
+ transaction.rollback_records
end
def within_new_transaction(options = {})
@@ -192,12 +187,12 @@ module ActiveRecord
ensure
unless error
if Thread.current.status == 'aborting'
- rollback_transaction
+ rollback_transaction if transaction
else
begin
commit_transaction
rescue Exception
- transaction.rollback unless transaction.state.completed?
+ rollback_transaction(transaction) unless transaction.state.completed?
raise
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 582dd360f0..ae42e8ef8d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -4,6 +4,7 @@ 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'
@@ -21,6 +22,7 @@ module ActiveRecord
autoload :IndexDefinition
autoload :ColumnDefinition
autoload :ChangeColumnDefinition
+ autoload :ForeignKeyDefinition
autoload :TableDefinition
autoload :Table
autoload :AlterTable
@@ -112,7 +114,8 @@ module ActiveRecord
class BindCollector < Arel::Collectors::Bind
def compile(bvs, conn)
- super(bvs.map { |bv| conn.quote(*bv.reverse) })
+ casted_binds = conn.prepare_binds_for_database(bvs)
+ super(casted_binds.map { |value| conn.quote(value) })
end
end
@@ -242,6 +245,11 @@ module ActiveRecord
false
end
+ # Does this adapter support datetime with precision?
+ def supports_datetime_with_precision?
+ false
+ end
+
# This is meant to be implemented by the adapters that support extensions
def disable_extension(name)
end
@@ -260,12 +268,10 @@ module ActiveRecord
{}
end
- # QUOTING ==================================================
-
- # Returns a bind substitution value given a bind +index+ and +column+
+ # Returns a bind substitution value given a bind +column+
# NOTE: The column param is currently being used by the sqlserver-adapter
- def substitute_at(column, index = 0)
- Arel::Nodes::BindParam.new '?'
+ def substitute_at(column, _unused = 0)
+ Arel::Nodes::BindParam.new
end
# REFERENTIAL INTEGRITY ====================================
@@ -340,9 +346,6 @@ module ActiveRecord
def create_savepoint(name = nil)
end
- def rollback_to_savepoint(name = nil)
- end
-
def release_savepoint(name = nil)
end
@@ -357,9 +360,18 @@ module ActiveRecord
end
def case_insensitive_comparison(table, attribute, column, value)
- table[attribute].lower.eq(table.lower(value))
+ if can_perform_case_insensitive_comparison_for?(column)
+ table[attribute].lower.eq(table.lower(value))
+ else
+ case_sensitive_comparison(table, attribute, column, value)
+ end
end
+ def can_perform_case_insensitive_comparison_for?(column)
+ true
+ end
+ private :can_perform_case_insensitive_comparison_for?
+
def current_savepoint_name
current_transaction.savepoint_name
end
@@ -375,8 +387,8 @@ module ActiveRecord
end
end
- def new_column(name, default, cast_type, sql_type = nil, null = true)
- Column.new(name, default, cast_type, sql_type, null)
+ def new_column(name, default, sql_type_metadata = nil, null = true)
+ Column.new(name, default, sql_type_metadata, null)
end
def lookup_cast_type(sql_type) # :nodoc:
@@ -384,21 +396,21 @@ module ActiveRecord
end
def column_name_for_operation(operation, node) # :nodoc:
- node.to_sql
+ visitor.accept(node, collector).value
end
protected
def initialize_type_map(m) # :nodoc:
- register_class_with_limit m, %r(boolean)i, Type::Boolean
- register_class_with_limit m, %r(char)i, Type::String
- register_class_with_limit m, %r(binary)i, Type::Binary
- register_class_with_limit m, %r(text)i, Type::Text
- register_class_with_limit m, %r(date)i, Type::Date
- register_class_with_limit m, %r(time)i, Type::Time
- register_class_with_limit m, %r(datetime)i, Type::DateTime
- register_class_with_limit m, %r(float)i, Type::Float
- register_class_with_limit m, %r(int)i, Type::Integer
+ register_class_with_limit m, %r(boolean)i, Type::Boolean
+ register_class_with_limit m, %r(char)i, Type::String
+ register_class_with_limit m, %r(binary)i, Type::Binary
+ register_class_with_limit m, %r(text)i, Type::Text
+ register_class_with_precision m, %r(date)i, Type::Date
+ register_class_with_precision m, %r(time)i, Type::Time
+ register_class_with_precision m, %r(datetime)i, Type::DateTime
+ register_class_with_limit m, %r(float)i, Type::Float
+ register_class_with_limit m, %r(int)i, Type::Integer
m.alias_type %r(blob)i, 'binary'
m.alias_type %r(clob)i, 'text'
@@ -432,6 +444,13 @@ module ActiveRecord
end
end
+ def register_class_with_precision(mapping, key, klass) # :nodoc:
+ mapping.register_type(key) do |*args|
+ precision = extract_precision(args.last)
+ klass.new(precision: precision)
+ end
+ end
+
def extract_scale(sql_type) # :nodoc:
case sql_type
when /\((\d+)\)/ then 0
@@ -444,12 +463,21 @@ module ActiveRecord
end
def extract_limit(sql_type) # :nodoc:
- $1.to_i if sql_type =~ /\((.*)\)/
+ case sql_type
+ when /^bigint/i
+ 8
+ when /\((.*)\)/
+ $1.to_i
+ end
end
def translate_exception_class(e, sql)
- message = "#{e.class.name}: #{e.message}: #{sql}"
- @logger.error message if @logger
+ begin
+ message = "#{e.class.name}: #{e.message}: #{sql}"
+ rescue Encoding::CompatibilityError
+ message = "#{e.class.name}: #{e.message.force_encoding sql.encoding}: #{sql}"
+ end
+
exception = translate_exception(e, message)
exception.set_backtrace e.backtrace
exception
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 7f04e35042..76aee452ca 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -6,6 +6,43 @@ module ActiveRecord
class AbstractMysqlAdapter < AbstractAdapter
include Savepoints
+ module ColumnMethods
+ def primary_key(name, type = :primary_key, **options)
+ options[:auto_increment] = true if type == :bigint
+ super
+ end
+ end
+
+ class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
+ attr_accessor :charset, :collation
+ end
+
+ class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
+ include ColumnMethods
+
+ def new_column_definition(name, type, options) # :nodoc:
+ column = super
+ case column.type
+ when :primary_key
+ column.type = :integer
+ column.auto_increment = true
+ end
+ column.charset = options[:charset]
+ column.collation = options[:collation]
+ column
+ end
+
+ private
+
+ def create_column_definition(name, type)
+ ColumnDefinition.new(name, type)
+ end
+ end
+
+ class Table < ActiveRecord::ConnectionAdapters::Table
+ include ColumnMethods
+ end
+
class SchemaCreation < AbstractAdapter::SchemaCreation
def visit_AddColumn(o)
add_column_position!(super, column_options(o))
@@ -31,12 +68,25 @@ module ActiveRecord
end
def visit_ChangeColumnDefinition(o)
- column = o.column
- options = o.options
- sql_type = type_to_sql(o.type, options[:limit], options[:precision], options[:scale])
- change_column_sql = "CHANGE #{quote_column_name(column.name)} #{quote_column_name(options[:name])} #{sql_type}"
- add_column_options!(change_column_sql, options.merge(column: column))
- add_column_position!(change_column_sql, options)
+ 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[:collation] = o.collation
+ 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)
@@ -54,18 +104,47 @@ module ActiveRecord
end
end
+ def update_table_definition(table_name, base) # :nodoc:
+ Table.new(table_name, base)
+ end
+
def schema_creation
SchemaCreation.new self
end
+ def column_spec_for_primary_key(column)
+ spec = {}
+ if column.auto_increment?
+ spec[:id] = ':bigint' if column.bigint?
+ return if spec.empty?
+ else
+ spec[:id] = column.type.inspect
+ spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) })
+ end
+ 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
+ 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"]
+ spec[:collation] = column.collation.inspect if column.collation != @collation_cache[table_name]
+ end
+ spec
+ end
+
+ def migration_keys
+ super + [:collation]
+ end
+
class Column < ConnectionAdapters::Column # :nodoc:
- attr_reader :collation, :strict, :extra
+ delegate :strict, :collation, :extra, to: :sql_type_metadata, allow_nil: true
- def initialize(name, default, cast_type, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
- @strict = strict
- @collation = collation
- @extra = extra
- super(name, default, cast_type, sql_type, null)
+ def initialize(*)
+ super
assert_valid_default(default)
extract_default
end
@@ -73,7 +152,7 @@ module ActiveRecord
def extract_default
if blob_or_text_column?
@default = null || strict ? nil : ''
- elsif missing_default_forged_as_empty_string?(@default)
+ elsif missing_default_forged_as_empty_string?(default)
@default = nil
end
end
@@ -91,11 +170,8 @@ module ActiveRecord
collation && !collation.match(/_ci$/)
end
- def ==(other)
- super &&
- collation == other.collation &&
- strict == other.strict &&
- extra == other.extra
+ def auto_increment?
+ extra == 'auto_increment'
end
private
@@ -116,9 +192,33 @@ module ActiveRecord
raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
end
end
+ end
+
+ class MysqlTypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
+ attr_reader :collation, :extra, :strict
+
+ def initialize(type_metadata, collation: "", extra: "", strict: false)
+ super(type_metadata)
+ @type_metadata = type_metadata
+ @collation = collation
+ @extra = extra
+ @strict = strict
+ end
+
+ def ==(other)
+ other.is_a?(MysqlTypeMetadata) &&
+ attributes_for_hash == other.attributes_for_hash
+ end
+ alias eql? ==
+
+ def hash
+ attributes_for_hash.hash
+ end
+
+ protected
def attributes_for_hash
- super + [collation, strict, extra]
+ [self.class, @type_metadata, collation, extra, strict]
end
end
@@ -173,6 +273,16 @@ module ActiveRecord
end
end
+ MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN = 191
+ CHARSETS_OF_4BYTES_MAXLEN = ['utf8mb4', 'utf16', 'utf16le', 'utf32']
+ def initialize_schema_migrations_table
+ if CHARSETS_OF_4BYTES_MAXLEN.include?(charset)
+ ActiveRecord::SchemaMigration.create_table(MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN)
+ else
+ ActiveRecord::SchemaMigration.create_table
+ end
+ end
+
# Returns true, since this connection adapter supports migrations.
def supports_migrations?
true
@@ -212,6 +322,10 @@ module ActiveRecord
version[0] >= 5
end
+ def supports_datetime_with_precision?
+ (version[0] == 5 && version[1] >= 6) || version[0] >= 6
+ end
+
def native_database_types
NATIVE_DATABASE_TYPES
end
@@ -228,8 +342,8 @@ module ActiveRecord
raise NotImplementedError
end
- def new_column(field, default, cast_type, sql_type = nil, null = true, collation = "", extra = "") # :nodoc:
- Column.new(field, default, cast_type, sql_type, null, collation, strict_mode?, extra)
+ def new_column(field, default, sql_type_metadata = nil, null = true) # :nodoc:
+ Column.new(field, default, sql_type_metadata, null)
end
# Must return the MySQL error number from the exception, if the exception has an
@@ -285,7 +399,9 @@ module ActiveRecord
end
end
+ #--
# DATABASE STATEMENTS ======================================
+ #++
def clear_cache!
super
@@ -322,7 +438,7 @@ module ActiveRecord
execute "COMMIT"
end
- def rollback_db_transaction #:nodoc:
+ def exec_rollback_db_transaction #:nodoc:
execute "ROLLBACK"
end
@@ -396,7 +512,7 @@ module ActiveRecord
sql << "LIKE #{quote(like)}" if like
execute_and_free(sql, 'SCHEMA') do |result|
- result.collect { |field| field.first }
+ result.collect(&:first)
end
end
@@ -450,8 +566,8 @@ module ActiveRecord
each_hash(result).map do |field|
field_name = set_field_encoding(field[:Field])
sql_type = field[:Type]
- cast_type = lookup_cast_type(sql_type)
- new_column(field_name, field[:Default], cast_type, sql_type, field[:Null] == "YES", field[:Collation], field[:Extra])
+ type_metadata = fetch_type_metadata(sql_type, field[:Collation], field[:Extra])
+ new_column(field_name, field[:Default], type_metadata, field[:Null] == "YES")
end
end
end
@@ -484,12 +600,29 @@ module ActiveRecord
rename_table_indexes(table_name, new_name)
end
+ # Drops a table from the database.
+ #
+ # [<tt>:force</tt>]
+ # Set to +:cascade+ to drop dependent objects as well.
+ # Defaults to false.
+ # [<tt>:if_exists</tt>]
+ # Set to +true+ to only drop the table if it exists.
+ # Defaults to false.
+ # [<tt>:temporary</tt>]
+ # Set to +true+ to drop temporary table.
+ # Defaults to false.
+ #
+ # Although this command ignores most +options+ and the block if one is given,
+ # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
+ # In that case, +options+ and the block will be used by create_table.
def drop_table(table_name, options = {})
- execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}"
+ execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
end
def rename_index(table_name, old_name, new_name)
if supports_rename_index?
+ validate_index_length!(table_name, new_name)
+
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
else
super
@@ -585,14 +718,6 @@ module ActiveRecord
end
end
- def add_column_position!(sql, options)
- if options[:first]
- sql << " FIRST"
- elsif options[:after]
- sql << " AFTER #{quote_column_name(options[:after])}"
- end
- end
-
# SHOW VARIABLES LIKE 'name'
def show_variable(name)
variables = select_all("SHOW VARIABLES LIKE '#{name}'", 'SCHEMA')
@@ -639,10 +764,6 @@ module ActiveRecord
end
end
- def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
- where_sql
- end
-
def strict_mode?
self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
end
@@ -656,6 +777,8 @@ module ActiveRecord
def initialize_type_map(m) # :nodoc:
super
+ register_class_with_limit m, %r(char)i, MysqlString
+
m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
@@ -664,14 +787,15 @@ module ActiveRecord
m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
- m.register_type %r(^bigint)i, Type::Integer.new(limit: 8)
- m.register_type %r(^int)i, Type::Integer.new(limit: 4)
- m.register_type %r(^mediumint)i, Type::Integer.new(limit: 3)
- m.register_type %r(^smallint)i, Type::Integer.new(limit: 2)
- m.register_type %r(^tinyint)i, Type::Integer.new(limit: 1)
m.register_type %r(^float)i, Type::Float.new(limit: 24)
m.register_type %r(^double)i, Type::Float.new(limit: 53)
+ register_integer_type m, %r(^bigint)i, limit: 8
+ register_integer_type m, %r(^int)i, limit: 4
+ register_integer_type m, %r(^mediumint)i, limit: 3
+ register_integer_type m, %r(^smallint)i, limit: 2
+ register_integer_type m, %r(^tinyint)i, limit: 1
+
m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans
m.alias_type %r(set)i, 'varchar'
m.alias_type %r(year)i, 'integer'
@@ -680,10 +804,32 @@ module ActiveRecord
m.register_type(%r(enum)i) do |sql_type|
limit = sql_type[/^enum\((.+)\)/i, 1]
.split(',').map{|enum| enum.strip.length - 2}.max
- Type::String.new(limit: limit)
+ MysqlString.new(limit: limit)
+ end
+ end
+
+ def register_integer_type(mapping, key, options) # :nodoc:
+ mapping.register_type(key) do |sql_type|
+ if /unsigned/i =~ sql_type
+ Type::UnsignedInteger.new(options)
+ else
+ Type::Integer.new(options)
+ end
end
end
+ def extract_precision(sql_type)
+ if /time/ === sql_type
+ super || 0
+ else
+ super
+ 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)
@@ -692,7 +838,9 @@ module ActiveRecord
subselect = Arel::SelectManager.new(select.engine)
subselect.project Arel.sql(key.name)
- subselect.from subsubselect.as('__active_record_temp')
+ # 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 add_index_length(option_strings, column_names, options = {})
@@ -732,7 +880,7 @@ module ActiveRecord
end
def add_column_sql(table_name, column_name, type, options = {})
- td = create_table_definition table_name, options[:temporary], options[:options]
+ td = create_table_definition(table_name)
cd = td.new_column_definition(column_name, type, options)
schema_creation.visit_AddColumn cd
end
@@ -748,21 +896,23 @@ module ActiveRecord
options[:null] = column.null
end
- options[:name] = column.name
- schema_creation.accept ChangeColumnDefinition.new column, type, options
+ td = create_table_definition(table_name)
+ cd = td.new_column_definition(column.name, type, options)
+ schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
end
def rename_column_sql(table_name, column_name, new_column_name)
column = column_for(table_name, column_name)
options = {
- name: new_column_name,
default: column.default,
null: column.null,
- auto_increment: column.extra == "auto_increment"
+ auto_increment: column.auto_increment?
}
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
- schema_creation.accept ChangeColumnDefinition.new column, current_type, options
+ td = create_table_definition(table_name)
+ cd = td.new_column_definition(new_column_name, current_type, options)
+ schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
end
def remove_column_sql(table_name, column_name, type = nil, options = {})
@@ -787,14 +937,14 @@ module ActiveRecord
[add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)]
end
- def remove_timestamps_sql(table_name)
+ def remove_timestamps_sql(table_name, options = {})
[remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
end
private
def version
- @version ||= full_version.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
+ @version ||= full_version.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map(&:to_i)
end
def mariadb?
@@ -808,8 +958,7 @@ module ActiveRecord
def configure_connection
variables = @config.fetch(:variables, {}).stringify_keys
- # By default, MySQL 'where id is null' selects the last inserted id.
- # Turn this off. http://dev.rubyonrails.org/ticket/6778
+ # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
variables['sql_auto_is_null'] = 0
# Increase timeout so the server doesn't disconnect us.
@@ -818,14 +967,14 @@ module ActiveRecord
variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout)
# Make MySQL reject illegal values rather than truncating or blanking them, see
- # http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables
+ # 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')
variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
end
# NAMES does not have an equals sign, see
- # http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430
+ # http://dev.mysql.com/doc/refman/5.6/en/set-statement.html#id944430
# (trailing comma because variable_assignments will always have content)
if @config[:encoding]
encoding = "NAMES #{@config[:encoding]}"
@@ -855,6 +1004,33 @@ module ActiveRecord
end
end
end
+
+ def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
+ TableDefinition.new(native_database_types, name, temporary, options, as)
+ end
+
+ class MysqlString < Type::String # :nodoc:
+ def serialize(value)
+ case value
+ when true then "1"
+ when false then "0"
+ else super
+ end
+ end
+
+ private
+
+ def cast_value(value)
+ case value
+ when true then "1"
+ when false then "0"
+ else super
+ end
+ end
+ end
+
+ ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql)
+ ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index dd303c73d5..f4dda5154e 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -5,7 +5,6 @@ module ActiveRecord
module ConnectionAdapters
# An abstract definition of a column in a table.
class Column
- TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON'].to_set
FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].to_set
module Format
@@ -13,34 +12,31 @@ module ActiveRecord
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
end
- attr_reader :name, :cast_type, :null, :sql_type, :default, :default_function
+ attr_reader :name, :null, :sql_type_metadata, :default, :default_function
- delegate :type, :precision, :scale, :limit, :klass, :accessor,
- :number?, :binary?, :changed?,
- :type_cast_from_user, :type_cast_from_database, :type_cast_for_database,
- :type_cast_for_schema,
- to: :cast_type
+ delegate :precision, :scale, :limit, :type, :sql_type, to: :sql_type_metadata, allow_nil: true
# Instantiates a new column in the table.
#
# +name+ is the column's name, such as <tt>supplier_id</tt> in <tt>supplier_id int(11)</tt>.
# +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
- # +cast_type+ is the object used for type casting and type information.
- # +sql_type+ is used to extract the column's length, if necessary. For example +60+ in
- # <tt>company_name varchar(60)</tt>.
- # It will be mapped to one of the standard Rails SQL types in the <tt>type</tt> attribute.
+ # +sql_type_metadata+ is various information about the type of the column
# +null+ determines if this column allows +NULL+ values.
- def initialize(name, default, cast_type, sql_type = nil, null = true)
- @name = name
- @cast_type = cast_type
- @sql_type = sql_type
- @null = null
- @default = default
- @default_function = nil
+ def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil)
+ @name = name
+ @sql_type_metadata = sql_type_metadata
+ @null = null
+ @default = default
+ @default_function = default_function
+ @table_name = nil
end
def has_default?
- !default.nil?
+ !default.nil? || default_function
+ end
+
+ def bigint?
+ /bigint/ === sql_type
end
# Returns the human name of the column name.
@@ -51,19 +47,9 @@ module ActiveRecord
Base.human_attribute_name(@name)
end
- def with_type(type)
- dup.tap do |clone|
- clone.instance_variable_set('@cast_type', type)
- end
- end
-
def ==(other)
- other.name == name &&
- other.default == default &&
- other.cast_type == cast_type &&
- other.sql_type == sql_type &&
- other.null == null &&
- other.default_function == default_function
+ other.is_a?(Column) &&
+ attributes_for_hash == other.attributes_for_hash
end
alias :eql? :==
@@ -71,10 +57,16 @@ module ActiveRecord
attributes_for_hash.hash
end
- private
+ protected
def attributes_for_hash
- [self.class, name, default, cast_type, sql_type, null, default_function]
+ [self.class, name, default, sql_type_metadata, null, default_function]
+ end
+ end
+
+ class NullColumn < Column
+ def initialize(name)
+ super(name, nil)
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index 609ec7dabd..08d46fca96 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -1,5 +1,4 @@
require 'uri'
-require 'active_support/core_ext/string/filters'
module ActiveRecord
module ConnectionAdapters
@@ -210,31 +209,13 @@ module ActiveRecord
when Symbol
resolve_symbol_connection spec
when String
- resolve_string_connection spec
+ resolve_url_connection spec
when Hash
resolve_hash_connection spec
end
end
- def resolve_string_connection(spec)
- # Rails has historically accepted a string to mean either
- # an environment key or a URL spec, so we have deprecated
- # this ambiguous behaviour and in the future this function
- # can be removed in favor of resolve_url_connection.
- if configurations.key?(spec) || spec !~ /:/
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing a string to ActiveRecord::Base.establish_connection for a
- configuration lookup is deprecated, please pass a symbol
- (#{spec.to_sym.inspect}) instead.
- MSG
-
- resolve_symbol_connection(spec)
- else
- resolve_url_connection(spec)
- end
- end
-
- # Takes the environment such as `:production` or `:development`.
+ # Takes the environment such as +:production+ or +:development+.
# This requires that the @configurations was initialized with a key that
# matches.
#
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 5b83131f0e..21631be25c 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -37,15 +37,6 @@ module ActiveRecord
configure_connection
end
- MAX_INDEX_LENGTH_FOR_UTF8MB4 = 191
- def initialize_schema_migrations_table
- if @config[:encoding] == 'utf8mb4'
- ActiveRecord::SchemaMigration.create_table(MAX_INDEX_LENGTH_FOR_UTF8MB4)
- else
- ActiveRecord::SchemaMigration.create_table
- end
- end
-
def supports_explain?
true
end
@@ -66,21 +57,17 @@ module ActiveRecord
exception.error_number if exception.respond_to?(:error_number)
end
+ #--
# QUOTING ==================================================
+ #++
def quote_string(string)
@connection.escape(string)
end
- def quoted_date(value)
- if value.acts_like?(:time) && value.respond_to?(:usec)
- "#{super}.#{sprintf("%06d", value.usec)}"
- else
- super
- end
- end
-
+ #--
# CONNECTION MANAGEMENT ====================================
+ #++
def active?
return false unless @connection
@@ -104,7 +91,9 @@ module ActiveRecord
end
end
+ #--
# DATABASE STATEMENTS ======================================
+ #++
def explain(arel, binds = [])
sql = "EXPLAIN #{to_sql(arel, binds.dup)}"
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 3357ed52e5..45b935f1d6 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -56,9 +56,9 @@ module ActiveRecord
# * <tt>:password</tt> - Defaults to nothing.
# * <tt>:database</tt> - The name of the database. No default, must be provided.
# * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
- # * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
- # * <tt>:strict</tt> - Defaults to true. Enable STRICT_ALL_TABLES. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html)
- # * <tt>:variables</tt> - (Optional) A hash session variables to send as `SET @@SESSION.key = value` on each database connection. Use the value `:default` to set a variable to its DEFAULT value. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/set-statement.html).
+ # * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.6/en/auto-reconnect.html).
+ # * <tt>:strict</tt> - Defaults to true. Enable STRICT_ALL_TABLES. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.6/en/sql-mode.html)
+ # * <tt>:variables</tt> - (Optional) A hash session variables to send as <tt>SET @@SESSION.key = value</tt> on each database connection. Use the value +:default+ to set a variable to its DEFAULT value. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.6/en/set-statement.html).
# * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
@@ -137,7 +137,9 @@ module ActiveRecord
@connection.quote(string)
end
+ #--
# CONNECTION MANAGEMENT ====================================
+ #++
def active?
if @connection.respond_to?(:stat)
@@ -178,7 +180,9 @@ module ActiveRecord
end
end
+ #--
# DATABASE STATEMENTS ======================================
+ #++
def select_rows(sql, name = nil, binds = [])
@connection.query_with_result = true
@@ -324,8 +328,8 @@ module ActiveRecord
def initialize_type_map(m) # :nodoc:
super
- m.register_type %r(datetime)i, Fields::DateTime.new
- m.register_type %r(time)i, Fields::Time.new
+ register_class_with_precision m, %r(datetime)i, Fields::DateTime
+ register_class_with_precision m, %r(time)i, Fields::Time
end
def exec_without_stmt(sql, name = 'SQL') # :nodoc:
@@ -391,11 +395,9 @@ module ActiveRecord
def exec_stmt(sql, name, binds)
cache = {}
- type_casted_binds = binds.map { |col, val|
- [col, type_cast(val, col)]
- }
+ type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
- log(sql, name, type_casted_binds) do
+ log(sql, name, binds) do
if binds.empty?
stmt = @connection.prepare(sql)
else
@@ -406,7 +408,7 @@ module ActiveRecord
end
begin
- stmt.execute(*type_casted_binds.map { |_, val| val })
+ stmt.execute(*type_casted_binds)
rescue Mysql::Error => e
# Older versions of MySQL leave the prepared statement in a bad
# place when an error occurs. To support older MySQL versions, we
@@ -419,9 +421,7 @@ module ActiveRecord
cols = nil
if metadata = stmt.result_metadata
- cols = cache[:cols] ||= metadata.fetch_fields.map { |field|
- field.name
- }
+ cols = cache[:cols] ||= metadata.fetch_fields.map(&:name)
metadata.free
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb b/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb
deleted file mode 100644
index 1b74c039ce..0000000000
--- a/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb
+++ /dev/null
@@ -1,93 +0,0 @@
-module ActiveRecord
- module ConnectionAdapters
- module PostgreSQL
- module ArrayParser # :nodoc:
-
- DOUBLE_QUOTE = '"'
- BACKSLASH = "\\"
- COMMA = ','
- BRACKET_OPEN = '{'
- BRACKET_CLOSE = '}'
-
- def parse_pg_array(string) # :nodoc:
- local_index = 0
- array = []
- while(local_index < string.length)
- case string[local_index]
- when BRACKET_OPEN
- local_index,array = parse_array_contents(array, string, local_index + 1)
- when BRACKET_CLOSE
- return array
- end
- local_index += 1
- end
-
- array
- end
-
- private
-
- def parse_array_contents(array, string, index)
- is_escaping = false
- is_quoted = false
- was_quoted = false
- current_item = ''
-
- local_index = index
- while local_index
- token = string[local_index]
- if is_escaping
- current_item << token
- is_escaping = false
- else
- if is_quoted
- case token
- when DOUBLE_QUOTE
- is_quoted = false
- was_quoted = true
- when BACKSLASH
- is_escaping = true
- else
- current_item << token
- end
- else
- case token
- when BACKSLASH
- is_escaping = true
- when COMMA
- add_item_to_array(array, current_item, was_quoted)
- current_item = ''
- was_quoted = false
- when DOUBLE_QUOTE
- is_quoted = true
- when BRACKET_OPEN
- internal_items = []
- local_index,internal_items = parse_array_contents(internal_items, string, local_index + 1)
- array.push(internal_items)
- when BRACKET_CLOSE
- add_item_to_array(array, current_item, was_quoted)
- return local_index,array
- else
- current_item << token
- end
- end
- end
-
- local_index += 1
- end
- return local_index,array
- end
-
- def add_item_to_array(array, current_item, quoted)
- return if !quoted && current_item.length == 0
-
- if !quoted && current_item == 'NULL'
- array.push nil
- else
- array.push current_item
- end
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
index 37e5c3859c..be13ead120 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
@@ -2,18 +2,13 @@ module ActiveRecord
module ConnectionAdapters
# PostgreSQL-specific extensions to column definitions in a table.
class PostgreSQLColumn < Column #:nodoc:
- attr_accessor :array
+ delegate :array, :oid, :fmod, to: :sql_type_metadata
+ alias :array? :array
- def initialize(name, default, cast_type, sql_type = nil, null = true, default_function = nil)
- if sql_type =~ /\[\]$/
- @array = true
- super(name, default, cast_type, sql_type[0..sql_type.length - 3], null)
- else
- @array = false
- super(name, default, cast_type, sql_type, null)
- end
+ def serial?
+ return unless default_function
- @default_function = default_function
+ %r{\Anextval\('(?<table_name>.+)_#{name}_seq'::regclass\)\z} === default_function
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
index cf379ab210..11d3f5301a 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -156,10 +156,6 @@ module ActiveRecord
end
end
- def substitute_at(column, index = 0)
- Arel::Nodes::BindParam.new "$#{index + 1}"
- end
-
def exec_query(sql, name = 'SQL', binds = [])
execute_and_clear(sql, name, binds) do |result|
types = {}
@@ -227,7 +223,7 @@ module ActiveRecord
end
# Aborts a transaction.
- def rollback_db_transaction
+ def exec_rollback_db_transaction
execute "ROLLBACK"
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 d28a2b4fa0..92349e2f9b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -1,25 +1,19 @@
-require 'active_record/connection_adapters/postgresql/oid/infinity'
-
require 'active_record/connection_adapters/postgresql/oid/array'
require 'active_record/connection_adapters/postgresql/oid/bit'
require 'active_record/connection_adapters/postgresql/oid/bit_varying'
require 'active_record/connection_adapters/postgresql/oid/bytea'
require 'active_record/connection_adapters/postgresql/oid/cidr'
-require 'active_record/connection_adapters/postgresql/oid/date'
require 'active_record/connection_adapters/postgresql/oid/date_time'
require 'active_record/connection_adapters/postgresql/oid/decimal'
require 'active_record/connection_adapters/postgresql/oid/enum'
-require 'active_record/connection_adapters/postgresql/oid/float'
require 'active_record/connection_adapters/postgresql/oid/hstore'
require 'active_record/connection_adapters/postgresql/oid/inet'
-require 'active_record/connection_adapters/postgresql/oid/integer'
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/range'
require 'active_record/connection_adapters/postgresql/oid/specialized_string'
-require 'active_record/connection_adapters/postgresql/oid/time'
require 'active_record/connection_adapters/postgresql/oid/uuid'
require 'active_record/connection_adapters/postgresql/oid/vector'
require 'active_record/connection_adapters/postgresql/oid/xml'
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 cd5efe2bb8..3de794f797 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
@@ -3,48 +3,48 @@ module ActiveRecord
module PostgreSQL
module OID # :nodoc:
class Array < Type::Value # :nodoc:
- include Type::Mutable
-
- # Loads pg_array_parser if available. String parsing can be
- # performed quicker by a native extension, which will not create
- # a large amount of Ruby objects that will need to be garbage
- # collected. pg_array_parser has a C and Java extension
- begin
- require 'pg_array_parser'
- include PgArrayParser
- rescue LoadError
- require 'active_record/connection_adapters/postgresql/array_parser'
- include PostgreSQL::ArrayParser
- end
+ include Type::Helpers::Mutable
attr_reader :subtype, :delimiter
- delegate :type, to: :subtype
+ delegate :type, :user_input_in_time_zone, :limit, to: :subtype
def initialize(subtype, delimiter = ',')
@subtype = subtype
@delimiter = delimiter
+
+ @pg_encoder = PG::TextEncoder::Array.new name: "#{type}[]", delimiter: delimiter
+ @pg_decoder = PG::TextDecoder::Array.new name: "#{type}[]", delimiter: delimiter
end
- def type_cast_from_database(value)
+ def deserialize(value)
if value.is_a?(::String)
- type_cast_array(parse_pg_array(value), :type_cast_from_database)
+ type_cast_array(@pg_decoder.decode(value), :deserialize)
else
super
end
end
- def type_cast_from_user(value)
- type_cast_array(value, :type_cast_from_user)
+ def cast(value)
+ if value.is_a?(::String)
+ value = @pg_decoder.decode(value)
+ end
+ type_cast_array(value, :cast)
end
- def type_cast_for_database(value)
+ def serialize(value)
if value.is_a?(::Array)
- cast_value_for_database(value)
+ @pg_encoder.encode(type_cast_array(value, :serialize))
else
super
end
end
+ def ==(other)
+ other.is_a?(Array) &&
+ subtype == other.subtype &&
+ delimiter == other.delimiter
+ end
+
private
def type_cast_array(value, method)
@@ -54,41 +54,6 @@ module ActiveRecord
@subtype.public_send(method, value)
end
end
-
- def cast_value_for_database(value)
- if value.is_a?(::Array)
- casted_values = value.map { |item| cast_value_for_database(item) }
- "{#{casted_values.join(delimiter)}}"
- else
- quote_and_escape(subtype.type_cast_for_database(value))
- end
- end
-
- ARRAY_ESCAPE = "\\" * 2 * 2 # escape the backslash twice for PG arrays
-
- def quote_and_escape(value)
- case value
- when ::String
- if string_requires_quoting?(value)
- value = value.gsub(/\\/, ARRAY_ESCAPE)
- value.gsub!(/"/,"\\\"")
- %("#{value}")
- else
- value
- end
- when nil then "NULL"
- else value
- end
- end
-
- # See http://www.postgresql.org/docs/9.2/static/arrays.html#ARRAYS-IO
- # for a list of all cases in which strings will be quoted.
- def string_requires_quoting?(string)
- string.empty? ||
- string == "NULL" ||
- string =~ /[\{\}"\\\s]/ ||
- string.include?(delimiter)
- end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb
index 1dbb40ca1d..ea0fa2517f 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb
@@ -7,7 +7,7 @@ module ActiveRecord
:bit
end
- def type_cast(value)
+ def cast(value)
if ::String === value
case value
when /^0x/i
@@ -20,7 +20,7 @@ module ActiveRecord
end
end
- def type_cast_for_database(value)
+ def serialize(value)
Data.new(super) if value
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb
index 997613d7be..8f9d6e7f9b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb
@@ -3,8 +3,9 @@ module ActiveRecord
module PostgreSQL
module OID # :nodoc:
class Bytea < Type::Binary # :nodoc:
- def type_cast_from_database(value)
+ def deserialize(value)
return if value.nil?
+ return value.to_s if value.is_a?(Type::Binary::Data)
PGconn.unescape_bytea(super)
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb
index 222f10fa8f..eeccb09bdf 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb
@@ -18,7 +18,7 @@ module ActiveRecord
end
end
- def type_cast_for_database(value)
+ def serialize(value)
if IPAddr === value
"#{value}/#{value.instance_variable_get(:@mask_addr).to_s(2).count('1')}"
else
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/date.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/date.rb
deleted file mode 100644
index 1d8d264530..0000000000
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/date.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-module ActiveRecord
- module ConnectionAdapters
- module PostgreSQL
- module OID # :nodoc:
- class Date < Type::Date # :nodoc:
- include Infinity
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb
index b9e7894e5c..2c04c46131 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb
@@ -3,8 +3,6 @@ module ActiveRecord
module PostgreSQL
module OID # :nodoc:
class DateTime < Type::DateTime # :nodoc:
- include Infinity
-
def cast_value(value)
if value.is_a?(::String)
case value
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb
index 77d5038efd..91d339f32c 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb
@@ -7,7 +7,9 @@ module ActiveRecord
:enum
end
- def type_cast(value)
+ private
+
+ def cast_value(value)
value.to_s
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb
deleted file mode 100644
index 78ef94b912..0000000000
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-module ActiveRecord
- module ConnectionAdapters
- module PostgreSQL
- module OID # :nodoc:
- class Float < Type::Float # :nodoc:
- include Infinity
-
- def cast_value(value)
- case value
- when ::Float then value
- when 'Infinity' then ::Float::INFINITY
- when '-Infinity' then -::Float::INFINITY
- when 'NaN' then ::Float::NAN
- else value.to_f
- end
- end
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb
index be4525c94f..9270fc9f21 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb
@@ -3,13 +3,13 @@ module ActiveRecord
module PostgreSQL
module OID # :nodoc:
class Hstore < Type::Value # :nodoc:
- include Type::Mutable
+ include Type::Helpers::Mutable
def type
:hstore
end
- def type_cast_from_database(value)
+ def deserialize(value)
if value.is_a?(::String)
::Hash[value.scan(HstorePair).map { |k, v|
v = v.upcase == 'NULL' ? nil : v.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
@@ -21,7 +21,7 @@ module ActiveRecord
end
end
- def type_cast_for_database(value)
+ def serialize(value)
if value.is_a?(::Hash)
value.map { |k, v| "#{escape_hstore(k)}=>#{escape_hstore(v)}" }.join(', ')
else
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/infinity.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/infinity.rb
deleted file mode 100644
index e47780399a..0000000000
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/infinity.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-module ActiveRecord
- module ConnectionAdapters
- module PostgreSQL
- module OID # :nodoc:
- module Infinity # :nodoc:
- def infinity(options = {})
- options[:negative] ? -::Float::INFINITY : ::Float::INFINITY
- end
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/integer.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/integer.rb
deleted file mode 100644
index 59abdc0009..0000000000
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/integer.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-module ActiveRecord
- module ConnectionAdapters
- module PostgreSQL
- module OID # :nodoc:
- class Integer < Type::Integer # :nodoc:
- include Infinity
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb
index e12ddd9901..8e1256baad 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb
@@ -3,25 +3,25 @@ module ActiveRecord
module PostgreSQL
module OID # :nodoc:
class Json < Type::Value # :nodoc:
- include Type::Mutable
+ include Type::Helpers::Mutable
def type
:json
end
- def type_cast_from_database(value)
+ def deserialize(value)
if value.is_a?(::String)
- ::ActiveSupport::JSON.decode(value)
+ ::ActiveSupport::JSON.decode(value) rescue nil
else
- super
+ value
end
end
- def type_cast_for_database(value)
+ def serialize(value)
if value.is_a?(::Array) || value.is_a?(::Hash)
::ActiveSupport::JSON.encode(value)
else
- super
+ value
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb
index 380c50fc14..afc9383f91 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb
@@ -13,7 +13,7 @@ module ActiveRecord
# the comparison here. Therefore, we need to parse and re-dump the
# raw value here to ensure the insignificant whitespaces are
# consistent with our encoder's output.
- raw_old_value = type_cast_for_database(type_cast_from_database(raw_old_value))
+ raw_old_value = serialize(deserialize(raw_old_value))
super(raw_old_value, new_value)
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb
index df890c2ed6..2163674019 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb
@@ -3,8 +3,6 @@ module ActiveRecord
module PostgreSQL
module OID # :nodoc:
class Money < Type::Decimal # :nodoc:
- include Infinity
-
class_attribute :precision
def type
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb
index bac8b01d6b..bf565bcf47 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb
@@ -3,19 +3,19 @@ module ActiveRecord
module PostgreSQL
module OID # :nodoc:
class Point < Type::Value # :nodoc:
- include Type::Mutable
+ include Type::Helpers::Mutable
def type
:point
end
- def type_cast(value)
+ def cast(value)
case value
when ::String
if value[0] == '(' && value[-1] == ')'
value = value[1...-1]
end
- type_cast(value.split(','))
+ cast(value.split(','))
when ::Array
value.map { |v| Float(v) }
else
@@ -23,7 +23,7 @@ module ActiveRecord
end
end
- def type_cast_for_database(value)
+ def serialize(value)
if value.is_a?(::Array)
"(#{number_for_point(value[0])},#{number_for_point(value[1])})"
else
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
index 961e6224c4..fc201f8fb9 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
@@ -7,7 +7,7 @@ module ActiveRecord
class Range < Type::Value # :nodoc:
attr_reader :subtype, :type
- def initialize(subtype, type)
+ def initialize(subtype, type = :range)
@subtype = subtype
@type = type
end
@@ -25,21 +25,12 @@ module ActiveRecord
to = type_cast_single extracted[:to]
if !infinity?(from) && extracted[:exclude_start]
- if from.respond_to?(:succ)
- from = from.succ
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Excluding the beginning of a Range is only partialy supported
- through `#succ`. This is not reliable and will be removed in
- the future.
- MSG
- else
- raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')"
- end
+ raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')"
end
::Range.new(from, to, extracted[:exclude_end])
end
- def type_cast_for_database(value)
+ def serialize(value)
if value.is_a?(::Range)
from = type_cast_single_for_database(value.begin)
to = type_cast_single_for_database(value.end)
@@ -49,26 +40,42 @@ module ActiveRecord
end
end
+ def ==(other)
+ other.is_a?(Range) &&
+ other.subtype == subtype &&
+ other.type == type
+ end
+
private
def type_cast_single(value)
- infinity?(value) ? value : @subtype.type_cast_from_database(value)
+ infinity?(value) ? value : @subtype.deserialize(value)
end
def type_cast_single_for_database(value)
- infinity?(value) ? '' : @subtype.type_cast_for_database(value)
+ infinity?(value) ? '' : @subtype.serialize(value)
end
def extract_bounds(value)
from, to = value[1..-2].split(',')
{
- from: (value[1] == ',' || from == '-infinity') ? @subtype.infinity(negative: true) : from,
- to: (value[-2] == ',' || to == 'infinity') ? @subtype.infinity : to,
+ from: (value[1] == ',' || from == '-infinity') ? infinity(negative: true) : from,
+ to: (value[-2] == ',' || to == 'infinity') ? infinity : to,
exclude_start: (value[0] == '('),
exclude_end: (value[-1] == ')')
}
end
+ def infinity(negative: false)
+ if subtype.respond_to?(:infinity)
+ subtype.infinity(negative: negative)
+ elsif negative
+ -::Float::INFINITY
+ else
+ ::Float::INFINITY
+ end
+ end
+
def infinity?(value)
value.respond_to?(:infinite?) && value.infinite?
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/time.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/time.rb
deleted file mode 100644
index 8f0246eddb..0000000000
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/time.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-module ActiveRecord
- module ConnectionAdapters
- module PostgreSQL
- module OID # :nodoc:
- class Time < Type::Time # :nodoc:
- include Infinity
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb
index 35e699eeda..191c828e60 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb
@@ -15,11 +15,11 @@ module ActiveRecord
def run(records)
nodes = records.reject { |row| @store.key? row['oid'].to_i }
mapped, nodes = nodes.partition { |row| @store.key? row['typname'] }
- ranges, nodes = nodes.partition { |row| row['typtype'] == 'r' }
- enums, nodes = nodes.partition { |row| row['typtype'] == 'e' }
- domains, nodes = nodes.partition { |row| row['typtype'] == 'd' }
- arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in' }
- composites, nodes = nodes.partition { |row| row['typelem'] != '0' }
+ ranges, nodes = nodes.partition { |row| row['typtype'] == 'r'.freeze }
+ enums, nodes = nodes.partition { |row| row['typtype'] == 'e'.freeze }
+ domains, nodes = nodes.partition { |row| row['typtype'] == 'd'.freeze }
+ arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in'.freeze }
+ composites, nodes = nodes.partition { |row| row['typelem'].to_i != 0 }
mapped.each { |row| register_mapped_type(row) }
enums.each { |row| register_enum_type(row) }
@@ -29,6 +29,18 @@ module ActiveRecord
composites.each { |row| register_composite_type(row) }
end
+ def query_conditions_for_initial_load(type_map)
+ known_type_names = type_map.keys.map { |n| "'#{n}'" }
+ known_type_types = %w('r' 'e' 'd')
+ <<-SQL % [known_type_names.join(", "), known_type_types.join(", ")]
+ WHERE
+ t.typname IN (%s)
+ OR t.typtype IN (%s)
+ OR t.typinput::varchar = 'array_in'
+ OR t.typelem != 0
+ SQL
+ end
+
private
def register_mapped_type(row)
alias_type row['oid'], row['typname']
@@ -39,14 +51,14 @@ module ActiveRecord
end
def register_array_type(row)
- if subtype = @store.lookup(row['typelem'].to_i)
- register row['oid'], OID::Array.new(subtype, row['typdelim'])
+ register_with_subtype(row['oid'], row['typelem'].to_i) do |subtype|
+ OID::Array.new(subtype, row['typdelim'])
end
end
def register_range_type(row)
- if subtype = @store.lookup(row['rngsubtype'].to_i)
- register row['oid'], OID::Range.new(subtype, row['typname'].to_sym)
+ register_with_subtype(row['oid'], row['rngsubtype'].to_i) do |subtype|
+ OID::Range.new(subtype, row['typname'].to_sym)
end
end
@@ -64,9 +76,13 @@ module ActiveRecord
end
end
- def register(oid, oid_type)
- oid = assert_valid_registration(oid, oid_type)
- @store.register_type(oid, oid_type)
+ def register(oid, oid_type = nil, &block)
+ oid = assert_valid_registration(oid, oid_type || block)
+ if block_given?
+ @store.register_type(oid, &block)
+ else
+ @store.register_type(oid, oid_type)
+ end
end
def alias_type(oid, target)
@@ -74,6 +90,14 @@ module ActiveRecord
@store.alias_type(oid, target)
end
+ def register_with_subtype(oid, target_oid)
+ if @store.key?(target_oid)
+ register(oid) do |_, *args|
+ yield @store.lookup(target_oid, *args)
+ end
+ end
+ end
+
def assert_valid_registration(oid, oid_type)
raise ArgumentError, "can't register nil type for OID #{oid}" if oid_type.nil?
oid.to_i
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb
index 033e0324bb..5e839228e9 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb
@@ -3,23 +3,16 @@ module ActiveRecord
module PostgreSQL
module OID # :nodoc:
class Uuid < Type::Value # :nodoc:
- RFC_4122 = %r{\A\{?[a-fA-F0-9]{4}-?
- [a-fA-F0-9]{4}-?
- [a-fA-F0-9]{4}-?
- [1-5][a-fA-F0-9]{3}-?
- [8-Bab][a-fA-F0-9]{3}-?
- [a-fA-F0-9]{4}-?
- [a-fA-F0-9]{4}-?
- [a-fA-F0-9]{4}-?\}?\z}x
+ ACCEPTABLE_UUID = %r{\A\{?([a-fA-F0-9]{4}-?){8}\}?\z}x
- alias_method :type_cast_for_database, :type_cast_from_database
+ alias_method :serialize, :deserialize
def type
:uuid
end
- def type_cast(value)
- value.to_s[RFC_4122, 0]
+ def cast(value)
+ value.to_s[ACCEPTABLE_UUID, 0]
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/vector.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/vector.rb
index de4187b028..b26e876b54 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/vector.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/vector.rb
@@ -16,7 +16,7 @@ module ActiveRecord
# FIXME: this should probably split on +delim+ and use +subtype+
# to cast the values. Unfortunately, the current Rails behavior
# is to just return the string.
- def type_cast(value)
+ def cast(value)
value
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/xml.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/xml.rb
index 334af7c598..d40d837cee 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/xml.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/xml.rb
@@ -7,7 +7,7 @@ module ActiveRecord
:xml
end
- def type_cast_for_database(value)
+ def serialize(value)
return unless value
Data.new(super)
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index f95f45c689..b7755c4593 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -14,22 +14,6 @@ module ActiveRecord
@connection.unescape_bytea(value) if value
end
- # Quotes PostgreSQL-specific data types for SQL input.
- def quote(value, column = nil) #:nodoc:
- return super unless column
-
- case value
- when Float
- if value.infinite? || value.nan?
- "'#{value}'"
- else
- super
- end
- else
- super
- end
- end
-
# Quotes strings for use in SQL input.
def quote_string(s) #:nodoc:
@connection.escape(s)
@@ -59,27 +43,30 @@ module ActiveRecord
# Quote date/time values for use in SQL input. Includes microseconds
# if the value is a Time responding to usec.
def quoted_date(value) #:nodoc:
- result = super
- if value.acts_like?(:time) && value.respond_to?(:usec)
- result = "#{result}.#{sprintf("%06d", value.usec)}"
- end
-
if value.year <= 0
bce_year = format("%04d", -value.year + 1)
- result = result.sub(/^-?\d+/, bce_year) + " BC"
+ super.sub(/^-?\d+/, bce_year) + " BC"
+ else
+ super
end
- result
end
# Does not quote function default values for UUID columns
- def quote_default_value(value, column) #:nodoc:
+ def quote_default_expression(value, column) #:nodoc:
if column.type == :uuid && value =~ /\(\)/
value
+ elsif column.respond_to?(:array?)
+ value = type_cast_from_column(column, value)
+ quote(value)
else
- quote(value, column)
+ super
end
end
+ def lookup_cast_type_from_column(column) # :nodoc:
+ type_map.lookup(column.oid, column.fmod, column.sql_type)
+ end
+
private
def _quote(value)
@@ -94,6 +81,12 @@ module ActiveRecord
elsif value.hex?
"X'#{value}'"
end
+ when Float
+ if value.infinite? || value.nan?
+ "'#{value}'"
+ else
+ super
+ end
else
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb
index 52b307c432..44a7338bf5 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb
@@ -8,20 +8,39 @@ module ActiveRecord
def disable_referential_integrity # :nodoc:
if supports_disable_referential_integrity?
+ original_exception = nil
+
begin
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
- rescue
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER USER" }.join(";"))
+ transaction(requires_new: true) do
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
+ end
+ rescue ActiveRecord::ActiveRecordError => e
+ original_exception = e
end
- end
- yield
- ensure
- if supports_disable_referential_integrity?
+
+ begin
+ yield
+ rescue ActiveRecord::InvalidForeignKey => e
+ warn <<-WARNING
+WARNING: Rails was not able to disable referential integrity.
+
+This is most likely caused due to missing permissions.
+Rails needs superuser privileges to disable referential integrity.
+
+ cause: #{original_exception.try(:message)}
+
+ WARNING
+ raise e
+ end
+
begin
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
- rescue
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER USER" }.join(";"))
+ transaction(requires_new: true) do
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
+ end
+ rescue ActiveRecord::ActiveRecordError
end
+ else
+ yield
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
index b37630a04c..022dbdfa27 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
@@ -2,90 +2,129 @@ module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module ColumnMethods
- def xml(*args)
- options = args.extract_options!
- column(args[0], :xml, options)
+ # Defines the primary key field.
+ # Use of the native PostgreSQL UUID type is supported, and can be used
+ # by defining your tables as such:
+ #
+ # create_table :stuffs, id: :uuid do |t|
+ # t.string :content
+ # t.timestamps
+ # end
+ #
+ # By default, this will use the +uuid_generate_v4()+ function from the
+ # +uuid-ossp+ extension, which MUST be enabled on your database. To enable
+ # the +uuid-ossp+ extension, you can use the +enable_extension+ method in your
+ # migrations. To use a UUID primary key without +uuid-ossp+ enabled, you can
+ # set the +:default+ option to +nil+:
+ #
+ # create_table :stuffs, id: false do |t|
+ # t.primary_key :id, :uuid, default: nil
+ # t.uuid :foo_id
+ # t.timestamps
+ # end
+ #
+ # You may also pass a different UUID generation function from +uuid-ossp+
+ # or another library.
+ #
+ # Note that setting the UUID primary key default value to +nil+ will
+ # require you to assure that you always provide a UUID value before saving
+ # a record (as primary keys cannot be +nil+). This might be done via the
+ # +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
+ def primary_key(name, type = :primary_key, **options)
+ options[:default] = options.fetch(:default, 'uuid_generate_v4()') if type == :uuid
+ super
+ end
+
+ def bigserial(*args, **options)
+ args.each { |name| column(name, :bigserial, options) }
+ end
+
+ def bit(*args, **options)
+ args.each { |name| column(name, :bit, options) }
end
- def tsvector(*args)
- options = args.extract_options!
- column(args[0], :tsvector, options)
+ def bit_varying(*args, **options)
+ args.each { |name| column(name, :bit_varying, options) }
end
- def int4range(name, options = {})
- column(name, :int4range, options)
+ def cidr(*args, **options)
+ args.each { |name| column(name, :cidr, options) }
end
- def int8range(name, options = {})
- column(name, :int8range, options)
+ def citext(*args, **options)
+ args.each { |name| column(name, :citext, options) }
end
- def tsrange(name, options = {})
- column(name, :tsrange, options)
+ def daterange(*args, **options)
+ args.each { |name| column(name, :daterange, options) }
end
- def tstzrange(name, options = {})
- column(name, :tstzrange, options)
+ def hstore(*args, **options)
+ args.each { |name| column(name, :hstore, options) }
end
- def numrange(name, options = {})
- column(name, :numrange, options)
+ def inet(*args, **options)
+ args.each { |name| column(name, :inet, options) }
end
- def daterange(name, options = {})
- column(name, :daterange, options)
+ def int4range(*args, **options)
+ args.each { |name| column(name, :int4range, options) }
end
- def hstore(name, options = {})
- column(name, :hstore, options)
+ def int8range(*args, **options)
+ args.each { |name| column(name, :int8range, options) }
end
- def ltree(name, options = {})
- column(name, :ltree, options)
+ def json(*args, **options)
+ args.each { |name| column(name, :json, options) }
end
- def inet(name, options = {})
- column(name, :inet, options)
+ def jsonb(*args, **options)
+ args.each { |name| column(name, :jsonb, options) }
end
- def cidr(name, options = {})
- column(name, :cidr, options)
+ def ltree(*args, **options)
+ args.each { |name| column(name, :ltree, options) }
end
- def macaddr(name, options = {})
- column(name, :macaddr, options)
+ def macaddr(*args, **options)
+ args.each { |name| column(name, :macaddr, options) }
end
- def uuid(name, options = {})
- column(name, :uuid, options)
+ def money(*args, **options)
+ args.each { |name| column(name, :money, options) }
end
- def json(name, options = {})
- column(name, :json, options)
+ def numrange(*args, **options)
+ args.each { |name| column(name, :numrange, options) }
end
- def jsonb(name, options = {})
- column(name, :jsonb, options)
+ def point(*args, **options)
+ args.each { |name| column(name, :point, options) }
end
- def citext(name, options = {})
- column(name, :citext, options)
+ def serial(*args, **options)
+ args.each { |name| column(name, :serial, options) }
end
- def point(name, options = {})
- column(name, :point, options)
+ def tsrange(*args, **options)
+ args.each { |name| column(name, :tsrange, options) }
end
- def bit(name, options)
- column(name, :bit, options)
+ def tstzrange(*args, **options)
+ args.each { |name| column(name, :tstzrange, options) }
end
- def bit_varying(name, options)
- column(name, :bit_varying, options)
+ def tsvector(*args, **options)
+ args.each { |name| column(name, :tsvector, options) }
end
- def money(name, options)
- column(name, :money, options)
+ def uuid(*args, **options)
+ args.each { |name| column(name, :uuid, options) }
+ end
+
+ def xml(*args, **options)
+ args.each { |name| column(name, :xml, options) }
end
end
@@ -96,41 +135,6 @@ module ActiveRecord
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
include ColumnMethods
- # Defines the primary key field.
- # Use of the native PostgreSQL UUID type is supported, and can be used
- # by defining your tables as such:
- #
- # create_table :stuffs, id: :uuid do |t|
- # t.string :content
- # t.timestamps
- # end
- #
- # By default, this will use the +uuid_generate_v4()+ function from the
- # +uuid-ossp+ extension, which MUST be enabled on your database. To enable
- # the +uuid-ossp+ extension, you can use the +enable_extension+ method in your
- # migrations. To use a UUID primary key without +uuid-ossp+ enabled, you can
- # set the +:default+ option to +nil+:
- #
- # create_table :stuffs, id: false do |t|
- # t.primary_key :id, :uuid, default: nil
- # t.uuid :foo_id
- # t.timestamps
- # end
- #
- # You may also pass a different UUID generation function from +uuid-ossp+
- # or another library.
- #
- # Note that setting the UUID primary key default value to +nil+ will
- # require you to assure that you always provide a UUID value before saving
- # a record (as primary keys cannot be +nil+). This might be done via the
- # +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
- def primary_key(name, type = :primary_key, options = {})
- return super unless type == :uuid
- options[:default] = options.fetch(:default, 'uuid_generate_v4()')
- options[:primary_key] = true
- column name, type, options
- end
-
def new_column_definition(name, type, options) # :nodoc:
column = super
column.array = options[:array]
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 51853c6812..168180cfd3 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -4,40 +4,9 @@ module ActiveRecord
class SchemaCreation < AbstractAdapter::SchemaCreation
private
- def visit_AddColumn(o)
- sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale)
- sql = "ADD COLUMN #{quote_column_name(o.name)} #{sql_type}"
- add_column_options!(sql, column_options(o))
- end
-
def visit_ColumnDefinition(o)
- sql = super
- if o.primary_key? && o.type != :primary_key
- sql << " PRIMARY KEY "
- add_column_options!(sql, column_options(o))
- end
- sql
- end
-
- def add_column_options!(sql, options)
- if options[:array] || options[:column].try(:array)
- sql << '[]'
- end
-
- column = options.fetch(:column) { return super }
- if column.type == :uuid && options[:default] =~ /\(\)/
- sql << " DEFAULT #{options[:default]}"
- else
- super
- end
- end
-
- def type_for_column(column)
- if column.array
- @conn.lookup_cast_type("#{column.sql_type}[]")
- else
- super
- end
+ o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.array)
+ super
end
end
@@ -118,6 +87,10 @@ module ActiveRecord
SQL
end
+ def drop_table(table_name, options = {}) # :nodoc:
+ execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
+ end
+
# Returns true if schema exists.
def schema_exists?(name)
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
@@ -156,8 +129,8 @@ module ActiveRecord
result.map do |row|
index_name = row[0]
- unique = row[1] == 't'
- indkey = row[2].split(" ")
+ unique = row[1]
+ indkey = row[2].split(" ").map(&:to_i)
inddef = row[3]
oid = row[4]
@@ -186,15 +159,17 @@ module ActiveRecord
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|
- oid = get_oid_type(oid.to_i, fmod.to_i, column_name, type)
- default_value = extract_value_from_default(oid, default)
+ 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, oid, type, notnull == 'f', default_function)
+ new_column(column_name, default_value, type_metadata, !notnull, default_function)
end
end
- def new_column(name, default, cast_type, sql_type = nil, null = true, default_function = nil) # :nodoc:
- PostgreSQLColumn.new(name, default, cast_type, sql_type, null, default_function)
+ 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)
end
# Returns the current database name.
@@ -388,15 +363,15 @@ module ActiveRecord
# Returns just a table's primary key
def primary_key(table)
- row = exec_query(<<-end_sql, 'SCHEMA').rows.first
+ pks = exec_query(<<-end_sql, 'SCHEMA').rows
SELECT attr.attname
FROM pg_attribute attr
- INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
+ INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey)
WHERE cons.contype = 'p'
AND cons.conrelid = '#{quote_table_name(table)}'::regclass
end_sql
-
- row && row.first
+ return nil unless pks.count == 1
+ pks[0][0]
end
# Renames a table.
@@ -411,7 +386,10 @@ module ActiveRecord
pk, seq = pk_and_sequence_for(new_name)
if seq && seq.identifier == "#{table_name}_#{pk}_seq"
new_seq = "#{new_name}_#{pk}_seq"
+ idx = "#{table_name}_pkey"
+ new_idx = "#{new_name}_pkey"
execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
+ execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
end
rename_table_indexes(table_name, new_name)
@@ -428,16 +406,23 @@ module ActiveRecord
def change_column(table_name, column_name, type, options = {})
clear_cache!
quoted_table_name = quote_table_name(table_name)
- sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale])
- sql_type << "[]" if options[:array]
- execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{sql_type}"
+ 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[:using]
+ sql << " USING #{options[:using]}"
+ elsif options[:cast_as]
+ cast_as_type = type_to_sql(options[:cast_as], options[:limit], options[:precision], options[:scale], options[:array])
+ sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
+ end
+ execute sql
change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
end
# Changes the default value of a table column.
- def change_column_default(table_name, column_name, default)
+ def change_column_default(table_name, column_name, default) # :nodoc:
clear_cache!
column = column_for(table_name, column_name)
return unless column
@@ -448,7 +433,7 @@ module ActiveRecord
# cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
execute alter_column_query % "DROP DEFAULT"
else
- execute alter_column_query % "SET DEFAULT #{quote_default_value(default, column)}"
+ execute alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
end
end
@@ -456,7 +441,7 @@ module ActiveRecord
clear_cache!
unless null || default.nil?
column = column_for(table_name, column_name)
- execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_value(default, column)} WHERE #{quote_column_name(column_name)} IS NULL") if column
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(default, column)} WHERE #{quote_column_name(column_name)} IS NULL") if column
end
execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
end
@@ -478,6 +463,8 @@ module ActiveRecord
end
def rename_index(table_name, old_name, new_name)
+ validate_index_length!(table_name, new_name)
+
execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
end
@@ -523,41 +510,35 @@ module ActiveRecord
end
# Maps logical Rails types to PostgreSQL-specific data types.
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
- case type.to_s
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil, array = nil)
+ sql = case type.to_s
when 'binary'
# PostgreSQL doesn't support limits on binary (bytea) columns.
- # The hard limit is 1Gb, because of a 32-bit size field, and TOAST.
+ # The hard limit is 1GB, because of a 32-bit size field, and TOAST.
case limit
when nil, 0..0x3fffffff; super(type)
else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
end
when 'text'
# PostgreSQL doesn't support limits on text columns.
- # The hard limit is 1Gb, according to section 8.3 in the manual.
+ # The hard limit is 1GB, according to section 8.3 in the manual.
case limit
when nil, 0..0x3fffffff; super(type)
else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
end
when 'integer'
- return 'integer' unless limit
-
case limit
- when 1, 2; 'smallint'
- when 3, 4; 'integer'
- when 5..8; 'bigint'
- else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
- end
- when 'datetime'
- return super unless precision
-
- case precision
- when 0..6; "timestamp(#{precision})"
- else raise(ActiveRecordError, "No timestamp type has precision of #{precision}. The allowed range of precision is from 0 to 6")
+ when 1, 2; 'smallint'
+ when nil, 3, 4; 'integer'
+ when 5..8; 'bigint'
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
end
else
- super
+ super(type, limit, precision, scale)
end
+
+ sql << '[]' if array && type != :primary_key
+ sql
end
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
@@ -573,6 +554,18 @@ module ActiveRecord
[super, *order_columns].join(', ')
end
+
+ def fetch_type_metadata(column_name, sql_type, oid, fmod)
+ cast_type = get_oid_type(oid, fmod, column_name, sql_type)
+ simple_type = SqlTypeMetadata.new(
+ sql_type: sql_type,
+ type: cast_type.type,
+ limit: cast_type.limit,
+ precision: cast_type.precision,
+ scale: cast_type.scale,
+ )
+ PostgreSQLTypeMetadata.new(simple_type, oid: oid, fmod: fmod)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
new file mode 100644
index 0000000000..58715978f7
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
@@ -0,0 +1,35 @@
+module ActiveRecord
+ module ConnectionAdapters
+ class PostgreSQLTypeMetadata < DelegateClass(SqlTypeMetadata)
+ attr_reader :oid, :fmod, :array
+
+ def initialize(type_metadata, oid: nil, fmod: nil)
+ super(type_metadata)
+ @type_metadata = type_metadata
+ @oid = oid
+ @fmod = fmod
+ @array = /\[\]$/ === type_metadata.sql_type
+ end
+
+ def sql_type
+ super.gsub(/\[\]$/, "")
+ end
+
+ def ==(other)
+ other.is_a?(PostgreSQLTypeMetadata) &&
+ attributes_for_hash == other.attributes_for_hash
+ end
+ alias eql? ==
+
+ def hash
+ attributes_for_hash.hash
+ end
+
+ protected
+
+ def attributes_for_hash
+ [self.class, @type_metadata, oid, fmod]
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb b/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb
index 0290bcb48c..9a0b80d7d3 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb
@@ -18,7 +18,11 @@ module ActiveRecord
end
def quoted
- parts.map { |p| PGconn.quote_ident(p) }.join SEPARATOR
+ if schema
+ PGconn.quote_ident(schema) << SEPARATOR << PGconn.quote_ident(identifier)
+ else
+ PGconn.quote_ident(identifier)
+ end
end
def ==(o)
@@ -32,8 +36,11 @@ module ActiveRecord
protected
def unquote(part)
- return unless part
- part.gsub(/(^"|"$)/,'')
+ if part && part.start_with?('"')
+ part[1..-2]
+ else
+ part
+ end
end
def parts
@@ -57,7 +64,11 @@ module ActiveRecord
# * <tt>"schema_name".table_name</tt>
# * <tt>"schema.name"."table name"</tt>
def extract_schema_qualified_name(string)
- table, schema = string.scan(/[^".\s]+|"[^"]*"/)[0..1].reverse
+ schema, table = string.scan(/[^".\s]+|"[^"]*"/)
+ if table.nil?
+ table = schema
+ schema = nil
+ end
PostgreSQL::Name.new(schema, table)
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 9941f74766..332ac9d88c 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -1,20 +1,20 @@
-require 'active_record/connection_adapters/abstract_adapter'
-require 'active_record/connection_adapters/statement_pool'
-
-require 'active_record/connection_adapters/postgresql/utils'
-require 'active_record/connection_adapters/postgresql/column'
-require 'active_record/connection_adapters/postgresql/oid'
-require 'active_record/connection_adapters/postgresql/quoting'
-require 'active_record/connection_adapters/postgresql/referential_integrity'
-require 'active_record/connection_adapters/postgresql/schema_definitions'
-require 'active_record/connection_adapters/postgresql/schema_statements'
-require 'active_record/connection_adapters/postgresql/database_statements'
+# Make sure we're using pg high enough for type casts and Ruby 2.2+ compatibility
+gem 'pg', '~> 0.18'
+require 'pg'
-require 'arel/visitors/bind_visitor'
+require "active_record/connection_adapters/abstract_adapter"
+require "active_record/connection_adapters/postgresql/column"
+require "active_record/connection_adapters/postgresql/database_statements"
+require "active_record/connection_adapters/postgresql/oid"
+require "active_record/connection_adapters/postgresql/quoting"
+require "active_record/connection_adapters/postgresql/referential_integrity"
+require "active_record/connection_adapters/postgresql/schema_definitions"
+require "active_record/connection_adapters/postgresql/schema_statements"
+require "active_record/connection_adapters/postgresql/type_metadata"
+require "active_record/connection_adapters/postgresql/utils"
+require "active_record/connection_adapters/statement_pool"
-# Make sure we're using pg high enough for PGResult#values
-gem 'pg', '~> 0.11'
-require 'pg'
+require 'arel/visitors/bind_visitor'
require 'ipaddr'
@@ -64,7 +64,7 @@ module ActiveRecord
# <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
# * <tt>:variables</tt> - An optional hash of additional parameters that
# will be used in <tt>SET SESSION key = val</tt> calls on the connection.
- # * <tt>:insert_returning</tt> - An optional boolean to control the use or <tt>RETURNING</tt> for <tt>INSERT</tt> statements
+ # * <tt>:insert_returning</tt> - An optional boolean to control the use of <tt>RETURNING</tt> for <tt>INSERT</tt> statements
# defaults to true.
#
# Any further options are used as connection parameters to libpq. See
@@ -125,20 +125,54 @@ module ActiveRecord
PostgreSQL::SchemaCreation.new self
end
- # Adds `:array` option to the default set provided by the
+ def column_spec_for_primary_key(column)
+ spec = {}
+ if column.serial?
+ return unless column.bigint?
+ spec[:id] = ':bigserial'
+ elsif column.type == :uuid
+ spec[:id] = ':uuid'
+ spec[:default] = column.default_function.inspect
+ else
+ spec[:id] = column.type.inspect
+ spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) })
+ end
+ spec
+ end
+
+ # Adds +:array+ option to the default set provided by the
# AbstractAdapter
- def prepare_column_options(column, types) # :nodoc:
+ def prepare_column_options(column) # :nodoc:
spec = super
- spec[:array] = 'true' if column.respond_to?(:array) && column.array
- spec[:default] = "\"#{column.default_function}\"" if column.default_function
+ spec[:array] = 'true' if column.array?
spec
end
- # Adds `:array` as a valid migration key
+ # Adds +:array+ as a valid migration key
def migration_keys
super + [:array]
end
+ def schema_type(column)
+ return super unless column.serial?
+
+ if column.bigint?
+ 'bigserial'
+ else
+ 'serial'
+ end
+ end
+ private :schema_type
+
+ def schema_default(column)
+ if column.default_function
+ column.default_function.inspect unless column.serial?
+ else
+ super
+ end
+ end
+ private :schema_default
+
# Returns +true+, since this connection adapter supports prepared statement
# caching.
def supports_statement_cache?
@@ -165,6 +199,10 @@ module ActiveRecord
true
end
+ def supports_datetime_with_precision?
+ true
+ end
+
def index_algorithms
{ concurrently: 'CONCURRENTLY' }
end
@@ -240,6 +278,7 @@ module ActiveRecord
@table_alias_length = nil
connect
+ add_pg_encoders
@statements = StatementPool.new @connection,
self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })
@@ -247,6 +286,8 @@ module ActiveRecord
raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
end
+ add_pg_decoders
+
@type_map = Type::HashLookupTypeMap.new
initialize_type_map(type_map)
@local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
@@ -444,11 +485,11 @@ module ActiveRecord
end
def initialize_type_map(m) # :nodoc:
- register_class_with_limit m, 'int2', OID::Integer
- m.alias_type 'int4', 'int2'
- m.alias_type 'int8', 'int2'
+ register_class_with_limit m, 'int2', Type::Integer
+ register_class_with_limit m, 'int4', Type::Integer
+ register_class_with_limit m, 'int8', Type::Integer
m.alias_type 'oid', 'int2'
- m.register_type 'float4', OID::Float.new
+ m.register_type 'float4', Type::Float.new
m.alias_type 'float8', 'float4'
m.register_type 'text', Type::Text.new
register_class_with_limit m, 'varchar', Type::String
@@ -459,8 +500,7 @@ module ActiveRecord
register_class_with_limit m, 'bit', OID::Bit
register_class_with_limit m, 'varbit', OID::BitVarying
m.alias_type 'timestamptz', 'timestamp'
- m.register_type 'date', OID::Date.new
- m.register_type 'time', OID::Time.new
+ m.register_type 'date', Type::Date.new
m.register_type 'money', OID::Money.new
m.register_type 'bytea', OID::Bytea.new
@@ -486,10 +526,8 @@ module ActiveRecord
m.alias_type 'lseg', 'varchar'
m.alias_type 'box', 'varchar'
- m.register_type 'timestamp' do |_, _, sql_type|
- precision = extract_precision(sql_type)
- OID::DateTime.new(precision: precision)
- end
+ register_class_with_precision m, 'time', Type::Time
+ register_class_with_precision m, 'timestamp', OID::DateTime
m.register_type 'numeric' do |_, fmod, sql_type|
precision = extract_precision(sql_type)
@@ -516,23 +554,26 @@ module ActiveRecord
def extract_limit(sql_type) # :nodoc:
case sql_type
- when /^bigint/i; 8
- when /^smallint/i; 2
- else super
+ when /^bigint/i, /^int8/i
+ 8
+ when /^smallint/i
+ 2
+ else
+ super
end
end
# Extracts the value from a PostgreSQL column default definition.
- def extract_value_from_default(oid, default) # :nodoc:
+ def extract_value_from_default(default) # :nodoc:
case default
# Quoted types
when /\A[\(B]?'(.*)'::/m
- $1.gsub(/''/, "'")
+ $1.gsub("''".freeze, "'".freeze)
# Boolean types
- when 'true', 'false'
+ when 'true'.freeze, 'false'.freeze
default
# Numeric types
- when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/
+ when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/
$1
# Object identifier types
when /\A-?\d+\z/
@@ -553,6 +594,8 @@ module ActiveRecord
end
def load_additional_types(type_map, oids = nil) # :nodoc:
+ initializer = OID::TypeMapInitializer.new(type_map)
+
if supports_ranges?
query = <<-SQL
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
@@ -568,11 +611,13 @@ module ActiveRecord
if oids
query += "WHERE t.oid::integer IN (%s)" % oids.join(", ")
+ else
+ query += initializer.query_conditions_for_initial_load(type_map)
end
- initializer = OID::TypeMapInitializer.new(type_map)
- records = execute(query, 'SCHEMA')
- initializer.run(records)
+ execute_and_clear(query, 'SCHEMA', []) do |records|
+ initializer.run(records)
+ end
end
FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
@@ -591,14 +636,10 @@ module ActiveRecord
def exec_cache(sql, name, binds)
stmt_key = prepare_statement(sql)
- type_casted_binds = binds.map { |col, val|
- [col, type_cast(val, col)]
- }
+ type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
- log(sql, name, type_casted_binds, stmt_key) do
- @connection.send_query_prepared(stmt_key, type_casted_binds.map { |_, val| val })
- @connection.block
- @connection.get_last_result
+ log(sql, name, binds, stmt_key) do
+ @connection.exec_prepared(stmt_key, type_casted_binds)
end
rescue ActiveRecord::StatementInvalid => e
pgerror = e.original_exception
@@ -745,9 +786,87 @@ module ActiveRecord
$1.strip if $1
end
- def create_table_definition(name, temporary, options, as = nil) # :nodoc:
+ def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
PostgreSQL::TableDefinition.new native_database_types, name, temporary, options, as
end
+
+ def can_perform_case_insensitive_comparison_for?(column)
+ @case_insensitive_cache ||= {}
+ @case_insensitive_cache[column.sql_type] ||= begin
+ sql = <<-end_sql
+ SELECT exists(
+ SELECT * FROM pg_proc
+ INNER JOIN pg_cast
+ ON casttarget::text::oidvector = proargtypes
+ WHERE proname = 'lower'
+ AND castsource = '#{column.sql_type}'::regtype::oid
+ )
+ end_sql
+ execute_and_clear(sql, "SCHEMA", []) do |result|
+ result.getvalue(0, 0)
+ end
+ end
+ end
+
+ def add_pg_encoders
+ map = PG::TypeMapByClass.new
+ map[Integer] = PG::TextEncoder::Integer.new
+ map[TrueClass] = PG::TextEncoder::Boolean.new
+ map[FalseClass] = PG::TextEncoder::Boolean.new
+ map[Float] = PG::TextEncoder::Float.new
+ @connection.type_map_for_queries = map
+ end
+
+ def add_pg_decoders
+ coders_by_name = {
+ 'int2' => PG::TextDecoder::Integer,
+ 'int4' => PG::TextDecoder::Integer,
+ 'int8' => PG::TextDecoder::Integer,
+ 'oid' => PG::TextDecoder::Integer,
+ 'float4' => PG::TextDecoder::Float,
+ 'float8' => PG::TextDecoder::Float,
+ 'bool' => PG::TextDecoder::Boolean,
+ }
+ known_coder_types = coders_by_name.keys.map { |n| quote(n) }
+ query = <<-SQL % known_coder_types.join(", ")
+ SELECT t.oid, t.typname
+ FROM pg_type as t
+ WHERE t.typname IN (%s)
+ SQL
+ coders = execute_and_clear(query, "SCHEMA", []) do |result|
+ result
+ .map { |row| construct_coder(row, coders_by_name[row['typname']]) }
+ .compact
+ end
+
+ map = PG::TypeMapByOid.new
+ coders.each { |coder| map.add_coder(coder) }
+ @connection.type_map_for_results = map
+ end
+
+ def construct_coder(row, coder_class)
+ return unless coder_class
+ coder_class.new(oid: row['oid'].to_i, name: row['typname'])
+ end
+
+ ActiveRecord::Type.add_modifier({ array: true }, OID::Array, adapter: :postgresql)
+ ActiveRecord::Type.add_modifier({ range: true }, OID::Range, adapter: :postgresql)
+ ActiveRecord::Type.register(:bit, OID::Bit, adapter: :postgresql)
+ ActiveRecord::Type.register(:bit_varying, OID::BitVarying, adapter: :postgresql)
+ ActiveRecord::Type.register(:binary, OID::Bytea, adapter: :postgresql)
+ ActiveRecord::Type.register(:cidr, OID::Cidr, adapter: :postgresql)
+ ActiveRecord::Type.register(:date_time, OID::DateTime, adapter: :postgresql)
+ ActiveRecord::Type.register(:decimal, OID::Decimal, adapter: :postgresql)
+ ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql)
+ ActiveRecord::Type.register(:hstore, OID::Hstore, adapter: :postgresql)
+ ActiveRecord::Type.register(:inet, OID::Inet, adapter: :postgresql)
+ ActiveRecord::Type.register(:json, OID::Json, adapter: :postgresql)
+ ActiveRecord::Type.register(:jsonb, OID::Jsonb, adapter: :postgresql)
+ ActiveRecord::Type.register(:money, OID::Money, adapter: :postgresql)
+ ActiveRecord::Type.register(:point, OID::Point, adapter: :postgresql)
+ ActiveRecord::Type.register(:uuid, OID::Uuid, adapter: :postgresql)
+ ActiveRecord::Type.register(:vector, OID::Vector, adapter: :postgresql)
+ ActiveRecord::Type.register(:xml, OID::Xml, adapter: :postgresql)
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
index a10ce330c7..37ff4e4613 100644
--- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
@@ -61,9 +61,7 @@ module ActiveRecord
end
def size
- [@columns, @columns_hash, @primary_keys, @tables].map { |x|
- x.size
- }.inject :+
+ [@columns, @columns_hash, @primary_keys, @tables].map(&:size).inject :+
end
# Clear out internal caches for table with +table_name+.
diff --git a/activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb b/activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb
new file mode 100644
index 0000000000..ccb7e154ee
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb
@@ -0,0 +1,32 @@
+module ActiveRecord
+ # :stopdoc:
+ module ConnectionAdapters
+ class SqlTypeMetadata
+ attr_reader :sql_type, :type, :limit, :precision, :scale
+
+ def initialize(sql_type: nil, type: nil, limit: nil, precision: nil, scale: nil)
+ @sql_type = sql_type
+ @type = type
+ @limit = limit
+ @precision = precision
+ @scale = scale
+ end
+
+ def ==(other)
+ other.is_a?(SqlTypeMetadata) &&
+ attributes_for_hash == other.attributes_for_hash
+ end
+ alias eql? ==
+
+ def hash
+ attributes_for_hash.hash
+ end
+
+ protected
+
+ def attributes_for_hash
+ [self.class, sql_type, type, limit, precision, scale]
+ 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 1b5e3bdbac..7e184dd510 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -41,25 +41,6 @@ module ActiveRecord
end
module ConnectionAdapters #:nodoc:
- class SQLite3Binary < Type::Binary # :nodoc:
- def cast_value(value)
- if value.encoding != Encoding::ASCII_8BIT
- value = value.force_encoding(Encoding::ASCII_8BIT)
- end
- value
- end
- end
-
- class SQLite3String < Type::String # :nodoc:
- def type_cast_for_database(value)
- if value.is_a?(::String) && value.encoding == Encoding::ASCII_8BIT
- value.encode(Encoding::UTF_8)
- else
- super
- end
- end
- end
-
# The SQLite3 adapter works SQLite 3.6.16 or newer
# with the sqlite3-ruby drivers (available as gem from https://rubygems.org/gems/sqlite3).
#
@@ -88,11 +69,11 @@ module ActiveRecord
include Comparable
def initialize(version_string)
- @version = version_string.split('.').map { |v| v.to_i }
+ @version = version_string.split('.').map(&:to_i)
end
def <=>(version_string)
- @version <=> version_string.split('.').map { |v| v.to_i }
+ @version <=> version_string.split('.').map(&:to_i)
end
end
@@ -140,6 +121,7 @@ module ActiveRecord
@config = config
@visitor = Arel::Visitors::SQLite.new self
+ @quoted_column_names = {}
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@prepared_statements = true
@@ -179,10 +161,6 @@ module ActiveRecord
true
end
- def supports_add_column?
- true
- end
-
def supports_views?
true
end
@@ -243,6 +221,12 @@ module ActiveRecord
case value
when BigDecimal
value.to_f
+ when String
+ if value.encoding == Encoding::ASCII_8BIT
+ super(value.encode(Encoding::UTF_8))
+ else
+ super
+ end
else
super
end
@@ -257,20 +241,12 @@ module ActiveRecord
end
def quote_column_name(name) #:nodoc:
- %Q("#{name.to_s.gsub('"', '""')}")
- 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) #:nodoc:
- if value.respond_to?(:usec)
- "#{super}.#{sprintf("%06d", value.usec)}"
- else
- super
- end
+ @quoted_column_names[name] ||= %Q("#{name.to_s.gsub('"', '""')}")
end
+ #--
# DATABASE STATEMENTS ======================================
+ #++
def explain(arel, binds = [])
sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
@@ -292,11 +268,9 @@ module ActiveRecord
end
def exec_query(sql, name = nil, binds = [])
- type_casted_binds = binds.map { |col, val|
- [col, type_cast(val, col)]
- }
+ type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
- log(sql, name, type_casted_binds) do
+ log(sql, name, binds) do
# Don't cache statements if they are not prepared
if without_prepared_statement?(binds)
stmt = @connection.prepare(sql)
@@ -314,7 +288,7 @@ module ActiveRecord
stmt = cache[:stmt]
cols = cache[:cols] ||= stmt.columns
stmt.reset!
- stmt.bind_params type_casted_binds.map { |_, val| val }
+ stmt.bind_params type_casted_binds
end
ActiveRecord::Result.new(cols, stmt.to_a)
@@ -363,7 +337,7 @@ module ActiveRecord
log('commit transaction',nil) { @connection.commit }
end
- def rollback_db_transaction #:nodoc:
+ def exec_rollback_db_transaction #:nodoc:
log('rollback transaction',nil) { @connection.rollback }
end
@@ -399,8 +373,8 @@ module ActiveRecord
end
sql_type = field['type']
- cast_type = lookup_cast_type(sql_type)
- new_column(field['name'], field['dflt_value'], cast_type, sql_type, field['notnull'].to_i == 0)
+ type_metadata = fetch_type_metadata(sql_type)
+ new_column(field['name'], field['dflt_value'], type_metadata, field['notnull'].to_i == 0)
end
end
@@ -430,10 +404,9 @@ module ActiveRecord
end
def primary_key(table_name) #:nodoc:
- column = table_structure(table_name).find { |field|
- field['pk'] == 1
- }
- column && column['name']
+ pks = table_structure(table_name).select { |f| f['pk'] > 0 }
+ return nil unless pks.count == 1
+ pks[0]['name']
end
def remove_index!(table_name, index_name) #:nodoc:
@@ -451,12 +424,12 @@ module ActiveRecord
# See: http://www.sqlite.org/lang_altertable.html
# SQLite has an additional restriction on the ALTER TABLE statement
- def valid_alter_table_options( type, options)
+ def valid_alter_table_type?(type)
type.to_sym != :primary_key
end
def add_column(table_name, column_name, type, options = {}) #:nodoc:
- if supports_add_column? && valid_alter_table_options( type, options )
+ if valid_alter_table_type?(type)
super(table_name, column_name, type, options)
else
alter_table(table_name) do |definition|
@@ -508,12 +481,6 @@ module ActiveRecord
protected
- def initialize_type_map(m)
- super
- m.register_type(/binary/i, SQLite3Binary.new)
- register_class_with_limit m, %r(char)i, SQLite3String
- end
-
def table_structure(table_name)
structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA').to_hash
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
@@ -558,7 +525,7 @@ module ActiveRecord
end
copy_table_indexes(from, to, options[:rename] || {})
copy_table_contents(from, to,
- @definition.columns.map {|column| column.name},
+ @definition.columns.map(&:name),
options[:rename] || {})
end
@@ -571,7 +538,7 @@ module ActiveRecord
name = name[1..-1]
end
- to_column_names = columns(to).map { |c| c.name }
+ to_column_names = columns(to).map(&:name)
columns = index.columns.map {|c| rename[c] || c }.select do |column|
to_column_names.include?(column)
end
@@ -588,25 +555,14 @@ module ActiveRecord
def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
column_mappings = Hash[columns.map {|name| [name, name]}]
rename.each { |a| column_mappings[a.last] = a.first }
- from_columns = columns(from).collect {|col| col.name}
+ from_columns = columns(from).collect(&:name)
columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
+ from_columns_to_copy = columns.map { |col| column_mappings[col] }
quoted_columns = columns.map { |col| quote_column_name(col) } * ','
+ quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ','
- quoted_to = quote_table_name(to)
-
- raw_column_mappings = Hash[columns(from).map { |c| [c.name, c] }]
-
- exec_query("SELECT * FROM #{quote_table_name(from)}").each do |row|
- sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
-
- column_values = columns.map do |col|
- quote(row[column_mappings[col]], raw_column_mappings[col])
- end
-
- sql << column_values * ', '
- sql << ')'
- exec_query sql
- end
+ exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
+ SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
end
def sqlite_version
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index 8f51590c99..24f5849e45 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -1,11 +1,11 @@
module ActiveRecord
module ConnectionHandling
- RAILS_ENV = -> { (Rails.env if defined?(Rails)) || ENV["RAILS_ENV"] || ENV["RACK_ENV"] }
+ RAILS_ENV = -> { (Rails.env if defined?(Rails.env)) || ENV["RAILS_ENV"] || ENV["RACK_ENV"] }
DEFAULT_ENV = -> { RAILS_ENV.call || "default_env" }
# Establishes the connection to the database. Accepts a hash as input where
# the <tt>:adapter</tt> key must be specified with the name of a database adapter (in lower-case)
- # example for regular databases (MySQL, Postgresql, etc):
+ # example for regular databases (MySQL, PostgreSQL, etc):
#
# ActiveRecord::Base.establish_connection(
# adapter: "mysql",
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 952aeaa703..1ad910c4bc 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -85,18 +85,29 @@ module ActiveRecord
mattr_accessor :dump_schema_after_migration, instance_writer: false
self.dump_schema_after_migration = true
- # :nodoc:
+ ##
+ # :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.
+ mattr_accessor :dump_schemas, instance_writer: false
+ self.dump_schemas = :schema_search_path
+
+ ##
+ # :singleton-method:
+ # Specify a threshold for the size of query result sets. If the number of
+ # records in the set exceeds the threshold, a warning is logged. This can
+ # be used to identify queries which load thousands of records and
+ # potentially cause memory bloat.
+ mattr_accessor :warn_on_records_fetched_greater_than, instance_writer: false
+ self.warn_on_records_fetched_greater_than = nil
+
mattr_accessor :maintain_test_schema, instance_accessor: false
- def self.disable_implicit_join_references=(value)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Implicit join references were removed with Rails 4.1.
- Make sure to remove this configuration because it does nothing.
- MSG
- end
+ mattr_accessor :belongs_to_required_by_default, instance_accessor: false
class_attribute :default_connection_handler, instance_writer: false
- class_attribute :find_by_statement_cache
def self.connection_handler
ActiveRecord::RuntimeRegistry.connection_handler || default_connection_handler
@@ -115,23 +126,22 @@ module ActiveRecord
super
end
- def initialize_find_by_cache
- self.find_by_statement_cache = {}.extend(Mutex_m)
+ def initialize_find_by_cache # :nodoc:
+ @find_by_statement_cache = {}.extend(Mutex_m)
end
- def inherited(child_class)
+ def inherited(child_class) # :nodoc:
+ # initialize cache at class definition for thread safety
child_class.initialize_find_by_cache
super
end
- def find(*ids)
+ def find(*ids) # :nodoc:
# We don't have cache keys for this stuff yet
return super unless ids.length == 1
- # Allow symbols to super to maintain compatibility for deprecated finders until Rails 5
- return super if ids.first.kind_of?(Symbol)
return super if block_given? ||
primary_key.nil? ||
- default_scopes.any? ||
+ scope_attributes? ||
columns_hash.include?(inheritance_column) ||
ids.first.kind_of?(Array)
@@ -143,14 +153,13 @@ module ActiveRecord
Please pass the id of the object by calling `.id`
MSG
end
+
key = primary_key
- s = find_by_statement_cache[key] || find_by_statement_cache.synchronize {
- find_by_statement_cache[key] ||= StatementCache.create(connection) { |params|
- where(key => params.bind).limit(1)
- }
+ statement = cached_find_by_statement(key) { |params|
+ where(key => params.bind).limit(1)
}
- record = s.execute([id], self, connection).first
+ record = statement.execute([id], self, connection).first
unless record
raise RecordNotFound, "Couldn't find #{name} with '#{primary_key}'=#{id}"
end
@@ -159,9 +168,8 @@ module ActiveRecord
raise RecordNotFound, "Couldn't find #{name} with an out of range value for '#{primary_key}'"
end
- def find_by(*args)
- return super if current_scope || !(Hash === args.first) || reflect_on_all_aggregations.any?
- return super if default_scopes.any?
+ def find_by(*args) # :nodoc:
+ return super if scope_attributes? || !(Hash === args.first) || reflect_on_all_aggregations.any?
hash = args.first
@@ -172,19 +180,16 @@ module ActiveRecord
# We can't cache Post.find_by(author: david) ...yet
return super unless hash.keys.all? { |k| columns_hash.has_key?(k.to_s) }
- key = hash.keys
+ keys = hash.keys
- klass = self
- s = find_by_statement_cache[key] || find_by_statement_cache.synchronize {
- find_by_statement_cache[key] ||= StatementCache.create(connection) { |params|
- wheres = key.each_with_object({}) { |param,o|
- o[param] = params.bind
- }
- klass.where(wheres).limit(1)
+ statement = cached_find_by_statement(keys) { |params|
+ wheres = keys.each_with_object({}) { |param, o|
+ o[param] = params.bind
}
+ where(wheres).limit(1)
}
begin
- s.execute(hash.values, self, connection).first
+ statement.execute(hash.values, self, connection).first
rescue TypeError => e
raise ActiveRecord::StatementInvalid.new(e.message, e)
rescue RangeError
@@ -192,11 +197,11 @@ module ActiveRecord
end
end
- def find_by!(*args)
+ def find_by!(*args) # :nodoc:
find_by(*args) or raise RecordNotFound.new("Couldn't find #{name}")
end
- def initialize_generated_modules
+ def initialize_generated_modules # :nodoc:
generated_association_methods
end
@@ -217,7 +222,7 @@ module ActiveRecord
elsif !connected?
"#{super} (call '#{super}.connection' to establish a connection)"
elsif table_exists?
- attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', '
+ attr_list = attribute_types.map { |name, type| "#{name}: #{type.type}" } * ', '
"#{super}(#{attr_list})"
else
"#{super}(Table doesn't exist)"
@@ -235,7 +240,7 @@ module ActiveRecord
# scope :published_and_commented, -> { published.and(self.arel_table[:comments_count].gt(0)) }
# end
def arel_table # :nodoc:
- @arel_table ||= Arel::Table.new(table_name, arel_engine)
+ @arel_table ||= Arel::Table.new(table_name, type_caster: type_caster)
end
# Returns the Arel engine.
@@ -248,10 +253,24 @@ module ActiveRecord
end
end
+ def predicate_builder # :nodoc:
+ @predicate_builder ||= PredicateBuilder.new(table_metadata)
+ end
+
+ def type_caster # :nodoc:
+ TypeCaster::Map.new(self)
+ end
+
private
- def relation #:nodoc:
- relation = Relation.create(self, arel_table)
+ def cached_find_by_statement(key, &block) # :nodoc:
+ @find_by_statement_cache[key] || @find_by_statement_cache.synchronize {
+ @find_by_statement_cache[key] ||= StatementCache.create(connection, &block)
+ }
+ end
+
+ def relation # :nodoc:
+ relation = Relation.create(self, arel_table, predicate_builder)
if finder_needs_type_condition?
relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
@@ -259,6 +278,10 @@ module ActiveRecord
relation
end
end
+
+ def table_metadata # :nodoc:
+ TableMetadata.new(self, arel_table)
+ end
end
# New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
@@ -269,32 +292,35 @@ module ActiveRecord
# ==== Example:
# # Instantiates a single new object
# User.new(first_name: 'Jamie')
- def initialize(attributes = nil, options = {})
+ def initialize(attributes = nil)
@attributes = self.class._default_attributes.dup
+ self.class.define_attribute_methods
init_internals
initialize_internals_callback
- self.class.define_attribute_methods
- # +options+ argument is only needed to make protected_attributes gem easier to hook.
- # Remove it when we drop support to this gem.
- init_attributes(attributes, options) if attributes
+ assign_attributes(attributes) if attributes
yield self if block_given?
- _run_initialize_callbacks
+ run_callbacks :initialize
end
- # Initialize an empty model object from +coder+. +coder+ must contain
- # the attributes necessary for initializing an empty model object. For
- # example:
+ # Initialize an empty model object from +coder+. +coder+ should be
+ # the result of previously encoding an Active Record model, using
+ # `encode_with`
#
# class Post < ActiveRecord::Base
# end
#
+ # old_post = Post.new(title: "hello world")
+ # coder = {}
+ # old_post.encode_with(coder)
+ #
# post = Post.allocate
- # post.init_with('attributes' => { 'title' => 'hello world' })
+ # post.init_with(coder)
# post.title # => 'hello world'
def init_with(coder)
+ coder = LegacyYamlAdapter.convert(self.class, coder)
@attributes = coder['attributes']
init_internals
@@ -303,8 +329,8 @@ module ActiveRecord
self.class.define_attribute_methods
- _run_find_callbacks
- _run_initialize_callbacks
+ run_callbacks :find
+ run_callbacks :initialize
self
end
@@ -340,10 +366,7 @@ module ActiveRecord
@attributes = @attributes.dup
@attributes.reset(self.class.primary_key)
- _run_initialize_callbacks
-
- @aggregation_cache = {}
- @association_cache = {}
+ run_callbacks(:initialize)
@new_record = true
@destroyed = false
@@ -368,6 +391,7 @@ module ActiveRecord
coder['raw_attributes'] = attributes_before_type_cast
coder['attributes'] = @attributes
coder['new_record'] = new_record?
+ coder['active_record_yaml_version'] = 1
end
# Returns true if +comparison_object+ is the same exact object, or +comparison_object+
@@ -453,6 +477,7 @@ module ActiveRecord
# Takes a PP and prettily prints this record to it, allowing you to get a nice result from `pp record`
# when pp is required.
def pretty_print(pp)
+ return super if custom_inspect_method_defined?
pp.object_address_group(self) do
if defined?(@attributes) && @attributes
column_names = self.class.column_names.select { |name| has_attribute?(name) || new_record? }
@@ -478,51 +503,8 @@ module ActiveRecord
Hash[methods.map! { |method| [method, public_send(method)] }].with_indifferent_access
end
- def set_transaction_state(state) # :nodoc:
- @transaction_state = state
- end
-
- def has_transactional_callbacks? # :nodoc:
- !_rollback_callbacks.empty? || !_commit_callbacks.empty? || !_create_callbacks.empty?
- end
-
private
- # Updates the attributes on this particular ActiveRecord object so that
- # if it is associated with a transaction, then the state of the AR object
- # will be updated to reflect the current state of the transaction
- #
- # The @transaction_state variable stores the states of the associated
- # transaction. This relies on the fact that a transaction can only be in
- # one rollback or commit (otherwise a list of states would be required)
- # Each AR object inside of a transaction carries that transaction's
- # TransactionState.
- #
- # This method checks to see if the ActiveRecord object's state reflects
- # the TransactionState, and rolls back or commits the ActiveRecord object
- # as appropriate.
- #
- # Since ActiveRecord objects can be inside multiple transactions, this
- # method recursively goes through the parent of the TransactionState and
- # checks if the ActiveRecord object reflects the state of the object.
- def sync_with_transaction_state
- update_attributes_from_transaction_state(@transaction_state, 0)
- end
-
- def update_attributes_from_transaction_state(transaction_state, depth)
- if transaction_state && transaction_state.finalized? && !has_transactional_callbacks?
- unless @reflects_state[depth]
- restore_transaction_record_state if transaction_state.rolledback?
- clear_transaction_record_state
- @reflects_state[depth] = true
- end
-
- if transaction_state.parent && !@reflects_state[depth+1]
- update_attributes_from_transaction_state(transaction_state.parent, depth+1)
- end
- end
- end
-
# Under Ruby 1.9, Array#flatten will call #to_ary (recursively) on each of the elements
# of the array, and then rescues from the possible NoMethodError. If those elements are
# ActiveRecord::Base's, then this triggers the various method_missing's that we have,
@@ -536,10 +518,6 @@ module ActiveRecord
end
def init_internals
- @attributes.ensure_initialized(self.class.primary_key)
-
- @aggregation_cache = {}
- @association_cache = {}
@readonly = false
@destroyed = false
@marked_for_destruction = false
@@ -548,22 +526,19 @@ module ActiveRecord
@txn = nil
@_start_transaction_state = {}
@transaction_state = nil
- @reflects_state = [false]
end
def initialize_internals_callback
end
- # This method is needed to make protected_attributes gem easier to hook.
- # Remove it when we drop support to this gem.
- def init_attributes(attributes, options)
- assign_attributes(attributes)
- end
-
def thaw
if frozen?
@attributes = @attributes.dup
end
end
+
+ def custom_inspect_method_defined?
+ self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner
+ end
end
end
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index 73fd96f979..82596b63df 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -37,10 +37,9 @@ module ActiveRecord
reflection = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
counter_name = reflection.counter_cache_column
- stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
- arel_table[counter_name] => object.send(counter_association).count(:all)
- }, primary_key)
- connection.update stmt
+ unscoped.where(primary_key => object.id).update_all(
+ counter_name => object.send(counter_association).count(:all)
+ )
end
return true
end
@@ -123,16 +122,6 @@ module ActiveRecord
end
end
- protected
-
- def actually_destroyed?
- @_actually_destroyed
- end
-
- def clear_destroy_state
- @_actually_destroyed = nil
- end
-
private
def _create_record(*)
@@ -167,7 +156,7 @@ module ActiveRecord
def each_counter_cached_associations
_reflections.each do |name, reflection|
- yield association(name) if reflection.belongs_to? && reflection.counter_cache_column
+ yield association(name.to_sym) if reflection.belongs_to? && reflection.counter_cache_column
end
end
diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb
index e94b74063e..b6dd6814db 100644
--- a/activerecord/lib/active_record/dynamic_matchers.rb
+++ b/activerecord/lib/active_record/dynamic_matchers.rb
@@ -1,10 +1,5 @@
module ActiveRecord
module DynamicMatchers #:nodoc:
- # This code in this file seems to have a lot of indirection, but the indirection
- # is there to provide extension points for the activerecord-deprecated_finders
- # gem. When we stop supporting activerecord-deprecated_finders (from Rails 5),
- # then we can remove the indirection.
-
def respond_to?(name, include_private = false)
if self == Base
super
@@ -72,26 +67,14 @@ module ActiveRecord
CODE
end
- def body
- raise NotImplementedError
- end
- end
+ private
- module Finder
- # Extended in activerecord-deprecated_finders
def body
- result
- end
-
- # Extended in activerecord-deprecated_finders
- def result
"#{finder}(#{attributes_hash})"
end
# The parameters in the signature may have reserved Ruby words, in order
# to prevent errors, we start each param name with `_`.
- #
- # Extended in activerecord-deprecated_finders
def signature
attribute_names.map { |name| "_#{name}" }.join(', ')
end
@@ -109,7 +92,6 @@ module ActiveRecord
class FindBy < Method
Method.matchers << self
- include Finder
def self.prefix
"find_by"
@@ -122,7 +104,6 @@ module ActiveRecord
class FindByBang < Method
Method.matchers << self
- include Finder
def self.prefix
"find_by"
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 5958373e88..ea88983917 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -32,6 +32,12 @@ module ActiveRecord
# Conversation.active
# Conversation.archived
#
+ # Of course, you can also query them directly if the scopes doesn't fit your
+ # needs:
+ #
+ # Conversation.where(status: [:active, :archived])
+ # Conversation.where.not(status: :active)
+ #
# You can set the default value from the database declaration, like:
#
# create_table :conversations do |t|
@@ -59,15 +65,17 @@ module ActiveRecord
#
# In rare circumstances you might need to access the mapping directly.
# The mappings are exposed through a class method with the pluralized attribute
- # name:
+ # name, which return the mapping in a +HashWithIndifferentAccess+:
#
- # Conversation.statuses # => { "active" => 0, "archived" => 1 }
+ # Conversation.statuses[:active] # => 0
+ # Conversation.statuses["archived"] # => 1
#
- # Use that class method when you need to know the ordinal value of an enum:
+ # Use that class method when you need to know the ordinal value of an enum.
+ # For example, you can use that when manually building SQL strings:
#
# Conversation.where("status <> ?", Conversation.statuses[:archived])
#
- # Where conditions on an enum attribute must use the ordinal value of an enum.
+
module Enum
def self.extended(base) # :nodoc:
base.class_attribute(:defined_enums)
@@ -79,6 +87,38 @@ module ActiveRecord
super
end
+ class EnumType < Type::Value
+ def initialize(name, mapping)
+ @name = name
+ @mapping = mapping
+ end
+
+ def cast(value)
+ return if value.blank?
+
+ if mapping.has_key?(value)
+ value.to_s
+ elsif mapping.has_value?(value)
+ mapping.key(value)
+ else
+ raise ArgumentError, "'#{value}' is not a valid #{name}"
+ end
+ end
+
+ def deserialize(value)
+ return if value.nil?
+ mapping.key(value.to_i)
+ end
+
+ def serialize(value)
+ mapping.fetch(value, value)
+ end
+
+ protected
+
+ attr_reader :name, :mapping
+ end
+
def enum(definitions)
klass = self
definitions.each do |name, values|
@@ -90,37 +130,19 @@ module ActiveRecord
detect_enum_conflict!(name, name.to_s.pluralize, true)
klass.singleton_class.send(:define_method, name.to_s.pluralize) { enum_values }
- _enum_methods_module.module_eval do
- # def status=(value) self[:status] = statuses[value] end
- klass.send(:detect_enum_conflict!, name, "#{name}=")
- define_method("#{name}=") { |value|
- if enum_values.has_key?(value) || value.blank?
- self[name] = enum_values[value]
- elsif enum_values.has_value?(value)
- # Assigning a value directly is not a end-user feature, hence it's not documented.
- # This is used internally to make building objects from the generated scopes work
- # as expected, i.e. +Conversation.archived.build.archived?+ should be true.
- self[name] = value
- else
- raise ArgumentError, "'#{value}' is not a valid #{name}"
- end
- }
-
- # def status() statuses.key self[:status] end
- klass.send(:detect_enum_conflict!, name, name)
- define_method(name) { enum_values.key self[name] }
+ detect_enum_conflict!(name, name)
+ detect_enum_conflict!(name, "#{name}=")
- # def status_before_type_cast() statuses.key self[:status] end
- klass.send(:detect_enum_conflict!, name, "#{name}_before_type_cast")
- define_method("#{name}_before_type_cast") { enum_values.key self[name] }
+ attribute name, EnumType.new(name, enum_values)
+ _enum_methods_module.module_eval do
pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
pairs.each do |value, i|
enum_values[value] = i
# def active?() status == 0 end
klass.send(:detect_enum_conflict!, name, "#{value}?")
- define_method("#{value}?") { self[name] == i }
+ define_method("#{value}?") { self[name] == value.to_s }
# def active!() update! status: :active end
klass.send(:detect_enum_conflict!, name, "#{value}!")
@@ -128,7 +150,7 @@ module ActiveRecord
# scope :active, -> { where status: 0 }
klass.send(:detect_enum_conflict!, name, value, true)
- klass.scope value, -> { klass.where name => i }
+ klass.scope value, -> { klass.where name => value }
end
end
defined_enums[name.to_s] = enum_values
@@ -138,25 +160,7 @@ module ActiveRecord
private
def _enum_methods_module
@_enum_methods_module ||= begin
- mod = Module.new do
- private
- def save_changed_attribute(attr_name, old)
- if (mapping = self.class.defined_enums[attr_name.to_s])
- value = read_attribute(attr_name)
- if attribute_changed?(attr_name)
- if mapping[old] == value
- clear_attribute_changes([attr_name])
- end
- else
- if old != value
- set_attribute_was(attr_name, mapping.key(old))
- end
- end
- else
- super
- end
- end
- end
+ mod = Module.new
include mod
mod
end
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 5b3fdf16f5..98aee77557 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -52,10 +52,29 @@ module ActiveRecord
# Raised by ActiveRecord::Base.save! and ActiveRecord::Base.create! methods when record cannot be
# saved because record is invalid.
class RecordNotSaved < ActiveRecordError
+ attr_reader :record
+
+ def initialize(message, record = nil)
+ @record = record
+ super(message)
+ end
end
# Raised by ActiveRecord::Base.destroy! when a call to destroy would return false.
+ #
+ # begin
+ # complex_operation_that_internally_calls_destroy!
+ # rescue ActiveRecord::RecordNotDestroyed => invalid
+ # puts invalid.record.errors
+ # end
+ #
class RecordNotDestroyed < ActiveRecordError
+ attr_reader :record
+
+ def initialize(record)
+ @record = record
+ super()
+ end
end
# Superclass for all database execution errors.
@@ -160,23 +179,14 @@ module ActiveRecord
end
# Raised when unknown attributes are supplied via mass assignment.
- class UnknownAttributeError < NoMethodError
-
- attr_reader :record, :attribute
-
- def initialize(record, attribute)
- @record = record
- @attribute = attribute.to_s
- super("unknown attribute '#{attribute}' for #{@record.class}.")
- end
-
- end
+ UnknownAttributeError = ActiveModel::UnknownAttributeError
# Raised when an error occurred while doing a mass assignment to an attribute through the
# +attributes=+ method. The exception has an +attribute+ property that is the name of the
# offending attribute.
class AttributeAssignmentError < ActiveRecordError
attr_reader :exception, :attribute
+
def initialize(message, exception, attribute)
super(message)
@exception = exception
@@ -189,6 +199,7 @@ module ActiveRecord
# objects, each corresponding to the error while assigning to an attribute.
class MultiparameterAssignmentErrors < ActiveRecordError
attr_reader :errors
+
def initialize(errors)
@errors = errors
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index db6421dacb..2c1771dd6c 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -1,6 +1,7 @@
require 'erb'
require 'yaml'
require 'zlib'
+require 'set'
require 'active_support/dependencies'
require 'active_support/core_ext/digest/uuid'
require 'active_record/fixture_set/file'
@@ -131,20 +132,20 @@ module ActiveRecord
# Digest::SHA2.hexdigest(File.read(Rails.root.join('test/fixtures', path)))
# end
# end
- # ActiveRecord::FixtureSet.context_class.send :include, FixtureFileHelpers
+ # ActiveRecord::FixtureSet.context_class.include FixtureFileHelpers
#
# - use the helper method in a fixture
# photo:
# name: kitten.png
# sha: <%= file_sha 'files/kitten.png' %>
#
- # = Transactional Fixtures
+ # = Transactional Tests
#
# Test cases can use begin+rollback to isolate their changes to the database instead of having to
# delete+insert for every test case.
#
# class FooTest < ActiveSupport::TestCase
- # self.use_transactional_fixtures = true
+ # self.use_transactional_tests = true
#
# test "godzilla" do
# assert !Foo.all.empty?
@@ -158,14 +159,14 @@ module ActiveRecord
# end
#
# If you preload your test database with all fixture data (probably in the rake task) and use
- # transactional fixtures, then you may omit all fixtures declarations in your test cases since
+ # transactional tests, then you may omit all fixtures declarations in your test cases since
# all the data's already there and every case rolls back its changes.
#
# In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to
# true. This will provide access to fixture data for every table that has been loaded through
# fixtures (depending on the value of +use_instantiated_fixtures+).
#
- # When *not* to use transactional fixtures:
+ # When *not* to use transactional tests:
#
# 1. You're testing whether a transaction works correctly. Nested transactions don't commit until
# all parent transactions commit, particularly, the fixtures transaction which is begun in setup
@@ -521,12 +522,16 @@ module ActiveRecord
update_all_loaded_fixtures fixtures_map
connection.transaction(:requires_new => true) do
+ deleted_tables = Set.new
fixture_sets.each do |fs|
conn = fs.model_class.respond_to?(:connection) ? fs.model_class.connection : connection
table_rows = fs.table_rows
table_rows.each_key do |table|
- conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete'
+ unless deleted_tables.include? table
+ conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete'
+ end
+ deleted_tables << table
end
table_rows.each do |fixture_set_name, rows|
@@ -534,12 +539,10 @@ module ActiveRecord
conn.insert_fixture(row, fixture_set_name)
end
end
- end
- # Cap primary key sequences to max(pk).
- if connection.respond_to?(:reset_pk_sequence!)
- fixture_sets.each do |fs|
- connection.reset_pk_sequence!(fs.table_name)
+ # Cap primary key sequences to max(pk).
+ if conn.respond_to?(:reset_pk_sequence!)
+ conn.reset_pk_sequence!(fs.table_name)
end
end
end
@@ -633,7 +636,7 @@ module ActiveRecord
# interpolate the fixture label
row.each do |key, value|
- row[key] = value.gsub("$LABEL", label) if value.is_a?(String)
+ row[key] = value.gsub("$LABEL", label.to_s) if value.is_a?(String)
end
# generate a primary key if necessary
@@ -661,7 +664,7 @@ module ActiveRecord
row[association.foreign_type] = $1
end
- fk_type = association.active_record.columns_hash[fk_name].type
+ fk_type = association.active_record.type_for_attribute(fk_name).type
row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type)
end
when :has_many
@@ -691,7 +694,7 @@ module ActiveRecord
end
def primary_key_type
- @association.klass.column_types[@association.klass.primary_key].type
+ @association.klass.type_for_attribute(@association.klass.primary_key).type
end
end
@@ -703,6 +706,10 @@ module ActiveRecord
def lhs_key
@association.through_reflection.foreign_key
end
+
+ def join_table
+ @association.through_reflection.table_name
+ end
end
private
@@ -711,7 +718,7 @@ module ActiveRecord
end
def primary_key_type
- @primary_key_type ||= model_class && model_class.column_types[model_class.primary_key].type
+ @primary_key_type ||= model_class && model_class.type_for_attribute(model_class.primary_key).type
end
def add_join_records(rows, row, association)
@@ -745,7 +752,7 @@ module ActiveRecord
end
def column_names
- @column_names ||= @connection.columns(@table_name).collect { |c| c.name }
+ @column_names ||= @connection.columns(@table_name).collect(&:name)
end
def read_fixture_files(path, model_class)
@@ -768,12 +775,6 @@ module ActiveRecord
end
- #--
- # Deprecate 'Fixtures' in favor of 'FixtureSet'.
- #++
- # :nodoc:
- Fixtures = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('ActiveRecord::Fixtures', 'ActiveRecord::FixtureSet')
-
class Fixture #:nodoc:
include Enumerable
@@ -806,7 +807,9 @@ module ActiveRecord
def find
if model_class
- model_class.find(fixture[model_class.primary_key])
+ model_class.unscoped do
+ model_class.find(fixture[model_class.primary_key])
+ end
else
raise FixtureClassNotFound, "No class attached to find."
end
@@ -832,13 +835,15 @@ module ActiveRecord
class_attribute :fixture_path, :instance_writer => false
class_attribute :fixture_table_names
class_attribute :fixture_class_names
+ class_attribute :use_transactional_tests
class_attribute :use_transactional_fixtures
class_attribute :use_instantiated_fixtures # true, false, or :no_instances
class_attribute :pre_loaded_fixtures
class_attribute :config
+ singleton_class.deprecate 'use_transactional_fixtures=' => 'use use_transactional_tests= instead'
+
self.fixture_table_names = []
- self.use_transactional_fixtures = true
self.use_instantiated_fixtures = false
self.pre_loaded_fixtures = false
self.config = ActiveRecord::Base
@@ -846,6 +851,16 @@ module ActiveRecord
self.fixture_class_names = Hash.new do |h, fixture_set_name|
h[fixture_set_name] = ActiveRecord::FixtureSet.default_fixture_model_name(fixture_set_name, self.config)
end
+
+ silence_warnings do
+ define_singleton_method :use_transactional_tests do
+ if use_transactional_fixtures.nil?
+ true
+ else
+ use_transactional_fixtures
+ end
+ end
+ end
end
module ClassMethods
@@ -866,7 +881,7 @@ module ActiveRecord
fixture_set_names = Dir["#{fixture_path}/{**,*}/*.{yml}"]
fixture_set_names.map! { |f| f[(fixture_path.to_s.size + 1)..-5] }
else
- fixture_set_names = fixture_set_names.flatten.map { |n| n.to_s }
+ fixture_set_names = fixture_set_names.flatten.map(&:to_s)
end
self.fixture_table_names |= fixture_set_names
@@ -886,7 +901,7 @@ module ActiveRecord
@fixture_cache[fs_name] ||= {}
instances = fixture_names.map do |f_name|
- f_name = f_name.to_s
+ f_name = f_name.to_s if f_name.is_a?(Symbol)
@fixture_cache[fs_name].delete(f_name) if force_reload
if @loaded_fixtures[fs_name][f_name]
@@ -906,7 +921,7 @@ module ActiveRecord
def uses_transaction(*methods)
@uses_transaction = [] unless defined?(@uses_transaction)
- @uses_transaction.concat methods.map { |m| m.to_s }
+ @uses_transaction.concat methods.map(&:to_s)
end
def uses_transaction?(method)
@@ -916,13 +931,13 @@ module ActiveRecord
end
def run_in_transaction?
- use_transactional_fixtures &&
+ use_transactional_tests &&
!self.class.uses_transaction?(method_name)
end
def setup_fixtures(config = ActiveRecord::Base)
- if pre_loaded_fixtures && !use_transactional_fixtures
- raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
+ if pre_loaded_fixtures && !use_transactional_tests
+ raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_tests'
end
@fixture_cache = {}
diff --git a/activerecord/lib/active_record/gem_version.rb b/activerecord/lib/active_record/gem_version.rb
index e820835626..a388b529c9 100644
--- a/activerecord/lib/active_record/gem_version.rb
+++ b/activerecord/lib/active_record/gem_version.rb
@@ -5,10 +5,10 @@ module ActiveRecord
end
module VERSION
- MAJOR = 4
- MINOR = 2
+ MAJOR = 5
+ MINOR = 0
TINY = 0
- PRE = "beta4"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index f58145ab05..24098f72dc 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -79,16 +79,6 @@ module ActiveRecord
:true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true)
end
- def symbolized_base_class
- ActiveSupport::Deprecation.warn('`ActiveRecord::Base.symbolized_base_class` is deprecated and will be removed without replacement.')
- @symbolized_base_class ||= base_class.to_s.to_sym
- end
-
- def symbolized_sti_name
- ActiveSupport::Deprecation.warn('`ActiveRecord::Base.symbolized_sti_name` is deprecated and will be removed without replacement.')
- @symbolized_sti_name ||= sti_name.present? ? sti_name.to_sym : symbolized_base_class
- end
-
# Returns the class descending directly from ActiveRecord::Base, or
# an abstract class, if any, in the inheritance hierarchy.
#
@@ -192,7 +182,7 @@ module ActiveRecord
def type_condition(table = arel_table)
sti_column = table[inheritance_column]
- sti_names = ([self] + descendants).map { |model| model.sti_name }
+ sti_names = ([self] + descendants).map(&:sti_name)
sti_column.in(sti_names)
end
@@ -202,7 +192,7 @@ module ActiveRecord
# If this is a StrongParameters hash, and access to inheritance_column is not permitted,
# this will ignore the inheritance column and return nil
def subclass_from_attributes?(attrs)
- columns_hash.include?(inheritance_column) && attrs.is_a?(Hash)
+ attribute_names.include?(inheritance_column) && attrs.is_a?(Hash)
end
def subclass_from_attributes(attrs)
diff --git a/activerecord/lib/active_record/legacy_yaml_adapter.rb b/activerecord/lib/active_record/legacy_yaml_adapter.rb
new file mode 100644
index 0000000000..89dee58423
--- /dev/null
+++ b/activerecord/lib/active_record/legacy_yaml_adapter.rb
@@ -0,0 +1,46 @@
+module ActiveRecord
+ module LegacyYamlAdapter
+ def self.convert(klass, coder)
+ return coder unless coder.is_a?(Psych::Coder)
+
+ case coder["active_record_yaml_version"]
+ when 1 then coder
+ else
+ if coder["attributes"].is_a?(AttributeSet)
+ Rails420.convert(klass, coder)
+ else
+ Rails41.convert(klass, coder)
+ end
+ end
+ end
+
+ module Rails420
+ def self.convert(klass, coder)
+ attribute_set = coder["attributes"]
+
+ klass.attribute_names.each do |attr_name|
+ attribute = attribute_set[attr_name]
+ if attribute.type.is_a?(Delegator)
+ type_from_klass = klass.type_for_attribute(attr_name)
+ attribute_set[attr_name] = attribute.with_type(type_from_klass)
+ end
+ end
+
+ coder
+ end
+ end
+
+ module Rails41
+ def self.convert(klass, coder)
+ attributes = klass.attributes_builder
+ .build_from_database(coder["attributes"])
+ new_record = coder["attributes"][klass.primary_key].blank?
+
+ {
+ "attributes" => attributes,
+ "new_record" => new_record,
+ }
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/locale/en.yml b/activerecord/lib/active_record/locale/en.yml
index b1fbd38622..8a3c27e6da 100644
--- a/activerecord/lib/active_record/locale/en.yml
+++ b/activerecord/lib/active_record/locale/en.yml
@@ -7,6 +7,7 @@ en:
# Default error messages
errors:
messages:
+ required: "must exist"
taken: "has already been taken"
# Active Record models configuration
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 52eeb8ae1f..a09437b4b0 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -11,7 +11,7 @@ module ActiveRecord
#
# == Usage
#
- # Active Records support optimistic locking if the field +lock_version+ is present. Each update to the
+ # Active Record supports optimistic locking if the +lock_version+ field is present. Each update to the
# record increments the +lock_version+ column and the locking facilities ensure that records instantiated twice
# will let the last one saved raise a +StaleObjectError+ if the first was also updated. Example:
#
@@ -66,6 +66,15 @@ module ActiveRecord
send(lock_col + '=', previous_lock_value + 1)
end
+ def _create_record(attribute_names = self.attribute_names, *) # :nodoc:
+ if locking_enabled?
+ # We always want to persist the locking version, even if we don't detect
+ # a change from the default, since the database might have no default
+ attribute_names |= [self.class.locking_column]
+ end
+ super
+ end
+
def _update_record(attribute_names = self.attribute_names) #:nodoc:
return super unless locking_enabled?
return 0 if attribute_names.empty?
@@ -80,17 +89,15 @@ module ActiveRecord
begin
relation = self.class.unscoped
- stmt = relation.where(
- relation.table[self.class.primary_key].eq(id).and(
- relation.table[lock_col].eq(self.class.quote_value(previous_lock_value, column_for_attribute(lock_col)))
- )
- ).arel.compile_update(
- arel_attributes_with_values_for_update(attribute_names),
- self.class.primary_key
+ affected_rows = relation.where(
+ self.class.primary_key => id,
+ lock_col => previous_lock_value,
+ ).update_all(
+ attributes_for_update(attribute_names).map do |name|
+ [name, _read_attribute(name)]
+ end.to_h
)
- affected_rows = self.class.connection.update stmt
-
unless affected_rows == 1
raise ActiveRecord::StaleObjectError.new(self, "update")
end
@@ -118,12 +125,8 @@ module ActiveRecord
relation = super
if locking_enabled?
- column_name = self.class.locking_column
- column = self.class.columns_hash[column_name]
- substitute = self.class.connection.substitute_at(column, relation.bind_values.length)
-
- relation = relation.where(self.class.arel_table[column_name].eq(substitute))
- relation.bind_values << [column, self[column_name].to_i]
+ locking_column = self.class.locking_column
+ relation = relation.where(locking_column => _read_attribute(locking_column))
end
relation
@@ -141,7 +144,7 @@ module ActiveRecord
# Set the column to use for optimistic locking. Defaults to +lock_version+.
def locking_column=(value)
- clear_caches_calculated_from_columns
+ reload_schema_from_cache
@locking_column = value.to_s
end
@@ -181,17 +184,12 @@ module ActiveRecord
end
end
- class LockingType < SimpleDelegator # :nodoc:
- def type_cast_from_database(value)
+ class LockingType < DelegateClass(Type::Value) # :nodoc:
+ def deserialize(value)
# `nil` *should* be changed to 0
super.to_i
end
- def changed?(old_value, *)
- # Ensure we save if the default was `nil`
- super || old_value == 0
- end
-
def init_with(coder)
__setobj__(coder['subtype'])
end
diff --git a/activerecord/lib/active_record/locking/pessimistic.rb b/activerecord/lib/active_record/locking/pessimistic.rb
index ff7102d35b..3d95c54ef3 100644
--- a/activerecord/lib/active_record/locking/pessimistic.rb
+++ b/activerecord/lib/active_record/locking/pessimistic.rb
@@ -51,7 +51,7 @@ module ActiveRecord
# end
#
# Database-specific information on row locking:
- # MySQL: http://dev.mysql.com/doc/refman/5.1/en/innodb-locking-reads.html
+ # MySQL: http://dev.mysql.com/doc/refman/5.6/en/innodb-locking-reads.html
# PostgreSQL: http://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
module Pessimistic
# Obtain a row lock on this record. Reloads the record to obtain the requested
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index eb64d197f0..af816a278e 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -20,24 +20,21 @@ module ActiveRecord
@odd = false
end
- def render_bind(column, value)
- if column
- if column.binary?
- # This specifically deals with the PG adapter that casts bytea columns into a Hash.
- value = value[:value] if value.is_a?(Hash)
- value = value ? "<#{value.bytesize} bytes of binary data>" : "<NULL binary data>"
- end
-
- [column.name, value]
+ def render_bind(attribute)
+ value = if attribute.type.binary? && attribute.value
+ "<#{attribute.value.bytesize} bytes of binary data>"
else
- [nil, value]
+ attribute.value_for_database
end
+
+ [attribute.name, value]
end
def sql(event)
- self.class.runtime += event.duration
return unless logger.debug?
+ self.class.runtime += event.duration
+
payload = event.payload
return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
@@ -47,9 +44,7 @@ module ActiveRecord
binds = nil
unless (payload[:binds] || []).empty?
- binds = " " + payload[:binds].map { |col,v|
- render_bind(col, v)
- }.inspect
+ binds = " " + payload[:binds].map { |attr| render_bind(attr) }.inspect
end
if odd?
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 2024b225e4..a83b90a95f 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -39,7 +39,7 @@ module ActiveRecord
class PendingMigrationError < MigrationError#:nodoc:
def initialize
- if defined?(Rails)
+ if defined?(Rails.env)
super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rake db:migrate RAILS_ENV=#{::Rails.env}")
else
super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rake db:migrate")
@@ -168,7 +168,7 @@ module ActiveRecord
# This will generate the file <tt>timestamp_add_fieldname_to_tablename</tt>, which will look like this:
# class AddFieldnameToTablename < ActiveRecord::Migration
# def change
- # add_column :tablenames, :field, :string
+ # add_column :tablenames, :fieldname, :string
# end
# end
#
@@ -184,8 +184,8 @@ module ActiveRecord
# you wish to downgrade. Alternatively, you can also use the STEP option if you
# wish to rollback last few migrations. <tt>rake db:migrate STEP=2</tt> will rollback
# the latest two migrations.
- #
- # If any of the migrations throw an <tt>ActiveRecord::IrreversibleMigration</tt> exception,
+ #
+ # If any of the migrations throw an <tt>ActiveRecord::IrreversibleMigration</tt> exception,
# that step will fail and you'll have some manual work to do.
#
# == Database support
@@ -395,7 +395,14 @@ module ActiveRecord
def load_schema_if_pending!
if ActiveRecord::Migrator.needs_migration? || !ActiveRecord::Migrator.any_migrations?
- ActiveRecord::Tasks::DatabaseTasks.load_schema_current_if_exists
+ # Roundtrip to Rake to allow plugins to hook into database initialization.
+ FileUtils.cd Rails.root do
+ current_config = Base.connection_config
+ Base.clear_all_connections!
+ system("bin/rake db:test:prepare")
+ # Establish a new connection, the old database may be gone (db:test:prepare uses purge)
+ Base.establish_connection(current_config)
+ end
check_pending!
end
end
@@ -640,7 +647,7 @@ module ActiveRecord
end
def method_missing(method, *arguments, &block)
- arg_list = arguments.map{ |a| a.inspect } * ', '
+ arg_list = arguments.map(&:inspect) * ', '
say_with_time "#{method}(#{arg_list})" do
unless @connection.respond_to? :revert
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index a444aac23c..3674f672cb 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -111,17 +111,6 @@ module ActiveRecord
# class Mouse < ActiveRecord::Base
# self.table_name = "mice"
# end
- #
- # Alternatively, you can override the table_name method to define your
- # own computation. (Possibly using <tt>super</tt> to manipulate the default
- # table name.) Example:
- #
- # class Post < ActiveRecord::Base
- # def self.table_name
- # "special_" + super
- # end
- # end
- # Post.table_name # => "special_posts"
def table_name
reset_table_name unless defined?(@table_name)
@table_name
@@ -132,9 +121,6 @@ module ActiveRecord
# class Project < ActiveRecord::Base
# self.table_name = "project"
# end
- #
- # You can also just define your own <tt>self.table_name</tt> method; see
- # the documentation for ActiveRecord::Base#table_name.
def table_name=(value)
value = value && value.to_s
@@ -147,7 +133,7 @@ module ActiveRecord
@quoted_table_name = nil
@arel_table = nil
@sequence_name = nil unless defined?(@explicit_sequence_name) && @explicit_sequence_name
- @relation = Relation.create(self, arel_table)
+ @predicate_builder = nil
end
# Returns a quoted version of the table name, used to construct SQL statements.
@@ -231,33 +217,42 @@ module ActiveRecord
end
def attributes_builder # :nodoc:
- @attributes_builder ||= AttributeSet::Builder.new(column_types)
+ @attributes_builder ||= AttributeSet::Builder.new(attribute_types, primary_key)
end
- def column_types # :nodoc:
- @column_types ||= columns_hash.transform_values(&:cast_type).tap do |h|
- h.default = Type::Value.new
- end
+ def columns_hash # :nodoc:
+ load_schema
+ @columns_hash
+ end
+
+ def columns
+ load_schema
+ @columns ||= columns_hash.values
+ end
+
+ def attribute_types # :nodoc:
+ load_schema
+ @attribute_types ||= Hash.new(Type::Value.new)
end
def type_for_attribute(attr_name) # :nodoc:
- column_types[attr_name]
+ attribute_types[attr_name]
end
# Returns a hash where the keys are column names and the values are
# default values when instantiating the AR object for this table.
def column_defaults
+ load_schema
_default_attributes.to_hash
end
def _default_attributes # :nodoc:
- @default_attributes ||= attributes_builder.build_from_database(
- raw_default_values)
+ @default_attributes ||= AttributeSet.new({})
end
# Returns an array of column names as strings.
def column_names
- @column_names ||= columns.map { |column| column.name }
+ @column_names ||= columns.map(&:name)
end
# Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
@@ -295,19 +290,49 @@ module ActiveRecord
def reset_column_information
connection.clear_cache!
undefine_attribute_methods
- connection.schema_cache.clear_table_cache!(table_name) if table_exists?
+ connection.schema_cache.clear_table_cache!(table_name)
- @arel_engine = nil
- @column_names = nil
- @column_types = nil
- @content_columns = nil
- @default_attributes = nil
- @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column
- @relation = nil
+ reload_schema_from_cache
end
private
+ def schema_loaded?
+ defined?(@columns_hash) && @columns_hash
+ end
+
+ def load_schema
+ unless schema_loaded?
+ load_schema!
+ end
+ end
+
+ def load_schema!
+ @columns_hash = connection.schema_cache.columns_hash(table_name)
+ @columns_hash.each do |name, column|
+ define_attribute(
+ name,
+ connection.lookup_cast_type_from_column(column),
+ default: column.default,
+ user_provided_default: false
+ )
+ end
+ end
+
+ def reload_schema_from_cache
+ @arel_engine = nil
+ @arel_table = nil
+ @column_names = nil
+ @attribute_types = nil
+ @content_columns = nil
+ @default_attributes = nil
+ @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column
+ @attributes_builder = nil
+ @columns = nil
+ @columns_hash = nil
+ @attribute_names = nil
+ end
+
# Guesses the table name, but does not decorate it with prefix and suffix information.
def undecorated_table_name(class_name = base_class.name)
table_name = class_name.to_s.demodulize.underscore
@@ -331,10 +356,6 @@ module ActiveRecord
base.table_name
end
end
-
- def raw_default_values
- columns_hash.transform_values(&:default)
- end
end
end
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 8a2a06f2ca..084ef397a8 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -81,6 +81,9 @@ module ActiveRecord
#
# Note that the model will _not_ be destroyed until the parent is saved.
#
+ # Also note that the model will not be destroyed unless you also specify
+ # its id in the updated hash.
+ #
# === One-to-many
#
# Consider a member that has a number of posts:
@@ -111,7 +114,7 @@ module ActiveRecord
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
#
- # You may also set a :reject_if proc to silently ignore any new record
+ # You may also set a +:reject_if+ proc to silently ignore any new record
# hashes if they fail to pass your criteria. For example, the previous
# example could be rewritten as:
#
@@ -133,7 +136,7 @@ module ActiveRecord
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
#
- # Alternatively, :reject_if also accepts a symbol for using methods:
+ # Alternatively, +:reject_if+ also accepts a symbol for using methods:
#
# class Member < ActiveRecord::Base
# has_many :posts
@@ -212,13 +215,13 @@ module ActiveRecord
# All changes to models, including the destruction of those marked for
# destruction, are saved and destroyed automatically and atomically when
# the parent model is saved. This happens inside the transaction initiated
- # by the parents save method. See ActiveRecord::AutosaveAssociation.
+ # by the parent's save method. See ActiveRecord::AutosaveAssociation.
#
# === Validating the presence of a parent model
#
# If you want to validate that a child record is associated with a parent
- # record, you can use <tt>validates_presence_of</tt> and
- # <tt>inverse_of</tt> as this example illustrates:
+ # record, you can use the +validates_presence_of+ method and the +:inverse_of+
+ # key as this example illustrates:
#
# class Member < ActiveRecord::Base
# has_many :posts, inverse_of: :member
@@ -230,7 +233,7 @@ module ActiveRecord
# validates_presence_of :member
# end
#
- # Note that if you do not specify the <tt>inverse_of</tt> option, then
+ # Note that if you do not specify the +:inverse_of+ option, then
# Active Record will try to automatically guess the inverse association
# based on heuristics.
#
@@ -264,29 +267,31 @@ module ActiveRecord
# Allows you to specify a Proc or a Symbol pointing to a method
# that checks whether a record should be built for a certain attribute
# hash. The hash is passed to the supplied Proc or the method
- # and it should return either +true+ or +false+. When no :reject_if
+ # and it should return either +true+ or +false+. When no +:reject_if+
# is specified, a record will be built for all attribute hashes that
# do not have a <tt>_destroy</tt> value that evaluates to true.
# Passing <tt>:all_blank</tt> instead of a Proc will create a proc
# that will reject a record where all the attributes are blank excluding
- # any value for _destroy.
+ # any value for +_destroy+.
# [:limit]
- # Allows you to specify the maximum number of the associated records that
- # can be processed with the nested attributes. Limit also can be specified as a
- # Proc or a Symbol pointing to a method that should return number. If the size of the
- # nested attributes array exceeds the specified limit, NestedAttributes::TooManyRecords
- # exception is raised. If omitted, any number associations can be processed.
- # Note that the :limit option is only applicable to one-to-many associations.
+ # Allows you to specify the maximum number of associated records that
+ # can be processed with the nested attributes. Limit also can be specified
+ # as a Proc or a Symbol pointing to a method that should return a number.
+ # If the size of the nested attributes array exceeds the specified limit,
+ # NestedAttributes::TooManyRecords exception is raised. If omitted, any
+ # number of associations can be processed.
+ # Note that the +:limit+ option is only applicable to one-to-many
+ # associations.
# [:update_only]
# For a one-to-one association, this option allows you to specify how
- # nested attributes are to be used when an associated record already
+ # nested attributes are going to be used when an associated record already
# exists. In general, an existing record may either be updated with the
# new set of attribute values or be replaced by a wholly new record
- # containing those values. By default the :update_only option is +false+
+ # containing those values. By default the +:update_only+ option is +false+
# and the nested attributes are used to update the existing record only
# if they include the record's <tt>:id</tt> value. Otherwise a new
# record will be instantiated and used to replace the existing one.
- # However if the :update_only option is +true+, the nested attributes
+ # However if the +:update_only+ option is +true+, the nested attributes
# are used to update the record's attributes always, regardless of
# whether the <tt>:id</tt> is present. The option is ignored for collection
# associations.
@@ -307,7 +312,7 @@ module ActiveRecord
attr_names.each do |association_name|
if reflection = _reflect_on_association(association_name)
reflection.autosave = true
- add_autosave_association_callbacks(reflection)
+ define_autosave_validation_callbacks(reflection)
nested_attributes_options = self.nested_attributes_options.dup
nested_attributes_options[association_name.to_sym] = options
@@ -516,7 +521,7 @@ module ActiveRecord
# Determines if a hash contains a truthy _destroy key.
def has_destroy_flag?(hash)
- Type::Boolean.new.type_cast_from_user(hash['_destroy'])
+ Type::Boolean.new.cast(hash['_destroy'])
end
# Determines if a new record should be rejected by checking
diff --git a/activerecord/lib/active_record/no_touching.rb b/activerecord/lib/active_record/no_touching.rb
index dbf4564ae5..edb5066fa0 100644
--- a/activerecord/lib/active_record/no_touching.rb
+++ b/activerecord/lib/active_record/no_touching.rb
@@ -45,7 +45,7 @@ module ActiveRecord
NoTouching.applied_to?(self.class)
end
- def touch(*)
+ def touch(*) # :nodoc:
super unless no_touching?
end
end
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
index 807c301596..74894d0c37 100644
--- a/activerecord/lib/active_record/null_relation.rb
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -14,7 +14,7 @@ module ActiveRecord
0
end
- def update_all(_updates, _conditions = nil, _options = {})
+ def update_all(_updates)
0
end
@@ -30,10 +30,18 @@ module ActiveRecord
true
end
+ def none?
+ true
+ end
+
def any?
false
end
+ def one?
+ false
+ end
+
def many?
false
end
@@ -62,9 +70,7 @@ module ActiveRecord
calculate :maximum, nil
end
- def calculate(operation, _column_name, _options = {})
- # TODO: Remove _options argument as soon we remove support to
- # activerecord-deprecated_finders.
+ def calculate(operation, _column_name)
if [:count, :sum, :size].include? operation
group_values.any? ? Hash.new : 0
elsif [:average, :minimum, :maximum].include?(operation) && group_values.any?
@@ -74,8 +80,12 @@ module ActiveRecord
end
end
- def exists?(_id = false)
+ def exists?(_conditions = :none)
false
end
+
+ def or(other)
+ other.spawn
+ end
end
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 755ff2b2f1..a1e1073792 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -96,7 +96,8 @@ module ActiveRecord
# Returns true if the record is persisted, i.e. it's not a new record and it was
# not destroyed, otherwise returns false.
def persisted?
- !(new_record? || destroyed?)
+ sync_with_transaction_state
+ !(@new_record || @destroyed)
end
# Saves the model.
@@ -109,37 +110,45 @@ module ActiveRecord
# validate: false, validations are bypassed altogether. See
# ActiveRecord::Validations for more information.
#
- # There's a series of callbacks associated with +save+. If any of the
- # <tt>before_*</tt> callbacks return +false+ the action is cancelled and
- # +save+ returns +false+. See ActiveRecord::Callbacks for further
+ # By default, #save also sets the +updated_at+/+updated_on+ attributes to
+ # the current time. However, if you supply <tt>touch: false</tt>, these
+ # timestamps will not be updated.
+ #
+ # There's a series of callbacks associated with #save. If any of the
+ # <tt>before_*</tt> callbacks throws +:abort+ the action is cancelled and
+ # #save returns +false+. See ActiveRecord::Callbacks for further
# details.
#
# Attributes marked as readonly are silently ignored if the record is
# being updated.
- def save(*)
- create_or_update
+ def save(*args)
+ create_or_update(*args)
rescue ActiveRecord::RecordInvalid
false
end
# Saves the model.
#
- # If the model is new a record gets created in the database, otherwise
+ # If the model is new, a record gets created in the database, otherwise
# the existing record gets updated.
#
# With <tt>save!</tt> validations always run. If any of them fail
# ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations
# for more information.
#
- # There's a series of callbacks associated with <tt>save!</tt>. If any of
- # the <tt>before_*</tt> callbacks return +false+ the action is cancelled
- # and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See
+ # By default, #save! also sets the +updated_at+/+updated_on+ attributes to
+ # the current time. However, if you supply <tt>touch: false</tt>, these
+ # timestamps will not be updated.
+ #
+ # There's a series of callbacks associated with #save!. If any of
+ # the <tt>before_*</tt> callbacks throws +:abort+ the action is cancelled
+ # and #save! raises ActiveRecord::RecordNotSaved. See
# ActiveRecord::Callbacks for further details.
#
# Attributes marked as readonly are silently ignored if the record is
# being updated.
- def save!(*)
- create_or_update || raise(RecordNotSaved)
+ def save!(*args)
+ create_or_update(*args) || raise(RecordNotSaved.new(nil, self))
end
# Deletes the record in the database and freezes this instance to
@@ -149,6 +158,8 @@ module ActiveRecord
# The row is simply removed with an SQL +DELETE+ statement on the
# record's primary key, and no callbacks are executed.
#
+ # Note that this will also delete records marked as <tt>readonly?</tt>.
+ #
# To enforce the object's +before_destroy+ and +after_destroy+
# callbacks or any <tt>:dependent</tt> association
# options, use <tt>#destroy</tt>.
@@ -161,13 +172,14 @@ module ActiveRecord
# Deletes the record in the database and freezes this instance to reflect
# that no changes should be made (since they can't be persisted).
#
- # There's a series of callbacks associated with <tt>destroy</tt>. If
- # the <tt>before_destroy</tt> callback return +false+ the action is cancelled
- # and <tt>destroy</tt> returns +false+. See
- # ActiveRecord::Callbacks for further details.
+ # There's a series of callbacks associated with #destroy. If the
+ # <tt>before_destroy</tt> callback throws +:abort+ the action is cancelled
+ # and #destroy returns +false+.
+ # See ActiveRecord::Callbacks for further details.
def destroy
- raise ReadOnlyRecord if readonly?
+ raise ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly?
destroy_associations
+ self.class.connection.add_transaction_record(self)
destroy_row if persisted?
@destroyed = true
freeze
@@ -176,12 +188,12 @@ module ActiveRecord
# Deletes the record in the database and freezes this instance to reflect
# that no changes should be made (since they can't be persisted).
#
- # There's a series of callbacks associated with <tt>destroy!</tt>. If
- # the <tt>before_destroy</tt> callback return +false+ the action is cancelled
- # and <tt>destroy!</tt> raises ActiveRecord::RecordNotDestroyed. See
- # ActiveRecord::Callbacks for further details.
+ # There's a series of callbacks associated with #destroy!. If the
+ # <tt>before_destroy</tt> callback throws +:abort+ the action is cancelled
+ # and #destroy! raises ActiveRecord::RecordNotDestroyed.
+ # See ActiveRecord::Callbacks for further details.
def destroy!
- destroy || raise(ActiveRecord::RecordNotDestroyed)
+ destroy || raise(ActiveRecord::RecordNotDestroyed, self)
end
# Returns an instance of the specified +klass+ with the attributes of the
@@ -197,7 +209,8 @@ module ActiveRecord
def becomes(klass)
became = klass.new
became.instance_variable_set("@attributes", @attributes)
- became.instance_variable_set("@changed_attributes", @changed_attributes) if defined?(@changed_attributes)
+ changed_attributes = @changed_attributes if defined?(@changed_attributes)
+ became.instance_variable_set("@changed_attributes", changed_attributes || {})
became.instance_variable_set("@new_record", new_record?)
became.instance_variable_set("@destroyed", destroyed?)
became.instance_variable_set("@errors", errors)
@@ -235,8 +248,8 @@ module ActiveRecord
def update_attribute(name, value)
name = name.to_s
verify_readonly_attribute(name)
- send("#{name}=", value)
- save(validate: false)
+ public_send("#{name}=", value)
+ save(validate: false) if changed?
end
# Updates the attributes of the model from the passed-in hash and saves the
@@ -342,7 +355,7 @@ module ActiveRecord
# method toggles directly the underlying value without calling any setter.
# Returns +self+.
def toggle(attribute)
- self[attribute] = !send("#{attribute}?")
+ self[attribute] = !public_send("#{attribute}?")
self
end
@@ -403,9 +416,6 @@ module ActiveRecord
# end
#
def reload(options = nil)
- clear_aggregation_cache
- clear_association_cache
-
fresh_object =
if options && options[:lock]
self.class.unscoped { self.class.lock(options[:lock]).find(id) }
@@ -418,14 +428,17 @@ 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.
#
+ # This method can be passed attribute names and an optional time argument.
# If attribute names are passed, they are updated along with updated_at/on
- # attributes.
+ # attributes. If no time argument is passed, the current time is used as default.
#
- # product.touch # updates updated_at/on
+ # product.touch # updates updated_at/on with current time
+ # product.touch(time: Time.new(2015, 2, 16, 0, 0, 0)) # updates updated_at/on with specified time
# product.touch(:designed_at) # updates the designed_at attribute and updated_at/on
# product.touch(:started_at, :ended_at) # updates started_at, ended_at and updated_at/on attributes
#
@@ -449,19 +462,19 @@ module ActiveRecord
# ball = Ball.new
# ball.touch(:updated_at) # => raises ActiveRecordError
#
- def touch(*names)
+ 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)
unless attributes.empty?
- current_time = current_time_from_proper_timezone
changes = {}
attributes.each do |column|
column = column.to_s
- changes[column] = write_attribute(column, current_time)
+ changes[column] = write_attribute(column, time)
end
changes[self.class.locking_column] = increment_lock if locking_enabled?
@@ -485,20 +498,12 @@ module ActiveRecord
end
def relation_for_destroy
- pk = self.class.primary_key
- column = self.class.columns_hash[pk]
- substitute = self.class.connection.substitute_at(column, 0)
-
- relation = self.class.unscoped.where(
- self.class.arel_table[pk].eq(substitute))
-
- relation.bind_values = [[column, id]]
- relation
+ self.class.unscoped.where(self.class.primary_key => id)
end
- def create_or_update
- raise ReadOnlyRecord if readonly?
- result = new_record? ? _create_record : _update_record
+ def create_or_update(*args)
+ raise ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly?
+ result = new_record? ? _create_record : _update_record(*args)
result != false
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index e8de4db3a7..4e597590e9 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -7,7 +7,7 @@ module ActiveRecord
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 :select, :group, :order, :except, :reorder, :limit, :offset, :joins,
+ 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
delegate :count, :average, :minimum, :maximum, :sum, :calculate, to: :all
@@ -55,11 +55,12 @@ module ActiveRecord
# The use of this method should be restricted to complicated SQL queries that can't be executed
# using the ActiveRecord::Calculations class methods. Look into those before using this.
#
- # ==== Parameters
+ # Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
+ # # => 12
#
- # * +sql+ - An SQL statement which should return a count query from the database, see the example below.
+ # ==== Parameters
#
- # Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
+ # * +sql+ - An SQL statement which should return a count query from the database, see the example above.
def count_by_sql(sql)
sql = sanitize_conditions(sql)
connection.select_value(sql, "#{name} Count").to_i
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index a4ceacbf44..7e907beec0 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -36,8 +36,6 @@ module ActiveRecord
config.eager_load_namespaces << ActiveRecord
rake_tasks do
- require "active_record/base"
-
namespace :db do
task :load_config do
ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration
@@ -104,6 +102,14 @@ module ActiveRecord
end
end
+ initializer "active_record.warn_on_records_fetched_greater_than" do
+ if config.active_record.warn_on_records_fetched_greater_than
+ ActiveSupport.on_load(:active_record) do
+ require 'active_record/relation/record_fetch_warning'
+ end
+ end
+ end
+
initializer "active_record.set_configs" do |app|
ActiveSupport.on_load(:active_record) do
app.config.active_record.each do |k,v|
diff --git a/activerecord/lib/active_record/railties/controller_runtime.rb b/activerecord/lib/active_record/railties/controller_runtime.rb
index af4840476c..8727e46cb3 100644
--- a/activerecord/lib/active_record/railties/controller_runtime.rb
+++ b/activerecord/lib/active_record/railties/controller_runtime.rb
@@ -19,7 +19,7 @@ module ActiveRecord
end
def cleanup_view_runtime
- if ActiveRecord::Base.connected?
+ if logger.info? && ActiveRecord::Base.connected?
db_rt_before_render = ActiveRecord::LogSubscriber.reset_runtime
self.db_runtime = (db_runtime || 0) + db_rt_before_render
runtime = super
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 3ec25f9f17..2591e7492d 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -240,7 +240,7 @@ db_namespace = namespace :db do
end
desc 'Load a schema.rb file into the database'
- task :load => [:environment, :load_config] do
+ task :load => [:load_config] do
ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:ruby, ENV['SCHEMA'])
end
@@ -269,9 +269,9 @@ db_namespace = namespace :db do
end
namespace :structure do
- desc 'Dump the database structure to db/structure.sql. Specify another file with DB_STRUCTURE=db/my_structure.sql'
+ desc 'Dump the database structure to db/structure.sql. Specify another file with SCHEMA=db/my_structure.sql'
task :dump => [:environment, :load_config] do
- filename = ENV['DB_STRUCTURE'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "structure.sql")
+ filename = ENV['SCHEMA'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "structure.sql")
current_config = ActiveRecord::Tasks::DatabaseTasks.current_config
ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename)
@@ -286,8 +286,8 @@ db_namespace = namespace :db do
end
desc "Recreate the databases from the structure.sql file"
- task :load => [:environment, :load_config] do
- ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:sql, ENV['DB_STRUCTURE'])
+ task :load => [:load_config] do
+ ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:sql, ENV['SCHEMA'])
end
task :load_if_sql => ['db:create', :environment] do
@@ -305,7 +305,7 @@ db_namespace = namespace :db do
end
# desc "Recreate the test database from the current schema"
- task :load => %w(db:test:deprecated db:test:purge) do
+ task :load => %w(db:test:purge) do
case ActiveRecord::Base.schema_format
when :ruby
db_namespace["test:load_schema"].invoke
@@ -315,11 +315,11 @@ db_namespace = namespace :db do
end
# desc "Recreate the test database from an existent schema.rb file"
- task :load_schema => %w(db:test:deprecated db:test:purge) do
+ task :load_schema => %w(db:test:purge) do
begin
should_reconnect = ActiveRecord::Base.connection_pool.active_connection?
ActiveRecord::Schema.verbose = false
- ActiveRecord::Tasks::DatabaseTasks.load_schema_for ActiveRecord::Base.configurations['test'], :ruby, ENV['SCHEMA']
+ ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations['test'], :ruby, ENV['SCHEMA']
ensure
if should_reconnect
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env])
@@ -328,8 +328,8 @@ db_namespace = namespace :db do
end
# desc "Recreate the test database from an existent structure.sql file"
- task :load_structure => %w(db:test:deprecated db:test:purge) do
- ActiveRecord::Tasks::DatabaseTasks.load_schema_for ActiveRecord::Base.configurations['test'], :sql, ENV['SCHEMA']
+ task :load_structure => %w(db:test:purge) do
+ ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations['test'], :sql, ENV['SCHEMA']
end
# desc "Recreate the test database from a fresh schema"
@@ -349,12 +349,12 @@ db_namespace = namespace :db do
task :clone_structure => %w(db:test:deprecated db:structure:dump db:test:load_structure)
# desc "Empty the test database"
- task :purge => %w(db:test:deprecated environment load_config) do
+ task :purge => %w(environment load_config) do
ActiveRecord::Tasks::DatabaseTasks.purge ActiveRecord::Base.configurations['test']
end
# desc 'Check for pending migrations and load the test schema'
- task :prepare => %w(db:test:deprecated environment load_config) do
+ task :prepare => %w(environment load_config) do
unless ActiveRecord::Base.configurations.blank?
db_namespace['test:load'].invoke
end
@@ -366,7 +366,7 @@ namespace :railties do
namespace :install do
# desc "Copies missing migrations from Railties (e.g. engines). You can specify Railties to use with FROM=railtie1,railtie2"
task :migrations => :'db:load_config' do
- to_load = ENV['FROM'].blank? ? :all : ENV['FROM'].split(",").map {|n| n.strip }
+ to_load = ENV['FROM'].blank? ? :all : ENV['FROM'].split(",").map(&:strip)
railties = {}
Rails.application.migration_railties.each do |railtie|
next unless to_load == :all || to_load.include?(railtie.railtie_name)
diff --git a/activerecord/lib/active_record/readonly_attributes.rb b/activerecord/lib/active_record/readonly_attributes.rb
index 85bbac43e4..ce78f1756d 100644
--- a/activerecord/lib/active_record/readonly_attributes.rb
+++ b/activerecord/lib/active_record/readonly_attributes.rb
@@ -11,7 +11,7 @@ module ActiveRecord
# Attributes listed as readonly will be used to create a new record but update operations will
# ignore these fields.
def attr_readonly(*attributes)
- self._attr_readonly = Set.new(attributes.map { |a| a.to_s }) + (self._attr_readonly || [])
+ self._attr_readonly = Set.new(attributes.map(&:to_s)) + (self._attr_readonly || [])
end
# Returns an array of all the attributes that have been specified as readonly.
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 4b58f2deb7..4265afc0a5 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -39,9 +39,9 @@ module ActiveRecord
ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_s => reflection)
end
- # \Reflection enables interrogating of Active Record classes and objects
- # about their associations and aggregations. This information can,
- # for example, be used in a form builder that takes an Active Record object
+ # \Reflection enables the ability to examine the associations and aggregations of
+ # Active Record classes and objects. This information, for example,
+ # can be used in a form builder that takes an Active Record object
# and creates input fields for all of the attributes depending on their type
# and displays the associations to other objects.
#
@@ -63,7 +63,7 @@ module ActiveRecord
# Returns a Hash of name of the reflection as the key and a AssociationReflection as the value.
#
- # Account.reflections # => {balance: AggregateReflection}
+ # Account.reflections # => {"balance" => AggregateReflection}
#
# @api public
def reflections
@@ -149,25 +149,25 @@ module ActiveRecord
JoinKeys = Struct.new(:key, :foreign_key) # :nodoc:
- def join_keys(assoc_klass)
+ def join_keys(association_klass)
JoinKeys.new(foreign_key, active_record_primary_key)
end
- def source_macro
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- ActiveRecord::Base.source_macro is deprecated and will be removed
- without replacement.
- MSG
+ def constraints
+ scope_chain.flatten
+ end
- macro
+ def alias_candidate(name)
+ "#{plural_name}_#{name}"
end
end
+
# Base class for AggregateReflection and AssociationReflection. Objects of
# AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
#
# MacroReflection
+ # AggregateReflection
# AssociationReflection
- # AggregateReflection
# HasManyReflection
# HasOneReflection
# BelongsToReflection
@@ -277,7 +277,7 @@ module ActiveRecord
def initialize(name, scope, options, active_record)
super
@automatic_inverse_of = nil
- @type = options[:as] && "#{options[:as]}_type"
+ @type = options[:as] && (options[:foreign_type] || "#{options[:as]}_type")
@foreign_type = options[:foreign_type] || "#{name}_type"
@constructable = calculate_constructable(macro, options)
@association_scope_cache = {}
@@ -287,7 +287,7 @@ module ActiveRecord
def association_scope_cache(conn, owner)
key = conn.prepared_statements
if polymorphic?
- key = [key, owner.read_attribute(@foreign_type)]
+ key = [key, owner._read_attribute(@foreign_type)]
end
@association_scope_cache[key] ||= @scope_lock.synchronize {
@association_scope_cache[key] ||= yield
@@ -343,13 +343,10 @@ module ActiveRecord
return unless scope
if scope.arity > 0
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ raise ArgumentError, <<-MSG.squish
The association scope '#{name}' is instance dependent (the scope
- block takes an argument). Preloading happens before the individual
- instances are created. This means that there is no instance being
- passed to the association scope. This will most likely result in
- broken or incorrect behavior. Joining, Preloading and eager loading
- of these associations is deprecated and will be removed in the future.
+ block takes an argument). Preloading instance dependent scopes is
+ not supported.
MSG
end
end
@@ -373,6 +370,12 @@ module ActiveRecord
[self]
end
+ # This is for clearing cache on the reflection. Useful for tests that need to compare
+ # SQL queries on associations.
+ def clear_association_scope_cache # :nodoc:
+ @association_scope_cache.clear
+ end
+
def nested?
false
end
@@ -499,7 +502,7 @@ module ActiveRecord
# returns either nil or the inverse association name that it finds.
def automatic_inverse_of
if can_find_inverse_of_automatically?(self)
- inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name).to_sym
+ inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
begin
reflection = klass._reflect_on_association(inverse_name)
@@ -601,8 +604,8 @@ module ActiveRecord
def belongs_to?; true; end
- def join_keys(assoc_klass)
- key = polymorphic? ? association_primary_key(assoc_klass) : association_primary_key
+ def join_keys(association_klass)
+ key = polymorphic? ? association_primary_key(association_klass) : association_primary_key
JoinKeys.new(key, foreign_key)
end
@@ -697,13 +700,27 @@ module ActiveRecord
def chain
@chain ||= begin
a = source_reflection.chain
- b = through_reflection.chain
+ b = through_reflection.chain.map(&:dup)
+
+ if options[:source_type]
+ b[0] = PolymorphicReflection.new(b[0], self)
+ end
+
chain = a + b
chain[0] = self # Use self so we don't lose the information from :source_type
chain
end
end
+ # This is for clearing cache on the reflection. Useful for tests that need to compare
+ # SQL queries on associations.
+ def clear_association_scope_cache # :nodoc:
+ @chain = nil
+ delegate_reflection.clear_association_scope_cache
+ source_reflection.clear_association_scope_cache
+ through_reflection.clear_association_scope_cache
+ end
+
# Consider the following example:
#
# class Person
@@ -745,18 +762,8 @@ module ActiveRecord
end
end
- def join_keys(assoc_klass)
- source_reflection.join_keys(assoc_klass)
- end
-
- # The macro used by the source association
- def source_macro
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- ActiveRecord::Base.source_macro is deprecated and will be removed
- without replacement.
- MSG
-
- source_reflection.source_macro
+ def join_keys(association_klass)
+ source_reflection.join_keys(association_klass)
end
# A through association is nested if there would be more than one join table
@@ -791,7 +798,7 @@ module ActiveRecord
def source_reflection_name # :nodoc:
return @source_reflection_name if @source_reflection_name
- names = [name.to_s.singularize, name].collect { |n| n.to_sym }.uniq
+ names = [name.to_s.singularize, name].collect(&:to_sym).uniq
names = names.find_all { |n|
through_reflection.klass._reflect_on_association(n)
}
@@ -855,6 +862,12 @@ module ActiveRecord
check_validity_of_inverse!
end
+ def constraints
+ scope_chain = source_reflection.constraints
+ scope_chain << scope if scope
+ scope_chain
+ end
+
protected
def actual_source_reflection # FIXME: this is a horrible name
@@ -877,5 +890,81 @@ module ActiveRecord
delegate(*delegate_methods, to: :delegate_reflection)
end
+
+ class PolymorphicReflection < ThroughReflection # :nodoc:
+ def initialize(reflection, previous_reflection)
+ @reflection = reflection
+ @previous_reflection = previous_reflection
+ end
+
+ def klass
+ @reflection.klass
+ end
+
+ def scope
+ @reflection.scope
+ end
+
+ def table_name
+ @reflection.table_name
+ end
+
+ def plural_name
+ @reflection.plural_name
+ end
+
+ def join_keys(association_klass)
+ @reflection.join_keys(association_klass)
+ end
+
+ def type
+ @reflection.type
+ end
+
+ def constraints
+ [source_type_info]
+ end
+
+ def source_type_info
+ type = @previous_reflection.foreign_type
+ source_type = @previous_reflection.options[:source_type]
+ lambda { |object| where(type => source_type) }
+ end
+ end
+
+ class RuntimeReflection < PolymorphicReflection # :nodoc:
+ attr_accessor :next
+
+ def initialize(reflection, association)
+ @reflection = reflection
+ @association = association
+ end
+
+ def klass
+ @association.klass
+ end
+
+ def table_name
+ klass.table_name
+ end
+
+ def constraints
+ @reflection.constraints
+ end
+
+ def source_type_info
+ @reflection.source_type_info
+ end
+
+ def alias_candidate(name)
+ "#{plural_name}_#{name}_join"
+ end
+
+ def alias_name
+ Arel::Table.new(table_name)
+ end
+
+ def all_includes; yield; end
+ end
end
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 03bce4f5b7..85648a7f8f 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -1,38 +1,39 @@
# -*- coding: utf-8 -*-
-require 'arel/collectors/bind'
+require "arel/collectors/bind"
module ActiveRecord
# = Active Record Relation
class Relation
MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
- :order, :joins, :where, :having, :bind, :references,
+ :order, :joins, :references,
:extending, :unscope]
- SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering,
+ SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering,
:reverse_order, :distinct, :create_with, :uniq]
+ CLAUSE_METHODS = [:where, :having, :from]
INVALID_METHODS_FOR_DELETE_ALL = [:limit, :distinct, :offset, :group, :having]
- VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS
+ VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
- attr_reader :table, :klass, :loaded
+ attr_reader :table, :klass, :loaded, :predicate_builder
alias :model :klass
alias :loaded? :loaded
- def initialize(klass, table, values = {})
+ def initialize(klass, table, predicate_builder, values = {})
@klass = klass
@table = table
@values = values
@offsets = {}
@loaded = false
+ @predicate_builder = predicate_builder
end
def initialize_copy(other)
# This method is a hot spot, so for now, use Hash[] to dup the hash.
# https://bugs.ruby-lang.org/issues/7166
@values = Hash[@values]
- @values[:bind] = @values[:bind].dup if @values.key? :bind
reset
end
@@ -80,11 +81,10 @@ module ActiveRecord
end
relation = scope.where(@klass.primary_key => (id_was || id))
- bvs = binds + relation.bind_values
+ bvs = binds + relation.bound_attributes
um = relation
.arel
.compile_update(substitutes, @klass.primary_key)
- reorder_bind_params(um.ast, bvs)
@klass.connection.update(
um,
@@ -95,11 +95,11 @@ module ActiveRecord
def substitute_values(values) # :nodoc:
binds = values.map do |arel_attr, value|
- [@klass.columns_hash[arel_attr.name], value]
+ QueryAttribute.new(arel_attr.name, value, klass.type_for_attribute(arel_attr.name))
end
- substitutes = values.each_with_index.map do |(arel_attr, _), i|
- [arel_attr, @klass.connection.substitute_at(binds[i][0], i)]
+ substitutes = values.map do |(arel_attr, _)|
+ [arel_attr, connection.substitute_at(klass.columns_hash[arel_attr.name])]
end
[substitutes, binds]
@@ -205,7 +205,9 @@ module ActiveRecord
# constraint an exception may be raised, just retry:
#
# begin
- # CreditAccount.find_or_create_by(user_id: user.id)
+ # CreditAccount.transaction(requires_new: true) do
+ # CreditAccount.find_or_create_by(user_id: user.id)
+ # end
# rescue ActiveRecord::RecordNotUnique
# retry
# end
@@ -271,6 +273,15 @@ module ActiveRecord
end
end
+ # Returns true if there are no records.
+ def none?
+ if block_given?
+ to_a.none? { |*block_args| yield(*block_args) }
+ else
+ empty?
+ end
+ end
+
# Returns true if there are any records.
def any?
if block_given?
@@ -280,6 +291,15 @@ module ActiveRecord
end
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
+ end
+
# Returns true if there is more than one record.
def many?
if block_given?
@@ -305,11 +325,11 @@ module ActiveRecord
klass.current_scope = previous
end
- # Updates all records with details given if they match a set of conditions supplied, limits and order can
- # also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the
- # database. It does not instantiate the involved models and it does not trigger Active Record callbacks
- # or validations. Values passed to `update_all` will not go through ActiveRecord's type-casting behavior.
- # It should receive only values that can be passed as-is to the SQL database.
+ # Updates all records in the current relation with details given. This method constructs a single SQL UPDATE
+ # statement and sends it straight to the database. It does not instantiate the involved models and it does not
+ # trigger Active Record callbacks or validations. Values passed to `update_all` will not go through
+ # ActiveRecord's type-casting behavior. It should receive only values that can be passed as-is to the SQL
+ # database.
#
# ==== Parameters
#
@@ -328,7 +348,7 @@ module ActiveRecord
def update_all(updates)
raise ArgumentError, "Empty list of attributes to change" if updates.blank?
- stmt = Arel::UpdateManager.new(arel.engine)
+ stmt = Arel::UpdateManager.new
stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates))
stmt.table(table)
@@ -342,8 +362,7 @@ module ActiveRecord
stmt.wheres = arel.constraints
end
- bvs = arel.bind_values + bind_values
- @klass.connection.update stmt, 'SQL', bvs
+ @klass.connection.update stmt, 'SQL', bound_attributes
end
# Updates an object (or multiple objects) and saves it to the database, if validations pass.
@@ -362,9 +381,21 @@ module ActiveRecord
# # Updates multiple records
# people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
# Person.update(people.keys, people.values)
- def update(id, attributes)
+ #
+ # # Updates multiple records from the result of a relation
+ # people = Person.where(group: 'expert')
+ # people.update(group: 'masters')
+ #
+ # Note: Updating a large number of records will run a
+ # UPDATE query for each record, which may cause a performance
+ # issue. So if it is not needed to run callbacks for each update, it is
+ # preferred to use <tt>update_all</tt> for updating all records using
+ # a single query.
+ def update(id = :all, attributes)
if id.is_a?(Array)
id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }
+ elsif id == :all
+ to_a.each { |record| record.update(attributes) }
else
object = find(id)
object.update(attributes)
@@ -401,7 +432,7 @@ module ActiveRecord
if conditions
where(conditions).destroy_all
else
- to_a.each {|object| object.destroy }.tap { reset }
+ to_a.each(&:destroy).tap { reset }
end
end
@@ -455,8 +486,10 @@ module ActiveRecord
invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select { |method|
if MULTI_VALUE_METHODS.include?(method)
send("#{method}_values").any?
- else
+ elsif SINGLE_VALUE_METHODS.include?(method)
send("#{method}_value")
+ elsif CLAUSE_METHODS.include?(method)
+ send("#{method}_clause").any?
end
}
if invalid_methods.any?
@@ -466,7 +499,7 @@ module ActiveRecord
if conditions
where(conditions).delete_all
else
- stmt = Arel::DeleteManager.new(arel.engine)
+ stmt = Arel::DeleteManager.new
stmt.from(table)
if joins_values.any?
@@ -475,7 +508,7 @@ module ActiveRecord
stmt.wheres = arel.constraints
end
- affected = @klass.connection.delete(stmt, 'SQL', bind_values)
+ affected = @klass.connection.delete(stmt, 'SQL', bound_attributes)
reset
affected
@@ -545,10 +578,10 @@ module ActiveRecord
find_with_associations { |rel| relation = rel }
end
- arel = relation.arel
- binds = (arel.bind_values + relation.bind_values).dup
- binds.map! { |bv| connection.quote(*bv.reverse) }
- collect = visitor.accept(arel.ast, Arel::Collectors::Bind.new)
+ binds = relation.bound_attributes
+ binds = connection.prepare_binds_for_database(binds)
+ binds.map! { |value| connection.quote(value) }
+ collect = visitor.accept(relation.arel.ast, Arel::Collectors::Bind.new)
collect.substitute_binds(binds).join
end
end
@@ -558,22 +591,7 @@ module ActiveRecord
# User.where(name: 'Oscar').where_values_hash
# # => {name: "Oscar"}
def where_values_hash(relation_table_name = table_name)
- equalities = where_values.grep(Arel::Nodes::Equality).find_all { |node|
- node.left.relation.name == relation_table_name
- }
-
- binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
-
- Hash[equalities.map { |where|
- name = where.left.name
- [name, binds.fetch(name.to_s) {
- case where.right
- when Array then where.right.map(&:val)
- else
- where.right.val
- end
- }]
- }]
+ where_clause.to_h(relation_table_name)
end
def scope_for_create
@@ -636,21 +654,25 @@ module ActiveRecord
private
def exec_queries
- @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, arel.bind_values + bind_values)
+ @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bound_attributes)
preload = preload_values
preload += includes_values unless eager_loading?
- preloader = ActiveRecord::Associations::Preloader.new
+ preloader = build_preloader
preload.each do |associations|
preloader.preload @records, associations
end
- @records.each { |record| record.readonly! } if readonly_value
+ @records.each(&:readonly!) if readonly_value
@loaded = true
@records
end
+ def build_preloader
+ ActiveRecord::Associations::Preloader.new
+ end
+
def references_eager_loaded_tables?
joined_tables = arel.join_sources.map do |join|
if join.is_a?(Arel::Nodes::StringJoin)
@@ -663,7 +685,7 @@ module ActiveRecord
joined_tables += [table.name, table.table_alias]
# always convert table names to downcase as in Oracle quoted table names are in uppercase
- joined_tables = joined_tables.flatten.compact.map { |t| t.downcase }.uniq
+ joined_tables = joined_tables.flatten.compact.map(&:downcase).uniq
(references_values - joined_tables).any?
end
@@ -672,7 +694,7 @@ module ActiveRecord
return [] if string.blank?
# always convert table names to downcase as in Oracle quoted table names are in uppercase
# ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
- string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map{ |s| s.downcase }.uniq - ['raw_sql_']
+ string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map(&:downcase).uniq - ['raw_sql_']
end
end
end
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index b069cdce7c..e07580a563 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -27,37 +27,46 @@ module ActiveRecord
#
# ==== Options
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
- # * <tt>:start</tt> - Specifies the starting point for the batch processing.
+ # * <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 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 +:start+ option on that worker).
+ # (by setting the +:begin_at+ and +:end_at+ option on each worker).
#
# # Let's process for a batch of 2000 records, skipping the first 2000 rows
- # Person.find_each(start: 2000, batch_size: 2000) do |person|
+ # Person.find_each(begin_at: 2000, batch_size: 2000) do |person|
# person.party_all_night!
# end
#
# 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
- # work. This also means that this method only works with integer-based
- # primary keys.
+ # work. This also means that this method only works when the primary key is
+ # orderable (e.g. an integer or string).
#
# NOTE: You can't set the limit either, that's used to control
# the batch sizes.
- def find_each(options = {})
+ def find_each(begin_at: nil, end_at: nil, batch_size: 1000, start: nil)
+ if start
+ begin_at = start
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing `start` value to find_each is deprecated, and will be removed in Rails 5.1.
+ Please pass `begin_at` instead.
+ MSG
+ end
if block_given?
- find_in_batches(options) do |records|
+ find_in_batches(begin_at: begin_at, end_at: end_at, batch_size: batch_size) do |records|
records.each { |record| yield record }
end
else
- enum_for :find_each, options do
- options[:start] ? where(table[primary_key].gteq(options[:start])).size : size
+ enum_for(:find_each, begin_at: begin_at, end_at: end_at, batch_size: batch_size) do
+ relation = self
+ apply_limits(relation, begin_at, end_at).size
end
end
end
- # Yields each batch of records that was found by the find +options+ as
+ # Yields each batch of records that was found by the find options as
# an array.
#
# Person.where("age > 21").find_in_batches do |group|
@@ -77,34 +86,38 @@ module ActiveRecord
#
# ==== Options
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
- # * <tt>:start</tt> - Specifies the starting point for the batch processing.
+ # * <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 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 +:start+ option on that worker).
+ # (by setting the +:begin_at+ and +:end_at+ option on each worker).
#
# # Let's process the next 2000 records
- # Person.find_in_batches(start: 2000, batch_size: 2000) do |group|
+ # Person.find_in_batches(begin_at: 2000, batch_size: 2000) do |group|
# group.each { |person| person.party_all_night! }
# end
#
# 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
- # work. This also means that this method only works with integer-based
- # primary keys.
+ # work. This also means that this method only works when the primary key is
+ # orderable (e.g. an integer or string).
#
# NOTE: You can't set the limit either, that's used to control
# the batch sizes.
- def find_in_batches(options = {})
- options.assert_valid_keys(:start, :batch_size)
+ def find_in_batches(begin_at: nil, end_at: nil, batch_size: 1000, start: nil)
+ if start
+ begin_at = start
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing `start` value to find_in_batches is deprecated, and will be removed in Rails 5.1.
+ Please pass `begin_at` instead.
+ MSG
+ end
relation = self
- start = options[:start]
- batch_size = options[:batch_size] || 1000
-
unless block_given?
- return to_enum(:find_in_batches, options) do
- total = start ? where(table[primary_key].gteq(start)).size : size
+ return to_enum(:find_in_batches, begin_at: begin_at, end_at: end_at, batch_size: batch_size) do
+ total = apply_limits(relation, begin_at, end_at).size
(total - 1).div(batch_size) + 1
end
end
@@ -114,7 +127,8 @@ module ActiveRecord
end
relation = relation.reorder(batch_order).limit(batch_size)
- records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a
+ relation = apply_limits(relation, begin_at, end_at)
+ records = relation.to_a
while records.any?
records_size = records.size
@@ -131,6 +145,12 @@ module ActiveRecord
private
+ def apply_limits(relation, begin_at, end_at)
+ relation = relation.where(table[primary_key].gteq(begin_at)) if begin_at
+ relation = relation.where(table[primary_key].lteq(end_at)) if end_at
+ relation
+ end
+
def batch_order
"#{quoted_table_name}.#{quoted_primary_key} ASC"
end
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index c8ebb41131..8f16de3519 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -35,21 +35,16 @@ module ActiveRecord
#
# Note: not all valid +select+ expressions are valid +count+ expressions. The specifics differ
# between databases. In invalid cases, an error from the database is thrown.
- def count(column_name = nil, options = {})
- # TODO: Remove options argument as soon we remove support to
- # activerecord-deprecated_finders.
- column_name, options = nil, column_name if column_name.is_a?(Hash)
- calculate(:count, column_name, options)
+ def count(column_name = nil)
+ calculate(:count, column_name)
end
# Calculates the average value on a given column. Returns +nil+ if there's
# no row. See +calculate+ for examples with options.
#
# Person.average(:age) # => 35.8
- def average(column_name, options = {})
- # TODO: Remove options argument as soon we remove support to
- # activerecord-deprecated_finders.
- calculate(:average, column_name, options)
+ def average(column_name)
+ calculate(:average, column_name)
end
# Calculates the minimum value on a given column. The value is returned
@@ -57,10 +52,8 @@ module ActiveRecord
# +calculate+ for examples with options.
#
# Person.minimum(:age) # => 7
- def minimum(column_name, options = {})
- # TODO: Remove options argument as soon we remove support to
- # activerecord-deprecated_finders.
- calculate(:minimum, column_name, options)
+ def minimum(column_name)
+ calculate(:minimum, column_name)
end
# Calculates the maximum value on a given column. The value is returned
@@ -68,10 +61,8 @@ module ActiveRecord
# +calculate+ for examples with options.
#
# Person.maximum(:age) # => 93
- def maximum(column_name, options = {})
- # TODO: Remove options argument as soon we remove support to
- # activerecord-deprecated_finders.
- calculate(:maximum, column_name, options)
+ def maximum(column_name)
+ calculate(:maximum, column_name)
end
# Calculates the sum of values on a given column. The value is returned
@@ -114,17 +105,15 @@ module ActiveRecord
# Person.group(:last_name).having("min(age) > 17").minimum(:age)
#
# Person.sum("2 * age")
- def calculate(operation, column_name, options = {})
- # TODO: Remove options argument as soon we remove support to
- # activerecord-deprecated_finders.
+ def calculate(operation, column_name)
if column_name.is_a?(Symbol) && attribute_alias?(column_name)
column_name = attribute_alias(column_name)
end
if has_include?(column_name)
- construct_relation_for_association_calculations.calculate(operation, column_name, options)
+ construct_relation_for_association_calculations.calculate(operation, column_name)
else
- perform_calculation(operation, column_name, options)
+ perform_calculation(operation, column_name)
end
end
@@ -177,8 +166,8 @@ module ActiveRecord
relation.select_values = column_names.map { |cn|
columns_hash.key?(cn) ? arel_table[cn] : cn
}
- result = klass.connection.select_all(relation.arel, nil, bind_values)
- result.cast_values(klass.column_types)
+ result = klass.connection.select_all(relation.arel, nil, bound_attributes)
+ result.cast_values(klass.attribute_types)
end
end
@@ -193,12 +182,10 @@ module ActiveRecord
private
def has_include?(column_name)
- eager_loading? || (includes_values.present? && ((column_name && column_name != :all) || references_eager_loaded_tables?))
+ eager_loading? || (includes_values.present? && column_name && column_name != :all)
end
- def perform_calculation(operation, column_name, options = {})
- # TODO: Remove options argument as soon we remove support to
- # activerecord-deprecated_finders.
+ 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)
@@ -235,19 +222,16 @@ module ActiveRecord
end
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
- # Postgresql doesn't like ORDER BY when there are no GROUP BY
+ # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
relation = unscope(:order)
column_alias = column_name
- bind_values = nil
-
if operation == "count" && (relation.limit_value || relation.offset_value)
# Shortcut when limit is zero.
return 0 if relation.limit_value == 0
query_builder = build_count_subquery(relation, column_name, distinct)
- bind_values = query_builder.bind_values + relation.bind_values
else
column = aggregate_column(column_name)
@@ -258,10 +242,9 @@ module ActiveRecord
relation.select_values = [select_value]
query_builder = relation.arel
- bind_values = query_builder.bind_values + relation.bind_values
end
- result = @klass.connection.select_all(query_builder, nil, bind_values)
+ result = @klass.connection.select_all(query_builder, nil, bound_attributes)
row = result.first
value = row && row.values.first
column = result.column_types.fetch(column_alias) do
@@ -303,7 +286,7 @@ module ActiveRecord
operation,
distinct).as(aggregate_alias)
]
- select_values += select_values unless having_values.empty?
+ select_values += select_values unless having_clause.empty?
select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
if field.respond_to?(:as)
@@ -317,11 +300,11 @@ module ActiveRecord
relation.group_values = group
relation.select_values = select_values
- calculated_data = @klass.connection.select_all(relation, nil, bind_values)
+ calculated_data = @klass.connection.select_all(relation, nil, relation.bound_attributes)
if association
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
- key_records = association.klass.base_class.find(key_ids)
+ key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
key_records = Hash[key_records.map { |r| [r.id, r] }]
end
@@ -370,9 +353,9 @@ module ActiveRecord
def type_cast_calculated_value(value, type, operation = nil)
case operation
when 'count' then value.to_i
- when 'sum' then type.type_cast_from_database(value || 0)
+ when 'sum' then type.deserialize(value || 0)
when 'average' then value.respond_to?(:to_d) ? value.to_d : value
- else type.type_cast_from_database(value)
+ else type.deserialize(value)
end
end
@@ -391,11 +374,9 @@ module ActiveRecord
aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
relation.select_values = [aliased_column]
- arel = relation.arel
- subquery = arel.as(subquery_alias)
+ subquery = relation.arel.as(subquery_alias)
sm = Arel::SelectManager.new relation.engine
- sm.bind_values = arel.bind_values
select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
sm.project(select_value).from(subquery)
end
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 50f4d5c7ab..d4a8823cfe 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -1,6 +1,5 @@
require 'set'
require 'active_support/concern'
-require 'active_support/deprecation'
module ActiveRecord
module Delegation # :nodoc:
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index eacae73ebb..6a3a56f1cc 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -1,4 +1,3 @@
-require 'active_support/deprecation'
require 'active_support/core_ext/string/filters'
module ActiveRecord
@@ -6,7 +5,7 @@ module ActiveRecord
ONE_AS_ONE = '1 AS one'
# Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
- # If no record can be found for all of the listed ids, then RecordNotFound will be raised. If the primary key
+ # If one or more records can not be found for the requested ids, then RecordNotFound will be raised. If the primary key
# is an integer, find by id coerces its arguments using +to_i+.
#
# Person.find(1) # returns the object for ID = 1
@@ -17,8 +16,6 @@ module ActiveRecord
# Person.find([1]) # returns an array for the object with ID = 1
# Person.where("administrator = 1").order("created_on DESC").find(1)
#
- # <tt>ActiveRecord::RecordNotFound</tt> will be raised if one or more ids are not found.
- #
# NOTE: The returned records may not be in the same order as the ids you
# provide since database rows are unordered. You'd need to provide an explicit <tt>order</tt>
# option if you want the results are sorted.
@@ -108,7 +105,7 @@ module ActiveRecord
# Same as +take+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
# is found. Note that <tt>take!</tt> accepts no arguments.
def take!
- take or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
+ take or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
end
# Find the first record (or first N records if a parameter is supplied).
@@ -176,7 +173,7 @@ module ActiveRecord
# Same as +last+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
# is found. Note that <tt>last!</tt> accepts no arguments.
def last!
- last or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
+ last or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
end
# Find the second record.
@@ -307,11 +304,11 @@ module ActiveRecord
relation = relation.where(conditions)
else
unless conditions == :none
- relation = where(primary_key => conditions)
+ relation = relation.where(primary_key => conditions)
end
end
- connection.select_value(relation, "#{name} Exists", relation.arel.bind_values + relation.bind_values) ? true : false
+ connection.select_value(relation, "#{name} Exists", relation.bound_attributes) ? true : false
end
# This method is called whenever no records are found with either a single
@@ -323,7 +320,7 @@ module ActiveRecord
# the expected number of results should be provided in the +expected_size+
# argument.
def raise_record_not_found_exception!(ids, result_size, expected_size) #:nodoc:
- conditions = arel.where_sql
+ conditions = arel.where_sql(@klass.arel_engine)
conditions = " [#{conditions}]" if conditions
if Array(ids).size == 1
@@ -365,7 +362,7 @@ module ActiveRecord
[]
else
arel = relation.arel
- rows = connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
+ rows = connection.select_all(arel, 'SQL', relation.bound_attributes)
join_dependency.instantiate(rows, aliases)
end
end
@@ -379,7 +376,7 @@ module ActiveRecord
def construct_relation_for_association_calculations
from = arel.froms.first
if Arel::Table === from
- apply_join_dependency(self, construct_join_dependency)
+ apply_join_dependency(self, construct_join_dependency(joins_values))
else
# FIXME: as far as I can tell, `from` will always be an Arel::Table.
# There are no tests that test this branch, but presumably it's
@@ -397,7 +394,7 @@ module ActiveRecord
else
if relation.limit_value
limited_ids = limited_ids_for(relation)
- limited_ids.empty? ? relation.none! : relation.where!(table[primary_key].in(limited_ids))
+ limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids)
end
relation.except(:limit, :offset)
end
@@ -410,12 +407,12 @@ module ActiveRecord
relation = relation.except(:select).select(values).distinct!
arel = relation.arel
- id_rows = @klass.connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
+ id_rows = @klass.connection.select_all(arel, 'SQL', relation.bound_attributes)
id_rows.map {|row| row[primary_key]}
end
def using_limitable_reflections?(reflections)
- reflections.none? { |r| r.collection? }
+ reflections.none?(&:collection?)
end
protected
@@ -498,7 +495,7 @@ module ActiveRecord
end
def find_nth!(index)
- find_nth(index, offset_index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
+ find_nth(index, offset_index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
end
def find_nth_with_limit(offset, limit)
diff --git a/activerecord/lib/active_record/relation/from_clause.rb b/activerecord/lib/active_record/relation/from_clause.rb
new file mode 100644
index 0000000000..a93952fa30
--- /dev/null
+++ b/activerecord/lib/active_record/relation/from_clause.rb
@@ -0,0 +1,32 @@
+module ActiveRecord
+ class Relation
+ class FromClause
+ attr_reader :value, :name
+
+ def initialize(value, name)
+ @value = value
+ @name = name
+ end
+
+ def binds
+ if value.is_a?(Relation)
+ value.bound_attributes
+ else
+ []
+ end
+ end
+
+ def merge(other)
+ self
+ end
+
+ def empty?
+ value.nil?
+ end
+
+ def self.empty
+ new(nil, nil)
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index a8febf6a18..65b607ff1c 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -22,7 +22,7 @@ module ActiveRecord
# build a relation to merge in rather than directly merging
# the values.
def other
- other = Relation.create(relation.klass, relation.table)
+ other = Relation.create(relation.klass, relation.table, relation.predicate_builder)
hash.each { |k, v|
if k == :joins
if Hash === v
@@ -49,9 +49,9 @@ module ActiveRecord
@other = other
end
- NORMAL_VALUES = Relation::SINGLE_VALUE_METHODS +
- Relation::MULTI_VALUE_METHODS -
- [:joins, :where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc:
+ NORMAL_VALUES = Relation::VALUE_METHODS -
+ Relation::CLAUSE_METHODS -
+ [:joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc:
def normal_values
NORMAL_VALUES
@@ -75,6 +75,7 @@ module ActiveRecord
merge_multi_values
merge_single_values
+ merge_clauses
merge_joins
relation
@@ -107,32 +108,6 @@ module ActiveRecord
end
def merge_multi_values
- lhs_wheres = relation.where_values
- rhs_wheres = other.where_values
-
- lhs_binds = relation.bind_values
- rhs_binds = other.bind_values
-
- removed, kept = partition_overwrites(lhs_wheres, rhs_wheres)
-
- where_values = kept + rhs_wheres
- bind_values = filter_binds(lhs_binds, removed) + rhs_binds
-
- conn = relation.klass.connection
- bv_index = 0
- where_values.map! do |node|
- if Arel::Nodes::Equality === node && Arel::Nodes::BindParam === node.right
- substitute = conn.substitute_at(bind_values[bv_index].first, bv_index)
- bv_index += 1
- Arel::Nodes::Equality.new(node.left, substitute)
- else
- node
- end
- end
-
- relation.where_values = where_values
- relation.bind_values = bind_values
-
if other.reordering_value
# override any order specified in the original relation
relation.reorder! other.order_values
@@ -145,36 +120,18 @@ module ActiveRecord
end
def merge_single_values
- relation.from_value = other.from_value unless relation.from_value
- relation.lock_value = other.lock_value unless relation.lock_value
+ relation.lock_value ||= other.lock_value
unless other.create_with_value.blank?
relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value)
end
end
- def filter_binds(lhs_binds, removed_wheres)
- return lhs_binds if removed_wheres.empty?
-
- set = Set.new removed_wheres.map { |x| x.left.name.to_s }
- lhs_binds.dup.delete_if { |col,_| set.include? col.name }
- end
-
- # Remove equalities from the existing relation with a LHS which is
- # present in the relation being merged in.
- # returns [things_to_remove, things_to_keep]
- def partition_overwrites(lhs_wheres, rhs_wheres)
- if lhs_wheres.empty? || rhs_wheres.empty?
- return [[], lhs_wheres]
- end
-
- nodes = rhs_wheres.find_all do |w|
- w.respond_to?(:operator) && w.operator == :==
- end
- seen = Set.new(nodes) { |node| node.left }
-
- lhs_wheres.partition do |w|
- w.respond_to?(:operator) && w.operator == :== && seen.include?(w.left)
+ def merge_clauses
+ CLAUSE_METHODS.each do |name|
+ clause = relation.send("#{name}_clause")
+ other_clause = other.send("#{name}_clause")
+ relation.send("#{name}_clause=", clause.merge(other_clause))
end
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index e4b6b49087..43e9afe853 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -1,82 +1,49 @@
module ActiveRecord
class PredicateBuilder # :nodoc:
- @handlers = []
-
- autoload :RelationHandler, 'active_record/relation/predicate_builder/relation_handler'
- autoload :ArrayHandler, 'active_record/relation/predicate_builder/array_handler'
-
- def self.resolve_column_aliases(klass, hash)
- hash = hash.dup
- hash.keys.grep(Symbol) do |key|
- if klass.attribute_alias? key
- hash[klass.attribute_alias(key)] = hash.delete key
- end
- end
- hash
+ require 'active_record/relation/predicate_builder/array_handler'
+ require 'active_record/relation/predicate_builder/association_query_handler'
+ require 'active_record/relation/predicate_builder/base_handler'
+ require 'active_record/relation/predicate_builder/basic_object_handler'
+ require 'active_record/relation/predicate_builder/class_handler'
+ require 'active_record/relation/predicate_builder/range_handler'
+ require 'active_record/relation/predicate_builder/relation_handler'
+
+ delegate :resolve_column_aliases, to: :table
+
+ def initialize(table)
+ @table = table
+ @handlers = []
+
+ register_handler(BasicObject, BasicObjectHandler.new(self))
+ register_handler(Class, ClassHandler.new(self))
+ register_handler(Base, BaseHandler.new(self))
+ register_handler(Range, RangeHandler.new(self))
+ register_handler(Relation, RelationHandler.new)
+ register_handler(Array, ArrayHandler.new(self))
+ register_handler(AssociationQueryValue, AssociationQueryHandler.new(self))
end
- def self.build_from_hash(klass, attributes, default_table)
- queries = []
-
- attributes.each do |column, value|
- table = default_table
-
- if value.is_a?(Hash)
- if value.empty?
- queries << '1=0'
- else
- table = Arel::Table.new(column, default_table.engine)
- association = klass._reflect_on_association(column)
-
- value.each do |k, v|
- queries.concat expand(association && association.klass, table, k, v)
- end
- end
- else
- column = column.to_s
-
- if column.include?('.')
- table_name, column = column.split('.', 2)
- table = Arel::Table.new(table_name, default_table.engine)
- end
-
- queries.concat expand(klass, table, column, value)
- end
- end
-
- queries
+ def build_from_hash(attributes)
+ attributes = convert_dot_notation_to_hash(attributes.stringify_keys)
+ expand_from_hash(attributes)
end
- def self.expand(klass, table, column, value)
- queries = []
+ def create_binds(attributes)
+ attributes = convert_dot_notation_to_hash(attributes.stringify_keys)
+ create_binds_for_hash(attributes)
+ end
+ def expand(column, value)
# Find the foreign key when using queries such as:
# Post.where(author: author)
#
# For polymorphic relationships, find the foreign key and type:
# PriceEstimate.where(estimate_of: treasure)
- if klass && reflection = klass._reflect_on_association(column)
- if reflection.polymorphic? && base_class = polymorphic_base_class_from_value(value)
- queries << build(table[reflection.foreign_type], base_class)
- end
-
- column = reflection.foreign_key
+ if table.associated_with?(column)
+ value = AssociationQueryValue.new(table.associated_table(column), value)
end
- queries << build(table[column], value)
- queries
- end
-
- def self.polymorphic_base_class_from_value(value)
- case value
- when Relation
- value.klass.base_class
- when Array
- val = value.compact.first
- val.class.base_class if val.is_a?(Base)
- when Base
- value.class.base_class
- end
+ build(table.arel_attribute(column), value)
end
def self.references(attributes)
@@ -101,26 +68,82 @@ module ActiveRecord
# )
# end
# ActiveRecord::PredicateBuilder.register_handler(MyCustomDateRange, handler)
- def self.register_handler(klass, handler)
+ def register_handler(klass, handler)
@handlers.unshift([klass, handler])
end
- register_handler(BasicObject, ->(attribute, value) { attribute.eq(value) })
- # FIXME: I think we need to deprecate this behavior
- register_handler(Class, ->(attribute, value) { attribute.eq(value.name) })
- register_handler(Base, ->(attribute, value) { attribute.eq(value.id) })
- register_handler(Range, ->(attribute, value) { attribute.between(value) })
- register_handler(Relation, RelationHandler.new)
- register_handler(Array, ArrayHandler.new)
-
- def self.build(attribute, value)
+ def build(attribute, value)
handler_for(value).call(attribute, value)
end
- private_class_method :build
- def self.handler_for(object)
+ protected
+
+ attr_reader :table
+
+ def expand_from_hash(attributes)
+ return ["1=0"] if attributes.empty?
+
+ attributes.flat_map do |key, value|
+ if value.is_a?(Hash)
+ associated_predicate_builder(key).expand_from_hash(value)
+ else
+ expand(key, value)
+ end
+ end
+ end
+
+
+ def create_binds_for_hash(attributes)
+ result = attributes.dup
+ binds = []
+
+ attributes.each do |column_name, value|
+ case value
+ when Hash
+ attrs, bvs = associated_predicate_builder(column_name).create_binds_for_hash(value)
+ result[column_name] = attrs
+ binds += bvs
+ when Relation
+ binds += value.bound_attributes
+ else
+ if can_be_bound?(column_name, value)
+ result[column_name] = Arel::Nodes::BindParam.new
+ binds << Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name))
+ end
+ end
+ end
+
+ [result, binds]
+ end
+
+ private
+
+ def associated_predicate_builder(association_name)
+ self.class.new(table.associated_table(association_name))
+ end
+
+ def convert_dot_notation_to_hash(attributes)
+ dot_notation = attributes.keys.select { |s| s.include?(".") }
+
+ dot_notation.each do |key|
+ table_name, column_name = key.split(".")
+ value = attributes.delete(key)
+ attributes[table_name] ||= {}
+
+ attributes[table_name] = attributes[table_name].merge(column_name => value)
+ end
+
+ attributes
+ end
+
+ def handler_for(object)
@handlers.detect { |klass, _| klass === object }.last
end
- private_class_method :handler_for
+
+ def can_be_bound?(column_name, value)
+ !value.nil? &&
+ handler_for(value).is_a?(BasicObjectHandler) &&
+ !table.associated_with?(column_name)
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
index b8f3285c3e..95dbd6a77f 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
@@ -1,22 +1,14 @@
-require 'active_support/core_ext/string/filters'
-
module ActiveRecord
class PredicateBuilder
class ArrayHandler # :nodoc:
+ def initialize(predicate_builder)
+ @predicate_builder = predicate_builder
+ end
+
def call(attribute, value)
values = value.map { |x| x.is_a?(Base) ? x.id : x }
nils, values = values.partition(&:nil?)
- if values.any? { |val| val.is_a?(Array) }
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing a nested array to Active Record finder methods is
- deprecated and will be removed. Flatten your array before using
- it for 'IN' conditions.
- MSG
-
- values = values.flatten
- end
-
return attribute.in([]) if values.empty? && nils.empty?
ranges, values = values.partition { |v| v.is_a?(Range) }
@@ -24,20 +16,24 @@ module ActiveRecord
values_predicate =
case values.length
when 0 then NullPredicate
- when 1 then attribute.eq(values.first)
+ when 1 then predicate_builder.build(attribute, values.first)
else attribute.in(values)
end
unless nils.empty?
- values_predicate = values_predicate.or(attribute.eq(nil))
+ values_predicate = values_predicate.or(predicate_builder.build(attribute, nil))
end
- array_predicates = ranges.map { |range| attribute.between(range) }
+ array_predicates = ranges.map { |range| predicate_builder.build(attribute, range) }
array_predicates.unshift(values_predicate)
array_predicates.inject { |composite, predicate| composite.or(predicate) }
end
- module NullPredicate
+ protected
+
+ attr_reader :predicate_builder
+
+ module NullPredicate # :nodoc:
def self.or(other)
other
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
new file mode 100644
index 0000000000..159889d3b8
--- /dev/null
+++ b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
@@ -0,0 +1,78 @@
+module ActiveRecord
+ class PredicateBuilder
+ class AssociationQueryHandler # :nodoc:
+ def initialize(predicate_builder)
+ @predicate_builder = predicate_builder
+ end
+
+ def call(attribute, value)
+ queries = {}
+
+ table = value.associated_table
+ if value.base_class
+ queries[table.association_foreign_type] = value.base_class.name
+ end
+
+ queries[table.association_foreign_key] = value.ids
+ predicate_builder.build_from_hash(queries)
+ end
+
+ protected
+
+ attr_reader :predicate_builder
+ end
+
+ class AssociationQueryValue # :nodoc:
+ attr_reader :associated_table, :value
+
+ def initialize(associated_table, value)
+ @associated_table = associated_table
+ @value = value
+ end
+
+ def ids
+ case value
+ when Relation
+ value.select(primary_key)
+ when Array
+ value.map { |v| convert_to_id(v) }
+ else
+ convert_to_id(value)
+ end
+ end
+
+ def base_class
+ if associated_table.polymorphic_association?
+ @base_class ||= polymorphic_base_class_from_value
+ end
+ end
+
+ private
+
+ def primary_key
+ associated_table.association_primary_key(base_class)
+ end
+
+ def polymorphic_base_class_from_value
+ case value
+ when Relation
+ value.klass.base_class
+ when Array
+ val = value.compact.first
+ val.class.base_class if val.is_a?(Base)
+ when Base
+ value.class.base_class
+ end
+ end
+
+ def convert_to_id(value)
+ case value
+ when Base
+ value._read_attribute(primary_key)
+ else
+ value
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb
new file mode 100644
index 0000000000..6fa5b16f73
--- /dev/null
+++ b/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb
@@ -0,0 +1,17 @@
+module ActiveRecord
+ class PredicateBuilder
+ class BaseHandler # :nodoc:
+ def initialize(predicate_builder)
+ @predicate_builder = predicate_builder
+ end
+
+ def call(attribute, value)
+ predicate_builder.build(attribute, value.id)
+ end
+
+ protected
+
+ attr_reader :predicate_builder
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb
new file mode 100644
index 0000000000..6cec75dc0a
--- /dev/null
+++ b/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb
@@ -0,0 +1,17 @@
+module ActiveRecord
+ class PredicateBuilder
+ class BasicObjectHandler # :nodoc:
+ def initialize(predicate_builder)
+ @predicate_builder = predicate_builder
+ end
+
+ def call(attribute, value)
+ attribute.eq(value)
+ end
+
+ protected
+
+ attr_reader :predicate_builder
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/class_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/class_handler.rb
new file mode 100644
index 0000000000..ed313fc9d4
--- /dev/null
+++ b/activerecord/lib/active_record/relation/predicate_builder/class_handler.rb
@@ -0,0 +1,27 @@
+module ActiveRecord
+ class PredicateBuilder
+ class ClassHandler # :nodoc:
+ def initialize(predicate_builder)
+ @predicate_builder = predicate_builder
+ end
+
+ def call(attribute, value)
+ print_deprecation_warning
+ predicate_builder.build(attribute, value.name)
+ end
+
+ protected
+
+ attr_reader :predicate_builder
+
+ private
+
+ def print_deprecation_warning
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing a class as a value in an Active Record query is deprecated and
+ will be removed. Pass a string instead.
+ MSG
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb
new file mode 100644
index 0000000000..1b3849e3ad
--- /dev/null
+++ b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb
@@ -0,0 +1,17 @@
+module ActiveRecord
+ class PredicateBuilder
+ class RangeHandler # :nodoc:
+ def initialize(predicate_builder)
+ @predicate_builder = predicate_builder
+ end
+
+ def call(attribute, value)
+ attribute.between(value)
+ end
+
+ protected
+
+ attr_reader :predicate_builder
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/query_attribute.rb b/activerecord/lib/active_record/relation/query_attribute.rb
new file mode 100644
index 0000000000..e69319b4de
--- /dev/null
+++ b/activerecord/lib/active_record/relation/query_attribute.rb
@@ -0,0 +1,19 @@
+require 'active_record/attribute'
+
+module ActiveRecord
+ class Relation
+ class QueryAttribute < Attribute
+ def type_cast(value)
+ value
+ end
+
+ def value_for_database
+ @value_for_database ||= super
+ end
+
+ def with_cast_value(value)
+ QueryAttribute.new(name, value, type)
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index a686e3263b..69ce5cdc2a 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -1,6 +1,9 @@
-require 'active_support/core_ext/array/wrap'
-require 'active_support/core_ext/string/filters'
+require "active_record/relation/from_clause"
+require "active_record/relation/query_attribute"
+require "active_record/relation/where_clause"
+require "active_record/relation/where_clause_factory"
require 'active_model/forbidden_attributes_protection'
+require 'active_support/core_ext/string/filters'
module ActiveRecord
module QueryMethods
@@ -39,38 +42,24 @@ module ActiveRecord
# User.where.not(name: "Jon", role: "admin")
# # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin'
def not(opts, *rest)
- where_value = @scope.send(:build_where, opts, rest).map do |rel|
- case rel
- when NilClass
- raise ArgumentError, 'Invalid argument for .where.not(), got nil.'
- when Arel::Nodes::In
- Arel::Nodes::NotIn.new(rel.left, rel.right)
- when Arel::Nodes::Equality
- Arel::Nodes::NotEqual.new(rel.left, rel.right)
- when String
- Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(rel))
- else
- Arel::Nodes::Not.new(rel)
- end
- end
+ where_clause = @scope.send(:where_clause_factory).build(opts, rest)
@scope.references!(PredicateBuilder.references(opts)) if Hash === opts
- @scope.where_values += where_value
+ @scope.where_clause += where_clause.invert
@scope
end
end
Relation::MULTI_VALUE_METHODS.each do |name|
class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}_values # def select_values
- @values[:#{name}] || [] # @values[:select] || []
- end # end
- #
- def #{name}_values=(values) # def select_values=(values)
- raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
- check_cached_relation
- @values[:#{name}] = values # @values[:select] = values
- end # end
+ def #{name}_values # def select_values
+ @values[:#{name}] || [] # @values[:select] || []
+ end # end
+ #
+ def #{name}_values=(values) # def select_values=(values)
+ assert_mutability! # assert_mutability!
+ @values[:#{name}] = values # @values[:select] = values
+ end # end
CODE
end
@@ -85,21 +74,27 @@ module ActiveRecord
Relation::SINGLE_VALUE_METHODS.each do |name|
class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{name}_value=(value) # def readonly_value=(value)
- raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
- check_cached_relation
+ assert_mutability! # assert_mutability!
@values[:#{name}] = value # @values[:readonly] = value
end # end
CODE
end
- def check_cached_relation # :nodoc:
- if defined?(@arel) && @arel
- @arel = nil
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Modifying already cached Relation. The cache will be reset. Use a
- cloned Relation to prevent this warning.
- MSG
- end
+ Relation::CLAUSE_METHODS.each do |name|
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name}_clause # def where_clause
+ @values[:#{name}] || new_#{name}_clause # @values[:where] || new_where_clause
+ end # end
+ #
+ def #{name}_clause=(value) # def where_clause=(value)
+ assert_mutability! # assert_mutability!
+ @values[:#{name}] = value # @values[:where] = value
+ end # end
+ CODE
+ end
+
+ def bound_attributes
+ from_clause.binds + arel.bind_values + where_clause.binds + having_clause.binds
end
def create_with_value # :nodoc:
@@ -404,9 +399,8 @@ module ActiveRecord
raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
end
- Array(target_value).each do |val|
- where_unscoping(val)
- end
+ target_values = Array(target_value).map(&:to_s)
+ self.where_clause = where_clause.except(*target_values)
end
else
raise ArgumentError, "Unrecognized scoping: #{args.inspect}. Use .unscope(where: :attribute_name) or .unscope(:order), for example."
@@ -427,27 +421,16 @@ module ActiveRecord
# => SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
def joins(*args)
check_if_method_has_arguments!(:joins, args)
-
- args.compact!
- args.flatten!
-
spawn.joins!(*args)
end
def joins!(*args) # :nodoc:
+ args.compact!
+ args.flatten!
self.joins_values += args
self
end
- def bind(value) # :nodoc:
- spawn.bind!(value)
- end
-
- def bind!(value) # :nodoc:
- self.bind_values += [value]
- self
- end
-
# Returns a new relation, which is the result of filtering the current relation
# according to the conditions in the arguments.
#
@@ -583,7 +566,7 @@ module ActiveRecord
references!(PredicateBuilder.references(opts))
end
- self.where_values += build_where(opts, rest)
+ self.where_clause += where_clause_factory.build(opts, rest)
self
end
@@ -599,6 +582,37 @@ module ActiveRecord
unscope(where: conditions.keys).where(conditions)
end
+ # Returns a new relation, which is the logical union of this relation and the one passed as an
+ # argument.
+ #
+ # 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.
+ #
+ # Post.where("id = 1").or(Post.where("id = 2"))
+ # # SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'id = 2'))
+ #
+ def or(other)
+ spawn.or!(other)
+ end
+
+ def or!(other) # :nodoc:
+ unless structurally_compatible_for_or?(other)
+ raise ArgumentError, 'Relation passed to #or must be structurally compatible'
+ end
+
+ self.where_clause = self.where_clause.or(other.where_clause)
+ self.having_clause = self.having_clause.or(other.having_clause)
+
+ self
+ end
+
+ private def structurally_compatible_for_or?(other) # :nodoc:
+ Relation::SINGLE_VALUE_METHODS.all? { |m| send("#{m}_value") == other.send("#{m}_value") } &&
+ (Relation::MULTI_VALUE_METHODS - [:extending]).all? { |m| send("#{m}_values") == other.send("#{m}_values") } &&
+ (Relation::CLAUSE_METHODS - [:having, :where]).all? { |m| send("#{m}_clause") != other.send("#{m}_clause") }
+ end
+
# Allows to specify a HAVING clause. Note that you can't use HAVING
# without also specifying a GROUP clause.
#
@@ -610,7 +624,7 @@ module ActiveRecord
def having!(opts, *rest) # :nodoc:
references!(PredicateBuilder.references(opts)) if Hash === opts
- self.having_values += build_where(opts, rest)
+ self.having_clause += having_clause_factory.build(opts, rest)
self
end
@@ -758,7 +772,7 @@ module ActiveRecord
end
def from!(value, subquery_name = nil) # :nodoc:
- self.from_value = [value, subquery_name]
+ self.from_clause = Relation::FromClause.new(value, subquery_name)
self
end
@@ -859,154 +873,56 @@ module ActiveRecord
private
+ def assert_mutability!
+ raise ImmutableRelation if @loaded
+ raise ImmutableRelation if defined?(@arel) && @arel
+ end
+
def build_arel
- arel = Arel::SelectManager.new(table.engine, table)
+ arel = Arel::SelectManager.new(table)
build_joins(arel, joins_values.flatten) unless joins_values.empty?
- collapse_wheres(arel, (where_values - [''])) #TODO: Add uniq with real value comparison / ignore uniqs that have binds
-
- arel.having(*having_values.uniq.reject(&:blank?)) unless having_values.empty?
-
+ arel.where(where_clause.ast) unless where_clause.empty?
+ arel.having(having_clause.ast) unless having_clause.empty?
arel.take(connection.sanitize_limit(limit_value)) if limit_value
arel.skip(offset_value.to_i) if offset_value
-
- arel.group(*group_values.uniq.reject(&:blank?)) unless group_values.empty?
+ arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
build_order(arel)
- build_select(arel, select_values.uniq)
+ build_select(arel)
arel.distinct(distinct_value)
- arel.from(build_from) if from_value
+ arel.from(build_from) unless from_clause.empty?
arel.lock(lock_value) if lock_value
- # Reorder bind indexes if joins produced bind values
- bvs = arel.bind_values + bind_values
- reorder_bind_params(arel.ast, bvs)
arel
end
- def reorder_bind_params(ast, bvs)
- ast.grep(Arel::Nodes::BindParam).each_with_index do |bp, i|
- column = bvs[i].first
- bp.replace connection.substitute_at(column, i)
- end
- end
-
def symbol_unscoping(scope)
if !VALID_UNSCOPING_VALUES.include?(scope)
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
end
- single_val_method = Relation::SINGLE_VALUE_METHODS.include?(scope)
- unscope_code = "#{scope}_value#{'s' unless single_val_method}="
+ clause_method = Relation::CLAUSE_METHODS.include?(scope)
+ multi_val_method = Relation::MULTI_VALUE_METHODS.include?(scope)
+ if clause_method
+ unscope_code = "#{scope}_clause="
+ else
+ unscope_code = "#{scope}_value#{'s' if multi_val_method}="
+ end
case scope
when :order
result = []
- when :where
- self.bind_values = []
else
- result = [] unless single_val_method
+ result = [] if multi_val_method
end
self.send(unscope_code, result)
end
- def where_unscoping(target_value)
- target_value = target_value.to_s
-
- where_values.reject! do |rel|
- case rel
- when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThanOrEqual
- subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right)
- subrelation.name == target_value
- end
- end
-
- bind_values.reject! { |col,_| col.name == target_value }
- end
-
- def custom_join_ast(table, joins)
- joins = joins.reject(&:blank?)
-
- return [] if joins.empty?
-
- joins.map! do |join|
- case join
- when Array
- join = Arel.sql(join.join(' ')) if array_of_strings?(join)
- when String
- join = Arel.sql(join)
- end
- table.create_string_join(join)
- end
- end
-
- def collapse_wheres(arel, wheres)
- predicates = wheres.map do |where|
- next where if ::Arel::Nodes::Equality === where
- where = Arel.sql(where) if String === where
- Arel::Nodes::Grouping.new(where)
- end
-
- arel.where(Arel::Nodes::And.new(predicates)) if predicates.present?
- end
-
- def build_where(opts, other = [])
- case opts
- when String, Array
- [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
- when Hash
- opts = PredicateBuilder.resolve_column_aliases(klass, opts)
-
- tmp_opts, bind_values = create_binds(opts)
- self.bind_values += bind_values
-
- attributes = @klass.send(:expand_hash_conditions_for_aggregates, tmp_opts)
- add_relations_to_bind_values(attributes)
-
- PredicateBuilder.build_from_hash(klass, attributes, table)
- else
- [opts]
- end
- end
-
- def create_binds(opts)
- bindable, non_binds = opts.partition do |column, value|
- case value
- when String, Integer, ActiveRecord::StatementCache::Substitute
- @klass.columns_hash.include? column.to_s
- else
- false
- end
- end
-
- association_binds, non_binds = non_binds.partition do |column, value|
- value.is_a?(Hash) && association_for_table(column)
- end
-
- new_opts = {}
- binds = []
-
- bindable.each do |(column,value)|
- binds.push [@klass.columns_hash[column.to_s], value]
- new_opts[column] = connection.substitute_at(column)
- end
-
- association_binds.each do |(column, value)|
- association_relation = association_for_table(column).klass.send(:relation)
- association_new_opts, association_bind = association_relation.send(:create_binds, value)
- new_opts[column] = association_new_opts
- binds += association_bind
- end
-
- non_binds.each { |column,value| new_opts[column] = value }
-
- [new_opts, binds]
- end
-
def association_for_table(table_name)
table_name = table_name.to_s
@klass._reflect_on_association(table_name) ||
@@ -1014,11 +930,11 @@ module ActiveRecord
end
def build_from
- opts, name = from_value
+ opts = from_clause.value
+ name = from_clause.name
case opts
when Relation
name ||= 'subquery'
- self.bind_values = opts.bind_values + self.bind_values
opts.arel.as(name.to_s)
else
opts
@@ -1040,13 +956,14 @@ module ActiveRecord
raise 'unknown class: %s' % join.class.name
end
end
+ buckets.default = []
- association_joins = buckets[:association_join] || []
- stashed_association_joins = buckets[:stashed_join] || []
- join_nodes = (buckets[:join_node] || []).uniq
- string_joins = (buckets[:string_join] || []).map(&:strip).uniq
+ association_joins = buckets[:association_join]
+ stashed_association_joins = buckets[:stashed_join]
+ join_nodes = buckets[:join_node].uniq
+ string_joins = buckets[:string_join].map(&:strip).uniq
- join_list = join_nodes + custom_join_ast(manager, string_joins)
+ join_list = join_nodes + convert_join_strings_to_ast(manager, string_joins)
join_dependency = ActiveRecord::Associations::JoinDependency.new(
@klass,
@@ -1066,17 +983,35 @@ module ActiveRecord
manager
end
- def build_select(arel, selects)
- if !selects.empty?
- expanded_select = selects.map do |field|
- columns_hash.key?(field.to_s) ? arel_table[field] : field
- end
- arel.project(*expanded_select)
+ def convert_join_strings_to_ast(table, joins)
+ joins
+ .flatten
+ .reject(&:blank?)
+ .map { |join| table.create_string_join(Arel.sql(join)) }
+ end
+
+ def build_select(arel)
+ if select_values.any?
+ arel.project(*arel_columns(select_values.uniq))
else
arel.project(@klass.arel_table[Arel.star])
end
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
+ end
+ end
+ end
+
def reverse_sql_order(order_query)
order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
@@ -1095,10 +1030,6 @@ module ActiveRecord
end
end
- def array_of_strings?(o)
- o.is_a?(Array) && o.all? { |obj| obj.is_a?(String) }
- end
-
def build_order(arel)
orders = order_values.uniq
orders.reject!(&:blank?)
@@ -1166,18 +1097,18 @@ module ActiveRecord
end
end
- # This function is recursive just for better readablity.
- # #where argument doesn't support more than one level nested hash in real world.
- def add_relations_to_bind_values(attributes)
- if attributes.is_a?(Hash)
- attributes.each_value do |value|
- if value.is_a?(ActiveRecord::Relation)
- self.bind_values += value.bind_values
- else
- add_relations_to_bind_values(value)
- end
- end
- end
+ def new_where_clause
+ Relation::WhereClause.empty
+ end
+ alias new_having_clause new_where_clause
+
+ def where_clause_factory
+ @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder)
+ end
+ alias having_clause_factory where_clause_factory
+
+ def new_from_clause
+ Relation::FromClause.empty
end
end
end
diff --git a/activerecord/lib/active_record/relation/record_fetch_warning.rb b/activerecord/lib/active_record/relation/record_fetch_warning.rb
new file mode 100644
index 0000000000..14e1bf89fa
--- /dev/null
+++ b/activerecord/lib/active_record/relation/record_fetch_warning.rb
@@ -0,0 +1,49 @@
+module ActiveRecord
+ class Relation
+ module RecordFetchWarning
+ # When this module is prepended to ActiveRecord::Relation and
+ # `config.active_record.warn_on_records_fetched_greater_than` is
+ # set to an integer, if the number of records a query returns is
+ # greater than the value of `warn_on_records_fetched_greater_than`,
+ # a warning is logged. This allows for the detection of queries that
+ # return a large number of records, which could cause memory bloat.
+ #
+ # In most cases, fetching large number of records can be performed
+ # efficiently using the ActiveRecord::Batches methods.
+ # See active_record/lib/relation/batches.rb for more information.
+ def exec_queries
+ QueryRegistry.reset
+
+ super.tap do
+ if logger && warn_on_records_fetched_greater_than
+ if @records.length > warn_on_records_fetched_greater_than
+ logger.warn "Query fetched #{@records.size} #{@klass} records: #{QueryRegistry.queries.join(";")}"
+ end
+ end
+ end
+ end
+
+ ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
+ payload = args.last
+
+ QueryRegistry.queries << payload[:sql]
+ end
+
+ class QueryRegistry # :nodoc:
+ extend ActiveSupport::PerThreadRegistry
+
+ attr_accessor :queries
+
+ def initialize
+ reset
+ end
+
+ def reset
+ @queries = []
+ end
+ end
+ end
+ end
+end
+
+ActiveRecord::Relation.prepend ActiveRecord::Relation::RecordFetchWarning
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index 57d66bce4b..70da37fa84 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -32,7 +32,7 @@ module ActiveRecord
elsif other
spawn.merge!(other)
else
- self
+ raise ArgumentError, "invalid argument: #{other.inspect}."
end
end
@@ -58,16 +58,13 @@ module ActiveRecord
# Post.order('id asc').only(:where) # discards the order condition
# Post.order('id asc').only(:where, :order) # uses the specified order
def only(*onlies)
- if onlies.any? { |o| o == :where }
- onlies << :bind
- end
relation_with values.slice(*onlies)
end
private
def relation_with(values) # :nodoc:
- result = Relation.create(klass, table, values)
+ result = Relation.create(klass, table, predicate_builder, values)
result.extend(*extending_values) if extending_values.any?
result
end
diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb
new file mode 100644
index 0000000000..f9b9e640ec
--- /dev/null
+++ b/activerecord/lib/active_record/relation/where_clause.rb
@@ -0,0 +1,173 @@
+module ActiveRecord
+ class Relation
+ class WhereClause # :nodoc:
+ attr_reader :binds
+
+ delegate :any?, :empty?, to: :predicates
+
+ def initialize(predicates, binds)
+ @predicates = predicates
+ @binds = binds
+ end
+
+ def +(other)
+ WhereClause.new(
+ predicates + other.predicates,
+ binds + other.binds,
+ )
+ end
+
+ def merge(other)
+ WhereClause.new(
+ predicates_unreferenced_by(other) + other.predicates,
+ non_conflicting_binds(other) + other.binds,
+ )
+ end
+
+ def except(*columns)
+ WhereClause.new(
+ predicates_except(columns),
+ binds_except(columns),
+ )
+ end
+
+ def or(other)
+ if empty?
+ self
+ elsif other.empty?
+ other
+ else
+ WhereClause.new(
+ [ast.or(other.ast)],
+ binds + other.binds
+ )
+ end
+ end
+
+ def to_h(table_name = nil)
+ equalities = predicates.grep(Arel::Nodes::Equality)
+ if table_name
+ equalities = equalities.select do |node|
+ node.left.relation.name == table_name
+ end
+ end
+
+ binds = self.binds.map { |attr| [attr.name, attr.value] }.to_h
+
+ equalities.map { |node|
+ name = node.left.name
+ [name, binds.fetch(name.to_s) {
+ case node.right
+ when Array then node.right.map(&:val)
+ when Arel::Nodes::Casted, Arel::Nodes::Quoted
+ node.right.val
+ end
+ }]
+ }.to_h
+ end
+
+ def ast
+ Arel::Nodes::And.new(predicates_with_wrapped_sql_literals)
+ end
+
+ def ==(other)
+ other.is_a?(WhereClause) &&
+ predicates == other.predicates &&
+ binds == other.binds
+ end
+
+ def invert
+ WhereClause.new(inverted_predicates, binds)
+ end
+
+ def self.empty
+ new([], [])
+ end
+
+ protected
+
+ attr_reader :predicates
+
+ def referenced_columns
+ @referenced_columns ||= begin
+ equality_nodes = predicates.select { |n| equality_node?(n) }
+ Set.new(equality_nodes, &:left)
+ end
+ end
+
+ private
+
+ def predicates_unreferenced_by(other)
+ predicates.reject do |n|
+ equality_node?(n) && other.referenced_columns.include?(n.left)
+ end
+ end
+
+ def equality_node?(node)
+ node.respond_to?(:operator) && node.operator == :==
+ end
+
+ def non_conflicting_binds(other)
+ conflicts = referenced_columns & other.referenced_columns
+ conflicts.map! { |node| node.name.to_s }
+ binds.reject { |attr| conflicts.include?(attr.name) }
+ end
+
+ def inverted_predicates
+ predicates.map { |node| invert_predicate(node) }
+ end
+
+ def invert_predicate(node)
+ case node
+ when NilClass
+ raise ArgumentError, 'Invalid argument for .where.not(), got nil.'
+ when Arel::Nodes::In
+ Arel::Nodes::NotIn.new(node.left, node.right)
+ when Arel::Nodes::Equality
+ Arel::Nodes::NotEqual.new(node.left, node.right)
+ when String
+ Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(node))
+ else
+ Arel::Nodes::Not.new(node)
+ end
+ end
+
+ def predicates_except(columns)
+ predicates.reject do |node|
+ case node
+ when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThanOrEqual
+ subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right)
+ columns.include?(subrelation.name.to_s)
+ end
+ end
+ end
+
+ def binds_except(columns)
+ binds.reject do |attr|
+ columns.include?(attr.name)
+ end
+ end
+
+ def predicates_with_wrapped_sql_literals
+ non_empty_predicates.map do |node|
+ if Arel::Nodes::Equality === node
+ node
+ else
+ wrap_sql_literal(node)
+ end
+ end
+ end
+
+ def non_empty_predicates
+ predicates - ['']
+ end
+
+ def wrap_sql_literal(node)
+ if ::String === node
+ node = Arel.sql(node)
+ end
+ Arel::Nodes::Grouping.new(node)
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb
new file mode 100644
index 0000000000..0430922be3
--- /dev/null
+++ b/activerecord/lib/active_record/relation/where_clause_factory.rb
@@ -0,0 +1,34 @@
+module ActiveRecord
+ class Relation
+ class WhereClauseFactory
+ def initialize(klass, predicate_builder)
+ @klass = klass
+ @predicate_builder = predicate_builder
+ end
+
+ def build(opts, other)
+ binds = []
+
+ case opts
+ when String, Array
+ parts = [klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
+ when Hash
+ attributes = predicate_builder.resolve_column_aliases(opts)
+ attributes = klass.send(:expand_hash_conditions_for_aggregates, attributes)
+
+ attributes, binds = predicate_builder.create_binds(attributes)
+
+ parts = predicate_builder.build_from_hash(attributes)
+ else
+ parts = [opts]
+ end
+
+ WhereClause.new(parts, binds)
+ end
+
+ protected
+
+ attr_reader :klass, :predicate_builder
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb
index 3a3e65ef32..500c478e65 100644
--- a/activerecord/lib/active_record/result.rb
+++ b/activerecord/lib/active_record/result.rb
@@ -81,7 +81,7 @@ module ActiveRecord
def cast_values(type_overrides = {}) # :nodoc:
types = columns.map { |name| column_type(name, type_overrides) }
result = rows.map do |values|
- types.zip(values).map { |type, value| type.type_cast_from_database(value) }
+ types.zip(values).map { |type, value| type.deserialize(value) }
end
columns.one? ? result.map!(&:first) : result
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 6a130c145b..c7f55ebaa1 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -3,14 +3,11 @@ module ActiveRecord
extend ActiveSupport::Concern
module ClassMethods
- def quote_value(value, column) #:nodoc:
- connection.quote(value, column)
- end
-
# Used to sanitize objects before they're used in an SQL SELECT statement. Delegates to <tt>connection.quote</tt>.
def sanitize(object) #:nodoc:
connection.quote(object)
end
+ alias_method :quote_value, :sanitize
protected
@@ -72,41 +69,14 @@ module ActiveRecord
expanded_attrs
end
- # Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
- # { name: "foo'bar", group_id: 4 }
- # # => "name='foo''bar' and group_id= 4"
- # { status: nil, group_id: [1,2,3] }
- # # => "status IS NULL and group_id IN (1,2,3)"
- # { age: 13..18 }
- # # => "age BETWEEN 13 AND 18"
- # { 'other_records.id' => 7 }
- # # => "`other_records`.`id` = 7"
- # { other_records: { id: 7 } }
- # # => "`other_records`.`id` = 7"
- # And for value objects on a composed_of relationship:
- # { address: Address.new("123 abc st.", "chicago") }
- # # => "address_street='123 abc st.' and address_city='chicago'"
- def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
- ActiveSupport::Deprecation.warn(<<-EOWARN)
-sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
- EOWARN
- attrs = PredicateBuilder.resolve_column_aliases self, attrs
- attrs = expand_hash_conditions_for_aggregates(attrs)
-
- table = Arel::Table.new(table_name, arel_engine).alias(default_table_name)
- PredicateBuilder.build_from_hash(self, attrs, table).map { |b|
- connection.visitor.compile b
- }.join(' AND ')
- end
- alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
-
# Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
# { status: nil, group_id: 1 }
# # => "status = NULL , group_id = 1"
def sanitize_sql_hash_for_assignment(attrs, table)
c = connection
attrs.map do |attr, value|
- "#{c.quote_table_name_for_assignment(table, attr)} = #{quote_bound_value(value, c, columns_hash[attr.to_s])}"
+ value = type_for_attribute(attr.to_s).serialize(value)
+ "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
end.join(', ')
end
@@ -162,10 +132,8 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
end
end
- def quote_bound_value(value, c = connection, column = nil) #:nodoc:
- if column
- c.quote(value, column)
- elsif value.respond_to?(:map) && !value.acts_like?(:string)
+ def quote_bound_value(value, c = connection) #:nodoc:
+ if value.respond_to?(:map) && !value.acts_like?(:string)
if value.respond_to?(:empty?) && value.empty?
c.quote(nil)
else
@@ -185,7 +153,7 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
# TODO: Deprecate this
def quoted_id
- self.class.quote_value(id, column_for_attribute(self.class.primary_key))
+ self.class.quote_value(@attributes[self.class.primary_key].value_for_database)
end
end
end
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 261fb9d992..eaeaf0321b 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -44,7 +44,6 @@ module ActiveRecord
def initialize(connection, options = {})
@connection = connection
- @types = @connection.native_database_types
@version = Migrator::current_version rescue nil
@options = options
end
@@ -106,7 +105,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
@@ -118,23 +120,24 @@ HEADER
if pkcol
if pk != 'id'
tbl.print %Q(, primary_key: "#{pk}")
- elsif pkcol.sql_type == 'bigint'
- tbl.print ", id: :bigserial"
- elsif pkcol.sql_type == 'uuid'
- tbl.print ", id: :uuid"
- tbl.print %Q(, default: "#{pkcol.default_function}") if pkcol.default_function
+ end
+ pkcolspec = @connection.column_spec_for_primary_key(pkcol)
+ if pkcolspec
+ pkcolspec.each do |key, value|
+ tbl.print ", #{key}: #{value}"
+ end
end
else
tbl.print ", id: false"
end
- tbl.print ", force: true"
+ tbl.print ", force: :cascade"
tbl.puts " do |t|"
# then dump all non-primary key columns
column_specs = columns.map do |column|
raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
next if column.name == pk
- @connection.column_spec(column, @types)
+ @connection.column_spec(column)
end.compact
# find all migration keys used in this table
@@ -244,12 +247,7 @@ HEADER
def ignored?(table_name)
['schema_migrations', ignore_tables].flatten.any? do |ignored|
- case ignored
- when String; remove_prefix_and_suffix(table_name) == ignored
- when Regexp; remove_prefix_and_suffix(table_name) =~ ignored
- else
- raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
- end
+ ignored === remove_prefix_and_suffix(table_name)
end
end
end
diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb
index 3e43591672..f049b658c4 100644
--- a/activerecord/lib/active_record/scoping.rb
+++ b/activerecord/lib/active_record/scoping.rb
@@ -11,11 +11,22 @@ module ActiveRecord
module ClassMethods
def current_scope #:nodoc:
- ScopeRegistry.value_for(:current_scope, base_class.to_s)
+ ScopeRegistry.value_for(:current_scope, self.to_s)
end
def current_scope=(scope) #:nodoc:
- ScopeRegistry.set_value_for(:current_scope, base_class.to_s, scope)
+ ScopeRegistry.set_value_for(:current_scope, self.to_s, scope)
+ end
+
+ # Collects attributes from scopes that should be applied when creating
+ # an AR instance for the particular class this is called on.
+ def scope_attributes # :nodoc:
+ all.scope_for_create
+ end
+
+ # Are there attributes associated with this scope?
+ def scope_attributes? # :nodoc:
+ current_scope
end
end
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index 18190cb535..3590b8846e 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -33,6 +33,11 @@ module ActiveRecord
block_given? ? relation.scoping { yield } : relation
end
+ # Are there attributes associated with this scope?
+ def scope_attributes? # :nodoc:
+ super || default_scopes.any? || respond_to?(:default_scope)
+ end
+
def before_remove_const #:nodoc:
self.current_scope = nil
end
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index ec1edf0e01..7b62626896 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -30,18 +30,13 @@ module ActiveRecord
end
def default_scoped # :nodoc:
- relation.merge(build_default_scope)
- end
-
- # Collects attributes from scopes that should be applied when creating
- # an AR instance for the particular class this is called on.
- def scope_attributes # :nodoc:
- all.scope_for_create
- end
+ scope = build_default_scope
- # Are there default attributes associated with this scope?
- def scope_attributes? # :nodoc:
- current_scope || default_scopes.any?
+ if scope
+ relation.spawn.merge!(scope)
+ else
+ relation
+ end
end
# Adds a class method for retrieving and querying objects. A \scope
@@ -139,7 +134,7 @@ module ActiveRecord
# Article.published.featured.latest_article
# Article.featured.titles
def scope(name, body, &block)
- unless body.respond_to?:call
+ unless body.respond_to?(:call)
raise ArgumentError, 'The scope body needs to be callable.'
end
@@ -151,11 +146,20 @@ module ActiveRecord
extension = Module.new(&block) if block
- singleton_class.send(:define_method, name) do |*args|
- scope = all.scoping { body.call(*args) }
- scope = scope.extending(extension) if extension
+ if body.respond_to?(:to_proc)
+ singleton_class.send(:define_method, name) do |*args|
+ scope = all.scoping { instance_exec(*args, &body) }
+ scope = scope.extending(extension) if extension
+
+ scope || all
+ end
+ else
+ singleton_class.send(:define_method, name) do |*args|
+ scope = all.scoping { body.call(*args) }
+ scope = scope.extending(extension) if extension
- scope || all
+ scope || all
+ end
end
end
end
diff --git a/activerecord/lib/active_record/secure_token.rb b/activerecord/lib/active_record/secure_token.rb
new file mode 100644
index 0000000000..a3023a0cb4
--- /dev/null
+++ b/activerecord/lib/active_record/secure_token.rb
@@ -0,0 +1,38 @@
+module ActiveRecord
+ module SecureToken
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ # Example using has_secure_token
+ #
+ # # Schema: User(token:string, auth_token:string)
+ # class User < ActiveRecord::Base
+ # has_secure_token
+ # has_secure_token :auth_token
+ # end
+ #
+ # user = User.new
+ # user.save
+ # user.token # => "4kUgL2pdQMSCQtjE"
+ # user.auth_token # => "77TMHrHJFvFDwodq8w7Ev2m7"
+ # user.regenerate_token # => true
+ # user.regenerate_auth_token # => true
+ #
+ # SecureRandom::base58 is used to generate the 24-character unique token, so collisions are highly unlikely.
+ #
+ # Note that it's still possible to generate a race condition in the database in the same way that
+ # <tt>validates_uniqueness_of</tt> can. You're encouraged to add a unique index in the database to deal
+ # with this even more unlikely scenario.
+ def has_secure_token(attribute = :token)
+ # Load securerandom only when has_secure_token is used.
+ require 'active_support/core_ext/securerandom'
+ define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token }
+ before_create { self.send("#{attribute}=", self.class.generate_unique_secure_token) unless self.send("#{attribute}?")}
+ end
+
+ def generate_unique_secure_token
+ SecureRandom.base58(24)
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb
index bd9079b596..48c12dcf9f 100644
--- a/activerecord/lib/active_record/serialization.rb
+++ b/activerecord/lib/active_record/serialization.rb
@@ -11,7 +11,7 @@ module ActiveRecord #:nodoc:
def serializable_hash(options = nil)
options = options.try(:clone) || {}
- options[:except] = Array(options[:except]).map { |n| n.to_s }
+ options[:except] = Array(options[:except]).map(&:to_s)
options[:except] |= Array(self.class.inheritance_column)
super(options)
diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb
index c2484d02ed..89b7e0be82 100644
--- a/activerecord/lib/active_record/serializers/xml_serializer.rb
+++ b/activerecord/lib/active_record/serializers/xml_serializer.rb
@@ -180,9 +180,9 @@ module ActiveRecord #:nodoc:
class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc:
def compute_type
klass = @serializable.class
- column = klass.columns_hash[name] || Type::Value.new
+ cast_type = klass.type_for_attribute(name)
- type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name] || column.type
+ type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name] || cast_type.type
{ :text => :string,
:time => :datetime }[type] || type
diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb
index aece446384..95986c820c 100644
--- a/activerecord/lib/active_record/statement_cache.rb
+++ b/activerecord/lib/active_record/statement_cache.rb
@@ -1,22 +1,33 @@
module ActiveRecord
# Statement cache is used to cache a single statement in order to avoid creating the AST again.
- # Initializing the cache is done by passing the statement in the initialization block:
+ # Initializing the cache is done by passing the statement in the create block:
#
- # cache = ActiveRecord::StatementCache.new do
- # Book.where(name: "my book").limit(100)
+ # cache = StatementCache.create(Book.connection) do |params|
+ # Book.where(name: "my book").where("author_id > 3")
# end
#
# The cached statement is executed by using the +execute+ method:
#
- # cache.execute
+ # cache.execute([], Book, Book.connection)
#
# The relation returned by the block is cached, and for each +execute+ call the cached relation gets duped.
# Database is queried when +to_a+ is called on the relation.
- class StatementCache
- class Substitute; end
+ #
+ # If you want to cache the statement without the values you can use the +bind+ method of the
+ # block parameter.
+ #
+ # cache = StatementCache.create(Book.connection) do |params|
+ # Book.where(name: params.bind)
+ # end
+ #
+ # And pass the bind values as the first argument of +execute+ call.
+ #
+ # cache.execute(["my book"], Book, Book.connection)
+ class StatementCache # :nodoc:
+ class Substitute; end # :nodoc:
- class Query
+ class Query # :nodoc:
def initialize(sql)
@sql = sql
end
@@ -26,7 +37,7 @@ module ActiveRecord
end
end
- class PartialQuery < Query
+ class PartialQuery < Query # :nodoc:
def initialize values
@values = values
@indexes = values.each_with_index.find_all { |thing,i|
@@ -36,8 +47,8 @@ module ActiveRecord
def sql_for(binds, connection)
val = @values.dup
- binds = binds.dup
- @indexes.each { |i| val[i] = connection.quote(*binds.shift.reverse) }
+ binds = connection.prepare_binds_for_database(binds)
+ @indexes.each { |i| val[i] = connection.quote(binds.shift) }
val.join
end
end
@@ -51,26 +62,26 @@ module ActiveRecord
PartialQuery.new collected
end
- class Params
+ class Params # :nodoc:
def bind; Substitute.new; end
end
- class BindMap
- def initialize(bind_values)
+ class BindMap # :nodoc:
+ def initialize(bound_attributes)
@indexes = []
- @bind_values = bind_values
+ @bound_attributes = bound_attributes
- bind_values.each_with_index do |(_, value), i|
- if Substitute === value
+ bound_attributes.each_with_index do |attr, i|
+ if Substitute === attr.value
@indexes << i
end
end
end
def bind(values)
- bvs = @bind_values.map { |pair| pair.dup }
- @indexes.each_with_index { |offset,i| bvs[offset][1] = values[i] }
- bvs
+ bas = @bound_attributes.dup
+ @indexes.each_with_index { |offset,i| bas[offset] = bas[offset].with_cast_value(values[i]) }
+ bas
end
end
@@ -78,7 +89,7 @@ module ActiveRecord
def self.create(connection, block = Proc.new)
relation = block.call Params.new
- bind_map = BindMap.new relation.bind_values
+ bind_map = BindMap.new relation.bound_attributes
query_builder = connection.cacheable_query relation.arel
new query_builder, bind_map
end
diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb
index 3c291f28e3..919bc58ba5 100644
--- a/activerecord/lib/active_record/store.rb
+++ b/activerecord/lib/active_record/store.rb
@@ -15,11 +15,15 @@ module ActiveRecord
# You can set custom coder to encode/decode your serialized attributes to/from different formats.
# JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+.
#
- # NOTE - If you are using PostgreSQL specific columns like +hstore+ or +json+ there is no need for
+ # NOTE: If you are using PostgreSQL specific columns like +hstore+ or +json+ there is no need for
# the serialization provided by +store+. Simply use +store_accessor+ instead to generate
# the accessor methods. Be aware that these columns use a string keyed hash and do not allow access
# using a symbol.
#
+ # NOTE: The default validations with the exception of +uniqueness+ will work.
+ # For example, if you want to check for +uniqueness+ with +hstore+ you will
+ # need to use a custom validation to handle it.
+ #
# Examples:
#
# class User < ActiveRecord::Base
diff --git a/activerecord/lib/active_record/suppressor.rb b/activerecord/lib/active_record/suppressor.rb
new file mode 100644
index 0000000000..b0b86865fd
--- /dev/null
+++ b/activerecord/lib/active_record/suppressor.rb
@@ -0,0 +1,55 @@
+module ActiveRecord
+ # ActiveRecord::Suppressor prevents the receiver from being saved during
+ # a given block.
+ #
+ # For example, here's a pattern of creating notifications when new comments
+ # are posted. (The notification may in turn trigger an email, a push
+ # notification, or just appear in the UI somewhere):
+ #
+ # class Comment < ActiveRecord::Base
+ # belongs_to :commentable, polymorphic: true
+ # after_create -> { Notification.create! comment: self,
+ # recipients: commentable.recipients }
+ # end
+ #
+ # That's what you want the bulk of the time. New comment creates a new
+ # Notification. But there may well be off cases, like copying a commentable
+ # and its comments, where you don't want that. So you'd have a concern
+ # something like this:
+ #
+ # module Copyable
+ # def copy_to(destination)
+ # Notification.suppress do
+ # # Copy logic that creates new comments that we do not want
+ # # triggering notifications.
+ # end
+ # end
+ # end
+ module Suppressor
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def suppress(&block)
+ SuppressorRegistry.suppressed[name] = true
+ yield
+ ensure
+ SuppressorRegistry.suppressed[name] = false
+ end
+ end
+
+ # Ignore saving events if we're in suppression mode.
+ def save!(*args) # :nodoc:
+ SuppressorRegistry.suppressed[self.class.name] ? true : super
+ end
+ end
+
+ class SuppressorRegistry # :nodoc:
+ extend ActiveSupport::PerThreadRegistry
+
+ attr_reader :suppressed
+
+ def initialize
+ @suppressed = {}
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb
new file mode 100644
index 0000000000..3dd6321a97
--- /dev/null
+++ b/activerecord/lib/active_record/table_metadata.rb
@@ -0,0 +1,62 @@
+module ActiveRecord
+ class TableMetadata # :nodoc:
+ delegate :foreign_type, :foreign_key, to: :association, prefix: true
+ delegate :association_primary_key, to: :association
+
+ def initialize(klass, arel_table, association = nil)
+ @klass = klass
+ @arel_table = arel_table
+ @association = association
+ end
+
+ def resolve_column_aliases(hash)
+ hash = hash.dup
+ hash.keys.grep(Symbol) do |key|
+ if klass.attribute_alias? key
+ hash[klass.attribute_alias(key)] = hash.delete key
+ end
+ end
+ hash
+ end
+
+ def arel_attribute(column_name)
+ arel_table[column_name]
+ end
+
+ def type(column_name)
+ if klass
+ klass.type_for_attribute(column_name.to_s)
+ else
+ Type::Value.new
+ end
+ end
+
+ def associated_with?(association_name)
+ klass && klass._reflect_on_association(association_name)
+ end
+
+ def associated_table(table_name)
+ return self if table_name == arel_table.name
+
+ association = klass._reflect_on_association(table_name)
+ if association && !association.polymorphic?
+ association_klass = association.klass
+ arel_table = association_klass.arel_table
+ else
+ type_caster = TypeCaster::Connection.new(klass, table_name)
+ association_klass = nil
+ arel_table = Arel::Table.new(table_name, type_caster: type_caster)
+ end
+
+ TableMetadata.new(association_klass, arel_table, association)
+ end
+
+ def polymorphic_association?
+ association && association.polymorphic?
+ end
+
+ protected
+
+ attr_reader :klass, :arel_table, :association
+ end
+end
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 1228de2bfd..683741768b 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -188,44 +188,39 @@ module ActiveRecord
class_for_adapter(configuration['adapter']).new(*arguments).structure_load(filename)
end
- def load_schema(format = ActiveRecord::Base.schema_format, file = nil)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- This method will act on a specific connection in the future.
- To act on the current connection, use `load_schema_current` instead.
- MSG
-
- load_schema_current(format, file)
- end
-
- def schema_file(format = ActiveSupport::Base.schema_format)
- case format
- when :ruby
- File.join(db_dir, "schema.rb")
- when :sql
- File.join(db_dir, "structure.sql")
- end
- end
-
- # This method is the successor of +load_schema+. We should rename it
- # after +load_schema+ went through a deprecation cycle. (Rails > 4.2)
- def load_schema_for(configuration, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc:
+ def load_schema(configuration, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc:
file ||= schema_file(format)
case format
when :ruby
check_schema_file(file)
- purge(configuration)
ActiveRecord::Base.establish_connection(configuration)
load(file)
when :sql
check_schema_file(file)
- purge(configuration)
structure_load(configuration, file)
else
raise ArgumentError, "unknown format #{format.inspect}"
end
end
+ def load_schema_for(*args)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ This method was renamed to `#load_schema` and will be removed in the future.
+ Use `#load_schema` instead.
+ MSG
+ load_schema(*args)
+ end
+
+ def schema_file(format = ActiveRecord::Base.schema_format)
+ case format
+ when :ruby
+ File.join(db_dir, "schema.rb")
+ when :sql
+ File.join(db_dir, "structure.sql")
+ end
+ end
+
def load_schema_current_if_exists(format = ActiveRecord::Base.schema_format, file = nil, environment = env)
if File.exist?(file || schema_file(format))
load_schema_current(format, file, environment)
@@ -234,7 +229,7 @@ module ActiveRecord
def load_schema_current(format = ActiveRecord::Base.schema_format, file = nil, environment = env)
each_current_configuration(environment) { |configuration|
- load_schema_for configuration, format, file
+ load_schema configuration, format, file
}
ActiveRecord::Base.establish_connection(environment.to_sym)
end
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index d890196f47..eafbb2c249 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -31,6 +31,7 @@ module ActiveRecord
end
establish_connection configuration
else
+ $stderr.puts error.inspect
$stderr.puts "Couldn't create database for #{configuration.inspect}, #{creation_options.inspect}"
$stderr.puts "(If you set the charset manually, make sure you have a matching collation)" if configuration['encoding']
end
diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
index ce1de4b76e..d7da95c8a9 100644
--- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
@@ -46,7 +46,15 @@ module ActiveRecord
def structure_dump(filename)
set_psql_env
- search_path = configuration['schema_search_path']
+
+ search_path = case ActiveRecord::Base.dump_schemas
+ when :schema_search_path
+ configuration['schema_search_path']
+ when :all
+ nil
+ when String
+ ActiveRecord::Base.dump_schemas
+ end
unless search_path.blank?
search_path = search_path.split(",").map{|search_path_part| "--schema=#{Shellwords.escape(search_path_part.strip)}" }.join(" ")
end
@@ -59,7 +67,7 @@ module ActiveRecord
def structure_load(filename)
set_psql_env
- Kernel.system("psql -q -f #{Shellwords.escape(filename)} #{configuration['database']}")
+ Kernel.system("psql -X -q -f #{Shellwords.escape(filename)} #{configuration['database']}")
end
private
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index 936a18d99a..20e4235788 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -57,8 +57,8 @@ module ActiveRecord
super
end
- def _update_record(*args)
- if should_record_timestamps?
+ def _update_record(*args, touch: true, **options)
+ if touch && should_record_timestamps?
current_time = current_time_from_proper_timezone
timestamp_attributes_for_update_in_model.each do |column|
@@ -67,7 +67,7 @@ module ActiveRecord
write_attribute(column, current_time)
end
end
- super
+ super(*args)
end
def should_record_timestamps?
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 f92e1de03b..311dacb449 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -2,24 +2,16 @@ module ActiveRecord
# See ActiveRecord::Transactions::ClassMethods for documentation.
module Transactions
extend ActiveSupport::Concern
+ #:nodoc:
ACTIONS = [:create, :destroy, :update]
- CALLBACK_WARN_MESSAGE = "Currently, Active Record suppresses errors raised " \
- "within `after_rollback`/`after_commit` callbacks and only print them to " \
- "the logs. In the next version, these errors will no longer be suppressed. " \
- "Instead, the errors will propagate normally just like in other Active " \
- "Record callbacks.\n" \
- "\n" \
- "You can opt into the new behavior and remove this warning by setting:\n" \
- "\n" \
- " config.active_record.raise_in_transactional_callbacks = true\n\n"
included do
define_callbacks :commit, :rollback,
- terminator: ->(_, result) { result == false },
+ :before_commit,
+ :before_commit_without_transaction_enrollment,
+ :commit_without_transaction_enrollment,
+ :rollback_without_transaction_enrollment,
scope: [:kind, :name]
-
- mattr_accessor :raise_in_transactional_callbacks, instance_writer: false
- self.raise_in_transactional_callbacks = false
end
# = Active Record Transactions
@@ -218,6 +210,11 @@ module ActiveRecord
connection.transaction(options, &block)
end
+ def before_commit(*args, &block) # :nodoc:
+ set_options_for_callbacks!(args)
+ set_callback(:before_commit, :before, *args, &block)
+ end
+
# This callback is called after a record has been created, updated, or destroyed.
#
# You can specify that the callback should only be fired by a certain action with
@@ -230,14 +227,9 @@ module ActiveRecord
# after_commit :do_foo_bar, on: [:create, :update]
# after_commit :do_bar_baz, on: [:update, :destroy]
#
- # Note that transactional fixtures do not play well with this feature. Please
- # use the +test_after_commit+ gem to have these hooks fired in tests.
def after_commit(*args, &block)
set_options_for_callbacks!(args)
set_callback(:commit, :after, *args, &block)
- unless ActiveRecord::Base.raise_in_transactional_callbacks
- ActiveSupport::Deprecation.warn(CALLBACK_WARN_MESSAGE)
- end
end
# This callback is called after a create, update, or destroy are rolled back.
@@ -246,9 +238,31 @@ module ActiveRecord
def after_rollback(*args, &block)
set_options_for_callbacks!(args)
set_callback(:rollback, :after, *args, &block)
- unless ActiveRecord::Base.raise_in_transactional_callbacks
- ActiveSupport::Deprecation.warn(CALLBACK_WARN_MESSAGE)
- end
+ end
+
+ def before_commit_without_transaction_enrollment(*args, &block) # :nodoc:
+ set_options_for_callbacks!(args)
+ set_callback(:before_commit_without_transaction_enrollment, :before, *args, &block)
+ end
+
+ def after_commit_without_transaction_enrollment(*args, &block) # :nodoc:
+ set_options_for_callbacks!(args)
+ set_callback(:commit_without_transaction_enrollment, :after, *args, &block)
+ end
+
+ def after_rollback_without_transaction_enrollment(*args, &block) # :nodoc:
+ set_options_for_callbacks!(args)
+ set_callback(:rollback_without_transaction_enrollment, :after, *args, &block)
+ end
+
+ def raise_in_transactional_callbacks
+ ActiveSupport::Deprecation.warn('ActiveRecord::Base.raise_in_transactional_callbacks is deprecated and will be removed without replacement.')
+ true
+ end
+
+ def raise_in_transactional_callbacks=(value)
+ ActiveSupport::Deprecation.warn('ActiveRecord::Base.raise_in_transactional_callbacks= is deprecated, has no effect and will be removed without replacement.')
+ value
end
private
@@ -265,7 +279,7 @@ module ActiveRecord
def assert_valid_transaction_action(actions)
if (actions - ACTIONS).any?
- raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS.join(",")}"
+ raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS}"
end
end
end
@@ -304,20 +318,31 @@ module ActiveRecord
clear_transaction_record_state
end
+ def before_committed! # :nodoc:
+ run_callbacks :before_commit_without_transaction_enrollment
+ run_callbacks :before_commit
+ end
+
# Call the +after_commit+ callbacks.
#
# Ensure that it is not called if the object was never persisted (failed create),
# but call it after the commit of a destroyed object.
- def committed!(should_run_callbacks = true) #:nodoc:
- _run_commit_callbacks if should_run_callbacks && destroyed? || persisted?
+ def committed!(should_run_callbacks: true) #:nodoc:
+ if should_run_callbacks && destroyed? || persisted?
+ run_callbacks :commit_without_transaction_enrollment
+ run_callbacks :commit
+ end
ensure
force_clear_transaction_record_state
end
# Call the +after_rollback+ callbacks. The +force_restore_state+ argument indicates if the record
# 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:
- _run_rollback_callbacks if should_run_callbacks
+ def rolledback!(force_restore_state: false, should_run_callbacks: true) #:nodoc:
+ if should_run_callbacks
+ run_callbacks :rollback
+ run_callbacks :rollback_without_transaction_enrollment
+ end
ensure
restore_transaction_record_state(force_restore_state)
clear_transaction_record_state
@@ -326,9 +351,13 @@ module ActiveRecord
# Add the record to the current transaction so that the +after_rollback+ and +after_commit+ callbacks
# can be called.
def add_to_transaction
- if self.class.connection.add_transaction_record(self)
- remember_transaction_record_state
+ if has_transactional_callbacks?
+ self.class.connection.add_transaction_record(self)
+ else
+ sync_with_transaction_state
+ set_transaction_state(self.class.connection.transaction_state)
end
+ remember_transaction_record_state
end
# Executes +method+ within a transaction and captures its return value as a
@@ -358,14 +387,12 @@ module ActiveRecord
# Save the new record state and id of a record so it can be restored later if a transaction fails.
def remember_transaction_record_state #:nodoc:
@_start_transaction_state[:id] = id
- unless @_start_transaction_state.include?(:new_record)
- @_start_transaction_state[:new_record] = @new_record
- end
- unless @_start_transaction_state.include?(:destroyed)
- @_start_transaction_state[:destroyed] = @destroyed
- end
+ @_start_transaction_state.reverse_merge!(
+ new_record: @new_record,
+ destroyed: @destroyed,
+ frozen?: frozen?,
+ )
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
- @_start_transaction_state[:frozen?] = frozen?
end
# Clear the new record state and id of a record.
@@ -385,10 +412,14 @@ module ActiveRecord
transaction_level = (@_start_transaction_state[:level] || 0) - 1
if transaction_level < 1 || force
restore_state = @_start_transaction_state
- thaw unless restore_state[:frozen?]
+ thaw
@new_record = restore_state[:new_record]
@destroyed = restore_state[:destroyed]
- write_attribute(self.class.primary_key, restore_state[:id])
+ pk = self.class.primary_key
+ if pk && read_attribute(pk) != restore_state[:id]
+ write_attribute(pk, restore_state[:id])
+ end
+ freeze if restore_state[:frozen?]
end
end
end
@@ -411,5 +442,43 @@ module ActiveRecord
end
end
end
+
+ private
+
+ def set_transaction_state(state) # :nodoc:
+ @transaction_state = state
+ end
+
+ def has_transactional_callbacks? # :nodoc:
+ !_rollback_callbacks.empty? || !_commit_callbacks.empty? || !_before_commit_callbacks.empty?
+ end
+
+ # Updates the attributes on this particular ActiveRecord object so that
+ # if it's associated with a transaction, then the state of the ActiveRecord
+ # object will be updated to reflect the current state of the transaction
+ #
+ # The @transaction_state variable stores the states of the associated
+ # transaction. This relies on the fact that a transaction can only be in
+ # one rollback or commit (otherwise a list of states would be required)
+ # Each ActiveRecord object inside of a transaction carries that transaction's
+ # TransactionState.
+ #
+ # This method checks to see if the ActiveRecord object's state reflects
+ # the TransactionState, and rolls back or commits the ActiveRecord object
+ # as appropriate.
+ #
+ # Since ActiveRecord objects can be inside multiple transactions, this
+ # method recursively goes through the parent of the TransactionState and
+ # checks if the ActiveRecord object reflects the state of the object.
+ def sync_with_transaction_state
+ update_attributes_from_transaction_state(@transaction_state)
+ end
+
+ def update_attributes_from_transaction_state(transaction_state)
+ if transaction_state && transaction_state.finalized?
+ restore_transaction_record_state if transaction_state.rolledback?
+ clear_transaction_record_state
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb
index e5acbbb6b3..2c0cda69d0 100644
--- a/activerecord/lib/active_record/type.rb
+++ b/activerecord/lib/active_record/type.rb
@@ -1,7 +1,4 @@
-require 'active_record/type/decorator'
-require 'active_record/type/mutable'
-require 'active_record/type/numeric'
-require 'active_record/type/time_value'
+require 'active_record/type/helpers'
require 'active_record/type/value'
require 'active_record/type/big_integer'
@@ -17,6 +14,53 @@ require 'active_record/type/serialized'
require 'active_record/type/string'
require 'active_record/type/text'
require 'active_record/type/time'
+require 'active_record/type/unsigned_integer'
+require 'active_record/type/adapter_specific_registry'
require 'active_record/type/type_map'
require 'active_record/type/hash_lookup_type_map'
+
+module ActiveRecord
+ module Type
+ @registry = AdapterSpecificRegistry.new
+
+ class << self
+ attr_accessor :registry # :nodoc:
+ delegate :add_modifier, to: :registry
+
+ # Add a new type to the registry, allowing it to be referenced as a
+ # symbol by ActiveRecord::Attributes::ClassMethods#attribute. If your
+ # type is only meant to be used with a specific database adapter, you can
+ # do so by passing +adapter: :postgresql+. If your type has the same
+ # name as a native type for the current adapter, an exception will be
+ # raised unless you specify an +:override+ option. +override: true+ will
+ # cause your type to be used instead of the native type. +override:
+ # false+ will cause the native type to be used over yours if one exists.
+ def register(type_name, klass = nil, **options, &block)
+ registry.register(type_name, klass, **options, &block)
+ end
+
+ def lookup(*args, adapter: current_adapter_name, **kwargs) # :nodoc:
+ registry.lookup(*args, adapter: adapter, **kwargs)
+ end
+
+ private
+
+ def current_adapter_name
+ ActiveRecord::Base.connection.adapter_name.downcase.to_sym
+ end
+ end
+
+ register(:big_integer, Type::BigInteger, override: false)
+ register(:binary, Type::Binary, override: false)
+ register(:boolean, Type::Boolean, override: false)
+ register(:date, Type::Date, override: false)
+ register(:date_time, Type::DateTime, override: false)
+ register(:decimal, Type::Decimal, override: false)
+ register(:float, Type::Float, override: false)
+ register(:integer, Type::Integer, override: false)
+ register(:string, Type::String, override: false)
+ register(:text, Type::Text, override: false)
+ register(:time, Type::Time, override: false)
+ end
+end
diff --git a/activerecord/lib/active_record/type/adapter_specific_registry.rb b/activerecord/lib/active_record/type/adapter_specific_registry.rb
new file mode 100644
index 0000000000..5f71b3cb94
--- /dev/null
+++ b/activerecord/lib/active_record/type/adapter_specific_registry.rb
@@ -0,0 +1,142 @@
+module ActiveRecord
+ # :stopdoc:
+ module Type
+ class AdapterSpecificRegistry
+ def initialize
+ @registrations = []
+ end
+
+ def register(type_name, klass = nil, **options, &block)
+ block ||= proc { |_, *args| klass.new(*args) }
+ registrations << Registration.new(type_name, block, **options)
+ end
+
+ def lookup(symbol, *args)
+ registration = registrations
+ .select { |r| r.matches?(symbol, *args) }
+ .max
+
+ if registration
+ registration.call(self, symbol, *args)
+ else
+ raise ArgumentError, "Unknown type #{symbol.inspect}"
+ end
+ end
+
+ def add_modifier(options, klass, **args)
+ registrations << DecorationRegistration.new(options, klass, **args)
+ end
+
+ protected
+
+ attr_reader :registrations
+ end
+
+ class Registration
+ def initialize(name, block, adapter: nil, override: nil)
+ @name = name
+ @block = block
+ @adapter = adapter
+ @override = override
+ end
+
+ def call(_registry, *args, adapter: nil, **kwargs)
+ if kwargs.any? # https://bugs.ruby-lang.org/issues/10856
+ block.call(*args, **kwargs)
+ else
+ block.call(*args)
+ end
+ end
+
+ def matches?(type_name, *args, **kwargs)
+ type_name == name && matches_adapter?(**kwargs)
+ end
+
+ def <=>(other)
+ if conflicts_with?(other)
+ raise TypeConflictError.new("Type #{name} was registered for all
+ adapters, but shadows a native type with
+ the same name for #{other.adapter}".squish)
+ end
+ priority <=> other.priority
+ end
+
+ protected
+
+ attr_reader :name, :block, :adapter, :override
+
+ def priority
+ result = 0
+ if adapter
+ result |= 1
+ end
+ if override
+ result |= 2
+ end
+ result
+ end
+
+ def priority_except_adapter
+ priority & 0b111111100
+ end
+
+ private
+
+ def matches_adapter?(adapter: nil, **)
+ (self.adapter.nil? || adapter == self.adapter)
+ end
+
+ def conflicts_with?(other)
+ same_priority_except_adapter?(other) &&
+ has_adapter_conflict?(other)
+ end
+
+ def same_priority_except_adapter?(other)
+ priority_except_adapter == other.priority_except_adapter
+ end
+
+ def has_adapter_conflict?(other)
+ (override.nil? && other.adapter) ||
+ (adapter && other.override.nil?)
+ end
+ end
+
+ class DecorationRegistration < Registration
+ def initialize(options, klass, adapter: nil)
+ @options = options
+ @klass = klass
+ @adapter = adapter
+ end
+
+ def call(registry, *args, **kwargs)
+ subtype = registry.lookup(*args, **kwargs.except(*options.keys))
+ klass.new(subtype)
+ end
+
+ def matches?(*args, **kwargs)
+ matches_adapter?(**kwargs) && matches_options?(**kwargs)
+ end
+
+ def priority
+ super | 4
+ end
+
+ protected
+
+ attr_reader :options, :klass
+
+ private
+
+ def matches_options?(**kwargs)
+ options.all? do |key, value|
+ kwargs[key] == value
+ end
+ end
+ end
+ end
+
+ class TypeConflictError < StandardError
+ end
+
+ # :startdoc:
+end
diff --git a/activerecord/lib/active_record/type/binary.rb b/activerecord/lib/active_record/type/binary.rb
index 005a48ef0d..0baf8c63ad 100644
--- a/activerecord/lib/active_record/type/binary.rb
+++ b/activerecord/lib/active_record/type/binary.rb
@@ -9,7 +9,7 @@ module ActiveRecord
true
end
- def type_cast(value)
+ def cast(value)
if value.is_a?(Data)
value.to_s
else
@@ -17,13 +17,13 @@ module ActiveRecord
end
end
- def type_cast_for_database(value)
+ def serialize(value)
return if value.nil?
Data.new(super)
end
def changed_in_place?(raw_old_value, value)
- old_value = type_cast_from_database(raw_old_value)
+ old_value = deserialize(raw_old_value)
old_value != value
end
diff --git a/activerecord/lib/active_record/type/boolean.rb b/activerecord/lib/active_record/type/boolean.rb
index 978d16d524..f6a75512fd 100644
--- a/activerecord/lib/active_record/type/boolean.rb
+++ b/activerecord/lib/active_record/type/boolean.rb
@@ -10,19 +10,8 @@ module ActiveRecord
def cast_value(value)
if value == ''
nil
- elsif ConnectionAdapters::Column::TRUE_VALUES.include?(value)
- true
else
- if !ConnectionAdapters::Column::FALSE_VALUES.include?(value)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- You attempted to assign a value which is not explicitly `true` or `false`
- to a boolean column. Currently this value casts to `false`. This will
- change to match Ruby's semantics, and will cast to `true` in Rails 5.
- If you would like to maintain the current behavior, you should
- explicitly handle the values you would like cast to `false`.
- MSG
- end
- false
+ !ConnectionAdapters::Column::FALSE_VALUES.include?(value)
end
end
end
diff --git a/activerecord/lib/active_record/type/date.rb b/activerecord/lib/active_record/type/date.rb
index d90a6069b7..3ceab59ebb 100644
--- a/activerecord/lib/active_record/type/date.rb
+++ b/activerecord/lib/active_record/type/date.rb
@@ -1,14 +1,12 @@
module ActiveRecord
module Type
class Date < Value # :nodoc:
+ include Helpers::AcceptsMultiparameterTime.new
+
def type
:date
end
- def klass
- ::Date
- end
-
def type_cast_for_schema(value)
"'#{value.to_s(:db)}'"
end
@@ -41,6 +39,11 @@ module ActiveRecord
::Date.new(year, mon, mday) rescue nil
end
end
+
+ def value_from_multiparameter_assignment(*)
+ time = super
+ time && time.to_date
+ end
end
end
end
diff --git a/activerecord/lib/active_record/type/date_time.rb b/activerecord/lib/active_record/type/date_time.rb
index 5f19608a33..a5199959b9 100644
--- a/activerecord/lib/active_record/type/date_time.rb
+++ b/activerecord/lib/active_record/type/date_time.rb
@@ -1,22 +1,15 @@
module ActiveRecord
module Type
class DateTime < Value # :nodoc:
- include TimeValue
+ include Helpers::TimeValue
+ include Helpers::AcceptsMultiparameterTime.new(
+ defaults: { 4 => 0, 5 => 0 }
+ )
def type
:datetime
end
- def type_cast_for_database(value)
- zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
-
- if value.acts_like?(:time)
- value.send(zone_conversion_method)
- else
- super
- end
- end
-
private
def cast_value(string)
@@ -38,6 +31,14 @@ module ActiveRecord
new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
end
+
+ def value_from_multiparameter_assignment(values_hash)
+ missing_parameter = (1..3).detect { |key| !values_hash.key?(key) }
+ if missing_parameter
+ raise ArgumentError, missing_parameter
+ end
+ super
+ end
end
end
end
diff --git a/activerecord/lib/active_record/type/decimal.rb b/activerecord/lib/active_record/type/decimal.rb
index d10778eeb6..867b5f75c7 100644
--- a/activerecord/lib/active_record/type/decimal.rb
+++ b/activerecord/lib/active_record/type/decimal.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module Type
class Decimal < Value # :nodoc:
- include Numeric
+ include Helpers::Numeric
def type
:decimal
@@ -16,7 +16,7 @@ module ActiveRecord
def cast_value(value)
case value
when ::Float
- BigDecimal(value, float_precision)
+ convert_float_to_big_decimal(value)
when ::Numeric, ::String
BigDecimal(value, precision.to_i)
else
@@ -28,6 +28,14 @@ module ActiveRecord
end
end
+ def convert_float_to_big_decimal(value)
+ if precision
+ BigDecimal(value, float_precision)
+ else
+ value.to_d
+ end
+ end
+
def float_precision
if precision.to_i > ::Float::DIG + 1
::Float::DIG + 1
diff --git a/activerecord/lib/active_record/type/decorator.rb b/activerecord/lib/active_record/type/decorator.rb
deleted file mode 100644
index 9fce38ea44..0000000000
--- a/activerecord/lib/active_record/type/decorator.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-module ActiveRecord
- module Type
- module Decorator # :nodoc:
- def init_with(coder)
- @subtype = coder['subtype']
- __setobj__(@subtype)
- end
-
- def encode_with(coder)
- coder['subtype'] = __getobj__
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/type/float.rb b/activerecord/lib/active_record/type/float.rb
index 42eb44b9a9..d88482b85d 100644
--- a/activerecord/lib/active_record/type/float.rb
+++ b/activerecord/lib/active_record/type/float.rb
@@ -1,18 +1,24 @@
module ActiveRecord
module Type
class Float < Value # :nodoc:
- include Numeric
+ include Helpers::Numeric
def type
:float
end
- alias type_cast_for_database type_cast
+ alias serialize cast
private
def cast_value(value)
- value.to_f
+ case value
+ when ::Float then value
+ when "Infinity" then ::Float::INFINITY
+ when "-Infinity" then -::Float::INFINITY
+ when "NaN" then ::Float::NAN
+ else value.to_f
+ end
end
end
end
diff --git a/activerecord/lib/active_record/type/hash_lookup_type_map.rb b/activerecord/lib/active_record/type/hash_lookup_type_map.rb
index bf92680268..3b01e3f8ca 100644
--- a/activerecord/lib/active_record/type/hash_lookup_type_map.rb
+++ b/activerecord/lib/active_record/type/hash_lookup_type_map.rb
@@ -1,18 +1,22 @@
module ActiveRecord
module Type
class HashLookupTypeMap < TypeMap # :nodoc:
- delegate :key?, to: :@mapping
+ def alias_type(type, alias_type)
+ register_type(type) { |_, *args| lookup(alias_type, *args) }
+ end
- def lookup(type, *args)
- @mapping.fetch(type, proc { default_value }).call(type, *args)
+ def key?(key)
+ @mapping.key?(key)
end
- def fetch(type, *args, &block)
- @mapping.fetch(type, block).call(type, *args)
+ def keys
+ @mapping.keys
end
- def alias_type(type, alias_type)
- register_type(type) { |_, *args| lookup(alias_type, *args) }
+ private
+
+ def perform_fetch(type, *args, &block)
+ @mapping.fetch(type, block).call(type, *args)
end
end
end
diff --git a/activerecord/lib/active_record/type/helpers.rb b/activerecord/lib/active_record/type/helpers.rb
new file mode 100644
index 0000000000..634d417d13
--- /dev/null
+++ b/activerecord/lib/active_record/type/helpers.rb
@@ -0,0 +1,4 @@
+require 'active_record/type/helpers/accepts_multiparameter_time'
+require 'active_record/type/helpers/numeric'
+require 'active_record/type/helpers/mutable'
+require 'active_record/type/helpers/time_value'
diff --git a/activerecord/lib/active_record/type/helpers/accepts_multiparameter_time.rb b/activerecord/lib/active_record/type/helpers/accepts_multiparameter_time.rb
new file mode 100644
index 0000000000..be571fc1c7
--- /dev/null
+++ b/activerecord/lib/active_record/type/helpers/accepts_multiparameter_time.rb
@@ -0,0 +1,30 @@
+module ActiveRecord
+ module Type
+ module Helpers
+ class AcceptsMultiparameterTime < Module # :nodoc:
+ def initialize(defaults: {})
+ define_method(:cast) do |value|
+ if value.is_a?(Hash)
+ value_from_multiparameter_assignment(value)
+ else
+ super(value)
+ end
+ end
+
+ define_method(:value_from_multiparameter_assignment) do |values_hash|
+ defaults.each do |k, v|
+ values_hash[k] ||= v
+ end
+ return unless values_hash[1] && values_hash[2] && values_hash[3]
+ values = values_hash.sort.map(&:last)
+ ::Time.send(
+ ActiveRecord::Base.default_timezone,
+ *values
+ )
+ end
+ private :value_from_multiparameter_assignment
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/helpers/mutable.rb b/activerecord/lib/active_record/type/helpers/mutable.rb
new file mode 100644
index 0000000000..88a9099277
--- /dev/null
+++ b/activerecord/lib/active_record/type/helpers/mutable.rb
@@ -0,0 +1,18 @@
+module ActiveRecord
+ module Type
+ module Helpers
+ module Mutable # :nodoc:
+ def cast(value)
+ deserialize(serialize(value))
+ end
+
+ # +raw_old_value+ will be the `_before_type_cast` version of the
+ # value (likely a string). +new_value+ will be the current, type
+ # cast value.
+ def changed_in_place?(raw_old_value, new_value)
+ raw_old_value != serialize(new_value)
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/helpers/numeric.rb b/activerecord/lib/active_record/type/helpers/numeric.rb
new file mode 100644
index 0000000000..a755a02a59
--- /dev/null
+++ b/activerecord/lib/active_record/type/helpers/numeric.rb
@@ -0,0 +1,34 @@
+module ActiveRecord
+ module Type
+ module Helpers
+ module Numeric # :nodoc:
+ def cast(value)
+ value = case value
+ when true then 1
+ when false then 0
+ when ::String then value.presence
+ else value
+ end
+ super(value)
+ end
+
+ def changed?(old_value, _new_value, new_value_before_type_cast) # :nodoc:
+ super || number_to_non_number?(old_value, new_value_before_type_cast)
+ end
+
+ private
+
+ def number_to_non_number?(old_value, new_value_before_type_cast)
+ old_value != nil && non_numeric_string?(new_value_before_type_cast)
+ end
+
+ def non_numeric_string?(value)
+ # 'wibble'.to_i will give zero, we want to make sure
+ # that we aren't marking int zero to string zero as
+ # changed.
+ value.to_s !~ /\A-?\d+\.?\d*\z/
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/helpers/time_value.rb b/activerecord/lib/active_record/type/helpers/time_value.rb
new file mode 100644
index 0000000000..7eb41557cb
--- /dev/null
+++ b/activerecord/lib/active_record/type/helpers/time_value.rb
@@ -0,0 +1,58 @@
+module ActiveRecord
+ module Type
+ module Helpers
+ module TimeValue # :nodoc:
+ def serialize(value)
+ if precision && value.respond_to?(:usec)
+ number_of_insignificant_digits = 6 - precision
+ round_power = 10 ** number_of_insignificant_digits
+ value = value.change(usec: value.usec / round_power * round_power)
+ end
+
+ if value.acts_like?(:time)
+ zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
+
+ if value.respond_to?(zone_conversion_method)
+ value = value.send(zone_conversion_method)
+ end
+ end
+
+ value
+ end
+
+ def type_cast_for_schema(value)
+ "'#{value.to_s(:db)}'"
+ end
+
+ def user_input_in_time_zone(value)
+ value.in_time_zone
+ end
+
+ private
+
+ def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil)
+ # Treat 0000-00-00 00:00:00 as nil.
+ return if year.nil? || (year == 0 && mon == 0 && mday == 0)
+
+ if offset
+ time = ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
+ return unless time
+
+ time -= offset
+ Base.default_timezone == :utc ? time : time.getlocal
+ else
+ ::Time.public_send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
+ end
+ end
+
+ # Doesn't handle time zones.
+ def fast_string_to_time(string)
+ if string =~ ConnectionAdapters::Column::Format::ISO_DATETIME
+ microsec = ($7.to_r * 1_000_000).to_i
+ new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/integer.rb b/activerecord/lib/active_record/type/integer.rb
index d69e5b3f28..2a1b04ac7f 100644
--- a/activerecord/lib/active_record/type/integer.rb
+++ b/activerecord/lib/active_record/type/integer.rb
@@ -1,18 +1,33 @@
module ActiveRecord
module Type
class Integer < Value # :nodoc:
- include Numeric
+ include Helpers::Numeric
+
+ # Column storage size in bytes.
+ # 4 bytes means a MySQL int or Postgres integer as opposed to smallint etc.
+ DEFAULT_LIMIT = 4
def initialize(*)
super
- @range = -max_value...max_value
+ @range = min_value...max_value
end
def type
:integer
end
- alias type_cast_for_database type_cast
+ def deserialize(value)
+ return if value.nil?
+ value.to_i
+ end
+
+ def serialize(value)
+ result = cast(value)
+ if result
+ ensure_in_range(result)
+ end
+ result
+ end
protected
@@ -25,22 +40,24 @@ module ActiveRecord
when true then 1
when false then 0
else
- result = value.to_i rescue nil
- ensure_in_range(result) if result
- result
+ value.to_i rescue nil
end
end
def ensure_in_range(value)
unless range.cover?(value)
- raise RangeError, "#{value} is too large for #{self.class} with limit #{limit || 4}"
+ raise RangeError, "#{value} is out of range for #{self.class} with limit #{limit || DEFAULT_LIMIT}"
end
end
def max_value
- limit = self.limit || 4
+ limit = self.limit || DEFAULT_LIMIT
1 << (limit * 8 - 1) # 8 bits per byte with one bit for sign
end
+
+ def min_value
+ -max_value
+ end
end
end
end
diff --git a/activerecord/lib/active_record/type/mutable.rb b/activerecord/lib/active_record/type/mutable.rb
deleted file mode 100644
index 066617ea59..0000000000
--- a/activerecord/lib/active_record/type/mutable.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-module ActiveRecord
- module Type
- module Mutable # :nodoc:
- def type_cast_from_user(value)
- type_cast_from_database(type_cast_for_database(value))
- end
-
- # +raw_old_value+ will be the `_before_type_cast` version of the
- # value (likely a string). +new_value+ will be the current, type
- # cast value.
- def changed_in_place?(raw_old_value, new_value)
- raw_old_value != type_cast_for_database(new_value)
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/type/numeric.rb b/activerecord/lib/active_record/type/numeric.rb
deleted file mode 100644
index fa43266504..0000000000
--- a/activerecord/lib/active_record/type/numeric.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-module ActiveRecord
- module Type
- module Numeric # :nodoc:
- def number?
- true
- end
-
- def type_cast(value)
- value = case value
- when true then 1
- when false then 0
- when ::String then value.presence
- else value
- end
- super(value)
- end
-
- def changed?(old_value, _new_value, new_value_before_type_cast) # :nodoc:
- super || number_to_non_number?(old_value, new_value_before_type_cast)
- end
-
- private
-
- def number_to_non_number?(old_value, new_value_before_type_cast)
- old_value != nil && non_numeric_string?(new_value_before_type_cast)
- end
-
- def non_numeric_string?(value)
- # 'wibble'.to_i will give zero, we want to make sure
- # that we aren't marking int zero to string zero as
- # changed.
- value.to_s !~ /\A\d+\.?\d*\z/
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb
index 17004b3593..ea3e0d6a45 100644
--- a/activerecord/lib/active_record/type/serialized.rb
+++ b/activerecord/lib/active_record/type/serialized.rb
@@ -1,8 +1,7 @@
module ActiveRecord
module Type
- class Serialized < SimpleDelegator # :nodoc:
- include Mutable
- include Decorator
+ class Serialized < DelegateClass(Type::Value) # :nodoc:
+ include Helpers::Mutable
attr_reader :subtype, :coder
@@ -12,7 +11,7 @@ module ActiveRecord
super(subtype)
end
- def type_cast_from_database(value)
+ def deserialize(value)
if default_value?(value)
value
else
@@ -20,32 +19,28 @@ module ActiveRecord
end
end
- def type_cast_for_database(value)
+ def serialize(value)
return if value.nil?
unless default_value?(value)
super coder.dump(value)
end
end
+ def inspect
+ Kernel.instance_method(:inspect).bind(self).call
+ end
+
def changed_in_place?(raw_old_value, value)
return false if value.nil?
- subtype.changed_in_place?(raw_old_value, coder.dump(value))
+ raw_new_value = serialize(value)
+ raw_old_value.nil? != raw_new_value.nil? ||
+ subtype.changed_in_place?(raw_old_value, raw_new_value)
end
def accessor
ActiveRecord::Store::IndifferentHashAccessor
end
- def init_with(coder)
- @coder = coder['coder']
- super
- end
-
- def encode_with(coder)
- coder['coder'] = @coder
- super
- end
-
private
def default_value?(value)
diff --git a/activerecord/lib/active_record/type/string.rb b/activerecord/lib/active_record/type/string.rb
index 150defb106..2662b7e874 100644
--- a/activerecord/lib/active_record/type/string.rb
+++ b/activerecord/lib/active_record/type/string.rb
@@ -11,12 +11,12 @@ module ActiveRecord
end
end
- def type_cast_for_database(value)
+ def serialize(value)
case value
when ::Numeric, ActiveSupport::Duration then value.to_s
when ::String then ::String.new(value)
- when true then "1"
- when false then "0"
+ when true then "t"
+ when false then "f"
else super
end
end
@@ -25,8 +25,8 @@ module ActiveRecord
def cast_value(value)
case value
- when true then "1"
- when false then "0"
+ when true then "t"
+ when false then "f"
# String.new is slightly faster than dup
else ::String.new(value.to_s)
end
diff --git a/activerecord/lib/active_record/type/time.rb b/activerecord/lib/active_record/type/time.rb
index 41f7d97f0c..19a10021bc 100644
--- a/activerecord/lib/active_record/type/time.rb
+++ b/activerecord/lib/active_record/type/time.rb
@@ -1,12 +1,28 @@
module ActiveRecord
module Type
class Time < Value # :nodoc:
- include TimeValue
+ include Helpers::TimeValue
+ include Helpers::AcceptsMultiparameterTime.new(
+ defaults: { 1 => 1970, 2 => 1, 3 => 1, 4 => 0, 5 => 0 }
+ )
def type
:time
end
+ def user_input_in_time_zone(value)
+ return unless value.present?
+
+ case value
+ when ::String
+ value = "2000-01-01 #{value}"
+ when ::Time
+ value = value.change(year: 2000, day: 1, month: 1)
+ end
+
+ super(value)
+ end
+
private
def cast_value(value)
diff --git a/activerecord/lib/active_record/type/time_value.rb b/activerecord/lib/active_record/type/time_value.rb
deleted file mode 100644
index d611d72dd4..0000000000
--- a/activerecord/lib/active_record/type/time_value.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-module ActiveRecord
- module Type
- module TimeValue # :nodoc:
- def klass
- ::Time
- end
-
- def type_cast_for_schema(value)
- "'#{value.to_s(:db)}'"
- end
-
- private
-
- def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil)
- # Treat 0000-00-00 00:00:00 as nil.
- return if year.nil? || (year == 0 && mon == 0 && mday == 0)
-
- if offset
- time = ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
- return unless time
-
- time -= offset
- Base.default_timezone == :utc ? time : time.getlocal
- else
- ::Time.public_send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
- end
- end
-
- # Doesn't handle time zones.
- def fast_string_to_time(string)
- if string =~ ConnectionAdapters::Column::Format::ISO_DATETIME
- microsec = ($7.to_r * 1_000_000).to_i
- new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/type/type_map.rb b/activerecord/lib/active_record/type/type_map.rb
index 88c5f9c497..09f5ba6b74 100644
--- a/activerecord/lib/active_record/type/type_map.rb
+++ b/activerecord/lib/active_record/type/type_map.rb
@@ -1,24 +1,28 @@
+require 'thread_safe'
+
module ActiveRecord
module Type
class TypeMap # :nodoc:
def initialize
@mapping = {}
+ @cache = ThreadSafe::Cache.new do |h, key|
+ h.fetch_or_store(key, ThreadSafe::Cache.new)
+ end
end
def lookup(lookup_key, *args)
- matching_pair = @mapping.reverse_each.detect do |key, _|
- key === lookup_key
- end
+ fetch(lookup_key, *args) { default_value }
+ end
- if matching_pair
- matching_pair.last.call(lookup_key, *args)
- else
- default_value
+ def fetch(lookup_key, *args, &block)
+ @cache[lookup_key].fetch_or_store(args) do
+ perform_fetch(lookup_key, *args, &block)
end
end
def register_type(key, value = nil, &block)
raise ::ArgumentError unless value || block
+ @cache.clear
if block
@mapping[key] = block
@@ -40,6 +44,18 @@ module ActiveRecord
private
+ def perform_fetch(lookup_key, *args)
+ matching_pair = @mapping.reverse_each.detect do |key, _|
+ key === lookup_key
+ end
+
+ if matching_pair
+ matching_pair.last.call(lookup_key, *args)
+ else
+ yield lookup_key, *args
+ end
+ end
+
def default_value
@default_value ||= Value.new
end
diff --git a/activerecord/lib/active_record/type/unsigned_integer.rb b/activerecord/lib/active_record/type/unsigned_integer.rb
new file mode 100644
index 0000000000..ed3e527483
--- /dev/null
+++ b/activerecord/lib/active_record/type/unsigned_integer.rb
@@ -0,0 +1,15 @@
+module ActiveRecord
+ module Type
+ class UnsignedInteger < Integer # :nodoc:
+ private
+
+ def max_value
+ super * 2
+ end
+
+ def min_value
+ 0
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/value.rb b/activerecord/lib/active_record/type/value.rb
index 9456a4a56c..6b9d147ecc 100644
--- a/activerecord/lib/active_record/type/value.rb
+++ b/activerecord/lib/active_record/type/value.rb
@@ -1,48 +1,50 @@
module ActiveRecord
module Type
- class Value # :nodoc:
+ class Value
attr_reader :precision, :scale, :limit
- # Valid options are +precision+, +scale+, and +limit+. They are only
- # used when dumping schema.
- def initialize(options = {})
- options.assert_valid_keys(:precision, :scale, :limit)
- @precision = options[:precision]
- @scale = options[:scale]
- @limit = options[:limit]
+ def initialize(precision: nil, limit: nil, scale: nil)
+ @precision = precision
+ @scale = scale
+ @limit = limit
end
- # The simplified type that this object represents. Returns a symbol such
- # as +:string+ or +:integer+
- def type; end
+ def type # :nodoc:
+ end
- # Type casts a string from the database into the appropriate ruby type.
- # Classes which do not need separate type casting behavior for database
- # and user provided values should override +cast_value+ instead.
- def type_cast_from_database(value)
- type_cast(value)
+ # Converts a value from database input to the appropriate ruby type. The
+ # return value of this method will be returned from
+ # ActiveRecord::AttributeMethods::Read#read_attribute. The default
+ # implementation just calls Value#cast.
+ #
+ # +value+ The raw input, as provided from the database.
+ def deserialize(value)
+ cast(value)
end
# Type casts a value from user input (e.g. from a setter). This value may
- # be a string from the form builder, or an already type cast value
- # provided manually to a setter.
+ # be a string from the form builder, or a ruby object passed to a setter.
+ # There is currently no way to differentiate between which source it came
+ # from.
+ #
+ # The return value of this method will be returned from
+ # ActiveRecord::AttributeMethods::Read#read_attribute. See also:
+ # Value#cast_value.
#
- # Classes which do not need separate type casting behavior for database
- # and user provided values should override +type_cast+ or +cast_value+
- # instead.
- def type_cast_from_user(value)
- type_cast(value)
+ # +value+ The raw input, as provided to the attribute setter.
+ def cast(value)
+ cast_value(value) unless value.nil?
end
- # Cast a value from the ruby type to a type that the database knows how
+ # Casts a value from the ruby type to a type that the database knows how
# to understand. The returned value from this method should be a
# +String+, +Numeric+, +Date+, +Time+, +Symbol+, +true+, +false+, or
- # +nil+
- def type_cast_for_database(value)
+ # +nil+.
+ def serialize(value)
value
end
- # Type cast a value for schema dumping. This method is private, as we are
+ # Type casts a value for schema dumping. This method is private, as we are
# hoping to remove it entirely.
def type_cast_for_schema(value) # :nodoc:
value.inspect
@@ -50,17 +52,10 @@ module ActiveRecord
# These predicates are not documented, as I need to look further into
# their use, and see if they can be removed entirely.
- def number? # :nodoc:
- false
- end
-
def binary? # :nodoc:
false
end
- def klass # :nodoc:
- end
-
# Determines whether a value has changed for dirty checking. +old_value+
# and +new_value+ will always be type-cast. Types should not need to
# override this method.
@@ -69,10 +64,23 @@ module ActiveRecord
end
# Determines whether the mutable value has been modified since it was
- # read. Returns +false+ by default. This method should not be overridden
- # directly. Types which return a mutable value should include
- # +Type::Mutable+, which will define this method.
- def changed_in_place?(*)
+ # read. Returns +false+ by default. If your type returns an object
+ # which could be mutated, you should override this method. You will need
+ # to either:
+ #
+ # - pass +new_value+ to Value#serialize and compare it to
+ # +raw_old_value+
+ #
+ # or
+ #
+ # - pass +raw_old_value+ to Value#deserialize and compare it to
+ # +new_value+
+ #
+ # +raw_old_value+ The original value, before being passed to
+ # +deserialize+.
+ #
+ # +new_value+ The current value, after type casting.
+ def changed_in_place?(raw_old_value, new_value)
false
end
@@ -85,14 +93,9 @@ module ActiveRecord
private
- def type_cast(value)
- cast_value(value) unless value.nil?
- end
-
# Convenience method for types which do not need separate type casting
- # behavior for user and database inputs. Called by
- # `type_cast_from_database` and `type_cast_from_user` for all values
- # except `nil`.
+ # behavior for user and database inputs. Called by Value#cast for
+ # values except +nil+.
def cast_value(value) # :doc:
value
end
diff --git a/activerecord/lib/active_record/type_caster.rb b/activerecord/lib/active_record/type_caster.rb
new file mode 100644
index 0000000000..63ba10c289
--- /dev/null
+++ b/activerecord/lib/active_record/type_caster.rb
@@ -0,0 +1,7 @@
+require 'active_record/type_caster/map'
+require 'active_record/type_caster/connection'
+
+module ActiveRecord
+ module TypeCaster
+ end
+end
diff --git a/activerecord/lib/active_record/type_caster/connection.rb b/activerecord/lib/active_record/type_caster/connection.rb
new file mode 100644
index 0000000000..3878270770
--- /dev/null
+++ b/activerecord/lib/active_record/type_caster/connection.rb
@@ -0,0 +1,29 @@
+module ActiveRecord
+ module TypeCaster
+ class Connection
+ def initialize(klass, table_name)
+ @klass = klass
+ @table_name = table_name
+ end
+
+ def type_cast_for_database(attribute_name, value)
+ return value if value.is_a?(Arel::Nodes::BindParam)
+ column = column_for(attribute_name)
+ connection.type_cast_from_column(column, value)
+ end
+
+ protected
+
+ attr_reader :table_name
+ delegate :connection, to: :@klass
+
+ private
+
+ def column_for(attribute_name)
+ if connection.schema_cache.table_exists?(table_name)
+ connection.schema_cache.columns_hash(table_name)[attribute_name.to_s]
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type_caster/map.rb b/activerecord/lib/active_record/type_caster/map.rb
new file mode 100644
index 0000000000..4b1941351c
--- /dev/null
+++ b/activerecord/lib/active_record/type_caster/map.rb
@@ -0,0 +1,19 @@
+module ActiveRecord
+ module TypeCaster
+ class Map
+ def initialize(types)
+ @types = types
+ end
+
+ def type_cast_for_database(attr_name, value)
+ return value if value.is_a?(Arel::Nodes::BindParam)
+ type = types.type_for_attribute(attr_name.to_s)
+ type.serialize(value)
+ end
+
+ protected
+
+ attr_reader :types
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 7f7d49cdb4..e227212827 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -5,13 +5,14 @@ module ActiveRecord
# +record+ method to retrieve the record which did not validate.
#
# begin
- # complex_operation_that_calls_save!_internally
+ # complex_operation_that_internally_calls_save!
# rescue ActiveRecord::RecordInvalid => invalid
# puts invalid.record.errors
# end
class RecordInvalid < ActiveRecordError
- attr_reader :record # :nodoc:
- def initialize(record) # :nodoc:
+ attr_reader :record
+
+ def initialize(record)
@record = record
errors = @record.errors.full_messages.join(", ")
super(I18n.t(:"#{@record.class.i18n_scope}.errors.messages.record_invalid", :errors => errors, :default => :"errors.messages.record_invalid"))
@@ -39,7 +40,7 @@ module ActiveRecord
# Attempts to save the record just like Base#save but will raise a +RecordInvalid+
# exception instead of returning +false+ if the record is not valid.
def save!(options={})
- perform_validations(options) ? super : raise_record_invalid
+ perform_validations(options) ? super : raise_validation_error
end
# Runs all the validations within the specified context. Returns +true+ if
@@ -60,21 +61,9 @@ module ActiveRecord
alias_method :validate, :valid?
- # Runs all the validations within the specified context. Returns +true+ if
- # no errors are found, raises +RecordInvalid+ otherwise.
- #
- # If the argument is +false+ (default is +nil+), the context is set to <tt>:create</tt> if
- # <tt>new_record?</tt> is +true+, and to <tt>:update</tt> if it is not.
- #
- # Validations with no <tt>:on</tt> option will run no matter the context. Validations with
- # some <tt>:on</tt> option will only run in the specified context.
- def validate!(context = nil)
- valid?(context) || raise_record_invalid
- end
-
protected
- def raise_record_invalid
+ def raise_validation_error
raise(RecordInvalid.new(self))
end
@@ -87,3 +76,4 @@ end
require "active_record/validations/associated"
require "active_record/validations/uniqueness"
require "active_record/validations/presence"
+require "active_record/validations/length"
diff --git a/activerecord/lib/active_record/validations/length.rb b/activerecord/lib/active_record/validations/length.rb
new file mode 100644
index 0000000000..5991fbad8e
--- /dev/null
+++ b/activerecord/lib/active_record/validations/length.rb
@@ -0,0 +1,33 @@
+module ActiveRecord
+ module Validations
+ class LengthValidator < ActiveModel::Validations::LengthValidator # :nodoc:
+ def validate_each(record, attribute, association_or_value)
+ return unless should_validate?(record) || associations_are_dirty?(record)
+ if association_or_value.respond_to?(:loaded?) && association_or_value.loaded?
+ association_or_value = association_or_value.target.reject(&:marked_for_destruction?)
+ end
+ super
+ end
+
+ def associations_are_dirty?(record)
+ attributes.any? do |attribute|
+ value = record.read_attribute_for_validation(attribute)
+ if value.respond_to?(:loaded?) && value.loaded?
+ value.target.any?(&:marked_for_destruction?)
+ else
+ false
+ end
+ end
+ end
+ end
+
+ module ClassMethods
+ # See <tt>ActiveModel::Validation::LengthValidator</tt> for more information.
+ def validates_length_of(*attr_names)
+ validates_with LengthValidator, _merge_attributes(attr_names)
+ end
+
+ alias_method :validates_size_of, :validates_length_of
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/validations/presence.rb b/activerecord/lib/active_record/validations/presence.rb
index c7aa814ba8..75d5bd5a35 100644
--- a/activerecord/lib/active_record/validations/presence.rb
+++ b/activerecord/lib/active_record/validations/presence.rb
@@ -2,13 +2,14 @@ module ActiveRecord
module Validations
class PresenceValidator < ActiveModel::Validations::PresenceValidator # :nodoc:
def validate(record)
+ 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? { |r| r.marked_for_destruction? }
+ if associated_records.present? && associated_records.all?(&:marked_for_destruction?)
record.errors.add(attribute, :blank, options)
end
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 2dba4c7b94..5106f4e127 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -11,14 +11,14 @@ module ActiveRecord
end
def validate_each(record, attribute, value)
+ return unless should_validate?(record)
finder_class = find_finder_class_for(record)
table = finder_class.arel_table
value = map_enum_attribute(finder_class, attribute, value)
relation = build_relation(finder_class, table, attribute, value)
- relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.id)) if record.persisted?
+ relation = relation.where.not(finder_class.primary_key => record.id) if record.persisted?
relation = scope_relation(record, table, relation)
- relation = finder_class.unscoped.where(relation)
relation = relation.merge(options[:conditions]) if options[:conditions]
if relation.exists?
@@ -60,17 +60,24 @@ module ActiveRecord
end
column = klass.columns_hash[attribute_name]
- value = klass.connection.type_cast(value, column)
+ cast_type = klass.type_for_attribute(attribute_name)
+ value = cast_type.serialize(value)
+ value = klass.connection.type_cast(value)
if value.is_a?(String) && column.limit
value = value.to_s[0, column.limit]
end
- if !options[:case_sensitive] && value.is_a?(String)
+ value = Arel::Nodes::Quoted.new(value)
+
+ comparison = if !options[:case_sensitive] && !value.nil?
# will use SQL LOWER function before comparison, unless it detects a case insensitive collation
klass.connection.case_insensitive_comparison(table, attribute, column, value)
else
klass.connection.case_sensitive_comparison(table, attribute, column, value)
end
+ klass.unscoped.where(comparison)
+ rescue RangeError
+ klass.none
end
def scope_relation(record, table, relation)
@@ -79,9 +86,9 @@ module ActiveRecord
scope_value = record.send(reflection.foreign_key)
scope_item = reflection.foreign_key
else
- scope_value = record.read_attribute(scope_item)
+ scope_value = record._read_attribute(scope_item)
end
- relation = relation.and(table[scope_item].eq(scope_value))
+ relation = relation.where(scope_item => scope_value)
end
relation
diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb
index f7bf6987c4..5b3e57dcf6 100644
--- a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb
@@ -4,14 +4,19 @@ class <%= migration_class_name %> < ActiveRecord::Migration
<% attributes.each do |attribute| -%>
<% if attribute.password_digest? -%>
t.string :password_digest<%= attribute.inject_options %>
+<% elsif attribute.token? -%>
+ t.string :<%= attribute.name %><%= attribute.inject_options %>
<% else -%>
t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %>
<% end -%>
<% end -%>
<% if options[:timestamps] %>
- t.timestamps null: false
+ t.timestamps
<% end -%>
end
+<% attributes.select(&:token?).each do |attribute| -%>
+ add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>, unique: true
+<% end -%>
<% attributes_with_index.each do |attribute| -%>
add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>
<% end -%>
diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
index ae9c74fd05..23a377db6a 100644
--- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
@@ -4,6 +4,9 @@ class <%= migration_class_name %> < ActiveRecord::Migration
<% attributes.each do |attribute| -%>
<%- if attribute.reference? -%>
add_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %>
+ <%- elsif attribute.token? -%>
+ add_column :<%= table_name %>, :<%= attribute.name %>, :string<%= attribute.inject_options %>
+ add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>, unique: true
<%- else -%>
add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %>
<%- if attribute.has_index? -%>
diff --git a/activerecord/lib/rails/generators/active_record/model/templates/model.rb b/activerecord/lib/rails/generators/active_record/model/templates/model.rb
index 539d969fce..55dc65c8ad 100644
--- a/activerecord/lib/rails/generators/active_record/model/templates/model.rb
+++ b/activerecord/lib/rails/generators/active_record/model/templates/model.rb
@@ -3,6 +3,9 @@ class <%= class_name %> < <%= parent_class_name.classify %>
<% attributes.select(&:reference?).each do |attribute| -%>
belongs_to :<%= attribute.name %><%= ', polymorphic: true' if attribute.polymorphic? %><%= ', required: true' if attribute.required? %>
<% end -%>
+<% attributes.select(&:token?).each do |attribute| -%>
+ has_secure_token<% if attribute.name != "token" %> :<%= attribute.name %><% end %>
+<% end -%>
<% if attributes.any?(&:password_digest?) -%>
has_secure_password
<% end -%>
diff --git a/activerecord/test/active_record/connection_adapters/fake_adapter.rb b/activerecord/test/active_record/connection_adapters/fake_adapter.rb
index 64cde143a1..49a68fb94c 100644
--- a/activerecord/test/active_record/connection_adapters/fake_adapter.rb
+++ b/activerecord/test/active_record/connection_adapters/fake_adapter.rb
@@ -22,15 +22,14 @@ module ActiveRecord
end
def primary_key(table)
- @primary_keys[table]
+ @primary_keys[table] || "id"
end
def merge_column(table_name, name, sql_type = nil, options = {})
@columns[table_name] << ActiveRecord::ConnectionAdapters::Column.new(
name.to_s,
options[:default],
- lookup_cast_type(sql_type.to_s),
- sql_type.to_s,
+ fetch_type_metadata(sql_type),
options[:null])
end
@@ -38,6 +37,10 @@ module ActiveRecord
@columns[table_name]
end
+ def table_exists?(*)
+ true
+ end
+
def active?
true
end
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 6f84bae432..1712ff0ac6 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -193,7 +193,7 @@ module ActiveRecord
author = Author.create!(name: 'john')
Post.create!(author: author, title: 'foo', body: 'bar')
query = author.posts.where(title: 'foo').select(:title)
- assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bind_values))
+ assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bound_attributes))
assert_equal({"title" => "foo"}, @connection.select_one(query))
assert @connection.select_all(query).is_a?(ActiveRecord::Result)
assert_equal "foo", @connection.select_value(query)
@@ -203,7 +203,7 @@ module ActiveRecord
def test_select_methods_passing_a_relation
Post.create!(title: 'foo', body: 'bar')
query = Post.where(title: 'foo').select(:title)
- assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bind_values))
+ assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bound_attributes))
assert_equal({"title" => "foo"}, @connection.select_one(query))
assert @connection.select_all(query).is_a?(ActiveRecord::Result)
assert_equal "foo", @connection.select_value(query)
@@ -213,10 +213,20 @@ module ActiveRecord
test "type_to_sql returns a String for unmapped types" do
assert_equal "special_db_type", @connection.type_to_sql(:special_db_type)
end
+
+ unless current_adapter?(:PostgreSQLAdapter)
+ def test_log_invalid_encoding
+ assert_raise ActiveRecord::StatementInvalid do
+ @connection.send :log, "SELECT 'ы' FROM DUAL" do
+ raise 'ы'.force_encoding(Encoding::ASCII_8BIT)
+ end
+ end
+ end
+ end
end
class AdapterTestWithoutTransaction < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
class Klass < ActiveRecord::Base
end
diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
index a84673e452..6577d56240 100644
--- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
@@ -92,7 +92,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
with_real_execute do
begin
ActiveRecord::Base.connection.create_table :delete_me
- ActiveRecord::Base.connection.add_timestamps :delete_me
+ ActiveRecord::Base.connection.add_timestamps :delete_me, null: true
assert column_present?('delete_me', 'updated_at', 'datetime')
assert column_present?('delete_me', 'created_at', 'datetime')
ensure
@@ -107,7 +107,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
ActiveRecord::Base.connection.create_table :delete_me do |t|
t.timestamps null: true
end
- ActiveRecord::Base.connection.remove_timestamps :delete_me
+ ActiveRecord::Base.connection.remove_timestamps :delete_me, { null: true }
assert !column_present?('delete_me', 'updated_at', 'datetime')
assert !column_present?('delete_me', 'created_at', 'datetime')
ensure
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..c8dd49d00a
--- /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 CharsetCollationTest < ActiveRecord::TestCase
+ 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 3dabb1104a..4762ef43b5 100644
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -47,9 +47,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase
assert !@connection.active?
# Repair all fixture connections so other tests won't break.
- @fixture_connections.each do |c|
- c.verify!
- end
+ @fixture_connections.each(&:verify!)
end
def test_successful_reconnection_after_timeout_with_manual_reconnect
@@ -69,8 +67,8 @@ class MysqlConnectionTest < ActiveRecord::TestCase
end
def test_bind_value_substitute
- bind_param = @connection.substitute_at('foo', 0)
- assert_equal Arel.sql('?'), bind_param
+ bind_param = @connection.substitute_at('foo')
+ assert_equal Arel.sql('?'), bind_param.to_sql
end
def test_exec_no_binds
@@ -96,7 +94,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase
with_example_table do
@connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
result = @connection.exec_query(
- 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]])
+ 'SELECT id, data FROM ex WHERE id = ?', nil, [ActiveRecord::Relation::QueryAttribute.new("id", 1, ActiveRecord::Type::Value.new)])
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
@@ -108,10 +106,10 @@ class MysqlConnectionTest < ActiveRecord::TestCase
def test_exec_typecasts_bind_vals
with_example_table do
@connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
- column = @connection.columns('ex').find { |col| col.name == 'id' }
+ bind = ActiveRecord::Relation::QueryAttribute.new("id", "1-fuu", ActiveRecord::Type::Integer.new)
result = @connection.exec_query(
- 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']])
+ 'SELECT id, data FROM ex WHERE id = ?', nil, [bind])
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
diff --git a/activerecord/test/cases/adapters/mysql/consistency_test.rb b/activerecord/test/cases/adapters/mysql/consistency_test.rb
index e972d6b330..ae190b728d 100644
--- a/activerecord/test/cases/adapters/mysql/consistency_test.rb
+++ b/activerecord/test/cases/adapters/mysql/consistency_test.rb
@@ -1,7 +1,7 @@
require "cases/helper"
class MysqlConsistencyTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
class Consistency < ActiveRecord::Base
self.table_name = "mysql_consistency"
diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
index 28106d3772..48ceef365e 100644
--- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require "cases/helper"
require 'support/ddl_helper'
@@ -16,7 +15,7 @@ module ActiveRecord
assert_raise ActiveRecord::NoDatabaseError do
configuration = ActiveRecord::Base.configurations['arunit'].merge(database: 'inexistent_activerecord_unittest')
connection = ActiveRecord::Base.mysql_connection(configuration)
- connection.exec_query('drop table if exists ex')
+ connection.drop_table 'ex', if_exists: true
end
end
@@ -99,13 +98,19 @@ module ActiveRecord
end
end
+ def test_composite_primary_key
+ with_example_table '`id` INT(11), `number` INT(11), foo INT(11), PRIMARY KEY (`id`, `number`)' do
+ assert_nil @conn.primary_key('ex')
+ end
+ end
+
def test_tinyint_integer_typecasting
with_example_table '`status` TINYINT(4)' do
insert(@conn, { 'status' => 2 }, 'ex')
result = @conn.exec_query('SELECT status FROM ex')
- assert_equal 2, result.column_types['status'].type_cast_from_database(result.last['status'])
+ assert_equal 2, result.column_types['status'].deserialize(result.last['status'])
end
end
@@ -123,10 +128,10 @@ module ActiveRecord
private
def insert(ctx, data, table='ex')
- binds = data.map { |name, value|
- [ctx.columns(table).find { |x| x.name == name }, value]
+ binds = data.map { |name, value|
+ Relation::QueryAttribute.new(name, value, Type::Value.new)
}
- columns = binds.map(&:first).map(&:name)
+ columns = binds.map(&:name)
sql = "INSERT INTO #{table} (#{columns.join(", ")})
VALUES (#{(['?'] * columns.length).join(', ')})"
diff --git a/activerecord/test/cases/adapters/mysql/quoting_test.rb b/activerecord/test/cases/adapters/mysql/quoting_test.rb
index d8a954efa8..a2206153e9 100644
--- a/activerecord/test/cases/adapters/mysql/quoting_test.rb
+++ b/activerecord/test/cases/adapters/mysql/quoting_test.rb
@@ -9,15 +9,11 @@ module ActiveRecord
end
def test_type_cast_true
- c = Column.new(nil, 1, Type::Boolean.new)
- assert_equal 1, @conn.type_cast(true, nil)
- assert_equal 1, @conn.type_cast(true, c)
+ assert_equal 1, @conn.type_cast(true)
end
def test_type_cast_false
- c = Column.new(nil, 1, Type::Boolean.new)
- assert_equal 0, @conn.type_cast(false, nil)
- assert_equal 0, @conn.type_cast(false, c)
+ assert_equal 0, @conn.type_cast(false)
end
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 61ae0abfd1..ec1c394f40 100644
--- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
@@ -71,7 +71,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
#fixtures
self.use_instantiated_fixtures = true
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
#activerecord model class with reserved-word table name
def test_activerecord_model
@@ -101,7 +101,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
gs = nil
assert_nothing_raised { gs = Select.find(2).groups }
assert_equal gs.length, 2
- assert(gs.collect{|x| x.id}.sort == [2, 3])
+ assert(gs.collect(&:id).sort == [2, 3])
end
# has_and_belongs_to_many with reserved-word table name
@@ -110,7 +110,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
s = nil
assert_nothing_raised { s = Distinct.find(1).selects }
assert_equal s.length, 2
- assert(s.collect{|x|x.id}.sort == [1, 2])
+ assert(s.collect(&:id).sort == [1, 2])
end
# activerecord model introspection with reserved-word table and column names
@@ -139,7 +139,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
# custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name
def drop_tables_directly(table_names, connection = @connection)
table_names.each do |name|
- connection.execute("DROP TABLE IF EXISTS `#{name}`")
+ connection.drop_table name, if_exists: true
end
end
diff --git a/activerecord/test/cases/adapters/mysql/schema_test.rb b/activerecord/test/cases/adapters/mysql/schema_test.rb
index 87c5277e64..b7f9c2ce84 100644
--- a/activerecord/test/cases/adapters/mysql/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/schema_test.rb
@@ -22,7 +22,7 @@ module ActiveRecord
end
teardown do
- @connection.execute "drop table if exists mysql_doubles"
+ @connection.drop_table "mysql_doubles", if_exists: true
end
class MysqlDouble < ActiveRecord::Base
@@ -81,7 +81,7 @@ module ActiveRecord
table = 'key_tests'
- indexes = @connection.indexes(table).sort_by {|i| i.name}
+ indexes = @connection.indexes(table).sort_by(&:name)
assert_equal 3,indexes.size
index_a = indexes.select{|i| i.name == index_a_name}[0]
diff --git a/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb
new file mode 100644
index 0000000000..e9edc53f93
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb
@@ -0,0 +1,30 @@
+require "cases/helper"
+
+class UnsignedTypeTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ class UnsignedType < ActiveRecord::Base
+ end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table("unsigned_types", force: true) do |t|
+ t.column :unsigned_integer, "int unsigned"
+ end
+ end
+
+ teardown do
+ @connection.drop_table "unsigned_types"
+ end
+
+ test "unsigned int max value is in range" do
+ assert expected = UnsignedType.create(unsigned_integer: 4294967295)
+ assert_equal expected, UnsignedType.find_by(unsigned_integer: 4294967295)
+ end
+
+ test "minus value is out of range" do
+ assert_raise(RangeError) do
+ UnsignedType.create(unsigned_integer: -10)
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
index 49cfafd7a5..e87cd3886a 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -92,7 +92,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
with_real_execute do
begin
ActiveRecord::Base.connection.create_table :delete_me
- ActiveRecord::Base.connection.add_timestamps :delete_me
+ ActiveRecord::Base.connection.add_timestamps :delete_me, null: true
assert column_present?('delete_me', 'updated_at', 'datetime')
assert column_present?('delete_me', 'created_at', 'datetime')
ensure
@@ -107,7 +107,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
ActiveRecord::Base.connection.create_table :delete_me do |t|
t.timestamps null: true
end
- ActiveRecord::Base.connection.remove_timestamps :delete_me
+ ActiveRecord::Base.connection.remove_timestamps :delete_me, { null: true }
assert !column_present?('delete_me', 'updated_at', 'datetime')
assert !column_present?('delete_me', 'created_at', 'datetime')
ensure
diff --git a/activerecord/test/cases/adapters/mysql2/boolean_test.rb b/activerecord/test/cases/adapters/mysql2/boolean_test.rb
index 03627135b2..0d81dd6eee 100644
--- a/activerecord/test/cases/adapters/mysql2/boolean_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/boolean_test.rb
@@ -1,7 +1,7 @@
require "cases/helper"
class Mysql2BooleanTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
class BooleanType < ActiveRecord::Base
self.table_name = "mysql_booleans"
@@ -47,8 +47,7 @@ class Mysql2BooleanTest < ActiveRecord::TestCase
assert_equal 1, attributes["archived"]
assert_equal "1", attributes["published"]
- assert_equal 1, @connection.type_cast(true, boolean_column)
- assert_equal "1", @connection.type_cast(true, string_column)
+ assert_equal 1, @connection.type_cast(true)
end
test "test type casting without emulated booleans" do
@@ -60,8 +59,7 @@ class Mysql2BooleanTest < ActiveRecord::TestCase
assert_equal 1, attributes["archived"]
assert_equal "1", attributes["published"]
- assert_equal 1, @connection.type_cast(true, boolean_column)
- assert_equal "1", @connection.type_cast(true, string_column)
+ assert_equal 1, @connection.type_cast(true)
end
test "with booleans stored as 1 and 0" do
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..c8dd49d00a
--- /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 CharsetCollationTest < ActiveRecord::TestCase
+ 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 62c5abbf41..a8b39b21d4 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -22,7 +22,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase
assert_raise ActiveRecord::NoDatabaseError do
configuration = ActiveRecord::Base.configurations['arunit'].merge(database: 'inexistent_activerecord_unittest')
connection = ActiveRecord::Base.mysql2_connection(configuration)
- connection.exec_query('drop table if exists ex')
+ connection.drop_table 'ex', if_exists: true
end
end
@@ -44,9 +44,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase
assert !@connection.active?
# Repair all fixture connections so other tests won't break.
- @fixture_connections.each do |c|
- c.verify!
- end
+ @fixture_connections.each(&:verify!)
end
def test_successful_reconnection_after_timeout_with_manual_reconnect
@@ -124,11 +122,4 @@ class MysqlConnectionTest < ActiveRecord::TestCase
ensure
@connection.execute "DROP TABLE `bar_baz`"
end
-
- if mysql_56?
- def test_quote_time_usec
- assert_equal "'1970-01-01 00:00:00.000000'", @connection.quote(Time.at(0))
- assert_equal "'1970-01-01 00:00:00.000000'", @connection.quote(Time.at(0).to_datetime)
- end
- end
end
diff --git a/activerecord/test/cases/adapters/mysql2/explain_test.rb b/activerecord/test/cases/adapters/mysql2/explain_test.rb
index 675703caa1..2b01d941b8 100644
--- a/activerecord/test/cases/adapters/mysql2/explain_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/explain_test.rb
@@ -1,5 +1,6 @@
require "cases/helper"
require 'models/developer'
+require 'models/computer'
module ActiveRecord
module ConnectionAdapters
@@ -17,7 +18,7 @@ module ActiveRecord
explain = Developer.where(:id => 1).includes(:audit_logs).explain
assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
assert_match %r(developers |.* const), explain
- assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` IN (1)), explain
+ assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` = 1), explain
assert_match %r(audit_logs |.* ALL), explain
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 799d927ee4..799e60a683 100644
--- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
@@ -71,7 +71,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
#fixtures
self.use_instantiated_fixtures = true
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
#activerecord model class with reserved-word table name
def test_activerecord_model
@@ -100,7 +100,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
gs = nil
assert_nothing_raised { gs = Select.find(2).groups }
assert_equal gs.length, 2
- assert(gs.collect{|x| x.id}.sort == [2, 3])
+ assert(gs.collect(&:id).sort == [2, 3])
end
# has_and_belongs_to_many with reserved-word table name
@@ -109,7 +109,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
s = nil
assert_nothing_raised { s = Distinct.find(1).selects }
assert_equal s.length, 2
- assert(s.collect{|x|x.id}.sort == [1, 2])
+ assert(s.collect(&:id).sort == [1, 2])
end
# activerecord model introspection with reserved-word table and column names
@@ -138,7 +138,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
# custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name
def drop_tables_directly(table_names, connection = @connection)
table_names.each do |name|
- connection.execute("DROP TABLE IF EXISTS `#{name}`")
+ connection.drop_table name, if_exists: true
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
index 9c49599d34..417ccf6d11 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
@@ -2,7 +2,7 @@ require "cases/helper"
module ActiveRecord
module ConnectionAdapters
- class Mysql2Adapter
+ class AbstractMysqlAdapter
class SchemaMigrationsTest < ActiveRecord::TestCase
def test_renaming_index_on_foreign_key
connection.add_index "engines", "car_id"
@@ -16,23 +16,31 @@ module ActiveRecord
def test_initializes_schema_migrations_for_encoding_utf8mb4
smtn = ActiveRecord::Migrator.schema_migrations_table_name
- connection.drop_table(smtn) if connection.table_exists?(smtn)
+ connection.drop_table smtn, if_exists: true
- config = connection.instance_variable_get(:@config)
- original_encoding = config[:encoding]
+ 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")
- config[:encoding] = 'utf8mb4'
connection.initialize_schema_migrations_table
- assert connection.column_exists?(smtn, :version, :string, limit: Mysql2Adapter::MAX_INDEX_LENGTH_FOR_UTF8MB4)
+ assert connection.column_exists?(smtn, :version, :string, limit: AbstractMysqlAdapter::MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN)
ensure
- config[:encoding] = original_encoding
+ 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
end
diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb
index 1b7e60565d..47707b7d4f 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb
@@ -51,7 +51,7 @@ module ActiveRecord
table = 'key_tests'
- indexes = @connection.indexes(table).sort_by {|i| i.name}
+ indexes = @connection.indexes(table).sort_by(&:name)
assert_equal 3,indexes.size
index_a = indexes.select{|i| i.name == index_a_name}[0]
diff --git a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
new file mode 100644
index 0000000000..e9edc53f93
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
@@ -0,0 +1,30 @@
+require "cases/helper"
+
+class UnsignedTypeTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ class UnsignedType < ActiveRecord::Base
+ end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table("unsigned_types", force: true) do |t|
+ t.column :unsigned_integer, "int unsigned"
+ end
+ end
+
+ teardown do
+ @connection.drop_table "unsigned_types"
+ end
+
+ test "unsigned int max value is in range" do
+ assert expected = UnsignedType.create(unsigned_integer: 4294967295)
+ assert_equal expected, UnsignedType.find_by(unsigned_integer: 4294967295)
+ end
+
+ test "minus value is out of range" do
+ assert_raise(RangeError) do
+ UnsignedType.create(unsigned_integer: -10)
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb
index fa7bebf08b..6edbd9c3a6 100644
--- a/activerecord/test/cases/adapters/postgresql/array_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/array_test.rb
@@ -1,7 +1,8 @@
-# encoding: utf-8
require "cases/helper"
+require 'support/schema_dumping_helper'
class PostgresqlArrayTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
include InTimeZone
OID = ActiveRecord::ConnectionAdapters::PostgreSQL::OID
@@ -22,25 +23,25 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
t.hstore :hstores, array: true
end
end
+ PgArray.reset_column_information
@column = PgArray.columns_hash['tags']
+ @type = PgArray.type_for_attribute("tags")
end
teardown do
- @connection.execute 'drop table if exists pg_arrays'
+ @connection.drop_table 'pg_arrays', if_exists: true
disable_extension!('hstore', @connection)
end
def test_column
assert_equal :string, @column.type
assert_equal "character varying", @column.sql_type
- assert @column.array
- assert_not @column.number?
- assert_not @column.binary?
+ assert @column.array?
+ assert_not @type.binary?
ratings_column = PgArray.columns_hash['ratings']
assert_equal :integer, ratings_column.type
- assert ratings_column.array
- assert_not ratings_column.number?
+ assert ratings_column.array?
end
def test_default
@@ -72,7 +73,7 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
assert_equal :text, column.type
assert_equal [], PgArray.column_defaults['snippets']
- assert column.array
+ assert column.array?
end
def test_change_column_cant_make_non_array_column_to_array
@@ -92,9 +93,9 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
end
def test_type_cast_array
- assert_equal(['1', '2', '3'], @column.type_cast_from_database('{1,2,3}'))
- assert_equal([], @column.type_cast_from_database('{}'))
- assert_equal([nil], @column.type_cast_from_database('{NULL}'))
+ assert_equal(['1', '2', '3'], @type.deserialize('{1,2,3}'))
+ assert_equal([], @type.deserialize('{}'))
+ assert_equal([nil], @type.deserialize('{NULL}'))
end
def test_type_cast_integers
@@ -108,6 +109,12 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
assert_equal([1, 2], x.ratings)
end
+ def test_schema_dump_with_shorthand
+ output = dump_table_schema "pg_arrays"
+ assert_match %r[t\.string\s+"tags",\s+array: true], output
+ assert_match %r[t\.integer\s+"ratings",\s+array: true], output
+ end
+
def test_select_with_strings
@connection.execute "insert into pg_arrays (tags) VALUES ('{1,2,3}')"
x = PgArray.first
@@ -200,7 +207,7 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
x = PgArray.create!(tags: tags)
x.reload
- assert_equal x.tags_before_type_cast, PgArray.columns_hash['tags'].type_cast_for_database(tags)
+ assert_equal x.tags_before_type_cast, PgArray.type_for_attribute('tags').serialize(tags)
end
def test_quoting_non_standard_delimiters
@@ -208,8 +215,8 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
comma_delim = OID::Array.new(ActiveRecord::Type::String.new, ',')
semicolon_delim = OID::Array.new(ActiveRecord::Type::String.new, ';')
- assert_equal %({"hello,",world;}), comma_delim.type_cast_for_database(strings)
- assert_equal %({hello,;"world;"}), semicolon_delim.type_cast_for_database(strings)
+ assert_equal %({"hello,",world;}), comma_delim.serialize(strings)
+ assert_equal %({hello,;"world;"}), semicolon_delim.serialize(strings)
end
def test_mutate_array
@@ -256,11 +263,41 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
def test_assigning_non_array_value
record = PgArray.new(tags: "not-an-array")
- assert_equal "not-an-array", record.tags
- e = assert_raises(ActiveRecord::StatementInvalid) do
- record.save!
+ assert_equal [], record.tags
+ assert_equal "not-an-array", record.tags_before_type_cast
+ assert record.save
+ assert_equal record.tags, record.reload.tags
+ end
+
+ def test_assigning_empty_string
+ record = PgArray.new(tags: "")
+ assert_equal [], record.tags
+ assert_equal "", record.tags_before_type_cast
+ assert record.save
+ assert_equal record.tags, record.reload.tags
+ end
+
+ def test_assigning_valid_pg_array_literal
+ record = PgArray.new(tags: "{1,2,3}")
+ assert_equal ["1", "2", "3"], record.tags
+ assert_equal "{1,2,3}", record.tags_before_type_cast
+ assert record.save
+ assert_equal record.tags, record.reload.tags
+ end
+
+ def test_uniqueness_validation
+ klass = Class.new(PgArray) do
+ validates_uniqueness_of :tags
+
+ def self.model_name; ActiveModel::Name.new(PgArray) end
end
- assert_instance_of PG::InvalidTextRepresentation, e.original_exception
+ e1 = klass.create("tags" => ["black", "blue"])
+ assert e1.persisted?, "Saving e1"
+
+ e2 = klass.create("tags" => ["black", "blue"])
+ assert !e2.persisted?, "e2 shouldn't be valid"
+ assert e2.errors[:tags].any?, "Should have errors for tags"
+ assert_equal ["has already been taken"], e2.errors[:tags], "Should have uniqueness message for tags"
end
private
diff --git a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
index 72222c01fd..1a5ff4316c 100644
--- a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
require "cases/helper"
require 'support/connection_helper'
require 'support/schema_dumping_helper'
@@ -14,30 +13,34 @@ class PostgresqlBitStringTest < ActiveRecord::TestCase
@connection.create_table('postgresql_bit_strings', :force => true) do |t|
t.bit :a_bit, default: "00000011", limit: 8
t.bit_varying :a_bit_varying, default: "0011", limit: 4
+ t.bit :another_bit
+ t.bit_varying :another_bit_varying
end
end
def teardown
return unless @connection
- @connection.execute 'DROP TABLE IF EXISTS postgresql_bit_strings'
+ @connection.drop_table 'postgresql_bit_strings', if_exists: true
end
def test_bit_string_column
column = PostgresqlBitString.columns_hash["a_bit"]
assert_equal :bit, column.type
assert_equal "bit(8)", column.sql_type
- assert_not column.number?
- assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
+
+ type = PostgresqlBitString.type_for_attribute("a_bit")
+ assert_not type.binary?
end
def test_bit_string_varying_column
column = PostgresqlBitString.columns_hash["a_bit_varying"]
assert_equal :bit_varying, column.type
assert_equal "bit varying(4)", column.sql_type
- assert_not column.number?
- assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
+
+ type = PostgresqlBitString.type_for_attribute("a_bit_varying")
+ assert_not type.binary?
end
def test_default
diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
index 7872f91943..16db5ab83d 100644
--- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require "cases/helper"
class PostgresqlByteaTest < ActiveRecord::TestCase
@@ -17,32 +16,40 @@ class PostgresqlByteaTest < ActiveRecord::TestCase
end
end
@column = ByteaDataType.columns_hash['payload']
- assert(@column.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLColumn))
+ @type = ByteaDataType.type_for_attribute("payload")
end
teardown do
- @connection.execute 'drop table if exists bytea_data_type'
+ @connection.drop_table 'bytea_data_type', if_exists: true
end
def test_column
+ assert @column.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLColumn)
assert_equal :binary, @column.type
end
+ def test_binary_columns_are_limitless_the_upper_limit_is_one_GB
+ assert_equal 'bytea', @connection.type_to_sql(:binary, 100_000)
+ assert_raise ActiveRecord::ActiveRecordError do
+ @connection.type_to_sql :binary, 4294967295
+ end
+ end
+
def test_type_cast_binary_converts_the_encoding
assert @column
data = "\u001F\x8B"
assert_equal('UTF-8', data.encoding.name)
- assert_equal('ASCII-8BIT', @column.type_cast_from_database(data).encoding.name)
+ assert_equal('ASCII-8BIT', @type.deserialize(data).encoding.name)
end
def test_type_cast_binary_value
data = "\u001F\x8B".force_encoding("BINARY")
- assert_equal(data, @column.type_cast_from_database(data))
+ assert_equal(data, @type.deserialize(data))
end
def test_type_case_nil
- assert_equal(nil, @column.type_cast_from_database(nil))
+ assert_equal(nil, @type.deserialize(nil))
end
def test_read_value
diff --git a/activerecord/test/cases/adapters/postgresql/change_schema_test.rb b/activerecord/test/cases/adapters/postgresql/change_schema_test.rb
new file mode 100644
index 0000000000..5a9796887c
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/change_schema_test.rb
@@ -0,0 +1,38 @@
+require 'cases/helper'
+
+module ActiveRecord
+ class Migration
+ class PGChangeSchemaTest < ActiveRecord::TestCase
+ attr_reader :connection
+
+ def setup
+ super
+ @connection = ActiveRecord::Base.connection
+ connection.create_table(:strings) do |t|
+ t.string :somedate
+ end
+ end
+
+ def teardown
+ connection.drop_table :strings
+ end
+
+ def test_change_string_to_date
+ connection.change_column :strings, :somedate, :timestamp, using: 'CAST("somedate" AS timestamp)'
+ assert_equal :datetime, connection.columns(:strings).find { |c| c.name == 'somedate' }.type
+ end
+
+ def test_change_type_with_symbol
+ connection.change_column :strings, :somedate, :timestamp, cast_as: :timestamp
+ assert_equal :datetime, connection.columns(:strings).find { |c| c.name == 'somedate' }.type
+ end
+
+ def test_change_type_with_array
+ connection.change_column :strings, :somedate, :timestamp, array: true, cast_as: :timestamp
+ column = connection.columns(:strings).find { |c| c.name == 'somedate' }
+ assert_equal :datetime, column.type
+ assert column.array?
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/cidr_test.rb b/activerecord/test/cases/adapters/postgresql/cidr_test.rb
new file mode 100644
index 0000000000..6cb11d17b4
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/cidr_test.rb
@@ -0,0 +1,25 @@
+require "cases/helper"
+require "ipaddr"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class PostgreSQLAdapter
+ class CidrTest < ActiveRecord::TestCase
+ test "type casting IPAddr for database" do
+ type = OID::Cidr.new
+ ip = IPAddr.new("255.0.0.0/8")
+ ip2 = IPAddr.new("127.0.0.1")
+
+ assert_equal "255.0.0.0/8", type.serialize(ip)
+ assert_equal "127.0.0.1/32", type.serialize(ip2)
+ end
+
+ test "casting does nothing with non-IPAddr objects" do
+ type = OID::Cidr.new
+
+ assert_equal "foo", type.serialize("foo")
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/citext_test.rb b/activerecord/test/cases/adapters/postgresql/citext_test.rb
index cb024463c9..f706847890 100644
--- a/activerecord/test/cases/adapters/postgresql/citext_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/citext_test.rb
@@ -1,8 +1,9 @@
-# encoding: utf-8
require 'cases/helper'
+require 'support/schema_dumping_helper'
if ActiveRecord::Base.connection.supports_extensions?
class PostgresqlCitextTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
class Citext < ActiveRecord::Base
self.table_name = 'citexts'
end
@@ -18,7 +19,7 @@ if ActiveRecord::Base.connection.supports_extensions?
end
teardown do
- @connection.execute 'DROP TABLE IF EXISTS citexts;'
+ @connection.drop_table 'citexts', if_exists: true
disable_extension!('citext', @connection)
end
@@ -30,9 +31,10 @@ if ActiveRecord::Base.connection.supports_extensions?
column = Citext.columns_hash['cival']
assert_equal :citext, column.type
assert_equal 'citext', column.sql_type
- assert_not column.number?
- assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
+
+ type = Citext.type_for_attribute('cival')
+ assert_not type.binary?
end
def test_change_table_supports_json
@@ -67,5 +69,10 @@ if ActiveRecord::Base.connection.supports_extensions?
x = Citext.where(cival: 'cased text').first
assert_equal 'Cased Text', x.cival
end
+
+ def test_schema_dump_with_shorthand
+ output = dump_table_schema("citexts")
+ assert_match %r[t\.citext "cival"], output
+ end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb
index cfab5ca902..16e3f90a47 100644
--- a/activerecord/test/cases/adapters/postgresql/composite_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
require "cases/helper"
require 'support/connection_helper'
@@ -30,7 +29,7 @@ module PostgresqlCompositeBehavior
def teardown
super
- @connection.execute 'DROP TABLE IF EXISTS postgresql_composites'
+ @connection.drop_table 'postgresql_composites', if_exists: true
@connection.execute 'DROP TYPE IF EXISTS full_address'
reset_connection
PostgresqlComposite.reset_column_information
@@ -50,9 +49,10 @@ class PostgresqlCompositeTest < ActiveRecord::TestCase
column = PostgresqlComposite.columns_hash["address"]
assert_nil column.type
assert_equal "full_address", column.sql_type
- assert_not column.number?
- assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
+
+ type = PostgresqlComposite.type_for_attribute("address")
+ assert_not type.binary?
end
def test_composite_mapping
@@ -83,17 +83,17 @@ class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::TestCase
class FullAddressType < ActiveRecord::Type::Value
def type; :full_address end
- def type_cast_from_database(value)
+ def deserialize(value)
if value =~ /\("?([^",]*)"?,"?([^",]*)"?\)/
FullAddress.new($1, $2)
end
end
- def type_cast_from_user(value)
+ def cast(value)
value
end
- def type_cast_for_database(value)
+ def serialize(value)
return if value.nil?
"(#{value.city},#{value.street})"
end
@@ -111,9 +111,10 @@ class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::TestCase
column = PostgresqlComposite.columns_hash["address"]
assert_equal :full_address, column.type
assert_equal "full_address", column.sql_type
- assert_not column.number?
- assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
+
+ type = PostgresqlComposite.type_for_attribute("address")
+ assert_not type.binary?
end
def test_composite_mapping
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index 7d179944d4..55ad76c8c0 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -126,12 +126,12 @@ module ActiveRecord
end
def test_statement_key_is_logged
- bindval = 1
- @connection.exec_query('SELECT $1::integer', 'SQL', [[nil, bindval]])
+ bind = Relation::QueryAttribute.new(nil, 1, Type::Value.new)
+ @connection.exec_query('SELECT $1::integer', 'SQL', [bind])
name = @subscriber.payloads.last[:statement_name]
assert name
- res = @connection.exec_query("EXPLAIN (FORMAT JSON) EXECUTE #{name}(#{bindval})")
- plan = res.column_types['QUERY PLAN'].type_cast_from_database res.rows.first.first
+ res = @connection.exec_query("EXPLAIN (FORMAT JSON) EXECUTE #{name}(1)")
+ plan = res.column_types['QUERY PLAN'].deserialize res.rows.first.first
assert_operator plan.length, :>, 0
end
@@ -177,9 +177,7 @@ module ActiveRecord
"successfully querying with the same connection pid."
# Repair all fixture connections so other tests won't break.
- @fixture_connections.each do |c|
- c.verify!
- end
+ @fixture_connections.each(&:verify!)
end
def test_set_session_variable_true
diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index a0a34e4b87..2c14252ae4 100644
--- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -2,9 +2,6 @@ require "cases/helper"
require 'support/ddl_helper'
-class PostgresqlNumber < ActiveRecord::Base
-end
-
class PostgresqlTime < ActiveRecord::Base
end
@@ -15,18 +12,11 @@ class PostgresqlLtree < ActiveRecord::Base
end
class PostgresqlDataTypeTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
def setup
@connection = ActiveRecord::Base.connection
- @connection.execute("INSERT INTO postgresql_numbers (id, single, double) VALUES (1, 123.456, 123456.789)")
- @connection.execute("INSERT INTO postgresql_numbers (id, single, double) VALUES (2, '-Infinity', 'Infinity')")
- @connection.execute("INSERT INTO postgresql_numbers (id, single, double) VALUES (3, 123.456, 'NaN')")
- @first_number = PostgresqlNumber.find(1)
- @second_number = PostgresqlNumber.find(2)
- @third_number = PostgresqlNumber.find(3)
-
@connection.execute("INSERT INTO postgresql_times (id, time_interval, scaled_time_interval) VALUES (1, '1 year 2 days ago', '3 weeks ago')")
@first_time = PostgresqlTime.find(1)
@@ -35,12 +25,7 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
end
teardown do
- [PostgresqlNumber, PostgresqlTime, PostgresqlOid].each(&:delete_all)
- end
-
- def test_data_type_of_number_types
- assert_equal :float, @first_number.column_for_attribute(:single).type
- assert_equal :float, @first_number.column_for_attribute(:double).type
+ [PostgresqlTime, PostgresqlOid].each(&:delete_all)
end
def test_data_type_of_time_types
@@ -52,14 +37,6 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
assert_equal :integer, @first_oid.column_for_attribute(:obj_id).type
end
- def test_number_values
- assert_equal 123.456, @first_number.single
- assert_equal 123456.789, @first_number.double
- assert_equal(-::Float::INFINITY, @second_number.single)
- assert_equal ::Float::INFINITY, @second_number.double
- assert_same ::Float::NAN, @third_number.double
- end
-
def test_time_values
assert_equal '-1 years -2 days', @first_time.time_interval
assert_equal '-21 days', @first_time.scaled_time_interval
@@ -69,17 +46,6 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
assert_equal 1234, @first_oid.obj_id
end
- def test_update_number
- new_single = 789.012
- new_double = 789012.345
- @first_number.single = new_single
- @first_number.double = new_double
- assert @first_number.save
- assert @first_number.reload
- assert_equal new_single, @first_number.single
- assert_equal new_double, @first_number.double
- end
-
def test_update_time
@first_time.time_interval = '2 years 3 minutes'
assert @first_time.save
@@ -94,6 +60,13 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
assert @first_oid.reload
assert_equal new_value, @first_oid.obj_id
end
+
+ def test_text_columns_are_limitless_the_upper_limit_is_one_GB
+ assert_equal 'text', @connection.type_to_sql(:text, 100_000)
+ assert_raise ActiveRecord::ActiveRecordError do
+ @connection.type_to_sql :text, 4294967295
+ end
+ end
end
class PostgresqlInternalDataTypeTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/adapters/postgresql/domain_test.rb b/activerecord/test/cases/adapters/postgresql/domain_test.rb
index 1500adb42d..26e064c937 100644
--- a/activerecord/test/cases/adapters/postgresql/domain_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/domain_test.rb
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
require "cases/helper"
require 'support/connection_helper'
@@ -20,7 +19,7 @@ class PostgresqlDomainTest < ActiveRecord::TestCase
end
teardown do
- @connection.execute 'DROP TABLE IF EXISTS postgresql_domains'
+ @connection.drop_table 'postgresql_domains', if_exists: true
@connection.execute 'DROP DOMAIN IF EXISTS custom_money'
reset_connection
end
@@ -29,9 +28,10 @@ class PostgresqlDomainTest < ActiveRecord::TestCase
column = PostgresqlDomain.columns_hash["price"]
assert_equal :decimal, column.type
assert_equal "custom_money", column.sql_type
- assert column.number?
- assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
+
+ type = PostgresqlDomain.type_for_attribute("price")
+ assert_not type.binary?
end
def test_domain_acts_like_basetype
diff --git a/activerecord/test/cases/adapters/postgresql/enum_test.rb b/activerecord/test/cases/adapters/postgresql/enum_test.rb
index d99c4a292e..ed084483bc 100644
--- a/activerecord/test/cases/adapters/postgresql/enum_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/enum_test.rb
@@ -1,8 +1,5 @@
-# -*- coding: utf-8 -*-
require "cases/helper"
require 'support/connection_helper'
-require 'active_record/base'
-require 'active_record/connection_adapters/postgresql_adapter'
class PostgresqlEnumTest < ActiveRecord::TestCase
include ConnectionHelper
@@ -24,7 +21,7 @@ class PostgresqlEnumTest < ActiveRecord::TestCase
end
teardown do
- @connection.execute 'DROP TABLE IF EXISTS postgresql_enums'
+ @connection.drop_table 'postgresql_enums', if_exists: true
@connection.execute 'DROP TYPE IF EXISTS mood'
reset_connection
end
@@ -33,9 +30,10 @@ class PostgresqlEnumTest < ActiveRecord::TestCase
column = PostgresqlEnum.columns_hash["current_mood"]
assert_equal :enum, column.type
assert_equal "mood", column.sql_type
- assert_not column.number?
- assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
+
+ type = PostgresqlEnum.type_for_attribute("current_mood")
+ assert_not type.binary?
end
def test_enum_defaults
@@ -82,4 +80,12 @@ class PostgresqlEnumTest < ActiveRecord::TestCase
assert_equal "happy", enum.current_mood
end
+
+ def test_assigning_enum_to_nil
+ model = PostgresqlEnum.new(current_mood: nil)
+
+ assert_nil model.current_mood
+ assert model.save
+ assert_nil model.reload.current_mood
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb
index 19053b6732..6ffb4c9f33 100644
--- a/activerecord/test/cases/adapters/postgresql/explain_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb
@@ -1,5 +1,6 @@
require "cases/helper"
require 'models/developer'
+require 'models/computer'
module ActiveRecord
module ConnectionAdapters
@@ -17,7 +18,7 @@ module ActiveRecord
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" IN (1)), explain
+ assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain
end
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 7b99fcdda0..06d427f464 100644
--- a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
@@ -1,7 +1,7 @@
require "cases/helper"
class PostgresqlExtensionMigrationTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
class EnableHstore < ActiveRecord::Migration
def change
diff --git a/activerecord/test/cases/adapters/postgresql/full_text_test.rb b/activerecord/test/cases/adapters/postgresql/full_text_test.rb
index 9dadb177ca..b83063c94e 100644
--- a/activerecord/test/cases/adapters/postgresql/full_text_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/full_text_test.rb
@@ -1,21 +1,34 @@
-# encoding: utf-8
require "cases/helper"
+require 'support/schema_dumping_helper'
class PostgresqlFullTextTest < ActiveRecord::TestCase
- class PostgresqlTsvector < ActiveRecord::Base; end
+ include SchemaDumpingHelper
+ class Tsvector < ActiveRecord::Base; end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table('tsvectors') do |t|
+ t.tsvector 'text_vector'
+ end
+ end
+
+ teardown do
+ @connection.drop_table 'tsvectors', if_exists: true
+ end
def test_tsvector_column
- column = PostgresqlTsvector.columns_hash["text_vector"]
+ column = Tsvector.columns_hash["text_vector"]
assert_equal :tsvector, column.type
assert_equal "tsvector", column.sql_type
- assert_not column.number?
- assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
+
+ type = Tsvector.type_for_attribute("text_vector")
+ assert_not type.binary?
end
def test_update_tsvector
- PostgresqlTsvector.create text_vector: "'text' 'vector'"
- tsvector = PostgresqlTsvector.first
+ Tsvector.create text_vector: "'text' 'vector'"
+ tsvector = Tsvector.first
assert_equal "'text' 'vector'", tsvector.text_vector
tsvector.text_vector = "'new' 'text' 'vector'"
@@ -23,4 +36,9 @@ class PostgresqlFullTextTest < ActiveRecord::TestCase
assert tsvector.reload
assert_equal "'new' 'text' 'vector'", tsvector.text_vector
end
+
+ def test_schema_dump_with_shorthand
+ output = dump_table_schema("tsvectors")
+ assert_match %r{t\.tsvector "text_vector"}, output
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
index 6c0adbbeaa..41e9572907 100644
--- a/activerecord/test/cases/adapters/postgresql/geometric_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
require "cases/helper"
require 'support/connection_helper'
require 'support/schema_dumping_helper'
@@ -11,26 +10,25 @@ class PostgresqlPointTest < ActiveRecord::TestCase
def setup
@connection = ActiveRecord::Base.connection
- @connection.transaction do
- @connection.create_table('postgresql_points') do |t|
- t.point :x
- t.point :y, default: [12.2, 13.3]
- t.point :z, default: "(14.4,15.5)"
- end
+ @connection.create_table('postgresql_points') do |t|
+ t.point :x
+ t.point :y, default: [12.2, 13.3]
+ t.point :z, default: "(14.4,15.5)"
end
end
teardown do
- @connection.execute 'DROP TABLE IF EXISTS postgresql_points'
+ @connection.drop_table 'postgresql_points', if_exists: true
end
def test_column
column = PostgresqlPoint.columns_hash["x"]
assert_equal :point, column.type
assert_equal "point", column.sql_type
- assert_not column.number?
- assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
+
+ type = PostgresqlPoint.type_for_attribute("x")
+ assert_not type.binary?
end
def test_default
@@ -70,3 +68,72 @@ class PostgresqlPointTest < ActiveRecord::TestCase
assert_not p.changed?
end
end
+
+class PostgresqlGeometricTest < ActiveRecord::TestCase
+ class PostgresqlGeometric < ActiveRecord::Base; end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table("postgresql_geometrics") do |t|
+ t.column :a_line_segment, :lseg
+ t.column :a_box, :box
+ t.column :a_path, :path
+ t.column :a_polygon, :polygon
+ t.column :a_circle, :circle
+ end
+ end
+
+ teardown do
+ @connection.drop_table 'postgresql_geometrics', if_exists: true
+ end
+
+ def test_geometric_types
+ g = PostgresqlGeometric.new(
+ :a_line_segment => '(2.0, 3), (5.5, 7.0)',
+ :a_box => '2.0, 3, 5.5, 7.0',
+ :a_path => '[(2.0, 3), (5.5, 7.0), (8.5, 11.0)]',
+ :a_polygon => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))',
+ :a_circle => '<(5.3, 10.4), 2>'
+ )
+
+ g.save!
+
+ h = PostgresqlGeometric.find(g.id)
+
+ assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
+ assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
+ assert_equal '[(2,3),(5.5,7),(8.5,11)]', h.a_path
+ assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
+ assert_equal '<(5.3,10.4),2>', h.a_circle
+ end
+
+ def test_alternative_format
+ g = PostgresqlGeometric.new(
+ :a_line_segment => '((2.0, 3), (5.5, 7.0))',
+ :a_box => '(2.0, 3), (5.5, 7.0)',
+ :a_path => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))',
+ :a_polygon => '2.0, 3, 5.5, 7.0, 8.5, 11.0',
+ :a_circle => '((5.3, 10.4), 2)'
+ )
+
+ g.save!
+
+ h = PostgresqlGeometric.find(g.id)
+ assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
+ assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
+ assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_path
+ assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
+ assert_equal '<(5.3,10.4),2>', h.a_circle
+ end
+
+ def test_geometric_function
+ PostgresqlGeometric.create! a_path: '[(2.0, 3), (5.5, 7.0), (8.5, 11.0)]' # [ ] is an open path
+ PostgresqlGeometric.create! a_path: '((2.0, 3), (5.5, 7.0), (8.5, 11.0))' # ( ) is a closed path
+
+ objs = PostgresqlGeometric.find_by_sql "SELECT isopen(a_path) FROM postgresql_geometrics ORDER BY id ASC"
+ assert_equal [true, false], objs.map(&:isopen)
+
+ objs = PostgresqlGeometric.find_by_sql "SELECT isclosed(a_path) FROM postgresql_geometrics ORDER BY id ASC"
+ assert_equal [false, true], objs.map(&:isclosed)
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
index 1296eb72c0..ad9dd311a6 100644
--- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
@@ -1,41 +1,41 @@
-# encoding: utf-8
-
require "cases/helper"
-require 'active_record/base'
-require 'active_record/connection_adapters/postgresql_adapter'
+require 'support/schema_dumping_helper'
-class PostgresqlHstoreTest < ActiveRecord::TestCase
- class Hstore < ActiveRecord::Base
- self.table_name = 'hstores'
+if ActiveRecord::Base.connection.supports_extensions?
+ class PostgresqlHstoreTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+ class Hstore < ActiveRecord::Base
+ self.table_name = 'hstores'
- store_accessor :settings, :language, :timezone
- end
+ store_accessor :settings, :language, :timezone
+ end
- def setup
- @connection = ActiveRecord::Base.connection
+ def setup
+ @connection = ActiveRecord::Base.connection
- unless @connection.extension_enabled?('hstore')
- @connection.enable_extension 'hstore'
- @connection.commit_db_transaction
- end
+ unless @connection.extension_enabled?('hstore')
+ @connection.enable_extension 'hstore'
+ @connection.commit_db_transaction
+ end
- @connection.reconnect!
+ @connection.reconnect!
- @connection.transaction do
- @connection.create_table('hstores') do |t|
- t.hstore 'tags', :default => ''
- t.hstore 'payload', array: true
- t.hstore 'settings'
+ @connection.transaction do
+ @connection.create_table('hstores') do |t|
+ t.hstore 'tags', :default => ''
+ t.hstore 'payload', array: true
+ t.hstore 'settings'
+ end
end
+ Hstore.reset_column_information
+ @column = Hstore.columns_hash['tags']
+ @type = Hstore.type_for_attribute("tags")
end
- @column = Hstore.columns_hash['tags']
- end
- teardown do
- @connection.execute 'drop table if exists hstores'
- end
+ teardown do
+ @connection.drop_table 'hstores', if_exists: true
+ end
- if ActiveRecord::Base.connection.supports_extensions?
def test_hstore_included_in_extensions
assert @connection.respond_to?(:extensions), "connection should have a list of extensions"
assert @connection.extensions.include?('hstore'), "extension list should include hstore"
@@ -55,9 +55,9 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
def test_column
assert_equal :hstore, @column.type
assert_equal "hstore", @column.sql_type
- assert_not @column.number?
- assert_not @column.binary?
- assert_not @column.array
+ assert_not @column.array?
+
+ assert_not @type.binary?
end
def test_default
@@ -111,10 +111,10 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
end
def test_type_cast_hstore
- assert_equal({'1' => '2'}, @column.type_cast_from_database("\"1\"=>\"2\""))
- assert_equal({}, @column.type_cast_from_database(""))
- assert_equal({'key'=>nil}, @column.type_cast_from_database('key => NULL'))
- assert_equal({'c'=>'}','"a"'=>'b "a b'}, @column.type_cast_from_database(%q(c=>"}", "\"a\""=>"b \"a b")))
+ assert_equal({'1' => '2'}, @type.deserialize("\"1\"=>\"2\""))
+ assert_equal({}, @type.deserialize(""))
+ assert_equal({'key'=>nil}, @type.deserialize('key => NULL'))
+ assert_equal({'c'=>'}','"a"'=>'b "a b'}, @type.deserialize(%q(c=>"}", "\"a\""=>"b \"a b")))
end
def test_with_store_accessors
@@ -166,47 +166,47 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
end
def test_gen1
- assert_equal(%q(" "=>""), @column.cast_type.type_cast_for_database({' '=>''}))
+ assert_equal(%q(" "=>""), @type.serialize({' '=>''}))
end
def test_gen2
- assert_equal(%q(","=>""), @column.cast_type.type_cast_for_database({','=>''}))
+ assert_equal(%q(","=>""), @type.serialize({','=>''}))
end
def test_gen3
- assert_equal(%q("="=>""), @column.cast_type.type_cast_for_database({'='=>''}))
+ assert_equal(%q("="=>""), @type.serialize({'='=>''}))
end
def test_gen4
- assert_equal(%q(">"=>""), @column.cast_type.type_cast_for_database({'>'=>''}))
+ assert_equal(%q(">"=>""), @type.serialize({'>'=>''}))
end
def test_parse1
- assert_equal({'a'=>nil,'b'=>nil,'c'=>'NuLl','null'=>'c'}, @column.type_cast_from_database('a=>null,b=>NuLl,c=>"NuLl",null=>c'))
+ assert_equal({'a'=>nil,'b'=>nil,'c'=>'NuLl','null'=>'c'}, @type.deserialize('a=>null,b=>NuLl,c=>"NuLl",null=>c'))
end
def test_parse2
- assert_equal({" " => " "}, @column.type_cast_from_database("\\ =>\\ "))
+ assert_equal({" " => " "}, @type.deserialize("\\ =>\\ "))
end
def test_parse3
- assert_equal({"=" => ">"}, @column.type_cast_from_database("==>>"))
+ assert_equal({"=" => ">"}, @type.deserialize("==>>"))
end
def test_parse4
- assert_equal({"=a"=>"q=w"}, @column.type_cast_from_database('\=a=>q=w'))
+ assert_equal({"=a"=>"q=w"}, @type.deserialize('\=a=>q=w'))
end
def test_parse5
- assert_equal({"=a"=>"q=w"}, @column.type_cast_from_database('"=a"=>q\=w'))
+ assert_equal({"=a"=>"q=w"}, @type.deserialize('"=a"=>q\=w'))
end
def test_parse6
- assert_equal({"\"a"=>"q>w"}, @column.type_cast_from_database('"\"a"=>q>w'))
+ assert_equal({"\"a"=>"q>w"}, @type.deserialize('"\"a"=>q>w'))
end
def test_parse7
- assert_equal({"\"a"=>"q\"w"}, @column.type_cast_from_database('\"a=>q"w'))
+ assert_equal({"\"a"=>"q\"w"}, @type.deserialize('\"a=>q"w'))
end
def test_rewrite
@@ -315,10 +315,13 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
dupe = record.dup
assert_equal({"one" => "two"}, dupe.tags.to_hash)
end
- end
- private
+ def test_schema_dump_with_shorthand
+ output = dump_table_schema("hstores")
+ assert_match %r[t\.hstore "tags",\s+default: {}], output
+ end
+ private
def assert_array_cycle(array)
# test creation
x = Hstore.create!(payload: array)
@@ -346,4 +349,5 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
x.reload
assert_equal(hash, x.tags)
end
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/infinity_test.rb b/activerecord/test/cases/adapters/postgresql/infinity_test.rb
index 22e8873333..d9d7832094 100644
--- a/activerecord/test/cases/adapters/postgresql/infinity_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/infinity_test.rb
@@ -1,6 +1,8 @@
require "cases/helper"
class PostgresqlInfinityTest < ActiveRecord::TestCase
+ include InTimeZone
+
class PostgresqlInfinity < ActiveRecord::Base
end
@@ -13,7 +15,7 @@ class PostgresqlInfinityTest < ActiveRecord::TestCase
end
teardown do
- @connection.execute("DROP TABLE IF EXISTS postgresql_infinities")
+ @connection.drop_table 'postgresql_infinities', if_exists: true
end
test "type casting infinity on a float column" do
@@ -22,6 +24,15 @@ class PostgresqlInfinityTest < ActiveRecord::TestCase
assert_equal Float::INFINITY, record.float
end
+ test "type casting string on a float column" do
+ record = PostgresqlInfinity.new(float: 'Infinity')
+ assert_equal Float::INFINITY, record.float
+ record = PostgresqlInfinity.new(float: '-Infinity')
+ assert_equal(-Float::INFINITY, record.float)
+ record = PostgresqlInfinity.new(float: 'NaN')
+ assert_send [record.float, :nan?]
+ end
+
test "update_all with infinity on a float column" do
record = PostgresqlInfinity.create!
PostgresqlInfinity.update_all(float: Float::INFINITY)
@@ -41,4 +52,18 @@ class PostgresqlInfinityTest < ActiveRecord::TestCase
record.reload
assert_equal Float::INFINITY, record.datetime
end
+
+ test "assigning 'infinity' on a datetime column with TZ aware attributes" do
+ begin
+ in_time_zone "Pacific Time (US & Canada)" do
+ record = PostgresqlInfinity.create!(datetime: "infinity")
+ assert_equal Float::INFINITY, record.datetime
+ assert_equal record.datetime, record.reload.datetime
+ end
+ ensure
+ # setting time_zone_aware_attributes causes the types to change.
+ # There is no way to do this automatically since it can be set on a superclass
+ PostgresqlInfinity.reset_column_information
+ end
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/integer_test.rb b/activerecord/test/cases/adapters/postgresql/integer_test.rb
new file mode 100644
index 0000000000..679a0fc7b3
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/integer_test.rb
@@ -0,0 +1,25 @@
+require "cases/helper"
+require "active_support/core_ext/numeric/bytes"
+
+class PostgresqlIntegerTest < ActiveRecord::TestCase
+ class PgInteger < ActiveRecord::Base
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+
+ @connection.transaction do
+ @connection.create_table "pg_integers", force: true do |t|
+ t.integer :quota, limit: 8, default: 2.gigabytes
+ end
+ end
+ end
+
+ teardown do
+ @connection.drop_table "pg_integers", if_exists: true
+ end
+
+ test "schema properly respects bigint ranges" do
+ assert_equal 2.gigabytes, PgInteger.new.quota
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb
index 7a9fdd45e8..6878516aeb 100644
--- a/activerecord/test/cases/adapters/postgresql/json_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/json_test.rb
@@ -1,8 +1,5 @@
-# encoding: utf-8
-
+# -*- coding: utf-8 -*-
require "cases/helper"
-require 'active_record/base'
-require 'active_record/connection_adapters/postgresql_adapter'
require 'support/schema_dumping_helper'
module PostgresqlJSONSharedTestCases
@@ -17,29 +14,28 @@ module PostgresqlJSONSharedTestCases
def setup
@connection = ActiveRecord::Base.connection
begin
- @connection.transaction do
- @connection.create_table('json_data_type') do |t|
- t.public_send column_type, 'payload', default: {} # t.json 'payload', default: {}
- t.public_send column_type, 'settings' # t.json 'settings'
- end
+ @connection.create_table('json_data_type') do |t|
+ t.public_send column_type, 'payload', default: {} # t.json 'payload', default: {}
+ t.public_send column_type, 'settings' # t.json 'settings'
end
rescue ActiveRecord::StatementInvalid
- skip "do not test on PG without json"
+ skip "do not test on PostgreSQL without #{column_type} type."
end
- @column = JsonDataType.columns_hash['payload']
end
def teardown
- @connection.execute 'drop table if exists json_data_type'
+ @connection.drop_table :json_data_type, if_exists: true
+ JsonDataType.reset_column_information
end
def test_column
column = JsonDataType.columns_hash["payload"]
assert_equal column_type, column.type
assert_equal column_type.to_s, column.sql_type
- assert_not column.number?
- assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
+
+ type = JsonDataType.type_for_attribute("payload")
+ assert_not type.binary?
end
def test_default
@@ -69,7 +65,7 @@ module PostgresqlJSONSharedTestCases
def test_schema_dumping
output = dump_table_schema("json_data_type")
- assert_match(/t.#{column_type.to_s}\s+"payload",\s+default: {}/, output)
+ assert_match(/t\.#{column_type.to_s}\s+"payload",\s+default: {}/, output)
end
def test_cast_value_on_write
@@ -81,16 +77,16 @@ module PostgresqlJSONSharedTestCases
end
def test_type_cast_json
- column = JsonDataType.columns_hash["payload"]
+ type = JsonDataType.type_for_attribute("payload")
data = "{\"a_key\":\"a_value\"}"
- hash = column.type_cast_from_database(data)
+ hash = type.deserialize(data)
assert_equal({'a_key' => 'a_value'}, hash)
- assert_equal({'a_key' => 'a_value'}, column.type_cast_from_database(data))
+ assert_equal({'a_key' => 'a_value'}, type.deserialize(data))
- assert_equal({}, column.type_cast_from_database("{}"))
- assert_equal({'key'=>nil}, column.type_cast_from_database('{"key": null}'))
- assert_equal({'c'=>'}','"a"'=>'b "a b'}, column.type_cast_from_database(%q({"c":"}", "\"a\"":"b \"a b"})))
+ assert_equal({}, type.deserialize("{}"))
+ assert_equal({'key'=>nil}, type.deserialize('{"key": null}'))
+ assert_equal({'c'=>'}','"a"'=>'b "a b'}, type.deserialize(%q({"c":"}", "\"a\"":"b \"a b"})))
end
def test_rewrite
@@ -182,6 +178,14 @@ module PostgresqlJSONSharedTestCases
assert_equal({ 'one' => 'two', 'three' => 'four' }, json.payload)
assert_not json.changed?
end
+
+ def test_assigning_invalid_json
+ json = JsonDataType.new
+
+ json.payload = 'foo'
+
+ assert_nil json.payload
+ end
end
class PostgresqlJSONTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/adapters/postgresql/ltree_test.rb b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
index 2968109346..ce0ad16557 100644
--- a/activerecord/test/cases/adapters/postgresql/ltree_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
@@ -1,7 +1,8 @@
-# encoding: utf-8
require "cases/helper"
+require 'support/schema_dumping_helper'
class PostgresqlLtreeTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
class Ltree < ActiveRecord::Base
self.table_name = 'ltrees'
end
@@ -21,16 +22,17 @@ class PostgresqlLtreeTest < ActiveRecord::TestCase
end
teardown do
- @connection.execute 'drop table if exists ltrees'
+ @connection.drop_table 'ltrees', if_exists: true
end
def test_column
column = Ltree.columns_hash['path']
assert_equal :ltree, column.type
assert_equal "ltree", column.sql_type
- assert_not column.number?
- assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
+
+ type = Ltree.type_for_attribute('path')
+ assert_not type.binary?
end
def test_write
@@ -43,4 +45,9 @@ class PostgresqlLtreeTest < ActiveRecord::TestCase
ltree = Ltree.first
assert_equal '1.2.3', ltree.path
end
+
+ def test_schema_dump_with_shorthand
+ output = dump_table_schema("ltrees")
+ assert_match %r[t\.ltree "path"], output
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/money_test.rb b/activerecord/test/cases/adapters/postgresql/money_test.rb
index 87183174f2..cedd399380 100644
--- a/activerecord/test/cases/adapters/postgresql/money_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/money_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require "cases/helper"
require 'support/schema_dumping_helper'
@@ -10,14 +9,14 @@ class PostgresqlMoneyTest < ActiveRecord::TestCase
setup do
@connection = ActiveRecord::Base.connection
@connection.execute("set lc_monetary = 'C'")
- @connection.create_table('postgresql_moneys') do |t|
- t.column "wealth", "money"
- t.column "depth", "money", default: "150.55"
+ @connection.create_table('postgresql_moneys', force: true) do |t|
+ t.money "wealth"
+ t.money "depth", default: "150.55"
end
end
teardown do
- @connection.execute 'DROP TABLE IF EXISTS postgresql_moneys'
+ @connection.drop_table 'postgresql_moneys', if_exists: true
end
def test_column
@@ -25,9 +24,10 @@ class PostgresqlMoneyTest < ActiveRecord::TestCase
assert_equal :money, column.type
assert_equal "money", column.sql_type
assert_equal 2, column.scale
- assert column.number?
- assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
+
+ type = PostgresqlMoney.type_for_attribute("wealth")
+ assert_not type.binary?
end
def test_default
@@ -46,17 +46,17 @@ class PostgresqlMoneyTest < ActiveRecord::TestCase
end
def test_money_type_cast
- column = PostgresqlMoney.columns_hash['wealth']
- assert_equal(12345678.12, column.type_cast_from_user("$12,345,678.12"))
- assert_equal(12345678.12, column.type_cast_from_user("$12.345.678,12"))
- assert_equal(-1.15, column.type_cast_from_user("-$1.15"))
- assert_equal(-2.25, column.type_cast_from_user("($2.25)"))
+ type = PostgresqlMoney.type_for_attribute('wealth')
+ assert_equal(12345678.12, type.cast("$12,345,678.12"))
+ assert_equal(12345678.12, type.cast("$12.345.678,12"))
+ assert_equal(-1.15, type.cast("-$1.15"))
+ assert_equal(-2.25, type.cast("($2.25)"))
end
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 4f4c1103fa..033695518e 100644
--- a/activerecord/test/cases/adapters/postgresql/network_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/network_test.rb
@@ -1,35 +1,51 @@
-# encoding: utf-8
require "cases/helper"
+require 'support/schema_dumping_helper'
class PostgresqlNetworkTest < ActiveRecord::TestCase
- class PostgresqlNetworkAddress < ActiveRecord::Base
+ include SchemaDumpingHelper
+ class PostgresqlNetworkAddress < ActiveRecord::Base; end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table('postgresql_network_addresses', force: true) do |t|
+ t.inet 'inet_address', default: "192.168.1.1"
+ t.cidr 'cidr_address', default: "192.168.1.0/24"
+ t.macaddr 'mac_address', default: "ff:ff:ff:ff:ff:ff"
+ end
+ end
+
+ teardown do
+ @connection.drop_table 'postgresql_network_addresses', if_exists: true
end
def test_cidr_column
column = PostgresqlNetworkAddress.columns_hash["cidr_address"]
assert_equal :cidr, column.type
assert_equal "cidr", column.sql_type
- assert_not column.number?
- assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
+
+ type = PostgresqlNetworkAddress.type_for_attribute("cidr_address")
+ assert_not type.binary?
end
def test_inet_column
column = PostgresqlNetworkAddress.columns_hash["inet_address"]
assert_equal :inet, column.type
assert_equal "inet", column.sql_type
- assert_not column.number?
- assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
+
+ type = PostgresqlNetworkAddress.type_for_attribute("inet_address")
+ assert_not type.binary?
end
def test_macaddr_column
column = PostgresqlNetworkAddress.columns_hash["mac_address"]
assert_equal :macaddr, column.type
assert_equal "macaddr", column.sql_type
- assert_not column.number?
- assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
+
+ type = PostgresqlNetworkAddress.type_for_attribute("mac_address")
+ assert_not type.binary?
end
def test_network_types
@@ -68,4 +84,11 @@ class PostgresqlNetworkTest < ActiveRecord::TestCase
assert_nil invalid_address.cidr_address_before_type_cast
assert_nil invalid_address.inet_address_before_type_cast
end
+
+ def test_schema_dump_with_shorthand
+ output = dump_table_schema("postgresql_network_addresses")
+ assert_match %r{t\.inet\s+"inet_address",\s+default: "192\.168\.1\.1"}, output
+ assert_match %r{t\.cidr\s+"cidr_address",\s+default: "192\.168\.1\.0/24"}, output
+ assert_match %r{t\.macaddr\s+"mac_address",\s+default: "ff:ff:ff:ff:ff:ff"}, output
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/numbers_test.rb b/activerecord/test/cases/adapters/postgresql/numbers_test.rb
new file mode 100644
index 0000000000..d8e01e3b89
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/numbers_test.rb
@@ -0,0 +1,49 @@
+require "cases/helper"
+
+class PostgresqlNumberTest < ActiveRecord::TestCase
+ class PostgresqlNumber < ActiveRecord::Base; end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table('postgresql_numbers', force: true) do |t|
+ t.column 'single', 'REAL'
+ t.column 'double', 'DOUBLE PRECISION'
+ end
+ end
+
+ teardown do
+ @connection.drop_table 'postgresql_numbers', if_exists: true
+ end
+
+ def test_data_type
+ assert_equal :float, PostgresqlNumber.columns_hash["single"].type
+ assert_equal :float, PostgresqlNumber.columns_hash["double"].type
+ end
+
+ def test_values
+ @connection.execute("INSERT INTO postgresql_numbers (id, single, double) VALUES (1, 123.456, 123456.789)")
+ @connection.execute("INSERT INTO postgresql_numbers (id, single, double) VALUES (2, '-Infinity', 'Infinity')")
+ @connection.execute("INSERT INTO postgresql_numbers (id, single, double) VALUES (3, 123.456, 'NaN')")
+
+ first, second, third = PostgresqlNumber.find(1, 2, 3)
+
+ assert_equal 123.456, first.single
+ assert_equal 123456.789, first.double
+ assert_equal(-::Float::INFINITY, second.single)
+ assert_equal ::Float::INFINITY, second.double
+ assert_send [third.double, :nan?]
+ end
+
+ def test_update
+ record = PostgresqlNumber.create! single: "123.456", double: "123456.789"
+ new_single = 789.012
+ new_double = 789012.345
+ record.single = new_single
+ record.double = new_double
+ record.save!
+
+ record.reload
+ assert_equal new_single, record.single
+ assert_equal new_double, record.double
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index a71c0dfb26..9a1b889d4d 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require "cases/helper"
require 'support/ddl_helper'
require 'support/connection_helper'
@@ -54,6 +53,12 @@ module ActiveRecord
end
end
+ def test_composite_primary_key
+ with_example_table 'id serial, number serial, PRIMARY KEY (id, number)' do
+ assert_nil @connection.primary_key('ex')
+ end
+ end
+
def test_primary_key_raises_error_if_table_not_found
assert_raises(ActiveRecord::StatementInvalid) do
@connection.primary_key('unobtainium')
@@ -63,7 +68,7 @@ module ActiveRecord
def test_insert_sql_with_proprietary_returning_clause
with_example_table do
id = @connection.insert_sql("insert into ex (number) values(5150)", nil, "number")
- assert_equal "5150", id
+ assert_equal 5150, id
end
end
@@ -101,21 +106,21 @@ module ActiveRecord
connection = connection_without_insert_returning
id = connection.insert_sql("insert into postgresql_partitioned_table_parent (number) VALUES (1)")
expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first
- assert_equal expect, id
+ assert_equal expect.to_i, id
end
def test_exec_insert_with_returning_disabled
connection = connection_without_insert_returning
result = connection.exec_insert("insert into postgresql_partitioned_table_parent (number) VALUES (1)", nil, [], 'id', 'postgresql_partitioned_table_parent_id_seq')
expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first
- assert_equal expect, result.rows.first.first
+ assert_equal expect.to_i, result.rows.first.first
end
def test_exec_insert_with_returning_disabled_and_no_sequence_name_given
connection = connection_without_insert_returning
result = connection.exec_insert("insert into postgresql_partitioned_table_parent (number) VALUES (1)", nil, [], 'id')
expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first
- assert_equal expect, result.rows.first.first
+ assert_equal expect.to_i, result.rows.first.first
end
def test_sql_for_insert_with_returning_disabled
@@ -222,8 +227,8 @@ module ActiveRecord
"DELETE FROM pg_depend WHERE objid = 'ex2_id_seq'::regclass AND refobjid = 'ex'::regclass AND deptype = 'a'"
)
ensure
- @connection.exec_query('DROP TABLE IF EXISTS ex')
- @connection.exec_query('DROP TABLE IF EXISTS ex2')
+ @connection.drop_table 'ex', if_exists: true
+ @connection.drop_table 'ex2', if_exists: true
end
def test_exec_insert_number
@@ -233,7 +238,7 @@ module ActiveRecord
result = @connection.exec_query('SELECT number FROM ex WHERE number = 10')
assert_equal 1, result.rows.length
- assert_equal "10", result.rows.last.last
+ assert_equal 10, result.rows.last.last
end
end
@@ -269,7 +274,7 @@ module ActiveRecord
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
- assert_equal [['1', 'foo']], result.rows
+ assert_equal [[1, 'foo']], result.rows
end
end
@@ -278,12 +283,12 @@ module ActiveRecord
string = @connection.quote('foo')
@connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
result = @connection.exec_query(
- 'SELECT id, data FROM ex WHERE id = $1', nil, [[nil, 1]])
+ 'SELECT id, data FROM ex WHERE id = $1', nil, [bind_param(1)])
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
- assert_equal [['1', 'foo']], result.rows
+ assert_equal [[1, 'foo']], result.rows
end
end
@@ -292,23 +297,20 @@ module ActiveRecord
string = @connection.quote('foo')
@connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
- column = @connection.columns('ex').find { |col| col.name == 'id' }
+ bind = ActiveRecord::Relation::QueryAttribute.new("id", "1-fuu", ActiveRecord::Type::Integer.new)
result = @connection.exec_query(
- 'SELECT id, data FROM ex WHERE id = $1', nil, [[column, '1-fuu']])
+ 'SELECT id, data FROM ex WHERE id = $1', nil, [bind])
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
- assert_equal [['1', 'foo']], result.rows
+ assert_equal [[1, 'foo']], result.rows
end
end
def test_substitute_at
- bind = @connection.substitute_at(nil, 0)
- assert_equal Arel.sql('$1'), bind
-
- bind = @connection.substitute_at(nil, 1)
- assert_equal Arel.sql('$2'), bind
+ bind = @connection.substitute_at(nil)
+ assert_equal Arel.sql('$1'), bind.to_sql
end
def test_partial_index
@@ -434,10 +436,10 @@ module ActiveRecord
private
def insert(ctx, data)
- binds = data.map { |name, value|
- [ctx.columns('ex').find { |x| x.name == name }, value]
+ binds = data.map { |name, value|
+ bind_param(value, name)
}
- columns = binds.map(&:first).map(&:name)
+ columns = binds.map(&:name)
bind_subs = columns.length.times.map { |x| "$#{x + 1}" }
@@ -454,6 +456,10 @@ module ActiveRecord
def connection_without_insert_returning
ActiveRecord::Base.postgresql_connection(ActiveRecord::Base.configurations['arunit'].merge(:insert_returning => false))
end
+
+ def bind_param(value, name = nil)
+ ActiveRecord::Relation::QueryAttribute.new(name, value, ActiveRecord::Type::Value.new)
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
index 11d5173d37..e4420d9d13 100644
--- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
@@ -10,63 +10,33 @@ module ActiveRecord
end
def test_type_cast_true
- c = PostgreSQLColumn.new(nil, 1, Type::Boolean.new, 'boolean')
- assert_equal 't', @conn.type_cast(true, nil)
- assert_equal 't', @conn.type_cast(true, c)
+ assert_equal 't', @conn.type_cast(true)
end
def test_type_cast_false
- c = PostgreSQLColumn.new(nil, 1, Type::Boolean.new, 'boolean')
- assert_equal 'f', @conn.type_cast(false, nil)
- assert_equal 'f', @conn.type_cast(false, c)
- end
-
- def test_type_cast_cidr
- ip = IPAddr.new('255.0.0.0/8')
- c = PostgreSQLColumn.new(nil, ip, OID::Cidr.new, 'cidr')
- assert_equal ip, @conn.type_cast(ip, c)
- end
-
- def test_type_cast_inet
- ip = IPAddr.new('255.1.0.0/8')
- c = PostgreSQLColumn.new(nil, ip, OID::Cidr.new, 'inet')
- assert_equal ip, @conn.type_cast(ip, c)
+ assert_equal 'f', @conn.type_cast(false)
end
def test_quote_float_nan
nan = 0.0/0
- c = PostgreSQLColumn.new(nil, 1, OID::Float.new, 'float')
- assert_equal "'NaN'", @conn.quote(nan, c)
+ assert_equal "'NaN'", @conn.quote(nan)
end
def test_quote_float_infinity
infinity = 1.0/0
- c = PostgreSQLColumn.new(nil, 1, OID::Float.new, 'float')
- assert_equal "'Infinity'", @conn.quote(infinity, c)
- end
-
- def test_quote_cast_numeric
- fixnum = 666
- c = PostgreSQLColumn.new(nil, nil, Type::String.new, 'varchar')
- assert_equal "'666'", @conn.quote(fixnum, c)
- c = PostgreSQLColumn.new(nil, nil, Type::Text.new, 'text')
- assert_equal "'666'", @conn.quote(fixnum, c)
- end
-
- def test_quote_time_usec
- assert_equal "'1970-01-01 00:00:00.000000'", @conn.quote(Time.at(0))
- assert_equal "'1970-01-01 00:00:00.000000'", @conn.quote(Time.at(0).to_datetime)
+ assert_equal "'Infinity'", @conn.quote(infinity)
end
def test_quote_range
range = "1,2]'; SELECT * FROM users; --".."a"
- c = PostgreSQLColumn.new(nil, nil, OID::Range.new(Type::Integer.new, :int8range))
- assert_equal "'[1,0]'", @conn.quote(range, c)
+ type = OID::Range.new(Type::Integer.new, :int8range)
+ assert_equal "'[1,0]'", @conn.quote(type.serialize(range))
end
def test_quote_bit_string
- c = PostgreSQLColumn.new(nil, 1, OID::Bit.new)
- assert_equal nil, @conn.quote("'); SELECT * FROM users; /*\n01\n*/--", c)
+ value = "'); SELECT * FROM users; /*\n01\n*/--"
+ type = OID::Bit.new
+ assert_equal nil, @conn.quote(type.serialize(value))
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb
index d812cd01c4..bbf96278b0 100644
--- a/activerecord/test/cases/adapters/postgresql/range_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/range_test.rb
@@ -7,7 +7,7 @@ if ActiveRecord::Base.connection.supports_ranges?
end
class PostgresqlRangeTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
include ConnectionHelper
def setup
@@ -91,7 +91,7 @@ _SQL
end
teardown do
- @connection.execute 'DROP TABLE IF EXISTS postgresql_ranges'
+ @connection.drop_table 'postgresql_ranges', if_exists: true
@connection.execute 'DROP TYPE IF EXISTS floatrange'
reset_connection
end
@@ -230,36 +230,14 @@ _SQL
assert_nil_round_trip(@first_range, :int8_range, 39999...39999)
end
- def test_exclude_beginning_for_subtypes_with_succ_method_is_deprecated
- tz = ::ActiveRecord::Base.default_timezone
-
- silence_warnings {
- assert_deprecated {
- range = PostgresqlRange.create!(date_range: "(''2012-01-02'', ''2012-01-04'']")
- assert_equal Date.new(2012, 1, 3)..Date.new(2012, 1, 4), range.date_range
- }
- assert_deprecated {
- range = PostgresqlRange.create!(ts_range: "(''2010-01-01 14:30'', ''2011-01-01 14:30'']")
- assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 1)..Time.send(tz, 2011, 1, 1, 14, 30, 0), range.ts_range
- }
- assert_deprecated {
- range = PostgresqlRange.create!(tstz_range: "(''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'']")
- assert_equal Time.parse('2010-01-01 09:30:01 UTC')..Time.parse('2011-01-01 17:30:00 UTC'), range.tstz_range
- }
- assert_deprecated {
- range = PostgresqlRange.create!(int4_range: "(1, 10]")
- assert_equal 2..10, range.int4_range
- }
- assert_deprecated {
- range = PostgresqlRange.create!(int8_range: "(10, 100]")
- assert_equal 11..100, range.int8_range
- }
- }
- end
-
def test_exclude_beginning_for_subtypes_without_succ_method_is_not_supported
assert_raises(ArgumentError) { PostgresqlRange.create!(num_range: "(0.1, 0.2]") }
assert_raises(ArgumentError) { PostgresqlRange.create!(float_range: "(0.5, 0.7]") }
+ assert_raises(ArgumentError) { PostgresqlRange.create!(int4_range: "(1, 10]") }
+ assert_raises(ArgumentError) { PostgresqlRange.create!(int8_range: "(10, 100]") }
+ assert_raises(ArgumentError) { PostgresqlRange.create!(date_range: "(''2012-01-02'', ''2012-01-04'']") }
+ assert_raises(ArgumentError) { PostgresqlRange.create!(ts_range: "(''2010-01-01 14:30'', ''2011-01-01 14:30'']") }
+ assert_raises(ArgumentError) { PostgresqlRange.create!(tstz_range: "(''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'']") }
end
def test_update_all_with_ranges
diff --git a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb
new file mode 100644
index 0000000000..7200ed2771
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb
@@ -0,0 +1,111 @@
+require 'cases/helper'
+require 'support/connection_helper'
+
+class PostgreSQLReferentialIntegrityTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ include ConnectionHelper
+
+ IS_REFERENTIAL_INTEGRITY_SQL = lambda do |sql|
+ sql.match(/DISABLE TRIGGER ALL/) || sql.match(/ENABLE TRIGGER ALL/)
+ end
+
+ module MissingSuperuserPrivileges
+ def execute(sql)
+ if IS_REFERENTIAL_INTEGRITY_SQL.call(sql)
+ super "BROKEN;" rescue nil # put transaction in broken state
+ raise ActiveRecord::StatementInvalid, 'PG::InsufficientPrivilege'
+ else
+ super
+ end
+ end
+ end
+
+ module ProgrammerMistake
+ def execute(sql)
+ if IS_REFERENTIAL_INTEGRITY_SQL.call(sql)
+ raise ArgumentError, 'something is not right.'
+ else
+ super
+ end
+ end
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def teardown
+ reset_connection
+ if ActiveRecord::Base.connection.is_a?(MissingSuperuserPrivileges)
+ raise "MissingSuperuserPrivileges patch was not removed"
+ end
+ end
+
+ def test_should_reraise_invalid_foreign_key_exception_and_show_warning
+ @connection.extend MissingSuperuserPrivileges
+
+ warning = capture(:stderr) do
+ e = assert_raises(ActiveRecord::InvalidForeignKey) do
+ @connection.disable_referential_integrity do
+ raise ActiveRecord::InvalidForeignKey, 'Should be re-raised'
+ end
+ end
+ assert_equal 'Should be re-raised', e.message
+ end
+ assert_match (/WARNING: Rails was not able to disable referential integrity/), warning
+ assert_match (/cause: PG::InsufficientPrivilege/), warning
+ end
+
+ def test_does_not_print_warning_if_no_invalid_foreign_key_exception_was_raised
+ @connection.extend MissingSuperuserPrivileges
+
+ warning = capture(:stderr) do
+ e = assert_raises(ActiveRecord::StatementInvalid) do
+ @connection.disable_referential_integrity do
+ raise ActiveRecord::StatementInvalid, 'Should be re-raised'
+ end
+ end
+ assert_equal 'Should be re-raised', e.message
+ end
+ assert warning.blank?, "expected no warnings but got:\n#{warning}"
+ end
+
+ def test_does_not_break_transactions
+ @connection.extend MissingSuperuserPrivileges
+
+ @connection.transaction do
+ @connection.disable_referential_integrity do
+ assert_transaction_is_not_broken
+ end
+ assert_transaction_is_not_broken
+ end
+ end
+
+ def test_does_not_break_nested_transactions
+ @connection.extend MissingSuperuserPrivileges
+
+ @connection.transaction do
+ @connection.transaction(requires_new: true) do
+ @connection.disable_referential_integrity do
+ assert_transaction_is_not_broken
+ end
+ end
+ assert_transaction_is_not_broken
+ end
+ end
+
+ def test_only_catch_active_record_errors_others_bubble_up
+ @connection.extend ProgrammerMistake
+
+ assert_raises ArgumentError do
+ @connection.disable_referential_integrity {}
+ end
+ end
+
+ private
+
+ def assert_transaction_is_not_broken
+ assert_equal 1, @connection.select_value("SELECT 1")
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/rename_table_test.rb b/activerecord/test/cases/adapters/postgresql/rename_table_test.rb
new file mode 100644
index 0000000000..f507328868
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/rename_table_test.rb
@@ -0,0 +1,34 @@
+require "cases/helper"
+
+class PostgresqlRenameTableTest < ActiveRecord::TestCase
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table :before_rename, force: true
+ end
+
+ def teardown
+ @connection.drop_table "before_rename", if_exists: true
+ @connection.drop_table "after_rename", if_exists: true
+ end
+
+ test "renaming a table also renames the primary key index" do
+ # sanity check
+ assert_equal 1, num_indices_named("before_rename_pkey")
+ assert_equal 0, num_indices_named("after_rename_pkey")
+
+ @connection.rename_table :before_rename, :after_rename
+
+ assert_equal 0, num_indices_named("before_rename_pkey")
+ assert_equal 1, num_indices_named("after_rename_pkey")
+ end
+
+ private
+
+ def num_indices_named(name)
+ @connection.execute(<<-SQL).values.length
+ SELECT 1 FROM "pg_index"
+ JOIN "pg_class" ON "pg_index"."indexrelid" = "pg_class"."oid"
+ WHERE "pg_class"."relname" = '#{name}'
+ SQL
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
index 99c26c4bf7..359a45bbd1 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
@@ -4,7 +4,7 @@ class SchemaThing < ActiveRecord::Base
end
class SchemaAuthorizationTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
TABLE_NAME = 'schema_things'
COLUMNS = [
@@ -55,7 +55,7 @@ class SchemaAuthorizationTest < ActiveRecord::TestCase
set_session_auth
USERS.each do |u|
set_session_auth u
- assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [[nil, 1]]).first['name']
+ assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [bind_param(1)]).first['name']
set_session_auth
end
end
@@ -67,7 +67,7 @@ class SchemaAuthorizationTest < ActiveRecord::TestCase
USERS.each do |u|
@connection.clear_cache!
set_session_auth u
- assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [[nil, 1]]).first['name']
+ assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [bind_param(1)]).first['name']
set_session_auth
end
end
@@ -111,4 +111,7 @@ class SchemaAuthorizationTest < ActiveRecord::TestCase
@connection.session_auth = auth || 'default'
end
+ def bind_param(value)
+ ActiveRecord::Relation::QueryAttribute.new(nil, value, ActiveRecord::Type::Value.new)
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index 350cb3e065..f925dcad97 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -1,8 +1,9 @@
require "cases/helper"
+require 'models/default'
require 'support/schema_dumping_helper'
class SchemaTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
SCHEMA_NAME = 'test_schema'
SCHEMA2_NAME = 'test_schema2'
@@ -88,7 +89,7 @@ class SchemaTest < ActiveRecord::TestCase
end
def test_schema_names
- assert_equal ["public", "schema_1", "test_schema", "test_schema2"], @connection.schema_names
+ assert_equal ["public", "test_schema", "test_schema2"], @connection.schema_names
end
def test_create_schema
@@ -150,10 +151,10 @@ class SchemaTest < ActiveRecord::TestCase
def test_schema_change_with_prepared_stmt
altered = false
- @connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]]
+ @connection.exec_query "select * from developers where id = $1", 'sql', [bind_param(1)]
@connection.exec_query "alter table developers add column zomg int", 'sql', []
altered = true
- @connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]]
+ @connection.exec_query "select * from developers where id = $1", 'sql', [bind_param(1)]
ensure
# We are not using DROP COLUMN IF EXISTS because that syntax is only
# supported by pg 9.X
@@ -383,16 +384,16 @@ class SchemaTest < ActiveRecord::TestCase
def test_reset_pk_sequence
sequence_name = "#{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}"
@connection.execute "SELECT setval('#{sequence_name}', 123)"
- assert_equal "124", @connection.select_value("SELECT nextval('#{sequence_name}')")
+ assert_equal 124, @connection.select_value("SELECT nextval('#{sequence_name}')")
@connection.reset_pk_sequence!("#{SCHEMA_NAME}.#{UNMATCHED_PK_TABLE_NAME}")
- assert_equal "1", @connection.select_value("SELECT nextval('#{sequence_name}')")
+ assert_equal 1, @connection.select_value("SELECT nextval('#{sequence_name}')")
end
def test_set_pk_sequence
table_name = "#{SCHEMA_NAME}.#{PK_TABLE_NAME}"
_, sequence_name = @connection.pk_and_sequence_for table_name
@connection.set_pk_sequence! table_name, 123
- assert_equal "124", @connection.select_value("SELECT nextval('#{sequence_name}')")
+ assert_equal 124, @connection.select_value("SELECT nextval('#{sequence_name}')")
@connection.reset_pk_sequence! table_name
end
@@ -412,7 +413,7 @@ class SchemaTest < ActiveRecord::TestCase
def do_dump_index_tests_for_schema(this_schema_name, first_index_column_name, second_index_column_name, third_index_column_name, fourth_index_column_name)
with_schema_search_path(this_schema_name) do
- indexes = @connection.indexes(TABLE_NAME).sort_by {|i| i.name}
+ indexes = @connection.indexes(TABLE_NAME).sort_by(&:name)
assert_equal 4,indexes.size
do_dump_index_assertions_for_one_index(indexes[0], INDEX_A_NAME, first_index_column_name)
@@ -434,6 +435,10 @@ class SchemaTest < ActiveRecord::TestCase
assert_equal this_index_column, this_index.columns[0]
assert_equal this_index_name, this_index.name
end
+
+ def bind_param(value)
+ ActiveRecord::Relation::QueryAttribute.new(nil, value, ActiveRecord::Type::Value.new)
+ end
end
class SchemaForeignKeyTest < ActiveRecord::TestCase
@@ -453,10 +458,59 @@ class SchemaForeignKeyTest < ActiveRecord::TestCase
end
@connection.add_foreign_key "wagons", "my_schema.trains", column: "train_id"
output = dump_table_schema "wagons"
- assert_match %r{\s+add_foreign_key "wagons", "my_schema.trains", column: "train_id"$}, output
+ assert_match %r{\s+add_foreign_key "wagons", "my_schema\.trains", column: "train_id"$}, output
ensure
- @connection.execute "DROP TABLE IF EXISTS wagons"
- @connection.execute "DROP TABLE IF EXISTS my_schema.trains"
+ @connection.drop_table "wagons", if_exists: true
+ @connection.drop_table "my_schema.trains", if_exists: true
@connection.execute "DROP SCHEMA IF EXISTS my_schema"
end
end
+
+class DefaultsUsingMultipleSchemasAndDomainTest < ActiveSupport::TestCase
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.execute "DROP SCHEMA IF EXISTS schema_1 CASCADE"
+ @connection.execute "CREATE SCHEMA schema_1"
+ @connection.execute "CREATE DOMAIN schema_1.text AS text"
+ @connection.execute "CREATE DOMAIN schema_1.varchar AS varchar"
+ @connection.execute "CREATE DOMAIN schema_1.bpchar AS bpchar"
+
+ @old_search_path = @connection.schema_search_path
+ @connection.schema_search_path = "schema_1, pg_catalog"
+ @connection.create_table "defaults" do |t|
+ t.text "text_col", default: "some value"
+ t.string "string_col", default: "some value"
+ end
+ Default.reset_column_information
+ end
+
+ teardown do
+ @connection.schema_search_path = @old_search_path
+ @connection.execute "DROP SCHEMA IF EXISTS schema_1 CASCADE"
+ Default.reset_column_information
+ end
+
+ def test_text_defaults_in_new_schema_when_overriding_domain
+ assert_equal "some value", Default.new.text_col, "Default of text column was not correctly parsed"
+ end
+
+ def test_string_defaults_in_new_schema_when_overriding_domain
+ assert_equal "some value", Default.new.string_col, "Default of string 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
+ assert_equal "some value", Default.new.bpchar_col, "Default of bpchar column was not correctly parsed"
+ end
+
+ def test_text_defaults_after_updating_column_default
+ @connection.execute "ALTER TABLE defaults ALTER COLUMN text_col SET DEFAULT 'some text'::schema_1.text"
+ assert_equal "some text", Default.new.text_col, "Default of text column was not correctly parsed after updating default using '::text' since postgreSQL will add parens to the default in db"
+ end
+
+ def test_default_containing_quote_and_colons
+ @connection.execute "ALTER TABLE defaults ALTER COLUMN string_col SET DEFAULT 'foo''::bar'"
+ assert_equal "foo'::bar", Default.new.string_col
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/serial_test.rb b/activerecord/test/cases/adapters/postgresql/serial_test.rb
new file mode 100644
index 0000000000..458a8dae6c
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/serial_test.rb
@@ -0,0 +1,60 @@
+require "cases/helper"
+require 'support/schema_dumping_helper'
+
+class PostgresqlSerialTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
+ class PostgresqlSerial < ActiveRecord::Base; end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table "postgresql_serials", force: true do |t|
+ t.serial :seq
+ end
+ end
+
+ teardown do
+ @connection.drop_table "postgresql_serials", if_exists: true
+ end
+
+ def test_serial_column
+ column = PostgresqlSerial.columns_hash["seq"]
+ assert_equal :integer, column.type
+ assert_equal "integer", column.sql_type
+ assert column.serial?
+ end
+
+ def test_schema_dump_with_shorthand
+ output = dump_table_schema "postgresql_serials"
+ assert_match %r{t\.serial\s+"seq"}, output
+ end
+end
+
+class PostgresqlBigSerialTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
+ class PostgresqlBigSerial < ActiveRecord::Base; end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table "postgresql_big_serials", force: true do |t|
+ t.bigserial :seq
+ end
+ end
+
+ teardown do
+ @connection.drop_table "postgresql_big_serials", if_exists: true
+ end
+
+ def test_bigserial_column
+ column = PostgresqlBigSerial.columns_hash["seq"]
+ assert_equal :integer, column.type
+ assert_equal "bigint", column.sql_type
+ assert column.serial?
+ end
+
+ def test_schema_dump_with_shorthand
+ output = dump_table_schema "postgresql_big_serials"
+ assert_match %r{t\.bigserial\s+"seq"}, output
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/sql_types_test.rb b/activerecord/test/cases/adapters/postgresql/sql_types_test.rb
deleted file mode 100644
index d7d40f6385..0000000000
--- a/activerecord/test/cases/adapters/postgresql/sql_types_test.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-require "cases/helper"
-
-class SqlTypesTest < ActiveRecord::TestCase
- def test_binary_types
- assert_equal 'bytea', type_to_sql(:binary, 100_000)
- assert_raise ActiveRecord::ActiveRecordError do
- type_to_sql :binary, 4294967295
- end
- assert_equal 'text', type_to_sql(:text, 100_000)
- assert_raise ActiveRecord::ActiveRecordError do
- type_to_sql :text, 4294967295
- end
- end
-
- def type_to_sql(*args)
- ActiveRecord::Base.connection.type_to_sql(*args)
- end
-end
diff --git a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb
index eb32c4d2c2..a639f98272 100644
--- a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb
@@ -5,7 +5,7 @@ require 'models/topic'
class PostgresqlTimestampTest < ActiveRecord::TestCase
class PostgresqlTimestampWithZone < ActiveRecord::Base; end
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
setup do
@connection = ActiveRecord::Base.connection
@@ -70,53 +70,6 @@ class TimestampTest < ActiveRecord::TestCase
assert_equal(-1.0 / 0.0, d.updated_at)
end
- def test_default_datetime_precision
- ActiveRecord::Base.connection.create_table(:foos)
- ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime
- ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime
- assert_nil activerecord_column_option('foos', 'created_at', 'precision')
- end
-
- def test_timestamp_data_type_with_precision
- ActiveRecord::Base.connection.create_table(:foos)
- ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime, :precision => 0
- ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime, :precision => 5
- assert_equal 0, activerecord_column_option('foos', 'created_at', 'precision')
- assert_equal 5, activerecord_column_option('foos', 'updated_at', 'precision')
- end
-
- def test_timestamps_helper_with_custom_precision
- ActiveRecord::Base.connection.create_table(:foos) do |t|
- t.timestamps :null => true, :precision => 4
- end
- assert_equal 4, activerecord_column_option('foos', 'created_at', 'precision')
- assert_equal 4, activerecord_column_option('foos', 'updated_at', 'precision')
- end
-
- def test_passing_precision_to_timestamp_does_not_set_limit
- ActiveRecord::Base.connection.create_table(:foos) do |t|
- t.timestamps :null => true, :precision => 4
- end
- assert_nil activerecord_column_option("foos", "created_at", "limit")
- assert_nil activerecord_column_option("foos", "updated_at", "limit")
- end
-
- def test_invalid_timestamp_precision_raises_error
- assert_raises ActiveRecord::ActiveRecordError do
- ActiveRecord::Base.connection.create_table(:foos) do |t|
- t.timestamps :null => true, :precision => 7
- end
- end
- end
-
- def test_postgres_agrees_with_activerecord_about_precision
- ActiveRecord::Base.connection.create_table(:foos) do |t|
- t.timestamps :null => true, :precision => 4
- end
- assert_equal '4', pg_datetime_precision('foos', 'created_at')
- assert_equal '4', pg_datetime_precision('foos', 'updated_at')
- end
-
def test_bc_timestamp
date = Date.new(0) - 1.week
Developer.create!(:name => "aaron", :updated_at => date)
@@ -134,21 +87,4 @@ class TimestampTest < ActiveRecord::TestCase
Developer.create!(:name => "yahagi", :updated_at => date)
assert_equal date, Developer.find_by_name("yahagi").updated_at
end
-
- private
-
- def pg_datetime_precision(table_name, column_name)
- results = ActiveRecord::Base.connection.execute("SELECT column_name, datetime_precision FROM information_schema.columns WHERE table_name ='#{table_name}'")
- result = results.find do |result_hash|
- result_hash["column_name"] == column_name
- end
- result && result["datetime_precision"]
- end
-
- def activerecord_column_option(tablename, column_name, option)
- result = ActiveRecord::Base.connection.columns(tablename).find do |column|
- column.name == column_name
- end
- result && result.send(option)
- end
end
diff --git a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
index 23817198b1..c0907b8f21 100644
--- a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
@@ -12,4 +12,22 @@ class PostgresqlTypeLookupTest < ActiveRecord::TestCase
assert_equal ';', box_array.delimiter
assert_equal ',', int_array.delimiter
end
+
+ test "array types correctly respect registration of subtypes" do
+ int_array = @connection.type_map.lookup(1007, -1, "integer[]")
+ bigint_array = @connection.type_map.lookup(1016, -1, "bigint[]")
+ big_array = [123456789123456789]
+
+ assert_raises(RangeError) { int_array.serialize(big_array) }
+ assert_equal "{123456789123456789}", bigint_array.serialize(big_array)
+ end
+
+ test "range types correctly respect registration of subtypes" do
+ int_range = @connection.type_map.lookup(3904, -1, "int4range")
+ bigint_range = @connection.type_map.lookup(3926, -1, "int8range")
+ big_range = 0..123456789123456789
+
+ assert_raises(RangeError) { int_range.serialize(big_range) }
+ assert_equal "[0,123456789123456789]", bigint_range.serialize(big_range)
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index 376dc2490e..e9379a1019 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -1,8 +1,5 @@
-# encoding: utf-8
-
require "cases/helper"
-require 'active_record/base'
-require 'active_record/connection_adapters/postgresql_adapter'
+require 'support/schema_dumping_helper'
module PostgresqlUUIDHelper
def connection
@@ -10,12 +7,13 @@ module PostgresqlUUIDHelper
end
def drop_table(name)
- connection.execute "drop table if exists #{name}"
+ connection.drop_table name, if_exists: true
end
end
class PostgresqlUUIDTest < ActiveRecord::TestCase
include PostgresqlUUIDHelper
+ include SchemaDumpingHelper
class UUIDType < ActiveRecord::Base
self.table_name = "uuid_data_type"
@@ -50,9 +48,10 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase
column = UUIDType.columns_hash["guid"]
assert_equal :uuid, column.type
assert_equal "uuid", column.sql_type
- assert_not column.number?
- assert_not column.binary?
- assert_not column.array
+ assert_not column.array?
+
+ type = UUIDType.type_for_attribute("guid")
+ assert_not type.binary?
end
def test_treat_blank_uuid_as_nil
@@ -70,13 +69,18 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase
assert_equal 'foobar', uuid.guid_before_type_cast
end
- def test_rfc_4122_regex
+ def test_acceptable_uuid_regex
# Valid uuids
['A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11',
'{a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}',
'a0eebc999c0b4ef8bb6d6bb9bd380a11',
'a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11',
- '{a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11}'].each do |valid_uuid|
+ '{a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11}',
+ # The following is not a valid RFC 4122 UUID, but PG doesn't seem to care,
+ # so we shouldn't block it either. (Pay attention to "fb6d" – the "f" here
+ # is invalid – it must be one of 8, 9, A, B, a, b according to the spec.)
+ '{a0eebc99-9c0b-4ef8-fb6d-6bb9bd380a11}',
+ ].each do |valid_uuid|
uuid = UUIDType.new guid: valid_uuid
assert_not_nil uuid.guid
end
@@ -88,7 +92,6 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase
0.0,
true,
'Z0000C99-9C0B-4EF8-BB6D-6BB9BD380A11',
- '{a0eebc99-9c0b-4ef8-fb6d-6bb9bd380a11}',
'a0eebc999r0b4ef8ab6d6bb9bd380a11',
'a0ee-bc99------4ef8-bb6d-6bb9-bd38-0a11',
'{a0eebc99-bb6d6bb9-bd380a11}'].each do |invalid_uuid|
@@ -108,30 +111,33 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase
assert_equal "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", uuid.guid
end
end
-end
-class PostgresqlLargeKeysTest < ActiveRecord::TestCase
- include PostgresqlUUIDHelper
- def setup
- connection.create_table('big_serials', id: :bigserial) do |t|
- t.string 'name'
- end
+ def test_schema_dump_with_shorthand
+ output = dump_table_schema "uuid_data_type"
+ assert_match %r{t\.uuid "guid"}, output
end
- def test_omg
- schema = StringIO.new
- ActiveRecord::SchemaDumper.dump(connection, schema)
- assert_match "create_table \"big_serials\", id: :bigserial, force: true",
- schema.string
- end
+ def test_uniqueness_validation_ignores_uuid
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "uuid_data_type"
+ validates :guid, uniqueness: { case_sensitive: false }
+
+ def self.name
+ "UUIDType"
+ end
+ end
- def teardown
- drop_table "big_serials"
+ record = klass.create!(guid: "a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11")
+ duplicate = klass.new(guid: record.guid)
+
+ assert record.guid.present? # Ensure we actually are testing a UUID
+ assert_not duplicate.valid?
end
end
class PostgresqlUUIDGenerationTest < ActiveRecord::TestCase
include PostgresqlUUIDHelper
+ include SchemaDumpingHelper
class UUID < ActiveRecord::Base
self.table_name = 'pg_uuids'
@@ -191,23 +197,22 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::TestCase
end
def test_schema_dumper_for_uuid_primary_key
- schema = StringIO.new
- ActiveRecord::SchemaDumper.dump(connection, schema)
- assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: "uuid_generate_v1\(\)"/, schema.string)
- assert_match(/t\.uuid "other_uuid", default: "uuid_generate_v4\(\)"/, schema.string)
+ schema = dump_table_schema "pg_uuids"
+ assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: "uuid_generate_v1\(\)"/, schema)
+ assert_match(/t\.uuid "other_uuid", default: "uuid_generate_v4\(\)"/, schema)
end
def test_schema_dumper_for_uuid_primary_key_with_custom_default
- schema = StringIO.new
- ActiveRecord::SchemaDumper.dump(connection, schema)
- assert_match(/\bcreate_table "pg_uuids_2", id: :uuid, default: "my_uuid_generator\(\)"/, schema.string)
- assert_match(/t\.uuid "other_uuid_2", default: "my_uuid_generator\(\)"/, schema.string)
+ schema = dump_table_schema "pg_uuids_2"
+ assert_match(/\bcreate_table "pg_uuids_2", id: :uuid, default: "my_uuid_generator\(\)"/, schema)
+ assert_match(/t\.uuid "other_uuid_2", default: "my_uuid_generator\(\)"/, schema)
end
end
end
class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase
include PostgresqlUUIDHelper
+ include SchemaDumpingHelper
setup do
enable_extension!('uuid-ossp', connection)
@@ -231,6 +236,11 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase
WHERE a.attname='id' AND a.attrelid = 'pg_uuids'::regclass").first
assert_nil col_desc["default"]
end
+
+ def test_schema_dumper_for_uuid_primary_key_with_default_override_via_nil
+ schema = dump_table_schema "pg_uuids"
+ assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: nil/, schema)
+ end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/xml_test.rb b/activerecord/test/cases/adapters/postgresql/xml_test.rb
index 4165dd5ac9..b097deb2f4 100644
--- a/activerecord/test/cases/adapters/postgresql/xml_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/xml_test.rb
@@ -1,7 +1,8 @@
-# encoding: utf-8
require 'cases/helper'
+require 'support/schema_dumping_helper'
class PostgresqlXMLTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
class XmlDataType < ActiveRecord::Base
self.table_name = 'xml_data_type'
end
@@ -21,7 +22,7 @@ class PostgresqlXMLTest < ActiveRecord::TestCase
end
teardown do
- @connection.execute 'drop table if exists xml_data_type'
+ @connection.drop_table 'xml_data_type', if_exists: true
end
def test_column
@@ -45,4 +46,9 @@ class PostgresqlXMLTest < ActiveRecord::TestCase
XmlDataType.update_all(payload: "<bar>baz</bar>")
assert_equal "<bar>baz</bar>", data.reload.payload
end
+
+ def test_schema_dump_with_shorthand
+ output = dump_table_schema("xml_data_type")
+ assert_match %r{t\.xml "payload"}, output
+ end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/explain_test.rb b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
index f1d6119d2e..7d66c44798 100644
--- a/activerecord/test/cases/adapters/sqlite3/explain_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
@@ -1,5 +1,6 @@
require "cases/helper"
require 'models/developer'
+require 'models/computer'
module ActiveRecord
module ConnectionAdapters
@@ -17,7 +18,7 @@ module ActiveRecord
explain = Developer.where(:id => 1).includes(:audit_logs).explain
assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = ?), explain
assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
- assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain
+ assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain
assert_match(/(SCAN )?TABLE audit_logs/, explain)
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
index ac8332e2fa..243f65df98 100644
--- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
@@ -15,73 +15,52 @@ module ActiveRecord
def test_type_cast_binary_encoding_without_logger
@conn.extend(Module.new { def logger; end })
- column = Column.new(nil, nil, Type::String.new)
binary = SecureRandom.hex
expected = binary.dup.encode!(Encoding::UTF_8)
- assert_equal expected, @conn.type_cast(binary, column)
+ assert_equal expected, @conn.type_cast(binary)
end
def test_type_cast_symbol
- assert_equal 'foo', @conn.type_cast(:foo, nil)
+ assert_equal 'foo', @conn.type_cast(:foo)
end
def test_type_cast_date
date = Date.today
expected = @conn.quoted_date(date)
- assert_equal expected, @conn.type_cast(date, nil)
+ assert_equal expected, @conn.type_cast(date)
end
def test_type_cast_time
time = Time.now
expected = @conn.quoted_date(time)
- assert_equal expected, @conn.type_cast(time, nil)
+ assert_equal expected, @conn.type_cast(time)
end
def test_type_cast_numeric
- assert_equal 10, @conn.type_cast(10, nil)
- assert_equal 2.2, @conn.type_cast(2.2, nil)
+ assert_equal 10, @conn.type_cast(10)
+ assert_equal 2.2, @conn.type_cast(2.2)
end
def test_type_cast_nil
- assert_equal nil, @conn.type_cast(nil, nil)
+ assert_equal nil, @conn.type_cast(nil)
end
def test_type_cast_true
- c = Column.new(nil, 1, Type::Integer.new)
- assert_equal 't', @conn.type_cast(true, nil)
- assert_equal 1, @conn.type_cast(true, c)
+ assert_equal 't', @conn.type_cast(true)
end
def test_type_cast_false
- c = Column.new(nil, 1, Type::Integer.new)
- assert_equal 'f', @conn.type_cast(false, nil)
- assert_equal 0, @conn.type_cast(false, c)
- end
-
- def test_type_cast_string
- assert_equal '10', @conn.type_cast('10', nil)
-
- c = Column.new(nil, 1, Type::Integer.new)
- assert_equal 10, @conn.type_cast('10', c)
-
- c = Column.new(nil, 1, Type::Float.new)
- assert_equal 10.1, @conn.type_cast('10.1', c)
-
- c = Column.new(nil, 1, Type::Binary.new)
- assert_equal '10.1', @conn.type_cast('10.1', c)
-
- c = Column.new(nil, 1, Type::Date.new)
- assert_equal '10.1', @conn.type_cast('10.1', c)
+ assert_equal 'f', @conn.type_cast(false)
end
def test_type_cast_bigdecimal
bd = BigDecimal.new '10.0'
- assert_equal bd.to_f, @conn.type_cast(bd, nil)
+ assert_equal bd.to_f, @conn.type_cast(bd)
end
def test_type_cast_unknown_should_raise_error
obj = Class.new.new
- assert_raise(TypeError) { @conn.type_cast(obj, nil) }
+ assert_raise(TypeError) { @conn.type_cast(obj) }
end
def test_type_cast_object_which_responds_to_quoted_id
@@ -94,21 +73,21 @@ module ActiveRecord
10
end
}.new
- assert_equal 10, @conn.type_cast(quoted_id_obj, nil)
+ assert_equal 10, @conn.type_cast(quoted_id_obj)
quoted_id_obj = Class.new {
def quoted_id
"'zomg'"
end
}.new
- assert_raise(TypeError) { @conn.type_cast(quoted_id_obj, nil) }
+ assert_raise(TypeError) { @conn.type_cast(quoted_id_obj) }
end
def test_quoting_binary_strings
value = "hello".encode('ascii-8bit')
- column = Column.new(nil, 1, SQLite3String.new)
+ type = Type::String.new
- assert_equal "'hello'", @conn.quote(value, column)
+ assert_equal "'hello'", @conn.quote(type.serialize(value))
end
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 8c1c22d3bf..27f4ba8eb6 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require "cases/helper"
require 'models/owner'
require 'tempfile'
@@ -9,7 +8,7 @@ module ActiveRecord
class SQLite3AdapterTest < ActiveRecord::TestCase
include DdlHelper
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
class DualEncoding < ActiveRecord::Base
end
@@ -23,7 +22,7 @@ module ActiveRecord
def test_bad_connection
assert_raise ActiveRecord::NoDatabaseError do
connection = ActiveRecord::Base.sqlite3_connection(adapter: "sqlite3", database: "/tmp/should/_not/_exist/-cinco-dog.db")
- connection.exec_query('drop table if exists ex')
+ connection.drop_table 'ex', if_exists: true
end
end
@@ -83,8 +82,7 @@ module ActiveRecord
def test_exec_insert
with_example_table do
- column = @conn.columns('ex').find { |col| col.name == 'number' }
- vals = [[column, 10]]
+ vals = [Relation::QueryAttribute.new("number", 10, Type::Value.new)]
@conn.exec_insert('insert into ex (number) VALUES (?)', 'SQL', vals)
result = @conn.exec_query(
@@ -133,8 +131,8 @@ module ActiveRecord
end
def test_bind_value_substitute
- bind_param = @conn.substitute_at('foo', 0)
- assert_equal Arel.sql('?'), bind_param
+ bind_param = @conn.substitute_at('foo')
+ assert_equal Arel.sql('?'), bind_param.to_sql
end
def test_exec_no_binds
@@ -157,7 +155,7 @@ module ActiveRecord
with_example_table 'id int, data string' do
@conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
result = @conn.exec_query(
- 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]])
+ 'SELECT id, data FROM ex WHERE id = ?', nil, [Relation::QueryAttribute.new(nil, 1, Type::Value.new)])
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
@@ -169,10 +167,9 @@ module ActiveRecord
def test_exec_query_typecasts_bind_vals
with_example_table 'id int, data string' do
@conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
- column = @conn.columns('ex').find { |col| col.name == 'id' }
result = @conn.exec_query(
- 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']])
+ 'SELECT id, data FROM ex WHERE id = ?', nil, [Relation::QueryAttribute.new("id", "1-fuu", Type::Integer.new)])
assert_equal 1, result.rows.length
assert_equal 2, result.columns.length
@@ -194,7 +191,7 @@ module ActiveRecord
binary.save!
assert_equal str, binary.data
ensure
- DualEncoding.connection.execute('DROP TABLE IF EXISTS dual_encodings')
+ DualEncoding.connection.drop_table 'dual_encodings', if_exists: true
end
def test_type_cast_should_not_mutate_encoding
@@ -327,11 +324,11 @@ module ActiveRecord
def test_columns
with_example_table do
- columns = @conn.columns('ex').sort_by { |x| x.name }
+ columns = @conn.columns('ex').sort_by(&:name)
assert_equal 2, columns.length
- assert_equal %w{ id number }.sort, columns.map { |x| x.name }
- assert_equal [nil, nil], columns.map { |x| x.default }
- assert_equal [true, true], columns.map { |x| x.null }
+ assert_equal %w{ id number }.sort, columns.map(&:name)
+ assert_equal [nil, nil], columns.map(&:default)
+ assert_equal [true, true], columns.map(&:null)
end
end
@@ -405,6 +402,12 @@ module ActiveRecord
end
end
+ def test_composite_primary_key
+ with_example_table 'id integer, number integer, foo integer, PRIMARY KEY (id, number)' do
+ assert_nil @conn.primary_key('ex')
+ end
+ end
+
def test_supports_extensions
assert_not @conn.supports_extensions?, 'does not support extensions'
end
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 f545fc2011..deedf67c8e 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require "cases/helper"
require 'models/owner'
diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb
index b6333240a8..9d5327bf35 100644
--- a/activerecord/test/cases/ar_schema_test.rb
+++ b/activerecord/test/cases/ar_schema_test.rb
@@ -3,7 +3,7 @@ require "cases/helper"
if ActiveRecord::Base.connection.supports_migrations?
class ActiveRecordSchemaTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
setup do
@original_verbose = ActiveRecord::Migration.verbose
@@ -93,60 +93,38 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_equal "20131219224947", ActiveRecord::SchemaMigration.normalize_migration_number("20131219224947")
end
- def test_timestamps_without_null_is_deprecated_on_create_table
- assert_deprecated do
- ActiveRecord::Schema.define do
- create_table :has_timestamps do |t|
- t.timestamps
- end
+ def test_timestamps_without_null_set_null_to_false_on_create_table
+ ActiveRecord::Schema.define do
+ create_table :has_timestamps do |t|
+ t.timestamps
end
end
- end
- def test_timestamps_without_null_is_deprecated_on_change_table
- assert_deprecated do
- ActiveRecord::Schema.define do
- create_table :has_timestamps
-
- change_table :has_timestamps do |t|
- t.timestamps
- end
- end
- end
+ assert !@connection.columns(:has_timestamps).find { |c| c.name == 'created_at' }.null
+ assert !@connection.columns(:has_timestamps).find { |c| c.name == 'updated_at' }.null
end
- def test_no_deprecation_warning_from_timestamps_on_create_table
- assert_not_deprecated do
- ActiveRecord::Schema.define do
- create_table :has_timestamps do |t|
- t.timestamps null: true
- end
-
- drop_table :has_timestamps
+ def test_timestamps_without_null_set_null_to_false_on_change_table
+ ActiveRecord::Schema.define do
+ create_table :has_timestamps
- create_table :has_timestamps do |t|
- t.timestamps null: false
- end
+ change_table :has_timestamps do |t|
+ t.timestamps default: Time.now
end
end
- end
-
- def test_no_deprecation_warning_from_timestamps_on_change_table
- assert_not_deprecated do
- ActiveRecord::Schema.define do
- create_table :has_timestamps
- change_table :has_timestamps do |t|
- t.timestamps null: true
- end
- drop_table :has_timestamps
+ assert !@connection.columns(:has_timestamps).find { |c| c.name == 'created_at' }.null
+ assert !@connection.columns(:has_timestamps).find { |c| c.name == 'updated_at' }.null
+ end
- create_table :has_timestamps
- change_table :has_timestamps do |t|
- t.timestamps null: false, default: Time.now
- end
- end
+ def test_timestamps_without_null_set_null_to_false_on_add_timestamps
+ ActiveRecord::Schema.define do
+ create_table :has_timestamps
+ add_timestamps :has_timestamps, default: Time.now
end
+
+ assert !@connection.columns(:has_timestamps).find { |c| c.name == 'created_at' }.null
+ assert !@connection.columns(:has_timestamps).find { |c| c.name == 'updated_at' }.null
end
end
end
diff --git a/activerecord/test/cases/associations/association_scope_test.rb b/activerecord/test/cases/associations/association_scope_test.rb
index 3e0032ec73..472e270f8c 100644
--- a/activerecord/test/cases/associations/association_scope_test.rb
+++ b/activerecord/test/cases/associations/association_scope_test.rb
@@ -8,12 +8,7 @@ module ActiveRecord
test 'does not duplicate conditions' do
scope = AssociationScope.scope(Author.new.association(:welcome_posts),
Author.connection)
- wheres = scope.where_values.map(&:right)
- binds = scope.bind_values.map(&:last)
- wheres = scope.where_values.map(&:right).reject { |node|
- Arel::Nodes::BindParam === node
- }
- assert_equal wheres.uniq, wheres
+ binds = scope.where_clause.binds.map(&:value)
assert_equal binds.uniq, binds
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 25555bd75c..95d00ab3a9 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -1,5 +1,6 @@
require 'cases/helper'
require 'models/developer'
+require 'models/computer'
require 'models/project'
require 'models/company'
require 'models/topic'
@@ -57,6 +58,85 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_optional_relation
+ original_value = ActiveRecord::Base.belongs_to_required_by_default
+ ActiveRecord::Base.belongs_to_required_by_default = true
+
+ model = Class.new(ActiveRecord::Base) do
+ self.table_name = "accounts"
+ def self.name; "Temp"; end
+ belongs_to :company, optional: true
+ end
+
+ account = model.new
+ assert account.valid?
+ ensure
+ ActiveRecord::Base.belongs_to_required_by_default = original_value
+ end
+
+ def test_not_optional_relation
+ original_value = ActiveRecord::Base.belongs_to_required_by_default
+ ActiveRecord::Base.belongs_to_required_by_default = true
+
+ model = Class.new(ActiveRecord::Base) do
+ self.table_name = "accounts"
+ def self.name; "Temp"; end
+ belongs_to :company, optional: false
+ end
+
+ account = model.new
+ refute account.valid?
+ assert_equal [{error: :blank}], account.errors.details[:company]
+ ensure
+ ActiveRecord::Base.belongs_to_required_by_default = original_value
+ end
+
+ def test_required_belongs_to_config
+ original_value = ActiveRecord::Base.belongs_to_required_by_default
+ ActiveRecord::Base.belongs_to_required_by_default = true
+
+ model = Class.new(ActiveRecord::Base) do
+ self.table_name = "accounts"
+ def self.name; "Temp"; end
+ belongs_to :company
+ end
+
+ account = model.new
+ refute account.valid?
+ assert_equal [{error: :blank}], account.errors.details[:company]
+ ensure
+ ActiveRecord::Base.belongs_to_required_by_default = original_value
+ end
+
+ def test_default_scope_on_relations_is_not_cached
+ counter = 0
+
+ comments = Class.new(ActiveRecord::Base) {
+ self.table_name = 'comments'
+ self.inheritance_column = 'not_there'
+
+ posts = Class.new(ActiveRecord::Base) {
+ self.table_name = 'posts'
+ self.inheritance_column = 'not_there'
+
+ default_scope -> {
+ counter += 1
+ where("id = :inc", :inc => counter)
+ }
+
+ has_many :comments, :class => comments
+ }
+ belongs_to :post, :class => posts, :inverse_of => false
+ }
+
+ assert_equal 0, counter
+ comment = comments.first
+ assert_equal 0, counter
+ sql = capture_sql { comment.post }
+ comment.reload
+ assert_not_equal sql, capture_sql { comment.post }
+ end
+
def test_proxy_assignment
account = Account.find(1)
assert_nothing_raised { account.firm = account.firm }
@@ -92,14 +172,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
Firm.create("name" => "Apple")
Client.create("name" => "Citibank", :firm_name => "Apple")
citibank_result = Client.all.merge!(:where => {:name => "Citibank"}, :includes => :firm_with_primary_key).first
- assert citibank_result.association_cache.key?(:firm_with_primary_key)
+ assert citibank_result.association(:firm_with_primary_key).loaded?
end
def test_eager_loading_with_primary_key_as_symbol
Firm.create("name" => "Apple")
Client.create("name" => "Citibank", :firm_name => "Apple")
citibank_result = Client.all.merge!(:where => {:name => "Citibank"}, :includes => :firm_with_primary_key_symbols).first
- assert citibank_result.association_cache.key?(:firm_with_primary_key_symbols)
+ assert citibank_result.association(:firm_with_primary_key_symbols).loaded?
end
def test_creating_the_belonging_object
@@ -342,6 +422,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")
diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb
index 5b7e462f64..a531e0e02c 100644
--- a/activerecord/test/cases/associations/callbacks_test.rb
+++ b/activerecord/test/cases/associations/callbacks_test.rb
@@ -3,6 +3,7 @@ require 'models/post'
require 'models/author'
require 'models/project'
require 'models/developer'
+require 'models/computer'
require 'models/company'
class AssociationCallbacksTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/associations/deprecated_counter_cache_on_has_many_through_test.rb b/activerecord/test/cases/associations/deprecated_counter_cache_on_has_many_through_test.rb
deleted file mode 100644
index 48f7ddbe83..0000000000
--- a/activerecord/test/cases/associations/deprecated_counter_cache_on_has_many_through_test.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-require "cases/helper"
-
-class DeprecatedCounterCacheOnHasManyThroughTest < ActiveRecord::TestCase
- class Post < ActiveRecord::Base
- has_many :taggings, as: :taggable
- has_many :tags, through: :taggings
- end
-
- class Tagging < ActiveRecord::Base
- belongs_to :taggable, polymorphic: true
- belongs_to :tag
- end
-
- class Tag < ActiveRecord::Base
- end
-
- test "counter caches are updated in the database if the belongs_to association doesn't specify a counter cache" do
- post = Post.create!(title: 'Hello', body: 'World!')
- assert_deprecated { post.tags << Tag.create!(name: 'whatever') }
-
- assert_equal 1, post.tags.size
- assert_equal 1, post.tags_count
- assert_equal 1, post.reload.tags.size
- assert_equal 1, post.reload.tags_count
- end
-end
diff --git a/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
index 0ff87d53ea..f571198079 100644
--- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb
+++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
@@ -70,9 +70,7 @@ class EagerLoadPolyAssocsTest < ActiveRecord::TestCase
teardown do
[Circle, Square, Triangle, PaintColor, PaintTexture,
- ShapeExpression, NonPolyOne, NonPolyTwo].each do |c|
- c.delete_all
- end
+ ShapeExpression, NonPolyOne, NonPolyTwo].each(&:delete_all)
end
def generate_test_object_graphs
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 8234ee95be..0ecf2ddfd1 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -17,6 +17,7 @@ require 'models/subscriber'
require 'models/subscription'
require 'models/book'
require 'models/developer'
+require 'models/computer'
require 'models/project'
require 'models/member'
require 'models/membership'
@@ -76,9 +77,17 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_has_many_through_with_order
authors = Author.includes(:favorite_authors).to_a
+ assert authors.count > 0
assert_no_queries { authors.map(&:favorite_authors) }
end
+ def test_eager_loaded_has_one_association_with_references_does_not_run_additional_queries
+ Post.update_all(author_id: nil)
+ authors = Author.includes(:post).references(:post).to_a
+ assert authors.count > 0
+ assert_no_queries { authors.map(&:post) }
+ end
+
def test_with_two_tables_in_from_without_getting_double_quoted
posts = Post.select("posts.*").from("authors, posts").eager_load(:comments).where("posts.author_id = authors.id").order("posts.id").to_a
assert_equal 2, posts.first.comments.size
@@ -269,6 +278,14 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
+ def test_three_level_nested_preloading_does_not_raise_exception_when_association_does_not_exist
+ post_id = Comment.where(author_id: nil).where.not(post_id: nil).first.post_id
+
+ assert_nothing_raised do
+ Post.preload(:comments => [{:author => :essays}]).find(post_id)
+ end
+ end
+
def test_nested_loading_through_has_one_association
aa = AuthorAddress.all.merge!(:includes => {:author => :posts}).find(author_addresses(:david_address).id)
assert_equal aa.author.posts.count, aa.author.posts.length
@@ -329,31 +346,31 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_association_loading_with_belongs_to_and_limit
comments = Comment.all.merge!(:includes => :post, :limit => 5, :order => 'comments.id').to_a
assert_equal 5, comments.length
- assert_equal [1,2,3,5,6], comments.collect { |c| c.id }
+ assert_equal [1,2,3,5,6], comments.collect(&:id)
end
def test_eager_association_loading_with_belongs_to_and_limit_and_conditions
comments = Comment.all.merge!(:includes => :post, :where => 'post_id = 4', :limit => 3, :order => 'comments.id').to_a
assert_equal 3, comments.length
- assert_equal [5,6,7], comments.collect { |c| c.id }
+ assert_equal [5,6,7], comments.collect(&:id)
end
def test_eager_association_loading_with_belongs_to_and_limit_and_offset
comments = Comment.all.merge!(:includes => :post, :limit => 3, :offset => 2, :order => 'comments.id').to_a
assert_equal 3, comments.length
- assert_equal [3,5,6], comments.collect { |c| c.id }
+ assert_equal [3,5,6], comments.collect(&:id)
end
def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions
comments = Comment.all.merge!(:includes => :post, :where => 'post_id = 4', :limit => 3, :offset => 1, :order => 'comments.id').to_a
assert_equal 3, comments.length
- assert_equal [6,7,8], comments.collect { |c| c.id }
+ assert_equal [6,7,8], comments.collect(&:id)
end
def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions_array
comments = Comment.all.merge!(:includes => :post, :where => ['post_id = ?',4], :limit => 3, :offset => 1, :order => 'comments.id').to_a
assert_equal 3, comments.length
- assert_equal [6,7,8], comments.collect { |c| c.id }
+ assert_equal [6,7,8], comments.collect(&:id)
end
def test_eager_association_loading_with_belongs_to_and_conditions_string_with_unquoted_table_name
@@ -368,7 +385,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
comments = Comment.all.merge!(:includes => :post, :where => {:posts => {:id => 4}}, :limit => 3, :order => 'comments.id').to_a
end
assert_equal 3, comments.length
- assert_equal [5,6,7], comments.collect { |c| c.id }
+ assert_equal [5,6,7], comments.collect(&:id)
assert_no_queries do
comments.first.post
end
@@ -397,13 +414,13 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_association_loading_with_belongs_to_and_limit_and_multiple_associations
posts = Post.all.merge!(:includes => [:author, :very_special_comment], :limit => 1, :order => 'posts.id').to_a
assert_equal 1, posts.length
- assert_equal [1], posts.collect { |p| p.id }
+ assert_equal [1], posts.collect(&:id)
end
def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_multiple_associations
posts = Post.all.merge!(:includes => [:author, :very_special_comment], :limit => 1, :offset => 1, :order => 'posts.id').to_a
assert_equal 1, posts.length
- assert_equal [2], posts.collect { |p| p.id }
+ assert_equal [2], posts.collect(&:id)
end
def test_eager_association_loading_with_belongs_to_inferred_foreign_key_from_association_name
@@ -494,8 +511,8 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_with_has_many_through_an_sti_join_model_with_conditions_on_both
- author = Author.all.merge!(:includes => :special_nonexistant_post_comments, :order => 'authors.id').first
- assert_equal [], author.special_nonexistant_post_comments
+ author = Author.all.merge!(:includes => :special_nonexistent_post_comments, :order => 'authors.id').first
+ assert_equal [], author.special_nonexistent_post_comments
end
def test_eager_with_has_many_through_join_model_with_conditions
@@ -536,13 +553,13 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_with_has_many_and_limit_and_conditions
posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :where => "posts.body = 'hello'", :order => "posts.id").to_a
assert_equal 2, posts.size
- assert_equal [4,5], posts.collect { |p| p.id }
+ assert_equal [4,5], posts.collect(&:id)
end
def test_eager_with_has_many_and_limit_and_conditions_array
posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :where => [ "posts.body = ?", 'hello' ], :order => "posts.id").to_a
assert_equal 2, posts.size
- assert_equal [4,5], posts.collect { |p| p.id }
+ assert_equal [4,5], posts.collect(&:id)
end
def test_eager_with_has_many_and_limit_and_conditions_array_on_the_eagers
@@ -742,6 +759,23 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
+ def test_eager_with_default_scope_as_class_method_using_find_method
+ david = developers(:david)
+ developer = EagerDeveloperWithClassMethodDefaultScope.find(david.id)
+ projects = Project.order(:id).to_a
+ assert_no_queries do
+ assert_equal(projects, developer.projects)
+ end
+ end
+
+ def test_eager_with_default_scope_as_class_method_using_find_by_method
+ developer = EagerDeveloperWithClassMethodDefaultScope.find_by(name: 'David')
+ projects = Project.order(:id).to_a
+ assert_no_queries do
+ assert_equal(projects, developer.projects)
+ end
+ end
+
def test_eager_with_default_scope_as_lambda
developer = EagerDeveloperWithLambdaDefaultScope.where(:name => 'David').first
projects = Project.order(:id).to_a
@@ -817,18 +851,6 @@ class EagerAssociationTest < ActiveRecord::TestCase
)
end
- def test_preload_with_interpolation
- assert_deprecated do
- post = Post.includes(:comments_with_interpolated_conditions).find(posts(:welcome).id)
- assert_equal [comments(:greetings)], post.comments_with_interpolated_conditions
- end
-
- assert_deprecated do
- post = Post.joins(:comments_with_interpolated_conditions).find(posts(:welcome).id)
- assert_equal [comments(:greetings)], post.comments_with_interpolated_conditions
- end
- end
-
def test_polymorphic_type_condition
post = Post.all.merge!(:includes => :taggings).find(posts(:thinking).id)
assert post.taggings.include?(taggings(:thinking_general))
@@ -903,6 +925,12 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_no_queries {assert_equal posts(:sti_comments), comment.post}
end
+ def test_eager_association_with_scope_with_joins
+ assert_nothing_raised do
+ Post.includes(:very_special_comment_with_post_with_joins).to_a
+ end
+ end
+
def test_preconfigured_includes_with_has_many
posts = authors(:david).posts_with_comments
one = posts.detect { |p| p.id == 1 }
@@ -1279,23 +1307,22 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal pets(:parrot), Owner.including_last_pet.first.last_pet
end
- test "include instance dependent associations is deprecated" do
+ test "preloading and eager loading of instance dependent associations is not supported" do
message = "association scope 'posts_with_signature' is"
- assert_deprecated message do
- begin
- Author.includes(:posts_with_signature).to_a
- rescue NoMethodError
- # it's expected that preloading of this association fails
- end
+ error = assert_raises(ArgumentError) do
+ Author.includes(:posts_with_signature).to_a
end
+ assert_match message, error.message
- assert_deprecated message do
- Author.preload(:posts_with_signature).to_a rescue NoMethodError
+ error = assert_raises(ArgumentError) do
+ Author.preload(:posts_with_signature).to_a
end
+ assert_match message, error.message
- assert_deprecated message do
+ error = assert_raises(ArgumentError) do
Author.eager_load(:posts_with_signature).to_a
end
+ assert_match message, error.message
end
test "preloading readonly association" do
@@ -1313,7 +1340,6 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
test "eager-loading readonly association" do
- skip "eager_load does not yet preserve readonly associations"
# has-one
firm = Firm.where(id: "1").eager_load(:readonly_account).first!
assert firm.readonly_account.readonly?
@@ -1325,6 +1351,10 @@ class EagerAssociationTest < ActiveRecord::TestCase
# has-many :through
david = Author.where(id: "1").eager_load(:readonly_comments).first!
assert david.readonly_comments.first.readonly?
+
+ # belongs_to
+ post = Post.where(id: "1").eager_load(:author).first!
+ assert post.author.readonly?
end
test "preloading a polymorphic association with references to the associated table" do
diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb
index 4c1fdfdd9a..b161cde335 100644
--- a/activerecord/test/cases/associations/extension_test.rb
+++ b/activerecord/test/cases/associations/extension_test.rb
@@ -3,6 +3,7 @@ require 'models/post'
require 'models/comment'
require 'models/project'
require 'models/developer'
+require 'models/computer'
require 'models/company_in_module'
class AssociationsExtensionsTest < ActiveRecord::TestCase
@@ -75,7 +76,6 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase
private
def extend!(model)
- builder = ActiveRecord::Associations::Builder::HasMany.new(model, :association_name, nil, {}) { }
- builder.define_extensions(model)
+ ActiveRecord::Associations::Builder::HasMany.define_extensions(model, :association_name) { }
end
end
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index 859310575e..aea9207bfe 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
@@ -1,5 +1,6 @@
require "cases/helper"
require 'models/developer'
+require 'models/computer'
require 'models/project'
require 'models/company'
require 'models/customer'
@@ -78,9 +79,13 @@ class SubDeveloper < Developer
:association_foreign_key => "developer_id"
end
+class DeveloperWithSymbolClassName < Developer
+ has_and_belongs_to_many :projects, class_name: :ProjectWithSymbolsForKeys
+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
+ :parrots, :pirates, :parrots_pirates, :treasures, :price_estimates, :tags, :taggings, :computers
def setup_data_for_habtm_case
ActiveRecord::Base.connection.execute('delete from countries_treaties')
@@ -550,7 +555,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_dynamic_find_all_should_respect_readonly_access
projects(:active_record).readonly_developers.each { |d| assert_raise(ActiveRecord::ReadOnlyRecord) { d.save! } if d.valid?}
- projects(:active_record).readonly_developers.each { |d| d.readonly? }
+ projects(:active_record).readonly_developers.each(&:readonly?)
end
def test_new_with_values_in_collection
@@ -883,4 +888,18 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
child.special_projects << SpecialProject.new("name" => "Special Project")
assert child.save, 'child object should be saved'
end
+
+ def test_habtm_with_reflection_using_class_name_and_fixtures
+ assert_not_nil Developer._reflections['shared_computers']
+ # Checking the fixture for named association is important here, because it's the only way
+ # we've been able to reproduce this bug
+ assert_not_nil File.read(File.expand_path("../../../fixtures/developers.yml", __FILE__)).index("shared_computers")
+ assert_equal developers(:david).shared_computers.first, computers(:laptop)
+ end
+
+ def test_with_symbol_class_name
+ assert_nothing_raised NoMethodError do
+ DeveloperWithSymbolClassName.new
+ end
+ end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index e34b993029..171cfbde44 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -1,11 +1,13 @@
require "cases/helper"
require 'models/developer'
+require 'models/computer'
require 'models/project'
require 'models/company'
require 'models/contract'
require 'models/topic'
require 'models/reply'
require 'models/category'
+require 'models/image'
require 'models/post'
require 'models/author'
require 'models/essay'
@@ -28,7 +30,12 @@ require 'models/college'
require 'models/student'
require 'models/pirate'
require 'models/ship'
+require 'models/ship_part'
require 'models/tyre'
+require 'models/subscriber'
+require 'models/subscription'
+require 'models/zine'
+require 'models/interest'
class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCase
fixtures :authors, :posts, :comments
@@ -41,12 +48,59 @@ class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCa
end
end
+class HasManyAssociationsTestPrimaryKeys < ActiveRecord::TestCase
+ fixtures :authors, :essays, :subscribers, :subscriptions, :people
+
+ def test_custom_primary_key_on_new_record_should_fetch_with_query
+ subscriber = Subscriber.new(nick: 'webster132')
+ assert !subscriber.subscriptions.loaded?
+
+ assert_queries 1 do
+ assert_equal 2, subscriber.subscriptions.size
+ end
+
+ assert_equal subscriber.subscriptions, Subscription.where(subscriber_id: 'webster132')
+ end
+
+ def test_association_primary_key_on_new_record_should_fetch_with_query
+ author = Author.new(:name => "David")
+ assert !author.essays.loaded?
+
+ assert_queries 1 do
+ assert_equal 1, author.essays.size
+ end
+
+ assert_equal author.essays, Essay.where(writer_id: "David")
+ end
+
+ def test_has_many_custom_primary_key
+ david = authors(:david)
+ assert_equal david.essays, Essay.where(writer_id: "David")
+ end
+
+ def test_has_many_assignment_with_custom_primary_key
+ david = people(:david)
+
+ assert_equal ["A Modest Proposal"], david.essays.map(&:name)
+ david.essays = [Essay.create!(name: "Remote Work" )]
+ assert_equal ["Remote Work"], david.essays.map(&:name)
+ end
+
+ def test_blank_custom_primary_key_on_new_record_should_not_run_queries
+ author = Author.new
+ assert !author.essays.loaded?
+
+ assert_queries 0 do
+ assert_equal 0, author.essays.size
+ end
+ end
+end
class HasManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :categories, :companies, :developers, :projects,
:developers_projects, :topics, :authors, :comments,
- :people, :posts, :readers, :taggings, :cars, :essays,
- :categorizations, :jobs, :tags
+ :posts, :readers, :taggings, :cars, :jobs, :tags,
+ :categorizations, :zines, :interests
def setup
Client.destroyed_client_ids.clear
@@ -76,6 +130,32 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
dev.developer_projects.map(&:project_id).sort
end
+ def test_default_scope_on_relations_is_not_cached
+ counter = 0
+ posts = Class.new(ActiveRecord::Base) {
+ self.table_name = 'posts'
+ self.inheritance_column = 'not_there'
+ post = self
+
+ comments = Class.new(ActiveRecord::Base) {
+ self.table_name = 'comments'
+ self.inheritance_column = 'not_there'
+ belongs_to :post, :class => post
+ default_scope -> {
+ counter += 1
+ where("id = :inc", :inc => counter)
+ }
+ }
+ has_many :comments, :class => comments, :foreign_key => 'post_id'
+ }
+ assert_equal 0, counter
+ post = posts.first
+ assert_equal 0, counter
+ sql = capture_sql { post.comments.to_a }
+ post.comments.reset
+ assert_not_equal sql, capture_sql { post.comments.to_a }
+ end
+
def test_has_many_build_with_options
college = College.create(name: 'UFMT')
Student.create(active: true, college_id: college.id, name: 'Sarah')
@@ -83,6 +163,30 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal college.students, Student.where(active: true, college_id: college.id)
end
+ def test_add_record_to_collection_should_change_its_updated_at
+ ship = Ship.create(name: 'dauntless')
+ part = ShipPart.create(name: 'cockpit')
+ updated_at = part.updated_at
+
+ ship.parts << part
+
+ assert_equal part.ship, ship
+ assert_not_equal part.updated_at, updated_at
+ end
+
+ def test_clear_collection_should_not_change_updated_at
+ # GH#17161: .clear calls delete_all (and returns the association),
+ # which is intended to not touch associated objects's updated_at field
+ ship = Ship.create(name: 'dauntless')
+ part = ShipPart.create(name: 'cockpit', ship_id: ship.id)
+
+ ship.parts.clear
+ part.reload
+
+ assert_equal nil, part.ship
+ assert !part.updated_at_changed?
+ end
+
def test_create_from_association_should_respect_default_scope
car = Car.create(:name => 'honda')
assert_equal 'honda', car.name
@@ -239,16 +343,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
# would be convenient), because this would cause that scope to be applied to any callbacks etc.
def test_build_and_create_should_not_happen_within_scope
car = cars(:honda)
- scoped_count = car.foo_bulbs.where_values.count
+ scope = car.foo_bulbs.where_values_hash
bulb = car.foo_bulbs.build
- assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count
+ assert_not_equal scope, bulb.scope_after_initialize.where_values_hash
bulb = car.foo_bulbs.create
- assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count
+ assert_not_equal scope, bulb.scope_after_initialize.where_values_hash
bulb = car.foo_bulbs.create!
- assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count
+ assert_not_equal scope, bulb.scope_after_initialize.where_values_hash
end
def test_no_sql_should_be_fired_if_association_already_loaded
@@ -358,6 +462,45 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal companies(:another_first_firm_client), companies(:first_firm).clients_sorted_desc.find_by_type('Client')
end
+ def test_taking
+ posts(:other_by_bob).destroy
+ assert_equal posts(:misc_by_bob), authors(:bob).posts.take
+ assert_equal posts(:misc_by_bob), authors(:bob).posts.take!
+ authors(:bob).posts.to_a
+ assert_equal posts(:misc_by_bob), authors(:bob).posts.take
+ assert_equal posts(:misc_by_bob), authors(:bob).posts.take!
+ end
+
+ def test_taking_not_found
+ authors(:bob).posts.delete_all
+ assert_raise(ActiveRecord::RecordNotFound) { authors(:bob).posts.take! }
+ authors(:bob).posts.to_a
+ assert_raise(ActiveRecord::RecordNotFound) { authors(:bob).posts.take! }
+ end
+
+ def test_taking_with_a_number
+ # taking from unloaded Relation
+ bob = Author.find(authors(:bob).id)
+ assert_equal [posts(:misc_by_bob)], bob.posts.take(1)
+ bob = Author.find(authors(:bob).id)
+ assert_equal [posts(:misc_by_bob), posts(:other_by_bob)], bob.posts.take(2)
+
+ # taking from loaded Relation
+ bob.posts.to_a
+ assert_equal [posts(:misc_by_bob)], authors(:bob).posts.take(1)
+ assert_equal [posts(:misc_by_bob), posts(:other_by_bob)], authors(:bob).posts.take(2)
+ end
+
+ def test_taking_with_inverse_of
+ interests(:woodsmanship).destroy
+ interests(:survival).destroy
+
+ zine = zines(:going_out)
+ interest = zine.interests.take
+ assert_equal interests(:hunting), interest
+ assert_same zine, interest.zine
+ end
+
def test_cant_save_has_many_readonly_association
authors(:david).readonly_comments.each { |c| assert_raise(ActiveRecord::ReadOnlyRecord) { c.save! } }
authors(:david).readonly_comments.each { |c| assert c.readonly? }
@@ -387,6 +530,25 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal "Summit", Firm.all.merge!(:order => "id").first.clients_using_primary_key.first.name
end
+ def test_update_all_on_association_accessed_before_save
+ firm = Firm.new(name: 'Firm')
+ clients_proxy_id = firm.clients.object_id
+ firm.clients << Client.first
+ firm.save!
+ assert_equal firm.clients.count, firm.clients.update_all(description: 'Great!')
+ assert_not_equal clients_proxy_id, firm.clients.object_id
+ end
+
+ def test_update_all_on_association_accessed_before_save_with_explicit_foreign_key
+ # We can use the same cached proxy object because the id is available for the scope
+ firm = Firm.new(name: 'Firm', id: 100)
+ clients_proxy_id = firm.clients.object_id
+ firm.clients << Client.first
+ firm.save!
+ assert_equal firm.clients.count, firm.clients.update_all(description: 'Great!')
+ assert_equal clients_proxy_id, firm.clients.object_id
+ end
+
def test_belongs_to_sanity
c = Client.new
assert_nil c.firm, "belongs_to failed sanity check on new object"
@@ -555,17 +717,21 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_create_with_bang_on_has_many_when_parent_is_new_raises
- assert_raise(ActiveRecord::RecordNotSaved) do
+ error = assert_raise(ActiveRecord::RecordNotSaved) do
firm = Firm.new
firm.plain_clients.create! :name=>"Whoever"
end
+
+ assert_equal "You cannot call create unless the parent is saved", error.message
end
def test_regular_create_on_has_many_when_parent_is_new_raises
- assert_raise(ActiveRecord::RecordNotSaved) do
+ error = assert_raise(ActiveRecord::RecordNotSaved) do
firm = Firm.new
firm.plain_clients.create :name=>"Whoever"
end
+
+ assert_equal "You cannot call create unless the parent is saved", error.message
end
def test_create_with_bang_on_has_many_raises_when_record_not_saved
@@ -576,9 +742,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_create_with_bang_on_habtm_when_parent_is_new_raises
- assert_raise(ActiveRecord::RecordNotSaved) do
+ error = assert_raise(ActiveRecord::RecordNotSaved) do
Developer.new("name" => "Aredridel").projects.create!
end
+
+ assert_equal "You cannot call create unless the parent is saved", error.message
end
def test_adding_a_mismatch_class
@@ -1179,7 +1347,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert !clients.empty?, "37signals has clients after load"
destroyed = companies(:first_firm).clients_of_firm.destroy_all
assert_equal clients.sort_by(&:id), destroyed.sort_by(&:id)
- assert destroyed.all? { |client| client.frozen? }, "destroyed clients should be frozen"
+ 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"
end
@@ -1217,7 +1385,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_nothing_raised { topic.destroy }
end
- uses_transaction :test_dependence_with_transaction_support_on_failure
def test_dependence_with_transaction_support_on_failure
firm = companies(:first_firm)
clients = firm.clients
@@ -1319,10 +1486,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert !account.valid?
assert !orig_accounts.empty?
- assert_raise ActiveRecord::RecordNotSaved do
+ error = assert_raise ActiveRecord::RecordNotSaved do
firm.accounts = [account]
end
+
assert_equal orig_accounts, firm.accounts
+ assert_equal "Failed to replace accounts because one or more of the " \
+ "new records could not be saved.", error.message
end
def test_replace_with_same_content
@@ -1333,6 +1503,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_queries(0, ignore_none: true) do
firm.clients = []
end
+
+ assert_equal [], firm.send('clients=', [])
end
def test_transactions_when_replacing_on_persisted
@@ -1534,39 +1706,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
end
- def test_custom_primary_key_on_new_record_should_fetch_with_query
- author = Author.new(:name => "David")
- assert !author.essays.loaded?
-
- assert_queries 1 do
- assert_equal 1, author.essays.size
- end
-
- assert_equal author.essays, Essay.where(writer_id: "David")
- end
-
- def test_has_many_custom_primary_key
- david = authors(:david)
- assert_equal david.essays, Essay.where(writer_id: "David")
- end
-
- def test_has_many_assignment_with_custom_primary_key
- david = people(:david)
-
- assert_equal ["A Modest Proposal"], david.essays.map(&:name)
- david.essays = [Essay.create!(name: "Remote Work" )]
- assert_equal ["Remote Work"], david.essays.map(&:name)
- end
-
- def test_blank_custom_primary_key_on_new_record_should_not_run_queries
- author = Author.new
- assert !author.essays.loaded?
-
- assert_queries 0 do
- assert_equal 0, author.essays.size
- end
- end
-
def test_calling_first_or_last_with_integer_on_association_should_not_load_association
firm = companies(:first_firm)
firm.clients.create(:name => 'Foo')
@@ -1619,6 +1758,82 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 3, firm.clients.size
end
+ def test_calling_none_should_count_instead_of_loading_association
+ firm = companies(:first_firm)
+ assert_queries(1) do
+ firm.clients.none? # use count query
+ end
+ assert !firm.clients.loaded?
+ end
+
+ def test_calling_none_on_loaded_association_should_not_use_query
+ firm = companies(:first_firm)
+ firm.clients.collect # force load
+ assert_no_queries { assert ! firm.clients.none? }
+ end
+
+ def test_calling_none_should_defer_to_collection_if_using_a_block
+ firm = companies(:first_firm)
+ assert_queries(1) do
+ firm.clients.expects(:size).never
+ firm.clients.none? { true }
+ end
+ assert firm.clients.loaded?
+ end
+
+ def test_calling_none_should_return_true_if_none
+ firm = companies(:another_firm)
+ assert firm.clients_like_ms.none?
+ assert_equal 0, firm.clients_like_ms.size
+ end
+
+ def test_calling_none_should_return_false_if_any
+ firm = companies(:first_firm)
+ assert !firm.limited_clients.none?
+ assert_equal 1, firm.limited_clients.size
+ end
+
+ def test_calling_one_should_count_instead_of_loading_association
+ firm = companies(:first_firm)
+ assert_queries(1) do
+ firm.clients.one? # use count query
+ end
+ assert !firm.clients.loaded?
+ end
+
+ def test_calling_one_on_loaded_association_should_not_use_query
+ firm = companies(:first_firm)
+ firm.clients.collect # force load
+ assert_no_queries { assert ! firm.clients.one? }
+ end
+
+ def test_calling_one_should_defer_to_collection_if_using_a_block
+ firm = companies(:first_firm)
+ assert_queries(1) do
+ firm.clients.expects(:size).never
+ firm.clients.one? { true }
+ end
+ assert firm.clients.loaded?
+ end
+
+ def test_calling_one_should_return_false_if_zero
+ firm = companies(:another_firm)
+ assert ! firm.clients_like_ms.one?
+ assert_equal 0, firm.clients_like_ms.size
+ end
+
+ def test_calling_one_should_return_true_if_one
+ firm = companies(:first_firm)
+ assert firm.limited_clients.one?
+ assert_equal 1, firm.limited_clients.size
+ end
+
+ def test_calling_one_should_return_false_if_more_than_one
+ firm = companies(:first_firm)
+ assert ! firm.clients.one?
+ assert_equal 3, firm.clients.size
+ end
+
def test_joins_with_namespaced_model_should_use_correct_type
old = ActiveRecord::Base.store_full_sti_class
ActiveRecord::Base.store_full_sti_class = true
@@ -1740,6 +1955,15 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [tagging], post.taggings
end
+ def test_with_polymorphic_has_many_with_custom_columns_name
+ post = Post.create! :title => 'foo', :body => 'bar'
+ image = Image.create!
+
+ post.images << image
+
+ assert_equal [image], post.images
+ end
+
def test_build_with_polymorphic_has_many_does_not_allow_to_override_type_and_id
welcome = posts(:welcome)
tagging = welcome.taggings.build(:taggable_id => 99, :taggable_type => 'ShouldNotChange')
@@ -1895,6 +2119,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [bulb1, bulb2], car.all_bulbs.sort_by(&:id)
end
+ test 'unscopes the default scope of associated model when used with include' do
+ car = Car.create!
+ bulb = Bulb.create! name: "other", car: car
+
+ assert_equal bulb, Car.find(car.id).all_bulbs.first
+ assert_equal bulb, Car.includes(:all_bulbs).find(car.id).all_bulbs.first
+ end
+
test "raises RecordNotDestroyed when replaced child can't be destroyed" do
car = Car.create!
original_child = FailedBulb.create!(car: car)
@@ -1955,4 +2187,68 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 1, car.bulbs.count
assert_equal 1, car.tyres.count
end
+
+ test 'associations replace in memory when records have the same id' do
+ bulb = Bulb.create!
+ car = Car.create!(bulbs: [bulb])
+
+ new_bulb = Bulb.find(bulb.id)
+ new_bulb.name = "foo"
+ car.bulbs = [new_bulb]
+
+ assert_equal "foo", car.bulbs.first.name
+ end
+
+ test 'in memory replacement executes no queries' do
+ bulb = Bulb.create!
+ car = Car.create!(bulbs: [bulb])
+
+ new_bulb = Bulb.find(bulb.id)
+
+ assert_no_queries do
+ car.bulbs = [new_bulb]
+ end
+ end
+
+ test 'in memory replacements do not execute callbacks' do
+ raise_after_add = false
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = :cars
+ has_many :bulbs, after_add: proc { raise if raise_after_add }
+
+ def self.name
+ "Car"
+ end
+ end
+ bulb = Bulb.create!
+ car = klass.create!(bulbs: [bulb])
+
+ new_bulb = Bulb.find(bulb.id)
+ raise_after_add = true
+
+ assert_nothing_raised do
+ car.bulbs = [new_bulb]
+ end
+ end
+
+ test 'in memory replacements sets inverse instance' do
+ bulb = Bulb.create!
+ car = Car.create!(bulbs: [bulb])
+
+ new_bulb = Bulb.find(bulb.id)
+ car.bulbs = [new_bulb]
+
+ assert_same car, new_bulb.car
+ end
+
+ test 'in memory replacement maintains order' do
+ first_bulb = Bulb.create!
+ second_bulb = Bulb.create!
+ car = Car.create!(bulbs: [first_bulb, second_bulb])
+
+ same_bulb = Bulb.find(first_bulb.id)
+ car.bulbs = [second_bulb, same_bulb]
+
+ assert_equal [first_bulb, second_bulb], car.bulbs
+ 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 cddf1a1f72..5f52c65412 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -15,6 +15,7 @@ require 'models/toy'
require 'models/contract'
require 'models/company'
require 'models/developer'
+require 'models/computer'
require 'models/subscriber'
require 'models/book'
require 'models/subscription'
@@ -24,12 +25,13 @@ require 'models/categorization'
require 'models/member'
require 'models/membership'
require 'models/club'
+require 'models/organization'
class HasManyThroughAssociationsTest < ActiveRecord::TestCase
fixtures :posts, :readers, :people, :comments, :authors, :categories, :taggings, :tags,
:owners, :pets, :toys, :jobs, :references, :companies, :members, :author_addresses,
:subscribers, :books, :subscriptions, :developers, :categorizations, :essays,
- :categories_posts, :clubs, :memberships
+ :categories_posts, :clubs, :memberships, :organizations
# Dummies to force column loads so query counts are clean.
def setup
@@ -40,7 +42,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_preload_sti_rhs_class
developers = Developer.includes(:firms).all.to_a
assert_no_queries do
- developers.each { |d| d.firms }
+ developers.each(&:firms)
end
end
@@ -593,6 +595,12 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert posts(:thinking).reload.people(true).collect(&:first_name).include?("Jeb")
end
+ def test_through_record_is_built_when_created_with_where
+ assert_difference("posts(:thinking).readers.count", 1) do
+ posts(:thinking).people.where(first_name: "Jeb").create
+ end
+ end
+
def test_associate_with_create_and_no_options
peeps = posts(:thinking).people.count
posts(:thinking).people.create(:first_name => 'foo')
@@ -614,8 +622,11 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_create_on_new_record
p = Post.new
- assert_raises(ActiveRecord::RecordNotSaved) { p.people.create(:first_name => "mew") }
- assert_raises(ActiveRecord::RecordNotSaved) { p.people.create!(:first_name => "snow") }
+ error = assert_raises(ActiveRecord::RecordNotSaved) { p.people.create(:first_name => "mew") }
+ assert_equal "You cannot call create unless the parent is saved", error.message
+
+ error = assert_raises(ActiveRecord::RecordNotSaved) { p.people.create!(:first_name => "snow") }
+ assert_equal "You cannot call create unless the parent is saved", error.message
end
def test_associate_with_create_and_invalid_options
@@ -1147,4 +1158,12 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
club.members << member
assert_equal 1, SuperMembership.where(member_id: member.id, club_id: club.id).count
end
+
+ def test_build_for_has_many_through_association
+ organization = organizations(:nsa)
+ author = organization.author
+ post_direct = author.posts.build
+ post_through = organization.posts.build
+ assert_equal post_direct.author_id, post_through.author_id
+ 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 d412b3168e..5c2e5e7b43 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -1,5 +1,6 @@
require "cases/helper"
require 'models/developer'
+require 'models/computer'
require 'models/project'
require 'models/company'
require 'models/ship'
@@ -7,10 +8,11 @@ require 'models/pirate'
require 'models/car'
require 'models/bulb'
require 'models/author'
+require 'models/image'
require 'models/post'
class HasOneAssociationsTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false unless supports_savepoints?
+ self.use_transactional_tests = false unless supports_savepoints?
fixtures :accounts, :companies, :developers, :projects, :developers_projects, :ships, :pirates
def setup
@@ -235,16 +237,16 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
def test_build_and_create_should_not_happen_within_scope
pirate = pirates(:blackbeard)
- scoped_count = pirate.association(:foo_bulb).scope.where_values.count
+ scope = pirate.association(:foo_bulb).scope.where_values_hash
bulb = pirate.build_foo_bulb
- assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count
+ assert_not_equal scope, bulb.scope_after_initialize.where_values_hash
bulb = pirate.create_foo_bulb
- assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count
+ assert_not_equal scope, bulb.scope_after_initialize.where_values_hash
bulb = pirate.create_foo_bulb!
- assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count
+ assert_not_equal scope, bulb.scope_after_initialize.where_values_hash
end
def test_create_association
@@ -271,6 +273,14 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal account, firm.reload.account
end
+ def test_create_with_inexistent_foreign_key_failing
+ firm = Firm.create(name: 'GlobalMegaCorp')
+
+ assert_raises(ActiveRecord::UnknownAttributeError) do
+ firm.create_account_with_inexistent_foreign_key
+ end
+ end
+
def test_build
firm = Firm.new("name" => "GlobalMegaCorp")
firm.save
@@ -409,9 +419,11 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
pirate = pirates(:redbeard)
new_ship = Ship.new
- assert_raise(ActiveRecord::RecordNotSaved) do
+ error = assert_raise(ActiveRecord::RecordNotSaved) do
pirate.ship = new_ship
end
+
+ assert_equal "Failed to save the new associated ship.", error.message
assert_nil pirate.ship
assert_nil new_ship.pirate_id
end
@@ -421,20 +433,25 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
pirate.ship.name = nil
assert !pirate.ship.valid?
- assert_raise(ActiveRecord::RecordNotSaved) do
+ error = assert_raise(ActiveRecord::RecordNotSaved) do
pirate.ship = ships(:interceptor)
end
+
assert_equal ships(:black_pearl), pirate.ship
assert_equal pirate.id, pirate.ship.pirate_id
+ assert_equal "Failed to remove the existing associated ship. " +
+ "The record failed to save after its foreign key was set to nil.", error.message
end
def test_replacement_failure_due_to_new_record_should_raise_error
pirate = pirates(:blackbeard)
new_ship = Ship.new
- assert_raise(ActiveRecord::RecordNotSaved) do
+ error = assert_raise(ActiveRecord::RecordNotSaved) do
pirate.ship = new_ship
end
+
+ assert_equal "Failed to save the new associated ship.", error.message
assert_equal ships(:black_pearl), pirate.ship
assert_equal pirate.id, pirate.ship.pirate_id
assert_equal pirate.id, ships(:black_pearl).reload.pirate_id
@@ -557,6 +574,12 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal author.post, post
end
+ def test_has_one_loading_for_new_record
+ post = Post.create!(author_id: 42, title: 'foo', body: 'bar')
+ author = Author.new(id: 42)
+ assert_equal post, author.post
+ end
+
def test_has_one_relationship_cannot_have_a_counter_cache
assert_raise(ArgumentError) do
Class.new(ActiveRecord::Base) do
@@ -565,6 +588,16 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_with_polymorphic_has_one_with_custom_columns_name
+ post = Post.create! :title => 'foo', :body => 'bar'
+ image = Image.create!
+
+ post.main_image = image
+ post.reload
+
+ assert_equal image, post.main_image
+ end
+
test 'dangerous association name raises ArgumentError' do
[:errors, 'errors', :save, 'save'].each do |name|
assert_raises(ArgumentError, "Association #{name} should not be allowed") do
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 19d1aa87a8..f8772547a2 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -15,6 +15,7 @@ require 'models/essay'
require 'models/owner'
require 'models/post'
require 'models/comment'
+require 'models/categorization'
class HasOneThroughAssociationsTest < ActiveRecord::TestCase
fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations, :minivans,
diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb
index 07cf65a760..b3fe759ad9 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -54,7 +54,7 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
def test_find_with_implicit_inner_joins_without_select_does_not_imply_readonly
authors = Author.joins(:posts)
assert_not authors.empty?, "expected authors to be non-empty"
- assert authors.none? {|a| a.readonly? }, "expected no authors to be readonly"
+ assert authors.none?(&:readonly?), "expected no authors to be readonly"
end
def test_find_with_implicit_inner_joins_honors_readonly_with_select
@@ -102,7 +102,7 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
def test_find_with_conditions_on_reflection
assert !posts(:welcome).comments.empty?
- assert Post.joins(:nonexistant_comments).where(:id => posts(:welcome).id).empty? # [sic!]
+ assert Post.joins(:nonexistent_comments).where(:id => posts(:welcome).id).empty? # [sic!]
end
def test_find_with_conditions_on_through_reflection
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index 60df4e14dd..423b8238b1 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -10,6 +10,9 @@ require 'models/comment'
require 'models/car'
require 'models/bulb'
require 'models/mixed_case_monkey'
+require 'models/admin'
+require 'models/admin/account'
+require 'models/admin/user'
class AutomaticInverseFindingTests < ActiveRecord::TestCase
fixtures :ratings, :comments, :cars
@@ -27,6 +30,15 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase
assert_equal monkey_reflection, man_reflection.inverse_of, "The man reflection's inverse should be the monkey reflection"
end
+ def test_has_many_and_belongs_to_should_find_inverse_automatically_for_model_in_module
+ account_reflection = Admin::Account.reflect_on_association(:users)
+ user_reflection = Admin::User.reflect_on_association(:account)
+
+ assert_respond_to account_reflection, :has_inverse?
+ assert account_reflection.has_inverse?, "The Admin::Account reflection should have an inverse"
+ assert_equal user_reflection, account_reflection.inverse_of, "The Admin::Account reflection's inverse should be the Admin::User reflection"
+ end
+
def test_has_one_and_belongs_to_should_find_inverse_automatically
car_reflection = Car.reflect_on_association(:bulb)
bulb_reflection = Bulb.reflect_on_association(:car)
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index cace7ba142..213be50e67 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -17,7 +17,7 @@ require 'models/engine'
require 'models/car'
class AssociationsJoinModelTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false unless supports_savepoints?
+ self.use_transactional_tests = false unless supports_savepoints?
fixtures :posts, :authors, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites, :vertices, :items, :books,
# Reload edges table from fixtures as otherwise repeated test was failing
@@ -393,18 +393,18 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_has_many_through_polymorphic_has_one
- assert_equal Tagging.find(1,2).sort_by { |t| t.id }, authors(:david).taggings_2
+ assert_equal Tagging.find(1,2).sort_by(&:id), authors(:david).taggings_2
end
def test_has_many_through_polymorphic_has_many
- assert_equal taggings(:welcome_general, :thinking_general), authors(:david).taggings.distinct.sort_by { |t| t.id }
+ assert_equal taggings(:welcome_general, :thinking_general), authors(:david).taggings.distinct.sort_by(&:id)
end
def test_include_has_many_through_polymorphic_has_many
author = Author.includes(:taggings).find authors(:david).id
expected_taggings = taggings(:welcome_general, :thinking_general)
assert_no_queries do
- assert_equal expected_taggings, author.taggings.distinct.sort_by { |t| t.id }
+ assert_equal expected_taggings, author.taggings.distinct.sort_by(&:id)
end
end
@@ -444,7 +444,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_has_many_through_uses_conditions_specified_on_the_has_many_association
author = Author.first
assert author.comments.present?
- assert author.nonexistant_comments.blank?
+ assert author.nonexistent_comments.blank?
end
def test_has_many_through_uses_correct_attributes
diff --git a/activerecord/test/cases/associations/required_test.rb b/activerecord/test/cases/associations/required_test.rb
index 321fb6c8dd..3e5494e897 100644
--- a/activerecord/test/cases/associations/required_test.rb
+++ b/activerecord/test/cases/associations/required_test.rb
@@ -1,7 +1,7 @@
require "cases/helper"
class RequiredAssociationsTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
class Parent < ActiveRecord::Base
end
@@ -18,8 +18,8 @@ class RequiredAssociationsTest < ActiveRecord::TestCase
end
teardown do
- @connection.drop_table 'parents' if @connection.table_exists? 'parents'
- @connection.drop_table 'children' if @connection.table_exists? 'children'
+ @connection.drop_table 'parents', if_exists: true
+ @connection.drop_table 'children', if_exists: true
end
test "belongs_to associations are not required by default" do
@@ -40,7 +40,7 @@ class RequiredAssociationsTest < ActiveRecord::TestCase
record = model.new
assert_not record.save
- assert_equal ["Parent can't be blank"], record.errors.full_messages
+ assert_equal ["Parent must exist"], record.errors.full_messages
record.parent = Parent.new
assert record.save
@@ -64,12 +64,32 @@ class RequiredAssociationsTest < ActiveRecord::TestCase
record = model.new
assert_not record.save
- assert_equal ["Child can't be blank"], record.errors.full_messages
+ assert_equal ["Child must exist"], record.errors.full_messages
record.child = Child.new
assert record.save
end
+ test "required has_one associations have a correct error message" do
+ model = subclass_of(Parent) do
+ has_one :child, required: true, inverse_of: false,
+ class_name: "RequiredAssociationsTest::Child"
+ end
+
+ record = model.create
+ assert_equal ["Child must exist"], record.errors.full_messages
+ end
+
+ test "required belongs_to associations have a correct error message" do
+ model = subclass_of(Child) do
+ belongs_to :parent, required: true, inverse_of: false,
+ class_name: "RequiredAssociationsTest::Parent"
+ end
+
+ record = model.create
+ assert_equal ["Parent must exist"], record.errors.full_messages
+ end
+
private
def subclass_of(klass, &block)
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 9b0cf4c18f..3d202a5527 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -1,6 +1,7 @@
require "cases/helper"
require 'models/computer'
require 'models/developer'
+require 'models/computer'
require 'models/project'
require 'models/company'
require 'models/categorization'
@@ -42,28 +43,6 @@ class AssociationsTest < ActiveRecord::TestCase
assert_equal favs, fav2
end
- def test_clear_association_cache_stored
- firm = Firm.find(1)
- assert_kind_of Firm, firm
-
- firm.clear_association_cache
- assert_equal Firm.find(1).clients.collect{ |x| x.name }.sort, firm.clients.collect{ |x| x.name }.sort
- end
-
- def test_clear_association_cache_new_record
- firm = Firm.new
- client_stored = Client.find(3)
- client_new = Client.new
- client_new.name = "The Joneses"
- clients = [ client_stored, client_new ]
-
- firm.clients << clients
- assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set
-
- firm.clear_association_cache
- assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set
- end
-
def test_loading_the_association_target_should_keep_child_records_marked_for_destruction
ship = Ship.create!(:name => "The good ship Dollypop")
part = ship.parts.create!(:name => "Mast")
@@ -140,7 +119,7 @@ class AssociationsTest < ActiveRecord::TestCase
def test_association_with_references
firm = companies(:first_firm)
- assert_equal ['foo'], firm.association_with_references.references_values
+ assert_includes firm.association_with_references.references_values, 'foo'
end
end
@@ -237,7 +216,7 @@ class AssociationProxyTest < ActiveRecord::TestCase
end
def test_scoped_allows_conditions
- assert developers(:david).projects.merge!(where: 'foo').where_values.include?('foo')
+ assert developers(:david).projects.merge(where: 'foo').to_sql.include?('foo')
end
test "getting a scope from an association" do
@@ -263,6 +242,11 @@ class AssociationProxyTest < ActiveRecord::TestCase
end
end
+ test "first! works on loaded associations" do
+ david = authors(:david)
+ assert_equal david.posts.first, david.posts.reload.first!
+ end
+
def test_reset_unloads_target
david = authors(:david)
david.posts.reload
diff --git a/activerecord/test/cases/attribute_decorators_test.rb b/activerecord/test/cases/attribute_decorators_test.rb
index 53bd58e22e..2aeb2601c2 100644
--- a/activerecord/test/cases/attribute_decorators_test.rb
+++ b/activerecord/test/cases/attribute_decorators_test.rb
@@ -12,11 +12,11 @@ module ActiveRecord
super(delegate)
end
- def type_cast_from_user(value)
+ def cast(value)
"#{super} #{@decoration}"
end
- alias type_cast_from_database type_cast_from_user
+ alias deserialize cast
end
setup do
@@ -28,7 +28,7 @@ module ActiveRecord
teardown do
return unless @connection
- @connection.drop_table 'attribute_decorators_model' if @connection.table_exists? 'attribute_decorators_model'
+ @connection.drop_table 'attribute_decorators_model', if_exists: true
Model.attribute_type_decorations.clear
Model.reset_column_information
end
@@ -51,7 +51,7 @@ module ActiveRecord
end
test "undecorated columns are not touched" do
- Model.attribute :another_string, Type::String.new, default: 'something or other'
+ Model.attribute :another_string, :string, default: 'something or other'
Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) }
assert_equal 'something or other', Model.new.another_string
@@ -86,7 +86,7 @@ module ActiveRecord
end
test "decorating attributes does not modify parent classes" do
- Model.attribute :another_string, Type::String.new, default: 'whatever'
+ Model.attribute :another_string, :string, default: 'whatever'
Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) }
child_class = Class.new(Model)
child_class.decorate_attribute_type(:another_string, :test) { |t| StringDecorator.new(t) }
@@ -102,15 +102,15 @@ module ActiveRecord
end
class Multiplier < SimpleDelegator
- def type_cast_from_user(value)
+ def cast(value)
return if value.nil?
value * 2
end
- alias type_cast_from_database type_cast_from_user
+ alias deserialize cast
end
test "decorating with a proc" do
- Model.attribute :an_int, Type::Integer.new
+ Model.attribute :an_int, :integer
type_is_integer = proc { |_, type| type.type == :integer }
Model.decorate_matching_attribute_types type_is_integer, :multiplier do |type|
Multiplier.new(type)
diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb
index e38b32d7fc..74e556211b 100644
--- a/activerecord/test/cases/attribute_methods/read_test.rb
+++ b/activerecord/test/cases/attribute_methods/read_test.rb
@@ -17,7 +17,7 @@ module ActiveRecord
include ActiveRecord::AttributeMethods
- def self.column_names
+ def self.attribute_names
%w{ one two three }
end
@@ -25,11 +25,11 @@ module ActiveRecord
end
def self.columns
- column_names.map { FakeColumn.new(name) }
+ attribute_names.map { FakeColumn.new(name) }
end
def self.columns_hash
- Hash[column_names.map { |name|
+ Hash[attribute_names.map { |name|
[name, FakeColumn.new(name)]
}]
end
@@ -39,13 +39,13 @@ module ActiveRecord
def test_define_attribute_methods
instance = @klass.new
- @klass.column_names.each do |name|
+ @klass.attribute_names.each do |name|
assert !instance.methods.map(&:to_s).include?(name)
end
@klass.define_attribute_methods
- @klass.column_names.each do |name|
+ @klass.attribute_names.each do |name|
assert instance.methods.map(&:to_s).include?(name), "#{name} is not defined"
end
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index c3e8ceb0da..ea2b94cbf4 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -1,6 +1,7 @@
require "cases/helper"
require 'models/minimalistic'
require 'models/developer'
+require 'models/computer'
require 'models/auto_id'
require 'models/boolean'
require 'models/computer'
@@ -501,7 +502,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
def test_typecast_attribute_from_select_to_false
Topic.create(:title => 'Budget')
# Oracle does not support boolean expressions in SELECT
- if current_adapter?(:OracleAdapter)
+ if current_adapter?(:OracleAdapter, :FbAdapter)
topic = Topic.all.merge!(:select => "topics.*, 0 as is_test").first
else
topic = Topic.all.merge!(:select => "topics.*, 1=2 as is_test").first
@@ -512,7 +513,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
def test_typecast_attribute_from_select_to_true
Topic.create(:title => 'Budget')
# Oracle does not support boolean expressions in SELECT
- if current_adapter?(:OracleAdapter)
+ if current_adapter?(:OracleAdapter, :FbAdapter)
topic = Topic.all.merge!(:select => "topics.*, 1 as is_test").first
else
topic = Topic.all.merge!(:select => "topics.*, 2=2 as is_test").first
@@ -530,20 +531,6 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_deprecated_cache_attributes
- assert_deprecated do
- Topic.cache_attributes :replies_count
- end
-
- assert_deprecated do
- Topic.cached_attributes
- end
-
- assert_deprecated do
- Topic.cache_attribute? :replies_count
- end
- end
-
def test_converted_values_are_returned_after_assignment
developer = Developer.new(name: 1337, salary: "50000")
@@ -670,7 +657,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_setting_time_zone_aware_attribute_in_current_time_zone
+ def test_setting_time_zone_aware_datetime_in_current_time_zone
utc_time = Time.utc(2008, 1, 1)
in_time_zone "Pacific Time (US & Canada)" do
record = @target.new
@@ -689,6 +676,47 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
+ def test_setting_time_zone_aware_time_in_current_time_zone
+ in_time_zone "Pacific Time (US & Canada)" do
+ record = @target.new
+ time_string = "10:00:00"
+ expected_time = Time.zone.parse("2000-01-01 #{time_string}")
+
+ record.bonus_time = time_string
+ assert_equal expected_time, record.bonus_time
+ assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.bonus_time.time_zone
+
+ record.bonus_time = ''
+ assert_nil record.bonus_time
+ end
+ end
+
+ def test_setting_time_zone_aware_time_with_dst
+ in_time_zone "Pacific Time (US & Canada)" do
+ current_time = Time.zone.local(2014, 06, 15, 10)
+ record = @target.new(bonus_time: current_time)
+ time_before_save = record.bonus_time
+
+ record.save
+ record.reload
+
+ assert_equal time_before_save, record.bonus_time
+ assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.bonus_time.time_zone
+ end
+ end
+
+ def test_removing_time_zone_aware_types
+ with_time_zone_aware_types(:datetime) do
+ in_time_zone "Pacific Time (US & Canada)" do
+ record = @target.new(bonus_time: "10:00:00")
+ expected_time = Time.utc(2000, 01, 01, 10)
+
+ assert_equal expected_time, record.bonus_time
+ assert record.bonus_time.utc?
+ end
+ end
+ end
+
def test_setting_time_zone_conversion_for_attributes_should_write_value_on_class_variable
Topic.skip_time_zone_conversion_for_attributes = [:field_a]
Minimalistic.skip_time_zone_conversion_for_attributes = [:field_b]
@@ -901,6 +929,24 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_not_equal ['id'], @target.column_names
end
+ def test_came_from_user
+ model = @target.first
+
+ assert_not model.id_came_from_user?
+ model.id = "omg"
+ assert model.id_came_from_user?
+ end
+
+ def test_accessed_fields
+ model = @target.first
+
+ assert_equal [], model.accessed_fields
+
+ model.title
+
+ assert_equal ["title"], model.accessed_fields
+ end
+
private
def new_topic_like_ar_class(&block)
@@ -913,6 +959,14 @@ class AttributeMethodsTest < ActiveRecord::TestCase
klass
end
+ def with_time_zone_aware_types(*types)
+ old_types = ActiveRecord::Base.time_zone_aware_types
+ ActiveRecord::Base.time_zone_aware_types = types
+ yield
+ ensure
+ ActiveRecord::Base.time_zone_aware_types = old_types
+ end
+
def cached_columns
Topic.columns.map(&:name)
end
diff --git a/activerecord/test/cases/attribute_set_test.rb b/activerecord/test/cases/attribute_set_test.rb
index dc20c3c676..9d927481ec 100644
--- a/activerecord/test/cases/attribute_set_test.rb
+++ b/activerecord/test/cases/attribute_set_test.rb
@@ -65,6 +65,16 @@ module ActiveRecord
assert_equal({ foo: 1, bar: 2.2 }, attributes.to_h)
end
+ test "to_hash maintains order" do
+ builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new)
+ attributes = builder.build_from_database(foo: '2.2', bar: '3.3')
+
+ attributes[:bar]
+ hash = attributes.to_h
+
+ assert_equal [[:foo, 2], [:bar, 3.3]], hash.to_a
+ end
+
test "values_before_type_cast" do
builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Integer.new)
attributes = builder.build_from_database(foo: '1.1', bar: '2.2')
@@ -109,7 +119,15 @@ module ActiveRecord
test "fetch_value returns nil for unknown attributes" do
attributes = attributes_with_uninitialized_key
- assert_nil attributes.fetch_value(:wibble)
+ assert_nil attributes.fetch_value(:wibble) { "hello" }
+ end
+
+ test "fetch_value returns nil for unknown attributes when types has a default" do
+ types = Hash.new(Type::Value.new)
+ builder = AttributeSet::Builder.new(types)
+ attributes = builder.build_from_database
+
+ assert_nil attributes.fetch_value(:wibble) { "hello" }
end
test "fetch_value uses the given block for uninitialized attributes" do
@@ -123,13 +141,22 @@ module ActiveRecord
assert_nil attributes.fetch_value(:bar)
end
+ test "the primary_key is always initialized" do
+ builder = AttributeSet::Builder.new({ foo: Type::Integer.new }, :foo)
+ attributes = builder.build_from_database
+
+ assert attributes.key?(:foo)
+ assert_equal [:foo], attributes.keys
+ assert attributes[:foo].initialized?
+ end
+
class MyType
- def type_cast_from_user(value)
+ def cast(value)
return if value.nil?
value + " from user"
end
- def type_cast_from_database(value)
+ def deserialize(value)
return if value.nil?
value + " from database"
end
@@ -161,5 +188,24 @@ module ActiveRecord
builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new)
builder.build_from_database(foo: '1.1')
end
+
+ test "freezing doesn't prevent the set from materializing" do
+ builder = AttributeSet::Builder.new(foo: Type::String.new)
+ attributes = builder.build_from_database(foo: "1")
+
+ attributes.freeze
+ assert_equal({ foo: "1" }, attributes.to_hash)
+ end
+
+ test "#accessed_attributes returns only attributes which have been read" do
+ builder = AttributeSet::Builder.new(foo: Type::Value.new, bar: Type::Value.new)
+ attributes = builder.build_from_database(foo: "1", bar: "2")
+
+ assert_equal [], attributes.accessed
+
+ attributes.fetch_value(:foo)
+
+ assert_equal [:foo], attributes.accessed
+ end
end
end
diff --git a/activerecord/test/cases/attribute_test.rb b/activerecord/test/cases/attribute_test.rb
index 7b325abf1d..aa419c7a67 100644
--- a/activerecord/test/cases/attribute_test.rb
+++ b/activerecord/test/cases/attribute_test.rb
@@ -5,6 +5,7 @@ module ActiveRecord
class AttributeTest < ActiveRecord::TestCase
setup do
@type = Minitest::Mock.new
+ @type.expect(:==, false, [false])
end
teardown do
@@ -12,7 +13,7 @@ module ActiveRecord
end
test "from_database + read type casts from database" do
- @type.expect(:type_cast_from_database, 'type cast from database', ['a value'])
+ @type.expect(:deserialize, 'type cast from database', ['a value'])
attribute = Attribute.from_database(nil, 'a value', @type)
type_cast_value = attribute.value
@@ -21,7 +22,7 @@ module ActiveRecord
end
test "from_user + read type casts from user" do
- @type.expect(:type_cast_from_user, 'type cast from user', ['a value'])
+ @type.expect(:cast, 'type cast from user', ['a value'])
attribute = Attribute.from_user(nil, 'a value', @type)
type_cast_value = attribute.value
@@ -30,7 +31,7 @@ module ActiveRecord
end
test "reading memoizes the value" do
- @type.expect(:type_cast_from_database, 'from the database', ['whatever'])
+ @type.expect(:deserialize, 'from the database', ['whatever'])
attribute = Attribute.from_database(nil, 'whatever', @type)
type_cast_value = attribute.value
@@ -41,7 +42,7 @@ module ActiveRecord
end
test "reading memoizes falsy values" do
- @type.expect(:type_cast_from_database, false, ['whatever'])
+ @type.expect(:deserialize, false, ['whatever'])
attribute = Attribute.from_database(nil, 'whatever', @type)
attribute.value
@@ -57,27 +58,27 @@ module ActiveRecord
end
test "from_database + read_for_database type casts to and from database" do
- @type.expect(:type_cast_from_database, 'read from database', ['whatever'])
- @type.expect(:type_cast_for_database, 'ready for database', ['read from database'])
+ @type.expect(:deserialize, 'read from database', ['whatever'])
+ @type.expect(:serialize, 'ready for database', ['read from database'])
attribute = Attribute.from_database(nil, 'whatever', @type)
- type_cast_for_database = attribute.value_for_database
+ serialize = attribute.value_for_database
- assert_equal 'ready for database', type_cast_for_database
+ assert_equal 'ready for database', serialize
end
test "from_user + read_for_database type casts from the user to the database" do
- @type.expect(:type_cast_from_user, 'read from user', ['whatever'])
- @type.expect(:type_cast_for_database, 'ready for database', ['read from user'])
+ @type.expect(:cast, 'read from user', ['whatever'])
+ @type.expect(:serialize, 'ready for database', ['read from user'])
attribute = Attribute.from_user(nil, 'whatever', @type)
- type_cast_for_database = attribute.value_for_database
+ serialize = attribute.value_for_database
- assert_equal 'ready for database', type_cast_for_database
+ assert_equal 'ready for database', serialize
end
test "duping dups the value" do
- @type.expect(:type_cast_from_database, 'type cast', ['a value'])
+ @type.expect(:deserialize, 'type cast', ['a value'])
attribute = Attribute.from_database(nil, 'a value', @type)
value_from_orig = attribute.value
@@ -89,7 +90,7 @@ module ActiveRecord
end
test "duping does not dup the value if it is not dupable" do
- @type.expect(:type_cast_from_database, false, ['a value'])
+ @type.expect(:deserialize, false, ['a value'])
attribute = Attribute.from_database(nil, 'a value', @type)
assert_same attribute.value, attribute.dup.value
@@ -101,11 +102,11 @@ module ActiveRecord
end
class MyType
- def type_cast_from_user(value)
+ def cast(value)
value + " from user"
end
- def type_cast_from_database(value)
+ def deserialize(value)
value + " from database"
end
end
@@ -168,5 +169,24 @@ module ActiveRecord
second = Attribute.from_user(:foo, 1, Type::Integer.new)
assert_not_equal first, second
end
+
+ test "an attribute has not been read by default" do
+ attribute = Attribute.from_database(:foo, 1, Type::Value.new)
+ assert_not attribute.has_been_read?
+ end
+
+ test "an attribute has been read when its value is calculated" do
+ attribute = Attribute.from_database(:foo, 1, Type::Value.new)
+ attribute.value
+ assert attribute.has_been_read?
+ end
+
+ test "an attribute can not be mutated if it has not been read,
+ and skips expensive calculations" do
+ type_which_raises_from_all_methods = Object.new
+ attribute = Attribute.from_database(:foo, "bar", type_which_raises_from_all_methods)
+
+ assert_not attribute.changed_in_place_from?("bar")
+ end
end
end
diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb
index dbe1eb48db..927d7950a5 100644
--- a/activerecord/test/cases/attributes_test.rb
+++ b/activerecord/test/cases/attributes_test.rb
@@ -1,17 +1,17 @@
require 'cases/helper'
class OverloadedType < ActiveRecord::Base
- attribute :overloaded_float, Type::Integer.new
- attribute :overloaded_string_with_limit, Type::String.new(limit: 50)
- attribute :non_existent_decimal, Type::Decimal.new
- attribute :string_with_default, Type::String.new, default: 'the overloaded default'
+ attribute :overloaded_float, :integer
+ attribute :overloaded_string_with_limit, :string, limit: 50
+ attribute :non_existent_decimal, :decimal
+ attribute :string_with_default, :string, default: 'the overloaded default'
end
class ChildOfOverloadedType < OverloadedType
end
class GrandchildOfOverloadedType < ChildOfOverloadedType
- attribute :overloaded_float, Type::Float.new
+ attribute :overloaded_float, :float
end
class UnoverloadedType < ActiveRecord::Base
@@ -50,8 +50,8 @@ module ActiveRecord
end
test "overloaded properties with limit" do
- assert_equal 50, OverloadedType.columns_hash['overloaded_string_with_limit'].limit
- assert_equal 255, UnoverloadedType.columns_hash['overloaded_string_with_limit'].limit
+ assert_equal 50, OverloadedType.type_for_attribute('overloaded_string_with_limit').limit
+ assert_equal 255, UnoverloadedType.type_for_attribute('overloaded_string_with_limit').limit
end
test "nonexistent attribute" do
@@ -87,29 +87,74 @@ module ActiveRecord
assert_equal 4.4, data.overloaded_float
end
- test "overloading properties does not change column order" do
- column_names = OverloadedType.column_names
- assert_equal %w(id overloaded_float unoverloaded_float overloaded_string_with_limit string_with_default non_existent_decimal), column_names
+ test "overloading properties does not attribute method order" do
+ attribute_names = OverloadedType.attribute_names
+ assert_equal %w(id overloaded_float unoverloaded_float overloaded_string_with_limit string_with_default non_existent_decimal), attribute_names
end
test "caches are cleared" do
klass = Class.new(OverloadedType)
- assert_equal 6, klass.columns.length
- assert_not klass.columns_hash.key?('wibble')
- assert_equal 6, klass.column_types.length
+ assert_equal 6, klass.attribute_types.length
assert_equal 6, klass.column_defaults.length
- assert_not klass.column_names.include?('wibble')
- assert_equal 5, klass.content_columns.length
+ assert_not klass.attribute_types.include?('wibble')
klass.attribute :wibble, Type::Value.new
- assert_equal 7, klass.columns.length
- assert klass.columns_hash.key?('wibble')
- assert_equal 7, klass.column_types.length
+ assert_equal 7, klass.attribute_types.length
assert_equal 7, klass.column_defaults.length
- assert klass.column_names.include?('wibble')
- assert_equal 6, klass.content_columns.length
+ assert klass.attribute_types.include?('wibble')
+ end
+
+ test "the given default value is cast from user" do
+ custom_type = Class.new(Type::Value) do
+ def cast(*)
+ "from user"
+ end
+
+ def deserialize(*)
+ "from database"
+ end
+ end
+
+ klass = Class.new(OverloadedType) do
+ attribute :wibble, custom_type.new, default: "default"
+ end
+ model = klass.new
+
+ assert_equal "from user", model.wibble
+ end
+
+ if current_adapter?(:PostgreSQLAdapter)
+ test "arrays 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
+ end
+
+ string_array = ConnectionAdapters::PostgreSQL::OID::Array.new(
+ Type::String.new(limit: 50))
+ int_array = ConnectionAdapters::PostgreSQL::OID::Array.new(
+ Type::Integer.new)
+ refute_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
+
+ test "range types can be specified" do
+ klass = Class.new(OverloadedType) do
+ attribute :my_range, :string, limit: 50, range: true
+ attribute :my_int_range, :integer, range: true
+ end
+
+ string_range = ConnectionAdapters::PostgreSQL::OID::Range.new(
+ Type::String.new(limit: 50))
+ int_range = ConnectionAdapters::PostgreSQL::OID::Range.new(
+ Type::Integer.new)
+ refute_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
end
end
end
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 734fd5fe18..8f0d7bd037 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -4,6 +4,7 @@ require 'models/comment'
require 'models/company'
require 'models/customer'
require 'models/developer'
+require 'models/computer'
require 'models/invoice'
require 'models/line_item'
require 'models/order'
@@ -628,7 +629,7 @@ class TestDefaultAutosaveAssociationOnNewRecord < ActiveRecord::TestCase
end
class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
setup do
@pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
@@ -636,7 +637,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
end
teardown do
- # We are running without transactional fixtures and need to cleanup.
+ # We are running without transactional tests and need to cleanup.
Bird.delete_all
Parrot.delete_all
@ship.delete
@@ -773,13 +774,13 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
def test_should_destroy_has_many_as_part_of_the_save_transaction_if_they_were_marked_for_destruction
2.times { |i| @pirate.birds.create!(:name => "birds_#{i}") }
- assert !@pirate.birds.any? { |child| child.marked_for_destruction? }
+ assert !@pirate.birds.any?(&:marked_for_destruction?)
- @pirate.birds.each { |child| child.mark_for_destruction }
+ @pirate.birds.each(&:mark_for_destruction)
klass = @pirate.birds.first.class
ids = @pirate.birds.map(&:id)
- assert @pirate.birds.all? { |child| child.marked_for_destruction? }
+ assert @pirate.birds.all?(&:marked_for_destruction?)
ids.each { |id| assert klass.find_by_id(id) }
@pirate.save
@@ -813,14 +814,14 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
@pirate.birds.each { |bird| bird.name = '' }
assert !@pirate.valid?
- @pirate.birds.each { |bird| bird.destroy }
+ @pirate.birds.each(&:destroy)
assert @pirate.valid?
end
def test_a_child_marked_for_destruction_should_not_be_destroyed_twice_while_saving_has_many
@pirate.birds.create!(:name => "birds_1")
- @pirate.birds.each { |bird| bird.mark_for_destruction }
+ @pirate.birds.each(&:mark_for_destruction)
assert @pirate.save
@pirate.birds.each { |bird| bird.expects(:destroy).never }
@@ -887,7 +888,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
association_name_with_callbacks = "birds_with_#{callback_type}_callbacks"
@pirate.send(association_name_with_callbacks).create!(:name => "Crowe the One-Eyed")
- @pirate.send(association_name_with_callbacks).each { |c| c.mark_for_destruction }
+ @pirate.send(association_name_with_callbacks).each(&:mark_for_destruction)
child_id = @pirate.send(association_name_with_callbacks).first.id
@pirate.ship_log.clear
@@ -905,8 +906,8 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
def test_should_destroy_habtm_as_part_of_the_save_transaction_if_they_were_marked_for_destruction
2.times { |i| @pirate.parrots.create!(:name => "parrots_#{i}") }
- assert !@pirate.parrots.any? { |parrot| parrot.marked_for_destruction? }
- @pirate.parrots.each { |parrot| parrot.mark_for_destruction }
+ assert !@pirate.parrots.any?(&:marked_for_destruction?)
+ @pirate.parrots.each(&:mark_for_destruction)
assert_no_difference "Parrot.count" do
@pirate.save
@@ -939,14 +940,14 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
@pirate.parrots.each { |parrot| parrot.name = '' }
assert !@pirate.valid?
- @pirate.parrots.each { |parrot| parrot.destroy }
+ @pirate.parrots.each(&:destroy)
assert @pirate.valid?
end
def test_a_child_marked_for_destruction_should_not_be_destroyed_twice_while_saving_habtm
@pirate.parrots.create!(:name => "parrots_1")
- @pirate.parrots.each { |parrot| parrot.mark_for_destruction }
+ @pirate.parrots.each(&:mark_for_destruction)
assert @pirate.save
Pirate.transaction do
@@ -991,7 +992,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
association_name_with_callbacks = "parrots_with_#{callback_type}_callbacks"
@pirate.send(association_name_with_callbacks).create!(:name => "Crowe the One-Eyed")
- @pirate.send(association_name_with_callbacks).each { |c| c.mark_for_destruction }
+ @pirate.send(association_name_with_callbacks).each(&:mark_for_destruction)
child_id = @pirate.send(association_name_with_callbacks).first.id
@pirate.ship_log.clear
@@ -1008,7 +1009,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
end
class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
- self.use_transactional_fixtures = false unless supports_savepoints?
+ self.use_transactional_tests = false unless supports_savepoints?
def setup
super
@@ -1029,6 +1030,16 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
assert_equal 'The Vile Insanity', @pirate.reload.ship.name
end
+ def test_changed_for_autosave_should_handle_cycles
+ @ship.pirate = @pirate
+ assert_queries(0) { @ship.save! }
+
+ @parrot = @pirate.parrots.create(name: "some_name")
+ @parrot.name="changed_name"
+ assert_queries(1) { @ship.save! }
+ assert_queries(0) { @ship.save! }
+ end
+
def test_should_automatically_save_bang_the_associated_model
@pirate.ship.name = 'The Vile Insanity'
@pirate.save!
@@ -1050,11 +1061,16 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
end
def test_should_not_ignore_different_error_messages_on_the_same_attribute
+ old_validators = Ship._validators.deep_dup
+ old_callbacks = Ship._validate_callbacks.deep_dup
Ship.validates_format_of :name, :with => /\w/
@pirate.ship.name = ""
@pirate.catchphrase = nil
assert @pirate.invalid?
assert_equal ["can't be blank", "is invalid"], @pirate.errors[:"ship.name"]
+ ensure
+ Ship._validators = old_validators if old_validators
+ Ship._validate_callbacks = old_callbacks if old_callbacks
end
def test_should_still_allow_to_bypass_validations_on_the_associated_model
@@ -1129,7 +1145,7 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
end
class TestAutosaveAssociationOnAHasOneThroughAssociation < ActiveRecord::TestCase
- self.use_transactional_fixtures = false unless supports_savepoints?
+ self.use_transactional_tests = false unless supports_savepoints?
def setup
super
@@ -1150,7 +1166,7 @@ class TestAutosaveAssociationOnAHasOneThroughAssociation < ActiveRecord::TestCas
end
class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
- self.use_transactional_fixtures = false unless supports_savepoints?
+ self.use_transactional_tests = false unless supports_savepoints?
def setup
super
@@ -1398,7 +1414,7 @@ module AutosaveAssociationOnACollectionAssociationTests
end
class TestAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase
- self.use_transactional_fixtures = false unless supports_savepoints?
+ self.use_transactional_tests = false unless supports_savepoints?
def setup
super
@@ -1414,7 +1430,7 @@ class TestAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase
end
class TestAutosaveAssociationOnAHasAndBelongsToManyAssociation < ActiveRecord::TestCase
- self.use_transactional_fixtures = false unless supports_savepoints?
+ self.use_transactional_tests = false unless supports_savepoints?
def setup
super
@@ -1431,7 +1447,7 @@ class TestAutosaveAssociationOnAHasAndBelongsToManyAssociation < ActiveRecord::T
end
class TestAutosaveAssociationOnAHasAndBelongsToManyAssociationWithAcceptsNestedAttributes < ActiveRecord::TestCase
- self.use_transactional_fixtures = false unless supports_savepoints?
+ self.use_transactional_tests = false unless supports_savepoints?
def setup
super
@@ -1448,7 +1464,7 @@ class TestAutosaveAssociationOnAHasAndBelongsToManyAssociationWithAcceptsNestedA
end
class TestAutosaveAssociationValidationsOnAHasManyAssociation < ActiveRecord::TestCase
- self.use_transactional_fixtures = false unless supports_savepoints?
+ self.use_transactional_tests = false unless supports_savepoints?
def setup
super
@@ -1465,7 +1481,7 @@ class TestAutosaveAssociationValidationsOnAHasManyAssociation < ActiveRecord::Te
end
class TestAutosaveAssociationValidationsOnAHasOneAssociation < ActiveRecord::TestCase
- self.use_transactional_fixtures = false unless supports_savepoints?
+ self.use_transactional_tests = false unless supports_savepoints?
def setup
super
@@ -1488,7 +1504,7 @@ class TestAutosaveAssociationValidationsOnAHasOneAssociation < ActiveRecord::Tes
end
class TestAutosaveAssociationValidationsOnABelongsToAssociation < ActiveRecord::TestCase
- self.use_transactional_fixtures = false unless supports_savepoints?
+ self.use_transactional_tests = false unless supports_savepoints?
def setup
super
@@ -1509,7 +1525,7 @@ class TestAutosaveAssociationValidationsOnABelongsToAssociation < ActiveRecord::
end
class TestAutosaveAssociationValidationsOnAHABTMAssociation < ActiveRecord::TestCase
- self.use_transactional_fixtures = false unless supports_savepoints?
+ self.use_transactional_tests = false unless supports_savepoints?
def setup
super
@@ -1532,7 +1548,7 @@ class TestAutosaveAssociationValidationsOnAHABTMAssociation < ActiveRecord::Test
end
class TestAutosaveAssociationValidationMethodsGeneration < ActiveRecord::TestCase
- self.use_transactional_fixtures = false unless supports_savepoints?
+ self.use_transactional_tests = false unless supports_savepoints?
def setup
super
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 3675401555..4306738670 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1,4 +1,4 @@
-# encoding: utf-8
+# -*- coding: utf-8 -*-
require "cases/helper"
require 'active_support/concurrency/latch'
@@ -10,6 +10,7 @@ require 'models/category'
require 'models/company'
require 'models/customer'
require 'models/developer'
+require 'models/computer'
require 'models/project'
require 'models/default'
require 'models/auto_id'
@@ -87,6 +88,7 @@ class BasicsTest < ActiveRecord::TestCase
'Mysql2Adapter' => '`',
'PostgreSQLAdapter' => '"',
'OracleAdapter' => '"',
+ 'FbAdapter' => '"'
}.fetch(classname) {
raise "need a bad char for #{classname}"
}
@@ -110,7 +112,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_nil Edge.primary_key
end
- unless current_adapter?(:PostgreSQLAdapter, :OracleAdapter, :SQLServerAdapter)
+ unless current_adapter?(:PostgreSQLAdapter, :OracleAdapter, :SQLServerAdapter, :FbAdapter)
def test_limit_with_comma
assert Topic.limit("1,2").to_a
end
@@ -811,7 +813,6 @@ class BasicsTest < ActiveRecord::TestCase
def test_dup_does_not_copy_associations
author = authors(:david)
assert_not_equal [], author.posts
- author.send(:clear_association_cache)
author_dup = author.dup
assert_equal [], author_dup.posts
@@ -897,100 +898,13 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal 'a text field', default.char3
end
end
-
- class Geometric < ActiveRecord::Base; end
- def test_geometric_content
-
- # accepted format notes:
- # ()'s aren't required
- # values can be a mix of float or integer
-
- g = Geometric.new(
- :a_point => '(5.0, 6.1)',
- #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql
- :a_line_segment => '(2.0, 3), (5.5, 7.0)',
- :a_box => '2.0, 3, 5.5, 7.0',
- :a_path => '[(2.0, 3), (5.5, 7.0), (8.5, 11.0)]', # [ ] is an open path
- :a_polygon => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))',
- :a_circle => '<(5.3, 10.4), 2>'
- )
-
- assert g.save
-
- # Reload and check that we have all the geometric attributes.
- h = Geometric.find(g.id)
-
- assert_equal [5.0, 6.1], h.a_point
- assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
- assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
- assert_equal '[(2,3),(5.5,7),(8.5,11)]', h.a_path
- assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
- assert_equal '<(5.3,10.4),2>', h.a_circle
-
- # use a geometric function to test for an open path
- objs = Geometric.find_by_sql ["select isopen(a_path) from geometrics where id = ?", g.id]
-
- assert_equal true, objs[0].isopen
-
- # test alternate formats when defining the geometric types
-
- g = Geometric.new(
- :a_point => '5.0, 6.1',
- #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql
- :a_line_segment => '((2.0, 3), (5.5, 7.0))',
- :a_box => '(2.0, 3), (5.5, 7.0)',
- :a_path => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))', # ( ) is a closed path
- :a_polygon => '2.0, 3, 5.5, 7.0, 8.5, 11.0',
- :a_circle => '((5.3, 10.4), 2)'
- )
-
- assert g.save
-
- # Reload and check that we have all the geometric attributes.
- h = Geometric.find(g.id)
-
- assert_equal [5.0, 6.1], h.a_point
- assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
- assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
- assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_path
- assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
- assert_equal '<(5.3,10.4),2>', h.a_circle
-
- # use a geometric function to test for an closed path
- objs = Geometric.find_by_sql ["select isclosed(a_path) from geometrics where id = ?", g.id]
-
- assert_equal true, objs[0].isclosed
-
- # test native ruby formats when defining the geometric types
- g = Geometric.new(
- :a_point => [5.0, 6.1],
- #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql
- :a_line_segment => '((2.0, 3), (5.5, 7.0))',
- :a_box => '(2.0, 3), (5.5, 7.0)',
- :a_path => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))', # ( ) is a closed path
- :a_polygon => '2.0, 3, 5.5, 7.0, 8.5, 11.0',
- :a_circle => '((5.3, 10.4), 2)'
- )
-
- assert g.save
-
- # Reload and check that we have all the geometric attributes.
- h = Geometric.find(g.id)
-
- assert_equal [5.0, 6.1], h.a_point
- assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
- assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
- assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_path
- assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
- assert_equal '<(5.3,10.4),2>', h.a_circle
- end
end
class NumericData < ActiveRecord::Base
self.table_name = 'numeric_data'
- attribute :my_house_population, Type::Integer.new
- attribute :atoms_in_universe, Type::Integer.new
+ attribute :my_house_population, :integer
+ attribute :atoms_in_universe, :integer
end
def test_big_decimal_conditions
@@ -1095,54 +1009,61 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_switching_between_table_name
+ k = Class.new(Joke)
+
assert_difference("GoodJoke.count") do
- Joke.table_name = "cold_jokes"
- Joke.create
+ k.table_name = "cold_jokes"
+ k.create
- Joke.table_name = "funny_jokes"
- Joke.create
+ k.table_name = "funny_jokes"
+ k.create
end
end
def test_clear_cash_when_setting_table_name
- Joke.table_name = "cold_jokes"
- before_columns = Joke.columns
- before_seq = Joke.sequence_name
+ original_table_name = Joke.table_name
Joke.table_name = "funny_jokes"
+ before_columns = Joke.columns
+ before_seq = Joke.sequence_name
+
+ Joke.table_name = "cold_jokes"
after_columns = Joke.columns
- after_seq = Joke.sequence_name
+ after_seq = Joke.sequence_name
assert_not_equal before_columns, after_columns
assert_not_equal before_seq, after_seq unless before_seq.nil? && after_seq.nil?
+ ensure
+ Joke.table_name = original_table_name
end
def test_dont_clear_sequence_name_when_setting_explicitly
- Joke.sequence_name = "black_jokes_seq"
- Joke.table_name = "cold_jokes"
- before_seq = Joke.sequence_name
+ k = Class.new(Joke)
+ k.sequence_name = "black_jokes_seq"
+ k.table_name = "cold_jokes"
+ before_seq = k.sequence_name
- Joke.table_name = "funny_jokes"
- after_seq = Joke.sequence_name
+ k.table_name = "funny_jokes"
+ after_seq = k.sequence_name
assert_equal before_seq, after_seq unless before_seq.nil? && after_seq.nil?
- ensure
- Joke.reset_sequence_name
end
def test_dont_clear_inheritance_column_when_setting_explicitly
- Joke.inheritance_column = "my_type"
- before_inherit = Joke.inheritance_column
+ k = Class.new(Joke)
+ k.inheritance_column = "my_type"
+ before_inherit = k.inheritance_column
- Joke.reset_column_information
- after_inherit = Joke.inheritance_column
+ k.reset_column_information
+ after_inherit = k.inheritance_column
assert_equal before_inherit, after_inherit unless before_inherit.blank? && after_inherit.blank?
end
def test_set_table_name_symbol_converted_to_string
- Joke.table_name = :cold_jokes
- assert_equal 'cold_jokes', Joke.table_name
+ k = Class.new(Joke)
+ k.table_name = :cold_jokes
+ assert_equal 'cold_jokes', k.table_name
end
def test_quoted_table_name_after_set_table_name
@@ -1513,7 +1434,7 @@ class BasicsTest < ActiveRecord::TestCase
attrs.delete 'id'
typecast = Class.new(ActiveRecord::Type::Value) {
- def type_cast value
+ def cast value
"t.lo"
end
}
@@ -1613,4 +1534,14 @@ class BasicsTest < ActiveRecord::TestCase
test "records without an id have unique hashes" do
assert_not_equal Post.new.hash, Post.new.hash
end
+
+ test "resetting column information doesn't remove attribute methods" do
+ topic = topics(:first)
+
+ assert_not topic.id_changed?
+
+ Topic.reset_column_information
+
+ assert_not topic.id_changed?
+ end
end
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index c12fa03015..0791dde1f2 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -37,9 +37,9 @@ class EachTest < ActiveRecord::TestCase
if Enumerator.method_defined? :size
def test_each_should_return_a_sized_enumerator
- assert_equal 11, Post.find_each(:batch_size => 1).size
- assert_equal 5, Post.find_each(:batch_size => 2, :start => 7).size
- assert_equal 11, Post.find_each(:batch_size => 10_000).size
+ assert_equal 11, Post.find_each(batch_size: 1).size
+ assert_equal 5, Post.find_each(batch_size: 2, begin_at: 7).size
+ assert_equal 11, Post.find_each(batch_size: 10_000).size
end
end
@@ -99,7 +99,16 @@ class EachTest < ActiveRecord::TestCase
def test_find_in_batches_should_start_from_the_start_option
assert_queries(@total) do
- Post.find_in_batches(:batch_size => 1, :start => 2) do |batch|
+ Post.find_in_batches(batch_size: 1, begin_at: 2) do |batch|
+ assert_kind_of Array, batch
+ assert_kind_of Post, batch.first
+ end
+ end
+ end
+
+ def test_find_in_batches_should_end_at_the_end_option
+ assert_queries(6) do
+ Post.find_in_batches(batch_size: 1, end_at: 5) do |batch|
assert_kind_of Array, batch
assert_kind_of Post, batch.first
end
@@ -163,7 +172,7 @@ class EachTest < ActiveRecord::TestCase
def test_find_in_batches_should_not_modify_passed_options
assert_nothing_raised do
- Post.find_in_batches({ batch_size: 42, start: 1 }.freeze){}
+ Post.find_in_batches({ batch_size: 42, begin_at: 1 }.freeze){}
end
end
@@ -172,7 +181,7 @@ class EachTest < ActiveRecord::TestCase
start_nick = nick_order_subscribers.second.nick
subscribers = []
- Subscriber.find_in_batches(:batch_size => 1, :start => start_nick) do |batch|
+ Subscriber.find_in_batches(batch_size: 1, begin_at: start_nick) do |batch|
subscribers.concat(batch)
end
@@ -181,8 +190,9 @@ class EachTest < ActiveRecord::TestCase
def test_find_in_batches_should_use_any_column_as_primary_key_when_start_is_not_specified
assert_queries(Subscriber.count + 1) do
- Subscriber.find_each(:batch_size => 1) do |subscriber|
- assert_kind_of Subscriber, subscriber
+ Subscriber.find_in_batches(batch_size: 1) do |batch|
+ assert_kind_of Array, batch
+ assert_kind_of Subscriber, batch.first
end
end
end
@@ -200,11 +210,32 @@ class EachTest < ActiveRecord::TestCase
end
end
+ def test_find_in_batches_start_deprecated
+ assert_deprecated do
+ assert_queries(@total) do
+ Post.find_in_batches(batch_size: 1, start: 2) do |batch|
+ assert_kind_of Array, batch
+ assert_kind_of Post, batch.first
+ end
+ end
+ end
+ end
+
+ def test_find_each_start_deprecated
+ assert_deprecated do
+ assert_queries(@total) do
+ Post.find_each(batch_size: 1, start: 2) do |post|
+ assert_kind_of Post, post
+ end
+ end
+ end
+ end
+
if Enumerator.method_defined? :size
def test_find_in_batches_should_return_a_sized_enumerator
assert_equal 11, Post.find_in_batches(:batch_size => 1).size
assert_equal 6, Post.find_in_batches(:batch_size => 2).size
- assert_equal 4, Post.find_in_batches(:batch_size => 2, :start => 4).size
+ assert_equal 4, Post.find_in_batches(batch_size: 2, begin_at: 4).size
assert_equal 4, Post.find_in_batches(:batch_size => 3).size
assert_equal 1, Post.find_in_batches(:batch_size => 10_000).size
end
diff --git a/activerecord/test/cases/binary_test.rb b/activerecord/test/cases/binary_test.rb
index ccf2be369d..86dee929bf 100644
--- a/activerecord/test/cases/binary_test.rb
+++ b/activerecord/test/cases/binary_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require "cases/helper"
# Without using prepared statements, it makes no sense to test
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 0bc7ee6d64..1e38b97c4a 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -1,9 +1,11 @@
require 'cases/helper'
require 'models/topic'
+require 'models/author'
+require 'models/post'
module ActiveRecord
class BindParameterTest < ActiveRecord::TestCase
- fixtures :topics
+ fixtures :topics, :authors, :posts
class LogListener
attr_accessor :calls
@@ -20,8 +22,8 @@ module ActiveRecord
def setup
super
@connection = ActiveRecord::Base.connection
- @subscriber = LogListener.new
- @pk = Topic.columns_hash[Topic.primary_key]
+ @subscriber = LogListener.new
+ @pk = Topic.columns_hash[Topic.primary_key]
@subscription = ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber)
end
@@ -30,40 +32,34 @@ module ActiveRecord
end
if ActiveRecord::Base.connection.supports_statement_cache?
- def test_binds_are_logged
- sub = @connection.substitute_at(@pk, 0)
- binds = [[@pk, 1]]
- sql = "select * from topics where id = #{sub}"
-
- @connection.exec_query(sql, 'SQL', binds)
-
- message = @subscriber.calls.find { |args| args[4][:sql] == sql }
- assert_equal binds, message[4][:binds]
+ def test_bind_from_join_in_subquery
+ subquery = Author.joins(:thinking_posts).where(name: 'David')
+ scope = Author.from(subquery, 'authors').where(id: 1)
+ assert_equal 1, scope.count
end
- def test_binds_are_logged_after_type_cast
- sub = @connection.substitute_at(@pk, 0)
- binds = [[@pk, "3"]]
- sql = "select * from topics where id = #{sub}"
+ def test_binds_are_logged
+ sub = @connection.substitute_at(@pk)
+ binds = [Relation::QueryAttribute.new("id", 1, Type::Value.new)]
+ sql = "select * from topics where id = #{sub.to_sql}"
@connection.exec_query(sql, 'SQL', binds)
message = @subscriber.calls.find { |args| args[4][:sql] == sql }
- assert_equal [[@pk, 3]], message[4][:binds]
+ assert_equal binds, message[4][:binds]
end
def test_find_one_uses_binds
Topic.find(1)
- binds = [[@pk, 1]]
- message = @subscriber.calls.find { |args| args[4][:binds] == binds }
+ message = @subscriber.calls.find { |args| args[4][:binds].any? { |attr| attr.value == 1 } }
assert message, 'expected a message with binds'
end
- def test_logs_bind_vars
+ def test_logs_bind_vars_after_type_cast
payload = {
:name => 'SQL',
:sql => 'select * from topics where id = ?',
- :binds => [[@pk, 10]]
+ :binds => [Relation::QueryAttribute.new("id", "10", Type::Integer.new)]
}
event = ActiveSupport::Notifications::Event.new(
'foo',
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index e886268a72..8fc996ee74 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -10,13 +10,18 @@ require 'models/reply'
require 'models/minivan'
require 'models/speedometer'
require 'models/ship_part'
+require 'models/treasure'
+require 'models/developer'
+require 'models/comment'
+require 'models/rating'
+require 'models/post'
class NumericData < ActiveRecord::Base
self.table_name = 'numeric_data'
- attribute :world_population, Type::Integer.new
- attribute :my_house_population, Type::Integer.new
- attribute :atoms_in_universe, Type::Integer.new
+ attribute :world_population, :integer
+ attribute :my_house_population, :integer
+ attribute :atoms_in_universe, :integer
end
class CalculationsTest < ActiveRecord::TestCase
@@ -609,4 +614,37 @@ class CalculationsTest < ActiveRecord::TestCase
.pluck('topics.title', 'replies_topics.title')
assert_equal expected, actual
end
+
+ def test_calculation_with_polymorphic_relation
+ part = ShipPart.create!(name: "has trinket")
+ part.trinkets.create!
+
+ assert_equal part.id, ShipPart.joins(:trinkets).sum(:id)
+ end
+
+ def test_pluck_joined_with_polymorphic_relation
+ part = ShipPart.create!(name: "has trinket")
+ part.trinkets.create!
+
+ assert_equal [part.id], ShipPart.joins(:trinkets).pluck(:id)
+ end
+
+ def test_grouped_calculation_with_polymorphic_relation
+ part = ShipPart.create!(name: "has trinket")
+ part.trinkets.create!
+
+ assert_equal({ "has trinket" => part.id }, ShipPart.joins(:trinkets).group("ship_parts.name").sum(:id))
+ end
+
+ def test_calculation_grouped_by_association_doesnt_error_when_no_records_have_association
+ Client.update_all(client_of: nil)
+ assert_equal({ nil => Client.count }, Client.group(:firm).count)
+ end
+
+ def test_should_reference_correct_aliases_while_joining_tables_of_has_many_through_association
+ assert_nothing_raised ActiveRecord::StatementInvalid do
+ developer = Developer.create!(name: 'developer')
+ developer.ratings.includes(comment: :post).where(posts: { id: 1 }).count
+ end
+ end
end
diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb
index c8f56e3c73..3ae4a6eade 100644
--- a/activerecord/test/cases/callbacks_test.rb
+++ b/activerecord/test/cases/callbacks_test.rb
@@ -1,4 +1,6 @@
require "cases/helper"
+require 'models/developer'
+require 'models/computer'
class CallbackDeveloper < ActiveRecord::Base
self.table_name = 'developers'
@@ -47,6 +49,11 @@ class CallbackDeveloperWithFalseValidation < CallbackDeveloper
before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] }
end
+class CallbackDeveloperWithHaltedValidation < CallbackDeveloper
+ before_validation proc { |model| model.history << [:before_validation, :throwing_abort]; throw(:abort) }
+ before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] }
+end
+
class ParentDeveloper < ActiveRecord::Base
self.table_name = 'developers'
attr_accessor :after_save_called
@@ -57,27 +64,6 @@ class ChildDeveloper < ParentDeveloper
end
-class RecursiveCallbackDeveloper < ActiveRecord::Base
- self.table_name = 'developers'
-
- before_save :on_before_save
- after_save :on_after_save
-
- attr_reader :on_before_save_called, :on_after_save_called
-
- def on_before_save
- @on_before_save_called ||= 0
- @on_before_save_called += 1
- save unless @on_before_save_called > 1
- end
-
- def on_after_save
- @on_after_save_called ||= 0
- @on_after_save_called += 1
- save unless @on_after_save_called > 1
- end
-end
-
class ImmutableDeveloper < ActiveRecord::Base
self.table_name = 'developers'
@@ -86,35 +72,24 @@ class ImmutableDeveloper < ActiveRecord::Base
before_save :cancel
before_destroy :cancel
- def cancelled?
- @cancelled == true
- end
-
private
def cancel
- @cancelled = true
false
end
end
-class ImmutableMethodDeveloper < ActiveRecord::Base
+class DeveloperWithCanceledCallbacks < ActiveRecord::Base
self.table_name = 'developers'
- validates_inclusion_of :salary, :in => 50000..200000
-
- def cancelled?
- @cancelled == true
- end
+ validates_inclusion_of :salary, in: 50000..200000
- before_save do
- @cancelled = true
- false
- end
+ before_save :cancel
+ before_destroy :cancel
- before_destroy do
- @cancelled = true
- false
- end
+ private
+ def cancel
+ throw(:abort)
+ end
end
class OnCallbacksDeveloper < ActiveRecord::Base
@@ -180,6 +155,23 @@ class CallbackCancellationDeveloper < ActiveRecord::Base
after_destroy { @after_destroy_called = true }
end
+class CallbackHaltedDeveloper < ActiveRecord::Base
+ self.table_name = 'developers'
+
+ attr_reader :after_save_called, :after_create_called, :after_update_called, :after_destroy_called
+ attr_accessor :cancel_before_save, :cancel_before_create, :cancel_before_update, :cancel_before_destroy
+
+ before_save { throw(:abort) if defined?(@cancel_before_save) }
+ before_create { throw(:abort) if @cancel_before_create }
+ before_update { throw(:abort) if @cancel_before_update }
+ before_destroy { throw(:abort) if @cancel_before_destroy }
+
+ after_save { @after_save_called = true }
+ after_update { @after_update_called = true }
+ after_create { @after_create_called = true }
+ after_destroy { @after_destroy_called = true }
+end
+
class CallbacksTest < ActiveRecord::TestCase
fixtures :developers
@@ -296,7 +288,12 @@ class CallbacksTest < ActiveRecord::TestCase
[ :after_save, :string ],
[ :after_save, :proc ],
[ :after_save, :object ],
- [ :after_save, :block ]
+ [ :after_save, :block ],
+ [ :after_commit, :block ],
+ [ :after_commit, :object ],
+ [ :after_commit, :proc ],
+ [ :after_commit, :string ],
+ [ :after_commit, :method ]
], david.history
end
@@ -365,7 +362,12 @@ class CallbacksTest < ActiveRecord::TestCase
[ :after_save, :string ],
[ :after_save, :proc ],
[ :after_save, :object ],
- [ :after_save, :block ]
+ [ :after_save, :block ],
+ [ :after_commit, :block ],
+ [ :after_commit, :object ],
+ [ :after_commit, :proc ],
+ [ :after_commit, :string ],
+ [ :after_commit, :method ]
], david.history
end
@@ -416,7 +418,12 @@ class CallbacksTest < ActiveRecord::TestCase
[ :after_destroy, :string ],
[ :after_destroy, :proc ],
[ :after_destroy, :object ],
- [ :after_destroy, :block ]
+ [ :after_destroy, :block ],
+ [ :after_commit, :block ],
+ [ :after_commit, :object ],
+ [ :after_commit, :proc ],
+ [ :after_commit, :string ],
+ [ :after_commit, :method ]
], david.history
end
@@ -437,11 +444,14 @@ class CallbacksTest < ActiveRecord::TestCase
], david.history
end
- def test_before_save_returning_false
+ def test_deprecated_before_save_returning_false
david = ImmutableDeveloper.find(1)
- assert david.valid?
- assert !david.save
- assert_raise(ActiveRecord::RecordNotSaved) { david.save! }
+ assert_deprecated do
+ assert david.valid?
+ assert !david.save
+ exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! }
+ assert_equal exc.record, david
+ end
david = ImmutableDeveloper.find(1)
david.salary = 10_000_000
@@ -451,37 +461,48 @@ class CallbacksTest < ActiveRecord::TestCase
someone = CallbackCancellationDeveloper.find(1)
someone.cancel_before_save = true
- assert someone.valid?
- assert !someone.save
+ assert_deprecated do
+ assert someone.valid?
+ assert !someone.save
+ end
assert_save_callbacks_not_called(someone)
end
- def test_before_create_returning_false
+ def test_deprecated_before_create_returning_false
someone = CallbackCancellationDeveloper.new
someone.cancel_before_create = true
- assert someone.valid?
- assert !someone.save
+ assert_deprecated do
+ assert someone.valid?
+ assert !someone.save
+ end
assert_save_callbacks_not_called(someone)
end
- def test_before_update_returning_false
+ def test_deprecated_before_update_returning_false
someone = CallbackCancellationDeveloper.find(1)
someone.cancel_before_update = true
- assert someone.valid?
- assert !someone.save
+ assert_deprecated do
+ assert someone.valid?
+ assert !someone.save
+ end
assert_save_callbacks_not_called(someone)
end
- def test_before_destroy_returning_false
+ def test_deprecated_before_destroy_returning_false
david = ImmutableDeveloper.find(1)
- assert !david.destroy
- assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! }
+ assert_deprecated do
+ assert !david.destroy
+ exc = assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! }
+ assert_equal exc.record, david
+ end
assert_not_nil ImmutableDeveloper.find_by_id(1)
someone = CallbackCancellationDeveloper.find(1)
someone.cancel_before_destroy = true
- assert !someone.destroy
- assert_raise(ActiveRecord::RecordNotDestroyed) { someone.destroy! }
+ assert_deprecated do
+ assert !someone.destroy
+ assert_raise(ActiveRecord::RecordNotDestroyed) { someone.destroy! }
+ end
assert !someone.after_destroy_called
end
@@ -492,9 +513,59 @@ class CallbacksTest < ActiveRecord::TestCase
end
private :assert_save_callbacks_not_called
+ def test_before_create_throwing_abort
+ someone = CallbackHaltedDeveloper.new
+ someone.cancel_before_create = true
+ assert someone.valid?
+ assert !someone.save
+ assert_save_callbacks_not_called(someone)
+ end
+
+ def test_before_save_throwing_abort
+ david = DeveloperWithCanceledCallbacks.find(1)
+ assert david.valid?
+ assert !david.save
+ exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! }
+ assert_equal exc.record, david
+
+ david = DeveloperWithCanceledCallbacks.find(1)
+ david.salary = 10_000_000
+ assert !david.valid?
+ assert !david.save
+ assert_raise(ActiveRecord::RecordInvalid) { david.save! }
+
+ someone = CallbackHaltedDeveloper.find(1)
+ someone.cancel_before_save = true
+ assert someone.valid?
+ assert !someone.save
+ assert_save_callbacks_not_called(someone)
+ end
+
+ def test_before_update_throwing_abort
+ someone = CallbackHaltedDeveloper.find(1)
+ someone.cancel_before_update = true
+ assert someone.valid?
+ assert !someone.save
+ assert_save_callbacks_not_called(someone)
+ end
+
+ def test_before_destroy_throwing_abort
+ david = DeveloperWithCanceledCallbacks.find(1)
+ assert !david.destroy
+ exc = assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! }
+ assert_equal exc.record, david
+ assert_not_nil ImmutableDeveloper.find_by_id(1)
+
+ someone = CallbackHaltedDeveloper.find(1)
+ someone.cancel_before_destroy = true
+ assert !someone.destroy
+ assert_raise(ActiveRecord::RecordNotDestroyed) { someone.destroy! }
+ assert !someone.after_destroy_called
+ end
+
def test_callback_returning_false
david = CallbackDeveloperWithFalseValidation.find(1)
- david.save
+ assert_deprecated { david.save }
assert_equal [
[ :after_find, :method ],
[ :after_find, :string ],
@@ -520,6 +591,34 @@ class CallbacksTest < ActiveRecord::TestCase
], david.history
end
+ def test_callback_throwing_abort
+ david = CallbackDeveloperWithHaltedValidation.find(1)
+ david.save
+ assert_equal [
+ [ :after_find, :method ],
+ [ :after_find, :string ],
+ [ :after_find, :proc ],
+ [ :after_find, :object ],
+ [ :after_find, :block ],
+ [ :after_initialize, :method ],
+ [ :after_initialize, :string ],
+ [ :after_initialize, :proc ],
+ [ :after_initialize, :object ],
+ [ :after_initialize, :block ],
+ [ :before_validation, :method ],
+ [ :before_validation, :string ],
+ [ :before_validation, :proc ],
+ [ :before_validation, :object ],
+ [ :before_validation, :block ],
+ [ :before_validation, :throwing_abort ],
+ [ :after_rollback, :block ],
+ [ :after_rollback, :object ],
+ [ :after_rollback, :proc ],
+ [ :after_rollback, :string ],
+ [ :after_rollback, :method ],
+ ], david.history
+ end
+
def test_inheritance_of_callbacks
parent = ParentDeveloper.new
assert !parent.after_save_called
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index bcfd66b4bf..14b95ecab1 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -14,7 +14,7 @@ module ActiveRecord
# Avoid column definitions in create table statements like:
# `title` varchar(255) DEFAULT NULL
def test_should_not_include_default_clause_when_default_is_null
- column = Column.new("title", nil, Type::String.new(limit: 20))
+ column = Column.new("title", nil, SqlTypeMetadata.new(limit: 20))
column_def = ColumnDefinition.new(
column.name, "string",
column.limit, column.precision, column.scale, column.default, column.null)
@@ -22,7 +22,7 @@ module ActiveRecord
end
def test_should_include_default_clause_when_default_is_present
- column = Column.new("title", "Hello", Type::String.new(limit: 20))
+ column = Column.new("title", "Hello", SqlTypeMetadata.new(limit: 20))
column_def = ColumnDefinition.new(
column.name, "string",
column.limit, column.precision, column.scale, column.default, column.null)
@@ -30,94 +30,53 @@ module ActiveRecord
end
def test_should_specify_not_null_if_null_option_is_false
- column = Column.new("title", "Hello", Type::String.new(limit: 20), "varchar(20)", false)
+ type_metadata = SqlTypeMetadata.new(limit: 20)
+ column = Column.new("title", "Hello", type_metadata, false)
column_def = ColumnDefinition.new(
column.name, "string",
column.limit, column.precision, column.scale, column.default, column.null)
assert_equal %Q{title varchar(20) DEFAULT 'Hello' NOT NULL}, @viz.accept(column_def)
end
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
def test_should_set_default_for_mysql_binary_data_types
- binary_column = MysqlAdapter::Column.new("title", "a", Type::Binary.new, "binary(1)")
+ type = SqlTypeMetadata.new(type: :binary, sql_type: "binary(1)")
+ binary_column = AbstractMysqlAdapter::Column.new("title", "a", type)
assert_equal "a", binary_column.default
- varbinary_column = MysqlAdapter::Column.new("title", "a", Type::Binary.new, "varbinary(1)")
+ type = SqlTypeMetadata.new(type: :binary, sql_type: "varbinary")
+ varbinary_column = AbstractMysqlAdapter::Column.new("title", "a", type)
assert_equal "a", varbinary_column.default
end
def test_should_not_set_default_for_blob_and_text_data_types
assert_raise ArgumentError do
- MysqlAdapter::Column.new("title", "a", Type::Binary.new, "blob")
+ AbstractMysqlAdapter::Column.new("title", "a", SqlTypeMetadata.new(sql_type: "blob"))
end
+ text_type = AbstractMysqlAdapter::MysqlTypeMetadata.new(
+ SqlTypeMetadata.new(type: :text))
assert_raise ArgumentError do
- MysqlAdapter::Column.new("title", "Hello", Type::Text.new)
+ AbstractMysqlAdapter::Column.new("title", "Hello", text_type)
end
- text_column = MysqlAdapter::Column.new("title", nil, Type::Text.new)
+ text_column = AbstractMysqlAdapter::Column.new("title", nil, text_type)
assert_equal nil, text_column.default
- not_null_text_column = MysqlAdapter::Column.new("title", nil, Type::Text.new, "text", false)
+ not_null_text_column = AbstractMysqlAdapter::Column.new("title", nil, text_type, false)
assert_equal "", not_null_text_column.default
end
def test_has_default_should_return_false_for_blob_and_text_data_types
- blob_column = MysqlAdapter::Column.new("title", nil, Type::Binary.new, "blob")
+ binary_type = SqlTypeMetadata.new(sql_type: "blob")
+ blob_column = AbstractMysqlAdapter::Column.new("title", nil, binary_type)
assert !blob_column.has_default?
- text_column = MysqlAdapter::Column.new("title", nil, Type::Text.new)
+ text_type = SqlTypeMetadata.new(type: :text)
+ text_column = AbstractMysqlAdapter::Column.new("title", nil, text_type)
assert !text_column.has_default?
end
end
-
- if current_adapter?(:Mysql2Adapter)
- def test_should_set_default_for_mysql_binary_data_types
- binary_column = Mysql2Adapter::Column.new("title", "a", Type::Binary.new, "binary(1)")
- assert_equal "a", binary_column.default
-
- varbinary_column = Mysql2Adapter::Column.new("title", "a", Type::Binary.new, "varbinary(1)")
- assert_equal "a", varbinary_column.default
- end
-
- def test_should_not_set_default_for_blob_and_text_data_types
- assert_raise ArgumentError do
- Mysql2Adapter::Column.new("title", "a", Type::Binary.new, "blob")
- end
-
- assert_raise ArgumentError do
- Mysql2Adapter::Column.new("title", "Hello", Type::Text.new)
- end
-
- text_column = Mysql2Adapter::Column.new("title", nil, Type::Text.new)
- assert_equal nil, text_column.default
-
- not_null_text_column = Mysql2Adapter::Column.new("title", nil, Type::Text.new, "text", false)
- assert_equal "", not_null_text_column.default
- end
-
- def test_has_default_should_return_false_for_blob_and_text_data_types
- blob_column = Mysql2Adapter::Column.new("title", nil, Type::Binary.new, "blob")
- assert !blob_column.has_default?
-
- text_column = Mysql2Adapter::Column.new("title", nil, Type::Text.new)
- assert !text_column.has_default?
- end
- end
-
- if current_adapter?(:PostgreSQLAdapter)
- def test_bigint_column_should_map_to_integer
- oid = PostgreSQLAdapter::OID::Integer.new
- bigint_column = PostgreSQLColumn.new('number', nil, oid, "bigint")
- assert_equal :integer, bigint_column.type
- end
-
- def test_smallint_column_should_map_to_integer
- oid = PostgreSQLAdapter::OID::Integer.new
- smallint_column = PostgreSQLColumn.new('number', nil, oid, "smallint")
- assert_equal :integer, smallint_column.type
- end
- end
end
end
end
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index 3e33b30144..b72f8ca88c 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -44,9 +44,7 @@ module ActiveRecord
end
def test_connection_pools
- assert_deprecated do
- assert_equal({ Base.connection_pool.spec => @pool }, @handler.connection_pools)
- end
+ assert_equal([@pool], @handler.connection_pools)
end
end
end
diff --git a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
index 37ad469476..9ee92a3cd2 100644
--- a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
+++ b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
@@ -51,34 +51,6 @@ module ActiveRecord
assert_equal expected, actual
end
- def test_resolver_with_database_uri_and_and_current_env_string_key
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
- config = { "default_env" => { "adapter" => "not_postgres", "database" => "not_foo" } }
- actual = assert_deprecated { resolve_spec("default_env", config) }
- expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
- assert_equal expected, actual
- end
-
- def test_resolver_with_database_uri_and_and_current_env_string_key_and_rails_env
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
- ENV['RAILS_ENV'] = "foo"
-
- config = { "not_production" => {"adapter" => "not_postgres", "database" => "not_foo" } }
- actual = assert_deprecated { resolve_spec("foo", config) }
- expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" }
- assert_equal expected, actual
- end
-
- def test_resolver_with_database_uri_and_and_current_env_string_key_and_rack_env
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
- ENV['RACK_ENV'] = "foo"
-
- config = { "not_production" => {"adapter" => "not_postgres", "database" => "not_foo" } }
- actual = assert_deprecated { resolve_spec("foo", config) }
- expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" }
- assert_equal expected, actual
- end
-
def test_resolver_with_database_uri_and_known_key
ENV['DATABASE_URL'] = "postgres://localhost/foo"
config = { "production" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } }
@@ -95,16 +67,6 @@ module ActiveRecord
end
end
- def test_resolver_with_database_uri_and_unknown_string_key
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
- config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
- assert_deprecated do
- assert_raises AdapterNotSpecified do
- resolve_spec("production", config)
- end
- end
- end
-
def test_resolver_with_database_uri_and_supplied_url
ENV['DATABASE_URL'] = "not-postgres://not-localhost/not_foo"
config = { "production" => { "adapter" => "also_not_postgres", "database" => "also_not_foo" } }
diff --git a/activerecord/test/cases/connection_adapters/type_lookup_test.rb b/activerecord/test/cases/connection_adapters/type_lookup_test.rb
index d5c1dc1e5d..05c57985a1 100644
--- a/activerecord/test/cases/connection_adapters/type_lookup_test.rb
+++ b/activerecord/test/cases/connection_adapters/type_lookup_test.rb
@@ -79,13 +79,18 @@ module ActiveRecord
assert_lookup_type :integer, 'bigint'
end
+ def test_bigint_limit
+ cast_type = @connection.type_map.lookup("bigint")
+ assert_equal 8, cast_type.limit
+ end
+
def test_decimal_without_scale
types = %w{decimal(2) decimal(2,0) numeric(2) numeric(2,0) number(2) number(2,0)}
types.each do |type|
cast_type = @connection.type_map.lookup(type)
assert_equal :decimal, cast_type.type
- assert_equal 2, cast_type.type_cast_from_user(2.1)
+ assert_equal 2, cast_type.cast(2.1)
end
end
diff --git a/activerecord/test/cases/core_test.rb b/activerecord/test/cases/core_test.rb
index 715d92af99..3cb98832c5 100644
--- a/activerecord/test/cases/core_test.rb
+++ b/activerecord/test/cases/core_test.rb
@@ -98,4 +98,15 @@ class CoreTest < ActiveRecord::TestCase
assert actual.start_with?(expected.split('XXXXXX').first)
assert actual.end_with?(expected.split('XXXXXX').last)
end
+
+ def test_pretty_print_overridden_by_inspect
+ subtopic = Class.new(Topic) do
+ def inspect
+ "inspecting topic"
+ end
+ end
+ actual = ''
+ PP.pp(subtopic.new, StringIO.new(actual))
+ assert_equal "inspecting topic\n", actual
+ end
end
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index 07a182070b..1f5055b2a2 100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
@@ -180,4 +180,22 @@ class CounterCacheTest < ActiveRecord::TestCase
SpecialTopic.reset_counters(special.id, :lightweight_special_replies)
end
end
+
+ test "counters are updated both in memory and in the database on create" do
+ car = Car.new(engines_count: 0)
+ car.engines = [Engine.new, Engine.new]
+ car.save!
+
+ assert_equal 2, car.engines_count
+ assert_equal 2, car.reload.engines_count
+ end
+
+ test "counter caches are updated in memory when the default value is nil" do
+ car = Car.new(engines_count: nil)
+ car.engines = [Engine.new, Engine.new]
+ car.save!
+
+ assert_equal 2, car.engines_count
+ assert_equal 2, car.reload.engines_count
+ end
end
diff --git a/activerecord/test/cases/date_time_precision_test.rb b/activerecord/test/cases/date_time_precision_test.rb
new file mode 100644
index 0000000000..698f1b852e
--- /dev/null
+++ b/activerecord/test/cases/date_time_precision_test.rb
@@ -0,0 +1,111 @@
+require 'cases/helper'
+require 'support/schema_dumping_helper'
+
+if ActiveRecord::Base.connection.supports_datetime_with_precision?
+class DateTimePrecisionTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+ self.use_transactional_tests = false
+
+ class Foo < ActiveRecord::Base; end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ end
+
+ teardown do
+ @connection.drop_table :foos, if_exists: true
+ end
+
+ def test_datetime_data_type_with_precision
+ @connection.create_table(:foos, force: true)
+ @connection.add_column :foos, :created_at, :datetime, precision: 0
+ @connection.add_column :foos, :updated_at, :datetime, precision: 5
+ assert_equal 0, activerecord_column_option('foos', 'created_at', 'precision')
+ assert_equal 5, activerecord_column_option('foos', 'updated_at', 'precision')
+ end
+
+ def test_timestamps_helper_with_custom_precision
+ @connection.create_table(:foos, force: true) do |t|
+ t.timestamps precision: 4
+ end
+ assert_equal 4, activerecord_column_option('foos', 'created_at', 'precision')
+ assert_equal 4, activerecord_column_option('foos', 'updated_at', 'precision')
+ end
+
+ def test_passing_precision_to_datetime_does_not_set_limit
+ @connection.create_table(:foos, force: true) do |t|
+ t.timestamps precision: 4
+ end
+ assert_nil activerecord_column_option('foos', 'created_at', 'limit')
+ assert_nil activerecord_column_option('foos', 'updated_at', 'limit')
+ end
+
+ def test_invalid_datetime_precision_raises_error
+ assert_raises ActiveRecord::ActiveRecordError do
+ @connection.create_table(:foos, force: true) do |t|
+ t.timestamps precision: 7
+ end
+ end
+ end
+
+ def test_database_agrees_with_activerecord_about_precision
+ @connection.create_table(:foos, force: true) do |t|
+ t.timestamps precision: 4
+ end
+ assert_equal 4, database_datetime_precision('foos', 'created_at')
+ assert_equal 4, database_datetime_precision('foos', 'updated_at')
+ end
+
+ def test_formatting_datetime_according_to_precision
+ @connection.create_table(:foos, force: true) do |t|
+ t.datetime :created_at, precision: 0
+ t.datetime :updated_at, precision: 4
+ end
+ date = ::Time.utc(2014, 8, 17, 12, 30, 0, 999999)
+ Foo.create!(created_at: date, updated_at: date)
+ assert foo = Foo.find_by(created_at: date)
+ assert_equal 1, Foo.where(updated_at: date).count
+ assert_equal date.to_s, foo.created_at.to_s
+ assert_equal date.to_s, foo.updated_at.to_s
+ assert_equal 000000, foo.created_at.usec
+ assert_equal 999900, foo.updated_at.usec
+ end
+
+ def test_schema_dump_includes_datetime_precision
+ @connection.create_table(:foos, force: true) do |t|
+ t.timestamps precision: 6
+ end
+ output = dump_table_schema("foos")
+ assert_match %r{t\.datetime\s+"created_at",\s+precision: 6,\s+null: false$}, output
+ assert_match %r{t\.datetime\s+"updated_at",\s+precision: 6,\s+null: false$}, output
+ end
+
+ if current_adapter?(:PostgreSQLAdapter)
+ def test_datetime_precision_with_zero_should_be_dumped
+ @connection.create_table(:foos, force: true) do |t|
+ t.timestamps precision: 0
+ end
+ output = dump_table_schema("foos")
+ assert_match %r{t\.datetime\s+"created_at",\s+precision: 0,\s+null: false$}, output
+ assert_match %r{t\.datetime\s+"updated_at",\s+precision: 0,\s+null: false$}, output
+ end
+ end
+
+ private
+
+ def database_datetime_precision(table_name, column_name)
+ results = @connection.exec_query("SELECT column_name, datetime_precision FROM information_schema.columns WHERE table_name = '#{table_name}'")
+ result = results.find do |result_hash|
+ result_hash["column_name"] == column_name
+ end
+ result && result["datetime_precision"].to_i
+ end
+
+ def activerecord_column_option(tablename, column_name, option)
+ result = @connection.columns(tablename).find do |column|
+ column.name == column_name
+ end
+ result && result.send(option)
+ end
+end
+end
diff --git a/activerecord/test/cases/date_time_test.rb b/activerecord/test/cases/date_time_test.rb
index c0491bbee5..4cbff564aa 100644
--- a/activerecord/test/cases/date_time_test.rb
+++ b/activerecord/test/cases/date_time_test.rb
@@ -3,6 +3,8 @@ require 'models/topic'
require 'models/task'
class DateTimeTest < ActiveRecord::TestCase
+ include InTimeZone
+
def test_saves_both_date_and_time
with_env_tz 'America/New_York' do
with_timezone_config default: :utc do
@@ -29,6 +31,14 @@ class DateTimeTest < ActiveRecord::TestCase
assert_nil task.ending
end
+ def test_assign_bad_date_time_with_timezone
+ in_time_zone "Pacific Time (US & Canada)" do
+ task = Task.new
+ task.starting = '2014-07-01T24:59:59GMT'
+ assert_nil task.starting
+ end
+ end
+
def test_assign_empty_date
topic = Topic.new
topic.last_read = ''
@@ -40,4 +50,12 @@ class DateTimeTest < ActiveRecord::TestCase
topic.bonus_time = ''
assert_nil topic.bonus_time
end
+
+ def test_assign_in_local_timezone
+ now = DateTime.now
+ with_timezone_config default: :local do
+ task = Task.new starting: now
+ assert_equal now, task.starting
+ end
+ end
end
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index c089e63128..67fddebf45 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -18,25 +18,48 @@ class DefaultTest < ActiveRecord::TestCase
end
end
- if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
- def test_default_integers
- default = Default.new
- assert_instance_of Fixnum, default.positive_integer
- assert_equal 1, default.positive_integer
- assert_instance_of Fixnum, default.negative_integer
- assert_equal(-1, default.negative_integer)
- assert_instance_of BigDecimal, default.decimal_number
- assert_equal BigDecimal.new("2.78"), default.decimal_number
- end
- end
-
if current_adapter?(:PostgreSQLAdapter)
def test_multiline_default_text
+ record = Default.new
# older postgres versions represent the default with escapes ("\\012" for a newline)
- assert( "--- []\n\n" == Default.columns_hash['multiline_default'].default ||
- "--- []\\012\\012" == Default.columns_hash['multiline_default'].default)
+ assert("--- []\n\n" == record.multiline_default || "--- []\\012\\012" == record.multiline_default)
+ end
+ end
+end
+
+class DefaultNumbersTest < ActiveRecord::TestCase
+ class DefaultNumber < ActiveRecord::Base; end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table :default_numbers do |t|
+ t.integer :positive_integer, default: 7
+ t.integer :negative_integer, default: -5
+ t.decimal :decimal_number, default: "2.78", precision: 5, scale: 2
end
end
+
+ teardown do
+ @connection.drop_table :default_numbers, if_exists: true
+ end
+
+ def test_default_positive_integer
+ record = DefaultNumber.new
+ assert_equal 7, record.positive_integer
+ assert_equal "7", record.positive_integer_before_type_cast
+ end
+
+ def test_default_negative_integer
+ record = DefaultNumber.new
+ assert_equal (-5), record.negative_integer
+ assert_equal "-5", record.negative_integer_before_type_cast
+ end
+
+ def test_default_decimal_number
+ record = DefaultNumber.new
+ assert_equal BigDecimal.new("2.78"), record.decimal_number
+ assert_equal "2.78", record.decimal_number_before_type_cast
+ end
end
class DefaultStringsTest < ActiveRecord::TestCase
@@ -67,14 +90,14 @@ end
if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
class DefaultsTestWithoutTransactionalFixtures < ActiveRecord::TestCase
# ActiveRecord::Base#create! (and #save and other related methods) will
- # open a new transaction. When in transactional fixtures mode, this will
+ # open a new transaction. When in transactional tests mode, this will
# cause Active Record to create a new savepoint. However, since MySQL doesn't
# support DDL transactions, creating a table will result in any created
# savepoints to be automatically released. This in turn causes the savepoint
# release code in AbstractAdapter#transaction to fail.
#
- # We don't want that to happen, so we disable transactional fixtures here.
- self.use_transactional_fixtures = false
+ # We don't want that to happen, so we disable transactional tests here.
+ self.use_transactional_tests = false
def using_strict(strict)
connection = ActiveRecord::Base.remove_connection
@@ -99,19 +122,21 @@ if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
def test_mysql_text_not_null_defaults_non_strict
using_strict(false) do
with_text_blob_not_null_table do |klass|
- assert_equal '', klass.columns_hash['non_null_blob'].default
- assert_equal '', klass.columns_hash['non_null_text'].default
+ record = klass.new
+ assert_equal '', record.non_null_blob
+ assert_equal '', record.non_null_text
- assert_nil klass.columns_hash['null_blob'].default
- assert_nil klass.columns_hash['null_text'].default
+ assert_nil record.null_blob
+ assert_nil record.null_text
- instance = klass.create!
+ record.save!
+ record.reload
- assert_equal '', instance.non_null_text
- assert_equal '', instance.non_null_blob
+ assert_equal '', record.non_null_text
+ assert_equal '', record.non_null_blob
- assert_nil instance.null_text
- assert_nil instance.null_blob
+ assert_nil record.null_text
+ assert_nil record.null_blob
end
end
end
@@ -119,10 +144,11 @@ if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
def test_mysql_text_not_null_defaults_strict
using_strict(true) do
with_text_blob_not_null_table do |klass|
- assert_nil klass.columns_hash['non_null_blob'].default
- assert_nil klass.columns_hash['non_null_text'].default
- assert_nil klass.columns_hash['null_blob'].default
- assert_nil klass.columns_hash['null_text'].default
+ record = klass.new
+ assert_nil record.non_null_blob
+ assert_nil record.non_null_text
+ assert_nil record.null_blob
+ assert_nil record.null_text
assert_raises(ActiveRecord::StatementInvalid) { klass.create }
end
@@ -172,48 +198,3 @@ if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
end
end
end
-
-if current_adapter?(:PostgreSQLAdapter)
- class DefaultsUsingMultipleSchemasAndDomainTest < ActiveSupport::TestCase
- def setup
- @connection = ActiveRecord::Base.connection
-
- @old_search_path = @connection.schema_search_path
- @connection.schema_search_path = "schema_1, pg_catalog"
- @connection.create_table "defaults" do |t|
- t.text "text_col", :default => "some value"
- t.string "string_col", :default => "some value"
- end
- Default.reset_column_information
- end
-
- def test_text_defaults_in_new_schema_when_overriding_domain
- assert_equal "some value", Default.new.text_col, "Default of text column was not correctly parse"
- end
-
- def test_string_defaults_in_new_schema_when_overriding_domain
- assert_equal "some value", Default.new.string_col, "Default of string column was not correctly parse"
- 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
- assert_equal "some value", Default.new.bpchar_col, "Default of bpchar column was not correctly parse"
- end
-
- def test_text_defaults_after_updating_column_default
- @connection.execute "ALTER TABLE defaults ALTER COLUMN text_col SET DEFAULT 'some text'::schema_1.text"
- assert_equal "some text", Default.new.text_col, "Default of text column was not correctly parse after updating default using '::text' since postgreSQL will add parens to the default in db"
- end
-
- def test_default_containing_quote_and_colons
- @connection.execute "ALTER TABLE defaults ALTER COLUMN string_col SET DEFAULT 'foo''::bar'"
- assert_equal "foo'::bar", Default.new.string_col
- end
-
- teardown do
- @connection.schema_search_path = @old_search_path
- Default.reset_column_information
- end
- end
-end
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index eb9b1a2d74..3a7cc572e6 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -165,18 +165,6 @@ class DirtyTest < ActiveRecord::TestCase
assert_equal parrot.name_change, parrot.title_change
end
- def test_reset_attribute!
- pirate = Pirate.create!(:catchphrase => 'Yar!')
- pirate.catchphrase = 'Ahoy!'
-
- assert_deprecated do
- pirate.reset_catchphrase!
- end
- assert_equal "Yar!", pirate.catchphrase
- assert_equal Hash.new, pirate.changes
- assert !pirate.catchphrase_changed?
- end
-
def test_restore_attribute!
pirate = Pirate.create!(:catchphrase => 'Yar!')
pirate.catchphrase = 'Ahoy!'
@@ -635,13 +623,13 @@ class DirtyTest < ActiveRecord::TestCase
end
end
- test "defaults with type that implements `type_cast_for_database`" do
+ test "defaults with type that implements `serialize`" do
type = Class.new(ActiveRecord::Type::Value) do
- def type_cast(value)
+ def cast(value)
value.to_i
end
- def type_cast_for_database(value)
+ def serialize(value)
value.to_s
end
end
@@ -688,7 +676,14 @@ class DirtyTest < ActiveRecord::TestCase
serialize :data
end
- klass.create!(data: "foo")
+ binary = klass.create!(data: "\\\\foo")
+
+ assert_not binary.changed?
+
+ binary.data = binary.data.dup
+
+ assert_not binary.changed?
+
binary = klass.last
assert_not binary.changed?
@@ -698,6 +693,42 @@ class DirtyTest < ActiveRecord::TestCase
assert binary.changed?
end
+ test "attribute_changed? doesn't compute in-place changes for unrelated attributes" do
+ test_type_class = Class.new(ActiveRecord::Type::Value) do
+ define_method(:changed_in_place?) do |*|
+ raise
+ end
+ end
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = 'people'
+ attribute :foo, test_type_class.new
+ end
+
+ model = klass.new(first_name: "Jim")
+ assert model.first_name_changed?
+ end
+
+ test "attribute_will_change! doesn't try to save non-persistable attributes" do
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = 'people'
+ attribute :non_persisted_attribute, :string
+ end
+
+ record = klass.new(first_name: "Sean")
+ record.non_persisted_attribute_will_change!
+
+ assert record.non_persisted_attribute_changed?
+ assert record.save
+ end
+
+ test "mutating and then assigning doesn't remove the change" do
+ pirate = Pirate.create!(catchphrase: "arrrr")
+ pirate.catchphrase << " matey!"
+ pirate.catchphrase = "arrrr matey!"
+
+ assert pirate.catchphrase_changed?(from: "arrrr", to: "arrrr matey!")
+ 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 94447addc1..55f0e51717 100644
--- a/activerecord/test/cases/disconnected_test.rb
+++ b/activerecord/test/cases/disconnected_test.rb
@@ -4,7 +4,7 @@ class TestRecord < ActiveRecord::Base
end
class TestDisconnectedAdapter < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
def setup
@connection = ActiveRecord::Base.connection
diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb
index 346fcab6ea..eea184e530 100644
--- a/activerecord/test/cases/enum_test.rb
+++ b/activerecord/test/cases/enum_test.rb
@@ -26,6 +26,49 @@ class EnumTest < ActiveRecord::TestCase
assert_equal @book, Book.unread.first
end
+ test "find via where with values" do
+ proposed, written = Book.statuses[:proposed], 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 <> ?", 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.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.not(status: "written").first
+ end
+
+ test "build from scope" do
+ assert Book.written.build.written?
+ refute 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 Book.where(status: :written).build.written?
+ refute Book.where(status: :written).build.proposed?
+ assert Book.where(status: "written").build.written?
+ refute Book.where(status: "written").build.proposed?
+ end
+
test "update by declaration" do
@book.written!
assert @book.written?
@@ -129,19 +172,24 @@ class EnumTest < ActiveRecord::TestCase
assert_equal "'unknown' is not a valid status", e.message
end
+ test "NULL values from database should be casted to nil" do
+ Book.where(id: @book.id).update_all("status = NULL")
+ assert_nil @book.reload.status
+ end
+
test "assign nil value" do
@book.status = nil
- assert @book.status.nil?
+ assert_nil @book.status
end
test "assign empty string value" do
@book.status = ''
- assert @book.status.nil?
+ assert_nil @book.status
end
test "assign long empty string value" do
@book.status = ' '
- assert @book.status.nil?
+ assert_nil @book.status
end
test "constant to access the mapping" do
@@ -161,7 +209,11 @@ class EnumTest < ActiveRecord::TestCase
end
test "_before_type_cast returns the enum label (required for form fields)" do
- assert_equal "proposed", @book.status_before_type_cast
+ if @book.status_came_from_user?
+ assert_equal "proposed", @book.status_before_type_cast
+ else
+ assert_equal "proposed", @book.status
+ end
end
test "reserved enum names" do
@@ -177,9 +229,10 @@ class EnumTest < ActiveRecord::TestCase
]
conflicts.each_with_index do |name, i|
- assert_raises(ArgumentError, "enum name `#{name}` should not be allowed") do
+ e = assert_raises(ArgumentError) do
klass.class_eval { enum name => ["value_#{i}"] }
end
+ assert_match(/You tried to define an enum named \"#{name}\" on the model/, e.message)
end
end
@@ -199,9 +252,10 @@ class EnumTest < ActiveRecord::TestCase
]
conflicts.each_with_index do |value, i|
- assert_raises(ArgumentError, "enum value `#{value}` should not be allowed") do
+ e = assert_raises(ArgumentError, "enum value `#{value}` should not be allowed") do
klass.class_eval { enum "status_#{i}" => [value] }
end
+ assert_match(/You tried to define an enum named .* on the model/, e.message)
end
end
@@ -287,4 +341,18 @@ class EnumTest < ActiveRecord::TestCase
book2.status = :uploaded
assert_equal ['drafted', 'uploaded'], book2.status_change
end
+
+ test "declare multiple enums at a time" do
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "books"
+ enum status: [:proposed, :written, :published],
+ nullable_status: [:single, :married]
+ end
+
+ book1 = klass.proposed.create!
+ assert book1.proposed?
+
+ book2 = klass.single.create!
+ assert book2.single?
+ end
end
diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb
index 9d25bdd82a..f1d5511bb8 100644
--- a/activerecord/test/cases/explain_test.rb
+++ b/activerecord/test/cases/explain_test.rb
@@ -28,7 +28,7 @@ if ActiveRecord::Base.connection.supports_explain?
assert_match "SELECT", sql
if binds.any?
assert_equal 1, binds.length
- assert_equal "honda", binds.flatten.last
+ assert_equal "honda", binds.last.value
else
assert_match 'honda', sql
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index dc73faa5be..4b819a82e8 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -10,13 +10,16 @@ require 'models/reply'
require 'models/entrant'
require 'models/project'
require 'models/developer'
+require 'models/computer'
require 'models/customer'
require 'models/toy'
require 'models/matey'
require 'models/dog'
+require 'models/car'
+require 'models/tyre'
class FinderTest < ActiveRecord::TestCase
- fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :customers, :categories, :categorizations
+ fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :customers, :categories, :categorizations, :cars
def test_find_by_id_with_hash
assert_raises(ActiveRecord::StatementInvalid) do
@@ -52,10 +55,13 @@ class FinderTest < ActiveRecord::TestCase
end
def test_symbols_table_ref
+ gc_disabled = GC.disable
Post.where("author_id" => nil) # warm up
x = Symbol.all_symbols.count
Post.where("title" => {"xxxqqqq" => "bar"})
assert_equal x, Symbol.all_symbols.count
+ ensure
+ GC.enable if gc_disabled == false
end
# find should handle strings that come from URLs
@@ -488,6 +494,12 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::RecordNotFound) { Topic.where(topics: { approved: true }).find(1) }
end
+ def test_find_on_combined_explicit_and_hashed_table_names
+ assert Topic.where('topics.approved' => false, topics: { author_name: "David" }).find(1)
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.where('topics.approved' => true, topics: { author_name: "David" }).find(1) }
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.where('topics.approved' => false, topics: { author_name: "Melanie" }).find(1) }
+ end
+
def test_find_with_hash_conditions_on_joined_table
firms = Firm.joins(:account).where(:accounts => { :credit_limit => 50 })
assert_equal 1, firms.size
@@ -536,30 +548,6 @@ class FinderTest < ActiveRecord::TestCase
assert_equal [1,2,6,7,8], Comment.where(id: [1..2, 6..8]).to_a.map(&:id).sort
end
- def test_find_on_hash_conditions_with_nested_array_of_integers_and_ranges
- assert_deprecated do
- assert_equal [1,2,3,5,6,7,8,9], Comment.where(id: [[1..2], 3, [5], 6..8, 9]).to_a.map(&:id).sort
- end
- end
-
- def test_find_on_hash_conditions_with_array_of_integers_and_arrays
- assert_deprecated do
- assert_equal [1,2,3,5,6,7,8,9], Comment.where(id: [[1, 2], 3, 5, [6, [7], 8], 9]).to_a.map(&:id).sort
- end
- end
-
- def test_find_on_hash_conditions_with_nested_array_of_integers_and_ranges_and_nils
- assert_deprecated do
- assert_equal [1,3,4,5], Topic.where(parent_id: [[2..6], nil]).to_a.map(&:id).sort
- end
- end
-
- def test_find_on_hash_conditions_with_nested_array_of_integers_and_ranges_and_more_nils
- assert_deprecated do
- assert_equal [], Topic.where(parent_id: [[7..10, nil, [nil]], [nil]]).to_a.map(&:id).sort
- end
- end
-
def test_find_on_multiple_hash_conditions
assert Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: false).find(1)
assert_raise(ActiveRecord::RecordNotFound) { Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: true).find(1) }
@@ -933,7 +921,7 @@ class FinderTest < ActiveRecord::TestCase
joins('LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id').
where('project_id=1').to_a
assert_equal 3, developers_on_project_one.length
- developer_names = developers_on_project_one.map { |d| d.name }
+ developer_names = developers_on_project_one.map(&:name)
assert developer_names.include?('David')
assert developer_names.include?('Jamis')
end
@@ -959,7 +947,6 @@ class FinderTest < ActiveRecord::TestCase
end
end
- # http://dev.rubyonrails.org/ticket/6778
def test_find_ignores_previously_inserted_record
Post.create!(:title => 'test', :body => 'it out')
assert_equal [], Post.where(id: nil)
@@ -988,7 +975,7 @@ class FinderTest < ActiveRecord::TestCase
end
def test_select_values
- assert_equal ["1","2","3","4","5","6","7","8","9", "10", "11"], Company.connection.select_values("SELECT id FROM companies ORDER BY id").map! { |i| i.to_s }
+ assert_equal ["1","2","3","4","5","6","7","8","9", "10", "11"], Company.connection.select_values("SELECT id FROM companies ORDER BY id").map!(&:to_s)
assert_equal ["37signals","Summit","Microsoft", "Flamboyant Software", "Ex Nihilo", "RailsCore", "Leetsoft", "Jadedpixel", "Odegy", "Ex Nihilo Part Deux", "Apex"], Company.connection.select_values("SELECT name FROM companies ORDER BY id")
end
@@ -1014,7 +1001,7 @@ class FinderTest < ActiveRecord::TestCase
where(client_of: [2, 1, nil],
name: ['37signals', 'Summit', 'Microsoft']).
order('client_of DESC').
- map { |x| x.client_of }
+ map(&:client_of)
assert client_of.include?(nil)
assert_equal [2, 1].sort, client_of.compact.sort
@@ -1024,7 +1011,7 @@ class FinderTest < ActiveRecord::TestCase
client_of = Company.
where(client_of: [nil]).
order('client_of DESC').
- map { |x| x.client_of }
+ map(&:client_of)
assert_equal [], client_of.compact
end
@@ -1115,6 +1102,26 @@ class FinderTest < ActiveRecord::TestCase
end
end
+ test "find on a scope does not perform statement caching" do
+ honda = cars(:honda)
+ zyke = cars(:zyke)
+ tyre = honda.tyres.create!
+ tyre2 = zyke.tyres.create!
+
+ assert_equal tyre, honda.tyres.custom_find(tyre.id)
+ assert_equal tyre2, zyke.tyres.custom_find(tyre2.id)
+ end
+
+ test "find_by on a scope does not perform statement caching" do
+ honda = cars(:honda)
+ zyke = cars(:zyke)
+ tyre = honda.tyres.create!
+ tyre2 = zyke.tyres.create!
+
+ assert_equal tyre, honda.tyres.custom_find_by(id: tyre.id)
+ assert_equal tyre2, zyke.tyres.custom_find_by(id: tyre2.id)
+ end
+
protected
def bind(statement, *vars)
if vars.first.is_a?(Hash)
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 7141d3ee7f..f8acdcb51e 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -5,11 +5,13 @@ require 'models/admin/randomly_named_c1'
require 'models/admin/user'
require 'models/binary'
require 'models/book'
+require 'models/bulb'
require 'models/category'
require 'models/company'
require 'models/computer'
require 'models/course'
require 'models/developer'
+require 'models/computer'
require 'models/joke'
require 'models/matey'
require 'models/parrot'
@@ -26,7 +28,7 @@ require 'tempfile'
class FixturesTest < ActiveRecord::TestCase
self.use_instantiated_fixtures = true
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
# other_topics fixture should not be included here
fixtures :topics, :developers, :accounts, :tasks, :categories, :funny_jokes, :binaries, :traffic_lights
@@ -271,7 +273,7 @@ class HasManyThroughFixture < ActiveSupport::TestCase
Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } }
end
- def test_has_many_through
+ def test_has_many_through_with_default_table_name
pt = make_model "ParrotTreasure"
parrot = make_model "Parrot"
treasure = make_model "Treasure"
@@ -290,6 +292,24 @@ class HasManyThroughFixture < ActiveSupport::TestCase
assert_equal load_has_and_belongs_to_many['parrots_treasures'], rows['parrots_treasures']
end
+ def test_has_many_through_with_renamed_table
+ pt = make_model "ParrotTreasure"
+ parrot = make_model "Parrot"
+ treasure = make_model "Treasure"
+
+ pt.belongs_to :parrot, :class => parrot
+ pt.belongs_to :treasure, :class => treasure
+
+ parrot.has_many :parrot_treasures, :class => pt
+ parrot.has_many :treasures, :through => :parrot_treasures
+
+ parrots = File.join FIXTURES_ROOT, 'parrots'
+
+ fs = ActiveRecord::FixtureSet.new parrot.connection, "parrots", parrot, parrots
+ rows = fs.table_rows
+ assert_equal load_has_and_belongs_to_many['parrots_treasures'], rows['parrot_treasures']
+ end
+
def load_has_and_belongs_to_many
parrot = make_model "Parrot"
parrot.has_and_belongs_to_many :treasures
@@ -307,7 +327,7 @@ if Account.connection.respond_to?(:reset_pk_sequence!)
fixtures :companies
def setup
- @instances = [Account.new(:credit_limit => 50), Company.new(:name => 'RoR Consulting')]
+ @instances = [Account.new(:credit_limit => 50), Company.new(:name => 'RoR Consulting'), Course.new(name: 'Test')]
ActiveRecord::FixtureSet.reset_cache # make sure tables get reinitialized
end
@@ -399,7 +419,7 @@ end
class TransactionalFixturesTest < ActiveRecord::TestCase
self.use_instantiated_fixtures = true
- self.use_transactional_fixtures = true
+ self.use_transactional_tests = true
fixtures :topics
@@ -491,7 +511,7 @@ class CheckSetTableNameFixturesTest < ActiveRecord::TestCase
fixtures :funny_jokes
# Set to false to blow away fixtures cache and ensure our fixtures are loaded
# and thus takes into account our set_fixture_class
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
def test_table_method
assert_kind_of Joke, funny_jokes(:a_joke)
@@ -503,7 +523,7 @@ class FixtureNameIsNotTableNameFixturesTest < ActiveRecord::TestCase
fixtures :items
# Set to false to blow away fixtures cache and ensure our fixtures are loaded
# and thus takes into account our set_fixture_class
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
def test_named_accessor
assert_kind_of Book, items(:dvd)
@@ -515,7 +535,7 @@ class FixtureNameIsNotTableNameMultipleFixturesTest < ActiveRecord::TestCase
fixtures :items, :funny_jokes
# Set to false to blow away fixtures cache and ensure our fixtures are loaded
# and thus takes into account our set_fixture_class
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
def test_named_accessor_of_differently_named_fixture
assert_kind_of Book, items(:dvd)
@@ -529,7 +549,7 @@ end
class CustomConnectionFixturesTest < ActiveRecord::TestCase
set_fixture_class :courses => Course
fixtures :courses
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
def test_leaky_destroy
assert_nothing_raised { courses(:ruby) }
@@ -544,7 +564,7 @@ end
class TransactionalFixturesOnCustomConnectionTest < ActiveRecord::TestCase
set_fixture_class :courses => Course
fixtures :courses
- self.use_transactional_fixtures = true
+ self.use_transactional_tests = true
def test_leaky_destroy
assert_nothing_raised { courses(:ruby) }
@@ -560,7 +580,7 @@ class InvalidTableNameFixturesTest < ActiveRecord::TestCase
fixtures :funny_jokes
# Set to false to blow away fixtures cache and ensure our fixtures are loaded
# and thus takes into account our lack of set_fixture_class
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
def test_raises_error
assert_raise ActiveRecord::FixtureClassNotFound do
@@ -574,7 +594,7 @@ class CheckEscapedYamlFixturesTest < ActiveRecord::TestCase
fixtures :funny_jokes
# Set to false to blow away fixtures cache and ensure our fixtures are loaded
# and thus takes into account our set_fixture_class
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
def test_proper_escaped_fixture
assert_equal "The \\n Aristocrats\nAte the candy\n", funny_jokes(:another_joke).name
@@ -644,7 +664,7 @@ class LoadAllFixturesWithPathnameTest < ActiveRecord::TestCase
end
class FasterFixturesTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
fixtures :categories, :authors
def load_extra_fixture(name)
@@ -670,7 +690,8 @@ class FasterFixturesTest < ActiveRecord::TestCase
end
class FoxyFixturesTest < ActiveRecord::TestCase
- fixtures :parrots, :parrots_pirates, :pirates, :treasures, :mateys, :ships, :computers, :developers, :"admin/accounts", :"admin/users"
+ fixtures :parrots, :parrots_pirates, :pirates, :treasures, :mateys, :ships, :computers,
+ :developers, :"admin/accounts", :"admin/users", :live_parrots, :dead_parrots
if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
require 'models/uuid_parent'
@@ -790,6 +811,10 @@ class FoxyFixturesTest < ActiveRecord::TestCase
assert_equal("X marks the spot!", pirates(:mark).catchphrase)
end
+ def test_supports_label_interpolation_for_fixnum_label
+ assert_equal("#1 pirate!", pirates(1).catchphrase)
+ end
+
def test_supports_polymorphic_belongs_to
assert_equal(pirates(:redbeard), treasures(:sapphire).looter)
assert_equal(parrots(:louis), treasures(:ruby).looter)
@@ -806,6 +831,12 @@ class FoxyFixturesTest < ActiveRecord::TestCase
assert_equal pirates(:blackbeard), parrots(:polly).killer
end
+ def test_supports_sti_with_respective_files
+ assert_kind_of LiveParrot, live_parrots(:dusty)
+ assert_kind_of DeadParrot, dead_parrots(:deadbird)
+ assert_equal pirates(:blackbeard), dead_parrots(:deadbird).killer
+ end
+
def test_namespaced_models
assert admin_accounts(:signals37).users.include?(admin_users(:david))
assert_equal 2, admin_accounts(:signals37).users.size
@@ -828,9 +859,9 @@ class CustomNameForFixtureOrModelTest < ActiveRecord::TestCase
set_fixture_class :randomly_named_a9 =>
ClassNameThatDoesNotFollowCONVENTIONS,
:'admin/randomly_named_a9' =>
- Admin::ClassNameThatDoesNotFollowCONVENTIONS,
+ Admin::ClassNameThatDoesNotFollowCONVENTIONS1,
'admin/randomly_named_b0' =>
- Admin::ClassNameThatDoesNotFollowCONVENTIONS
+ Admin::ClassNameThatDoesNotFollowCONVENTIONS2
fixtures :randomly_named_a9, 'admin/randomly_named_a9',
:'admin/randomly_named_b0'
@@ -841,14 +872,27 @@ class CustomNameForFixtureOrModelTest < ActiveRecord::TestCase
end
def test_named_accessor_for_randomly_named_namespaced_fixture_and_class
- assert_kind_of Admin::ClassNameThatDoesNotFollowCONVENTIONS,
+ assert_kind_of Admin::ClassNameThatDoesNotFollowCONVENTIONS1,
admin_randomly_named_a9(:first_instance)
- assert_kind_of Admin::ClassNameThatDoesNotFollowCONVENTIONS,
+ assert_kind_of Admin::ClassNameThatDoesNotFollowCONVENTIONS2,
admin_randomly_named_b0(:second_instance)
end
def test_table_name_is_defined_in_the_model
- assert_equal 'randomly_named_table', ActiveRecord::FixtureSet::all_loaded_fixtures["admin/randomly_named_a9"].table_name
- assert_equal 'randomly_named_table', Admin::ClassNameThatDoesNotFollowCONVENTIONS.table_name
+ assert_equal 'randomly_named_table2', ActiveRecord::FixtureSet::all_loaded_fixtures["admin/randomly_named_a9"].table_name
+ assert_equal 'randomly_named_table2', Admin::ClassNameThatDoesNotFollowCONVENTIONS1.table_name
+ end
+end
+
+class FixturesWithDefaultScopeTest < ActiveRecord::TestCase
+ fixtures :bulbs
+
+ test "inserts fixtures excluded by a default scope" do
+ assert_equal 1, Bulb.count
+ assert_equal 2, Bulb.unscoped.count
+ end
+
+ test "allows access to fixtures excluded by a default scope" do
+ assert_equal "special", bulbs(:special).name
end
end
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 80ac57ec7c..12c793c408 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -24,15 +24,15 @@ ActiveSupport::Deprecation.debug = true
# Disable available locale checks to avoid warnings running the test suite.
I18n.enforce_available_locales = false
-# Enable raise errors in after_commit and after_rollback.
-ActiveRecord::Base.raise_in_transactional_callbacks = true
-
# Connect to the database
ARTest.connect
# Quote "type" if it's a reserved word for the current connection.
QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name('type')
+# FIXME: Remove this when the deprecation cycle on TZ aware types by default ends.
+ActiveRecord::Base.time_zone_aware_types << :time
+
def current_adapter?(*types)
types.any? do |type|
ActiveRecord::ConnectionAdapters.const_defined?(type) &&
@@ -46,7 +46,7 @@ def in_memory_db?
end
def mysql_56?
- current_adapter?(:Mysql2Adapter) &&
+ current_adapter?(:MysqlAdapter, :Mysql2Adapter) &&
ActiveRecord::Base.connection.send(:version).join(".") >= "5.6.0"
end
@@ -124,7 +124,7 @@ def enable_extension!(extension, connection)
return connection.reconnect! if connection.extension_enabled?(extension)
connection.enable_extension extension
- connection.commit_db_transaction
+ connection.commit_db_transaction if connection.transaction_open?
connection.reconnect!
end
@@ -143,7 +143,7 @@ class ActiveSupport::TestCase
self.fixture_path = FIXTURES_ROOT
self.use_instantiated_fixtures = false
- self.use_transactional_fixtures = true
+ self.use_transactional_tests = true
def create_fixtures(*fixture_set_names, &block)
ActiveRecord::FixtureSet.create_fixtures(ActiveSupport::TestCase.fixture_path, fixture_set_names, fixture_class_names, &block)
@@ -203,8 +203,3 @@ module InTimeZone
end
require 'mocha/setup' # FIXME: stop using mocha
-
-# FIXME: we have tests that depend on run order, we should fix that and
-# remove this method call.
-require 'active_support/test_case'
-ActiveSupport::TestCase.test_order = :sorted
diff --git a/activerecord/test/cases/hot_compatibility_test.rb b/activerecord/test/cases/hot_compatibility_test.rb
index b4617cf6f9..5ba9a1029a 100644
--- a/activerecord/test/cases/hot_compatibility_test.rb
+++ b/activerecord/test/cases/hot_compatibility_test.rb
@@ -1,7 +1,7 @@
require 'cases/helper'
class HotCompatibilityTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
setup do
@klass = Class.new(ActiveRecord::Base) do
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 792950d24d..3268555cb8 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -22,7 +22,7 @@ class InheritanceTest < ActiveRecord::TestCase
company = Company.first
company = company.dup
company.extend(Module.new {
- def read_attribute(name)
+ def _read_attribute(name)
return ' ' if name == 'type'
super
end
@@ -121,6 +121,12 @@ class InheritanceTest < ActiveRecord::TestCase
assert_kind_of Cabbage, cabbage
end
+ def test_becomes_and_change_tracking_for_inheritance_columns
+ cucumber = Vegetable.find(1)
+ cabbage = cucumber.becomes!(Cabbage)
+ assert_equal ['Cucumber', 'Cabbage'], cabbage.custom_type_change
+ end
+
def test_alt_becomes_bang_resets_inheritance_type_column
vegetable = Vegetable.create!(name: "Red Pepper")
assert_nil vegetable.custom_type
@@ -294,17 +300,17 @@ class InheritanceTest < ActiveRecord::TestCase
def test_eager_load_belongs_to_something_inherited
account = Account.all.merge!(:includes => :firm).find(1)
- assert account.association_cache.key?(:firm), "nil proves eager load failed"
+ assert account.association(:firm).loaded?, "association was not eager loaded"
end
def test_alt_eager_loading
cabbage = RedCabbage.all.merge!(:includes => :seller).find(4)
- assert cabbage.association_cache.key?(:seller), "nil proves eager load failed"
+ assert cabbage.association(:seller).loaded?, "association was not eager loaded"
end
def test_eager_load_belongs_to_primary_key_quoting
con = Account.connection
- assert_sql(/#{con.quote_table_name('companies')}.#{con.quote_column_name('id')} IN \(1\)/) do
+ assert_sql(/#{con.quote_table_name('companies')}.#{con.quote_column_name('id')} = 1/) do
Account.all.merge!(:includes => :firm).find(1)
end
end
diff --git a/activerecord/test/cases/integration_test.rb b/activerecord/test/cases/integration_test.rb
index dfb8a608cb..018b7b0d8f 100644
--- a/activerecord/test/cases/integration_test.rb
+++ b/activerecord/test/cases/integration_test.rb
@@ -1,8 +1,8 @@
-# encoding: utf-8
require 'cases/helper'
require 'models/company'
require 'models/developer'
+require 'models/computer'
require 'models/owner'
require 'models/pet'
diff --git a/activerecord/test/cases/invalid_connection_test.rb b/activerecord/test/cases/invalid_connection_test.rb
index 8416c81f45..6523fc29fd 100644
--- a/activerecord/test/cases/invalid_connection_test.rb
+++ b/activerecord/test/cases/invalid_connection_test.rb
@@ -1,7 +1,7 @@
require "cases/helper"
class TestAdapterWithInvalidConnection < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
class Bird < ActiveRecord::Base
end
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 5a4b1fb919..9e4998a946 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -33,8 +33,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase
p1 = Person.find(1)
assert_equal 0, p1.lock_version
- Person.expects(:quote_value).with(0, Person.columns_hash[Person.locking_column]).returns('0').once
-
p1.first_name = 'anika2'
p1.save!
@@ -217,10 +215,12 @@ class OptimisticLockingTest < ActiveRecord::TestCase
def test_lock_with_custom_column_without_default_sets_version_to_zero
t1 = LockWithCustomColumnWithoutDefault.new
assert_equal 0, t1.custom_lock_version
+ assert_nil t1.custom_lock_version_before_type_cast
- t1.save
- t1 = LockWithCustomColumnWithoutDefault.find(t1.id)
+ t1.save!
+ t1.reload
assert_equal 0, t1.custom_lock_version
+ assert [0, "0"].include?(t1.custom_lock_version_before_type_cast)
end
def test_readonly_attributes
@@ -285,10 +285,10 @@ end
class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase
fixtures :people, :legacy_things, :references
- # need to disable transactional fixtures, because otherwise the sqlite3
+ # need to disable transactional tests, because otherwise the sqlite3
# adapter (at least) chokes when we try and change the schema in the middle
# of a test (see test_increment_counter_*).
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
{ :lock_version => Person, :custom_lock_version => LegacyThing }.each do |name, model|
define_method("test_increment_counter_updates_#{name}") do
@@ -365,7 +365,7 @@ end
# (See exec vs. async_exec in the PostgreSQL adapter.)
unless in_memory_db?
class PessimisticLockingTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
fixtures :people, :readers
def setup
diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index a578e81844..4192d12ff4 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -63,14 +63,6 @@ class LogSubscriberTest < ActiveRecord::TestCase
assert_match(/ruby rails/, logger.debugs.first)
end
- def test_ignore_binds_payload_with_nil_column
- event = Struct.new(:duration, :payload)
-
- logger = TestDebugLogSubscriber.new
- logger.sql(event.new(0, sql: 'hi mom!', binds: [[nil, 1]]))
- assert_equal 1, logger.debugs.length
- end
-
def test_basic_query_logging
Developer.all.load
wait
@@ -125,12 +117,5 @@ class LogSubscriberTest < ActiveRecord::TestCase
wait
assert_match(/<16 bytes of binary data>/, @logger.logged(:debug).join)
end
-
- def test_nil_binary_data_is_logged
- binary = Binary.create(data: "")
- binary.update_attributes(data: nil)
- wait
- assert_match(/<NULL binary data>/, @logger.logged(:debug).join)
- end
end
end
diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
index d91e7142b3..46a62c272f 100644
--- a/activerecord/test/cases/migration/change_schema_test.rb
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -68,8 +68,8 @@ module ActiveRecord
five = columns.detect { |c| c.name == "five" } unless mysql
assert_equal "hello", one.default
- assert_equal true, two.type_cast_from_database(two.default)
- assert_equal false, three.type_cast_from_database(three.default)
+ assert_equal true, connection.lookup_cast_type_from_column(two).deserialize(two.default)
+ assert_equal false, connection.lookup_cast_type_from_column(three).deserialize(three.default)
assert_equal '1', four.default
assert_equal "hello", five.default unless mysql
end
@@ -82,7 +82,7 @@ module ActiveRecord
columns = connection.columns(:testings)
array_column = columns.detect { |c| c.name == "foo" }
- assert array_column.array
+ assert array_column.array?
end
def test_create_table_with_array_column
@@ -93,7 +93,7 @@ module ActiveRecord
columns = connection.columns(:testings)
array_column = columns.detect { |c| c.name == "foo" }
- assert array_column.array
+ assert array_column.array?
end
end
@@ -195,32 +195,29 @@ module ActiveRecord
end
def test_create_table_with_timestamps_should_create_datetime_columns
- # FIXME: Remove the silence when we change the default `null` behavior
- ActiveSupport::Deprecation.silence do
- connection.create_table table_name do |t|
- t.timestamps
- end
+ connection.create_table table_name do |t|
+ t.timestamps
end
created_columns = connection.columns(table_name)
created_at_column = created_columns.detect {|c| c.name == 'created_at' }
updated_at_column = created_columns.detect {|c| c.name == 'updated_at' }
- assert created_at_column.null
- assert updated_at_column.null
+ assert !created_at_column.null
+ assert !updated_at_column.null
end
def test_create_table_with_timestamps_should_create_datetime_columns_with_options
connection.create_table table_name do |t|
- t.timestamps :null => false
+ t.timestamps null: true
end
created_columns = connection.columns(table_name)
created_at_column = created_columns.detect {|c| c.name == 'created_at' }
updated_at_column = created_columns.detect {|c| c.name == 'updated_at' }
- assert !created_at_column.null
- assert !updated_at_column.null
+ assert created_at_column.null
+ assert updated_at_column.null
end
def test_create_table_without_a_block
@@ -406,6 +403,17 @@ module ActiveRecord
end
end
+ def test_drop_table_if_exists
+ connection.create_table(:testings)
+ assert connection.table_exists?(:testings)
+ connection.drop_table(:testings, if_exists: true)
+ assert_not connection.table_exists?(:testings)
+ end
+
+ def test_drop_table_if_exists_nothing_raised
+ assert_nothing_raised { connection.drop_table(:nonexistent, if_exists: true) }
+ end
+
private
def testing_table_with_only_foo_attribute
connection.create_table :testings, :id => false do |t|
@@ -415,5 +423,36 @@ module ActiveRecord
yield
end
end
+
+ if ActiveRecord::Base.connection.supports_foreign_keys?
+ class ChangeSchemaWithDependentObjectsTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table :trains
+ @connection.create_table(:wagons) { |t| t.references :train }
+ @connection.add_foreign_key :wagons, :trains
+ end
+
+ teardown do
+ [:wagons, :trains].each do |table|
+ @connection.drop_table table, if_exists: true
+ end
+ end
+
+ def test_create_table_with_force_cascade_drops_dependent_objects
+ skip "MySQL > 5.5 does not drop dependent objects with DROP TABLE CASCADE" if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+ # can't re-create table referenced by foreign key
+ assert_raises(ActiveRecord::StatementInvalid) do
+ @connection.create_table :trains, force: true
+ end
+
+ # can recreate referenced table with force: :cascade
+ @connection.create_table :trains, force: :cascade
+ assert_equal [], @connection.foreign_keys(:wagons)
+ end
+ end
+ end
end
end
diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb
index 777a48ad14..2ffe7a1b0d 100644
--- a/activerecord/test/cases/migration/change_table_test.rb
+++ b/activerecord/test/cases/migration/change_table_test.rb
@@ -13,7 +13,7 @@ module ActiveRecord
end
def with_change_table
- yield ConnectionAdapters::Table.new(:delete_me, @connection)
+ yield ActiveRecord::Base.connection.update_table_definition(:delete_me, @connection)
end
def test_references_column_type_adds_id
@@ -95,8 +95,15 @@ module ActiveRecord
def test_remove_timestamps_creates_updated_at_and_created_at
with_change_table do |t|
- @connection.expect :remove_timestamps, nil, [:delete_me]
- t.remove_timestamps
+ @connection.expect :remove_timestamps, nil, [:delete_me, { null: true }]
+ t.remove_timestamps({ null: true })
+ end
+ end
+
+ def test_primary_key_creates_primary_key_column
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, :id, :primary_key, primary_key: true, first: true]
+ t.primary_key :id, first: true
end
end
@@ -108,6 +115,14 @@ module ActiveRecord
end
end
+ def test_bigint_creates_bigint_column
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, :foo, :bigint, {}]
+ @connection.expect :add_column, nil, [:delete_me, :bar, :bigint, {}]
+ t.bigint :foo, :bar
+ end
+ end
+
def test_string_creates_string_column
with_change_table do |t|
@connection.expect :add_column, nil, [:delete_me, :foo, :string, {}]
@@ -116,6 +131,24 @@ module ActiveRecord
end
end
+ if current_adapter?(:PostgreSQLAdapter)
+ def test_json_creates_json_column
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, :foo, :json, {}]
+ @connection.expect :add_column, nil, [:delete_me, :bar, :json, {}]
+ t.json :foo, :bar
+ end
+ end
+
+ def test_xml_creates_xml_column
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, :foo, :xml, {}]
+ @connection.expect :add_column, nil, [:delete_me, :bar, :xml, {}]
+ t.xml :foo, :bar
+ end
+ end
+ end
+
def test_column_creates_column
with_change_table do |t|
@connection.expect :add_column, nil, [:delete_me, :bar, :integer, {}]
@@ -213,6 +246,12 @@ module ActiveRecord
t.rename :bar, :baz
end
end
+
+ def test_table_name_set
+ with_change_table do |t|
+ assert_equal :delete_me, t.name
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb
index 763aa88f72..8d8e661aa5 100644
--- a/activerecord/test/cases/migration/column_attributes_test.rb
+++ b/activerecord/test/cases/migration/column_attributes_test.rb
@@ -5,7 +5,7 @@ module ActiveRecord
class ColumnAttributesTest < ActiveRecord::TestCase
include ActiveRecord::Migration::TestHelper
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
def test_add_column_newline_default
string = "foo\nbar"
diff --git a/activerecord/test/cases/migration/column_positioning_test.rb b/activerecord/test/cases/migration/column_positioning_test.rb
index 77a752f050..4637970ce0 100644
--- a/activerecord/test/cases/migration/column_positioning_test.rb
+++ b/activerecord/test/cases/migration/column_positioning_test.rb
@@ -3,7 +3,7 @@ require 'cases/helper'
module ActiveRecord
class Migration
class ColumnPositioningTest < ActiveRecord::TestCase
- attr_reader :connection, :table_name
+ attr_reader :connection
alias :conn :connection
def setup
@@ -25,30 +25,30 @@ module ActiveRecord
if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
def test_column_positioning
- assert_equal %w(first second third), conn.columns(:testings).map {|c| c.name }
+ assert_equal %w(first second third), conn.columns(:testings).map(&:name)
end
def test_add_column_with_positioning
conn.add_column :testings, :new_col, :integer
- assert_equal %w(first second third new_col), conn.columns(:testings).map {|c| c.name }
+ assert_equal %w(first second third new_col), conn.columns(:testings).map(&:name)
end
def test_add_column_with_positioning_first
conn.add_column :testings, :new_col, :integer, :first => true
- assert_equal %w(new_col first second third), conn.columns(:testings).map {|c| c.name }
+ assert_equal %w(new_col first second third), conn.columns(:testings).map(&:name)
end
def test_add_column_with_positioning_after
conn.add_column :testings, :new_col, :integer, :after => :first
- assert_equal %w(first new_col second third), conn.columns(:testings).map {|c| c.name }
+ assert_equal %w(first new_col second third), conn.columns(:testings).map(&:name)
end
def test_change_column_with_positioning
conn.change_column :testings, :second, :integer, :first => true
- assert_equal %w(second first third), conn.columns(:testings).map {|c| c.name }
+ assert_equal %w(second first third), conn.columns(:testings).map(&:name)
conn.change_column :testings, :second, :integer, :after => :third
- assert_equal %w(first third second), conn.columns(:testings).map {|c| c.name }
+ assert_equal %w(first third second), conn.columns(:testings).map(&:name)
end
end
end
diff --git a/activerecord/test/cases/migration/columns_test.rb b/activerecord/test/cases/migration/columns_test.rb
index e6aa901814..5fc7702dfa 100644
--- a/activerecord/test/cases/migration/columns_test.rb
+++ b/activerecord/test/cases/migration/columns_test.rb
@@ -5,7 +5,7 @@ module ActiveRecord
class ColumnsTest < ActiveRecord::TestCase
include ActiveRecord::Migration::TestHelper
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
# FIXME: this is more of an integration test with AR::Base and the
# schema modifications. Maybe we should move this?
@@ -65,7 +65,7 @@ module ActiveRecord
if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
def test_mysql_rename_column_preserves_auto_increment
rename_column "test_models", "id", "id_test"
- assert_equal "auto_increment", connection.columns("test_models").find { |c| c.name == "id_test" }.extra
+ assert connection.columns("test_models").find { |c| c.name == "id_test" }.auto_increment?
TestModel.reset_column_information
ensure
rename_column "test_models", "id_test", "id"
@@ -196,7 +196,7 @@ module ActiveRecord
old_columns = connection.columns(TestModel.table_name)
assert old_columns.find { |c|
- default = c.type_cast_from_database(c.default)
+ default = connection.lookup_cast_type_from_column(c).deserialize(c.default)
c.name == 'approved' && c.type == :boolean && default == true
}
@@ -204,11 +204,11 @@ module ActiveRecord
new_columns = connection.columns(TestModel.table_name)
assert_not new_columns.find { |c|
- default = c.type_cast_from_database(c.default)
+ default = connection.lookup_cast_type_from_column(c).deserialize(c.default)
c.name == 'approved' and c.type == :boolean and default == true
}
assert new_columns.find { |c|
- default = c.type_cast_from_database(c.default)
+ default = connection.lookup_cast_type_from_column(c).deserialize(c.default)
c.name == 'approved' and c.type == :boolean and default == false
}
change_column :test_models, :approved, :boolean, :default => true
diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb
index e955beae1a..3844b1a92e 100644
--- a/activerecord/test/cases/migration/command_recorder_test.rb
+++ b/activerecord/test/cases/migration/command_recorder_test.rb
@@ -237,8 +237,8 @@ module ActiveRecord
end
def test_invert_remove_timestamps
- add = @recorder.inverse_of :remove_timestamps, [:table]
- assert_equal [:add_timestamps, [:table], nil], add
+ add = @recorder.inverse_of :remove_timestamps, [:table, { null: true }]
+ assert_equal [:add_timestamps, [:table, {null: true }], nil], add
end
def test_invert_add_reference
@@ -256,6 +256,11 @@ module ActiveRecord
assert_equal [:add_reference, [:table, :taggable, { polymorphic: true }], nil], add
end
+ def test_invert_remove_reference_with_index_and_foreign_key
+ add = @recorder.inverse_of :remove_reference, [:table, :taggable, { index: true, foreign_key: true }]
+ assert_equal [:add_reference, [:table, :taggable, { index: true, foreign_key: true }], nil], add
+ end
+
def test_invert_remove_belongs_to_alias
add = @recorder.inverse_of :remove_belongs_to, [:table, :user]
assert_equal [:add_reference, [:table, :user], nil], add
diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb
index bea9d6b2c9..8fd08fe4ce 100644
--- a/activerecord/test/cases/migration/create_join_table_test.rb
+++ b/activerecord/test/cases/migration/create_join_table_test.rb
@@ -140,7 +140,7 @@ module ActiveRecord
tables_after = connection.tables - tables_before
tables_after.each do |table|
- connection.execute "DROP TABLE #{table}"
+ connection.drop_table table
end
end
end
diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb
index 406dd70c37..7f4790bf3e 100644
--- a/activerecord/test/cases/migration/foreign_key_test.rb
+++ b/activerecord/test/cases/migration/foreign_key_test.rb
@@ -8,6 +8,7 @@ module ActiveRecord
class ForeignKeyTest < ActiveRecord::TestCase
include DdlHelper
include SchemaDumpingHelper
+ include ActiveSupport::Testing::Stream
class Rocket < ActiveRecord::Base
end
@@ -17,11 +18,11 @@ module ActiveRecord
setup do
@connection = ActiveRecord::Base.connection
- @connection.create_table "rockets" do |t|
+ @connection.create_table "rockets", force: true do |t|
t.string :name
end
- @connection.create_table "astronauts" do |t|
+ @connection.create_table "astronauts", force: true do |t|
t.string :name
t.references :rocket
end
@@ -29,8 +30,8 @@ module ActiveRecord
teardown do
if defined?(@connection)
- @connection.drop_table "astronauts" if @connection.table_exists? 'astronauts'
- @connection.drop_table "rockets" if @connection.table_exists? 'rockets'
+ @connection.drop_table "astronauts", if_exists: true
+ @connection.drop_table "rockets", if_exists: true
end
end
@@ -57,7 +58,7 @@ module ActiveRecord
assert_equal "rockets", fk.to_table
assert_equal "rocket_id", fk.column
assert_equal "id", fk.primary_key
- assert_match(/^fk_rails_.{10}$/, fk.name)
+ assert_equal("fk_rails_78146ddd2e", fk.name)
end
def test_add_foreign_key_with_column
@@ -71,7 +72,7 @@ module ActiveRecord
assert_equal "rockets", fk.to_table
assert_equal "rocket_id", fk.column
assert_equal "id", fk.primary_key
- assert_match(/^fk_rails_.{10}$/, fk.name)
+ assert_equal("fk_rails_78146ddd2e", fk.name)
end
def test_add_foreign_key_with_non_standard_primary_key
@@ -146,6 +147,27 @@ module ActiveRecord
assert_equal :nullify, fk.on_update
end
+ def test_foreign_key_exists
+ @connection.add_foreign_key :astronauts, :rockets
+
+ assert @connection.foreign_key_exists?(:astronauts, :rockets)
+ assert_not @connection.foreign_key_exists?(:astronauts, :stars)
+ end
+
+ def test_foreign_key_exists_by_column
+ @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id"
+
+ assert @connection.foreign_key_exists?(:astronauts, column: "rocket_id")
+ assert_not @connection.foreign_key_exists?(:astronauts, column: "star_id")
+ end
+
+ def test_foreign_key_exists_by_name
+ @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", name: "fancy_named_fk"
+
+ assert @connection.foreign_key_exists?(:astronauts, name: "fancy_named_fk")
+ assert_not @connection.foreign_key_exists?(:astronauts, name: "other_fancy_named_fk")
+ end
+
def test_remove_foreign_key_inferes_column
@connection.add_foreign_key :astronauts, :rockets
@@ -162,6 +184,14 @@ module ActiveRecord
assert_equal [], @connection.foreign_keys("astronauts")
end
+ def test_remove_foreign_key_by_symbol_column
+ @connection.add_foreign_key :astronauts, :rockets, column: :rocket_id
+
+ assert_equal 1, @connection.foreign_keys("astronauts").size
+ @connection.remove_foreign_key :astronauts, column: :rocket_id
+ assert_equal [], @connection.foreign_keys("astronauts")
+ end
+
def test_remove_foreign_key_by_name
@connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", name: "fancy_named_fk"
@@ -212,6 +242,7 @@ module ActiveRecord
ensure
silence_stream($stdout) { migration.migrate(:down) }
end
+
end
end
end
diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb
index ac932378fd..b23b9a679f 100644
--- a/activerecord/test/cases/migration/index_test.rb
+++ b/activerecord/test/cases/migration/index_test.rb
@@ -36,6 +36,20 @@ module ActiveRecord
assert connection.index_name_exists?(table_name, 'new_idx', true)
end
+ def test_rename_index_too_long
+ too_long_index_name = good_index_name + 'x'
+ # keep the names short to make Oracle and similar behave
+ connection.add_index(table_name, [:foo], :name => 'old_idx')
+ e = assert_raises(ArgumentError) {
+ connection.rename_index(table_name, 'old_idx', too_long_index_name)
+ }
+ assert_match(/too long; the limit is #{connection.allowed_index_name_length} characters/, e.message)
+
+ # if the adapter doesn't support the indexes call, pick defaults that let the test pass
+ assert connection.index_name_exists?(table_name, 'old_idx', false)
+ end
+
+
def test_double_add_index
connection.add_index(table_name, [:foo], :name => 'some_idx')
assert_raises(ArgumentError) {
diff --git a/activerecord/test/cases/migration/logger_test.rb b/activerecord/test/cases/migration/logger_test.rb
index 319d3e1af3..bf6e684887 100644
--- a/activerecord/test/cases/migration/logger_test.rb
+++ b/activerecord/test/cases/migration/logger_test.rb
@@ -4,7 +4,7 @@ module ActiveRecord
class Migration
class LoggerTest < ActiveRecord::TestCase
# MySQL can't roll back ddl changes
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
Migration = Struct.new(:name, :version) do
def disable_ddl_transaction; false end
diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb
new file mode 100644
index 0000000000..87348d0f90
--- /dev/null
+++ b/activerecord/test/cases/migration/references_foreign_key_test.rb
@@ -0,0 +1,133 @@
+require 'cases/helper'
+
+if ActiveRecord::Base.connection.supports_foreign_keys?
+module ActiveRecord
+ class Migration
+ class ReferencesForeignKeyTest < ActiveRecord::TestCase
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table(:testing_parents, force: true)
+ end
+
+ teardown do
+ @connection.drop_table "testings", if_exists: true
+ @connection.drop_table "testing_parents", if_exists: true
+ end
+
+ test "foreign keys can be created with the table" do
+ @connection.create_table :testings do |t|
+ t.references :testing_parent, foreign_key: true
+ end
+
+ fk = @connection.foreign_keys("testings").first
+ assert_equal "testings", fk.from_table
+ assert_equal "testing_parents", fk.to_table
+ end
+
+ test "no foreign key is created by default" do
+ @connection.create_table :testings do |t|
+ t.references :testing_parent
+ end
+
+ assert_equal [], @connection.foreign_keys("testings")
+ end
+
+ test "options hash can be passed" do
+ @connection.change_table :testing_parents do |t|
+ t.integer :other_id
+ t.index :other_id, unique: true
+ end
+ @connection.create_table :testings do |t|
+ t.references :testing_parent, foreign_key: { primary_key: :other_id }
+ end
+
+ fk = @connection.foreign_keys("testings").find { |k| k.to_table == "testing_parents" }
+ assert_equal "other_id", fk.primary_key
+ end
+
+ test "foreign keys cannot be added to polymorphic relations when creating the table" do
+ @connection.create_table :testings do |t|
+ assert_raises(ArgumentError) do
+ t.references :testing_parent, polymorphic: true, foreign_key: true
+ end
+ end
+ end
+
+ test "foreign keys can be created while changing the table" do
+ @connection.create_table :testings
+ @connection.change_table :testings do |t|
+ t.references :testing_parent, foreign_key: true
+ end
+
+ fk = @connection.foreign_keys("testings").first
+ assert_equal "testings", fk.from_table
+ assert_equal "testing_parents", fk.to_table
+ end
+
+ test "foreign keys are not added by default when changing the table" do
+ @connection.create_table :testings
+ @connection.change_table :testings do |t|
+ t.references :testing_parent
+ end
+
+ assert_equal [], @connection.foreign_keys("testings")
+ end
+
+ test "foreign keys accept options when changing the table" do
+ @connection.change_table :testing_parents do |t|
+ t.integer :other_id
+ t.index :other_id, unique: true
+ end
+ @connection.create_table :testings
+ @connection.change_table :testings do |t|
+ t.references :testing_parent, foreign_key: { primary_key: :other_id }
+ end
+
+ fk = @connection.foreign_keys("testings").find { |k| k.to_table == "testing_parents" }
+ assert_equal "other_id", fk.primary_key
+ end
+
+ test "foreign keys cannot be added to polymorphic relations when changing the table" do
+ @connection.create_table :testings
+ @connection.change_table :testings do |t|
+ assert_raises(ArgumentError) do
+ t.references :testing_parent, polymorphic: true, foreign_key: true
+ end
+ end
+ end
+
+ test "foreign key column can be removed" do
+ @connection.create_table :testings do |t|
+ t.references :testing_parent, index: true, foreign_key: true
+ end
+
+ assert_difference "@connection.foreign_keys('testings').size", -1 do
+ @connection.remove_reference :testings, :testing_parent, foreign_key: true
+ end
+ end
+
+ test "foreign key methods respect pluralize_table_names" do
+ begin
+ original_pluralize_table_names = ActiveRecord::Base.pluralize_table_names
+ ActiveRecord::Base.pluralize_table_names = false
+ @connection.create_table :testing
+ @connection.change_table :testing_parents do |t|
+ t.references :testing, foreign_key: true
+ end
+
+ fk = @connection.foreign_keys("testing_parents").first
+ assert_equal "testing_parents", fk.from_table
+ assert_equal "testing", fk.to_table
+
+ assert_difference "@connection.foreign_keys('testing_parents').size", -1 do
+ @connection.remove_reference :testing_parents, :testing, foreign_key: true
+ end
+ ensure
+ ActiveRecord::Base.pluralize_table_names = original_pluralize_table_names
+ @connection.drop_table "testing", if_exists: true
+ end
+ end
+ end
+ end
+end
+end
diff --git a/activerecord/test/cases/migration/references_statements_test.rb b/activerecord/test/cases/migration/references_statements_test.rb
index 988bd9c89f..f613fd66c3 100644
--- a/activerecord/test/cases/migration/references_statements_test.rb
+++ b/activerecord/test/cases/migration/references_statements_test.rb
@@ -5,7 +5,7 @@ module ActiveRecord
class ReferencesStatementsTest < ActiveRecord::TestCase
include ActiveRecord::Migration::TestHelper
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
def setup
super
diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb
index c8b3f75e10..6d742d3f2f 100644
--- a/activerecord/test/cases/migration/rename_table_test.rb
+++ b/activerecord/test/cases/migration/rename_table_test.rb
@@ -5,7 +5,7 @@ module ActiveRecord
class RenameTableTest < ActiveRecord::TestCase
include ActiveRecord::Migration::TestHelper
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
def setup
super
@@ -39,33 +39,35 @@ module ActiveRecord
end
end
- def test_rename_table
- rename_table :test_models, :octopi
+ unless current_adapter?(:FbAdapter) # Firebird cannot rename tables
+ def test_rename_table
+ rename_table :test_models, :octopi
- connection.execute "INSERT INTO octopi (#{connection.quote_column_name('id')}, #{connection.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')"
+ connection.execute "INSERT INTO octopi (#{connection.quote_column_name('id')}, #{connection.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')"
- assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1")
- end
+ assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1")
+ end
- def test_rename_table_with_an_index
- add_index :test_models, :url
+ def test_rename_table_with_an_index
+ add_index :test_models, :url
- rename_table :test_models, :octopi
+ rename_table :test_models, :octopi
- connection.execute "INSERT INTO octopi (#{connection.quote_column_name('id')}, #{connection.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')"
+ connection.execute "INSERT INTO octopi (#{connection.quote_column_name('id')}, #{connection.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')"
- assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1")
- index = connection.indexes(:octopi).first
- assert index.columns.include?("url")
- assert_equal 'index_octopi_on_url', index.name
- end
+ assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1")
+ index = connection.indexes(:octopi).first
+ assert index.columns.include?("url")
+ assert_equal 'index_octopi_on_url', index.name
+ end
- def test_rename_table_does_not_rename_custom_named_index
- add_index :test_models, :url, name: 'special_url_idx'
+ def test_rename_table_does_not_rename_custom_named_index
+ add_index :test_models, :url, name: 'special_url_idx'
- rename_table :test_models, :octopi
+ rename_table :test_models, :octopi
- assert_equal ['special_url_idx'], connection.indexes(:octopi).map(&:name)
+ assert_equal ['special_url_idx'], connection.indexes(:octopi).map(&:name)
+ end
end
if current_adapter?(:PostgreSQLAdapter)
@@ -84,8 +86,8 @@ module ActiveRecord
assert connection.table_exists? :felines
ensure
disable_extension!('uuid-ossp', connection)
- connection.drop_table :cats if connection.table_exists? :cats
- connection.drop_table :felines if connection.table_exists? :felines
+ connection.drop_table :cats, if_exists: true
+ connection.drop_table :felines, if_exists: true
end
end
end
diff --git a/activerecord/test/cases/migration/table_and_index_test.rb b/activerecord/test/cases/migration/table_and_index_test.rb
index 8fd770abd1..24cba84a09 100644
--- a/activerecord/test/cases/migration/table_and_index_test.rb
+++ b/activerecord/test/cases/migration/table_and_index_test.rb
@@ -6,11 +6,11 @@ module ActiveRecord
def test_add_schema_info_respects_prefix_and_suffix
conn = ActiveRecord::Base.connection
- conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name)
+ conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name, if_exists: true)
# Use shorter prefix and suffix as in Oracle database identifier cannot be larger than 30 characters
ActiveRecord::Base.table_name_prefix = 'p_'
ActiveRecord::Base.table_name_suffix = '_s'
- conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name)
+ conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name, if_exists: true)
conn.initialize_schema_migrations_table
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 6b7a0a9000..b2f209fe97 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -5,6 +5,7 @@ require 'bigdecimal/util'
require 'models/person'
require 'models/topic'
require 'models/developer'
+require 'models/computer'
require MIGRATIONS_ROOT + "/valid/2_we_need_reminders"
require MIGRATIONS_ROOT + "/rename/1_we_need_things"
@@ -13,9 +14,9 @@ require MIGRATIONS_ROOT + "/decimal/1_give_me_big_numbers"
class BigNumber < ActiveRecord::Base
unless current_adapter?(:PostgreSQLAdapter, :SQLite3Adapter)
- attribute :value_of_e, Type::Integer.new
+ attribute :value_of_e, :integer
end
- attribute :my_house_population, Type::Integer.new
+ attribute :my_house_population, :integer
end
class Reminder < ActiveRecord::Base; end
@@ -23,7 +24,7 @@ class Reminder < ActiveRecord::Base; end
class Thing < ActiveRecord::Base; end
class MigrationTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
fixtures :people
@@ -89,7 +90,7 @@ class MigrationTest < ActiveRecord::TestCase
end
def test_migration_detection_without_schema_migration_table
- ActiveRecord::Base.connection.drop_table('schema_migrations') if ActiveRecord::Base.connection.table_exists?('schema_migrations')
+ ActiveRecord::Base.connection.drop_table 'schema_migrations', if_exists: true
migrations_path = MIGRATIONS_ROOT + "/valid"
old_path = ActiveRecord::Migrator.migrations_paths
@@ -118,10 +119,6 @@ class MigrationTest < ActiveRecord::TestCase
end
def test_create_table_with_force_true_does_not_drop_nonexisting_table
- if Person.connection.table_exists?(:testings2)
- Person.connection.drop_table :testings2
- end
-
# using a copy as we need the drop_table method to
# continue to work for the ensure block of the test
temp_conn = Person.connection.dup
@@ -132,7 +129,7 @@ class MigrationTest < ActiveRecord::TestCase
t.column :foo, :string
end
ensure
- Person.connection.drop_table :testings2 rescue nil
+ Person.connection.drop_table :testings2, if_exists: true
end
def connection
@@ -161,6 +158,7 @@ class MigrationTest < ActiveRecord::TestCase
assert !BigNumber.table_exists?
GiveMeBigNumbers.up
+ BigNumber.reset_column_information
assert BigNumber.create(
:bank_balance => 1586.43,
@@ -396,6 +394,7 @@ class MigrationTest < ActiveRecord::TestCase
Thing.reset_table_name
Thing.reset_sequence_name
WeNeedThings.up
+ Thing.reset_column_information
assert Thing.create("content" => "hello world")
assert_equal "hello world", Thing.first.content
@@ -415,6 +414,7 @@ class MigrationTest < ActiveRecord::TestCase
ActiveRecord::Base.table_name_suffix = '_suffix'
Reminder.reset_table_name
Reminder.reset_sequence_name
+ Reminder.reset_column_information
WeNeedReminders.up
assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
assert_equal "hello world", Reminder.first.content
@@ -426,8 +426,6 @@ class MigrationTest < ActiveRecord::TestCase
end
def test_create_table_with_binary_column
- Person.connection.drop_table :binary_testings rescue nil
-
assert_nothing_raised {
Person.connection.create_table :binary_testings do |t|
t.column "data", :binary, :null => false
@@ -439,7 +437,7 @@ class MigrationTest < ActiveRecord::TestCase
assert_nil data_column.default
- Person.connection.drop_table :binary_testings rescue nil
+ Person.connection.drop_table :binary_testings, if_exists: true
end
unless mysql_enforcing_gtid_consistency?
@@ -510,12 +508,14 @@ class MigrationTest < ActiveRecord::TestCase
if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter)
def test_out_of_range_limit_should_raise
Person.connection.drop_table :test_limits rescue nil
- assert_raise(ActiveRecord::ActiveRecordError, "integer limit didn't raise") do
+ e = assert_raise(ActiveRecord::ActiveRecordError, "integer limit didn't raise") do
Person.connection.create_table :test_integer_limits, :force => true do |t|
t.column :bigone, :integer, :limit => 10
end
end
+ assert_match(/No integer type has byte size 10/, e.message)
+
unless current_adapter?(:PostgreSQLAdapter)
assert_raise(ActiveRecord::ActiveRecordError, "text limit didn't raise") do
Person.connection.create_table :test_text_limits, :force => true do |t|
@@ -717,6 +717,8 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
end
class CopyMigrationsTest < ActiveRecord::TestCase
+ include ActiveSupport::Testing::Stream
+
def setup
end
@@ -926,13 +928,4 @@ class CopyMigrationsTest < ActiveRecord::TestCase
ActiveRecord::Base.logger = old
end
- private
-
- def quietly
- silence_stream(STDOUT) do
- silence_stream(STDERR) do
- yield
- end
- end
- end
end
diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb
index f05ca900aa..2ff6938e7b 100644
--- a/activerecord/test/cases/migrator_test.rb
+++ b/activerecord/test/cases/migrator_test.rb
@@ -2,7 +2,7 @@ require "cases/helper"
require "cases/migration/helper"
class MigratorTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
# Use this class to sense if migrations have gone
# up or down.
@@ -149,7 +149,7 @@ class MigratorTest < ActiveRecord::TestCase
def test_up_calls_up
migrations = [Sensor.new(nil, 0), Sensor.new(nil, 1), Sensor.new(nil, 2)]
ActiveRecord::Migrator.new(:up, migrations).migrate
- assert migrations.all? { |m| m.went_up }
+ assert migrations.all?(&:went_up)
assert migrations.all? { |m| !m.went_down }
assert_equal 2, ActiveRecord::Migrator.current_version
end
@@ -160,7 +160,7 @@ class MigratorTest < ActiveRecord::TestCase
migrations = [Sensor.new(nil, 0), Sensor.new(nil, 1), Sensor.new(nil, 2)]
ActiveRecord::Migrator.new(:down, migrations).migrate
assert migrations.all? { |m| !m.went_up }
- assert migrations.all? { |m| m.went_down }
+ assert migrations.all?(&:went_down)
assert_equal 0, ActiveRecord::Migrator.current_version
end
@@ -312,7 +312,7 @@ class MigratorTest < ActiveRecord::TestCase
def test_migrator_db_has_no_schema_migrations_table
_, migrator = migrator_class(3)
- ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations")
+ ActiveRecord::Base.connection.drop_table "schema_migrations", if_exists: true
assert_not ActiveRecord::Base.connection.table_exists?('schema_migrations')
migrator.migrate("valid", 1)
assert ActiveRecord::Base.connection.table_exists?('schema_migrations')
diff --git a/activerecord/test/cases/mixin_test.rb b/activerecord/test/cases/mixin_test.rb
index 7ddb2bfee1..7ebdcac711 100644
--- a/activerecord/test/cases/mixin_test.rb
+++ b/activerecord/test/cases/mixin_test.rb
@@ -61,8 +61,6 @@ class TouchTest < ActiveRecord::TestCase
# Make sure Mixin.record_timestamps gets reset, even if this test fails,
# so that other tests do not fail because Mixin.record_timestamps == false
- rescue Exception => e
- raise e
ensure
Mixin.record_timestamps = true
end
diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb
index e87773df94..7f31325f47 100644
--- a/activerecord/test/cases/modules_test.rb
+++ b/activerecord/test/cases/modules_test.rb
@@ -2,6 +2,7 @@ require "cases/helper"
require 'models/company_in_module'
require 'models/shop'
require 'models/developer'
+require 'models/computer'
class ModulesTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :projects, :developers, :collections, :products, :variants
@@ -67,8 +68,7 @@ class ModulesTest < ActiveRecord::TestCase
end
end
- # need to add an eager loading condition to force the eager loading model into
- # the old join model, to test that. See http://dev.rubyonrails.org/ticket/9640
+ # An eager loading condition to force the eager loading model into the old join model.
def test_eager_loading_in_modules
clients = []
diff --git a/activerecord/test/cases/multiparameter_attributes_test.rb b/activerecord/test/cases/multiparameter_attributes_test.rb
index 14d4ef457d..ae18573126 100644
--- a/activerecord/test/cases/multiparameter_attributes_test.rb
+++ b/activerecord/test/cases/multiparameter_attributes_test.rb
@@ -199,6 +199,7 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes
with_timezone_config default: :utc, aware_attributes: true, zone: -28800 do
+ Topic.reset_column_information
attributes = {
"written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
"written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
@@ -209,6 +210,8 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on.time
assert_equal Time.zone, topic.written_on.time_zone
end
+ ensure
+ Topic.reset_column_information
end
def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes_false
@@ -227,6 +230,7 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
def test_multiparameter_attributes_on_time_with_skip_time_zone_conversion_for_attributes
with_timezone_config default: :utc, aware_attributes: true, zone: -28800 do
Topic.skip_time_zone_conversion_for_attributes = [:written_on]
+ Topic.reset_column_information
attributes = {
"written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
"written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
@@ -238,21 +242,25 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
end
ensure
Topic.skip_time_zone_conversion_for_attributes = []
+ Topic.reset_column_information
end
# Oracle does not have a TIME datatype.
unless current_adapter?(:OracleAdapter)
def test_multiparameter_attributes_on_time_only_column_with_time_zone_aware_attributes_does_not_do_time_zone_conversion
with_timezone_config default: :utc, aware_attributes: true, zone: -28800 do
+ Topic.reset_column_information
attributes = {
"bonus_time(1i)" => "2000", "bonus_time(2i)" => "1", "bonus_time(3i)" => "1",
"bonus_time(4i)" => "16", "bonus_time(5i)" => "24"
}
topic = Topic.find(1)
topic.attributes = attributes
- assert_equal Time.utc(2000, 1, 1, 16, 24, 0), topic.bonus_time
- assert topic.bonus_time.utc?
+ assert_equal Time.zone.local(2000, 1, 1, 16, 24, 0), topic.bonus_time
+ assert_not topic.bonus_time.utc?
end
+ ensure
+ Topic.reset_column_information
end
end
diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb
index 3831de6ae3..39cdcf5403 100644
--- a/activerecord/test/cases/multiple_db_test.rb
+++ b/activerecord/test/cases/multiple_db_test.rb
@@ -4,7 +4,7 @@ require 'models/bird'
require 'models/course'
class MultipleDbTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
def setup
@courses = create_fixtures("courses") { Course.retrieve_connection }
@@ -94,6 +94,13 @@ class MultipleDbTest < ActiveRecord::TestCase
end
unless in_memory_db?
+ def test_count_on_custom_connection
+ ActiveRecord::Base.remove_connection
+ assert_equal 1, College.count
+ ensure
+ ActiveRecord::Base.establish_connection :arunit
+ end
+
def test_associations_should_work_when_model_has_no_connection
begin
ActiveRecord::Base.remove_connection
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index cf96c3fccf..6b4addd52f 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -13,7 +13,7 @@ require 'active_support/hash_with_indifferent_access'
class TestNestedAttributesInGeneral < ActiveRecord::TestCase
teardown do
- Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
+ Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc(&:empty?)
end
def test_base_should_have_an_empty_nested_attributes_options
@@ -300,13 +300,13 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
end
def test_should_not_destroy_an_existing_record_if_allow_destroy_is_false
- Pirate.accepts_nested_attributes_for :ship, :allow_destroy => false, :reject_if => proc { |attributes| attributes.empty? }
+ Pirate.accepts_nested_attributes_for :ship, :allow_destroy => false, :reject_if => proc(&:empty?)
@pirate.update(ship_attributes: { id: @pirate.ship.id, _destroy: '1' })
assert_equal @ship, @pirate.reload.ship
- Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
+ Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc(&:empty?)
end
def test_should_also_work_with_a_HashWithIndifferentAccess
@@ -494,12 +494,12 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
end
def test_should_not_destroy_an_existing_record_if_allow_destroy_is_false
- Ship.accepts_nested_attributes_for :pirate, :allow_destroy => false, :reject_if => proc { |attributes| attributes.empty? }
+ Ship.accepts_nested_attributes_for :pirate, :allow_destroy => false, :reject_if => proc(&:empty?)
@ship.update(pirate_attributes: { id: @ship.pirate.id, _destroy: '1' })
assert_nothing_raised(ActiveRecord::RecordNotFound) { @ship.pirate.reload }
ensure
- Ship.accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
+ Ship.accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc(&:empty?)
end
def test_should_work_with_update_as_well
@@ -855,7 +855,7 @@ end
module NestedAttributesLimitTests
def teardown
- Pirate.accepts_nested_attributes_for :parrots, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
+ Pirate.accepts_nested_attributes_for :parrots, :allow_destroy => true, :reject_if => proc(&:empty?)
end
def test_limit_with_less_records
@@ -943,7 +943,7 @@ class TestNestedAttributesWithNonStandardPrimaryKeys < ActiveRecord::TestCase
end
class TestHasOneAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveRecord::TestCase
- self.use_transactional_fixtures = false unless supports_savepoints?
+ self.use_transactional_tests = false unless supports_savepoints?
def setup
@pirate = Pirate.create!(:catchphrase => "My baby takes tha mornin' train!")
@@ -983,7 +983,7 @@ class TestHasOneAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveRe
end
class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveRecord::TestCase
- self.use_transactional_fixtures = false unless supports_savepoints?
+ self.use_transactional_tests = false unless supports_savepoints?
def setup
@ship = Ship.create!(:name => "The good ship Dollypop")
@@ -1037,4 +1037,21 @@ class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveR
ShipPart.create!(:ship => @ship, :name => "Stern")
assert_no_queries { @ship.valid? }
end
+
+ test "circular references do not perform unnecessary queries" do
+ ship = Ship.new(name: "The Black Rock")
+ part = ship.parts.build(name: "Stern")
+ ship.treasures.build(looter: part)
+
+ assert_queries 3 do
+ ship.save!
+ end
+ end
+
+ test "nested singular associations are validated" do
+ part = ShipPart.new(name: "Stern", ship_attributes: { name: nil })
+
+ assert_not part.valid?
+ assert_equal ["Ship name can't be blank"], part.errors.full_messages
+ end
end
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 2170fe6118..1e93e2a05c 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -8,6 +8,7 @@ require 'models/reply'
require 'models/category'
require 'models/company'
require 'models/developer'
+require 'models/computer'
require 'models/project'
require 'models/minimalistic'
require 'models/warehouse_thing'
@@ -126,7 +127,7 @@ class PersistenceTest < ActiveRecord::TestCase
assert_difference('Topic.count', -topics_by_mary.size) do
destroyed = Topic.destroy_all(conditions).sort_by(&:id)
assert_equal topics_by_mary, destroyed
- assert destroyed.all? { |topic| topic.frozen? }, "destroyed topics should be frozen"
+ assert destroyed.all?(&:frozen?), "destroyed topics should be frozen"
end
end
@@ -136,7 +137,7 @@ class PersistenceTest < ActiveRecord::TestCase
assert_difference('Client.count', -2) do
destroyed = Client.destroy([2, 3]).sort_by(&:id)
assert_equal clients, destroyed
- assert destroyed.all? { |client| client.frozen? }, "destroyed clients should be frozen"
+ assert destroyed.all?(&:frozen?), "destroyed clients should be frozen"
end
end
@@ -251,8 +252,10 @@ class PersistenceTest < ActiveRecord::TestCase
def test_create_columns_not_equal_attributes
topic = Topic.instantiate(
- 'title' => 'Another New Topic',
- 'does_not_exist' => 'test'
+ 'attributes' => {
+ 'title' => 'Another New Topic',
+ 'does_not_exist' => 'test'
+ }
)
assert_nothing_raised { topic.save }
end
@@ -353,6 +356,22 @@ class PersistenceTest < ActiveRecord::TestCase
assert_equal("David", topic_reloaded.author_name)
end
+ def test_update_attribute_does_not_run_sql_if_attribute_is_not_changed
+ klass = Class.new(Topic) do
+ def self.name; 'Topic'; end
+ end
+ topic = klass.create(title: 'Another New Topic')
+ assert_queries(0) do
+ topic.update_attribute(:title, 'Another New Topic')
+ end
+ end
+
+ def test_update_does_not_run_sql_if_record_has_not_changed
+ topic = Topic.create(title: 'Another New Topic')
+ assert_queries(0) { topic.update(title: 'Another New Topic') }
+ assert_queries(0) { topic.update_attributes(title: 'Another New Topic') }
+ end
+
def test_delete
topic = Topic.find(1)
assert_equal topic, topic.delete, 'topic.delete did not return self'
@@ -877,4 +896,36 @@ class PersistenceTest < ActiveRecord::TestCase
assert_equal "Welcome to the weblog", post.title
assert_not post.new_record?
end
+
+ class SaveTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ def test_save_touch_false
+ widget = Class.new(ActiveRecord::Base) do
+ connection.create_table :widgets, force: true do |t|
+ t.string :name
+ t.timestamps null: false
+ end
+
+ self.table_name = :widgets
+ end
+
+ instance = widget.create!({
+ name: 'Bob',
+ created_at: 1.day.ago,
+ updated_at: 1.day.ago
+ })
+
+ created_at = instance.created_at
+ updated_at = instance.updated_at
+
+ instance.name = 'Barb'
+ instance.save!(touch: false)
+ assert_equal instance.created_at, created_at
+ assert_equal instance.updated_at, updated_at
+ ensure
+ ActiveRecord::Base.connection.drop_table widget.table_name
+ widget.reset_column_information
+ end
+ end
end
diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb
index 8eea10143f..daa3271777 100644
--- a/activerecord/test/cases/pooled_connections_test.rb
+++ b/activerecord/test/cases/pooled_connections_test.rb
@@ -3,7 +3,7 @@ require "models/project"
require "timeout"
class PooledConnectionsTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
def setup
@per_test_teardown = []
@@ -13,7 +13,7 @@ class PooledConnectionsTest < ActiveRecord::TestCase
teardown do
ActiveRecord::Base.clear_all_connections!
ActiveRecord::Base.establish_connection(@connection)
- @per_test_teardown.each {|td| td.call }
+ @per_test_teardown.each(&:call)
end
# Will deadlock due to lack of Monitor timeouts in 1.9
@@ -35,6 +35,22 @@ class PooledConnectionsTest < ActiveRecord::TestCase
end
end
+ def checkout_checkin_connections_loop(pool_size, loops)
+ ActiveRecord::Base.establish_connection(@connection.merge({:pool => pool_size, :checkout_timeout => 0.5}))
+ @connection_count = 0
+ @timed_out = 0
+ loops.times do
+ begin
+ conn = ActiveRecord::Base.connection_pool.checkout
+ ActiveRecord::Base.connection_pool.checkin conn
+ @connection_count += 1
+ ActiveRecord::Base.connection.tables
+ rescue ActiveRecord::ConnectionTimeoutError
+ @timed_out += 1
+ end
+ end
+ end
+
def test_pooled_connection_checkin_one
checkout_checkin_connections 1, 2
assert_equal 2, @connection_count
@@ -42,6 +58,20 @@ class PooledConnectionsTest < ActiveRecord::TestCase
assert_equal 1, ActiveRecord::Base.connection_pool.connections.size
end
+ def test_pooled_connection_checkin_two
+ checkout_checkin_connections_loop 2, 3
+ assert_equal 3, @connection_count
+ assert_equal 0, @timed_out
+ assert_equal 2, ActiveRecord::Base.connection_pool.connections.size
+ end
+
+ def test_pooled_connection_remove
+ ActiveRecord::Base.establish_connection(@connection.merge({:pool => 2, :checkout_timeout => 0.5}))
+ old_connection = ActiveRecord::Base.connection
+ extra_connection = ActiveRecord::Base.connection_pool.checkout
+ ActiveRecord::Base.connection_pool.remove(extra_connection)
+ assert_equal ActiveRecord::Base.connection, old_connection
+ end
private
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index f19a6ea5e3..83be9a75d8 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'support/schema_dumping_helper'
require 'models/topic'
require 'models/reply'
require 'models/subscriber'
@@ -177,7 +178,7 @@ class PrimaryKeysTest < ActiveRecord::TestCase
end
class PrimaryKeyWithNoConnectionTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
unless in_memory_db?
def test_set_primary_key_with_no_connection
@@ -195,9 +196,40 @@ class PrimaryKeyWithNoConnectionTest < ActiveRecord::TestCase
end
end
+class PrimaryKeyAnyTypeTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
+ self.use_transactional_tests = false
+
+ class Barcode < ActiveRecord::Base
+ end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table(:barcodes, primary_key: "code", id: :string, limit: 42, force: true)
+ end
+
+ teardown do
+ @connection.drop_table(:barcodes) if @connection.table_exists? :barcodes
+ end
+
+ def test_any_type_primary_key
+ assert_equal "code", Barcode.primary_key
+
+ column_type = Barcode.type_for_attribute(Barcode.primary_key)
+ assert_equal :string, column_type.type
+ assert_equal 42, column_type.limit
+ end
+
+ test "schema dump primary key includes type and options" do
+ schema = dump_table_schema "barcodes"
+ assert_match %r{create_table "barcodes", primary_key: "code", id: :string, limit: 42}, schema
+ end
+end
+
if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
class PrimaryKeyWithAnsiQuotesTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
def test_primary_key_method_with_ansi_quotes
con = ActiveRecord::Base.connection
@@ -209,28 +241,57 @@ if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
end
end
-if current_adapter?(:PostgreSQLAdapter)
+if current_adapter?(:PostgreSQLAdapter, :MysqlAdapter, :Mysql2Adapter)
class PrimaryKeyBigSerialTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ include SchemaDumpingHelper
+
+ self.use_transactional_tests = false
class Widget < ActiveRecord::Base
end
setup do
@connection = ActiveRecord::Base.connection
- @connection.create_table(:widgets, id: :bigserial) { |t| }
+ if current_adapter?(:PostgreSQLAdapter)
+ @connection.create_table(:widgets, id: :bigserial, force: true)
+ else
+ @connection.create_table(:widgets, id: :bigint, force: true)
+ end
end
teardown do
- @connection.drop_table :widgets
+ @connection.drop_table :widgets, if_exists: true
+ Widget.reset_column_information
end
- def test_bigserial_primary_key
- assert_equal "id", Widget.primary_key
- assert_equal :integer, Widget.columns_hash[Widget.primary_key].type
+ test "primary key column type with bigserial" do
+ column_type = Widget.type_for_attribute(Widget.primary_key)
+ assert_equal :integer, column_type.type
+ assert_equal 8, column_type.limit
+ end
+ test "primary key with bigserial are automatically numbered" do
widget = Widget.create!
assert_not_nil widget.id
end
+
+ test "schema dump primary key with bigserial" do
+ schema = dump_table_schema "widgets"
+ if current_adapter?(:PostgreSQLAdapter)
+ assert_match %r{create_table "widgets", id: :bigserial}, schema
+ else
+ assert_match %r{create_table "widgets", id: :bigint}, schema
+ end
+ end
+
+ if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+ test "primary key column type with options" do
+ @connection.create_table(:widgets, id: :primary_key, limit: 8, force: true)
+ column = @connection.columns(:widgets).find { |c| c.name == 'id' }
+ assert column.auto_increment?
+ assert_equal :integer, column.type
+ assert_equal 8, column.limit
+ end
+ end
end
end
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 9d89d6a1e8..2f0b5df286 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -184,7 +184,7 @@ class QueryCacheTest < ActiveRecord::TestCase
# Oracle adapter returns count() as Fixnum or Float
if current_adapter?(:OracleAdapter)
assert_kind_of Numeric, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
- elsif current_adapter?(:SQLite3Adapter, :Mysql2Adapter)
+ elsif current_adapter?(:SQLite3Adapter, :Mysql2Adapter, :PostgreSQLAdapter)
# Future versions of the sqlite3 adapter will return numeric
assert_instance_of Fixnum,
Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
@@ -212,6 +212,38 @@ class QueryCacheTest < ActiveRecord::TestCase
ensure
ActiveRecord::Base.configurations = conf
end
+
+ def test_query_cache_doesnt_leak_cached_results_of_rolled_back_queries
+ ActiveRecord::Base.connection.enable_query_cache!
+ post = Post.first
+
+ Post.transaction do
+ post.update_attributes(title: 'rollback')
+ assert_equal 1, Post.where(title: 'rollback').to_a.count
+ raise ActiveRecord::Rollback
+ end
+
+ assert_equal 0, Post.where(title: 'rollback').to_a.count
+
+ ActiveRecord::Base.connection.uncached do
+ assert_equal 0, Post.where(title: 'rollback').to_a.count
+ end
+
+ begin
+ Post.transaction do
+ post.update_attributes(title: 'rollback')
+ assert_equal 1, Post.where(title: 'rollback').to_a.count
+ raise 'broken'
+ end
+ rescue Exception
+ end
+
+ assert_equal 0, Post.where(title: 'rollback').to_a.count
+
+ ActiveRecord::Base.connection.uncached do
+ assert_equal 0, Post.where(title: 'rollback').to_a.count
+ end
+ end
end
class QueryCacheExpiryTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb
index 1d6ae2f67f..6d91f96bf6 100644
--- a/activerecord/test/cases/quoting_test.rb
+++ b/activerecord/test/cases/quoting_test.rb
@@ -46,28 +46,28 @@ module ActiveRecord
def test_quoted_time_utc
with_timezone_config default: :utc do
- t = Time.now
+ t = Time.now.change(usec: 0)
assert_equal t.getutc.to_s(:db), @quoter.quoted_date(t)
end
end
def test_quoted_time_local
with_timezone_config default: :local do
- t = Time.now
+ t = Time.now.change(usec: 0)
assert_equal t.getlocal.to_s(:db), @quoter.quoted_date(t)
end
end
def test_quoted_time_crazy
with_timezone_config default: :asdfasdf do
- t = Time.now
+ t = Time.now.change(usec: 0)
assert_equal t.getlocal.to_s(:db), @quoter.quoted_date(t)
end
end
def test_quoted_datetime_utc
with_timezone_config default: :utc do
- t = DateTime.now
+ t = Time.now.change(usec: 0).to_datetime
assert_equal t.getutc.to_s(:db), @quoter.quoted_date(t)
end
end
@@ -76,7 +76,7 @@ module ActiveRecord
# DateTime doesn't define getlocal, so make sure it does nothing
def test_quoted_datetime_local
with_timezone_config default: :local do
- t = DateTime.now
+ t = Time.now.change(usec: 0).to_datetime
assert_equal t.to_s(:db), @quoter.quoted_date(t)
end
end
@@ -125,14 +125,11 @@ module ActiveRecord
end
def test_crazy_object
- crazy = Class.new.new
- expected = "'#{YAML.dump(crazy)}'"
- assert_equal expected, @quoter.quote(crazy, nil)
- end
-
- def test_crazy_object_calls_quote_string
- crazy = Class.new { def initialize; @lol = 'lo\l' end }.new
- assert_match "lo\\\\l", @quoter.quote(crazy, nil)
+ crazy = Object.new
+ e = assert_raises(TypeError) do
+ @quoter.quote(crazy, nil)
+ end
+ assert_equal "can't quote Object", e.message
end
def test_quote_string_no_column
diff --git a/activerecord/test/cases/readonly_test.rb b/activerecord/test/cases/readonly_test.rb
index 2afd25c989..1c919f0b57 100644
--- a/activerecord/test/cases/readonly_test.rb
+++ b/activerecord/test/cases/readonly_test.rb
@@ -3,6 +3,7 @@ require 'models/author'
require 'models/post'
require 'models/comment'
require 'models/developer'
+require 'models/computer'
require 'models/project'
require 'models/reader'
require 'models/person'
@@ -22,9 +23,15 @@ class ReadOnlyTest < ActiveRecord::TestCase
assert !dev.save
dev.name = 'Forbidden.'
end
- assert_raise(ActiveRecord::ReadOnlyRecord) { dev.save }
- assert_raise(ActiveRecord::ReadOnlyRecord) { dev.save! }
- assert_raise(ActiveRecord::ReadOnlyRecord) { dev.destroy }
+
+ e = assert_raise(ActiveRecord::ReadOnlyRecord) { dev.save }
+ assert_equal "Developer is marked as readonly", e.message
+
+ e = assert_raise(ActiveRecord::ReadOnlyRecord) { dev.save! }
+ assert_equal "Developer is marked as readonly", e.message
+
+ e = assert_raise(ActiveRecord::ReadOnlyRecord) { dev.destroy }
+ assert_equal "Developer is marked as readonly", e.message
end
diff --git a/activerecord/test/cases/reaper_test.rb b/activerecord/test/cases/reaper_test.rb
index f52fd22489..cccfc6774e 100644
--- a/activerecord/test/cases/reaper_test.rb
+++ b/activerecord/test/cases/reaper_test.rb
@@ -60,7 +60,7 @@ module ActiveRecord
def test_connection_pool_starts_reaper
spec = ActiveRecord::Base.connection_pool.spec.dup
- spec.config[:reaping_frequency] = 0.0001
+ spec.config[:reaping_frequency] = '0.0001'
pool = ConnectionPool.new spec
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index 84abaf0291..7b47c80331 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -23,6 +23,7 @@ require 'models/chef'
require 'models/department'
require 'models/cake_designer'
require 'models/drink_designer'
+require 'models/recipe'
class ReflectionTest < ActiveRecord::TestCase
include ActiveRecord::Reflection
@@ -50,13 +51,13 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_columns_are_returned_in_the_order_they_were_declared
- column_names = Topic.columns.map { |column| column.name }
+ column_names = Topic.columns.map(&:name)
assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content important approved replies_count unique_replies_count parent_id parent_title type group created_at updated_at), column_names
end
def test_content_columns
content_columns = Topic.content_columns
- content_column_names = content_columns.map {|column| column.name}
+ content_column_names = content_columns.map(&:name)
assert_equal 13, content_columns.length
assert_equal %w(title author_name author_email_address written_on bonus_time last_read content important group approved parent_title created_at updated_at).sort, content_column_names.sort
end
@@ -80,10 +81,21 @@ class ReflectionTest < ActiveRecord::TestCase
assert_equal :integer, @first.column_for_attribute("id").type
end
- def test_non_existent_columns_return_nil
- assert_deprecated do
- assert_nil @first.column_for_attribute("attribute_that_doesnt_exist")
- end
+ def test_non_existent_columns_return_null_object
+ column = @first.column_for_attribute("attribute_that_doesnt_exist")
+ assert_instance_of ActiveRecord::ConnectionAdapters::NullColumn, column
+ assert_equal "attribute_that_doesnt_exist", column.name
+ assert_equal nil, column.sql_type
+ assert_equal nil, column.type
+ end
+
+ def test_non_existent_types_are_identity_types
+ type = @first.type_for_attribute("attribute_that_doesnt_exist")
+ object = Object.new
+
+ assert_equal object, type.deserialize(object)
+ assert_equal object, type.cast(object)
+ assert_equal object, type.serialize(object)
end
def test_reflection_klass_for_nested_class_name
@@ -209,6 +221,10 @@ class ReflectionTest < ActiveRecord::TestCase
assert_not_equal Object.new, Firm._reflections['clients']
end
+ def test_reflections_should_return_keys_as_strings
+ assert Category.reflections.keys.all? { |key| key.is_a? String }, "Model.reflections is expected to return string for keys"
+ end
+
def test_has_and_belongs_to_many_reflection
assert_equal :has_and_belongs_to_many, Category.reflections['posts'].macro
assert_equal :posts, Category.reflect_on_all_associations(:has_and_belongs_to_many).first.name
@@ -262,6 +278,22 @@ class ReflectionTest < ActiveRecord::TestCase
assert_equal 2, @hotel.chefs.size
end
+ def test_scope_chain_of_polymorphic_association_does_not_leak_into_other_hmt_associations
+ hotel = Hotel.create!
+ department = hotel.departments.create!
+ drink = department.chefs.create!(employable: DrinkDesigner.create!)
+ Recipe.create!(chef_id: drink.id, hotel_id: hotel.id)
+
+ expected_sql = capture_sql { hotel.recipes.to_a }
+
+ Hotel.reflect_on_association(:recipes).clear_association_scope_cache
+ hotel.reload
+ hotel.drink_designers.to_a
+ loaded_sql = capture_sql { hotel.recipes.to_a }
+
+ assert_equal expected_sql, loaded_sql
+ end
+
def test_nested?
assert !Author.reflect_on_association(:comments).nested?
assert Author.reflect_on_association(:tags).nested?
diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb
index 8f40a1890d..0a2e874e4f 100644
--- a/activerecord/test/cases/relation/merging_test.rb
+++ b/activerecord/test/cases/relation/merging_test.rb
@@ -2,6 +2,7 @@ require 'cases/helper'
require 'models/author'
require 'models/comment'
require 'models/developer'
+require 'models/computer'
require 'models/post'
require 'models/project'
require 'models/rating'
@@ -81,26 +82,15 @@ class RelationMergingTest < ActiveRecord::TestCase
left = Post.where(title: "omg").where(comments_count: 1)
right = Post.where(title: "wtf").where(title: "bbq")
- expected = [left.bind_values[1]] + right.bind_values
+ expected = [left.bound_attributes[1]] + right.bound_attributes
merged = left.merge(right)
- assert_equal expected, merged.bind_values
+ assert_equal expected, merged.bound_attributes
assert !merged.to_sql.include?("omg")
assert merged.to_sql.include?("wtf")
assert merged.to_sql.include?("bbq")
end
- def test_merging_keeps_lhs_bind_parameters
- column = Post.columns_hash['id']
- binds = [[column, 20]]
-
- right = Post.where(id: 20)
- left = Post.where(id: 10)
-
- merged = left.merge(right)
- assert_equal binds, merged.bind_values
- end
-
def test_merging_reorders_bind_params
post = Post.first
right = Post.where(id: 1)
diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb
index 4c94c2fd0d..45ead08bd5 100644
--- a/activerecord/test/cases/relation/mutation_test.rb
+++ b/activerecord/test/cases/relation/mutation_test.rb
@@ -25,7 +25,7 @@ module ActiveRecord
end
def relation
- @relation ||= Relation.new FakeKlass.new('posts'), Post.arel_table
+ @relation ||= Relation.new FakeKlass.new('posts'), Post.arel_table, Post.predicate_builder
end
(Relation::MULTI_VALUE_METHODS - [:references, :extending, :order, :unscope, :select]).each do |method|
@@ -81,7 +81,7 @@ module ActiveRecord
assert_equal [], relation.extending_values
end
- (Relation::SINGLE_VALUE_METHODS - [:from, :lock, :reordering, :reverse_order, :create_with]).each do |method|
+ (Relation::SINGLE_VALUE_METHODS - [:lock, :reordering, :reverse_order, :create_with]).each do |method|
test "##{method}!" do
assert relation.public_send("#{method}!", :foo).equal?(relation)
assert_equal :foo, relation.public_send("#{method}_value")
@@ -90,7 +90,7 @@ module ActiveRecord
test '#from!' do
assert relation.from!('foo').equal?(relation)
- assert_equal ['foo', nil], relation.from_value
+ assert_equal 'foo', relation.from_clause.value
end
test '#lock!' do
@@ -99,7 +99,7 @@ module ActiveRecord
end
test '#reorder!' do
- relation = self.relation.order('foo')
+ @relation = self.relation.order('foo')
assert relation.reorder!('bar').equal?(relation)
assert_equal ['bar'], relation.order_values
@@ -116,7 +116,7 @@ module ActiveRecord
end
test 'reverse_order!' do
- relation = Post.order('title ASC, comments_count DESC')
+ @relation = Post.order('title ASC, comments_count DESC')
relation.reverse_order!
@@ -136,12 +136,12 @@ module ActiveRecord
end
test 'test_merge!' do
- assert relation.merge!(where: :foo).equal?(relation)
- assert_equal [:foo], relation.where_values
+ assert relation.merge!(select: :foo).equal?(relation)
+ assert_equal [:foo], relation.select_values
end
test 'merge with a proc' do
- assert_equal [:foo], relation.merge(-> { where(:foo) }).where_values
+ assert_equal [:foo], relation.merge(-> { select(:foo) }).select_values
end
test 'none!' do
diff --git a/activerecord/test/cases/relation/or_test.rb b/activerecord/test/cases/relation/or_test.rb
new file mode 100644
index 0000000000..2006fc9611
--- /dev/null
+++ b/activerecord/test/cases/relation/or_test.rb
@@ -0,0 +1,84 @@
+require "cases/helper"
+require 'models/post'
+
+module ActiveRecord
+ class OrTest < ActiveRecord::TestCase
+ fixtures :posts
+
+ def test_or_with_relation
+ expected = Post.where('id = 1 or id = 2').to_a
+ assert_equal expected, Post.where('id = 1').or(Post.where('id = 2')).to_a
+ end
+
+ def test_or_identity
+ expected = Post.where('id = 1').to_a
+ assert_equal expected, Post.where('id = 1').or(Post.where('id = 1')).to_a
+ end
+
+ def test_or_with_null_left
+ expected = Post.where('id = 1').to_a
+ assert_equal expected, Post.none.or(Post.where('id = 1')).to_a
+ end
+
+ def test_or_with_null_right
+ expected = Post.where('id = 1').to_a
+ assert_equal expected, Post.where('id = 1').or(Post.none).to_a
+ end
+
+ def test_or_with_bind_params
+ assert_equal Post.find([1, 2]), Post.where(id: 1).or(Post.where(id: 2)).to_a
+ end
+
+ def test_or_with_null_both
+ expected = Post.none.to_a
+ assert_equal expected, Post.none.or(Post.none).to_a
+ end
+
+ def test_or_without_left_where
+ expected = Post.all
+ assert_equal expected, Post.or(Post.where('id = 1')).to_a
+ end
+
+ def test_or_without_right_where
+ expected = Post.all
+ assert_equal expected, Post.where('id = 1').or(Post.all).to_a
+ end
+
+ def test_or_preserves_other_querying_methods
+ expected = Post.where('id = 1 or id = 2 or id = 3').order('body asc').to_a
+ partial = Post.order('body asc')
+ assert_equal expected, partial.where('id = 1').or(partial.where(:id => [2, 3])).to_a
+ assert_equal expected, Post.order('body asc').where('id = 1').or(Post.order('body asc').where(:id => [2, 3])).to_a
+ end
+
+ def test_or_with_incompatible_relations
+ assert_raises ArgumentError do
+ Post.order('body asc').where('id = 1').or(Post.order('id desc').where(:id => [2, 3])).to_a
+ end
+ end
+
+ def test_or_when_grouping
+ groups = Post.where('id < 10').group('body').select('body, COUNT(*) AS c')
+ expected = groups.having("COUNT(*) > 1 OR body like 'Such%'").to_a.map {|o| [o.body, o.c] }
+ assert_equal expected, groups.having('COUNT(*) > 1').or(groups.having("body like 'Such%'")).to_a.map {|o| [o.body, o.c] }
+ end
+
+ def test_or_with_named_scope
+ expected = Post.where("id = 1 or body LIKE '\%a\%'").to_a
+ assert_equal expected, Post.where('id = 1').or(Post.containing_the_letter_a)
+ end
+
+ def test_or_inside_named_scope
+ expected = Post.where("body LIKE '\%a\%' OR title LIKE ?", "%'%").order('id DESC').to_a
+ assert_equal expected, Post.order(id: :desc).typographically_interesting
+ end
+
+ def test_or_on_loaded_relation
+ expected = Post.where('id = 1 or id = 2').to_a
+ p = Post.where('id = 1')
+ p.load
+ assert_equal p.loaded?, true
+ assert_equal expected, p.or(Post.where('id = 2')).to_a
+ end
+ end
+end
diff --git a/activerecord/test/cases/relation/predicate_builder_test.rb b/activerecord/test/cases/relation/predicate_builder_test.rb
index 4057835688..8f62014622 100644
--- a/activerecord/test/cases/relation/predicate_builder_test.rb
+++ b/activerecord/test/cases/relation/predicate_builder_test.rb
@@ -4,11 +4,13 @@ require 'models/topic'
module ActiveRecord
class PredicateBuilderTest < ActiveRecord::TestCase
def test_registering_new_handlers
- PredicateBuilder.register_handler(Regexp, proc do |column, value|
+ Topic.predicate_builder.register_handler(Regexp, proc do |column, value|
Arel::Nodes::InfixOperation.new('~', column, Arel.sql(value.source))
end)
- assert_match %r{["`]topics["`].["`]title["`] ~ rails}i, Topic.where(title: /rails/).to_sql
+ assert_match %r{["`]topics["`]\.["`]title["`] ~ rails}i, Topic.where(title: /rails/).to_sql
+ ensure
+ Topic.reset_column_information
end
end
end
diff --git a/activerecord/test/cases/relation/record_fetch_warning_test.rb b/activerecord/test/cases/relation/record_fetch_warning_test.rb
new file mode 100644
index 0000000000..62f0a7cc49
--- /dev/null
+++ b/activerecord/test/cases/relation/record_fetch_warning_test.rb
@@ -0,0 +1,28 @@
+require 'cases/helper'
+require 'models/post'
+
+module ActiveRecord
+ class RecordFetchWarningTest < ActiveRecord::TestCase
+ fixtures :posts
+
+ def test_warn_on_records_fetched_greater_than
+ original_logger = ActiveRecord::Base.logger
+ orginal_warn_on_records_fetched_greater_than = ActiveRecord::Base.warn_on_records_fetched_greater_than
+
+ log = StringIO.new
+ ActiveRecord::Base.logger = ActiveSupport::Logger.new(log)
+ ActiveRecord::Base.logger.level = Logger::WARN
+
+ require 'active_record/relation/record_fetch_warning'
+
+ ActiveRecord::Base.warn_on_records_fetched_greater_than = 1
+
+ Post.all.to_a
+
+ assert_match(/Query fetched/, log.string)
+ ensure
+ ActiveRecord::Base.logger = original_logger
+ ActiveRecord::Base.warn_on_records_fetched_greater_than = orginal_warn_on_records_fetched_greater_than
+ end
+ end
+end
diff --git a/activerecord/test/cases/relation/where_chain_test.rb b/activerecord/test/cases/relation/where_chain_test.rb
index 619055f1e7..27bbd80f79 100644
--- a/activerecord/test/cases/relation/where_chain_test.rb
+++ b/activerecord/test/cases/relation/where_chain_test.rb
@@ -11,22 +11,11 @@ module ActiveRecord
@name = 'title'
end
- def test_not_eq
+ def test_not_inverts_where_clause
relation = Post.where.not(title: 'hello')
+ expected_where_clause = Post.where(title: 'hello').where_clause.invert
- assert_equal 1, relation.where_values.length
-
- value = relation.where_values.first
- bind = relation.bind_values.first
-
- assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual
- assert_equal 'hello', bind.last
- end
-
- def test_not_null
- expected = Post.arel_table[@name].not_eq(nil)
- relation = Post.where.not(title: nil)
- assert_equal([expected], relation.where_values)
+ assert_equal expected_where_clause, relation.where_clause
end
def test_not_with_nil
@@ -35,146 +24,81 @@ module ActiveRecord
end
end
- def test_not_in
- expected = Post.arel_table[@name].not_in(%w[hello goodbye])
- relation = Post.where.not(title: %w[hello goodbye])
- assert_equal([expected], relation.where_values)
- end
-
def test_association_not_eq
- expected = Comment.arel_table[@name].not_eq('hello')
+ expected = Arel::Nodes::Grouping.new(Comment.arel_table[@name].not_eq(Arel::Nodes::BindParam.new))
relation = Post.joins(:comments).where.not(comments: {title: 'hello'})
- assert_equal(expected.to_sql, relation.where_values.first.to_sql)
+ assert_equal(expected.to_sql, relation.where_clause.ast.to_sql)
end
def test_not_eq_with_preceding_where
relation = Post.where(title: 'hello').where.not(title: 'world')
+ expected_where_clause =
+ Post.where(title: 'hello').where_clause +
+ Post.where(title: 'world').where_clause.invert
- value = relation.where_values.first
- bind = relation.bind_values.first
- assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::Equality
- assert_equal 'hello', bind.last
-
- value = relation.where_values.last
- bind = relation.bind_values.last
- assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual
- assert_equal 'world', bind.last
+ assert_equal expected_where_clause, relation.where_clause
end
def test_not_eq_with_succeeding_where
relation = Post.where.not(title: 'hello').where(title: 'world')
+ expected_where_clause =
+ Post.where(title: 'hello').where_clause.invert +
+ Post.where(title: 'world').where_clause
- value = relation.where_values.first
- bind = relation.bind_values.first
- assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual
- assert_equal 'hello', bind.last
-
- value = relation.where_values.last
- bind = relation.bind_values.last
- assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::Equality
- assert_equal 'world', bind.last
- end
-
- def test_not_eq_with_string_parameter
- expected = Arel::Nodes::Not.new("title = 'hello'")
- relation = Post.where.not("title = 'hello'")
- assert_equal([expected], relation.where_values)
- end
-
- def test_not_eq_with_array_parameter
- expected = Arel::Nodes::Not.new("title = 'hello'")
- relation = Post.where.not(['title = ?', 'hello'])
- assert_equal([expected], relation.where_values)
+ assert_equal expected_where_clause, relation.where_clause
end
def test_chaining_multiple
relation = Post.where.not(author_id: [1, 2]).where.not(title: 'ruby on rails')
+ expected_where_clause =
+ Post.where(author_id: [1, 2]).where_clause.invert +
+ Post.where(title: 'ruby on rails').where_clause.invert
- expected = Post.arel_table['author_id'].not_in([1, 2])
- assert_equal(expected, relation.where_values[0])
-
- value = relation.where_values[1]
- bind = relation.bind_values.first
-
- assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual
- assert_equal 'ruby on rails', bind.last
+ assert_equal expected_where_clause, relation.where_clause
end
def test_rewhere_with_one_condition
relation = Post.where(title: 'hello').where(title: 'world').rewhere(title: 'alone')
+ expected = Post.where(title: 'alone')
- assert_equal 1, relation.where_values.size
- value = relation.where_values.first
- bind = relation.bind_values.first
- assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::Equality
- assert_equal 'alone', bind.last
+ assert_equal expected.where_clause, relation.where_clause
end
def test_rewhere_with_multiple_overwriting_conditions
relation = Post.where(title: 'hello').where(body: 'world').rewhere(title: 'alone', body: 'again')
+ expected = Post.where(title: 'alone', body: 'again')
- assert_equal 2, relation.where_values.size
-
- value = relation.where_values.first
- bind = relation.bind_values.first
- assert_bound_ast value, Post.arel_table['title'], Arel::Nodes::Equality
- assert_equal 'alone', bind.last
-
- value = relation.where_values[1]
- bind = relation.bind_values[1]
- assert_bound_ast value, Post.arel_table['body'], Arel::Nodes::Equality
- assert_equal 'again', bind.last
- end
-
- def assert_bound_ast value, table, type
- assert_equal table, value.left
- assert_kind_of type, value
- assert_kind_of Arel::Nodes::BindParam, value.right
+ assert_equal expected.where_clause, relation.where_clause
end
def test_rewhere_with_one_overwriting_condition_and_one_unrelated
relation = Post.where(title: 'hello').where(body: 'world').rewhere(title: 'alone')
+ expected = Post.where(body: 'world', title: 'alone')
- assert_equal 2, relation.where_values.size
-
- value = relation.where_values.first
- bind = relation.bind_values.first
-
- assert_bound_ast value, Post.arel_table['body'], Arel::Nodes::Equality
- assert_equal 'world', bind.last
-
- value = relation.where_values.second
- bind = relation.bind_values.second
-
- assert_bound_ast value, Post.arel_table['title'], Arel::Nodes::Equality
- assert_equal 'alone', bind.last
+ assert_equal expected.where_clause, relation.where_clause
end
def test_rewhere_with_range
relation = Post.where(comments_count: 1..3).rewhere(comments_count: 3..5)
- assert_equal 1, relation.where_values.size
assert_equal Post.where(comments_count: 3..5), relation
end
def test_rewhere_with_infinite_upper_bound_range
relation = Post.where(comments_count: 1..Float::INFINITY).rewhere(comments_count: 3..5)
- assert_equal 1, relation.where_values.size
assert_equal Post.where(comments_count: 3..5), relation
end
def test_rewhere_with_infinite_lower_bound_range
relation = Post.where(comments_count: -Float::INFINITY..1).rewhere(comments_count: 3..5)
- assert_equal 1, relation.where_values.size
assert_equal Post.where(comments_count: 3..5), relation
end
def test_rewhere_with_infinite_range
relation = Post.where(comments_count: -Float::INFINITY..Float::INFINITY).rewhere(comments_count: 3..5)
- assert_equal 1, relation.where_values.size
assert_equal Post.where(comments_count: 3..5), relation
end
end
diff --git a/activerecord/test/cases/relation/where_clause_test.rb b/activerecord/test/cases/relation/where_clause_test.rb
new file mode 100644
index 0000000000..c20ed94d90
--- /dev/null
+++ b/activerecord/test/cases/relation/where_clause_test.rb
@@ -0,0 +1,182 @@
+require "cases/helper"
+
+class ActiveRecord::Relation
+ class WhereClauseTest < ActiveRecord::TestCase
+ test "+ combines two where clauses" do
+ first_clause = WhereClause.new([table["id"].eq(bind_param)], [["id", 1]])
+ second_clause = WhereClause.new([table["name"].eq(bind_param)], [["name", "Sean"]])
+ combined = WhereClause.new(
+ [table["id"].eq(bind_param), table["name"].eq(bind_param)],
+ [["id", 1], ["name", "Sean"]],
+ )
+
+ assert_equal combined, first_clause + second_clause
+ end
+
+ test "+ is associative, but not commutative" do
+ a = WhereClause.new(["a"], ["bind a"])
+ b = WhereClause.new(["b"], ["bind b"])
+ c = WhereClause.new(["c"], ["bind c"])
+
+ assert_equal a + (b + c), (a + b) + c
+ assert_not_equal a + b, b + a
+ end
+
+ test "an empty where clause is the identity value for +" do
+ clause = WhereClause.new([table["id"].eq(bind_param)], [["id", 1]])
+
+ assert_equal clause, clause + WhereClause.empty
+ end
+
+ test "merge combines two where clauses" do
+ a = WhereClause.new([table["id"].eq(1)], [])
+ b = WhereClause.new([table["name"].eq("Sean")], [])
+ expected = WhereClause.new([table["id"].eq(1), table["name"].eq("Sean")], [])
+
+ assert_equal expected, a.merge(b)
+ end
+
+ test "merge keeps the right side, when two equality clauses reference the same column" do
+ a = WhereClause.new([table["id"].eq(1), table["name"].eq("Sean")], [])
+ b = WhereClause.new([table["name"].eq("Jim")], [])
+ expected = WhereClause.new([table["id"].eq(1), table["name"].eq("Jim")], [])
+
+ assert_equal expected, a.merge(b)
+ end
+
+ test "merge removes bind parameters matching overlapping equality clauses" do
+ a = WhereClause.new(
+ [table["id"].eq(bind_param), table["name"].eq(bind_param)],
+ [attribute("id", 1), attribute("name", "Sean")],
+ )
+ b = WhereClause.new(
+ [table["name"].eq(bind_param)],
+ [attribute("name", "Jim")]
+ )
+ expected = WhereClause.new(
+ [table["id"].eq(bind_param), table["name"].eq(bind_param)],
+ [attribute("id", 1), attribute("name", "Jim")],
+ )
+
+ assert_equal expected, a.merge(b)
+ end
+
+ test "merge allows for columns with the same name from different tables" do
+ skip "This is not possible as of 4.2, and the binds do not yet contain sufficient information for this to happen"
+ # We might be able to change the implementation to remove conflicts by index, rather than column name
+ end
+
+ test "a clause knows if it is empty" do
+ assert WhereClause.empty.empty?
+ assert_not WhereClause.new(["anything"], []).empty?
+ end
+
+ test "invert cannot handle nil" do
+ where_clause = WhereClause.new([nil], [])
+
+ assert_raises ArgumentError do
+ where_clause.invert
+ end
+ end
+
+ test "invert replaces each part of the predicate with its inverse" do
+ random_object = Object.new
+ original = WhereClause.new([
+ table["id"].in([1, 2, 3]),
+ table["id"].eq(1),
+ "sql literal",
+ random_object
+ ], [])
+ expected = WhereClause.new([
+ table["id"].not_in([1, 2, 3]),
+ table["id"].not_eq(1),
+ Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new("sql literal")),
+ Arel::Nodes::Not.new(random_object)
+ ], [])
+
+ assert_equal expected, original.invert
+ end
+
+ test "accept removes binary predicates referencing a given column" do
+ where_clause = WhereClause.new([
+ table["id"].in([1, 2, 3]),
+ table["name"].eq(bind_param),
+ table["age"].gteq(bind_param),
+ ], [
+ attribute("name", "Sean"),
+ attribute("age", 30),
+ ])
+ expected = WhereClause.new([table["age"].gteq(bind_param)], [attribute("age", 30)])
+
+ assert_equal expected, where_clause.except("id", "name")
+ end
+
+ test "ast groups its predicates with AND" do
+ predicates = [
+ table["id"].in([1, 2, 3]),
+ table["name"].eq(bind_param),
+ ]
+ where_clause = WhereClause.new(predicates, [])
+ expected = Arel::Nodes::And.new(predicates)
+
+ assert_equal expected, where_clause.ast
+ end
+
+ test "ast wraps any SQL literals in parenthesis" do
+ random_object = Object.new
+ where_clause = WhereClause.new([
+ table["id"].in([1, 2, 3]),
+ "foo = bar",
+ random_object,
+ ], [])
+ expected = Arel::Nodes::And.new([
+ table["id"].in([1, 2, 3]),
+ Arel::Nodes::Grouping.new(Arel.sql("foo = bar")),
+ Arel::Nodes::Grouping.new(random_object),
+ ])
+
+ assert_equal expected, where_clause.ast
+ end
+
+ test "ast removes any empty strings" do
+ where_clause = WhereClause.new([table["id"].in([1, 2, 3])], [])
+ where_clause_with_empty = WhereClause.new([table["id"].in([1, 2, 3]), ''], [])
+
+ assert_equal where_clause.ast, where_clause_with_empty.ast
+ end
+
+ test "or joins the two clauses using OR" do
+ where_clause = WhereClause.new([table["id"].eq(bind_param)], [attribute("id", 1)])
+ other_clause = WhereClause.new([table["name"].eq(bind_param)], [attribute("name", "Sean")])
+ expected_ast =
+ Arel::Nodes::Grouping.new(
+ Arel::Nodes::Or.new(table["id"].eq(bind_param), table["name"].eq(bind_param))
+ )
+ expected_binds = where_clause.binds + other_clause.binds
+
+ assert_equal expected_ast.to_sql, where_clause.or(other_clause).ast.to_sql
+ assert_equal expected_binds, where_clause.or(other_clause).binds
+ end
+
+ test "or returns an empty where clause when either side is empty" do
+ where_clause = WhereClause.new([table["id"].eq(bind_param)], [attribute("id", 1)])
+
+ assert_equal WhereClause.empty, where_clause.or(WhereClause.empty)
+ assert_equal WhereClause.empty, WhereClause.empty.or(where_clause)
+ end
+
+ private
+
+ def table
+ Arel::Table.new("table")
+ end
+
+ def bind_param
+ Arel::Nodes::BindParam.new
+ end
+
+ def attribute(name, value)
+ ActiveRecord::Attribute.with_cast_value(name, value, ActiveRecord::Type::Value.new)
+ end
+ end
+end
diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb
index a453203e15..6af31017d6 100644
--- a/activerecord/test/cases/relation/where_test.rb
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -1,17 +1,20 @@
require "cases/helper"
-require 'models/author'
-require 'models/price_estimate'
-require 'models/treasure'
-require 'models/post'
-require 'models/comment'
-require 'models/edge'
-require 'models/topic'
-require 'models/binary'
-require 'models/vertex'
+require "models/author"
+require "models/binary"
+require "models/cake_designer"
+require "models/chef"
+require "models/comment"
+require "models/edge"
+require "models/essay"
+require "models/post"
+require "models/price_estimate"
+require "models/topic"
+require "models/treasure"
+require "models/vertex"
module ActiveRecord
class WhereTest < ActiveRecord::TestCase
- fixtures :posts, :edges, :authors, :binaries
+ fixtures :posts, :edges, :authors, :binaries, :essays
def test_where_copies_bind_params
author = authors(:david)
@@ -26,6 +29,24 @@ module ActiveRecord
}
end
+ def test_where_copies_bind_params_in_the_right_order
+ author = authors(:david)
+ posts = author.posts.where.not(id: 1)
+ joined = Post.where(id: posts, title: posts.first.title)
+
+ assert_equal joined, [posts.first]
+ end
+
+ def test_where_copies_arel_bind_params
+ chef = Chef.create!
+ CakeDesigner.create!(chef: chef)
+
+ cake_designers = CakeDesigner.joins(:chef).where(chefs: { id: chef.id })
+ chefs = Chef.where(employable: cake_designers)
+
+ assert_equal [chef], chefs.to_a
+ end
+
def test_rewhere_on_root
assert_equal posts(:welcome), Post.rewhere(title: 'Welcome to the weblog').first
end
@@ -181,12 +202,6 @@ module ActiveRecord
assert_equal 0, Post.where(:id => []).count
end
- def test_where_with_table_name_and_nested_empty_array
- assert_deprecated do
- assert_equal [], Post.where(:id => [[]]).to_a
- end
- end
-
def test_where_with_empty_hash_and_no_foreign_key
assert_equal 0, Edge.where(:sink => {}).count
end
@@ -226,5 +241,40 @@ module ActiveRecord
count = Binary.where(:data => 0).count
assert_equal 0, count
end
+
+ def test_where_on_association_with_custom_primary_key
+ author = authors(:david)
+ essay = Essay.where(writer: author).first
+
+ assert_equal essays(:david_modest_proposal), essay
+ end
+
+ def test_where_on_association_with_custom_primary_key_with_relation
+ author = authors(:david)
+ essay = Essay.where(writer: Author.where(id: author.id)).first
+
+ assert_equal essays(:david_modest_proposal), essay
+ end
+
+ def test_where_on_association_with_relation_performs_subselect_not_two_queries
+ author = authors(:david)
+
+ assert_queries(1) do
+ Essay.where(writer: Author.where(id: author.id)).to_a
+ end
+ end
+
+ def test_where_on_association_with_custom_primary_key_with_array_of_base
+ author = authors(:david)
+ essay = Essay.where(writer: [author]).first
+
+ assert_equal essays(:david_modest_proposal), essay
+ end
+
+ def test_where_on_association_with_custom_primary_key_with_array_of_ids
+ essay = Essay.where(writer: ["David"]).first
+
+ assert_equal essays(:david_modest_proposal), essay
+ end
end
end
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 3280945d09..9353be1ba7 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -23,19 +23,19 @@ module ActiveRecord
end
def test_construction
- relation = Relation.new FakeKlass, :b
+ relation = Relation.new(FakeKlass, :b, nil)
assert_equal FakeKlass, relation.klass
assert_equal :b, relation.table
assert !relation.loaded, 'relation is not loaded'
end
def test_responds_to_model_and_returns_klass
- relation = Relation.new FakeKlass, :b
+ relation = Relation.new(FakeKlass, :b, nil)
assert_equal FakeKlass, relation.model
end
def test_initialize_single_values
- relation = Relation.new FakeKlass, :b
+ relation = Relation.new(FakeKlass, :b, nil)
(Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |method|
assert_nil relation.send("#{method}_value"), method.to_s
end
@@ -43,19 +43,19 @@ module ActiveRecord
end
def test_multi_value_initialize
- relation = Relation.new FakeKlass, :b
+ relation = Relation.new(FakeKlass, :b, nil)
Relation::MULTI_VALUE_METHODS.each do |method|
assert_equal [], relation.send("#{method}_values"), method.to_s
end
end
def test_extensions
- relation = Relation.new FakeKlass, :b
+ relation = Relation.new(FakeKlass, :b, nil)
assert_equal [], relation.extensions
end
def test_empty_where_values_hash
- relation = Relation.new FakeKlass, :b
+ relation = Relation.new(FakeKlass, :b, nil)
assert_equal({}, relation.where_values_hash)
relation.where! :hello
@@ -63,19 +63,20 @@ module ActiveRecord
end
def test_has_values
- relation = Relation.new Post, Post.arel_table
+ relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
relation.where! relation.table[:id].eq(10)
assert_equal({:id => 10}, relation.where_values_hash)
end
def test_values_wrong_table
- relation = Relation.new Post, Post.arel_table
+ relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
relation.where! Comment.arel_table[:id].eq(10)
assert_equal({}, relation.where_values_hash)
end
def test_tree_is_not_traversed
- relation = Relation.new Post, Post.arel_table
+ relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
+ # FIXME: Remove the Arel::Nodes::Quoted in Rails 5.1
left = relation.table[:id].eq(10)
right = relation.table[:id].eq(10)
combine = left.and right
@@ -84,24 +85,25 @@ module ActiveRecord
end
def test_table_name_delegates_to_klass
- relation = Relation.new FakeKlass.new('posts'), :b
+ relation = Relation.new(FakeKlass.new('posts'), :b, Post.predicate_builder)
assert_equal 'posts', relation.table_name
end
def test_scope_for_create
- relation = Relation.new FakeKlass, :b
+ relation = Relation.new(FakeKlass, :b, nil)
assert_equal({}, relation.scope_for_create)
end
def test_create_with_value
- relation = Relation.new Post, Post.arel_table
+ relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
hash = { :hello => 'world' }
relation.create_with_value = hash
assert_equal hash, relation.scope_for_create
end
def test_create_with_value_with_wheres
- relation = Relation.new Post, Post.arel_table
+ relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
+ # FIXME: Remove the Arel::Nodes::Quoted in Rails 5.1
relation.where! relation.table[:id].eq(10)
relation.create_with_value = {:hello => 'world'}
assert_equal({:hello => 'world', :id => 10}, relation.scope_for_create)
@@ -109,9 +111,10 @@ module ActiveRecord
# FIXME: is this really wanted or expected behavior?
def test_scope_for_create_is_cached
- relation = Relation.new Post, Post.arel_table
+ relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
assert_equal({}, relation.scope_for_create)
+ # FIXME: Remove the Arel::Nodes::Quoted in Rails 5.1
relation.where! relation.table[:id].eq(10)
assert_equal({}, relation.scope_for_create)
@@ -126,62 +129,72 @@ module ActiveRecord
end
def test_empty_eager_loading?
- relation = Relation.new FakeKlass, :b
+ relation = Relation.new(FakeKlass, :b, nil)
assert !relation.eager_loading?
end
def test_eager_load_values
- relation = Relation.new FakeKlass, :b
+ relation = Relation.new(FakeKlass, :b, nil)
relation.eager_load! :b
assert relation.eager_loading?
end
def test_references_values
- relation = Relation.new FakeKlass, :b
+ relation = Relation.new(FakeKlass, :b, nil)
assert_equal [], relation.references_values
relation = relation.references(:foo).references(:omg, :lol)
assert_equal ['foo', 'omg', 'lol'], relation.references_values
end
def test_references_values_dont_duplicate
- relation = Relation.new FakeKlass, :b
+ relation = Relation.new(FakeKlass, :b, nil)
relation = relation.references(:foo).references(:foo)
assert_equal ['foo'], relation.references_values
end
test 'merging a hash into a relation' do
- relation = Relation.new FakeKlass, :b
+ relation = Relation.new(FakeKlass, :b, nil)
relation = relation.merge where: :lol, readonly: true
- assert_equal [:lol], relation.where_values
+ assert_equal Relation::WhereClause.new([:lol], []), relation.where_clause
assert_equal true, relation.readonly_value
end
test 'merging an empty hash into a relation' do
- assert_equal [], Relation.new(FakeKlass, :b).merge({}).where_values
+ assert_equal Relation::WhereClause.empty, Relation.new(FakeKlass, :b, nil).merge({}).where_clause
end
test 'merging a hash with unknown keys raises' do
assert_raises(ArgumentError) { Relation::HashMerger.new(nil, omg: 'lol') }
end
+ test 'merging nil or false raises' do
+ relation = Relation.new(FakeKlass, :b, nil)
+
+ e = assert_raises(ArgumentError) do
+ relation = relation.merge nil
+ end
+
+ assert_equal 'invalid argument: nil.', e.message
+
+ e = assert_raises(ArgumentError) do
+ relation = relation.merge false
+ end
+
+ assert_equal 'invalid argument: false.', e.message
+ end
+
test '#values returns a dup of the values' do
- relation = Relation.new(FakeKlass, :b).where! :foo
+ relation = Relation.new(FakeKlass, :b, nil).where! :foo
values = relation.values
values[:where] = nil
- assert_not_nil relation.where_values
+ assert_not_nil relation.where_clause
end
test 'relations can be created with a values hash' do
- relation = Relation.new(FakeKlass, :b, where: [:foo])
- assert_equal [:foo], relation.where_values
- end
-
- test 'merging a single where value' do
- relation = Relation.new(FakeKlass, :b)
- relation.merge!(where: :foo)
- assert_equal [:foo], relation.where_values
+ relation = Relation.new(FakeKlass, :b, nil, select: [:foo])
+ assert_equal [:foo], relation.select_values
end
test 'merging a hash interpolates conditions' do
@@ -192,13 +205,13 @@ module ActiveRecord
end
end
- relation = Relation.new(klass, :b)
+ relation = Relation.new(klass, :b, nil)
relation.merge!(where: ['foo = ?', 'bar'])
- assert_equal ['foo = bar'], relation.where_values
+ assert_equal Relation::WhereClause.new(['foo = bar'], []), relation.where_clause
end
def test_merging_readonly_false
- relation = Relation.new FakeKlass, :b
+ relation = Relation.new(FakeKlass, :b, nil)
readonly_false_relation = relation.readonly(false)
# test merging in both directions
assert_equal false, relation.merge(readonly_false_relation).readonly_value
@@ -241,12 +254,12 @@ module ActiveRecord
:string
end
- def type_cast_from_database(value)
+ def deserialize(value)
raise value unless value == "type cast for database"
"type cast from database"
end
- def type_cast_for_database(value)
+ def serialize(value)
raise value unless value == "value from user"
"type cast for database"
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 410b5ba5a3..0cf44388fa 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -7,6 +7,7 @@ require 'models/comment'
require 'models/author'
require 'models/entrant'
require 'models/developer'
+require 'models/computer'
require 'models/reply'
require 'models/company'
require 'models/bird'
@@ -15,12 +16,18 @@ require 'models/engine'
require 'models/tyre'
require 'models/minivan'
require 'models/aircraft'
+require "models/possession"
class RelationTest < ActiveRecord::TestCase
fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments,
:tags, :taggings, :cars, :minivans
+ class TopicWithCallbacks < ActiveRecord::Base
+ self.table_name = :topics
+ before_update { |topic| topic.author_name = 'David' if topic.author_name.blank? }
+ end
+
def test_do_not_double_quote_string_id
van = Minivan.last
assert van
@@ -33,15 +40,6 @@ class RelationTest < ActiveRecord::TestCase
assert_equal van, Minivan.where(:minivan_id => [van]).to_a.first
end
- def test_bind_values
- relation = Post.all
- assert_equal [], relation.bind_values
-
- relation2 = relation.bind 'foo'
- assert_equal %w{ foo }, relation2.bind_values
- assert_equal [], relation.bind_values
- end
-
def test_two_scopes_with_includes_should_not_drop_any_include
# heat habtm cache
car = Car.incl_engines.incl_tyres.first
@@ -159,6 +157,17 @@ class RelationTest < ActiveRecord::TestCase
end
end
+ def test_select_with_subquery_in_from_does_not_use_original_table_name
+ relation = Comment.group(:type).select('COUNT(post_id) AS post_count, type')
+ subquery = Comment.from(relation).select('type','post_count')
+ assert_equal(relation.map(&:post_count).sort,subquery.map(&:post_count).sort)
+ end
+
+ def test_group_with_subquery_in_from_does_not_use_original_table_name
+ relation = Comment.group(:type).select('COUNT(post_id) AS post_count,type')
+ subquery = Comment.from(relation).group('type').average("post_count")
+ assert_equal(relation.map(&:post_count).sort,subquery.values.sort)
+ end
def test_finding_with_conditions
assert_equal ["David"], Author.where(:name => 'David').map(&:name)
@@ -248,7 +257,7 @@ class RelationTest < ActiveRecord::TestCase
def test_finding_with_reorder
topics = Topic.order('author_name').order('title').reorder('id').to_a
- topics_titles = topics.map{ |t| t.title }
+ topics_titles = topics.map(&:title)
assert_equal ['The First Topic', 'The Second Topic of the day', 'The Third Topic of the day', 'The Fourth Topic of the day', 'The Fifth Topic of the day'], topics_titles
end
@@ -344,7 +353,9 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 0, Developer.none.size
assert_equal 0, Developer.none.count
assert_equal true, Developer.none.empty?
+ assert_equal true, Developer.none.none?
assert_equal false, Developer.none.any?
+ assert_equal false, Developer.none.one?
assert_equal false, Developer.none.many?
end
end
@@ -352,7 +363,7 @@ class RelationTest < ActiveRecord::TestCase
def test_null_relation_calculations_methods
assert_no_queries(ignore_none: false) do
assert_equal 0, Developer.none.count
- assert_equal 0, Developer.none.calculate(:count, nil, {})
+ assert_equal 0, Developer.none.calculate(:count, nil)
assert_equal nil, Developer.none.calculate(:average, 'salary')
end
end
@@ -440,7 +451,7 @@ class RelationTest < ActiveRecord::TestCase
where('project_id=1').to_a
assert_equal 3, developers_on_project_one.length
- developer_names = developers_on_project_one.map { |d| d.name }
+ developer_names = developers_on_project_one.map(&:name)
assert developer_names.include?('David')
assert developer_names.include?('Jamis')
end
@@ -651,8 +662,8 @@ class RelationTest < ActiveRecord::TestCase
expected_taggings = taggings(:welcome_general, :thinking_general)
assert_no_queries do
- assert_equal expected_taggings, author.taggings.distinct.sort_by { |t| t.id }
- assert_equal expected_taggings, author.taggings.uniq.sort_by { |t| t.id }
+ assert_equal expected_taggings, author.taggings.distinct.sort_by(&:id)
+ assert_equal expected_taggings, author.taggings.uniq.sort_by(&:id)
end
authors = Author.all
@@ -711,7 +722,9 @@ class RelationTest < ActiveRecord::TestCase
def test_find_by_classname
Author.create!(:name => Mary.name)
- assert_equal 1, Author.where(:name => Mary).size
+ assert_deprecated do
+ assert_equal 1, Author.where(:name => Mary).size
+ end
end
def test_find_by_id_with_list_of_ar
@@ -849,6 +862,12 @@ class RelationTest < ActiveRecord::TestCase
assert ! fake.exists?(authors(:david).id)
end
+ def test_exists_uses_existing_scope
+ post = authors(:david).posts.first
+ authors = Author.includes(:posts).where(name: "David", posts: { id: post.id })
+ assert authors.exists?(authors(:david).id)
+ end
+
def test_last
authors = Author.all
assert_equal authors(:bob), authors.last
@@ -1096,6 +1115,38 @@ class RelationTest < ActiveRecord::TestCase
assert ! posts.limit(1).many?
end
+ def test_none?
+ posts = Post.all
+ assert_queries(1) do
+ assert ! posts.none? # Uses COUNT()
+ end
+
+ assert ! posts.loaded?
+
+ assert_queries(1) do
+ assert posts.none? {|p| p.id < 0 }
+ assert ! posts.none? {|p| p.id == 1 }
+ end
+
+ assert posts.loaded?
+ end
+
+ def test_one
+ posts = Post.all
+ assert_queries(1) do
+ assert ! posts.one? # Uses COUNT()
+ end
+
+ assert ! posts.loaded?
+
+ assert_queries(1) do
+ assert ! posts.one? {|p| p.id < 3 }
+ assert posts.one? {|p| p.id == 1 }
+ end
+
+ assert posts.loaded?
+ end
+
def test_build
posts = Post.all
@@ -1374,12 +1425,6 @@ class RelationTest < ActiveRecord::TestCase
assert_equal "id", Post.all.primary_key
end
- def test_disable_implicit_join_references_is_deprecated
- assert_deprecated do
- ActiveRecord::Base.disable_implicit_join_references = true
- end
- end
-
def test_ordering_with_extra_spaces
assert_equal authors(:david), Author.order('id DESC , name DESC').last
end
@@ -1426,6 +1471,19 @@ class RelationTest < ActiveRecord::TestCase
assert_equal posts(:welcome), comments(:greetings).post
end
+ def test_update_on_relation
+ topic1 = TopicWithCallbacks.create! title: 'arel', author_name: nil
+ topic2 = TopicWithCallbacks.create! title: 'activerecord', author_name: nil
+ topics = TopicWithCallbacks.where(id: [topic1.id, topic2.id])
+ topics.update(title: 'adequaterecord')
+
+ assert_equal 'adequaterecord', topic1.reload.title
+ assert_equal 'adequaterecord', topic2.reload.title
+ # Testing that the before_update callbacks have run
+ assert_equal 'David', topic1.reload.author_name
+ assert_equal 'David', topic2.reload.author_name
+ end
+
def test_distinct
tag1 = Tag.create(:name => 'Foo')
tag2 = Tag.create(:name => 'Foo')
@@ -1447,10 +1505,31 @@ class RelationTest < ActiveRecord::TestCase
def test_doesnt_add_having_values_if_options_are_blank
scope = Post.having('')
- assert_equal [], scope.having_values
+ assert scope.having_clause.empty?
scope = Post.having([])
- assert_equal [], scope.having_values
+ assert scope.having_clause.empty?
+ end
+
+ def test_having_with_binds_for_both_where_and_having
+ post = Post.first
+ having_then_where = Post.having(id: post.id).where(title: post.title).group(:id)
+ where_then_having = Post.where(title: post.title).having(id: post.id).group(:id)
+
+ assert_equal [post], having_then_where
+ assert_equal [post], where_then_having
+ end
+
+ def test_multiple_where_and_having_clauses
+ post = Post.first
+ having_then_where = Post.having(id: post.id).where(title: post.title)
+ .having(id: post.id).where(title: post.title).group(:id)
+
+ assert_equal [post], having_then_where
+ end
+
+ def test_grouping_by_column_with_reserved_name
+ assert_equal [], Possession.select(:where).group(:where).to_a
end
def test_references_triggers_eager_loading
@@ -1636,6 +1715,14 @@ class RelationTest < ActiveRecord::TestCase
end
end
+ test "relations with cached arel can't be mutated [internal API]" do
+ relation = Post.all
+ relation.count
+
+ assert_raises(ActiveRecord::ImmutableRelation) { relation.limit!(5) }
+ assert_raises(ActiveRecord::ImmutableRelation) { relation.where!("1 = 2") }
+ end
+
test "relations show the records in #inspect" do
relation = Post.limit(2)
assert_equal "#<ActiveRecord::Relation [#{Post.limit(2).map(&:inspect).join(', ')}]>", relation.inspect
@@ -1660,7 +1747,9 @@ class RelationTest < ActiveRecord::TestCase
test 'using a custom table affects the wheres' do
table_alias = Post.arel_table.alias('omg_posts')
- relation = ActiveRecord::Relation.new Post, table_alias
+ table_metadata = ActiveRecord::TableMetadata.new(Post, table_alias)
+ predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata)
+ relation = ActiveRecord::Relation.new(Post, table_alias, predicate_builder)
relation.where!(:foo => "bar")
node = relation.arel.constraints.first.grep(Arel::Attributes::Attribute).first
@@ -1702,7 +1791,7 @@ class RelationTest < ActiveRecord::TestCase
end
def test_unscope_removes_binds
- left = Post.where(id: Arel::Nodes::BindParam.new('?'))
+ left = Post.where(id: Arel::Nodes::BindParam.new)
column = Post.columns_hash['id']
left.bind_values += [[column, 20]]
@@ -1719,14 +1808,13 @@ class RelationTest < ActiveRecord::TestCase
end
def test_merging_keeps_lhs_bind_parameters
- column = Post.columns_hash['id']
- binds = [[column, 20]]
+ binds = [ActiveRecord::Relation::QueryAttribute.new("id", 20, Post.type_for_attribute("id"))]
right = Post.where(id: 20)
left = Post.where(id: 10)
merged = left.merge(right)
- assert_equal binds, merged.bind_values
+ assert_equal binds, merged.bound_attributes
end
def test_merging_reorders_bind_params
diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb
index f477cf7d92..262e0abc22 100644
--- a/activerecord/test/cases/sanitize_test.rb
+++ b/activerecord/test/cases/sanitize_test.rb
@@ -7,17 +7,6 @@ class SanitizeTest < ActiveRecord::TestCase
def setup
end
- def test_sanitize_sql_hash_handles_associations
- quoted_bambi = ActiveRecord::Base.connection.quote("Bambi")
- quoted_column_name = ActiveRecord::Base.connection.quote_column_name("name")
- quoted_table_name = ActiveRecord::Base.connection.quote_table_name("adorable_animals")
- expected_value = "#{quoted_table_name}.#{quoted_column_name} = #{quoted_bambi}"
-
- assert_deprecated do
- assert_equal expected_value, Binary.send(:sanitize_sql_hash, {adorable_animals: {name: 'Bambi'}})
- end
- end
-
def test_sanitize_sql_array_handles_string_interpolation
quoted_bambi = ActiveRecord::Base.connection.quote_string("Bambi")
assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=%s", "Bambi"])
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 6303393c22..6c099719c0 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -3,7 +3,7 @@ require 'support/schema_dumping_helper'
class SchemaDumperTest < ActiveRecord::TestCase
include SchemaDumpingHelper
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
setup do
ActiveRecord::SchemaMigration.create_table
@@ -40,6 +40,11 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_no_match %r{create_table "schema_migrations"}, output
end
+ def test_schema_dump_uses_force_cascade_on_create_table
+ output = dump_table_schema "authors"
+ assert_match %r{create_table "authors", force: :cascade}, output
+ end
+
def test_schema_dump_excludes_sqlite_sequence
output = standard_dump
assert_no_match %r{create_table "sqlite_sequence"}, output
@@ -68,10 +73,10 @@ 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|uuid|point)\s+"/)
+ if match = column.match(/t\.(?:integer|decimal|float|datetime|timestamp|time|date|text|binary|string|boolean|xml|uuid|point)\s+"/)
match[0].length
end
- end
+ end.compact
assert_equal 1, lengths.uniq.length
end
@@ -162,16 +167,6 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_no_match %r{create_table "schema_migrations"}, output
end
- def test_schema_dump_illegal_ignored_table_value
- stream = StringIO.new
- old_ignore_tables, ActiveRecord::SchemaDumper.ignore_tables = ActiveRecord::SchemaDumper.ignore_tables, [5]
- assert_raise(StandardError) do
- ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
- end
- ensure
- ActiveRecord::SchemaDumper.ignore_tables = old_ignore_tables
- end
-
def test_schema_dumps_index_columns_in_right_order
index_definition = standard_dump.split(/\n/).grep(/add_index.*companies/).first.strip
if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter)
@@ -209,25 +204,30 @@ class SchemaDumperTest < ActiveRecord::TestCase
if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
def test_schema_dump_should_add_default_value_for_mysql_text_field
output = standard_dump
- assert_match %r{t.text\s+"body",\s+limit: 65535,\s+null: false$}, output
+ assert_match %r{t\.text\s+"body",\s+limit: 65535,\s+null: false$}, output
end
def test_schema_dump_includes_length_for_mysql_binary_fields
output = standard_dump
- assert_match %r{t.binary\s+"var_binary",\s+limit: 255$}, output
- assert_match %r{t.binary\s+"var_binary_large",\s+limit: 4095$}, output
+ assert_match %r{t\.binary\s+"var_binary",\s+limit: 255$}, output
+ assert_match %r{t\.binary\s+"var_binary_large",\s+limit: 4095$}, output
end
def test_schema_dump_includes_length_for_mysql_blob_and_text_fields
output = standard_dump
- assert_match %r{t.binary\s+"tiny_blob",\s+limit: 255$}, output
- assert_match %r{t.binary\s+"normal_blob",\s+limit: 65535$}, output
- assert_match %r{t.binary\s+"medium_blob",\s+limit: 16777215$}, output
- assert_match %r{t.binary\s+"long_blob",\s+limit: 4294967295$}, output
- assert_match %r{t.text\s+"tiny_text",\s+limit: 255$}, output
- assert_match %r{t.text\s+"normal_text",\s+limit: 65535$}, output
- assert_match %r{t.text\s+"medium_text",\s+limit: 16777215$}, output
- assert_match %r{t.text\s+"long_text",\s+limit: 4294967295$}, output
+ assert_match %r{t\.binary\s+"tiny_blob",\s+limit: 255$}, output
+ assert_match %r{t\.binary\s+"normal_blob",\s+limit: 65535$}, output
+ assert_match %r{t\.binary\s+"medium_blob",\s+limit: 16777215$}, output
+ assert_match %r{t\.binary\s+"long_blob",\s+limit: 4294967295$}, output
+ assert_match %r{t\.text\s+"tiny_text",\s+limit: 255$}, output
+ assert_match %r{t\.text\s+"normal_text",\s+limit: 65535$}, output
+ assert_match %r{t\.text\s+"medium_text",\s+limit: 16777215$}, output
+ assert_match %r{t\.text\s+"long_text",\s+limit: 4294967295$}, output
+ end
+
+ def test_schema_does_not_include_limit_for_emulated_mysql_boolean_fields
+ output = standard_dump
+ assert_no_match %r{t\.boolean\s+"has_fun",.+limit: 1}, output
end
def test_schema_dumps_index_type
@@ -239,13 +239,18 @@ class SchemaDumperTest < ActiveRecord::TestCase
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)
def test_schema_dump_includes_bigint_default
output = standard_dump
- assert_match %r{t.integer\s+"bigint_default",\s+limit: 8,\s+default: 0}, output
+ assert_match %r{t\.integer\s+"bigint_default",\s+limit: 8,\s+default: 0}, output
+ end
+
+ def test_schema_dump_includes_limit_on_array_type
+ output = standard_dump
+ assert_match %r{t\.integer\s+"big_int_data_points\",\s+limit: 8,\s+array: true}, output
end
if ActiveRecord::Base.connection.supports_extensions?
@@ -263,86 +268,17 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_no_match %r{enable_extension}, output
end
end
-
- def test_schema_dump_includes_xml_shorthand_definition
- output = standard_dump
- if %r{create_table "postgresql_xml_data_type"} =~ output
- assert_match %r{t.xml "data"}, output
- end
- end
-
- def test_schema_dump_includes_inet_shorthand_definition
- output = standard_dump
- if %r{create_table "postgresql_network_addresses"} =~ output
- assert_match %r{t.inet\s+"inet_address",\s+default: "192.168.1.1"}, output
- end
- end
-
- def test_schema_dump_includes_cidr_shorthand_definition
- output = standard_dump
- if %r{create_table "postgresql_network_addresses"} =~ output
- assert_match %r{t.cidr\s+"cidr_address",\s+default: "192.168.1.0/24"}, output
- end
- end
-
- def test_schema_dump_includes_macaddr_shorthand_definition
- output = standard_dump
- if %r{create_table "postgresql_network_addresses"} =~ output
- assert_match %r{t.macaddr\s+"mac_address",\s+default: "ff:ff:ff:ff:ff:ff"}, output
- end
- end
-
- def test_schema_dump_includes_uuid_shorthand_definition
- output = standard_dump
- if %r{create_table "postgresql_uuids"} =~ output
- assert_match %r{t.uuid "guid"}, output
- end
- end
-
- def test_schema_dump_includes_hstores_shorthand_definition
- output = standard_dump
- if %r{create_table "postgresql_hstores"} =~ output
- assert_match %r[t.hstore "hash_store", default: {}], output
- end
- end
-
- def test_schema_dump_includes_citext_shorthand_definition
- output = standard_dump
- if %r{create_table "postgresql_citext"} =~ output
- assert_match %r[t.citext "text_citext"], output
- end
- end
-
- def test_schema_dump_includes_ltrees_shorthand_definition
- output = standard_dump
- if %r{create_table "postgresql_ltrees"} =~ output
- assert_match %r[t.ltree "path"], output
- end
- end
-
- def test_schema_dump_includes_arrays_shorthand_definition
- output = standard_dump
- if %r{create_table "postgresql_arrays"} =~ output
- assert_match %r[t.text\s+"nicknames",\s+array: true], output
- assert_match %r[t.integer\s+"commission_by_quarter",\s+array: true], output
- end
- end
-
- def test_schema_dump_includes_tsvector_shorthand_definition
- output = standard_dump
- if %r{create_table "postgresql_tsvectors"} =~ output
- assert_match %r{t.tsvector "text_vector"}, output
- end
- end
end
def test_schema_dump_keeps_large_precision_integer_columns_as_decimal
output = standard_dump
# Oracle supports precision up to 38 and it identifies decimals with scale 0 as integers
if current_adapter?(:OracleAdapter)
- assert_match %r{t.integer\s+"atoms_in_universe",\s+precision: 38}, output
+ assert_match %r{t\.integer\s+"atoms_in_universe",\s+precision: 38}, output
+ elsif current_adapter?(:FbAdapter)
+ assert_match %r{t\.integer\s+"atoms_in_universe",\s+precision: 18}, output
else
- assert_match %r{t.decimal\s+"atoms_in_universe",\s+precision: 55}, output
+ assert_match %r{t\.decimal\s+"atoms_in_universe",\s+precision: 55}, output
end
end
@@ -351,7 +287,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
match = output.match(%r{create_table "goofy_string_id"(.*)do.*\n(.*)\n})
assert_not_nil(match, "goofy_string_id table not found")
assert_match %r(id: false), match[1], "no table id not preserved"
- assert_match %r{t.string\s+"id",.*?null: false$}, match[2], "non-primary key id column not preserved"
+ assert_match %r{t\.string\s+"id",.*?null: false$}, match[2], "non-primary key id column not preserved"
end
def test_schema_dump_keeps_id_false_when_id_is_false_and_unique_not_null_column_added
@@ -429,7 +365,7 @@ class SchemaDumperDefaultsTest < ActiveRecord::TestCase
teardown do
return unless @connection
- @connection.execute 'DROP TABLE defaults' if @connection.table_exists? 'defaults'
+ @connection.drop_table 'defaults', if_exists: true
end
def test_schema_dump_defaults_with_universally_supported_types
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index a5c4404175..4137b20c4a 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -2,13 +2,14 @@ require 'cases/helper'
require 'models/post'
require 'models/comment'
require 'models/developer'
+require 'models/computer'
class DefaultScopingTest < ActiveRecord::TestCase
fixtures :developers, :posts, :comments
def test_default_scope
- expected = Developer.all.merge!(:order => 'salary DESC').to_a.collect { |dev| dev.salary }
- received = DeveloperOrderedBySalary.all.collect { |dev| dev.salary }
+ expected = Developer.all.merge!(:order => 'salary DESC').to_a.collect(&:salary)
+ received = DeveloperOrderedBySalary.all.collect(&:salary)
assert_equal expected, received
end
@@ -85,14 +86,14 @@ class DefaultScopingTest < ActiveRecord::TestCase
end
def test_scope_overwrites_default
- expected = Developer.all.merge!(order: 'salary DESC, name DESC').to_a.collect { |dev| dev.name }
- received = DeveloperOrderedBySalary.by_name.to_a.collect { |dev| dev.name }
+ expected = Developer.all.merge!(order: 'salary DESC, name DESC').to_a.collect(&:name)
+ received = DeveloperOrderedBySalary.by_name.to_a.collect(&:name)
assert_equal expected, received
end
def test_reorder_overrides_default_scope_order
- expected = Developer.order('name DESC').collect { |dev| dev.name }
- received = DeveloperOrderedBySalary.reorder('name DESC').collect { |dev| dev.name }
+ expected = Developer.order('name DESC').collect(&:name)
+ received = DeveloperOrderedBySalary.reorder('name DESC').collect(&:name)
assert_equal expected, received
end
@@ -142,37 +143,45 @@ class DefaultScopingTest < ActiveRecord::TestCase
expected_5 = Developer.order('salary DESC').collect(&:name)
received_5 = DeveloperOrderedBySalary.where.not("name" => ["Jamis", "David"]).unscope(where: :name).collect(&:name)
assert_equal expected_5, received_5
+
+ expected_6 = Developer.order('salary DESC').collect(&:name)
+ received_6 = DeveloperOrderedBySalary.where(Developer.arel_table['name'].eq('David')).unscope(where: :name).collect(&:name)
+ assert_equal expected_6, received_6
+
+ expected_7 = Developer.order('salary DESC').collect(&:name)
+ received_7 = DeveloperOrderedBySalary.where(Developer.arel_table[:name].eq('David')).unscope(where: :name).collect(&:name)
+ assert_equal expected_7, received_7
end
def test_unscope_multiple_where_clauses
- expected = Developer.order('salary DESC').collect { |dev| dev.name }
- received = DeveloperOrderedBySalary.where(name: 'Jamis').where(id: 1).unscope(where: [:name, :id]).collect { |dev| dev.name }
+ expected = Developer.order('salary DESC').collect(&:name)
+ received = DeveloperOrderedBySalary.where(name: 'Jamis').where(id: 1).unscope(where: [:name, :id]).collect(&:name)
assert_equal expected, received
end
def test_unscope_string_where_clauses_involved
dev_relation = Developer.order('salary DESC').where("created_at > ?", 1.year.ago)
- expected = dev_relation.collect { |dev| dev.name }
+ expected = dev_relation.collect(&:name)
dev_ordered_relation = DeveloperOrderedBySalary.where(name: 'Jamis').where("created_at > ?", 1.year.ago)
- received = dev_ordered_relation.unscope(where: [:name]).collect { |dev| dev.name }
+ received = dev_ordered_relation.unscope(where: [:name]).collect(&:name)
assert_equal expected, received
end
def test_unscope_with_grouping_attributes
- expected = Developer.order('salary DESC').collect { |dev| dev.name }
- received = DeveloperOrderedBySalary.group(:name).unscope(:group).collect { |dev| dev.name }
+ expected = Developer.order('salary DESC').collect(&:name)
+ received = DeveloperOrderedBySalary.group(:name).unscope(:group).collect(&:name)
assert_equal expected, received
- expected_2 = Developer.order('salary DESC').collect { |dev| dev.name }
- received_2 = DeveloperOrderedBySalary.group("name").unscope(:group).collect { |dev| dev.name }
+ expected_2 = Developer.order('salary DESC').collect(&:name)
+ received_2 = DeveloperOrderedBySalary.group("name").unscope(:group).collect(&:name)
assert_equal expected_2, received_2
end
def test_unscope_with_limit_in_query
- expected = Developer.order('salary DESC').collect { |dev| dev.name }
- received = DeveloperOrderedBySalary.limit(1).unscope(:limit).collect { |dev| dev.name }
+ expected = Developer.order('salary DESC').collect(&:name)
+ received = DeveloperOrderedBySalary.limit(1).unscope(:limit).collect(&:name)
assert_equal expected, received
end
@@ -182,42 +191,42 @@ class DefaultScopingTest < ActiveRecord::TestCase
end
def test_unscope_reverse_order
- expected = Developer.all.collect { |dev| dev.name }
- received = Developer.order('salary DESC').reverse_order.unscope(:order).collect { |dev| dev.name }
+ expected = Developer.all.collect(&:name)
+ received = Developer.order('salary DESC').reverse_order.unscope(:order).collect(&:name)
assert_equal expected, received
end
def test_unscope_select
- expected = Developer.order('salary ASC').collect { |dev| dev.name }
- received = Developer.order('salary DESC').reverse_order.select(:name).unscope(:select).collect { |dev| dev.name }
+ expected = Developer.order('salary ASC').collect(&:name)
+ received = Developer.order('salary DESC').reverse_order.select(:name).unscope(:select).collect(&:name)
assert_equal expected, received
- expected_2 = Developer.all.collect { |dev| dev.id }
- received_2 = Developer.select(:name).unscope(:select).collect { |dev| dev.id }
+ expected_2 = Developer.all.collect(&:id)
+ received_2 = Developer.select(:name).unscope(:select).collect(&:id)
assert_equal expected_2, received_2
end
def test_unscope_offset
- expected = Developer.all.collect { |dev| dev.name }
- received = Developer.offset(5).unscope(:offset).collect { |dev| dev.name }
+ expected = Developer.all.collect(&:name)
+ received = Developer.offset(5).unscope(:offset).collect(&:name)
assert_equal expected, received
end
def test_unscope_joins_and_select_on_developers_projects
- expected = Developer.all.collect { |dev| dev.name }
- received = Developer.joins('JOIN developers_projects ON id = developer_id').select(:id).unscope(:joins, :select).collect { |dev| dev.name }
+ expected = Developer.all.collect(&:name)
+ received = Developer.joins('JOIN developers_projects ON id = developer_id').select(:id).unscope(:joins, :select).collect(&:name)
assert_equal expected, received
end
def test_unscope_includes
- expected = Developer.all.collect { |dev| dev.name }
- received = Developer.includes(:projects).select(:id).unscope(:includes, :select).collect { |dev| dev.name }
+ expected = Developer.all.collect(&:name)
+ received = Developer.includes(:projects).select(:id).unscope(:includes, :select).collect(&:name)
assert_equal expected, received
end
def test_unscope_having
- expected = DeveloperOrderedBySalary.all.collect { |dev| dev.name }
- received = DeveloperOrderedBySalary.having("name IN ('Jamis', 'David')").unscope(:having).collect { |dev| dev.name }
+ expected = DeveloperOrderedBySalary.all.collect(&:name)
+ received = DeveloperOrderedBySalary.having("name IN ('Jamis', 'David')").unscope(:having).collect(&:name)
assert_equal expected, received
end
@@ -275,13 +284,13 @@ class DefaultScopingTest < ActiveRecord::TestCase
def test_unscope_merging
merged = Developer.where(name: "Jamis").merge(Developer.unscope(:where))
- assert merged.where_values.empty?
- assert !merged.where(name: "Jon").where_values.empty?
+ assert merged.where_clause.empty?
+ assert !merged.where(name: "Jon").where_clause.empty?
end
def test_order_in_default_scope_should_not_prevail
- expected = Developer.all.merge!(order: 'salary desc').to_a.collect { |dev| dev.salary }
- received = DeveloperOrderedBySalary.all.merge!(order: 'salary').to_a.collect { |dev| dev.salary }
+ expected = Developer.all.merge!(order: 'salary desc').to_a.collect(&:salary)
+ received = DeveloperOrderedBySalary.all.merge!(order: 'salary').to_a.collect(&:salary)
assert_equal expected, received
end
@@ -417,19 +426,19 @@ class DefaultScopingTest < ActiveRecord::TestCase
test "additional conditions are ANDed with the default scope" do
scope = DeveloperCalledJamis.where(name: "David")
- assert_equal 2, scope.where_values.length
+ assert_equal 2, scope.where_clause.ast.children.length
assert_equal [], scope.to_a
end
test "additional conditions in a scope are ANDed with the default scope" do
scope = DeveloperCalledJamis.david
- assert_equal 2, scope.where_values.length
+ assert_equal 2, scope.where_clause.ast.children.length
assert_equal [], scope.to_a
end
test "a scope can remove the condition from the default scope" do
scope = DeveloperCalledJamis.david2
- assert_equal 1, scope.where_values.length
- assert_equal Developer.where(name: "David").map(&:id), scope.map(&:id)
+ assert_equal 1, scope.where_clause.ast.children.length
+ assert_equal Developer.where(name: "David"), scope
end
end
diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb
index c2816c3670..e4cc533517 100644
--- a/activerecord/test/cases/scoping/named_scoping_test.rb
+++ b/activerecord/test/cases/scoping/named_scoping_test.rb
@@ -5,6 +5,7 @@ require 'models/comment'
require 'models/reply'
require 'models/author'
require 'models/developer'
+require 'models/computer'
class NamedScopingTest < ActiveRecord::TestCase
fixtures :posts, :authors, :topics, :comments, :author_addresses
@@ -316,13 +317,15 @@ class NamedScopingTest < ActiveRecord::TestCase
]
conflicts.each do |name|
- assert_raises(ArgumentError, "scope `#{name}` should not be allowed") do
+ e = assert_raises(ArgumentError, "scope `#{name}` should not be allowed") do
klass.class_eval { scope name, ->{ where(approved: true) } }
end
+ assert_match(/You tried to define a scope named \"#{name}\" on the model/, e.message)
- assert_raises(ArgumentError, "scope `#{name}` should not be allowed") do
+ e = assert_raises(ArgumentError, "scope `#{name}` should not be allowed") do
subklass.class_eval { scope name, ->{ where(approved: true) } }
end
+ assert_match(/You tried to define a scope named \"#{name}\" on the model/, e.message)
end
non_conflicts.each do |name|
@@ -379,8 +382,8 @@ class NamedScopingTest < ActiveRecord::TestCase
end
def test_should_not_duplicates_where_values
- where_values = Topic.where("1=1").scope_with_lambda.where_values
- assert_equal ["1=1"], where_values
+ relation = Topic.where("1=1")
+ assert_equal relation.where_clause, relation.scope_with_lambda.where_clause
end
def test_chaining_with_duplicate_joins
diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb
index 73835c85a8..4bfffbe9c6 100644
--- a/activerecord/test/cases/scoping/relation_scoping_test.rb
+++ b/activerecord/test/cases/scoping/relation_scoping_test.rb
@@ -2,6 +2,7 @@ require "cases/helper"
require 'models/post'
require 'models/author'
require 'models/developer'
+require 'models/computer'
require 'models/project'
require 'models/comment'
require 'models/category'
@@ -183,7 +184,7 @@ class RelationScopingTest < ActiveRecord::TestCase
rescue
end
- assert !Developer.all.where_values.include?("name = 'Jamis'")
+ assert_not Developer.all.to_sql.include?("name = 'Jamis'"), "scope was not restored"
end
def test_default_scope_filters_on_joins
@@ -207,6 +208,12 @@ class RelationScopingTest < ActiveRecord::TestCase
assert_equal [], DeveloperFilteredOnJoins.all
assert_not_equal [], Developer.all
end
+
+ def test_current_scope_does_not_pollute_other_subclasses
+ Post.none.scoping do
+ assert StiPost.all.any?
+ end
+ end
end
class NestedRelationScopingTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/secure_token_test.rb b/activerecord/test/cases/secure_token_test.rb
new file mode 100644
index 0000000000..e731443fc2
--- /dev/null
+++ b/activerecord/test/cases/secure_token_test.rb
@@ -0,0 +1,32 @@
+require 'cases/helper'
+require 'models/user'
+
+class SecureTokenTest < ActiveRecord::TestCase
+ setup do
+ @user = User.new
+ end
+
+ def test_token_values_are_generated_for_specified_attributes_and_persisted_on_save
+ @user.save
+ assert_not_nil @user.token
+ assert_not_nil @user.auth_token
+ end
+
+ def test_regenerating_the_secure_token
+ @user.save
+ old_token = @user.token
+ old_auth_token = @user.auth_token
+ @user.regenerate_token
+ @user.regenerate_auth_token
+
+ assert_not_equal @user.token, old_token
+ assert_not_equal @user.auth_token, old_auth_token
+ end
+
+ def test_token_value_not_overwritten_when_present
+ @user.token = "custom-secure-token"
+ @user.save
+
+ assert_equal @user.token, "custom-secure-token"
+ end
+end
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index 66f345d805..7c92453ee3 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -22,12 +22,6 @@ class SerializedAttributeTest < ActiveRecord::TestCase
end
end
- def test_list_of_serialized_attributes
- assert_deprecated do
- assert_equal %w(content), Topic.serialized_attributes.keys
- end
- end
-
def test_serialized_attribute
Topic.serialize("content", MyObject)
@@ -243,8 +237,9 @@ class SerializedAttributeTest < ActiveRecord::TestCase
t = Topic.create(content: "first")
assert_equal("first", t.content)
- t.update_column(:content, Topic.type_for_attribute('content').type_cast_for_database("second"))
- assert_equal("second", t.content)
+ t.update_column(:content, ["second"])
+ assert_equal(["second"], t.content)
+ assert_equal(["second"], t.reload.content)
end
def test_serialized_column_should_unserialize_after_update_attribute
@@ -253,5 +248,30 @@ class SerializedAttributeTest < ActiveRecord::TestCase
t.update_attribute(:content, "second")
assert_equal("second", t.content)
+ assert_equal("second", t.reload.content)
+ end
+
+ def test_nil_is_not_changed_when_serialized_with_a_class
+ Topic.serialize(:content, Array)
+
+ topic = Topic.new(content: nil)
+
+ assert_not topic.content_changed?
+ end
+
+ def test_classes_without_no_arg_constructors_are_not_supported
+ assert_raises(ArgumentError) do
+ Topic.serialize(:content, Regexp)
+ end
+ end
+
+ def test_newly_emptied_serialized_hash_is_changed
+ Topic.serialize(:content, Hash)
+ topic = Topic.create(content: { "things" => "stuff" })
+ topic.content.delete("things")
+ topic.save!
+ topic.reload
+
+ assert_equal({}, topic.content)
end
end
diff --git a/activerecord/test/cases/suppressor_test.rb b/activerecord/test/cases/suppressor_test.rb
new file mode 100644
index 0000000000..1c449d42fe
--- /dev/null
+++ b/activerecord/test/cases/suppressor_test.rb
@@ -0,0 +1,21 @@
+require 'cases/helper'
+require 'models/notification'
+require 'models/user'
+
+class SuppressorTest < ActiveRecord::TestCase
+ def test_suppresses_creation_of_record_generated_by_callback
+ assert_difference -> { User.count } do
+ assert_no_difference -> { Notification.count } do
+ Notification.suppress { UserWithNotification.create! }
+ end
+ end
+ end
+
+ def test_resumes_saving_after_suppression_complete
+ Notification.suppress { UserWithNotification.create! }
+
+ assert_difference -> { Notification.count } do
+ Notification.create!
+ end
+ end
+end
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index 2fa033ed45..38164b2228 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -377,4 +377,20 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.check_schema_file("awesome-file.sql")
end
end
+
+ class DatabaseTasksCheckSchemaFileDefaultsTest < ActiveRecord::TestCase
+ def test_check_schema_file_defaults
+ ActiveRecord::Tasks::DatabaseTasks.stubs(:db_dir).returns('/tmp')
+ assert_equal '/tmp/schema.rb', ActiveRecord::Tasks::DatabaseTasks.schema_file
+ end
+ end
+
+ class DatabaseTasksCheckSchemaFileSpecifiedFormatsTest < ActiveRecord::TestCase
+ {ruby: 'schema.rb', sql: 'structure.sql'}.each_pair do |fmt, filename|
+ define_method("test_check_schema_file_for_#{fmt}_format") do
+ ActiveRecord::Tasks::DatabaseTasks.stubs(:db_dir).returns('/tmp')
+ assert_equal "/tmp/#{filename}", ActiveRecord::Tasks::DatabaseTasks.schema_file(fmt)
+ end
+ end
+ end
end
diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb
index 0d574d071c..084302cde5 100644
--- a/activerecord/test/cases/tasks/postgresql_rake_test.rb
+++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb
@@ -195,21 +195,54 @@ module ActiveRecord
'adapter' => 'postgresql',
'database' => 'my-app-db'
}
+ @filename = "awesome-file.sql"
ActiveRecord::Base.stubs(:connection).returns(@connection)
ActiveRecord::Base.stubs(:establish_connection).returns(true)
Kernel.stubs(:system)
+ File.stubs(:open)
end
def test_structure_dump
- filename = "awesome-file.sql"
- Kernel.expects(:system).with("pg_dump -i -s -x -O -f #{filename} my-app-db").returns(true)
- @connection.expects(:schema_search_path).returns("foo")
+ 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
+
+ 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)
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
+ end
+
+ 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)
+
+ with_dump_schemas(:all) do
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
+ end
+ 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)
+
+ with_dump_schemas('foo,bar') do
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
+ end
+ end
+
+ private
- ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
- assert File.exist?(filename)
+ def with_dump_schemas(value, &block)
+ old_dump_schemas = ActiveRecord::Base.dump_schemas
+ ActiveRecord::Base.dump_schemas = value
+ yield
ensure
- FileUtils.rm(filename)
+ ActiveRecord::Base.dump_schemas = old_dump_schemas
end
end
@@ -228,14 +261,14 @@ module ActiveRecord
def test_structure_load
filename = "awesome-file.sql"
- Kernel.expects(:system).with("psql -q -f #{filename} my-app-db")
+ Kernel.expects(:system).with("psql -X -q -f #{filename} my-app-db")
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 -q -f awesome\\ file.sql my-app-db")
+ Kernel.expects(:system).with("psql -X -q -f awesome\\ file.sql my-app-db")
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 eb44c4a83c..e0b01ae8e0 100644
--- a/activerecord/test/cases/test_case.rb
+++ b/activerecord/test/cases/test_case.rb
@@ -1,10 +1,13 @@
require 'active_support/test_case'
+require 'active_support/testing/stream'
module ActiveRecord
# = Active Record Test Case
#
# Defines some test assertions to test against SQL queries.
class TestCase < ActiveSupport::TestCase #:nodoc:
+ include ActiveSupport::Testing::Stream
+
def teardown
SQLCounter.clear_log
end
@@ -13,23 +16,6 @@ module ActiveRecord
assert_equal expected.to_s, actual.to_s, message
end
- def capture(stream)
- stream = stream.to_s
- captured_stream = Tempfile.new(stream)
- stream_io = eval("$#{stream}")
- origin_stream = stream_io.dup
- stream_io.reopen(captured_stream)
-
- yield
-
- stream_io.rewind
- return captured_stream.read
- ensure
- captured_stream.close
- captured_stream.unlink
- stream_io.reopen(origin_stream)
- end
-
def capture_sql
SQLCounter.clear_log
yield
@@ -43,7 +29,7 @@ module ActiveRecord
patterns_to_match.each do |pattern|
failed_patterns << pattern unless SQLCounter.log_all.any?{ |sql| pattern === sql }
end
- assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{SQLCounter.log.join("\n")}"}"
+ assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map(&:inspect).join(', ')} not found.#{SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{SQLCounter.log.join("\n")}"}"
end
def assert_queries(num = 1, options = {})
diff --git a/activerecord/test/cases/test_fixtures_test.rb b/activerecord/test/cases/test_fixtures_test.rb
new file mode 100644
index 0000000000..3f4baf8378
--- /dev/null
+++ b/activerecord/test/cases/test_fixtures_test.rb
@@ -0,0 +1,36 @@
+require 'cases/helper'
+
+class TestFixturesTest < ActiveRecord::TestCase
+ setup do
+ @klass = Class.new
+ @klass.send(:include, ActiveRecord::TestFixtures)
+ end
+
+ def test_deprecated_use_transactional_fixtures=
+ assert_deprecated 'use use_transactional_tests= instead' do
+ @klass.use_transactional_fixtures = true
+ end
+ end
+
+ def test_use_transactional_tests_prefers_use_transactional_fixtures
+ ActiveSupport::Deprecation.silence do
+ @klass.use_transactional_fixtures = false
+ end
+
+ assert_equal false, @klass.use_transactional_tests
+ end
+
+ def test_use_transactional_tests_defaults_to_true
+ ActiveSupport::Deprecation.silence do
+ @klass.use_transactional_fixtures = nil
+ end
+
+ assert_equal true, @klass.use_transactional_tests
+ end
+
+ def test_use_transactional_tests_can_be_overriden
+ @klass.use_transactional_tests = "foobar"
+
+ assert_equal "foobar", @klass.use_transactional_tests
+ end
+end
diff --git a/activerecord/test/cases/time_precision_test.rb b/activerecord/test/cases/time_precision_test.rb
new file mode 100644
index 0000000000..ff7a81fe60
--- /dev/null
+++ b/activerecord/test/cases/time_precision_test.rb
@@ -0,0 +1,108 @@
+require 'cases/helper'
+require 'support/schema_dumping_helper'
+
+if ActiveRecord::Base.connection.supports_datetime_with_precision?
+class TimePrecisionTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+ self.use_transactional_tests = false
+
+ class Foo < ActiveRecord::Base; end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ end
+
+ teardown do
+ @connection.drop_table :foos, if_exists: true
+ end
+
+ def test_time_data_type_with_precision
+ @connection.create_table(:foos, force: true)
+ @connection.add_column :foos, :start, :time, precision: 3
+ @connection.add_column :foos, :finish, :time, precision: 6
+ assert_equal 3, activerecord_column_option('foos', 'start', 'precision')
+ assert_equal 6, activerecord_column_option('foos', 'finish', 'precision')
+ end
+
+ def test_passing_precision_to_time_does_not_set_limit
+ @connection.create_table(:foos, force: true) do |t|
+ t.time :start, precision: 3
+ t.time :finish, precision: 6
+ end
+ assert_nil activerecord_column_option('foos', 'start', 'limit')
+ assert_nil activerecord_column_option('foos', 'finish', 'limit')
+ end
+
+ def test_invalid_time_precision_raises_error
+ assert_raises ActiveRecord::ActiveRecordError do
+ @connection.create_table(:foos, force: true) do |t|
+ t.time :start, precision: 7
+ t.time :finish, precision: 7
+ end
+ end
+ end
+
+ def test_database_agrees_with_activerecord_about_precision
+ @connection.create_table(:foos, force: true) do |t|
+ t.time :start, precision: 2
+ t.time :finish, precision: 4
+ end
+ assert_equal 2, database_datetime_precision('foos', 'start')
+ assert_equal 4, database_datetime_precision('foos', 'finish')
+ end
+
+ def test_formatting_time_according_to_precision
+ @connection.create_table(:foos, force: true) do |t|
+ t.time :start, precision: 0
+ t.time :finish, precision: 4
+ end
+ time = ::Time.utc(2000, 1, 1, 12, 30, 0, 999999)
+ Foo.create!(start: time, finish: time)
+ assert foo = Foo.find_by(start: time)
+ assert_equal 1, Foo.where(finish: time).count
+ assert_equal time.to_s, foo.start.to_s
+ assert_equal time.to_s, foo.finish.to_s
+ assert_equal 000000, foo.start.usec
+ assert_equal 999900, foo.finish.usec
+ end
+
+ def test_schema_dump_includes_time_precision
+ @connection.create_table(:foos, force: true) do |t|
+ t.time :start, precision: 4
+ t.time :finish, precision: 6
+ end
+ output = dump_table_schema("foos")
+ assert_match %r{t\.time\s+"start",\s+precision: 4$}, output
+ assert_match %r{t\.time\s+"finish",\s+precision: 6$}, output
+ end
+
+ if current_adapter?(:PostgreSQLAdapter)
+ def test_time_precision_with_zero_should_be_dumped
+ @connection.create_table(:foos, force: true) do |t|
+ t.time :start, precision: 0
+ t.time :finish, precision: 0
+ end
+ output = dump_table_schema("foos")
+ assert_match %r{t\.time\s+"start",\s+precision: 0$}, output
+ assert_match %r{t\.time\s+"finish",\s+precision: 0$}, output
+ end
+ end
+
+ private
+
+ def database_datetime_precision(table_name, column_name)
+ results = @connection.exec_query("SELECT column_name, datetime_precision FROM information_schema.columns WHERE table_name = '#{table_name}'")
+ result = results.find do |result_hash|
+ result_hash["column_name"] == column_name
+ end
+ result && result["datetime_precision"].to_i
+ end
+
+ def activerecord_column_option(tablename, column_name, option)
+ result = @connection.columns(tablename).find do |column|
+ column.name == column_name
+ end
+ result && result.send(option)
+ end
+end
+end
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index abf6becc17..7c89b4b9e8 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -1,6 +1,7 @@
require 'cases/helper'
require 'support/ddl_helper'
require 'models/developer'
+require 'models/computer'
require 'models/owner'
require 'models/pet'
require 'models/toy'
@@ -72,6 +73,15 @@ class TimestampTest < ActiveRecord::TestCase
assert_equal @previously_updated_at, @developer.updated_at
end
+ def test_touching_updates_timestamp_with_given_time
+ previously_updated_at = @developer.updated_at
+ new_time = Time.utc(2015, 2, 16, 0, 0, 0)
+ @developer.touch(time: new_time)
+
+ assert_not_equal previously_updated_at, @developer.updated_at
+ assert_equal new_time, @developer.updated_at
+ end
+
def test_touching_an_attribute_updates_timestamp
previously_created_at = @developer.created_at
@developer.touch(:created_at)
@@ -90,6 +100,18 @@ class TimestampTest < ActiveRecord::TestCase
assert_in_delta Time.now, task.ending, 1
end
+ def test_touching_an_attribute_updates_timestamp_with_given_time
+ previously_updated_at = @developer.updated_at
+ previously_created_at = @developer.created_at
+ new_time = Time.utc(2015, 2, 16, 4, 54, 0)
+ @developer.touch(:created_at, time: new_time)
+
+ assert_not_equal previously_created_at, @developer.created_at
+ assert_not_equal previously_updated_at, @developer.updated_at
+ assert_equal new_time, @developer.created_at
+ assert_equal new_time, @developer.updated_at
+ end
+
def test_touching_many_attributes_updates_them
task = Task.first
previous_starting = task.starting
@@ -428,7 +450,7 @@ end
class TimestampsWithoutTransactionTest < ActiveRecord::TestCase
include DdlHelper
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
class TimestampAttributePost < ActiveRecord::Base
attr_accessor :created_at, :updated_at
diff --git a/activerecord/test/cases/touch_later_test.rb b/activerecord/test/cases/touch_later_test.rb
new file mode 100644
index 0000000000..11804ff90b
--- /dev/null
+++ b/activerecord/test/cases/touch_later_test.rb
@@ -0,0 +1,93 @@
+require 'cases/helper'
+require 'models/invoice'
+require 'models/line_item'
+require 'models/topic'
+
+class TouchLaterTest < ActiveRecord::TestCase
+
+ def test_touch_laster_raise_if_non_persisted
+ invoice = Invoice.new
+ Invoice.transaction do
+ refute 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
+ refute 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
+end
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index b5ac1bdaf9..f2229939c8 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -4,7 +4,6 @@ require 'models/pet'
require 'models/topic'
class TransactionCallbacksTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
fixtures :topics, :owners, :pets
class ReplyWithCallbacks < ActiveRecord::Base
@@ -200,21 +199,21 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
end
def test_call_after_rollback_when_commit_fails
- @first.class.connection.singleton_class.send(:alias_method, :real_method_commit_db_transaction, :commit_db_transaction)
- begin
- @first.class.connection.singleton_class.class_eval do
- def commit_db_transaction; raise "boom!"; end
- end
+ @first.after_commit_block { |r| r.history << :after_commit }
+ @first.after_rollback_block { |r| r.history << :after_rollback }
- @first.after_commit_block{|r| r.history << :after_commit}
- @first.after_rollback_block{|r| r.history << :after_rollback}
+ assert_raises RuntimeError do
+ @first.transaction do
+ tx = @first.class.connection.transaction_manager.current_transaction
+ def tx.commit
+ raise
+ end
- assert !@first.save rescue nil
- assert_equal [:after_rollback], @first.history
- ensure
- @first.class.connection.singleton_class.send(:remove_method, :commit_db_transaction)
- @first.class.connection.singleton_class.send(:alias_method, :commit_db_transaction, :real_method_commit_db_transaction)
+ @first.save
+ end
end
+
+ assert_equal [:after_rollback], @first.history
end
def test_only_call_after_rollback_on_records_rolled_back_to_a_savepoint
@@ -266,47 +265,6 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
assert_equal 2, @first.rollbacks
end
- def test_after_transaction_callbacks_should_prevent_callbacks_from_being_called
- old_transaction_config = ActiveRecord::Base.raise_in_transactional_callbacks
- ActiveRecord::Base.raise_in_transactional_callbacks = false
-
- def @first.last_after_transaction_error=(e); @last_transaction_error = e; end
- def @first.last_after_transaction_error; @last_transaction_error; end
- @first.after_commit_block{|r| r.last_after_transaction_error = :commit; raise "fail!";}
- @first.after_rollback_block{|r| r.last_after_transaction_error = :rollback; raise "fail!";}
-
- second = TopicWithCallbacks.find(3)
- second.after_commit_block{|r| r.history << :after_commit}
- second.after_rollback_block{|r| r.history << :after_rollback}
-
- Topic.transaction do
- @first.save!
- second.save!
- end
- assert_equal :commit, @first.last_after_transaction_error
- assert_equal [:after_commit], second.history
-
- second.history.clear
- Topic.transaction do
- @first.save!
- second.save!
- raise ActiveRecord::Rollback
- end
- assert_equal :rollback, @first.last_after_transaction_error
- assert_equal [:after_rollback], second.history
- ensure
- ActiveRecord::Base.raise_in_transactional_callbacks = old_transaction_config
- end
-
- def test_after_commit_should_not_raise_when_raise_in_transactional_callbacks_false
- old_transaction_config = ActiveRecord::Base.raise_in_transactional_callbacks
- ActiveRecord::Base.raise_in_transactional_callbacks = false
- @first.after_commit_block{ fail "boom" }
- Topic.transaction { @first.save! }
- ensure
- ActiveRecord::Base.raise_in_transactional_callbacks = old_transaction_config
- end
-
def test_after_commit_callback_should_not_swallow_errors
@first.after_commit_block{ fail "boom" }
assert_raises(RuntimeError) do
@@ -371,10 +329,14 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
def test_after_rollback_callbacks_should_validate_on_condition
assert_raise(ArgumentError) { Topic.after_rollback(on: :save) }
+ e = assert_raise(ArgumentError) { Topic.after_rollback(on: 'create') }
+ assert_match(/:on conditions for after_commit and after_rollback callbacks have to be one of \[:create, :destroy, :update\]/, e.message)
end
def test_after_commit_callbacks_should_validate_on_condition
assert_raise(ArgumentError) { Topic.after_commit(on: :save) }
+ e = assert_raise(ArgumentError) { Topic.after_commit(on: 'create') }
+ assert_match(/:on conditions for after_commit and after_rollback callbacks have to be one of \[:create, :destroy, :update\]/, e.message)
end
def test_saving_a_record_with_a_belongs_to_that_specifies_touching_the_parent_should_call_callbacks_on_the_parent_object
@@ -405,7 +367,7 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
end
class CallbacksOnMultipleActionsTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
class TopicWithCallbacksOnMultipleActions < ActiveRecord::Base
self.table_name = :topics
@@ -414,6 +376,9 @@ class CallbacksOnMultipleActionsTest < ActiveRecord::TestCase
after_commit(on: [:create, :update]) { |record| record.history << :create_and_update }
after_commit(on: [:update, :destroy]) { |record| record.history << :update_and_destroy }
+ before_commit(if: :save_before_commit_history) { |record| record.history << :before_commit }
+ before_commit(if: :update_title) { |record| record.update(title: "before commit title") }
+
def clear_history
@history = []
end
@@ -421,6 +386,8 @@ class CallbacksOnMultipleActionsTest < ActiveRecord::TestCase
def history
@history ||= []
end
+
+ attr_accessor :save_before_commit_history, :update_title
end
def test_after_commit_on_multiple_actions
@@ -437,4 +404,81 @@ class CallbacksOnMultipleActionsTest < ActiveRecord::TestCase
topic.destroy
assert_equal [:update_and_destroy, :create_and_destroy], topic.history
end
+
+ def test_before_commit_actions
+ topic = TopicWithCallbacksOnMultipleActions.new
+ topic.save_before_commit_history = true
+ topic.save
+
+ assert_equal [:before_commit, :create_and_update, :create_and_destroy], topic.history
+ end
+
+ def test_before_commit_update_in_same_transaction
+ topic = TopicWithCallbacksOnMultipleActions.new
+ topic.update_title = true
+ topic.save
+
+ assert_equal "before commit title", topic.title
+ assert_equal "before commit title", topic.reload.title
+ end
+end
+
+
+class TransactionEnrollmentCallbacksTest < ActiveRecord::TestCase
+
+ class TopicWithoutTransactionalEnrollmentCallbacks < ActiveRecord::Base
+ self.table_name = :topics
+
+ before_commit_without_transaction_enrollment { |r| r.history << :before_commit }
+ after_commit_without_transaction_enrollment { |r| r.history << :after_commit }
+ after_rollback_without_transaction_enrollment { |r| r.history << :rollback }
+
+ def history
+ @history ||= []
+ end
+ end
+
+ def setup
+ @topic = TopicWithoutTransactionalEnrollmentCallbacks.create!
+ end
+
+ def test_commit_does_not_run_transactions_callbacks_without_enrollment
+ @topic.transaction do
+ @topic.content = 'foo'
+ @topic.save!
+ end
+ assert @topic.history.empty?
+ end
+
+ def test_commit_run_transactions_callbacks_with_explicit_enrollment
+ @topic.transaction do
+ 2.times do
+ @topic.content = 'foo'
+ @topic.save!
+ end
+ @topic.class.connection.add_transaction_record(@topic)
+ end
+ assert_equal [:before_commit, :after_commit], @topic.history
+ end
+
+ def test_rollback_does_not_run_transactions_callbacks_without_enrollment
+ @topic.transaction do
+ @topic.content = 'foo'
+ @topic.save!
+ raise ActiveRecord::Rollback
+ end
+ assert @topic.history.empty?
+ end
+
+ def test_rollback_run_transactions_callbacks_with_explicit_enrollment
+ @topic.transaction do
+ 2.times do
+ @topic.content = 'foo'
+ @topic.save!
+ end
+ @topic.class.connection.add_transaction_record(@topic)
+ raise ActiveRecord::Rollback
+ end
+ assert_equal [:rollback], @topic.history
+ end
end
diff --git a/activerecord/test/cases/transaction_isolation_test.rb b/activerecord/test/cases/transaction_isolation_test.rb
index f89c26532d..2f7d208ed2 100644
--- a/activerecord/test/cases/transaction_isolation_test.rb
+++ b/activerecord/test/cases/transaction_isolation_test.rb
@@ -2,7 +2,7 @@ require 'cases/helper'
unless ActiveRecord::Base.connection.supports_transaction_isolation?
class TransactionIsolationUnsupportedTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
class Tag < ActiveRecord::Base
end
@@ -17,7 +17,7 @@ end
if ActiveRecord::Base.connection.supports_transaction_isolation?
class TransactionIsolationTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
class Tag < ActiveRecord::Base
self.table_name = 'tags'
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 5cccf2dda5..2468a91969 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -2,17 +2,18 @@ require "cases/helper"
require 'models/topic'
require 'models/reply'
require 'models/developer'
+require 'models/computer'
require 'models/book'
require 'models/author'
require 'models/post'
require 'models/movie'
class TransactionTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
fixtures :topics, :developers, :authors, :posts
def setup
- @first, @second = Topic.find(1, 2).sort_by { |t| t.id }
+ @first, @second = Topic.find(1, 2).sort_by(&:id)
end
def test_persisted_in_a_model_with_custom_primary_key_after_failed_save
@@ -193,6 +194,16 @@ class TransactionTest < ActiveRecord::TestCase
assert_equal posts_count, author.posts(true).size
end
+ def test_cancellation_from_returning_false_in_before_filter
+ def @first.before_save_for_transaction
+ false
+ end
+
+ assert_deprecated do
+ @first.save
+ end
+ end
+
def test_cancellation_from_before_destroy_rollbacks_in_destroy
add_cancelling_before_destroy_with_db_side_effect_to_topic @first
nbooks_before_destroy = Book.count
@@ -492,35 +503,32 @@ class TransactionTest < ActiveRecord::TestCase
assert topic.frozen?, 'not frozen'
end
- # The behavior of killed threads having a status of "aborting" was changed
- # in Ruby 2.0, so Thread#kill on 1.9 will prematurely commit the transaction
- # and there's nothing we can do about it.
- unless RUBY_VERSION.start_with? '1.9'
- def test_rollback_when_thread_killed
- queue = Queue.new
- thread = Thread.new do
- Topic.transaction do
- @first.approved = true
- @second.approved = false
- @first.save
+ def test_rollback_when_thread_killed
+ return if in_memory_db?
- queue.push nil
- sleep
+ queue = Queue.new
+ thread = Thread.new do
+ Topic.transaction do
+ @first.approved = true
+ @second.approved = false
+ @first.save
- @second.save
- end
+ queue.push nil
+ sleep
+
+ @second.save
end
+ end
- queue.pop
- thread.kill
- thread.join
+ queue.pop
+ thread.kill
+ thread.join
- assert @first.approved?, "First should still be changed in the objects"
- assert !@second.approved?, "Second should still be changed in the objects"
+ assert @first.approved?, "First should still be changed in the objects"
+ assert !@second.approved?, "Second should still be changed in the objects"
- assert !Topic.find(1).approved?, "First shouldn't have been approved"
- assert Topic.find(2).approved?, "Second should still be approved"
- end
+ assert !Topic.find(1).approved?, "First shouldn't have been approved"
+ assert Topic.find(2).approved?, "Second should still be approved"
end
def test_restore_active_record_state_for_all_records_in_a_transaction
@@ -561,6 +569,39 @@ class TransactionTest < ActiveRecord::TestCase
assert !@second.destroyed?, 'not destroyed'
end
+ def test_restore_frozen_state_after_double_destroy
+ topic = Topic.create
+ reply = topic.replies.create
+
+ Topic.transaction do
+ topic.destroy # calls #destroy on reply (since dependent: destroy)
+ reply.destroy
+
+ raise ActiveRecord::Rollback
+ end
+
+ assert_not reply.frozen?
+ assert_not topic.frozen?
+ end
+
+ def test_rollback_of_frozen_records
+ topic = Topic.create.freeze
+ Topic.transaction do
+ topic.destroy
+ raise ActiveRecord::Rollback
+ end
+ assert topic.frozen?, 'frozen'
+ end
+
+ def test_rollback_for_freshly_persisted_records
+ topic = Topic.create
+ Topic.transaction do
+ topic.destroy
+ raise ActiveRecord::Rollback
+ end
+ assert topic.persisted?, 'persisted'
+ end
+
def test_sqlite_add_column_in_transaction
return true unless current_adapter?(:SQLite3Adapter)
@@ -627,6 +668,27 @@ class TransactionTest < ActiveRecord::TestCase
assert transaction.state.committed?
end
+ def test_transaction_rollback_with_primarykeyless_tables
+ connection = ActiveRecord::Base.connection
+ connection.create_table(:transaction_without_primary_keys, force: true, id: false) do |t|
+ t.integer :thing_id
+ end
+
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = 'transaction_without_primary_keys'
+ after_commit { } # necessary to trigger the has_transactional_callbacks branch
+ end
+
+ assert_no_difference(-> { klass.count }) do
+ ActiveRecord::Base.transaction do
+ klass.create!
+ raise ActiveRecord::Rollback
+ end
+ end
+ ensure
+ connection.drop_table 'transaction_without_primary_keys', if_exists: true
+ end
+
private
%w(validation save destroy).each do |filter|
@@ -634,14 +696,14 @@ class TransactionTest < ActiveRecord::TestCase
meta = class << topic; self; end
meta.send("define_method", "before_#{filter}_for_transaction") do
Book.create
- false
+ throw(:abort)
end
end
end
end
class TransactionsWithTransactionalFixturesTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = true
+ self.use_transactional_tests = true
fixtures :topics
def test_automatic_savepoint_in_outer_transaction
@@ -698,7 +760,7 @@ if current_adapter?(:PostgreSQLAdapter)
end
end
- threads.each { |t| t.join }
+ threads.each(&:join)
end
end
@@ -746,7 +808,7 @@ if current_adapter?(:PostgreSQLAdapter)
Developer.connection.close
end
- threads.each { |t| t.join }
+ threads.each(&:join)
end
assert_equal original_salary, Developer.find(1).salary
diff --git a/activerecord/test/cases/type/adapter_specific_registry_test.rb b/activerecord/test/cases/type/adapter_specific_registry_test.rb
new file mode 100644
index 0000000000..8b836b4793
--- /dev/null
+++ b/activerecord/test/cases/type/adapter_specific_registry_test.rb
@@ -0,0 +1,133 @@
+require "cases/helper"
+
+module ActiveRecord
+ class AdapterSpecificRegistryTest < ActiveRecord::TestCase
+ test "a class can be registered for a symbol" do
+ registry = Type::AdapterSpecificRegistry.new
+ registry.register(:foo, ::String)
+ registry.register(:bar, ::Array)
+
+ assert_equal "", registry.lookup(:foo)
+ assert_equal [], registry.lookup(:bar)
+ end
+
+ test "a block can be registered" do
+ registry = Type::AdapterSpecificRegistry.new
+ registry.register(:foo) do |*args|
+ [*args, "block for foo"]
+ end
+ registry.register(:bar) do |*args|
+ [*args, "block for bar"]
+ end
+
+ assert_equal [:foo, 1, "block for foo"], registry.lookup(:foo, 1)
+ assert_equal [:foo, 2, "block for foo"], registry.lookup(:foo, 2)
+ assert_equal [:bar, 1, 2, 3, "block for bar"], registry.lookup(:bar, 1, 2, 3)
+ end
+
+ test "filtering by adapter" do
+ registry = Type::AdapterSpecificRegistry.new
+ registry.register(:foo, String, adapter: :sqlite3)
+ registry.register(:foo, Array, adapter: :postgresql)
+
+ assert_equal "", registry.lookup(:foo, adapter: :sqlite3)
+ assert_equal [], registry.lookup(:foo, adapter: :postgresql)
+ end
+
+ test "an error is raised if both a generic and adapter specific type match" do
+ registry = Type::AdapterSpecificRegistry.new
+ registry.register(:foo, String)
+ registry.register(:foo, Array, adapter: :postgresql)
+
+ assert_raises TypeConflictError do
+ registry.lookup(:foo, adapter: :postgresql)
+ end
+ assert_equal "", registry.lookup(:foo, adapter: :sqlite3)
+ end
+
+ test "a generic type can explicitly override an adapter specific type" do
+ registry = Type::AdapterSpecificRegistry.new
+ registry.register(:foo, String, override: true)
+ registry.register(:foo, Array, adapter: :postgresql)
+
+ assert_equal "", registry.lookup(:foo, adapter: :postgresql)
+ assert_equal "", registry.lookup(:foo, adapter: :sqlite3)
+ end
+
+ test "a generic type can explicitly allow an adapter type to be used instead" do
+ registry = Type::AdapterSpecificRegistry.new
+ registry.register(:foo, String, override: false)
+ registry.register(:foo, Array, adapter: :postgresql)
+
+ assert_equal [], registry.lookup(:foo, adapter: :postgresql)
+ assert_equal "", registry.lookup(:foo, adapter: :sqlite3)
+ end
+
+ test "a reasonable error is given when no type is found" do
+ registry = Type::AdapterSpecificRegistry.new
+
+ e = assert_raises(ArgumentError) do
+ registry.lookup(:foo)
+ end
+
+ assert_equal "Unknown type :foo", e.message
+ end
+
+ test "construct args are passed to the type" do
+ type = Struct.new(:args)
+ registry = Type::AdapterSpecificRegistry.new
+ registry.register(:foo, type)
+
+ assert_equal type.new, registry.lookup(:foo)
+ assert_equal type.new(:ordered_arg), registry.lookup(:foo, :ordered_arg)
+ assert_equal type.new(keyword: :arg), registry.lookup(:foo, keyword: :arg)
+ assert_equal type.new(keyword: :arg), registry.lookup(:foo, keyword: :arg, adapter: :postgresql)
+ end
+
+ test "registering a modifier" do
+ decoration = Struct.new(:value)
+ registry = Type::AdapterSpecificRegistry.new
+ registry.register(:foo, String)
+ registry.register(:bar, Hash)
+ registry.add_modifier({ array: true }, decoration)
+
+ assert_equal decoration.new(""), registry.lookup(:foo, array: true)
+ assert_equal decoration.new({}), registry.lookup(:bar, array: true)
+ assert_equal "", registry.lookup(:foo)
+ end
+
+ test "registering multiple modifiers" do
+ decoration = Struct.new(:value)
+ other_decoration = Struct.new(:value)
+ registry = Type::AdapterSpecificRegistry.new
+ registry.register(:foo, String)
+ registry.add_modifier({ array: true }, decoration)
+ registry.add_modifier({ range: true }, other_decoration)
+
+ assert_equal "", registry.lookup(:foo)
+ assert_equal decoration.new(""), registry.lookup(:foo, array: true)
+ assert_equal other_decoration.new(""), registry.lookup(:foo, range: true)
+ assert_equal(
+ decoration.new(other_decoration.new("")),
+ registry.lookup(:foo, array: true, range: true)
+ )
+ end
+
+ test "registering adapter specific modifiers" do
+ decoration = Struct.new(:value)
+ type = Struct.new(:args)
+ registry = Type::AdapterSpecificRegistry.new
+ registry.register(:foo, type)
+ registry.add_modifier({ array: true }, decoration, adapter: :postgresql)
+
+ assert_equal(
+ decoration.new(type.new(keyword: :arg)),
+ registry.lookup(:foo, array: true, adapter: :postgresql, keyword: :arg)
+ )
+ assert_equal(
+ type.new(array: true),
+ registry.lookup(:foo, array: true, adapter: :sqlite3)
+ )
+ end
+ end
+end
diff --git a/activerecord/test/cases/type/decimal_test.rb b/activerecord/test/cases/type/decimal_test.rb
index da30de373e..fe49d0e79a 100644
--- a/activerecord/test/cases/type/decimal_test.rb
+++ b/activerecord/test/cases/type/decimal_test.rb
@@ -5,24 +5,29 @@ module ActiveRecord
class DecimalTest < ActiveRecord::TestCase
def test_type_cast_decimal
type = Decimal.new
- assert_equal BigDecimal.new("0"), type.type_cast_from_user(BigDecimal.new("0"))
- assert_equal BigDecimal.new("123"), type.type_cast_from_user(123.0)
- assert_equal BigDecimal.new("1"), type.type_cast_from_user(:"1")
+ assert_equal BigDecimal.new("0"), type.cast(BigDecimal.new("0"))
+ assert_equal BigDecimal.new("123"), type.cast(123.0)
+ assert_equal BigDecimal.new("1"), type.cast(:"1")
end
def test_type_cast_decimal_from_float_with_large_precision
type = Decimal.new(precision: ::Float::DIG + 2)
- assert_equal BigDecimal.new("123.0"), type.type_cast_from_user(123.0)
+ assert_equal BigDecimal.new("123.0"), type.cast(123.0)
+ end
+
+ def test_type_cast_from_float_with_unspecified_precision
+ type = Decimal.new
+ assert_equal 22.68.to_d, type.cast(22.68)
end
def test_type_cast_decimal_from_rational_with_precision
type = Decimal.new(precision: 2)
- assert_equal BigDecimal("0.33"), type.type_cast_from_user(Rational(1, 3))
+ assert_equal BigDecimal("0.33"), type.cast(Rational(1, 3))
end
def test_type_cast_decimal_from_rational_without_precision_defaults_to_18_36
type = Decimal.new
- assert_equal BigDecimal("0.333333333333333333E0"), type.type_cast_from_user(Rational(1, 3))
+ assert_equal BigDecimal("0.333333333333333333E0"), type.cast(Rational(1, 3))
end
def test_type_cast_decimal_from_object_responding_to_d
@@ -31,7 +36,15 @@ module ActiveRecord
BigDecimal.new("1")
end
type = Decimal.new
- assert_equal BigDecimal("1"), type.type_cast_from_user(value)
+ assert_equal BigDecimal("1"), type.cast(value)
+ end
+
+ def test_changed?
+ type = Decimal.new
+
+ assert type.changed?(5.0, 5.0, '5.0wibble')
+ assert_not type.changed?(5.0, 5.0, '5.0')
+ assert_not type.changed?(-5.0, -5.0, '-5.0')
end
end
end
diff --git a/activerecord/test/cases/type/integer_test.rb b/activerecord/test/cases/type/integer_test.rb
index 53d6a3a6aa..84fb05dd8e 100644
--- a/activerecord/test/cases/type/integer_test.rb
+++ b/activerecord/test/cases/type/integer_test.rb
@@ -6,39 +6,45 @@ module ActiveRecord
class IntegerTest < ActiveRecord::TestCase
test "simple values" do
type = Type::Integer.new
- assert_equal 1, type.type_cast_from_user(1)
- assert_equal 1, type.type_cast_from_user('1')
- assert_equal 1, type.type_cast_from_user('1ignore')
- assert_equal 0, type.type_cast_from_user('bad1')
- assert_equal 0, type.type_cast_from_user('bad')
- assert_equal 1, type.type_cast_from_user(1.7)
- assert_equal 0, type.type_cast_from_user(false)
- assert_equal 1, type.type_cast_from_user(true)
- assert_nil type.type_cast_from_user(nil)
+ assert_equal 1, type.cast(1)
+ assert_equal 1, type.cast('1')
+ assert_equal 1, type.cast('1ignore')
+ assert_equal 0, type.cast('bad1')
+ assert_equal 0, type.cast('bad')
+ assert_equal 1, type.cast(1.7)
+ assert_equal 0, type.cast(false)
+ assert_equal 1, type.cast(true)
+ assert_nil type.cast(nil)
end
test "random objects cast to nil" do
type = Type::Integer.new
- assert_nil type.type_cast_from_user([1,2])
- assert_nil type.type_cast_from_user({1 => 2})
- assert_nil type.type_cast_from_user((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
type = Type::Integer.new
firm = Firm.create(:name => 'Apple')
- assert_nil type.type_cast_from_user(firm)
+ assert_nil type.cast(firm)
end
test "casting objects without to_i" do
type = Type::Integer.new
- assert_nil type.type_cast_from_user(::Object.new)
+ assert_nil type.cast(::Object.new)
end
test "casting nan and infinity" do
type = Type::Integer.new
- assert_nil type.type_cast_from_user(::Float::NAN)
- assert_nil type.type_cast_from_user(1.0/0.0)
+ assert_nil type.cast(::Float::NAN)
+ assert_nil type.cast(1.0/0.0)
+ end
+
+ test "casting booleans for database" do
+ type = Type::Integer.new
+ assert_equal 1, type.serialize(true)
+ assert_equal 0, type.serialize(false)
end
test "changed?" do
@@ -47,59 +53,74 @@ module ActiveRecord
assert type.changed?(5, 5, '5wibble')
assert_not type.changed?(5, 5, '5')
assert_not type.changed?(5, 5, '5.0')
+ assert_not type.changed?(-5, -5, '-5')
+ assert_not type.changed?(-5, -5, '-5.0')
assert_not type.changed?(nil, nil, nil)
end
test "values below int min value are out of range" do
assert_raises(::RangeError) do
- Integer.new.type_cast_from_user("-2147483649")
+ Integer.new.serialize(-2147483649)
end
end
test "values above int max value are out of range" do
assert_raises(::RangeError) do
- Integer.new.type_cast_from_user("2147483648")
+ Integer.new.serialize(2147483648)
end
end
test "very small numbers are out of range" do
assert_raises(::RangeError) do
- Integer.new.type_cast_from_user("-9999999999999999999999999999999")
+ Integer.new.serialize(-9999999999999999999999999999999)
end
end
- test "very large numbers are in range" do
+ test "very large numbers are out of range" do
assert_raises(::RangeError) do
- Integer.new.type_cast_from_user("9999999999999999999999999999999")
+ Integer.new.serialize(9999999999999999999999999999999)
end
end
test "normal numbers are in range" do
type = Integer.new
- assert_equal(0, type.type_cast_from_user("0"))
- assert_equal(-1, type.type_cast_from_user("-1"))
- assert_equal(1, type.type_cast_from_user("1"))
+ assert_equal(0, type.serialize(0))
+ assert_equal(-1, type.serialize(-1))
+ assert_equal(1, type.serialize(1))
end
test "int max value is in range" do
- assert_equal(2147483647, Integer.new.type_cast_from_user("2147483647"))
+ assert_equal(2147483647, Integer.new.serialize(2147483647))
end
test "int min value is in range" do
- assert_equal(-2147483648, Integer.new.type_cast_from_user("-2147483648"))
+ assert_equal(-2147483648, Integer.new.serialize(-2147483648))
end
test "columns with a larger limit have larger ranges" do
type = Integer.new(limit: 8)
- assert_equal(9223372036854775807, type.type_cast_from_user("9223372036854775807"))
- assert_equal(-9223372036854775808, type.type_cast_from_user("-9223372036854775808"))
+ assert_equal(9223372036854775807, type.serialize(9223372036854775807))
+ assert_equal(-9223372036854775808, type.serialize(-9223372036854775808))
assert_raises(::RangeError) do
- type.type_cast_from_user("-9999999999999999999999999999999")
+ type.serialize(-9999999999999999999999999999999)
end
assert_raises(::RangeError) do
- type.type_cast_from_user("9999999999999999999999999999999")
+ type.serialize(9999999999999999999999999999999)
+ end
+ end
+
+ test "values which are out of range can be re-assigned" do
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = 'posts'
+ attribute :foo, :integer
end
+ model = klass.new
+
+ model.foo = 2147483648
+ model.foo = 1
+
+ assert_equal 1, model.foo
end
end
end
diff --git a/activerecord/test/cases/type/string_test.rb b/activerecord/test/cases/type/string_test.rb
index 420177ed49..56e9bf434d 100644
--- a/activerecord/test/cases/type/string_test.rb
+++ b/activerecord/test/cases/type/string_test.rb
@@ -4,16 +4,16 @@ module ActiveRecord
class StringTypeTest < ActiveRecord::TestCase
test "type casting" do
type = Type::String.new
- assert_equal "1", type.type_cast_from_user(true)
- assert_equal "0", type.type_cast_from_user(false)
- assert_equal "123", type.type_cast_from_user(123)
+ assert_equal "t", type.cast(true)
+ assert_equal "f", type.cast(false)
+ assert_equal "123", type.cast(123)
end
test "values are duped coming out" do
s = "foo"
type = Type::String.new
- assert_not_same s, type.type_cast_from_user(s)
- assert_not_same s, type.type_cast_from_database(s)
+ assert_not_same s, type.cast(s)
+ assert_not_same s, type.deserialize(s)
end
test "string mutations are detected" do
diff --git a/activerecord/test/cases/type/type_map_test.rb b/activerecord/test/cases/type/type_map_test.rb
index 4e32f92dd0..172c6dfc4c 100644
--- a/activerecord/test/cases/type/type_map_test.rb
+++ b/activerecord/test/cases/type/type_map_test.rb
@@ -124,6 +124,53 @@ module ActiveRecord
assert_equal mapping.lookup(3), 'string'
assert_kind_of Type::Value, mapping.lookup(4)
end
+
+ def test_fetch
+ mapping = TypeMap.new
+ mapping.register_type(1, "string")
+
+ assert_equal "string", mapping.fetch(1) { "int" }
+ assert_equal "int", mapping.fetch(2) { "int" }
+ end
+
+ def test_fetch_yields_args
+ mapping = TypeMap.new
+
+ assert_equal "foo-1-2-3", mapping.fetch("foo", 1, 2, 3) { |*args| args.join("-") }
+ assert_equal "bar-1-2-3", mapping.fetch("bar", 1, 2, 3) { |*args| args.join("-") }
+ end
+
+ def test_fetch_memoizes
+ mapping = TypeMap.new
+
+ looked_up = false
+ mapping.register_type(1) do
+ fail if looked_up
+ looked_up = true
+ "string"
+ end
+
+ assert_equal "string", mapping.fetch(1)
+ assert_equal "string", mapping.fetch(1)
+ end
+
+ def test_fetch_memoizes_on_args
+ mapping = TypeMap.new
+ mapping.register_type("foo") { |*args| args.join("-") }
+
+ assert_equal "foo-1-2-3", mapping.fetch("foo", 1, 2, 3) { |*args| args.join("-") }
+ assert_equal "foo-2-3-4", mapping.fetch("foo", 2, 3, 4) { |*args| args.join("-") }
+ end
+
+ def test_register_clears_cache
+ mapping = TypeMap.new
+
+ mapping.register_type(1, "string")
+ mapping.lookup(1)
+ mapping.register_type(1, "int")
+
+ assert_equal "int", mapping.lookup(1)
+ end
end
end
end
diff --git a/activerecord/test/cases/type/unsigned_integer_test.rb b/activerecord/test/cases/type/unsigned_integer_test.rb
new file mode 100644
index 0000000000..f2c910eade
--- /dev/null
+++ b/activerecord/test/cases/type/unsigned_integer_test.rb
@@ -0,0 +1,17 @@
+require "cases/helper"
+
+module ActiveRecord
+ module Type
+ class UnsignedIntegerTest < ActiveRecord::TestCase
+ test "unsigned int max value is in range" do
+ assert_equal(4294967295, UnsignedInteger.new.serialize(4294967295))
+ end
+
+ test "minus value is out of range" do
+ assert_raises(::RangeError) do
+ UnsignedInteger.new.serialize(-1)
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/type_test.rb b/activerecord/test/cases/type_test.rb
new file mode 100644
index 0000000000..d45a9b3141
--- /dev/null
+++ b/activerecord/test/cases/type_test.rb
@@ -0,0 +1,39 @@
+require "cases/helper"
+
+class TypeTest < ActiveRecord::TestCase
+ setup do
+ @old_registry = ActiveRecord::Type.registry
+ ActiveRecord::Type.registry = ActiveRecord::Type::AdapterSpecificRegistry.new
+ end
+
+ teardown do
+ ActiveRecord::Type.registry = @old_registry
+ end
+
+ test "registering a new type" do
+ type = Struct.new(:args)
+ ActiveRecord::Type.register(:foo, type)
+
+ assert_equal type.new(:arg), ActiveRecord::Type.lookup(:foo, :arg)
+ end
+
+ test "looking up a type for a specific adapter" do
+ type = Struct.new(:args)
+ pgtype = Struct.new(:args)
+ ActiveRecord::Type.register(:foo, type, override: false)
+ ActiveRecord::Type.register(:foo, pgtype, adapter: :postgresql)
+
+ assert_equal type.new, ActiveRecord::Type.lookup(:foo, adapter: :sqlite)
+ assert_equal pgtype.new, ActiveRecord::Type.lookup(:foo, adapter: :postgresql)
+ end
+
+ test "lookup defaults to the current adapter" do
+ current_adapter = ActiveRecord::Base.connection.adapter_name.downcase.to_sym
+ type = Struct.new(:args)
+ adapter_type = Struct.new(:args)
+ ActiveRecord::Type.register(:foo, type, override: false)
+ ActiveRecord::Type.register(:foo, adapter_type, adapter: current_adapter)
+
+ assert_equal adapter_type.new, ActiveRecord::Type.lookup(:foo)
+ end
+end
diff --git a/activerecord/test/cases/types_test.rb b/activerecord/test/cases/types_test.rb
index b0979cbe1f..9b1859c2ce 100644
--- a/activerecord/test/cases/types_test.rb
+++ b/activerecord/test/cases/types_test.rb
@@ -5,40 +5,38 @@ module ActiveRecord
class TypesTest < ActiveRecord::TestCase
def test_type_cast_boolean
type = Type::Boolean.new
- assert type.type_cast_from_user('').nil?
- assert type.type_cast_from_user(nil).nil?
-
- assert type.type_cast_from_user(true)
- assert type.type_cast_from_user(1)
- assert type.type_cast_from_user('1')
- assert type.type_cast_from_user('t')
- assert type.type_cast_from_user('T')
- assert type.type_cast_from_user('true')
- assert type.type_cast_from_user('TRUE')
- assert type.type_cast_from_user('on')
- assert type.type_cast_from_user('ON')
+ assert type.cast('').nil?
+ assert type.cast(nil).nil?
+
+ assert type.cast(true)
+ assert type.cast(1)
+ assert type.cast('1')
+ assert type.cast('t')
+ assert type.cast('T')
+ assert type.cast('true')
+ assert type.cast('TRUE')
+ assert type.cast('on')
+ assert type.cast('ON')
+ assert type.cast(' ')
+ assert type.cast("\u3000\r\n")
+ assert type.cast("\u0000")
+ assert type.cast('SOMETHING RANDOM')
# explicitly check for false vs nil
- assert_equal false, type.type_cast_from_user(false)
- assert_equal false, type.type_cast_from_user(0)
- assert_equal false, type.type_cast_from_user('0')
- assert_equal false, type.type_cast_from_user('f')
- assert_equal false, type.type_cast_from_user('F')
- assert_equal false, type.type_cast_from_user('false')
- assert_equal false, type.type_cast_from_user('FALSE')
- assert_equal false, type.type_cast_from_user('off')
- assert_equal false, type.type_cast_from_user('OFF')
- assert_deprecated do
- assert_equal false, type.type_cast_from_user(' ')
- assert_equal false, type.type_cast_from_user("\u3000\r\n")
- assert_equal false, type.type_cast_from_user("\u0000")
- assert_equal false, type.type_cast_from_user('SOMETHING RANDOM')
- end
+ assert_equal false, type.cast(false)
+ assert_equal false, type.cast(0)
+ assert_equal false, type.cast('0')
+ assert_equal false, type.cast('f')
+ assert_equal false, type.cast('F')
+ assert_equal false, type.cast('false')
+ assert_equal false, type.cast('FALSE')
+ assert_equal false, type.cast('off')
+ assert_equal false, type.cast('OFF')
end
def test_type_cast_float
type = Type::Float.new
- assert_equal 1.0, type.type_cast_from_user("1")
+ assert_equal 1.0, type.cast("1")
end
def test_changing_float
@@ -52,54 +50,54 @@ module ActiveRecord
def test_type_cast_binary
type = Type::Binary.new
- assert_equal nil, type.type_cast_from_user(nil)
- assert_equal "1", type.type_cast_from_user("1")
- assert_equal 1, type.type_cast_from_user(1)
+ assert_equal nil, type.cast(nil)
+ assert_equal "1", type.cast("1")
+ assert_equal 1, type.cast(1)
end
def test_type_cast_time
type = Type::Time.new
- assert_equal nil, type.type_cast_from_user(nil)
- assert_equal nil, type.type_cast_from_user('')
- assert_equal nil, type.type_cast_from_user('ABC')
+ assert_equal nil, type.cast(nil)
+ assert_equal nil, type.cast('')
+ assert_equal nil, type.cast('ABC')
time_string = Time.now.utc.strftime("%T")
- assert_equal time_string, type.type_cast_from_user(time_string).strftime("%T")
+ assert_equal time_string, type.cast(time_string).strftime("%T")
end
def test_type_cast_datetime_and_timestamp
type = Type::DateTime.new
- assert_equal nil, type.type_cast_from_user(nil)
- assert_equal nil, type.type_cast_from_user('')
- assert_equal nil, type.type_cast_from_user(' ')
- assert_equal nil, type.type_cast_from_user('ABC')
+ assert_equal nil, type.cast(nil)
+ assert_equal nil, type.cast('')
+ assert_equal nil, type.cast(' ')
+ assert_equal nil, type.cast('ABC')
datetime_string = Time.now.utc.strftime("%FT%T")
- assert_equal datetime_string, type.type_cast_from_user(datetime_string).strftime("%FT%T")
+ assert_equal datetime_string, type.cast(datetime_string).strftime("%FT%T")
end
def test_type_cast_date
type = Type::Date.new
- assert_equal nil, type.type_cast_from_user(nil)
- assert_equal nil, type.type_cast_from_user('')
- assert_equal nil, type.type_cast_from_user(' ')
- assert_equal nil, type.type_cast_from_user('ABC')
+ assert_equal nil, type.cast(nil)
+ assert_equal nil, type.cast('')
+ assert_equal nil, type.cast(' ')
+ assert_equal nil, type.cast('ABC')
date_string = Time.now.utc.strftime("%F")
- assert_equal date_string, type.type_cast_from_user(date_string).strftime("%F")
+ assert_equal date_string, type.cast(date_string).strftime("%F")
end
def test_type_cast_duration_to_integer
type = Type::Integer.new
- assert_equal 1800, type.type_cast_from_user(30.minutes)
- assert_equal 7200, type.type_cast_from_user(2.hours)
+ assert_equal 1800, type.cast(30.minutes)
+ assert_equal 7200, type.cast(2.hours)
end
def test_string_to_time_with_timezone
[:utc, :local].each do |zone|
with_timezone_config default: zone do
type = Type::DateTime.new
- assert_equal Time.utc(2013, 9, 4, 0, 0, 0), type.type_cast_from_user("Wed, 04 Sep 2013 03:00:00 EAT")
+ assert_equal Time.utc(2013, 9, 4, 0, 0, 0), type.cast("Wed, 04 Sep 2013 03:00:00 EAT")
end
end
end
@@ -110,14 +108,21 @@ module ActiveRecord
assert_not_equal Type::Value.new(precision: 1), Type::Value.new(precision: 2)
end
- if current_adapter?(:SQLite3Adapter)
- def test_binary_encoding
- type = SQLite3Binary.new
- utf8_string = "a string".encode(Encoding::UTF_8)
- type_cast = type.type_cast_from_user(utf8_string)
-
- assert_equal Encoding::ASCII_8BIT, type_cast.encoding
+ def test_attributes_which_are_invalid_for_database_can_still_be_reassigned
+ type_which_cannot_go_to_the_database = Type::Value.new
+ def type_which_cannot_go_to_the_database.serialize(*)
+ raise
+ end
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = 'posts'
+ attribute :foo, type_which_cannot_go_to_the_database
end
+ model = klass.new
+
+ model.foo = "foo"
+ model.foo = "bar"
+
+ assert_equal "bar", model.foo
end
end
end
diff --git a/activerecord/test/cases/unconnected_test.rb b/activerecord/test/cases/unconnected_test.rb
index afb893a52c..b210584644 100644
--- a/activerecord/test/cases/unconnected_test.rb
+++ b/activerecord/test/cases/unconnected_test.rb
@@ -4,7 +4,7 @@ class TestRecord < ActiveRecord::Base
end
class TestUnconnectedAdapter < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_tests = false
def setup
@underlying = ActiveRecord::Base.connection
diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb
index e4edc437e6..bff5ffa65e 100644
--- a/activerecord/test/cases/validations/association_validation_test.rb
+++ b/activerecord/test/cases/validations/association_validation_test.rb
@@ -50,7 +50,7 @@ class AssociationValidationTest < ActiveRecord::TestCase
Topic.validates_presence_of :content
r = Reply.create("title" => "A reply", "content" => "with content!")
r.topic = Topic.create("title" => "uhohuhoh")
- assert !r.valid?
+ assert_not_operator r, :valid?
assert_equal ["This string contains 'single' and \"double\" quotes"], r.errors[:topic]
end
@@ -82,5 +82,4 @@ class AssociationValidationTest < ActiveRecord::TestCase
assert interest.valid?, "Expected interest to be valid, but was not. Interest should have a man object associated"
end
end
-
end
diff --git a/activerecord/test/cases/validations/length_validation_test.rb b/activerecord/test/cases/validations/length_validation_test.rb
index 4a92da38ce..f95f8f0b8f 100644
--- a/activerecord/test/cases/validations/length_validation_test.rb
+++ b/activerecord/test/cases/validations/length_validation_test.rb
@@ -2,46 +2,77 @@
require "cases/helper"
require 'models/owner'
require 'models/pet'
+require 'models/person'
class LengthValidationTest < ActiveRecord::TestCase
fixtures :owners
- repair_validations(Owner)
- def test_validates_size_of_association
- repair_validations Owner do
- assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 }
- o = Owner.new('name' => 'nopets')
- assert !o.save
- assert o.errors[:pets].any?
- o.pets.build('name' => 'apet')
- assert o.valid?
+ setup do
+ @owner = Class.new(Owner) do
+ def self.name; 'Owner'; end
end
end
+
+ def test_validates_size_of_association
+ assert_nothing_raised { @owner.validates_size_of :pets, minimum: 1 }
+ o = @owner.new('name' => 'nopets')
+ assert !o.save
+ assert o.errors[:pets].any?
+ o.pets.build('name' => 'apet')
+ assert o.valid?
+ end
+
def test_validates_size_of_association_using_within
- repair_validations Owner do
- assert_nothing_raised { Owner.validates_size_of :pets, :within => 1..2 }
- o = Owner.new('name' => 'nopets')
- assert !o.save
- assert o.errors[:pets].any?
-
- o.pets.build('name' => 'apet')
- assert o.valid?
-
- 2.times { o.pets.build('name' => 'apet') }
- assert !o.save
- assert o.errors[:pets].any?
- end
+ assert_nothing_raised { @owner.validates_size_of :pets, within: 1..2 }
+ o = @owner.new('name' => 'nopets')
+ assert !o.save
+ assert o.errors[:pets].any?
+
+ o.pets.build('name' => 'apet')
+ assert o.valid?
+
+ 2.times { o.pets.build('name' => 'apet') }
+ assert !o.save
+ assert o.errors[:pets].any?
end
def test_validates_size_of_association_utf8
- repair_validations Owner do
- assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 }
- o = Owner.new('name' => 'あいうえおかきくけこ')
- assert !o.save
- assert o.errors[:pets].any?
- o.pets.build('name' => 'あいうえおかきくけこ')
- assert o.valid?
- end
+ @owner.validates_size_of :pets, minimum: 1
+ o = @owner.new('name' => 'あいうえおかきくけこ')
+ assert !o.save
+ assert o.errors[:pets].any?
+ o.pets.build('name' => 'あいうえおかきくけこ')
+ assert o.valid?
+ end
+
+ def test_validates_size_of_respects_records_marked_for_destruction
+ @owner.validates_size_of :pets, minimum: 1
+ owner = @owner.new
+ assert_not owner.save
+ assert owner.errors[:pets].any?
+ pet = owner.pets.build
+ assert owner.valid?
+ assert owner.save
+
+ pet_count = Pet.count
+ assert_not owner.update_attributes pets_attributes: [ {_destroy: 1, id: pet.id} ]
+ assert_not owner.valid?
+ assert owner.errors[:pets].any?
+ assert_equal pet_count, Pet.count
+ end
+
+ def test_does_not_validate_length_of_if_parent_record_is_validate_false
+ @owner.validates_length_of :name, minimum: 1
+ owner = @owner.new
+ owner.save!(validate: false)
+ assert owner.persisted?
+
+ pet = Pet.new(owner_id: owner.id)
+ pet.save!
+
+ assert_equal owner.pets.size, 1
+ assert owner.valid?
+ assert pet.valid?
end
end
diff --git a/activerecord/test/cases/validations/presence_validation_test.rb b/activerecord/test/cases/validations/presence_validation_test.rb
index 4f38849131..6f8ad06ab6 100644
--- a/activerecord/test/cases/validations/presence_validation_test.rb
+++ b/activerecord/test/cases/validations/presence_validation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require "cases/helper"
require 'models/man'
require 'models/face'
@@ -65,4 +64,20 @@ class PresenceValidationTest < ActiveRecord::TestCase
assert_nothing_raised { s.valid? }
end
+
+ def test_does_not_validate_presence_of_if_parent_record_is_validate_false
+ repair_validations(Interest) do
+ Interest.validates_presence_of(:topic)
+ interest = Interest.new
+ 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 18221cc73d..2608c84be2 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require "cases/helper"
require 'models/topic'
require 'models/reply'
@@ -30,18 +29,28 @@ class ReplyWithTitleObject < Reply
def title; ReplyTitle.new; end
end
-class Employee < ActiveRecord::Base
- self.table_name = 'postgresql_arrays'
- validates_uniqueness_of :nicknames
-end
-
class TopicWithUniqEvent < Topic
belongs_to :event, foreign_key: :parent_id
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
- fixtures :topics, 'warehouse-things', :developers
+ INT_MAX_VALUE = 2147483647
+
+ fixtures :topics, 'warehouse-things'
repair_validations(Topic, Reply)
@@ -92,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)
@@ -378,18 +397,6 @@ class UniquenessValidationTest < ActiveRecord::TestCase
}
end
- if current_adapter? :PostgreSQLAdapter
- def test_validate_uniqueness_with_array_column
- e1 = Employee.create("nicknames" => ["john", "johnny"], "commission_by_quarter" => [1000, 1200])
- assert e1.persisted?, "Saving e1"
-
- e2 = Employee.create("nicknames" => ["john", "johnny"], "commission_by_quarter" => [2200])
- assert !e2.persisted?, "e2 shouldn't be valid"
- assert e2.errors[:nicknames].any?, "Should have errors for nicknames"
- assert_equal ["has already been taken"], e2.errors[:nicknames], "Should have uniqueness message for nicknames"
- end
- end
-
def test_validate_uniqueness_on_existing_relation
event = Event.create
assert TopicWithUniqEvent.create(event: event).valid?
@@ -403,4 +410,21 @@ class UniquenessValidationTest < ActiveRecord::TestCase
topic = TopicWithUniqEvent.new
assert topic.valid?
end
+
+ def test_does_not_validate_uniqueness_of_if_parent_record_is_validate_false
+ Reply.validates_uniqueness_of(:content)
+
+ Reply.create!(content: "Topic Title")
+
+ reply = Reply.new(content: "Topic Title")
+ reply.save!(validate: false)
+ assert reply.persisted?
+
+ topic = Topic.new(reply_ids: [reply.id])
+ topic.save!
+
+ assert_equal topic.replies.size, 1
+ assert reply.valid?
+ assert topic.valid?
+ end
end
diff --git a/activerecord/test/cases/validations_repair_helper.rb b/activerecord/test/cases/validations_repair_helper.rb
index 2bbf0f23b3..b30666d876 100644
--- a/activerecord/test/cases/validations_repair_helper.rb
+++ b/activerecord/test/cases/validations_repair_helper.rb
@@ -5,9 +5,7 @@ module ActiveRecord
module ClassMethods
def repair_validations(*model_classes)
teardown do
- model_classes.each do |k|
- k.clear_validators!
- end
+ model_classes.each(&:clear_validators!)
end
end
end
@@ -15,9 +13,7 @@ module ActiveRecord
def repair_validations(*model_classes)
yield if block_given?
ensure
- model_classes.each do |k|
- k.clear_validators!
- end
+ model_classes.each(&:clear_validators!)
end
end
end
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index 55804f9576..f4f316f393 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -1,9 +1,9 @@
-# encoding: utf-8
require "cases/helper"
require 'models/topic'
require 'models/reply'
require 'models/person'
require 'models/developer'
+require 'models/computer'
require 'models/parrot'
require 'models/company'
@@ -148,4 +148,17 @@ class ValidationsTest < ActiveRecord::TestCase
assert_equal 1, Company.validators_on(:name).size
end
+ def test_numericality_validation_with_mutation
+ Topic.class_eval do
+ attribute :wibble, :string
+ validates_numericality_of :wibble, only_integer: true
+ end
+
+ topic = Topic.new(wibble: '123-4567')
+ topic.wibble.gsub!('-', '')
+
+ assert topic.valid?
+ ensure
+ Topic.reset_column_information
+ end
end
diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb
index c34e7d5a30..b30b50f597 100644
--- a/activerecord/test/cases/xml_serialization_test.rb
+++ b/activerecord/test/cases/xml_serialization_test.rb
@@ -19,7 +19,7 @@ class XmlSerializationTest < ActiveRecord::TestCase
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 xmlns="http://xml\.rubyonrails\.org/contact">}, @xml
assert_match %r{</contact>$}, @xml
end
diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb
index bce59b4fcd..56909a8630 100644
--- a/activerecord/test/cases/yaml_serialization_test.rb
+++ b/activerecord/test/cases/yaml_serialization_test.rb
@@ -83,4 +83,39 @@ class YamlSerializationTest < ActiveRecord::TestCase
assert_equal 5, author.posts_count
assert_equal 5, dumped.posts_count
end
+
+ def test_a_yaml_version_is_provided_for_future_backwards_compat
+ coder = {}
+ Topic.first.encode_with(coder)
+
+ assert coder['active_record_yaml_version']
+ end
+
+ def test_deserializing_rails_41_yaml
+ topic = YAML.load(yaml_fixture("rails_4_1"))
+
+ assert topic.new_record?
+ assert_equal nil, topic.id
+ assert_equal "The First Topic", topic.title
+ assert_equal({ omg: :lol }, topic.content)
+ end
+
+ def test_deserializing_rails_4_2_0_yaml
+ topic = YAML.load(yaml_fixture("rails_4_2_0"))
+
+ assert_not topic.new_record?
+ assert_equal 1, topic.id
+ assert_equal "The First Topic", topic.title
+ assert_equal("Have a nice day", topic.content)
+ end
+
+ private
+
+ def yaml_fixture(file_name)
+ path = File.expand_path(
+ "../../support/yaml_compatibility_fixtures/#{file_name}.yml",
+ __FILE__
+ )
+ File.read(path)
+ end
end
diff --git a/activerecord/test/config.example.yml b/activerecord/test/config.example.yml
index ce30cff9e7..e3b55d640e 100644
--- a/activerecord/test/config.example.yml
+++ b/activerecord/test/config.example.yml
@@ -69,12 +69,6 @@ connections:
username: rails
encoding: utf8
- openbase:
- arunit:
- username: admin
- arunit2:
- username: admin
-
oracle:
arunit:
adapter: oracle_enhanced
diff --git a/activerecord/test/fixtures/bulbs.yml b/activerecord/test/fixtures/bulbs.yml
new file mode 100644
index 0000000000..e5ce2b796c
--- /dev/null
+++ b/activerecord/test/fixtures/bulbs.yml
@@ -0,0 +1,5 @@
+defaulty:
+ name: defaulty
+
+special:
+ name: special
diff --git a/activerecord/test/fixtures/computers.yml b/activerecord/test/fixtures/computers.yml
index 7281a4d768..ad5ae2ec71 100644
--- a/activerecord/test/fixtures/computers.yml
+++ b/activerecord/test/fixtures/computers.yml
@@ -3,3 +3,8 @@ workstation:
system: 'Linux'
developer: 1
extendedWarranty: 1
+
+laptop:
+ system: 'MacOS 1'
+ developer: 1
+ extendedWarranty: 1
diff --git a/activerecord/test/fixtures/dead_parrots.yml b/activerecord/test/fixtures/dead_parrots.yml
new file mode 100644
index 0000000000..da5529dd27
--- /dev/null
+++ b/activerecord/test/fixtures/dead_parrots.yml
@@ -0,0 +1,5 @@
+deadbird:
+ name: "Dusty DeadBird"
+ treasures: [ruby, sapphire]
+ parrot_sti_class: DeadParrot
+ killer: blackbeard
diff --git a/activerecord/test/fixtures/developers.yml b/activerecord/test/fixtures/developers.yml
index 1a74563dc6..54b74e7a74 100644
--- a/activerecord/test/fixtures/developers.yml
+++ b/activerecord/test/fixtures/developers.yml
@@ -2,6 +2,7 @@ david:
id: 1
name: David
salary: 80000
+ shared_computers: laptop
jamis:
id: 2
diff --git a/activerecord/test/fixtures/live_parrots.yml b/activerecord/test/fixtures/live_parrots.yml
new file mode 100644
index 0000000000..95b2078da7
--- /dev/null
+++ b/activerecord/test/fixtures/live_parrots.yml
@@ -0,0 +1,4 @@
+dusty:
+ name: "Dusty Bluebird"
+ treasures: [ruby, sapphire]
+ parrot_sti_class: LiveParrot
diff --git a/activerecord/test/fixtures/naked/csv/accounts.csv b/activerecord/test/fixtures/naked/csv/accounts.csv
deleted file mode 100644
index 8b13789179..0000000000
--- a/activerecord/test/fixtures/naked/csv/accounts.csv
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/activerecord/test/fixtures/pirates.yml b/activerecord/test/fixtures/pirates.yml
index 1bb3bf0051..0b1a785853 100644
--- a/activerecord/test/fixtures/pirates.yml
+++ b/activerecord/test/fixtures/pirates.yml
@@ -10,3 +10,6 @@ redbeard:
mark:
catchphrase: "X $LABELs the spot!"
+
+1:
+ catchphrase: "#$LABEL pirate!"
diff --git a/activerecord/test/migrations/missing/1000_people_have_middle_names.rb b/activerecord/test/migrations/missing/1000_people_have_middle_names.rb
index 9fd495b97c..4b83d61beb 100644
--- a/activerecord/test/migrations/missing/1000_people_have_middle_names.rb
+++ b/activerecord/test/migrations/missing/1000_people_have_middle_names.rb
@@ -6,4 +6,4 @@ class PeopleHaveMiddleNames < ActiveRecord::Migration
def self.down
remove_column "people", "middle_name"
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/migrations/missing/1_people_have_last_names.rb b/activerecord/test/migrations/missing/1_people_have_last_names.rb
index 81af5fef5e..68209f3ce9 100644
--- a/activerecord/test/migrations/missing/1_people_have_last_names.rb
+++ b/activerecord/test/migrations/missing/1_people_have_last_names.rb
@@ -6,4 +6,4 @@ class PeopleHaveLastNames < ActiveRecord::Migration
def self.down
remove_column "people", "last_name"
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/migrations/missing/3_we_need_reminders.rb b/activerecord/test/migrations/missing/3_we_need_reminders.rb
index d5e71ce8ef..25bb49cb32 100644
--- a/activerecord/test/migrations/missing/3_we_need_reminders.rb
+++ b/activerecord/test/migrations/missing/3_we_need_reminders.rb
@@ -9,4 +9,4 @@ class WeNeedReminders < ActiveRecord::Migration
def self.down
drop_table "reminders"
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/migrations/missing/4_innocent_jointable.rb b/activerecord/test/migrations/missing/4_innocent_jointable.rb
index 21c9ca5328..002a1bf2a6 100644
--- a/activerecord/test/migrations/missing/4_innocent_jointable.rb
+++ b/activerecord/test/migrations/missing/4_innocent_jointable.rb
@@ -9,4 +9,4 @@ class InnocentJointable < ActiveRecord::Migration
def self.down
drop_table "people_reminders"
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/migrations/rename/1_we_need_things.rb b/activerecord/test/migrations/rename/1_we_need_things.rb
index cdbe0b1679..f5484ac54f 100644
--- a/activerecord/test/migrations/rename/1_we_need_things.rb
+++ b/activerecord/test/migrations/rename/1_we_need_things.rb
@@ -8,4 +8,4 @@ class WeNeedThings < ActiveRecord::Migration
def self.down
drop_table "things"
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/migrations/rename/2_rename_things.rb b/activerecord/test/migrations/rename/2_rename_things.rb
index d441b71fc9..533a113ea8 100644
--- a/activerecord/test/migrations/rename/2_rename_things.rb
+++ b/activerecord/test/migrations/rename/2_rename_things.rb
@@ -6,4 +6,4 @@ class RenameThings < ActiveRecord::Migration
def self.down
rename_table "awesome_things", "things"
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/migrations/valid/2_we_need_reminders.rb b/activerecord/test/migrations/valid/2_we_need_reminders.rb
index d5e71ce8ef..25bb49cb32 100644
--- a/activerecord/test/migrations/valid/2_we_need_reminders.rb
+++ b/activerecord/test/migrations/valid/2_we_need_reminders.rb
@@ -9,4 +9,4 @@ class WeNeedReminders < ActiveRecord::Migration
def self.down
drop_table "reminders"
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/migrations/valid/3_innocent_jointable.rb b/activerecord/test/migrations/valid/3_innocent_jointable.rb
index 21c9ca5328..002a1bf2a6 100644
--- a/activerecord/test/migrations/valid/3_innocent_jointable.rb
+++ b/activerecord/test/migrations/valid/3_innocent_jointable.rb
@@ -9,4 +9,4 @@ class InnocentJointable < ActiveRecord::Migration
def self.down
drop_table "people_reminders"
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/migrations/valid_with_subdirectories/sub/2_we_need_reminders.rb b/activerecord/test/migrations/valid_with_subdirectories/sub/2_we_need_reminders.rb
index d5e71ce8ef..25bb49cb32 100644
--- a/activerecord/test/migrations/valid_with_subdirectories/sub/2_we_need_reminders.rb
+++ b/activerecord/test/migrations/valid_with_subdirectories/sub/2_we_need_reminders.rb
@@ -9,4 +9,4 @@ class WeNeedReminders < ActiveRecord::Migration
def self.down
drop_table "reminders"
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/migrations/valid_with_subdirectories/sub1/3_innocent_jointable.rb b/activerecord/test/migrations/valid_with_subdirectories/sub1/3_innocent_jointable.rb
index 21c9ca5328..002a1bf2a6 100644
--- a/activerecord/test/migrations/valid_with_subdirectories/sub1/3_innocent_jointable.rb
+++ b/activerecord/test/migrations/valid_with_subdirectories/sub1/3_innocent_jointable.rb
@@ -9,4 +9,4 @@ class InnocentJointable < ActiveRecord::Migration
def self.down
drop_table "people_reminders"
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/models/admin.rb b/activerecord/test/models/admin.rb
index 00e69fbed8..a38e3f4846 100644
--- a/activerecord/test/models/admin.rb
+++ b/activerecord/test/models/admin.rb
@@ -2,4 +2,4 @@ module Admin
def self.table_name_prefix
'admin_'
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/models/admin/account.rb b/activerecord/test/models/admin/account.rb
index 46de28aae1..bd23192d20 100644
--- a/activerecord/test/models/admin/account.rb
+++ b/activerecord/test/models/admin/account.rb
@@ -1,3 +1,3 @@
class Admin::Account < ActiveRecord::Base
has_many :users
-end \ No newline at end of file
+end
diff --git a/activerecord/test/models/admin/randomly_named_c1.rb b/activerecord/test/models/admin/randomly_named_c1.rb
index 2f81d5b831..b64ae7fc41 100644
--- a/activerecord/test/models/admin/randomly_named_c1.rb
+++ b/activerecord/test/models/admin/randomly_named_c1.rb
@@ -1,3 +1,7 @@
-class Admin::ClassNameThatDoesNotFollowCONVENTIONS < ActiveRecord::Base
- self.table_name = :randomly_named_table
+class Admin::ClassNameThatDoesNotFollowCONVENTIONS1 < ActiveRecord::Base
+ self.table_name = :randomly_named_table2
+end
+
+class Admin::ClassNameThatDoesNotFollowCONVENTIONS2 < ActiveRecord::Base
+ self.table_name = :randomly_named_table3
end
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index 3da3a1fd59..8c1f14bd36 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -50,9 +50,9 @@ class Author < ActiveRecord::Base
has_many :sti_posts, :class_name => 'StiPost'
has_many :sti_post_comments, :through => :sti_posts, :source => :comments
- has_many :special_nonexistant_posts, -> { where("posts.body = 'nonexistant'") }, :class_name => "SpecialPost"
- has_many :special_nonexistant_post_comments, -> { where('comments.post_id' => 0) }, :through => :special_nonexistant_posts, :source => :comments
- has_many :nonexistant_comments, :through => :posts
+ has_many :special_nonexistent_posts, -> { where("posts.body = 'nonexistent'") }, :class_name => "SpecialPost"
+ has_many :special_nonexistent_post_comments, -> { where('comments.post_id' => 0) }, :through => :special_nonexistent_posts, :source => :comments
+ has_many :nonexistent_comments, :through => :posts
has_many :hello_posts, -> { where "posts.body = 'hello'" }, :class_name => "Post"
has_many :hello_post_comments, :through => :hello_posts, :source => :comments
diff --git a/activerecord/test/models/binary.rb b/activerecord/test/models/binary.rb
index 950c459199..39b2f5090a 100644
--- a/activerecord/test/models/binary.rb
+++ b/activerecord/test/models/binary.rb
@@ -1,2 +1,2 @@
class Binary < ActiveRecord::Base
-end \ No newline at end of file
+end
diff --git a/activerecord/test/models/bird.rb b/activerecord/test/models/bird.rb
index dff099c1fb..2a51d903b8 100644
--- a/activerecord/test/models/bird.rb
+++ b/activerecord/test/models/bird.rb
@@ -7,6 +7,6 @@ class Bird < ActiveRecord::Base
attr_accessor :cancel_save_from_callback
before_save :cancel_save_callback_method, :if => :cancel_save_from_callback
def cancel_save_callback_method
- false
+ throw(:abort)
end
end
diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb
index 831a0d5387..a6e83fe353 100644
--- a/activerecord/test/models/bulb.rb
+++ b/activerecord/test/models/bulb.rb
@@ -46,6 +46,6 @@ end
class FailedBulb < Bulb
before_destroy do
- false
+ throw(:abort)
end
end
diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb
index db0f93f63b..81263b79d1 100644
--- a/activerecord/test/models/car.rb
+++ b/activerecord/test/models/car.rb
@@ -8,7 +8,7 @@ class Car < ActiveRecord::Base
has_one :bulb
has_many :tyres
- has_many :engines, :dependent => :destroy
+ has_many :engines, :dependent => :destroy, inverse_of: :my_car
has_many :wheels, :as => :wheelable, :dependent => :destroy
scope :incl_tyres, -> { includes(:tyres) }
diff --git a/activerecord/test/models/chef.rb b/activerecord/test/models/chef.rb
index 67a4e54f06..698a52e045 100644
--- a/activerecord/test/models/chef.rb
+++ b/activerecord/test/models/chef.rb
@@ -1,3 +1,4 @@
class Chef < ActiveRecord::Base
belongs_to :employable, polymorphic: true
+ has_many :recipes
end
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index 42f7fb4680..6961f8fd6f 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -72,6 +72,7 @@ class Firm < Company
# Oracle tests were failing because of that as the second fixture was selected
has_one :account_using_primary_key, -> { order('id') }, :primary_key => "firm_id", :class_name => "Account"
has_one :account_using_foreign_and_primary_keys, :foreign_key => "firm_name", :primary_key => "name", :class_name => "Account"
+ has_one :account_with_inexistent_foreign_key, class_name: 'Account', foreign_key: "inexistent"
has_one :deletable_account, :foreign_key => "firm_id", :class_name => "Account", :dependent => :delete
has_one :account_limit_500_with_hash_conditions, -> { where :credit_limit => 500 }, :foreign_key => "firm_id", :class_name => "Account"
@@ -213,7 +214,7 @@ class Account < ActiveRecord::Base
protected
def check_empty_credit_limit
- errors.add_on_empty "credit_limit"
+ errors.add("credit_limit", :blank) if credit_limit.blank?
end
private
diff --git a/activerecord/test/models/company_in_module.rb b/activerecord/test/models/company_in_module.rb
index dae102d12b..bf0a0d1c3e 100644
--- a/activerecord/test/models/company_in_module.rb
+++ b/activerecord/test/models/company_in_module.rb
@@ -91,7 +91,7 @@ module MyApplication
protected
def check_empty_credit_limit
- errors.add_on_empty "credit_limit"
+ errors.add("credit_card", :blank) if credit_card.blank?
end
end
end
diff --git a/activerecord/test/models/customer.rb b/activerecord/test/models/customer.rb
index 7e8e82542f..afe4b3d707 100644
--- a/activerecord/test/models/customer.rb
+++ b/activerecord/test/models/customer.rb
@@ -2,7 +2,7 @@ class Customer < ActiveRecord::Base
cattr_accessor :gps_conversion_was_run
composed_of :address, :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ], :allow_nil => true
- composed_of :balance, :class_name => "Money", :mapping => %w(balance amount), :converter => Proc.new { |balance| balance.to_money }
+ composed_of :balance, :class_name => "Money", :mapping => %w(balance amount), :converter => Proc.new(&:to_money)
composed_of :gps_location, :allow_nil => true
composed_of :non_blank_gps_location, :class_name => "GpsLocation", :allow_nil => true, :mapping => %w(gps_location gps_location),
:converter => lambda { |gps| self.gps_conversion_was_run = true; gps.blank? ? nil : GpsLocation.new(gps)}
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index 3627cfdd09..d2a5a7fc49 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -15,6 +15,8 @@ class Developer < ActiveRecord::Base
accepts_nested_attributes_for :projects
+ has_and_belongs_to_many :shared_computers, class_name: "Computer"
+
has_and_belongs_to_many :projects_extended_by_name,
-> { extending(DeveloperProjectsAssociationExtension) },
:class_name => "Project",
diff --git a/activerecord/test/models/event.rb b/activerecord/test/models/event.rb
index 99fa0feeb7..365ab32b0b 100644
--- a/activerecord/test/models/event.rb
+++ b/activerecord/test/models/event.rb
@@ -1,3 +1,3 @@
class Event < ActiveRecord::Base
validates_uniqueness_of :title
-end \ No newline at end of file
+end
diff --git a/activerecord/test/models/guid.rb b/activerecord/test/models/guid.rb
index 9208dc28fa..05653ba498 100644
--- a/activerecord/test/models/guid.rb
+++ b/activerecord/test/models/guid.rb
@@ -1,2 +1,2 @@
class Guid < ActiveRecord::Base
-end \ No newline at end of file
+end
diff --git a/activerecord/test/models/hotel.rb b/activerecord/test/models/hotel.rb
index b352cd22f3..491f8dfde3 100644
--- a/activerecord/test/models/hotel.rb
+++ b/activerecord/test/models/hotel.rb
@@ -3,4 +3,5 @@ class Hotel < ActiveRecord::Base
has_many :chefs, through: :departments
has_many :cake_designers, source_type: 'CakeDesigner', source: :employable, through: :chefs
has_many :drink_designers, source_type: 'DrinkDesigner', source: :employable, through: :chefs
+ has_many :recipes, through: :chefs
end
diff --git a/activerecord/test/models/image.rb b/activerecord/test/models/image.rb
new file mode 100644
index 0000000000..7ae8e4a7f6
--- /dev/null
+++ b/activerecord/test/models/image.rb
@@ -0,0 +1,3 @@
+class Image < ActiveRecord::Base
+ belongs_to :imageable, foreign_key: :imageable_identifier, foreign_type: :imageable_class
+end
diff --git a/activerecord/test/models/notification.rb b/activerecord/test/models/notification.rb
new file mode 100644
index 0000000000..b4b4b8f1b6
--- /dev/null
+++ b/activerecord/test/models/notification.rb
@@ -0,0 +1,2 @@
+class Notification < ActiveRecord::Base
+end
diff --git a/activerecord/test/models/organization.rb b/activerecord/test/models/organization.rb
index 72e7bade68..f3e92f3067 100644
--- a/activerecord/test/models/organization.rb
+++ b/activerecord/test/models/organization.rb
@@ -8,5 +8,7 @@ class Organization < ActiveRecord::Base
has_one :author, :primary_key => :name
has_one :author_owned_essay_category, :through => :author, :source => :owned_essay_category
+ has_many :posts, :through => :author, :source => :posts
+
scope :clubs, -> { from('clubs') }
end
diff --git a/activerecord/test/models/owner.rb b/activerecord/test/models/owner.rb
index 2e3a9a3681..cedb774b10 100644
--- a/activerecord/test/models/owner.rb
+++ b/activerecord/test/models/owner.rb
@@ -17,6 +17,8 @@ class Owner < ActiveRecord::Base
after_commit :execute_blocks
+ accepts_nested_attributes_for :pets, allow_destroy: true
+
def blocks
@blocks ||= []
end
diff --git a/activerecord/test/models/parrot.rb b/activerecord/test/models/parrot.rb
index 8c83de573f..b26035d944 100644
--- a/activerecord/test/models/parrot.rb
+++ b/activerecord/test/models/parrot.rb
@@ -11,7 +11,7 @@ class Parrot < ActiveRecord::Base
attr_accessor :cancel_save_from_callback
before_save :cancel_save_callback_method, :if => :cancel_save_from_callback
def cancel_save_callback_method
- false
+ throw(:abort)
end
end
diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb
index 90a3c3ecee..30545bdcd7 100644
--- a/activerecord/test/models/pirate.rb
+++ b/activerecord/test/models/pirate.rb
@@ -36,8 +36,8 @@ class Pirate < ActiveRecord::Base
has_one :foo_bulb, -> { where :name => 'foo' }, :foreign_key => :car_id, :class_name => "Bulb"
- accepts_nested_attributes_for :parrots, :birds, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
- accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
+ accepts_nested_attributes_for :parrots, :birds, :allow_destroy => true, :reject_if => proc(&:empty?)
+ accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc(&:empty?)
accepts_nested_attributes_for :update_only_ship, :update_only => true
accepts_nested_attributes_for :parrots_with_method_callbacks, :parrots_with_proc_callbacks,
:birds_with_method_callbacks, :birds_with_proc_callbacks, :allow_destroy => true
@@ -56,7 +56,7 @@ class Pirate < ActiveRecord::Base
attr_accessor :cancel_save_from_callback, :parrots_limit
before_save :cancel_save_callback_method, :if => :cancel_save_from_callback
def cancel_save_callback_method
- false
+ throw(:abort)
end
private
@@ -89,4 +89,4 @@ class FamousPirate < ActiveRecord::Base
self.table_name = 'pirates'
has_many :famous_ships
validates_presence_of :catchphrase, on: :conference
-end \ No newline at end of file
+end
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 36cf221d45..052b1c9690 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -18,6 +18,7 @@ class Post < ActiveRecord::Base
end
scope :containing_the_letter_a, -> { where("body LIKE '%a%'") }
+ scope :titled_with_an_apostrophe, -> { where("title LIKE '%''%'") }
scope :ranked_by_comments, -> { order("comments_count DESC") }
scope :limit_by, lambda {|l| limit(l) }
@@ -43,6 +44,8 @@ class Post < ActiveRecord::Base
scope :tagged_with, ->(id) { joins(:taggings).where(taggings: { tag_id: id }) }
scope :tagged_with_comment, ->(comment) { joins(:taggings).where(taggings: { comment: comment }) }
+ scope :typographically_interesting, -> { containing_the_letter_a.or(titled_with_an_apostrophe) }
+
has_many :comments do
def find_most_recent
order("id DESC").first
@@ -72,14 +75,11 @@ class Post < ActiveRecord::Base
through: :author_with_address,
source: :author_address_extra
- has_many :comments_with_interpolated_conditions,
- ->(p) { where "#{"#{p.aliased_table_name}." rescue ""}body = ?", 'Thank you for the welcome' },
- :class_name => 'Comment'
-
has_one :very_special_comment
has_one :very_special_comment_with_post, -> { includes(:post) }, :class_name => "VerySpecialComment"
+ has_one :very_special_comment_with_post_with_joins, -> { joins(:post).order('posts.id') }, class_name: "VerySpecialComment"
has_many :special_comments
- has_many :nonexistant_comments, -> { where 'comments.id < 0' }, :class_name => 'Comment'
+ has_many :nonexistent_comments, -> { where 'comments.id < 0' }, :class_name => 'Comment'
has_many :special_comments_ratings, :through => :special_comments, :source => :ratings
has_many :special_comments_ratings_taggings, :through => :special_comments_ratings, :source => :taggings
@@ -127,6 +127,9 @@ class Post < ActiveRecord::Base
has_many :taggings_using_author_id, :primary_key => :author_id, :as => :taggable, :class_name => 'Tagging'
has_many :tags_using_author_id, :through => :taggings_using_author_id, :source => :tag
+ has_many :images, :as => :imageable, :foreign_key => :imageable_identifier, :foreign_type => :imageable_class
+ has_one :main_image, :as => :imageable, :foreign_key => :imageable_identifier, :foreign_type => :imageable_class, :class_name => 'Image'
+
has_many :standard_categorizations, :class_name => 'Categorization', :foreign_key => :post_id
has_many :author_using_custom_pk, :through => :standard_categorizations
has_many :authors_using_custom_pk, :through => :standard_categorizations
diff --git a/activerecord/test/models/randomly_named_c1.rb b/activerecord/test/models/randomly_named_c1.rb
index 18a86c4989..d4be1e13b4 100644
--- a/activerecord/test/models/randomly_named_c1.rb
+++ b/activerecord/test/models/randomly_named_c1.rb
@@ -1,3 +1,3 @@
class ClassNameThatDoesNotFollowCONVENTIONS < ActiveRecord::Base
- self.table_name = :randomly_named_table
+ self.table_name = :randomly_named_table1
end
diff --git a/activerecord/test/models/recipe.rb b/activerecord/test/models/recipe.rb
new file mode 100644
index 0000000000..c387230603
--- /dev/null
+++ b/activerecord/test/models/recipe.rb
@@ -0,0 +1,3 @@
+class Recipe < ActiveRecord::Base
+ belongs_to :chef
+end
diff --git a/activerecord/test/models/ship.rb b/activerecord/test/models/ship.rb
index 77a4728d0b..312caef604 100644
--- a/activerecord/test/models/ship.rb
+++ b/activerecord/test/models/ship.rb
@@ -4,9 +4,10 @@ class Ship < ActiveRecord::Base
belongs_to :pirate
belongs_to :update_only_pirate, :class_name => 'Pirate'
has_many :parts, :class_name => 'ShipPart'
+ has_many :treasures
accepts_nested_attributes_for :parts, :allow_destroy => true
- accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
+ accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc(&:empty?)
accepts_nested_attributes_for :update_only_pirate, :update_only => true
validates_presence_of :name
@@ -14,7 +15,7 @@ class Ship < ActiveRecord::Base
attr_accessor :cancel_save_from_callback
before_save :cancel_save_callback_method, :if => :cancel_save_from_callback
def cancel_save_callback_method
- false
+ throw(:abort)
end
end
diff --git a/activerecord/test/models/ship_part.rb b/activerecord/test/models/ship_part.rb
index b6a8a506b4..05c65f8a4a 100644
--- a/activerecord/test/models/ship_part.rb
+++ b/activerecord/test/models/ship_part.rb
@@ -2,6 +2,7 @@ class ShipPart < ActiveRecord::Base
belongs_to :ship
has_many :trinkets, :class_name => "Treasure", :as => :looter
accepts_nested_attributes_for :trinkets, :allow_destroy => true
+ accepts_nested_attributes_for :ship
validates_presence_of :name
-end \ No newline at end of file
+end
diff --git a/activerecord/test/models/treasure.rb b/activerecord/test/models/treasure.rb
index a69d3fd3df..ffc65466d5 100644
--- a/activerecord/test/models/treasure.rb
+++ b/activerecord/test/models/treasure.rb
@@ -1,6 +1,7 @@
class Treasure < ActiveRecord::Base
has_and_belongs_to_many :parrots
belongs_to :looter, :polymorphic => true
+ belongs_to :ship
has_many :price_estimates, :as => :estimate_of
has_and_belongs_to_many :rich_people, join_table: 'peoples_treasures', validate: false
diff --git a/activerecord/test/models/tyre.rb b/activerecord/test/models/tyre.rb
index bc3444aa7d..e50a21ca68 100644
--- a/activerecord/test/models/tyre.rb
+++ b/activerecord/test/models/tyre.rb
@@ -1,3 +1,11 @@
class Tyre < ActiveRecord::Base
belongs_to :car
+
+ def self.custom_find(id)
+ find(id)
+ end
+
+ def self.custom_find_by(*args)
+ find_by(*args)
+ end
end
diff --git a/activerecord/test/models/user.rb b/activerecord/test/models/user.rb
new file mode 100644
index 0000000000..f5dc93e994
--- /dev/null
+++ b/activerecord/test/models/user.rb
@@ -0,0 +1,8 @@
+class User < ActiveRecord::Base
+ has_secure_token
+ has_secure_token :auth_token
+end
+
+class UserWithNotification < User
+ after_create -> { Notification.create! message: "A new user has been created." }
+end
diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb
index a9a6514c9d..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,20 +40,7 @@ BEGIN
END
SQL
- ActiveRecord::Base.connection.execute <<-SQL
-DROP TABLE IF EXISTS collation_tests;
-SQL
-
- 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.execute <<-SQL
-DROP TABLE IF EXISTS enum_tests;
-SQL
+ ActiveRecord::Base.connection.drop_table "enum_tests", if_exists: true
ActiveRecord::Base.connection.execute <<-SQL
CREATE TABLE enum_tests (
diff --git a/activerecord/test/schema/mysql_specific_schema.rb b/activerecord/test/schema/mysql_specific_schema.rb
index f2cffca52c..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,20 +51,7 @@ BEGIN
END
SQL
- ActiveRecord::Base.connection.execute <<-SQL
-DROP TABLE IF EXISTS collation_tests;
-SQL
-
- 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.execute <<-SQL
-DROP TABLE IF EXISTS enum_tests;
-SQL
+ ActiveRecord::Base.connection.drop_table "enum_tests", if_exists: true
ActiveRecord::Base.connection.execute <<-SQL
CREATE TABLE enum_tests (
diff --git a/activerecord/test/schema/oracle_specific_schema.rb b/activerecord/test/schema/oracle_specific_schema.rb
index a7817772f4..264d9b8910 100644
--- a/activerecord/test/schema/oracle_specific_schema.rb
+++ b/activerecord/test/schema/oracle_specific_schema.rb
@@ -32,10 +32,7 @@ create sequence test_oracle_defaults_seq minvalue 10000
fixed_time date default TO_DATE('2004-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'),
char1 varchar2(1) default 'Y',
char2 varchar2(50) default 'a varchar field',
- char3 clob default 'a text field',
- positive_integer integer default 1,
- negative_integer integer default -1,
- decimal_number number(3,2) default 2.78
+ char3 clob default 'a text field'
)
SQL
execute "create sequence defaults_seq minvalue 10000"
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index 7c3b170c08..008503bc24 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -1,10 +1,8 @@
ActiveRecord::Schema.define do
- %w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times
- postgresql_network_addresses postgresql_uuids postgresql_ltrees postgresql_oids postgresql_xml_data_type defaults
- geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent
- postgresql_citext).each do |table_name|
- execute "DROP TABLE IF EXISTS #{quote_table_name table_name}"
+ %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
end
execute 'DROP SEQUENCE IF EXISTS companies_nonstd_seq CASCADE'
@@ -14,8 +12,6 @@ ActiveRecord::Schema.define do
execute 'DROP FUNCTION IF EXISTS partitioned_insert_trigger()'
- execute "DROP SCHEMA IF EXISTS schema_1 CASCADE"
-
%w(accounts_id_seq developers_id_seq projects_id_seq topics_id_seq customers_id_seq orders_id_seq).each do |seq_name|
execute "SELECT setval('#{seq_name}', 100)"
end
@@ -32,92 +28,13 @@ ActiveRecord::Schema.define do
char1 char(1) default 'Y',
char2 character varying(50) default 'a varchar field',
char3 text default 'a text field',
- positive_integer integer default 1,
- negative_integer integer default -1,
bigint_default bigint default 0::bigint,
- decimal_number decimal(3,2) default 2.78,
multiline_default text DEFAULT '--- []
'::text
);
_SQL
- execute "CREATE SCHEMA schema_1"
- execute "CREATE DOMAIN schema_1.text AS text"
- execute "CREATE DOMAIN schema_1.varchar AS varchar"
- execute "CREATE DOMAIN schema_1.bpchar AS bpchar"
-
- execute <<_SQL
- CREATE TABLE geometrics (
- id serial primary key,
- a_point point,
- -- a_line line, (the line type is currently not implemented in postgresql)
- a_line_segment lseg,
- a_box box,
- a_path path,
- a_polygon polygon,
- a_circle circle
- );
-_SQL
-
- execute <<_SQL
- CREATE TABLE postgresql_arrays (
- id SERIAL PRIMARY KEY,
- commission_by_quarter INTEGER[],
- nicknames TEXT[]
- );
-_SQL
-
- execute <<_SQL
- CREATE TABLE postgresql_uuids (
- id SERIAL PRIMARY KEY,
- guid uuid,
- compact_guid uuid
- );
-_SQL
-
- execute <<_SQL
- CREATE TABLE postgresql_tsvectors (
- id SERIAL PRIMARY KEY,
- text_vector tsvector
- );
-_SQL
-
- if 't' == select_value("select 'hstore'=ANY(select typname from pg_type)")
- execute <<_SQL
- CREATE TABLE postgresql_hstores (
- id SERIAL PRIMARY KEY,
- hash_store hstore default ''::hstore
- );
-_SQL
- end
-
- if 't' == select_value("select 'ltree'=ANY(select typname from pg_type)")
- execute <<_SQL
- CREATE TABLE postgresql_ltrees (
- id SERIAL PRIMARY KEY,
- path ltree
- );
-_SQL
- end
-
- if 't' == select_value("select 'citext'=ANY(select typname from pg_type)")
- execute <<_SQL
- CREATE TABLE postgresql_citext (
- id SERIAL PRIMARY KEY,
- text_citext citext default ''::citext
- );
-_SQL
- end
-
- execute <<_SQL
- CREATE TABLE postgresql_numbers (
- id SERIAL PRIMARY KEY,
- single REAL,
- double DOUBLE PRECISION
- );
-_SQL
-
execute <<_SQL
CREATE TABLE postgresql_times (
id SERIAL PRIMARY KEY,
@@ -127,15 +44,6 @@ _SQL
_SQL
execute <<_SQL
- CREATE TABLE postgresql_network_addresses (
- id SERIAL PRIMARY KEY,
- cidr_address CIDR default '192.168.1.0/24',
- inet_address INET default '192.168.1.1',
- mac_address MACADDR default 'ff:ff:ff:ff:ff:ff'
- );
-_SQL
-
- execute <<_SQL
CREATE TABLE postgresql_oids (
id SERIAL PRIMARY KEY,
obj_id OID
@@ -180,19 +88,13 @@ _SQL
end
end
- begin
- execute <<_SQL
- CREATE TABLE postgresql_xml_data_type (
- id SERIAL PRIMARY KEY,
- data xml
- );
-_SQL
- rescue #This version of PostgreSQL either has no XML support or is was not compiled with XML support: skipping table
- end
-
# This table is to verify if the :limit option is being ignored for text and binary columns
create_table :limitless_fields, force: true do |t|
t.binary :binary, limit: 100_000
t.text :text, limit: 100_000
end
+
+ create_table :bigint_array, force: true do |t|
+ t.integer :big_int_data_points, limit: 8, array: true
+ end
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 539a0d2a49..7b42f8a4a5 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
ActiveRecord::Schema.define do
def except(adapter_names_to_exclude)
@@ -228,6 +227,11 @@ ActiveRecord::Schema.define do
t.integer :extendedWarranty, null: false
end
+ create_table :computers_developers, id: false, force: true do |t|
+ t.references :computer
+ t.references :developer
+ end
+
create_table :contracts, force: true do |t|
t.integer :developer_id
t.integer :company_id
@@ -464,6 +468,10 @@ ActiveRecord::Schema.define do
t.string :name
end
+ create_table :notifications, force: true do |t|
+ t.string :message
+ end
+
create_table :numeric_data, force: true do |t|
t.decimal :bank_balance, precision: 10, scale: 2
t.decimal :big_bank_balance, precision: 15, scale: 2
@@ -474,6 +482,8 @@ ActiveRecord::Schema.define do
# Oracle/SQLServer supports precision up to 38
if current_adapter?(:OracleAdapter, :SQLServerAdapter)
t.decimal :atoms_in_universe, precision: 38, scale: 0
+ elsif current_adapter?(:FbAdapter)
+ t.decimal :atoms_in_universe, precision: 18, scale: 0
else
t.decimal :atoms_in_universe, precision: 55, scale: 0
end
@@ -590,6 +600,11 @@ ActiveRecord::Schema.define do
t.string :title, null: false
end
+ create_table :images, force: true do |t|
+ t.integer :imageable_identifier
+ t.string :imageable_class
+ end
+
create_table :price_estimates, force: true do |t|
t.string :estimate_of_type
t.integer :estimate_of_id
@@ -611,7 +626,17 @@ ActiveRecord::Schema.define do
t.string :type
end
- create_table :randomly_named_table, force: true do |t|
+ create_table :randomly_named_table1, force: true do |t|
+ t.string :some_attribute
+ t.integer :another_attribute
+ end
+
+ create_table :randomly_named_table2, force: true do |t|
+ t.string :some_attribute
+ t.integer :another_attribute
+ end
+
+ create_table :randomly_named_table3, force: true do |t|
t.string :some_attribute
t.integer :another_attribute
end
@@ -655,6 +680,7 @@ ActiveRecord::Schema.define do
create_table :ship_parts, force: true do |t|
t.string :name
t.integer :ship_id
+ t.datetime :updated_at
end
create_table :speedometers, force: true, id: false do |t|
@@ -716,7 +742,7 @@ ActiveRecord::Schema.define do
t.string :author_name
t.string :author_email_address
if mysql_56?
- t.datetime :written_on, limit: 6
+ t.datetime :written_on, precision: 6
else
t.datetime :written_on
end
@@ -760,6 +786,7 @@ ActiveRecord::Schema.define do
t.column :type, :string
t.column :looter_id, :integer
t.column :looter_type, :string
+ t.belongs_to :ship
end
create_table :tyres, force: true do |t|
@@ -859,6 +886,10 @@ ActiveRecord::Schema.define do
t.string :employable_type
t.integer :department_id
end
+ create_table :recipes, force: true do |t|
+ t.integer :chef_id
+ t.integer :hotel_id
+ end
create_table :records, force: true do |t|
end
@@ -882,6 +913,11 @@ ActiveRecord::Schema.define do
t.string :overloaded_string_with_limit, limit: 255
t.string :string_with_default, default: 'the original default'
end
+
+ create_table :users, force: true do |t|
+ t.string :token
+ t.string :auth_token
+ end
end
Course.connection.create_table :courses, force: true do |t|
diff --git a/activerecord/test/support/yaml_compatibility_fixtures/rails_4_1.yml b/activerecord/test/support/yaml_compatibility_fixtures/rails_4_1.yml
new file mode 100644
index 0000000000..20b128db9c
--- /dev/null
+++ b/activerecord/test/support/yaml_compatibility_fixtures/rails_4_1.yml
@@ -0,0 +1,22 @@
+--- !ruby/object:Topic
+ attributes:
+ id:
+ title: The First Topic
+ author_name: David
+ author_email_address: david@loudthinking.com
+ written_on: 2003-07-16 14:28:11.223300000 Z
+ bonus_time: 2000-01-01 14:28:00.000000000 Z
+ last_read: 2004-04-15
+ content: |
+ ---
+ :omg: :lol
+ important:
+ approved: false
+ replies_count: 1
+ unique_replies_count: 0
+ parent_id:
+ parent_title:
+ type:
+ group:
+ created_at: 2015-03-10 17:05:42.000000000 Z
+ updated_at: 2015-03-10 17:05:42.000000000 Z
diff --git a/activerecord/test/support/yaml_compatibility_fixtures/rails_4_2_0.yml b/activerecord/test/support/yaml_compatibility_fixtures/rails_4_2_0.yml
new file mode 100644
index 0000000000..b3d3b33141
--- /dev/null
+++ b/activerecord/test/support/yaml_compatibility_fixtures/rails_4_2_0.yml
@@ -0,0 +1,182 @@
+--- !ruby/object:Topic
+raw_attributes:
+ id: 1
+ title: The First Topic
+ author_name: David
+ author_email_address: david@loudthinking.com
+ written_on: '2003-07-16 14:28:11.223300'
+ bonus_time: '2005-01-30 14:28:00.000000'
+ last_read: '2004-04-15'
+ content: |
+ --- Have a nice day
+ ...
+ important:
+ approved: f
+ replies_count: 1
+ unique_replies_count: 0
+ parent_id:
+ parent_title:
+ type:
+ group:
+ created_at: '2015-03-10 17:44:41'
+ updated_at: '2015-03-10 17:44:41'
+attributes: !ruby/object:ActiveRecord::AttributeSet
+ attributes: !ruby/object:ActiveRecord::LazyAttributeHash
+ types:
+ id: &5 !ruby/object:ActiveRecord::Type::Integer
+ precision:
+ scale:
+ limit:
+ range: !ruby/range
+ begin: -2147483648
+ end: 2147483648
+ excl: true
+ title: &6 !ruby/object:ActiveRecord::Type::String
+ precision:
+ scale:
+ limit: 250
+ author_name: &1 !ruby/object:ActiveRecord::Type::String
+ precision:
+ scale:
+ limit:
+ author_email_address: *1
+ written_on: &4 !ruby/object:ActiveRecord::Type::DateTime
+ precision:
+ scale:
+ limit:
+ bonus_time: &7 !ruby/object:ActiveRecord::Type::Time
+ precision:
+ scale:
+ limit:
+ last_read: &8 !ruby/object:ActiveRecord::Type::Date
+ precision:
+ scale:
+ limit:
+ content: !ruby/object:ActiveRecord::Type::Serialized
+ coder: &9 !ruby/object:ActiveRecord::Coders::YAMLColumn
+ object_class: !ruby/class 'Object'
+ subtype: &2 !ruby/object:ActiveRecord::Type::Text
+ precision:
+ scale:
+ limit:
+ important: *2
+ approved: &10 !ruby/object:ActiveRecord::Type::Boolean
+ precision:
+ scale:
+ limit:
+ replies_count: &3 !ruby/object:ActiveRecord::Type::Integer
+ precision:
+ scale:
+ limit:
+ range: !ruby/range
+ begin: -2147483648
+ end: 2147483648
+ excl: true
+ unique_replies_count: *3
+ parent_id: *3
+ parent_title: *1
+ type: *1
+ group: *1
+ created_at: *4
+ updated_at: *4
+ values:
+ id: 1
+ title: The First Topic
+ author_name: David
+ author_email_address: david@loudthinking.com
+ written_on: '2003-07-16 14:28:11.223300'
+ bonus_time: '2005-01-30 14:28:00.000000'
+ last_read: '2004-04-15'
+ content: |
+ --- Have a nice day
+ ...
+ important:
+ approved: f
+ replies_count: 1
+ unique_replies_count: 0
+ parent_id:
+ parent_title:
+ type:
+ group:
+ created_at: '2015-03-10 17:44:41'
+ updated_at: '2015-03-10 17:44:41'
+ additional_types: {}
+ materialized: true
+ delegate_hash:
+ id: !ruby/object:ActiveRecord::Attribute::FromDatabase
+ name: id
+ value_before_type_cast: 1
+ type: *5
+ title: !ruby/object:ActiveRecord::Attribute::FromDatabase
+ name: title
+ value_before_type_cast: The First Topic
+ type: *6
+ author_name: !ruby/object:ActiveRecord::Attribute::FromDatabase
+ name: author_name
+ value_before_type_cast: David
+ type: *1
+ author_email_address: !ruby/object:ActiveRecord::Attribute::FromDatabase
+ name: author_email_address
+ value_before_type_cast: david@loudthinking.com
+ type: *1
+ written_on: !ruby/object:ActiveRecord::Attribute::FromDatabase
+ name: written_on
+ value_before_type_cast: '2003-07-16 14:28:11.223300'
+ type: *4
+ bonus_time: !ruby/object:ActiveRecord::Attribute::FromDatabase
+ name: bonus_time
+ value_before_type_cast: '2005-01-30 14:28:00.000000'
+ type: *7
+ last_read: !ruby/object:ActiveRecord::Attribute::FromDatabase
+ name: last_read
+ value_before_type_cast: '2004-04-15'
+ type: *8
+ content: !ruby/object:ActiveRecord::Attribute::FromDatabase
+ name: content
+ value_before_type_cast: |
+ --- Have a nice day
+ ...
+ type: !ruby/object:ActiveRecord::Type::Serialized
+ coder: *9
+ subtype: *2
+ important: !ruby/object:ActiveRecord::Attribute::FromDatabase
+ name: important
+ value_before_type_cast:
+ type: *2
+ approved: !ruby/object:ActiveRecord::Attribute::FromDatabase
+ name: approved
+ value_before_type_cast: f
+ type: *10
+ replies_count: !ruby/object:ActiveRecord::Attribute::FromDatabase
+ name: replies_count
+ value_before_type_cast: 1
+ type: *3
+ unique_replies_count: !ruby/object:ActiveRecord::Attribute::FromDatabase
+ name: unique_replies_count
+ value_before_type_cast: 0
+ type: *3
+ parent_id: !ruby/object:ActiveRecord::Attribute::FromDatabase
+ name: parent_id
+ value_before_type_cast:
+ type: *3
+ parent_title: !ruby/object:ActiveRecord::Attribute::FromDatabase
+ name: parent_title
+ value_before_type_cast:
+ type: *1
+ type: !ruby/object:ActiveRecord::Attribute::FromDatabase
+ name: type
+ value_before_type_cast:
+ type: *1
+ group: !ruby/object:ActiveRecord::Attribute::FromDatabase
+ name: group
+ value_before_type_cast:
+ type: *1
+ created_at: !ruby/object:ActiveRecord::Attribute::FromDatabase
+ name: created_at
+ value_before_type_cast: '2015-03-10 17:44:41'
+ type: *4
+ updated_at: !ruby/object:ActiveRecord::Attribute::FromDatabase
+ name: updated_at
+ value_before_type_cast: '2015-03-10 17:44:41'
+ type: *4
+new_record: false
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index b2b3cf4bd4..3ad2392365 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,382 +1,239 @@
-* `String#remove` and `String#remove!` accept multiple arguments.
+* `ActiveSupport::Callbacks#skip_callback` now raises an `ArgumentError` if
+ an unrecognized callback is removed.
- *Pavel Pravosud*
+ *Iain Beeston*
-* TimeWithZone#strftime now delegates every directive to Time#strftime except for '%Z',
- it also now correctly handles escaped '%' characters placed just before time zone related directives.
+* Added `ActiveSupport::ArrayInquirer` and `Array#inquiry`.
- *Pablo Herrero*
+ Wrapping an array in an `ArrayInquirer` gives a friendlier way to check its
+ contents:
-* Corrected Inflector#underscore handling of multiple successive acroynms.
+ variants = ActiveSupport::ArrayInquirer.new([:phone, :tablet])
- *James Le Cuirot*
+ variants.phone? # => true
+ variants.tablet? # => true
+ variants.desktop? # => false
-* Delegation now works with ruby reserved words passed to `:to` option.
+ variants.any?(:phone, :tablet) # => true
+ variants.any?(:phone, :desktop) # => true
+ variants.any?(:desktop, :watch) # => false
- Fixes #16956.
+ `Array#inquiry` is a shortcut for wrapping the receiving array in an
+ `ArrayInquirer`.
- *Agis Anastasopoulos*
+ *George Claghorn*
-* Added method `#eql?` to `ActiveSupport::Duration`, in addition to `#==`.
+* Deprecate `alias_method_chain` in favour of `Module#prepend` introduced in Ruby 2.0
- Currently, the following returns `false`, contrary to expectation:
+ *Kir Shatrov*
- 1.minute.eql?(1.minute)
+* Added `#without` on `Enumerable` and `Array` to return a copy of an
+ enumerable without the specified elements.
- Adding method `#eql?` will make this behave like expected. Method `#eql?` is
- just a bit stricter than `#==`, as it checks whether the argument is also a duration. Their
- parts may be different though.
+ *Todd Bealmear*
- 1.minute.eql?(60.seconds) # => true
- 1.minute.eql?(60) # => false
+* Fixed a problem where String#truncate_words would get stuck with a complex
+ string.
- *Joost Lubach*
+ *Henrik Nygren*
-* `Time#change` can now change nanoseconds (`:nsec`) as a higher-precision
- alternative to microseconds (`:usec`).
-
- *Agis Anastasooulos*
-
-* `MessageVerifier.new` raises an appropriate exception if the secret is `nil`.
- This prevents `MessageVerifier#generate` from raising a cryptic error later on.
-
- *Kostiantyn Kahanskyi*
-
-* Introduced new configuration option `active_support.test_order` for
- specifying the order in which test cases are executed. This option currently defaults
- to `:sorted` but will be changed to `:random` in Rails 5.0.
-
- *Akira Matsuda*, *Godfrey Chan*
-
-* Fixed a bug in `Inflector#underscore` where acroynms in nested constant names
- are incorrectly parsed as camelCase.
-
- Fixes #8015.
-
- *Fred Wu*, *Matthew Draper*
-
-* Make `Time#change` throw an exception if the `:usec` option is out of range and
- the time has an offset other than UTC or local.
-
- *Agis Anastasopoulos*
-
-* `Method` objects now report themselves as not `duplicable?`. This allows
- hashes and arrays containing `Method` objects to be `deep_dup`ed.
-
- *Peter Jaros*
-
-* `determine_constant_from_test_name` does no longer shadow `NameError`s
- which happens during constant autoloading.
-
- Fixes #9933.
-
- *Guo Xiang Tan*
-
-* Added instance_eval version to Object#try and Object#try!, so you can do this:
-
- person.try { name.first }
-
- instead of:
-
- person.try { |person| person.name.first }
-
- *DHH*, *Ari Pollak*
-
-* Fix the `ActiveSupport::Duration#instance_of?` method to return the right
- value with the class itself since it was previously delegated to the
- internal value.
-
- *Robin Dupret*
-
-* Fix rounding errors with `#travel_to` by resetting the usec on any passed time to zero, so we only travel
- with per-second precision, not anything deeper than that.
-
- *DHH*
-
-* Fix DateTime comparison with `DateTime::Infinity` object.
-
- *Rafael Mendonça França*
-
-* Added Object#itself which returns the object itself. Useful when dealing with a chaining scenario, like Active Record scopes:
-
- Event.public_send(state.presence_in([ :trashed, :drafted ]) || :itself).order(:created_at)
-
- *DHH*
-
-* `Object#with_options` executes block in merging option context when
- explicit receiver in not passed.
-
- *Pavel Pravosud*
-
-* Fixed a compatibility issue with the `Oj` gem when cherry-picking the file
- `active_support/core_ext/object/json` without requiring `active_support/json`.
-
- Fixes #16131.
-
- *Godfrey Chan*
-
-* Make `Hash#with_indifferent_access` copy the default proc too.
-
- *arthurnn*, *Xanders*
-
-* Add `String#truncate_words` to truncate a string by a number of words.
-
- *Mohamed Osama*
-
-* Deprecate `capture` and `quietly`.
-
- These methods are not thread safe and may cause issues when used in threaded environments.
- To avoid problems we are deprecating them.
-
- *Tom Meier*
-
-* `DateTime#to_f` now preserves the fractional seconds instead of always
- rounding to `.0`.
-
- Fixes #15994.
-
- *John Paul Ashenfelter*
-
-* Add `Hash#transform_values` to simplify a common pattern where the values of a
- hash must change, but the keys are left the same.
-
- *Sean Griffin*
-
-* Always instrument `ActiveSupport::Cache`.
-
- Since `ActiveSupport::Notifications` only instruments items when there
- are attached subscribers, we don't need to disable instrumentation.
-
- *Peter Wagenet*
-
-* Make the `apply_inflections` method case-insensitive when checking
- whether a word is uncountable or not.
-
- *Robin Dupret*
-
-* Make Dependencies pass a name to NameError error.
-
- *arthurnn*
-
-* Fixed `ActiveSupport::Cache::FileStore` exploding with long paths.
-
- *Adam Panzer / Michael Grosser*
-
-* Fixed `ActiveSupport::TimeWithZone#-` so precision is not unnecessarily lost
- when working with objects with a nanosecond component.
-
- `ActiveSupport::TimeWithZone#-` should return the same result as if we were
- using `Time#-`:
-
- Time.now.end_of_day - Time.now.beginning_of_day # => 86399.999999999
+* Fixed a roundtrip problem with AS::SafeBuffer where primitive-like strings
+ will be dumped as primitives:
Before:
- Time.zone.now.end_of_day.nsec # => 999999999
- Time.zone.now.end_of_day - Time.zone.now.beginning_of_day # => 86400.0
+ YAML.load ActiveSupport::SafeBuffer.new("Hello").to_yaml # => "Hello"
+ YAML.load ActiveSupport::SafeBuffer.new("true").to_yaml # => true
+ YAML.load ActiveSupport::SafeBuffer.new("false").to_yaml # => false
+ YAML.load ActiveSupport::SafeBuffer.new("1").to_yaml # => 1
+ YAML.load ActiveSupport::SafeBuffer.new("1.1").to_yaml # => 1.1
After:
- Time.zone.now.end_of_day - Time.zone.now.beginning_of_day
- # => 86399.999999999
-
- *Gordon Chan*
-
-* Fixed precision error in NumberHelper when using Rationals.
-
- Before:
-
- ActiveSupport::NumberHelper.number_to_rounded Rational(1000, 3), precision: 2
- # => "330.00"
-
- After:
-
- ActiveSupport::NumberHelper.number_to_rounded Rational(1000, 3), precision: 2
- # => "333.33"
-
- See #15379.
-
- *Juanjo Bazán*
-
-* Removed deprecated `Numeric#ago` and friends
-
- Replacements:
-
- 5.ago => 5.seconds.ago
- 5.until => 5.seconds.until
- 5.since => 5.seconds.since
- 5.from_now => 5.seconds.from_now
-
- See #12389 for the history and rationale behind this.
+ YAML.load ActiveSupport::SafeBuffer.new("Hello").to_yaml # => "Hello"
+ YAML.load ActiveSupport::SafeBuffer.new("true").to_yaml # => "true"
+ YAML.load ActiveSupport::SafeBuffer.new("false").to_yaml # => "false"
+ YAML.load ActiveSupport::SafeBuffer.new("1").to_yaml # => "1"
+ YAML.load ActiveSupport::SafeBuffer.new("1.1").to_yaml # => "1.1"
*Godfrey Chan*
-* DateTime `advance` now supports partial days.
+* Enable `number_to_percentage` to keep the number's precision by allowing
+ `:precision` to be `nil`.
- Before:
+ *Jack Xu*
- DateTime.now.advance(days: 1, hours: 12)
+* `config_accessor` became a private method, as with Ruby's `attr_accessor`.
- After:
+ *Akira Matsuda*
- DateTime.now.advance(days: 1.5)
+* `AS::Testing::TimeHelpers#travel_to` now changes `DateTime.now` as well as
+ `Time.now` and `Date.today`.
- Fixes #12005.
+ *Yuki Nishijima*
- *Shay Davidson*
+* Add `file_fixture` to `ActiveSupport::TestCase`.
+ It provides a simple mechanism to access sample files in your test cases.
-* `Hash#deep_transform_keys` and `Hash#deep_transform_keys!` now transform hashes
- in nested arrays. This change also applies to `Hash#deep_stringify_keys`,
- `Hash#deep_stringify_keys!`, `Hash#deep_symbolize_keys` and
- `Hash#deep_symbolize_keys!`.
+ By default file fixtures are stored in `test/fixtures/files`. This can be
+ configured per test-case using the `file_fixture_path` class attribute.
- *OZAWA Sakuro*
+ *Yves Senn*
-* Fixed confusing `DelegationError` in `Module#delegate`.
+* Return value of yielded block in `File.atomic_write`.
- See #15186.
+ *Ian Ker-Seymer*
- *Vladimir Yarotsky*
+* 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`.
-* Fixed `ActiveSupport::Subscriber` so that no duplicate subscriber is created
- when a subscriber method is redefined.
+ Fixes #18550.
- *Dennis Schön*
+ *Aditya Kapoor*
-* Remove deprecated string based terminators for `ActiveSupport::Callbacks`.
+* Add missing time zone definitions for Russian Federation and sync them
+ with `zone.tab` file from tzdata version 2014j (latest).
- *Eileen M. Uchitelle*
+ *Andrey Novikov*
-* Fixed an issue when using
- `ActiveSupport::NumberHelper::NumberToDelimitedConverter` to
- convert a value that is an `ActiveSupport::SafeBuffer` introduced
- in 2da9d67.
+* Add `SecureRandom.base58` for generation of random base58 strings.
- See #15064.
+ *Matthew Draper*, *Guillermo Iguaran*
- *Mark J. Titorenko*
+* Add `#prev_day` and `#next_day` counterparts to `#yesterday` and
+ `#tomorrow` for `Date`, `Time`, and `DateTime`.
-* `TimeZone#parse` defaults the day of the month to '1' if any other date
- components are specified. This is more consistent with the behavior of
- `Time#parse`.
+ *George Claghorn*
- *Ulysse Carion*
+* Add `same_time` option to `#next_week` and `#prev_week` for `Date`, `Time`,
+ and `DateTime`.
-* `humanize` strips leading underscores, if any.
+ *George Claghorn*
- Before:
+* Add `#on_weekend?`, `#next_weekday`, `#prev_weekday` methods to `Date`,
+ `Time`, and `DateTime`.
- '_id'.humanize # => ""
+ `#on_weekend?` returns true if the receiving date/time falls on a Saturday
+ or Sunday.
- After:
+ `#next_weekday` returns a new date/time representing the next day that does
+ not fall on a Saturday or Sunday.
- '_id'.humanize # => "Id"
+ `#prev_weekday` returns a new date/time representing the previous day that
+ does not fall on a Saturday or Sunday.
- *Xavier Noria*
+ *George Claghorn*
-* Fixed backward compatibility issues introduced in 326e652.
+* Change the default test order from `:sorted` to `:random`.
- Empty Hash or Array should not be present in serialization result.
+ *Rafael Mendonça França*
- {a: []}.to_query # => ""
- {a: {}}.to_query # => ""
+* Remove deprecated `ActiveSupport::JSON::Encoding::CircularReferenceError`.
- For more info see #14948.
+ *Rafael Mendonça França*
- *Bogdan Gusiev*
+* Remove deprecated methods `ActiveSupport::JSON::Encoding.encode_big_decimal_as_string=`
+ and `ActiveSupport::JSON::Encoding.encode_big_decimal_as_string`.
-* Add `Digest::UUID::uuid_v3` and `Digest::UUID::uuid_v5` to support stable
- UUID fixtures on PostgreSQL.
+ *Rafael Mendonça França*
- *Roderick van Domburg*
+* Remove deprecated `ActiveSupport::SafeBuffer#prepend`.
-* Fixed `ActiveSupport::Duration#eql?` so that `1.second.eql?(1.second)` is
- true.
+ *Rafael Mendonça França*
- This fixes the current situation of:
+* Remove deprecated methods at `Kernel`.
- 1.second.eql?(1.second) # => false
+ `silence_stderr`, `silence_stream`, `capture` and `quietly`.
- `eql?` also requires that the other object is an `ActiveSupport::Duration`.
- This requirement makes `ActiveSupport::Duration`'s behavior consistent with
- the behavior of Ruby's numeric types:
+ *Rafael Mendonça França*
- 1.eql?(1.0) # => false
- 1.0.eql?(1) # => false
+* Remove deprecated `active_support/core_ext/big_decimal/yaml_conversions`
+ file.
- 1.second.eql?(1) # => false (was true)
- 1.eql?(1.second) # => false
+ *Rafael Mendonça França*
- { 1 => "foo", 1.0 => "bar" }
- # => { 1 => "foo", 1.0 => "bar" }
+* Remove deprecated methods `ActiveSupport::Cache::Store.instrument` and
+ `ActiveSupport::Cache::Store.instrument=`.
- { 1 => "foo", 1.second => "bar" }
- # now => { 1 => "foo", 1.second => "bar" }
- # was => { 1 => "bar" }
+ *Rafael Mendonça França*
- And though the behavior of these hasn't changed, for reference:
+* Change the way in which callback chains can be halted.
- 1 == 1.0 # => true
- 1.0 == 1 # => true
+ The preferred method to halt a callback chain from now on is to explicitly
+ `throw(:abort)`.
+ In the past, returning `false` in an ActiveSupport callback had the side
+ effect of halting the callback chain. This is not recommended anymore and,
+ depending on the value of
+ `Callbacks::CallbackChain.halt_and_display_warning_on_return_false`, will
+ either not work at all or display a deprecation warning.
- 1 == 1.second # => true
- 1.second == 1 # => true
+* Add Callbacks::CallbackChain.halt_and_display_warning_on_return_false
- *Emily Dobervich*
+ Setting `Callbacks::CallbackChain.halt_and_display_warning_on_return_false`
+ to true will let an app support the deprecated way of halting callback
+ chains by returning `false`.
-* `ActiveSupport::SafeBuffer#prepend` acts like `String#prepend` and modifies
- instance in-place, returning self. `ActiveSupport::SafeBuffer#prepend!` is
- deprecated.
+ 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)`.
- *Pavel Pravosud*
+ The value can also be set with the Rails configuration option
+ `config.active_support.halt_callback_chains_on_return_false`.
-* `HashWithIndifferentAccess` better respects `#to_hash` on objects it
- receives. In particular, `.new`, `#update`, `#merge`, and `#replace` accept
- objects which respond to `#to_hash`, even if those objects are not hashes
- directly.
+ When the configuration option is missing, its value is `true`, so older apps
+ ported to Rails 5.0 will not break (but display a deprecation warning).
+ For new Rails 5.0 apps, its value is set to `false` in an initializer, so
+ these apps will support the new behavior by default.
- *Peter Jaros*
+ *claudiob*
-* Deprecate `Class#superclass_delegating_accessor`, use `Class#class_attribute` instead.
+* Changes arguments and default value of CallbackChain's :terminator option
- *Akshay Vishnoi*
+ Chains of callbacks defined without an explicit `:terminator` option will
+ now be halted as soon as a `before_` callback throws `:abort`.
-* Ensure classes which `include Enumerable` get `#to_json` in addition to
- `#as_json`.
+ Chains of callbacks defined with a `:terminator` option will maintain their
+ existing behavior of halting as soon as a `before_` callback matches the
+ terminator's expectation.
- *Sammy Larbi*
+ *claudiob*
-* Change the signature of `fetch_multi` to return a hash rather than an
- array. This makes it consistent with the output of `read_multi`.
+* Deprecate `MissingSourceFile` in favor of `LoadError`.
- *Parker Selbert*
+ `MissingSourceFile` was just an alias to `LoadError` and was not being
+ raised inside the framework.
-* Introduce `Concern#class_methods` as a sleek alternative to clunky
- `module ClassMethods`. Add `Kernel#concern` to define at the toplevel
- without chunky `module Foo; extend ActiveSupport::Concern` boilerplate.
+ *Rafael Mendonça França*
- # app/models/concerns/authentication.rb
- concern :Authentication do
- included do
- after_create :generate_private_key
- end
+* Add support for error dispatcher classes in `ActiveSupport::Rescuable`.
+ Now it acts closer to Ruby's rescue.
- class_methods do
- def authenticate(credentials)
- # ...
+ Example:
+
+ class BaseController < ApplicationController
+ module ErrorDispatcher
+ def self.===(other)
+ Exception === other && other.respond_to?(:status)
end
end
- def generate_private_key
- # ...
+ rescue_from ErrorDispatcher do |error|
+ render status: error.status, json: { error: error.to_s }
end
end
- # app/models/user.rb
- class User < ActiveRecord::Base
- include Authentication
- end
+ *Genadi Samokovarov*
+
+* Add `#verified` and `#valid_message?` methods to `ActiveSupport::MessageVerifier`
+
+ Previously, the only way to decode a message with `ActiveSupport::MessageVerifier`
+ was to use `#verify`, which would raise an exception on invalid messages. Now
+ `#verified` can also be used, which returns `nil` on messages that cannot be
+ decoded.
+
+ Previously, there was no way to check if a message's format was valid without
+ attempting to decode it. `#valid_message?` is a boolean convenience method that
+ checks whether the message is valid without actually decoding it.
- *Jeremy Kemper*
+ *Logan Leger*
-Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/activesupport/CHANGELOG.md) for previous changes.
+Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activesupport/CHANGELOG.md) for previous changes.
diff --git a/activesupport/MIT-LICENSE b/activesupport/MIT-LICENSE
index d06d4f3b2d..7bffebb076 100644
--- a/activesupport/MIT-LICENSE
+++ b/activesupport/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2005-2014 David Heinemeier Hansson
+Copyright (c) 2005-2015 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index 80d1d35888..2cb455cb41 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 = '>= 1.9.3'
+ s.required_ruby_version = '>= 2.2.1'
s.license = 'MIT'
@@ -20,9 +20,9 @@ Gem::Specification.new do |s|
s.rdoc_options.concat ['--encoding', 'UTF-8']
- s.add_dependency 'i18n', '>= 0.7.0.beta1', '< 0.8'
+ s.add_dependency 'i18n', '~> 0.7'
s.add_dependency 'json', '~> 1.7', '>= 1.7.7'
s.add_dependency 'tzinfo', '~> 1.1'
s.add_dependency 'minitest', '~> 5.1'
- s.add_dependency 'thread_safe','~> 0.1'
+ s.add_dependency 'thread_safe','~> 0.3', '>= 0.3.4'
end
diff --git a/activesupport/bin/generate_tables b/activesupport/bin/generate_tables
index f39e89b7d0..71a6b78652 100755
--- a/activesupport/bin/generate_tables
+++ b/activesupport/bin/generate_tables
@@ -55,7 +55,7 @@ module ActiveSupport
codepoint.combining_class = Integer($4)
#codepoint.bidi_class = $5
codepoint.decomp_type = $7
- codepoint.decomp_mapping = ($8=='') ? nil : $8.split.collect { |element| element.hex }
+ codepoint.decomp_mapping = ($8=='') ? nil : $8.split.collect(&:hex)
#codepoint.bidi_mirrored = ($13=='Y') ? true : false
codepoint.uppercase_mapping = ($16=='') ? 0 : $16.hex
codepoint.lowercase_mapping = ($17=='') ? 0 : $17.hex
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 94468240a4..588d6c49f9 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2005-2014 David Heinemeier Hansson
+# Copyright (c) 2005-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -59,6 +59,7 @@ module ActiveSupport
autoload :StringInquirer
autoload :TaggedLogging
autoload :XmlMini
+ autoload :ArrayInquirer
end
autoload :Rescuable
@@ -71,14 +72,14 @@ module ActiveSupport
NumberHelper.eager_load!
end
- @@test_order = nil
+ cattr_accessor :test_order # :nodoc:
- def self.test_order=(new_order)
- @@test_order = new_order
+ def self.halt_callback_chains_on_return_false
+ Callbacks::CallbackChain.halt_and_display_warning_on_return_false
end
- def self.test_order
- @@test_order
+ def self.halt_callback_chains_on_return_false=(value)
+ Callbacks::CallbackChain.halt_and_display_warning_on_return_false = value
end
end
diff --git a/activesupport/lib/active_support/array_inquirer.rb b/activesupport/lib/active_support/array_inquirer.rb
new file mode 100644
index 0000000000..0ae534da00
--- /dev/null
+++ b/activesupport/lib/active_support/array_inquirer.rb
@@ -0,0 +1,38 @@
+module ActiveSupport
+ # Wrapping an array in an +ArrayInquirer+ gives a friendlier way to check
+ # its string-like contents:
+ #
+ # variants = ActiveSupport::ArrayInquirer.new([:phone, :tablet])
+ #
+ # 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
+ def any?(*candidates, &block)
+ if candidates.none?
+ super
+ else
+ candidates.any? do |candidate|
+ include?(candidate) || include?(candidate.to_sym)
+ end
+ end
+ end
+
+ private
+ def respond_to_missing?(name, include_private = false)
+ name[-1] == '?'
+ end
+
+ def method_missing(name, *args)
+ if name[-1] == '?'
+ any?(name[0..-2])
+ else
+ super
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index ff67a6828c..837974bc85 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -8,7 +8,6 @@ require 'active_support/core_ext/numeric/bytes'
require 'active_support/core_ext/numeric/time'
require 'active_support/core_ext/object/to_param'
require 'active_support/core_ext/string/inflections'
-require 'active_support/deprecation'
module ActiveSupport
# See ActiveSupport::Cache::Store for documentation.
@@ -179,18 +178,6 @@ module ActiveSupport
@silence = previous_silence
end
- # :deprecated:
- def self.instrument=(boolean)
- ActiveSupport::Deprecation.warn "ActiveSupport::Cache.instrument= is deprecated and will be removed in Rails 5. Instrumentation is now always on so you can safely stop using it."
- true
- end
-
- # :deprecated:
- def self.instrument
- ActiveSupport::Deprecation.warn "ActiveSupport::Cache.instrument is deprecated and will be removed in Rails 5. Instrumentation is now always on so you can safely stop using it."
- true
- end
-
# Fetches data from the cache, using the given key. If there is data in
# the cache with the given key, then that data is returned.
#
@@ -338,19 +325,22 @@ module ActiveSupport
def read_multi(*names)
options = names.extract_options!
options = merged_options(options)
- results = {}
- names.each do |name|
- key = namespaced_key(name, options)
- entry = read_entry(key, options)
- if entry
- if entry.expired?
- delete_entry(key, options)
- else
- results[name] = entry.value
+
+ instrument_multi(:read, names, options) do |payload|
+ results = {}
+ names.each do |name|
+ key = namespaced_key(name, options)
+ entry = read_entry(key, options)
+ if entry
+ if entry.expired?
+ delete_entry(key, options)
+ else
+ results[name] = entry.value
+ end
end
end
+ results
end
- results
end
# Fetches data from the cache, using the given keys. If there is data in
@@ -363,8 +353,11 @@ module ActiveSupport
# Returns a hash with the data for each of the names. For example:
#
# cache.write("bim", "bam")
- # cache.fetch_multi("bim", "boom") { |key| key * 2 }
- # # => { "bam" => "bam", "boom" => "boomboom" }
+ # cache.fetch_multi("bim", "unknown_key") do |key|
+ # "Fallback value for key: #{key}"
+ # end
+ # # => { "bim" => "bam",
+ # # "unknown_key" => "Fallback value for key: unknown_key" }
#
def fetch_multi(*names)
options = names.extract_options!
@@ -540,16 +533,27 @@ module ActiveSupport
end
def instrument(operation, key, options = nil)
- log(operation, key, options)
+ log { "Cache #{operation}: #{key}#{options.blank? ? "" : " (#{options.inspect})"}" }
payload = { :key => key }
payload.merge!(options) if options.is_a?(Hash)
ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload){ yield(payload) }
end
- def log(operation, key, options = nil)
+ def instrument_multi(operation, keys, options = nil)
+ log do
+ formatted_keys = keys.map { |k| "- #{k}" }.join("\n")
+ "Caches multi #{operation}:\n#{formatted_keys}#{options.blank? ? "" : " (#{options.inspect})"}"
+ end
+
+ payload = { key: keys }
+ payload.merge!(options) if options.is_a?(Hash)
+ ActiveSupport::Notifications.instrument("cache_#{operation}_multi.active_support", payload) { yield(payload) }
+ end
+
+ def log
return unless logger && logger.debug? && !silence?
- logger.debug("Cache #{operation}: #{key}#{options.blank? ? "" : " (#{options.inspect})"}")
+ logger.debug(yield)
end
def find_cached_entry(key, name, options)
@@ -562,9 +566,9 @@ module ActiveSupport
def handle_expired_entry(entry, key, options)
if entry && entry.expired?
race_ttl = options[:race_condition_ttl].to_i
- if race_ttl && (Time.now.to_f - entry.expires_at <= race_ttl)
- # When an entry has :race_condition_ttl defined, put the stale entry back into the cache
- # for a brief period while the entry is begin recalculated.
+ if (race_ttl > 0) && (Time.now.to_f - entry.expires_at <= race_ttl)
+ # When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache
+ # for a brief period while the entry is being recalculated.
entry.expires_at = Time.now + race_ttl
write_entry(key, entry, :expires_in => race_ttl * 2)
else
@@ -615,14 +619,12 @@ module ActiveSupport
end
def value
- convert_version_4beta1_entry! if defined?(@v)
compressed? ? uncompress(@value) : @value
end
# Check if the entry is expired. The +expires_in+ parameter can override
# the value set when the entry was created.
def expired?
- convert_version_4beta1_entry! if defined?(@value)
@expires_in && @created_at + @expires_in <= Time.now.to_f
end
@@ -658,8 +660,6 @@ module ActiveSupport
# Duplicate the value in a class. This is used by cache implementations that don't natively
# serialize entries to protect against accidental cache modifications.
def dup_value!
- convert_version_4beta1_entry! if defined?(@v)
-
if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false)
if @value.is_a?(String)
@value = @value.dup
@@ -692,26 +692,6 @@ module ActiveSupport
def uncompress(value)
Marshal.load(Zlib::Inflate.inflate(value))
end
-
- # The internals of this method changed between Rails 3.x and 4.0. This method provides the glue
- # to ensure that cache entries created under the old version still work with the new class definition.
- def convert_version_4beta1_entry!
- if defined?(@v)
- @value = @v
- remove_instance_variable(:@v)
- end
-
- if defined?(@c)
- @compressed = @c
- remove_instance_variable(:@c)
- end
-
- if defined?(@x) && @x
- @created_at ||= Time.now.to_f
- @expires_in = @x - @created_at
- remove_instance_variable(:@x)
- end
- end
end
end
end
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 61b4f0b8b0..73ae3acea5 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -66,14 +66,17 @@ module ActiveSupport
def read_multi(*names)
options = names.extract_options!
options = merged_options(options)
- keys_to_names = Hash[names.map{|name| [escape_key(namespaced_key(name, options)), name]}]
- raw_values = @data.get_multi(keys_to_names.keys, :raw => true)
- values = {}
- raw_values.each do |key, value|
- entry = deserialize_entry(value)
- values[keys_to_names[key]] = entry.value unless entry.expired?
+
+ instrument_multi(:read, names, options) do
+ keys_to_names = Hash[names.map{|name| [escape_key(namespaced_key(name, options)), name]}]
+ raw_values = @data.get_multi(keys_to_names.keys, :raw => true)
+ values = {}
+ raw_values.each do |key, value|
+ entry = deserialize_entry(value)
+ values[keys_to_names[key]] = entry.value unless entry.expired?
+ end
+ values
end
- values
end
# Increment a cached value. This method uses the memcached incr atomic
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb
index 73c6b3cb88..a913736fc3 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb
@@ -39,7 +39,7 @@ module ActiveSupport
@data = {}
end
- # Don't allow synchronizing since it isn't thread safe,
+ # Don't allow synchronizing since it isn't thread safe.
def synchronize # :nodoc:
yield
end
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 24c702b602..814fd288cf 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -4,6 +4,8 @@ require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/kernel/singleton_class'
+require 'active_support/core_ext/string/filters'
+require 'active_support/deprecation'
require 'thread'
module ActiveSupport
@@ -78,14 +80,10 @@ module ActiveSupport
# save
# end
def run_callbacks(kind, &block)
- send "_run_#{kind}_callbacks", &block
- end
-
- private
+ callbacks = send("_#{kind}_callbacks")
- def _run_callbacks(callbacks, &block)
if callbacks.empty?
- block.call if block
+ yield if block_given?
else
runner = callbacks.compile
e = Filters::Environment.new(self, false, nil, block)
@@ -93,6 +91,8 @@ 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.
@@ -121,102 +121,106 @@ module ActiveSupport
ENDING = End.new
class Before
- def self.build(next_callback, user_callback, user_conditions, chain_config, filter)
+ def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter)
halted_lambda = chain_config[:terminator]
if chain_config.key?(:terminator) && user_conditions.any?
- halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter)
+ halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter)
elsif chain_config.key? :terminator
- halting(next_callback, user_callback, halted_lambda, filter)
+ halting(callback_sequence, user_callback, halted_lambda, filter)
elsif user_conditions.any?
- conditional(next_callback, user_callback, user_conditions)
+ conditional(callback_sequence, user_callback, user_conditions)
else
- simple next_callback, user_callback
+ simple callback_sequence, user_callback
end
end
- def self.halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter)
- lambda { |env|
+ def self.halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter)
+ callback_sequence.before do |env|
target = env.target
value = env.value
halted = env.halted
if !halted && user_conditions.all? { |c| c.call(target, value) }
- result = user_callback.call target, value
- env.halted = halted_lambda.call(target, result)
+ result_lambda = -> { user_callback.call target, value }
+ env.halted = halted_lambda.call(target, result_lambda)
if env.halted
target.send :halted_callback_hook, filter
end
end
- next_callback.call env
- }
+
+ env
+ end
end
private_class_method :halting_and_conditional
- def self.halting(next_callback, user_callback, halted_lambda, filter)
- lambda { |env|
+ def self.halting(callback_sequence, user_callback, halted_lambda, filter)
+ callback_sequence.before do |env|
target = env.target
value = env.value
halted = env.halted
unless halted
- result = user_callback.call target, value
- env.halted = halted_lambda.call(target, result)
+ result_lambda = -> { user_callback.call target, value }
+ env.halted = halted_lambda.call(target, result_lambda)
+
if env.halted
target.send :halted_callback_hook, filter
end
end
- next_callback.call env
- }
+
+ env
+ end
end
private_class_method :halting
- def self.conditional(next_callback, user_callback, user_conditions)
- lambda { |env|
+ def self.conditional(callback_sequence, user_callback, user_conditions)
+ callback_sequence.before do |env|
target = env.target
value = env.value
if user_conditions.all? { |c| c.call(target, value) }
user_callback.call target, value
end
- next_callback.call env
- }
+
+ env
+ end
end
private_class_method :conditional
- def self.simple(next_callback, user_callback)
- lambda { |env|
+ def self.simple(callback_sequence, user_callback)
+ callback_sequence.before do |env|
user_callback.call env.target, env.value
- next_callback.call env
- }
+
+ env
+ end
end
private_class_method :simple
end
class After
- def self.build(next_callback, user_callback, user_conditions, chain_config)
+ def self.build(callback_sequence, user_callback, user_conditions, chain_config)
if chain_config[:skip_after_callbacks_if_terminated]
if chain_config.key?(:terminator) && user_conditions.any?
- halting_and_conditional(next_callback, user_callback, user_conditions)
+ halting_and_conditional(callback_sequence, user_callback, user_conditions)
elsif chain_config.key?(:terminator)
- halting(next_callback, user_callback)
+ halting(callback_sequence, user_callback)
elsif user_conditions.any?
- conditional next_callback, user_callback, user_conditions
+ conditional callback_sequence, user_callback, user_conditions
else
- simple next_callback, user_callback
+ simple callback_sequence, user_callback
end
else
if user_conditions.any?
- conditional next_callback, user_callback, user_conditions
+ conditional callback_sequence, user_callback, user_conditions
else
- simple next_callback, user_callback
+ simple callback_sequence, user_callback
end
end
end
- def self.halting_and_conditional(next_callback, user_callback, user_conditions)
- lambda { |env|
- env = next_callback.call env
+ def self.halting_and_conditional(callback_sequence, user_callback, user_conditions)
+ callback_sequence.after do |env|
target = env.target
value = env.value
halted = env.halted
@@ -224,122 +228,119 @@ module ActiveSupport
if !halted && user_conditions.all? { |c| c.call(target, value) }
user_callback.call target, value
end
+
env
- }
+ end
end
private_class_method :halting_and_conditional
- def self.halting(next_callback, user_callback)
- lambda { |env|
- env = next_callback.call env
+ def self.halting(callback_sequence, user_callback)
+ callback_sequence.after do |env|
unless env.halted
user_callback.call env.target, env.value
end
+
env
- }
+ end
end
private_class_method :halting
- def self.conditional(next_callback, user_callback, user_conditions)
- lambda { |env|
- env = next_callback.call env
+ def self.conditional(callback_sequence, user_callback, user_conditions)
+ callback_sequence.after do |env|
target = env.target
value = env.value
if user_conditions.all? { |c| c.call(target, value) }
user_callback.call target, value
end
+
env
- }
+ end
end
private_class_method :conditional
- def self.simple(next_callback, user_callback)
- lambda { |env|
- env = next_callback.call env
+ def self.simple(callback_sequence, user_callback)
+ callback_sequence.after do |env|
user_callback.call env.target, env.value
+
env
- }
+ end
end
private_class_method :simple
end
class Around
- def self.build(next_callback, user_callback, user_conditions, chain_config)
+ def self.build(callback_sequence, user_callback, user_conditions, chain_config)
if chain_config.key?(:terminator) && user_conditions.any?
- halting_and_conditional(next_callback, user_callback, user_conditions)
+ halting_and_conditional(callback_sequence, user_callback, user_conditions)
elsif chain_config.key? :terminator
- halting(next_callback, user_callback)
+ halting(callback_sequence, user_callback)
elsif user_conditions.any?
- conditional(next_callback, user_callback, user_conditions)
+ conditional(callback_sequence, user_callback, user_conditions)
else
- simple(next_callback, user_callback)
+ simple(callback_sequence, user_callback)
end
end
- def self.halting_and_conditional(next_callback, user_callback, user_conditions)
- lambda { |env|
+ def self.halting_and_conditional(callback_sequence, user_callback, user_conditions)
+ callback_sequence.around do |env, &run|
target = env.target
value = env.value
halted = env.halted
if !halted && user_conditions.all? { |c| c.call(target, value) }
user_callback.call(target, value) {
- env = next_callback.call env
- env.value
+ run.call.value
}
env
else
- next_callback.call env
+ run.call
end
- }
+ end
end
private_class_method :halting_and_conditional
- def self.halting(next_callback, user_callback)
- lambda { |env|
+ def self.halting(callback_sequence, user_callback)
+ callback_sequence.around do |env, &run|
target = env.target
value = env.value
if env.halted
- next_callback.call env
+ run.call
else
user_callback.call(target, value) {
- env = next_callback.call env
- env.value
+ run.call.value
}
env
end
- }
+ end
end
private_class_method :halting
- def self.conditional(next_callback, user_callback, user_conditions)
- lambda { |env|
+ def self.conditional(callback_sequence, user_callback, user_conditions)
+ callback_sequence.around do |env, &run|
target = env.target
value = env.value
if user_conditions.all? { |c| c.call(target, value) }
user_callback.call(target, value) {
- env = next_callback.call env
- env.value
+ run.call.value
}
env
else
- next_callback.call env
+ run.call
end
- }
+ end
end
private_class_method :conditional
- def self.simple(next_callback, user_callback)
- lambda { |env|
+ def self.simple(callback_sequence, user_callback)
+ callback_sequence.around do |env, &run|
user_callback.call(env.target, env.value) {
- env = next_callback.call env
- env.value
+ run.call.value
}
env
- }
+ end
end
private_class_method :simple
end
@@ -366,14 +367,14 @@ module ActiveSupport
def filter; @key; end
def raw_filter; @filter; end
- def merge(chain, new_options)
+ def merge_conditional_options(chain, if_option:, unless_option:)
options = {
:if => @if.dup,
:unless => @unless.dup
}
- options[:if].concat Array(new_options.fetch(:unless, []))
- options[:unless].concat Array(new_options.fetch(:if, []))
+ options[:if].concat Array(unless_option)
+ options[:unless].concat Array(if_option)
self.class.build chain, @filter, @kind, options
end
@@ -392,17 +393,17 @@ module ActiveSupport
end
# Wraps code with filter
- def apply(next_callback)
+ def apply(callback_sequence)
user_conditions = conditions_lambdas
user_callback = make_lambda @filter
case kind
when :before
- Filters::Before.build(next_callback, user_callback, user_conditions, chain_config, @filter)
+ Filters::Before.build(callback_sequence, user_callback, user_conditions, chain_config, @filter)
when :after
- Filters::After.build(next_callback, user_callback, user_conditions, chain_config)
+ Filters::After.build(callback_sequence, user_callback, user_conditions, chain_config)
when :around
- Filters::Around.build(next_callback, user_callback, user_conditions, chain_config)
+ Filters::Around.build(callback_sequence, user_callback, user_conditions, chain_config)
end
end
@@ -467,16 +468,59 @@ module ActiveSupport
end
end
+ # Execute before and after filters in a sequence instead of
+ # chaining them with nested lambda calls, see:
+ # https://github.com/rails/rails/issues/18011
+ class CallbackSequence
+ def initialize(&call)
+ @call = call
+ @before = []
+ @after = []
+ end
+
+ def before(&before)
+ @before.unshift(before)
+ self
+ end
+
+ def after(&after)
+ @after.push(after)
+ self
+ end
+
+ def around(&around)
+ CallbackSequence.new do |arg|
+ around.call(arg) {
+ self.call(arg)
+ }
+ end
+ end
+
+ def call(arg)
+ @before.each { |b| b.call(arg) }
+ value = @call.call(arg)
+ @after.each { |a| a.call(arg) }
+ value
+ end
+ end
+
# An Array with a compile method.
class CallbackChain #:nodoc:#
include Enumerable
attr_reader :name, :config
+ # If true, any callback returning +false+ will halt the entire callback
+ # chain and display a deprecation message. If false, callback chains will
+ # only be halted by calling +throw :abort+. Defaults to +true+.
+ class_attribute :halt_and_display_warning_on_return_false
+ self.halt_and_display_warning_on_return_false = true
+
def initialize(name, config)
@name = name
@config = {
- :scope => [ :kind ]
+ scope: [:kind],
+ terminator: default_terminator
}.merge!(config)
@chain = []
@callbacks = nil
@@ -511,8 +555,9 @@ module ActiveSupport
def compile
@callbacks || @mutex.synchronize do
- @callbacks ||= @chain.reverse.inject(Filters::ENDING) do |chain, callback|
- callback.apply chain
+ final_sequence = CallbackSequence.new { |env| Filters::ENDING.call(env) }
+ @callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback|
+ callback.apply callback_sequence
end
end
end
@@ -546,6 +591,28 @@ module ActiveSupport
@callbacks = nil
@chain.delete_if { |c| callback.duplicates?(c) }
end
+
+ def default_terminator
+ Proc.new do |target, result_lambda|
+ terminate = true
+ catch(:abort) do
+ result = result_lambda.call if result_lambda.is_a?(Proc)
+ if halt_and_display_warning_on_return_false && result == false
+ display_deprecation_warning_for_false_terminator
+ else
+ terminate = false
+ end
+ end
+ terminate
+ end
+ end
+
+ def display_deprecation_warning_for_false_terminator
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Returning `false` in a callback will not implicitly halt a callback chain in the next release of Rails.
+ To explicitly halt a callback chain, please use `throw :abort` instead.
+ MSG
+ end
end
module ClassMethods
@@ -594,10 +661,12 @@ module ActiveSupport
#
# ===== Options
#
- # * <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, a string or an array of symbols and strings,
+ # each naming an instance method or a proc; the callback will be called
+ # only when they all return a true value.
+ # * <tt>:unless</tt> - A symbol, a string or an array of symbols and
+ # strings, each naming an instance method or a proc; the callback will
+ # be called only when they all return a false value.
# * <tt>:prepend</tt> - If +true+, the callback will be prepended to the
# existing chain rather than appended.
def set_callback(name, *filter_list, &block)
@@ -620,19 +689,27 @@ module ActiveSupport
# class Writer < Person
# skip_callback :validate, :before, :check_membership, if: -> { self.age > 18 }
# end
+ #
+ # An <tt>ArgumentError</tt> will be raised if the callback has not
+ # already been set (unless the <tt>:raise</tt> option is set to <tt>false</tt>).
def skip_callback(name, *filter_list, &block)
type, filters, options = normalize_callback_params(filter_list, block)
+ options[:raise] = true unless options.key?(:raise)
__update_callbacks(name) do |target, chain|
filters.each do |filter|
- filter = chain.find {|c| c.matches?(type, filter) }
+ callback = chain.find {|c| c.matches?(type, filter) }
- if filter && options.any?
- new_filter = filter.merge(chain, options)
- chain.insert(chain.index(filter), new_filter)
+ if !callback && options[:raise]
+ raise ArgumentError, "#{type.to_s.capitalize} #{name} callback #{filter.inspect} has not been defined"
end
- chain.delete(filter)
+ if callback && (options.key?(:if) || options.key?(:unless))
+ new_callback = callback.merge_conditional_options(chain, if_option: options[:if], unless_option: options[:unless])
+ chain.insert(chain.index(callback), new_callback)
+ end
+
+ chain.delete(callback)
end
target.set_callbacks name, chain
end
@@ -659,21 +736,22 @@ module ActiveSupport
# ===== Options
#
# * <tt>:terminator</tt> - Determines when a before filter will halt the
- # callback chain, preventing following callbacks from being called and
- # the event from being triggered. This should be a lambda to be executed.
+ # 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.
#
# define_callbacks :validate, terminator: ->(target, result) { result == false }
#
# In this example, if any before validate callbacks returns +false+,
- # other callbacks are not executed. Defaults to +false+, meaning no value
- # halts the chain.
+ # any successive before and around callback is not executed.
+ # Defaults to +false+, meaning no value halts the chain.
#
# * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after
# callbacks should be terminated by the <tt>:terminator</tt> option. By
- # default after callbacks executed no matter if callback chain was
- # terminated or not. Option makes sense only when <tt>:terminator</tt>
+ # default after callbacks are executed no matter if callback chain was
+ # terminated or not. This option makes sense only when <tt>:terminator</tt>
# option is specified.
#
# * <tt>:scope</tt> - Indicates which methods should be executed when an
@@ -728,12 +806,6 @@ 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 342d3a9d52..4082d2d464 100644
--- a/activesupport/lib/active_support/concern.rb
+++ b/activesupport/lib/active_support/concern.rb
@@ -114,7 +114,7 @@ module ActiveSupport
return false
else
return false if base < self
- @_dependencies.each { |dep| base.send(:include, dep) }
+ @_dependencies.each { |dep| base.include(dep) }
super
base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods)
base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block)
diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb
index 3dd44e32d8..8256c325af 100644
--- a/activesupport/lib/active_support/configurable.rb
+++ b/activesupport/lib/active_support/configurable.rb
@@ -122,6 +122,7 @@ module ActiveSupport
send("#{name}=", yield) if block_given?
end
end
+ private :config_accessor
end
# Reads and writes attributes from a configuration <tt>OrderedHash</tt>.
diff --git a/activesupport/lib/active_support/core_ext.rb b/activesupport/lib/active_support/core_ext.rb
index 199aa91020..52706c3d7a 100644
--- a/activesupport/lib/active_support/core_ext.rb
+++ b/activesupport/lib/active_support/core_ext.rb
@@ -1,3 +1,4 @@
-Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"].each do |path|
+DEPRECATED_FILES = ["#{File.dirname(__FILE__)}/core_ext/struct.rb"]
+(Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"] - DEPRECATED_FILES).each do |path|
require path
end
diff --git a/activesupport/lib/active_support/core_ext/array.rb b/activesupport/lib/active_support/core_ext/array.rb
index 7d0c1e4c8d..7551551bd7 100644
--- a/activesupport/lib/active_support/core_ext/array.rb
+++ b/activesupport/lib/active_support/core_ext/array.rb
@@ -4,3 +4,4 @@ require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/array/grouping'
require 'active_support/core_ext/array/prepend_and_append'
+require 'active_support/core_ext/array/inquiry'
diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb
index 45b89d2705..3177d8498e 100644
--- a/activesupport/lib/active_support/core_ext/array/access.rb
+++ b/activesupport/lib/active_support/core_ext/array/access.rb
@@ -21,12 +21,24 @@ class Array
# %w( a b c ).to(-10) # => []
def to(position)
if position >= 0
- first position + 1
+ take position + 1
else
self[0..position]
end
end
+ # Returns a copy of the Array without the specified elements.
+ #
+ # people = ["David", "Rafael", "Aaron", "Todd"]
+ # people.without "Aaron", "Todd"
+ # => ["David", "Rafael"]
+ #
+ # Note: This is an optimization of `Enumerable#without` that uses `Array#-`
+ # instead of `Array#reject` for performance reasons.
+ def without(*elements)
+ self - elements
+ end
+
# Equal to <tt>self[1]</tt>.
#
# %w( a b c d e ).second # => "b"
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index 76ffd23ed1..d80df21e7d 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -74,7 +74,7 @@ class Array
when 0
''
when 1
- self[0].to_s.dup
+ "#{self[0]}"
when 2
"#{self[0]}#{options[:two_words_connector]}#{self[1]}"
else
@@ -92,7 +92,7 @@ class Array
if empty?
'null'
else
- collect { |element| element.id }.join(',')
+ collect(&:id).join(',')
end
else
to_default_s
diff --git a/activesupport/lib/active_support/core_ext/array/inquiry.rb b/activesupport/lib/active_support/core_ext/array/inquiry.rb
new file mode 100644
index 0000000000..de623c466c
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/array/inquiry.rb
@@ -0,0 +1,15 @@
+class Array
+ # Wraps the array in an +ArrayInquirer+ object, which gives a friendlier way
+ # to check its string-like contents.
+ #
+ # pets = [:cat, :dog].inquiry
+ #
+ # pets.cat? # => true
+ # pets.ferret? # => false
+ #
+ # pets.any?(:cat, :ferret) # => true
+ # pets.any?(:ferret, :alligator) # => false
+ def inquiry
+ ActiveSupport::ArrayInquirer.new(self)
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
index 843c592669..234283e792 100644
--- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
@@ -3,14 +3,13 @@ require 'bigdecimal/util'
class BigDecimal
DEFAULT_STRING_FORMAT = 'F'
- def to_formatted_s(*args)
- if args[0].is_a?(Symbol)
- super
+ alias_method :to_default_s, :to_s
+
+ def to_s(format = nil, options = nil)
+ if format.is_a?(Symbol)
+ to_formatted_s(format, options || {})
else
- format = args[0] || DEFAULT_STRING_FORMAT
- _original_to_s(format)
+ to_default_s(format || DEFAULT_STRING_FORMAT)
end
end
- alias_method :_original_to_s, :to_s
- alias_method :to_s, :to_formatted_s
end
diff --git a/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb
deleted file mode 100644
index 46ba93ead4..0000000000
--- a/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-ActiveSupport::Deprecation.warn 'core_ext/big_decimal/yaml_conversions is deprecated and will be removed in the future.'
-
-require 'bigdecimal'
-require 'yaml'
-require 'active_support/core_ext/big_decimal/conversions'
-
-class BigDecimal
- YAML_MAPPING = { 'Infinity' => '.Inf', '-Infinity' => '-.Inf', 'NaN' => '.NaN' }
-
- def encode_with(coder)
- string = to_s
- coder.represent_scalar(nil, YAML_MAPPING[string] || string)
- end
-end
diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb
index f2a221c396..f2b7bb3ef1 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute.rb
@@ -116,12 +116,4 @@ class Class
attr_writer name if instance_writer
end
end
-
- private
-
- unless respond_to?(:singleton_class?)
- def singleton_class?
- ancestors.first != self
- end
- end
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 b85e49aca5..9525c10112 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
@@ -9,15 +9,26 @@ module DateAndTime
:saturday => 5,
:sunday => 6
}
+ WEEKEND_DAYS = [ 6, 0 ]
# Returns a new date/time representing yesterday.
def yesterday
- advance(:days => -1)
+ advance(days: -1)
+ end
+
+ # Returns a new date/time representing the previous day.
+ def prev_day
+ advance(days: -1)
end
# Returns a new date/time representing tomorrow.
def tomorrow
- advance(:days => 1)
+ advance(days: 1)
+ end
+
+ # Returns a new date/time representing the next day.
+ def next_day
+ advance(days: 1)
end
# Returns true if the date/time is today.
@@ -35,6 +46,11 @@ module DateAndTime
self > self.class.current
end
+ # Returns true if the date/time falls on a Saturday or Sunday.
+ def on_weekend?
+ WEEKEND_DAYS.include?(wday)
+ end
+
# Returns a new date/time the specified number of days ago.
def days_ago(days)
advance(:days => -days)
@@ -111,9 +127,19 @@ module DateAndTime
# Returns a new date/time representing the given day in the next week.
# 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.
- def next_week(given_day_in_next_week = Date.beginning_of_week)
- first_hour(weeks_since(1).beginning_of_week.days_since(days_span(given_day_in_next_week)))
+ # when set. +DateTime+ objects have their time set to 0:00 unless +same_time+ is true.
+ 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
+ end
+
+ # Returns a new date/time representing the next weekday.
+ def next_weekday
+ if next_day.on_weekend?
+ next_week(:monday, same_time: true)
+ else
+ next_day
+ end
end
# Short-hand for months_since(1).
@@ -134,12 +160,23 @@ module DateAndTime
# Returns a new date/time representing the given day in the previous week.
# Week is assumed to start on +start_day+, default is
# +Date.beginning_of_week+ or +config.beginning_of_week+ when set.
- # DateTime objects have their time set to 0:00.
- def prev_week(start_day = Date.beginning_of_week)
- first_hour(weeks_ago(1).beginning_of_week.days_since(days_span(start_day)))
+ # DateTime objects have their time set to 0:00 unless +same_time+ is true.
+ def prev_week(start_day = Date.beginning_of_week, same_time: false)
+ result = first_hour(weeks_ago(1).beginning_of_week.days_since(days_span(start_day)))
+ same_time ? copy_time_to(result) : result
end
alias_method :last_week, :prev_week
+ # Returns a new date/time representing the previous weekday.
+ def prev_weekday
+ if prev_day.on_weekend?
+ copy_time_to(beginning_of_week(:friday))
+ else
+ prev_day
+ end
+ end
+ alias_method :last_weekday, :prev_weekday
+
# Short-hand for months_ago(1).
def prev_month
months_ago(1)
@@ -235,17 +272,20 @@ module DateAndTime
end
private
+ def first_hour(date_or_time)
+ date_or_time.acts_like?(:time) ? date_or_time.beginning_of_day : date_or_time
+ end
- def first_hour(date_or_time)
- date_or_time.acts_like?(:time) ? date_or_time.beginning_of_day : date_or_time
- end
+ def last_hour(date_or_time)
+ date_or_time.acts_like?(:time) ? date_or_time.end_of_day : date_or_time
+ end
- def last_hour(date_or_time)
- date_or_time.acts_like?(:time) ? date_or_time.end_of_day : date_or_time
- end
+ def days_span(day)
+ (DAYS_INTO_WEEK[day] - DAYS_INTO_WEEK[Date.beginning_of_week]) % 7
+ end
- def days_span(day)
- (DAYS_INTO_WEEK[day] - DAYS_INTO_WEEK[Date.beginning_of_week]) % 7
- end
+ def copy_time_to(other)
+ other.change(hour: hour, min: min, sec: sec, usec: try(:usec))
+ end
end
end
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 dc4e767e9d..55ad384f4f 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -10,7 +10,11 @@ class DateTime
end
end
- # Seconds since midnight: DateTime.now.seconds_since_midnight.
+ # Returns the number of seconds since 00:00:00.
+ #
+ # DateTime.new(2012, 8, 29, 0, 0, 0).seconds_since_midnight # => 0
+ # DateTime.new(2012, 8, 29, 12, 34, 56).seconds_since_midnight # => 45296
+ # DateTime.new(2012, 8, 29, 23, 59, 59).seconds_since_midnight # => 86399
def seconds_since_midnight
sec + (min * 60) + (hour * 3600)
end
diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb
index 1343beb87a..7a893292b3 100644
--- a/activesupport/lib/active_support/core_ext/enumerable.rb
+++ b/activesupport/lib/active_support/core_ext/enumerable.rb
@@ -60,6 +60,17 @@ module Enumerable
def exclude?(object)
!include?(object)
end
+
+ # Returns a copy of the enumerable without the specified elements.
+ #
+ # ["David", "Rafael", "Aaron", "Todd"].without "Aaron", "Todd"
+ # => ["David", "Rafael"]
+ #
+ # {foo: 1, bar: 2, baz: 3}.without :bar
+ # => {foo: 1, baz: 3}
+ def without(*elements)
+ reject { |element| elements.include?(element) }
+ end
end
class Range #:nodoc:
diff --git a/activesupport/lib/active_support/core_ext/file/atomic.rb b/activesupport/lib/active_support/core_ext/file/atomic.rb
index 38374af388..fad6fa8d9d 100644
--- a/activesupport/lib/active_support/core_ext/file/atomic.rb
+++ b/activesupport/lib/active_support/core_ext/file/atomic.rb
@@ -20,7 +20,7 @@ class File
temp_file = Tempfile.new(basename(file_name), temp_dir)
temp_file.binmode
- yield temp_file
+ return_val = yield temp_file
temp_file.close
if File.exist?(file_name)
@@ -40,6 +40,9 @@ class File
chown(old_stat.uid, old_stat.gid, file_name)
# This operation will affect filesystem ACL's
chmod(old_stat.mode, file_name)
+
+ # Make sure we return the result of the yielded block
+ return_val
rescue Errno::EPERM, Errno::EACCES
# Changing file ownership failed, moving on.
end
diff --git a/activesupport/lib/active_support/core_ext/hash/compact.rb b/activesupport/lib/active_support/core_ext/hash/compact.rb
index 6566215a4d..5dc9a05ec7 100644
--- a/activesupport/lib/active_support/core_ext/hash/compact.rb
+++ b/activesupport/lib/active_support/core_ext/hash/compact.rb
@@ -1,6 +1,6 @@
class Hash
# Returns a hash with non +nil+ values.
- #
+ #
# hash = { a: true, b: false, c: nil}
# hash.compact # => { a: true, b: false}
# hash # => { a: true, b: false, c: nil}
@@ -8,9 +8,9 @@ class Hash
def compact
self.select { |_, value| !value.nil? }
end
-
+
# Replaces current hash with non +nil+ values.
- #
+ #
# hash = { a: true, b: false, c: nil}
# hash.compact! # => { a: true, b: false}
# hash # => { a: true, b: false}
diff --git a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
index 763d563231..9c9faf67ea 100644
--- a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
+++ b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
@@ -4,7 +4,7 @@ class Hash
# h1 = { a: true, b: { c: [1, 2, 3] } }
# h2 = { a: false, b: { x: [3, 4, 5] } }
#
- # h1.deep_merge(h2) #=> { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } }
+ # h1.deep_merge(h2) # => { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } }
#
# Like with Hash#merge in the standard library, a block can be provided
# to merge values:
diff --git a/activesupport/lib/active_support/core_ext/hash/except.rb b/activesupport/lib/active_support/core_ext/hash/except.rb
index 682d089881..6e397abf51 100644
--- a/activesupport/lib/active_support/core_ext/hash/except.rb
+++ b/activesupport/lib/active_support/core_ext/hash/except.rb
@@ -1,13 +1,19 @@
class Hash
- # Returns a hash that includes everything but the given keys. This is useful for
- # limiting a set of parameters to everything but a few known toggles:
+ # 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}
#
+ # This is useful for limiting a set of parameters to everything but a few known toggles:
# @person.update(params[:person].except(:admin))
def except(*keys)
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 }
def except!(*keys)
keys.each { |key| delete(key) }
self
diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb
index f4105f66b0..c30044b9ff 100644
--- a/activesupport/lib/active_support/core_ext/hash/keys.rb
+++ b/activesupport/lib/active_support/core_ext/hash/keys.rb
@@ -14,7 +14,7 @@ class Hash
result
end
- # Destructively convert all keys using the block operations.
+ # Destructively converts all keys using the block operations.
# Same as transform_keys but modifies +self+.
def transform_keys!
return enum_for(:transform_keys!) unless block_given?
@@ -31,13 +31,13 @@ class Hash
# hash.stringify_keys
# # => {"name"=>"Rob", "age"=>"28"}
def stringify_keys
- transform_keys{ |key| key.to_s }
+ transform_keys(&:to_s)
end
- # Destructively convert all keys to strings. Same as
+ # Destructively converts all keys to strings. Same as
# +stringify_keys+, but modifies +self+.
def stringify_keys!
- transform_keys!{ |key| key.to_s }
+ transform_keys!(&:to_s)
end
# Returns a new hash with all keys converted to symbols, as long as
@@ -52,14 +52,14 @@ class Hash
end
alias_method :to_options, :symbolize_keys
- # Destructively convert all keys to symbols, as long as they respond
+ # Destructively converts all keys to symbols, as long as they respond
# to +to_sym+. Same as +symbolize_keys+, but modifies +self+.
def symbolize_keys!
transform_keys!{ |key| key.to_sym rescue key }
end
alias_method :to_options!, :symbolize_keys!
- # Validate all keys in a hash match <tt>*valid_keys</tt>, raising
+ # Validates all keys in a hash match <tt>*valid_keys</tt>, raising
# ArgumentError on a mismatch.
#
# Note that keys are treated differently than HashWithIndifferentAccess,
@@ -89,7 +89,7 @@ class Hash
_deep_transform_keys_in_object(self, &block)
end
- # Destructively convert all keys by using the block operation.
+ # Destructively converts all keys by using the block operation.
# This includes the keys from the root hash and from all
# nested hashes and arrays.
def deep_transform_keys!(&block)
@@ -105,14 +105,14 @@ class Hash
# hash.deep_stringify_keys
# # => {"person"=>{"name"=>"Rob", "age"=>"28"}}
def deep_stringify_keys
- deep_transform_keys{ |key| key.to_s }
+ deep_transform_keys(&:to_s)
end
- # Destructively convert all keys to strings.
+ # Destructively converts all keys to strings.
# This includes the keys from the root hash and from all
# nested hashes and arrays.
def deep_stringify_keys!
- deep_transform_keys!{ |key| key.to_s }
+ deep_transform_keys!(&:to_s)
end
# Returns a new hash with all keys converted to symbols, as long as
@@ -127,7 +127,7 @@ class Hash
deep_transform_keys{ |key| key.to_sym rescue key }
end
- # Destructively convert all keys to symbols, as long as they respond
+ # Destructively converts all keys to symbols, as long as they respond
# to +to_sym+. This includes the keys from the root hash and from all
# nested hashes and arrays.
def deep_symbolize_keys!
diff --git a/activesupport/lib/active_support/core_ext/hash/slice.rb b/activesupport/lib/active_support/core_ext/hash/slice.rb
index 41b2279013..1d5f38231a 100644
--- a/activesupport/lib/active_support/core_ext/hash/slice.rb
+++ b/activesupport/lib/active_support/core_ext/hash/slice.rb
@@ -1,5 +1,5 @@
class Hash
- # Slice a hash to include only the given keys. Returns a hash containing
+ # Slices a hash to include only the given keys. Returns a hash containing
# the given keys.
#
# { a: 1, b: 2, c: 3, d: 4 }.slice(:a, :b)
diff --git a/activesupport/lib/active_support/core_ext/integer/time.rb b/activesupport/lib/active_support/core_ext/integer/time.rb
index 82080ffe51..f0b7382ef3 100644
--- a/activesupport/lib/active_support/core_ext/integer/time.rb
+++ b/activesupport/lib/active_support/core_ext/integer/time.rb
@@ -17,21 +17,6 @@ class Integer
#
# # equivalent to Time.now.advance(months: 4, years: 5)
# (4.months + 5.years).from_now
- #
- # While these methods provide precise calculation when used as in the examples
- # above, care should be taken to note that this is not true if the result of
- # +months+, +years+, etc is converted before use:
- #
- # # equivalent to 30.days.to_i.from_now
- # 1.month.to_i.from_now
- #
- # # equivalent to 365.25.days.to_f.from_now
- # 1.year.to_f.from_now
- #
- # In such cases, Ruby's core
- # Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
- # Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
- # date and time arithmetic.
def months
ActiveSupport::Duration.new(self * 30.days, [[:months, self]])
end
diff --git a/activesupport/lib/active_support/core_ext/kernel.rb b/activesupport/lib/active_support/core_ext/kernel.rb
index 293a3b2619..364ed9d65f 100644
--- a/activesupport/lib/active_support/core_ext/kernel.rb
+++ b/activesupport/lib/active_support/core_ext/kernel.rb
@@ -1,5 +1,4 @@
require 'active_support/core_ext/kernel/agnostics'
require 'active_support/core_ext/kernel/concern'
-require 'active_support/core_ext/kernel/debugger' if RUBY_VERSION < '2.0.0'
require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/kernel/singleton_class'
diff --git a/activesupport/lib/active_support/core_ext/kernel/debugger.rb b/activesupport/lib/active_support/core_ext/kernel/debugger.rb
index 2073cac98d..1fde3db070 100644
--- a/activesupport/lib/active_support/core_ext/kernel/debugger.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/debugger.rb
@@ -1,10 +1,3 @@
-module Kernel
- unless respond_to?(:debugger)
- # Starts a debugging session if the +debugger+ gem has been loaded (call rails server --debugger to do load it).
- def debugger
- message = "\n***** Debugger requested, but was not available (ensure the debugger gem is listed in Gemfile/installed as gem): Start server with --debugger to enable *****\n"
- defined?(Rails) ? Rails.logger.info(message) : $stderr.puts(message)
- end
- alias breakpoint debugger unless respond_to?(:breakpoint)
- end
-end
+require 'active_support/deprecation'
+
+ActiveSupport::Deprecation.warn("This file is deprecated and will be removed in Rails 5.1 with no replacement.")
diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
index f5179552bb..9189e6d977 100644
--- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
@@ -1,4 +1,3 @@
-require 'rbconfig'
require 'tempfile'
module Kernel
@@ -29,34 +28,6 @@ module Kernel
$VERBOSE = old_verbose
end
- # For compatibility
- def silence_stderr #:nodoc:
- ActiveSupport::Deprecation.warn(
- "`#silence_stderr` is deprecated and will be removed in the next release."
- ) #not thread-safe
- silence_stream(STDERR) { yield }
- end
-
- # Deprecated : this method is not thread safe
- # Silences any stream for the duration of the block.
- #
- # silence_stream(STDOUT) do
- # puts 'This will never be seen'
- # end
- #
- # puts 'But this will'
- #
- # This method is not thread-safe.
- def silence_stream(stream)
- old_stream = stream.dup
- stream.reopen(RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? 'NUL:' : '/dev/null')
- stream.sync = true
- yield
- ensure
- stream.reopen(old_stream)
- old_stream.close
- end
-
# Blocks and ignores any exception passed as argument if raised within the block.
#
# suppress(ZeroDivisionError) do
@@ -69,56 +40,4 @@ module Kernel
yield
rescue *exception_classes
end
-
- # Captures the given stream and returns it:
- #
- # stream = capture(:stdout) { puts 'notice' }
- # stream # => "notice\n"
- #
- # stream = capture(:stderr) { warn 'error' }
- # stream # => "error\n"
- #
- # even for subprocesses:
- #
- # stream = capture(:stdout) { system('echo notice') }
- # stream # => "notice\n"
- #
- # stream = capture(:stderr) { system('echo error 1>&2') }
- # stream # => "error\n"
- def capture(stream)
- ActiveSupport::Deprecation.warn(
- "`#capture(stream)` is deprecated and will be removed in the next release."
- ) #not thread-safe
- stream = stream.to_s
- captured_stream = Tempfile.new(stream)
- stream_io = eval("$#{stream}")
- origin_stream = stream_io.dup
- stream_io.reopen(captured_stream)
-
- yield
-
- stream_io.rewind
- return captured_stream.read
- ensure
- captured_stream.close
- captured_stream.unlink
- stream_io.reopen(origin_stream)
- end
- alias :silence :capture
-
- # Silences both STDOUT and STDERR, even for subprocesses.
- #
- # quietly { system 'bundle install' }
- #
- # This method is not thread-safe.
- def quietly
- ActiveSupport::Deprecation.warn(
- "`#quietly` is deprecated and will be removed in the next release."
- ) #not thread-safe
- silence_stream(STDOUT) do
- silence_stream(STDERR) do
- yield
- end
- end
- end
end
diff --git a/activesupport/lib/active_support/core_ext/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb
index 768b980f21..d9fb392752 100644
--- a/activesupport/lib/active_support/core_ext/load_error.rb
+++ b/activesupport/lib/active_support/core_ext/load_error.rb
@@ -1,3 +1,5 @@
+require 'active_support/deprecation/proxy_wrappers'
+
class LoadError
REGEXPS = [
/^no such file to load -- (.+)$/i,
@@ -25,4 +27,4 @@ class LoadError
end
end
-MissingSourceFile = LoadError
+MissingSourceFile = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('MissingSourceFile', 'LoadError')
diff --git a/activesupport/lib/active_support/core_ext/marshal.rb b/activesupport/lib/active_support/core_ext/marshal.rb
index 56c79c04bd..20a0856e71 100644
--- a/activesupport/lib/active_support/core_ext/marshal.rb
+++ b/activesupport/lib/active_support/core_ext/marshal.rb
@@ -1,9 +1,7 @@
-require 'active_support/core_ext/module/aliasing'
-
-module Marshal
- class << self
- def load_with_autoloading(source)
- load_without_autoloading(source)
+module ActiveSupport
+ module MarshalWithAutoloading # :nodoc:
+ def load(source)
+ super(source)
rescue ArgumentError, NameError => exc
if exc.message.match(%r|undefined class/module (.+)|)
# try loading the class/module
@@ -15,7 +13,7 @@ module Marshal
raise exc
end
end
-
- alias_method_chain :load, :autoloading
end
end
+
+Marshal.singleton_class.prepend(ActiveSupport::MarshalWithAutoloading)
diff --git a/activesupport/lib/active_support/core_ext/module/aliasing.rb b/activesupport/lib/active_support/core_ext/module/aliasing.rb
index 0a6fadf928..a4c40b25ff 100644
--- a/activesupport/lib/active_support/core_ext/module/aliasing.rb
+++ b/activesupport/lib/active_support/core_ext/module/aliasing.rb
@@ -21,6 +21,8 @@ class Module
#
# so you can safely chain foo, foo?, foo! and/or foo= with the same feature.
def alias_method_chain(target, feature)
+ ActiveSupport::Deprecation.warn("alias_method_chain is deprecated. Please, use Module#prepend instead. From module, you can access the original method using super.")
+
# Strip out punctuation on predicates, bang or writer methods since
# e.g. target?_without_feature is not a valid method name.
aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
@@ -43,7 +45,7 @@ class Module
end
# Allows you to make aliases for attributes, which includes
- # getter, setter, and query methods.
+ # getter, setter, and a predicate.
#
# class Content < ActiveRecord::Base
# # has a title attribute
diff --git a/activesupport/lib/active_support/core_ext/module/anonymous.rb b/activesupport/lib/active_support/core_ext/module/anonymous.rb
index b0c7b021db..0ecc67a855 100644
--- a/activesupport/lib/active_support/core_ext/module/anonymous.rb
+++ b/activesupport/lib/active_support/core_ext/module/anonymous.rb
@@ -7,6 +7,13 @@ class Module
# m = Module.new
# m.name # => nil
#
+ # +anonymous?+ method returns true if module does not have a name:
+ #
+ # Module.new.anonymous? # => true
+ #
+ # module M; end
+ # M.anonymous? # => false
+ #
# A module gets a name when it is first assigned to a constant. Either
# via the +module+ or +class+ keyword or by an explicit assignment:
#
diff --git a/activesupport/lib/active_support/core_ext/module/attr_internal.rb b/activesupport/lib/active_support/core_ext/module/attr_internal.rb
index 67f0e0335d..93fb598650 100644
--- a/activesupport/lib/active_support/core_ext/module/attr_internal.rb
+++ b/activesupport/lib/active_support/core_ext/module/attr_internal.rb
@@ -27,11 +27,8 @@ class Module
def attr_internal_define(attr_name, type)
internal_name = attr_internal_ivar_name(attr_name).sub(/\A@/, '')
- # class_eval is necessary on 1.9 or else the methods are made private
- class_eval do
- # use native attr_* methods as they are faster on some Ruby implementations
- send("attr_#{type}", internal_name)
- end
+ # use native attr_* methods as they are faster on some Ruby implementations
+ send("attr_#{type}", internal_name)
attr_name, internal_name = "#{attr_name}=", "#{internal_name}=" if type == :writer
alias_method attr_name, internal_name
remove_method internal_name
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 d317df5079..d4e6b5a1ac 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -203,7 +203,7 @@ class Module
# include HairColors
# end
#
- # Person.class_variable_get("@@hair_colors") #=> [:brown, :black, :blonde, :red]
+ # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
def mattr_accessor(*syms, &blk)
mattr_reader(*syms, &blk)
mattr_writer(*syms, &blk)
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 570585b89a..9b7a429db9 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -17,7 +17,7 @@ class Module
# ==== Options
# * <tt>:to</tt> - Specifies the target object
# * <tt>:prefix</tt> - Prefixes the new method with the target name or a custom prefix
- # * <tt>:allow_nil</tt> - if set to true, prevents a +NoMethodError+ to be raised
+ # * <tt>:allow_nil</tt> - if set to true, prevents a +NoMethodError+ from being raised
#
# The macro receives one or more method names (specified as symbols or
# strings) and the name of the target object via the <tt>:to</tt> option
@@ -167,7 +167,7 @@ class Module
''
end
- file, line = caller.first.split(':', 2)
+ file, line = caller(1, 1).first.split(':', 2)
line = line.to_i
to = to.to_s
@@ -185,19 +185,31 @@ class Module
# On the other hand it could be that the target has side-effects,
# whereas conceptually, from the user point of view, the delegator should
# be doing one call.
-
- exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
-
- method_def = [
- "def #{method_prefix}#{method}(#{definition})",
- " _ = #{to}",
- " if !_.nil? || nil.respond_to?(:#{method})",
- " _.#{method}(#{definition})",
- " else",
- " #{exception unless allow_nil}",
- " end",
+ if allow_nil
+ method_def = [
+ "def #{method_prefix}#{method}(#{definition})",
+ "_ = #{to}",
+ "if !_.nil? || nil.respond_to?(:#{method})",
+ " _.#{method}(#{definition})",
+ "end",
"end"
- ].join ';'
+ ].join ';'
+ else
+ exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
+
+ method_def = [
+ "def #{method_prefix}#{method}(#{definition})",
+ " _ = #{to}",
+ " _.#{method}(#{definition})",
+ "rescue NoMethodError => e",
+ " if _.nil? && e.name == :#{method}",
+ " #{exception}",
+ " else",
+ " raise",
+ " end",
+ "end"
+ ].join ';'
+ end
module_eval(method_def, file, line)
end
diff --git a/activesupport/lib/active_support/core_ext/module/method_transplanting.rb b/activesupport/lib/active_support/core_ext/module/method_transplanting.rb
index b1097cc83b..1fde3db070 100644
--- a/activesupport/lib/active_support/core_ext/module/method_transplanting.rb
+++ b/activesupport/lib/active_support/core_ext/module/method_transplanting.rb
@@ -1,11 +1,3 @@
-class Module
- ###
- # TODO: remove this after 1.9 support is dropped
- def methods_transplantable? # :nodoc:
- x = Module.new { def foo; end }
- Module.new { define_method :bar, x.instance_method(:foo) }
- true
- rescue TypeError
- false
- end
-end
+require 'active_support/deprecation'
+
+ActiveSupport::Deprecation.warn("This file is deprecated and will be removed in Rails 5.1 with no replacement.")
diff --git a/activesupport/lib/active_support/core_ext/module/remove_method.rb b/activesupport/lib/active_support/core_ext/module/remove_method.rb
index 719071d1c2..52632d2c6b 100644
--- a/activesupport/lib/active_support/core_ext/module/remove_method.rb
+++ b/activesupport/lib/active_support/core_ext/module/remove_method.rb
@@ -1,10 +1,13 @@
class Module
+ # Removes the named method, if it exists.
def remove_possible_method(method)
if method_defined?(method) || private_method_defined?(method)
undef_method(method)
end
end
+ # Replaces the existing method definition, if there is one, with the passed
+ # block as its body.
def redefine_method(method, &block)
remove_possible_method(method)
define_method(method, &block)
diff --git a/activesupport/lib/active_support/core_ext/name_error.rb b/activesupport/lib/active_support/core_ext/name_error.rb
index e1ebd4f91c..6b447d772b 100644
--- a/activesupport/lib/active_support/core_ext/name_error.rb
+++ b/activesupport/lib/active_support/core_ext/name_error.rb
@@ -1,5 +1,12 @@
class NameError
# Extract the name of the missing constant from the exception message.
+ #
+ # begin
+ # HelloWorld
+ # rescue NameError => e
+ # e.missing_name
+ # end
+ # # => "HelloWorld"
def missing_name
if /undefined local variable or method/ !~ message
$1 if /((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/ =~ message
@@ -7,10 +14,16 @@ class NameError
end
# Was this exception raised because the given name was missing?
+ #
+ # begin
+ # HelloWorld
+ # rescue NameError => e
+ # e.missing_name?("HelloWorld")
+ # end
+ # # => true
def missing_name?(name)
if name.is_a? Symbol
- last_name = (missing_name || '').split('::').last
- last_name == name.to_s
+ self.name == name
else
missing_name == name.to_s
end
diff --git a/activesupport/lib/active_support/core_ext/numeric/bytes.rb b/activesupport/lib/active_support/core_ext/numeric/bytes.rb
index deea8e9358..dfbca32474 100644
--- a/activesupport/lib/active_support/core_ext/numeric/bytes.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/bytes.rb
@@ -7,36 +7,56 @@ class Numeric
EXABYTE = PETABYTE * 1024
# Enables the use of byte calculations and declarations, like 45.bytes + 2.6.megabytes
+ #
+ # 2.bytes # => 2
def bytes
self
end
alias :byte :bytes
+ # Returns the number of bytes equivalent to the kilobytes provided.
+ #
+ # 2.kilobytes # => 2048
def kilobytes
self * KILOBYTE
end
alias :kilobyte :kilobytes
+ # Returns the number of bytes equivalent to the megabytes provided.
+ #
+ # 2.megabytes # => 2_097_152
def megabytes
self * MEGABYTE
end
alias :megabyte :megabytes
+ # Returns the number of bytes equivalent to the gigabytes provided.
+ #
+ # 2.gigabytes # => 2_147_483_648
def gigabytes
self * GIGABYTE
end
alias :gigabyte :gigabytes
+ # Returns the number of bytes equivalent to the terabytes provided.
+ #
+ # 2.terabytes # => 2_199_023_255_552
def terabytes
self * TERABYTE
end
alias :terabyte :terabytes
+ # Returns the number of bytes equivalent to the petabytes provided.
+ #
+ # 2.petabytes # => 2_251_799_813_685_248
def petabytes
self * PETABYTE
end
alias :petabyte :petabytes
+ # Returns the number of bytes equivalent to the exabytes provided.
+ #
+ # 2.exabytes # => 2_305_843_009_213_693_952
def exabytes
self * EXABYTE
end
diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
index 6d3635c69a..0c8ff79237 100644
--- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
@@ -118,18 +118,28 @@ class Numeric
end
end
- [Float, Fixnum, Bignum, BigDecimal].each do |klass|
- klass.send(:alias_method, :to_default_s, :to_s)
-
- klass.send(:define_method, :to_s) do |*args|
- if args[0].is_a?(Symbol)
- format = args[0]
- options = args[1] || {}
+ [Fixnum, Bignum].each do |klass|
+ klass.class_eval do
+ alias_method :to_default_s, :to_s
+ def to_s(base_or_format = 10, options = nil)
+ if base_or_format.is_a?(Symbol)
+ to_formatted_s(base_or_format, options || {})
+ else
+ to_default_s(base_or_format)
+ end
+ end
+ end
+ end
- self.to_formatted_s(format, options)
+ Float.class_eval do
+ alias_method :to_default_s, :to_s
+ def to_s(*args)
+ if args.empty?
+ to_default_s
else
- to_default_s(*args)
+ to_formatted_s(*args)
end
end
end
+
end
diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb
index 689fae4830..6c4a975495 100644
--- a/activesupport/lib/active_support/core_ext/numeric/time.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/time.rb
@@ -1,6 +1,8 @@
require 'active_support/duration'
require 'active_support/core_ext/time/calculations'
require 'active_support/core_ext/time/acts_like'
+require 'active_support/core_ext/date/calculations'
+require 'active_support/core_ext/date/acts_like'
class Numeric
# Enables the use of time calculations and declarations, like 45.minutes + 2.hours + 4.years.
@@ -16,53 +18,56 @@ class Numeric
#
# # equivalent to Time.current.advance(months: 4, years: 5)
# (4.months + 5.years).from_now
- #
- # While these methods provide precise calculation when used as in the examples above, care
- # should be taken to note that this is not true if the result of `months', `years', etc is
- # converted before use:
- #
- # # equivalent to 30.days.to_i.from_now
- # 1.month.to_i.from_now
- #
- # # equivalent to 365.25.days.to_f.from_now
- # 1.year.to_f.from_now
- #
- # In such cases, Ruby's core
- # Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
- # Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
- # date and time arithmetic.
def seconds
ActiveSupport::Duration.new(self, [[:seconds, self]])
end
alias :second :seconds
+ # Returns a Duration instance matching the number of minutes provided.
+ #
+ # 2.minutes # => 120 seconds
def minutes
ActiveSupport::Duration.new(self * 60, [[:seconds, self * 60]])
end
alias :minute :minutes
+ # Returns a Duration instance matching the number of hours provided.
+ #
+ # 2.hours # => 7_200 seconds
def hours
ActiveSupport::Duration.new(self * 3600, [[:seconds, self * 3600]])
end
alias :hour :hours
+ # Returns a Duration instance matching the number of days provided.
+ #
+ # 2.days # => 2 days
def days
ActiveSupport::Duration.new(self * 24.hours, [[:days, self]])
end
alias :day :days
+ # Returns a Duration instance matching the number of weeks provided.
+ #
+ # 2.weeks # => 14 days
def weeks
ActiveSupport::Duration.new(self * 7.days, [[:days, self * 7]])
end
alias :week :weeks
+ # Returns a Duration instance matching the number of fortnights provided.
+ #
+ # 2.fortnights # => 28 days
def fortnights
ActiveSupport::Duration.new(self * 2.weeks, [[:days, self * 14]])
end
alias :fortnight :fortnights
+ # Returns the number of milliseconds equivalent to the seconds provided.
# Used with the standard time durations, like 1.hour.in_milliseconds --
# so we can feed them to JavaScript functions like getTime().
+ #
+ # 2.in_milliseconds # => 2_000
def in_milliseconds
self * 1000
end
diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb
index f1106cca9b..f4f9152d6a 100644
--- a/activesupport/lib/active_support/core_ext/object.rb
+++ b/activesupport/lib/active_support/core_ext/object.rb
@@ -2,7 +2,6 @@ require 'active_support/core_ext/object/acts_like'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/object/deep_dup'
-require 'active_support/core_ext/object/itself'
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/object/inclusion'
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 2e99f4a1b8..0191d2e973 100644
--- a/activesupport/lib/active_support/core_ext/object/deep_dup.rb
+++ b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
@@ -25,7 +25,7 @@ class Array
# array[1][2] # => nil
# dup[1][2] # => 4
def deep_dup
- map { |it| it.deep_dup }
+ map(&: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 665cb0f96d..620f7b6561 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 and BigDecimal(in 1.9.x) objects;
+ # False for +nil+, +false+, +true+, symbol, number objects;
# true otherwise.
def duplicable?
true
@@ -78,17 +78,8 @@ end
require 'bigdecimal'
class BigDecimal
- # Needed to support Ruby 1.9.x, as it doesn't allow dup on BigDecimal, instead
- # raises TypeError exception. Checking here on the runtime whether BigDecimal
- # will allow dup or not.
- begin
- BigDecimal.new('4.56').dup
-
- def duplicable?
- true
- end
- rescue TypeError
- # can't dup, so use superclass implementation
+ def duplicable?
+ true
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/instance_variables.rb b/activesupport/lib/active_support/core_ext/object/instance_variables.rb
index 755e1c6b16..593a7a4940 100644
--- a/activesupport/lib/active_support/core_ext/object/instance_variables.rb
+++ b/activesupport/lib/active_support/core_ext/object/instance_variables.rb
@@ -23,6 +23,6 @@ class Object
#
# C.new(0, 1).instance_variable_names # => ["@y", "@x"]
def instance_variable_names
- instance_variables.map { |var| var.to_s }
+ instance_variables.map(&:to_s)
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/itself.rb b/activesupport/lib/active_support/core_ext/object/itself.rb
deleted file mode 100644
index d71cea6674..0000000000
--- a/activesupport/lib/active_support/core_ext/object/itself.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-class Object
- # TODO: Remove this file when we drop support for Ruby < 2.2
- unless respond_to?(:itself)
- # Returns the object itself.
- #
- # Useful for chaining methods, such as Active Record scopes:
- #
- # Event.public_send(state.presence_in([ :trashed, :drafted ]) || :itself).order(:created_at)
- #
- # @return Object
- def itself
- self
- end
- end
-end
diff --git a/activesupport/lib/active_support/core_ext/object/json.rb b/activesupport/lib/active_support/core_ext/object/json.rb
index 698b2d1920..0db787010c 100644
--- a/activesupport/lib/active_support/core_ext/object/json.rb
+++ b/activesupport/lib/active_support/core_ext/object/json.rb
@@ -9,7 +9,6 @@ require 'time'
require 'active_support/core_ext/time/conversions'
require 'active_support/core_ext/date_time/conversions'
require 'active_support/core_ext/date/conversions'
-require 'active_support/core_ext/module/aliasing'
# The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting
# their default behavior. That said, we need to define the basic to_json method in all of them,
@@ -26,22 +25,25 @@ require 'active_support/core_ext/module/aliasing'
# bypassed completely. This means that as_json won't be invoked and the JSON gem will simply
# ignore any options it does not natively understand. This also means that ::JSON.{generate,dump}
# should give exactly the same results with or without active support.
-[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass, Enumerable].each do |klass|
- klass.class_eval do
- def to_json_with_active_support_encoder(options = nil)
+
+module ActiveSupport
+ module ToJsonWithActiveSupportEncoder # :nodoc:
+ def to_json(options = nil)
if options.is_a?(::JSON::State)
# Called from JSON.{generate,dump}, forward it to JSON gem's to_json
- self.to_json_without_active_support_encoder(options)
+ super(options)
else
# to_json is being invoked directly, use ActiveSupport's encoder
ActiveSupport::JSON.encode(self, options)
end
end
-
- alias_method_chain :to_json, :active_support_encoder
end
end
+[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass, Enumerable].reverse_each do |klass|
+ klass.prepend(ActiveSupport::ToJsonWithActiveSupportEncoder)
+end
+
class Object
def as_json(options = nil) #:nodoc:
if respond_to?(:to_hash)
diff --git a/activesupport/lib/active_support/core_ext/object/to_query.rb b/activesupport/lib/active_support/core_ext/object/to_query.rb
index ccd568bbf5..ec5ace4e16 100644
--- a/activesupport/lib/active_support/core_ext/object/to_query.rb
+++ b/activesupport/lib/active_support/core_ext/object/to_query.rb
@@ -38,7 +38,7 @@ class Array
# Calls <tt>to_param</tt> on all its elements and joins the result with
# slashes. This is used by <tt>url_for</tt> in Action Pack.
def to_param
- collect { |e| e.to_param }.join '/'
+ collect(&:to_param).join '/'
end
# Converts an array into a string suitable for use as a URL query string,
diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb
index 56da398978..e0f70b9caa 100644
--- a/activesupport/lib/active_support/core_ext/object/try.rb
+++ b/activesupport/lib/active_support/core_ext/object/try.rb
@@ -21,11 +21,11 @@ class Object
#
# +try+ will also return +nil+ if the receiver does not respond to the method:
#
- # @person.try(:non_existing_method) #=> nil
+ # @person.try(:non_existing_method) # => nil
#
# instead of
#
- # @person.non_existing_method if @person.respond_to?(:non_existing_method) #=> nil
+ # @person.non_existing_method if @person.respond_to?(:non_existing_method) # => nil
#
# +try+ returns +nil+ when called on +nil+ regardless of whether it responds
# to the method:
@@ -63,9 +63,12 @@ class Object
try!(*a, &b) if a.empty? || respond_to?(a.first)
end
- # Same as #try, but will raise a NoMethodError exception if the receiver is not +nil+ and
- # does not implement the tried method.
-
+ # 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?
@@ -94,6 +97,9 @@ class NilClass
nil
end
+ # Calling +try!+ on +nil+ always returns +nil+.
+ #
+ # nil.try!(:name) # => nil
def try!(*args)
nil
end
diff --git a/activesupport/lib/active_support/core_ext/range/conversions.rb b/activesupport/lib/active_support/core_ext/range/conversions.rb
index b1a12781f3..83eced50bf 100644
--- a/activesupport/lib/active_support/core_ext/range/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/range/conversions.rb
@@ -3,9 +3,24 @@ class Range
:db => Proc.new { |start, stop| "BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'" }
}
- # Gives a human readable format of the range.
+ # Convert range to a formatted string. See RANGE_FORMATS for predefined formats.
#
- # (1..100).to_formatted_s # => "1..100"
+ # This method is aliased to <tt>to_s</tt>.
+ #
+ # range = (1..100) # => 1..100
+ #
+ # range.to_formatted_s # => "1..100"
+ # range.to_s # => "1..100"
+ #
+ # range.to_formatted_s(:db) # => "BETWEEN '1' AND '100'"
+ # range.to_s(:db) # => "BETWEEN '1' AND '100'"
+ #
+ # == Adding your own range formats to to_formatted_s
+ # You can add your own formats to the Range::RANGE_FORMATS hash.
+ # Use the format name as the hash key and a Proc instance.
+ #
+ # # config/initializers/range_formats.rb
+ # Range::RANGE_FORMATS[:short] = ->(start, stop) { "Between #{start.to_s(:db)} and #{stop.to_s(:db)}" }
def to_formatted_s(format = :default)
if formatter = RANGE_FORMATS[format]
formatter.call(first, last)
diff --git a/activesupport/lib/active_support/core_ext/range/each.rb b/activesupport/lib/active_support/core_ext/range/each.rb
index ecef78f55f..f666480fe6 100644
--- a/activesupport/lib/active_support/core_ext/range/each.rb
+++ b/activesupport/lib/active_support/core_ext/range/each.rb
@@ -1,18 +1,22 @@
-require 'active_support/core_ext/module/aliasing'
-
class Range #:nodoc:
def each_with_time_with_zone(&block)
ensure_iteration_allowed
each_without_time_with_zone(&block)
end
- alias_method_chain :each, :time_with_zone
+ # 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_with_time_with_zone(n = 1, &block)
ensure_iteration_allowed
step_without_time_with_zone(n, &block)
end
- alias_method_chain :step, :time_with_zone
+ # 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
def ensure_iteration_allowed
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 3a07401c8a..9d20920dd0 100644
--- a/activesupport/lib/active_support/core_ext/range/include_range.rb
+++ b/activesupport/lib/active_support/core_ext/range/include_range.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/module/aliasing'
-
class Range
# Extends the default Range#include? to support range comparisons.
# (1..5).include?(1..5) # => true
@@ -18,6 +16,8 @@ class Range
include_without_range?(value)
end
end
-
- alias_method_chain :include?, :range
+ # 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
diff --git a/activesupport/lib/active_support/core_ext/securerandom.rb b/activesupport/lib/active_support/core_ext/securerandom.rb
new file mode 100644
index 0000000000..6cdbea1f37
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/securerandom.rb
@@ -0,0 +1,23 @@
+require 'securerandom'
+
+module SecureRandom
+ BASE58_ALPHABET = ('0'..'9').to_a + ('A'..'Z').to_a + ('a'..'z').to_a - ['0', 'O', 'I', 'l']
+ # SecureRandom.base58 generates a random base58 string.
+ #
+ # The argument _n_ specifies the length, of the random string to be generated.
+ #
+ # If _n_ is not specified or is nil, 16 is assumed. It may be larger in the future.
+ #
+ # The result may contain alphanumeric characters except 0, O, I and l
+ #
+ # p SecureRandom.base58 #=> "4kUgL2pdQMSCQtjE"
+ # p SecureRandom.base58(24) #=> "77TMHrHJFvFDwodq8w7Ev2m7"
+ #
+ def self.base58(n = 16)
+ SecureRandom.random_bytes(n).unpack("C*").map do |byte|
+ idx = byte % 64
+ idx = SecureRandom.random_number(58) if idx >= 58
+ BASE58_ALPHABET[idx]
+ end.join
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/string/behavior.rb b/activesupport/lib/active_support/core_ext/string/behavior.rb
index 4aa960039b..710f1f4670 100644
--- a/activesupport/lib/active_support/core_ext/string/behavior.rb
+++ b/activesupport/lib/active_support/core_ext/string/behavior.rb
@@ -1,5 +1,5 @@
class String
- # Enable more predictable duck-typing on String-like classes. See <tt>Object#acts_like?</tt>.
+ # Enables more predictable duck-typing on String-like classes. See <tt>Object#acts_like?</tt>.
def acts_like_string?
true
end
diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb
index 499b9b26bc..7461d03acc 100644
--- a/activesupport/lib/active_support/core_ext/string/filters.rb
+++ b/activesupport/lib/active_support/core_ext/string/filters.rb
@@ -13,6 +13,9 @@ class String
end
# Performs a destructive squish. See String#squish.
+ # str = " foo bar \n \t boo"
+ # str.squish! # => "foo bar boo"
+ # str # => "foo bar boo"
def squish!
gsub!(/\A[[:space:]]+/, '')
gsub!(/[[:space:]]+\z/, '')
@@ -21,11 +24,18 @@ class String
end
# Returns a new string with all occurrences of the patterns removed.
+ # str = "foo bar test"
+ # str.remove(" test") # => "foo bar"
+ # str.remove(" test", /bar/) # => "foo "
+ # str # => "foo bar test"
def remove(*patterns)
dup.remove!(*patterns)
end
# Alters the string by removing all occurrences of the patterns.
+ # str = "foo bar test"
+ # str.remove!(" test", /bar/) # => "foo "
+ # str # => "foo "
def remove!(*patterns)
patterns.each do |pattern|
gsub! pattern, ""
@@ -84,7 +94,7 @@ class String
def truncate_words(words_count, options = {})
sep = options[:separator] || /\s+/
sep = Regexp.escape(sep.to_s) unless Regexp === sep
- if self =~ /\A((?:.+?#{sep}){#{words_count - 1}}.+?)#{sep}.*/m
+ if self =~ /\A((?>.+?#{sep}){#{words_count - 1}}.+?)#{sep}.*/m
$1 + (options[:omission] || '...')
else
dup
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index 38d567c014..97f9720b2b 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -178,7 +178,7 @@ class String
ActiveSupport::Inflector.tableize(self)
end
- # Create a class name from a plural table name like Rails does for table names to models.
+ # Creates a class name from a plural table name like Rails does for table names to models.
# Note that this returns a string and not a class. (To convert to an actual class
# follow +classify+ with +constantize+.)
#
diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb
index a124202936..7055f7f699 100644
--- a/activesupport/lib/active_support/core_ext/string/multibyte.rb
+++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'active_support/multibyte'
class String
@@ -36,6 +35,13 @@ class String
ActiveSupport::Multibyte.proxy_class.new(self)
end
+ # Returns +true+ if string has utf_8 encoding.
+ #
+ # utf_8_str = "some string".encode "UTF-8"
+ # iso_str = "some string".encode "ISO-8859-1"
+ #
+ # utf_8_str.is_utf8? # => true
+ # iso_str.is_utf8? # => false
def is_utf8?
case encoding
when Encoding::UTF_8
diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb
index 042283e4fc..c676b26b06 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -1,6 +1,5 @@
require 'erb'
require 'active_support/core_ext/kernel/singleton_class'
-require 'active_support/deprecation'
class ERB
module Util
@@ -14,7 +13,7 @@ class ERB
# This method is also aliased as <tt>h</tt>.
#
# In your ERB templates, use this method to escape any unsafe content. For example:
- # <%=h @person.name %>
+ # <%= h @person.name %>
#
# puts html_escape('is a > 0 & a < 10?')
# # => is a &gt; 0 &amp; a &lt; 10?
@@ -86,6 +85,11 @@ class ERB
# automatically flag the result as HTML safe, since the raw value is unsafe to
# use inside HTML attributes.
#
+ # If your JSON is being used downstream for insertion into the DOM, be aware of
+ # whether or not it is being inserted via +html()+. Most JQuery plugins do this.
+ # If that is the case, be sure to +html_escape+ or +sanitize+ any user-generated
+ # content returned by your JSON.
+ #
# If you need to output JSON elsewhere in your HTML, you can just do something
# like this, as any unsafe characters (including quotation marks) will be
# automatically escaped for you:
@@ -150,7 +154,11 @@ module ActiveSupport #:nodoc:
else
if html_safe?
new_safe_buffer = super
- new_safe_buffer.instance_variable_set :@html_safe, true
+
+ if new_safe_buffer
+ new_safe_buffer.instance_variable_set :@html_safe, true
+ end
+
new_safe_buffer
else
to_str[*args]
@@ -186,11 +194,6 @@ module ActiveSupport #:nodoc:
super(html_escape_interpolated_argument(value))
end
- def prepend!(value)
- ActiveSupport::Deprecation.deprecation_warning "ActiveSupport::SafeBuffer#prepend!", :prepend
- prepend value
- end
-
def +(other)
dup.concat(other)
end
@@ -219,7 +222,7 @@ module ActiveSupport #:nodoc:
end
def encode_with(coder)
- coder.represent_scalar nil, to_str
+ coder.represent_object nil, to_str
end
UNSAFE_STRING_METHODS.each do |unsafe_method|
@@ -247,6 +250,11 @@ module ActiveSupport #:nodoc:
end
class String
+ # Marks a string as trusted safe. It will be inserted into HTML with no
+ # additional escaping performed. It is your responsibilty to ensure that the
+ # string contains no malicious content. This method is equivalent to the
+ # `raw` helper in views. It is recommended that you use `sanitize` instead of
+ # this method. It should never be called on user input.
def html_safe
ActiveSupport::SafeBuffer.new(self)
end
diff --git a/activesupport/lib/active_support/core_ext/struct.rb b/activesupport/lib/active_support/core_ext/struct.rb
index c2c30044f2..1fde3db070 100644
--- a/activesupport/lib/active_support/core_ext/struct.rb
+++ b/activesupport/lib/active_support/core_ext/struct.rb
@@ -1,6 +1,3 @@
-# Backport of Struct#to_h from Ruby 2.0
-class Struct # :nodoc:
- def to_h
- Hash[members.zip(values)]
- end
-end unless Struct.instance_methods.include?(:to_h)
+require 'active_support/deprecation'
+
+ActiveSupport::Deprecation.warn("This file is deprecated and will be removed in Rails 5.1 with no replacement.")
diff --git a/activesupport/lib/active_support/core_ext/thread.rb b/activesupport/lib/active_support/core_ext/thread.rb
deleted file mode 100644
index 4cd6634558..0000000000
--- a/activesupport/lib/active_support/core_ext/thread.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-class Thread
- LOCK = Mutex.new # :nodoc:
-
- # Returns the value of a thread local variable that has been set. Note that
- # these are different than fiber local values.
- #
- # Thread local values are carried along with threads, and do not respect
- # fibers. For example:
- #
- # Thread.new {
- # Thread.current.thread_variable_set("foo", "bar") # set a thread local
- # Thread.current["foo"] = "bar" # set a fiber local
- #
- # Fiber.new {
- # Fiber.yield [
- # Thread.current.thread_variable_get("foo"), # get the thread local
- # Thread.current["foo"], # get the fiber local
- # ]
- # }.resume
- # }.join.value # => ['bar', nil]
- #
- # The value <tt>"bar"</tt> is returned for the thread local, where +nil+ is returned
- # for the fiber local. The fiber is executed in the same thread, so the
- # thread local values are available.
- def thread_variable_get(key)
- _locals[key.to_sym]
- end
-
- # Sets a thread local with +key+ to +value+. Note that these are local to
- # threads, and not to fibers. Please see Thread#thread_variable_get for
- # more information.
- def thread_variable_set(key, value)
- _locals[key.to_sym] = value
- end
-
- # Returns an array of the names of the thread-local variables (as Symbols).
- #
- # thr = Thread.new do
- # Thread.current.thread_variable_set(:cat, 'meow')
- # Thread.current.thread_variable_set("dog", 'woof')
- # end
- # thr.join # => #<Thread:0x401b3f10 dead>
- # thr.thread_variables # => [:dog, :cat]
- #
- # Note that these are not fiber local variables. Please see Thread#thread_variable_get
- # for more details.
- def thread_variables
- _locals.keys
- end
-
- # Returns <tt>true</tt> if the given string (or symbol) exists as a
- # thread-local variable.
- #
- # me = Thread.current
- # me.thread_variable_set(:oliver, "a")
- # me.thread_variable?(:oliver) # => true
- # me.thread_variable?(:stanley) # => false
- #
- # Note that these are not fiber local variables. Please see Thread#thread_variable_get
- # for more details.
- def thread_variable?(key)
- _locals.has_key?(key.to_sym)
- end
-
- # Freezes the thread so that thread local variables cannot be set via
- # Thread#thread_variable_set, nor can fiber local variables be set.
- #
- # me = Thread.current
- # me.freeze
- # me.thread_variable_set(:oliver, "a") #=> RuntimeError: can't modify frozen thread locals
- # me[:oliver] = "a" #=> RuntimeError: can't modify frozen thread locals
- def freeze
- _locals.freeze
- super
- end
-
- private
-
- def _locals
- if defined?(@_locals)
- @_locals
- else
- LOCK.synchronize { @_locals ||= {} }
- end
- end
-end unless Thread.instance_methods.include?(:thread_variable_set)
diff --git a/activesupport/lib/active_support/core_ext/time.rb b/activesupport/lib/active_support/core_ext/time.rb
index 32cffe237d..72c3234630 100644
--- a/activesupport/lib/active_support/core_ext/time.rb
+++ b/activesupport/lib/active_support/core_ext/time.rb
@@ -1,5 +1,4 @@
require 'active_support/core_ext/time/acts_like'
require 'active_support/core_ext/time/calculations'
require 'active_support/core_ext/time/conversions'
-require 'active_support/core_ext/time/marshal'
require 'active_support/core_ext/time/zones'
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index ab8307429a..1ce68ea7c7 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -48,7 +48,11 @@ class Time
alias_method :at, :at_with_coercion
end
- # Seconds since midnight: Time.now.seconds_since_midnight
+ # Returns the number of seconds since 00:00:00.
+ #
+ # Time.new(2012, 8, 29, 0, 0, 0).seconds_since_midnight # => 0
+ # Time.new(2012, 8, 29, 12, 34, 56).seconds_since_midnight # => 45296
+ # Time.new(2012, 8, 29, 23, 59, 59).seconds_since_midnight # => 86399
def seconds_since_midnight
to_i - change(:hour => 0).to_i + (usec / 1.0e+6)
end
@@ -69,7 +73,7 @@ class Time
# and minute is passed, then sec, usec and nsec is set to 0. The +options+
# parameter takes a hash with any of these keys: <tt>:year</tt>, <tt>:month</tt>,
# <tt>:day</tt>, <tt>:hour</tt>, <tt>:min</tt>, <tt>:sec</tt>, <tt>:usec</tt>
- # <tt>:nsec</tt>. Path either <tt>:usec</tt> or <tt>:nsec</tt>, not both.
+ # <tt>:nsec</tt>. Pass either <tt>:usec</tt> or <tt>:nsec</tt>, not both.
#
# Time.new(2012, 8, 29, 22, 35, 0).change(day: 1) # => Time.new(2012, 8, 1, 22, 35, 0)
# Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => Time.new(1981, 8, 1, 22, 35, 0)
@@ -162,7 +166,7 @@ class Time
alias :at_noon :middle_of_day
alias :at_middle_of_day :middle_of_day
- # Returns a new Time representing the end of the day, 23:59:59.999999 (.999999999 in ruby1.9)
+ # Returns a new Time representing the end of the day, 23:59:59.999999
def end_of_day
change(
:hour => 23,
@@ -179,7 +183,7 @@ class Time
end
alias :at_beginning_of_hour :beginning_of_hour
- # Returns a new Time representing the end of the hour, x:59:59.999999 (.999999999 in ruby1.9)
+ # Returns a new Time representing the end of the hour, x:59:59.999999
def end_of_hour
change(
:min => 59,
@@ -195,7 +199,7 @@ class Time
end
alias :at_beginning_of_minute :beginning_of_minute
- # Returns a new Time representing the end of the minute, x:xx:59.999999 (.999999999 in ruby1.9)
+ # Returns a new Time representing the end of the minute, x:xx:59.999999
def end_of_minute
change(
:sec => 59,
@@ -242,8 +246,10 @@ class Time
# Layers additional behavior on Time#<=> so that DateTime and ActiveSupport::TimeWithZone instances
# can be chronologically compared with a Time
def compare_with_coercion(other)
- # we're avoiding Time#to_datetime cause it's expensive
- if other.is_a?(Time)
+ # we're avoiding Time#to_datetime and Time#to_time because they're expensive
+ if other.class == Time
+ compare_without_coercion(other)
+ elsif other.is_a?(Time)
compare_without_coercion(other.to_time)
else
to_datetime <=> other
diff --git a/activesupport/lib/active_support/core_ext/time/marshal.rb b/activesupport/lib/active_support/core_ext/time/marshal.rb
index 497c4c3fb8..467bad1726 100644
--- a/activesupport/lib/active_support/core_ext/time/marshal.rb
+++ b/activesupport/lib/active_support/core_ext/time/marshal.rb
@@ -1,30 +1,3 @@
-# Ruby 1.9.2 adds utc_offset and zone to Time, but marshaling only
-# preserves utc_offset. Preserve zone also, even though it may not
-# work in some edge cases.
-if Time.local(2010).zone != Marshal.load(Marshal.dump(Time.local(2010))).zone
- class Time
- class << self
- alias_method :_load_without_zone, :_load
- def _load(marshaled_time)
- time = _load_without_zone(marshaled_time)
- time.instance_eval do
- if zone = defined?(@_zone) && remove_instance_variable('@_zone')
- ary = to_a
- ary[0] += subsec if ary[0] == sec
- ary[-1] = zone
- utc? ? Time.utc(*ary) : Time.local(*ary)
- else
- self
- end
- end
- end
- end
+require 'active_support/deprecation'
- alias_method :_dump_without_zone, :_dump
- def _dump(*args)
- obj = dup
- obj.instance_variable_set('@_zone', zone)
- obj.send :_dump_without_zone, *args
- end
- end
-end
+ActiveSupport::Deprecation.warn("This is deprecated and will be removed in Rails 5.1 with no replacement.")
diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb
index bbda04d60c..d683e7c777 100644
--- a/activesupport/lib/active_support/core_ext/time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/time/zones.rb
@@ -1,4 +1,5 @@
require 'active_support/time_with_zone'
+require 'active_support/core_ext/time/acts_like'
require 'active_support/core_ext/date_and_time/zones'
class Time
@@ -25,7 +26,7 @@ class Time
# <tt>current_user.time_zone</tt> just needs to return a string identifying the user's preferred time zone:
#
# class ApplicationController < ActionController::Base
- # around_filter :set_time_zone
+ # around_action :set_time_zone
#
# def set_time_zone
# if logged_in?
@@ -50,7 +51,16 @@ class Time
end
end
- # Returns a TimeZone instance or nil, or raises an ArgumentError for invalid timezones.
+ # Returns a TimeZone instance matching the time zone provided.
+ # Accepts the time zone in any format supported by <tt>Time.zone=</tt>.
+ # Raises an ArgumentError for invalid time zones.
+ #
+ # Time.find_zone! "America/New_York" # => #<ActiveSupport::TimeZone @name="America/New_York" ...>
+ # Time.find_zone! "EST" # => #<ActiveSupport::TimeZone @name="EST" ...>
+ # Time.find_zone! -5.hours # => #<ActiveSupport::TimeZone @name="Bogota" ...>
+ # Time.find_zone! nil # => nil
+ # Time.find_zone! false # => false
+ # Time.find_zone! "NOT-A-TIMEZONE" # => ArgumentError: Invalid Timezone: NOT-A-TIMEZONE
def find_zone!(time_zone)
if !time_zone || time_zone.is_a?(ActiveSupport::TimeZone)
time_zone
@@ -71,6 +81,12 @@ class Time
raise ArgumentError, "Invalid Timezone: #{time_zone}"
end
+ # Returns a TimeZone instance matching the time zone provided.
+ # Accepts the time zone in any format supported by <tt>Time.zone=</tt>.
+ # Returns +nil+ for invalid time zones.
+ #
+ # Time.find_zone "America/New_York" # => #<ActiveSupport::TimeZone @name="America/New_York" ...>
+ # Time.find_zone "NOT-A-TIMEZONE" # => nil
def find_zone(time_zone)
find_zone!(time_zone) rescue nil
end
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index a89c769e34..664cc15a29 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -205,7 +205,10 @@ module ActiveSupport #:nodoc:
# Object includes this module.
module Loadable #:nodoc:
def self.exclude_from(base)
- base.class_eval { define_method(:load, Kernel.instance_method(:load)) }
+ base.class_eval do
+ define_method(:load, Kernel.instance_method(:load))
+ private :load
+ end
end
def require_or_load(file_name)
@@ -241,18 +244,6 @@ module ActiveSupport #:nodoc:
raise
end
- def load(file, wrap = false)
- result = false
- load_dependency(file) { result = super }
- result
- end
-
- def require(file)
- result = false
- load_dependency(file) { result = super }
- result
- end
-
# Mark the given constant as unloadable. Unloadable constants are removed
# each time dependencies are cleared.
#
@@ -269,6 +260,20 @@ module ActiveSupport #:nodoc:
def unloadable(const_desc)
Dependencies.mark_for_unload const_desc
end
+
+ private
+
+ def load(file, wrap = false)
+ result = false
+ load_dependency(file) { result = super }
+ result
+ end
+
+ def require(file)
+ result = false
+ load_dependency(file) { result = super }
+ result
+ end
end
# Exception file-blaming.
@@ -368,7 +373,7 @@ module ActiveSupport #:nodoc:
# Is the provided constant path defined?
def qualified_const_defined?(path)
- Object.qualified_const_defined?(path.sub(/^::/, ''), false)
+ Object.const_defined?(path, false)
end
# Given +path+, a filesystem path to a ruby file, return an array of
@@ -416,7 +421,7 @@ module ActiveSupport #:nodoc:
end
def load_once_path?(path)
- # to_s works around a ruby1.9 issue where String#starts_with?(Pathname)
+ # to_s works around a ruby issue where String#starts_with?(Pathname)
# will raise a TypeError: no implicit conversion of Pathname into String
autoload_once_paths.any? { |base| path.starts_with? base.to_s }
end
@@ -602,7 +607,7 @@ module ActiveSupport #:nodoc:
def autoloaded?(desc)
return false if desc.is_a?(Module) && desc.anonymous?
name = to_constant_name desc
- return false unless qualified_const_defined? name
+ return false unless qualified_const_defined?(name)
return autoloaded_constants.include?(name)
end
@@ -737,7 +742,7 @@ module ActiveSupport #:nodoc:
protected
def log_call(*args)
if log_activity?
- arg_str = args.collect { |arg| arg.inspect } * ', '
+ arg_str = args.collect(&:inspect) * ', '
/in `([a-z_\?\!]+)'/ =~ caller(1).first
selector = $1 || '<unknown>'
log "called #{selector}(#{arg_str})"
diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb
index 328b8c320a..9f9dca8453 100644
--- a/activesupport/lib/active_support/deprecation/behaviors.rb
+++ b/activesupport/lib/active_support/deprecation/behaviors.rb
@@ -20,7 +20,7 @@ module ActiveSupport
log: ->(message, callstack) {
logger =
- if defined?(Rails) && Rails.logger
+ if defined?(Rails.logger) && Rails.logger
Rails.logger
else
require 'active_support/logger'
diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb
index cab8a1b14d..c74e9c40ac 100644
--- a/activesupport/lib/active_support/deprecation/method_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb
@@ -31,12 +31,14 @@ module ActiveSupport
method_names += options.keys
method_names.each do |method_name|
- target_module.alias_method_chain(method_name, :deprecation) do |target, punctuation|
- target_module.send(:define_method, "#{target}_with_deprecation#{punctuation}") do |*args, &block|
+ mod = Module.new do
+ define_method(method_name) do |*args, &block|
deprecator.deprecation_warning(method_name, options[method_name])
- send(:"#{target}_without_deprecation#{punctuation}", *args, &block)
+ super(*args, &block)
end
end
+
+ target_module.prepend(mod)
end
end
end
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 584fc1e1c5..4c0d1197fe 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -1,4 +1,3 @@
-require 'active_support/proxy_object'
require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/object/acts_like'
@@ -57,6 +56,30 @@ module ActiveSupport
@value.to_s
end
+ # Returns the number of seconds that this Duration represents.
+ #
+ # 1.minute.to_i # => 60
+ # 1.hour.to_i # => 3600
+ # 1.day.to_i # => 86400
+ #
+ # Note that this conversion makes some assumptions about the
+ # duration of some periods, e.g. months are always 30 days
+ # and years are 365.25 days:
+ #
+ # # equivalent to 30.days.to_i
+ # 1.month.to_i # => 2592000
+ #
+ # # equivalent to 365.25.days.to_i
+ # 1.year.to_i # => 31557600
+ #
+ # In such cases, Ruby's core
+ # Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
+ # Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
+ # date and time arithmetic.
+ def to_i
+ @value.to_i
+ end
+
# Returns +true+ if +other+ is also a Duration instance, which has the
# same parts as this one.
def eql?(other)
@@ -92,17 +115,19 @@ module ActiveSupport
reduce(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }.
sort_by {|unit, _ | [:years, :months, :days, :minutes, :seconds].index(unit)}.
map {|unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}"}.
- to_sentence(:locale => :en)
+ to_sentence(locale: ::I18n.default_locale)
end
def as_json(options = nil) #:nodoc:
to_i
end
- def respond_to_missing?(method, include_private=false) #:nodoc
+ def respond_to_missing?(method, include_private=false) #:nodoc:
@value.respond_to?(method, include_private)
end
+ delegate :<=>, to: :value
+
protected
def sum(sign, time = ::Time.current) #:nodoc:
@@ -121,13 +146,6 @@ module ActiveSupport
private
- # We define it as a workaround to Ruby 2.0.0-p353 bug.
- # For more information, check rails/rails#13055.
- # Remove it when we drop support for 2.0.0-p353.
- def ===(other) #:nodoc:
- value === other
- end
-
def method_missing(method, *args, &block) #:nodoc:
value.send(method, *args, &block)
end
diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb
index bc7933e38b..7068f09d87 100644
--- a/activesupport/lib/active_support/gem_version.rb
+++ b/activesupport/lib/active_support/gem_version.rb
@@ -5,10 +5,10 @@ module ActiveSupport
end
module VERSION
- MAJOR = 4
- MINOR = 2
+ MAJOR = 5
+ MINOR = 0
TINY = 0
- PRE = "beta4"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 1468c62151..4f71f13971 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -267,7 +267,7 @@ module ActiveSupport
value.nested_under_indifferent_access
end
elsif value.is_a?(Array)
- unless options[:for] == :assignment
+ if options[:for] != :assignment || value.frozen?
value = value.dup
end
value.map! { |e| convert_value(e, options) }
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index affcfb7398..95f3f6255a 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -55,14 +55,20 @@ module I18n
reloader = ActiveSupport::FileUpdateChecker.new(I18n.load_path.dup){ I18n.reload! }
app.reloaders << reloader
- ActionDispatch::Reloader.to_prepare { reloader.execute_if_updated }
+ ActionDispatch::Reloader.to_prepare do
+ reloader.execute_if_updated
+ # TODO: remove the following line as soon as the return value of
+ # callbacks is ignored, that is, returning `false` does not
+ # display a deprecation warning or halts the callback chain.
+ true
+ end
reloader.execute
@i18n_inited = true
end
def self.include_fallbacks_module
- I18n.backend.class.send(:include, I18n::Backend::Fallbacks)
+ I18n.backend.class.include(I18n::Backend::Fallbacks)
end
def self.init_fallbacks(fallbacks)
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 74b3a7c2a9..a08c655d69 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -22,49 +22,49 @@ module ActiveSupport
# pluralized using rules defined for that language. By default,
# this parameter is set to <tt>:en</tt>.
#
- # 'post'.pluralize # => "posts"
- # 'octopus'.pluralize # => "octopi"
- # 'sheep'.pluralize # => "sheep"
- # 'words'.pluralize # => "words"
- # 'CamelOctopus'.pluralize # => "CamelOctopi"
- # 'ley'.pluralize(:es) # => "leyes"
+ # pluralize('post') # => "posts"
+ # pluralize('octopus') # => "octopi"
+ # pluralize('sheep') # => "sheep"
+ # pluralize('words') # => "words"
+ # pluralize('CamelOctopus') # => "CamelOctopi"
+ # pluralize('ley', :es) # => "leyes"
def pluralize(word, locale = :en)
apply_inflections(word, inflections(locale).plurals)
end
- # The reverse of +pluralize+, returns the singular form of a word in a
+ # The reverse of #pluralize, returns the singular form of a word in a
# string.
#
# If passed an optional +locale+ parameter, the word will be
# singularized using rules defined for that language. By default,
# this parameter is set to <tt>:en</tt>.
#
- # 'posts'.singularize # => "post"
- # 'octopi'.singularize # => "octopus"
- # 'sheep'.singularize # => "sheep"
- # 'word'.singularize # => "word"
- # 'CamelOctopi'.singularize # => "CamelOctopus"
- # 'leyes'.singularize(:es) # => "ley"
+ # singularize('posts') # => "post"
+ # singularize('octopi') # => "octopus"
+ # singularize('sheep') # => "sheep"
+ # singularize('word') # => "word"
+ # singularize('CamelOctopi') # => "CamelOctopus"
+ # singularize('leyes', :es) # => "ley"
def singularize(word, locale = :en)
apply_inflections(word, inflections(locale).singulars)
end
- # By default, +camelize+ converts strings to UpperCamelCase. If the argument
- # to +camelize+ is set to <tt>:lower</tt> then +camelize+ produces
+ # Converts strings to UpperCamelCase.
+ # If the +uppercase_first_letter+ parameter is set to false, then produces
# lowerCamelCase.
#
- # +camelize+ will also convert '/' to '::' which is useful for converting
+ # Also converts '/' to '::' which is useful for converting
# paths to namespaces.
#
- # 'active_model'.camelize # => "ActiveModel"
- # 'active_model'.camelize(:lower) # => "activeModel"
- # 'active_model/errors'.camelize # => "ActiveModel::Errors"
- # 'active_model/errors'.camelize(:lower) # => "activeModel::Errors"
+ # camelize('active_model') # => "ActiveModel"
+ # camelize('active_model', false) # => "activeModel"
+ # camelize('active_model/errors') # => "ActiveModel::Errors"
+ # camelize('active_model/errors', false) # => "activeModel::Errors"
#
# As a rule of thumb you can think of +camelize+ as the inverse of
- # +underscore+, though there are cases where that does not hold:
+ # #underscore, though there are cases where that does not hold:
#
- # 'SSLError'.underscore.camelize # => "SslError"
+ # camelize(underscore('SSLError')) # => "SslError"
def camelize(term, uppercase_first_letter = true)
string = term.to_s
if uppercase_first_letter
@@ -73,7 +73,7 @@ module ActiveSupport
string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { $&.downcase }
end
string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }
- string.gsub!(/\//, '::')
+ string.gsub!('/'.freeze, '::'.freeze)
string
end
@@ -81,16 +81,16 @@ module ActiveSupport
#
# Changes '::' to '/' to convert namespaces to paths.
#
- # 'ActiveModel'.underscore # => "active_model"
- # 'ActiveModel::Errors'.underscore # => "active_model/errors"
+ # underscore('ActiveModel') # => "active_model"
+ # underscore('ActiveModel::Errors') # => "active_model/errors"
#
# As a rule of thumb you can think of +underscore+ as the inverse of
- # +camelize+, though there are cases where that does not hold:
+ # #camelize, though there are cases where that does not hold:
#
- # 'SSLError'.underscore.camelize # => "SslError"
+ # camelize(underscore('SSLError')) # => "SslError"
def underscore(camel_cased_word)
return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/
- word = camel_cased_word.to_s.gsub(/::/, '/')
+ 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')
@@ -101,14 +101,14 @@ module ActiveSupport
# Tweaks an attribute name for display to end users.
#
- # Specifically, +humanize+ performs these transformations:
+ # Specifically, performs these transformations:
#
- # * Applies human inflection rules to the argument.
- # * Deletes leading underscores, if any.
- # * Removes a "_id" suffix if present.
- # * Replaces underscores with spaces, if any.
- # * Downcases all words except acronyms.
- # * Capitalizes the first word.
+ # * Applies human inflection rules to the argument.
+ # * Deletes leading underscores, if any.
+ # * Removes a "_id" suffix if present.
+ # * Replaces underscores with spaces, if any.
+ # * Downcases all words except acronyms.
+ # * Capitalizes the first word.
#
# The capitalization of the first word can be turned off by setting the
# +:capitalize+ option to false (default is true).
@@ -148,34 +148,34 @@ module ActiveSupport
#
# +titleize+ is also aliased as +titlecase+.
#
- # 'man from the boondocks'.titleize # => "Man From The Boondocks"
- # 'x-men: the last stand'.titleize # => "X Men: The Last Stand"
- # 'TheManWithoutAPast'.titleize # => "The Man Without A Past"
- # 'raiders_of_the_lost_ark'.titleize # => "Raiders Of The Lost Ark"
+ # titleize('man from the boondocks') # => "Man From The Boondocks"
+ # titleize('x-men: the last stand') # => "X Men: The Last Stand"
+ # 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 }
end
- # Create 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.
+ # 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.
#
- # 'RawScaledScorer'.tableize # => "raw_scaled_scorers"
- # 'egg_and_ham'.tableize # => "egg_and_hams"
- # 'fancyCategory'.tableize # => "fancy_categories"
+ # tableize('RawScaledScorer') # => "raw_scaled_scorers"
+ # tableize('egg_and_ham') # => "egg_and_hams"
+ # tableize('fancyCategory') # => "fancy_categories"
def tableize(class_name)
pluralize(underscore(class_name))
end
- # Create a class name from a plural table name like Rails does for table
+ # Creates a class name from a plural table name like Rails does for table
# names to models. Note that this returns a string and not a Class (To
- # convert to an actual class follow +classify+ with +constantize+).
+ # convert to an actual class follow +classify+ with #constantize).
#
- # 'egg_and_hams'.classify # => "EggAndHam"
- # 'posts'.classify # => "Post"
+ # classify('egg_and_hams') # => "EggAndHam"
+ # classify('posts') # => "Post"
#
# Singular names are not handled correctly:
#
- # 'calculus'.classify # => "Calculu"
+ # classify('calculus') # => "Calculu"
def classify(table_name)
# strip out any leading schema name
camelize(singularize(table_name.to_s.sub(/.*\./, '')))
@@ -183,19 +183,19 @@ module ActiveSupport
# Replaces underscores with dashes in the string.
#
- # 'puni_puni'.dasherize # => "puni-puni"
+ # dasherize('puni_puni') # => "puni-puni"
def dasherize(underscored_word)
underscored_word.tr('_', '-')
end
# Removes the module part from the expression in the string.
#
- # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections"
- # 'Inflections'.demodulize # => "Inflections"
- # '::Inflections'.demodulize # => "Inflections"
- # ''.demodulize # => ""
+ # demodulize('ActiveRecord::CoreExtensions::String::Inflections') # => "Inflections"
+ # demodulize('Inflections') # => "Inflections"
+ # demodulize('::Inflections') # => "Inflections"
+ # demodulize('') # => ""
#
- # See also +deconstantize+.
+ # See also #deconstantize.
def demodulize(path)
path = path.to_s
if i = path.rindex('::')
@@ -207,13 +207,13 @@ module ActiveSupport
# Removes the rightmost segment from the constant expression in the string.
#
- # 'Net::HTTP'.deconstantize # => "Net"
- # '::Net::HTTP'.deconstantize # => "::Net"
- # 'String'.deconstantize # => ""
- # '::String'.deconstantize # => ""
- # ''.deconstantize # => ""
+ # deconstantize('Net::HTTP') # => "Net"
+ # deconstantize('::Net::HTTP') # => "::Net"
+ # deconstantize('String') # => ""
+ # deconstantize('::String') # => ""
+ # deconstantize('') # => ""
#
- # See also +demodulize+.
+ # See also #demodulize.
def deconstantize(path)
path.to_s[0, path.rindex('::') || 0] # implementation based on the one in facets' Module#spacename
end
@@ -222,9 +222,9 @@ module ActiveSupport
# +separate_class_name_and_id_with_underscore+ sets whether
# the method should put '_' between the name and 'id'.
#
- # 'Message'.foreign_key # => "message_id"
- # 'Message'.foreign_key(false) # => "messageid"
- # 'Admin::Post'.foreign_key # => "post_id"
+ # foreign_key('Message') # => "message_id"
+ # foreign_key('Message', false) # => "messageid"
+ # foreign_key('Admin::Post') # => "post_id"
def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
end
@@ -280,8 +280,8 @@ module ActiveSupport
# Tries to find a constant with the name specified in the argument string.
#
- # 'Module'.safe_constantize # => Module
- # 'Test::Unit'.safe_constantize # => Test::Unit
+ # safe_constantize('Module') # => Module
+ # safe_constantize('Test::Unit') # => Test::Unit
#
# 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
@@ -290,16 +290,16 @@ module ActiveSupport
# C = 'outside'
# module M
# C = 'inside'
- # C # => 'inside'
- # 'C'.safe_constantize # => 'outside', same as ::C
+ # C # => 'inside'
+ # safe_constantize('C') # => 'outside', same as ::C
# end
#
# +nil+ is returned when the name is not in CamelCase or the constant (or
# part of it) is unknown.
#
- # 'blargle'.safe_constantize # => nil
- # 'UnknownModule'.safe_constantize # => nil
- # 'UnknownModule::Foo::Bar'.safe_constantize # => nil
+ # safe_constantize('blargle') # => nil
+ # safe_constantize('UnknownModule') # => nil
+ # safe_constantize('UnknownModule::Foo::Bar') # => nil
def safe_constantize(camel_cased_word)
constantize(camel_cased_word)
rescue NameError => e
diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb
index 1cde417fc5..edea142e82 100644
--- a/activesupport/lib/active_support/inflector/transliterate.rb
+++ b/activesupport/lib/active_support/inflector/transliterate.rb
@@ -67,17 +67,8 @@ module ActiveSupport
# Replaces special characters in a string so that it may be used as part of
# a 'pretty' URL.
#
- # class Person
- # def to_param
- # "#{id}-#{name.parameterize}"
- # end
- # end
- #
- # @person = Person.find(1)
- # # => #<Person id: 1, name: "Donald E. Knuth">
- #
- # <%= link_to(@person.name, person_path(@person)) %>
- # # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a>
+ # parameterize("Donald E. Knuth") # => "donald-e-knuth"
+ # parameterize("^trés|Jolie-- ") # => "tres-jolie"
def parameterize(string, sep = '-')
# replace accented chars with their ascii equivalents
parameterized_string = transliterate(string)
@@ -92,6 +83,5 @@ module ActiveSupport
end
parameterized_string.downcase
end
-
end
end
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index c0ac5af153..48f4967892 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -6,7 +6,6 @@ module ActiveSupport
delegate :use_standard_json_time_format, :use_standard_json_time_format=,
:time_precision, :time_precision=,
:escape_html_entities_in_json, :escape_html_entities_in_json=,
- :encode_big_decimal_as_string, :encode_big_decimal_as_string=,
:json_encoder, :json_encoder=,
:to => :'ActiveSupport::JSON::Encoding'
end
@@ -113,54 +112,6 @@ module ActiveSupport
# Sets the encoder used by Rails to encode Ruby objects into JSON strings
# in +Object#to_json+ and +ActiveSupport::JSON.encode+.
attr_accessor :json_encoder
-
- def encode_big_decimal_as_string=(as_string)
- message = \
- "The JSON encoder in Rails 4.1 no longer supports encoding BigDecimals as JSON numbers. Instead, " \
- "the new encoder will always encode them as strings.\n\n" \
- "You are seeing this error because you have 'active_support.encode_big_decimal_as_string' in " \
- "your configuration file. If you have been setting this to true, you can safely remove it from " \
- "your configuration. Otherwise, you should add the 'activesupport-json_encoder' gem to your " \
- "Gemfile in order to restore this functionality."
-
- raise NotImplementedError, message
- end
-
- def encode_big_decimal_as_string
- message = \
- "The JSON encoder in Rails 4.1 no longer supports encoding BigDecimals as JSON numbers. Instead, " \
- "the new encoder will always encode them as strings.\n\n" \
- "You are seeing this error because you are trying to check the value of the related configuration, " \
- "`active_support.encode_big_decimal_as_string`. If your application depends on this option, you should " \
- "add the 'activesupport-json_encoder' gem to your Gemfile. For now, this option will always be true. " \
- "In the future, it will be removed from Rails, so you should stop checking its value."
-
- ActiveSupport::Deprecation.warn message
-
- true
- end
-
- # Deprecate CircularReferenceError
- def const_missing(name)
- if name == :CircularReferenceError
- message = "The JSON encoder in Rails 4.1 no longer offers protection from circular references. " \
- "You are seeing this warning because you are rescuing from (or otherwise referencing) " \
- "ActiveSupport::Encoding::CircularReferenceError. In the future, this error will be " \
- "removed from Rails. You should remove these rescue blocks from your code and ensure " \
- "that your data structures are free of circular references so they can be properly " \
- "serialized into JSON.\n\n" \
- "For example, the following Hash contains a circular reference to itself:\n" \
- " h = {}\n" \
- " h['circular'] = h\n" \
- "In this case, calling h.to_json would not work properly."
-
- ActiveSupport::Deprecation.warn message
-
- SystemStackError
- else
- super
- end
- end
end
self.use_standard_json_time_format = true
diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb
index 4e0796f4f8..eee9bbaead 100644
--- a/activesupport/lib/active_support/message_verifier.rb
+++ b/activesupport/lib/active_support/message_verifier.rb
@@ -34,28 +34,92 @@ module ActiveSupport
@serializer = options[:serializer] || Marshal
end
- def verify(signed_message)
- raise InvalidSignature if signed_message.blank?
+ # Checks if a signed message could have been generated by signing an object
+ # with the +MessageVerifier+'s secret.
+ #
+ # verifier = ActiveSupport::MessageVerifier.new 's3Krit'
+ # signed_message = verifier.generate 'a private message'
+ # verifier.valid_message?(signed_message) # => true
+ #
+ # 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?
data, digest = signed_message.split("--")
- if data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data))
+ data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data))
+ end
+
+ # Decodes the signed message using the +MessageVerifier+'s secret.
+ #
+ # verifier = ActiveSupport::MessageVerifier.new 's3Krit'
+ #
+ # signed_message = verifier.generate 'a private message'
+ # verifier.verified(signed_message) # => 'a private message'
+ #
+ # Returns +nil+ if the message was not signed with the same secret.
+ #
+ # other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit'
+ # other_verifier.verified(signed_message) # => nil
+ #
+ # Returns +nil+ if the message is not Base64-encoded.
+ #
+ # invalid_message = "f--46a0120593880c733a53b6dad75b42ddc1c8996d"
+ # verifier.verified(invalid_message) # => nil
+ #
+ # Raises any error raised while decoding the signed message.
+ #
+ # incompatible_message = "test--dad7b06c94abba8d46a15fafaef56c327665d5ff"
+ # verifier.verified(incompatible_message) # => TypeError: incompatible marshal file format
+ def verified(signed_message)
+ if valid_message?(signed_message)
begin
- @serializer.load(::Base64.strict_decode64(data))
+ data = signed_message.split("--")[0]
+ @serializer.load(decode(data))
rescue ArgumentError => argument_error
- raise InvalidSignature if argument_error.message =~ %r{invalid base64}
+ return if argument_error.message =~ %r{invalid base64}
raise
end
- else
- raise InvalidSignature
end
end
+ # Decodes the signed message using the +MessageVerifier+'s secret.
+ #
+ # verifier = ActiveSupport::MessageVerifier.new 's3Krit'
+ # signed_message = verifier.generate 'a private message'
+ #
+ # verifier.verify(signed_message) # => 'a private message'
+ #
+ # Raises +InvalidSignature+ if the message was not signed with the same
+ # secret or was not Base64-encoded.
+ #
+ # other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit'
+ # other_verifier.verify(signed_message) # => ActiveSupport::MessageVerifier::InvalidSignature
+ def verify(signed_message)
+ verified(signed_message) || raise(InvalidSignature)
+ end
+
+ # Generates a signed message for the provided value.
+ #
+ # The message is signed with the +MessageVerifier+'s secret. Without knowing
+ # the secret, the original value cannot be extracted from the message.
+ #
+ # verifier = ActiveSupport::MessageVerifier.new 's3Krit'
+ # verifier.generate 'a private message' # => "BAhJIhRwcml2YXRlLW1lc3NhZ2UGOgZFVA==--e2d724331ebdee96a10fb99b089508d1c72bd772"
def generate(value)
- data = ::Base64.strict_encode64(@serializer.dump(value))
+ data = encode(@serializer.dump(value))
"#{data}--#{generate_digest(data)}"
end
private
+ def encode(data)
+ ::Base64.strict_encode64(data)
+ end
+
+ def decode(data)
+ ::Base64.strict_decode64(data)
+ end
+
def generate_digest(data)
require 'openssl' unless defined?(OpenSSL)
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data)
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 7d45961515..35efebc65f 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -11,7 +11,7 @@ module ActiveSupport
NORMALIZATION_FORMS = [:c, :kc, :d, :kd]
# The Unicode version that is supported by the implementation
- UNICODE_VERSION = '6.3.0'
+ UNICODE_VERSION = '7.0.0'
# The default normalization used for operations that require
# normalization. It can be set to any of the normalizations
@@ -211,9 +211,8 @@ module ActiveSupport
codepoints
end
- # Ruby >= 2.1 has String#scrub, which is faster than the workaround used for < 2.1.
# Rubinius' String#scrub, however, doesn't support ASCII-incompatible chars.
- if '<3'.respond_to?(:scrub) && !defined?(Rubinius)
+ if !defined?(Rubinius)
# Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
# resulting in a valid UTF-8 string.
#
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index 325a3d75dc..b9f8e1ab2c 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -16,7 +16,7 @@ module ActiveSupport
# render text: 'Foo'
# end
#
- # That executes the block first and notifies all subscribers once done.
+ # That first executes the block and then notifies all subscribers once done.
#
# In the example above +render+ is the name of the event, and the rest is called
# the _payload_. The payload is a mechanism that allows instrumenters to pass
diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb
index 3a244b34b5..075ddc2382 100644
--- a/activesupport/lib/active_support/notifications/instrumenter.rb
+++ b/activesupport/lib/active_support/notifications/instrumenter.rb
@@ -57,6 +57,18 @@ module ActiveSupport
@duration = nil
end
+ # Returns the difference in milliseconds between when the execution of the
+ # event started and when it ended.
+ #
+ # ActiveSupport::Notifications.subscribe('wait') do |*args|
+ # @event = ActiveSupport::Notifications::Event.new(*args)
+ # end
+ #
+ # ActiveSupport::Notifications.instrument('wait') do
+ # sleep 1
+ # end
+ #
+ # @event.duration # => 1000.138
def duration
@duration ||= 1000.0 * (self.end - time)
end
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index 34439ee8be..258d9b34e1 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -94,9 +94,9 @@ module ActiveSupport
# * <tt>:locale</tt> - Sets the locale to be used for formatting
# (defaults to current locale).
# * <tt>:precision</tt> - Sets the precision of the number
- # (defaults to 3).
- # * <tt>:significant</tt> - If +true+, precision will be the #
- # of significant_digits. If +false+, the # of fractional
+ # (defaults to 3). Keeps the number's precision if nil.
+ # * <tt>:significant</tt> - If +true+, precision will be the number
+ # of significant_digits. If +false+, the number of fractional
# digits (defaults to +false+).
# * <tt>:separator</tt> - Sets the separator between the
# fractional and integer digits (defaults to ".").
@@ -116,6 +116,7 @@ module ActiveSupport
# number_to_percentage(1000, delimiter: '.', separator: ',') # => 1.000,000%
# number_to_percentage(302.24398923423, precision: 5) # => 302.24399%
# number_to_percentage(1000, locale: :fr) # => 1 000,000%
+ # number_to_percentage:(1000, precision: nil) # => 1000%
# number_to_percentage('98a') # => 98a%
# number_to_percentage(100, format: '%n %') # => 100 %
def number_to_percentage(number, options = {})
@@ -161,9 +162,9 @@ module ActiveSupport
# * <tt>:locale</tt> - Sets the locale to be used for formatting
# (defaults to current locale).
# * <tt>:precision</tt> - Sets the precision of the number
- # (defaults to 3).
- # * <tt>:significant</tt> - If +true+, precision will be the #
- # of significant_digits. If +false+, the # of fractional
+ # (defaults to 3). Keeps the number's precision if nil.
+ # * <tt>:significant</tt> - If +true+, precision will be the number
+ # of significant_digits. If +false+, the number of fractional
# digits (defaults to +false+).
# * <tt>:separator</tt> - Sets the separator between the
# fractional and integer digits (defaults to ".").
@@ -182,6 +183,7 @@ module ActiveSupport
# number_to_rounded(111.2345, significant: true) # => 111
# number_to_rounded(111.2345, precision: 1, significant: true) # => 100
# number_to_rounded(13, precision: 5, significant: true) # => 13.000
+ # number_to_rounded(13, precision: nil) # => 13
# number_to_rounded(111.234, locale: :fr) # => 111,234
#
# number_to_rounded(13, precision: 5, significant: true, strip_insignificant_zeros: true)
@@ -208,8 +210,8 @@ module ActiveSupport
# (defaults to current locale).
# * <tt>:precision</tt> - Sets the precision of the number
# (defaults to 3).
- # * <tt>:significant</tt> - If +true+, precision will be the #
- # of significant_digits. If +false+, the # of fractional
+ # * <tt>:significant</tt> - If +true+, precision will be the number
+ # of significant_digits. If +false+, the number of fractional
# digits (defaults to +true+)
# * <tt>:separator</tt> - Sets the separator between the
# fractional and integer digits (defaults to ".").
@@ -258,8 +260,8 @@ module ActiveSupport
# (defaults to current locale).
# * <tt>:precision</tt> - Sets the precision of the number
# (defaults to 3).
- # * <tt>:significant</tt> - If +true+, precision will be the #
- # of significant_digits. If +false+, the # of fractional
+ # * <tt>:significant</tt> - If +true+, precision will be the number
+ # of significant_digits. If +false+, the number of fractional
# digits (defaults to +true+)
# * <tt>:separator</tt> - Sets the separator between the
# fractional and integer digits (defaults to ".").
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 fb5adb574a..cd5a2b3cbb 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
@@ -13,7 +13,7 @@ module ActiveSupport
end
rounded_number = NumberToRoundedConverter.convert(number, options)
- format.gsub(/%n/, rounded_number).gsub(/%u/, options[:unit])
+ format.gsub('%n'.freeze, rounded_number).gsub('%u'.freeze, options[:unit])
end
private
@@ -29,14 +29,14 @@ module ActiveSupport
def options
@options ||= begin
defaults = default_format_options.merge(i18n_opts)
- # Override negative format if format options is given
+ # Override negative format if format options are given
defaults[:negative_format] = "-#{opts[:format]}" if opts[:format]
defaults.merge!(opts)
end
end
def i18n_opts
- # Set International negative format if not exists
+ # Set International negative format if it does not exist
i18n = i18n_format_options
i18n[:negative_format] ||= "-#{i18n[:format]}" if i18n[:format]
i18n
diff --git a/activesupport/lib/active_support/number_helper/number_to_human_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb
index 9a3dc526ae..5c6fe2df83 100644
--- a/activesupport/lib/active_support/number_helper/number_to_human_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb
@@ -23,7 +23,7 @@ module ActiveSupport
unit = determine_unit(units, exponent)
rounded_number = NumberToRoundedConverter.convert(number, options)
- format.gsub(/%n/, rounded_number).gsub(/%u/, unit).strip
+ format.gsub('%n'.freeze, rounded_number).gsub('%u'.freeze, unit).strip
end
private
@@ -59,7 +59,7 @@ module ActiveSupport
translate_in_locale("human.decimal_units.units", raise: true)
else
raise ArgumentError, ":units must be a Hash or String translation scope."
- end.keys.map { |e_name| INVERTED_DECIMAL_UNITS[e_name] }.sort_by { |e| -e }
+ end.keys.map { |e_name| INVERTED_DECIMAL_UNITS[e_name] }.sort_by(&:-@)
end
end
end
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 78d2c9ae6e..ac0d20b454 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
@@ -20,7 +20,7 @@ module ActiveSupport
human_size = number / (base ** exponent)
number_to_format = NumberToRoundedConverter.convert(human_size, options)
end
- conversion_format.gsub(/%n/, number_to_format).gsub(/%u/, unit)
+ conversion_format.gsub('%n'.freeze, number_to_format).gsub('%u'.freeze, unit)
end
private
diff --git a/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb b/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb
index 1af294a03e..4c04d40c19 100644
--- a/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb
@@ -5,7 +5,7 @@ module ActiveSupport
def convert
rounded_number = NumberToRoundedConverter.convert(number, options)
- options[:format].gsub(/%n/, rounded_number)
+ options[:format].gsub('%n'.freeze, rounded_number)
end
end
end
diff --git a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
index dcf9a567e8..981c562551 100644
--- a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
@@ -6,36 +6,39 @@ module ActiveSupport
def convert
precision = options.delete :precision
- significant = options.delete :significant
- case number
- when Float, String
- @number = BigDecimal(number.to_s)
- when Rational
- @number = BigDecimal(number, digit_count(number.to_i) + precision)
- else
- @number = number.to_d
- end
-
- if significant && precision > 0
- digits, rounded_number = digits_and_rounded_number(precision)
- precision -= digits
- precision = 0 if precision < 0 # don't let it be negative
- else
- rounded_number = number.round(precision)
- rounded_number = rounded_number.to_i if precision == 0
- rounded_number = rounded_number.abs if rounded_number.zero? # prevent showing negative zeros
- end
+ if precision
+ case number
+ when Float, String
+ @number = BigDecimal(number.to_s)
+ when Rational
+ @number = BigDecimal(number, digit_count(number.to_i) + precision)
+ else
+ @number = number.to_d
+ end
- formatted_string =
- if BigDecimal === rounded_number && rounded_number.finite?
- s = rounded_number.to_s('F') + '0'*precision
- a, b = s.split('.', 2)
- a + '.' + b[0, precision]
+ if options.delete(:significant) && precision > 0
+ digits, rounded_number = digits_and_rounded_number(precision)
+ precision -= digits
+ precision = 0 if precision < 0 # don't let it be negative
else
- "%00.#{precision}f" % rounded_number
+ rounded_number = number.round(precision)
+ rounded_number = rounded_number.to_i if precision == 0 && rounded_number.finite?
+ rounded_number = rounded_number.abs if rounded_number.zero? # prevent showing negative zeros
end
+ formatted_string =
+ if BigDecimal === rounded_number && rounded_number.finite?
+ s = rounded_number.to_s('F') + '0'*precision
+ a, b = s.split('.', 2)
+ a + '.' + b[0, precision]
+ else
+ "%00.#{precision}f" % rounded_number
+ end
+ else
+ formatted_string = number
+ end
+
delimited_number = NumberToDelimitedConverter.convert(formatted_string, options)
format_number(delimited_number)
end
diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb
index 133aa6a054..cd0fb51009 100644
--- a/activesupport/lib/active_support/railtie.rb
+++ b/activesupport/lib/active_support/railtie.rb
@@ -16,6 +16,11 @@ module ActiveSupport
# 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|
+ begin
+ TZInfo::DataSource.get
+ rescue TZInfo::DataSourceNotFound => e
+ raise e.exception "tzinfo-data is not present. Please add gem 'tzinfo-data' to your Gemfile and run bundle install"
+ end
require 'active_support/core_ext/time/zones'
zone_default = Time.find_zone!(app.config.time_zone)
diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb
index a7eba91ac5..67aac32742 100644
--- a/activesupport/lib/active_support/rescuable.rb
+++ b/activesupport/lib/active_support/rescuable.rb
@@ -60,7 +60,7 @@ module ActiveSupport
end
klasses.each do |klass|
- key = if klass.is_a?(Class) && klass <= Exception
+ key = if klass.is_a?(Module) && klass.respond_to?(:===)
klass.name
elsif klass.is_a?(String)
klass
@@ -100,8 +100,8 @@ module ActiveSupport
# a string, otherwise a NameError will be raised by the interpreter
# itself when rescue_from CONSTANT is executed.
klass = self.class.const_get(klass_name) rescue nil
- klass ||= klass_name.constantize rescue nil
- exception.is_a?(klass) if klass
+ klass ||= (klass_name.constantize rescue nil)
+ klass === exception if klass
end
case rescuer
diff --git a/activesupport/lib/active_support/string_inquirer.rb b/activesupport/lib/active_support/string_inquirer.rb
index 45271c9163..bc673150d0 100644
--- a/activesupport/lib/active_support/string_inquirer.rb
+++ b/activesupport/lib/active_support/string_inquirer.rb
@@ -1,7 +1,7 @@
module ActiveSupport
# Wrapping a string in this class gives you a prettier way to test
# for equality. The value returned by <tt>Rails.env</tt> is wrapped
- # in a StringInquirer object so instead of calling this:
+ # in a StringInquirer object, so instead of calling this:
#
# Rails.env == 'production'
#
diff --git a/activesupport/lib/active_support/subscriber.rb b/activesupport/lib/active_support/subscriber.rb
index 98be78b41b..8db423f0e9 100644
--- a/activesupport/lib/active_support/subscriber.rb
+++ b/activesupport/lib/active_support/subscriber.rb
@@ -10,19 +10,14 @@ module ActiveSupport
#
# module ActiveRecord
# class StatsSubscriber < ActiveSupport::Subscriber
+ # attach_to :active_record
+ #
# def sql(event)
# Statsd.timing("sql.#{event.payload[:name]}", event.duration)
# end
# end
# end
#
- # And it's finally registered as:
- #
- # ActiveRecord::StatsSubscriber.attach_to :active_record
- #
- # Since we need to know all instance methods before attaching the log
- # subscriber, the line above should be called after your subscriber definition.
- #
# After configured, whenever a "sql.active_record" notification is published,
# it will properly dispatch the event (ActiveSupport::Notifications::Event) to
# the +sql+ method.
@@ -96,7 +91,7 @@ module ActiveSupport
event.end = finished
event.payload.merge!(payload)
- method = name.split('.').first
+ method = name.split('.'.freeze).first
send(method, event)
end
diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb
index d5c2222d2e..bcd7bf74c0 100644
--- a/activesupport/lib/active_support/tagged_logging.rb
+++ b/activesupport/lib/active_support/tagged_logging.rb
@@ -43,7 +43,9 @@ module ActiveSupport
end
def current_tags
- Thread.current[:activesupport_tagged_logging_tags] ||= []
+ # We use our object ID here to avoid conflicting with other instances
+ thread_key = @thread_key ||= "activesupport_tagged_logging_tags:#{object_id}".freeze
+ Thread.current[thread_key] ||= []
end
private
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index a4ba5989b1..24b8f4b9f9 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -8,44 +8,43 @@ require 'active_support/testing/declarative'
require 'active_support/testing/isolation'
require 'active_support/testing/constant_lookup'
require 'active_support/testing/time_helpers'
+require 'active_support/testing/file_fixtures'
require 'active_support/core_ext/kernel/reporting'
-require 'active_support/deprecation'
module ActiveSupport
class TestCase < ::Minitest::Test
Assertion = Minitest::Assertion
class << self
+ # Sets the order in which test cases are run.
+ #
+ # ActiveSupport::TestCase.test_order = :random # => :random
+ #
+ # Valid values are:
+ # * +:random+ (to run tests in random order)
+ # * +:parallel+ (to run tests in parallel)
+ # * +:sorted+ (to run tests alphabetically by method name)
+ # * +:alpha+ (equivalent to +:sorted+)
def test_order=(new_order)
ActiveSupport.test_order = new_order
end
+ # Returns the order in which test cases are run.
+ #
+ # ActiveSupport::TestCase.test_order # => :random
+ #
+ # Possible values are +:random+, +:parallel+, +:alpha+, +:sorted+.
+ # Defaults to +:random+.
def test_order
test_order = ActiveSupport.test_order
if test_order.nil?
- ActiveSupport::Deprecation.warn "You did not specify a value for the " \
- "configuration option `active_support.test_order`. In Rails 5, " \
- "the default value of this option will change from `:sorted` to " \
- "`:random`.\n" \
- "To disable this warning and keep the current behavior, you can add " \
- "the following line to your `config/environments/test.rb`:\n" \
- "\n" \
- " Rails.application.configure do\n" \
- " config.active_support.test_order = :sorted\n" \
- " end\n" \
- "\n" \
- "Alternatively, you can opt into the future behavior by setting this " \
- "option to `:random`."
-
- test_order = :sorted
+ test_order = :random
self.test_order = test_order
end
test_order
end
-
- alias :my_tests_are_order_dependent! :i_suck_and_my_tests_are_order_dependent!
end
alias_method :method_name, :name
@@ -55,6 +54,7 @@ module ActiveSupport
include ActiveSupport::Testing::Assertions
include ActiveSupport::Testing::Deprecation
include ActiveSupport::Testing::TimeHelpers
+ include ActiveSupport::Testing::FileFixtures
extend ActiveSupport::Testing::Declarative
# test/unit backwards compatibility methods
@@ -73,7 +73,7 @@ module ActiveSupport
alias :assert_not_respond_to :refute_respond_to
alias :assert_not_same :refute_same
- # Fails if the block raises an exception.
+ # Reveals the intention that the block should not raise any exception.
#
# assert_nothing_raised do
# ...
diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb
index 11cca82995..8b649c193f 100644
--- a/activesupport/lib/active_support/testing/assertions.rb
+++ b/activesupport/lib/active_support/testing/assertions.rb
@@ -66,7 +66,7 @@ module ActiveSupport
exps = expressions.map { |e|
e.respond_to?(:call) ? e : lambda { eval(e, block.binding) }
}
- before = exps.map { |e| e.call }
+ before = exps.map(&:call)
yield
diff --git a/activesupport/lib/active_support/testing/file_fixtures.rb b/activesupport/lib/active_support/testing/file_fixtures.rb
new file mode 100644
index 0000000000..4c6a0801b8
--- /dev/null
+++ b/activesupport/lib/active_support/testing/file_fixtures.rb
@@ -0,0 +1,34 @@
+module ActiveSupport
+ module Testing
+ # Adds simple access to sample files called file fixtures.
+ # File fixtures are normal files stored in
+ # <tt>ActiveSupport::TestCase.file_fixture_path</tt>.
+ #
+ # File fixtures are represented as +Pathname+ objects.
+ # This makes it easy to extract specific information:
+ #
+ # file_fixture("example.txt").read # get the file's content
+ # file_fixture("example.mp3").size # get the file size
+ module FileFixtures
+ extend ActiveSupport::Concern
+
+ included do
+ class_attribute :file_fixture_path, instance_writer: false
+ end
+
+ # Returns a +Pathname+ to the fixture file named +fixture_name+.
+ #
+ # Raises ArgumentError if +fixture_name+ can't be found.
+ def file_fixture(fixture_name)
+ path = Pathname.new(File.join(file_fixture_path, fixture_name))
+
+ if path.exist?
+ path
+ else
+ msg = "the directory '%s' does not contain a file named '%s'"
+ raise ArgumentError, msg % [file_fixture_path, fixture_name]
+ end
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb
index 68bda35980..247df7423b 100644
--- a/activesupport/lib/active_support/testing/isolation.rb
+++ b/activesupport/lib/active_support/testing/isolation.rb
@@ -1,5 +1,3 @@
-require 'rbconfig'
-
module ActiveSupport
module Testing
module Isolation
@@ -12,7 +10,7 @@ module ActiveSupport
end
def self.forking_env?
- !ENV["NO_FORK"] && ((RbConfig::CONFIG['host_os'] !~ /mswin|mingw/) && (RUBY_PLATFORM !~ /java/))
+ !ENV["NO_FORK"] && Process.respond_to?(:fork)
end
@@class_setup_mutex = Mutex.new
diff --git a/activesupport/lib/active_support/testing/stream.rb b/activesupport/lib/active_support/testing/stream.rb
new file mode 100644
index 0000000000..895192ad05
--- /dev/null
+++ b/activesupport/lib/active_support/testing/stream.rb
@@ -0,0 +1,42 @@
+module ActiveSupport
+ module Testing
+ module Stream #:nodoc:
+ private
+
+ def silence_stream(stream)
+ old_stream = stream.dup
+ stream.reopen(IO::NULL)
+ stream.sync = true
+ yield
+ ensure
+ stream.reopen(old_stream)
+ old_stream.close
+ end
+
+ def quietly
+ silence_stream(STDOUT) do
+ silence_stream(STDERR) do
+ yield
+ end
+ end
+ end
+
+ def capture(stream)
+ stream = stream.to_s
+ captured_stream = Tempfile.new(stream)
+ stream_io = eval("$#{stream}")
+ origin_stream = stream_io.dup
+ stream_io.reopen(captured_stream)
+
+ yield
+
+ stream_io.rewind
+ return captured_stream.read
+ ensure
+ captured_stream.close
+ captured_stream.unlink
+ stream_io.reopen(origin_stream)
+ 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 8c63815660..3478b09423 100644
--- a/activesupport/lib/active_support/testing/time_helpers.rb
+++ b/activesupport/lib/active_support/testing/time_helpers.rb
@@ -39,15 +39,16 @@ module ActiveSupport
end
end
- # Containing helpers that helps you test passage of time.
+ # Contain 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+ and +Date.today+.
+ # stubbing +Time.now+, +Date.today+, and +DateTime.now+.
#
- # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
+ # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
# travel 1.day
- # Time.current # => Sun, 10 Nov 2013 15:34:49 EST -05:00
- # Date.current # => Sun, 10 Nov 2013
+ # Time.current # => Sun, 10 Nov 2013 15:34:49 EST -05:00
+ # Date.current # => Sun, 10 Nov 2013
+ # DateTime.current # => Sun, 10 Nov 2013 15:34:49 -0500
#
# This method also accepts a block, which will return the current time back to its original
# state at the end of the block:
@@ -61,13 +62,14 @@ module ActiveSupport
travel_to Time.now + duration, &block
end
- # Changes current time to the given time by stubbing +Time.now+ and
- # +Date.today+ to return the time or date passed into this method.
+ # Changes current time to the given time by stubbing +Time.now+,
+ # +Date.today+, and +DateTime.now+ to return the time or date passed into this method.
#
- # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
+ # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
# travel_to Time.new(2004, 11, 24, 01, 04, 44)
- # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
- # Date.current # => Wed, 24 Nov 2004
+ # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
+ # Date.current # => Wed, 24 Nov 2004
+ # DateTime.current # => Wed, 24 Nov 2004 01:04:44 -0500
#
# Dates are taken as their timestamp at the beginning of the day in the
# application time zone. <tt>Time.current</tt> returns said timestamp,
@@ -99,6 +101,7 @@ module ActiveSupport
simple_stubs.stub_object(Time, :now, now)
simple_stubs.stub_object(Date, :today, now.to_date)
+ simple_stubs.stub_object(DateTime, :now, now.to_datetime)
if block_given?
begin
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index dbee145196..c28de4e21c 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -121,16 +121,25 @@ module ActiveSupport
utc? && alternate_utc_string || TimeZone.seconds_to_utc_offset(utc_offset, colon)
end
- # Time uses +zone+ to display the time zone abbreviation, so we're
- # duck-typing it.
+ # Returns the time zone abbreviation.
+ #
+ # Time.zone = 'Eastern Time (US & Canada)' # => "Eastern Time (US & Canada)"
+ # Time.zone.now.zone # => "EST"
def zone
period.zone_identifier.to_s
end
+ # Returns a string of the object's date, time, zone and offset from UTC.
+ #
+ # Time.zone.now.httpdate # => "Thu, 04 Dec 2014 11:00:25 EST -05:00"
def inspect
"#{time.strftime('%a, %d %b %Y %H:%M:%S')} #{zone} #{formatted_offset}"
end
+ # Returns a string of the object's date and time in the ISO 8601 standard
+ # format.
+ #
+ # Time.zone.now.xmlschema # => "2014-12-04T11:02:37-05:00"
def xmlschema(fraction_digits = 0)
fraction = if fraction_digits.to_i > 0
(".%06i" % time.usec)[0, fraction_digits.to_i + 1]
@@ -187,7 +196,7 @@ module ActiveSupport
# Returns a string of the object's date and time.
# Accepts an optional <tt>format</tt>:
- # * <tt>:default</tt> - default value, mimics Ruby 1.9 Time#to_s format.
+ # * <tt>:default</tt> - default value, mimics Ruby Time#to_s format.
# * <tt>:db</tt> - format outputs time in UTC :db time. See Time#to_formatted_s(:db).
# * Any key in <tt>Time::DATE_FORMATS</tt> can be used. See active_support/core_ext/time/conversions.rb.
def to_s(format = :default)
@@ -196,7 +205,7 @@ module ActiveSupport
elsif formatter = ::Time::DATE_FORMATS[format]
formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
else
- "#{time.strftime("%Y-%m-%d %H:%M:%S")} #{formatted_offset(false, 'UTC')}" # mimicking Ruby 1.9 Time#to_s format
+ "#{time.strftime("%Y-%m-%d %H:%M:%S")} #{formatted_offset(false, 'UTC')}" # mimicking Ruby Time#to_s format
end
end
alias_method :to_formatted_s, :to_s
@@ -243,9 +252,23 @@ module ActiveSupport
utc.hash
end
+ # Adds an interval of time to the current object's time and returns that
+ # value as a new TimeWithZone object.
+ #
+ # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
+ # now = Time.zone.now # => Sun, 02 Nov 2014 01:26:28 EDT -04:00
+ # now + 1000 # => Sun, 02 Nov 2014 01:43:08 EDT -04:00
+ #
+ # If we're adding a Duration of variable length (i.e., years, months, days),
+ # move forward from #time, otherwise move forward from #utc, for accuracy
+ # when moving across DST boundaries.
+ #
+ # For instance, a time + 24.hours will advance exactly 24 hours, while a
+ # time + 1.day will advance 23-25 hours, depending on the day.
+ #
+ # now + 24.hours # => Mon, 03 Nov 2014 00:26:28 EST -05:00
+ # now + 1.day # => Mon, 03 Nov 2014 01:26:28 EST -05:00
def +(other)
- # If we're adding a Duration of variable length (i.e., years, months, days), move forward from #time,
- # otherwise move forward from #utc, for accuracy when moving across DST boundaries
if duration_of_variable_length?(other)
method_missing(:+, other)
else
@@ -253,10 +276,25 @@ module ActiveSupport
result.in_time_zone(time_zone)
end
end
+ alias_method :since, :+
+ # Returns a new TimeWithZone object that represents the difference between
+ # the current object's time and the +other+ time.
+ #
+ # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
+ # now = Time.zone.now # => Sun, 02 Nov 2014 01:26:28 EST -05:00
+ # now - 1000 # => Sun, 02 Nov 2014 01:09:48 EST -05:00
+ #
+ # If subtracting a Duration of variable length (i.e., years, months, days),
+ # move backward from #time, otherwise move backward from #utc, for accuracy
+ # when moving across DST boundaries.
+ #
+ # For instance, a time - 24.hours will go subtract exactly 24 hours, while a
+ # time - 1.day will subtract 23-25 hours, depending on the day.
+ #
+ # now - 24.hours # => Sat, 01 Nov 2014 02:26:28 EDT -04:00
+ # now - 1.day # => Sat, 01 Nov 2014 01:26:28 EDT -04:00
def -(other)
- # If we're subtracting a Duration of variable length (i.e., years, months, days), move backwards from #time,
- # otherwise move backwards #utc, for accuracy when moving across DST boundaries
if other.acts_like?(:time)
to_time - other.to_time
elsif duration_of_variable_length?(other)
@@ -267,16 +305,6 @@ module ActiveSupport
end
end
- def since(other)
- # If we're adding a Duration of variable length (i.e., years, months, days), move forward from #time,
- # otherwise move forward from #utc, for accuracy when moving across DST boundaries
- if duration_of_variable_length?(other)
- method_missing(:since, other)
- else
- utc.since(other).in_time_zone(time_zone)
- end
- end
-
def ago(other)
since(-other)
end
@@ -303,15 +331,27 @@ module ActiveSupport
[time.sec, time.min, time.hour, time.day, time.mon, time.year, time.wday, time.yday, dst?, zone]
end
+ # Returns the object's date and time as a floating point number of seconds
+ # since the Epoch (January 1, 1970 00:00 UTC).
+ #
+ # Time.zone.now.to_f # => 1417709320.285418
def to_f
utc.to_f
end
+ # Returns the object's date and time as an integer number of seconds
+ # since the Epoch (January 1, 1970 00:00 UTC).
+ #
+ # Time.zone.now.to_i # => 1417709320
def to_i
utc.to_i
end
alias_method :tv_sec, :to_i
+ # Returns the object's date and time as a rational number of seconds
+ # since the Epoch (January 1, 1970 00:00 UTC).
+ #
+ # Time.zone.now.to_r # => (708854548642709/500000)
def to_r
utc.to_r
end
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 55533a5d40..da39f0d245 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -111,9 +111,11 @@ module ActiveSupport
"Jerusalem" => "Asia/Jerusalem",
"Harare" => "Africa/Harare",
"Pretoria" => "Africa/Johannesburg",
+ "Kaliningrad" => "Europe/Kaliningrad",
"Moscow" => "Europe/Moscow",
"St. Petersburg" => "Europe/Moscow",
- "Volgograd" => "Europe/Moscow",
+ "Volgograd" => "Europe/Volgograd",
+ "Samara" => "Europe/Samara",
"Kuwait" => "Asia/Kuwait",
"Riyadh" => "Asia/Riyadh",
"Nairobi" => "Africa/Nairobi",
@@ -170,6 +172,7 @@ module ActiveSupport
"Guam" => "Pacific/Guam",
"Port Moresby" => "Pacific/Port_Moresby",
"Magadan" => "Asia/Magadan",
+ "Srednekolymsk" => "Asia/Srednekolymsk",
"Solomon Is." => "Pacific/Guadalcanal",
"New Caledonia" => "Pacific/Noumea",
"Fiji" => "Pacific/Fiji",
@@ -202,7 +205,7 @@ module ActiveSupport
end
def find_tzinfo(name)
- TZInfo::TimezoneProxy.new(MAPPING[name] || name)
+ TZInfo::Timezone.new(MAPPING[name] || name)
end
alias_method :create, :new
@@ -221,13 +224,6 @@ module ActiveSupport
@zones ||= zones_map.values.sort
end
- def zones_map
- @zones_map ||= begin
- MAPPING.each_key {|place| self[place]} # load all the zones
- @lazy_zones_map
- end
- end
-
# Locate a specific time zone object. If the argument is a string, it
# is interpreted to mean the name of the timezone to locate. If it is a
# numeric value it is either the hour offset, or the second offset, of the
@@ -237,7 +233,7 @@ module ActiveSupport
case arg
when String
begin
- @lazy_zones_map[arg] ||= create(arg).tap { |tz| tz.utc_offset }
+ @lazy_zones_map[arg] ||= create(arg)
rescue TZInfo::InvalidTimezoneIdentifier
nil
end
@@ -254,6 +250,14 @@ module ActiveSupport
def us_zones
@us_zones ||= all.find_all { |z| z.name =~ /US|Arizona|Indiana|Hawaii|Alaska/ }
end
+
+ private
+ def zones_map
+ @zones_map ||= begin
+ MAPPING.each_key {|place| self[place]} # load all the zones
+ @lazy_zones_map
+ end
+ end
end
include Comparable
diff --git a/activesupport/lib/active_support/values/unicode_tables.dat b/activesupport/lib/active_support/values/unicode_tables.dat
index 394ee95f4b..760be4c07a 100644
--- a/activesupport/lib/active_support/values/unicode_tables.dat
+++ b/activesupport/lib/active_support/values/unicode_tables.dat
Binary files differ
diff --git a/activesupport/lib/active_support/xml_mini/jdom.rb b/activesupport/lib/active_support/xml_mini/jdom.rb
index 27c64c4dca..f303daa1a7 100644
--- a/activesupport/lib/active_support/xml_mini/jdom.rb
+++ b/activesupport/lib/active_support/xml_mini/jdom.rb
@@ -141,7 +141,7 @@ module ActiveSupport
(0...attributes.length).each do |i|
attribute_hash[CONTENT_KEY] ||= ''
attribute_hash[attributes.item(i).name] = attributes.item(i).value
- end
+ end
attribute_hash
end
diff --git a/activesupport/lib/active_support/xml_mini/libxml.rb b/activesupport/lib/active_support/xml_mini/libxml.rb
index 47a2824186..bb0ea9c582 100644
--- a/activesupport/lib/active_support/xml_mini/libxml.rb
+++ b/activesupport/lib/active_support/xml_mini/libxml.rb
@@ -75,5 +75,5 @@ module LibXML #:nodoc:
end
end
-LibXML::XML::Document.send(:include, LibXML::Conversions::Document)
-LibXML::XML::Node.send(:include, LibXML::Conversions::Node)
+LibXML::XML::Document.include(LibXML::Conversions::Document)
+LibXML::XML::Node.include(LibXML::Conversions::Node)
diff --git a/activesupport/lib/active_support/xml_mini/nokogiri.rb b/activesupport/lib/active_support/xml_mini/nokogiri.rb
index 7398d4fa82..619cc7522d 100644
--- a/activesupport/lib/active_support/xml_mini/nokogiri.rb
+++ b/activesupport/lib/active_support/xml_mini/nokogiri.rb
@@ -77,7 +77,7 @@ module ActiveSupport
end
end
- Nokogiri::XML::Document.send(:include, Conversions::Document)
- Nokogiri::XML::Node.send(:include, Conversions::Node)
+ Nokogiri::XML::Document.include(Conversions::Document)
+ Nokogiri::XML::Node.include(Conversions::Node)
end
end
diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb
index f65ec962f9..7ffcae6007 100644
--- a/activesupport/test/abstract_unit.rb
+++ b/activesupport/test/abstract_unit.rb
@@ -38,8 +38,3 @@ def jruby_skip(message = '')
end
require 'mocha/setup' # FIXME: stop using mocha
-
-# FIXME: we have tests that depend on run order, we should fix that and
-# remove this method call.
-require 'active_support/test_case'
-ActiveSupport::TestCase.test_order = :sorted
diff --git a/activesupport/test/array_inquirer_test.rb b/activesupport/test/array_inquirer_test.rb
new file mode 100644
index 0000000000..b25e5cca86
--- /dev/null
+++ b/activesupport/test/array_inquirer_test.rb
@@ -0,0 +1,36 @@
+require 'abstract_unit'
+require 'active_support/core_ext/array'
+
+class ArrayInquirerTest < ActiveSupport::TestCase
+ def setup
+ @array_inquirer = ActiveSupport::ArrayInquirer.new([:mobile, :tablet])
+ end
+
+ def test_individual
+ assert @array_inquirer.mobile?
+ assert @array_inquirer.tablet?
+ assert_not @array_inquirer.desktop?
+ end
+
+ def test_any
+ assert @array_inquirer.any?(:mobile, :desktop)
+ assert @array_inquirer.any?(:watch, :tablet)
+ assert_not @array_inquirer.any?(:desktop, :watch)
+ end
+
+ def test_any_with_block
+ assert @array_inquirer.any? { |v| v == :mobile }
+ assert_not @array_inquirer.any? { |v| v == :desktop }
+ end
+
+ def test_respond_to
+ assert_respond_to @array_inquirer, :development?
+ end
+
+ def test_inquiry
+ result = [:mobile, :tablet].inquiry
+
+ assert_instance_of ActiveSupport::ArrayInquirer, result
+ assert_equal @array_inquirer, result
+ end
+end
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index 5945605f7b..527538ed9a 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -399,15 +399,16 @@ module CacheStoreBehavior
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"
+ 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'
end
- assert_equal "baz", result
+ assert_equal 'baz', result
end
def test_race_condition_protection_is_limited
@@ -437,6 +438,17 @@ module CacheStoreBehavior
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"
+ end
+ assert_equal "baz", result
+ end
+
def test_crazy_key_characters
crazy_key = "#/:*(<+=> )&$%@?;'\"\'`~-"
assert @cache.write(crazy_key, "1", :raw => true)
@@ -672,6 +684,7 @@ class FileStoreTest < ActiveSupport::TestCase
def teardown
FileUtils.rm_r(cache_dir)
+ rescue Errno::ENOENT
end
def cache_dir
@@ -691,6 +704,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)
@@ -1021,6 +1039,15 @@ class CacheStoreLoggerTest < ActiveSupport::TestCase
@cache.mute { @cache.fetch('foo') { 'bar' } }
assert @buffer.string.blank?
end
+
+ def test_multi_read_loggin
+ @cache.write 'hello', 'goodbye'
+ @cache.write 'world', 'earth'
+
+ @cache.read_multi('hello', 'world')
+
+ assert_match "Caches multi read:\n- hello\n- world", @buffer.string
+ end
end
class CacheEntryTest < ActiveSupport::TestCase
@@ -1047,30 +1074,4 @@ class CacheEntryTest < ActiveSupport::TestCase
assert_equal value, entry.value
assert_equal value.bytesize, entry.size
end
-
- def test_restoring_version_4beta1_entries
- version_4beta1_entry = ActiveSupport::Cache::Entry.allocate
- version_4beta1_entry.instance_variable_set(:@v, "hello")
- version_4beta1_entry.instance_variable_set(:@x, Time.now.to_i + 60)
- entry = Marshal.load(Marshal.dump(version_4beta1_entry))
- assert_equal "hello", entry.value
- assert_equal false, entry.expired?
- end
-
- def test_restoring_compressed_version_4beta1_entries
- version_4beta1_entry = ActiveSupport::Cache::Entry.allocate
- version_4beta1_entry.instance_variable_set(:@v, Zlib::Deflate.deflate(Marshal.dump("hello")))
- version_4beta1_entry.instance_variable_set(:@c, true)
- entry = Marshal.load(Marshal.dump(version_4beta1_entry))
- assert_equal "hello", entry.value
- end
-
- def test_restoring_expired_version_4beta1_entries
- version_4beta1_entry = ActiveSupport::Cache::Entry.allocate
- version_4beta1_entry.instance_variable_set(:@v, "hello")
- version_4beta1_entry.instance_variable_set(:@x, Time.now.to_i - 1)
- entry = Marshal.load(Marshal.dump(version_4beta1_entry))
- assert_equal "hello", entry.value
- assert_equal true, entry.expired?
- end
end
diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb
index 32c2dfdfc0..cda9732cae 100644
--- a/activesupport/test/callbacks_test.rb
+++ b/activesupport/test/callbacks_test.rb
@@ -49,7 +49,7 @@ module CallbacksTest
def self.before(model)
model.history << [:before_save, :class]
end
-
+
def self.after(model)
model.history << [:after_save, :class]
end
@@ -73,8 +73,8 @@ module CallbacksTest
class PersonSkipper < Person
skip_callback :save, :before, :before_save_method, :if => :yes
- skip_callback :save, :after, :before_save_method, :unless => :yes
- skip_callback :save, :after, :before_save_method, :if => :no
+ skip_callback :save, :after, :after_save_method, :unless => :yes
+ skip_callback :save, :after, :after_save_method, :if => :no
skip_callback :save, :before, :before_save_method, :unless => :no
skip_callback :save, :before, CallbackClass , :if => :yes
def yes; true; end
@@ -501,21 +501,18 @@ module CallbacksTest
end
end
- class CallbackTerminator
+ class AbstractCallbackTerminator
include ActiveSupport::Callbacks
- define_callbacks :save, :terminator => ->(_,result) { result == :halt }
-
- set_callback :save, :before, :first
- set_callback :save, :before, :second
- set_callback :save, :around, :around_it
- set_callback :save, :before, :third
- set_callback :save, :after, :first
- set_callback :save, :around, :around_it
- set_callback :save, :after, :second
- set_callback :save, :around, :around_it
- set_callback :save, :after, :third
-
+ def self.set_save_callbacks
+ set_callback :save, :before, :first
+ set_callback :save, :before, :second
+ set_callback :save, :around, :around_it
+ set_callback :save, :before, :third
+ set_callback :save, :after, :first
+ set_callback :save, :around, :around_it
+ set_callback :save, :after, :third
+ end
attr_reader :history, :saved, :halted
def initialize
@@ -552,6 +549,39 @@ module CallbacksTest
end
end
+ class CallbackTerminator < AbstractCallbackTerminator
+ define_callbacks :save, terminator: ->(_, result_lambda) { result_lambda.call == :halt }
+ set_save_callbacks
+ end
+
+ class CallbackTerminatorSkippingAfterCallbacks < AbstractCallbackTerminator
+ define_callbacks :save, terminator: ->(_, result_lambda) { result_lambda.call == :halt },
+ skip_after_callbacks_if_terminated: true
+ set_save_callbacks
+ end
+
+ class CallbackDefaultTerminator < AbstractCallbackTerminator
+ define_callbacks :save
+
+ def second
+ @history << "second"
+ throw(:abort)
+ end
+
+ set_save_callbacks
+ end
+
+ class CallbackFalseTerminator < AbstractCallbackTerminator
+ define_callbacks :save
+
+ def second
+ @history << "second"
+ false
+ end
+
+ set_save_callbacks
+ end
+
class CallbackObject
def before(caller)
caller.record << "before"
@@ -688,10 +718,10 @@ module CallbacksTest
end
class CallbackTerminatorTest < ActiveSupport::TestCase
- def test_termination
+ def test_termination_skips_following_before_and_around_callbacks
terminator = CallbackTerminator.new
terminator.save
- assert_equal ["first", "second", "third", "second", "first"], terminator.history
+ assert_equal ["first", "second", "third", "first"], terminator.history
end
def test_termination_invokes_hook
@@ -707,6 +737,73 @@ module CallbacksTest
end
end
+ class CallbackTerminatorSkippingAfterCallbacksTest < ActiveSupport::TestCase
+ def test_termination_skips_after_callbacks
+ terminator = CallbackTerminatorSkippingAfterCallbacks.new
+ terminator.save
+ assert_equal ["first", "second"], terminator.history
+ end
+ end
+
+ class CallbackDefaultTerminatorTest < ActiveSupport::TestCase
+ def test_default_termination
+ terminator = CallbackDefaultTerminator.new
+ terminator.save
+ assert_equal ["first", "second", "third", "first"], terminator.history
+ end
+
+ def test_default_termination_invokes_hook
+ terminator = CallbackDefaultTerminator.new
+ terminator.save
+ assert_equal :second, terminator.halted
+ end
+
+ def test_block_never_called_if_abort_is_thrown
+ obj = CallbackDefaultTerminator.new
+ obj.save
+ assert !obj.saved
+ end
+ end
+
+ class CallbackFalseTerminatorWithoutConfigTest < ActiveSupport::TestCase
+ def test_returning_false_halts_callback_if_config_variable_is_not_set
+ obj = CallbackFalseTerminator.new
+ assert_deprecated do
+ obj.save
+ assert_equal :second, obj.halted
+ assert !obj.saved
+ end
+ end
+ end
+
+ class CallbackFalseTerminatorWithConfigTrueTest < ActiveSupport::TestCase
+ def setup
+ ActiveSupport::Callbacks::CallbackChain.halt_and_display_warning_on_return_false = true
+ end
+
+ def test_returning_false_halts_callback_if_config_variable_is_true
+ obj = CallbackFalseTerminator.new
+ assert_deprecated do
+ obj.save
+ assert_equal :second, obj.halted
+ assert !obj.saved
+ end
+ end
+ end
+
+ class CallbackFalseTerminatorWithConfigFalseTest < ActiveSupport::TestCase
+ def setup
+ ActiveSupport::Callbacks::CallbackChain.halt_and_display_warning_on_return_false = false
+ end
+
+ def test_returning_false_does_not_halt_callback_if_config_variable_is_false
+ obj = CallbackFalseTerminator.new
+ obj.save
+ assert_equal nil, obj.halted
+ assert obj.saved
+ end
+ end
+
class HyphenatedKeyTest < ActiveSupport::TestCase
def test_save
obj = HyphenatedCallbacks.new
@@ -924,7 +1021,7 @@ module CallbacksTest
define_callbacks :foo
n.times { set_callback :foo, :before, callback }
def run; run_callbacks :foo; end
- def self.skip(thing); skip_callback :foo, :before, thing; end
+ def self.skip(*things); skip_callback :foo, :before, *things; end
}
end
@@ -973,11 +1070,11 @@ module CallbacksTest
}
end
- def test_skip_lambda # removes nothing
+ def test_skip_lambda # raises error
calls = []
callback = ->(o) { calls << o }
klass = build_class(callback)
- 10.times { klass.skip callback }
+ assert_raises(ArgumentError) { klass.skip callback }
klass.new.run
assert_equal 10, calls.length
end
@@ -991,11 +1088,29 @@ module CallbacksTest
assert_equal 0, calls.length
end
- def test_skip_eval # removes nothing
+ def test_skip_string # raises error
calls = []
klass = build_class("bar")
klass.class_eval { define_method(:bar) { calls << klass } }
- klass.skip "bar"
+ assert_raises(ArgumentError) { klass.skip "bar" }
+ klass.new.run
+ assert_equal 1, calls.length
+ end
+
+ def test_skip_undefined_callback # raises error
+ calls = []
+ klass = build_class(:bar)
+ klass.class_eval { define_method(:bar) { calls << klass } }
+ assert_raises(ArgumentError) { klass.skip :qux }
+ klass.new.run
+ assert_equal 1, calls.length
+ end
+
+ def test_skip_without_raise # removes nothing
+ calls = []
+ klass = build_class(:bar)
+ klass.class_eval { define_method(:bar) { calls << klass } }
+ klass.skip :qux, raise: false
klass.new.run
assert_equal 1, calls.length
end
diff --git a/activesupport/test/concern_test.rb b/activesupport/test/concern_test.rb
index 60bd8a06aa..253c1adc23 100644
--- a/activesupport/test/concern_test.rb
+++ b/activesupport/test/concern_test.rb
@@ -59,24 +59,24 @@ class ConcernTest < ActiveSupport::TestCase
end
def test_module_is_included_normally
- @klass.send(:include, Baz)
+ @klass.include(Baz)
assert_equal "baz", @klass.new.baz
assert @klass.included_modules.include?(ConcernTest::Baz)
end
def test_class_methods_are_extended
- @klass.send(:include, Baz)
+ @klass.include(Baz)
assert_equal "baz", @klass.baz
assert_equal ConcernTest::Baz::ClassMethods, (class << @klass; self.included_modules; end)[0]
end
def test_included_block_is_ran
- @klass.send(:include, Baz)
+ @klass.include(Baz)
assert_equal true, @klass.included_ran
end
def test_modules_dependencies_are_met
- @klass.send(:include, Bar)
+ @klass.include(Bar)
assert_equal "bar", @klass.new.bar
assert_equal "bar+baz", @klass.new.baz
assert_equal "bar's baz + baz", @klass.baz
@@ -84,7 +84,7 @@ class ConcernTest < ActiveSupport::TestCase
end
def test_dependencies_with_multiple_modules
- @klass.send(:include, Foo)
+ @klass.include(Foo)
assert_equal [ConcernTest::Foo, ConcernTest::Bar, ConcernTest::Baz], @klass.included_modules[0..2]
end
diff --git a/activesupport/test/configurable_test.rb b/activesupport/test/configurable_test.rb
index ef847fc557..5d22ded2de 100644
--- a/activesupport/test/configurable_test.rb
+++ b/activesupport/test/configurable_test.rb
@@ -111,6 +111,14 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase
end
end
+ test 'the config_accessor method should not be publicly callable' do
+ assert_raises NoMethodError do
+ Class.new {
+ include ActiveSupport::Configurable
+ }.config_accessor :foo
+ end
+ end
+
def assert_method_defined(object, method)
methods = object.public_methods.map(&:to_s)
assert methods.include?(method.to_s), "Expected #{methods.inspect} to include #{method.to_s.inspect}"
diff --git a/activesupport/test/core_ext/array/access_test.rb b/activesupport/test/core_ext/array/access_test.rb
index f14f64421d..3f1e0c4cb4 100644
--- a/activesupport/test/core_ext/array/access_test.rb
+++ b/activesupport/test/core_ext/array/access_test.rb
@@ -27,4 +27,8 @@ class AccessTest < ActiveSupport::TestCase
assert_equal array[4], array.fifth
assert_equal array[41], array.forty_two
end
+
+ def test_without
+ assert_equal [1, 2, 4], [1, 2, 3, 4, 5].without(3, 5)
+ end
end
diff --git a/activesupport/test/core_ext/array/conversions_test.rb b/activesupport/test/core_ext/array/conversions_test.rb
index 577b889410..507e13f968 100644
--- a/activesupport/test/core_ext/array/conversions_test.rb
+++ b/activesupport/test/core_ext/array/conversions_test.rb
@@ -60,6 +60,12 @@ class ToSentenceTest < ActiveSupport::TestCase
assert_equal exception.message, "Unknown key: :passing. Valid keys are: :words_connector, :two_words_connector, :last_word_connector, :locale"
end
+
+ def test_always_returns_string
+ assert_instance_of String, [ActiveSupport::SafeBuffer.new('one')].to_sentence
+ assert_instance_of String, [ActiveSupport::SafeBuffer.new('one'), 'two'].to_sentence
+ assert_instance_of String, [ActiveSupport::SafeBuffer.new('one'), 'two', 'three'].to_sentence
+ end
end
class ToSTest < ActiveSupport::TestCase
diff --git a/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb b/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb
deleted file mode 100644
index e634679d20..0000000000
--- a/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-require 'abstract_unit'
-
-class BigDecimalYamlConversionsTest < ActiveSupport::TestCase
- def test_to_yaml
- assert_deprecated { require 'active_support/core_ext/big_decimal/yaml_conversions' }
- assert_match("--- 100000.30020320320000000000000000000000000000001\n", BigDecimal.new('100000.30020320320000000000000000000000000000001').to_yaml)
- assert_match("--- .Inf\n", BigDecimal.new('Infinity').to_yaml)
- assert_match("--- .NaN\n", BigDecimal.new('NaN').to_yaml)
- assert_match("--- -.Inf\n", BigDecimal.new('-Infinity').to_yaml)
- end
-end
diff --git a/activesupport/test/core_ext/date_and_time_behavior.rb b/activesupport/test/core_ext/date_and_time_behavior.rb
index b4ef5a0597..784547bdf8 100644
--- a/activesupport/test/core_ext/date_and_time_behavior.rb
+++ b/activesupport/test/core_ext/date_and_time_behavior.rb
@@ -6,11 +6,21 @@ module DateAndTimeBehavior
assert_equal date_time_init(2005,2,28,10,10,10), date_time_init(2005,3,2,10,10,10).yesterday.yesterday
end
+ def test_prev_day
+ assert_equal date_time_init(2005,2,21,10,10,10), date_time_init(2005,2,22,10,10,10).prev_day
+ assert_equal date_time_init(2005,2,28,10,10,10), date_time_init(2005,3,2,10,10,10).prev_day.prev_day
+ end
+
def test_tomorrow
assert_equal date_time_init(2005,2,23,10,10,10), date_time_init(2005,2,22,10,10,10).tomorrow
assert_equal date_time_init(2005,3,2,10,10,10), date_time_init(2005,2,28,10,10,10).tomorrow.tomorrow
end
+ def test_next_day
+ assert_equal date_time_init(2005,2,23,10,10,10), date_time_init(2005,2,22,10,10,10).next_day
+ assert_equal date_time_init(2005,3,2,10,10,10), date_time_init(2005,2,28,10,10,10).next_day.next_day
+ end
+
def test_days_ago
assert_equal date_time_init(2005,6,4,10,10,10), date_time_init(2005,6,5,10,10,10).days_ago(1)
assert_equal date_time_init(2005,5,31,10,10,10), date_time_init(2005,6,5,10,10,10).days_ago(5)
@@ -115,6 +125,28 @@ module DateAndTimeBehavior
end
end
+ def test_next_week_at_same_time
+ assert_equal date_time_init(2005,2,28,15,15,10), date_time_init(2005,2,22,15,15,10).next_week(:monday, same_time: true)
+ assert_equal date_time_init(2005,3,4,15,15,10), date_time_init(2005,2,22,15,15,10).next_week(:friday, same_time: true)
+ assert_equal date_time_init(2006,10,30,0,0,0), date_time_init(2006,10,23,0,0,0).next_week(:monday, same_time: true)
+ assert_equal date_time_init(2006,11,1,0,0,0), date_time_init(2006,10,23,0,0,0).next_week(:wednesday, same_time: true)
+ end
+
+ def test_next_weekday_on_wednesday
+ assert_equal date_time_init(2015,1,8,0,0,0), date_time_init(2015,1,7,0,0,0).next_weekday
+ assert_equal date_time_init(2015,1,8,15,15,10), date_time_init(2015,1,7,15,15,10).next_weekday
+ end
+
+ def test_next_weekday_on_friday
+ assert_equal date_time_init(2015,1,5,0,0,0), date_time_init(2015,1,2,0,0,0).next_weekday
+ assert_equal date_time_init(2015,1,5,15,15,10), date_time_init(2015,1,2,15,15,10).next_weekday
+ end
+
+ def test_next_weekday_on_saturday
+ assert_equal date_time_init(2015,1,5,0,0,0), date_time_init(2015,1,3,0,0,0).next_weekday
+ assert_equal date_time_init(2015,1,5,15,15,10), date_time_init(2015,1,3,15,15,10).next_weekday
+ end
+
def test_next_month_on_31st
assert_equal date_time_init(2005,9,30,15,15,10), date_time_init(2005,8,31,15,15,10).next_month
end
@@ -144,6 +176,29 @@ module DateAndTimeBehavior
end
end
+ def test_prev_week_at_same_time
+ assert_equal date_time_init(2005,2,21,15,15,10), date_time_init(2005,3,1,15,15,10).prev_week(:monday, same_time: true)
+ assert_equal date_time_init(2005,2,22,15,15,10), date_time_init(2005,3,1,15,15,10).prev_week(:tuesday, same_time: true)
+ assert_equal date_time_init(2005,2,25,15,15,10), date_time_init(2005,3,1,15,15,10).prev_week(:friday, same_time: true)
+ assert_equal date_time_init(2006,10,30,0,0,0), date_time_init(2006,11,6,0,0,0).prev_week(:monday, same_time: true)
+ assert_equal date_time_init(2006,11,15,0,0,0), date_time_init(2006,11,23,0,0,0).prev_week(:wednesday, same_time: true)
+ end
+
+ def test_prev_weekday_on_wednesday
+ assert_equal date_time_init(2015,1,6,0,0,0), date_time_init(2015,1,7,0,0,0).prev_weekday
+ assert_equal date_time_init(2015,1,6,15,15,10), date_time_init(2015,1,7,15,15,10).prev_weekday
+ end
+
+ def test_prev_weekday_on_monday
+ assert_equal date_time_init(2015,1,2,0,0,0), date_time_init(2015,1,5,0,0,0).prev_weekday
+ assert_equal date_time_init(2015,1,2,15,15,10), date_time_init(2015,1,5,15,15,10).prev_weekday
+ end
+
+ def test_prev_weekday_on_sunday
+ assert_equal date_time_init(2015,1,2,0,0,0), date_time_init(2015,1,4,0,0,0).prev_weekday
+ assert_equal date_time_init(2015,1,2,15,15,10), date_time_init(2015,1,4,15,15,10).prev_weekday
+ end
+
def test_prev_month_on_31st
assert_equal date_time_init(2004,2,29,10,10,10), date_time_init(2004,3,31,10,10,10).prev_month
end
@@ -231,6 +286,21 @@ module DateAndTimeBehavior
end
end
+ def test_on_weekend_on_saturday
+ assert date_time_init(2015,1,3,0,0,0).on_weekend?
+ assert date_time_init(2015,1,3,15,15,10).on_weekend?
+ end
+
+ def test_on_weekend_on_sunday
+ assert date_time_init(2015,1,4,0,0,0).on_weekend?
+ assert date_time_init(2015,1,4,15,15,10).on_weekend?
+ end
+
+ def test_on_weekend_on_monday
+ assert_not date_time_init(2015,1,5,0,0,0).on_weekend?
+ assert_not date_time_init(2015,1,5,15,15,10).on_weekend?
+ end
+
def with_bw_default(bw = :monday)
old_bw = Date.beginning_of_week
Date.beginning_of_week = bw
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index 5a2af7f943..c283b546e6 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -30,7 +30,6 @@ class DurationTest < ActiveSupport::TestCase
assert ActiveSupport::Duration === 1.day
assert !(ActiveSupport::Duration === 1.day.to_i)
assert !(ActiveSupport::Duration === 'foo')
- assert !(ActiveSupport::Duration === ActiveSupport::ProxyObject.new)
end
def test_equals
@@ -71,6 +70,15 @@ class DurationTest < ActiveSupport::TestCase
assert_equal '14 days', 1.fortnight.inspect
end
+ def test_inspect_locale
+ current_locale = I18n.default_locale
+ I18n.default_locale = :de
+ I18n.backend.store_translations(:de, { support: { array: { last_word_connector: ' und ' } } })
+ assert_equal '10 years, 1 month und 1 day', (10.years + 1.month + 1.day).inspect
+ ensure
+ I18n.default_locale = current_locale
+ end
+
def test_minus_with_duration_does_not_break_subtraction_of_date_from_date
assert_nothing_raised { Date.today - Date.today }
end
@@ -200,4 +208,16 @@ class DurationTest < ActiveSupport::TestCase
def test_hash
assert_equal 1.minute.hash, 60.seconds.hash
end
+
+ def test_comparable
+ assert_equal(-1, (0.seconds <=> 1.second))
+ assert_equal(-1, (1.second <=> 1.minute))
+ assert_equal(-1, (1 <=> 1.minute))
+ assert_equal(0, (0.seconds <=> 0.seconds))
+ assert_equal(0, (0.seconds <=> 0.minutes))
+ assert_equal(0, (1.second <=> 1.second))
+ assert_equal(1, (1.second <=> 0.second))
+ assert_equal(1, (1.minute <=> 1.second))
+ assert_equal(1, (61 <=> 1.minute))
+ end
end
diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb
index 6fcf6e8743..e5d8ae7882 100644
--- a/activesupport/test/core_ext/enumerable_test.rb
+++ b/activesupport/test/core_ext/enumerable_test.rb
@@ -71,14 +71,14 @@ class EnumerableTests < ActiveSupport::TestCase
def test_index_by
payments = GenericEnumerable.new([ Payment.new(5), Payment.new(15), Payment.new(10) ])
assert_equal({ 5 => Payment.new(5), 15 => Payment.new(15), 10 => Payment.new(10) },
- payments.index_by { |p| p.price })
+ payments.index_by(&:price))
assert_equal Enumerator, payments.index_by.class
if Enumerator.method_defined? :size
assert_equal nil, payments.index_by.size
assert_equal 42, (1..42).index_by.size
end
assert_equal({ 5 => Payment.new(5), 15 => Payment.new(15), 10 => Payment.new(10) },
- payments.index_by.each { |p| p.price })
+ payments.index_by.each(&:price))
end
def test_many
@@ -103,4 +103,11 @@ class EnumerableTests < ActiveSupport::TestCase
assert_equal true, GenericEnumerable.new([ 1 ]).exclude?(2)
assert_equal false, GenericEnumerable.new([ 1 ]).exclude?(1)
end
+
+ def test_without
+ assert_equal [1, 2, 4], GenericEnumerable.new((1..5).to_a).without(3, 5)
+ assert_equal [1, 2, 4], (1..5).to_a.without(3, 5)
+ assert_equal [1, 2, 4], (1..5).to_set.without(3, 5)
+ assert_equal({foo: 1, baz: 3}, {foo: 1, bar: 2, baz: 3}.without(:bar))
+ end
end
diff --git a/activesupport/test/core_ext/file_test.rb b/activesupport/test/core_ext/file_test.rb
index 2c04e9687c..cde0132b97 100644
--- a/activesupport/test/core_ext/file_test.rb
+++ b/activesupport/test/core_ext/file_test.rb
@@ -57,6 +57,16 @@ class AtomicWriteTest < ActiveSupport::TestCase
File.unlink(file_name) rescue nil
end
+ def test_atomic_write_returns_result_from_yielded_block
+ block_return_value = File.atomic_write(file_name, Dir.pwd) do |file|
+ "Hello world!"
+ end
+
+ assert_equal "Hello world!", block_return_value
+ ensure
+ File.unlink(file_name) rescue nil
+ end
+
private
def file_name
"atomic.file"
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 5e9fdfd872..e10bee5e00 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -365,7 +365,7 @@ class HashExtTest < ActiveSupport::TestCase
:member? => true }
hashes.each do |name, hash|
- method_map.sort_by { |m| m.to_s }.each do |meth, expected|
+ method_map.sort_by(&:to_s).each do |meth, expected|
assert_equal(expected, hash.__send__(meth, 'a'),
"Calling #{name}.#{meth} 'a'")
assert_equal(expected, hash.__send__(meth, :a),
@@ -1549,6 +1549,14 @@ class HashToXmlTest < ActiveSupport::TestCase
assert_not_same hash_wia, hash_wia.with_indifferent_access
end
+
+ def test_allows_setting_frozen_array_values_with_indifferent_access
+ value = [1, 2, 3].freeze
+ hash = HashWithIndifferentAccess.new
+ hash[:key] = value
+ assert_equal hash[:key], value
+ end
+
def test_should_copy_the_default_value_when_converting_to_hash_with_indifferent_access
hash = Hash.new(3)
hash_wia = hash.with_indifferent_access
diff --git a/activesupport/test/core_ext/kernel_test.rb b/activesupport/test/core_ext/kernel_test.rb
index a87af0007c..503e6595cb 100644
--- a/activesupport/test/core_ext/kernel_test.rb
+++ b/activesupport/test/core_ext/kernel_test.rb
@@ -15,7 +15,6 @@ class KernelTest < ActiveSupport::TestCase
assert_equal old_verbose, $VERBOSE
end
-
def test_enable_warnings
enable_warnings { assert_equal true, $VERBOSE }
assert_equal 1234, enable_warnings { 1234 }
@@ -29,57 +28,11 @@ class KernelTest < ActiveSupport::TestCase
assert_equal old_verbose, $VERBOSE
end
-
- def test_silence_stream
- old_stream_position = STDOUT.tell
- silence_stream(STDOUT) { STDOUT.puts 'hello world' }
- assert_equal old_stream_position, STDOUT.tell
- rescue Errno::ESPIPE
- # Skip if we can't stream.tell
- end
-
- def test_silence_stream_closes_file_descriptors
- stream = StringIO.new
- dup_stream = StringIO.new
- stream.stubs(:dup).returns(dup_stream)
- dup_stream.expects(:close)
- silence_stream(stream) { stream.puts 'hello world' }
- end
-
- def test_quietly
- old_stdout_position, old_stderr_position = STDOUT.tell, STDERR.tell
- assert_deprecated do
- quietly do
- puts 'see me, feel me'
- STDERR.puts 'touch me, heal me'
- end
- end
- assert_equal old_stdout_position, STDOUT.tell
- assert_equal old_stderr_position, STDERR.tell
- rescue Errno::ESPIPE
- # Skip if we can't STDERR.tell
- end
-
def test_class_eval
o = Object.new
class << o; @x = 1; end
assert_equal 1, o.class_eval { @x }
end
-
- def test_capture
- assert_deprecated do
- assert_equal 'STDERR', capture(:stderr) { $stderr.print 'STDERR' }
- end
- assert_deprecated do
- assert_equal 'STDOUT', capture(:stdout) { print 'STDOUT' }
- end
- assert_deprecated do
- assert_equal "STDERR\n", capture(:stderr) { system('echo STDERR 1>&2') }
- end
- assert_deprecated do
- assert_equal "STDOUT\n", capture(:stdout) { system('echo STDOUT') }
- end
- end
end
class KernelSuppressTest < ActiveSupport::TestCase
@@ -112,27 +65,3 @@ class MockStdErr
puts(message)
end
end
-
-class KernelDebuggerTest < ActiveSupport::TestCase
- def test_debugger_not_available_message_to_stderr
- old_stderr = $stderr
- $stderr = MockStdErr.new
- debugger
- assert_match(/Debugger requested/, $stderr.output.first)
- ensure
- $stderr = old_stderr
- end
-
- def test_debugger_not_available_message_to_rails_logger
- rails = Class.new do
- def self.logger
- @logger ||= MockStdErr.new
- end
- end
- Object.const_set(:Rails, rails)
- debugger
- assert_match(/Debugger requested/, rails.logger.output.first)
- ensure
- Object.send(:remove_const, :Rails)
- end
-end if RUBY_VERSION < '2.0.0'
diff --git a/activesupport/test/core_ext/load_error_test.rb b/activesupport/test/core_ext/load_error_test.rb
index 5f804c749b..b2a75a2bcc 100644
--- a/activesupport/test/core_ext/load_error_test.rb
+++ b/activesupport/test/core_ext/load_error_test.rb
@@ -1,26 +1,11 @@
require 'abstract_unit'
require 'active_support/core_ext/load_error'
-class TestMissingSourceFile < ActiveSupport::TestCase
- def test_with_require
- assert_raise(MissingSourceFile) { require 'no_this_file_don\'t_exist' }
- end
- def test_with_load
- assert_raise(MissingSourceFile) { load 'nor_does_this_one' }
- end
- def test_path
- begin load 'nor/this/one.rb'
- rescue MissingSourceFile => e
- assert_equal 'nor/this/one.rb', e.path
- end
- end
- def test_is_missing
- begin load 'nor_does_this_one'
- rescue MissingSourceFile => e
- assert e.is_missing?('nor_does_this_one')
- assert e.is_missing?('nor_does_this_one.rb')
- assert_not e.is_missing?('some_other_file')
+class TestMissingSourceFile < ActiveSupport::TestCase
+ def test_it_is_deprecated
+ assert_deprecated do
+ MissingSourceFile.new
end
end
end
diff --git a/activesupport/test/core_ext/marshal_test.rb b/activesupport/test/core_ext/marshal_test.rb
index 8f3f710dfd..e49330128b 100644
--- a/activesupport/test/core_ext/marshal_test.rb
+++ b/activesupport/test/core_ext/marshal_test.rb
@@ -15,7 +15,7 @@ class MarshalTest < ActiveSupport::TestCase
sanity_data = ["test", [1, 2, 3], {a: [1, 2, 3]}, ActiveSupport::TestCase]
sanity_data.each do |obj|
dumped = Marshal.dump(obj)
- assert_equal Marshal.load_without_autoloading(dumped), Marshal.load(dumped)
+ assert_equal Marshal.method(:load).super_method.call(dumped), Marshal.load(dumped)
end
end
@@ -121,4 +121,4 @@ class MarshalTest < ActiveSupport::TestCase
end
end
end
-end \ No newline at end of file
+end
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index 3c49c4d14f..bdfbadcf1d 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -78,7 +78,7 @@ Product = Struct.new(:name) do
def type
@type ||= begin
- :thing_without_same_method_name_as_delegated.name
+ nil.type_name
end
end
end
@@ -358,148 +358,178 @@ class MethodAliasingTest < ActiveSupport::TestCase
Object.instance_eval { remove_const :FooClassWithBarMethod }
end
- def test_alias_method_chain
- assert @instance.respond_to?(:bar)
- feature_aliases = [:bar_with_baz, :bar_without_baz]
+ def test_alias_method_chain_deprecated
+ assert_deprecated(/alias_method_chain/) do
+ Module.new do
+ def base
+ end
+
+ def base_with_deprecated
+ end
- feature_aliases.each do |method|
- assert !@instance.respond_to?(method)
+ alias_method_chain :base, :deprecated
+ end
end
+ end
- assert_equal 'bar', @instance.bar
+ def test_alias_method_chain
+ assert_deprecated(/alias_method_chain/) do
+ assert @instance.respond_to?(:bar)
+ feature_aliases = [:bar_with_baz, :bar_without_baz]
- FooClassWithBarMethod.class_eval { include BarMethodAliaser }
+ feature_aliases.each do |method|
+ assert !@instance.respond_to?(method)
+ end
- feature_aliases.each do |method|
- assert_respond_to @instance, method
- end
+ assert_equal 'bar', @instance.bar
+
+ FooClassWithBarMethod.class_eval { include BarMethodAliaser }
+
+ feature_aliases.each do |method|
+ assert_respond_to @instance, method
+ end
- assert_equal 'bar_with_baz', @instance.bar
- assert_equal 'bar', @instance.bar_without_baz
+ assert_equal 'bar_with_baz', @instance.bar
+ assert_equal 'bar', @instance.bar_without_baz
+ end
end
def test_alias_method_chain_with_punctuation_method
- FooClassWithBarMethod.class_eval do
- def quux!; 'quux' end
- end
+ assert_deprecated(/alias_method_chain/) do
+ FooClassWithBarMethod.class_eval do
+ def quux!; 'quux' end
+ end
- assert !@instance.respond_to?(:quux_with_baz!)
- FooClassWithBarMethod.class_eval do
- include BarMethodAliaser
- alias_method_chain :quux!, :baz
- end
- assert_respond_to @instance, :quux_with_baz!
+ assert !@instance.respond_to?(:quux_with_baz!)
+ FooClassWithBarMethod.class_eval do
+ include BarMethodAliaser
+ alias_method_chain :quux!, :baz
+ end
+ assert_respond_to @instance, :quux_with_baz!
- assert_equal 'quux_with_baz', @instance.quux!
- assert_equal 'quux', @instance.quux_without_baz!
+ assert_equal 'quux_with_baz', @instance.quux!
+ assert_equal 'quux', @instance.quux_without_baz!
+ end
end
def test_alias_method_chain_with_same_names_between_predicates_and_bang_methods
- FooClassWithBarMethod.class_eval do
- def quux!; 'quux!' end
- def quux?; true end
- def quux=(v); 'quux=' end
- end
+ assert_deprecated(/alias_method_chain/) do
+ FooClassWithBarMethod.class_eval do
+ def quux!; 'quux!' end
+ def quux?; true end
+ def quux=(v); 'quux=' end
+ end
- assert !@instance.respond_to?(:quux_with_baz!)
- assert !@instance.respond_to?(:quux_with_baz?)
- assert !@instance.respond_to?(:quux_with_baz=)
+ assert !@instance.respond_to?(:quux_with_baz!)
+ assert !@instance.respond_to?(:quux_with_baz?)
+ assert !@instance.respond_to?(:quux_with_baz=)
- FooClassWithBarMethod.class_eval { include BarMethodAliaser }
- assert_respond_to @instance, :quux_with_baz!
- assert_respond_to @instance, :quux_with_baz?
- assert_respond_to @instance, :quux_with_baz=
+ FooClassWithBarMethod.class_eval { include BarMethodAliaser }
+ assert_respond_to @instance, :quux_with_baz!
+ assert_respond_to @instance, :quux_with_baz?
+ assert_respond_to @instance, :quux_with_baz=
- FooClassWithBarMethod.alias_method_chain :quux!, :baz
- assert_equal 'quux!_with_baz', @instance.quux!
- assert_equal 'quux!', @instance.quux_without_baz!
+ FooClassWithBarMethod.alias_method_chain :quux!, :baz
+ assert_equal 'quux!_with_baz', @instance.quux!
+ assert_equal 'quux!', @instance.quux_without_baz!
- FooClassWithBarMethod.alias_method_chain :quux?, :baz
- assert_equal false, @instance.quux?
- assert_equal true, @instance.quux_without_baz?
+ FooClassWithBarMethod.alias_method_chain :quux?, :baz
+ assert_equal false, @instance.quux?
+ assert_equal true, @instance.quux_without_baz?
- FooClassWithBarMethod.alias_method_chain :quux=, :baz
- assert_equal 'quux=_with_baz', @instance.send(:quux=, 1234)
- assert_equal 'quux=', @instance.send(:quux_without_baz=, 1234)
+ FooClassWithBarMethod.alias_method_chain :quux=, :baz
+ assert_equal 'quux=_with_baz', @instance.send(:quux=, 1234)
+ assert_equal 'quux=', @instance.send(:quux_without_baz=, 1234)
+ end
end
def test_alias_method_chain_with_feature_punctuation
- FooClassWithBarMethod.class_eval do
- def quux; 'quux' end
- def quux?; 'quux?' end
- include BarMethodAliaser
- alias_method_chain :quux, :baz!
- end
+ assert_deprecated(/alias_method_chain/) do
+ FooClassWithBarMethod.class_eval do
+ def quux; 'quux' end
+ def quux?; 'quux?' end
+ include BarMethodAliaser
+ alias_method_chain :quux, :baz!
+ end
- assert_nothing_raised do
- assert_equal 'quux_with_baz', @instance.quux_with_baz!
- end
+ assert_nothing_raised do
+ assert_equal 'quux_with_baz', @instance.quux_with_baz!
+ end
- assert_raise(NameError) do
- FooClassWithBarMethod.alias_method_chain :quux?, :baz!
+ assert_raise(NameError) do
+ FooClassWithBarMethod.alias_method_chain :quux?, :baz!
+ end
end
end
def test_alias_method_chain_yields_target_and_punctuation
- args = nil
+ assert_deprecated(/alias_method_chain/) do
+ args = nil
- FooClassWithBarMethod.class_eval do
- def quux?; end
- include BarMethods
+ FooClassWithBarMethod.class_eval do
+ def quux?; end
+ include BarMethods
- FooClassWithBarMethod.alias_method_chain :quux?, :baz do |target, punctuation|
- args = [target, punctuation]
+ FooClassWithBarMethod.alias_method_chain :quux?, :baz do |target, punctuation|
+ args = [target, punctuation]
+ end
end
- end
- assert_not_nil args
- assert_equal 'quux', args[0]
- assert_equal '?', args[1]
+ assert_not_nil args
+ assert_equal 'quux', args[0]
+ assert_equal '?', args[1]
+ end
end
def test_alias_method_chain_preserves_private_method_status
- FooClassWithBarMethod.class_eval do
- def duck; 'duck' end
- include BarMethodAliaser
- private :duck
- alias_method_chain :duck, :orange
- end
+ assert_deprecated(/alias_method_chain/) do
+ FooClassWithBarMethod.class_eval do
+ def duck; 'duck' end
+ include BarMethodAliaser
+ private :duck
+ alias_method_chain :duck, :orange
+ end
- assert_raise NoMethodError do
- @instance.duck
- end
+ assert_raise NoMethodError do
+ @instance.duck
+ end
- assert_equal 'duck_with_orange', @instance.instance_eval { duck }
- assert FooClassWithBarMethod.private_method_defined?(:duck)
+ assert_equal 'duck_with_orange', @instance.instance_eval { duck }
+ assert FooClassWithBarMethod.private_method_defined?(:duck)
+ end
end
def test_alias_method_chain_preserves_protected_method_status
- FooClassWithBarMethod.class_eval do
- def duck; 'duck' end
- include BarMethodAliaser
- protected :duck
- alias_method_chain :duck, :orange
- end
+ assert_deprecated(/alias_method_chain/) do
+ FooClassWithBarMethod.class_eval do
+ def duck; 'duck' end
+ include BarMethodAliaser
+ protected :duck
+ alias_method_chain :duck, :orange
+ end
- assert_raise NoMethodError do
- @instance.duck
- end
+ assert_raise NoMethodError do
+ @instance.duck
+ end
- assert_equal 'duck_with_orange', @instance.instance_eval { duck }
- assert FooClassWithBarMethod.protected_method_defined?(:duck)
+ assert_equal 'duck_with_orange', @instance.instance_eval { duck }
+ assert FooClassWithBarMethod.protected_method_defined?(:duck)
+ end
end
def test_alias_method_chain_preserves_public_method_status
- FooClassWithBarMethod.class_eval do
- def duck; 'duck' end
- include BarMethodAliaser
- public :duck
- alias_method_chain :duck, :orange
- end
+ assert_deprecated(/alias_method_chain/) do
+ FooClassWithBarMethod.class_eval do
+ def duck; 'duck' end
+ include BarMethodAliaser
+ public :duck
+ alias_method_chain :duck, :orange
+ end
- assert_equal 'duck_with_orange', @instance.duck
- assert FooClassWithBarMethod.public_method_defined?(:duck)
+ assert_equal 'duck_with_orange', @instance.duck
+ assert FooClassWithBarMethod.public_method_defined?(:duck)
+ end
end
def test_delegate_with_case
diff --git a/activesupport/test/core_ext/object/blank_test.rb b/activesupport/test/core_ext/object/blank_test.rb
index 246bc7fa61..8a5e385dd7 100644
--- a/activesupport/test/core_ext/object/blank_test.rb
+++ b/activesupport/test/core_ext/object/blank_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'active_support/core_ext/object/blank'
diff --git a/activesupport/test/core_ext/object/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb
index 8cc39ae7b9..042f5cfb34 100644
--- a/activesupport/test/core_ext/object/duplicable_test.rb
+++ b/activesupport/test/core_ext/object/duplicable_test.rb
@@ -6,18 +6,12 @@ require 'active_support/core_ext/numeric/time'
class DuplicableTest < ActiveSupport::TestCase
RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, method(:puts)]
ALLOW_DUP = ['1', Object.new, /foo/, [], {}, Time.now, Class.new, Module.new]
-
- # Needed to support Ruby 1.9.x, as it doesn't allow dup on BigDecimal, instead
- # raises TypeError exception. Checking here on the runtime whether BigDecimal
- # will allow dup or not.
- begin
- bd = BigDecimal.new('4.56')
- ALLOW_DUP << bd.dup
- rescue TypeError
- RAISE_DUP << bd
- end
+ ALLOW_DUP << BigDecimal.new('4.56')
def test_duplicable
+ rubinius_skip "* Method#dup is allowed at the moment on Rubinius\n" \
+ "* https://github.com/rubinius/rubinius/issues/3089"
+
RAISE_DUP.each do |v|
assert !v.duplicable?
assert_raises(TypeError, v.class.name) { v.dup }
diff --git a/activesupport/test/core_ext/object/itself_test.rb b/activesupport/test/core_ext/object/itself_test.rb
deleted file mode 100644
index 65db0ddf40..0000000000
--- a/activesupport/test/core_ext/object/itself_test.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-require 'abstract_unit'
-require 'active_support/core_ext/object'
-
-class Object::ItselfTest < ActiveSupport::TestCase
- test 'itself returns self' do
- object = 'fun'
- assert_equal object, object.itself
- end
-end
diff --git a/activesupport/test/core_ext/object/try_test.rb b/activesupport/test/core_ext/object/try_test.rb
index efc6beaf02..89438675c1 100644
--- a/activesupport/test/core_ext/object/try_test.rb
+++ b/activesupport/test/core_ext/object/try_test.rb
@@ -52,11 +52,11 @@ class ObjectTryTest < ActiveSupport::TestCase
end
def test_try_only_block
- assert_equal @string.reverse, @string.try { |s| s.reverse }
+ assert_equal @string.reverse, @string.try(&:reverse)
end
def test_try_only_block_bang
- assert_equal @string.reverse, @string.try! { |s| s.reverse }
+ assert_equal @string.reverse, @string.try!(&:reverse)
end
def test_try_only_block_nil
diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb
index 98c4ec6b5e..f096328cee 100644
--- a/activesupport/test/core_ext/range_ext_test.rb
+++ b/activesupport/test/core_ext/range_ext_test.rb
@@ -115,11 +115,11 @@ class RangeTest < ActiveSupport::TestCase
def test_date_time_with_each
datetime = DateTime.now
- assert ((datetime - 1.hour)..datetime).each {}
+ assert(((datetime - 1.hour)..datetime).each {})
end
def test_date_time_with_step
datetime = DateTime.now
- assert ((datetime - 1.hour)..datetime).step(1) {}
+ assert(((datetime - 1.hour)..datetime).step(1) {})
end
end
diff --git a/activesupport/test/core_ext/secure_random_test.rb b/activesupport/test/core_ext/secure_random_test.rb
new file mode 100644
index 0000000000..dfacb7fe9f
--- /dev/null
+++ b/activesupport/test/core_ext/secure_random_test.rb
@@ -0,0 +1,20 @@
+require 'abstract_unit'
+require 'active_support/core_ext/securerandom'
+
+class SecureRandomTest < ActiveSupport::TestCase
+ def test_base58
+ s1 = SecureRandom.base58
+ s2 = SecureRandom.base58
+
+ assert_not_equal s1, s2
+ assert_equal 16, s1.length
+ end
+
+ def test_base58_with_length
+ s1 = SecureRandom.base58(24)
+ s2 = SecureRandom.base58(24)
+
+ assert_not_equal s1, s2
+ assert_equal 24, s1.length
+ end
+end
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 35095f2b2d..cb24147ab3 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'date'
require 'abstract_unit'
require 'inflector_test_cases'
@@ -250,6 +249,15 @@ class StringInflectionsTest < ActiveSupport::TestCase
assert_equal "Hello<br>Big<br>World!", "Hello<br>Big<br>World!".truncate_words(3, :omission => "[...]", :separator => '<br>')
end
+ def test_truncate_words_with_complex_string
+ Timeout.timeout(10) do
+ complex_string = "aa aa aaa aa aaa aaa aaa aa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaaa aaaaa aaaaa aaaaaa aa aa aa aaa aa aaa aa aa aa aa a aaa aaa \n a aaa <<s"
+ assert_equal complex_string.truncate_words(80), complex_string
+ end
+ rescue Timeout::Error
+ assert false
+ end
+
def test_truncate_multibyte
assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding(Encoding::UTF_8),
"\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding(Encoding::UTF_8).truncate(10)
@@ -266,6 +274,12 @@ class StringInflectionsTest < ActiveSupport::TestCase
assert_equal "This is a good day to die", original
end
+ def test_remove_for_multiple_occurrences
+ original = "This is a good day to die to die"
+ assert_equal "This is a good day", original.remove(" to die")
+ assert_equal "This is a good day to die to die", original
+ end
+
def test_remove!
original = "This is a very good day to die"
assert_equal "This is a good day to die", original.remove!(" very")
@@ -275,15 +289,11 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
def test_constantize
- run_constantize_tests_on do |string|
- string.constantize
- end
+ run_constantize_tests_on(&:constantize)
end
def test_safe_constantize
- run_safe_constantize_tests_on do |string|
- string.safe_constantize
- end
+ run_safe_constantize_tests_on(&:safe_constantize)
end
end
@@ -414,11 +424,11 @@ class StringConversionsTest < ActiveSupport::TestCase
end
def test_partial_string_to_time
- with_env_tz "Europe/Moscow" do
+ with_env_tz "Europe/Moscow" do # use timezone which does not observe DST.
now = Time.now
assert_equal Time.local(now.year, now.month, now.day, 23, 50), "23:50".to_time
assert_equal Time.utc(now.year, now.month, now.day, 23, 50), "23:50".to_time(:utc)
- assert_equal Time.local(now.year, now.month, now.day, 18, 50), "13:50 -0100".to_time
+ assert_equal Time.local(now.year, now.month, now.day, 17, 50), "13:50 -0100".to_time
assert_equal Time.utc(now.year, now.month, now.day, 23, 50), "22:50 -0100".to_time(:utc)
end
end
@@ -665,16 +675,6 @@ class OutputSafetyTest < ActiveSupport::TestCase
assert_equal other, "&lt;foo&gt;other"
end
- test "Deprecated #prepend! method is still present" do
- other = "other".html_safe
-
- assert_deprecated do
- other.prepend! "<foo>"
- end
-
- assert_equal other, "&lt;foo&gt;other"
- end
-
test "Concatting safe onto unsafe yields unsafe" do
@other_string = "other"
diff --git a/activesupport/test/core_ext/struct_test.rb b/activesupport/test/core_ext/struct_test.rb
deleted file mode 100644
index 0dff7b32d2..0000000000
--- a/activesupport/test/core_ext/struct_test.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-require 'abstract_unit'
-require 'active_support/core_ext/struct'
-
-class StructExt < ActiveSupport::TestCase
- def test_to_h
- x = Struct.new(:foo, :bar)
- z = x.new(1, 2)
- assert_equal({ foo: 1, bar: 2 }, z.to_h)
- end
-end
diff --git a/activesupport/test/core_ext/thread_test.rb b/activesupport/test/core_ext/thread_test.rb
deleted file mode 100644
index 6a7c6e0604..0000000000
--- a/activesupport/test/core_ext/thread_test.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-require 'abstract_unit'
-require 'active_support/core_ext/thread'
-
-class ThreadExt < ActiveSupport::TestCase
- def test_main_thread_variable_in_enumerator
- assert_equal Thread.main, Thread.current
-
- Thread.current.thread_variable_set :foo, "bar"
-
- thread, value = Fiber.new {
- Fiber.yield [Thread.current, Thread.current.thread_variable_get(:foo)]
- }.resume
-
- assert_equal Thread.current, thread
- assert_equal Thread.current.thread_variable_get(:foo), value
- end
-
- def test_thread_variable_in_enumerator
- Thread.new {
- Thread.current.thread_variable_set :foo, "bar"
-
- thread, value = Fiber.new {
- Fiber.yield [Thread.current, Thread.current.thread_variable_get(:foo)]
- }.resume
-
- assert_equal Thread.current, thread
- assert_equal Thread.current.thread_variable_get(:foo), value
- }.join
- end
-
- def test_thread_variables
- assert_equal [], Thread.new { Thread.current.thread_variables }.join.value
-
- t = Thread.new {
- Thread.current.thread_variable_set(:foo, "bar")
- Thread.current.thread_variables
- }
- assert_equal [:foo], t.join.value
- end
-
- def test_thread_variable?
- assert_not Thread.new { Thread.current.thread_variable?("foo") }.join.value
- t = Thread.new {
- Thread.current.thread_variable_set("foo", "bar")
- }.join
-
- assert t.thread_variable?("foo")
- assert t.thread_variable?(:foo)
- assert_not t.thread_variable?(:bar)
- end
-
- def test_thread_variable_strings_and_symbols_are_the_same_key
- t = Thread.new {}.join
- t.thread_variable_set("foo", "bar")
- assert_equal "bar", t.thread_variable_get(:foo)
- end
-
- def test_thread_variable_frozen
- t = Thread.new { }.join
- t.freeze
- assert_raises(RuntimeError) do
- t.thread_variable_set(:foo, "bar")
- end
- end
-
- def test_thread_variable_frozen_after_set
- t = Thread.new { }.join
- t.thread_variable_set :foo, "bar"
- t.freeze
- assert_raises(RuntimeError) do
- t.thread_variable_set(:baz, "qux")
- end
- end
-
-end
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index ad4062e5fe..92c233d567 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -812,6 +812,8 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_no_method_error_has_proper_context
+ rubinius_skip "Error message inconsistency"
+
e = assert_raises(NoMethodError) {
@twz.this_method_does_not_exist
}
diff --git a/activesupport/test/core_ext/uri_ext_test.rb b/activesupport/test/core_ext/uri_ext_test.rb
index 43a5997ddd..1694fe7e72 100644
--- a/activesupport/test/core_ext/uri_ext_test.rb
+++ b/activesupport/test/core_ext/uri_ext_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'uri'
require 'active_support/core_ext/uri'
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index f2f60167c4..4a1d90bfd6 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -16,15 +16,18 @@ module ModuleWithConstant
end
class DependenciesTest < ActiveSupport::TestCase
- def teardown
- ActiveSupport::Dependencies.clear
+ include DependenciesTestHelpers
+
+ setup do
+ @loaded_features_copy = $LOADED_FEATURES.dup
end
- include DependenciesTestHelpers
+ teardown do
+ ActiveSupport::Dependencies.clear
+ $LOADED_FEATURES.replace(@loaded_features_copy)
+ end
def test_depend_on_path
- skip "LoadError#path does not exist" if RUBY_VERSION < '2.0.0'
-
expected = assert_raises(LoadError) do
Kernel.require 'omgwtfbbq'
end
@@ -53,8 +56,7 @@ class DependenciesTest < ActiveSupport::TestCase
assert_equal 2, ActiveSupport::Dependencies.loaded.size
end
ensure
- Object.send(:remove_const, :ServiceOne) if Object.const_defined?(:ServiceOne)
- Object.send(:remove_const, :ServiceTwo) if Object.const_defined?(:ServiceTwo)
+ remove_constants(:ServiceOne, :ServiceTwo)
end
def test_tracking_identical_loaded_files
@@ -64,11 +66,11 @@ class DependenciesTest < ActiveSupport::TestCase
assert_equal 1, ActiveSupport::Dependencies.loaded.size
end
ensure
- Object.send(:remove_const, :ServiceOne) if Object.const_defined?(:ServiceOne)
+ remove_constants(:ServiceOne)
end
def test_missing_dependency_raises_missing_source_file
- assert_raise(MissingSourceFile) { require_dependency("missing_service") }
+ assert_raise(LoadError) { require_dependency("missing_service") }
end
def test_dependency_which_raises_exception_isnt_added_to_loaded_set
@@ -84,8 +86,8 @@ class DependenciesTest < ActiveSupport::TestCase
assert_equal 'Loading me failed, so do not add to loaded or history.', e.message
assert_equal count + 1, $raises_exception_load_count
- assert !ActiveSupport::Dependencies.loaded.include?(filename)
- assert !ActiveSupport::Dependencies.history.include?(filename)
+ assert_not ActiveSupport::Dependencies.loaded.include?(filename)
+ assert_not ActiveSupport::Dependencies.history.include?(filename)
end
end
end
@@ -93,7 +95,6 @@ class DependenciesTest < ActiveSupport::TestCase
def test_dependency_which_raises_doesnt_blindly_call_blame_file!
with_loading do
filename = 'dependencies/raises_exception_without_blame_file'
-
assert_raises(Exception) { require_dependency filename }
end
end
@@ -101,13 +102,12 @@ class DependenciesTest < ActiveSupport::TestCase
def test_warnings_should_be_enabled_on_first_load
with_loading 'dependencies' do
old_warnings, ActiveSupport::Dependencies.warnings_on_first_load = ActiveSupport::Dependencies.warnings_on_first_load, true
-
filename = "check_warnings"
expanded = File.expand_path("#{File.dirname(__FILE__)}/dependencies/#{filename}")
$check_warnings_load_count = 0
- assert !ActiveSupport::Dependencies.loaded.include?(expanded)
- assert !ActiveSupport::Dependencies.history.include?(expanded)
+ assert_not ActiveSupport::Dependencies.loaded.include?(expanded)
+ assert_not ActiveSupport::Dependencies.history.include?(expanded)
silence_warnings { require_dependency filename }
assert_equal 1, $check_warnings_load_count
@@ -115,7 +115,7 @@ class DependenciesTest < ActiveSupport::TestCase
assert ActiveSupport::Dependencies.loaded.include?(expanded)
ActiveSupport::Dependencies.clear
- assert !ActiveSupport::Dependencies.loaded.include?(expanded)
+ assert_not ActiveSupport::Dependencies.loaded.include?(expanded)
assert ActiveSupport::Dependencies.history.include?(expanded)
silence_warnings { require_dependency filename }
@@ -124,7 +124,7 @@ class DependenciesTest < ActiveSupport::TestCase
assert ActiveSupport::Dependencies.loaded.include?(expanded)
ActiveSupport::Dependencies.clear
- assert !ActiveSupport::Dependencies.loaded.include?(expanded)
+ assert_not ActiveSupport::Dependencies.loaded.include?(expanded)
assert ActiveSupport::Dependencies.history.include?(expanded)
enable_warnings { require_dependency filename }
@@ -160,7 +160,7 @@ class DependenciesTest < ActiveSupport::TestCase
def test_ensures_the_expected_constant_is_defined
with_autoloading_fixtures do
e = assert_raise(LoadError) { Typo }
- assert_match %r{Unable to autoload constant Typo, expected .*activesupport/test/autoloading_fixtures/typo.rb to define it}, e.message
+ assert_match %r{Unable to autoload constant Typo, expected .*/test/autoloading_fixtures/typo.rb to define it}, e.message
end
end
@@ -178,7 +178,7 @@ class DependenciesTest < ActiveSupport::TestCase
assert_equal 1, TypO
e = assert_raise(LoadError) { Typo }
- assert_match %r{Unable to autoload constant Typo, expected .*activesupport/test/autoloading_fixtures/typo.rb to define it}, e.message
+ assert_match %r{Unable to autoload constant Typo, expected .*/test/autoloading_fixtures/typo.rb to define it}, e.message
end
end
@@ -203,43 +203,49 @@ class DependenciesTest < ActiveSupport::TestCase
def test_directories_manifest_as_modules_unless_const_defined
with_autoloading_fixtures do
assert_kind_of Module, ModuleFolder
- Object.__send__ :remove_const, :ModuleFolder
end
+ ensure
+ remove_constants(:ModuleFolder)
end
def test_module_with_nested_class
with_autoloading_fixtures do
assert_kind_of Class, ModuleFolder::NestedClass
- Object.__send__ :remove_const, :ModuleFolder
end
+ ensure
+ remove_constants(:ModuleFolder)
end
def test_module_with_nested_inline_class
with_autoloading_fixtures do
assert_kind_of Class, ModuleFolder::InlineClass
- Object.__send__ :remove_const, :ModuleFolder
end
+ ensure
+ remove_constants(:ModuleFolder)
end
def test_directories_may_manifest_as_nested_classes
with_autoloading_fixtures do
assert_kind_of Class, ClassFolder
- Object.__send__ :remove_const, :ClassFolder
end
+ ensure
+ remove_constants(:ClassFolder)
end
def test_class_with_nested_class
with_autoloading_fixtures do
assert_kind_of Class, ClassFolder::NestedClass
- Object.__send__ :remove_const, :ClassFolder
end
+ ensure
+ remove_constants(:ClassFolder)
end
def test_class_with_nested_inline_class
with_autoloading_fixtures do
assert_kind_of Class, ClassFolder::InlineClass
- Object.__send__ :remove_const, :ClassFolder
end
+ ensure
+ remove_constants(:ClassFolder)
end
def test_class_with_nested_inline_subclass_of_parent
@@ -247,8 +253,9 @@ class DependenciesTest < ActiveSupport::TestCase
assert_kind_of Class, ClassFolder::ClassFolderSubclass
assert_kind_of Class, ClassFolder
assert_equal 'indeed', ClassFolder::ClassFolderSubclass::ConstantInClassFolder
- Object.__send__ :remove_const, :ClassFolder
end
+ ensure
+ remove_constants(:ClassFolder)
end
def test_nested_class_can_access_sibling
@@ -256,16 +263,15 @@ class DependenciesTest < ActiveSupport::TestCase
sibling = ModuleFolder::NestedClass.class_eval "NestedSibling"
assert defined?(ModuleFolder::NestedSibling)
assert_equal ModuleFolder::NestedSibling, sibling
- Object.__send__ :remove_const, :ModuleFolder
end
+ ensure
+ remove_constants(:ModuleFolder)
end
def test_doesnt_break_normal_require
path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
original_path = $:.dup
- original_features = $".dup
$:.push(path)
-
with_autoloading_fixtures do
# The _ = assignments are to prevent warnings
_ = RequiresConstant
@@ -277,15 +283,13 @@ class DependenciesTest < ActiveSupport::TestCase
assert defined?(LoadedConstant)
end
ensure
- remove_constants(:RequiresConstant, :LoadedConstant, :LoadsConstant)
- $".replace(original_features)
+ remove_constants(:RequiresConstant, :LoadedConstant)
$:.replace(original_path)
end
def test_doesnt_break_normal_require_nested
path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
original_path = $:.dup
- original_features = $".dup
$:.push(path)
with_autoloading_fixtures do
@@ -300,14 +304,12 @@ class DependenciesTest < ActiveSupport::TestCase
end
ensure
remove_constants(:RequiresConstant, :LoadedConstant, :LoadsConstant)
- $".replace(original_features)
$:.replace(original_path)
end
def test_require_returns_true_when_file_not_yet_required
path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
original_path = $:.dup
- original_features = $".dup
$:.push(path)
with_loading do
@@ -315,14 +317,12 @@ class DependenciesTest < ActiveSupport::TestCase
end
ensure
remove_constants(:LoadedConstant)
- $".replace(original_features)
$:.replace(original_path)
end
def test_require_returns_true_when_file_not_yet_required_even_when_no_new_constants_added
path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
original_path = $:.dup
- original_features = $".dup
$:.push(path)
with_loading do
@@ -331,14 +331,12 @@ class DependenciesTest < ActiveSupport::TestCase
end
ensure
remove_constants(:LoadedConstant)
- $".replace(original_features)
$:.replace(original_path)
end
def test_require_returns_false_when_file_already_required
path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
original_path = $:.dup
- original_features = $".dup
$:.push(path)
with_loading do
@@ -347,7 +345,6 @@ class DependenciesTest < ActiveSupport::TestCase
end
ensure
remove_constants(:LoadedConstant)
- $".replace(original_features)
$:.replace(original_path)
end
@@ -355,14 +352,11 @@ class DependenciesTest < ActiveSupport::TestCase
with_loading do
assert_raise(LoadError) { require 'this_file_dont_exist_dude' }
end
- ensure
- remove_constants(:LoadedConstant)
end
def test_load_returns_true_when_file_found
path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
original_path = $:.dup
- original_features = $".dup
$:.push(path)
with_loading do
@@ -371,7 +365,6 @@ class DependenciesTest < ActiveSupport::TestCase
end
ensure
remove_constants(:LoadedConstant)
- $".replace(original_features)
$:.replace(original_path)
end
@@ -379,17 +372,16 @@ class DependenciesTest < ActiveSupport::TestCase
with_loading do
assert_raise(LoadError) { load 'this_file_dont_exist_dude.rb' }
end
- ensure
- remove_constants(:LoadedConstant)
end
def failing_test_access_thru_and_upwards_fails
with_autoloading_fixtures do
- assert ! defined?(ModuleFolder)
+ assert_not defined?(ModuleFolder)
assert_raise(NameError) { ModuleFolder::Object }
assert_raise(NameError) { ModuleFolder::NestedClass::Object }
- Object.__send__ :remove_const, :ModuleFolder
end
+ ensure
+ remove_constants(:ModuleFolder)
end
def test_non_existing_const_raises_name_error_with_fully_qualified_name
@@ -402,6 +394,8 @@ class DependenciesTest < ActiveSupport::TestCase
assert_equal "uninitialized constant A::B::DoesNotExist", e.message
assert_equal :DoesNotExist, e.name
end
+ ensure
+ remove_constants(:A)
end
def test_smart_name_error_strings
@@ -491,9 +485,9 @@ class DependenciesTest < ActiveSupport::TestCase
nil_name = Module.new
def nil_name.name() nil end
assert !ActiveSupport::Dependencies.autoloaded?(nil_name)
-
- Object.class_eval { remove_const :ModuleFolder }
end
+ ensure
+ remove_constants(:ModuleFolder)
end
def test_qualified_name_for
@@ -551,6 +545,8 @@ class DependenciesTest < ActiveSupport::TestCase
assert_kind_of Module, ::ModuleWithCustomConstMissing::A
assert_kind_of String, ::ModuleWithCustomConstMissing::A::B
end
+ ensure
+ remove_constants(:ModuleWithCustomConstMissing)
end
def test_const_missing_in_anonymous_modules_loads_top_level_constants
@@ -559,6 +555,8 @@ class DependenciesTest < ActiveSupport::TestCase
klass = Class.new.class_eval "E"
assert_equal E, klass
end
+ ensure
+ remove_constants(:E)
end
def test_const_missing_in_anonymous_modules_raises_if_the_constant_belongs_to_Object
@@ -570,18 +568,22 @@ class DependenciesTest < ActiveSupport::TestCase
assert_equal 'E cannot be autoloaded from an anonymous class or module', e.message
assert_equal :E, e.name
end
+ ensure
+ remove_constants(:E)
end
def test_removal_from_tree_should_be_detected
with_loading 'dependencies' do
c = ServiceOne
ActiveSupport::Dependencies.clear
- assert ! defined?(ServiceOne)
+ assert_not defined?(ServiceOne)
e = assert_raise ArgumentError do
ActiveSupport::Dependencies.load_missing_constant(c, :FakeMissing)
end
assert_match %r{ServiceOne has been removed from the module tree}i, e.message
end
+ ensure
+ remove_constants(:ServiceOne)
end
def test_references_should_work
@@ -590,42 +592,46 @@ class DependenciesTest < ActiveSupport::TestCase
service_one_first = ServiceOne
assert_equal service_one_first, c.get("ServiceOne")
ActiveSupport::Dependencies.clear
- assert ! defined?(ServiceOne)
-
+ assert_not defined?(ServiceOne)
service_one_second = ServiceOne
assert_not_equal service_one_first, c.get("ServiceOne")
assert_equal service_one_second, c.get("ServiceOne")
end
+ ensure
+ remove_constants(:ServiceOne)
end
def test_constantize_shortcut_for_cached_constant_lookups
with_loading 'dependencies' do
assert_equal ServiceOne, ActiveSupport::Dependencies.constantize("ServiceOne")
end
+ ensure
+ remove_constants(:ServiceOne)
end
def test_nested_load_error_isnt_rescued
with_loading 'dependencies' do
- assert_raise(MissingSourceFile) do
+ assert_raise(LoadError) do
RequiresNonexistent1
end
end
end
def test_autoload_once_paths_do_not_add_to_autoloaded_constants
+ old_path = ActiveSupport::Dependencies.autoload_once_paths
with_autoloading_fixtures do
ActiveSupport::Dependencies.autoload_once_paths = ActiveSupport::Dependencies.autoload_paths.dup
- assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder")
- assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass")
- assert ! ActiveSupport::Dependencies.autoloaded?(ModuleFolder)
+ assert_not ActiveSupport::Dependencies.autoloaded?("ModuleFolder")
+ assert_not ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass")
+ assert_not ActiveSupport::Dependencies.autoloaded?(ModuleFolder)
1 if ModuleFolder::NestedClass # 1 if to avoid warning
- assert ! ActiveSupport::Dependencies.autoloaded?(ModuleFolder::NestedClass)
+ assert_not ActiveSupport::Dependencies.autoloaded?(ModuleFolder::NestedClass)
end
ensure
- Object.class_eval { remove_const :ModuleFolder }
- ActiveSupport::Dependencies.autoload_once_paths = []
+ remove_constants(:ModuleFolder)
+ ActiveSupport::Dependencies.autoload_once_paths = old_path
end
def test_autoload_once_pathnames_do_not_add_to_autoloaded_constants
@@ -634,15 +640,15 @@ class DependenciesTest < ActiveSupport::TestCase
ActiveSupport::Dependencies.autoload_paths = pathnames
ActiveSupport::Dependencies.autoload_once_paths = pathnames
- assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder")
- assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass")
- assert ! ActiveSupport::Dependencies.autoloaded?(ModuleFolder)
+ assert_not ActiveSupport::Dependencies.autoloaded?("ModuleFolder")
+ assert_not ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass")
+ assert_not ActiveSupport::Dependencies.autoloaded?(ModuleFolder)
1 if ModuleFolder::NestedClass # 1 if to avoid warning
- assert ! ActiveSupport::Dependencies.autoloaded?(ModuleFolder::NestedClass)
+ assert_not ActiveSupport::Dependencies.autoloaded?(ModuleFolder::NestedClass)
end
ensure
- Object.class_eval { remove_const :ModuleFolder }
+ remove_constants(:ModuleFolder)
ActiveSupport::Dependencies.autoload_once_paths = []
end
@@ -652,6 +658,8 @@ class DependenciesTest < ActiveSupport::TestCase
assert_equal 10, ApplicationController
assert ActiveSupport::Dependencies.autoloaded?(:ApplicationController)
end
+ ensure
+ remove_constants(:ApplicationController)
end
def test_preexisting_constants_are_not_marked_as_autoloaded
@@ -667,9 +675,8 @@ class DependenciesTest < ActiveSupport::TestCase
assert ! ActiveSupport::Dependencies.autoloaded?(:E), "E shouldn't be marked autoloaded!"
ActiveSupport::Dependencies.clear
end
-
ensure
- Object.class_eval { remove_const :E }
+ remove_constants(:E)
end
def test_constants_in_capitalized_nesting_marked_as_autoloaded
@@ -678,6 +685,8 @@ class DependenciesTest < ActiveSupport::TestCase
assert ActiveSupport::Dependencies.autoloaded?("HTML::SomeClass")
end
+ ensure
+ remove_constants(:HTML)
end
def test_unloadable
@@ -708,7 +717,7 @@ class DependenciesTest < ActiveSupport::TestCase
assert_equal false, M.unloadable
end
ensure
- Object.class_eval { remove_const :M }
+ remove_constants(:M)
end
def test_unloadable_constants_should_receive_callback
@@ -719,7 +728,7 @@ class DependenciesTest < ActiveSupport::TestCase
ActiveSupport::Dependencies.clear
assert !defined?(C)
ensure
- Object.class_eval { remove_const :C } if defined?(C)
+ remove_constants(:C)
end
def test_new_contants_in_without_constants
@@ -733,7 +742,7 @@ class DependenciesTest < ActiveSupport::TestCase
}.map(&:to_s)
assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? }
ensure
- Object.class_eval { remove_const :Hello }
+ remove_constants(:Hello)
end
def test_new_constants_in_with_nesting
@@ -750,9 +759,7 @@ class DependenciesTest < ActiveSupport::TestCase
assert_equal ["OuterAfter", "OuterBefore"], outer.sort.map(&:to_s)
assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? }
ensure
- %w(OuterBefore Inner OuterAfter).each do |name|
- Object.class_eval { remove_const name if const_defined?(name) }
- end
+ remove_constants(:OuterBefore, :Inner, :OuterAfter)
end
def test_new_constants_in_module
@@ -771,7 +778,7 @@ class DependenciesTest < ActiveSupport::TestCase
assert_equal ["M::OuterAfter", "M::OuterBefore"], outer.sort
assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? }
ensure
- Object.class_eval { remove_const :M }
+ remove_constants(:M)
end
def test_new_constants_in_module_using_name
@@ -789,7 +796,7 @@ class DependenciesTest < ActiveSupport::TestCase
assert_equal ["M::OuterAfter", "M::OuterBefore"], outer.sort
assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? }
ensure
- Object.class_eval { remove_const :M }
+ remove_constants(:M)
end
def test_new_constants_in_with_inherited_constants
@@ -807,26 +814,27 @@ class DependenciesTest < ActiveSupport::TestCase
def test_file_with_multiple_constants_and_require_dependency
with_autoloading_fixtures do
- assert ! defined?(MultipleConstantFile)
- assert ! defined?(SiblingConstant)
+ assert_not defined?(MultipleConstantFile)
+ assert_not defined?(SiblingConstant)
require_dependency 'multiple_constant_file'
assert defined?(MultipleConstantFile)
assert defined?(SiblingConstant)
assert ActiveSupport::Dependencies.autoloaded?(:MultipleConstantFile)
assert ActiveSupport::Dependencies.autoloaded?(:SiblingConstant)
-
ActiveSupport::Dependencies.clear
- assert ! defined?(MultipleConstantFile)
- assert ! defined?(SiblingConstant)
+ assert_not defined?(MultipleConstantFile)
+ assert_not defined?(SiblingConstant)
end
+ ensure
+ remove_constants(:MultipleConstantFile, :SiblingConstant)
end
def test_file_with_multiple_constants_and_auto_loading
with_autoloading_fixtures do
- assert ! defined?(MultipleConstantFile)
- assert ! defined?(SiblingConstant)
+ assert_not defined?(MultipleConstantFile)
+ assert_not defined?(SiblingConstant)
assert_equal 10, MultipleConstantFile
@@ -837,15 +845,17 @@ class DependenciesTest < ActiveSupport::TestCase
ActiveSupport::Dependencies.clear
- assert ! defined?(MultipleConstantFile)
- assert ! defined?(SiblingConstant)
+ assert_not defined?(MultipleConstantFile)
+ assert_not defined?(SiblingConstant)
end
+ ensure
+ remove_constants(:MultipleConstantFile, :SiblingConstant)
end
def test_nested_file_with_multiple_constants_and_require_dependency
with_autoloading_fixtures do
- assert ! defined?(ClassFolder::NestedClass)
- assert ! defined?(ClassFolder::SiblingClass)
+ assert_not defined?(ClassFolder::NestedClass)
+ assert_not defined?(ClassFolder::SiblingClass)
require_dependency 'class_folder/nested_class'
@@ -856,15 +866,17 @@ class DependenciesTest < ActiveSupport::TestCase
ActiveSupport::Dependencies.clear
- assert ! defined?(ClassFolder::NestedClass)
- assert ! defined?(ClassFolder::SiblingClass)
+ assert_not defined?(ClassFolder::NestedClass)
+ assert_not defined?(ClassFolder::SiblingClass)
end
+ ensure
+ remove_constants(:ClassFolder)
end
def test_nested_file_with_multiple_constants_and_auto_loading
with_autoloading_fixtures do
- assert ! defined?(ClassFolder::NestedClass)
- assert ! defined?(ClassFolder::SiblingClass)
+ assert_not defined?(ClassFolder::NestedClass)
+ assert_not defined?(ClassFolder::SiblingClass)
assert_kind_of Class, ClassFolder::NestedClass
@@ -875,9 +887,11 @@ class DependenciesTest < ActiveSupport::TestCase
ActiveSupport::Dependencies.clear
- assert ! defined?(ClassFolder::NestedClass)
- assert ! defined?(ClassFolder::SiblingClass)
+ assert_not defined?(ClassFolder::NestedClass)
+ assert_not defined?(ClassFolder::SiblingClass)
end
+ ensure
+ remove_constants(:ClassFolder)
end
def test_autoload_doesnt_shadow_no_method_error_with_relative_constant
@@ -888,9 +902,8 @@ class DependenciesTest < ActiveSupport::TestCase
assert !defined?(::RaisesNoMethodError), "::RaisesNoMethodError is defined but it should have failed!"
end
end
-
ensure
- Object.class_eval { remove_const :RaisesNoMethodError if const_defined?(:RaisesNoMethodError) }
+ remove_constants(:RaisesNoMethodError)
end
def test_autoload_doesnt_shadow_no_method_error_with_absolute_constant
@@ -901,9 +914,8 @@ class DependenciesTest < ActiveSupport::TestCase
assert !defined?(::RaisesNoMethodError), "::RaisesNoMethodError is defined but it should have failed!"
end
end
-
ensure
- Object.class_eval { remove_const :RaisesNoMethodError if const_defined?(:RaisesNoMethodError) }
+ remove_constants(:RaisesNoMethodError)
end
def test_autoload_doesnt_shadow_error_when_mechanism_not_set_to_load
@@ -919,7 +931,6 @@ class DependenciesTest < ActiveSupport::TestCase
def test_autoload_doesnt_shadow_name_error
with_autoloading_fixtures do
- Object.send(:remove_const, :RaisesNameError) if defined?(::RaisesNameError)
2.times do
e = assert_raise NameError do
::RaisesNameError::FooBarBaz.object_id
@@ -934,19 +945,20 @@ class DependenciesTest < ActiveSupport::TestCase
assert !defined?(::RaisesNameError), "::RaisesNameError is defined but it should have failed!"
end
end
-
ensure
- Object.class_eval { remove_const :RaisesNoMethodError if const_defined?(:RaisesNoMethodError) }
+ remove_constants(:RaisesNameError)
end
def test_remove_constant_handles_double_colon_at_start
Object.const_set 'DeleteMe', Module.new
DeleteMe.const_set 'OrMe', Module.new
ActiveSupport::Dependencies.remove_constant "::DeleteMe::OrMe"
- assert ! defined?(DeleteMe::OrMe)
+ assert_not defined?(DeleteMe::OrMe)
assert defined?(DeleteMe)
ActiveSupport::Dependencies.remove_constant "::DeleteMe"
- assert ! defined?(DeleteMe)
+ assert_not defined?(DeleteMe)
+ ensure
+ remove_constants(:DeleteMe)
end
def test_remove_constant_does_not_trigger_loading_autoloads
@@ -956,7 +968,9 @@ class DependenciesTest < ActiveSupport::TestCase
end
assert_nil ActiveSupport::Dependencies.remove_constant(constant), "Kernel#autoload has been triggered by remove_constant"
- assert !defined?(ShouldNotBeAutoloaded)
+ assert_not defined?(ShouldNotBeAutoloaded)
+ ensure
+ remove_constants(constant)
end
def test_remove_constant_does_not_autoload_already_removed_parents_as_a_side_effect
@@ -965,11 +979,14 @@ class DependenciesTest < ActiveSupport::TestCase
_ = ::A::B # assignment to silence parse-time warning "possibly useless use of :: in void context"
ActiveSupport::Dependencies.remove_constant('A')
ActiveSupport::Dependencies.remove_constant('A::B')
- assert !defined?(A)
+ assert_not defined?(A)
end
+ ensure
+ remove_constants(:A)
end
def test_load_once_constants_should_not_be_unloaded
+ old_path = ActiveSupport::Dependencies.autoload_once_paths
with_autoloading_fixtures do
ActiveSupport::Dependencies.autoload_once_paths = ActiveSupport::Dependencies.autoload_paths
_ = ::A # assignment to silence parse-time warning "possibly useless use of :: in void context"
@@ -978,8 +995,8 @@ class DependenciesTest < ActiveSupport::TestCase
assert defined?(A)
end
ensure
- ActiveSupport::Dependencies.autoload_once_paths = []
- Object.class_eval { remove_const :A if const_defined?(:A) }
+ ActiveSupport::Dependencies.autoload_once_paths = old_path
+ remove_constants(:A)
end
def test_access_unloaded_constants_for_reload
@@ -991,29 +1008,45 @@ class DependenciesTest < ActiveSupport::TestCase
A::B # Make sure no circular dependency error
end
+ ensure
+ remove_constants(:A)
end
def test_autoload_once_paths_should_behave_when_recursively_loading
+ old_path = ActiveSupport::Dependencies.autoload_once_paths
with_loading 'dependencies', 'autoloading_fixtures' do
ActiveSupport::Dependencies.autoload_once_paths = [ActiveSupport::Dependencies.autoload_paths.last]
- assert !defined?(CrossSiteDependency)
+ assert_not defined?(CrossSiteDependency)
assert_nothing_raised { CrossSiteDepender.nil? }
assert defined?(CrossSiteDependency)
- assert !ActiveSupport::Dependencies.autoloaded?(CrossSiteDependency),
+ assert_not ActiveSupport::Dependencies.autoloaded?(CrossSiteDependency),
"CrossSiteDependency shouldn't be marked as autoloaded!"
ActiveSupport::Dependencies.clear
assert defined?(CrossSiteDependency),
"CrossSiteDependency shouldn't have been unloaded!"
end
ensure
- ActiveSupport::Dependencies.autoload_once_paths = []
+ ActiveSupport::Dependencies.autoload_once_paths = old_path
+ remove_constants(:CrossSiteDependency)
end
def test_hook_called_multiple_times
assert_nothing_raised { ActiveSupport::Dependencies.hook! }
end
+ def test_load_and_require_stay_private
+ assert Object.private_methods.include?(:load)
+ assert Object.private_methods.include?(:require)
+
+ ActiveSupport::Dependencies.unhook!
+
+ assert Object.private_methods.include?(:load)
+ assert Object.private_methods.include?(:require)
+ ensure
+ ActiveSupport::Dependencies.hook!
+ end
+
def test_unhook
ActiveSupport::Dependencies.unhook!
assert !Module.new.respond_to?(:const_missing_without_dependencies)
diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb
index 7aff56cbad..20bd8ee5dd 100644
--- a/activesupport/test/deprecation_test.rb
+++ b/activesupport/test/deprecation_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'active_support/testing/stream'
class Deprecatee
def initialize
@@ -36,6 +37,8 @@ end
class DeprecationTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Stream
+
def setup
# Track the last warning.
@old_behavior = ActiveSupport::Deprecation.behavior
@@ -356,20 +359,4 @@ class DeprecationTest < ActiveSupport::TestCase
deprecator
end
- def capture(stream)
- stream = stream.to_s
- captured_stream = Tempfile.new(stream)
- stream_io = eval("$#{stream}")
- origin_stream = stream_io.dup
- stream_io.reopen(captured_stream)
-
- yield
-
- stream_io.rewind
- return captured_stream.read
- ensure
- captured_stream.close
- captured_stream.unlink
- stream_io.reopen(origin_stream)
- end
end
diff --git a/activesupport/test/file_fixtures/sample.txt b/activesupport/test/file_fixtures/sample.txt
new file mode 100644
index 0000000000..0fa80e7383
--- /dev/null
+++ b/activesupport/test/file_fixtures/sample.txt
@@ -0,0 +1 @@
+sample file fixture
diff --git a/activesupport/test/hash_with_indifferent_access_test.rb b/activesupport/test/hash_with_indifferent_access_test.rb
index 843994147b..1facd691fa 100644
--- a/activesupport/test/hash_with_indifferent_access_test.rb
+++ b/activesupport/test/hash_with_indifferent_access_test.rb
@@ -7,4 +7,5 @@ class HashWithIndifferentAccessTest < ActiveSupport::TestCase
hash.reverse_merge! key: :new_value
assert_equal :old_value, hash[:key]
end
-end \ No newline at end of file
+
+end
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index 3770f00fe3..18a8b92eb9 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
module InflectorTestCases
SingularToPlural = {
diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb
index 80bf255080..f2fc456f4b 100644
--- a/activesupport/test/json/decoding_test.rb
+++ b/activesupport/test/json/decoding_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'active_support/json'
require 'active_support/time'
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index 7e976aa772..2f269a66f0 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'securerandom'
require 'abstract_unit'
require 'active_support/core_ext/string/inflections'
@@ -131,6 +130,8 @@ class TestJSONEncoding < ActiveSupport::TestCase
end
def test_process_status
+ rubinius_skip "https://github.com/rubinius/rubinius/issues/3334"
+
# There doesn't seem to be a good way to get a handle on a Process::Status object without actually
# creating a child process, hence this to populate $?
system("not_a_real_program_#{SecureRandom.hex}")
@@ -176,46 +177,6 @@ class TestJSONEncoding < ActiveSupport::TestCase
assert_equal "𐒑", decoded_hash['string']
end
- def test_reading_encode_big_decimal_as_string_option
- assert_deprecated do
- assert ActiveSupport.encode_big_decimal_as_string
- end
- end
-
- def test_setting_deprecated_encode_big_decimal_as_string_option
- assert_raise(NotImplementedError) do
- ActiveSupport.encode_big_decimal_as_string = true
- end
-
- assert_raise(NotImplementedError) do
- ActiveSupport.encode_big_decimal_as_string = false
- end
- end
-
- def test_exception_raised_when_encoding_circular_reference_in_array
- a = [1]
- a << a
- assert_deprecated do
- assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) }
- end
- end
-
- def test_exception_raised_when_encoding_circular_reference_in_hash
- a = { :name => 'foo' }
- a[:next] = a
- assert_deprecated do
- assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) }
- end
- end
-
- def test_exception_raised_when_encoding_circular_reference_in_hash_inside_array
- a = { :name => 'foo', :sub => [] }
- a[:sub] << a
- assert_deprecated do
- assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) }
- end
- end
-
def test_hash_key_identifiers_are_always_quoted
values = {0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B"}
assert_equal %w( "$" "A" "A0" "A0B" "_" "a" "0" "1" ).sort, object_keys(ActiveSupport::JSON.encode(values))
diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb
index b6c0a08b05..eb71369397 100644
--- a/activesupport/test/message_encryptor_test.rb
+++ b/activesupport/test/message_encryptor_test.rb
@@ -1,12 +1,5 @@
require 'abstract_unit'
-
-begin
- require 'openssl'
- OpenSSL::Digest::SHA1
-rescue LoadError, NameError
- $stderr.puts "Skipping MessageEncryptor test: broken OpenSSL install"
-else
-
+require 'openssl'
require 'active_support/time'
require 'active_support/json'
@@ -97,5 +90,3 @@ class MessageEncryptorTest < ActiveSupport::TestCase
::Base64.strict_encode64(bits)
end
end
-
-end
diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb
index 28035bc428..6c3519df9a 100644
--- a/activesupport/test/message_verifier_test.rb
+++ b/activesupport/test/message_verifier_test.rb
@@ -1,12 +1,5 @@
require 'abstract_unit'
-
-begin
- require 'openssl'
- OpenSSL::Digest::SHA1
-rescue LoadError, NameError
- $stderr.puts "Skipping MessageVerifier test: broken OpenSSL install"
-else
-
+require 'openssl'
require 'active_support/time'
require 'active_support/json'
@@ -27,21 +20,29 @@ class MessageVerifierTest < ActiveSupport::TestCase
@data = { :some => "data", :now => Time.local(2010) }
end
+ def test_valid_message
+ data, hash = @verifier.generate(@data).split("--")
+ assert !@verifier.valid_message?(nil)
+ assert !@verifier.valid_message?("")
+ assert !@verifier.valid_message?("#{data.reverse}--#{hash}")
+ assert !@verifier.valid_message?("#{data}--#{hash.reverse}")
+ assert !@verifier.valid_message?("purejunk")
+ end
+
def test_simple_round_tripping
message = @verifier.generate(@data)
+ assert_equal @data, @verifier.verified(message)
assert_equal @data, @verifier.verify(message)
end
- def test_missing_signature_raises
- assert_not_verified(nil)
- assert_not_verified("")
+ def test_verified_returns_false_on_invalid_message
+ assert !@verifier.verified("purejunk")
end
- def test_tampered_data_raises
- data, hash = @verifier.generate(@data).split("--")
- assert_not_verified("#{data.reverse}--#{hash}")
- assert_not_verified("#{data}--#{hash.reverse}")
- assert_not_verified("purejunk")
+ def test_verify_exception_on_invalid_message
+ assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do
+ @verifier.verify("purejunk")
+ end
end
def test_alternative_serialization_method
@@ -50,6 +51,7 @@ class MessageVerifierTest < ActiveSupport::TestCase
verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!", :serializer => JSONSerializer.new)
message = verifier.generate({ :foo => 123, 'bar' => Time.utc(2010) })
exp = { "foo" => 123, "bar" => "2010-01-01T00:00:00.000Z" }
+ assert_equal exp, verifier.verified(message)
assert_equal exp, verifier.verify(message)
ensure
ActiveSupport.use_standard_json_time_format = prev
@@ -63,6 +65,11 @@ class MessageVerifierTest < ActiveSupport::TestCase
#
valid_message = "BAh7BjoIZm9vbzonTWVzc2FnZVZlcmlmaWVyVGVzdDo6QXV0b2xvYWRDbGFzcwY6CUBmb29JIghmb28GOgZFVA==--f3ef39a5241c365083770566dc7a9eb5d6ace914"
exception = assert_raise(ArgumentError, NameError) do
+ @verifier.verified(valid_message)
+ end
+ assert_includes ["uninitialized constant MessageVerifierTest::AutoloadClass",
+ "undefined class/module MessageVerifierTest::AutoloadClass"], exception.message
+ exception = assert_raise(ArgumentError, NameError) do
@verifier.verify(valid_message)
end
assert_includes ["uninitialized constant MessageVerifierTest::AutoloadClass",
@@ -75,12 +82,4 @@ class MessageVerifierTest < ActiveSupport::TestCase
end
assert_equal exception.message, 'Secret should not be nil.'
end
-
- def assert_not_verified(message)
- assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do
- @verifier.verify(message)
- end
- end
-end
-
end
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index feca013675..e1c4b705f8 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'multibyte_test_helpers'
require 'active_support/core_ext/string/multibyte'
@@ -182,7 +181,7 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
end
def test_sortability
- words = %w(builder armor zebra).sort_by { |s| s.mb_chars }
+ words = %w(builder armor zebra).sort_by(&:mb_chars)
assert_equal %w(armor builder zebra), words
end
diff --git a/activesupport/test/multibyte_conformance_test.rb b/activesupport/test/multibyte_conformance_test.rb
index aba81b8248..d8704716e7 100644
--- a/activesupport/test/multibyte_conformance_test.rb
+++ b/activesupport/test/multibyte_conformance_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'multibyte_test_helpers'
@@ -115,7 +114,7 @@ class MultibyteConformanceTest < ActiveSupport::TestCase
next if (line.empty? || line =~ /^\#/)
cols, comment = line.split("#")
- cols = cols.split(";").map{|e| e.strip}.reject{|e| e.empty? }
+ cols = cols.split(";").map(&:strip).reject(&:empty?)
next unless cols.length == 5
# codepoints are in hex in the test suite, pack wants them as integers
diff --git a/activesupport/test/multibyte_proxy_test.rb b/activesupport/test/multibyte_proxy_test.rb
index d8ffd7ca9c..11f5374017 100644
--- a/activesupport/test/multibyte_proxy_test.rb
+++ b/activesupport/test/multibyte_proxy_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
diff --git a/activesupport/test/multibyte_test_helpers.rb b/activesupport/test/multibyte_test_helpers.rb
index 90af2dadd4..2e4b5cc873 100644
--- a/activesupport/test/multibyte_test_helpers.rb
+++ b/activesupport/test/multibyte_test_helpers.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
module MultibyteTestHelpers
UNICODE_STRING = 'こにちわ'.freeze
diff --git a/activesupport/test/multibyte_unicode_database_test.rb b/activesupport/test/multibyte_unicode_database_test.rb
index bec65daf50..52ae266f01 100644
--- a/activesupport/test/multibyte_unicode_database_test.rb
+++ b/activesupport/test/multibyte_unicode_database_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb
index 50d84a9470..83efbffdfb 100644
--- a/activesupport/test/number_helper_test.rb
+++ b/activesupport/test/number_helper_test.rb
@@ -83,6 +83,14 @@ module ActiveSupport
assert_equal("98a%", number_helper.number_to_percentage("98a"))
assert_equal("NaN%", number_helper.number_to_percentage(Float::NAN))
assert_equal("Inf%", number_helper.number_to_percentage(Float::INFINITY))
+ assert_equal("NaN%", number_helper.number_to_percentage(Float::NAN, precision: 0))
+ assert_equal("Inf%", number_helper.number_to_percentage(Float::INFINITY, precision: 0))
+ assert_equal("NaN%", number_helper.number_to_percentage(Float::NAN, precision: 1))
+ assert_equal("Inf%", number_helper.number_to_percentage(Float::INFINITY, precision: 1))
+ assert_equal("1000%", number_helper.number_to_percentage(1000, precision: nil))
+ assert_equal("1000%", number_helper.number_to_percentage(1000, precision: nil))
+ assert_equal("1000.1%", number_helper.number_to_percentage(1000.1, precision: nil))
+ assert_equal("-0.13 %", number_helper.number_to_percentage("-0.13", precision: nil, format: "%n %"))
end
end
diff --git a/activesupport/test/rescuable_test.rb b/activesupport/test/rescuable_test.rb
index ec9d231125..bd43ad0797 100644
--- a/activesupport/test/rescuable_test.rb
+++ b/activesupport/test/rescuable_test.rb
@@ -12,6 +12,12 @@ end
class CoolError < StandardError
end
+module WeirdError
+ def self.===(other)
+ Exception === other && other.respond_to?(:weird?)
+ end
+end
+
class Stargate
attr_accessor :result
@@ -29,6 +35,10 @@ class Stargate
@result = e.message
end
+ rescue_from WeirdError do
+ @result = 'weird'
+ end
+
def dispatch(method)
send(method)
rescue Exception => e
@@ -47,6 +57,16 @@ class Stargate
raise MadRonon.new("dex")
end
+ def weird
+ StandardError.new.tap do |exc|
+ def exc.weird?
+ true
+ end
+
+ raise exc
+ end
+ end
+
def sos
@result = 'killed'
end
@@ -91,15 +111,20 @@ class RescuableTest < ActiveSupport::TestCase
assert_equal 'dex', @stargate.result
end
+ def test_rescue_from_error_dispatchers_with_case_operator
+ @stargate.dispatch :weird
+ assert_equal 'weird', @stargate.result
+ end
+
def test_rescues_defined_later_are_added_at_end_of_the_rescue_handlers_array
- expected = ["WraithAttack", "WraithAttack", "NuclearExplosion", "MadRonon"]
- result = @stargate.send(:rescue_handlers).collect {|e| e.first}
+ expected = ["WraithAttack", "WraithAttack", "NuclearExplosion", "MadRonon", "WeirdError"]
+ result = @stargate.send(:rescue_handlers).collect(&:first)
assert_equal expected, result
end
def test_children_should_inherit_rescue_definitions_from_parents_and_child_rescue_should_be_appended
- expected = ["WraithAttack", "WraithAttack", "NuclearExplosion", "MadRonon", "CoolError"]
- result = @cool_stargate.send(:rescue_handlers).collect {|e| e.first}
+ expected = ["WraithAttack", "WraithAttack", "NuclearExplosion", "MadRonon", "WeirdError", "CoolError"]
+ result = @cool_stargate.send(:rescue_handlers).collect(&:first)
assert_equal expected, result
end
end
diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb
index efa9d5e61f..18fb6d2fbf 100644
--- a/activesupport/test/safe_buffer_test.rb
+++ b/activesupport/test/safe_buffer_test.rb
@@ -61,6 +61,13 @@ class SafeBufferTest < ActiveSupport::TestCase
assert_equal({'str' => str}, YAML.load(yaml))
end
+ test "Should work with primitive-like-strings in to_yaml conversion" do
+ assert_equal 'true', YAML.load(ActiveSupport::SafeBuffer.new('true').to_yaml)
+ assert_equal 'false', YAML.load(ActiveSupport::SafeBuffer.new('false').to_yaml)
+ assert_equal '1', YAML.load(ActiveSupport::SafeBuffer.new('1').to_yaml)
+ assert_equal '1.1', YAML.load(ActiveSupport::SafeBuffer.new('1.1').to_yaml)
+ end
+
test "Should work with underscore" do
str = "MyTest".html_safe.underscore
assert_equal "my_test", str
@@ -165,4 +172,9 @@ class SafeBufferTest < ActiveSupport::TestCase
x = 'foo %{x} bar'.html_safe % { x: 'qux' }
assert x.html_safe?, 'should be safe'
end
+
+ test 'Should not affect frozen objects when accessing characters' do
+ x = 'Hello'.html_safe
+ assert_equal x[/a/, 1], nil
+ end
end
diff --git a/activesupport/test/tagged_logging_test.rb b/activesupport/test/tagged_logging_test.rb
index 27f629474e..03a63e94e8 100644
--- a/activesupport/test/tagged_logging_test.rb
+++ b/activesupport/test/tagged_logging_test.rb
@@ -79,6 +79,19 @@ class TaggedLoggingTest < ActiveSupport::TestCase
assert_equal "[OMG] Cool story bro\n[BCX] Funky time\n", @output.string
end
+ test "keeps each tag in their own instance" do
+ @other_output = StringIO.new
+ @other_logger = ActiveSupport::TaggedLogging.new(MyLogger.new(@other_output))
+ @logger.tagged("OMG") do
+ @other_logger.tagged("BCX") do
+ @logger.info "Cool story bro"
+ @other_logger.info "Funky time"
+ end
+ end
+ assert_equal "[OMG] Cool story bro\n", @output.string
+ assert_equal "[BCX] Funky time\n", @other_output.string
+ end
+
test "cleans up the taggings on flush" do
@logger.tagged("BCX") do
Thread.new do
diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb
index 5e852c8050..9e6d1a91d0 100644
--- a/activesupport/test/test_case_test.rb
+++ b/activesupport/test/test_case_test.rb
@@ -182,40 +182,27 @@ class TestOrderTest < ActiveSupport::TestCase
ActiveSupport::TestCase.test_order = @original_test_order
end
- def test_defaults_to_sorted_with_warning
+ def test_defaults_to_random
ActiveSupport::TestCase.test_order = nil
- assert_equal :sorted, assert_deprecated { ActiveSupport::TestCase.test_order }
+ assert_equal :random, ActiveSupport::TestCase.test_order
- # It should only produce a deprecation warning the first time this is accessed
- assert_equal :sorted, assert_not_deprecated { ActiveSupport::TestCase.test_order }
- assert_equal :sorted, assert_not_deprecated { ActiveSupport.test_order }
+ assert_equal :random, ActiveSupport.test_order
end
def test_test_order_is_global
- ActiveSupport::TestCase.test_order = :random
-
- assert_equal :random, ActiveSupport.test_order
- assert_equal :random, ActiveSupport::TestCase.test_order
- assert_equal :random, self.class.test_order
- assert_equal :random, Class.new(ActiveSupport::TestCase).test_order
-
- ActiveSupport.test_order = :sorted
+ ActiveSupport::TestCase.test_order = :sorted
assert_equal :sorted, ActiveSupport.test_order
assert_equal :sorted, ActiveSupport::TestCase.test_order
assert_equal :sorted, self.class.test_order
assert_equal :sorted, Class.new(ActiveSupport::TestCase).test_order
- end
- def test_i_suck_and_my_tests_are_order_dependent!
- ActiveSupport::TestCase.test_order = :random
-
- klass = Class.new(ActiveSupport::TestCase) do
- i_suck_and_my_tests_are_order_dependent!
- end
+ ActiveSupport.test_order = :random
- assert_equal :alpha, klass.test_order
+ assert_equal :random, ActiveSupport.test_order
assert_equal :random, ActiveSupport::TestCase.test_order
+ assert_equal :random, self.class.test_order
+ assert_equal :random, Class.new(ActiveSupport::TestCase).test_order
end
end
diff --git a/activesupport/test/testing/file_fixtures_test.rb b/activesupport/test/testing/file_fixtures_test.rb
new file mode 100644
index 0000000000..91b8a9071c
--- /dev/null
+++ b/activesupport/test/testing/file_fixtures_test.rb
@@ -0,0 +1,28 @@
+require 'abstract_unit'
+
+class FileFixturesTest < ActiveSupport::TestCase
+ self.file_fixture_path = File.expand_path("../../file_fixtures", __FILE__)
+
+ test "#file_fixture returns Pathname to file fixture" do
+ path = file_fixture("sample.txt")
+ assert_kind_of Pathname, path
+ assert_match %r{activesupport/test/file_fixtures/sample.txt$}, path.to_s
+ end
+
+ test "raises an exception when the fixture file does not exist" do
+ e = assert_raises(ArgumentError) do
+ file_fixture("nope")
+ end
+ assert_match(/^the directory '[^']+test\/file_fixtures' does not contain a file named 'nope'$/, e.message)
+ end
+end
+
+class FileFixturesPathnameDirectoryTest < ActiveSupport::TestCase
+ self.file_fixture_path = Pathname.new(File.expand_path("../../file_fixtures", __FILE__))
+
+ test "#file_fixture_path returns Pathname to file fixture" do
+ path = file_fixture("sample.txt")
+ assert_kind_of Pathname, path
+ assert_match %r{activesupport/test/file_fixtures/sample.txt$}, path.to_s
+ end
+end
diff --git a/activesupport/test/time_travel_test.rb b/activesupport/test/time_travel_test.rb
index 065539671d..676a143692 100644
--- a/activesupport/test/time_travel_test.rb
+++ b/activesupport/test/time_travel_test.rb
@@ -1,5 +1,6 @@
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
@@ -17,6 +18,7 @@ class TimeTravelTest < ActiveSupport::TestCase
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
@@ -25,10 +27,12 @@ class TimeTravelTest < ActiveSupport::TestCase
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
@@ -37,6 +41,7 @@ class TimeTravelTest < ActiveSupport::TestCase
assert_equal expected_time, Time.now
assert_equal Date.new(2004, 11, 24), Date.today
+ assert_equal expected_time.to_datetime, DateTime.now
end
def test_time_helper_travel_to_with_block
@@ -45,10 +50,12 @@ class TimeTravelTest < ActiveSupport::TestCase
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
def test_time_helper_travel_back
@@ -57,16 +64,20 @@ class TimeTravelTest < ActiveSupport::TestCase
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
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
end
end
end
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index 3e6d9652bb..7888b9919b 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -395,20 +395,14 @@ class TimeZoneTest < ActiveSupport::TestCase
assert_raise(ArgumentError) { ActiveSupport::TimeZone[false] }
end
- def test_unknown_zone_should_have_tzinfo_but_exception_on_utc_offset
- zone = ActiveSupport::TimeZone.create("bogus")
- assert_instance_of TZInfo::TimezoneProxy, zone.tzinfo
- assert_raise(TZInfo::InvalidTimezoneIdentifier) { zone.utc_offset }
- end
-
- def test_unknown_zone_with_utc_offset
- zone = ActiveSupport::TimeZone.create("bogus", -21_600)
- assert_equal(-21_600, zone.utc_offset)
+ def test_unknown_zone_raises_exception
+ assert_raise TZInfo::InvalidTimezoneIdentifier do
+ ActiveSupport::TimeZone.create("bogus")
+ end
end
def test_unknown_zones_dont_store_mapping_keys
- ActiveSupport::TimeZone["bogus"]
- assert !ActiveSupport::TimeZone.zones_map.key?("bogus")
+ assert_nil ActiveSupport::TimeZone["bogus"]
end
def test_new
diff --git a/activesupport/test/transliterate_test.rb b/activesupport/test/transliterate_test.rb
index 6833ae68a7..378421fedd 100644
--- a/activesupport/test/transliterate_test.rb
+++ b/activesupport/test/transliterate_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'active_support/inflector/transliterate'
diff --git a/activesupport/test/xml_mini/nokogiri_engine_test.rb b/activesupport/test/xml_mini/nokogiri_engine_test.rb
index 2e962576b5..1314c9065a 100644
--- a/activesupport/test/xml_mini/nokogiri_engine_test.rb
+++ b/activesupport/test/xml_mini/nokogiri_engine_test.rb
@@ -8,15 +8,13 @@ require 'active_support/xml_mini'
require 'active_support/core_ext/hash/conversions'
class NokogiriEngineTest < ActiveSupport::TestCase
- include ActiveSupport
-
def setup
- @default_backend = XmlMini.backend
- XmlMini.backend = 'Nokogiri'
+ @default_backend = ActiveSupport::XmlMini.backend
+ ActiveSupport::XmlMini.backend = 'Nokogiri'
end
def teardown
- XmlMini.backend = @default_backend
+ ActiveSupport::XmlMini.backend = @default_backend
end
def test_file_from_xml
@@ -56,13 +54,13 @@ class NokogiriEngineTest < ActiveSupport::TestCase
end
def test_setting_nokogiri_as_backend
- XmlMini.backend = 'Nokogiri'
- assert_equal XmlMini_Nokogiri, XmlMini.backend
+ ActiveSupport::XmlMini.backend = 'Nokogiri'
+ assert_equal ActiveSupport::XmlMini_Nokogiri, ActiveSupport::XmlMini.backend
end
def test_blank_returns_empty_hash
- assert_equal({}, XmlMini.parse(nil))
- assert_equal({}, XmlMini.parse(''))
+ assert_equal({}, ActiveSupport::XmlMini.parse(nil))
+ assert_equal({}, ActiveSupport::XmlMini.parse(''))
end
def test_array_type_makes_an_array
@@ -207,9 +205,9 @@ class NokogiriEngineTest < ActiveSupport::TestCase
private
def assert_equal_rexml(xml)
- parsed_xml = XmlMini.parse(xml)
+ parsed_xml = ActiveSupport::XmlMini.parse(xml)
xml.rewind if xml.respond_to?(:rewind)
- hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) }
+ hash = ActiveSupport::XmlMini.with_backend('REXML') { ActiveSupport::XmlMini.parse(xml) }
assert_equal(hash, parsed_xml)
end
end
diff --git a/activesupport/test/xml_mini/nokogirisax_engine_test.rb b/activesupport/test/xml_mini/nokogirisax_engine_test.rb
index 4f078f31e0..7978a50921 100644
--- a/activesupport/test/xml_mini/nokogirisax_engine_test.rb
+++ b/activesupport/test/xml_mini/nokogirisax_engine_test.rb
@@ -8,15 +8,13 @@ require 'active_support/xml_mini'
require 'active_support/core_ext/hash/conversions'
class NokogiriSAXEngineTest < ActiveSupport::TestCase
- include ActiveSupport
-
def setup
- @default_backend = XmlMini.backend
- XmlMini.backend = 'NokogiriSAX'
+ @default_backend = ActiveSupport::XmlMini.backend
+ ActiveSupport::XmlMini.backend = 'NokogiriSAX'
end
def teardown
- XmlMini.backend = @default_backend
+ ActiveSupport::XmlMini.backend = @default_backend
end
def test_file_from_xml
@@ -57,13 +55,13 @@ class NokogiriSAXEngineTest < ActiveSupport::TestCase
end
def test_setting_nokogirisax_as_backend
- XmlMini.backend = 'NokogiriSAX'
- assert_equal XmlMini_NokogiriSAX, XmlMini.backend
+ ActiveSupport::XmlMini.backend = 'NokogiriSAX'
+ assert_equal ActiveSupport::XmlMini_NokogiriSAX, ActiveSupport::XmlMini.backend
end
def test_blank_returns_empty_hash
- assert_equal({}, XmlMini.parse(nil))
- assert_equal({}, XmlMini.parse(''))
+ assert_equal({}, ActiveSupport::XmlMini.parse(nil))
+ assert_equal({}, ActiveSupport::XmlMini.parse(''))
end
def test_array_type_makes_an_array
@@ -208,9 +206,9 @@ class NokogiriSAXEngineTest < ActiveSupport::TestCase
private
def assert_equal_rexml(xml)
- parsed_xml = XmlMini.parse(xml)
+ parsed_xml = ActiveSupport::XmlMini.parse(xml)
xml.rewind if xml.respond_to?(:rewind)
- hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) }
+ hash = ActiveSupport::XmlMini.with_backend('REXML') { ActiveSupport::XmlMini.parse(xml) }
assert_equal(hash, parsed_xml)
end
end
diff --git a/activesupport/test/xml_mini/rexml_engine_test.rb b/activesupport/test/xml_mini/rexml_engine_test.rb
index 0c1f11803c..f0067ca656 100644
--- a/activesupport/test/xml_mini/rexml_engine_test.rb
+++ b/activesupport/test/xml_mini/rexml_engine_test.rb
@@ -2,19 +2,17 @@ require 'abstract_unit'
require 'active_support/xml_mini'
class REXMLEngineTest < ActiveSupport::TestCase
- include ActiveSupport
-
def test_default_is_rexml
- assert_equal XmlMini_REXML, XmlMini.backend
+ assert_equal ActiveSupport::XmlMini_REXML, ActiveSupport::XmlMini.backend
end
def test_set_rexml_as_backend
- XmlMini.backend = 'REXML'
- assert_equal XmlMini_REXML, XmlMini.backend
+ ActiveSupport::XmlMini.backend = 'REXML'
+ assert_equal ActiveSupport::XmlMini_REXML, ActiveSupport::XmlMini.backend
end
def test_parse_from_io
- XmlMini.backend = 'REXML'
+ ActiveSupport::XmlMini.backend = 'REXML'
io = StringIO.new(<<-eoxml)
<root>
good
@@ -29,9 +27,9 @@ class REXMLEngineTest < ActiveSupport::TestCase
private
def assert_equal_rexml(xml)
- parsed_xml = XmlMini.parse(xml)
+ parsed_xml = ActiveSupport::XmlMini.parse(xml)
xml.rewind if xml.respond_to?(:rewind)
- hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) }
+ hash = ActiveSupport::XmlMini.with_backend('REXML') { ActiveSupport::XmlMini.parse(xml) }
assert_equal(hash, parsed_xml)
end
end
diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb
index f49431cbbf..bcd6997b06 100644
--- a/activesupport/test/xml_mini_test.rb
+++ b/activesupport/test/xml_mini_test.rb
@@ -11,7 +11,7 @@ module XmlMiniTest
assert_equal "my-key", ActiveSupport::XmlMini.rename_key("my_key")
end
- def test_rename_key_does_nothing_with_dasherize_true
+ def test_rename_key_dasherizes_with_dasherize_true
assert_equal "my-key", ActiveSupport::XmlMini.rename_key("my_key", :dasherize => true)
end
diff --git a/ci/travis.rb b/ci/travis.rb
index a1920a7a1f..b62f90a59e 100755
--- a/ci/travis.rb
+++ b/ci/travis.rb
@@ -140,6 +140,6 @@ if failures.empty?
else
puts
puts "Rails build FAILED"
- puts "Failed components: #{failures.map { |component| component.first }.join(', ')}"
+ puts "Failed components: #{failures.map(&:first).join(', ')}"
exit(false)
end
diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md
index 2770fc73e7..fd177b4238 100644
--- a/guides/CHANGELOG.md
+++ b/guides/CHANGELOG.md
@@ -1,27 +1,17 @@
-* Change Posts to Articles in Getting Started sample application in order to
-better align with the actual guides.
+* New section in Configuring: Configuring Active Job
- *John Kelly Ferguson*
+ *Eliot Sykes*
-* Update all Rails 4.1.0 references to 4.1.1 within the guides and code.
+* New section in Active Record Association Basics: Single Table Inheritance
- *John Kelly Ferguson*
+ *Andrey Nering*
-* Split up rows in the Explain Queries table of the ActiveRecord Querying section
-in order to improve readability.
+* New section in Active Record Querying: Understanding The Method Chaining
- *John Kelly Ferguson*
+ *Andrey Nering*
-* Change all non-HTTP method 'post' references to 'article'.
+* New section in Configuring: Search Engines Indexing
- *John Kelly Ferguson*
+ *Andrey Nering*
-* Updates the maintenance policy to match the latest versions of Rails
-
- *Matias Korhonen*
-
-* Switched the order of `Applying a default scope` and `Merging of scopes` subsections so default scopes are introduced first.
-
- *Alex Riabov*
-
-Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/guides/CHANGELOG.md) for previous changes.
+Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/guides/CHANGELOG.md) for previous changes.
diff --git a/guides/Rakefile b/guides/Rakefile
index 94d4be8c0a..3c2099ac02 100644
--- a/guides/Rakefile
+++ b/guides/Rakefile
@@ -11,7 +11,7 @@ namespace :guides do
ruby "rails_guides.rb"
end
- desc "Generate .mobi file. The kindlegen executable must be in your PATH. You can get it for free from http://www.amazon.com/kindlepublishing"
+ desc "Generate .mobi file. The kindlegen executable must be in your PATH. You can get it for free from http://www.amazon.com/gp/feature.html?docId=1000765211"
task :kindle do
unless `kindlerb -v 2> /dev/null` =~ /kindlerb 0.1.1/
abort "Please `gem install kindlerb` and make sure you have `kindlegen` in your PATH"
@@ -34,11 +34,13 @@ namespace :guides do
task :help do
puts <<-help
-Guides are taken from the source directory, and the resulting HTML goes into the
+Guides are taken from the source directory, and the result goes into the
output directory. Assets are stored under files, and copied to output/files as
part of the generation process.
-All this process is handled via rake tasks, here's a full list of them:
+You can generate HTML, Kindle or both formats using the `guides:generate` task.
+
+All of these processes are handled via rake tasks, here's a full list of them:
#{%x[rake -T]}
Some arguments may be passed via environment variables:
diff --git a/guides/assets/images/favicon.ico b/guides/assets/images/favicon.ico
index e0e80cf8f1..faa10b4580 100644
--- a/guides/assets/images/favicon.ico
+++ b/guides/assets/images/favicon.ico
Binary files differ
diff --git a/guides/assets/images/getting_started/article_with_comments.png b/guides/assets/images/getting_started/article_with_comments.png
index 117a78a39f..c489e4c00e 100644
--- a/guides/assets/images/getting_started/article_with_comments.png
+++ b/guides/assets/images/getting_started/article_with_comments.png
Binary files differ
diff --git a/guides/assets/stylesheets/main.css b/guides/assets/stylesheets/main.css
index 318a1ef1c7..ed558e4793 100644
--- a/guides/assets/stylesheets/main.css
+++ b/guides/assets/stylesheets/main.css
@@ -34,7 +34,7 @@ pre, code {
overflow: auto;
color: #222;
}
-pre,tt,code,.note>p {
+pre, tt, code {
white-space: pre-wrap; /* css-3 */
white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
diff --git a/guides/bug_report_templates/action_controller_gem.rb b/guides/bug_report_templates/action_controller_gem.rb
index 9387e3dc1d..032e6bfe11 100644
--- a/guides/bug_report_templates/action_controller_gem.rb
+++ b/guides/bug_report_templates/action_controller_gem.rb
@@ -1,14 +1,15 @@
# Activate the gem you are reporting the issue against.
-gem 'rails', '4.0.0'
+gem 'rails', '4.2.0'
require 'rails'
+require 'rack/test'
require 'action_controller/railtie'
class TestApp < Rails::Application
config.root = File.dirname(__FILE__)
config.session_store :cookie_store, key: 'cookie_store_key'
- config.secret_token = 'secret_token'
- config.secret_key_base = 'secret_key_base'
+ secrets.secret_token = 'secret_token'
+ secrets.secret_key_base = 'secret_key_base'
config.logger = Logger.new($stdout)
Rails.logger = config.logger
@@ -27,7 +28,6 @@ class TestController < ActionController::Base
end
require 'minitest/autorun'
-require 'rack/test'
# Ensure backward compatibility with Minitest 4
Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
diff --git a/guides/bug_report_templates/action_controller_master.rb b/guides/bug_report_templates/action_controller_master.rb
index 20c64b4a85..9be8130884 100644
--- a/guides/bug_report_templates/action_controller_master.rb
+++ b/guides/bug_report_templates/action_controller_master.rb
@@ -3,8 +3,6 @@ unless File.exist?('Gemfile')
source 'https://rubygems.org'
gem 'rails', github: 'rails/rails'
gem 'arel', github: 'rails/arel'
- gem 'rack', github: 'rack/rack'
- gem 'i18n', github: 'svenfuchs/i18n'
GEMFILE
system 'bundle'
@@ -19,8 +17,8 @@ require 'action_controller/railtie'
class TestApp < Rails::Application
config.root = File.dirname(__FILE__)
config.session_store :cookie_store, key: 'cookie_store_key'
- config.secret_token = 'secret_token'
- config.secret_key_base = 'secret_key_base'
+ secrets.secret_token = 'secret_token'
+ secrets.secret_key_base = 'secret_key_base'
config.logger = Logger.new($stdout)
Rails.logger = config.logger
diff --git a/guides/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb
index d72633d0b2..b295d9d21f 100644
--- a/guides/bug_report_templates/active_record_gem.rb
+++ b/guides/bug_report_templates/active_record_gem.rb
@@ -1,5 +1,5 @@
# Activate the gem you are reporting the issue against.
-gem 'activerecord', '4.0.0'
+gem 'activerecord', '4.2.0'
require 'active_record'
require 'minitest/autorun'
require 'logger'
@@ -12,10 +12,10 @@ ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:'
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Schema.define do
- create_table :posts do |t|
+ create_table :posts, force: true do |t|
end
- create_table :comments do |t|
+ create_table :comments, force: true do |t|
t.integer :post_id
end
end
diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb
index e7f5d0d5ff..9557f0b7c5 100644
--- a/guides/bug_report_templates/active_record_master.rb
+++ b/guides/bug_report_templates/active_record_master.rb
@@ -3,8 +3,6 @@ unless File.exist?('Gemfile')
source 'https://rubygems.org'
gem 'rails', github: 'rails/rails'
gem 'arel', github: 'rails/arel'
- gem 'rack', github: 'rack/rack'
- gem 'i18n', github: 'svenfuchs/i18n'
gem 'sqlite3'
GEMFILE
@@ -23,10 +21,10 @@ ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:'
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Schema.define do
- create_table :posts do |t|
+ create_table :posts, force: true do |t|
end
- create_table :comments do |t|
+ create_table :comments, force: true do |t|
t.integer :post_id
end
end
diff --git a/guides/rails_guides.rb b/guides/rails_guides.rb
index 9d1d5567f6..367ed0b12e 100644
--- a/guides/rails_guides.rb
+++ b/guides/rails_guides.rb
@@ -1,13 +1,6 @@
pwd = File.dirname(__FILE__)
$:.unshift pwd
-# This is a predicate useful for the doc:guides task of applications.
-def bundler?
- # Note that rake sets the cwd to the one that contains the Rakefile
- # being executed.
- File.exist?('Gemfile')
-end
-
begin
# Guides generation in the Rails repo.
as_lib = File.join(pwd, "../activesupport/lib")
@@ -20,44 +13,5 @@ rescue LoadError
gem "actionpack", '>= 3.0'
end
-begin
- require 'redcarpet'
-rescue LoadError
- # This can happen if doc:guides is executed in an application.
- $stderr.puts('Generating guides requires Redcarpet 3.1.2+.')
- $stderr.puts(<<ERROR) if bundler?
-Please add
-
- gem 'redcarpet', '~> 3.1.2'
-
-to the Gemfile, run
-
- bundle install
-
-and try again.
-ERROR
- exit 1
-end
-
-begin
- require 'nokogiri'
-rescue LoadError
- # This can happen if doc:guides is executed in an application.
- $stderr.puts('Generating guides requires Nokogiri.')
- $stderr.puts(<<ERROR) if bundler?
-Please add
-
- gem 'nokogiri'
-
-to the Gemfile, run
-
- bundle install
-
-and try again.
-ERROR
- exit 1
-end
-
-require 'rails_guides/markdown'
require "rails_guides/generator"
RailsGuides::Generator.new.generate
diff --git a/guides/rails_guides/generator.rb b/guides/rails_guides/generator.rb
index aa900454c8..43f6f7eecf 100644
--- a/guides/rails_guides/generator.rb
+++ b/guides/rails_guides/generator.rb
@@ -57,6 +57,7 @@ require 'active_support/core_ext/object/blank'
require 'action_controller'
require 'action_view'
+require 'rails_guides/markdown'
require 'rails_guides/indexer'
require 'rails_guides/helpers'
require 'rails_guides/levenshtein'
diff --git a/guides/rails_guides/kindle.rb b/guides/rails_guides/kindle.rb
index 09eecd5634..32926622e3 100644
--- a/guides/rails_guides/kindle.rb
+++ b/guides/rails_guides/kindle.rb
@@ -70,7 +70,7 @@ module Kindle
File.open("sections/%03d/_section.txt" % section_idx, 'w') {|f| f.puts title}
doc.xpath("//h3[@id]").each_with_index do |h3,item_idx|
subsection = h3.inner_text
- content = h3.xpath("./following-sibling::*").take_while {|x| x.name != "h3"}.map {|x| x.to_html}
+ content = h3.xpath("./following-sibling::*").take_while {|x| x.name != "h3"}.map(&:to_html)
item = Nokogiri::HTML(h3.to_html + content.join("\n"))
item_path = "sections/%03d/%03d.html" % [section_idx, item_idx]
add_head_section(item, subsection)
diff --git a/guides/rails_guides/levenshtein.rb b/guides/rails_guides/levenshtein.rb
index 8a908a4339..36183fd321 100644
--- a/guides/rails_guides/levenshtein.rb
+++ b/guides/rails_guides/levenshtein.rb
@@ -7,11 +7,9 @@ module RailsGuides
t = str2
n = s.length
m = t.length
- max = n/2
return m if (0 == n)
return n if (0 == m)
- return n if (n - m).abs > max
d = (0..m).to_a
x = nil
diff --git a/guides/rails_guides/markdown/renderer.rb b/guides/rails_guides/markdown/renderer.rb
index 688f177578..554d94ad50 100644
--- a/guides/rails_guides/markdown/renderer.rb
+++ b/guides/rails_guides/markdown/renderer.rb
@@ -23,8 +23,9 @@ HTML
end
def paragraph(text)
- if text =~ /^(TIP|IMPORTANT|CAUTION|WARNING|NOTE|INFO|TODO)[.:](.*?)/
+ if text =~ /^(TIP|IMPORTANT|CAUTION|WARNING|NOTE|INFO|TODO)[.:]/
convert_notes(text)
+ elsif text.include?('DO NOT READ THIS FILE ON GITHUB')
elsif text =~ /^\[<sup>(\d+)\]:<\/sup> (.+)$/
linkback = %(<a href="#footnote-#{$1}-ref"><sup>#{$1}</sup></a>)
%(<p class="footnote" id="footnote-#{$1}">#{linkback} #{$2}</p>)
@@ -47,7 +48,7 @@ HTML
case code_type
when 'ruby', 'sql', 'plain'
code_type
- when 'erb'
+ when 'erb', 'html+erb'
'ruby; html-script: true'
when 'html'
'xml' # HTML is understood, but there are .xml rules in the CSS
diff --git a/guides/source/2_2_release_notes.md b/guides/source/2_2_release_notes.md
index b11aaa15a8..be00087f63 100644
--- a/guides/source/2_2_release_notes.md
+++ b/guides/source/2_2_release_notes.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Ruby on Rails 2.2 Release Notes
===============================
diff --git a/guides/source/2_3_release_notes.md b/guides/source/2_3_release_notes.md
index 20566c9155..0a62f34371 100644
--- a/guides/source/2_3_release_notes.md
+++ b/guides/source/2_3_release_notes.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Ruby on Rails 2.3 Release Notes
===============================
@@ -185,7 +187,7 @@ MySQL supports a reconnect flag in its connections - if set to true, then the cl
* Lead Contributor: [Dov Murik](http://twitter.com/dubek)
* More information:
- * [Controlling Automatic Reconnection Behavior](http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html)
+ * [Controlling Automatic Reconnection Behavior](http://dev.mysql.com/doc/refman/5.6/en/auto-reconnect.html)
* [MySQL auto-reconnect revisited](http://groups.google.com/group/rubyonrails-core/browse_thread/thread/49d2a7e9c96cb9f4)
### Other Active Record Changes
diff --git a/guides/source/3_0_release_notes.md b/guides/source/3_0_release_notes.md
index 46be2613ab..9ad32e8168 100644
--- a/guides/source/3_0_release_notes.md
+++ b/guides/source/3_0_release_notes.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Ruby on Rails 3.0 Release Notes
===============================
@@ -138,7 +140,7 @@ More Information: - [Rails Edge Architecture](http://yehudakatz.com/2009/06/11/r
[Arel](http://github.com/brynary/arel) (or Active Relation) has been taken on as the underpinnings of Active Record and is now required for Rails. Arel provides an SQL abstraction that simplifies out Active Record and provides the underpinnings for the relation functionality in Active Record.
-More information: - [Why I wrote Arel](http://magicscalingsprinkles.wordpress.com/2010/01/28/why-i-wrote-arel/.)
+More information: - [Why I wrote Arel](https://web.archive.org/web/20120718093140/http://magicscalingsprinkles.wordpress.com/2010/01/28/why-i-wrote-arel/)
### Mail Extraction
@@ -298,7 +300,7 @@ Deprecations
More Information:
* [The Rails 3 Router: Rack it Up](http://yehudakatz.com/2009/12/26/the-rails-3-router-rack-it-up/)
-* [Revamped Routes in Rails 3](http://rizwanreza.com/2009/12/20/revamped-routes-in-rails-3)
+* [Revamped Routes in Rails 3](https://medium.com/fusion-of-thoughts/revamped-routes-in-rails-3-b6d00654e5b0)
* [Generic Actions in Rails 3](http://yehudakatz.com/2009/12/20/generic-actions-in-rails-3/)
diff --git a/guides/source/3_1_release_notes.md b/guides/source/3_1_release_notes.md
index b7ed285b96..537aa5a371 100644
--- a/guides/source/3_1_release_notes.md
+++ b/guides/source/3_1_release_notes.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Ruby on Rails 3.1 Release Notes
===============================
@@ -172,7 +174,7 @@ Rails Architectural Changes
The major change in Rails 3.1 is the Assets Pipeline. It makes CSS and JavaScript first-class code citizens and enables proper organization, including use in plugins and engines.
-The assets pipeline is powered by [Sprockets](https://github.com/sstephenson/sprockets) and is covered in the [Asset Pipeline](asset_pipeline.html) guide.
+The assets pipeline is powered by [Sprockets](https://github.com/rails/sprockets) and is covered in the [Asset Pipeline](asset_pipeline.html) guide.
### HTTP Streaming
diff --git a/guides/source/3_2_release_notes.md b/guides/source/3_2_release_notes.md
index c5db0262e9..6ddf77d9c0 100644
--- a/guides/source/3_2_release_notes.md
+++ b/guides/source/3_2_release_notes.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Ruby on Rails 3.2 Release Notes
===============================
diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md
index 84a65df2bc..9feaff098a 100644
--- a/guides/source/4_0_release_notes.md
+++ b/guides/source/4_0_release_notes.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Ruby on Rails 4.0 Release Notes
===============================
@@ -57,25 +59,25 @@ Major Features
### Upgrade
- * **Ruby 1.9.3** ([commit](https://github.com/rails/rails/commit/a0380e808d3dbd2462df17f5d3b7fcd8bd812496)) - Ruby 2.0 preferred; 1.9.3+ required
- * **[New deprecation policy](http://www.youtube.com/watch?v=z6YgD6tVPQs)** - Deprecated features are warnings in Rails 4.0 and will be removed in Rails 4.1.
- * **ActionPack page and action caching** ([commit](https://github.com/rails/rails/commit/b0a7068564f0c95e7ef28fc39d0335ed17d93e90)) - Page and action caching are extracted to a separate gem. Page and action caching requires too much manual intervention (manually expiring caches when the underlying model objects are updated). Instead, use Russian doll caching.
- * **ActiveRecord observers** ([commit](https://github.com/rails/rails/commit/ccecab3ba950a288b61a516bf9b6962e384aae0b)) - Observers are extracted to a separate gem. Observers are only needed for page and action caching, and can lead to spaghetti code.
- * **ActiveRecord session store** ([commit](https://github.com/rails/rails/commit/0ffe19056c8e8b2f9ae9d487b896cad2ce9387ad)) - The ActiveRecord session store is extracted to a separate gem. Storing sessions in SQL is costly. Instead, use cookie sessions, memcache sessions, or a custom session store.
- * **ActiveModel mass assignment protection** ([commit](https://github.com/rails/rails/commit/f8c9a4d3e88181cee644f91e1342bfe896ca64c6)) - Rails 3 mass assignment protection is deprecated. Instead, use strong parameters.
- * **ActiveResource** ([commit](https://github.com/rails/rails/commit/f1637bf2bb00490203503fbd943b73406e043d1d)) - ActiveResource is extracted to a separate gem. ActiveResource was not widely used.
- * **vendor/plugins removed** ([commit](https://github.com/rails/rails/commit/853de2bd9ac572735fa6cf59fcf827e485a231c3)) - Use a Gemfile to manage installed gems.
+* **Ruby 1.9.3** ([commit](https://github.com/rails/rails/commit/a0380e808d3dbd2462df17f5d3b7fcd8bd812496)) - Ruby 2.0 preferred; 1.9.3+ required
+* **[New deprecation policy](http://www.youtube.com/watch?v=z6YgD6tVPQs)** - Deprecated features are warnings in Rails 4.0 and will be removed in Rails 4.1.
+* **ActionPack page and action caching** ([commit](https://github.com/rails/rails/commit/b0a7068564f0c95e7ef28fc39d0335ed17d93e90)) - Page and action caching are extracted to a separate gem. Page and action caching requires too much manual intervention (manually expiring caches when the underlying model objects are updated). Instead, use Russian doll caching.
+* **ActiveRecord observers** ([commit](https://github.com/rails/rails/commit/ccecab3ba950a288b61a516bf9b6962e384aae0b)) - Observers are extracted to a separate gem. Observers are only needed for page and action caching, and can lead to spaghetti code.
+* **ActiveRecord session store** ([commit](https://github.com/rails/rails/commit/0ffe19056c8e8b2f9ae9d487b896cad2ce9387ad)) - The ActiveRecord session store is extracted to a separate gem. Storing sessions in SQL is costly. Instead, use cookie sessions, memcache sessions, or a custom session store.
+* **ActiveModel mass assignment protection** ([commit](https://github.com/rails/rails/commit/f8c9a4d3e88181cee644f91e1342bfe896ca64c6)) - Rails 3 mass assignment protection is deprecated. Instead, use strong parameters.
+* **ActiveResource** ([commit](https://github.com/rails/rails/commit/f1637bf2bb00490203503fbd943b73406e043d1d)) - ActiveResource is extracted to a separate gem. ActiveResource was not widely used.
+* **vendor/plugins removed** ([commit](https://github.com/rails/rails/commit/853de2bd9ac572735fa6cf59fcf827e485a231c3)) - Use a Gemfile to manage installed gems.
### ActionPack
- * **Strong parameters** ([commit](https://github.com/rails/rails/commit/a8f6d5c6450a7fe058348a7f10a908352bb6c7fc)) - Only allow whitelisted parameters to update model objects (`params.permit(:title, :text)`).
- * **Routing concerns** ([commit](https://github.com/rails/rails/commit/0dd24728a088fcb4ae616bb5d62734aca5276b1b)) - In the routing DSL, factor out common subroutes (`comments` from `/posts/1/comments` and `/videos/1/comments`).
- * **ActionController::Live** ([commit](https://github.com/rails/rails/commit/af0a9f9eefaee3a8120cfd8d05cbc431af376da3)) - Stream JSON with `response.stream`.
- * **Declarative ETags** ([commit](https://github.com/rails/rails/commit/ed5c938fa36995f06d4917d9543ba78ed506bb8d)) - Add controller-level etag additions that will be part of the action etag computation
- * **[Russian doll caching](http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works)** ([commit](https://github.com/rails/rails/commit/4154bf012d2bec2aae79e4a49aa94a70d3e91d49)) - Cache nested fragments of views. Each fragment expires based on a set of dependencies (a cache key). The cache key is usually a template version number and a model object.
- * **Turbolinks** ([commit](https://github.com/rails/rails/commit/e35d8b18d0649c0ecc58f6b73df6b3c8d0c6bb74)) - Serve only one initial HTML page. When the user navigates to another page, use pushState to update the URL and use AJAX to update the title and body.
- * **Decouple ActionView from ActionController** ([commit](https://github.com/rails/rails/commit/78b0934dd1bb84e8f093fb8ef95ca99b297b51cd)) - ActionView was decoupled from ActionPack and will be moved to a separated gem in Rails 4.1.
- * **Do not depend on ActiveModel** ([commit](https://github.com/rails/rails/commit/166dbaa7526a96fdf046f093f25b0a134b277a68)) - ActionPack no longer depends on ActiveModel.
+* **Strong parameters** ([commit](https://github.com/rails/rails/commit/a8f6d5c6450a7fe058348a7f10a908352bb6c7fc)) - Only allow whitelisted parameters to update model objects (`params.permit(:title, :text)`).
+* **Routing concerns** ([commit](https://github.com/rails/rails/commit/0dd24728a088fcb4ae616bb5d62734aca5276b1b)) - In the routing DSL, factor out common subroutes (`comments` from `/posts/1/comments` and `/videos/1/comments`).
+* **ActionController::Live** ([commit](https://github.com/rails/rails/commit/af0a9f9eefaee3a8120cfd8d05cbc431af376da3)) - Stream JSON with `response.stream`.
+* **Declarative ETags** ([commit](https://github.com/rails/rails/commit/ed5c938fa36995f06d4917d9543ba78ed506bb8d)) - Add controller-level etag additions that will be part of the action etag computation.
+* **[Russian doll caching](http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works)** ([commit](https://github.com/rails/rails/commit/4154bf012d2bec2aae79e4a49aa94a70d3e91d49)) - Cache nested fragments of views. Each fragment expires based on a set of dependencies (a cache key). The cache key is usually a template version number and a model object.
+* **Turbolinks** ([commit](https://github.com/rails/rails/commit/e35d8b18d0649c0ecc58f6b73df6b3c8d0c6bb74)) - Serve only one initial HTML page. When the user navigates to another page, use pushState to update the URL and use AJAX to update the title and body.
+* **Decouple ActionView from ActionController** ([commit](https://github.com/rails/rails/commit/78b0934dd1bb84e8f093fb8ef95ca99b297b51cd)) - ActionView was decoupled from ActionPack and will be moved to a separated gem in Rails 4.1.
+* **Do not depend on ActiveModel** ([commit](https://github.com/rails/rails/commit/166dbaa7526a96fdf046f093f25b0a134b277a68)) - ActionPack no longer depends on ActiveModel.
### General
@@ -85,14 +87,17 @@ Major Features
* **Support for specifying transaction isolation level** ([commit](https://github.com/rails/rails/commit/392eeecc11a291e406db927a18b75f41b2658253)) - Choose whether repeatable reads or improved performance (less locking) is more important.
* **Dalli** ([commit](https://github.com/rails/rails/commit/82663306f428a5bbc90c511458432afb26d2f238)) - Use Dalli memcache client for the memcache store.
* **Notifications start &amp; finish** ([commit](https://github.com/rails/rails/commit/f08f8750a512f741acb004d0cebe210c5f949f28)) - Active Support instrumentation reports start and finish notifications to subscribers.
- * **Thread safe by default** ([commit](https://github.com/rails/rails/commit/5d416b907864d99af55ebaa400fff217e17570cd)) - Rails can run in threaded app servers without additional configuration. Note: Check that the gems you are using are threadsafe.
+ * **Thread safe by default** ([commit](https://github.com/rails/rails/commit/5d416b907864d99af55ebaa400fff217e17570cd)) - Rails can run in threaded app servers without additional configuration.
+
+NOTE: Check that the gems you are using are threadsafe.
+
* **PATCH verb** ([commit](https://github.com/rails/rails/commit/eed9f2539e3ab5a68e798802f464b8e4e95e619e)) - In Rails, PATCH replaces PUT. PATCH is used for partial updates of resources.
### Security
- * **match do not catch all** ([commit](https://github.com/rails/rails/commit/90d2802b71a6e89aedfe40564a37bd35f777e541)) - In the routing DSL, match requires the HTTP verb or verbs to be specified.
- * **html entities escaped by default** ([commit](https://github.com/rails/rails/commit/5f189f41258b83d49012ec5a0678d827327e7543)) - Strings rendered in erb are escaped unless wrapped with `raw` or `html_safe` is called.
- * **New security headers** ([commit](https://github.com/rails/rails/commit/6794e92b204572d75a07bd6413bdae6ae22d5a82)) - Rails sends the following headers with every HTTP request: `X-Frame-Options` (prevents clickjacking by forbidding the browser from embedding the page in a frame), `X-XSS-Protection` (asks the browser to halt script injection) and `X-Content-Type-Options` (prevents the browser from opening a jpeg as an exe).
+* **match do not catch all** ([commit](https://github.com/rails/rails/commit/90d2802b71a6e89aedfe40564a37bd35f777e541)) - In the routing DSL, match requires the HTTP verb or verbs to be specified.
+* **html entities escaped by default** ([commit](https://github.com/rails/rails/commit/5f189f41258b83d49012ec5a0678d827327e7543)) - Strings rendered in erb are escaped unless wrapped with `raw` or `html_safe` is called.
+* **New security headers** ([commit](https://github.com/rails/rails/commit/6794e92b204572d75a07bd6413bdae6ae22d5a82)) - Rails sends the following headers with every HTTP request: `X-Frame-Options` (prevents clickjacking by forbidding the browser from embedding the page in a frame), `X-XSS-Protection` (asks the browser to halt script injection) and `X-Content-Type-Options` (prevents the browser from opening a jpeg as an exe).
Extraction of features to gems
---------------------------
@@ -179,7 +184,7 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/4-0-stable/a
* `String#to_date` now raises `ArgumentError: invalid date` instead of `NoMethodError: undefined method 'div' for nil:NilClass`
when given an invalid date. It is now the same as `Date.parse`, and it accepts more invalid dates than 3.x, such as:
- ```
+ ```ruby
# ActiveSupport 3.x
"asdf".to_date # => NoMethodError: undefined method `div' for nil:NilClass
"333".to_date # => NoMethodError: undefined method `div' for nil:NilClass
diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md
index 52a5acb75e..6bf65757ec 100644
--- a/guides/source/4_1_release_notes.md
+++ b/guides/source/4_1_release_notes.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Ruby on Rails 4.1 Release Notes
===============================
@@ -136,7 +138,7 @@ end
### Action Mailer Previews
-Action Mailer previews provide a way to visually see how emails look by visiting
+Action Mailer previews provide a way to see how emails look by visiting
a special URL that renders them.
You implement a preview class whose methods return the mail object you'd like
@@ -315,15 +317,15 @@ for detailed changes.
* Removed deprecated constants from Action Controller:
- | Removed | Successor |
- |:-----------------------------------|:--------------------------------|
- | ActionController::AbstractRequest | ActionDispatch::Request |
- | ActionController::Request | ActionDispatch::Request |
- | ActionController::AbstractResponse | ActionDispatch::Response |
- | ActionController::Response | ActionDispatch::Response |
- | ActionController::Routing | ActionDispatch::Routing |
- | ActionController::Integration | ActionDispatch::Integration |
- | ActionController::IntegrationTest | ActionDispatch::IntegrationTest |
+| Removed | Successor |
+|:-----------------------------------|:--------------------------------|
+| ActionController::AbstractRequest | ActionDispatch::Request |
+| ActionController::Request | ActionDispatch::Request |
+| ActionController::AbstractResponse | ActionDispatch::Response |
+| ActionController::Response | ActionDispatch::Response |
+| ActionController::Routing | ActionDispatch::Routing |
+| ActionController::Integration | ActionDispatch::Integration |
+| ActionController::IntegrationTest | ActionDispatch::IntegrationTest |
### Notable changes
diff --git a/guides/source/4_2_release_notes.md b/guides/source/4_2_release_notes.md
index d9f49aac55..684bd286bc 100644
--- a/guides/source/4_2_release_notes.md
+++ b/guides/source/4_2_release_notes.md
@@ -1,23 +1,23 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Ruby on Rails 4.2 Release Notes
===============================
Highlights in Rails 4.2:
-* Active Job, Action Mailer #deliver_later
+* Active Job
+* Asynchronous mails
* Adequate Record
* Web Console
* Foreign key support
-These release notes cover only the major changes. To learn about various bug
-fixes and changes, please refer to the change logs or check out the [list of
-commits](https://github.com/rails/rails/commits/master) in the main Rails
-repository on GitHub.
+These release notes cover only the major changes. To learn about other
+features, bug fixes, and changes, please refer to the changelogs or check out
+the [list of commits](https://github.com/rails/rails/commits/4-2-stable) in
+the main Rails repository on GitHub.
--------------------------------------------------------------------------------
-NOTE: This document is a work in progress, please help to improve this by sending
-a [pull request](https://github.com/rails/rails/edit/master/guides/source/4_2_release_notes.md).
-
Upgrading to Rails 4.2
----------------------
@@ -25,96 +25,115 @@ If you're upgrading an existing application, it's a great idea to have good test
coverage before going in. You should also first upgrade to Rails 4.1 in case you
haven't and make sure your application still runs as expected before attempting
to upgrade to Rails 4.2. A list of things to watch out for when upgrading is
-available in the guide: [Upgrading Ruby on
-Rails](upgrading_ruby_on_rails.html#upgrading-from-rails-4-1-to-rails-4-2)
+available in the guide [Upgrading Ruby on
+Rails](upgrading_ruby_on_rails.html#upgrading-from-rails-4-1-to-rails-4-2).
Major Features
--------------
-### Active Job, Action Mailer #deliver_later
+### Active Job
-Active Job is a new framework in Rails 4.2. It is an adapter layer on top of
+Active Job is a new framework in Rails 4.2. It is a common interface on top of
queuing systems like [Resque](https://github.com/resque/resque), [Delayed
Job](https://github.com/collectiveidea/delayed_job),
[Sidekiq](https://github.com/mperham/sidekiq), and more.
-You can write your jobs with the Active Job API, and it'll run on all these
-queues with no changes (it comes pre-configured with an inline runner).
+Jobs written with the Active Job API run on any of the supported queues thanks
+to their respective adapters. Active Job comes pre-configured with an inline
+runner that executes jobs right away.
+
+Jobs often need to take Active Record objects as arguments. Active Job passes
+object references as URIs (uniform resource identifiers) instead of marshaling
+the object itself. The new [Global ID](https://github.com/rails/globalid)
+library builds URIs and looks up the objects they reference. Passing Active
+Record objects as job arguments just works by using Global ID internally.
+
+For example, if `trashable` is an Active Record object, then this job runs
+just fine with no serialization involved:
+
+```ruby
+class TrashableCleanupJob < ActiveJob::Base
+ def perform(trashable, depth)
+ trashable.cleanup(depth)
+ end
+end
+```
+
+See the [Active Job Basics](active_job_basics.html) guide for more
+information.
+
+### Asynchronous Mails
-Building on top of Active Job, Action Mailer now comes with a `#deliver_later`
-method, which adds your email to be sent as a job to a queue, so it doesn't
-bog down the controller or model.
+Building on top of Active Job, Action Mailer now comes with a `deliver_later`
+method that sends emails via the queue, so it doesn't block the controller or
+model if the queue is asynchronous (the default inline queue blocks).
-The new GlobalID library makes it easy to pass Active Record objects to jobs by
-serializing them in a generic form. This means you no longer have to manually
-pack and unpack your Active Records by passing ids. Just give the job the
-straight Active Record object, and it'll serialize it using GlobalID, and
-deserialize it at run time.
+Sending emails right away is still possible with `deliver_now`.
### Adequate Record
-Adequate Record is a set of refactorings that make Active Record `find` and
-`find_by` methods and some association queries upto 2x faster.
+Adequate Record is a set of performance improvements in Active Record that makes
+common `find` and `find_by` calls and some association queries up to 2x faster.
-It works by caching SQL query patterns while executing the Active Record calls.
-The cache helps skip parts of the computation involved in the transformation of
-the calls into SQL queries. More details in [Aaron Patterson's
+It works by caching common SQL queries as prepared statements and reusing them
+on similar calls, skipping most of the query-generation work on subsequent
+calls. For more details, please refer to [Aaron Patterson's blog
post](http://tenderlovemaking.com/2014/02/19/adequaterecord-pro-like-activerecord.html).
-Nothing special has to be done to activate this feature. Most `find` and
-`find_by` calls and association queries will use it automatically. Examples:
+Active Record will automatically take advantage of this feature on
+supported operations without any user involvement or code changes. Here are
+some examples of supported operations:
```ruby
-Post.find 1 # caches query pattern
-Post.find 2 # uses the cached pattern
+Post.find(1) # First call generates and cache the prepared statement
+Post.find(2) # Subsequent calls reuse the cached prepared statement
+
+Post.find_by_title('first post')
+Post.find_by_title('second post')
-Post.find_by_title 'first post' # caches query pattern
-Post.find_by_title 'second post' # uses the cached pattern
+Post.find_by(title: 'first post')
+Post.find_by(title: 'second post')
-post.comments # caches query pattern
-post.comments(true) # uses cached pattern
+post.comments
+post.comments(true)
```
-The caching is not used in the following scenarios:
+It's important to highlight that, as the examples above suggest, the prepared
+statements do not cache the values passed in the method calls; rather, they
+have placeholders for them.
+
+Caching is not used in the following scenarios:
- The model has a default scope
-- The model uses single table inheritance to inherit from another model
-- `find` with a list of ids. eg:
+- The model uses single table inheritance
+- `find` with a list of ids, e.g.:
```ruby
- Post.find(1,2,3)
- OR
- Post.find [1,2]
+ # not cached
+ Post.find(1, 2, 3)
+ Post.find([1,2])
```
-- `find_by` with sql fragments:
+- `find_by` with SQL fragments:
```ruby
- Post.find_by "published_at < ?", 2.weeks.ago
+ Post.find_by('published_at < ?', 2.weeks.ago)
```
### Web Console
-New applications generated from Rails 4.2 now come with the Web Console gem by
-default.
-
-Web Console is a set of debugging tools for your Rails application. It will add
-an interactive console on every error page, a `console` view helper and a VT100
-compatible terminal.
-
-The interactive console on the error pages lets you execute code where the
-exception originated. It's quite handy to introspect the state that led to the
-error.
+New applications generated with Rails 4.2 now come with the [Web
+Console](https://github.com/rails/web-console) gem by default. Web Console adds
+an interactive Ruby console on every error page and provides a `console` view
+and controller helpers.
-The `console` view helper launches an interactive console within the context of
-the view where it is invoked.
+The interactive console on error pages lets you execute code in the context of
+the place where the exception originated. The `console` helper, if called
+anywhere in a view or controller, launches an interactive console with the final
+context, once rendering has completed.
-Finally, you can launch a VT100 terminal that runs `rails console`. If you need
-to create or modify existing test data, you can do that straight from the
-browser.
-
-### Foreign key support
+### Foreign Key Support
The migration DSL now supports adding and removing foreign keys. They are dumped
to `schema.rb` as well. At this time, only the `mysql`, `mysql2` and `postgresql`
@@ -149,18 +168,18 @@ individual components for new deprecations in this release.
The following changes may require immediate action upon upgrade.
-### `render` with a String argument
+### `render` with a String Argument
-Previously, calling `render "foo/bar"` in a controller action is equivalent to
-`render file: "foo/bar"`. In Rails 4.2, this has been changed to mean `render template: "foo/bar"`
-instead. If you need to render a file, please change your code to use the
-explicit form (`render file: "foo/bar"`) instead.
+Previously, calling `render "foo/bar"` in a controller action was equivalent to
+`render file: "foo/bar"`. In Rails 4.2, this has been changed to mean
+`render template: "foo/bar"` instead. If you need to render a file, please
+change your code to use the explicit form (`render file: "foo/bar"`) instead.
-### `respond_with` / class-level `respond_to`
+### `respond_with` / Class-Level `respond_to`
-`respond_with` and the corresponding class-level `respond_to` have been moved to
-the `responders` gem. To use the following, add `gem 'responders', '~> 2.0'` to
-your Gemfile:
+`respond_with` and the corresponding class-level `respond_to` have been moved
+to the [responders](https://github.com/plataformatec/responders) gem. Add
+`gem 'responders', '~> 2.0'` to your Gemfile to use it:
```ruby
# app/controllers/users_controller.rb
@@ -191,70 +210,61 @@ class UsersController < ApplicationController
end
```
-### Default host for `rails server`
+### Default Host for `rails server`
Due to a [change in Rack](https://github.com/rack/rack/commit/28b014484a8ac0bbb388e7eaeeef159598ec64fc),
`rails server` now listens on `localhost` instead of `0.0.0.0` by default. This
-should have minimal impact on the standard development workflow as both http://127.0.0.1:3000
-and http://localhost:3000 would continue to work as before on your own machine.
+should have minimal impact on the standard development workflow as both
+http://127.0.0.1:3000 and http://localhost:3000 will continue to work as before
+on your own machine.
-However, with this change you would no longer be able to access the Rails server
-from a different machine (e.g. your development environment is in a virtual
-machine and you would like to access it from the host machine), you would need
-to start the server with `rails server -b 0.0.0.0` to restore the old behavior.
+However, with this change you will no longer be able to access the Rails
+server from a different machine, for example if your development environment
+is in a virtual machine and you would like to access it from the host machine.
+In such cases, please start the server with `rails server -b 0.0.0.0` to
+restore the old behavior.
If you do this, be sure to configure your firewall properly such that only
trusted machines on your network can access your development server.
-### Production logging
-
-The default log level in the `production` environment is now `:debug`. This
-makes it consistent with the other environments, and ensures plenty of
-information is available to diagnose problems.
-
-It can be returned to the previous level, `:info`, in the environment
-configuration:
-
-```ruby
-# config/environments/production.rb
-
-# Decrease the log volume.
-config.log_level = :info
-```
-
### HTML Sanitizer
The HTML sanitizer has been replaced with a new, more robust, implementation
-built upon Loofah and Nokogiri. The new sanitizer is more secure and its
-sanitization is more powerful and flexible.
+built upon [Loofah](https://github.com/flavorjones/loofah) and
+[Nokogiri](https://github.com/sparklemotion/nokogiri). The new sanitizer is
+more secure and its sanitization is more powerful and flexible.
-With a new sanitization algorithm, the sanitized output will change for certain
+Due to the new algorithm, the sanitized output may be different for certain
pathological inputs.
-If you have particular need for the exact output of the old sanitizer, you can
-add `rails-deprecated_sanitizer` to your Gemfile, and it will automatically
-replace the new implementation. Because it is opt-in, the legacy gem will not
-give deprecation warnings.
+If you have a particular need for the exact output of the old sanitizer, you
+can add the [rails-deprecated_sanitizer](https://github.com/kaspth/rails-deprecated_sanitizer)
+gem to the `Gemfile`, to have the old behavior. The gem does not issue
+deprecation warnings because it is opt-in.
`rails-deprecated_sanitizer` will be supported for Rails 4.2 only; it will not
be maintained for Rails 5.0.
-See [the blog post](http://blog.plataformatec.com.br/2014/07/the-new-html-sanitizer-in-rails-4-2/)
-for more detail on the changes in the new sanitizer.
+See [this blog post](http://blog.plataformatec.com.br/2014/07/the-new-html-sanitizer-in-rails-4-2/)
+for more details on the changes in the new sanitizer.
### `assert_select`
-`assert_select` is now based on Nokogiri, making it (TODO: betterer).
-
+`assert_select` is now based on [Nokogiri](https://github.com/sparklemotion/nokogiri).
As a result, some previously-valid selectors are now unsupported. If your
application is using any of these spellings, you will need to update them:
* Values in attribute selectors may need to be quoted if they contain
non-alphanumeric characters.
- ```
- a[href=/] => a[href="/"]
- a[href$=/] => a[href$="/"]
+ ```ruby
+ # before
+ a[href=/]
+ a[href$=/]
+
+ # now
+ a[href="/"]
+ a[href$="/"]
```
* DOMs built from HTML source containing invalid HTML with improperly
@@ -262,7 +272,7 @@ application is using any of these spellings, you will need to update them:
For example:
- ``` ruby
+ ```ruby
# content: <div><i><p></i></div>
# before:
@@ -280,7 +290,7 @@ application is using any of these spellings, you will need to update them:
used to be raw (e.g. `AT&amp;T`), and now is evaluated
(e.g. `AT&T`).
- ``` ruby
+ ```ruby
# content: <p>AT&amp;T</p>
# before:
@@ -292,6 +302,30 @@ application is using any of these spellings, you will need to update them:
assert_select('p', 'AT&amp;T') # => false
```
+Furthermore substitutions have changed syntax.
+
+Now you have to use a `:match` CSS-like selector:
+
+```ruby
+assert_select ":match('id', ?)", 'comment_1'
+```
+
+Additionally Regexp substitutions look different when the assertion fails.
+Notice how `/hello/` here:
+
+```ruby
+assert_select(":match('id', ?)", /hello/)
+```
+
+becomes `"(?-mix:hello)"`:
+
+```
+Expected at least 1 element matching "div:match('id', "(?-mix:hello)")", found 0..
+Expected 0 to be >= 1.
+```
+
+See the [Rails Dom Testing](https://github.com/rails/rails-dom-testing/tree/8798b9349fb9540ad8cb9a0ce6cb88d1384a210b) documentation for more on `assert_select`.
+
Railties
--------
@@ -300,11 +334,24 @@ Please refer to the [Changelog][railties] for detailed changes.
### Removals
+* The `--skip-action-view` option has been removed from the
+ app generator. ([Pull Request](https://github.com/rails/rails/pull/17042))
+
* The `rails application` command has been removed without replacement.
([Pull Request](https://github.com/rails/rails/pull/11616))
### Deprecations
+* Deprecated missing `config.log_level` for production environments.
+ ([Pull Request](https://github.com/rails/rails/pull/16622))
+
+* Deprecated `rake test:all` in favor of `rake test` as it now run all tests
+ in the `test` folder.
+ ([Pull Request](https://github.com/rails/rails/pull/17348))
+
+* Deprecated `rake test:all:db` in favor of `rake test:db`.
+ ([Pull Request](https://github.com/rails/rails/pull/17348))
+
* Deprecated `Rails::Rack::LogTailer` without replacement.
([Commit](https://github.com/rails/rails/commit/84a13e019e93efaa8994b3f8303d635a7702dbce))
@@ -316,9 +363,6 @@ Please refer to the [Changelog][railties] for detailed changes.
* Added a `required` option to the model generator for associations.
([Pull Request](https://github.com/rails/rails/pull/16062))
-* Introduced an `after_bundle` callback for use in Rails templates.
- ([Pull Request](https://github.com/rails/rails/pull/16359))
-
* Introduced the `x` namespace for defining custom configuration options:
```ruby
@@ -358,20 +402,23 @@ Please refer to the [Changelog][railties] for detailed changes.
([Pull Request](https://github.com/rails/rails/pull/16129))
-* Introduced a `--skip-gems` option in the app generator to skip gems such as
- `turbolinks` and `coffee-rails` that do not have their own specific flags.
- ([Commit](https://github.com/rails/rails/commit/10565895805887d4faf004a6f71219da177f78b7))
+* Introduced a `--skip-turbolinks` option in the app generator to not generate
+ turbolinks integration.
+ ([Commit](https://github.com/rails/rails/commit/bf17c8a531bc8059d50ad731398002a3e7162a7d))
-* Introduced a `bin/setup` script to enable automated setup code when
+* Introduced a `bin/setup` script as a convention for automated setup code when
bootstrapping an application.
([Pull Request](https://github.com/rails/rails/pull/15189))
-* Changed default value for `config.assets.digest` to `true` in development.
+* Changed the default value for `config.assets.digest` to `true` in development.
([Pull Request](https://github.com/rails/rails/pull/15155))
* Introduced an API to register new extensions for `rake notes`.
([Pull Request](https://github.com/rails/rails/pull/14379))
+* Introduced an `after_bundle` callback for use in Rails templates.
+ ([Pull Request](https://github.com/rails/rails/pull/16359))
+
* Introduced `Rails.gem_version` as a convenience method to return
`Gem::Version.new(Rails.version)`.
([Pull Request](https://github.com/rails/rails/pull/14101))
@@ -384,10 +431,11 @@ Please refer to the [Changelog][action-pack] for detailed changes.
### Removals
-* `respond_with` and the class-level `respond_to` were removed from Rails and
+* `respond_with` and the class-level `respond_to` have been removed from Rails and
moved to the `responders` gem (version 2.0). Add `gem 'responders', '~> 2.0'`
to your `Gemfile` to continue using these features.
- ([Pull Request](https://github.com/rails/rails/pull/16526))
+ ([Pull Request](https://github.com/rails/rails/pull/16526),
+ [More Details](http://guides.rubyonrails.org/upgrading_ruby_on_rails.html#responders))
* Removed deprecated `AbstractController::Helpers::ClassMethods::MissingHelperError`
in favor of `AbstractController::Helpers::MissingHelperError`.
@@ -403,7 +451,7 @@ Please refer to the [Changelog][action-pack] for detailed changes.
([Commit](https://github.com/rails/rails-dom-testing/commit/b12850bc5ff23ba4b599bf2770874dd4f11bf750))
* Deprecated support for setting the `:to` option of a router to a symbol or a
- string that does not contain a `#` character:
+ string that does not contain a "#" character:
```ruby
get '/posts', to: MyRackApp => (No change necessary)
@@ -414,22 +462,22 @@ Please refer to the [Changelog][action-pack] for detailed changes.
([Commit](https://github.com/rails/rails/commit/cc26b6b7bccf0eea2e2c1a9ebdcc9d30ca7390d9))
-### Notable changes
+* Deprecated support for string keys in URL helpers:
-* Rails will now automatically include the template's digest in ETags.
- ([Pull Request](https://github.com/rails/rails/pull/16527))
+ ```ruby
+ # bad
+ root_path('controller' => 'posts', 'action' => 'index')
-* `render nothing: true` or rendering a `nil` body no longer add a single
- space padding to the response body.
- ([Pull Request](https://github.com/rails/rails/pull/14883))
+ # good
+ root_path(controller: 'posts', action: 'index')
+ ```
-* Introduced the `always_permitted_parameters` option to configure which
- parameters are permitted globally. The default value of this configuration
- is `['controller', 'action']`.
- ([Pull Request](https://github.com/rails/rails/pull/15933))
+ ([Pull Request](https://github.com/rails/rails/pull/17743))
+
+### Notable changes
-* The `*_filter` family methods have been removed from the documentation. Their
- usage is discouraged in favor of the `*_action` family methods:
+* The `*_filter` family of methods have been removed from the documentation. Their
+ usage is discouraged in favor of the `*_action` family of methods:
```
after_filter => after_action
@@ -454,16 +502,28 @@ Please refer to the [Changelog][action-pack] for detailed changes.
(Commit [1](https://github.com/rails/rails/commit/6c5f43bab8206747a8591435b2aa0ff7051ad3de),
[2](https://github.com/rails/rails/commit/489a8f2a44dc9cea09154ee1ee2557d1f037c7d4))
-* Added HTTP method `MKCALENDAR` from RFC-4791
+* `render nothing: true` or rendering a `nil` body no longer add a single
+ space padding to the response body.
+ ([Pull Request](https://github.com/rails/rails/pull/14883))
+
+* Rails now automatically includes the template's digest in ETags.
+ ([Pull Request](https://github.com/rails/rails/pull/16527))
+
+* Segments that are passed into URL helpers are now automatically escaped.
+ ([Commit](https://github.com/rails/rails/commit/5460591f0226a9d248b7b4f89186bd5553e7768f))
+
+* Introduced the `always_permitted_parameters` option to configure which
+ parameters are permitted globally. The default value of this configuration
+ is `['controller', 'action']`.
+ ([Pull Request](https://github.com/rails/rails/pull/15933))
+
+* Added the HTTP method `MKCALENDAR` from [RFC 4791](https://tools.ietf.org/html/rfc4791).
([Pull Request](https://github.com/rails/rails/pull/15121))
* `*_fragment.action_controller` notifications now include the controller
and action name in the payload.
([Pull Request](https://github.com/rails/rails/pull/14137))
-* Segments that are passed into URL helpers are now automatically escaped.
- ([Commit](https://github.com/rails/rails/commit/5460591f0226a9d248b7b4f89186bd5553e7768f))
-
* Improved the Routing Error page with fuzzy matching for route search.
([Pull Request](https://github.com/rails/rails/pull/14619))
@@ -471,28 +531,26 @@ Please refer to the [Changelog][action-pack] for detailed changes.
([Pull Request](https://github.com/rails/rails/pull/14280))
* When the Rails server is set to serve static assets, gzip assets will now be
- served if the client supports it and a pre-generated gzip file (.gz) is on disk.
+ served if the client supports it and a pre-generated gzip file (`.gz`) is on disk.
By default the asset pipeline generates `.gz` files for all compressible assets.
Serving gzip files minimizes data transfer and speeds up asset requests. Always
[use a CDN](http://guides.rubyonrails.org/asset_pipeline.html#cdns) if you are
serving assets from your Rails server in production.
([Pull Request](https://github.com/rails/rails/pull/16466))
-* The way `assert_select` works has changed; specifically a different library
- is used to interpret css selectors, build the transient DOM that the
- selectors are applied against, and to extract the data from that DOM. These
- changes should only affect edge cases. Examples:
- * Values in attribute selectors may need to be quoted if they contain
- non-alphanumeric characters.
- * DOMs built from HTML source containing invalid HTML with improperly
- nested elements may differ.
- * If the data selected contains entities, the value selected for comparison
- used to be raw (e.g. `AT&amp;T`), and now is evaluated
- (e.g. `AT&T`).
+* When calling the `process` helpers in an integration test the path needs to have
+ a leading slash. Previously you could omit it but that was a byproduct of the
+ implementation and not an intentional feature, e.g.:
+ ```ruby
+ test "list all posts" do
+ get "/posts"
+ assert_response :success
+ end
+ ```
Action View
--------------
+-----------
Please refer to the [Changelog][action-view] for detailed changes.
@@ -513,16 +571,16 @@ Please refer to the [Changelog][action-view] for detailed changes.
`render file: "foo/bar"`.
([Pull Request](https://github.com/rails/rails/pull/16888))
-* Introduced a `#{partial_name}_iteration` special local variable for use with
- partials that are rendered with a collection. It provides access to the
- current state of the iteration via the `#index`, `#size`, `#first?` and
- `#last?` methods.
- ([Pull Request](https://github.com/rails/rails/pull/7698))
-
* The form helpers no longer generate a `<div>` element with inline CSS around
the hidden fields.
([Pull Request](https://github.com/rails/rails/pull/14738))
+* Introduced a `#{partial_name}_iteration` special local variable for use with
+ partials that are rendered with a collection. It provides access to the
+ current state of the iteration via the `index`, `size`, `first?` and
+ `last?` methods.
+ ([Pull Request](https://github.com/rails/rails/pull/7698))
+
* Placeholder I18n follows the same convention as `label` I18n.
([Pull Request](https://github.com/rails/rails/pull/16438))
@@ -537,11 +595,15 @@ Please refer to the [Changelog][action-mailer] for detailed changes.
* Deprecated `*_path` helpers in mailers. Always use `*_url` helpers instead.
([Pull Request](https://github.com/rails/rails/pull/15840))
-* Deprecated `deliver` / `deliver!` in favour of `deliver_now` / `deliver_now!`.
+* Deprecated `deliver` / `deliver!` in favor of `deliver_now` / `deliver_now!`.
([Pull Request](https://github.com/rails/rails/pull/16582))
### Notable changes
+* `link_to` and `url_for` generate absolute URLs by default in templates,
+ it is no longer needed to pass `only_path: false`.
+ ([Commit](https://github.com/rails/rails/commit/9685080a7677abfa5d288a81c3e078368c6bb67c))
+
* Introduced `deliver_later` which enqueues a job on the application's queue
to deliver emails asynchronously.
([Pull Request](https://github.com/rails/rails/pull/16485))
@@ -570,7 +632,7 @@ Please refer to the [Changelog][active-record] for detailed changes.
* Removed unused `:timestamp` type. Transparently alias it to `:datetime`
in all cases. Fixes inconsistencies when column types are sent outside of
- `ActiveRecord`, such as for XML serialization.
+ Active Record, such as for XML serialization.
([Pull Request](https://github.com/rails/rails/pull/15184))
### Deprecations
@@ -578,112 +640,123 @@ Please refer to the [Changelog][active-record] for detailed changes.
* Deprecated swallowing of errors inside `after_commit` and `after_rollback`.
([Pull Request](https://github.com/rails/rails/pull/16537))
-* Deprecated calling `DatabaseTasks.load_schema` without a connection. Use
- `DatabaseTasks.load_schema_current` instead.
- ([Commit](https://github.com/rails/rails/commit/f15cef67f75e4b52fd45655d7c6ab6b35623c608))
-
-* Deprecated `Reflection#source_macro` without replacement as it is no longer
- needed in Active Record.
- ([Pull Request](https://github.com/rails/rails/pull/16373))
-
* Deprecated broken support for automatic detection of counter caches on
`has_many :through` associations. You should instead manually specify the
counter cache on the `has_many` and `belongs_to` associations for the
through records.
([Pull Request](https://github.com/rails/rails/pull/15754))
-* Deprecated `serialized_attributes` without replacement.
- ([Pull Request](https://github.com/rails/rails/pull/15704))
-
-* Deprecated returning `nil` from `column_for_attribute` when no column
- exists. It will return a null object in Rails 5.0
- ([Pull Request](https://github.com/rails/rails/pull/15878))
-
-* Deprecated using `.joins`, `.preload` and `.eager_load` with associations
- that depends on the instance state (i.e. those defined with a scope that
- takes an argument) without replacement.
- ([Commit](https://github.com/rails/rails/commit/ed56e596a0467390011bc9d56d462539776adac1))
-
* Deprecated passing Active Record objects to `.find` or `.exists?`. Call
- `#id` on the objects first.
+ `id` on the objects first.
(Commit [1](https://github.com/rails/rails/commit/d92ae6ccca3bcfd73546d612efaea011270bd270),
[2](https://github.com/rails/rails/commit/d35f0033c7dec2b8d8b52058fb8db495d49596f7))
* Deprecated half-baked support for PostgreSQL range values with excluding
beginnings. We currently map PostgreSQL ranges to Ruby ranges. This conversion
- is not fully possible because the Ruby range does not support excluded
- beginnings.
+ is not fully possible because Ruby ranges do not support excluded beginnings.
The current solution of incrementing the beginning is not correct
and is now deprecated. For subtypes where we don't know how to increment
- (e.g. `#succ` is not defined) it will raise an `ArgumentError` for ranges
+ (e.g. `succ` is not defined) it will raise an `ArgumentError` for ranges
with excluding beginnings.
-
([Commit](https://github.com/rails/rails/commit/91949e48cf41af9f3e4ffba3e5eecf9b0a08bfc3))
-### Notable changes
+* Deprecated calling `DatabaseTasks.load_schema` without a connection. Use
+ `DatabaseTasks.load_schema_current` instead.
+ ([Commit](https://github.com/rails/rails/commit/f15cef67f75e4b52fd45655d7c6ab6b35623c608))
-* The PostgreSQL adapter now supports the `JSONB` datatype in PostgreSQL 9.4+.
- ([Pull Request](https://github.com/rails/rails/pull/16220))
+* Deprecated `sanitize_sql_hash_for_conditions` without replacement. Using a
+ `Relation` for performing queries and updates is the preferred API.
+ ([Commit](https://github.com/rails/rails/commit/d5902c9e))
-* The `#references` method in migrations now supports a `type` option for
- specifying the type of the foreign key (e.g. `:uuid`).
- ([Pull Request](https://github.com/rails/rails/pull/16231))
+* Deprecated `add_timestamps` and `t.timestamps` without passing the `:null`
+ option. The default of `null: true` will change in Rails 5 to `null: false`.
+ ([Pull Request](https://github.com/rails/rails/pull/16481))
-* Added a `:required` option to singular associations, which defines a
- presence validation on the association.
- ([Pull Request](https://github.com/rails/rails/pull/16056))
+* Deprecated `Reflection#source_macro` without replacement as it is no longer
+ needed in Active Record.
+ ([Pull Request](https://github.com/rails/rails/pull/16373))
-* Introduced `ActiveRecord::Base#validate!` that raises `RecordInvalid` if the
- record is invalid.
- ([Pull Request](https://github.com/rails/rails/pull/8639))
+* Deprecated `serialized_attributes` without replacement.
+ ([Pull Request](https://github.com/rails/rails/pull/15704))
-* `ActiveRecord::Base#reload` now behaves the same as `m = Model.find(m.id)`,
- meaning that it no longer retains the extra attributes from custom
- `select`s.
- ([Pull Request](https://github.com/rails/rails/pull/15866))
+* Deprecated returning `nil` from `column_for_attribute` when no column
+ exists. It will return a null object in Rails 5.0.
+ ([Pull Request](https://github.com/rails/rails/pull/15878))
-* Introduced the `bin/rake db:purge` task to empty the database for the
- current environment.
- ([Commit](https://github.com/rails/rails/commit/e2f232aba15937a4b9d14bd91e0392c6d55be58d))
+* Deprecated using `.joins`, `.preload` and `.eager_load` with associations
+ that depend on the instance state (i.e. those defined with a scope that
+ takes an argument) without replacement.
+ ([Commit](https://github.com/rails/rails/commit/ed56e596a0467390011bc9d56d462539776adac1))
+
+### Notable changes
+
+* `SchemaDumper` uses `force: :cascade` on `create_table`. This makes it
+ possible to reload a schema when foreign keys are in place.
+
+* Added a `:required` option to singular associations, which defines a
+ presence validation on the association.
+ ([Pull Request](https://github.com/rails/rails/pull/16056))
* `ActiveRecord::Dirty` now detects in-place changes to mutable values.
- Serialized attributes on ActiveRecord models will no longer save when
+ Serialized attributes on Active Record models are no longer saved when
unchanged. This also works with other types such as string columns and json
columns on PostgreSQL.
(Pull Requests [1](https://github.com/rails/rails/pull/15674),
[2](https://github.com/rails/rails/pull/15786),
[3](https://github.com/rails/rails/pull/15788))
-* Added support for `#pretty_print` in `ActiveRecord::Base` objects.
- ([Pull Request](https://github.com/rails/rails/pull/15172))
+* Introduced the `db:purge` Rake task to empty the database for the
+ current environment.
+ ([Commit](https://github.com/rails/rails/commit/e2f232aba15937a4b9d14bd91e0392c6d55be58d))
+
+* Introduced `ActiveRecord::Base#validate!` that raises
+ `ActiveRecord::RecordInvalid` if the record is invalid.
+ ([Pull Request](https://github.com/rails/rails/pull/8639))
+
+* Introduced `validate` as an alias for `valid?`.
+ ([Pull Request](https://github.com/rails/rails/pull/14456))
+
+* `touch` now accepts multiple attributes to be touched at once.
+ ([Pull Request](https://github.com/rails/rails/pull/14423))
+
+* The PostgreSQL adapter now supports the `jsonb` datatype in PostgreSQL 9.4+.
+ ([Pull Request](https://github.com/rails/rails/pull/16220))
-* PostgreSQL and SQLite adapters no longer add a default limit of 255
+* The PostgreSQL and SQLite adapters no longer add a default limit of 255
characters on string columns.
([Pull Request](https://github.com/rails/rails/pull/14579))
+* Added support for the `citext` column type in the PostgreSQL adapter.
+ ([Pull Request](https://github.com/rails/rails/pull/12523))
+
+* Added support for user-created range types in the PostgreSQL adapter.
+ ([Commit](https://github.com/rails/rails/commit/4cb47167e747e8f9dc12b0ddaf82bdb68c03e032))
+
* `sqlite3:///some/path` now resolves to the absolute system path
`/some/path`. For relative paths, use `sqlite3:some/path` instead.
(Previously, `sqlite3:///some/path` resolved to the relative path
- `some/path`. This behaviour was deprecated on Rails 4.1).
+ `some/path`. This behavior was deprecated on Rails 4.1).
([Pull Request](https://github.com/rails/rails/pull/14569))
-* Introduced `#validate` as an alias for `#valid?`.
- ([Pull Request](https://github.com/rails/rails/pull/14456))
-
-* `#touch` now accepts multiple attributes to be touched at once.
- ([Pull Request](https://github.com/rails/rails/pull/14423))
-
* Added support for fractional seconds for MySQL 5.6 and above.
(Pull Request [1](https://github.com/rails/rails/pull/8240),
[2](https://github.com/rails/rails/pull/14359))
-* Added support for the `citext` column type in PostgreSQL adapter.
- ([Pull Request](https://github.com/rails/rails/pull/12523))
+* Added `ActiveRecord::Base#pretty_print` to pretty print models.
+ ([Pull Request](https://github.com/rails/rails/pull/15172))
-* Added support for user-created range types in PostgreSQL adapter.
- ([Commit](https://github.com/rails/rails/commit/4cb47167e747e8f9dc12b0ddaf82bdb68c03e032))
+* `ActiveRecord::Base#reload` now behaves the same as `m = Model.find(m.id)`,
+ meaning that it no longer retains the extra attributes from custom
+ `SELECT`s.
+ ([Pull Request](https://github.com/rails/rails/pull/15866))
+* `ActiveRecord::Base#reflections` now returns a hash with string keys instead
+ of symbol keys. ([Pull Request](https://github.com/rails/rails/pull/17718))
+
+* The `references` method in migrations now supports a `type` option for
+ specifying the type of the foreign key (e.g. `:uuid`).
+ ([Pull Request](https://github.com/rails/rails/pull/16231))
Active Model
------------
@@ -701,11 +774,14 @@ Please refer to the [Changelog][active-model] for detailed changes.
([Pull Request](https://github.com/rails/rails/pull/16180))
* Deprecated `ActiveModel::Dirty#reset_changes` in favor of
- `#clear_changes_information`.
+ `clear_changes_information`.
([Pull Request](https://github.com/rails/rails/pull/16180))
### Notable changes
+* Introduced `validate` as an alias for `valid?`.
+ ([Pull Request](https://github.com/rails/rails/pull/14456))
+
* Introduced the `restore_attributes` method in `ActiveModel::Dirty` to restore
the changed (dirty) attributes to their previous values.
(Pull Request [1](https://github.com/rails/rails/pull/14861),
@@ -719,10 +795,6 @@ Please refer to the [Changelog][active-model] for detailed changes.
characters if validations are enabled.
([Pull Request](https://github.com/rails/rails/pull/15708))
-* Introduced `#validate` as an alias for `#valid?`.
- ([Pull Request](https://github.com/rails/rails/pull/14456))
-
-
Active Support
--------------
@@ -753,15 +825,15 @@ Please refer to the [Changelog][active-support] for detailed changes.
### Notable changes
-* `Object#try` and `Object#try!` can now be used without an explicit receiver.
- ([Commit](https://github.com/rails/rails/commit/5e51bdda59c9ba8e5faf86294e3e431bd45f1830),
- [Pull Request](https://github.com/rails/rails/pull/17361))
-
-* Introduced new configuration option `active_support.test_order` for
+* Introduced a new configuration option `active_support.test_order` for
specifying the order test cases are executed. This option currently defaults
to `:sorted` but will be changed to `:random` in Rails 5.0.
([Commit](https://github.com/rails/rails/commit/53e877f7d9291b2bf0b8c425f9e32ef35829f35b))
+* `Object#try` and `Object#try!` can now be used without an explicit receiver in the block.
+ ([Commit](https://github.com/rails/rails/commit/5e51bdda59c9ba8e5faf86294e3e431bd45f1830),
+ [Pull Request](https://github.com/rails/rails/pull/17361))
+
* The `travel_to` test helper now truncates the `usec` component to 0.
([Commit](https://github.com/rails/rails/commit/9f6e82ee4783e491c20f5244a613fdeb4024beb5))
@@ -769,7 +841,7 @@ Please refer to the [Changelog][active-support] for detailed changes.
(Commit [1](https://github.com/rails/rails/commit/702ad710b57bef45b081ebf42e6fa70820fdd810),
[2](https://github.com/rails/rails/commit/64d91122222c11ad3918cc8e2e3ebc4b0a03448a))
-* `Object#with_options` can now be used without an explicit receiver.
+* `Object#with_options` can now be used without an explicit receiver in the block.
([Pull Request](https://github.com/rails/rails/pull/16339))
* Introduced `String#truncate_words` to truncate a string by a number of words.
@@ -788,6 +860,7 @@ Please refer to the [Changelog][active-support] for detailed changes.
`module Foo; extend ActiveSupport::Concern; end` boilerplate.
([Commit](https://github.com/rails/rails/commit/b16c36e688970df2f96f793a759365b248b582ad))
+* New [guide](constant_autoloading_and_reloading.html) about constant autoloading and reloading.
Credits
-------
diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb
index f2315bfe22..67f5f1cdd5 100644
--- a/guides/source/_welcome.html.erb
+++ b/guides/source/_welcome.html.erb
@@ -10,10 +10,15 @@
</p>
<% else %>
<p>
- These are the new guides for Rails 4.2 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>.
+ These are the new guides for Rails 5.0 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>.
These guides are designed to make you immediately productive with Rails, and to help you understand how all of the pieces fit together.
</p>
<% end %>
<p>
- The guides for earlier releases: <a href="http://guides.rubyonrails.org/v4.1.6/">Rails 4.1.6</a>, <a href="http://guides.rubyonrails.org/v4.0.10/">Rails 4.0.10</a>, <a href="http://guides.rubyonrails.org/v3.2.19/">Rails 3.2.19</a> and <a href="http://guides.rubyonrails.org/v2.3.11/">Rails 2.3.11</a>.
+The guides for earlier releases:
+<a href="http://guides.rubyonrails.org/v4.2.0/">Rails 4.2.0</a>,
+<a href="http://guides.rubyonrails.org/v4.1.8/">Rails 4.1.8</a>,
+<a href="http://guides.rubyonrails.org/v4.0.12/">Rails 4.0.12</a>,
+<a href="http://guides.rubyonrails.org/v3.2.21/">Rails 3.2.21</a> and
+<a href="http://guides.rubyonrails.org/v2.3.11/">Rails 2.3.11</a>.
</p>
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index 1ca0d9ed55..a9725964a2 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Action Controller Overview
==========================
@@ -7,7 +9,7 @@ After reading this guide, you will know:
* How to follow the flow of a request through a controller.
* How to restrict parameters passed to your controller.
-* Why and how to store data in the session or cookies.
+* How and why to store data in the session or cookies.
* How to work with filters to execute code during request processing.
* How to use Action Controller's built-in HTTP authentication.
* How to stream data directly to the user's browser.
@@ -19,11 +21,11 @@ After reading this guide, you will know:
What Does a Controller Do?
--------------------------
-Action Controller is the C in MVC. After routing has determined which controller to use for a request, your controller is responsible for making sense of the request and producing the appropriate output. Luckily, Action Controller does most of the groundwork for you and uses smart conventions to make this as straightforward as possible.
+Action Controller is the C in MVC. After routing has determined which controller to use for a request, the controller is responsible for making sense of the request and producing the appropriate output. Luckily, Action Controller does most of the groundwork for you and uses smart conventions to make this as straightforward as possible.
For most conventional [RESTful](http://en.wikipedia.org/wiki/Representational_state_transfer) applications, the controller will receive the request (this is invisible to you as the developer), fetch or save data from a model and use a view to create HTML output. If your controller needs to do things a little differently, that's not a problem, this is just the most common way for a controller to work.
-A controller can thus be thought of as a middle man between models and views. It makes the model data available to the view so it can display that data to the user, and it saves or updates data from the user to the model.
+A controller can thus be thought of as a middleman between models and views. It makes the model data available to the view so it can display that data to the user, and it saves or updates user data to the model.
NOTE: For more details on the routing process, see [Rails Routing from the Outside In](routing.html).
@@ -32,7 +34,7 @@ Controller Naming Convention
The naming convention of controllers in Rails favors pluralization of the last word in the controller's name, although it is not strictly required (e.g. `ApplicationController`). For example, `ClientsController` is preferable to `ClientController`, `SiteAdminsController` is preferable to `SiteAdminController` or `SitesAdminsController`, and so on.
-Following this convention will allow you to use the default route generators (e.g. `resources`, etc) without needing to qualify each `:path` or `:controller`, and keeps URL and path helpers' usage consistent throughout your application. See [Layouts & Rendering Guide](layouts_and_rendering.html) for more details.
+Following this convention will allow you to use the default route generators (e.g. `resources`, etc) without needing to qualify each `:path` or `:controller`, and will keep URL and path helpers' usage consistent throughout your application. See [Layouts & Rendering Guide](layouts_and_rendering.html) for more details.
NOTE: The controller naming convention differs from the naming convention of models, which are expected to be named in singular form.
@@ -49,7 +51,7 @@ class ClientsController < ApplicationController
end
```
-As an example, if a user goes to `/clients/new` in your application to add a new client, Rails will create an instance of `ClientsController` and run the `new` method. Note that the empty method from the example above would work just fine because Rails will by default render the `new.html.erb` view unless the action says otherwise. The `new` method could make available to the view a `@client` instance variable by creating a new `Client`:
+As an example, if a user goes to `/clients/new` in your application to add a new client, Rails will create an instance of `ClientsController` and call its `new` method. Note that the empty method from the example above would work just fine because Rails will by default render the `new.html.erb` view unless the action says otherwise. The `new` method could make available to the view a `@client` instance variable by creating a new `Client`:
```ruby
def new
@@ -61,7 +63,7 @@ The [Layouts & Rendering Guide](layouts_and_rendering.html) explains this in mor
`ApplicationController` inherits from `ActionController::Base`, which defines a number of helpful methods. This guide will cover some of these, but if you're curious to see what's in there, you can see all of them in the API documentation or in the source itself.
-Only public methods are callable as actions. It is a best practice to lower the visibility of methods which are not intended to be actions, like auxiliary methods or filters.
+Only public methods are callable as actions. It is a best practice to lower the visibility of methods (with `private` or `protected`) which are not intended to be actions, like auxiliary methods or filters.
Parameters
----------
@@ -102,21 +104,21 @@ end
### Hash and Array Parameters
-The `params` hash is not limited to one-dimensional keys and values. It can contain arrays and (nested) hashes. To send an array of values, append an empty pair of square brackets "[]" to the key name:
+The `params` hash is not limited to one-dimensional keys and values. It can contain nested arrays and hashes. To send an array of values, append an empty pair of square brackets "[]" to the key name:
```
GET /clients?ids[]=1&ids[]=2&ids[]=3
```
-NOTE: The actual URL in this example will be encoded as "/clients?ids%5b%5d=1&ids%5b%5d=2&ids%5b%5d=3" as "[" and "]" are not allowed in URLs. Most of the time you don't have to worry about this because the browser will take care of it for you, and Rails will decode it back when it receives it, but if you ever find yourself having to send those requests to the server manually you have to keep this in mind.
+NOTE: The actual URL in this example will be encoded as "/clients?ids%5b%5d=1&ids%5b%5d=2&ids%5b%5d=3" as the "[" and "]" characters are not allowed in URLs. Most of the time you don't have to worry about this because the browser will encode it for you, and Rails will decode it automatically, but if you ever find yourself having to send those requests to the server manually you should keep this in mind.
The value of `params[:ids]` will now be `["1", "2", "3"]`. Note that parameter values are always strings; Rails makes no attempt to guess or cast the type.
-NOTE: Values such as `[]`, `[nil]` or `[nil, nil, ...]` in `params` are replaced
-with `nil` for security reasons by default. See [Security Guide](security.html#unsafe-query-generation)
+NOTE: Values such as `[nil]` or `[nil, nil, ...]` in `params` are replaced
+with `[]` for security reasons by default. See [Security Guide](security.html#unsafe-query-generation)
for more information.
-To send a hash you include the key name inside the brackets:
+To send a hash, you include the key name inside the brackets:
```html
<form accept-charset="UTF-8" action="/clients" method="post">
@@ -129,11 +131,11 @@ To send a hash you include the key name inside the brackets:
When this form is submitted, the value of `params[:client]` will be `{ "name" => "Acme", "phone" => "12345", "address" => { "postcode" => "12345", "city" => "Carrot City" } }`. Note the nested hash in `params[:client][:address]`.
-Note that the `params` hash is actually an instance of `ActiveSupport::HashWithIndifferentAccess`, which acts like a hash but lets you use symbols and strings interchangeably as keys.
+The `params` object acts like a Hash, but lets you use symbols and strings interchangeably as keys.
### JSON parameters
-If you're writing a web service application, you might find yourself more comfortable accepting parameters in JSON format. If the "Content-Type" header of your request is set to "application/json", Rails will automatically convert your parameters into the `params` hash, which you can access as you would normally.
+If you're writing a web service application, you might find yourself more comfortable accepting parameters in JSON format. If the "Content-Type" header of your request is set to "application/json", Rails will automatically load your parameters into the `params` hash, which you can access as you would normally.
So for example, if you are sending this JSON content:
@@ -141,15 +143,15 @@ So for example, if you are sending this JSON content:
{ "company": { "name": "acme", "address": "123 Carrot Street" } }
```
-You'll get `params[:company]` as `{ "name" => "acme", "address" => "123 Carrot Street" }`.
+Your controller will receive `params[:company]` as `{ "name" => "acme", "address" => "123 Carrot Street" }`.
-Also, if you've turned on `config.wrap_parameters` in your initializer or calling `wrap_parameters` in your controller, you can safely omit the root element in the JSON parameter. The parameters will be cloned and wrapped in the key according to your controller's name by default. So the above parameter can be written as:
+Also, if you've turned on `config.wrap_parameters` in your initializer or called `wrap_parameters` in your controller, you can safely omit the root element in the JSON parameter. In this case, the parameters will be cloned and wrapped with a key chosen based on your controller's name. So the above JSON POST can be written as:
```json
{ "name": "acme", "address": "123 Carrot Street" }
```
-And assume that you're sending the data to `CompaniesController`, it would then be wrapped in `:company` key like this:
+And, assuming that you're sending the data to `CompaniesController`, it would then be wrapped within the `:company` key like this:
```ruby
{ name: "acme", address: "123 Carrot Street", company: { name: "acme", address: "123 Carrot Street" } }
@@ -157,17 +159,17 @@ And assume that you're sending the data to `CompaniesController`, it would then
You can customize the name of the key or specific parameters you want to wrap by consulting the [API documentation](http://api.rubyonrails.org/classes/ActionController/ParamsWrapper.html)
-NOTE: Support for parsing XML parameters has been extracted into a gem named `actionpack-xml_parser`
+NOTE: Support for parsing XML parameters has been extracted into a gem named `actionpack-xml_parser`.
### Routing Parameters
-The `params` hash will always contain the `:controller` and `:action` keys, but you should use the methods `controller_name` and `action_name` instead to access these values. Any other parameters defined by the routing, such as `:id` will also be available. As an example, consider a listing of clients where the list can show either active or inactive clients. We can add a route which captures the `:status` parameter in a "pretty" URL:
+The `params` hash will always contain the `:controller` and `:action` keys, but you should use the methods `controller_name` and `action_name` instead to access these values. Any other parameters defined by the routing, such as `:id`, will also be available. As an example, consider a listing of clients where the list can show either active or inactive clients. We can add a route which captures the `:status` parameter in a "pretty" URL:
```ruby
get '/clients/:status' => 'clients#index', foo: 'bar'
```
-In this case, when a user opens the URL `/clients/active`, `params[:status]` will be set to "active". When this route is used, `params[:foo]` will also be set to "bar" just like it was passed in the query string. In the same way `params[:action]` will contain "index".
+In this case, when a user opens the URL `/clients/active`, `params[:status]` will be set to "active". When this route is used, `params[:foo]` will also be set to "bar", as if it were passed in the query string. Your controller will also receive `params[:action]` as "index" and `params[:controller]` as "clients".
### `default_url_options`
@@ -181,21 +183,21 @@ class ApplicationController < ActionController::Base
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 in `url_for` calls.
+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 would be used for all URL generation. The method can also be defined in one specific controller, in which case it only affects URLs generated there.
+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.
### Strong Parameters
With strong parameters, Action Controller parameters are forbidden to
be used in Active Model mass assignments until they have been
-whitelisted. This means you'll have to make a conscious choice about
-which attributes to allow for mass updating and thus prevent
-accidentally exposing that which shouldn't be exposed.
+whitelisted. This means that you'll have to make a conscious decision about
+which attributes to allow for mass update. This is a better security
+practice to help prevent accidentally allowing users to update sensitive
+model attributes.
-In addition, parameters can be marked as required and flow through a
-predefined raise/rescue flow to end up as a 400 Bad Request with no
-effort.
+In addition, parameters can be marked as required and will flow through a
+predefined raise/rescue flow to end up as a 400 Bad Request.
```ruby
class PeopleController < ActionController::Base
@@ -237,17 +239,17 @@ params.permit(:id)
```
the key `:id` will pass the whitelisting if it appears in `params` and
-it has a permitted scalar value associated. Otherwise the key is going
+it has a permitted scalar value associated. Otherwise, the key is going
to be filtered out, so arrays, hashes, or any other objects cannot be
injected.
The permitted scalar types are `String`, `Symbol`, `NilClass`,
`Numeric`, `TrueClass`, `FalseClass`, `Date`, `Time`, `DateTime`,
-`StringIO`, `IO`, `ActionDispatch::Http::UploadedFile` and
+`StringIO`, `IO`, `ActionDispatch::Http::UploadedFile`, and
`Rack::Test::UploadedFile`.
To declare that the value in `params` must be an array of permitted
-scalar values map the key to an empty array:
+scalar values, map the key to an empty array:
```ruby
params.permit(id: [])
@@ -260,14 +262,13 @@ used:
params.require(:log_entry).permit!
```
-This will mark the `:log_entry` parameters hash and any sub-hash of it
-permitted. Extreme care should be taken when using `permit!` as it
-will allow all current and future model attributes to be
-mass-assigned.
+This will mark the `:log_entry` parameters hash and any sub-hash of it as
+permitted. Extreme care should be taken when using `permit!`, as it
+will allow all current and future model attributes to be mass-assigned.
#### Nested Parameters
-You can also use permit on nested parameters, like:
+You can also use `permit` on nested parameters, like:
```ruby
params.permit(:name, { emails: [] },
@@ -275,19 +276,19 @@ params.permit(:name, { emails: [] },
{ family: [ :name ], hobbies: [] }])
```
-This declaration whitelists the `name`, `emails` and `friends`
+This declaration whitelists the `name`, `emails`, and `friends`
attributes. It is expected that `emails` will be an array of permitted
-scalar values and that `friends` will be an array of resources with
-specific attributes : they should have a `name` attribute (any
+scalar values, and that `friends` will be an array of resources with
+specific attributes: they should have a `name` attribute (any
permitted scalar values allowed), a `hobbies` attribute as an array of
permitted scalar values, and a `family` attribute which is restricted
-to having a `name` (any permitted scalar values allowed, too).
+to having a `name` (any permitted scalar values allowed here, too).
#### More Examples
-You want to also use the permitted attributes in the `new`
+You may want to also use the permitted attributes in your `new`
action. This raises the problem that you can't use `require` on the
-root key because normally it does not exist when calling `new`:
+root key because, normally, it does not exist when calling `new`:
```ruby
# using `fetch` you can supply a default and use
@@ -295,8 +296,8 @@ root key because normally it does not exist when calling `new`:
params.fetch(:blog, {}).permit(:title, :author)
```
-`accepts_nested_attributes_for` allows you to update and destroy
-associated records. This is based on the `id` and `_destroy`
+The model class method `accepts_nested_attributes_for` allows you to
+update and destroy associated records. This is based on the `id` and `_destroy`
parameters:
```ruby
@@ -304,7 +305,7 @@ parameters:
params.require(:author).permit(:name, books_attributes: [:title, :id, :_destroy])
```
-Hashes with integer keys are treated differently and you can declare
+Hashes with integer keys are treated differently, and you can declare
the attributes as if they were direct children. You get these kinds of
parameters when you use `accepts_nested_attributes_for` in combination
with a `has_many` association:
@@ -321,13 +322,13 @@ params.require(:book).permit(:title, chapters_attributes: [:title])
#### Outside the Scope of Strong Parameters
The strong parameter API was designed with the most common use cases
-in mind. It is not meant as a silver bullet to handle all your
-whitelisting problems. However you can easily mix the API with your
+in mind. It is not meant as a silver bullet to handle all of your
+whitelisting problems. However, you can easily mix the API with your
own code to adapt to your situation.
Imagine a scenario where you have parameters representing a product
name and a hash of arbitrary data associated with that product, and
-you want to whitelist the product name attribute but also the whole
+you want to whitelist the product name attribute and also the whole
data hash. The strong parameters API doesn't let you directly
whitelist the whole of a nested hash with any keys, but you can use
the keys of your nested hash to declare what to whitelist:
@@ -670,7 +671,7 @@ 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
@@ -703,9 +704,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:
@@ -735,7 +736,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, and the `require_login` filter from above could be rewritten to use a block:
```ruby
class ApplicationController < ActionController::Base
@@ -992,6 +993,11 @@ you would like in a response object. The `ActionController::Live` module allows
you to create a persistent connection with a browser. Using this module, you will
be able to send arbitrary data to the browser at specific points in time.
+NOTE: The default Rails server (WEBrick) is a buffering web server and does not
+support streaming. In order to use this feature, you'll need to use a non buffering
+server like [Puma](http://puma.io), [Rainbows](http://rainbows.bogomips.org)
+or [Passenger](https://www.phusionpassenger.com).
+
#### Incorporating Live Streaming
Including `ActionController::Live` inside of your controller class will provide
@@ -1164,67 +1170,10 @@ class ClientsController < ApplicationController
end
```
-WARNING: You shouldn't do `rescue_from Exception` or `rescue_from StandardError` unless you have a particular reason as it will cause serious side-effects (e.g. you won't be able to see exception details and tracebacks during development). If you would like to dynamically generate error pages, see [Custom errors page](#custom-errors-page).
+WARNING: You shouldn't do `rescue_from Exception` or `rescue_from StandardError` unless you have a particular reason as it will cause serious side-effects (e.g. you won't be able to see exception details and tracebacks during development).
NOTE: Certain exceptions are only rescuable from the `ApplicationController` class, as they are raised before the controller gets initialized and the action gets executed. See Pratik Naik's [article](http://m.onkey.org/2008/7/20/rescue-from-dispatching) on the subject for more information.
-
-### Custom errors page
-
-You can customize the layout of your error handling using controllers and views.
-First define your app own routes to display the errors page.
-
-* `config/application.rb`
-
- ```ruby
- config.exceptions_app = self.routes
- ```
-
-* `config/routes.rb`
-
- ```ruby
- match '/404', via: :all, to: 'errors#not_found'
- match '/422', via: :all, to: 'errors#unprocessable_entity'
- match '/500', via: :all, to: 'errors#server_error'
- ```
-
-Create the controller and views.
-
-* `app/controllers/errors_controller.rb`
-
- ```ruby
- class ErrorsController < ActionController::Base
- layout 'error'
-
- def not_found
- render status: :not_found
- end
-
- def unprocessable_entity
- render status: :unprocessable_entity
- end
-
- def server_error
- render status: :server_error
- end
- end
- ```
-
-* `app/views`
-
- ```
- errors/
- not_found.html.erb
- unprocessable_entity.html.erb
- server_error.html.erb
- layouts/
- error.html.erb
- ```
-
-Do not forget to set the correct status code on the controller as shown before.
-
-WARNING: You should avoid using the database or any complex operations because the user is already on the error page. Generating another error while on an error page could cause issues like presenting an empty page for the users.
-
Force HTTPS protocol
--------------------
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index f6c974c87a..73b240ff2c 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Action Mailer Basics
====================
@@ -35,10 +37,26 @@ views.
```bash
$ bin/rails generate mailer UserMailer
create app/mailers/user_mailer.rb
+create app/mailers/application_mailer.rb
invoke erb
create app/views/user_mailer
+create app/views/layouts/mailer.text.erb
+create app/views/layouts/mailer.html.erb
invoke test_unit
create test/mailers/user_mailer_test.rb
+create test/mailers/previews/user_mailer_preview.rb
+```
+
+```ruby
+# app/mailers/application_mailer.rb
+class ApplicationMailer < ActionMailer::Base
+ default from: "from@example.com"
+ layout 'mailer'
+end
+
+# app/mailers/user_mailer.rb
+class UserMailer < ApplicationMailer
+end
```
As you can see, you can generate mailers just like you use other generators with
@@ -63,8 +81,7 @@ delivered via email.
`app/mailers/user_mailer.rb` contains an empty mailer:
```ruby
-class UserMailer < ActionMailer::Base
- default from: 'from@example.com'
+class UserMailer < ApplicationMailer
end
```
@@ -72,7 +89,7 @@ Let's add a method called `welcome_email`, that will send an email to the user's
registered email address:
```ruby
-class UserMailer < ActionMailer::Base
+class UserMailer < ApplicationMailer
default from: 'notifications@example.com'
def welcome_email(user)
@@ -348,7 +365,7 @@ for the HTML version and `welcome_email.text.erb` for the plain text version.
To change the default mailer view for your action you do something like:
```ruby
-class UserMailer < ActionMailer::Base
+class UserMailer < ApplicationMailer
default from: 'notifications@example.com'
def welcome_email(user)
@@ -370,7 +387,7 @@ If you want more flexibility you can also pass a block and render specific
templates or even render inline or text without using a template file:
```ruby
-class UserMailer < ActionMailer::Base
+class UserMailer < ApplicationMailer
default from: 'notifications@example.com'
def welcome_email(user)
@@ -400,7 +417,7 @@ layout.
In order to use a different file, call `layout` in your mailer:
```ruby
-class UserMailer < ActionMailer::Base
+class UserMailer < ApplicationMailer
layout 'awesome' # use awesome.(html|text).erb as the layout
end
```
@@ -412,7 +429,7 @@ You can also pass in a `layout: 'layout_name'` option to the render call inside
the format block to specify different layouts for different formats:
```ruby
-class UserMailer < ActionMailer::Base
+class UserMailer < ApplicationMailer
def welcome_email(user)
mail(to: user.email) do |format|
format.html { render layout: 'my_layout' }
@@ -425,6 +442,39 @@ end
Will render the HTML part using the `my_layout.html.erb` file and the text part
with the usual `user_mailer.text.erb` file if it exists.
+### Previewing Emails
+
+Action Mailer previews provide a way to see how emails look by visiting a
+special URL that renders them. In the above example, the preview class for
+`UserMailer` should be named `UserMailerPreview` and located in
+`test/mailers/previews/user_mailer_preview.rb`. To see the preview of
+`welcome_email`, implement a method that has the same name and call
+`UserMailer.welcome_email`:
+
+```ruby
+class UserMailerPreview < ActionMailer::Preview
+ def welcome_email
+ UserMailer.welcome_email(User.first)
+ end
+end
+```
+
+Then the preview will be available in <http://localhost:3000/rails/mailers/user_mailer/welcome_email>.
+
+If you change something in `app/views/user_mailer/welcome_email.html.erb`
+or the mailer itself, it'll automatically reload and render it so you can
+visually see the new style instantly. A list of previews are also available
+in <http://localhost:3000/rails/mailers>.
+
+By default, these preview classes live in `test/mailers/previews`.
+This can be configured using the `preview_path` option. For example, if you
+want to change it to `lib/mailer_previews`, you can configure it in
+`config/application.rb`:
+
+```ruby
+config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews"
+```
+
### Generating URLs in Action Mailer Views
Unlike controllers, the mailer instance doesn't have any context about the
@@ -455,16 +505,7 @@ By using the full URL, your links will now work in your emails.
#### generating URLs with `url_for`
-You need to pass the `only_path: false` option when using `url_for`. This will
-ensure that absolute URLs are generated because the `url_for` view helper will,
-by default, generate relative URLs when a `:host` option isn't explicitly
-provided.
-
-```erb
-<%= url_for(controller: 'welcome',
- action: 'greeting',
- only_path: false) %>
-```
+`url_for` generate full URL by default in templates.
If you did not configure the `:host` option globally make sure to pass it to
`url_for`.
@@ -476,9 +517,6 @@ If you did not configure the `:host` option globally make sure to pass it to
action: 'greeting') %>
```
-NOTE: When you explicitly pass the `:host` Rails will always generate absolute
-URLs, so there is no need to pass `only_path: false`.
-
#### generating URLs with named routes
Email clients have no web context and so paths have no base URL to form complete
@@ -510,7 +548,7 @@ while delivering emails, you can do this using `delivery_method_options` in the
mailer action.
```ruby
-class UserMailer < ActionMailer::Base
+class UserMailer < ApplicationMailer
def welcome_email(user, company)
@user = user
@url = user_url(@user)
@@ -532,7 +570,7 @@ option. In such cases don't forget to add the `:content_type` option. Rails
will default to `text/plain` otherwise.
```ruby
-class UserMailer < ActionMailer::Base
+class UserMailer < ApplicationMailer
def welcome_email(user, email_body)
mail(to: user.email,
body: email_body,
@@ -562,7 +600,7 @@ mailer, and pass the email object to the mailer `receive` instance
method. Here's an example:
```ruby
-class UserMailer < ActionMailer::Base
+class UserMailer < ApplicationMailer
def receive(email)
page = Page.find_by(address: email.to.first)
page.emails.create(
@@ -598,7 +636,7 @@ Action Mailer allows for you to specify a `before_action`, `after_action` and
using instance variables set in your mailer action.
```ruby
-class UserMailer < ActionMailer::Base
+class UserMailer < ApplicationMailer
after_action :set_delivery_options,
:prevent_delivery_to_guests,
:set_business_headers
@@ -728,7 +766,9 @@ Mailer framework. You can do this in an initializer file
`config/initializers/sandbox_email_interceptor.rb`
```ruby
-ActionMailer::Base.register_interceptor(SandboxEmailInterceptor) if Rails.env.staging?
+if Rails.env.staging?
+ ActionMailer::Base.register_interceptor(SandboxEmailInterceptor)
+end
```
NOTE: The example above uses a custom environment called "staging" for a
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index 683e633668..02ef32d66e 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Action View Overview
====================
@@ -7,7 +9,6 @@ After reading this guide, you will know:
* How best to use templates, partials, and layouts.
* What helpers are provided by Action View and how to make your own.
* How to use localized views.
-* How to use Action View outside of Rails.
--------------------------------------------------------------------------------
@@ -181,7 +182,7 @@ One way to use partials is to treat them as the equivalent of subroutines; a way
<p>Here are a few of our fine products:</p>
<% @products.each do |product| %>
- <%= render partial: "product", locals: {product: product} %>
+ <%= render partial: "product", locals: { product: product } %>
<% end %>
<%= render "shared/footer" %>
@@ -189,6 +190,22 @@ One way to use partials is to treat them as the equivalent of subroutines; a way
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.
+#### `render` without `partial` and `locals` options
+
+In the above example, `render` takes 2 options: `partial` and `locals`. But if
+these are the only options you want to pass, you can skip using these options.
+For example, instead of:
+
+```erb
+<%= render partial: "product", locals: { product: @product } %>
+```
+
+You can also do:
+
+```erb
+<%= render "product", product: @product %>
+```
+
#### The `as` and `object` options
By default `ActionView::Partials::PartialRenderer` has its object in a local variable with the same name as the template. So, given:
@@ -200,7 +217,7 @@ By default `ActionView::Partials::PartialRenderer` has its object in a local var
within product we'll get `@product` in the local variable `product`, as if we had written:
```erb
-<%= render partial: "product", locals: {product: @product} %>
+<%= render partial: "product", locals: { product: @product } %>
```
With the `as` option we can specify a different name for the local variable. For example, if we wanted it to be `item` instead of `product` we would do:
@@ -214,7 +231,7 @@ The `object` option can be used to directly specify which object is rendered int
For example, instead of:
```erb
-<%= render partial: "product", locals: {product: @item} %>
+<%= render partial: "product", locals: { product: @item } %>
```
we would do:
@@ -287,7 +304,7 @@ In the `show` template, we'll render the `_article` partial wrapped in the `box`
**articles/show.html.erb**
```erb
-<%= render partial: 'article', layout: 'box', locals: {article: @article} %>
+<%= render partial: 'article', layout: 'box', locals: { article: @article } %>
```
The `box` layout simply wraps the `_article` partial in a `div`:
@@ -327,7 +344,7 @@ You can also render a block of code within a partial layout instead of calling `
**articles/show.html.erb**
```html+erb
-<% render(layout: 'box', locals: {article: @article}) do %>
+<% render(layout: 'box', locals: { article: @article }) do %>
<%= div_for(article) do %>
<p><%= article.body %></p>
<% end %>
@@ -348,83 +365,6 @@ WIP: Not all the helpers are listed here. For a full list see the [API documenta
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.
-### RecordTagHelper
-
-This module provides methods for generating container tags, such as `div`, for your record. This is the recommended way of creating a container for render your Active Record object, as it adds an appropriate class and id attributes to that container. You can then refer to those containers easily by following the convention, instead of having to think about which class or id attribute you should use.
-
-#### content_tag_for
-
-Renders a container tag that relates to your Active Record Object.
-
-For example, given `@article` is the object of `Article` class, you can do:
-
-```html+erb
-<%= content_tag_for(:tr, @article) do %>
- <td><%= @article.title %></td>
-<% end %>
-```
-
-This will generate this HTML output:
-
-```html
-<tr id="article_1234" class="article">
- <td>Hello World!</td>
-</tr>
-```
-
-You can also supply HTML attributes as an additional option hash. For example:
-
-```html+erb
-<%= content_tag_for(:tr, @article, class: "frontpage") do %>
- <td><%= @article.title %></td>
-<% end %>
-```
-
-Will generate this HTML output:
-
-```html
-<tr id="article_1234" class="article frontpage">
- <td>Hello World!</td>
-</tr>
-```
-
-You can pass a collection of Active Record objects. This method will loop through your objects and create a container for each of them. For example, given `@articles` is an array of two `Article` objects:
-
-```html+erb
-<%= content_tag_for(:tr, @articles) do |article| %>
- <td><%= article.title %></td>
-<% end %>
-```
-
-Will generate this HTML output:
-
-```html
-<tr id="article_1234" class="article">
- <td>Hello World!</td>
-</tr>
-<tr id="article_1235" class="article">
- <td>Ruby on Rails Rocks!</td>
-</tr>
-```
-
-#### div_for
-
-This is actually a convenient method which calls `content_tag_for` internally with `:div` as the tag name. You can pass either an Active Record object or a collection of objects. For example:
-
-```html+erb
-<%= div_for(@article, class: "frontpage") do %>
- <td><%= @article.title %></td>
-<% end %>
-```
-
-Will generate this HTML output:
-
-```html
-<div id="article_1234" class="article frontpage">
- <td>Hello World!</td>
-</div>
-```
-
### AssetTagHelper
This module provides methods for generating HTML that links views to assets such as images, JavaScript files, stylesheets, and feeds.
@@ -467,8 +407,8 @@ stylesheet_link_tag :monkey # =>
Returns a link tag that browsers and feed readers can use to auto-detect an RSS or Atom feed.
```ruby
-auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {title: "RSS Feed"}) # =>
- <link rel="alternate" type="application/rss+xml" title="RSS Feed" href="http://www.example.com/feed" />
+auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", { title: "RSS Feed" }) # =>
+ <link rel="alternate" type="application/rss+xml" title="RSS Feed" href="http://www.example.com/feed.rss" />
```
#### image_path
@@ -849,7 +789,7 @@ time_select("order", "submitted")
Returns a `pre` tag that has object dumped by YAML. This creates a very readable way to inspect an object.
```ruby
-my_hash = {'first' => 1, 'second' => 'two', 'third' => [1,2,3]}
+my_hash = { 'first' => 1, 'second' => 'two', 'third' => [1,2,3] }
debug(my_hash)
```
@@ -874,7 +814,7 @@ The core method of this helper, form_for, gives you the ability to create a form
```html+erb
# Note: a @person variable will have been created in the controller (e.g. @person = Person.new)
-<%= form_for @person, url: {action: "create"} do |f| %>
+<%= form_for @person, url: { action: "create" } do |f| %>
<%= f.text_field :first_name %>
<%= f.text_field :last_name %>
<%= submit_tag 'Create' %>
@@ -894,7 +834,7 @@ The HTML generated for this would be:
The params object created when this form is submitted would look like:
```ruby
-{"action" => "create", "controller" => "people", "person" => {"first_name" => "William", "last_name" => "Smith"}}
+{ "action" => "create", "controller" => "people", "person" => { "first_name" => "William", "last_name" => "Smith" } }
```
The params hash has a nested person value, which can therefore be accessed with params[:person] in the controller.
@@ -915,7 +855,7 @@ check_box("article", "validated")
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| %>
+<%= form_for @person, url: { action: "update" } do |person_form| %>
First name: <%= person_form.text_field :first_name %>
Last name : <%= person_form.text_field :last_name %>
@@ -1050,7 +990,7 @@ end
Sample usage (selecting the associated Author for an instance of Article, `@article`):
```ruby
-collection_select(:article, :author_id, Author.all, :id, :name_with_initial, {prompt: true})
+collection_select(:article, :author_id, Author.all, :id, :name_with_initial, { prompt: true })
```
If `@article.author_id` is 1, this would return:
@@ -1222,7 +1162,7 @@ Create a select tag and a series of contained option tags for the provided objec
Example:
```ruby
-select("article", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {include_blank: true})
+select("article", "person_id", Person.all.collect { |p| [ p.name, p.id ] }, { include_blank: true })
```
If `@article.person_id` is 1, this would become:
@@ -1285,7 +1225,7 @@ Creates a field set for grouping HTML form elements.
Creates a file upload field.
```html+erb
-<%= form_tag({action:"post"}, multipart: true) do %>
+<%= form_tag({ action: "post" }, multipart: true) do %>
<label for="file">File to Upload</label> <%= file_field_tag "file" %>
<%= submit_tag %>
<% end %>
@@ -1600,7 +1540,7 @@ details can be found in the [Rails Security Guide](security.html#cross-site-requ
Localized Views
---------------
-Action View has the ability render different templates depending on the current locale.
+Action View has the ability to render different templates depending on the current locale.
For example, suppose you have a `ArticlesController` with a show action. By default, calling this action will render `app/views/articles/show.html.erb`. But if you set `I18n.locale = :de`, then `app/views/articles/show.de.html.erb` will be rendered instead. If the localized template isn't present, the undecorated version will be used. This means you're not required to provide localized views for all cases, but they will be preferred and used if available.
diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md
index ca851371a9..953c29719d 100644
--- a/guides/source/active_job_basics.md
+++ b/guides/source/active_job_basics.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Active Job Basics
=================
@@ -23,7 +25,7 @@ clean-ups, to billing charges, to mailings. Anything that can be chopped up
into small units of work and run in parallel, really.
-The Purpose of the Active Job
+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
@@ -56,9 +58,6 @@ You can also create a job that will run on a specific queue:
$ bin/rails generate job guests_cleanup --queue urgent
```
-As you can see, you can generate jobs just like you use other generators with
-Rails.
-
If you don't want to use a generator, you could create your own file inside of
`app/jobs`, just make sure that it inherits from `ActiveJob::Base`.
@@ -79,7 +78,8 @@ end
Enqueue a job like so:
```ruby
-# Enqueue a job to be performed as soon the queueing system is free.
+# Enqueue a job to be performed as soon the queueing system is
+# free.
MyJob.perform_later record
```
@@ -107,14 +107,20 @@ Active Job has built-in adapters for multiple queueing 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).
-### Changing the Backend
+### Setting the Backend
-You can easily change your queueing backend:
+You can easily set your queueing backend:
```ruby
-# be sure to have the adapter gem in your Gemfile and follow
-# the adapter specific installation and deployment instructions
-config.active_job.queue_adapter = :sidekiq
+# config/application.rb
+module YourApp
+ class Application < Rails::Application
+ # 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
+ end
+end
```
@@ -149,11 +155,11 @@ class GuestsCleanupJob < ActiveJob::Base
end
# Now your job will run on queue production_low_priority on your
-# production environment and on beta_low_priority on your beta
-# environment
+# production environment and on staging_low_priority
+# on your staging environment
```
-The default queue name prefix delimiter is '_'. This can be changed by setting
+The default queue name prefix delimiter is '\_'. This can be changed by setting
`config.active_job.queue_name_delimiter` in `application.rb`:
```ruby
@@ -172,8 +178,8 @@ class GuestsCleanupJob < ActiveJob::Base
end
# Now your job will run on queue production.low_priority on your
-# production environment and on staging.low_priority on your staging
-# environment
+# production environment and on staging.low_priority
+# on your staging environment
```
If you want more control on what queue a job will be run you can pass a `:queue`
@@ -199,7 +205,7 @@ class ProcessVideoJob < ActiveJob::Base
end
def perform(video)
- # do process video
+ # Do process video
end
end
@@ -213,8 +219,8 @@ backends you need to specify the queues to listen to.
Callbacks
---------
-Active Job provides hooks during the lifecycle of a job. Callbacks allow you to
-trigger logic during the lifecycle of a job.
+Active Job provides hooks during the life cycle of a job. Callbacks allow you to
+trigger logic during the life cycle of a job.
### Available callbacks
@@ -232,13 +238,13 @@ class GuestsCleanupJob < ActiveJob::Base
queue_as :default
before_enqueue do |job|
- # do something with the job instance
+ # Do something with the job instance
end
around_perform do |job, block|
- # do something before perform
+ # Do something before perform
block.call
- # do something after perform
+ # Do something after perform
end
def perform
@@ -248,7 +254,7 @@ end
```
-ActionMailer
+Action Mailer
------------
One of the most common jobs in a modern web application is sending emails outside
@@ -291,7 +297,7 @@ end
```
This works with any class that mixes in `GlobalID::Identification`, which
-by default has been mixed into Active Model classes.
+by default has been mixed into Active Record classes.
Exceptions
@@ -301,12 +307,11 @@ Active Job provides a way to catch exceptions raised during the execution of the
job:
```ruby
-
class GuestsCleanupJob < ActiveJob::Base
queue_as :default
rescue_from(ActiveRecord::RecordNotFound) do |exception|
- # do something with the exception
+ # Do something with the exception
end
def perform
diff --git a/guides/source/active_model_basics.md b/guides/source/active_model_basics.md
index a520b91a4d..4b2bfaee2f 100644
--- a/guides/source/active_model_basics.md
+++ b/guides/source/active_model_basics.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Active Model Basics
===================
@@ -394,7 +396,7 @@ class Person
end
```
-With the `to_xml` you have a XML representing the model.
+With the `to_xml` you have an XML representing the model.
```ruby
person = Person.new
@@ -403,7 +405,7 @@ person.name = "Bob"
person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<person>\n <name>Bob</name>\n</person>\n"
```
-From a XML string you define the attributes of the model.
+From an XML string you define the attributes of the model.
You need to have the `attributes=` method defined on your class:
```ruby
diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md
index bd074d0055..6551ba0389 100644
--- a/guides/source/active_record_basics.md
+++ b/guides/source/active_record_basics.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Active Record Basics
====================
@@ -18,7 +20,7 @@ After reading this guide, you will know:
What is Active Record?
----------------------
-Active Record is the M in [MVC](getting_started.html#the-mvc-architecture) - the
+Active Record is the M in [MVC](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) - the
model - which is the layer of the system responsible for representing business
data and logic. Active Record facilitates the creation and use of business
objects whose data requires persistent storage to a database. It is an
@@ -36,7 +38,7 @@ object on how to write to and read from the database.
### Object Relational Mapping
-Object-Relational Mapping, commonly referred to as its abbreviation ORM, is
+Object Relational Mapping, commonly referred to as its abbreviation ORM, is
a technique that connects the rich objects of an application to tables in
a relational database management system. Using ORM, the properties and
relationships of the objects in an application can be easily stored and
@@ -60,7 +62,7 @@ Convention over Configuration in Active Record
When writing applications using other programming languages or frameworks, it
may be necessary to write a lot of configuration code. This is particularly true
for ORM frameworks in general. However, if you follow the conventions adopted by
-Rails, you'll need to write very little configuration (in some case no
+Rails, you'll need to write very little configuration (in some cases no
configuration at all) when creating Active Record models. The idea is that if
you configure your applications in the very same way most of the time then this
should be the default way. Thus, explicit configuration would be needed
@@ -120,7 +122,7 @@ to Active Record instances:
* `(association_name)_type` - Stores the type for
[polymorphic associations](association_basics.html#polymorphic-associations).
* `(table_name)_count` - Used to cache the number of belonging objects on
- associations. For example, a `comments_count` column in a `Articles` class that
+ associations. For example, a `comments_count` column in an `Article` class that
has many instances of `Comment` will cache the number of existent comments
for each article.
@@ -171,18 +173,18 @@ name that should be used:
```ruby
class Product < ActiveRecord::Base
- self.table_name = "PRODUCT"
+ self.table_name = "my_products"
end
```
If you do so, you will have to define manually the class name that is hosting
-the fixtures (class_name.yml) using the `set_fixture_class` method in your test
+the fixtures (my_products.yml) using the `set_fixture_class` method in your test
definition:
```ruby
-class FunnyJoke < ActiveSupport::TestCase
- set_fixture_class funny_jokes: Joke
- fixtures :funny_jokes
+class ProductTest < ActiveSupport::TestCase
+ set_fixture_class my_products: Product
+ fixtures :my_products
...
end
```
@@ -358,7 +360,7 @@ class CreatePublications < ActiveRecord::Migration
t.string :publisher_type
t.boolean :single_issue
- t.timestamps
+ t.timestamps null: false
end
add_index :publications, :publication_type_id
end
diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md
index 9c7e60cbb0..13989a3b33 100644
--- a/guides/source/active_record_callbacks.md
+++ b/guides/source/active_record_callbacks.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Active Record Callbacks
=======================
@@ -66,7 +68,7 @@ class User < ActiveRecord::Base
protected
def normalize_name
- self.name = self.name.downcase.titleize
+ self.name = name.downcase.titleize
end
def set_location
diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md
index c8a31fe7b8..7a994cc5de 100644
--- a/guides/source/active_record_migrations.md
+++ b/guides/source/active_record_migrations.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Active Record Migrations
========================
@@ -39,7 +41,7 @@ class CreateProducts < ActiveRecord::Migration
t.string :name
t.text :description
- t.timestamps
+ t.timestamps null: false
end
end
end
@@ -239,7 +241,7 @@ generates
```ruby
class AddUserRefToProducts < ActiveRecord::Migration
def change
- add_reference :products, :user, index: true
+ add_reference :products, :user, index: true, foreign_key: true
end
end
```
@@ -285,7 +287,7 @@ class CreateProducts < ActiveRecord::Migration
t.string :name
t.text :description
- t.timestamps
+ t.timestamps null: false
end
end
end
@@ -355,7 +357,7 @@ will append `ENGINE=BLACKHOLE` to the SQL statement used to create the table
### Creating a Join Table
-Migration method `create_join_table` creates a HABTM join table. A typical use
+Migration method `create_join_table` creates an HABTM join table. A typical use
would be:
```ruby
@@ -423,7 +425,7 @@ change_column :products, :part_number, :text
This changes the column `part_number` on products table to be a `:text` field.
Besides `change_column`, the `change_column_null` and `change_column_default`
-methods are used specifically to change the null and default values of a
+methods are used specifically to change a not null constraint and default values of a
column.
```ruby
@@ -452,6 +454,8 @@ number of digits after the decimal point.
are using a dynamic value (such as a date), the default will only be calculated
the first time (i.e. on the date the migration is applied).
* `index` Adds an index for the column.
+* `required` Adds `required: true` for `belongs_to` associations and
+`null: false` to the column in the migration.
Some adapters may support additional options; see the adapter specific API docs
for further information.
@@ -475,7 +479,8 @@ Rails will generate a name for every foreign key starting with
There is a `:name` option to specify a different name if needed.
NOTE: Active Record only supports single column foreign keys. `execute` and
-`structure.sql` are required to use composite foreign keys.
+`structure.sql` are required to use composite foreign keys. See
+[Schema Dumping and You](#schema-dumping-and-you).
Removing a foreign key is easy as well:
@@ -496,7 +501,7 @@ If the helpers provided by Active Record aren't enough you can use the `execute`
method to execute arbitrary SQL:
```ruby
-Product.connection.execute('UPDATE `products` SET `price`=`free` WHERE 1')
+Product.connection.execute("UPDATE products SET price = 'free' WHERE 1=1")
```
For more details and examples of individual methods, check the API documentation.
@@ -534,6 +539,14 @@ definitions:
`change_table` is also reversible, as long as the block does not call `change`,
`change_default` or `remove`.
+`remove_column` is reversible if you supply the column type as the third
+argument. Provide the original column options too, otherwise Rails can't
+recreate the column exactly when rolling back:
+
+```ruby
+remove_column :posts, :slug, :string, null: false, default: '', index: true
+```
+
If you're going to need to use any other methods, you should use `reversible`
or write the `up` and `down` methods instead of using the `change` method.
@@ -691,6 +704,10 @@ of `create_table` and `reversible`, replacing `create_table`
by `drop_table`, and finally replacing `up` by `down` and vice-versa.
This is all taken care of by `revert`.
+NOTE: If you want to add check constraints like in the examples above,
+you will have to use `structure.sql` as dump method. See
+[Schema Dumping and You](#schema-dumping-and-you).
+
Running Migrations
------------------
@@ -824,7 +841,7 @@ class CreateProducts < ActiveRecord::Migration
create_table :products do |t|
t.string :name
t.text :description
- t.timestamps
+ t.timestamps null: false
end
end
@@ -939,10 +956,10 @@ that Active Record supports. This could be very useful if you were to
distribute an application that is able to run against multiple databases.
There is however a trade-off: `db/schema.rb` cannot express database specific
-items such as triggers, or stored procedures. While in a migration you can
-execute custom SQL statements, the schema dumper cannot reconstitute those
-statements from the database. If you are using features like this, then you
-should set the schema format to `:sql`.
+items such as triggers, stored procedures or check constraints. While in a
+migration you can execute custom SQL statements, the schema dumper cannot
+reconstitute those statements from the database. If you are using features like
+this, then you should set the schema format to `:sql`.
Instead of using Active Record's schema dumper, the database's structure will
be dumped using a tool specific to the database (via the `db:structure:dump`
diff --git a/guides/source/active_record_postgresql.md b/guides/source/active_record_postgresql.md
index 6c94218ef6..4d9c1776f4 100644
--- a/guides/source/active_record_postgresql.md
+++ b/guides/source/active_record_postgresql.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Active Record and PostgreSQL
============================
@@ -83,9 +85,12 @@ Book.where("array_length(ratings, 1) >= 3")
* [type definition](http://www.postgresql.org/docs/9.3/static/hstore.html)
+NOTE: you need to enable the `hstore` extension to use hstore.
+
```ruby
# db/migrate/20131009135255_create_profiles.rb
ActiveRecord::Schema.define do
+ enable_extension 'hstore' unless extension_enabled?('hstore')
create_table :profiles do |t|
t.hstore 'settings'
end
@@ -103,11 +108,6 @@ profile.settings # => {"color"=>"blue", "resolution"=>"800x600"}
profile.settings = {"color" => "yellow", "resolution" => "1280x1024"}
profile.save!
-
-## you need to call _will_change! if you are editing the store in place
-profile.settings["color"] = "green"
-profile.settings_will_change!
-profile.save!
```
### JSON
@@ -219,7 +219,7 @@ Currently there is no special support for enumerated types. They are mapped as
normal text columns:
```ruby
-# db/migrate/20131220144913_create_events.rb
+# db/migrate/20131220144913_create_articles.rb
execute <<-SQL
CREATE TYPE article_status AS ENUM ('draft', 'published');
SQL
@@ -281,7 +281,7 @@ end
# Usage
User.create settings: "01010011"
user = User.first
-user.settings # => "(Paris,Champs-Élysées)"
+user.settings # => "01010011"
user.settings = "0xAF"
user.settings # => 10101111
user.save!
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index e1a465c64f..98fb566222 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Active Record Query Interface
=============================
@@ -9,6 +11,7 @@ After reading this guide, you will know:
* How to specify the order, retrieved attributes, grouping, and other properties of the found records.
* How to use eager loading to reduce the number of database queries needed for data retrieval.
* How to use dynamic finders methods.
+* How to use method chaining to use multiple ActiveRecord methods together.
* How to check for the existence of particular records.
* How to perform various calculations on Active Record models.
* How to run EXPLAIN on relations.
@@ -87,7 +90,7 @@ The primary operation of `Model.find(options)` can be summarized as:
* Convert the supplied options to an equivalent SQL query.
* Fire the SQL query and retrieve the corresponding results from the database.
* Instantiate the equivalent Ruby object of the appropriate model for every resulting row.
-* Run `after_find` callbacks, if any.
+* Run `after_find` and then `after_initialize` callbacks, if any.
### Retrieving a Single Object
@@ -254,6 +257,12 @@ It is equivalent to writing:
Client.where(first_name: 'Lifo').take
```
+The SQL equivalent of the above is:
+
+```sql
+SELECT * FROM clients WHERE (clients.first_name = 'Lifo') LIMIT 1
+```
+
The `find_by!` method behaves exactly like `find_by`, except that it will raise `ActiveRecord::RecordNotFound` if no matching record is found. For example:
```ruby
@@ -308,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 `:start`, are available as well.
+Two additional options, `:batch_size` and `:begin_at`, are available as well.
**`:batch_size`**
@@ -320,19 +329,32 @@ User.find_each(batch_size: 5000) do |user|
end
```
-**`:start`**
+**`:begin_at`**
-By default, records are fetched in ascending order of the primary key, which must be an integer. The `:start` option allows you to configure the first ID of the sequence whenever the lowest ID is not the one you need. This would be useful, for example, if you wanted to resume an interrupted batch process, provided you saved the last processed ID as a checkpoint.
+By default, records are fetched in ascending order of the primary key, which must be an integer. The `:begin_at` option allows you to configure the first ID of the sequence whenever the lowest ID is not the one you need. This would be useful, for example, if you wanted to resume an interrupted batch process, provided you saved the last processed ID as a checkpoint.
For example, to send newsletters only to users with the primary key starting from 2000, and to retrieve them in batches of 5000:
```ruby
-User.find_each(start: 2000, batch_size: 5000) do |user|
+User.find_each(begin_at: 2000, batch_size: 5000) do |user|
NewsMailer.weekly(user).deliver_now
end
```
-Another example would be if you wanted multiple workers handling the same processing queue. You could have each worker handle 10000 records by setting the appropriate `:start` option on each worker.
+Another example would be if you wanted multiple workers handling the same processing queue. You could have each worker handle 10000 records by setting the appropriate `:begin_at` option on each worker.
+
+**`:end_at`**
+
+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:
+
+```ruby
+User.find_each(begin_at: 2000, end_at: 10000, batch_size: 5000) do |user|
+ NewsMailer.weekly(user).deliver_now
+end
+```
#### `find_in_batches`
@@ -347,7 +369,7 @@ end
##### Options for `find_in_batches`
-The `find_in_batches` method accepts the same `:batch_size` and `:start` options as `find_each`.
+The `find_in_batches` method accepts the same `:batch_size`, `:begin_at` and `:end_at` options as `find_each`.
Conditions
----------
@@ -633,7 +655,7 @@ GROUP BY status
Having
------
-SQL uses the `HAVING` clause to specify conditions on the `GROUP BY` fields. You can add the `HAVING` clause to the SQL fired by the `Model.find` by adding the `:having` option to the find.
+SQL uses the `HAVING` clause to specify conditions on the `GROUP BY` fields. You can add the `HAVING` clause to the SQL fired by the `Model.find` by adding the `having` method to the find.
For example:
@@ -1125,7 +1147,7 @@ This would generate a query which contains a `LEFT OUTER JOIN` whereas the
If there was no `where` condition, this would generate the normal set of two queries.
NOTE: Using `where` like this will only work when you pass it a Hash. For
-SQL-fragments you need use `references` to force joined tables:
+SQL-fragments you need to use `references` to force joined tables:
```ruby
Article.includes(:comments).where("comments.visible = true").references(:comments)
@@ -1266,7 +1288,7 @@ User.active.where(state: 'finished')
# SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'finished'
```
-If we do want the `last where clause` to win then `Relation#merge` can
+If we do want the last `where` clause to win then `Relation#merge` can
be used.
```ruby
@@ -1321,20 +1343,73 @@ Client.unscoped {
Dynamic Finders
---------------
-For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called `first_name` on your `Client` model for example, you get `find_by_first_name` for free from Active Record. If you have a `locked` field on the `Client` model, you also get `find_by_locked` and methods.
+For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called `first_name` on your `Client` model for example, you get `find_by_first_name` for free from Active Record. If you have a `locked` field on the `Client` model, you also get `find_by_locked` method.
You can specify an exclamation point (`!`) on the end of the dynamic finders to get them to raise an `ActiveRecord::RecordNotFound` error if they do not return any records, like `Client.find_by_name!("Ryan")`
If you want to find both by name and locked, you can chain these finders together by simply typing "`and`" between the fields. For example, `Client.find_by_first_name_and_locked("Ryan", true)`.
+Understanding The Method Chaining
+---------------------------------
+
+The Active Record pattern implements [Method Chaining](http://en.wikipedia.org/wiki/Method_chaining),
+which allow us to use multiple Active Record methods together in a simple and straightforward way.
+
+You can chain methods in a statement when the previous method called returns an
+`ActiveRecord::Relation`, like `all`, `where`, and `joins`. Methods that return
+a single object (see [Retrieving a Single Object Section](#retrieving-a-single-object))
+have to be at the end of the statement.
+
+There are some examples below. This guide won't cover all the possibilities, just a few as examples.
+When an Active Record method is called, the query is not immediately generated and sent to the database,
+this just happens when the data is actually needed. So each example below generates a single query.
+
+### Retrieving filtered data from multiple tables
+
+```ruby
+Person
+ .select('people.id, people.name, comments.text')
+ .joins(:comments)
+ .where('comments.created_at > ?', 1.week.ago)
+```
+
+The result should be something like this:
+
+```sql
+SELECT people.id, people.name, comments.text
+FROM people
+INNER JOIN comments
+ ON comments.person_id = people.id
+WHERE comments.created_at = '2015-01-01'
+```
+
+### Retrieving specific data from multiple tables
+
+```ruby
+Person
+ .select('people.id, people.name, companies.name')
+ .joins(:company)
+ .find_by('people.name' => 'John') # this should be the last
+```
+
+The above should generate:
+
+```sql
+SELECT people.id, people.name, companies.name
+FROM people
+INNER JOIN companies
+ ON companies.person_id = people.id
+WHERE people.name = 'John'
+LIMIT 1
+```
+
+NOTE: Note that if a query matches multiple records, `find_by` will
+fetch only the first one and ignore the others (see the `LIMIT 1`
+statement above).
+
Find or Build a New Object
--------------------------
-NOTE: Some dynamic finders have been deprecated in Rails 4.0 and will be
-removed in Rails 4.1. The best practice is to use Active Record scopes
-instead. You can find the deprecation gem at
-https://github.com/rails/activerecord-deprecated_finders
-
It's common that you need to find a record or create it if it doesn't exist. You can do that with the `find_or_create_by` and `find_or_create_by!` methods.
### `find_or_create_by`
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index 546c0608ee..d251c5c0b1 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Active Record Validations
=========================
@@ -225,8 +227,26 @@ end
```
We'll cover validation errors in greater depth in the [Working with Validation
-Errors](#working-with-validation-errors) section. For now, let's turn to the
-built-in validation helpers that Rails provides by default.
+Errors](#working-with-validation-errors) section.
+
+### `errors.details`
+
+To check which validations failed on an invalid attribute, you can use
+`errors.details[:attribute]`. It returns an array of hashes with an `:error`
+key to get the symbol of the validator:
+
+```ruby
+class Person < ActiveRecord::Base
+ validates :name, presence: true
+end
+
+>> person = Person.new
+>> person.valid?
+>> person.errors.details[:name] #=> [{error: :blank}]
+```
+
+Using `details` with custom validators is covered in the [Working with
+Validation Errors](#working-with-validation-errors) section.
Validation Helpers
------------------
@@ -450,7 +470,7 @@ point number. To specify that only integral numbers are allowed set
If you set `:only_integer` to `true`, then it will use the
```ruby
-/\A[+-]?\d+\Z/
+/\A[+-]?\d+\z/
```
regular expression to validate the attribute's value. Otherwise, it will try to
@@ -487,6 +507,8 @@ constraints to acceptable values:
* `:even` - Specifies the value must be an even number if set to true. The
default error message for this option is _"must be even"_.
+NOTE: By default, `numericality` doesn't allow `nil` values. You can use `allow_nil: true` option to permit it.
+
The default error message is _"is not a number"_.
### `presence`
@@ -584,9 +606,7 @@ This helper validates that the attribute's value is unique right before the
object gets saved. It does not create a uniqueness constraint in the database,
so it may happen that two different database connections create two records
with the same value for a column that you intend to be unique. To avoid that,
-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.
+you must create a unique index on that column in your database.
```ruby
class Account < ActiveRecord::Base
@@ -606,6 +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.
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
@@ -896,8 +917,8 @@ write your own validators or validation methods as you prefer.
### Custom Validators
-Custom validators are classes that extend `ActiveModel::Validator`. These
-classes must implement a `validate` method which takes a record as an argument
+Custom validators are classes that inherit from `ActiveModel::Validator`. These
+classes must implement the `validate` method which takes a record as an argument
and performs the validation on it. The custom validator is called using the
`validates_with` method.
@@ -1034,7 +1055,9 @@ person.errors[:name]
### `errors.add`
-The `add` method lets you manually add messages that are related to particular attributes. You can use the `errors.full_messages` or `errors.to_a` methods to view the messages in the form they might be displayed to a user. Those particular messages get the attribute name prepended (and capitalized). `add` receives the name of the attribute you want to add the message to, and the message itself.
+The `add` method lets you add an error message related to a particular attribute. It takes as arguments the attribute and the error message.
+
+The `errors.full_messages` method (or its equivalent, `errors.to_a`) returns the error messages in a user-friendly format, with the capitalized attribute name prepended to each message, as shown in the examples below.
```ruby
class Person < ActiveRecord::Base
@@ -1052,12 +1075,12 @@ person.errors.full_messages
# => ["Name cannot contain the characters !@#%*()_-+="]
```
-Another way to do this is using `[]=` setter
+An equivalent to `errors#add` is to use `<<` to append a message to the `errors.messages` array for an attribute:
```ruby
class Person < ActiveRecord::Base
def a_method_used_for_validation_purposes
- errors[:name] = "cannot contain the characters !@#%*()_-+="
+ errors.messages[:name] << "cannot contain the characters !@#%*()_-+="
end
end
@@ -1070,6 +1093,43 @@ Another way to do this is using `[]=` setter
# => ["Name cannot contain the characters !@#%*()_-+="]
```
+### `errors.details`
+
+You can specify a validator type to the returned error details hash using the
+`errors.add` method.
+
+```ruby
+class Person < ActiveRecord::Base
+ def a_method_used_for_validation_purposes
+ errors.add(:name, :invalid_characters)
+ end
+end
+
+person = Person.create(name: "!@#")
+
+person.errors.details[:name]
+# => [{error: :invalid_characters}]
+```
+
+To improve the error details to contain the unallowed characters set for instance,
+you can pass additional keys to `errors.add`.
+
+```ruby
+class Person < ActiveRecord::Base
+ def a_method_used_for_validation_purposes
+ errors.add(:name, :invalid_characters, not_allowed: "!@#%*()_-+=")
+ end
+end
+
+person = Person.create(name: "!@#")
+
+person.errors.details[:name]
+# => [{error: :invalid_characters, not_allowed: "!@#%*()_-+="}]
+```
+
+All built in Rails validators populate the details hash with the corresponding
+validator type.
+
### `errors[:base]`
You can add error messages that are related to the object's state as a whole, instead of being related to a specific attribute. You can use this method when you want to say that the object is invalid, no matter the values of its attributes. Since `errors[:base]` is an array, you can simply add a string to it and it will be used as an error message.
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index f6f96b79c6..ff60f95a2c 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Active Support Core Extensions
==============================
@@ -347,7 +349,7 @@ end
we get:
```ruby
-current_user.to_query('user') # => user=357-john-smith
+current_user.to_query('user') # => "user=357-john-smith"
```
This method escapes whatever is needed, both for the key and the value:
@@ -465,7 +467,7 @@ C.new(0, 1).instance_variable_names # => ["@x", "@y"]
NOTE: Defined in `active_support/core_ext/object/instance_variables.rb`.
-### Silencing Warnings, Streams, and Exceptions
+### Silencing Warnings and Exceptions
The methods `silence_warnings` and `enable_warnings` change the value of `$VERBOSE` accordingly for the duration of their block, and reset it afterwards:
@@ -473,26 +475,10 @@ The methods `silence_warnings` and `enable_warnings` change the value of `$VERBO
silence_warnings { Object.const_set "RAILS_DEFAULT_LOGGER", logger }
```
-You can silence any stream while a block runs with `silence_stream`:
-
-```ruby
-silence_stream(STDOUT) do
- # STDOUT is silent here
-end
-```
-
-The `quietly` method addresses the common use case where you want to silence STDOUT and STDERR, even in subprocesses:
+Silencing exceptions is also possible with `suppress`. This method receives an arbitrary number of exception classes. If an exception is raised during the execution of the block and is `kind_of?` any of the arguments, `suppress` captures it and returns silently. Otherwise the exception is not captured:
```ruby
-quietly { system 'bundle install' }
-```
-
-For example, the railties test suite uses that one in a few places to prevent command messages from being echoed intermixed with the progress status.
-
-Silencing exceptions is also possible with `suppress`. This method receives an arbitrary number of exception classes. If an exception is raised during the execution of the block and is `kind_of?` any of the arguments, `suppress` captures it and returns silently. Otherwise the exception is reraised:
-
-```ruby
-# If the user is locked the increment is lost, no big deal.
+# If the user is locked, the increment is lost, no big deal.
suppress(ActiveRecord::StaleObjectError) do
current_user.increment! :visits
end
@@ -520,6 +506,8 @@ Extensions to `Module`
### `alias_method_chain`
+**This method is deprecated in favour of using Module#prepend.**
+
Using plain Ruby you can wrap methods with other methods, that's called _alias chaining_.
For example, let's say you'd like params to be strings in functional tests, as they are in real requests, but still want the convenience of assigning integers and other kind of values. To accomplish that you could wrap `ActionController::TestCase#process` this way in `test/test_helper.rb`:
@@ -564,8 +552,6 @@ ActionController::TestCase.class_eval do
end
```
-Rails uses `alias_method_chain` all over the code base. For example validations are added to `ActiveRecord::Base#save` by wrapping the method that way in a separate module specialized in validations.
-
NOTE: Defined in `active_support/core_ext/module/aliasing.rb`.
### Attributes
@@ -741,7 +727,7 @@ NOTE: Defined in `active_support/core_ext/module/introspection.rb`.
#### Qualified Constant Names
-The standard methods `const_defined?`, `const_get` , and `const_set` accept
+The standard methods `const_defined?`, `const_get`, and `const_set` accept
bare constant names. Active Support extends this API to be able to pass
relative qualified constant names.
@@ -1011,7 +997,7 @@ self.default_params = {
}.freeze
```
-They can be also accessed and overridden at the instance level.
+They can also be accessed and overridden at the instance level.
```ruby
A.x = 1
@@ -1251,7 +1237,7 @@ Calling `dup` or `clone` on safe strings yields safe strings.
The method `remove` will remove all occurrences of the pattern:
```ruby
-"Hello World".remove(/Hello /) => "World"
+"Hello World".remove(/Hello /) # => "World"
```
There's also the destructive version `String#remove!`.
@@ -1447,7 +1433,7 @@ Returns the substring of the string starting at position `position`:
"hello".from(0) # => "hello"
"hello".from(2) # => "llo"
"hello".from(-2) # => "lo"
-"hello".from(10) # => "" if < 1.9, nil in 1.9
+"hello".from(10) # => nil
```
NOTE: Defined in `active_support/core_ext/string/access.rb`.
@@ -1950,24 +1936,6 @@ as well as adding or subtracting their results from a Time object. For example:
(4.months + 5.years).from_now
```
-While these methods provide precise calculation when used as in the examples above, care
-should be taken to note that this is not true if the result of `months', `years', etc is
-converted before use:
-
-```ruby
-# equivalent to 30.days.to_i.from_now
-1.month.to_i.from_now
-
-# equivalent to 365.25.days.to_f.from_now
-1.year.to_f.from_now
-```
-
-In such cases, Ruby's core [Date](http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html) and
-[Time](http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html) should be used for precision
-date and time arithmetic.
-
-NOTE: Defined in `active_support/core_ext/numeric/time.rb`.
-
### Formatting
Enables the formatting of numbers in a variety of ways.
@@ -2214,6 +2182,17 @@ to_visit << node if visited.exclude?(node)
NOTE: Defined in `active_support/core_ext/enumerable.rb`.
+### `without`
+
+The method `without` returns a copy of an enumerable with the specified elements
+removed:
+
+```ruby
+people.without("Aaron", "Todd")
+```
+
+NOTE: Defined in `active_support/core_ext/enumerable.rb`.
+
Extensions to `Array`
---------------------
@@ -3061,53 +3040,6 @@ The method `Range#overlaps?` says whether any two given ranges have non-void int
NOTE: Defined in `active_support/core_ext/range/overlaps.rb`.
-Extensions to `Proc`
---------------------
-
-### `bind`
-
-As you surely know Ruby has an `UnboundMethod` class whose instances are methods that belong to the limbo of methods without a self. The method `Module#instance_method` returns an unbound method for example:
-
-```ruby
-Hash.instance_method(:delete) # => #<UnboundMethod: Hash#delete>
-```
-
-An unbound method is not callable as is, you need to bind it first to an object with `bind`:
-
-```ruby
-clear = Hash.instance_method(:clear)
-clear.bind({a: 1}).call # => {}
-```
-
-Active Support defines `Proc#bind` with an analogous purpose:
-
-```ruby
-Proc.new { size }.bind([]).call # => 0
-```
-
-As you see that's callable and bound to the argument, the return value is indeed a `Method`.
-
-NOTE: To do so `Proc#bind` actually creates a method under the hood. If you ever see a method with a weird name like `__bind_1256598120_237302` in a stack trace you know now where it comes from.
-
-Action Pack uses this trick in `rescue_from` for example, which accepts the name of a method and also a proc as callbacks for a given rescued exception. It has to call them in either case, so a bound method is returned by `handler_for_rescue`, thus simplifying the code in the caller:
-
-```ruby
-def handler_for_rescue(exception)
- _, rescuer = Array(rescue_handlers).reverse.detect do |klass_name, handler|
- ...
- end
-
- case rescuer
- when Symbol
- method(rescuer)
- when Proc
- rescuer.bind(self)
- end
-end
-```
-
-NOTE: Defined in `active_support/core_ext/proc.rb`.
-
Extensions to `Date`
--------------------
@@ -3829,50 +3761,6 @@ WARNING. If the argument is an `IO` it needs to respond to `rewind` to be able t
NOTE: Defined in `active_support/core_ext/marshal.rb`.
-Extensions to `Logger`
-----------------------
-
-### `around_[level]`
-
-Takes two arguments, a `before_message` and `after_message` and calls the current level method on the `Logger` instance, passing in the `before_message`, then the specified message, then the `after_message`:
-
-```ruby
-logger = Logger.new("log/development.log")
-logger.around_info("before", "after") { |logger| logger.info("during") }
-```
-
-### `silence`
-
-Silences every log level lesser to the specified one for the duration of the given block. Log level orders are: debug, info, error and fatal.
-
-```ruby
-logger = Logger.new("log/development.log")
-logger.silence(Logger::INFO) do
- logger.debug("In space, no one can hear you scream.")
- logger.info("Scream all you want, small mailman!")
-end
-```
-
-### `datetime_format=`
-
-Modifies the datetime format output by the formatter class associated with this logger. If the formatter class does not have a `datetime_format` method then this is ignored.
-
-```ruby
-class Logger::FormatWithTime < Logger::Formatter
- cattr_accessor(:datetime_format) { "%Y%m%d%H%m%S" }
-
- def self.call(severity, timestamp, progname, msg)
- "#{timestamp.strftime(datetime_format)} -- #{String === msg ? msg : msg.inspect}\n"
- end
-end
-
-logger = Logger.new("log/development.log")
-logger.formatter = Logger::FormatWithTime
-logger.info("<- is the current time")
-```
-
-NOTE: Defined in `active_support/core_ext/logger.rb`.
-
Extensions to `NameError`
-------------------------
@@ -3889,7 +3777,7 @@ def default_helper_module!
module_name = name.sub(/Controller$/, '')
module_path = module_name.underscore
helper module_path
-rescue MissingSourceFile => e
+rescue LoadError => e
raise e unless e.is_missing? "helpers/#{module_path}_helper"
rescue NameError => e
raise e unless e.missing_name? "#{module_name}Helper"
@@ -3901,7 +3789,7 @@ NOTE: Defined in `active_support/core_ext/name_error.rb`.
Extensions to `LoadError`
-------------------------
-Active Support adds `is_missing?` to `LoadError`, and also assigns that class to the constant `MissingSourceFile` for backwards compatibility.
+Active Support adds `is_missing?` to `LoadError`.
Given a path name `is_missing?` tests whether the exception was raised due to that particular file (except perhaps for the ".rb" extension).
@@ -3912,7 +3800,7 @@ def default_helper_module!
module_name = name.sub(/Controller$/, '')
module_path = module_name.underscore
helper module_path
-rescue MissingSourceFile => e
+rescue LoadError => e
raise e unless e.is_missing? "helpers/#{module_path}_helper"
rescue NameError => e
raise e unless e.missing_name? "#{module_name}Helper"
diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md
index 9dfacce560..1b14bedfbf 100644
--- a/guides/source/active_support_instrumentation.md
+++ b/guides/source/active_support_instrumentation.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Active Support Instrumentation
==============================
@@ -17,7 +19,7 @@ After reading this guide, you will know:
Introduction to instrumentation
-------------------------------
-The instrumentation API provided by Active Support allows developers to provide hooks which other developers may hook into. There are several of these within the Rails framework, as described below in (TODO: link to section detailing each hook point). With this API, developers can choose to be notified when certain events occur inside their application or another piece of Ruby code.
+The instrumentation API provided by Active Support allows developers to provide hooks which other developers may hook into. There are several of these within the [Rails framework](#rails-framework-hooks). With this API, developers can choose to be notified when certain events occur inside their application or another piece of Ruby code.
For example, there is a hook provided within Active Record that is called every time Active Record uses an SQL query on a database. This hook could be **subscribed** to, and used to track the number of queries during a certain action. There's another hook around the processing of an action of a controller. This could be used, for instance, to track how long a specific action has taken.
@@ -216,7 +218,7 @@ Action View
```ruby
{
- identifier: "/Users/adam/projects/notifications/app/views/posts/_form.html.erb",
+ identifier: "/Users/adam/projects/notifications/app/views/posts/_form.html.erb"
}
```
@@ -305,7 +307,7 @@ Action Mailer
}
```
-ActiveResource
+Active Resource
--------------
### request.active_resource
diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md
index a2ebf55335..b385bdbe83 100644
--- a/guides/source/api_documentation_guidelines.md
+++ b/guides/source/api_documentation_guidelines.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
API Documentation Guidelines
============================
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index ae0f19c02a..9da0ef1eb3 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
The Asset Pipeline
==================
@@ -147,7 +149,7 @@ clients to fetch them again, even when the content of those assets has not chang
Fingerprinting fixes these problems by avoiding query strings, and by ensuring
that filenames are consistent based on their content.
-Fingerprinting is enabled by default for production and disabled for all other
+Fingerprinting is enabled by default for both the development and production
environments. You can enable or disable it in your configuration through the
`config.assets.digest` option.
@@ -167,9 +169,8 @@ directory. Files in this directory are served by the Sprockets middleware.
Assets can still be placed in the `public` hierarchy. Any assets under `public`
will be served as static files by the application or web server when
-`config.serve_static_assets` is set to true. You should use
-`app/assets` for files that must undergo some pre-processing before they are
-served.
+`config.serve_static_files` is set to true. You should use `app/assets` for
+files that must undergo some pre-processing before they are served.
In production, Rails precompiles these files to `public/assets` by default. The
precompiled copies are then served as static assets by the web server. The files
@@ -181,12 +182,12 @@ When you generate a scaffold or a controller, Rails also generates a JavaScript
file (or CoffeeScript file if the `coffee-rails` gem is in the `Gemfile`) and a
Cascading Style Sheet file (or SCSS file if `sass-rails` is in the `Gemfile`)
for that controller. Additionally, when generating a scaffold, Rails generates
-the file scaffolds.css (or scaffolds.css.scss if `sass-rails` is in the
+the file scaffolds.css (or scaffolds.scss if `sass-rails` is in the
`Gemfile`.)
For example, if you generate a `ProjectsController`, Rails will also add a new
-file at `app/assets/javascripts/projects.js.coffee` and another at
-`app/assets/stylesheets/projects.css.scss`. By default these files will be ready
+file at `app/assets/javascripts/projects.coffee` and another at
+`app/assets/stylesheets/projects.scss`. By default these files will be ready
to use by your application immediately using the `require_tree` directive. See
[Manifest Files and Directives](#manifest-files-and-directives) for more details
on require_tree.
@@ -208,7 +209,7 @@ precompiling works.
NOTE: You must have an ExecJS supported runtime in order to use CoffeeScript.
If you are using Mac OS X or Windows, you have a JavaScript runtime installed in
-your operating system. Check [ExecJS](https://github.com/sstephenson/execjs#readme) documentation to know all supported JavaScript runtimes.
+your operating system. Check [ExecJS](https://github.com/rails/execjs#readme) documentation to know all supported JavaScript runtimes.
You can also disable generation of controller specific asset files by adding the
following to your `config/application.rb` configuration:
@@ -231,7 +232,9 @@ images, JavaScript files or stylesheets.
scope of the application or those libraries which are shared across applications.
* `vendor/assets` is for assets that are owned by outside entities, such as
-code for JavaScript plugins and CSS frameworks.
+code for JavaScript plugins and CSS frameworks. Keep in mind that third party
+code with references to other files also processed by the asset Pipeline (images,
+stylesheets, etc.), will need to be rewritten to use helpers like `asset_path`.
WARNING: If you are upgrading from Rails 3, please take into account that assets
under `lib/assets` or `vendor/assets` are available for inclusion via the
@@ -421,7 +424,7 @@ $('#logo').attr({ src: "<%= asset_path('logo.png') %>" });
This writes the path to the particular asset being referenced.
Similarly, you can use the `asset_path` helper in CoffeeScript files with `erb`
-extension (e.g., `application.js.coffee.erb`):
+extension (e.g., `application.coffee.erb`):
```js
$('#logo').attr src: "<%= asset_path('logo.png') %>"
@@ -522,8 +525,8 @@ The file extensions used on an asset determine what preprocessing is applied.
When a controller or a scaffold is generated with the default Rails gemset, a
CoffeeScript file and a SCSS file are generated in place of a regular JavaScript
and CSS file. The example used before was a controller called "projects", which
-generated an `app/assets/javascripts/projects.js.coffee` and an
-`app/assets/stylesheets/projects.css.scss` file.
+generated an `app/assets/javascripts/projects.coffee` and an
+`app/assets/stylesheets/projects.scss` file.
In development mode, or if the asset pipeline is disabled, when these files are
requested they are processed by the processors provided by the `coffee-script`
@@ -535,13 +538,13 @@ web server.
Additional layers of preprocessing can be requested by adding other extensions,
where each extension is processed in a right-to-left manner. These should be
used in the order the processing should be applied. For example, a stylesheet
-called `app/assets/stylesheets/projects.css.scss.erb` is first processed as ERB,
+called `app/assets/stylesheets/projects.scss.erb` is first processed as ERB,
then SCSS, and finally served as CSS. The same applies to a JavaScript file -
-`app/assets/javascripts/projects.js.coffee.erb` is processed as ERB, then
+`app/assets/javascripts/projects.coffee.erb` is processed as ERB, then
CoffeeScript, and served as JavaScript.
Keep in mind the order of these preprocessors is important. For example, if
-you called your JavaScript file `app/assets/javascripts/projects.js.erb.coffee`
+you called your JavaScript file `app/assets/javascripts/projects.erb.coffee`
then it would be processed with the CoffeeScript interpreter first, which
wouldn't understand ERB and therefore you would run into problems.
@@ -664,8 +667,7 @@ anymore, delete these options from the `javascript_include_tag` and
`stylesheet_link_tag`.
The fingerprinting behavior is controlled by the `config.assets.digest`
-initialization option (which defaults to `true` for production and `false` for
-everything else).
+initialization option (which defaults to `true` for production and development).
NOTE: Under normal circumstances the default `config.assets.digest` option
should not be changed. If there are no digests in the filenames, and far-future
@@ -726,27 +728,6 @@ include, you can add them to the `precompile` array in `config/initializers/asse
Rails.application.config.assets.precompile += ['admin.js', 'admin.css', 'swfObject.js']
```
-Or, you can opt to precompile all assets with something like this:
-
-```ruby
-# config/initializers/assets.rb
-Rails.application.config.assets.precompile << Proc.new do |path|
- if path =~ /\.(css|js)\z/
- full_path = Rails.application.assets.resolve(path).to_path
- app_assets_path = Rails.root.join('app', 'assets').to_path
- if full_path.starts_with? app_assets_path
- logger.info "including asset: " + full_path
- true
- else
- logger.info "excluding asset: " + full_path
- false
- end
- else
- false
- end
-end
-```
-
NOTE. Always specify an expected compiled filename that ends with .js or .css,
even if you want to add Sass or CoffeeScript files to the precompile array.
@@ -939,7 +920,7 @@ focus on serving application code as fast as possible.
#### Set up a CDN to Serve Static Assets
To set up your CDN you have to have your application running in production on
-the internet at a publically available URL, for example `example.com`. Next
+the internet at a publicly available URL, for example `example.com`. Next
you'll need to sign up for a CDN service from a cloud hosting provider. When you
do this you need to configure the "origin" of the CDN to point back at your
website `example.com`, check your provider for documentation on configuring the
@@ -992,7 +973,7 @@ http://mycdnsubdomain.fictional-cdn.com/assets/smile.png
If the CDN has a copy of `smile.png` it will serve it to the browser and your
server doesn't even know it was requested. If the CDN does not have a copy it
-will try to find it a the "origin" `example.com/assets/smile.png` and then store
+will try to find it at the "origin" `example.com/assets/smile.png` and then store
it for future use.
If you want to serve only some assets from your CDN, you can use custom `:host`
@@ -1155,7 +1136,7 @@ The following line invokes `uglifier` for JavaScript compression.
config.assets.js_compressor = :uglifier
```
-NOTE: You will need an [ExecJS](https://github.com/sstephenson/execjs#readme)
+NOTE: You will need an [ExecJS](https://github.com/rails/execjs#readme)
supported runtime in order to use `uglifier`. If you are using Mac OS X or
Windows you have a JavaScript runtime installed in your operating system.
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index 61490ceb54..ec6017ff73 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Active Record Associations
==========================
@@ -101,13 +103,13 @@ class CreateOrders < ActiveRecord::Migration
def change
create_table :customers do |t|
t.string :name
- t.timestamps
+ t.timestamps null: false
end
create_table :orders do |t|
t.belongs_to :customer, index: true
t.datetime :order_date
- t.timestamps
+ t.timestamps null: false
end
end
end
@@ -132,13 +134,13 @@ class CreateSuppliers < ActiveRecord::Migration
def change
create_table :suppliers do |t|
t.string :name
- t.timestamps
+ t.timestamps null: false
end
create_table :accounts do |t|
t.belongs_to :supplier, index: true
t.string :account_number
- t.timestamps
+ t.timestamps null: false
end
end
end
@@ -165,13 +167,13 @@ class CreateCustomers < ActiveRecord::Migration
def change
create_table :customers do |t|
t.string :name
- t.timestamps
+ t.timestamps null: false
end
create_table :orders do |t|
- t.belongs_to :customer, index:true
+ t.belongs_to :customer, index: true
t.datetime :order_date
- t.timestamps
+ t.timestamps null: false
end
end
end
@@ -207,19 +209,19 @@ class CreateAppointments < ActiveRecord::Migration
def change
create_table :physicians do |t|
t.string :name
- t.timestamps
+ t.timestamps null: false
end
create_table :patients do |t|
t.string :name
- t.timestamps
+ t.timestamps null: false
end
create_table :appointments do |t|
t.belongs_to :physician, index: true
t.belongs_to :patient, index: true
t.datetime :appointment_date
- t.timestamps
+ t.timestamps null: false
end
end
end
@@ -291,19 +293,19 @@ class CreateAccountHistories < ActiveRecord::Migration
def change
create_table :suppliers do |t|
t.string :name
- t.timestamps
+ t.timestamps null: false
end
create_table :accounts do |t|
t.belongs_to :supplier, index: true
t.string :account_number
- t.timestamps
+ t.timestamps null: false
end
create_table :account_histories do |t|
t.belongs_to :account, index: true
t.integer :credit_rating
- t.timestamps
+ t.timestamps null: false
end
end
end
@@ -332,12 +334,12 @@ class CreateAssembliesAndParts < ActiveRecord::Migration
def change
create_table :assemblies do |t|
t.string :name
- t.timestamps
+ t.timestamps null: false
end
create_table :parts do |t|
t.string :part_number
- t.timestamps
+ t.timestamps null: false
end
create_table :assemblies_parts, id: false do |t|
@@ -371,13 +373,13 @@ class CreateSuppliers < ActiveRecord::Migration
def change
create_table :suppliers do |t|
t.string :name
- t.timestamps
+ t.timestamps null: false
end
create_table :accounts do |t|
t.integer :supplier_id
t.string :account_number
- t.timestamps
+ t.timestamps null: false
end
add_index :accounts, :supplier_id
@@ -455,10 +457,10 @@ class CreatePictures < ActiveRecord::Migration
t.string :name
t.integer :imageable_id
t.string :imageable_type
- t.timestamps
+ t.timestamps null: false
end
- add_index :pictures, :imageable_id
+ add_index :pictures, [:imageable_type, :imageable_id]
end
end
```
@@ -471,7 +473,7 @@ class CreatePictures < ActiveRecord::Migration
create_table :pictures do |t|
t.string :name
t.references :imageable, polymorphic: true, index: true
- t.timestamps
+ t.timestamps null: false
end
end
end
@@ -501,7 +503,7 @@ class CreateEmployees < ActiveRecord::Migration
def change
create_table :employees do |t|
t.references :manager, index: true
- t.timestamps
+ t.timestamps null: false
end
end
end
@@ -689,7 +691,7 @@ c.first_name = 'Manny'
c.first_name == o.customer.first_name # => false
```
-This happens because c and o.customer are two different in-memory representations of the same data, and neither one is automatically refreshed from changes to the other. Active Record provides the `:inverse_of` option so that you can inform it of these relations:
+This happens because `c` and `o.customer` are two different in-memory representations of the same data, and neither one is automatically refreshed from changes to the other. Active Record provides the `:inverse_of` option so that you can inform it of these relations:
```ruby
class Customer < ActiveRecord::Base
@@ -724,10 +726,10 @@ Most associations with standard names will be supported. However, associations
that contain the following options will not have their inverses set
automatically:
-* :conditions
-* :through
-* :polymorphic
-* :foreign_key
+* `:conditions`
+* `:through`
+* `:polymorphic`
+* `:foreign_key`
Detailed Association Reference
------------------------------
@@ -831,6 +833,7 @@ The `belongs_to` association supports these options:
* `:polymorphic`
* `:touch`
* `:validate`
+* `:optional`
##### `:autosave`
@@ -879,10 +882,12 @@ class Order < ActiveRecord::Base
belongs_to :customer, counter_cache: :count_of_orders
end
class Customer < ActiveRecord::Base
- has_many :orders
+ has_many :orders, counter_cache: :count_of_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.
+
Counter cache columns are added to the containing model's list of read-only attributes through `attr_readonly`.
##### `:dependent`
@@ -928,7 +933,7 @@ Passing `true` to the `:polymorphic` option indicates that this is a polymorphic
##### `:touch`
-If you set the `:touch` option to `:true`, then the `updated_at` or `updated_on` timestamp on the associated object will be set to the current time whenever this object is saved or destroyed:
+If you set the `:touch` option to `true`, then the `updated_at` or `updated_on` timestamp on the associated object will be set to the current time whenever this object is saved or destroyed:
```ruby
class Order < ActiveRecord::Base
@@ -952,6 +957,11 @@ end
If you set the `:validate` option to `true`, then associated objects will be validated whenever you save this object. By default, this is `false`: associated objects will not be validated when this object is saved.
+##### `:optional`
+
+If you set the `:optional` option to `true`, then the presence of the associated
+object won't be validated. By default, this option is set to `false`.
+
#### Scopes for `belongs_to`
There may be times when you wish to customize the query used by `belongs_to`. Such customizations can be achieved via a scope block. For example:
@@ -1486,7 +1496,7 @@ While Rails uses intelligent defaults that will work well in most situations, th
```ruby
class Customer < ActiveRecord::Base
- has_many :orders, dependent: :delete_all, validate: :false
+ has_many :orders, dependent: :delete_all, validate: false
end
```
@@ -1495,6 +1505,7 @@ The `has_many` association supports these options:
* `:as`
* `:autosave`
* `:class_name`
+* `:counter_cache`
* `:dependent`
* `:foreign_key`
* `:inverse_of`
@@ -1522,6 +1533,10 @@ class Customer < ActiveRecord::Base
end
```
+##### `:counter_cache`
+
+This option can be used to configure a custom named `:counter_cache`. You only need this option when you customized the name of your `:counter_cache` on the [belongs_to association](#options-for-belongs-to).
+
##### `:dependent`
Controls what happens to the associated objects when their owner is destroyed:
@@ -1532,8 +1547,6 @@ Controls what happens to the associated objects when their owner is destroyed:
* `:restrict_with_exception` causes an exception to be raised if there are any associated records
* `:restrict_with_error` causes an error to be added to the owner if there are any associated objects
-NOTE: This option is ignored when you use the `:through` option on the association.
-
##### `:foreign_key`
By convention, Rails assumes that the column used to hold the foreign key on the other model is the name of this model with the suffix `_id` added. The `:foreign_key` option lets you set the name of the foreign key directly:
@@ -1979,8 +1992,8 @@ While Rails uses intelligent defaults that will work well in most situations, th
```ruby
class Parts < ActiveRecord::Base
- has_and_belongs_to_many :assemblies, autosave: true,
- readonly: true
+ has_and_belongs_to_many :assemblies, -> { readonly },
+ autosave: true
end
```
@@ -1992,7 +2005,6 @@ The `has_and_belongs_to_many` association supports these options:
* `:foreign_key`
* `:join_table`
* `:validate`
-* `:readonly`
##### `:association_foreign_key`
@@ -2236,3 +2248,67 @@ Extensions can refer to the internals of the association proxy using these three
* `proxy_association.owner` returns the object that the association is a part of.
* `proxy_association.reflection` returns the reflection object that describes the association.
* `proxy_association.target` returns the associated object for `belongs_to` or `has_one`, or the collection of associated objects for `has_many` or `has_and_belongs_to_many`.
+
+Single Table Inheritance
+------------------------
+
+Sometimes, you may want to share fields and behavior between different models.
+Let's say we have Car, Motorcycle and Bicycle models. We will want to share
+the `color` and `price` fields and some methods for all of them, but having some
+specific behavior for each, and separated controllers too.
+
+Rails makes this quite easy. First, let's generate the base Vehicle model:
+
+```bash
+$ rails generate model vehicle type:string color:string price:decimal{10.2}
+```
+
+Did you note we are adding a "type" field? Since all models will be saved in a
+single database table, Rails will save in this column the name of the model that
+is being saved. In our example, this can be "Car", "Motorcycle" or "Bicycle."
+STI won't work without a "type" field in the table.
+
+Next, we will generate the three models that inherit from Vehicle. For this,
+we can use the `--parent=PARENT` option, which will generate a model that
+inherits from the specified parent and without equivalent migration (since the
+table already exists).
+
+For example, to generate the Car model:
+
+```bash
+$ rails generate model car --parent=Vehicle
+```
+
+The generated model will look like this:
+
+```ruby
+class Car < Vehicle
+end
+```
+
+This means that all behavior added to Vehicle is available for Car too, as
+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
+```
+
+will generate the following SQL:
+
+```sql
+INSERT INTO "vehicles" ("type", "color", "price") VALUES ("Car", "Red", 10000)
+```
+
+Querying car records will just search for vehicles that are cars:
+
+```ruby
+Car.all
+```
+
+will run a query like:
+
+```sql
+SELECT "vehicles".* FROM "vehicles" WHERE "vehicles"."type" IN ('Car')
+```
diff --git a/guides/source/autoloading_and_reloading_constants.md b/guides/source/autoloading_and_reloading_constants.md
new file mode 100644
index 0000000000..c6149abcba
--- /dev/null
+++ b/guides/source/autoloading_and_reloading_constants.md
@@ -0,0 +1,1316 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
+Autoloading and Reloading Constants
+===================================
+
+This guide documents how constant autoloading and reloading works.
+
+After reading this guide, you will know:
+
+* Key aspects of Ruby constants
+* What is `autoload_paths`
+* How constant autoloading works
+* What is `require_dependency`
+* How constant reloading works
+* Solutions to common autoloading gotchas
+
+--------------------------------------------------------------------------------
+
+
+Introduction
+------------
+
+Ruby on Rails allows applications to be written as if their code was preloaded.
+
+In a normal Ruby program classes need to load their dependencies:
+
+```ruby
+require 'application_controller'
+require 'post'
+
+class PostsController < ApplicationController
+ def index
+ @posts = Post.all
+ end
+end
+```
+
+Our Rubyist instinct quickly sees some redundancy in there: If classes were
+defined in files matching their name, couldn't their loading be automated
+somehow? We could save scanning the file for dependencies, which is brittle.
+
+Moreover, `Kernel#require` loads files once, but development is much more smooth
+if code gets refreshed when it changes without restarting the server. It would
+be nice to be able to use `Kernel#load` in development, and `Kernel#require` in
+production.
+
+Indeed, those features are provided by Ruby on Rails, where we just write
+
+```ruby
+class PostsController < ApplicationController
+ def index
+ @posts = Post.all
+ end
+end
+```
+
+This guide documents how that works.
+
+
+Constants Refresher
+-------------------
+
+While constants are trivial in most programming languages, they are a rich
+topic in Ruby.
+
+It is beyond the scope of this guide to document Ruby constants, but we are
+nevertheless going to highlight a few key topics. Truly grasping the following
+sections is instrumental to understanding constant autoloading and reloading.
+
+### Nesting
+
+Class and module definitions can be nested to create namespaces:
+
+```ruby
+module XML
+ class SAXParser
+ # (1)
+ end
+end
+```
+
+The *nesting* at any given place is the collection of enclosing nested class and
+module objects outwards. The nesting at any given place can be inspected with
+`Module.nesting`. For example, in the previous example, the nesting at
+(1) is
+
+```ruby
+[XML::SAXParser, XML]
+```
+
+It is important to understand that the nesting is composed of class and module
+*objects*, it has nothing to do with the constants used to access them, and is
+also unrelated to their names.
+
+For instance, while this definition is similar to the previous one:
+
+```ruby
+class XML::SAXParser
+ # (2)
+end
+```
+
+the nesting in (2) is different:
+
+```ruby
+[XML::SAXParser]
+```
+
+`XML` does not belong to it.
+
+We can see in this example that the name of a class or module that belongs to a
+certain nesting does not necessarily correlate with the namespaces at the spot.
+
+Even more, they are totally independent, take for instance
+
+```ruby
+module X
+ module Y
+ end
+end
+
+module A
+ module B
+ end
+end
+
+module X::Y
+ module A::B
+ # (3)
+ end
+end
+```
+
+The nesting in (3) consists of two module objects:
+
+```ruby
+[A::B, X::Y]
+```
+
+So, it not only doesn't end in `A`, which does not even belong to the nesting,
+but it also contains `X::Y`, which is independent from `A::B`.
+
+The nesting is an internal stack maintained by the interpreter, and it gets
+modified according to these rules:
+
+* The class object following a `class` keyword gets pushed when its body is
+executed, and popped after it.
+
+* The module object following a `module` keyword gets pushed when its body is
+executed, and popped after it.
+
+* A singleton class opened with `class << object` gets pushed, and popped later.
+
+* When `instance_eval` is called using a string argument,
+the singleton class of the receiver is pushed to the nesting of the eval'ed
+code. When `class_eval` or `module_eval` is called using a string argument,
+the receiver is pushed to the nesting of the eval'ed code.
+
+* The nesting at the top-level of code interpreted by `Kernel#load` is empty
+unless the `load` call receives a true value as second argument, in which case
+a newly created anonymous module is pushed by Ruby.
+
+It is interesting to observe that blocks do not modify the stack. In particular
+the blocks that may be passed to `Class.new` and `Module.new` do not get the
+class or module being defined pushed to their nesting. That's one of the
+differences between defining classes and modules in one way or another.
+
+### Class and Module Definitions are Constant Assignments
+
+Let's suppose the following snippet creates a class (rather than reopening it):
+
+```ruby
+class C
+end
+```
+
+Ruby creates a constant `C` in `Object` and stores in that constant a class
+object. The name of the class instance is "C", a string, named after the
+constant.
+
+That is,
+
+```ruby
+class Project < ActiveRecord::Base
+end
+```
+
+performs a constant assignment equivalent to
+
+```ruby
+Project = Class.new(ActiveRecord::Base)
+```
+
+including setting the name of the class as a side-effect:
+
+```ruby
+Project.name # => "Project"
+```
+
+Constant assignment has a special rule to make that happen: if the object
+being assigned is an anonymous class or module, Ruby sets the object's name to
+the name of the constant.
+
+INFO. From then on, what happens to the constant and the instance does not
+matter. For example, the constant could be deleted, the class object could be
+assigned to a different constant, be stored in no constant anymore, etc. Once
+the name is set, it doesn't change.
+
+Similarly, module creation using the `module` keyword as in
+
+```ruby
+module Admin
+end
+```
+
+performs a constant assignment equivalent to
+
+```ruby
+Admin = Module.new
+```
+
+including setting the name as a side-effect:
+
+```ruby
+Admin.name # => "Admin"
+```
+
+WARNING. The execution context of a block passed to `Class.new` or `Module.new`
+is not entirely equivalent to the one of the body of the definitions using the
+`class` and `module` keywords. But both idioms result in the same constant
+assignment.
+
+Thus, when one informally says "the `String` class", that really means: the
+class object stored in the constant called "String" in the class object stored
+in the `Object` constant. `String` is otherwise an ordinary Ruby constant and
+everything related to constants such as resolution algorithms applies to it.
+
+Likewise, in the controller
+
+```ruby
+class PostsController < ApplicationController
+ def index
+ @posts = Post.all
+ end
+end
+```
+
+`Post` is not syntax for a class. Rather, `Post` is a regular Ruby constant. If
+all is good, the constant is evaluated to an object that responds to `all`.
+
+That is why we talk about *constant* autoloading, Rails has the ability to
+load constants on the fly.
+
+### Constants are Stored in Modules
+
+Constants belong to modules in a very literal sense. Classes and modules have
+a constant table; think of it as a hash table.
+
+Let's analyze an example to really understand what that means. While common
+abuses of language like "the `String` class" are convenient, the exposition is
+going to be precise here for didactic purposes.
+
+Let's consider the following module definition:
+
+```ruby
+module Colors
+ RED = '0xff0000'
+end
+```
+
+First, when the `module` keyword is processed, the interpreter creates a new
+entry in the constant table of the class object stored in the `Object` constant.
+Said entry associates the name "Colors" to a newly created module object.
+Furthermore, the interpreter sets the name of the new module object to be the
+string "Colors".
+
+Later, when the body of the module definition is interpreted, a new entry is
+created in the constant table of the module object stored in the `Colors`
+constant. That entry maps the name "RED" to the string "0xff0000".
+
+In particular, `Colors::RED` is totally unrelated to any other `RED` constant
+that may live in any other class or module object. If there were any, they
+would have separate entries in their respective constant tables.
+
+Pay special attention in the previous paragraphs to the distinction between
+class and module objects, constant names, and value objects associated to them
+in constant tables.
+
+### Resolution Algorithms
+
+#### Resolution Algorithm for Relative Constants
+
+At any given place in the code, let's define *cref* to be the first element of
+the nesting if it is not empty, or `Object` otherwise.
+
+Without getting too much into the details, the resolution algorithm for relative
+constant references goes like this:
+
+1. If the nesting is not empty the constant is looked up in its elements and in
+order. The ancestors of those elements are ignored.
+
+2. If not found, then the algorithm walks up the ancestor chain of the cref.
+
+3. If not found and the cref is a module, the constant is looked up in `Object`.
+
+4. If not found, `const_missing` is invoked on the cref. The default
+implementation of `const_missing` raises `NameError`, but it can be overridden.
+
+Rails autoloading **does not emulate this algorithm**, but its starting point is
+the name of the constant to be autoloaded, and the cref. See more in [Relative
+References](#autoloading-algorithms-relative-references).
+
+#### Resolution Algorithm for Qualified Constants
+
+Qualified constants look like this:
+
+```ruby
+Billing::Invoice
+```
+
+`Billing::Invoice` is composed of two constants: `Billing` is relative and is
+resolved using the algorithm of the previous section.
+
+INFO. Leading colons would make the first segment absolute rather than
+relative: `::Billing::Invoice`. That would force `Billing` to be looked up
+only as a top-level constant.
+
+`Invoice` on the other hand is qualified by `Billing` and we are going to see
+its resolution next. Let's define *parent* to be that qualifying class or module
+object, that is, `Billing` in the example above. The algorithm for qualified
+constants goes like this:
+
+1. The constant is looked up in the parent and its ancestors.
+
+2. If the lookup fails, `const_missing` is invoked in the parent. The default
+implementation of `const_missing` raises `NameError`, but it can be overridden.
+
+As you see, this algorithm is simpler than the one for relative constants. In
+particular, the nesting plays no role here, and modules are not special-cased,
+if neither they nor their ancestors have the constants, `Object` is **not**
+checked.
+
+Rails autoloading **does not emulate this algorithm**, but its starting point is
+the name of the constant to be autoloaded, and the parent. See more in
+[Qualified References](#autoloading-algorithms-qualified-references).
+
+
+Vocabulary
+----------
+
+### Parent Namespaces
+
+Given a string with a constant path we define its *parent namespace* to be the
+string that results from removing its rightmost segment.
+
+For example, the parent namespace of the string "A::B::C" is the string "A::B",
+the parent namespace of "A::B" is "A", and the parent namespace of "A" is "".
+
+The interpretation of a parent namespace when thinking about classes and modules
+is tricky though. Let's consider a module M named "A::B":
+
+* The parent namespace, "A", may not reflect nesting at a given spot.
+
+* The constant `A` may no longer exist, some code could have removed it from
+`Object`.
+
+* If `A` exists, the class or module that was originally in `A` may not be there
+anymore. For example, if after a constant removal there was another constant
+assignment there would generally be a different object in there.
+
+* In such case, it could even happen that the reassigned `A` held a new class or
+module called also "A"!
+
+* In the previous scenarios M would no longer be reachable through `A::B` but
+the module object itself could still be alive somewhere and its name would
+still be "A::B".
+
+The idea of a parent namespace is at the core of the autoloading algorithms
+and helps explain and understand their motivation intuitively, but as you see
+that metaphor leaks easily. Given an edge case to reason about, take always into
+account that by "parent namespace" the guide means exactly that specific string
+derivation.
+
+### Loading Mechanism
+
+Rails autoloads files with `Kernel#load` when `config.cache_classes` is false,
+the default in development mode, and with `Kernel#require` otherwise, the
+default in production mode.
+
+`Kernel#load` allows Rails to execute files more than once if [constant
+reloading](#constant-reloading) is enabled.
+
+This guide uses the word "load" freely to mean a given file is interpreted, but
+the actual mechanism can be `Kernel#load` or `Kernel#require` depending on that
+flag.
+
+
+Autoloading Availability
+------------------------
+
+Rails is always able to autoload provided its environment is in place. For
+example the `runner` command autoloads:
+
+```
+$ bin/rails runner 'p User.column_names'
+["id", "email", "created_at", "updated_at"]
+```
+
+The console autoloads, the test suite autoloads, and of course the application
+autoloads.
+
+By default, Rails eager loads the application files when it boots in production
+mode, so most of the autoloading going on in development does not happen. But
+autoloading may still be triggered during eager loading.
+
+For example, given
+
+```ruby
+class BeachHouse < House
+end
+```
+
+if `House` is still unknown when `app/models/beach_house.rb` is being eager
+loaded, Rails autoloads it.
+
+
+autoload_paths
+--------------
+
+As you probably know, when `require` gets a relative file name:
+
+```ruby
+require 'erb'
+```
+
+Ruby looks for the file in the directories listed in `$LOAD_PATH`. That is, Ruby
+iterates over all its directories and for each one of them checks whether they
+have a file called "erb.rb", or "erb.so", or "erb.o", or "erb.dll". If it finds
+any of them, the interpreter loads it and ends the search. Otherwise, it tries
+again in the next directory of the list. If the list gets exhausted, `LoadError`
+is raised.
+
+We are going to cover how constant autoloading works in more detail later, but
+the idea is that when a constant like `Post` is hit and missing, if there's a
+`post.rb` file for example in `app/models` Rails is going to find it, evaluate
+it, and have `Post` defined as a side-effect.
+
+Alright, Rails has a collection of directories similar to `$LOAD_PATH` in which
+to look up `post.rb`. That collection is called `autoload_paths` and by
+default it contains:
+
+* All subdirectories of `app` in the application and engines. For example,
+ `app/controllers`. They do not need to be the default ones, any custom
+ directories like `app/workers` belong automatically to `autoload_paths`.
+
+* Any existing second level directories called `app/*/concerns` in the
+ application and engines.
+
+* The directory `test/mailers/previews`.
+
+Also, this collection is configurable via `config.autoload_paths`. For example,
+`lib` was in the list years ago, but no longer is. An application can opt-in
+by adding this to `config/application.rb`:
+
+```ruby
+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.
+
+The value of `autoload_paths` can be inspected. In a just generated application
+it is (edited):
+
+```
+$ bin/rails r 'puts ActiveSupport::Dependencies.autoload_paths'
+.../app/assets
+.../app/controllers
+.../app/helpers
+.../app/mailers
+.../app/models
+.../app/controllers/concerns
+.../app/models/concerns
+.../test/mailers/previews
+```
+
+INFO. `autoload_paths` is computed and cached during the initialization process.
+The application needs to be restarted to reflect any changes in the directory
+structure.
+
+
+Autoloading Algorithms
+----------------------
+
+### Relative References
+
+A relative constant reference may appear in several places, for example, in
+
+```ruby
+class PostsController < ApplicationController
+ def index
+ @posts = Post.all
+ end
+end
+```
+
+all three constant references are relative.
+
+#### Constants after the `class` and `module` Keywords
+
+Ruby performs a lookup for the constant that follows a `class` or `module`
+keyword because it needs to know if the class or module is going to be created
+or reopened.
+
+If the constant is not defined at that point it is not considered to be a
+missing constant, autoloading is **not** triggered.
+
+So, in the previous example, if `PostsController` is not defined when the file
+is interpreted Rails autoloading is not going to be triggered, Ruby will just
+define the controller.
+
+#### Top-Level Constants
+
+On the contrary, if `ApplicationController` is unknown, the constant is
+considered missing and an autoload is going to be attempted by Rails.
+
+In order to load `ApplicationController`, Rails iterates over `autoload_paths`.
+First checks if `app/assets/application_controller.rb` exists. If it does not,
+which is normally the case, it continues and finds
+`app/controllers/application_controller.rb`.
+
+If the file defines the constant `ApplicationController` all is fine, otherwise
+`LoadError` is raised:
+
+```
+unable to autoload constant ApplicationController, expected
+<full path to application_controller.rb> to define it (LoadError)
+```
+
+INFO. Rails does not require the value of autoloaded constants to be a class or
+module object. For example, if the file `app/models/max_clients.rb` defines
+`MAX_CLIENTS = 100` autoloading `MAX_CLIENTS` works just fine.
+
+#### Namespaces
+
+Autoloading `ApplicationController` looks directly under the directories of
+`autoload_paths` because the nesting in that spot is empty. The situation of
+`Post` is different, the nesting in that line is `[PostsController]` and support
+for namespaces comes into play.
+
+The basic idea is that given
+
+```ruby
+module Admin
+ class BaseController < ApplicationController
+ @@all_roles = Role.all
+ end
+end
+```
+
+to autoload `Role` we are going to check if it is defined in the current or
+parent namespaces, one at a time. So, conceptually we want to try to autoload
+any of
+
+```
+Admin::BaseController::Role
+Admin::Role
+Role
+```
+
+in that order. That's the idea. To do so, Rails looks in `autoload_paths`
+respectively for file names like these:
+
+```
+admin/base_controller/role.rb
+admin/role.rb
+role.rb
+```
+
+modulus some additional directory lookups we are going to cover soon.
+
+INFO. `'Constant::Name'.underscore` gives the relative path without extension of
+the file name where `Constant::Name` is expected to be defined.
+
+Let's see how Rails autoloads the `Post` constant in the `PostsController`
+above assuming the application has a `Post` model defined in
+`app/models/post.rb`.
+
+First it checks for `posts_controller/post.rb` in `autoload_paths`:
+
+```
+app/assets/posts_controller/post.rb
+app/controllers/posts_controller/post.rb
+app/helpers/posts_controller/post.rb
+...
+test/mailers/previews/posts_controller/post.rb
+```
+
+Since the lookup is exhausted without success, a similar search for a directory
+is performed, we are going to see why in the [next section](#automatic-modules):
+
+```
+app/assets/posts_controller/post
+app/controllers/posts_controller/post
+app/helpers/posts_controller/post
+...
+test/mailers/previews/posts_controller/post
+```
+
+If all those attempts fail, then Rails starts the lookup again in the parent
+namespace. In this case only the top-level remains:
+
+```
+app/assets/post.rb
+app/controllers/post.rb
+app/helpers/post.rb
+app/mailers/post.rb
+app/models/post.rb
+```
+
+A matching file is found in `app/models/post.rb`. The lookup stops there and the
+file is loaded. If the file actually defines `Post` all is fine, otherwise
+`LoadError` is raised.
+
+### Qualified References
+
+When a qualified constant is missing Rails does not look for it in the parent
+namespaces. But there is a caveat: When a constant is missing, Rails is
+unable to tell if the trigger was a relative reference or a qualified one.
+
+For example, consider
+
+```ruby
+module Admin
+ User
+end
+```
+
+and
+
+```ruby
+Admin::User
+```
+
+If `User` is missing, in either case all Rails knows is that a constant called
+"User" was missing in a module called "Admin".
+
+If there is a top-level `User` Ruby would resolve it in the former example, but
+wouldn't in the latter. In general, Rails does not emulate the Ruby constant
+resolution algorithms, but in this case it tries using the following heuristic:
+
+> If none of the parent namespaces of the class or module has the missing
+> constant then Rails assumes the reference is relative. Otherwise qualified.
+
+For example, if this code triggers autoloading
+
+```ruby
+Admin::User
+```
+
+and the `User` constant is already present in `Object`, it is not possible that
+the situation is
+
+```ruby
+module Admin
+ User
+end
+```
+
+because otherwise Ruby would have resolved `User` and no autoloading would have
+been triggered in the first place. Thus, Rails assumes a qualified reference and
+considers the file `admin/user.rb` and directory `admin/user` to be the only
+valid options.
+
+In practice, this works quite well as long as the nesting matches all parent
+namespaces respectively and the constants that make the rule apply are known at
+that time.
+
+However, autoloading happens on demand. If by chance the top-level `User` was
+not yet loaded, then Rails assumes a relative reference by contract.
+
+Naming conflicts of this kind are rare in practice, but if one occurs,
+`require_dependency` provides a solution by ensuring that the constant needed
+to trigger the heuristic is defined in the conflicting place.
+
+### Automatic Modules
+
+When a module acts as a namespace, Rails does not require the application to
+defines a file for it, a directory matching the namespace is enough.
+
+Suppose an application has a back office whose controllers are stored in
+`app/controllers/admin`. If the `Admin` module is not yet loaded when
+`Admin::UsersController` is hit, Rails needs first to autoload the constant
+`Admin`.
+
+If `autoload_paths` has a file called `admin.rb` Rails is going to load that
+one, but if there's no such file and a directory called `admin` is found, Rails
+creates an empty module and assigns it to the `Admin` constant on the fly.
+
+### Generic Procedure
+
+Relative references are reported to be missing in the cref where they were hit,
+and qualified references are reported to be missing in their parent (see
+[Resolution Algorithm for Relative
+Constants](#resolution-algorithm-for-relative-constants) at the beginning of
+this guide for the definition of *cref*, and [Resolution Algorithm for Qualified
+Constants](#resolution-algorithm-for-qualified-constants) for the definition of
+*parent*).
+
+The procedure to autoload constant `C` in an arbitrary situation is as follows:
+
+```
+if the class or module in which C is missing is Object
+ let ns = ''
+else
+ let M = the class or module in which C is missing
+
+ if M is anonymous
+ let ns = ''
+ else
+ let ns = M.name
+ end
+end
+
+loop do
+ # Look for a regular file.
+ for dir in autoload_paths
+ if the file "#{dir}/#{ns.underscore}/c.rb" exists
+ load/require "#{dir}/#{ns.underscore}/c.rb"
+
+ if C is now defined
+ return
+ else
+ raise LoadError
+ end
+ end
+ end
+
+ # Look for an automatic module.
+ for dir in autoload_paths
+ if the directory "#{dir}/#{ns.underscore}/c" exists
+ if ns is an empty string
+ let C = Module.new in Object and return
+ else
+ let C = Module.new in ns.constantize and return
+ end
+ end
+ end
+
+ if ns is empty
+ # We reached the top-level without finding the constant.
+ raise NameError
+ else
+ if C exists in any of the parent namespaces
+ # Qualified constants heuristic.
+ raise NameError
+ else
+ # Try again in the parent namespace.
+ let ns = the parent namespace of ns and retry
+ end
+ end
+end
+```
+
+
+require_dependency
+------------------
+
+Constant autoloading is triggered on demand and therefore code that uses a
+certain constant may have it already defined or may trigger an autoload. That
+depends on the execution path and it may vary between runs.
+
+There are times, however, in which you want to make sure a certain constant is
+known when the execution reaches some code. `require_dependency` provides a way
+to load a file using the current [loading mechanism](#loading-mechanism), and
+keeping track of constants defined in that file as if they were autoloaded to
+have them reloaded as needed.
+
+`require_dependency` is rarely needed, but see a couple of use-cases in
+[Autoloading and STI](#autoloading-and-sti) and [When Constants aren't
+Triggered](#when-constants-aren-t-missed).
+
+WARNING. Unlike autoloading, `require_dependency` does not expect the file to
+define any particular constant. Exploiting this behavior would be a bad practice
+though, file and constant paths should match.
+
+
+Constant Reloading
+------------------
+
+When `config.cache_classes` is false Rails is able to reload autoloaded
+constants.
+
+For example, in you're in a console session and edit some file behind the
+scenes, the code can be reloaded with the `reload!` command:
+
+```
+> reload!
+```
+
+When the application runs, code is reloaded when something relevant to this
+logic changes. In order to do that, Rails monitors a number of things:
+
+* `config/routes.rb`.
+
+* Locales.
+
+* Ruby files under `autoload_paths`.
+
+* `db/schema.rb` and `db/structure.sql`.
+
+If anything in there changes, there is a middleware that detects it and reloads
+the code.
+
+Autoloading keeps track of autoloaded constants. Reloading is implemented by
+removing them all from their respective classes and modules using
+`Module#remove_const`. That way, when the code goes on, those constants are
+going to be unknown again, and files reloaded on demand.
+
+INFO. This is an all-or-nothing operation, Rails does not attempt to reload only
+what changed since dependencies between classes makes that really tricky.
+Instead, everything is wiped.
+
+
+Module#autoload isn't Involved
+------------------------------
+
+`Module#autoload` provides a lazy way to load constants that is fully integrated
+with the Ruby constant lookup algorithms, dynamic constant API, etc. It is quite
+transparent.
+
+Rails internals make extensive use of it to defer as much work as possible from
+the boot process. But constant autoloading in Rails is **not** implemented with
+`Module#autoload`.
+
+One possible implementation based on `Module#autoload` would be to walk the
+application tree and issue `autoload` calls that map existing file names to
+their conventional constant name.
+
+There are a number of reasons that prevent Rails from using that implementation.
+
+For example, `Module#autoload` is only capable of loading files using `require`,
+so reloading would not be possible. Not only that, it uses an internal `require`
+which is not `Kernel#require`.
+
+Then, it provides no way to remove declarations in case a file is deleted. If a
+constant gets removed with `Module#remove_const` its `autoload` is not triggered
+again. Also, it doesn't support qualified names, so files with namespaces should
+be interpreted during the walk tree to install their own `autoload` calls, but
+those files could have constant references not yet configured.
+
+An implementation based on `Module#autoload` would be awesome but, as you see,
+at least as of today it is not possible. Constant autoloading in Rails is
+implemented with `Module#const_missing`, and that's why it has its own contract,
+documented in this guide.
+
+
+Common Gotchas
+--------------
+
+### Nesting and Qualified Constants
+
+Let's consider
+
+```ruby
+module Admin
+ class UsersController < ApplicationController
+ def index
+ @users = User.all
+ end
+ end
+end
+```
+
+and
+
+```ruby
+class Admin::UsersController < ApplicationController
+ def index
+ @users = User.all
+ end
+end
+```
+
+To resolve `User` Ruby checks `Admin` in the former case, but it does not in
+the latter because it does not belong to the nesting (see [Nesting](#nesting)
+and [Resolution Algorithms](#resolution-algorithms)).
+
+Unfortunately Rails autoloading does not know the nesting in the spot where the
+constant was missing and so it is not able to act as Ruby would. In particular,
+`Admin::User` will get autoloaded in either case.
+
+Albeit qualified constants with `class` and `module` keywords may technically
+work with autoloading in some cases, it is preferable to use relative constants
+instead:
+
+```ruby
+module Admin
+ class UsersController < ApplicationController
+ def index
+ @users = User.all
+ end
+ end
+end
+```
+
+### Autoloading and STI
+
+Single Table Inheritance (STI) is a feature of Active Record that enables
+storing a hierarchy of models in one single table. The API of such models is
+aware of the hierarchy and encapsulates some common needs. For example, given
+these classes:
+
+```ruby
+# app/models/polygon.rb
+class Polygon < ActiveRecord::Base
+end
+
+# app/models/triangle.rb
+class Triangle < Polygon
+end
+
+# app/models/rectangle.rb
+class Rectangle < Polygon
+end
+```
+
+`Triangle.create` creates a row that represents a triangle, and
+`Rectangle.create` creates a row that represents a rectangle. If `id` is the
+ID of an existing record, `Polygon.find(id)` returns an object of the correct
+type.
+
+Methods that operate on collections are also aware of the hierarchy. For
+example, `Polygon.all` returns all the records of the table, because all
+rectangles and triangles are polygons. Active Record takes care of returning
+instances of their corresponding class in the result set.
+
+Types are autoloaded as needed. For example, if `Polygon.first` is a rectangle
+and `Rectangle` has not yet been loaded, Active Record autoloads it and the
+record is correctly instantiated.
+
+All good, but if instead of performing queries based on the root class we need
+to work on some subclass, things get interesting.
+
+While working with `Polygon` you do not need to be aware of all its descendants,
+because anything in the table is by definition a polygon, but when working with
+subclasses Active Record needs to be able to enumerate the types it is looking
+for. Let’s see an example.
+
+`Rectangle.all` only loads rectangles by adding a type constraint to the query:
+
+```sql
+SELECT "polygons".* FROM "polygons"
+WHERE "polygons"."type" IN ("Rectangle")
+```
+
+Let’s introduce now a subclass of `Rectangle`:
+
+```ruby
+# app/models/square.rb
+class Square < Rectangle
+end
+```
+
+`Rectangle.all` should now return rectangles **and** squares:
+
+```sql
+SELECT "polygons".* FROM "polygons"
+WHERE "polygons"."type" IN ("Rectangle", "Square")
+```
+
+But there’s a caveat here: How does Active Record know that the class `Square`
+exists at all?
+
+Even if the file `app/models/square.rb` exists and defines the `Square` class,
+if no code yet used that class, `Rectangle.all` issues the query
+
+```sql
+SELECT "polygons".* FROM "polygons"
+WHERE "polygons"."type" IN ("Rectangle")
+```
+
+That is not a bug, the query includes all *known* descendants of `Rectangle`.
+
+A way to ensure this works correctly regardless of the order of execution is to
+load the leaves of the tree by hand at the bottom of the file that defines the
+root class:
+
+```ruby
+# app/models/polygon.rb
+class Polygon < ActiveRecord::Base
+end
+require_dependency ‘square’
+```
+
+Only the leaves that are **at least grandchildren** need to be loaded this
+way. Direct subclasses do not need to be preloaded. If the hierarchy is
+deeper, intermediate classes will be autoloaded recursively from the bottom
+because their constant will appear in the class definitions as superclass.
+
+### Autoloading and `require`
+
+Files defining constants to be autoloaded should never be `require`d:
+
+```ruby
+require 'user' # DO NOT DO THIS
+
+class UsersController < ApplicationController
+ ...
+end
+```
+
+There are two possible gotchas here in development mode:
+
+1. If `User` is autoloaded before reaching the `require`, `app/models/user.rb`
+runs again because `load` does not update `$LOADED_FEATURES`.
+
+2. If the `require` runs first Rails does not mark `User` as an autoloaded
+constant and changes to `app/models/user.rb` aren't reloaded.
+
+Just follow the flow and use constant autoloading always, never mix
+autoloading and `require`. As a last resort, if some file absolutely needs to
+load a certain file use `require_dependency` to play nice with constant
+autoloading. This option is rarely needed in practice, though.
+
+Of course, using `require` in autoloaded files to load ordinary 3rd party
+libraries is fine, and Rails is able to distinguish their constants, they are
+not marked as autoloaded.
+
+### Autoloading and Initializers
+
+Consider this assignment in `config/initializers/set_auth_service.rb`:
+
+```ruby
+AUTH_SERVICE = if Rails.env.production?
+ RealAuthService
+else
+ MockedAuthService
+end
+```
+
+The purpose of this setup would be that the application uses the class that
+corresponds to the environment via `AUTH_SERVICE`. In development mode
+`MockedAuthService` gets autoloaded when the initializer runs. Let’s suppose
+we do some requests, change its implementation, and hit the application again.
+To our surprise the changes are not reflected. Why?
+
+As [we saw earlier](#constant-reloading), Rails removes autoloaded constants,
+but `AUTH_SERVICE` stores the original class object. Stale, non-reachable
+using the original constant, but perfectly functional.
+
+The following code summarizes the situation:
+
+```ruby
+class C
+ def quack
+ 'quack!'
+ end
+end
+
+X = C
+Object.instance_eval { remove_const(:C) }
+X.new.quack # => quack!
+X.name # => C
+C # => uninitialized constant C (NameError)
+```
+
+Because of that, it is not a good idea to autoload constants on application
+initialization.
+
+In the case above we could implement a dynamic access point:
+
+```ruby
+# app/models/auth_service.rb
+class AuthService
+ if Rails.env.production?
+ def self.instance
+ RealAuthService
+ end
+ else
+ def self.instance
+ MockedAuthService
+ end
+ end
+end
+```
+
+and have the application use `AuthService.instance` instead. `AuthService`
+would be loaded on demand and be autoload-friendly.
+
+### `require_dependency` and Initializers
+
+As we saw before, `require_dependency` loads files in an autoloading-friendly
+way. Normally, though, such a call does not make sense in an initializer.
+
+One could think about doing some [`require_dependency`](#require-dependency)
+calls in an initializer to make sure certain constants are loaded upfront, for
+example as an attempt to address the [gotcha with STIs](#autoloading-and-sti).
+
+Problem is, in development mode [autoloaded constants are wiped](#constant-reloading)
+if there is any relevant change in the file system. If that happens then
+we are in the very same situation the initializer wanted to avoid!
+
+Calls to `require_dependency` have to be strategically written in autoloaded
+spots.
+
+### When Constants aren't Missed
+
+#### Relative References
+
+Let's consider a flight simulator. The application has a default flight model
+
+```ruby
+# app/models/flight_model.rb
+class FlightModel
+end
+```
+
+that can be overridden by each airplane, for instance
+
+```ruby
+# app/models/bell_x1/flight_model.rb
+module BellX1
+ class FlightModel < FlightModel
+ end
+end
+
+# app/models/bell_x1/aircraft.rb
+module BellX1
+ class Aircraft
+ def initialize
+ @flight_model = FlightModel.new
+ end
+ end
+end
+```
+
+The initializer wants to create a `BellX1::FlightModel` and nesting has
+`BellX1`, that looks good. But if the default flight model is loaded and the
+one for the Bell-X1 is not, the interpreter is able to resolve the top-level
+`FlightModel` and autoloading is thus not triggered for `BellX1::FlightModel`.
+
+That code depends on the execution path.
+
+These kind of ambiguities can often be resolved using qualified constants:
+
+```ruby
+module BellX1
+ class Plane
+ def flight_model
+ @flight_model ||= BellX1::FlightModel.new
+ end
+ end
+end
+```
+
+Also, `require_dependency` is a solution:
+
+```ruby
+require_dependency 'bell_x1/flight_model'
+
+module BellX1
+ class Plane
+ def flight_model
+ @flight_model ||= FlightModel.new
+ end
+ end
+end
+```
+
+#### Qualified References
+
+Given
+
+```ruby
+# app/models/hotel.rb
+class Hotel
+end
+
+# app/models/image.rb
+class Image
+end
+
+# app/models/hotel/image.rb
+class Hotel
+ class Image < Image
+ end
+end
+```
+
+the expression `Hotel::Image` is ambiguous because it depends on the execution
+path.
+
+As [we saw before](#resolution-algorithm-for-qualified-constants), Ruby looks
+up the constant in `Hotel` and its ancestors. If `app/models/image.rb` has
+been loaded but `app/models/hotel/image.rb` hasn't, Ruby does not find `Image`
+in `Hotel`, but it does in `Object`:
+
+```
+$ bin/rails r 'Image; p Hotel::Image' 2>/dev/null
+Image # NOT Hotel::Image!
+```
+
+The code evaluating `Hotel::Image` needs to make sure
+`app/models/hotel/image.rb` has been loaded, possibly with
+`require_dependency`.
+
+In these cases the interpreter issues a warning though:
+
+```
+warning: toplevel constant Image referenced by Hotel::Image
+```
+
+This surprising constant resolution can be observed with any qualifying class:
+
+```
+2.1.5 :001 > String::Array
+(irb):1: warning: toplevel constant Array referenced by String::Array
+ => Array
+```
+
+WARNING. To find this gotcha the qualifying namespace has to be a class,
+`Object` is not an ancestor of modules.
+
+### Autoloading within Singleton Classes
+
+Let's suppose we have these class definitions:
+
+```ruby
+# app/models/hotel/services.rb
+module Hotel
+ class Services
+ end
+end
+
+# app/models/hotel/geo_location.rb
+module Hotel
+ class GeoLocation
+ class << self
+ Services
+ end
+ end
+end
+```
+
+If `Hotel::Services` is known by the time `app/models/hotel/geo_location.rb`
+is being loaded, `Services` is resolved by Ruby because `Hotel` belongs to the
+nesting when the singleton class of `Hotel::GeoLocation` is opened.
+
+But if `Hotel::Services` is not known, Rails is not able to autoload it, the
+application raises `NameError`.
+
+The reason is that autoloading is triggered for the singleton class, which is
+anonymous, and as [we saw before](#generic-procedure), Rails only checks the
+top-level namespace in that edge case.
+
+An easy solution to this caveat is to qualify the constant:
+
+```ruby
+module Hotel
+ class GeoLocation
+ class << self
+ Hotel::Services
+ end
+ end
+end
+```
+
+### Autoloading in `BasicObject`
+
+Direct descendants of `BasicObject` do not have `Object` among their ancestors
+and cannot resolve top-level constants:
+
+```ruby
+class C < BasicObject
+ String # NameError: uninitialized constant C::String
+end
+```
+
+When autoloading is involved that plot has a twist. Let's consider:
+
+```ruby
+class C < BasicObject
+ def user
+ User # WRONG
+ end
+end
+```
+
+Since Rails checks the top-level namespace `User` gets autoloaded just fine the
+first time the `user` method is invoked. You only get the exception if the
+`User` constant is known at that point, in particular in a *second* call to
+`user`:
+
+```ruby
+c = C.new
+c.user # surprisingly fine, User
+c.user # NameError: uninitialized constant C::User
+```
+
+because it detects that a parent namespace already has the constant (see [Qualified
+References](#autoloading-algorithms-qualified-references)).
+
+As with pure Ruby, within the body of a direct descendant of `BasicObject` use
+always absolute constant paths:
+
+```ruby
+class C < BasicObject
+ ::String # RIGHT
+
+ def user
+ ::User # RIGHT
+ end
+end
+```
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index cbcd053950..716beb9178 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Caching with Rails: An overview
===============================
@@ -30,7 +32,7 @@ config.action_controller.perform_caching = true
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.
-INFO: Page Caching has been removed from Rails 4. See the [actionpack-page_caching gem](https://github.com/rails/actionpack-page_caching). See [DHH's key-based cache expiration overview](http://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method.
+INFO: Page Caching has been removed from Rails 4. See the [actionpack-page_caching gem](https://github.com/rails/actionpack-page_caching).
### Action Caching
@@ -105,7 +107,7 @@ This method generates a cache key that depends on all products and can be used i
<% end %>
```
-If you want to cache a fragment under certain condition you can use `cache_if` or `cache_unless`
+If you want to cache a fragment under certain conditions, you can use `cache_if` or `cache_unless`
```erb
<% cache_if (condition, cache_key_for_products) do %>
@@ -182,6 +184,10 @@ class ProductsController < ApplicationController
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.
+
Cache Stores
------------
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index b9014724bd..3bd84b1ce6 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
The Rails Command Line
======================
@@ -24,7 +26,7 @@ There are a few commands that are absolutely critical to your everyday usage of
* `rails dbconsole`
* `rails new app_name`
-All commands can run with ```-h or --help``` to list more information.
+All commands can run with `-h` or `--help` to list more information.
Let's create a simple Rails application to step through each of these commands in context.
@@ -61,7 +63,7 @@ With no further work, `rails server` will run our new shiny Rails app:
$ cd commandsapp
$ bin/rails server
=> Booting WEBrick
-=> Rails 4.2.0 application starting in development on http://0.0.0.0:3000
+=> Rails 5.0.0 application starting in development on http://localhost:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
[2013-08-07 02:00:01] INFO WEBrick 1.3.1
@@ -79,7 +81,7 @@ The server can be run on a different port using the `-p` option. The default dev
$ bin/rails server -e production -p 4000
```
-The `-b` option binds Rails to the specified IP, by default it is 0.0.0.0. You can run a server as a daemon by passing a `-d` option.
+The `-b` option binds Rails to the specified IP, by default it is localhost. You can run a server as a daemon by passing a `-d` option.
### `rails generate`
@@ -151,9 +153,9 @@ $ bin/rails generate controller Greetings hello
create app/helpers/greetings_helper.rb
invoke assets
invoke coffee
- create app/assets/javascripts/greetings.js.coffee
+ create app/assets/javascripts/greetings.coffee
invoke scss
- create app/assets/stylesheets/greetings.css.scss
+ create app/assets/stylesheets/greetings.scss
```
What all did this generate? It made sure a bunch of directories were in our application, and created a controller file, a view file, a functional test file, a helper for the view, a JavaScript file and a stylesheet file.
@@ -239,11 +241,11 @@ $ bin/rails generate scaffold HighScore game:string score:integer
create app/views/high_scores/show.json.jbuilder
invoke assets
invoke coffee
- create app/assets/javascripts/high_scores.js.coffee
+ create app/assets/javascripts/high_scores.coffee
invoke scss
- create app/assets/stylesheets/high_scores.css.scss
+ create app/assets/stylesheets/high_scores.scss
invoke scss
- identical app/assets/stylesheets/scaffolds.css.scss
+ identical app/assets/stylesheets/scaffolds.scss
```
The generator checks that there exist the directories for models, controllers, helpers, layouts, functional and unit tests, stylesheets, creates the views, controller, model and database migration for HighScore (creating the `high_scores` table and fields), takes care of the route for the **resource**, and new tests for everything.
@@ -284,7 +286,7 @@ If you wish to test out some code without changing any data, you can do that by
```bash
$ bin/rails console --sandbox
-Loading development environment in sandbox (Rails 4.2.0)
+Loading development environment in sandbox (Rails 5.0.0)
Any modifications you make will be rolled back on exit
irb(main):001:0>
```
@@ -336,6 +338,12 @@ You can specify the environment in which the `runner` command should operate usi
$ bin/rails runner -e staging "Model.long_running_method"
```
+You can even execute ruby code written in a file with runner.
+
+```bash
+$ bin/rails runner lib/code_to_be_run.rb
+```
+
### `rails destroy`
Think of `destroy` as the opposite of `generate`. It'll figure out what generate did, and undo it.
@@ -368,8 +376,7 @@ Rake is Ruby Make, a standalone Ruby utility that replaces the Unix utility 'mak
You can get a list of Rake tasks available to you, which will often depend on your current directory, by typing `rake --tasks`. Each task has a description, and should help you find the thing you need.
-To get the full backtrace for running rake task you can pass the option
-```--trace``` to command line, for example ```rake db:create --trace```.
+To get the full backtrace for running rake task you can pass the option `--trace` to command line, for example `rake db:create --trace`.
```bash
$ bin/rake --tasks
@@ -382,10 +389,10 @@ rake db:create # Create the database from config/database.yml for the c
rake log:clear # Truncates all *.log files in log/ to zero bytes (specify which logs with LOGS=test,development)
rake middleware # Prints out your Rack middleware stack
...
-rake tmp:clear # Clear session, cache, and socket files from tmp/ (narrow w/ tmp:sessions:clear, tmp:cache:clear, tmp:sockets:clear)
-rake tmp:create # Creates tmp directories for sessions, cache, sockets, and pids
+rake tmp:clear # Clear cache and socket files from tmp/ (narrow w/ tmp:cache:clear, tmp:sockets:clear)
+rake tmp:create # Creates tmp directories for cache, sockets, and pids
```
-INFO: You can also use ```rake -T``` to get the list of tasks.
+INFO: You can also use `rake -T` to get the list of tasks.
### `about`
@@ -394,16 +401,11 @@ INFO: You can also use ```rake -T``` to get the list of tasks.
```bash
$ bin/rake about
About your application's environment
-Ruby version 1.9.3 (x86_64-linux)
-RubyGems version 1.3.6
-Rack version 1.3
-Rails version 4.2.0
+Rails version 5.0.0
+Ruby version 2.2.1 (x86_64-linux)
+RubyGems version 2.4.5
+Rack version 1.6
JavaScript Runtime Node.js (V8)
-Active Record version 4.2.0
-Action Pack version 4.2.0
-Action View version 4.2.0
-Action Mailer version 4.2.0
-Active Support version 4.2.0
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
Application root /home/foobar/commandsapp
Environment development
@@ -413,10 +415,7 @@ Database schema version 20110805173523
### `assets`
-You can precompile the assets in `app/assets` using `rake assets:precompile`,
-and remove older compiled assets using `rake assets:clean`. The `assets:clean`
-task allows for rolling deploys that may still be linking to an old asset while
-the new assets are being built.
+You can precompile the assets in `app/assets` using `rake assets:precompile`, and remove older compiled assets using `rake assets:clean`. The `assets:clean` task allows for rolling deploys that may still be linking to an old asset while the new assets are being built.
If you want to clear `public/assets` completely, you can use `rake assets:clobber`.
@@ -426,14 +425,6 @@ The most common tasks of the `db:` Rake namespace are `migrate` and `create`, an
More information about migrations can be found in the [Migrations](migrations.html) guide.
-### `doc`
-
-The `doc:` namespace has the tools to generate documentation for your app, API documentation, guides. Documentation can also be stripped which is mainly useful for slimming your codebase, like if you're writing a Rails application for an embedded platform.
-
-* `rake doc:app` generates documentation for your application in `doc/app`.
-* `rake doc:guides` generates Rails guides in `doc/guides`.
-* `rake doc:rails` generates API documentation for Rails in `doc/api`.
-
### `notes`
`rake notes` will search through your code for comments beginning with FIXME, OPTIMIZE or TODO. The search is done in files with extension `.builder`, `.rb`, `.rake`, `.yml`, `.yaml`, `.ruby`, `.css`, `.js` and `.erb` for both default and custom annotations.
@@ -479,7 +470,7 @@ app/models/article.rb:
NOTE. When using specific annotations and custom annotations, the annotation name (FIXME, BUG etc) is not displayed in the output lines.
-By default, `rake notes` will look in the `app`, `config`, `lib`, `bin` and `test` directories. If you would like to search other directories, you can provide them as a comma separated list in an environment variable `SOURCE_ANNOTATION_DIRECTORIES`.
+By default, `rake notes` will look in the `app`, `config`, `db`, `lib` and `test` directories. If you would like to search other directories, you can provide them as a comma separated list in an environment variable `SOURCE_ANNOTATION_DIRECTORIES`.
```bash
$ export SOURCE_ANNOTATION_DIRECTORIES='spec,vendor'
@@ -503,15 +494,14 @@ Rails comes with a test suite called Minitest. Rails owes its stability to the u
### `tmp`
-The `Rails.root/tmp` directory is, like the *nix /tmp directory, the holding place for temporary files like sessions (if you're using a file store for files), process id files, and cached actions.
+The `Rails.root/tmp` directory is, like the *nix /tmp directory, the holding place for temporary files like process id files and cached actions.
The `tmp:` namespaced tasks will help you clear and create the `Rails.root/tmp` directory:
* `rake tmp:cache:clear` clears `tmp/cache`.
-* `rake tmp:sessions:clear` clears `tmp/sessions`.
* `rake tmp:sockets:clear` clears `tmp/sockets`.
-* `rake tmp:clear` clears all the three: cache, sessions and sockets.
-* `rake tmp:create` creates tmp directories for sessions, cache, sockets, and pids.
+* `rake tmp:clear` clears all cache and sockets files.
+* `rake tmp:create` creates tmp directories for cache, sockets and pids.
### Miscellaneous
@@ -536,8 +526,8 @@ end
To pass arguments to your custom rake task:
```ruby
-task :task_name, [:arg_1] => [:pre_1, :pre_2] do |t, args|
- # You can use args from here
+task :task_name, [:arg_1] => [:prerequisite_1, :prerequisite_2] do |task, args|
+ argument_1 = args.arg_1
end
```
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 58c3f217eb..43ddcf0767 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Configuring Rails Applications
==============================
@@ -108,7 +110,9 @@ numbers. New applications filter out passwords by adding the following `config.f
* `config.log_formatter` defines the formatter of the Rails logger. This option defaults to an instance of `ActiveSupport::Logger::SimpleFormatter` for all modes except production, where it defaults to `Logger::Formatter`.
-* `config.log_level` defines the verbosity of the Rails logger. This option defaults to `:debug` for all environments.
+* `config.log_level` defines the verbosity of the Rails logger. This option
+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.
@@ -120,7 +124,7 @@ numbers. New applications filter out passwords by adding the following `config.f
* `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`.
-* `config.serve_static_assets` configures Rails itself to serve static assets. Defaults to true, but in the production environment is turned off as the server software (e.g. NGINX or Apache) used to run the application should serve static assets instead. Unlike the default setting set this to true when running (absolutely not recommended!) or testing your app in production mode using WEBrick. Otherwise you won't be able use page caching and requests for files that exist regularly under the public directory will anyway hit your Rails app.
+* `config.serve_static_files` configures Rails to serve static files. This option defaults to true, but in the production environment it is set to false because the server software (e.g. NGINX or Apache) used to run the application should serve static files instead. If you are running or testing your app in production mode using WEBrick (it is not recommended to use WEBrick in production) set the option to true. Otherwise, you won't be able to use page caching and request for files that exist under the public directory.
* `config.session_store` is usually set up in `config/initializers/session_store.rb` and specifies what class to use to store the session. Possible values are `:cookie_store` which is the default, `:mem_cache_store`, and `:disabled`. The last one tells Rails not to deal with sessions. Custom session stores can also be specified:
@@ -153,7 +157,7 @@ pipeline is enabled. It is set to true by default.
* `config.assets.manifest` defines the full path to be used for the asset precompiler's manifest file. Defaults to a file named `manifest-<random>.json` in the `config.assets.prefix` directory within the public folder.
-* `config.assets.digest` enables the use of MD5 fingerprints in asset names. Set to `true` by default in `production.rb`.
+* `config.assets.digest` enables the use of MD5 fingerprints in asset names. Set to `true` by default in `production.rb` and `development.rb`.
* `config.assets.debug` disables the concatenation and compression of assets. Set to `true` by default in `development.rb`.
@@ -197,7 +201,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_assets` is `false`.
+* `ActionDispatch::Static` is used to serve static assets. Disabled if `config.serve_static_files` is `false`.
* `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.
@@ -214,7 +218,7 @@ Every Rails application comes with a standard set of middleware which it uses in
* `ActionDispatch::Flash` sets up the `flash` keys. Only available if `config.action_controller.session_store` is set to a value.
* `ActionDispatch::ParamsParser` parses out parameters from the request into `params`.
* `Rack::MethodOverride` allows the method to be overridden if `params[:_method]` is set. This is the middleware which supports the PATCH, PUT, and DELETE HTTP method types.
-* `ActionDispatch::Head` converts HEAD requests to GET requests and serves them as so.
+* `Rack::Head` converts HEAD requests to GET requests and serves them as so.
Besides these usual middleware, you can add your own by using the `config.middleware.use` method:
@@ -225,13 +229,13 @@ config.middleware.use Magical::Unicorns
This will put the `Magical::Unicorns` middleware on the end of the stack. You can use `insert_before` if you wish to add a middleware before another.
```ruby
-config.middleware.insert_before ActionDispatch::Head, Magical::Unicorns
+config.middleware.insert_before Rack::Head, Magical::Unicorns
```
There's also `insert_after` which will insert a middleware after another:
```ruby
-config.middleware.insert_after ActionDispatch::Head, Magical::Unicorns
+config.middleware.insert_after Rack::Head, Magical::Unicorns
```
Middlewares can also be completely swapped out and replaced with others:
@@ -284,7 +288,7 @@ All these configuration options are delegated to the `I18n` library.
* `config.active_record.lock_optimistically` controls whether Active Record will use optimistic locking and is true by default.
-* `config.active_record.cache_timestamp_format` controls the format of the timestamp value in the cache key. Default is `:number`.
+* `config.active_record.cache_timestamp_format` controls the format of the timestamp value in the cache key. Default is `:nsec`.
* `config.active_record.record_timestamps` is a boolean value which controls whether or not timestamping of `create` and `update` operations on a model occur. The default value is `true`.
@@ -298,6 +302,18 @@ All these configuration options are delegated to the `I18n` library.
`config/environments/production.rb` which is generated by Rails. The
default value is true if this configuration is not set.
+* `config.active_record.dump_schemas` controls which database schemas will be dumped when calling db:structure:dump.
+ The options are `:schema_search_path` (the default) which dumps any schemas listed in schema_search_path,
+ `:all` which always dumps all schemas regardless of the schema_search_path,
+ or a string of comma separated schemas.
+
+* `config.active_record.belongs_to_required_by_default` is a boolean value and controls whether `belongs_to` association is required by default.
+
+* `config.active_record.warn_on_records_fetched_greater_than` allows setting a
+ warning threshold for query result size. If the number of records returned
+ by a query exceeds the threshold, a warning is logged. This can be used to
+ identify queries which might be causing memory bloat.
+
The MySQL adapter adds one additional configuration option:
* `ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns in a MySQL database to be booleans and is true by default.
@@ -318,6 +334,8 @@ The schema dumper adds one additional configuration option:
* `config.action_controller.default_charset` specifies the default character set for all renders. The default is "utf-8".
+* `config.action_controller.include_all_helpers` configures whether all view helpers are available everywhere or are scoped to the corresponding controller. If set to `false`, `UsersHelper` methods are only available for views rendered as part of `UsersController`. If `true`, `UsersHelper` methods are available everywhere. The default is `true`.
+
* `config.action_controller.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 Controller. Set to `nil` to disable logging.
* `config.action_controller.request_forgery_protection_token` sets the token parameter name for RequestForgery. Calling `protect_from_forgery` sets it to `:authenticity_token` by default.
@@ -503,6 +521,8 @@ There are a few configuration options available in Active Support:
* `config.active_support.time_precision` sets the precision of JSON encoded time values. Defaults to `3`.
+* `config.active_support.halt_callback_chains_on_return_false` specifies whether ActiveRecord, ActiveModel and ActiveModel::Validations callback chains can be halted by returning `false` in a 'before' callback. Defaults to `true`.
+
* `ActiveSupport::Logger.silencer` is set to `false` to disable the ability to silence logging in a block. The default is `true`.
* `ActiveSupport::Cache::Store.logger` specifies the logger to use within cache store operations.
@@ -513,6 +533,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
@@ -686,7 +758,7 @@ development:
pool: 5
```
-Prepared Statements are enabled by default on PostgreSQL. You can be disable prepared statements by setting `prepared_statements` to `false`:
+Prepared Statements are enabled by default on PostgreSQL. You can disable prepared statements by setting `prepared_statements` to `false`:
```yaml
production:
@@ -811,15 +883,6 @@ server {
Be sure to read the [NGINX documentation](http://nginx.org/en/docs/) for the most up-to-date information.
-#### Considerations when deploying to a subdirectory
-
-Deploying to a subdirectory in production has implications on various parts of
-Rails.
-
-* development environment:
-* testing environment:
-* serving static assets:
-* asset pipeline:
Rails Environment Settings
--------------------------
@@ -949,6 +1012,10 @@ 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.
@@ -967,8 +1034,6 @@ Below is a comprehensive list of all the initializers found in Rails in the orde
* `load_environment_config` Loads the `config/environments` file for the current environment.
-* `append_asset_paths` Finds asset paths for the application and all attached railties and keeps a track of the available directories in `config.static_asset_paths`.
-
* `prepend_helpers_path` Adds the directory `app/helpers` from the application, railties and engines to the lookup path for helpers for the application.
* `load_config_initializers` Loads all Ruby files from `config/initializers` in the application, railties and engines. The files in this directory can be used to hold configuration settings that should be made after all of the frameworks are loaded.
@@ -1043,3 +1108,23 @@ These configuration points are then available through the configuration object:
Rails.configuration.x.super_debugger # => true
Rails.configuration.x.super_debugger.not_set # => nil
```
+
+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
+know which pages it is allowed to index.
+
+Rails creates this file for you inside the `/public` folder. By default, it allows
+search engines to index all pages of your application. If you want to block
+indexing on all pages of you application, use this:
+
+```
+User-agent: *
+Disallow: /
+```
+
+To block just specific pages, it's necessary to use a more complex syntax. Learn
+it on the [official documentation](http://www.robotstxt.org/robotstxt.html).
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index 4eb360cc7a..018100c316 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Contributing to Ruby on Rails
=============================
@@ -24,7 +26,7 @@ NOTE: Bugs in the most recent released version of Ruby on Rails are likely to ge
### Creating a Bug Report
-If you've found a problem in Ruby on Rails which is not a security risk, do a search in GitHub under [Issues](https://github.com/rails/rails/issues) in case it has already been reported. If you do not find any issue addressing it you may proceed to [open a new one](https://github.com/rails/rails/issues/new). (See the next section for reporting security issues.)
+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.
@@ -171,6 +173,14 @@ $ git checkout -b my_new_branch
It doesn't matter much what name you use, because this branch will only exist on your local computer and your personal repository on GitHub. It won't be part of the Rails Git repository.
+### Bundle install
+
+Install the required gems.
+
+```bash
+$ bundle install
+```
+
### Running an Application Against Your Local Branch
In case you need a dummy Rails app to test changes, the `--dev` flag of `rails new` generates an application that uses your local branch:
@@ -205,7 +215,7 @@ Rails follows a simple set of coding style conventions:
* Use Ruby >= 1.9 syntax for hashes. Prefer `{ a: :b }` over `{ :a => :b }`.
* Prefer `&&`/`||` over `and`/`or`.
* Prefer class << self over self.method for class methods.
-* Use `MyClass.my_method(my_arg)` not `my_method( my_arg )` or `my_method my_arg`.
+* Use `my_method(my_arg)` not `my_method( my_arg )` or `my_method my_arg`.
* Use `a = b` and not `a=b`.
* Use assert_not methods instead of refute.
* Prefer `method { do_stuff }` instead of `method{do_stuff}` for single-line blocks.
@@ -234,11 +244,11 @@ This will generate a report with the following information:
```
Calculating -------------------------------------
- addition 69114 i/100ms
- addition with send 64062 i/100ms
+ addition 132.013k i/100ms
+ addition with send 125.413k i/100ms
-------------------------------------------------
- addition 5307644.4 (±3.5%) i/s - 26539776 in 5.007219s
- addition with send 3702897.9 (±3.5%) i/s - 18513918 in 5.006723s
+ addition 9.677M (± 1.7%) i/s - 48.449M
+ addition with send 6.794M (± 1.1%) i/s - 33.987M
```
Please see the benchmark/ips [README](https://github.com/evanphx/benchmark-ips/blob/master/README.md) for more information.
@@ -287,7 +297,12 @@ $ 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
file.
-##### Testing Active Record
+#### Testing Active Record
+
+First, create the databases you'll need. For MySQL and PostgreSQL,
+running the SQL statements `create database activerecord_unittest` and
+`create database activerecord_unittest2` is sufficient. This is not
+necessary for SQLite3.
This is how you run the Active Record test suite only for SQLite3:
@@ -361,6 +376,10 @@ A CHANGELOG entry should summarize what was changed and should end with author's
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.
+### Updating the Gemfile.lock
+
+Some changes require the dependencies to be upgraded. In these cases make sure you run `bundle update` to get the right version of the dependency and commit the `Gemfile.lock` file within your changes.
+
### Sanity Check
You should not be the only person who looks at the code before you submit it.
diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md
index 1a647f8375..a9715fb837 100644
--- a/guides/source/debugging_rails_applications.md
+++ b/guides/source/debugging_rails_applications.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Debugging Rails Applications
============================
@@ -309,7 +311,7 @@ For example:
```bash
=> Booting WEBrick
-=> Rails 4.2.0 application starting in development on http://0.0.0.0:3000
+=> Rails 5.0.0 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
=> Notice: server is listening on all interfaces (0.0.0.0). Consider using 127.0.0.1 (--binding option)
=> Ctrl-C to shutdown server
@@ -422,11 +424,11 @@ then `backtrace` will supply the answer.
--> #0 ArticlesController.index
at /PathTo/project/test_app/app/controllers/articles_controller.rb:8
#1 ActionController::ImplicitRender.send_action(method#String, *args#Array)
- at /PathToGems/actionpack-4.2.0/lib/action_controller/metal/implicit_render.rb:4
+ at /PathToGems/actionpack-5.0.0/lib/action_controller/metal/implicit_render.rb:4
#2 AbstractController::Base.process_action(action#NilClass, *args#Array)
- at /PathToGems/actionpack-4.2.0/lib/abstract_controller/base.rb:189
+ at /PathToGems/actionpack-5.0.0/lib/abstract_controller/base.rb:189
#3 ActionController::Rendering.process_action(action#NilClass, *args#NilClass)
- at /PathToGems/actionpack-4.2.0/lib/action_controller/metal/rendering.rb:10
+ at /PathToGems/actionpack-5.0.0/lib/action_controller/metal/rendering.rb:10
...
```
@@ -438,7 +440,7 @@ context.
```
(byebug) frame 2
-[184, 193] in /PathToGems/actionpack-4.2.0/lib/abstract_controller/base.rb
+[184, 193] in /PathToGems/actionpack-5.0.0/lib/abstract_controller/base.rb
184: # is the intended way to override action dispatching.
185: #
186: # Notice that the first argument is the method to be dispatched
@@ -542,7 +544,7 @@ This way an irb session will be started 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` to help us with it.
+Let's let `byebug` help us with it.
```
(byebug) help var
@@ -655,7 +657,7 @@ instruction to be executed. In this case, the activesupport's `week` method.
```
(byebug) step
-[50, 59] in /PathToGems/activesupport-4.2.0/lib/active_support/core_ext/numeric/time.rb
+[50, 59] in /PathToGems/activesupport-5.0.0/lib/active_support/core_ext/numeric/time.rb
50: ActiveSupport::Duration.new(self * 24.hours, [[:days, self]])
51: end
52: alias :day :days
@@ -780,10 +782,10 @@ will be stopped and you will have to start it again.
`byebug` has a few available options to tweak its behaviour:
-* `set autoreload`: Reload source code when changed (default: true).
-* `set autolist`: Execute `list` command on every breakpoint (default: true).
+* `set autoreload`: Reload source code when changed (defaults: true).
+* `set autolist`: Execute `list` command on every breakpoint (defaults: true).
* `set listsize _n_`: Set number of source lines to list by default to _n_
-(default: 10)
+(defaults: 10)
* `set forcestep`: Make sure the `next` and `step` commands always move to a new
line.
@@ -798,6 +800,63 @@ set forcestep
set listsize 25
```
+Debugging with the `web-console` gem
+------------------------------------
+
+Web Console is a bit like `byebug`, but it runs in the browser. In any page you
+are developing, you can request a console in the context of a view or a
+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
+calling the `console` method.
+
+For example, in a controller:
+
+```ruby
+class PostsController < ApplicationController
+ def new
+ console
+ @post = Post.new
+ end
+end
+```
+
+Or in a view:
+
+```html+erb
+<% console %>
+
+<h2>New Post</h2>
+```
+
+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
+custom classes, create new models and inspect variables.
+
+NOTE: Only one console can be rendered per request. Otherwise `web-console`
+will raise an error on the second `console` invocation.
+
+### Inspecting Variables
+
+You can invoke `instance_variables` to list all the instance variables
+available in your context. If you want to list all the local variables, you can
+do that with `local_variables`.
+
+### Settings
+
+* `config.web_console.whitelisted_ips`: Authorized list of IPv4 or IPv6
+addresses and networks (defaults: `127.0.0.1/8, ::1`).
+* `config.web_console.whiny_requests`: Log a message when a console rendering
+is prevented (defaults: `true`).
+
+Since `web-console` evaluates plain Ruby code remotely on the server, don't try
+to use it in production.
+
Debugging Memory Leaks
----------------------
@@ -830,7 +889,7 @@ application. Here is a list of useful plugins for debugging:
* [Footnotes](https://github.com/josevalim/rails-footnotes) Every Rails page has
footnotes that give request information and link back to your source via
TextMate.
-* [Query Trace](https://github.com/ntalbott/query_trace/tree/master) Adds query
+* [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
not only runs "EXPLAIN" before each of your select queries in development, but
@@ -854,6 +913,7 @@ References
* [ruby-debug Homepage](http://bashdb.sourceforge.net/ruby-debug/home-page.html)
* [debugger Homepage](https://github.com/cldwalker/debugger)
* [byebug Homepage](https://github.com/deivid-rodriguez/byebug)
+* [web-console Homepage](https://github.com/rails/web-console)
* [Article: Debugging a Rails application with ruby-debug](http://www.sitepoint.com/debug-rails-app-ruby-debug/)
* [Ryan Bates' debugging ruby (revised) screencast](http://railscasts.com/episodes/54-debugging-ruby-revised)
* [Ryan Bates' stack trace screencast](http://railscasts.com/episodes/24-the-stack-trace)
diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md
index 3d9ec578ae..989b29956c 100644
--- a/guides/source/development_dependencies_install.md
+++ b/guides/source/development_dependencies_install.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Development Dependencies Install
================================
diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml
index 4c98d3e1d5..7ae3640937 100644
--- a/guides/source/documents.yaml
+++ b/guides/source/documents.yaml
@@ -33,7 +33,7 @@
url: active_record_querying.html
description: This guide covers the database query interface provided by Active Record.
-
- name: Active Model basics
+ name: Active Model Basics
url: active_model_basics.html
description: This guide covers the use of model classes without Active Record.
work_in_progress: true
@@ -85,9 +85,9 @@
description: This guide provides you with all you need to get started in creating, enqueueing and executing background jobs.
-
name: Testing Rails Applications
- url: testing.html
work_in_progress: true
- description: This is a rather comprehensive guide to doing both unit and functional tests in Rails. It covers everything from 'What is a test?' to the testing APIs. Enjoy.
+ url: testing.html
+ description: This is a rather comprehensive guide to the various testing facilities in Rails. It covers everything from 'What is a test?' to the testing APIs. Enjoy.
-
name: Securing Rails Applications
url: security.html
@@ -113,15 +113,25 @@
url: working_with_javascript_in_rails.html
description: This guide covers the built-in Ajax/JavaScript functionality of Rails.
-
- name: Getting Started with Engines
- url: engines.html
- description: This guide explains how to write a mountable engine.
- work_in_progress: true
- -
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
+ -
+ name: Autoloading and Reloading Constants
+ url: autoloading_and_reloading_constants.html
+ description: This guide documents how autoloading and reloading constants work.
+ -
+ name: Active Support Instrumentation
+ work_in_progress: true
+ url: active_support_instrumentation.html
+ description: This guide explains how to use the instrumentation API inside of Active Support to measure events inside of Rails and other Ruby code.
+ -
+ name: Profiling Rails Applications
+ work_in_progress: true
+ url: profiling.html
+ description: This guide explains how to profile your Rails applications to improve performance.
+
-
name: Extending Rails
documents:
@@ -138,6 +148,11 @@
name: Creating and Customizing Rails Generators
url: generators.html
description: This guide covers the process of adding a brand new generator to your extension or providing an alternative to an element of a built-in Rails generator (such as providing alternative test stubs for the scaffold generator).
+ -
+ name: Getting Started with Engines
+ url: engines.html
+ description: This guide explains how to write a mountable engine.
+ work_in_progress: true
-
name: Contributing to Ruby on Rails
documents:
@@ -171,7 +186,6 @@
name: Ruby on Rails 4.2 Release Notes
url: 4_2_release_notes.html
description: Release notes for Rails 4.2.
- work_in_progress: true
-
name: Ruby on Rails 4.1 Release Notes
url: 4_1_release_notes.html
diff --git a/guides/source/engines.md b/guides/source/engines.md
index 21ac941ac0..84017d5e13 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Getting Started with Engines
============================
@@ -32,7 +34,7 @@ directory structure, and are both generated using the `rails plugin new`
generator. The difference is that an engine is considered a "full plugin" by
Rails (as indicated by the `--full` option that's passed to the generator
command). We'll actually be using the `--mountable` option here, which includes
-all the features of `--full`, and then some. This guide will refer to these
+all the features of `--full`, and then some. This guide will refer to these
"full plugins" simply as "engines" throughout. An engine **can** be a plugin,
and a plugin **can** be an engine.
@@ -888,7 +890,9 @@ engine this would be done by changing
`app/controllers/blorgh/application_controller.rb` to look like:
```ruby
-class Blorgh::ApplicationController < ApplicationController
+module Blorgh
+ class ApplicationController < ::ApplicationController
+ end
end
```
@@ -1036,31 +1040,42 @@ functionality, especially controllers. This means that if you were to make a
typical `GET` to a controller in a controller's functional test like this:
```ruby
-get :index
+module Blorgh
+ class FooControllerTest < ActionController::TestCase
+ def test_index
+ get :index
+ ...
+ end
+ end
+end
```
It may not function correctly. This is because the application doesn't know how
to route these requests to the engine unless you explicitly tell it **how**. To
-do this, you must also pass the `:use_route` option as a parameter on these
-requests:
+do this, you must set the `@routes` instance variable to the engine's route set
+in your setup code:
```ruby
-get :index, use_route: :blorgh
+module Blorgh
+ class FooControllerTest < ActionController::TestCase
+ setup do
+ @routes = Engine.routes
+ end
+
+ def test_index
+ get :index
+ ...
+ end
+ end
+end
```
This tells the application that you still want to perform a `GET` request to the
`index` action of this controller, but you want to use the engine's route to get
there, rather than the application's one.
-Another way to do this is to assign the `@routes` instance variable to `Engine.routes` in your test setup:
-
-```ruby
-setup do
- @routes = Engine.routes
-end
-```
-
-This will also ensure url helpers for the engine will work as expected in your tests.
+This also ensures that the engine's URL helpers will work as expected in your
+tests.
Improving engine functionality
------------------------------
@@ -1155,7 +1170,7 @@ end
Using `Class#class_eval` is great for simple adjustments, but for more complex
class modifications, you might want to consider using [`ActiveSupport::Concern`]
-(http://edgeapi.rubyonrails.org/classes/ActiveSupport/Concern.html).
+(http://api.rubyonrails.org/classes/ActiveSupport/Concern.html).
ActiveSupport::Concern manages load order of interlinked dependent modules and
classes at run time allowing you to significantly modularize your code.
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index cb45e38614..853227e2a1 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Form Helpers
============
@@ -96,7 +98,15 @@ form_tag({controller: "people", action: "search"}, method: "get", class: "nifty_
### Helpers for Generating Form Elements
-Rails provides a series of helpers for generating form elements such as checkboxes, text fields, and radio buttons. These basic helpers, with names ending in "_tag" (such as `text_field_tag` and `check_box_tag`), generate just a single `<input>` element. The first parameter to these is always the name of the input. When the form is submitted, the name will be passed along with the form data, and will make its way to the `params` hash in the controller with the value entered by the user for that field. For example, if the form contains `<%= text_field_tag(:query) %>`, then you would be able to get the value of this field in the controller with `params[:query]`.
+Rails provides a series of helpers for generating form elements such as
+checkboxes, text fields, and radio buttons. These basic helpers, with names
+ending in `_tag` (such as `text_field_tag` and `check_box_tag`), generate just a
+single `<input>` element. The first parameter to these is always the name of the
+input. When the form is submitted, the name will be passed along with the form
+data, and will make its way to the `params` hash in the controller with the
+value entered by the user for that field. For example, if the form contains `<%=
+text_field_tag(:query) %>`, then you would be able to get the value of this
+field in the controller with `params[:query]`.
When naming inputs, Rails uses certain conventions that make it possible to submit parameters with non-scalar values such as arrays or hashes, which will also be accessible in `params`. You can read more about them in [chapter 7 of this guide](#understanding-parameter-naming-conventions). For details on the precise usage of these helpers, please refer to the [API documentation](http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html).
@@ -231,7 +241,7 @@ Upon form submission the value entered by the user will be stored in `params[:pe
WARNING: You must pass the name of an instance variable, i.e. `:person` or `"person"`, not an actual instance of your model object.
-Rails provides helpers for displaying the validation errors associated with a model object. These are covered in detail by the [Active Record Validations](./active_record_validations.html#displaying-validation-errors-in-views) guide.
+Rails provides helpers for displaying the validation errors associated with a model object. These are covered in detail by the [Active Record Validations](active_record_validations.html#displaying-validation-errors-in-views) guide.
### Binding a Form to an Object
@@ -265,14 +275,14 @@ There are a few things to note here:
The resulting HTML is:
```html
-<form accept-charset="UTF-8" action="/articles/create" method="post" class="nifty_form">
+<form accept-charset="UTF-8" action="/articles" method="post" class="nifty_form">
<input id="article_title" name="article[title]" type="text" />
<textarea id="article_body" name="article[body]" cols="60" rows="12"></textarea>
<input name="commit" type="submit" value="Create" />
</form>
```
-The name passed to `form_for` controls the key used in `params` to access the form's values. Here the name is `article` and so all the inputs have names of the form `article[attribute_name]`. Accordingly, in the `create` action `params[:article]` will be a hash with keys `:title` and `:body`. You can read more about the significance of input names in the parameter_names section.
+The name passed to `form_for` controls the key used in `params` to access the form's values. Here the name is `article` and so all the inputs have names of the form `article[attribute_name]`. Accordingly, in the `create` action `params[:article]` will be a hash with keys `:title` and `:body`. You can read more about the significance of input names in the [parameter_names section](#understanding-parameter-naming-conventions).
The helper methods called on the form builder are identical to the model object helpers except that it is not necessary to specify which object is being edited since this is already managed by the form builder.
@@ -290,7 +300,7 @@ You can create a similar binding without actually creating `<form>` tags with th
which produces the following output:
```html
-<form accept-charset="UTF-8" action="/people/create" class="new_person" id="new_person" method="post">
+<form accept-charset="UTF-8" action="/people" class="new_person" id="new_person" method="post">
<input id="person_name" name="person[name]" type="text" />
<input id="contact_detail_phone_number" name="contact_detail[phone_number]" type="text" />
</form>
@@ -677,7 +687,14 @@ class LabellingFormBuilder < ActionView::Helpers::FormBuilder
end
```
-If you reuse this frequently you could define a `labeled_form_for` helper that automatically applies the `builder: LabellingFormBuilder` option.
+If you reuse this frequently you could define a `labeled_form_for` helper that automatically applies the `builder: LabellingFormBuilder` option:
+
+```ruby
+def labeled_form_for(record, options = {}, &block)
+ options.merge! builder: LabellingFormBuilder
+ form_for record, options, &block
+end
+```
The form builder used also determines what happens when you do
@@ -712,7 +729,7 @@ The two basic structures are arrays and hashes. Hashes mirror the syntax used fo
the `params` hash will contain
-```erb
+```ruby
{'person' => {'name' => 'Henry'}}
```
diff --git a/guides/source/generators.md b/guides/source/generators.md
index f5d2c67cb4..14f451cbc9 100644
--- a/guides/source/generators.md
+++ b/guides/source/generators.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Creating and Customizing Rails Generators & Templates
=====================================================
@@ -197,11 +199,11 @@ $ bin/rails generate scaffold User name:string
create app/views/users/show.json.jbuilder
invoke assets
invoke coffee
- create app/assets/javascripts/users.js.coffee
+ create app/assets/javascripts/users.coffee
invoke scss
- create app/assets/stylesheets/users.css.scss
+ create app/assets/stylesheets/users.scss
invoke scss
- create app/assets/stylesheets/scaffolds.css.scss
+ create app/assets/stylesheets/scaffolds.scss
```
Looking at this output, it's easy to understand how generators work in Rails 3.0 and above. The scaffold generator doesn't actually generate anything, it just invokes others to do the work. This allows us to add/replace/remove any of those invocations. For instance, the scaffold generator invokes the scaffold_controller generator, which invokes erb, test_unit and helper generators. Since each generator has a single responsibility, they are easy to reuse, avoiding code duplication.
@@ -407,7 +409,7 @@ $ bin/rails generate scaffold Comment body:text
create app/views/comments/show.json.jbuilder
invoke assets
invoke coffee
- create app/assets/javascripts/comments.js.coffee
+ create app/assets/javascripts/comments.coffee
invoke scss
```
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index 92f8ef5b08..db4e81e32e 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Getting Started with Rails
==========================
@@ -90,18 +92,18 @@ current version of Ruby installed:
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
```
-If you don't have Ruby installed have a look at
-[ruby-lang.org](https://www.ruby-lang.org/en/installation/) for possible ways to
-install Ruby on your platform.
-
-Many popular UNIX-like OSes ship with an acceptable version of SQLite3. Windows
-users and others can find installation instructions at [the SQLite3 website](https://www.sqlite.org).
+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
+at the [SQLite3 website](https://www.sqlite.org).
Verify that it is correctly installed and in your PATH:
```bash
@@ -123,7 +125,7 @@ run the following:
$ rails --version
```
-If it says something like "Rails 4.2.0", you are ready to continue.
+If it says something like "Rails 5.0.0", you are ready to continue.
### Creating the Blog Application
@@ -165,14 +167,14 @@ of the files and folders that Rails created by default:
|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.|
-|Gemfile<br>Gemfile.lock|These files allow you to specify what gem dependencies are needed for your Rails application. These files are used by the Bundler gem. For more information about Bundler, see [the Bundler website](http://bundler.io).|
+|Gemfile<br>Gemfile.lock|These files allow you to specify what gem dependencies are needed for your Rails application. These files are used by the Bundler gem. For more information about Bundler, see the [Bundler website](http://bundler.io).|
|lib/|Extended modules for your application.|
|log/|Application log files.|
|public/|The only folder seen by the world as-is. Contains static files and compiled assets.|
|Rakefile|This file locates and loads tasks that can be run from the command line. The task definitions are defined throughout the components of Rails. Rather than changing Rakefile, you should add your own tasks by adding files to the lib/tasks directory of your application.|
|README.rdoc|This is a brief instruction manual for your application. You should edit this file to tell others what your application does, how to set it up, and so on.|
|test/|Unit tests, fixtures, and other test apparatus. These are covered in [Testing Rails Applications](testing.html).|
-|tmp/|Temporary files (like cache, pid, and session files).|
+|tmp/|Temporary files (like cache and pid files).|
|vendor/|A place for all third-party code. In a typical Rails application this includes vendored gems.|
Hello, Rails!
@@ -191,6 +193,9 @@ following in the `blog` directory:
$ bin/rails server
```
+TIP: If you are using Windows, you have to pass the scripts under the `bin`
+folder directly to the Ruby interpreter e.g. `ruby bin\rails server`.
+
TIP: Compiling CoffeeScript and JavaScript asset compression requires you
have a JavaScript runtime available on your system, in the absence
of a runtime you will see an `execjs` error during asset compilation.
@@ -199,7 +204,7 @@ Rails adds the `therubyracer` gem to the generated `Gemfile` in a
commented line for new apps and you can uncomment if you need it.
`therubyrhino` is the recommended runtime for JRuby users and is added by
default to the `Gemfile` in apps generated under JRuby. You can investigate
-all the supported runtimes at [ExecJS](https://github.com/sstephenson/execjs#readme).
+all the supported runtimes at [ExecJS](https://github.com/rails/execjs#readme).
This will fire up WEBrick, a web server distributed with Ruby by default. To see
your application in action, open a browser window and navigate to
@@ -259,9 +264,9 @@ invoke helper
create app/helpers/welcome_helper.rb
invoke assets
invoke coffee
-create app/assets/javascripts/welcome.js.coffee
+create app/assets/javascripts/welcome.coffee
invoke scss
-create app/assets/stylesheets/welcome.css.scss
+create app/assets/stylesheets/welcome.scss
```
Most important of these are of course the controller, located at
@@ -300,8 +305,9 @@ Rails.application.routes.draw do
# ...
```
-This is your application's _routing file_ which holds entries in a special DSL
-(domain-specific language) that tells Rails how to connect incoming requests to
+This is your application's _routing file_ which holds entries in a special
+[DSL (domain-specific language)](http://en.wikipedia.org/wiki/Domain-specific_language)
+that tells Rails how to connect incoming requests to
controllers and actions. This file contains many sample routes on commented
lines, and one of them actually shows you how to connect the root of your site
to a specific controller and action. Find the line beginning with `root` and
@@ -315,9 +321,9 @@ root 'welcome#index'
application to the welcome controller's index action and `get 'welcome/index'`
tells Rails to map requests to <http://localhost:3000/welcome/index> to the
welcome controller's index action. This was created earlier when you ran the
-controller generator (`rails generate controller welcome index`).
+controller generator (`bin/rails generate controller welcome index`).
-Launch the web server again if you stopped it to generate the controller (`rails
+Launch the web server again if you stopped it to generate the controller (`bin/rails
server`) and navigate to <http://localhost:3000> in your browser. You'll see the
"Hello, Rails!" message you put into `app/views/welcome/index.html.erb`,
indicating that this new route is indeed going to `WelcomeController`'s `index`
@@ -350,7 +356,7 @@ Rails.application.routes.draw do
end
```
-If you run `rake routes`, you'll see that it has defined routes for all the
+If you run `bin/rake routes`, you'll see that it has defined routes for all the
standard RESTful actions. The meaning of the prefix column (and other columns)
will be seen later, but for now notice that Rails has inferred the
singular form `article` and makes meaningful use of the distinction.
@@ -422,12 +428,12 @@ If you refresh <http://localhost:3000/articles/new> now, you'll get a new error:
This error indicates that Rails cannot find the `new` action inside the
`ArticlesController` that you just generated. This is because when controllers
are generated in Rails they are empty by default, unless you tell it
-your wanted actions during the generation process.
+your desired actions during the generation process.
To manually define an action inside a controller, all you need to do is to
define a new method inside the controller. Open
`app/controllers/articles_controller.rb` and inside the `ArticlesController`
-class, define a `new` method so that the controller now looks like this:
+class, define the `new` method so that your controller now looks like this:
```ruby
class ArticlesController < ApplicationController
@@ -444,23 +450,23 @@ With the `new` method defined in `ArticlesController`, if you refresh
You're getting this error now because Rails expects plain actions like this one
to have views associated with them to display their information. With no view
-available, Rails errors out.
+available, Rails will raise an exception.
In the above image, the bottom line has been truncated. Let's see what the full
-thing looks like:
+error message looks like:
>Missing template articles/new, application/new with {locale:[:en], formats:[:html], handlers:[:erb, :builder, :coffee]}. Searched in: * "/path/to/blog/app/views"
That's quite a lot of text! Let's quickly go through and understand what each
-part of it does.
+part of it means.
-The first part identifies what template is missing. In this case, it's the
+The first part identifies which template is missing. In this case, it's the
`articles/new` template. Rails will first look for this template. If not found,
then it will attempt to load a template called `application/new`. It looks for
one here because the `ArticlesController` inherits from `ApplicationController`.
The next part of the message contains a hash. The `:locale` key in this hash
-simply indicates what spoken language template should be retrieved. By default,
+simply indicates which spoken language template should be retrieved. By default,
this is the English - or "en" - template. The next key, `:formats` specifies the
format of template to be served in response. The default format is `:html`, and
so Rails is looking for an HTML template. The final key, `:handlers`, is telling
@@ -473,14 +479,16 @@ Templates within a basic Rails application like this are kept in a single
location, but in more complex applications it could be many different paths.
The simplest template that would work in this case would be one located at
-`app/views/articles/new.html.erb`. The extension of this file name is key: the
-first extension is the _format_ of the template, and the second extension is the
-_handler_ that will be used. Rails is attempting to find a template called
-`articles/new` within `app/views` for the application. The format for this
-template can only be `html` and the handler must be one of `erb`, `builder` or
-`coffee`. Because you want to create a new HTML form, you will be using the `ERB`
-language. Therefore the file should be called `articles/new.html.erb` and needs
-to be located inside the `app/views` directory of the application.
+`app/views/articles/new.html.erb`. The extension of this file name is important:
+the first extension is the _format_ of the template, and the second extension
+is the _handler_ that will be used. Rails is attempting to find a template
+called `articles/new` within `app/views` for the application. The format for
+this template can only be `html` and the handler must be one of `erb`,
+`builder` or `coffee`. Because you want to create a new HTML form, you will be
+using the `ERB` language which is designed to embed Ruby in HTML.
+
+Therefore the file should be called `articles/new.html.erb` and needs to be
+located inside the `app/views` directory of the application.
Go ahead now and create a new file at `app/views/articles/new.html.erb` and
write this content in it:
@@ -548,7 +556,7 @@ this:
In this example, the `articles_path` helper is passed to the `:url` option.
To see what Rails will do with this, we look back at the output of
-`rake routes`:
+`bin/rake routes`:
```bash
$ bin/rake routes
@@ -658,15 +666,15 @@ models, as that will be done automatically by Active Record.
### Running a Migration
-As we've just seen, `rails generate model` created a _database migration_ file
+As we've just seen, `bin/rails generate model` created a _database migration_ file
inside the `db/migrate` directory. Migrations are Ruby classes that are
designed to make it simple to create and modify database tables. Rails uses
rake commands to run migrations, and it's possible to undo a migration after
it's been applied to your database. Migration filenames include a timestamp to
ensure that they're processed in the order that they were created.
-If you look in the `db/migrate/20140120191729_create_articles.rb` file (remember,
-yours will have a slightly different name), here's what you'll find:
+If you look in the `db/migrate/YYYYMMDDHHMMSS_create_articles.rb` file
+(remember, yours will have a slightly different name), here's what you'll find:
```ruby
class CreateArticles < ActiveRecord::Migration
@@ -675,7 +683,7 @@ class CreateArticles < ActiveRecord::Migration
t.string :title
t.text :text
- t.timestamps
+ t.timestamps null: false
end
end
end
@@ -711,7 +719,7 @@ NOTE. Because you're working in the development environment by default, this
command will apply to the database defined in the `development` section of your
`config/database.yml` file. If you would like to execute migrations in another
environment, for instance in production, you must explicitly pass it when
-invoking the command: `rake db:migrate RAILS_ENV=production`.
+invoking the command: `bin/rake db:migrate RAILS_ENV=production`.
### Saving data in the controller
@@ -736,7 +744,7 @@ database columns. In the first line we do just that (remember that
`@article.save` is responsible for saving the model in the database. Finally,
we redirect the user to the `show` action, which we'll define later.
-TIP: You might be wondering why the `A` in `Article.new` is capitalized above, whereas most other references to articles in this guide have used lowercase. In this context, we are referring to the class named `Article` that is defined in `\models\article.rb`. Class names in Ruby must begin with a capital letter.
+TIP: You might be wondering why the `A` in `Article.new` is capitalized above, whereas most other references to articles in this guide have used lowercase. In this context, we are referring to the class named `Article` that is defined in `app/models/article.rb`. Class names in Ruby must begin with a capital letter.
TIP: As we'll see later, `@article.save` returns a boolean indicating whether
the article was saved or not.
@@ -798,7 +806,7 @@ If you submit the form again now, Rails will complain about not finding the
`show` action. That's not very useful though, so let's add the `show` action
before proceeding.
-As we have seen in the output of `rake routes`, the route for `show` action is
+As we have seen in the output of `bin/rake routes`, the route for `show` action is
as follows:
```
@@ -833,7 +841,7 @@ class ArticlesController < ApplicationController
A couple of things to note. We use `Article.find` to find the article we're
interested in, passing in `params[:id]` to get the `:id` parameter from the
-request. We also use an instance variable (prefixed by `@`) to hold a
+request. We also use an instance variable (prefixed with `@`) to hold a
reference to the article object. We do this because Rails will pass all instance
variables to the view.
@@ -860,7 +868,7 @@ Visit <http://localhost:3000/articles/new> and give it a try!
### Listing all articles
We still need a way to list all our articles, so let's do that.
-The route for this as per output of `rake routes` is:
+The route for this as per output of `bin/rake routes` is:
```
articles GET /articles(.:format) articles#index
@@ -903,6 +911,7 @@ And then finally, add the view for this action, located at
<tr>
<td><%= article.title %></td>
<td><%= article.text %></td>
+ <td><%= link_to 'Show', article_path(article) %></td>
</tr>
<% end %>
</table>
@@ -1266,8 +1275,8 @@ bottom of the template:
```html+erb
...
-<%= link_to 'Back', articles_path %> |
-<%= link_to 'Edit', edit_article_path(@article) %>
+<%= link_to 'Edit', edit_article_path(@article) %> |
+<%= link_to 'Back', articles_path %>
```
And here's how our app looks so far:
@@ -1279,7 +1288,7 @@ And here's how our app looks so far:
Our `edit` page looks very similar to the `new` page; in fact, they
both share the same code for displaying the form. Let's remove this
duplication by using a view partial. By convention, partial files are
-prefixed by an underscore.
+prefixed with an underscore.
TIP: You can read more about partials in the
[Layouts and Rendering in Rails](layouts_and_rendering.html) guide.
@@ -1354,7 +1363,7 @@ Then do the same for the `app/views/articles/edit.html.erb` view:
We're now ready to cover the "D" part of CRUD, deleting articles from the
database. Following the REST convention, the route for
-deleting articles as per output of `rake routes` is:
+deleting articles as per output of `bin/rake routes` is:
```ruby
DELETE /articles/:id(.:format) articles#destroy
@@ -1470,16 +1479,20 @@ Finally, add a 'Destroy' link to your `index` action template
```
Here we're using `link_to` in a different way. We pass the named route as the
-second argument, and then the options as another argument. The `:method` and
-`:'data-confirm'` options are used as HTML5 attributes so that when the link is
-clicked, Rails will first show a confirm dialog to the user, and then submit the
-link with method `delete`. This is done via the JavaScript file `jquery_ujs`
-which is automatically included into your application's layout
-(`app/views/layouts/application.html.erb`) when you generated the application.
-Without this file, the confirmation dialog box wouldn't appear.
+second argument, and then the options as another argument. The `method: :delete`
+and `data: { confirm: 'Are you sure?' }` options are used as HTML5 attributes so
+that when the link is clicked, Rails will first show a confirm dialog to the
+user, and then submit the link with method `delete`. This is done via the
+JavaScript file `jquery_ujs` which is automatically included in your
+application's layout (`app/views/layouts/application.html.erb`) when you
+generated the application. Without this file, the confirmation dialog box won't
+appear.
![Confirm Dialog](images/getting_started/confirm_dialog.png)
+TIP: Learn more about jQuery Unobtrusive Adapter (jQuery UJS) on
+[Working With Javascript in Rails](working_with_javascript_in_rails.html) guide.
+
Congratulations, you can now create, show, list, update and destroy
articles.
@@ -1497,7 +1510,7 @@ comments on articles.
We're going to see the same generator that we used before when creating
the `Article` model. This time we'll create a `Comment` model to hold
-reference of article comments. Run this command in your terminal:
+reference to an article. Run this command in your terminal:
```bash
$ bin/rails generate model Comment commenter:string body:text article:references
@@ -1509,7 +1522,7 @@ This command will generate four files:
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| db/migrate/20140120201010_create_comments.rb | Migration to create the comments table in your database (your name will include a different timestamp) |
| app/models/comment.rb | The Comment model |
-| test/models/comment_test.rb | Testing harness for the comments model |
+| test/models/comment_test.rb | Testing harness for the comment model |
| test/fixtures/comments.yml | Sample comments for use in testing |
First, take a look at `app/models/comment.rb`:
@@ -1537,8 +1550,9 @@ class CreateComments < ActiveRecord::Migration
# this line adds an integer column called `article_id`.
t.references :article, index: true
- t.timestamps
+ t.timestamps null: false
end
+ add_foreign_key :comments, :articles
end
end
```
@@ -1558,6 +1572,8 @@ 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) ========================================
```
@@ -1635,8 +1651,8 @@ This creates five files and one empty directory:
| app/views/comments/ | Views of the controller are stored here |
| test/controllers/comments_controller_test.rb | The test for the controller |
| app/helpers/comments_helper.rb | A view helper file |
-| app/assets/javascripts/comment.js.coffee | CoffeeScript for the controller |
-| app/assets/stylesheets/comment.css.scss | Cascading style sheet for the controller |
+| app/assets/javascripts/comment.coffee | CoffeeScript for the controller |
+| app/assets/stylesheets/comment.scss | Cascading style sheet for the controller |
Like with any blog, our readers will create their comments directly after
reading the article, and once they have added their comment, will be sent back
@@ -1673,8 +1689,8 @@ So first, we'll wire up the Article show template
</p>
<% end %>
-<%= link_to 'Back', articles_path %> |
-<%= link_to 'Edit', edit_article_path(@article) %>
+<%= link_to 'Edit', edit_article_path(@article) %> |
+<%= link_to 'Back', articles_path %>
```
This adds a form on the `Article` show page that creates a new comment by
@@ -1754,8 +1770,8 @@ add that to the `app/views/articles/show.html.erb`.
</p>
<% end %>
-<%= link_to 'Edit Article', edit_article_path(@article) %> |
-<%= link_to 'Back to Articles', articles_path %>
+<%= link_to 'Edit', edit_article_path(@article) %> |
+<%= link_to 'Back', articles_path %>
```
Now you can add articles and comments to your blog and have them show up in the
@@ -1820,8 +1836,8 @@ following:
</p>
<% end %>
-<%= link_to 'Edit Article', edit_article_path(@article) %> |
-<%= link_to 'Back to Articles', articles_path %>
+<%= link_to 'Edit', edit_article_path(@article) %> |
+<%= link_to 'Back', articles_path %>
```
This will now render the partial in `app/views/comments/_comment.html.erb` once
@@ -1870,8 +1886,8 @@ Then you make the `app/views/articles/show.html.erb` look like the following:
<h2>Add a comment:</h2>
<%= render 'comments/form' %>
-<%= link_to 'Edit Article', edit_article_path(@article) %> |
-<%= link_to 'Back to Articles', articles_path %>
+<%= link_to 'Edit', edit_article_path(@article) %> |
+<%= link_to 'Back', articles_path %>
```
The second render just defines the partial template we want to render,
@@ -2029,28 +2045,17 @@ What's Next?
------------
Now that you've seen your first Rails application, you should feel free to
-update it and experiment on your own. But you don't have to do everything
-without help. As you need assistance getting up and running with Rails, feel
-free to consult these support resources:
+update it and experiment on your own.
+
+Remember you don't have to do everything without help. As you need assistance
+getting up and running with Rails, feel free to consult these support
+resources:
* The [Ruby on Rails Guides](index.html)
* The [Ruby on Rails Tutorial](http://railstutorial.org/book)
* The [Ruby on Rails mailing list](http://groups.google.com/group/rubyonrails-talk)
* The [#rubyonrails](irc://irc.freenode.net/#rubyonrails) channel on irc.freenode.net
-Rails also comes with built-in help that you can generate using the rake
-command-line utility:
-
-* Running `rake doc:guides` will put a full copy of the Rails Guides in the
- `doc/guides` folder of your application. Open `doc/guides/index.html` in your
- web browser to explore the Guides.
-* Running `rake doc:rails` will put a full copy of the API documentation for
- Rails in the `doc/api` folder of your application. Open `doc/api/index.html`
- in your web browser to explore the API documentation.
-
-TIP: To be able to generate the Rails Guides locally with the `doc:guides` rake
-task you need to install the RedCloth gem. Add it to your `Gemfile` and run
-`bundle install` and you're ready to go.
Configuration Gotchas
---------------------
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index f6cbc1823a..27f11ebbee 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Rails Internationalization (I18n) API
=====================================
@@ -199,7 +201,7 @@ end
If your application includes a locale switching menu, you would then have something like this in it:
```ruby
-link_to("Deutsch", "#{APP_CONFIG[:deutsch_website_url]}#{request.env['REQUEST_URI']}")
+link_to("Deutsch", "#{APP_CONFIG[:deutsch_website_url]}#{request.env['PATH_INFO']}")
```
assuming you would set `APP_CONFIG[:deutsch_website_url]` to some value like `http://www.application.de`.
@@ -528,7 +530,7 @@ Thus the following calls are equivalent:
```ruby
I18n.t 'activerecord.errors.messages.record_invalid'
-I18n.t 'errors.messages.record_invalid', scope: :active_record
+I18n.t 'errors.messages.record_invalid', scope: :activerecord
I18n.t :record_invalid, scope: 'activerecord.errors.messages'
I18n.t :record_invalid, scope: [:activerecord, :errors, :messages]
```
@@ -586,6 +588,26 @@ you can look up the `books.index.title` value **inside** `app/views/books/index.
NOTE: Automatic translation scoping by partial is only available from the `translate` view helper method.
+"Lazy" lookup can also be used in controllers:
+
+```yaml
+en:
+ books:
+ create:
+ success: Book created!
+```
+
+This is useful for setting flash messages for instance:
+
+```ruby
+class BooksController < ApplicationController
+ def create
+ # ...
+ redirect_to books_url, notice: t('.success')
+ end
+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.
@@ -626,7 +648,7 @@ entry[count == 1 ? 0 : 1]
I.e. the translation denoted as `:one` is regarded as singular, the other is used as plural (including the count being zero).
-If the lookup for the key does not return a Hash suitable for pluralization, an `18n::InvalidPluralizationData` exception is raised.
+If the lookup for the key does not return a Hash suitable for pluralization, an `I18n::InvalidPluralizationData` exception is raised.
### Setting and Passing a Locale
@@ -685,7 +707,7 @@ you can safely pass the username as set by the user:
```erb
<%# This is safe, it is going to be escaped if needed. %>
-<%= t('welcome_html', username: @current_user.username %>
+<%= t('welcome_html', username: @current_user.username) %>
```
Safe strings on the other hand are interpolated verbatim.
@@ -807,7 +829,7 @@ So, for example, instead of the default error message `"cannot be blank"` you co
| validation | with option | message | interpolation |
| ------------ | ------------------------- | ------------------------- | ------------- |
-| confirmation | - | :confirmation | - |
+| confirmation | - | :confirmation | attribute |
| acceptance | - | :accepted | - |
| presence | - | :blank | - |
| absence | - | :present | - |
@@ -827,6 +849,7 @@ So, for example, instead of the default error message `"cannot be blank"` you co
| numericality | :equal_to | :equal_to | count |
| numericality | :less_than | :less_than | count |
| numericality | :less_than_or_equal_to | :less_than_or_equal_to | count |
+| numericality | :other_than | :other_than | count |
| numericality | :only_integer | :not_an_integer | - |
| numericality | :odd | :odd | - |
| numericality | :even | :even | - |
@@ -1007,7 +1030,7 @@ In other contexts you might want to change this behavior, though. E.g. the defau
module I18n
class JustRaiseExceptionHandler < ExceptionHandler
def call(exception, locale, key, options)
- if exception.is_a?(MissingTranslation)
+ if exception.is_a?(MissingTranslationData)
raise exception.to_exception
else
super
@@ -1024,7 +1047,7 @@ This would re-raise only the `MissingTranslationData` exception, passing all oth
However, if you are using `I18n::Backend::Pluralization` this handler will also raise `I18n::MissingTranslationData: translation missing: en.i18n.plural.rule` exception that should normally be ignored to fall back to the default pluralization rule for English locale. To avoid this you may use additional check for translation key:
```ruby
-if exception.is_a?(MissingTranslation) && key.to_s != 'i18n.plural.rule'
+if exception.is_a?(MissingTranslationData) && key.to_s != 'i18n.plural.rule'
raise exception.to_exception
else
super
@@ -1070,11 +1093,8 @@ Resources
Authors
-------
-* [Sven Fuchs](http://www.workingwithrails.com/person/9963-sven-fuchs) (initial author)
-* [Karel Minařík](http://www.workingwithrails.com/person/7476-karel-mina-k)
-
-If you found this guide useful, please consider recommending its authors on [workingwithrails](http://www.workingwithrails.com).
-
+* [Sven Fuchs](http://svenfuchs.com) (initial author)
+* [Karel Minařík](http://www.karmi.cz)
Footnotes
---------
diff --git a/guides/source/initialization.md b/guides/source/initialization.md
index 53bf3039fa..c0c8b7d4b6 100644
--- a/guides/source/initialization.md
+++ b/guides/source/initialization.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
The Rails Initialization Process
================================
@@ -161,7 +163,7 @@ throwing an error message. If the command is valid, a method of the same name
is called.
```ruby
-COMMAND_WHITELIST = %(plugin generate destroy console server dbconsole application runner new version help)
+COMMAND_WHITELIST = %w(plugin generate destroy console server dbconsole application runner new version help)
def run_command!(command)
command = parse_command(command)
@@ -357,7 +359,7 @@ private
end
def create_tmp_directories
- %w(cache pids sessions sockets).each do |dir_to_make|
+ %w(cache pids sockets).each do |dir_to_make|
FileUtils.mkdir_p(File.join(Rails.root, 'tmp', dir_to_make))
end
end
@@ -373,13 +375,12 @@ private
end
```
-This is where the first output of the Rails initialization happens. This
-method creates a trap for `INT` signals, so if you `CTRL-C` the server,
-it will exit the process. As we can see from the code here, it will
-create the `tmp/cache`, `tmp/pids`, `tmp/sessions` and `tmp/sockets`
-directories. It then calls `wrapped_app` which is responsible for
-creating the Rack app, before creating and assigning an
-instance of `ActiveSupport::Logger`.
+This is where the first output of the Rails initialization happens. This method
+creates a trap for `INT` signals, so if you `CTRL-C` the server, it will exit the
+process. As we can see from the code here, it will create the `tmp/cache`,
+`tmp/pids`, and `tmp/sockets` directories. It then calls `wrapped_app` which is
+responsible for creating the Rack app, before creating and assigning an instance
+of `ActiveSupport::Logger`.
The `super` method will call `Rack::Server.start` which begins its definition like this:
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index ae16ad86cd..c57fa358d6 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Layouts and Rendering in Rails
==============================
@@ -253,7 +255,7 @@ extension for the layout file.
#### Rendering HTML
-You can send a HTML string back to the browser by using the `:html` option to
+You can send an HTML string back to the browser by using the `:html` option to
`render`:
```ruby
@@ -314,12 +316,13 @@ NOTE: Unless overridden, your response returned from this render option will be
#### Options for `render`
-Calls to the `render` method generally accept four options:
+Calls to the `render` method generally accept five options:
* `:content_type`
* `:layout`
* `:location`
* `:status`
+* `:formats`
##### The `:content_type` Option
@@ -425,6 +428,19 @@ Rails understands both numeric status codes and the corresponding symbols shown
| | 510 | :not_extended |
| | 511 | :network_authentication_required |
+NOTE: If you try to render content along with a non-content status code
+(100-199, 204, 205 or 304), it will be dropped from the response.
+
+##### The `:formats` Option
+
+Rails uses the format specified in the request (or `:html` by default). You can
+change this passing the `:formats` option with a symbol or an array:
+
+```ruby
+render formats: :xml
+render formats: [:json, :xml]
+```
+
#### Finding Layouts
To find the current layout, Rails first looks for a file in `app/views/layouts` with the same base name as the controller. For example, rendering actions from the `PhotosController` class will use `app/views/layouts/photos.html.erb` (or `app/views/layouts/photos.builder`). If there is no such controller-specific layout, Rails will use `app/views/layouts/application.html.erb` or `app/views/layouts/application.builder`. If there is no `.erb` layout, Rails will use a `.builder` layout if one exists. Rails also provides several ways to more precisely assign specific layouts to individual controllers and actions.
@@ -548,6 +564,42 @@ In this application:
* `OldArticlesController#show` will use no layout at all
* `OldArticlesController#index` will use the `old` layout
+##### Template Inheritance
+
+Similar to the Layout Inheritance logic, if a template or partial is not found in the conventional path, the controller will look for a template or partial to render in its inheritance chain. For example:
+
+```ruby
+# in app/controllers/application_controller
+class ApplicationController < ActionController::Base
+end
+
+# in app/controllers/admin_controller
+class AdminController < ApplicationController
+end
+
+# in app/controllers/admin/products_controller
+class Admin::ProductsController < AdminController
+ def index
+ end
+end
+```
+
+The lookup order for a `admin/products#index` action will be:
+
+* `app/views/admin/products/`
+* `app/views/admin/`
+* `app/views/application/`
+
+This makes `app/views/application/` a great place for your shared partials, which can then be rendered in your ERB as such:
+
+```erb
+<%# app/views/admin/products/index.html.erb %>
+<%= render @products || "empty_list" %>
+
+<%# app/views/application/_empty_list.html.erb %>
+There are no items in this list <em>yet</em>.
+```
+
#### Avoiding Double Render Errors
Sooner or later, most Rails developers will see the error message "Can only render or redirect once per action". While this is annoying, it's relatively easy to fix. Usually it happens because of a fundamental misunderstanding of the way that `render` works.
@@ -904,7 +956,10 @@ You can also specify multiple videos to play by passing an array of videos to th
This will produce:
```erb
-<video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
+<video>
+ <source src="/videos/trailer.ogg">
+ <source src="/videos/movie.ogg">
+</video>
```
#### Linking to Audio Files with the `audio_tag`
@@ -1022,6 +1077,42 @@ One way to use partials is to treat them as the equivalent of subroutines: as a
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.
+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:
+
+* `users/index.html.erb`
+
+ ```html+erb
+ <%= render "shared/search_filters", search: @q do |f| %>
+ <p>
+ Name contains: <%= f.text_field :name_contains %>
+ </p>
+ <% end %>
+ ```
+
+* `roles/index.html.erb`
+
+ ```html+erb
+ <%= render "shared/search_filters", search: @q do |f| %>
+ <p>
+ Title contains: <%= f.text_field :title_contains %>
+ </p>
+ <% end %>
+ ```
+
+* `shared/_search_filters.html.erb`
+
+ ```html+erb
+ <%= form_for(@q) do |f| %>
+ <h1>Search form:</h1>
+ <fieldset>
+ <%= yield f %>
+ </fieldset>
+ <p>
+ <%= f.submit "Search" %>
+ </p>
+ <% end %>
+ ```
+
TIP: For content that is shared among all pages in your application, you can use partials directly from layouts.
#### Partial Layouts
@@ -1070,6 +1161,36 @@ You can also pass local variables into partials, making them even more powerful
Although the same partial will be rendered into both views, Action View's submit helper will return "Create Zone" for the new action and "Update Zone" for the edit action.
+To pass a local variable to a partial in only specific cases use the `local_assigns`.
+
+* `index.html.erb`
+
+ ```erb
+ <%= render user.articles %>
+ ```
+
+* `show.html.erb`
+
+ ```erb
+ <%= render article, full: true %>
+ ```
+
+* `_articles.html.erb`
+
+ ```erb
+ <%= content_tag_for :article, article do |article| %>
+ <h2><%= article.title %></h2>
+
+ <% if local_assigns[:full] %>
+ <%= simple_format article.body %>
+ <% else %>
+ <%= truncate article.body %>
+ <% end %>
+ <% end %>
+ ```
+
+This way it is possible to use the partial without the need to declare all local variables.
+
Every partial also has a local variable with the same name as the partial (minus the underscore). You can pass an object in to this local variable via the `:object` option:
```erb
diff --git a/guides/source/maintenance_policy.md b/guides/source/maintenance_policy.md
index 050a64ddf3..50308f505a 100644
--- a/guides/source/maintenance_policy.md
+++ b/guides/source/maintenance_policy.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Maintenance Policy for Ruby on Rails
====================================
diff --git a/guides/source/nested_model_forms.md b/guides/source/nested_model_forms.md
index f0ee34cfb1..1937369776 100644
--- a/guides/source/nested_model_forms.md
+++ b/guides/source/nested_model_forms.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Rails Nested Model Forms
========================
diff --git a/guides/source/plugins.md b/guides/source/plugins.md
index 7b7eb80081..4e630a39f3 100644
--- a/guides/source/plugins.md
+++ b/guides/source/plugins.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
The Basics of Creating Rails Plugins
====================================
@@ -263,7 +265,7 @@ module Yaffle
end
end
-ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle
+ActiveRecord::Base.include(Yaffle::ActsAsYaffle)
```
You can then return to the root directory (`cd ../..`) of your plugin and rerun the tests using `rake`.
@@ -306,7 +308,7 @@ module Yaffle
end
end
-ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle
+ActiveRecord::Base.include(Yaffle::ActsAsYaffle)
```
When you run `rake`, you should see the tests all pass:
@@ -380,7 +382,7 @@ module Yaffle
end
end
-ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle
+ActiveRecord::Base.include(Yaffle::ActsAsYaffle)
```
Run `rake` one final time and you should see:
@@ -433,7 +435,7 @@ Once your README is solid, go through and add rdoc comments to all of the method
Once your comments are good to go, navigate to your plugin directory and run:
```bash
-$ bin/rake rdoc
+$ bundle exec rake rdoc
```
### References
diff --git a/guides/source/profiling.md b/guides/source/profiling.md
new file mode 100644
index 0000000000..ce093f78ba
--- /dev/null
+++ b/guides/source/profiling.md
@@ -0,0 +1,16 @@
+*DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
+A Guide to Profiling Rails Applications
+=======================================
+
+This guide covers built-in mechanisms in Rails for profiling your application.
+
+After reading this guide, you will know:
+
+* Rails profiling terminology.
+* How to write benchmark tests for your application.
+* Other benchmarking approaches and plugins.
+
+--------------------------------------------------------------------------------
+
+
diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md
index 6512b14e60..b3e1874048 100644
--- a/guides/source/rails_application_templates.md
+++ b/guides/source/rails_application_templates.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Rails Application Templates
===========================
diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md
index 8bc2678d8f..993cd5ac44 100644
--- a/guides/source/rails_on_rack.md
+++ b/guides/source/rails_on_rack.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Rails on Rack
=============
@@ -61,7 +63,6 @@ Here's how it loads the middlewares:
```ruby
def middleware
middlewares = []
- middlewares << [Rails::Rack::Debugger] if options[:debugger]
middlewares << [::Rack::ContentLength]
Hash.new(middlewares)
end
@@ -233,7 +234,7 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol
**`ActionDispatch::Static`**
-* Used to serve static assets. Disabled if `config.serve_static_assets` is `false`.
+* Used to serve static files. Disabled if `config.serve_static_files` is `false`.
**`Rack::Lock`**
@@ -253,7 +254,7 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol
**`ActionDispatch::RequestId`**
-* Makes a unique `X-Request-Id` header available to the response and enables the `ActionDispatch::Request#uuid` method.
+* Makes a unique `X-Request-Id` header available to the response and enables the `ActionDispatch::Request#request_id` method.
**`Rails::Rack::Logger`**
@@ -277,7 +278,7 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol
**`ActionDispatch::Callbacks`**
-* Runs the prepare callbacks before serving the request.
+* Provides callbacks to be executed before and after dispatching the request.
**`ActiveRecord::Migration::CheckPending`**
@@ -307,7 +308,7 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol
* Parses out parameters from the request into `params`.
-**`ActionDispatch::Head`**
+**`Rack::Head`**
* Converts HEAD requests to `GET` requests and serves them as so.
diff --git a/guides/source/routing.md b/guides/source/routing.md
index b1a287f53a..4ccc50a4d9 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Rails Routing from the Outside In
=================================
@@ -227,7 +229,7 @@ or, for a single case:
resources :articles, path: '/admin/articles'
```
-In each of these cases, the named routes remain the same as if you did not use `scope`. In the last case, the following paths map to `PostsController`:
+In each of these cases, the named routes remain the same as if you did not use `scope`. In the last case, the following paths map to `ArticlesController`:
| HTTP Verb | Path | Controller#Action | Named Helper |
| --------- | ------------------------ | -------------------- | ---------------------- |
@@ -805,6 +807,18 @@ 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':
+
+```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:
+
+```ruby
+mount AdminApp, at: '/admin'
+```
+
### Using `root`
You can specify what Rails should route `'/'` to with the `root` method:
@@ -907,7 +921,7 @@ The `:as` option lets you override the normal naming for the named route helpers
resources :photos, as: 'images'
```
-will recognize incoming paths beginning with `/photos` and route the requests to `PhotosController`, but use the value of the :as option to name the helpers.
+will recognize incoming paths beginning with `/photos` and route the requests to `PhotosController`, but use the value of the `:as` option to name the helpers.
| HTTP Verb | Path | Controller#Action | Named Helper |
| --------- | ---------------- | ----------------- | -------------------- |
@@ -1004,7 +1018,7 @@ TIP: If your application has many RESTful routes, using `:only` and `:except` to
### Translated Paths
-Using `scope`, we can alter path names generated by resources:
+Using `scope`, we can alter path names generated by `resources`:
```ruby
scope(path_names: { new: 'neu', edit: 'bearbeiten' }) do
diff --git a/guides/source/ruby_on_rails_guides_guidelines.md b/guides/source/ruby_on_rails_guides_guidelines.md
index c0438f6341..1323742488 100644
--- a/guides/source/ruby_on_rails_guides_guidelines.md
+++ b/guides/source/ruby_on_rails_guides_guidelines.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Ruby on Rails Guides Guidelines
===============================
diff --git a/guides/source/security.md b/guides/source/security.md
index 125dd82666..184af98d65 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Ruby on Rails Security Guide
============================
@@ -237,7 +239,7 @@ Or the attacker places the code into the onmouseover event handler of an image:
<img src="http://www.harmless.com/img" width="400" height="400" onmouseover="..." />
```
-There are many other possibilities, like using a `<script>` tag to make a cross-site request to a URL with a JSONP or JavaScript response. The response is executable code that the attacker can find a way to run, possibly extracting sensitive data. To protect against this data leakage, we disallow cross-site `<script>` tags. Only Ajax requests may have JavaScript responses since XmlHttpRequest is subject to the browser Same-Origin policy - meaning only your site can initiate the request.
+There are many other possibilities, like using a `<script>` tag to make a cross-site request to a URL with a JSONP or JavaScript response. The response is executable code that the attacker can find a way to run, possibly extracting sensitive data. To protect against this data leakage, we disallow cross-site `<script>` tags. Only Ajax requests may have JavaScript responses since `XMLHttpRequest` is subject to the browser Same-Origin policy - meaning only your site can initiate the request.
To protect against all other forged requests, we introduce a _required security token_ that our site knows but other sites don't know. We include the security token in requests and verify it on the server. This is a one-liner in your application controller, and is the default for newly created rails applications:
@@ -247,6 +249,15 @@ protect_from_forgery with: :exception
This will automatically include a security token in all forms and Ajax requests generated by Rails. If the security token doesn't match what was expected, an exception will be thrown.
+NOTE: By default, Rails includes jQuery and an [unobtrusive scripting adapter for
+jQuery](https://github.com/rails/jquery-ujs), which adds a header called
+`X-CSRF-Token` on every non-GET Ajax call made by jQuery with the security token.
+Without this header, non-GET Ajax requests won't be accepted by Rails. When using
+another library to make Ajax calls, it is necessary to add the security token as
+a default header for Ajax calls in your library. To get the token, have a look at
+`<meta name='csrf-token' content='THE-TOKEN'>` tag printed by
+`<%= csrf_meta_tags %>` in your application view.
+
It is common to use persistent cookies to store user information, with `cookies.permanent` for example. In this case, the cookies will not be cleared and the out of the box CSRF protection will not be effective. If you are using a different cookie store than the session for this information, you must handle what to do with it yourself:
```ruby
@@ -362,7 +373,7 @@ Refer to the Injection section for countermeasures against XSS. It is _recommend
**CSRF** Cross-Site Request Forgery (CSRF), also known as Cross-Site Reference Forgery (XSRF), is a gigantic attack method, it allows the attacker to do everything the administrator or Intranet user may do. As you have already seen above how CSRF works, here are a few examples of what attackers can do in the Intranet or admin interface.
-A real-world example is a [router reconfiguration by CSRF](http://www.h-online.com/security/Symantec-reports-first-active-attack-on-a-DSL-router--/news/102352). The attackers sent a malicious e-mail, with CSRF in it, to Mexican users. The e-mail claimed there was an e-card waiting for them, but it also contained an image tag that resulted in a HTTP-GET request to reconfigure the user's router (which is a popular model in Mexico). The request changed the DNS-settings so that requests to a Mexico-based banking site would be mapped to the attacker's site. Everyone who accessed the banking site through that router saw the attacker's fake web site and had their credentials stolen.
+A real-world example is a [router reconfiguration by CSRF](http://www.h-online.com/security/news/item/Symantec-reports-first-active-attack-on-a-DSL-router-735883.html). The attackers sent a malicious e-mail, with CSRF in it, to Mexican users. The e-mail claimed there was an e-card waiting for them, but it also contained an image tag that resulted in a HTTP-GET request to reconfigure the user's router (which is a popular model in Mexico). The request changed the DNS-settings so that requests to a Mexico-based banking site would be mapped to the attacker's site. Everyone who accessed the banking site through that router saw the attacker's fake web site and had their credentials stolen.
Another example changed Google Adsense's e-mail address and password by. If the victim was logged into Google Adsense, the administration interface for Google advertisements campaigns, an attacker could change their credentials.

@@ -699,7 +710,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](http://dev.rubyonrails.org/ticket/8895) 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](http://ha.ckers.org/blog/20070719/firefox-implements-httponly-and-is-vulnerable-to-xmlhttprequest/), though.
##### Defacement
@@ -942,7 +953,7 @@ unless params[:token].nil?
end
```
-When `params[:token]` is one of: `[]`, `[nil]`, `[nil, nil, ...]` or
+When `params[:token]` is one of: `[nil]`, `[nil, nil, ...]` or
`['foo', nil]` it will bypass the test for `nil`, but `IS NULL` or
`IN ('foo', NULL)` where clauses still will be added to the SQL query.
@@ -953,9 +964,9 @@ request:
| JSON | Parameters |
|-----------------------------------|--------------------------|
| `{ "person": null }` | `{ :person => nil }` |
-| `{ "person": [] }` | `{ :person => nil }` |
-| `{ "person": [null] }` | `{ :person => nil }` |
-| `{ "person": [null, null, ...] }` | `{ :person => nil }` |
+| `{ "person": [] }` | `{ :person => [] }` |
+| `{ "person": [null] }` | `{ :person => [] }` |
+| `{ "person": [null, null, ...] }` | `{ :person => [] }` |
| `{ "person": ["foo", null] }` | `{ :person => ["foo"] }` |
It is possible to return to old behaviour and disable `deep_munge` configuring
@@ -1018,7 +1029,6 @@ Additional Resources
The security landscape shifts and it is important to keep up to date, because missing a new vulnerability can be catastrophic. You can find additional resources about (Rails) security here:
-* The Ruby on Rails security project posts security news regularly: [http://www.rorsecurity.info](http://www.rorsecurity.info)
* 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)
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 8ad1eed72c..752ef48b16 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
A Guide to Testing Rails Applications
=====================================
@@ -29,11 +31,13 @@ Testing support was woven into the Rails fabric from the beginning. It wasn't an
By default, every Rails application has three environments: development, test, and production. The database for each one of them is configured in `config/database.yml`.
-A dedicated test database allows you to set up and interact with test data in isolation. Tests can mangle test data with confidence, that won't touch the data in the development or production databases.
+A dedicated test database allows you to set up and interact with test data in isolation. This way your tests can mangle test data with confidence, without worrying about the data in the development or production databases.
+
+Also, each environment's configuration can be modified similarly. In this case, we can modify our test environment by changing the options found in `config/environments/test.rb`.
### Rails Sets up for Testing from the Word Go
-Rails creates a `test` folder for you as soon as you create a Rails project using `rails new` _application_name_. If you list the contents of this folder then you shall see:
+Rails creates a `test` directory for you as soon as you create a Rails project using `rails new` _application_name_. If you list the contents of this directory then you shall see:
```bash
$ ls -F test
@@ -41,9 +45,9 @@ controllers/ helpers/ mailers/ test_helper.rb
fixtures/ integration/ models/
```
-The `models` directory is meant to hold tests for your models, the `controllers` directory is meant to hold tests for your controllers and the `integration` directory is meant to hold tests that involve any number of controllers interacting.
+The `models` directory is meant to hold tests for your models, the `controllers` directory is meant to hold tests for your controllers and the `integration` directory is meant to hold tests that involve any number of controllers interacting. There is also a directory for testing your mailers and one for testing view helpers.
-Fixtures are a way of organizing test data; they reside in the `fixtures` folder.
+Fixtures are a way of organizing test data; they reside in the `fixtures` directory.
The `test_helper.rb` file holds the default configuration for your tests.
@@ -51,17 +55,17 @@ The `test_helper.rb` file holds the default configuration for your tests.
For good tests, you'll need to give some thought to setting up test data.
In Rails, you can handle this by defining and customizing fixtures.
-You can find comprehensive documentation in the [fixture api documentation](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html).
+You can find comprehensive documentation in the [Fixtures API documentation](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html).
#### What Are Fixtures?
-_Fixtures_ is a fancy word for sample data. Fixtures allow you to populate your testing database with predefined data before your tests run. Fixtures are database independent written in YAML. There is one file per model.
+_Fixtures_ is a fancy word for sample data. Fixtures allow you to populate your testing database with predefined data before your tests run. Fixtures are database independent and written in YAML. There is one file per model.
-You'll find fixtures under your `test/fixtures` directory. When you run `rails generate model` to create a new model fixture stubs will be automatically created and placed in this directory.
+You'll find fixtures under your `test/fixtures` directory. When you run `rails generate model` to create a new model, Rails automatically creates fixture stubs in this directory.
#### YAML
-YAML-formatted fixtures are a very human-friendly way to describe your sample data. These types of fixtures have the **.yml** file extension (as in `users.yml`).
+YAML-formatted fixtures are a human-friendly way to describe your sample data. These types of fixtures have the **.yml** file extension (as in `users.yml`).
Here's a sample YAML fixture file:
@@ -78,11 +82,11 @@ steve:
profession: guy with keyboard
```
-Each fixture is given a name followed by an indented list of colon-separated key/value pairs. Records are typically separated by a blank space. You can place comments in a fixture file by using the # character in the first column. Keys which resemble YAML keywords such as 'yes' and 'no' are quoted so that the YAML Parser correctly interprets them.
+Each fixture is given a name followed by an indented list of colon-separated key/value pairs. Records are typically separated by a blank line. You can place comments in a fixture file by using the # character in the first column.
If you are working with [associations](/association_basics.html), you can simply
define a reference node between two different fixtures. Here's an example with
-a belongs_to/has_many association:
+a `belongs_to`/`has_many` association:
```yaml
# In fixtures/categories.yml
@@ -96,11 +100,9 @@ one:
category: about
```
-Note: For associations to reference one another by name, you cannot specify the `id:`
- attribute on the fixtures. Rails will auto assign a primary key to be consistent between
- runs. If you manually specify an `id:` attribute, this behavior will not work. For more
- information on this association behavior please read the
- [fixture api documentation](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html).
+Notice the `category` key of the `one` article found in `fixtures/articles.yml` has a value of `about`. This tells Rails to load the category `about` found in `fixtures/categories.yml`.
+
+NOTE: For associations to reference one another by name, you cannot specify the `id:` attribute on the associated fixtures. Rails will auto assign a primary key to be consistent between runs. For more information on this association behavior please read the [Fixtures API documentation](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html).
#### ERB'in It Up
@@ -116,15 +118,17 @@ user_<%= n %>:
#### Fixtures in Action
-Rails by default automatically loads all fixtures from the `test/fixtures` folder for your models and controllers test. Loading involves three steps:
+Rails by default automatically loads all fixtures from the `test/fixtures` directory for your models and controllers test. Loading involves three steps:
-* Remove any existing data from the table corresponding to the fixture
-* Load the fixture data into the table
-* Dump the fixture data into a variable in case you want to access it directly
+1. Remove any existing data from the table corresponding to the fixture
+2. Load the fixture data into the table
+3. Dump the fixture data into a method in case you want to access it directly
+
+TIP: In order to remove existing data from the database, Rails tries to disable referential integrity triggers (like foreign keys and check constraints). If you are getting annoying permission errors on running tests, make sure the database user has privilege to disable these triggers in testing environment. (In PostgreSQL, only superusers can disable all triggers. Read more about PostgreSQL permissions [here](http://blog.endpoint.com/2012/10/postgres-system-triggers-error.html))
#### Fixtures are Active Record objects
-Fixtures are instances of Active Record. As mentioned in point #3 above, you can access the object directly because it is automatically setup as a local variable of the test case. For example:
+Fixtures are instances of Active Record. As mentioned in point #3 above, you can access the object directly because it is automatically available as a method whose scope is local of the test case. For example:
```ruby
# this will return the User object for the fixture named david
@@ -134,19 +138,38 @@ users(:david)
users(:david).id
# one can also access methods available on the User class
-email(david.girlfriend.email, david.location_tonight)
+email(david.partner.email, david.location_tonight)
```
-Unit Testing your Models
-------------------------
+### Rake Tasks for Running your Tests
-In Rails, models tests are what you write to test your models.
+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.
-For this guide we will be using Rails _scaffolding_. It will create the model, a migration, controller and views for the new resource in a single operation. It will also create a full test suite following Rails best practices. We will be using examples from this generated code and will be supplementing it with additional examples where necessary.
+| 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 |
+
+We will cover each of types Rails tests listed above in this guide.
-NOTE: For more information on Rails _scaffolding_, refer to [Getting Started with Rails](getting_started.html)
+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.
-When you use `rails generate scaffold`, for a resource among other things it creates a test stub in the `test/models` folder:
+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:
```bash
$ bin/rails generate scaffold article title:string body:text
@@ -175,18 +198,18 @@ A line by line examination of this file will help get you oriented to Rails test
require 'test_helper'
```
-As you know by now, `test_helper.rb` specifies the default configuration to run our tests. This is included with all the tests, so any methods added to this file are available to all your tests.
+By requiring this file, `test_helper.rb` the default configuration to run our tests is loaded. We will include this with all the tests we write, so any methods added to this file are available to all your tests.
```ruby
class ArticleTest < ActiveSupport::TestCase
```
-The `ArticleTest` class defines a _test case_ because it inherits from `ActiveSupport::TestCase`. `ArticleTest` thus has all the methods available from `ActiveSupport::TestCase`. You'll see those methods a little later in this guide.
+The `ArticleTest` class defines a _test case_ because it inherits from `ActiveSupport::TestCase`. `ArticleTest` thus has all the methods available from `ActiveSupport::TestCase`. Later in this guide, you'll see some of the methods it gives you.
Any method defined within a class inherited from `Minitest::Test`
-(which is the superclass of `ActiveSupport::TestCase`) that begins with `test_` (case sensitive) is simply called a test. So, `test_password` and `test_valid_password` are legal test names and are run automatically when the test case is run.
+(which is the superclass of `ActiveSupport::TestCase`) that begins with `test_` (case sensitive) is simply called a test. So, methods defined as `test_password` and `test_valid_password` are legal test names and are run automatically when the test case is run.
-Rails adds a `test` method that takes a test name and a block. It generates a normal `Minitest::Unit` test with method names prefixed with `test_`. So,
+Rails also adds a `test` method that takes a test name and a block. It generates a normal `Minitest::Unit` test with method names prefixed with `test_`. So you don't have to worry about naming the methods, and you can write something like:
```ruby
test "the truth" do
@@ -194,7 +217,7 @@ test "the truth" do
end
```
-acts as if you had written
+Which is approximately the same as writing this:
```ruby
def test_the_truth
@@ -202,26 +225,37 @@ def test_the_truth
end
```
-only the `test` macro allows a more readable test name. You can still use regular method definitions though.
+However only the `test` macro allows a more readable test name. You can still use regular method definitions though.
-NOTE: The method name is generated by replacing spaces with underscores. The result does not need to be a valid Ruby identifier though, the name may contain punctuation characters etc. That's because in Ruby technically any string may be a method name. Odd ones need `define_method` and `send` calls, but formally there's no restriction.
+NOTE: The method name is generated by replacing spaces with underscores. The result does not need to be a valid Ruby identifier though, the name may contain punctuation characters etc. That's because in Ruby technically any string may be a method name. This may require use of `define_method` and `send` calls to function properly, but formally there's little restriction on the name.
+
+Next, let's look at our first assertion:
```ruby
assert true
```
-This line of code is called an _assertion_. An assertion is a line of code that evaluates an object (or expression) for expected results. For example, an assertion can check:
+An assertion is a line of code that evaluates an object (or expression) for expected results. For example, an assertion can check:
* does this value = that value?
* is this object nil?
* does this line of code throw an exception?
* is the user's password greater than 5 characters?
-Every test contains one or more assertions. Only when all the assertions are successful will the test pass.
+Every test must contain at least one assertion, with no restriction as to how many assertions are allowed. Only when all the assertions are successful will the test pass.
### Maintaining the test database schema
-In order to run your tests, your test database will need to have the current structure. The test helper checks whether your test database has any pending migrations. If so, it will try to load your `db/schema.rb` or `db/structure.sql` into the test database. If migrations are still pending, an error will be raised.
+In order to run your tests, your test database will need to have the current
+structure. The test helper checks whether your test database has any pending
+migrations. If so, it will try to load your `db/schema.rb` or `db/structure.sql`
+into the test database. If migrations are still pending, an error will be
+raised. Usually this indicates that your schema is not fully migrated. Running
+the migrations against the development database (`bin/rake db:migrate`) will
+bring the schema up to date.
+
+NOTE: If existing migrations required modifications, the test database needs to
+be rebuilt. This can be done by executing `bin/rake db:test:prepare`.
### Running Tests
@@ -236,6 +270,8 @@ Finished tests in 0.009262s, 107.9680 tests/s, 107.9680 assertions/s.
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
```
+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
@@ -247,10 +283,10 @@ Finished tests in 0.009064s, 110.3266 tests/s, 110.3266 assertions/s.
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
```
-This will run all test methods from the test case. Note that `test_helper.rb` is in the `test` directory, hence this directory needs to be added to the load path using the `-I` switch.
-
The `.` (dot) above indicates a passing test. When a test fails you see an `F`; when a test throws an error you see an `E` in its place. The last line of the output is the summary.
+#### Your first failing test
+
To see how a test failure is reported, you can add a failing test to the `article_test.rb` test case.
```ruby
@@ -311,9 +347,13 @@ Finished tests in 0.047721s, 20.9551 tests/s, 20.9551 assertions/s.
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
```
-Now, if you noticed, we first wrote a test which fails for a desired functionality, then we wrote some code which adds the functionality and finally we ensured that our test passes. This approach to software development is referred to as _Test-Driven Development_ (TDD).
+Now, if you noticed, we first wrote a test which fails for a desired
+functionality, then we wrote some code which adds the functionality and finally
+we ensured that our test passes. This approach to software development is
+referred to as
+[_Test-Driven Development_ (TDD)](http://c2.com/cgi/wiki?TestDrivenDevelopment).
-TIP: Many Rails developers practice _Test-Driven Development_ (TDD). This is an excellent way to build up a test suite that exercises every part of your application. TDD is beyond the scope of this guide, but one place to start is with [15 TDD steps to create a Rails application](http://andrzejonsoftware.blogspot.com/2007/05/15-tdd-steps-to-create-rails.html).
+#### What an error looks like
To see how an error gets reported, here's a test containing an error:
@@ -343,7 +383,11 @@ NameError: undefined local variable or method `some_undefined_variable' for #<Ar
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 alphabetical order.
+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)
+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
@@ -356,52 +400,26 @@ behavior:
$ BACKTRACE=1 bin/rake test test/models/article_test.rb
```
-### What to Include in Your Unit Tests
+If we want this test to pass we can modify it to use `assert_raises` like so:
-Ideally, you would like to include a test for everything which could possibly break. It's a good practice to have at least one test for each of your validations and at least one test for every method in your model.
+```ruby
+test "should report error" do
+ # some_undefined_variable is not defined elsewhere in the test case
+ assert_raises(NameError) do
+ some_undefined_variable
+ end
+end
+```
+
+This test should now pass.
### Available Assertions
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.
-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 or descends from `class`.|
-| `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 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)
+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)
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.
@@ -423,10 +441,25 @@ Rails adds some custom assertions of its own to the `minitest` framework:
You'll see the usage of some of these assertions in the next chapter.
+### A Brief Note About Minitest
+
+All the basic assertions such as `assert_equal` defined in `Minitest::Assertions` are also available in the classes we use in our own test cases. In fact, Rails provides the following classes for you to inherit from:
+
+* `ActiveSupport::TestCase`
+* `ActionController::TestCase`
+* `ActionMailer::TestCase`
+* `ActionView::TestCase`
+* `ActionDispatch::IntegrationTest`
+* `ActiveJob::TestCase`
+
+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)
+
Functional Tests for Your Controllers
-------------------------------------
-In Rails, testing the various actions of a single controller is called writing functional tests for that controller. Controllers handle the incoming web requests to your application and eventually respond with a rendered view.
+In Rails, testing the various actions of a controller is a form of writing functional tests. Remember your controllers handle the incoming web requests to your application and eventually respond with a rendered view. When writing functional tests, you're testing how your actions handle the requests and the expected result, or response in some cases an HTML view.
### What to Include in your Functional Tests
@@ -456,21 +489,28 @@ In the `test_should_get_index` test, Rails simulates a request on the action cal
The `get` method kicks off the web request and populates the results into the response. It accepts 4 arguments:
-* The action of the controller you are requesting. This can be in the form of a string or a symbol.
-* An optional hash of request parameters to pass into the action (eg. query string parameters or article variables).
-* An optional hash of session variables to pass along with the request.
-* An optional hash of flash values.
+* The action of the controller you are requesting.
+ This can be in the form of a string or a symbol.
+
+* `params`: option with a hash of request parameters to pass into the action
+ (e.g. query string parameters or article variables).
+
+* `session`: option with a hash of session variables to pass along with the request.
+
+* `flash`: option with a hash of flash values.
+
+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, {'id' => "12"}, {'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, {'id' => '12'}, nil, {'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.
@@ -480,7 +520,7 @@ Let us modify `test_should_create_article` test in `articles_controller_test.rb`
```ruby
test "should create article" do
assert_difference('Article.count') do
- post :create, article: {title: 'Some title'}
+ post :create, params: { article: { title: 'Some title' } }
end
assert_redirected_to article_path(assigns(:article))
@@ -500,13 +540,27 @@ If you're familiar with the HTTP protocol, you'll know that `get` is a type of r
* `head`
* `delete`
-All of request types are methods that you can use, however, you'll probably end up using the first two more often than the others.
+All of request types have equivalent methods that you can use. In a typical C.R.U.D. application you'll be using `get`, `post`, `put` and `delete` more often.
-NOTE: Functional tests do not verify whether the specified request type should be accepted by the action. Request types in this context exist to make your tests more descriptive.
+NOTE: Functional tests do not verify whether the specified request type is accepted by the action, we're more concerned with the result. Request tests exist for this use case to make your tests more purposeful.
+
+### Testing XHR (AJAX) requests
+
+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
+ get :show, params: { id: articles(:first).id }, xhr: true
+
+ assert_template :index
+ assert_template layout: nil
+end
+```
### The Four Hashes of the Apocalypse
-After a request has been made using one of the 6 methods (`get`, `post`, etc.) and processed, you will have 4 Hash objects ready for use:
+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.
@@ -529,8 +583,8 @@ assigns["something"] assigns(:something)
You also have access to three instance variables in your functional tests:
* `@controller` - The controller processing the request
-* `@request` - The request
-* `@response` - The response
+* `@request` - The request object
+* `@response` - The response object
### Setting Headers and CGI variables
@@ -551,6 +605,10 @@ 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:
@@ -559,24 +617,22 @@ 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 that you cannot test for template and layout at the same time, with one call to `assert_template` method.
-Also, for the `layout` test, you can give a regular expression instead of a string, but using the string, makes
-things clearer. On the other hand, you have to include the "layouts" directory name even if you save your layout
-file in this standard layout directory. Hence,
+NOTE: You cannot test for template and layout at the same time, with a single call to `assert_template`.
-```ruby
-assert_template layout: "application"
-```
+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.
-will not work.
+#### Asserting Partials
-If your view renders any partial, when asserting for the layout, you have to assert for the partial at the same time.
+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.
-Hence:
+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
@@ -585,27 +641,234 @@ test "new should render correct layout" do
end
```
-is the correct way to assert for the layout when the view renders a partial with name `_form`. Omitting the `:partial` key in your `assert_template` call will complain.
+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
-### A Fuller Functional Test Example
+If you remember from earlier one of the Four Hashes of the Apocalypse was `flash`.
-Here's another example that uses `flash`, `assert_redirected_to`, and `assert_difference`:
+We want to add a `flash` message to our blog application whenever someone
+successfully creates a new Article.
+
+Let's start by adding this assertion to our `test_should_create_article` test:
```ruby
test "should create article" do
assert_difference('Article.count') do
- post :create, article: {title: 'Hi', body: 'This is my first article.'}
+ post :create, params: { article: { title: 'Some title' } }
end
+
assert_redirected_to article_path(assigns(:article))
assert_equal 'Article was successfully created.', flash[:notice]
end
```
-### Testing Views
+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
+Run options: -n test_should_create_article --seed 32266
+
+# Running:
+
+F
+
+Finished in 0.114870s, 8.7055 runs/s, 34.8220 assertions/s.
+
+ 1) Failure:
+ArticlesControllerTest#test_should_create_article [/Users/zzak/code/bench/sharedapp/test/controllers/articles_controller_test.rb:16]:
+--- expected
++++ actual
+@@ -1 +1 @@
+-"Article was successfully created."
++nil
+
+1 runs, 4 assertions, 1 failures, 0 errors, 0 skips
+```
+
+Let's implement the flash message now in our controller. Our `:create` action should now look like this:
+
+```ruby
+def create
+ @article = Article.new(article_params)
+
+ if @article.save
+ flash[:notice] = 'Article was successfully created.'
+ redirect_to @article
+ else
+ render 'new'
+ end
+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
+Run options: -n test_should_create_article --seed 18981
+
+# Running:
+
+.
+
+Finished in 0.081972s, 12.1993 runs/s, 48.7972 assertions/s.
+
+1 runs, 4 assertions, 0 failures, 0 errors, 0 skips
+```
+
+### Putting it together
+
+At this point our Articles controller tests the `:index` as well as `:new` and `:create` actions. What about dealing with existing data?
+
+Let's write a test for the `:show` action:
+
+```ruby
+test "should show article" do
+ article = articles(:one)
+ get :show, params: { id: article.id }
+ assert_response :success
+end
+```
+
+Remember from our discussion earlier on fixtures the `articles()` method will give us access to our Articles fixtures.
+
+How about deleting an existing Article?
+
+```ruby
+test "should destroy article" do
+ article = articles(:one)
+ assert_difference('Article.count', -1) do
+ delete :destroy, params: { id: article.id }
+ end
+
+ assert_redirected_to articles_path
+end
+```
+
+We can also add a test for updating an existing Article.
+
+```ruby
+test "should update article" do
+ article = articles(:one)
+ patch :update, params: { id: article.id, article: { title: "updated" } }
+ assert_redirected_to article_path(assigns(:article))
+end
+```
+
+Notice we're starting to see some duplication in these three tests, they both access the same Article fixture data. We can D.R.Y. this up by using the `setup` and `teardown` methods provided by `ActiveSupport::Callbacks`.
+
+Our test should now look something like this, disregard the other tests we're leaving them out for brevity.
+
+```ruby
+require 'test_helper'
+
+class ArticlesControllerTest < ActionController::TestCase
+ # called before every single test
+ def setup
+ @article = articles(:one)
+ end
+
+ # called after every single test
+ def teardown
+ # when controller is using cache it may be a good idea to reset it afterwards
+ Rails.cache.clear
+ end
+
+ test "should show article" do
+ # Reuse the @article instance variable from setup
+ get :show, params: { id: @article.id }
+ assert_response :success
+ end
+
+ test "should destroy article" do
+ assert_difference('Article.count', -1) do
+ delete :destroy, params: { id: @article.id }
+ end
+
+ assert_redirected_to articles_path
+ end
+
+ test "should update article" do
+ patch :update, params: { id: @article.id, article: { title: "updated" } }
+ assert_redirected_to article_path(assigns(:article))
+ end
+end
+```
+
+Similar to other callbacks in Rails, the `setup` and `teardown` methods can also be used by passing a block, lambda, or method name as a symbol to call.
+
+### Test helpers
+
+To avoid code duplication, you can add your own test helpers.
+Sign in helper can be a good example:
+
+```ruby
+test/test_helper.rb
+
+module SignInHelper
+ def sign_in(user)
+ session[:user_id] = user.id
+ end
+end
+
+class ActionController::TestCase
+ include SignInHelper
+end
+```
+
+```ruby
+require 'test_helper'
+
+class ProfileControllerTest < ActionController::TestCase
+
+ test "should show profile" do
+ # helper is now reusable from any controller test case
+ sign_in users(:david)
+
+ get :show
+ assert_response :success
+ assert_equal users(:david), assigns(:user)
+ end
+end
+```
+
+Testing Routes
+--------------
+
+Like everything else in your Rails application, it is recommended that you test your routes. Below are example tests for the routes of default `show` and `create` action of `Articles` controller above and it should look like:
+
+```ruby
+class ArticleRoutesTest < ActionController::TestCase
+ test "should route to article" do
+ assert_routing '/articles/1', { controller: "articles", action: "show", id: "1" }
+ end
+
+ test "should route to create article" do
+ assert_routing({ method: 'post', path: '/articles' }, { controller: "articles", action: "create" })
+ end
+end
+```
+
+I've added this file here `test/controllers/articles_routes_test.rb` and if we run the test we should see:
-Testing the response to your request by asserting the presence of key HTML elements and their content is a useful way to test the views of your application. The `assert_select` assertion allows you to do this by using a simple yet powerful syntax.
+```bash
+$ bin/rake test test/controllers/articles_routes_test.rb
+
+# Running:
+
+..
+
+Finished in 0.069381s, 28.8263 runs/s, 86.4790 assertions/s.
+
+2 runs, 6 assertions, 0 failures, 0 errors, 0 skips
+```
-NOTE: You may find references to `assert_tag` in other documentation. This has been removed in 4.2. Use `assert_select` instead.
+For more information on routing assertions available in Rails, see the API documentation for [`ActionDispatch::Assertions::RoutingAssertions`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html).
+
+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.
There are two forms of `assert_select`:
@@ -619,7 +882,10 @@ For example, you could verify the contents on the title element in your response
assert_select 'title', "Welcome to Rails Testing Guide"
```
-You can also use nested `assert_select` blocks. In this case the inner `assert_select` runs the assertion on the complete collection of elements selected by the outer `assert_select` block:
+You can also use nested `assert_select` blocks for deeper investigation.
+
+In the following example, the inner `assert_select` for `li.menu_item` runs
+within the collection of elements selected by the outer block:
```ruby
assert_select 'ul.navigation' do
@@ -627,7 +893,9 @@ assert_select 'ul.navigation' do
end
```
-Alternatively the collection of elements selected by the outer `assert_select` may be iterated through so that `assert_select` may be called separately for each element. Suppose for example that the response contains two ordered lists, each with four list elements then the following tests will both pass.
+A collection of selected elements may be iterated through so that `assert_select` may be called separately for each element.
+
+For example if the response contains two ordered lists, each with four nested list elements then the following tests will both pass.
```ruby
assert_select "ol" do |elements|
@@ -641,7 +909,7 @@ assert_select "ol" do
end
```
-The `assert_select` assertion is quite powerful. For more advanced usage, refer to its [documentation](https://github.com/rails/rails-dom-testing/blob/master/lib/rails/dom/testing/assertions/selector_assertions.rb).
+This assertion is quite powerful. For more advanced usage, refer to its [documentation](http://www.rubydoc.info/github/rails/rails-dom-testing).
#### Additional View-Based Assertions
@@ -661,12 +929,45 @@ assert_select_email do
end
```
+Testing Helpers
+---------------
+
+In order to test helpers, all you need to do is check that the output of the
+helper method matches what you'd expect. Tests related to the helpers are
+located under the `test/helpers` directory.
+
+A helper test looks like so:
+
+```ruby
+require 'test_helper'
+
+class UserHelperTest < ActionView::TestCase
+end
+```
+
+A helper is just a simple module where you can define methods which are
+available into your views. To test the output of the helper's methods, you just
+have to use a mixin like this:
+
+```ruby
+class UserHelperTest < ActionView::TestCase
+ include UserHelper
+
+ test "should return the user name" do
+ # ...
+ end
+end
+```
+
+Moreover, since the test class extends from `ActionView::TestCase`, you have
+access to Rails' helper methods such as `link_to` or `pluralize`.
+
Integration Testing
-------------------
-Integration tests are used to test the interaction among any number of controllers. They are generally used to test important work flows within your application.
+Integration tests are used to test how various parts of your application interact. They are generally used to test important work flows within your application.
-Unlike Unit and Functional tests, integration tests have to be explicitly created under the 'test/integration' folder within your application. Rails provides a generator to create an integration test skeleton for you.
+For creating Rails integration tests, we use the 'test/integration' directory for your application. Rails provides a generator to create an integration test skeleton for you.
```bash
$ bin/rails generate integration_test user_flows
@@ -686,231 +987,94 @@ class UserFlowsTest < ActionDispatch::IntegrationTest
end
```
-Integration tests inherit from `ActionDispatch::IntegrationTest`. This makes available some additional helpers to use in your integration tests. Also you need to explicitly include the fixtures to be made available to the test.
+Inheriting from `ActionDispatch::IntegrationTest` comes with some advantages. This makes available some additional helpers to use in your integration tests.
### Helpers Available for Integration Tests
-In addition to the standard testing helpers, there are some additional helpers available to integration tests:
+In addition to the standard testing helpers, inheriting `ActionDispatch::IntegrationTest` comes with some additional helpers available when writing integration tests. Let's briefly introduce you to the three categories of helpers you get to choose from.
-| Helper | Purpose |
-| ------------------------------------------------------------------ | ------- |
-| `https?` | Returns `true` if the session is mimicking a secure HTTPS request.|
-| `https!` | Allows you to mimic a secure HTTPS request.|
-| `host!` | Allows you to set the host name to use in the next request.|
-| `redirect?` | Returns `true` if the last request was a redirect.|
-| `follow_redirect!` | Follows a single redirect response.|
-| `request_via_redirect(http_method, path, [parameters], [headers])` | Allows you to make an HTTP request and follow any subsequent redirects.|
-| `post_via_redirect(path, [parameters], [headers])` | Allows you to make an HTTP POST request and follow any subsequent redirects.|
-| `get_via_redirect(path, [parameters], [headers])` | Allows you to make an HTTP GET request and follow any subsequent redirects.|
-| `patch_via_redirect(path, [parameters], [headers])` | Allows you to make an HTTP PATCH request and follow any subsequent redirects.|
-| `put_via_redirect(path, [parameters], [headers])` | Allows you to make an HTTP PUT request and follow any subsequent redirects.|
-| `delete_via_redirect(path, [parameters], [headers])` | Allows you to make an HTTP DELETE request and follow any subsequent redirects.|
-| `open_session` | Opens a new session instance.|
+For dealing with the integration test runner, see [`ActionDispatch::Integration::Runner`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/Runner.html).
-### Integration Testing Examples
+When performing requests, you will have [`ActionDispatch::Integration::RequestHelpers`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/RequestHelpers.html) available for your use.
-A simple integration test that exercises multiple controllers:
+If you'd like to modify the session, or state of your integration test you should look for [`ActionDispatch::Integration::Session`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/Session.html) to help.
-```ruby
-require 'test_helper'
+### Implementing an integration test
-class UserFlowsTest < ActionDispatch::IntegrationTest
- test "login and browse site" do
- # login via https
- https!
- get "/login"
- assert_response :success
+Let's add an integration test to our blog application. We'll start with a basic workflow of creating a new blog article, to verify that everything is working properly.
- post_via_redirect "/login", username: users(:david).username, password: users(:david).password
- assert_equal '/welcome', path
- assert_equal 'Welcome david!', flash[:notice]
+We'll start by generating our integration test skeleton:
- https!(false)
- get "/articles/all"
- assert_response :success
- assert assigns(:products)
- end
-end
+```bash
+$ bin/rails generate integration_test blog_flow
```
-As you can see the integration test involves multiple controllers and exercises the entire stack from database to dispatcher. In addition you can have multiple session instances open simultaneously in a test and extend those instances with assertion methods to create a very powerful testing DSL (domain-specific language) just for your application.
+It should have created a test file placeholder for us, with the output of the previous command you should see:
+
+```bash
+ invoke test_unit
+ create test/integration/blog_flow_test.rb
+```
-Here's an example of multiple sessions and custom DSL in an integration test
+Now let's open that file and write our first assertion:
```ruby
require 'test_helper'
-class UserFlowsTest < ActionDispatch::IntegrationTest
- test "login and browse site" do
- # User david logs in
- david = login(:david)
- # User guest logs in
- guest = login(:guest)
-
- # Both are now available in different sessions
- assert_equal 'Welcome david!', david.flash[:notice]
- assert_equal 'Welcome guest!', guest.flash[:notice]
-
- # User david can browse site
- david.browses_site
- # User guest can browse site as well
- guest.browses_site
-
- # Continue with other assertions
+class BlogFlowTest < ActionDispatch::IntegrationTest
+ test "can see the welcome page" do
+ get "/"
+ assert_select "h1", "Welcome#index"
end
-
- private
-
- module CustomDsl
- def browses_site
- get "/products/all"
- assert_response :success
- assert assigns(:products)
- end
- end
-
- def login(user)
- open_session do |sess|
- sess.extend(CustomDsl)
- u = users(user)
- sess.https!
- sess.post "/login", username: u.username, password: u.password
- assert_equal '/welcome', sess.path
- sess.https!(false)
- end
- end
end
```
-Rake Tasks for Running your Tests
----------------------------------
-
-You don't need to set up and run your tests by hand on a test-by-test basis.
-Rails comes with a number of commands to help in testing.
-The table below lists all commands that come along in the default Rakefile
-when you initiate a Rails project.
-
-| Tasks | Description |
-| ----------------------- | ----------- |
-| `rake test` | Runs all unit, functional and integration tests. You can also simply run `rake` as Rails will run all the 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:all` | Runs all tests quickly by merging all types and not resetting db |
-| `rake test:all:db` | Runs all tests quickly by merging all types and resetting db |
-
-
-Brief Note About `Minitest`
------------------------------
+If you remember from earlier in the "Testing Views" section we covered `assert_select` to query the resulting HTML of a request.
-Ruby ships with a vast Standard Library for all common use-cases including testing. Since version 1.9, Ruby provides `Minitest`, a framework for testing. All the basic assertions such as `assert_equal` discussed above are actually defined in `Minitest::Assertions`. The classes `ActiveSupport::TestCase`, `ActionController::TestCase`, `ActionMailer::TestCase`, `ActionView::TestCase` and `ActionDispatch::IntegrationTest` - which we have been inheriting in our test classes - include `Minitest::Assertions`, allowing us to use all of the basic assertions in our tests.
+When visit our root path, we should see `welcome/index.html.erb` rendered for the view. So this assertion should pass.
-NOTE: For more information on `Minitest`, refer to [Minitest](http://ruby-doc.org/stdlib-2.1.0/libdoc/minitest/rdoc/MiniTest.html)
-
-Setup and Teardown
-------------------
+#### Creating articles integration
-If you would like to run a block of code before the start of each test and another block of code after the end of each test you have two special callbacks for your rescue. Let's take note of this by looking at an example for our functional test in `Articles` controller:
+How about testing our ability to create a new article in our blog and see the resulting article.
```ruby
-require 'test_helper'
-
-class ArticlesControllerTest < ActionController::TestCase
-
- # called before every single test
- def setup
- @article = articles(:one)
- end
-
- # called after every single test
- def teardown
- # as we are re-initializing @article before every test
- # setting it to nil here is not essential but I hope
- # you understand how you can use the teardown method
- @article = nil
- end
-
- test "should show article" do
- get :show, id: @article.id
- assert_response :success
- end
-
- test "should destroy article" do
- assert_difference('Article.count', -1) do
- delete :destroy, id: @article.id
- end
-
- assert_redirected_to articles_path
- end
-
+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
```
-Above, the `setup` method is called before each test and so `@article` is available for each of the tests. Rails implements `setup` and `teardown` as `ActiveSupport::Callbacks`. Which essentially means you need not only use `setup` and `teardown` as methods in your tests. You could specify them by using:
+Let's break this test down so we can understand it.
-* a block
-* a method (like in the earlier example)
-* a method name as a symbol
-* a lambda
+We start by calling the `:new` action on our Articles controller. This response should be successful, and we can verify the correct template is rendered including the form partial.
-Let's see the earlier example by specifying `setup` callback by specifying a method name as a symbol:
+After this we make a post request to the `:create` action of our Articles controller:
```ruby
-require 'test_helper'
-
-class ArticlesControllerTest < ActionController::TestCase
-
- # called before every single test
- setup :initialize_article
-
- # called after every single test
- def teardown
- @article = nil
- end
-
- test "should show article" do
- get :show, id: @article.id
- assert_response :success
- end
-
- test "should update article" do
- patch :update, id: @article.id, article: {}
- assert_redirected_to article_path(assigns(:article))
- end
-
- test "should destroy article" do
- assert_difference('Article.count', -1) do
- delete :destroy, id: @article.id
- end
+post "/articles",
+ params: { article: { title: "can create", body: "article successfully." } }
+assert_response :redirect
+follow_redirect!
+```
- assert_redirected_to articles_path
- end
+The two lines following the request are to handle the redirect we setup when creating a new article.
- private
+NOTE: Don't forget to call `follow_redirect!` if you plan to make subsequent requests after a redirect is made.
- def initialize_article
- @article = articles(:one)
- end
-end
-```
+Finally we can assert that our response was successful, template was rendered, and our new article is readable on the page.
-Testing Routes
---------------
+#### Taking it further
-Like everything else in your Rails application, it is recommended that you test your routes. An example test for a route in the default `show` action of `Articles` controller above should look like:
-
-```ruby
-class ArticleRoutesTest < ActionController::TestCase
- test "should route to article" do
- assert_routing '/articles/1', { controller: "articles", action: "show", id: "1" }
- end
-end
-```
+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.
Testing Your Mailers
--------------------
@@ -919,7 +1083,7 @@ Testing mailer classes requires some specific tools to do a thorough job.
### Keeping the Postman in Check
-Your mailer classes - like every other part of your Rails application - should be tested to ensure that it is working as expected.
+Your mailer classes - like every other part of your Rails application - should be tested to ensure that they are working as expected.
The goals of testing your mailer classes are to ensure that:
@@ -951,9 +1115,10 @@ require 'test_helper'
class UserMailerTest < ActionMailer::TestCase
test "invite" do
# Send the email, then test that it got queued
- email = UserMailer.create_invite('me@example.com',
- 'friend@example.com', Time.now).deliver_now
- assert_not ActionMailer::Base.deliveries.empty?
+ assert_emails 1 do
+ email = UserMailer.create_invite('me@example.com',
+ 'friend@example.com', Time.now).deliver_now
+ end
# Test the body of the sent email contains what we expect it to
assert_equal ['me@example.com'], email.from
@@ -1001,7 +1166,7 @@ require 'test_helper'
class UserControllerTest < ActionController::TestCase
test "invite friend" do
assert_difference 'ActionMailer::Base.deliveries.size', +1 do
- post :invite_friend, email: 'friend@example.com'
+ post :invite_friend, params: { email: 'friend@example.com' }
end
invite_email = ActionMailer::Base.deliveries.last
@@ -1012,39 +1177,58 @@ class UserControllerTest < ActionController::TestCase
end
```
-Testing helpers
----------------
+Testing Jobs
+------------
-In order to test helpers, all you need to do is check that the output of the
-helper method matches what you'd expect. Tests related to the helpers are
-located under the `test/helpers` directory.
+Since your custom jobs can be queued at different levels inside your application,
+you'll need to test both jobs themselves (their behavior when they get enqueued)
+and that other entities correctly enqueue them.
-A helper test looks like so:
+### A Basic Test Case
+
+By default, when you generate a job, an associated test will be generated as well
+under the `test/jobs` directory. Here's an example test with a billing job:
```ruby
require 'test_helper'
-class UserHelperTest < ActionView::TestCase
+class BillingJobTest < ActiveJob::TestCase
+ test 'that account is charged' do
+ BillingJob.perform_now(account, product)
+ assert account.reload.charged_for?(product)
+ end
end
```
-A helper is just a simple module where you can define methods which are
-available into your views. To test the output of the helper's methods, you just
-have to use a mixin like this:
+This test is pretty simple and only asserts that the job get the work done
+as expected.
+
+By default, `ActiveJob::TestCase` will set the queue adapter to `:test` so that
+your jobs are performed inline. It will also ensure that all previously performed
+and enqueued jobs are cleared before any test run so you can safely assume that
+no jobs have already been executed in the scope of each test.
+
+### Custom Assertions And Testing Jobs Inside Other Components
+
+Active Job ships with a bunch of custom assertions that can be used to lessen the verbosity of tests. For a full list of available assertions, see the API documentation for [`ActiveJob::TestHelper`](http://api.rubyonrails.org/classes/ActiveJob/TestHelper.html).
+
+It's a good practice to ensure that your jobs correctly get enqueued or performed
+wherever you invoke them (e.g. inside your controllers). This is precisely where
+the custom assertions provided by Active Job are pretty useful. For instance,
+within a model:
```ruby
-class UserHelperTest < ActionView::TestCase
- include UserHelper
+require 'test_helper'
- test "should return the user name" do
- # ...
+class ProductTest < ActiveJob::TestCase
+ test 'billing job scheduling' do
+ assert_enqueued_with(job: BillingJob) do
+ product.charge(account)
+ end
end
end
```
-Moreover, since the test class extends from `ActionView::TestCase`, you have
-access to Rails' helper methods such as `link_to` or `pluralize`.
-
Other Testing Approaches
------------------------
@@ -1056,3 +1240,4 @@ The built-in `minitest` based testing is not the only way to test Rails applicat
* [MiniTest::Spec Rails](https://github.com/metaskills/minitest-spec-rails), use the MiniTest::Spec DSL within your rails tests.
* [Shoulda](http://www.thoughtbot.com/projects/shoulda), an extension to `test/unit` with additional helpers, macros, and assertions.
* [RSpec](http://relishapp.com/rspec), a behavior-driven development framework
+* [Capybara](http://jnicklas.github.com/capybara/), Acceptance test framework for web applications
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index aac2aef615..7666601bd7 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
A Guide for Upgrading Ruby on Rails
===================================
@@ -18,9 +20,10 @@ 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 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.
-* Rails 3.2.x is the last branch to support Ruby 1.8.7.
+* Rails 5 requires Ruby 2.2.1 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.
TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition has these fixed since the release of 1.8.7-2010.02. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x, jump straight to 1.9.3 for smooth sailing.
@@ -47,25 +50,55 @@ Overwrite /myapp/config/application.rb? (enter "h" for help) [Ynaqdh]
Don't forget to review the difference, to see if there were any unexpected changes.
-Upgrading from Rails 4.1 to Rails 4.2
+Upgrading from Rails 4.2 to Rails 5.0
-------------------------------------
-NOTE: This section is a work in progress, please help to improve this by sending
-a [pull request](https://github.com/rails/rails/edit/master/guides/source/upgrading_ruby_on_rails.md).
+### Halting callback chains by returning `false`
-### Web Console
+In Rails 4.2, when a 'before' callback returns `false` in ActiveRecord,
+ActiveModel and ActiveModel::Validations, then the entire callback chain
+is halted. In other words, successive 'before' callbacks are not executed,
+and neither is the action wrapped in callbacks.
-First, add `gem 'web-console', '~> 2.0'` to the `:development` group in your Gemfile and run `bundle install` (it won't have been included when you upgraded Rails). Once it's been installed, you can simply drop a reference to the console helper (i.e., `<%= console %>`) into any view you want to enable it for. A console will also be provided on any error page you view in your development environment.
+In Rails 5.0, returning `false` in a callback will not have this side effect
+of halting the callback chain. Instead, callback chains must be explicitly
+halted by calling `throw(:abort)`.
-Additionally, you can tell Rails to automatically mount a VT100-compatible console on a predetermined path by setting the appropriate configuration flags in your development config:
+When you upgrade from Rails 4.2 to Rails 5.0, returning `false` in a callback
+will still halt the callback chain, but you will receive a deprecation warning
+about this upcoming change.
-```ruby
-# config/environments/development.rb
+When you are ready, you can opt into the new behavior and remove the deprecation
+warning by adding the following configuration to your `config/application.rb`:
+
+ config.active_support.halt_callback_chains_on_return_false = false
+
+See [#17227](https://github.com/rails/rails/pull/17227) for more details.
+
+### ActiveJob jobs now inherent 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`.
-config.web_console.automount = true
-config.web_console.default_mount_path = '/terminal' # Optional, defaults to /console
+When upgrading from Rails 4.2 to Rails 5.0 you need to create an
+`application_job.rb` file in `app/jobs/` and add the following content:
+
+```
+class ApplicationJob < ActiveJob::Base
+end
```
+Then make sure that all your job classes inherit from it.
+
+See [#19034](https://github.com/rails/rails/pull/19034) for more details.
+
+Upgrading from Rails 4.1 to Rails 4.2
+-------------------------------------
+
+### Web Console
+
+First, add `gem 'web-console', '~> 2.0'` to the `:development` group in your Gemfile and run `bundle install` (it won't have been included when you upgraded Rails). Once it's been installed, you can simply drop a reference to the console helper (i.e., `<%= console %>`) into any view you want to enable it for. A console will also be provided on any error page you view in your development environment.
+
### Responders
`respond_with` and the class-level `respond_to` methods have been extracted to the `responders` gem. To use them, simply add `gem 'responders', '~> 2.0'` to your Gemfile. Calls to `respond_with` and `respond_to` (again, at the class level) will no longer work without having included the `responders` gem in your dependencies:
@@ -145,6 +178,18 @@ assigning `nil` to a serialized attribute will save it to the database
as `NULL` instead of passing the `nil` value through the coder (e.g. `"null"`
when using the `JSON` coder).
+### Production log level
+
+In Rails 5, the default log level for the production environment will be changed
+to `:debug` (from `:info`). To preserve the current default, add the following
+line to your `production.rb`:
+
+```ruby
+# Set to `:info` to match the current default, or set to `:debug` to opt-into
+# the future default.
+config.log_level = :info
+```
+
### `after_bundle` in Rails templates
If you have a Rails template that adds all the files in version control, it
@@ -181,7 +226,7 @@ end
There's a new choice for sanitizing HTML fragments in your applications. The
venerable html-scanner approach is now officially being deprecated in favor of
-[`Rails Html Sanitizer`](https://github.com/rails/rails-html-sanitizer).
+[`Rails HTML Sanitizer`](https://github.com/rails/rails-html-sanitizer).
This means the methods `sanitize`, `sanitize_css`, `strip_tags` and
`strip_links` are backed by a new implementation.
@@ -207,12 +252,63 @@ gem 'rails-deprecated_sanitizer'
```
### Rails DOM Testing
-The [`TagAssertions` module](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/TagAssertions.html) (containing methods such as `assert_tag`), [has been deprecated](https://github.com/rails/rails/blob/6061472b8c310158a2a2e8e9a6b81a1aef6b60fe/actionpack/lib/action_dispatch/testing/assertions/dom.rb) in favor of the `assert_select` methods from the `SelectorAssertions` module, which has been extracted into the [rails-dom-testing gem](https://github.com/rails/rails-dom-testing).
+
+The [`TagAssertions` module](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/TagAssertions.html) (containing methods such as `assert_tag`), [has been deprecated](https://github.com/rails/rails/blob/6061472b8c310158a2a2e8e9a6b81a1aef6b60fe/actionpack/lib/action_dispatch/testing/assertions/dom.rb) in favor of the `assert_select` methods from the `SelectorAssertions` module, which has been extracted into the [rails-dom-testing gem](https://github.com/rails/rails-dom-testing).
### Masked Authenticity Tokens
+
In order to mitigate SSL attacks, `form_authenticity_token` is now masked so that it varies with each request. Thus, tokens are validated by unmasking and then decrypting. As a result, any strategies for verifying requests from non-rails forms that relied on a static session CSRF token have to take this into account.
+### Action Mailer
+
+Previously, calling a mailer method on a mailer class will result in the
+corresponding instance method being executed directly. With the introduction of
+Active Job and `#deliver_later`, this is no longer true. In Rails 4.2, the
+invocation of the instance methods are deferred until either `deliver_now` or
+`deliver_later` is called. For example:
+
+```ruby
+class Notifier < ActionMailer::Base
+ def notify(user, ...)
+ puts "Called"
+ mail(to: user.email, ...)
+ end
+end
+
+mail = Notifier.notify(user, ...) # Notifier#notify is not yet called at this point
+mail = mail.deliver_now # Prints "Called"
+```
+
+This should not result in any noticeable differences for most applications.
+However, if you need some non-mailer methods to be executed synchronously, and
+you were previously relying on the synchronous proxying behavior, you should
+define them as class methods on the mailer class directly:
+
+```ruby
+class Notifier < ActionMailer::Base
+ def self.broadcast_notifications(users, ...)
+ users.each { |user| Notifier.notify(user, ...) }
+ end
+end
+```
+
+### Foreign Key Support
+
+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
+migration DSL counterpart.
+
+The migration procedure is as follows:
+
+1. remove `gem "foreigner"` from the Gemfile.
+2. run `bundle install`.
+3. run `bin/rake db:schema:dump`.
+4. make sure that `db/schema.rb` contains every foreign key definition with
+the necessary options.
+
Upgrading from Rails 4.0 to Rails 4.1
-------------------------------------
@@ -421,7 +517,7 @@ class ReadOnlyModel < ActiveRecord::Base
end
```
-This behaviour was never intentionally supported. Due to a change in the internals
+This behavior was never intentionally supported. Due to a change in the internals
of `ActiveSupport::Callbacks`, this is no longer allowed in Rails 4.1. Using a
`return` statement in an inline callback block causes a `LocalJumpError` to
be raised when the callback is executed.
@@ -471,7 +567,7 @@ module FixtureFileHelpers
Digest::SHA2.hexdigest(File.read(Rails.root.join('test/fixtures', path)))
end
end
-ActiveRecord::FixtureSet.context_class.send :include, FixtureFileHelpers
+ActiveRecord::FixtureSet.context_class.include FixtureFileHelpers
```
### I18n enforcing available locales
@@ -731,7 +827,7 @@ file (in `config/application.rb`):
```ruby
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
-Bundler.require(:default, Rails.env)
+Bundler.require(*Rails.groups)
```
### vendor/plugins
@@ -795,7 +891,7 @@ Rails 4.0 extracted Active Resource to its own gem. If you still need the featur
* Rails 4.0 has changed how errors attach with the `ActiveModel::Validations::ConfirmationValidator`. Now when confirmation validations fail, the error will be attached to `:#{attribute}_confirmation` instead of `attribute`.
-* Rails 4.0 has changed `ActiveModel::Serializers::JSON.include_root_in_json` default value to `false`. Now, Active Model Serializers and Active Record objects have the same default behaviour. This means that you can comment or remove the following option in the `config/initializers/wrap_parameters.rb` file:
+* Rails 4.0 has changed `ActiveModel::Serializers::JSON.include_root_in_json` default value to `false`. Now, Active Model Serializers and Active Record objects have the same default behavior. This means that you can comment or remove the following option in the `config/initializers/wrap_parameters.rb` file:
```ruby
# Disable root element in JSON by default.
@@ -950,7 +1046,7 @@ The following changes are meant for upgrading your application to the latest
Make the following changes to your `Gemfile`.
```ruby
-gem 'rails', '3.2.18'
+gem 'rails', '3.2.21'
group :assets do
gem 'sass-rails', '~> 3.2.6'
@@ -1075,7 +1171,7 @@ You can help test performance with these additions to your test environment:
```ruby
# Configure static asset server for tests with Cache-Control for performance
-config.serve_static_assets = true
+config.serve_static_files = true
config.static_cache_control = 'public, max-age=3600'
```
diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md
index 7c3fd9f69d..e3856a285a 100644
--- a/guides/source/working_with_javascript_in_rails.md
+++ b/guides/source/working_with_javascript_in_rails.md
@@ -1,3 +1,5 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Working with JavaScript in Rails
================================
@@ -189,6 +191,34 @@ $(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)
@@ -355,7 +385,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/DOM/Manipulating_the_browser_history#The_pushState(\).C2.A0method),
+[PushState](https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Manipulating_the_browser_history#The_pushState()_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/rails.gemspec b/rails.gemspec
index 99685252e0..23404da9be 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 = '>= 1.9.3'
+ s.required_ruby_version = '>= 2.2.1'
s.required_rubygems_version = '>= 1.8.11'
s.license = 'MIT'
@@ -16,7 +16,7 @@ Gem::Specification.new do |s|
s.email = 'david@loudthinking.com'
s.homepage = 'http://www.rubyonrails.org'
- s.files = ['README.md'] + Dir['guides/**/*'] - Dir['guides/output/**/*']
+ s.files = ['README.md']
s.add_dependency 'activesupport', version
s.add_dependency 'actionpack', version
@@ -28,5 +28,5 @@ Gem::Specification.new do |s|
s.add_dependency 'railties', version
s.add_dependency 'bundler', '>= 1.3.0', '< 2.0'
- s.add_dependency 'sprockets-rails', '~> 3.0.0.beta1'
+ s.add_dependency 'sprockets-rails'
end
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 2960eccb3f..e60feed15f 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,178 +1,143 @@
-* Remove `--skip-action-view` option from `Rails::Generators::AppBase`.
+* Print `bundle install` output in `rails new` as soon as it's available
- Fixes #17023.
+ Running `rails new` will now print the output of `bundle install` as
+ it is available, instead of waiting until all gems finish installing.
- *Dan Olson*
+ *Max Holder*
-* Specify dummy app's db migrate path in plugin's test_helper.rb.
+* Respect `pluralize_table_names` when generating fixture file.
- Fixes #16877.
+ Fixes #19519.
- *Yukio Mizuta*
+ *Yuji Yaginuma*
-* Inject `Rack::Lock` if `config.eager_load` is false.
+* Add a new-line to the end of route method generated code.
- Fixes #15089.
+ We need to add a `\n`, because we cannot have two routes
+ in the same line.
- *Xavier Noria*
-
-* Change the path of dummy app location in plugin's test_helper.rb for cases
- you specify dummy_path option.
-
- *Yukio Mizuta*
-
-* Fix a bug in the `gem` method for Rails templates when non-String options
- are used.
-
- Fixes #16709.
-
- *Yves Senn*
+ *arthurnn*
-* The [web-console](https://github.com/rails/web-console) gem is now
- installed by default for new applications. It can help you debug
- development exceptions by spawning an interactive console in its cause
- binding.
+* Add `rake initializers`
- *Ryan Dao*, *Genadi Samokovarov*, *Guillermo Iguaran*
+ 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
+ process.
-* Add a `required` option to the model generator for associations
+ *Naoto Kaneko*
- *Sean Griffin*
-
-* Add `after_bundle` callbacks in Rails templates. Useful for allowing the
- generated binstubs to be added to version control.
-
- Fixes #16292.
+* Created rake restart task. Restarts your Rails app by touching the
+ `tmp/restart.txt`.
- *Stefan Kanev*
+ Fixes #18876.
-* Pull in the custom configuration concept from dhh/custom_configuration, which allows you to
- configure your own code through the Rails configuration object with custom configuration:
+ *Hyonjee Joo*
- # config/environments/production.rb
- config.x.payment_processing.schedule = :daily
- config.x.payment_processing.retries = 3
- config.x.super_debugger = true
+* Add `config/initializers/active_record_belongs_to_required_by_default.rb`
- These configuration points are then available through the configuration object:
+ Newly generated Rails apps have a new initializer called
+ `active_record_belongs_to_required_by_default.rb` which sets the value of
+ the configuration option `config.active_record.belongs_to_required_by_default`
+ to `true` when ActiveRecord is not skipped.
- Rails.configuration.x.payment_processing.schedule # => :daily
- Rails.configuration.x.payment_processing.retries # => 3
- Rails.configuration.x.super_debugger # => true
+ As a result, new Rails apps require `belongs_to` association on model
+ to be valid.
- *DHH*
+ This initializer is *not* added when running `rake rails:update`, so
+ old apps ported to Rails 5 will work without any change.
-* Scaffold generator `_form` partial adds `class="field"` for password
- confirmation fields.
+ *Josef Šimánek*
- *noinkling*
+* `delete` operations in configurations are run last in order to eliminate
+ 'No such middleware' errors when `insert_before` or `insert_after` are added
+ after the `delete` operation for the middleware being deleted.
-* Add `Rails::Application.config_for` to load a configuration for the current
- environment.
+ Fixes: #16433.
- # config/exception_notification.yml:
- production:
- url: http://127.0.0.1:8080
- namespace: my_app_production
- development:
- url: http://localhost:3001
- namespace: my_app_development
+ *Guo Xiang Tan*
- # config/production.rb
- Rails.application.configure do
- config.middleware.use ExceptionNotifier, config_for(:exception_notification)
- end
+* Newly generated applications get a `README.md` in Markdown.
- *Rafael Mendonça França*, *DHH*
-
-* Deprecate `Rails::Rack::LogTailer` without replacement.
-
- *Rafael Mendonça França*
-
-* Add a generic --skip-turbolinks options to generator.
+ *Xavier Noria*
- *Rafael Mendonça França*
+* Remove the documentation tasks `doc:app`, `doc:rails`, and `doc:guides`.
-* Invalid `bin/rails generate` commands will now show spelling suggestions.
+ *Xavier Noria*
- *Richard Schneeman*
+* Force generated routes to be inserted into routes.rb
-* Add `bin/setup` script to bootstrap an application.
+ *Andrew White*
- *Yves Senn*
+* Don't remove all line endings from routes.rb when revoking scaffold.
-* Replace double quotes with single quotes while adding an entry into Gemfile.
+ Fixes #15913.
- *Alexander Belaev*
+ *Andrew White*
-* Default `config.assets.digest` to `true` in development.
+* Rename `--skip-test-unit` option to `--skip-test` in app generator
- *Dan Kang*
+ *Melanie Gilman*
-* Load database configuration from the first `database.yml` available in paths.
+* Add the `method_source` gem to the default Gemfile for apps
- *Pier-Olivier Thibault*
+ *Sean Griffin*
-* Reading name and email from git for plugin gemspec.
+* Drop old test locations from `rake stats`
+ - test/functional
+ - test/unit
- Fixes #9589.
+ *Ravil Bayramgalin*
- *Arun Agrawal*, *Abd ar-Rahman Hamidi*, *Roman Shmatov*
+* Update `rake stats` to correctly count declarative tests
+ as methods in `_test.rb` files.
-* Fix `console` and `generators` blocks defined at different environments.
+ *Ravil Bayramgalin*
- Fixes #14748.
+* Remove deprecated `test:all` and `test:all:db` tasks.
*Rafael Mendonça França*
-* Move configuration of asset precompile list and version to an initializer.
-
- *Matthew Draper*
+* Remove deprecated `Rails::Rack::LogTailer`.
-* Remove sqlite3 lines from `.gitignore` if the application is not using sqlite3.
-
- *Dmitrii Golub*
-
-* Add public API to register new extensions for `rake notes`.
+ *Rafael Mendonça França*
- Example:
+* Remove deprecated `RAILS_CACHE` constant.
- config.annotations.register_extensions("scss", "sass") { |tag| /\/\/\s*(#{tag}):?\s*(.*)$/ }
+ *Rafael Mendonça França*
- *Roberto Miranda*
+* Remove deprecated `serve_static_assets` configuration.
-* Removed unnecessary `rails application` command.
+ *Rafael Mendonça França*
- *Arun Agrawal*
+* Use local variables in `_form.html.erb` partial generated by scaffold.
-* Make the `rails:template` rake task load the application's initializers.
+ *Andrew Kozlov*
- Fixes #12133.
+* Add `config/initializers/callback_terminator.rb`
- *Robin Dupret*
+ Newly generated Rails apps have a new initializer called
+ `callback_terminator.rb` which sets the value of the configuration option
+ `config.active_support.halt_callback_chains_on_return_false` to `false`.
-* Introduce `Rails.gem_version` as a convenience method to return
- `Gem::Version.new(Rails.version)`, suggesting a more reliable way to perform
- version comparison.
+ As a result, new Rails apps do not halt callback chains when a callback
+ returns `false`; only when they are explicitly halted with `throw(:abort)`.
- Example:
+ The terminator is *not* added when running `rake rails:update`, so returning
+ `false` will still work on old apps ported to Rails 5, displaying a
+ deprecation warning to prompt users to update their code to the new syntax.
- Rails.version #=> "4.1.2"
- Rails.gem_version #=> #<Gem::Version "4.1.2">
+ *claudiob*
- Rails.version > "4.1.10" #=> false
- Rails.gem_version > Gem::Version.new("4.1.10") #=> true
- Gem::Requirement.new("~> 4.1.2") =~ Rails.gem_version #=> true
+* Generated fixtures won't use the id when generated with references attributes.
- *Prem Sichanugrist*
+ *Pablo Olmos de Aguilera Corradini*
-* Avoid namespacing routes inside engines.
+* Add `--skip-action-mailer` option to the app generator.
- Mountable engines are namespaced by default so the generated routes
- were too while they should not.
+ *claudiob*
- Fixes #14079.
+* Autoload any second level directories called `app/*/concerns`.
- *Yves Senn*, *Carlos Antonio da Silva*, *Robin Dupret*
+ *Alex Robbin*
-Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/railties/CHANGELOG.md) for previous changes.
+Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/railties/CHANGELOG.md) for previous changes.
diff --git a/railties/MIT-LICENSE b/railties/MIT-LICENSE
index 2950f05b11..7c2197229d 100644
--- a/railties/MIT-LICENSE
+++ b/railties/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2014 David Heinemeier Hansson
+Copyright (c) 2004-2015 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index e7172e491f..b1f7c29b5a 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -14,7 +14,7 @@ require 'rails/version'
require 'active_support/railtie'
require 'action_dispatch/railtie'
-# For Ruby 1.9, UTF-8 is the default internal and external encoding.
+# UTF-8 is the default internal and external encoding.
silence_warnings do
Encoding.default_external = Encoding::UTF_8
Encoding.default_internal = Encoding::UTF_8
@@ -56,10 +56,18 @@ module Rails
application && application.config.root
end
+ # Returns the current Rails environment.
+ #
+ # Rails.env # => "development"
+ # Rails.env.development? # => true
+ # Rails.env.production? # => false
def env
@_env ||= ActiveSupport::StringInquirer.new(ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development")
end
+ # Sets the Rails environment.
+ #
+ # Rails.env = "staging" # => "staging"
def env=(environment)
@_env = ActiveSupport::StringInquirer.new(environment)
end
diff --git a/railties/lib/rails/api/task.rb b/railties/lib/rails/api/task.rb
index 4d49244807..a082932632 100644
--- a/railties/lib/rails/api/task.rb
+++ b/railties/lib/rails/api/task.rb
@@ -152,19 +152,5 @@ module Rails
File.read('RAILS_VERSION').strip
end
end
-
- class AppTask < Task
- def component_root_dir(gem_name)
- $:.grep(%r{#{gem_name}[\w.-]*/lib\z}).first[0..-5]
- end
-
- def api_dir
- 'doc/api'
- end
-
- def rails_version
- Rails::VERSION::STRING
- end
- end
end
end
diff --git a/railties/lib/rails/app_rails_loader.rb b/railties/lib/rails/app_rails_loader.rb
index 39d8007333..9a7c6c5f2d 100644
--- a/railties/lib/rails/app_rails_loader.rb
+++ b/railties/lib/rails/app_rails_loader.rb
@@ -1,4 +1,5 @@
require 'pathname'
+require 'rails/version'
module Rails
module AppRailsLoader
@@ -9,7 +10,7 @@ module Rails
BUNDLER_WARNING = <<EOS
Looks like your app's ./bin/rails is a stub that was generated by Bundler.
-In Rails 4, your app's bin/ directory contains executables that are versioned
+In Rails #{Rails::VERSION::MAJOR}, your app's bin/ directory contains executables that are versioned
like any other source code, rather than stubs that are generated on demand.
Here's how to upgrade:
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 18d9cb72d6..e9683d4a95 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -159,8 +159,9 @@ module Rails
# Implements call according to the Rack API. It simply
# dispatches the request to the underlying middleware stack.
def call(env)
- env["ORIGINAL_FULLPATH"] = build_original_fullpath(env)
- env["ORIGINAL_SCRIPT_NAME"] = env["SCRIPT_NAME"]
+ req = ActionDispatch::Request.new env
+ env["ORIGINAL_FULLPATH"] = req.fullpath
+ env["ORIGINAL_SCRIPT_NAME"] = req.script_name
super(env)
end
@@ -178,7 +179,7 @@ module Rails
key_generator = ActiveSupport::KeyGenerator.new(secrets.secret_key_base, iterations: 1000)
ActiveSupport::CachingKeyGenerator.new(key_generator)
else
- ActiveSupport::LegacyKeyGenerator.new(config.secret_token)
+ ActiveSupport::LegacyKeyGenerator.new(secrets.secret_token)
end
end
@@ -248,7 +249,7 @@ module Rails
super.merge({
"action_dispatch.parameter_filter" => config.filter_parameters,
"action_dispatch.redirect_filter" => config.filter_redirect,
- "action_dispatch.secret_token" => config.secret_token,
+ "action_dispatch.secret_token" => secrets.secret_token,
"action_dispatch.secret_key_base" => secrets.secret_key_base,
"action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions,
"action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local,
@@ -368,7 +369,21 @@ module Rails
@config = configuration
end
- def secrets #:nodoc:
+ # Returns secrets added to config/secrets.yml.
+ #
+ # Example:
+ #
+ # development:
+ # secret_key_base: 836fa3665997a860728bcb9e9a1e704d427cfc920e79d847d79c8a9a907b9e965defa4154b2b86bdec6930adbe33f21364523a6f6ce363865724549fdfc08553
+ # test:
+ # secret_key_base: 5a37811464e7d378488b0f073e2193b093682e4e21f5d6f3ae0a4e1781e61a351fdc878a843424e81c73fb484a40d23f92c8dafac4870e74ede6e5e174423010
+ # production:
+ # secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
+ # namespace: my_app_production
+ #
+ # +Rails.application.secrets.namespace+ returns +my_app_production+ in the
+ # production environment.
+ def secrets
@secrets ||= begin
secrets = ActiveSupport::OrderedOptions.new
yaml = config.paths["config/secrets"].first
@@ -381,6 +396,8 @@ module Rails
# Fallback to config.secret_key_base if secrets.secret_key_base isn't set
secrets.secret_key_base ||= config.secret_key_base
+ # Fallback to config.secret_token if secrets.secret_token isn't set
+ secrets.secret_token ||= config.secret_token
secrets
end
@@ -404,25 +421,16 @@ module Rails
console do
unless ::Kernel.private_method_defined?(:y)
- if RUBY_VERSION >= '2.0'
- require "psych/y"
- else
- module ::Kernel
- def y(*objects)
- puts ::Psych.dump_stream(*objects)
- end
- private :y
- end
- end
+ require "psych/y"
end
end
# Return an array of railties respecting the order they're loaded
# and the order specified by the +railties_order+ config.
#
- # While when running initializers we need engines in reverse
- # order here when copying migrations from railties we need then in the same
- # order as given by +railties_order+
+ # While running initializers we need engines in reverse order here when
+ # copying migrations from railties ; we need them in the order given by
+ # +railties_order+.
def migration_railties # :nodoc:
ordered_railties.flatten - [self]
end
@@ -497,21 +505,14 @@ module Rails
default_stack.build_stack
end
- def build_original_fullpath(env) #:nodoc:
- path_info = env["PATH_INFO"]
- query_string = env["QUERY_STRING"]
- script_name = env["SCRIPT_NAME"]
-
- if query_string.present?
- "#{script_name}#{path_info}?#{query_string}"
- else
- "#{script_name}#{path_info}"
- end
- end
-
def validate_secret_key_config! #:nodoc:
- if secrets.secret_key_base.blank? && config.secret_token.blank?
- raise "Missing `secret_key_base` for '#{Rails.env}' environment, set this value in `config/secrets.yml`"
+ if secrets.secret_key_base.blank?
+ ActiveSupport::Deprecation.warn "You didn't set `secret_key_base`. " +
+ "Read the upgrade documentation to learn more about this new config option."
+
+ if secrets.secret_token.blank?
+ raise "Missing `secret_token` and `secret_key_base` for '#{Rails.env}' environment, set these values in `config/secrets.yml`"
+ end
end
end
end
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 786dcee007..dc3ec4274b 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -6,12 +6,12 @@ require 'rails/source_annotation_extractor'
module Rails
class Application
class Configuration < ::Rails::Engine::Configuration
- attr_accessor :allow_concurrency, :asset_host, :assets, :autoflush_log,
+ attr_accessor :allow_concurrency, :asset_host, :autoflush_log,
:cache_classes, :cache_store, :consider_all_requests_local, :console,
: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_assets, :ssl_options, :static_cache_control, :session_options,
+ :serve_static_files, :ssl_options, :static_cache_control, :session_options,
:time_zone, :reload_classes_only_on_change,
:beginning_of_week, :filter_redirect, :x
@@ -26,7 +26,7 @@ module Rails
@filter_parameters = []
@filter_redirect = []
@helpers_paths = []
- @serve_static_assets = true
+ @serve_static_files = true
@static_cache_control = nil
@force_ssl = false
@ssl_options = {}
@@ -49,21 +49,6 @@ module Rails
@secret_token = nil
@secret_key_base = nil
@x = Custom.new
-
- @assets = ActiveSupport::OrderedOptions.new
- @assets.enabled = true
- @assets.paths = []
- @assets.precompile = [ Proc.new { |path, fn| fn =~ /app\/assets/ && !%w(.js .css).include?(File.extname(path)) },
- /(?:\/|\\|\A)application\.(css|js)$/ ]
- @assets.prefix = "/assets"
- @assets.version = '1.0'
- @assets.debug = false
- @assets.compile = true
- @assets.digest = false
- @assets.cache_store = [ :file_store, "#{root}/tmp/cache/assets/#{Rails.env}/" ]
- @assets.js_compressor = nil
- @assets.css_compressor = nil
- @assets.logger = nil
end
def encoding=(value)
@@ -118,7 +103,7 @@ module Rails
end
def log_level
- @log_level ||= :debug
+ @log_level ||= (Rails.env.production? ? :info : :debug)
end
def colorize_logging
diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb
index d1789192ef..02eea82b0c 100644
--- a/railties/lib/rails/application/default_middleware_stack.rb
+++ b/railties/lib/rails/application/default_middleware_stack.rb
@@ -17,7 +17,7 @@ module Rails
middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header
- if config.serve_static_assets
+ if config.serve_static_files
middleware.use ::ActionDispatch::Static, paths["public"].first, config.static_cache_control
end
diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb
index 7a1bb1e25c..0599e988d9 100644
--- a/railties/lib/rails/application/finisher.rb
+++ b/railties/lib/rails/application/finisher.rb
@@ -108,6 +108,13 @@ module Rails
ActionDispatch::Reloader.to_cleanup(&callback)
end
end
+
+ # Disable dependency loading during request cycle
+ initializer :disable_dependency_loading do
+ if config.eager_load && config.cache_classes
+ ActiveSupport::Dependencies.unhook!
+ end
+ end
end
end
end
diff --git a/railties/lib/rails/application/routes_reloader.rb b/railties/lib/rails/application/routes_reloader.rb
index 737977adf9..cf0a4e128f 100644
--- a/railties/lib/rails/application/routes_reloader.rb
+++ b/railties/lib/rails/application/routes_reloader.rb
@@ -41,9 +41,7 @@ module Rails
end
def finalize!
- route_sets.each do |routes|
- routes.finalize!
- end
+ route_sets.each(&:finalize!)
end
def revert
diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb
index 0ae6d2a455..fd352dc9b7 100644
--- a/railties/lib/rails/code_statistics.rb
+++ b/railties/lib/rails/code_statistics.rb
@@ -6,9 +6,8 @@ class CodeStatistics #:nodoc:
'Helper tests',
'Model tests',
'Mailer tests',
- 'Integration tests',
- 'Functional tests (old)',
- 'Unit tests (old)']
+ 'Job tests',
+ 'Integration tests']
def initialize(*pairs)
@pairs = pairs
@@ -42,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
@@ -72,12 +69,12 @@ class CodeStatistics #:nodoc:
def print_header
print_splitter
- puts "| Name | Lines | LOC | Classes | Methods | M/C | LOC/M |"
+ puts "| Name | Lines | LOC | Classes | Methods | M/C | LOC/M |"
print_splitter
end
def print_splitter
- puts "+----------------------+-------+-------+---------+---------+-----+-------+"
+ puts "+----------------------+--------+--------+---------+---------+-----+-------+"
end
def print_line(name, statistics)
@@ -85,8 +82,8 @@ class CodeStatistics #:nodoc:
loc_over_m = (statistics.code_lines / statistics.methods) - 2 rescue loc_over_m = 0
puts "| #{name.ljust(20)} " \
- "| #{statistics.lines.to_s.rjust(5)} " \
- "| #{statistics.code_lines.to_s.rjust(5)} " \
+ "| #{statistics.lines.to_s.rjust(6)} " \
+ "| #{statistics.code_lines.to_s.rjust(6)} " \
"| #{statistics.classes.to_s.rjust(7)} " \
"| #{statistics.methods.to_s.rjust(7)} " \
"| #{m_over_c.to_s.rjust(3)} " \
diff --git a/railties/lib/rails/code_statistics_calculator.rb b/railties/lib/rails/code_statistics_calculator.rb
index 60e4aef9b7..a142236dbe 100644
--- a/railties/lib/rails/code_statistics_calculator.rb
+++ b/railties/lib/rails/code_statistics_calculator.rb
@@ -24,6 +24,8 @@ class CodeStatisticsCalculator #:nodoc:
}
}
+ PATTERNS[:minitest] = PATTERNS[:rb].merge method: /^\s*(def|test)\s+['"_a-z]/
+
def initialize(lines = 0, code_lines = 0, classes = 0, methods = 0)
@lines = lines
@code_lines = code_lines
@@ -74,6 +76,10 @@ class CodeStatisticsCalculator #:nodoc:
private
def file_type(file_path)
- File.extname(file_path).sub(/\A\./, '').downcase.to_sym
+ if file_path.end_with? '_test.rb'
+ :minitest
+ else
+ File.extname(file_path).sub(/\A\./, '').downcase.to_sym
+ end
end
end
diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb
index f32bf772a5..12bd73db24 100644
--- a/railties/lib/rails/commands.rb
+++ b/railties/lib/rails/commands.rb
@@ -6,7 +6,8 @@ aliases = {
"c" => "console",
"s" => "server",
"db" => "dbconsole",
- "r" => "runner"
+ "r" => "runner",
+ "t" => "test",
}
command = ARGV.shift
diff --git a/railties/lib/rails/commands/commands_tasks.rb b/railties/lib/rails/commands/commands_tasks.rb
index 8bae08e44e..d8d4080c3e 100644
--- a/railties/lib/rails/commands/commands_tasks.rb
+++ b/railties/lib/rails/commands/commands_tasks.rb
@@ -14,6 +14,7 @@ The most common rails commands are:
generate Generate new code (short-cut alias: "g")
console Start the Rails console (short-cut alias: "c")
server Start the Rails server (short-cut alias: "s")
+ test Run tests (short-cut alias: "t")
dbconsole Start a console for the database specified in config/database.yml
(short-cut alias: "db")
new Create a new Rails application. "rails new my_app" creates a
@@ -27,7 +28,7 @@ In addition to those, there are:
All commands can be run with -h (or --help) for more information.
EOT
- COMMAND_WHITELIST = %w(plugin generate destroy console server dbconsole runner new version help)
+ COMMAND_WHITELIST = %w(plugin generate destroy console server dbconsole runner new version help test)
def initialize(argv)
@argv = argv
@@ -81,6 +82,10 @@ EOT
end
end
+ def test
+ require_command!("test")
+ end
+
def dbconsole
require_command!("dbconsole")
Rails::DBConsole.start
diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb
index 96ced3c2f9..5d37a2b699 100644
--- a/railties/lib/rails/commands/console.rb
+++ b/railties/lib/rails/commands/console.rb
@@ -18,14 +18,6 @@ module Rails
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.on("--debugger", 'Enables the debugger.') do |v|
- if RUBY_VERSION < '2.0.0'
- options[:debugger] = v
- else
- puts "=> Notice: debugger option is ignored since Ruby 2.0 and " \
- "it will be removed in future versions."
- end
- end
opt.parse!(arguments)
end
@@ -76,25 +68,7 @@ module Rails
Rails.env = environment
end
- if RUBY_VERSION < '2.0.0'
- def debugger?
- options[:debugger]
- end
-
- def require_debugger
- require 'debugger'
- puts "=> Debugger enabled"
- rescue LoadError
- puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle it and try again."
- exit(1)
- end
- end
-
def start
- if RUBY_VERSION < '2.0.0'
- require_debugger if debugger?
- end
-
set_environment! if environment?
if sandbox?
@@ -105,7 +79,7 @@ module Rails
end
if defined?(console::ExtendCommandBundle)
- console::ExtendCommandBundle.send :include, Rails::ConsoleMethods
+ console::ExtendCommandBundle.include(Rails::ConsoleMethods)
end
console.start
end
diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb
index 1a2613a8d0..5175e31f14 100644
--- a/railties/lib/rails/commands/dbconsole.rb
+++ b/railties/lib/rails/commands/dbconsole.rb
@@ -1,7 +1,6 @@
require 'erb'
require 'yaml'
require 'optparse'
-require 'rbconfig'
module Rails
class DBConsole
@@ -44,7 +43,7 @@ module Rails
find_cmd_and_exec(['mysql', 'mysql5'], *args)
- when "postgresql", "postgres", "postgis"
+ when /^postgres|^postgis/
ENV['PGUSER'] = config["username"] if config["username"]
ENV['PGHOST'] = config["host"] if config["host"]
ENV['PGPORT'] = config["port"].to_s if config["port"]
@@ -74,6 +73,21 @@ module Rails
find_cmd_and_exec('sqlplus', logon)
+ when "sqlserver"
+ args = []
+
+ args += ["-D", "#{config['database']}"] if config['database']
+ args += ["-U", "#{config['username']}"] if config['username']
+ args += ["-P", "#{config['password']}"] if config['password']
+
+ if config['host']
+ host_arg = "#{config['host']}"
+ host_arg << ":#{config['port']}" if config['port']
+ args += ["-S", host_arg]
+ end
+
+ find_cmd_and_exec("sqsh", *args)
+
else
abort "Unknown command-line client for #{config['database']}. Submit a Rails patch to add support!"
end
@@ -157,13 +171,15 @@ module Rails
commands = Array(commands)
dirs_on_path = ENV['PATH'].to_s.split(File::PATH_SEPARATOR)
- commands += commands.map{|cmd| "#{cmd}.exe"} if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
+ 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.executable? full_path_command
+ File.file?(full_path_command) && File.executable?(full_path_command)
end
end
diff --git a/railties/lib/rails/commands/destroy.rb b/railties/lib/rails/commands/destroy.rb
index 5479da86a0..ce26cc3fde 100644
--- a/railties/lib/rails/commands/destroy.rb
+++ b/railties/lib/rails/commands/destroy.rb
@@ -1,5 +1,7 @@
require 'rails/generators'
+#if no argument/-h/--help is passed to rails destroy command, then
+#it generates the help associated.
if [nil, "-h", "--help"].include?(ARGV.first)
Rails::Generators.help 'destroy'
exit
diff --git a/railties/lib/rails/commands/generate.rb b/railties/lib/rails/commands/generate.rb
index 351c59c645..926c36b967 100644
--- a/railties/lib/rails/commands/generate.rb
+++ b/railties/lib/rails/commands/generate.rb
@@ -1,5 +1,7 @@
require 'rails/generators'
+#if no argument/-h/--help is passed to rails generate command, then
+#it generates the help associated.
if [nil, "-h", "--help"].include?(ARGV.first)
Rails::Generators.help 'generate'
exit
diff --git a/railties/lib/rails/commands/plugin.rb b/railties/lib/rails/commands/plugin.rb
index 95bbdd4cdf..52d8966ead 100644
--- a/railties/lib/rails/commands/plugin.rb
+++ b/railties/lib/rails/commands/plugin.rb
@@ -11,7 +11,7 @@ else
end
if File.exist?(railsrc)
extra_args_string = File.read(railsrc)
- extra_args = extra_args_string.split(/\n+/).flat_map {|l| l.split}
+ extra_args = extra_args_string.split(/\n+/).flat_map(&:split)
puts "Using #{extra_args.join(" ")} from #{railsrc}"
ARGV.insert(1, *extra_args)
end
diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb
index 3a71f8d3f8..86bce9b2fe 100644
--- a/railties/lib/rails/commands/runner.rb
+++ b/railties/lib/rails/commands/runner.rb
@@ -1,5 +1,4 @@
require 'optparse'
-require 'rbconfig'
options = { environment: (ENV['RAILS_ENV'] || ENV['RACK_ENV'] || "development").dup }
code_or_file = nil
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index e39f0920af..546d3725d8 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -28,14 +28,6 @@ module Rails
opts.on("-c", "--config=file", String,
"Uses a custom rackup configuration.") { |v| options[:config] = v }
opts.on("-d", "--daemon", "Runs server as a Daemon.") { options[:daemonize] = true }
- opts.on("-u", "--debugger", "Enables the debugger.") do
- if RUBY_VERSION < '2.0.0'
- options[:debugger] = true
- else
- puts "=> Notice: debugger option is ignored since Ruby 2.0 and " \
- "it will be removed in future versions."
- end
- end
opts.on("-e", "--environment=name", String,
"Specifies the environment to run this server under (test/development/production).",
"Default: development") { |v| options[:environment] = v }
@@ -86,9 +78,6 @@ module Rails
def middleware
middlewares = []
- if RUBY_VERSION < '2.0.0'
- middlewares << [Rails::Rack::Debugger] if options[:debugger]
- end
middlewares << [::Rack::ContentLength]
# FIXME: add Rack::Lock in the case people are using webrick.
@@ -102,17 +91,12 @@ module Rails
Hash.new(middlewares)
end
- def log_path
- "log/#{options[:environment]}.log"
- end
-
def default_options
super.merge({
Port: 3000,
DoNotReverseLookup: true,
environment: (ENV['RAILS_ENV'] || ENV['RACK_ENV'] || "development").dup,
daemonize: false,
- debugger: false,
pid: File.expand_path("tmp/pids/server.pid"),
config: File.expand_path("config.ru")
})
@@ -130,7 +114,7 @@ module Rails
end
def create_tmp_directories
- %w(cache pids sessions sockets).each do |dir_to_make|
+ %w(cache pids sockets).each do |dir_to_make|
FileUtils.mkdir_p(File.join(Rails.root, 'tmp', dir_to_make))
end
end
diff --git a/railties/lib/rails/commands/test.rb b/railties/lib/rails/commands/test.rb
new file mode 100644
index 0000000000..598e224a6f
--- /dev/null
+++ b/railties/lib/rails/commands/test.rb
@@ -0,0 +1,5 @@
+require "rails/test_unit/runner"
+
+$: << File.expand_path("../../test", APP_PATH)
+
+Rails::TestRunner.run(ARGV)
diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb
index f5d7dede66..76364cea8f 100644
--- a/railties/lib/rails/configuration.rb
+++ b/railties/lib/rails/configuration.rb
@@ -18,11 +18,11 @@ module Rails
# This will put the <tt>Magical::Unicorns</tt> middleware on the end of the stack.
# You can use +insert_before+ if you wish to add a middleware before another:
#
- # config.middleware.insert_before ActionDispatch::Head, Magical::Unicorns
+ # config.middleware.insert_before Rack::Head, Magical::Unicorns
#
# There's also +insert_after+ which will insert a middleware after another:
#
- # config.middleware.insert_after ActionDispatch::Head, Magical::Unicorns
+ # config.middleware.insert_after Rack::Head, Magical::Unicorns
#
# Middlewares can also be completely swapped out and replaced with others:
#
@@ -35,6 +35,7 @@ module Rails
class MiddlewareStackProxy
def initialize
@operations = []
+ @delete_operations = []
end
def insert_before(*args, &block)
@@ -56,7 +57,7 @@ module Rails
end
def delete(*args, &block)
- @operations << [__method__, args, block]
+ @delete_operations << [__method__, args, block]
end
def unshift(*args, &block)
@@ -64,9 +65,10 @@ module Rails
end
def merge_into(other) #:nodoc:
- @operations.each do |operation, args, block|
+ (@operations + @delete_operations).each do |operation, args, block|
other.send(operation, *args, &block)
end
+
other
end
end
diff --git a/railties/lib/rails/deprecation.rb b/railties/lib/rails/deprecation.rb
deleted file mode 100644
index 89f54069e9..0000000000
--- a/railties/lib/rails/deprecation.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-require 'active_support/deprecation/proxy_wrappers'
-
-module Rails
- class DeprecatedConstant < ActiveSupport::Deprecation::DeprecatedConstantProxy
- def self.deprecate(old, current)
- # double assignment is used to avoid "assigned but unused variable" warning
- constant = constant = new(old, current)
- eval "::#{old} = constant"
- end
-
- private
-
- def target
- ::Kernel.eval @new_const.to_s
- end
- end
-
- DeprecatedConstant.deprecate('RAILS_CACHE', '::Rails.cache')
-end
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index d985518fd9..83cee28fa3 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -110,8 +110,8 @@ module Rails
#
# == Endpoint
#
- # An engine can be also a rack application. It can be useful if you have a rack application that
- # you would like to wrap with +Engine+ and provide some of the +Engine+'s features.
+ # An engine can also be a rack application. It can be useful if you have a rack application that
+ # you would like to wrap with +Engine+ and provide with some of the +Engine+'s features.
#
# To do that, use the +endpoint+ method:
#
@@ -217,7 +217,7 @@ module Rails
# <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 do namespace all your routes. With an isolated engine,
+ # 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:
#
# MyEngine::Engine.routes.draw do
@@ -296,7 +296,7 @@ module Rails
# helper MyEngine::SharedEngineHelper
# end
#
- # If you want to include all of the engine's helpers, you can use #helper method on an engine's
+ # If you want to include all of the engine's helpers, you can use the #helper method on an engine's
# instance:
#
# class ApplicationController < ActionController::Base
@@ -312,7 +312,7 @@ module Rails
# Engines can have their own migrations. The default path for migrations is exactly the same
# as in application: <tt>db/migrate</tt>
#
- # To use engine's migrations in application you can use rake task, which copies them to
+ # To use engine's migrations in application you can use the rake task below, which copies them to
# application's dir:
#
# rake ENGINE_NAME:install:migrations
@@ -328,7 +328,7 @@ module Rails
#
# == Loading priority
#
- # In order to change engine's priority you can use +config.railties_order+ in main application.
+ # In order to change engine's priority you can use +config.railties_order+ in the main application.
# It will affect the priority of loading views, helpers, assets and all the other files
# related to engine or application.
#
@@ -351,7 +351,7 @@ module Rails
base.called_from = begin
call_stack = if Kernel.respond_to?(:caller_locations)
- caller_locations.map(&:path)
+ caller_locations.map { |l| l.absolute_path || l.path }
else
# Remove the line number from backtraces making sure we don't leave anything behind
caller.map { |p| p.sub(/:\d+.*/, '') }
@@ -484,7 +484,7 @@ module Rails
helpers = Module.new
all = ActionController::Base.all_helpers_from_path(helpers_paths)
ActionController::Base.modules_for_helpers(all).each do |mod|
- helpers.send(:include, mod)
+ helpers.include(mod)
end
helpers
end
@@ -513,7 +513,7 @@ module Rails
def call(env)
env.merge!(env_config)
if env['SCRIPT_NAME']
- env["ROUTES_#{routes.object_id}_SCRIPT_NAME"] = env['SCRIPT_NAME'].dup
+ env[routes.env_key] = env['SCRIPT_NAME'].dup
end
app.call(env)
end
@@ -528,7 +528,7 @@ module Rails
# Defines the routes for this engine. If a block is given to
# routes, it is appended to the engine.
def routes
- @routes ||= ActionDispatch::Routing::RouteSet.new
+ @routes ||= ActionDispatch::Routing::RouteSet.new_with_config(config)
@routes.append(&Proc.new) if block_given?
@routes
end
@@ -571,10 +571,10 @@ module Rails
end
initializer :add_routing_paths do |app|
- paths = self.paths["config/routes.rb"].existent
+ routing_paths = self.paths["config/routes.rb"].existent
- if routes? || paths.any?
- app.routes_reloader.paths.unshift(*paths)
+ if routes? || routing_paths.any?
+ app.routes_reloader.paths.unshift(*routing_paths)
app.routes_reloader.route_sets << routes
end
end
@@ -599,12 +599,6 @@ module Rails
end
end
- initializer :append_assets_path, group: :all do |app|
- app.config.assets.paths.unshift(*paths["vendor/assets"].existent_directories)
- app.config.assets.paths.unshift(*paths["lib/assets"].existent_directories)
- app.config.assets.paths.unshift(*paths["app/assets"].existent_directories)
- end
-
initializer :prepend_helpers_path do |app|
if !isolated? || (app == self)
app.config.helpers_paths.unshift(*paths["app/helpers"].existent)
diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb
index 10d1821709..62a4139d07 100644
--- a/railties/lib/rails/engine/configuration.rb
+++ b/railties/lib/rails/engine/configuration.rb
@@ -39,7 +39,7 @@ module Rails
@paths ||= begin
paths = Rails::Paths::Root.new(@root)
- paths.add "app", eager_load: true, glob: "*"
+ paths.add "app", eager_load: true, glob: "{*,*/concerns}"
paths.add "app/assets", glob: "*"
paths.add "app/controllers", eager_load: true
paths.add "app/helpers", eager_load: true
@@ -47,9 +47,6 @@ module Rails
paths.add "app/mailers", eager_load: true
paths.add "app/views"
- paths.add "app/controllers/concerns", eager_load: true
- paths.add "app/models/concerns", eager_load: true
-
paths.add "lib", load_path: true
paths.add "lib/assets", glob: "*"
paths.add "lib/tasks", glob: "**/*.rake"
diff --git a/railties/lib/rails/gem_version.rb b/railties/lib/rails/gem_version.rb
index 8abed99f2c..7d74b1bfe5 100644
--- a/railties/lib/rails/gem_version.rb
+++ b/railties/lib/rails/gem_version.rb
@@ -5,10 +5,10 @@ module Rails
end
module VERSION
- MAJOR = 4
- MINOR = 2
+ MAJOR = 5
+ MINOR = 0
TINY = 0
- PRE = "beta4"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index bf2390cb7e..341291f08b 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -153,13 +153,13 @@ module Rails
def self.invoke(namespace, args=ARGV, config={})
names = namespace.to_s.split(':')
if klass = find_by_namespace(names.pop, names.any? && names.join(':'))
- args << "--help" if args.empty? && klass.arguments.any? { |a| a.required? }
+ args << "--help" if args.empty? && klass.arguments.any?(&:required?)
klass.start(args, config)
else
- options = sorted_groups.map(&:last).flatten
+ options = sorted_groups.flat_map(&:last)
suggestions = options.sort_by {|suggested| levenshtein_distance(namespace.to_s, suggested) }.first(3)
msg = "Could not find generator '#{namespace}'. "
- msg << "Maybe you meant #{ suggestions.map {|s| "'#{s}'"}.join(" or ") }\n"
+ msg << "Maybe you meant #{ suggestions.map {|s| "'#{s}'"}.to_sentence(last_word_connector: " or ") }\n"
msg << "Run `rails generate --help` for more options."
puts msg
end
@@ -226,7 +226,7 @@ module Rails
def self.public_namespaces
lookup!
- subclasses.map { |k| k.namespace }
+ subclasses.map(&:namespace)
end
def self.print_generators
@@ -260,11 +260,9 @@ module Rails
t = str2
n = s.length
m = t.length
- max = n/2
return m if (0 == n)
return n if (0 == m)
- return n if (n - m).abs > max
d = (0..m).to_a
x = nil
@@ -286,7 +284,7 @@ module Rails
d[m] = x
end
- return x
+ x
end
# Prints a list of generators.
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index ffdb314612..70a20801a0 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -1,5 +1,4 @@
require 'open-uri'
-require 'rbconfig'
module Rails
module Generators
@@ -189,7 +188,7 @@ module Rails
# generate(:authenticated, "user session")
def generate(what, *args)
log :generate, what
- argument = args.flat_map {|arg| arg.to_s }.join(" ")
+ argument = args.flat_map(&:to_s).join(" ")
in_root { run_ruby_script("bin/rails generate #{what} #{argument}", verbose: false) }
end
@@ -219,10 +218,10 @@ module Rails
# route "root 'welcome#index'"
def route(routing_code)
log :route, routing_code
- sentinel = /\.routes\.draw do\s*$/
+ sentinel = /\.routes\.draw do\s*\n/m
in_root do
- inject_into_file 'config/routes.rb', "\n #{routing_code}", { after: sentinel, verbose: false }
+ inject_into_file 'config/routes.rb', " #{routing_code}\n", { after: sentinel, verbose: false, force: true }
end
end
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 92ed9136a0..10deeb0ba2 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -38,6 +38,10 @@ module Rails
class_option :skip_keeps, type: :boolean, default: false,
desc: 'Skip source control .keep files'
+ class_option :skip_action_mailer, type: :boolean, aliases: "-M",
+ default: false,
+ desc: "Skip Action Mailer files"
+
class_option :skip_active_record, type: :boolean, aliases: '-O', default: false,
desc: 'Skip Active Record files'
@@ -65,8 +69,8 @@ module Rails
class_option :skip_turbolinks, type: :boolean, default: false,
desc: 'Skip turbolinks gem'
- class_option :skip_test_unit, type: :boolean, aliases: '-T', default: false,
- desc: 'Skip Test::Unit files'
+ class_option :skip_test, type: :boolean, aliases: '-T', default: false,
+ desc: 'Skip test files'
class_option :rc, type: :string, default: false,
desc: "Path to file containing extra configuration options for rails command"
@@ -109,7 +113,6 @@ module Rails
assets_gemfile_entry,
javascript_gemfile_entry,
jbuilder_gemfile_entry,
- sdoc_gemfile_entry,
psych_gemfile_entry,
@extra_entries].flatten.find_all(&@gem_filter)
end
@@ -123,7 +126,7 @@ module Rails
def builder
@builder ||= begin
builder_class = get_builder_class
- builder_class.send(:include, ActionMethods)
+ builder_class.include(ActionMethods)
builder_class.new(self)
end
end
@@ -164,7 +167,7 @@ module Rails
end
def include_all_railties?
- !options[:skip_active_record] && !options[:skip_test_unit] && !options[:skip_sprockets]
+ options.values_at(:skip_active_record, :skip_action_mailer, :skip_test, :skip_sprockets).none?
end
def comment_if(value)
@@ -180,8 +183,12 @@ module Rails
super
end
- def self.github(name, github, comment = nil)
- new(name, nil, comment, github: github)
+ def self.github(name, github, branch = nil, comment = nil)
+ if branch
+ new(name, nil, comment, github: github, branch: branch)
+ else
+ new(name, nil, comment, github: github)
+ end
end
def self.version(name, version, comment = nil)
@@ -195,9 +202,15 @@ module Rails
def rails_gemfile_entry
if options.dev?
- [GemfileEntry.path('rails', Rails::Generators::RAILS_DEV_PATH)]
+ [
+ GemfileEntry.path('rails', Rails::Generators::RAILS_DEV_PATH),
+ GemfileEntry.github('arel', 'rails/arel')
+ ]
elsif options.edge?
- [GemfileEntry.github('rails', 'rails/rails')]
+ [
+ GemfileEntry.github('rails', 'rails/rails'),
+ GemfileEntry.github('arel', 'rails/arel')
+ ]
else
[GemfileEntry.version('rails',
Rails::VERSION::STRING,
@@ -236,16 +249,8 @@ module Rails
return [] if options[:skip_sprockets]
gems = []
- if options.dev? || options.edge?
- gems << GemfileEntry.github('sprockets-rails', 'rails/sprockets-rails',
- 'Use edge version of sprockets-rails')
- gems << GemfileEntry.github('sass-rails', 'rails/sass-rails',
- 'Use SCSS for stylesheets')
- else
- gems << GemfileEntry.version('sass-rails',
- '~> 5.0.0.beta1',
+ gems << GemfileEntry.version('sass-rails', '~> 5.0',
'Use SCSS for stylesheets')
- end
gems << GemfileEntry.version('uglifier',
'>= 1.3.0',
@@ -259,15 +264,10 @@ module Rails
GemfileEntry.version('jbuilder', '~> 2.0', comment)
end
- def sdoc_gemfile_entry
- comment = 'bundle exec rake doc:rails generates the API under doc/api.'
- GemfileEntry.new('sdoc', '~> 0.4.0', comment, group: :doc)
- end
-
def coffee_gemfile_entry
comment = 'Use CoffeeScript for .coffee assets and views'
if options.dev? || options.edge?
- GemfileEntry.github 'coffee-rails', 'rails/coffee-rails', comment
+ GemfileEntry.github 'coffee-rails', 'rails/coffee-rails', nil, comment
else
GemfileEntry.version 'coffee-rails', '~> 4.1.0', comment
end
@@ -278,14 +278,8 @@ module Rails
[]
else
gems = [coffee_gemfile_entry, javascript_runtime_gemfile_entry]
-
- if options[:javascript] == 'jquery'
- gems << GemfileEntry.version('jquery-rails', '~> 4.0.0.beta2',
- 'Use jQuery as the JavaScript library')
- else
- gems << GemfileEntry.version("#{options[:javascript]}-rails", nil,
- "Use #{options[:javascript]} as the JavaScript library")
- end
+ gems << GemfileEntry.version("#{options[:javascript]}-rails", nil,
+ "Use #{options[:javascript]} as the JavaScript library")
unless options[:skip_turbolinks]
gems << GemfileEntry.version("turbolinks", nil,
@@ -297,7 +291,7 @@ module Rails
end
def javascript_runtime_gemfile_entry
- comment = 'See https://github.com/sstephenson/execjs#readme for more supported runtimes'
+ comment = 'See https://github.com/rails/execjs#readme for more supported runtimes'
if defined?(JRUBY_VERSION)
GemfileEntry.version 'therubyrhino', nil, comment
else
@@ -321,10 +315,6 @@ module Rails
# its own vendored Thor, which could be a different version. Running both
# things in the same process is a recipe for a night with paracetamol.
#
- # We use backticks and #print here instead of vanilla #system because it
- # is easier to silence stdout in the existing test suite this way. The
- # end-user gets the bundler commands called anyway, so no big deal.
- #
# We unset temporary bundler variables to load proper bundler and Gemfile.
#
# Thanks to James Tucker for the Gem tricks involved in this call.
@@ -332,8 +322,12 @@ module Rails
require 'bundler'
Bundler.with_clean_env do
- output = `"#{Gem.ruby}" "#{_bundle_command}" #{command}`
- print output unless options[:quiet]
+ full_command = %Q["#{Gem.ruby}" "#{_bundle_command}" #{command}]
+ if options[:quiet]
+ system(full_command, out: File::NULL)
+ else
+ system(full_command)
+ end
end
end
@@ -342,7 +336,7 @@ module Rails
end
def spring_install?
- !options[:skip_spring] && Process.respond_to?(:fork)
+ !options[:skip_spring] && Process.respond_to?(:fork) && !RUBY_PLATFORM.include?("cygwin")
end
def run_bundle
diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb
index 9af6435f23..813b8b629e 100644
--- a/railties/lib/rails/generators/base.rb
+++ b/railties/lib/rails/generators/base.rb
@@ -273,7 +273,7 @@ module Rails
# Use Rails default banner.
def self.banner
- "rails generate #{namespace.sub(/^rails:/,'')} #{self.arguments.map{ |a| a.usage }.join(' ')} [options]".gsub(/\s+/, ' ')
+ "rails generate #{namespace.sub(/^rails:/,'')} #{self.arguments.map(&:usage).join(' ')} [options]".gsub(/\s+/, ' ')
end
# Sets the base_name taking into account the current class namespace.
diff --git a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
index 66b17bd10e..65563aa6db 100644
--- a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
+++ b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
@@ -1,13 +1,40 @@
-require 'rails/generators/erb/controller/controller_generator'
+require 'rails/generators/erb'
module Erb # :nodoc:
module Generators # :nodoc:
- class MailerGenerator < ControllerGenerator # :nodoc:
+ class MailerGenerator < Base # :nodoc:
+ argument :actions, type: :array, default: [], banner: "method method"
+
+ def copy_view_files
+ view_base_path = File.join("app/views", class_path, file_name + '_mailer')
+ empty_directory view_base_path
+
+ if self.behavior == :invoke
+ formats.each do |format|
+ layout_path = File.join("app/views/layouts", filename_with_extensions("mailer", format))
+ template filename_with_extensions(:layout, format), layout_path
+ end
+ end
+
+ actions.each do |action|
+ @action = action
+
+ formats.each do |format|
+ @path = File.join(view_base_path, filename_with_extensions(action, format))
+ template filename_with_extensions(:view, format), @path
+ end
+ end
+ end
+
protected
def formats
[:text, :html]
end
+
+ def file_name
+ @_file_name ||= super.gsub(/\_mailer/i, '')
+ end
end
end
end
diff --git a/railties/lib/rails/generators/erb/mailer/templates/layout.html.erb b/railties/lib/rails/generators/erb/mailer/templates/layout.html.erb
new file mode 100644
index 0000000000..93110e74ad
--- /dev/null
+++ b/railties/lib/rails/generators/erb/mailer/templates/layout.html.erb
@@ -0,0 +1,5 @@
+<html>
+ <body>
+ <%%= yield %>
+ </body>
+</html>
diff --git a/railties/lib/rails/generators/erb/mailer/templates/layout.text.erb b/railties/lib/rails/generators/erb/mailer/templates/layout.text.erb
new file mode 100644
index 0000000000..6363733e6e
--- /dev/null
+++ b/railties/lib/rails/generators/erb/mailer/templates/layout.text.erb
@@ -0,0 +1 @@
+<%%= yield %>
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 bba9141fb8..d9713b0238 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
@@ -1,10 +1,10 @@
-<%%= form_for(@<%= singular_table_name %>) do |f| %>
- <%% if @<%= singular_table_name %>.errors.any? %>
+<%%= form_for(<%= singular_table_name %>) do |f| %>
+ <%% if <%= singular_table_name %>.errors.any? %>
<div id="error_explanation">
- <h2><%%= pluralize(@<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</h2>
+ <h2><%%= pluralize(<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</h2>
<ul>
- <%% @<%= singular_table_name %>.errors.full_messages.each do |message| %>
+ <%% <%= singular_table_name %>.errors.full_messages.each do |message| %>
<li><%%= message %></li>
<%% end %>
</ul>
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb
index 5620fcc850..81329473d9 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb
@@ -1,6 +1,6 @@
<h1>Editing <%= singular_table_name.titleize %></h1>
-<%%= render 'form' %>
+<%%= render 'form', <%= singular_table_name %>: @<%= singular_table_name %> %>
<%%= link_to 'Show', @<%= singular_table_name %> %> |
<%%= link_to 'Back', <%= index_helper %>_path %>
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 5e194783ff..c3b8ef1181 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
@@ -1,6 +1,6 @@
<p id="notice"><%%= notice %></p>
-<h1>Listing <%= plural_table_name.titleize %></h1>
+<h1><%= plural_table_name.titleize %></h1>
<table>
<thead>
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb
index db13a5d870..9b2b2f4875 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb
@@ -1,5 +1,5 @@
<h1>New <%= singular_table_name.titleize %></h1>
-<%%= render 'form' %>
+<%%= render 'form', <%= singular_table_name %>: @<%= singular_table_name %> %>
<%%= link_to 'Back', <%= index_helper %>_path %>
diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb
index f16bd8e082..8145a26e22 100644
--- a/railties/lib/rails/generators/generated_attribute.rb
+++ b/railties/lib/rails/generators/generated_attribute.rb
@@ -142,7 +142,11 @@ module Rails
end
def password_digest?
- name == 'password' && type == :digest
+ name == 'password' && type == :digest
+ end
+
+ def token?
+ type == :token
end
def inject_options
@@ -159,6 +163,10 @@ module Rails
options.delete(:required)
options[:null] = false
end
+
+ if reference? && !polymorphic?
+ options[:foreign_key] = true
+ end
end
end
end
diff --git a/railties/lib/rails/generators/migration.rb b/railties/lib/rails/generators/migration.rb
index cd388e590a..51e6d68bf0 100644
--- a/railties/lib/rails/generators/migration.rb
+++ b/railties/lib/rails/generators/migration.rb
@@ -3,8 +3,8 @@ require 'rails/generators/actions/create_migration'
module Rails
module Generators
- # Holds common methods for migrations. It assumes that migrations has the
- # [0-9]*_name format and can be used by another frameworks (like Sequel)
+ # Holds common methods for migrations. It assumes that migrations have the
+ # [0-9]*_name format and can be used by other frameworks (like Sequel)
# just by implementing the next migration version method.
module Migration
extend ActiveSupport::Concern
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index b7da44ca2d..01a8e2e9b4 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -99,7 +99,7 @@ module Rails
end
def class_name
- (class_path + [file_name]).map!{ |m| m.camelize }.join('::')
+ (class_path + [file_name]).map!(&:camelize).join('::')
end
def human_name
@@ -141,11 +141,15 @@ module Rails
@plural_file_name ||= file_name.pluralize
end
+ def fixture_file_name
+ @fixture_file_name ||= (pluralize_table_names? ? plural_file_name : file_name)
+ end
+
def route_url
@route_url ||= class_path.collect {|dname| "/" + dname }.join + "/" + plural_file_name
end
- # Tries to retrieve the application name or simple return application.
+ # Tries to retrieve the application name or simply return application.
def application_name
if defined?(Rails) && Rails.application
Rails.application.class.name.split('::').first.underscore
@@ -156,7 +160,7 @@ module Rails
def assign_names!(name) #:nodoc:
@class_path = name.include?('/') ? name.split('/') : name.split('::')
- @class_path.map! { |m| m.underscore }
+ @class_path.map!(&:underscore)
@file_name = @class_path.pop
end
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 9110c129d1..899b33e529 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -38,7 +38,7 @@ module Rails
end
def readme
- copy_file "README.rdoc", "README.rdoc"
+ copy_file "README.md", "README.md"
end
def gemfile
@@ -88,12 +88,22 @@ module Rails
def config_when_updating
cookie_serializer_config_exist = File.exist?('config/initializers/cookies_serializer.rb')
+ callback_terminator_config_exist = File.exist?('config/initializers/callback_terminator.rb')
+ active_record_belongs_to_required_by_default_config_exist = File.exist?('config/initializers/active_record_belongs_to_required_by_default.rb')
config
+ unless callback_terminator_config_exist
+ remove_file 'config/initializers/callback_terminator.rb'
+ end
+
unless cookie_serializer_config_exist
gsub_file 'config/initializers/cookies_serializer.rb', /json/, 'marshal'
end
+
+ unless active_record_belongs_to_required_by_default_config_exist
+ remove_file 'config/initializers/active_record_belongs_to_required_by_default.rb'
+ end
end
def database_yml
@@ -120,6 +130,7 @@ module Rails
def test
empty_directory_with_keep_file 'test/fixtures'
+ empty_directory_with_keep_file 'test/fixtures/files'
empty_directory_with_keep_file 'test/controllers'
empty_directory_with_keep_file 'test/mailers'
empty_directory_with_keep_file 'test/models'
@@ -229,7 +240,7 @@ module Rails
end
def create_test_files
- build(:test) unless options[:skip_test_unit]
+ build(:test) unless options[:skip_test]
end
def create_tmp_files
@@ -252,6 +263,12 @@ module Rails
end
end
+ def delete_active_record_initializers_skipping_active_record
+ if options[:skip_active_record]
+ remove_file 'config/initializers/active_record_belongs_to_required_by_default.rb'
+ end
+ end
+
def finish_template
build(:leftovers)
end
@@ -260,9 +277,7 @@ module Rails
public_task :generate_spring_binstubs
def run_after_bundle_callbacks
- @after_bundle_callbacks.each do |callback|
- callback.call
- end
+ @after_bundle_callbacks.each(&:call)
end
protected
@@ -340,7 +355,7 @@ module Rails
#
# This class should be called before the AppGenerator is required and started
# since it configures and mutates ARGV correctly.
- class ARGVScrubber # :nodoc
+ class ARGVScrubber # :nodoc:
def initialize(argv = ARGV)
@argv = argv
end
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index 5961f7515c..c11bb58bfa 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -22,25 +22,24 @@ source 'https://rubygems.org'
# gem 'capistrano-rails', group: :development
group :development, :test do
-<% unless defined?(JRUBY_VERSION) -%>
- <%- if RUBY_VERSION < '2.0.0' -%>
- # Call 'debugger' anywhere in the code to stop execution and get a debugger console
- gem 'debugger'
- <%- else -%>
+<% if RUBY_ENGINE == 'ruby' -%>
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug'
- <%- end -%>
# Access an IRB console on exception pages or by using <%%= console %> in views
- gem 'web-console', '~> 2.0.0.beta4'
+ <%- if options.dev? || options.edge? -%>
+ gem 'web-console', github: "rails/web-console"
+ <%- else -%>
+ gem 'web-console', '~> 2.0'
+ <%- 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
-
<% if RUBY_PLATFORM.match(/bccwin|cygwin|emx|mingw|mswin|wince|java/) -%>
+
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
<% end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/README.rdoc b/railties/lib/rails/generators/rails/app/templates/README.md
index dd4e97e22e..55e144da18 100644
--- a/railties/lib/rails/generators/rails/app/templates/README.rdoc
+++ b/railties/lib/rails/generators/rails/app/templates/README.md
@@ -1,4 +1,4 @@
-== README
+## README
This README would normally document whatever steps are necessary to get the
application up and running.
@@ -22,7 +22,3 @@ Things you may want to cover:
* Deployment instructions
* ...
-
-
-Please feel free to use a different markup language if you do not plan to run
-<tt>rake doc:app</tt>.
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 07ea09cdbd..cb86978d4c 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
@@ -2,12 +2,12 @@
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
-// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
+// 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.
//
-// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
+// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
<% unless options[:skip_javascript] -%>
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 a443db3401..0cdd2788d0 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
@@ -3,12 +3,11 @@
* listed below.
*
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
- * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
- * compiled file so the styles you add here take precedence over styles defined in any styles
- * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
- * file per style scope.
+ * 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.
*
*= require_tree .
*= require_self
diff --git a/railties/lib/rails/generators/rails/app/templates/app/jobs/application_job.rb b/railties/lib/rails/generators/rails/app/templates/app/jobs/application_job.rb
new file mode 100644
index 0000000000..a009ace51c
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/app/jobs/application_job.rb
@@ -0,0 +1,2 @@
+class ApplicationJob < ActiveJob::Base
+end
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/rails b/railties/lib/rails/generators/rails/app/templates/bin/rails
index 6a128b95e5..80ec8080ab 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/rails
+++ b/railties/lib/rails/generators/rails/app/templates/bin/rails
@@ -1,3 +1,3 @@
-APP_PATH = File.expand_path('../../config/application', __FILE__)
+APP_PATH = File.expand_path('../../config/application', __FILE__)
require_relative '../config/boot'
require 'rails/commands'
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/setup b/railties/lib/rails/generators/rails/app/templates/bin/setup
index 0e22b3fa5c..eee810be30 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/setup
+++ b/railties/lib/rails/generators/rails/app/templates/bin/setup
@@ -1,28 +1,30 @@
require 'pathname'
+require 'fileutils'
+include FileUtils
# path to your application root.
-APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
+APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
-Dir.chdir APP_ROOT do
+chdir APP_ROOT do
# This script is a starting point to setup your application.
- # Add necessary setup steps to this file:
+ # Add necessary setup steps to this file.
- puts "== Installing dependencies =="
- system "gem install bundler --conservative"
- system "bundle check || bundle install"
+ puts '== Installing dependencies =='
+ system 'gem install bundler --conservative'
+ system('bundle check') or system('bundle install')
# puts "\n== Copying sample files =="
- # unless File.exist?("config/database.yml")
- # system "cp config/database.yml.sample config/database.yml"
+ # unless File.exist?('config/database.yml')
+ # cp 'config/database.yml.sample', 'config/database.yml'
# end
puts "\n== Preparing database =="
- system "bin/rake db:setup"
+ system 'ruby bin/rake db:setup'
puts "\n== Removing old logs and tempfiles =="
- system "rm -f log/*"
- system "rm -rf tmp/cache"
+ rm_f Dir.glob('log/*')
+ rm_rf 'tmp/cache'
puts "\n== Restarting application server =="
- system "touch tmp/restart.txt"
+ touch 'tmp/restart.txt'
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config.ru b/railties/lib/rails/generators/rails/app/templates/config.ru
index 5bc2a619e8..bd83b25412 100644
--- a/railties/lib/rails/generators/rails/app/templates/config.ru
+++ b/railties/lib/rails/generators/rails/app/templates/config.ru
@@ -1,4 +1,4 @@
# This file is used by Rack-based servers to start the application.
-require ::File.expand_path('../config/environment', __FILE__)
+require ::File.expand_path('../config/environment', __FILE__)
run Rails.application
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 111b680e4b..a2661bfb51 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -3,15 +3,16 @@ require File.expand_path('../boot', __FILE__)
<% if include_all_railties? -%>
require 'rails/all'
<% else -%>
+require "rails"
# Pick the frameworks you want:
require "active_model/railtie"
require "active_job/railtie"
<%= comment_if :skip_active_record %>require "active_record/railtie"
require "action_controller/railtie"
-require "action_mailer/railtie"
+<%= comment_if :skip_action_mailer %>require "action_mailer/railtie"
require "action_view/railtie"
<%= comment_if :skip_sprockets %>require "sprockets/railtie"
-<%= comment_if :skip_test_unit %>require "rails/test_unit/railtie"
+<%= comment_if :skip_test %>require "rails/test_unit/railtie"
<% end -%>
# Require the gems listed in Gemfile, including any gems
@@ -31,10 +32,5 @@ 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
- <%- unless options.skip_active_record? -%>
-
- # Do not swallow errors in after_commit/after_rollback callbacks.
- config.active_record.raise_in_transactional_callbacks = true
- <%- end -%>
end
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml
index acb93939e1..f5b62e8fb3 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml
@@ -7,7 +7,7 @@
# gem 'activerecord-jdbcmysql-adapter'
#
# And be sure to use new-style password hashing:
-# http://dev.mysql.com/doc/refman/5.0/en/old-client.html
+# http://dev.mysql.com/doc/refman/5.6/en/old-client.html
#
default: &default
adapter: mysql
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
index 4b2e6646c7..b0767bd93a 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
@@ -1,13 +1,13 @@
# MySQL. Versions 5.0+ are recommended.
#
-# Install the MYSQL driver
+# Install the MySQL driver
# gem install mysql2
#
# Ensure the MySQL gem is defined in your Gemfile
# gem 'mysql2'
#
# And be sure to use new-style password hashing:
-# http://dev.mysql.com/doc/refman/5.0/en/old-client.html
+# http://dev.mysql.com/doc/refman/5.6/en/old-client.html
#
default: &default
adapter: mysql2
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 d8326d1728..ecb5d4170f 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
@@ -12,9 +12,11 @@ Rails.application.configure do
# Show full error reports and disable caching.
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
+ <%- unless options.skip_action_mailer? -%>
# Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false
+ <%- end -%>
# Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
index 92ff0de030..8c09396fc1 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
@@ -14,14 +14,9 @@ Rails.application.configure do
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
- # Enable Rack::Cache to put a simple HTTP cache in front of your application
- # Add `rack-cache` to your Gemfile before enabling this.
- # For large-scale production use, consider using a caching reverse proxy like
- # NGINX, varnish or squid.
- # config.action_dispatch.rack_cache = true
-
- # Disable Rails's static asset server (Apache or NGINX will already do this).
- config.serve_static_assets = false
+ # Disable serving static files from the `/public` folder by default since
+ # Apache or NGINX already handles this.
+ config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?
<%- unless options.skip_sprockets? -%>
# Compress JavaScripts and CSS.
@@ -38,6 +33,9 @@ Rails.application.configure do
# `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
<%- end -%>
+ # Enable serving of images, stylesheets, and JavaScripts from an asset server.
+ # config.action_controller.asset_host = 'http://assets.example.com'
+
# Specifies the header that your server uses for sending files.
# config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
@@ -45,11 +43,12 @@ Rails.application.configure do
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
- # Decrease the log volume.
- # config.log_level = :info
+ # Use the lowest log level to ensure availability of diagnostic information
+ # when problems arise.
+ config.log_level = :debug
# Prepend all log lines with the following tags.
- # config.log_tags = [ :subdomain, :uuid ]
+ # config.log_tags = [ :subdomain, :request_id ]
# Use a different logger for distributed setups.
# config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
@@ -57,12 +56,15 @@ Rails.application.configure do
# Use a different cache store in production.
# config.cache_store = :mem_cache_store
- # Enable serving of images, stylesheets, and JavaScripts from an asset server.
- # config.action_controller.asset_host = 'http://assets.example.com'
+ # Use a real queuing backend for Active Job (and separate queues per environment)
+ # config.active_job.queue_adapter = :resque
+ # config.active_job.queue_name_prefix = "<%= app_name %>_#{Rails.env}"
+ <%- unless options.skip_action_mailer? -%>
# Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
# config.action_mailer.raise_delivery_errors = false
+ <%- end -%>
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation cannot be found).
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
index 32756eb88b..0306deb18c 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
@@ -12,8 +12,8 @@ Rails.application.configure do
# preloads Rails for running tests, you may have to set it to true.
config.eager_load = false
- # Configure static asset server for tests with Cache-Control for performance.
- config.serve_static_assets = true
+ # Configure static file server for tests with Cache-Control for performance.
+ config.serve_static_files = true
config.static_cache_control = 'public, max-age=3600'
# Show full error reports and disable caching.
@@ -25,13 +25,15 @@ Rails.application.configure do
# Disable request forgery protection in test environment.
config.action_controller.allow_forgery_protection = false
+ <%- unless options.skip_action_mailer? -%>
# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
+ <%- end -%>
- # Randomize the order test cases are executed
+ # Randomize the order test cases are executed.
config.active_support.test_order = :random
# Print deprecation notices to the stderr.
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb
new file mode 100644
index 0000000000..30c4f89792
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb
@@ -0,0 +1,4 @@
+# Be sure to restart your server when you modify this file.
+
+# Require `belongs_to` associations by default.
+Rails.application.config.active_record.belongs_to_required_by_default = true
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb
new file mode 100644
index 0000000000..ea930f54da
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb
@@ -0,0 +1,6 @@
+## Change renderer defaults here.
+#
+# ApplicationController.renderer.defaults.merge!(
+# http_host: 'example.org',
+# https: false
+# )
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
new file mode 100644
index 0000000000..a70a1b9cde
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb
@@ -0,0 +1,4 @@
+# Be sure to restart your server when you modify this file.
+
+# Do not halt callback chains when a callback returns false.
+ActiveSupport.halt_callback_chains_on_return_false = false
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 f2110c2c70..94f612c3dd 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
@@ -11,6 +11,6 @@ end
# To enable root element in JSON for ActiveRecord objects.
# ActiveSupport.on_load(:active_record) do
-# self.include_root_in_json = true
+# self.include_root_in_json = true
# end
<%- end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/gitignore b/railties/lib/rails/generators/rails/app/templates/gitignore
index 8775e5e235..7c6f2098b8 100644
--- a/railties/lib/rails/generators/rails/app/templates/gitignore
+++ b/railties/lib/rails/generators/rails/app/templates/gitignore
@@ -14,5 +14,6 @@
<% end -%>
# Ignore all logfiles and tempfiles.
-/log/*.log
+/log/*
+!/log/.keep
/tmp
diff --git a/railties/lib/rails/generators/rails/migration/migration_generator.rb b/railties/lib/rails/generators/rails/migration/migration_generator.rb
index 965c42db36..fca2a8fef4 100644
--- a/railties/lib/rails/generators/rails/migration/migration_generator.rb
+++ b/railties/lib/rails/generators/rails/migration/migration_generator.rb
@@ -2,7 +2,7 @@ module Rails
module Generators
class MigrationGenerator < NamedBase # :nodoc:
argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]"
- hook_for :orm, required: true
+ hook_for :orm, required: true, desc: "ORM to be invoked"
end
end
end
diff --git a/railties/lib/rails/generators/rails/model/USAGE b/railties/lib/rails/generators/rails/model/USAGE
index 8c3b63c3b4..11daa5c3cb 100644
--- a/railties/lib/rails/generators/rails/model/USAGE
+++ b/railties/lib/rails/generators/rails/model/USAGE
@@ -22,7 +22,7 @@ Description:
If you pass a namespaced model name (e.g. admin/account or Admin::Account)
then the generator will create a module with a table_name_prefix method
- to prefix the model's table name with the module name (e.g. admin_account)
+ to prefix the model's table name with the module name (e.g. admin_accounts)
Available field types:
@@ -79,10 +79,15 @@ Available field types:
`rails generate model product supplier:references{polymorphic}:index`
If you require a `password_digest` string column for use with
- has_secure_password, you should specify `password:digest`:
+ has_secure_password, you can specify `password:digest`:
`rails generate model user password:digest`
+ If you require a `token` string column for use with
+ has_secure_token, you can specify `auth_token:token`:
+
+ `rails generate model user auth_token:token`
+
Examples:
`rails generate model account`
diff --git a/railties/lib/rails/generators/rails/model/model_generator.rb b/railties/lib/rails/generators/rails/model/model_generator.rb
index 87bab129bb..ec78fd855d 100644
--- a/railties/lib/rails/generators/rails/model/model_generator.rb
+++ b/railties/lib/rails/generators/rails/model/model_generator.rb
@@ -6,7 +6,7 @@ module Rails
include Rails::Generators::ModelHelpers
argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]"
- hook_for :orm, required: true
+ hook_for :orm, required: true, desc: "ORM to be invoked"
end
end
end
diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
index 584f776c01..68c3829515 100644
--- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
@@ -18,14 +18,14 @@ module Rails
def app
if mountable?
directory 'app'
- empty_directory_with_keep_file "app/assets/images/#{name}"
+ empty_directory_with_keep_file "app/assets/images/#{namespaced_name}"
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/#{name}"
+ empty_directory_with_keep_file "app/assets/images/#{namespaced_name}"
end
end
@@ -50,10 +50,10 @@ module Rails
end
def lib
- template "lib/%name%.rb"
- template "lib/tasks/%name%_tasks.rake"
- template "lib/%name%/version.rb"
- template "lib/%name%/engine.rb" if engine?
+ template "lib/%namespaced_name%.rb"
+ template "lib/tasks/%namespaced_name%_tasks.rake"
+ template "lib/%namespaced_name%/version.rb"
+ template "lib/%namespaced_name%/engine.rb" if engine?
end
def config
@@ -62,7 +62,7 @@ module Rails
def test
template "test/test_helper.rb"
- template "test/%name%_test.rb"
+ template "test/%namespaced_name%_test.rb"
append_file "Rakefile", <<-EOF
#{rakefile_test_tasks}
@@ -74,7 +74,8 @@ task default: :test
end
PASSTHROUGH_OPTIONS = [
- :skip_active_record, :skip_javascript, :database, :javascript, :quiet, :pretend, :force, :skip
+ :skip_active_record, :skip_action_mailer, :skip_javascript, :database,
+ :javascript, :quiet, :pretend, :force, :skip
]
def generate_test_dummy(force = false)
@@ -116,9 +117,9 @@ task default: :test
def stylesheets
if mountable?
copy_file "rails/stylesheets.css",
- "app/assets/stylesheets/#{name}/application.css"
+ "app/assets/stylesheets/#{namespaced_name}/application.css"
elsif full?
- empty_directory_with_keep_file "app/assets/stylesheets/#{name}"
+ empty_directory_with_keep_file "app/assets/stylesheets/#{namespaced_name}"
end
end
@@ -127,9 +128,9 @@ task default: :test
if mountable?
template "rails/javascripts.js",
- "app/assets/javascripts/#{name}/application.js"
+ "app/assets/javascripts/#{namespaced_name}/application.js"
elsif full?
- empty_directory_with_keep_file "app/assets/javascripts/#{name}"
+ empty_directory_with_keep_file "app/assets/javascripts/#{namespaced_name}"
end
end
@@ -225,7 +226,7 @@ task default: :test
end
def create_test_files
- build(:test) unless options[:skip_test_unit]
+ build(:test) unless options[:skip_test]
end
def create_test_dummy_files
@@ -255,6 +256,14 @@ task default: :test
end
end
+ def underscored_name
+ @underscored_name ||= original_name.underscore
+ end
+
+ def namespaced_name
+ @namespaced_name ||= name.gsub('-', '/')
+ end
+
protected
def app_templates_dir
@@ -293,7 +302,7 @@ task default: :test
end
def with_dummy_app?
- options[:skip_test_unit].blank? || options[:dummy_path] != 'test/dummy'
+ options[:skip_test].blank? || options[:dummy_path] != 'test/dummy'
end
def self.banner
@@ -304,6 +313,27 @@ task default: :test
@original_name ||= File.basename(destination_root)
end
+ def modules
+ @modules ||= namespaced_name.camelize.split("::")
+ end
+
+ def wrap_in_modules(unwrapped_code)
+ unwrapped_code = "#{unwrapped_code}".strip.gsub(/\W$\n/, '')
+ modules.reverse.inject(unwrapped_code) do |content, mod|
+ str = "module #{mod}\n"
+ str += content.lines.map { |line| " #{line}" }.join
+ str += content.present? ? "\nend" : "end"
+ end
+ end
+
+ def camelized_modules
+ @camelized_modules ||= namespaced_name.camelize
+ end
+
+ def humanized
+ @humanized ||= original_name.underscore.humanize
+ end
+
def camelized
@camelized ||= name.gsub(/\W/, '_').squeeze('_').camelize
end
@@ -327,8 +357,10 @@ task default: :test
end
def valid_const?
- if original_name =~ /[^0-9a-zA-Z_]+/
- raise Error, "Invalid plugin name #{original_name}. Please give a name which use only alphabetic or numeric or \"_\" characters."
+ if original_name =~ /-\d/
+ raise Error, "Invalid plugin name #{original_name}. Please give a name which does not contain a namespace starting with numeric characters."
+ elsif original_name =~ /[^\w-]+/
+ raise Error, "Invalid plugin name #{original_name}. Please give a name which uses only alphabetic, numeric, \"_\" or \"-\" characters."
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)
diff --git a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec
index 919c349470..f8ece4fe73 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec
+++ b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec
@@ -1,21 +1,21 @@
$:.push File.expand_path("../lib", __FILE__)
# Maintain your gem's version:
-require "<%= name %>/version"
+require "<%= namespaced_name %>/version"
# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
s.name = "<%= name %>"
- s.version = <%= camelized %>::VERSION
+ s.version = <%= camelized_modules %>::VERSION
s.authors = ["<%= author %>"]
s.email = ["<%= email %>"]
s.homepage = "TODO"
- s.summary = "TODO: Summary of <%= camelized %>."
- s.description = "TODO: Description of <%= camelized %>."
+ s.summary = "TODO: Summary of <%= camelized_modules %>."
+ s.description = "TODO: Description of <%= camelized_modules %>."
s.license = "MIT"
s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.rdoc"]
-<% unless options.skip_test_unit? -%>
+<% unless options.skip_test? -%>
s.test_files = Dir["test/**/*"]
<% end -%>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/Gemfile b/railties/lib/rails/generators/rails/plugin/templates/Gemfile
index 35ad9fbf9e..2c91c6a0ea 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/plugin/templates/Gemfile
@@ -37,11 +37,11 @@ end
<% end -%>
<% end -%>
-<% unless defined?(JRUBY_VERSION) -%>
+<% if RUBY_ENGINE == 'ruby' -%>
# To use a debugger
- <%- if RUBY_VERSION < '2.0.0' -%>
-# gem 'debugger', group: [:development, :test]
- <%- else -%>
# gem 'byebug', group: [:development, :test]
- <%- end -%>
+<% end -%>
+<% if RUBY_PLATFORM.match(/bccwin|cygwin|emx|mingw|mswin|wince|java/) -%>
+
+gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
<% end -%>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/README.rdoc b/railties/lib/rails/generators/rails/plugin/templates/README.rdoc
index 301d647731..25983ca5da 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/README.rdoc
+++ b/railties/lib/rails/generators/rails/plugin/templates/README.rdoc
@@ -1,3 +1,3 @@
-= <%= camelized %>
+= <%= camelized_modules %>
This project rocks and uses MIT-LICENSE. \ No newline at end of file
diff --git a/railties/lib/rails/generators/rails/plugin/templates/Rakefile b/railties/lib/rails/generators/rails/plugin/templates/Rakefile
index c338a0bdb1..bda55bae29 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/plugin/templates/Rakefile
@@ -8,7 +8,7 @@ require 'rdoc/task'
RDoc::Task.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
- rdoc.title = '<%= camelized %>'
+ rdoc.title = '<%= camelized_modules %>'
rdoc.options << '--line-numbers'
rdoc.rdoc_files.include('README.rdoc')
rdoc.rdoc_files.include('lib/**/*.rb')
diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%name%/application_controller.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt
index 448ad7f989..7157e48c42 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%name%/application_controller.rb.tt
+++ b/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt
@@ -1,4 +1,5 @@
-module <%= camelized %>
+<%= wrap_in_modules <<-rb.strip_heredoc
class ApplicationController < ActionController::Base
end
-end
+rb
+%>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/helpers/%name%/application_helper.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/helpers/%name%/application_helper.rb.tt
deleted file mode 100644
index 40ae9f52c2..0000000000
--- a/railties/lib/rails/generators/rails/plugin/templates/app/helpers/%name%/application_helper.rb.tt
+++ /dev/null
@@ -1,4 +0,0 @@
-module <%= camelized %>
- module ApplicationHelper
- end
-end
diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/helpers/%namespaced_name%/application_helper.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/helpers/%namespaced_name%/application_helper.rb.tt
new file mode 100644
index 0000000000..25d692732d
--- /dev/null
+++ b/railties/lib/rails/generators/rails/plugin/templates/app/helpers/%namespaced_name%/application_helper.rb.tt
@@ -0,0 +1,5 @@
+<%= wrap_in_modules <<-rb.strip_heredoc
+ module ApplicationHelper
+ end
+rb
+%>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/views/layouts/%name%/application.html.erb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/views/layouts/%name%/application.html.erb.tt
deleted file mode 100644
index 1d380420b4..0000000000
--- a/railties/lib/rails/generators/rails/plugin/templates/app/views/layouts/%name%/application.html.erb.tt
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
- <title><%= camelized %></title>
- <%%= stylesheet_link_tag "<%= name %>/application", media: "all" %>
- <%%= javascript_include_tag "<%= name %>/application" %>
- <%%= csrf_meta_tags %>
-</head>
-<body>
-
-<%%= yield %>
-
-</body>
-</html>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/views/layouts/%namespaced_name%/application.html.erb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/views/layouts/%namespaced_name%/application.html.erb.tt
new file mode 100644
index 0000000000..6bc480161d
--- /dev/null
+++ b/railties/lib/rails/generators/rails/plugin/templates/app/views/layouts/%namespaced_name%/application.html.erb.tt
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title><%= humanized %></title>
+ <%%= stylesheet_link_tag "<%= namespaced_name %>/application", media: "all" %>
+ <%%= javascript_include_tag "<%= namespaced_name %>/application" %>
+ <%%= csrf_meta_tags %>
+</head>
+<body>
+
+<%%= yield %>
+
+</body>
+</html>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt
index c3314d7e68..3edaac35c9 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt
+++ b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt
@@ -1,7 +1,7 @@
# This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.
ENGINE_ROOT = File.expand_path('../..', __FILE__)
-ENGINE_PATH = File.expand_path('../../lib/<%= name -%>/engine', __FILE__)
+ENGINE_PATH = File.expand_path('../../lib/<%= namespaced_name -%>/engine', __FILE__)
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
diff --git a/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb b/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb
index 8e158d5831..154452bfe5 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb
@@ -1,5 +1,5 @@
<% if mountable? -%>
-<%= camelized %>::Engine.routes.draw do
+<%= camelized_modules %>::Engine.routes.draw do
<% else -%>
Rails.application.routes.draw do
<% end -%>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/gitignore b/railties/lib/rails/generators/rails/plugin/templates/gitignore
index 086d87818a..d524fcbc4e 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/gitignore
+++ b/railties/lib/rails/generators/rails/plugin/templates/gitignore
@@ -1,10 +1,10 @@
.bundle/
log/*.log
pkg/
-<% unless options[:skip_test_unit] && options[:dummy_path] == 'test/dummy' -%>
+<% unless options[:skip_test] && options[:dummy_path] == 'test/dummy' -%>
<%= dummy_path %>/db/*.sqlite3
<%= dummy_path %>/db/*.sqlite3-journal
<%= dummy_path %>/log/*.log
<%= dummy_path %>/tmp/
<%= dummy_path %>/.sass-cache
-<% end -%> \ No newline at end of file
+<% end -%>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%name%.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%name%.rb
deleted file mode 100644
index 40c074cced..0000000000
--- a/railties/lib/rails/generators/rails/plugin/templates/lib/%name%.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-<% if engine? -%>
-require "<%= name %>/engine"
-
-<% end -%>
-module <%= camelized %>
-end
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%name%/engine.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%name%/engine.rb
deleted file mode 100644
index 967668fe66..0000000000
--- a/railties/lib/rails/generators/rails/plugin/templates/lib/%name%/engine.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-module <%= camelized %>
- class Engine < ::Rails::Engine
-<% if mountable? -%>
- isolate_namespace <%= camelized %>
-<% end -%>
- end
-end
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%name%/version.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%name%/version.rb
deleted file mode 100644
index ef07ef2e19..0000000000
--- a/railties/lib/rails/generators/rails/plugin/templates/lib/%name%/version.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-module <%= camelized %>
- VERSION = "0.0.1"
-end
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb
new file mode 100644
index 0000000000..40b1c4cee7
--- /dev/null
+++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb
@@ -0,0 +1,5 @@
+<% if engine? -%>
+require "<%= namespaced_name %>/engine"
+
+<% end -%>
+<%= wrap_in_modules "# Your code goes here..." %>
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
new file mode 100644
index 0000000000..17afd52177
--- /dev/null
+++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb
@@ -0,0 +1,6 @@
+<%= wrap_in_modules <<-rb.strip_heredoc
+ class Engine < ::Rails::Engine
+ #{mountable? ? ' isolate_namespace ' + camelized_modules : ' '}
+ end
+rb
+%>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb
new file mode 100644
index 0000000000..d257295988
--- /dev/null
+++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb
@@ -0,0 +1 @@
+<%= wrap_in_modules 'VERSION = "0.0.1"' %>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%name%_tasks.rake b/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%namespaced_name%_tasks.rake
index 7121f5ae23..88a2c4120f 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%name%_tasks.rake
+++ b/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%namespaced_name%_tasks.rake
@@ -1,4 +1,4 @@
# desc "Explaining what the task does"
-# task :<%= name %> do
+# task :<%= underscored_name %> do
# # Task goes here
# end
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb
index b2aa82344a..b1038c839e 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb
@@ -6,13 +6,13 @@ require 'rails/all'
# Pick the frameworks you want:
<%= comment_if :skip_active_record %>require "active_record/railtie"
require "action_controller/railtie"
-require "action_mailer/railtie"
+<%= comment_if :skip_action_mailer %>require "action_mailer/railtie"
require "action_view/railtie"
<%= comment_if :skip_sprockets %>require "sprockets/railtie"
-<%= comment_if :skip_test_unit %>require "rails/test_unit/railtie"
+<%= comment_if :skip_test %>require "rails/test_unit/railtie"
<% end -%>
Bundler.require(*Rails.groups)
-require "<%= name %>"
+require "<%= namespaced_name %>"
<%= application_definition %>
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 5bc2e1c8b5..8913b40f69 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js
@@ -2,12 +2,12 @@
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
-// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
+// 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.
//
-// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
+// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require_tree .
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb
index 730ee31c3d..673de44108 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb
@@ -1,4 +1,4 @@
Rails.application.routes.draw do
- mount <%= camelized %>::Engine => "/<%= name %>"
+ mount <%= camelized_modules %>::Engine => "/<%= name %>"
end
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 a443db3401..0cdd2788d0 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css
@@ -3,12 +3,11 @@
* listed below.
*
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
- * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
- * compiled file so the styles you add here take precedence over styles defined in any styles
- * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
- * file per style scope.
+ * 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.
*
*= require_tree .
*= require_self
diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/%name%_test.rb b/railties/lib/rails/generators/rails/plugin/templates/test/%name%_test.rb
deleted file mode 100644
index 0a8bbd4aaf..0000000000
--- a/railties/lib/rails/generators/rails/plugin/templates/test/%name%_test.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-require 'test_helper'
-
-class <%= camelized %>Test < ActiveSupport::TestCase
- test "truth" do
- assert_kind_of Module, <%= camelized %>
- end
-end
diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb b/railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb
new file mode 100644
index 0000000000..1ee05d7871
--- /dev/null
+++ b/railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb
@@ -0,0 +1,7 @@
+require 'test_helper'
+
+class <%= camelized_modules %>::Test < ActiveSupport::TestCase
+ test "truth" do
+ assert_kind_of Module, <%= camelized_modules %>
+ end
+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 d492e68357..0852ffce9a 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
@@ -10,7 +10,9 @@ ActiveRecord::Migrator.migrations_paths << File.expand_path('../../db/migrate',
<% end -%>
require "rails/test_help"
-Rails.backtrace_cleaner.remove_silencers!
+# Filter out Minitest backtrace while allowing backtrace from other libraries
+# to be shown.
+Minitest.backtrace_filter = Minitest::BacktraceFilter.new
# Load support files
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
@@ -18,4 +20,6 @@ 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"
+ ActiveSupport::TestCase.fixtures :all
end
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 e4a2bc2b0f..c986f95e67 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
@@ -29,8 +29,10 @@ module Rails
write("end", route_length - index)
end
- # route prepends two spaces onto the front of the string that is passed, this corrects that
- route route_string[2..-1]
+ # route prepends two spaces onto the front of the string that is passed, this corrects that.
+ # Also it adds a \n to the end of each line, as route already adds that
+ # we need to correct that too.
+ route route_string[2..-2]
end
private
diff --git a/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css b/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css
index 1ae7000299..69af1e8307 100644
--- a/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css
+++ b/railties/lib/rails/generators/rails/scaffold/templates/scaffold.css
@@ -4,6 +4,7 @@ body, p, ol, ul, td {
font-family: verdana, arial, helvetica, sans-serif;
font-size: 13px;
line-height: 18px;
+ margin: 33px;
}
pre {
@@ -16,6 +17,16 @@ a { color: #000; }
a:visited { color: #666; }
a:hover { color: #fff; background-color:#000; }
+th {
+ padding-bottom: 5px;
+}
+
+td {
+ padding-bottom: 7px;
+ padding-left: 5px;
+ padding-right: 5px;
+}
+
div.field, div.actions {
margin-bottom: 10px;
}
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 6bf0a33a5f..c01b82884d 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
@@ -7,6 +7,7 @@ module Rails
check_class_collision suffix: "Controller"
+ class_option :helper, type: :boolean
class_option :orm, banner: "NAME", type: :string, required: true,
desc: "ORM to generate the controller for"
diff --git a/railties/lib/rails/generators/resource_helpers.rb b/railties/lib/rails/generators/resource_helpers.rb
index 4669935156..9c2037783e 100644
--- a/railties/lib/rails/generators/resource_helpers.rb
+++ b/railties/lib/rails/generators/resource_helpers.rb
@@ -8,7 +8,7 @@ module Rails
module ResourceHelpers # :nodoc:
def self.included(base) #:nodoc:
- base.send :include, Rails::Generators::ModelHelpers
+ base.include(Rails::Generators::ModelHelpers)
base.class_option :model_name, type: :string, desc: "ModelName to be used"
end
@@ -39,7 +39,7 @@ module Rails
def assign_controller_names!(name)
@controller_name = name
@controller_class_path = name.include?('/') ? name.split('/') : name.split('::')
- @controller_class_path.map! { |m| m.underscore }
+ @controller_class_path.map!(&:underscore)
@controller_file_name = @controller_class_path.pop
end
@@ -48,7 +48,7 @@ module Rails
end
def controller_class_name
- (controller_class_path + [controller_file_name]).map!{ |m| m.camelize }.join('::')
+ (controller_class_path + [controller_file_name]).map!(&:camelize).join('::')
end
def controller_i18n_scope
diff --git a/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb b/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb
index 85dee1a066..343c8a3949 100644
--- a/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb
+++ b/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb
@@ -6,16 +6,21 @@ module TestUnit # :nodoc:
argument :actions, type: :array, default: [], banner: "method method"
def check_class_collision
- class_collisions "#{class_name}Test", "#{class_name}Preview"
+ class_collisions "#{class_name}MailerTest", "#{class_name}MailerPreview"
end
def create_test_files
- template "functional_test.rb", File.join('test/mailers', class_path, "#{file_name}_test.rb")
+ template "functional_test.rb", File.join('test/mailers', class_path, "#{file_name}_mailer_test.rb")
end
def create_preview_files
- template "preview.rb", File.join('test/mailers/previews', class_path, "#{file_name}_preview.rb")
+ template "preview.rb", File.join('test/mailers/previews', class_path, "#{file_name}_mailer_preview.rb")
end
+
+ protected
+ def file_name
+ @_file_name ||= super.gsub(/\_mailer/i, '')
+ end
end
end
end
diff --git a/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb
index 7e204105a3..a2f2d30de5 100644
--- a/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb
@@ -1,10 +1,10 @@
require 'test_helper'
<% module_namespacing do -%>
-class <%= class_name %>Test < ActionMailer::TestCase
+class <%= class_name %>MailerTest < ActionMailer::TestCase
<% actions.each do |action| -%>
test "<%= action %>" do
- mail = <%= class_name %>.<%= action %>
+ mail = <%= class_name %>Mailer.<%= action %>
assert_equal <%= action.to_s.humanize.inspect %>, mail.subject
assert_equal ["to@example.org"], mail.to
assert_equal ["from@example.com"], mail.from
diff --git a/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb b/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb
index 3bfd5426e8..b063cbc47b 100644
--- a/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb
+++ b/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb
@@ -1,11 +1,11 @@
<% module_namespacing do -%>
-# Preview all emails at http://localhost:3000/rails/mailers/<%= file_path %>
-class <%= class_name %>Preview < ActionMailer::Preview
+# Preview all emails at http://localhost:3000/rails/mailers/<%= file_path %>_mailer
+class <%= class_name %>MailerPreview < ActionMailer::Preview
<% actions.each do |action| -%>
- # Preview this email at http://localhost:3000/rails/mailers/<%= file_path %>/<%= action %>
+ # Preview this email at http://localhost:3000/rails/mailers/<%= file_path %>_mailer/<%= action %>
def <%= action %>
- <%= class_name %>.<%= action %>
+ <%= class_name %>Mailer.<%= action %>
end
<% end -%>
diff --git a/railties/lib/rails/generators/test_unit/model/model_generator.rb b/railties/lib/rails/generators/test_unit/model/model_generator.rb
index 2826a3ffa1..086588750e 100644
--- a/railties/lib/rails/generators/test_unit/model/model_generator.rb
+++ b/railties/lib/rails/generators/test_unit/model/model_generator.rb
@@ -19,7 +19,7 @@ module TestUnit # :nodoc:
def create_fixture_file
if options[:fixture] && options[:fixture_replacement].nil?
- template 'fixtures.yml', File.join('test/fixtures', class_path, "#{plural_file_name}.yml")
+ template 'fixtures.yml', File.join('test/fixtures', class_path, "#{fixture_file_name}.yml")
end
end
diff --git a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml
index f19e9d1d87..50ca61a35b 100644
--- a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml
+++ b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml
@@ -5,6 +5,8 @@
<% attributes.each do |attribute| -%>
<%- if attribute.password_digest? -%>
password_digest: <%%= BCrypt::Password.create('secret') %>
+ <%- elsif attribute.reference? -%>
+ <%= yaml_key_value(attribute.column_name.sub(/_id$/, ''), attribute.default) %>
<%- else -%>
<%= yaml_key_value(attribute.column_name, attribute.default) %>
<%- 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 18bd1ece9d..8d825ae7b0 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
@@ -19,30 +19,30 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase
test "should create <%= singular_table_name %>" do
assert_difference('<%= class_name %>.count') do
- post :create, <%= "#{singular_table_name}: { #{attributes_hash} }" %>
+ post :create, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> }
end
assert_redirected_to <%= singular_table_name %>_path(assigns(:<%= singular_table_name %>))
end
test "should show <%= singular_table_name %>" do
- get :show, id: <%= "@#{singular_table_name}" %>
+ get :show, params: { id: <%= "@#{singular_table_name}" %> }
assert_response :success
end
test "should get edit" do
- get :edit, id: <%= "@#{singular_table_name}" %>
+ get :edit, params: { id: <%= "@#{singular_table_name}" %> }
assert_response :success
end
test "should update <%= singular_table_name %>" do
- patch :update, id: <%= "@#{singular_table_name}" %>, <%= "#{singular_table_name}: { #{attributes_hash} }" %>
+ patch :update, params: { id: <%= "@#{singular_table_name}" %>, <%= "#{singular_table_name}: { #{attributes_hash} }" %> }
assert_redirected_to <%= singular_table_name %>_path(assigns(:<%= singular_table_name %>))
end
test "should destroy <%= singular_table_name %>" do
assert_difference('<%= class_name %>.count', -1) do
- delete :destroy, id: <%= "@#{singular_table_name}" %>
+ delete :destroy, params: { id: <%= "@#{singular_table_name}" %> }
end
assert_redirected_to <%= index_helper %>_path
diff --git a/railties/lib/rails/generators/testing/behaviour.rb b/railties/lib/rails/generators/testing/behaviour.rb
index fd2ea274e1..c9700e1cd7 100644
--- a/railties/lib/rails/generators/testing/behaviour.rb
+++ b/railties/lib/rails/generators/testing/behaviour.rb
@@ -2,6 +2,7 @@ require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/hash/reverse_merge'
require 'active_support/core_ext/kernel/reporting'
+require 'active_support/testing/stream'
require 'active_support/concern'
require 'rails/generators'
@@ -10,6 +11,7 @@ module Rails
module Testing
module Behaviour
extend ActiveSupport::Concern
+ include ActiveSupport::Testing::Stream
included do
class_attribute :destination_root, :current_path, :generator_class, :default_arguments
@@ -101,22 +103,6 @@ module Rails
Dir.glob("#{dirname}/[0-9]*_*.rb").grep(/\d+_#{file_name}.rb$/).first
end
- def capture(stream)
- stream = stream.to_s
- captured_stream = Tempfile.new(stream)
- stream_io = eval("$#{stream}")
- origin_stream = stream_io.dup
- stream_io.reopen(captured_stream)
-
- yield
-
- stream_io.rewind
- return captured_stream.read
- ensure
- captured_stream.close
- captured_stream.unlink
- stream_io.reopen(origin_stream)
- end
end
end
end
diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb
index 357aebf584..5909446b66 100644
--- a/railties/lib/rails/info.rb
+++ b/railties/lib/rails/info.rb
@@ -1,11 +1,14 @@
require "cgi"
module Rails
+ # This module helps build the runtime properties used to display in the
+ # Rails::InfoController responses. Including the active Rails version, Ruby
+ # version, Rack version, and so on.
module Info
mattr_accessor :properties
class << (@@properties = [])
def names
- map {|val| val.first }
+ map(&:first)
end
def value_for(property_name)
@@ -23,7 +26,7 @@ module Rails
end
def to_s
- column_width = properties.names.map {|name| name.length}.max
+ column_width = properties.names.map(&:length).max
info = properties.map do |name, value|
value = value.join(", ") if value.is_a?(Array)
"%-#{column_width}s %s" % [name, value]
diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb
index 49e5431a16..6e61cc3cb5 100644
--- a/railties/lib/rails/info_controller.rb
+++ b/railties/lib/rails/info_controller.rb
@@ -17,7 +17,28 @@ class Rails::InfoController < Rails::ApplicationController # :nodoc:
end
def routes
- @routes_inspector = ActionDispatch::Routing::RoutesInspector.new(_routes.routes)
- @page_title = 'Routes'
+ if path = params[:path]
+ path = URI.escape path
+ normalized_path = with_leading_slash path
+ render json: {
+ exact: match_route {|it| it.match normalized_path },
+ fuzzy: match_route {|it| it.spec.to_s.match path }
+ }
+ else
+ @routes_inspector = ActionDispatch::Routing::RoutesInspector.new(_routes.routes)
+ @page_title = 'Routes'
+ end
+ end
+
+ private
+
+ def match_route
+ _routes.routes.select {|route|
+ yield route.path
+ }.map {|route| route.path.spec.to_s }
+ end
+
+ def with_leading_slash(path)
+ ('/' + path).squeeze('/')
end
end
diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb
index 3eb66c07af..ebcaaaba46 100644
--- a/railties/lib/rails/paths.rb
+++ b/railties/lib/rails/paths.rb
@@ -7,7 +7,7 @@ module Rails
# root = Root.new "/rails"
# root.add "app/controllers", eager_load: true
#
- # The command above creates a new root object and add "app/controllers" as a path.
+ # The command above creates a new root object and adds "app/controllers" as a path.
# This means we can get a <tt>Rails::Paths::Path</tt> object back like below:
#
# path = root["app/controllers"]
@@ -77,23 +77,23 @@ module Rails
end
def all_paths
- values.tap { |v| v.uniq! }
+ values.tap(&:uniq!)
end
def autoload_once
- filter_by { |p| p.autoload_once? }
+ filter_by(&:autoload_once?)
end
def eager_load
- filter_by { |p| p.eager_load? }
+ filter_by(&:eager_load?)
end
def autoload_paths
- filter_by { |p| p.autoload? }
+ filter_by(&:autoload?)
end
def load_paths
- filter_by { |p| p.load_path? }
+ filter_by(&:load_path?)
end
private
@@ -167,8 +167,8 @@ module Rails
@paths.concat paths
end
- def unshift(path)
- @paths.unshift path
+ def unshift(*paths)
+ @paths.unshift(*paths)
end
def to_ary
diff --git a/railties/lib/rails/rack.rb b/railties/lib/rails/rack.rb
index 886f0e52e1..a4c4527a72 100644
--- a/railties/lib/rails/rack.rb
+++ b/railties/lib/rails/rack.rb
@@ -1,7 +1,5 @@
module Rails
module Rack
- autoload :Debugger, "rails/rack/debugger" if RUBY_VERSION < '2.0.0'
- autoload :Logger, "rails/rack/logger"
- autoload :LogTailer, "rails/rack/log_tailer"
+ autoload :Logger, "rails/rack/logger"
end
end
diff --git a/railties/lib/rails/rack/debugger.rb b/railties/lib/rails/rack/debugger.rb
index f7b77bcb3b..1fde3db070 100644
--- a/railties/lib/rails/rack/debugger.rb
+++ b/railties/lib/rails/rack/debugger.rb
@@ -1,24 +1,3 @@
-module Rails
- module Rack
- class Debugger
- def initialize(app)
- @app = app
+require 'active_support/deprecation'
- ARGV.clear # clear ARGV so that rails server options aren't passed to IRB
-
- require 'debugger'
-
- ::Debugger.start
- ::Debugger.settings[:autoeval] = true if ::Debugger.respond_to?(:settings)
- puts "=> Debugger enabled"
- rescue LoadError
- puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle it and try again."
- exit(1)
- end
-
- def call(env)
- @app.call(env)
- end
- end
- end
-end
+ActiveSupport::Deprecation.warn("This file is deprecated and will be removed in Rails 5.1 with no replacement.")
diff --git a/railties/lib/rails/rack/log_tailer.rb b/railties/lib/rails/rack/log_tailer.rb
deleted file mode 100644
index 46517713c9..0000000000
--- a/railties/lib/rails/rack/log_tailer.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-require 'active_support/deprecation'
-
-module Rails
- module Rack
- class LogTailer
- def initialize(app, log = nil)
- ActiveSupport::Deprecation.warn('LogTailer is deprecated and will be removed on Rails 5.')
-
- @app = app
-
- path = Pathname.new(log || "#{::File.expand_path(Rails.root)}/log/#{Rails.env}.log").cleanpath
-
- @cursor = @file = nil
- if ::File.exist?(path)
- @cursor = ::File.size(path)
- @file = ::File.open(path, 'r')
- end
- end
-
- def call(env)
- response = @app.call(env)
- tail!
- response
- end
-
- def tail!
- return unless @cursor
- @file.seek @cursor
-
- unless @file.eof?
- contents = @file.read
- @cursor = @file.tell
- $stdout.print contents
- end
- end
- end
- end
-end
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index 2b33beaa2b..8c24d1d56d 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -93,7 +93,7 @@ module Rails
# end
# end
#
- # By default, Rails load generators from your load path. However, if you want to place
+ # By default, Rails loads generators from your load path. However, if you want to place
# your generators at a different location, you can specify in your Railtie a block which
# will load them during normal generators lookup:
#
diff --git a/railties/lib/rails/ruby_version_check.rb b/railties/lib/rails/ruby_version_check.rb
index df74643a59..9131c51e91 100644
--- a/railties/lib/rails/ruby_version_check.rb
+++ b/railties/lib/rails/ruby_version_check.rb
@@ -1,13 +1,13 @@
-if RUBY_VERSION < '1.9.3'
+if RUBY_VERSION < '2.2.1' && RUBY_ENGINE == 'ruby'
desc = defined?(RUBY_DESCRIPTION) ? RUBY_DESCRIPTION : "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})"
abort <<-end_message
- Rails 4 prefers to run on Ruby 2.1 or newer.
+ Rails 5 requires to run on Ruby 2.2.1 or newer.
You're running
#{desc}
- Please upgrade to Ruby 1.9.3 or newer to continue.
+ Please upgrade to Ruby 2.2.1 or newer to continue.
end_message
end
diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb
index 201532d299..9b058a1848 100644
--- a/railties/lib/rails/source_annotation_extractor.rb
+++ b/railties/lib/rails/source_annotation_extractor.rb
@@ -80,9 +80,8 @@ class SourceAnnotationExtractor
# Returns a hash that maps filenames under +dir+ (recursively) to arrays
# with their annotations. Only files with annotations are included. Files
- # with extension +.builder+, +.rb+, +.erb+, +.haml+, +.slim+, +.css+,
- # +.scss+, +.js+, +.coffee+, +.rake+, +.sass+ and +.less+
- # are taken into account.
+ # with extension +.builder+, +.rb+, +.rake+, +.yml+, +.yaml+, +.ruby+,
+ # +.css+, +.js+ and +.erb+ are taken into account.
def find_in(dir)
results = {}
diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb
index af5f2707b1..2c3d278eca 100644
--- a/railties/lib/rails/tasks.rb
+++ b/railties/lib/rails/tasks.rb
@@ -1,11 +1,14 @@
+require 'rake'
+
# Load Rails Rakefile extensions
%w(
annotations
- documentation
framework
+ initializers
log
middleware
misc
+ restart
routes
statistics
tmp
diff --git a/railties/lib/rails/tasks/documentation.rake b/railties/lib/rails/tasks/documentation.rake
deleted file mode 100644
index 8544890553..0000000000
--- a/railties/lib/rails/tasks/documentation.rake
+++ /dev/null
@@ -1,70 +0,0 @@
-begin
- require 'rdoc/task'
-rescue LoadError
- # Rubinius installs RDoc as a gem, and for this interpreter "rdoc/task" is
- # available only if the application bundle includes "rdoc" (normally as a
- # dependency of the "sdoc" gem.)
- #
- # If RDoc is not available it is fine that we do not generate the tasks that
- # depend on it. Just be robust to this gotcha and go on.
-else
- require 'rails/api/task'
-
- # Monkey-patch to remove redoc'ing and clobber descriptions to cut down on rake -T noise
- class RDocTaskWithoutDescriptions < RDoc::Task
- include ::Rake::DSL
-
- def define
- task rdoc_task_name
-
- task rerdoc_task_name => [clobber_task_name, rdoc_task_name]
-
- task clobber_task_name do
- rm_r rdoc_dir rescue nil
- end
-
- task :clobber => [clobber_task_name]
-
- directory @rdoc_dir
- task rdoc_task_name => [rdoc_target]
- file rdoc_target => @rdoc_files + [Rake.application.rakefile] do
- rm_r @rdoc_dir rescue nil
- @before_running_rdoc.call if @before_running_rdoc
- args = option_list + @rdoc_files
- if @external
- argstring = args.join(' ')
- sh %{ruby -Ivendor vendor/rd #{argstring}}
- else
- require 'rdoc/rdoc'
- RDoc::RDoc.new.document(args)
- end
- end
- self
- end
- end
-
- namespace :doc do
- RDocTaskWithoutDescriptions.new("app") { |rdoc|
- rdoc.rdoc_dir = 'doc/app'
- rdoc.template = ENV['template'] if ENV['template']
- rdoc.title = ENV['title'] || "Rails Application Documentation"
- rdoc.options << '--line-numbers'
- rdoc.options << '--charset' << 'utf-8'
- rdoc.rdoc_files.include('README.rdoc')
- rdoc.rdoc_files.include('app/**/*.rb')
- rdoc.rdoc_files.include('lib/**/*.rb')
- }
- Rake::Task['doc:app'].comment = "Generate docs for the app -- also available doc:rails, doc:guides (options: TEMPLATE=/rdoc-template.rb, TITLE=\"Custom Title\")"
-
- # desc 'Generate documentation for the Rails framework.'
- Rails::API::AppTask.new('rails')
- end
-end
-
-namespace :doc do
- task :guides do
- rails_gem_dir = Gem::Specification.find_by_name("rails").gem_dir
- require File.expand_path(File.join(rails_gem_dir, "/guides/rails_guides"))
- RailsGuides::Generator.new(Rails.root.join("doc/guides")).generate
- end
-end
diff --git a/railties/lib/rails/tasks/initializers.rake b/railties/lib/rails/tasks/initializers.rake
new file mode 100644
index 0000000000..2968b5cb53
--- /dev/null
+++ b/railties/lib/rails/tasks/initializers.rake
@@ -0,0 +1,6 @@
+desc "Print out all defined initializers in the order they are invoked by Rails."
+task initializers: :environment do
+ Rails.application.initializers.tsort_each do |initializer|
+ puts initializer.name
+ end
+end
diff --git a/railties/lib/rails/tasks/restart.rake b/railties/lib/rails/tasks/restart.rake
new file mode 100644
index 0000000000..1e8940b675
--- /dev/null
+++ b/railties/lib/rails/tasks/restart.rake
@@ -0,0 +1,4 @@
+desc "Restart app by touching tmp/restart.txt"
+task :restart do
+ FileUtils.touch('tmp/restart.txt')
+end
diff --git a/railties/lib/rails/tasks/statistics.rake b/railties/lib/rails/tasks/statistics.rake
index b94cd244be..735c36eb3a 100644
--- a/railties/lib/rails/tasks/statistics.rake
+++ b/railties/lib/rails/tasks/statistics.rake
@@ -1,6 +1,6 @@
-# while having global constant is not good,
-# many 3rd party tools depend on it, like rspec-rails, cucumber-rails, etc
-# so if will be removed - deprecation warning is needed
+# While global constants are bad, many 3rd party tools depend on this one (e.g
+# rspec-rails & cucumber-rails). So a deprecation warning is needed if we want
+# to remove it.
STATS_DIRECTORIES = [
%w(Controllers app/controllers),
%w(Helpers app/helpers),
@@ -14,10 +14,9 @@ STATS_DIRECTORIES = [
%w(Helper\ tests test/helpers),
%w(Model\ tests test/models),
%w(Mailer\ tests test/mailers),
+ %w(Job\ tests test/jobs),
%w(Integration\ tests test/integration),
- %w(Functional\ tests\ (old) test/functional),
- %w(Unit\ tests \ (old) test/unit)
-].collect do |name, dir|
+].collect do |name, dir|
[ name, "#{File.dirname(Rake.application.rakefile_location)}/#{dir}" ]
end.select { |name, dir| File.directory?(dir) }
diff --git a/railties/lib/rails/tasks/tmp.rake b/railties/lib/rails/tasks/tmp.rake
index 116988665f..9162ef234a 100644
--- a/railties/lib/rails/tasks/tmp.rake
+++ b/railties/lib/rails/tasks/tmp.rake
@@ -1,9 +1,8 @@
namespace :tmp do
- desc "Clear session, cache, and socket files from tmp/ (narrow w/ tmp:sessions:clear, tmp:cache:clear, tmp:sockets:clear)"
- task clear: [ "tmp:sessions:clear", "tmp:cache:clear", "tmp:sockets:clear"]
+ desc "Clear cache and socket files from tmp/ (narrow w/ tmp:cache:clear, tmp:sockets:clear)"
+ task clear: ["tmp:cache:clear", "tmp:sockets:clear"]
- tmp_dirs = [ 'tmp/sessions',
- 'tmp/cache',
+ tmp_dirs = [ 'tmp/cache',
'tmp/sockets',
'tmp/pids',
'tmp/cache/assets/development',
@@ -12,16 +11,9 @@ namespace :tmp do
tmp_dirs.each { |d| directory d }
- desc "Creates tmp directories for sessions, cache, sockets, and pids"
+ desc "Creates tmp directories for cache, sockets, and pids"
task create: tmp_dirs
- namespace :sessions do
- # desc "Clears all files in tmp/sessions"
- task :clear do
- FileUtils.rm(Dir['tmp/sessions/[^.]*'])
- end
- end
-
namespace :cache do
# desc "Clears all files and directories in tmp/cache"
task :clear do
diff --git a/railties/lib/rails/templates/rails/welcome/index.html.erb b/railties/lib/rails/templates/rails/welcome/index.html.erb
index 89792066d5..6726c23fc9 100644
--- a/railties/lib/rails/templates/rails/welcome/index.html.erb
+++ b/railties/lib/rails/templates/rails/welcome/index.html.erb
@@ -237,7 +237,7 @@
<ol>
<li>
- <h2>Use <code>rails generate</code> to create your models and controllers</h2>
+ <h2>Use <code>bin/rails generate</code> to create your models and controllers</h2>
<p>To see all available options, run it without parameters.</p>
</li>
diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb
index c837fadb40..5cf44e6331 100644
--- a/railties/lib/rails/test_help.rb
+++ b/railties/lib/rails/test_help.rb
@@ -2,6 +2,7 @@
# so fixtures aren't loaded into that environment
abort("Abort testing: Your Rails environment is running in production mode!") if Rails.env.production?
+require "rails/test_unit/minitest_plugin"
require 'active_support/testing/autorun'
require 'active_support/test_case'
require 'action_controller'
@@ -9,18 +10,13 @@ require 'action_controller/test_case'
require 'action_dispatch/testing/integration'
require 'rails/generators/test_case'
-# Config Rails backtrace in tests.
-require 'rails/backtrace_cleaner'
-if ENV["BACKTRACE"].nil?
- Minitest.backtrace_filter = Rails.backtrace_cleaner
-end
-
if defined?(ActiveRecord::Base)
ActiveRecord::Migration.maintain_test_schema!
class ActiveSupport::TestCase
include ActiveRecord::TestFixtures
self.fixture_path = "#{Rails.root}/test/fixtures/"
+ self.file_fixture_path = self.fixture_path + "files"
end
ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path
diff --git a/railties/lib/rails/test_unit/minitest_plugin.rb b/railties/lib/rails/test_unit/minitest_plugin.rb
new file mode 100644
index 0000000000..70ce9d3360
--- /dev/null
+++ b/railties/lib/rails/test_unit/minitest_plugin.rb
@@ -0,0 +1,14 @@
+require "minitest"
+require "rails/test_unit/reporter"
+
+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
+ end
+
+ if !($rails_test_runner && $rails_test_runner.show_backtrace?)
+ Minitest.backtrace_filter = Rails.backtrace_cleaner
+ end
+end
+Minitest.extensions << 'rails'
diff --git a/railties/lib/rails/test_unit/reporter.rb b/railties/lib/rails/test_unit/reporter.rb
new file mode 100644
index 0000000000..64e99626eb
--- /dev/null
+++ b/railties/lib/rails/test_unit/reporter.rb
@@ -0,0 +1,22 @@
+require "minitest"
+
+module Rails
+ class TestUnitReporter < Minitest::StatisticsReporter
+ def report
+ return if results.empty?
+ io.puts
+ io.puts "Failed tests:"
+ io.puts
+ io.puts aggregated_results
+ 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}"
+ end.join "\n"
+ end
+ end
+end
diff --git a/railties/lib/rails/test_unit/runner.rb b/railties/lib/rails/test_unit/runner.rb
new file mode 100644
index 0000000000..5573fa6904
--- /dev/null
+++ b/railties/lib/rails/test_unit/runner.rb
@@ -0,0 +1,137 @@
+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/testing.rake b/railties/lib/rails/test_unit/testing.rake
index 957deb8a60..0f26621b59 100644
--- a/railties/lib/rails/test_unit/testing.rake
+++ b/railties/lib/rails/test_unit/testing.rake
@@ -1,48 +1,44 @@
-require 'rake/testtask'
-require 'rails/test_unit/sub_test_task'
+require "rails/test_unit/runner"
task default: :test
-desc 'Runs test:units, test:functionals, test:generators, test:integration, test:jobs together'
+desc "Runs all tests in test folder"
task :test do
- Rails::TestTask.test_creator(Rake.application.top_level_tasks).invoke_rake_task
+ $: << "test"
+ args = ARGV[0] == "test" ? ARGV[1..-1] : []
+ Rails::TestRunner.run(args)
end
namespace :test do
task :prepare do
- # Placeholder task for other Railtie and plugins to enhance. See Active Record for an example.
+ # Placeholder task for other Railtie and plugins to enhance.
+ # If used with Active Record, this task runs before the database schema is synchronized.
end
- task :run => ['test:units', 'test:functionals', 'test:generators', 'test:integration', 'test:jobs']
+ task :run => %w[test]
- # Inspired by: http://ngauthier.com/2012/02/quick-tests-with-bash.html
- desc "Run tests quickly by merging all types and not resetting db"
- Rails::TestTask.new(:all) do |t|
- t.pattern = "test/**/*_test.rb"
- end
-
- namespace :all do
- desc "Run tests quickly, but also reset db"
- task :db => %w[db:test:prepare test:all]
- end
-
- Rails::TestTask.new(single: "test:prepare")
+ desc "Run tests quickly, but also reset db"
+ task :db => %w[db:test:prepare test]
["models", "helpers", "controllers", "mailers", "integration", "jobs"].each do |name|
- Rails::TestTask.new(name => "test:prepare") do |t|
- t.pattern = "test/#{name}/**/*_test.rb"
+ task name => "test:prepare" do
+ $: << "test"
+ Rails::TestRunner.run(["test/#{name}"])
end
end
- Rails::TestTask.new(generators: "test:prepare") do |t|
- t.pattern = "test/lib/generators/**/*_test.rb"
+ task :generators => "test:prepare" do
+ $: << "test"
+ Rails::TestRunner.run(["test/lib/generators"])
end
- Rails::TestTask.new(units: "test:prepare") do |t|
- t.pattern = 'test/{models,helpers,unit}/**/*_test.rb'
+ task :units => "test:prepare" do
+ $: << "test"
+ Rails::TestRunner.run(["test/models", "test/helpers", "test/unit"])
end
- Rails::TestTask.new(functionals: "test:prepare") do |t|
- t.pattern = 'test/{controllers,mailers,functional}/**/*_test.rb'
+ task :functionals => "test:prepare" do
+ $: << "test"
+ Rails::TestRunner.run(["test/controllers", "test/mailers", "test/functional"])
end
end
diff --git a/railties/railties.gemspec b/railties/railties.gemspec
index 56b8736800..001882fdc6 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 = '>= 1.9.3'
+ s.required_ruby_version = '>= 2.2.1'
s.license = 'MIT'
@@ -28,6 +28,7 @@ Gem::Specification.new do |s|
s.add_dependency 'rake', '>= 0.8.7'
s.add_dependency 'thor', '>= 0.18.1', '< 2.0'
+ s.add_dependency 'method_source'
s.add_development_dependency 'actionview', version
end
diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb
index 0749615d03..794d180e5d 100644
--- a/railties/test/abstract_unit.rb
+++ b/railties/test/abstract_unit.rb
@@ -4,6 +4,7 @@ require File.expand_path("../../../load_paths", __FILE__)
require 'stringio'
require 'active_support/testing/autorun'
+require 'active_support/testing/stream'
require 'fileutils'
require 'active_support'
@@ -28,26 +29,5 @@ def jruby_skip(message = '')
end
class ActiveSupport::TestCase
- # FIXME: we have tests that depend on run order, we should fix that and
- # remove this method call.
- self.test_order = :sorted
-
- private
-
- def capture(stream)
- stream = stream.to_s
- captured_stream = Tempfile.new(stream)
- stream_io = eval("$#{stream}")
- origin_stream = stream_io.dup
- stream_io.reopen(captured_stream)
-
- yield
-
- stream_io.rewind
- return captured_stream.read
- ensure
- captured_stream.close
- captured_stream.unlink
- stream_io.reopen(origin_stream)
- end
+ include ActiveSupport::Testing::Stream
end
diff --git a/railties/test/application/asset_debugging_test.rb b/railties/test/application/asset_debugging_test.rb
index 9a571fac3a..acd387256c 100644
--- a/railties/test/application/asset_debugging_test.rb
+++ b/railties/test/application/asset_debugging_test.rb
@@ -57,8 +57,8 @@ module ApplicationTests
class ::PostsController < ActionController::Base ; end
get '/posts?debug_assets=true'
- assert_match(/<script src="\/assets\/application-([0-z]+)\.js\?body=1"><\/script>/, last_response.body)
- assert_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js\?body=1"><\/script>/, last_response.body)
+ assert_match(/<script src="\/assets\/application(\.self)?-([0-z]+)\.js\?body=1"><\/script>/, last_response.body)
+ assert_match(/<script src="\/assets\/xmlhr(\.self)?-([0-z]+)\.js\?body=1"><\/script>/, last_response.body)
end
end
end
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index 8f091cfdbf..f6b7d4c855 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
require 'isolation/abstract_unit'
require 'rack/test'
require 'active_support/json'
@@ -205,7 +204,7 @@ module ApplicationTests
app_file "app/assets/javascripts/application.js", "alert();"
precompile!
- manifest = Dir["#{app_path}/public/assets/manifest-*.json"].first
+ manifest = Dir["#{app_path}/public/assets/.sprockets-manifest-*.json"].first
assets = ActiveSupport::JSON.decode(File.read(manifest))
assert_match(/application-([0-z]+)\.js/, assets["assets"]["application.js"])
@@ -218,19 +217,19 @@ module ApplicationTests
precompile!
- manifest = Dir["#{app_path}/public/x/manifest-*.json"].first
+ manifest = Dir["#{app_path}/public/x/.sprockets-manifest-*.json"].first
assets = ActiveSupport::JSON.decode(File.read(manifest))
assert_match(/application-([0-z]+)\.js/, assets["assets"]["application.js"])
end
test "assets do not require any assets group gem when manifest file is present" do
app_file "app/assets/javascripts/application.js", "alert();"
- add_to_env_config "production", "config.serve_static_assets = true"
+ add_to_env_config "production", "config.serve_static_files = true"
ENV["RAILS_ENV"] = "production"
precompile!
- manifest = Dir["#{app_path}/public/assets/manifest-*.json"].first
+ manifest = Dir["#{app_path}/public/assets/.sprockets-manifest-*.json"].first
assets = ActiveSupport::JSON.decode(File.read(manifest))
asset_path = assets["assets"]["application.js"]
@@ -262,7 +261,7 @@ module ApplicationTests
ENV["RAILS_ENV"] = "production"
precompile!
- manifest = Dir["#{app_path}/public/assets/manifest-*.json"].first
+ manifest = Dir["#{app_path}/public/assets/.sprockets-manifest-*.json"].first
assets = ActiveSupport::JSON.decode(File.read(manifest))
asset_path = assets["assets"]["application.css"]
@@ -292,7 +291,7 @@ module ApplicationTests
precompile!
- manifest = Dir["#{app_path}/public/assets/manifest-*.json"].first
+ manifest = Dir["#{app_path}/public/assets/.sprockets-manifest-*.json"].first
assets = ActiveSupport::JSON.decode(File.read(manifest))
assert asset_path = assets["assets"].find { |(k, _)| k && k =~ /.png/ }[1]
@@ -438,9 +437,9 @@ module ApplicationTests
class ::PostsController < ActionController::Base; end
get '/posts', {}, {'HTTPS'=>'off'}
- assert_match('src="http://example.com/assets/application.js', last_response.body)
+ assert_match('src="http://example.com/assets/application.self.js', last_response.body)
get '/posts', {}, {'HTTPS'=>'on'}
- assert_match('src="https://example.com/assets/application.js', last_response.body)
+ assert_match('src="https://example.com/assets/application.self.js', last_response.body)
end
test "asset urls should be protocol-relative if no request is in scope" do
diff --git a/railties/test/application/build_original_fullpath_test.rb b/railties/test/application/build_original_fullpath_test.rb
deleted file mode 100644
index 647ffb097a..0000000000
--- a/railties/test/application/build_original_fullpath_test.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-require "abstract_unit"
-
-module ApplicationTests
- class BuildOriginalPathTest < ActiveSupport::TestCase
- def test_include_original_PATH_info_in_ORIGINAL_FULLPATH
- env = { 'PATH_INFO' => '/foo/' }
- assert_equal "/foo/", Rails.application.send(:build_original_fullpath, env)
- end
-
- def test_include_SCRIPT_NAME
- env = {
- 'SCRIPT_NAME' => '/foo',
- 'PATH_INFO' => '/bar'
- }
-
- assert_equal "/foo/bar", Rails.application.send(:build_original_fullpath, env)
- end
-
- def test_include_QUERY_STRING
- env = {
- 'PATH_INFO' => '/foo',
- 'QUERY_STRING' => 'bar',
- }
- assert_equal "/foo?bar", Rails.application.send(:build_original_fullpath, env)
- end
- end
-end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 2b6eb3624a..8f5b2d0d68 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -41,7 +41,7 @@ module ApplicationTests
def setup
build_app
boot_rails
- FileUtils.rm_rf("#{app_path}/config/environments")
+ supress_default_config
end
def teardown
@@ -49,6 +49,15 @@ module ApplicationTests
FileUtils.rm_rf(new_app) if File.directory?(new_app)
end
+ def supress_default_config
+ FileUtils.mv("#{app_path}/config/environments", "#{app_path}/config/__environments__")
+ end
+
+ def restore_default_config
+ FileUtils.rm_rf("#{app_path}/config/environments")
+ FileUtils.mv("#{app_path}/config/__environments__", "#{app_path}/config/environments")
+ end
+
test "Rails.env does not set the RAILS_ENV environment variable which would leak out into rake tasks" do
require "rails"
@@ -280,10 +289,41 @@ module ApplicationTests
assert_equal Pathname.new(app_path).join("somewhere"), Rails.public_path
end
+ test "In production mode, config.serve_static_files is off by default" do
+ restore_default_config
+
+ with_rails_env "production" do
+ require "#{app_path}/config/environment"
+ assert_not app.config.serve_static_files
+ end
+ end
+
+ test "In production mode, config.serve_static_files is enabled when RAILS_SERVE_STATIC_FILES is set" do
+ restore_default_config
+
+ with_rails_env "production" do
+ switch_env "RAILS_SERVE_STATIC_FILES", "1" do
+ require "#{app_path}/config/environment"
+ assert app.config.serve_static_files
+ end
+ end
+ end
+
+ test "In production mode, config.serve_static_files is disabled when RAILS_SERVE_STATIC_FILES is blank" do
+ restore_default_config
+
+ with_rails_env "production" do
+ switch_env "RAILS_SERVE_STATIC_FILES", " " do
+ require "#{app_path}/config/environment"
+ assert_not app.config.serve_static_files
+ end
+ end
+ end
+
test "Use key_generator when secret_key_base is set" do
- make_basic_app do |app|
- app.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33'
- app.config.session_store :disabled
+ make_basic_app do |application|
+ application.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33'
+ application.config.session_store :disabled
end
class ::OmgController < ActionController::Base
@@ -301,9 +341,9 @@ module ApplicationTests
end
test "application verifier can be used in the entire application" do
- make_basic_app do |app|
- app.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33'
- app.config.session_store :disabled
+ make_basic_app do |application|
+ application.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33'
+ application.config.session_store :disabled
end
message = app.message_verifier(:sensitive_value).generate("some_value")
@@ -315,10 +355,55 @@ module ApplicationTests
assert_equal 'some_value', verifier.verify(message)
end
+ test "application message verifier can be used when the key_generator is ActiveSupport::LegacyKeyGenerator" do
+ app_file 'config/initializers/secret_token.rb', <<-RUBY
+ Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33"
+ RUBY
+ app_file 'config/secrets.yml', <<-YAML
+ development:
+ secret_key_base:
+ YAML
+ require "#{app_path}/config/environment"
+
+
+ assert_equal app.env_config['action_dispatch.key_generator'], Rails.application.key_generator
+ assert_equal app.env_config['action_dispatch.key_generator'].class, ActiveSupport::LegacyKeyGenerator
+ message = app.message_verifier(:sensitive_value).generate("some_value")
+ assert_equal 'some_value', Rails.application.message_verifier(:sensitive_value).verify(message)
+ end
+
+ test "warns when secrets.secret_key_base is blank and config.secret_token is set" do
+ app_file 'config/initializers/secret_token.rb', <<-RUBY
+ Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33"
+ RUBY
+ app_file 'config/secrets.yml', <<-YAML
+ development:
+ secret_key_base:
+ YAML
+ require "#{app_path}/config/environment"
+
+ assert_deprecated(/You didn't set `secret_key_base`./) do
+ app.env_config
+ end
+ end
+
+ test "prefer secrets.secret_token over config.secret_token" do
+ app_file 'config/initializers/secret_token.rb', <<-RUBY
+ Rails.application.config.secret_token = ""
+ RUBY
+ app_file 'config/secrets.yml', <<-YAML
+ development:
+ secret_token: 3b7cd727ee24e8444053437c36cc66c3
+ YAML
+ require "#{app_path}/config/environment"
+
+ assert_equal '3b7cd727ee24e8444053437c36cc66c3', app.secrets.secret_token
+ end
+
test "application verifier can build different verifiers" do
- make_basic_app do |app|
- app.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33'
- app.config.session_store :disabled
+ make_basic_app do |application|
+ application.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33'
+ application.config.session_store :disabled
end
default_verifier = app.message_verifier(:sensitive_value)
@@ -355,6 +440,21 @@ module ApplicationTests
assert_equal '3b7cd727ee24e8444053437c36cc66c3', app.secrets.secret_key_base
end
+ test "config.secret_token over-writes a blank secrets.secret_token" do
+ app_file 'config/initializers/secret_token.rb', <<-RUBY
+ Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33"
+ RUBY
+ app_file 'config/secrets.yml', <<-YAML
+ development:
+ secret_key_base:
+ secret_token:
+ YAML
+ require "#{app_path}/config/environment"
+
+ assert_equal 'b3c631c314c0bbca50c1b2843150fe33', app.secrets.secret_token
+ assert_equal 'b3c631c314c0bbca50c1b2843150fe33', app.config.secret_token
+ end
+
test "custom secrets saved in config/secrets.yml are loaded in app secrets" do
app_file 'config/secrets.yml', <<-YAML
development:
@@ -376,6 +476,51 @@ module ApplicationTests
assert_nil app.secrets.not_defined
end
+ test "config.secret_key_base over-writes a blank secrets.secret_key_base" do
+ app_file 'config/initializers/secret_token.rb', <<-RUBY
+ Rails.application.config.secret_key_base = "iaminallyoursecretkeybase"
+ RUBY
+ app_file 'config/secrets.yml', <<-YAML
+ development:
+ secret_key_base:
+ YAML
+ require "#{app_path}/config/environment"
+
+ assert_equal "iaminallyoursecretkeybase", app.secrets.secret_key_base
+ end
+
+ test "uses ActiveSupport::LegacyKeyGenerator as app.key_generator when secrets.secret_key_base is blank" do
+ app_file 'config/initializers/secret_token.rb', <<-RUBY
+ Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33"
+ RUBY
+ app_file 'config/secrets.yml', <<-YAML
+ development:
+ secret_key_base:
+ YAML
+ require "#{app_path}/config/environment"
+
+ assert_equal 'b3c631c314c0bbca50c1b2843150fe33', app.config.secret_token
+ assert_equal nil, app.secrets.secret_key_base
+ assert_equal app.key_generator.class, ActiveSupport::LegacyKeyGenerator
+ end
+
+ test "uses ActiveSupport::LegacyKeyGenerator with config.secret_token as app.key_generator when secrets.secret_key_base is blank" do
+ app_file 'config/initializers/secret_token.rb', <<-RUBY
+ Rails.application.config.secret_token = ""
+ RUBY
+ app_file 'config/secrets.yml', <<-YAML
+ development:
+ secret_key_base:
+ YAML
+ require "#{app_path}/config/environment"
+
+ assert_equal '', app.config.secret_token
+ assert_equal nil, app.secrets.secret_key_base
+ assert_raise ArgumentError, /\AA secret is required/ do
+ app.key_generator
+ end
+ end
+
test "protect from forgery is the default in a new app" do
make_basic_app
@@ -389,6 +534,45 @@ module ApplicationTests
assert last_response.body =~ /csrf\-param/
end
+ test "default form builder specified as a string" do
+
+ app_file 'config/initializers/form_builder.rb', <<-RUBY
+ class CustomFormBuilder < ActionView::Helpers::FormBuilder
+ def text_field(attribute, *args)
+ label(attribute) + super(attribute, *args)
+ end
+ end
+ Rails.configuration.action_view.default_form_builder = "CustomFormBuilder"
+ RUBY
+
+ app_file 'app/models/post.rb', <<-RUBY
+ class Post
+ include ActiveModel::Model
+ attr_accessor :name
+ end
+ RUBY
+
+
+ app_file 'app/controllers/posts_controller.rb', <<-RUBY
+ class PostsController < ApplicationController
+ def index
+ render inline: "<%= begin; form_for(Post.new) {|f| f.text_field(:name)}; rescue => e; e.to_s; end %>"
+ end
+ end
+ RUBY
+
+ add_to_config <<-RUBY
+ routes.prepend do
+ resources :posts
+ end
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ get "/posts"
+ assert_match(/label/, last_response.body)
+ end
+
test "default method for update can be changed" do
app_file 'app/models/post.rb', <<-RUBY
class Post
@@ -443,8 +627,8 @@ module ApplicationTests
end
test "request forgery token param can be changed" do
- make_basic_app do
- app.config.action_controller.request_forgery_protection_token = '_xsrf_token_here'
+ make_basic_app do |application|
+ application.config.action_controller.request_forgery_protection_token = '_xsrf_token_here'
end
class ::OmgController < ActionController::Base
@@ -463,8 +647,8 @@ module ApplicationTests
end
test "sets ActionDispatch::Response.default_charset" do
- make_basic_app do |app|
- app.config.action_dispatch.default_charset = "utf-16"
+ make_basic_app do |application|
+ application.config.action_dispatch.default_charset = "utf-16"
end
assert_equal "utf-16", ActionDispatch::Response.default_charset
@@ -645,8 +829,8 @@ module ApplicationTests
end
test "config.action_dispatch.show_exceptions is sent in env" do
- make_basic_app do |app|
- app.config.action_dispatch.show_exceptions = true
+ make_basic_app do |application|
+ application.config.action_dispatch.show_exceptions = true
end
class ::OmgController < ActionController::Base
@@ -807,8 +991,8 @@ module ApplicationTests
end
test "config.action_dispatch.ignore_accept_header" do
- make_basic_app do |app|
- app.config.action_dispatch.ignore_accept_header = true
+ make_basic_app do |application|
+ application.config.action_dispatch.ignore_accept_header = true
end
class ::OmgController < ActionController::Base
@@ -845,9 +1029,9 @@ module ApplicationTests
test "config.session_store with :active_record_store with activerecord-session_store gem" do
begin
- make_basic_app do |app|
+ make_basic_app do |application|
ActionDispatch::Session::ActiveRecordStore = Class.new(ActionDispatch::Session::CookieStore)
- app.config.session_store :active_record_store
+ application.config.session_store :active_record_store
end
ensure
ActionDispatch::Session.send :remove_const, :ActiveRecordStore
@@ -856,16 +1040,16 @@ module ApplicationTests
test "config.session_store with :active_record_store without activerecord-session_store gem" do
assert_raise RuntimeError, /activerecord-session_store/ do
- make_basic_app do |app|
- app.config.session_store :active_record_store
+ make_basic_app do |application|
+ application.config.session_store :active_record_store
end
end
end
test "config.log_level with custom logger" do
- make_basic_app do |app|
- app.config.logger = Logger.new(STDOUT)
- app.config.log_level = :info
+ make_basic_app do |application|
+ application.config.logger = Logger.new(STDOUT)
+ application.config.log_level = :info
end
assert_equal Logger::INFO, Rails.logger.level
end
@@ -895,8 +1079,8 @@ module ApplicationTests
end
test "config.annotations wrapping SourceAnnotationExtractor::Annotation class" do
- make_basic_app do |app|
- app.config.annotations.register_extensions("coffee") do |tag|
+ make_basic_app do |application|
+ application.config.annotations.register_extensions("coffee") do |tag|
/#\s*(#{tag}):?\s*(.*)$/
end
end
@@ -984,7 +1168,7 @@ module ApplicationTests
app_file 'config/environments/development.rb', <<-RUBY
Rails.application.configure do
- config.paths.add 'config/database', with: 'config/nonexistant.yml'
+ config.paths.add 'config/database', with: 'config/nonexistent.yml'
config.paths['config/database'] << 'config/database.yml'
end
RUBY
diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb
index 2d45c9b53f..97b51911d9 100644
--- a/railties/test/application/initializers/frameworks_test.rb
+++ b/railties/test/application/initializers/frameworks_test.rb
@@ -65,7 +65,6 @@ module ApplicationTests
RUBY
require "#{app_path}/config/environment"
- assert Foo.method_defined?(:foo_path)
assert Foo.method_defined?(:foo_url)
assert Foo.method_defined?(:main_app)
end
diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb
index 4f30f30f95..85066210f3 100644
--- a/railties/test/application/loading_test.rb
+++ b/railties/test/application/loading_test.rb
@@ -33,6 +33,35 @@ class LoadingTest < ActiveSupport::TestCase
assert_equal 'omg', p.title
end
+ test "concerns in app are autoloaded" do
+ app_file "app/controllers/concerns/trackable.rb", <<-CONCERN
+ module Trackable
+ end
+ CONCERN
+
+ app_file "app/mailers/concerns/email_loggable.rb", <<-CONCERN
+ module EmailLoggable
+ end
+ CONCERN
+
+ app_file "app/models/concerns/orderable.rb", <<-CONCERN
+ module Orderable
+ end
+ CONCERN
+
+ app_file "app/validators/concerns/matchable.rb", <<-CONCERN
+ module Matchable
+ end
+ CONCERN
+
+ require "#{rails_root}/config/environment"
+
+ assert_nothing_raised(NameError) { Trackable }
+ assert_nothing_raised(NameError) { EmailLoggable }
+ assert_nothing_raised(NameError) { Orderable }
+ assert_nothing_raised(NameError) { Matchable }
+ end
+
test "models without table do not panic on scope definitions when loaded" do
app_file "app/models/user.rb", <<-MODEL
class User < ActiveRecord::Base
diff --git a/railties/test/application/mailer_previews_test.rb b/railties/test/application/mailer_previews_test.rb
index 55e917c3ec..1752a9f3c6 100644
--- a/railties/test/application/mailer_previews_test.rb
+++ b/railties/test/application/mailer_previews_test.rb
@@ -40,6 +40,17 @@ module ApplicationTests
assert_equal 404, last_response.status
end
+ test "/rails/mailers is accessible with globbing route present" do
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ get '*foo', to: 'foo#index'
+ end
+ RUBY
+ app("development")
+ get "/rails/mailers"
+ assert_equal 200, last_response.status
+ end
+
test "mailer previews are loaded from the default preview_path" do
mailer 'notifier', <<-RUBY
class Notifier < ActionMailer::Base
@@ -417,58 +428,6 @@ module ApplicationTests
assert_match '<option selected value="?part=text%2Fplain">View as plain-text email</option>', last_response.body
end
- test "*_path helpers emit a deprecation" do
-
- app_file "config/routes.rb", <<-RUBY
- Rails.application.routes.draw do
- get 'foo', to: 'foo#index'
- end
- RUBY
-
- mailer 'notifier', <<-RUBY
- class Notifier < ActionMailer::Base
- default from: "from@example.com"
-
- def path_in_view
- mail to: "to@example.org"
- end
-
- def path_in_mailer
- @url = foo_path
- mail to: "to@example.org"
- end
- end
- RUBY
-
- html_template 'notifier/path_in_view', "<%= link_to 'foo', foo_path %>"
-
- mailer_preview 'notifier', <<-RUBY
- class NotifierPreview < ActionMailer::Preview
- def path_in_view
- Notifier.path_in_view
- end
-
- def path_in_mailer
- Notifier.path_in_mailer
- end
- end
- RUBY
-
- app('development')
-
- assert_deprecated do
- get "/rails/mailers/notifier/path_in_view.html"
- assert_equal 200, last_response.status
- end
-
- html_template 'notifier/path_in_mailer', "No ERB in here"
-
- assert_deprecated do
- get "/rails/mailers/notifier/path_in_mailer.html"
- assert_equal 200, last_response.status
- end
- end
-
private
def build_app
super
diff --git a/railties/test/application/middleware/exceptions_test.rb b/railties/test/application/middleware/exceptions_test.rb
index a7472b37f1..4906f9a1e8 100644
--- a/railties/test/application/middleware/exceptions_test.rb
+++ b/railties/test/application/middleware/exceptions_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'isolation/abstract_unit'
require 'rack/test'
diff --git a/railties/test/application/middleware/sendfile_test.rb b/railties/test/application/middleware/sendfile_test.rb
index eb791f5687..dc96480d6d 100644
--- a/railties/test/application/middleware/sendfile_test.rb
+++ b/railties/test/application/middleware/sendfile_test.rb
@@ -61,7 +61,7 @@ module ApplicationTests
test "files handled by ActionDispatch::Static are handled by Rack::Sendfile" do
make_basic_app do |app|
app.config.action_dispatch.x_sendfile_header = 'X-Sendfile'
- app.config.serve_static_assets = true
+ app.config.serve_static_files = true
app.paths["public"] = File.join(rails_root, "public")
end
diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb
index 31a64c2f5a..a8dc79d10a 100644
--- a/railties/test/application/middleware/session_test.rb
+++ b/railties/test/application/middleware/session_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'isolation/abstract_unit'
require 'rack/test'
@@ -203,7 +202,7 @@ module ApplicationTests
RUBY
add_to_config <<-RUBY
- config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
+ secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
RUBY
require "#{app_path}/config/environment"
@@ -258,7 +257,7 @@ module ApplicationTests
RUBY
add_to_config <<-RUBY
- config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
+ secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
RUBY
require "#{app_path}/config/environment"
@@ -317,7 +316,7 @@ module ApplicationTests
RUBY
add_to_config <<-RUBY
- config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
+ secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
secrets.secret_key_base = nil
RUBY
@@ -334,7 +333,7 @@ module ApplicationTests
get '/foo/read_signed_cookie'
assert_equal '2', last_response.body
- verifier = ActiveSupport::MessageVerifier.new(app.config.secret_token)
+ verifier = ActiveSupport::MessageVerifier.new(app.secrets.secret_token)
get '/foo/read_raw_cookie'
assert_equal 2, verifier.verify(last_response.body)['foo']
diff --git a/railties/test/application/middleware/static_test.rb b/railties/test/application/middleware/static_test.rb
index 0a793f8f60..121c5d3321 100644
--- a/railties/test/application/middleware/static_test.rb
+++ b/railties/test/application/middleware/static_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'isolation/abstract_unit'
require 'rack/test'
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index caef39d16f..04bd19784a 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -113,8 +113,8 @@ module ApplicationTests
assert !middleware.include?("Rack::Lock")
end
- test "removes static asset server if serve_static_assets is disabled" do
- add_to_config "config.serve_static_assets = false"
+ test "removes static asset server if serve_static_files is disabled" do
+ add_to_config "config.serve_static_files = false"
boot!
assert !middleware.include?("ActionDispatch::Static")
end
@@ -125,6 +125,22 @@ module ApplicationTests
assert !middleware.include?("ActionDispatch::Static")
end
+ test "can delete a middleware from the stack even if insert_before is added after delete" do
+ add_to_config "config.middleware.delete Rack::Runtime"
+ add_to_config "config.middleware.insert_before(Rack::Runtime, Rack::Config)"
+ boot!
+ assert middleware.include?("Rack::Config")
+ assert_not middleware.include?("Rack::Runtime")
+ end
+
+ test "can delete a middleware from the stack even if insert_after is added after delete" do
+ add_to_config "config.middleware.delete Rack::Runtime"
+ add_to_config "config.middleware.insert_after(Rack::Runtime, Rack::Config)"
+ boot!
+ assert middleware.include?("Rack::Config")
+ assert_not middleware.include?("Rack::Runtime")
+ end
+
test "includes exceptions middlewares even if action_dispatch.show_exceptions is disabled" do
add_to_config "config.action_dispatch.show_exceptions = false"
boot!
diff --git a/railties/test/application/multiple_applications_test.rb b/railties/test/application/multiple_applications_test.rb
index 9ebf163671..cddc79cc85 100644
--- a/railties/test/application/multiple_applications_test.rb
+++ b/railties/test/application/multiple_applications_test.rb
@@ -8,6 +8,7 @@ module ApplicationTests
build_app(initializers: true)
boot_rails
require "#{rails_root}/config/environment"
+ Rails.application.config.some_setting = 'something_or_other'
end
def teardown
@@ -18,7 +19,7 @@ module ApplicationTests
clone = Rails.application.clone
assert_equal Rails.application.config, clone.config, "The cloned application should get a copy of the config"
- assert_equal Rails.application.config.secret_key_base, clone.config.secret_key_base, "The base secret key on the config should be the same"
+ assert_equal Rails.application.config.some_setting, clone.config.some_setting, "The some_setting on the config should be the same"
end
def test_inheriting_multiple_times_from_application
@@ -160,13 +161,14 @@ module ApplicationTests
def test_inserting_configuration_into_application
app = AppTemplate::Application.new(config: Rails.application.config)
- new_config = Rails::Application::Configuration.new("root_of_application")
- new_config.secret_key_base = "some_secret_key_dude"
- app.config.secret_key_base = "a_different_secret_key"
+ app.config.some_setting = "a_different_setting"
+ assert_equal "a_different_setting", app.config.some_setting, "The configuration's some_setting should be set."
- assert_equal "a_different_secret_key", app.config.secret_key_base, "The configuration's secret key should be set."
+ new_config = Rails::Application::Configuration.new("root_of_application")
+ new_config.some_setting = "some_setting_dude"
app.config = new_config
- assert_equal "some_secret_key_dude", app.config.secret_key_base, "The configuration's secret key should have changed."
+
+ assert_equal "some_setting_dude", app.config.some_setting, "The configuration's some_setting should have changed."
assert_equal "root_of_application", app.config.root, "The root should have changed to the new config's root."
assert_equal new_config, app.config, "The application's config should have changed to the new config."
end
diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb
index 524c70aad2..c414732f92 100644
--- a/railties/test/application/rake/dbs_test.rb
+++ b/railties/test/application/rake/dbs_test.rb
@@ -156,6 +156,31 @@ module ApplicationTests
end
end
+ test 'db:schema:load and db:structure:load do not purge the existing database' do
+ Dir.chdir(app_path) do
+ `bin/rails runner 'ActiveRecord::Base.connection.create_table(:posts) {|t| t.string :title }'`
+
+ app_file 'db/schema.rb', <<-RUBY
+ ActiveRecord::Schema.define(version: 20140423102712) do
+ create_table(:comments) {}
+ end
+ RUBY
+
+ list_tables = lambda { `bin/rails runner 'p ActiveRecord::Base.connection.tables'`.strip }
+
+ assert_equal '["posts"]', list_tables[]
+ `bin/rake db:schema:load`
+ assert_equal '["posts", "comments", "schema_migrations"]', list_tables[]
+
+ app_file 'db/structure.sql', <<-SQL
+ CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255));
+ SQL
+
+ `bin/rake db:structure:load`
+ assert_equal '["posts", "comments", "schema_migrations", "users"]', list_tables[]
+ end
+ end
+
def db_test_load_structure
Dir.chdir(app_path) do
`rails generate model book title:string;
@@ -175,15 +200,6 @@ module ApplicationTests
db_test_load_structure
end
- test 'db:test deprecation' do
- require "#{app_path}/config/environment"
- Dir.chdir(app_path) do
- output = `bundle exec rake db:migrate db:test:prepare 2>&1`
- assert_equal "WARNING: db:test:prepare is deprecated. The Rails test helper now maintains " \
- "your test schema automatically, see the release notes for details.\n", output
- end
- end
-
test 'db:setup loads schema and seeds database' do
begin
@old_rails_env = ENV["RAILS_ENV"]
diff --git a/railties/test/application/rake/restart_test.rb b/railties/test/application/rake/restart_test.rb
new file mode 100644
index 0000000000..35099913fb
--- /dev/null
+++ b/railties/test/application/rake/restart_test.rb
@@ -0,0 +1,31 @@
+require "isolation/abstract_unit"
+
+module ApplicationTests
+ module RakeTests
+ class RakeRestartTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def setup
+ build_app
+ boot_rails
+ end
+
+ 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`
+ curr_mtime = File.mtime("tmp/restart.txt")
+ assert_not_equal prev_mtime, curr_mtime
+ end
+ end
+ end
+ end
+end
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index e8c8de9f73..0648b11813 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -99,7 +99,7 @@ module ApplicationTests
end
def test_code_statistics_sanity
- assert_match "Code LOC: 5 Test LOC: 0 Code to Test Ratio: 1:0.0",
+ assert_match "Code LOC: 7 Test LOC: 0 Code to Test Ratio: 1:0.0",
Dir.chdir(app_path){ `rake stats` }
end
@@ -194,7 +194,10 @@ module ApplicationTests
assert_no_match(/Errors running/, output)
end
- def test_scaffold_with_references_columns_tests_pass_by_default
+ def test_scaffold_with_references_columns_tests_pass_when_belongs_to_is_optional
+ app_file "config/initializers/active_record_belongs_to_required_by_default.rb",
+ "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`
@@ -227,7 +230,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 DB_STRUCTURE=db/my_structure.sql`
+ `bundle exec rake db:migrate db:structure:dump SCHEMA=db/my_structure.sql`
end
assert File.exist?(File.join(app_path, 'db', 'my_structure.sql'))
end
diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb
index 8576a2b738..cbada6be97 100644
--- a/railties/test/application/routing_test.rb
+++ b/railties/test/application/routing_test.rb
@@ -123,6 +123,26 @@ module ApplicationTests
assert_equal '/archives', last_response.body
end
+ test "mount named rack app" do
+ controller :foo, <<-RUBY
+ class FooController < ApplicationController
+ def index
+ render text: my_blog_path
+ end
+ end
+ RUBY
+
+ app_file 'config/routes.rb', <<-RUBY
+ Rails.application.routes.draw do
+ mount lambda { |env| [200, {}, [env["PATH_INFO"]]] }, at: "/blog", as: "my_blog"
+ get '/foo' => 'foo#index'
+ end
+ RUBY
+
+ get '/foo'
+ assert_equal '/blog', last_response.body
+ end
+
test "multiple controllers" do
controller :foo, <<-RUBY
class FooController < ApplicationController
diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb
index 032b11a95f..c122b315c0 100644
--- a/railties/test/application/test_runner_test.rb
+++ b/railties/test/application/test_runner_test.rb
@@ -7,7 +7,6 @@ module ApplicationTests
def setup
build_app
- ENV['RAILS_ENV'] = nil
create_schema
end
@@ -47,16 +46,15 @@ module ApplicationTests
def; end
RUBY
- error_stream = Tempfile.new('error')
- redirect_stderr(error_stream) { run_test_command('test/models/error_test.rb') }
- assert_match "syntax error", error_stream.read
+ error = capture(:stderr) { run_test_command('test/models/error_test.rb') }
+ assert_match "syntax error", error
end
def test_run_models
create_test_file :models, 'foo'
create_test_file :models, 'bar'
create_test_file :controllers, 'foobar_controller'
- run_test_models_command.tap do |output|
+ run_test_command("test/models").tap do |output|
assert_match "FooTest", output
assert_match "BarTest", output
assert_match "2 runs, 2 assertions, 0 failures", output
@@ -67,7 +65,7 @@ module ApplicationTests
create_test_file :helpers, 'foo_helper'
create_test_file :helpers, 'bar_helper'
create_test_file :controllers, 'foobar_controller'
- run_test_helpers_command.tap do |output|
+ run_test_command("test/helpers").tap do |output|
assert_match "FooHelperTest", output
assert_match "BarHelperTest", output
assert_match "2 runs, 2 assertions, 0 failures", output
@@ -75,6 +73,7 @@ module ApplicationTests
end
def test_run_units
+ skip "we no longer have the concept of unit tests. Just different directories..."
create_test_file :models, 'foo'
create_test_file :helpers, 'bar_helper'
create_test_file :unit, 'baz_unit'
@@ -91,7 +90,7 @@ module ApplicationTests
create_test_file :controllers, 'foo_controller'
create_test_file :controllers, 'bar_controller'
create_test_file :models, 'foo'
- run_test_controllers_command.tap do |output|
+ run_test_command("test/controllers").tap do |output|
assert_match "FooControllerTest", output
assert_match "BarControllerTest", output
assert_match "2 runs, 2 assertions, 0 failures", output
@@ -102,7 +101,7 @@ module ApplicationTests
create_test_file :mailers, 'foo_mailer'
create_test_file :mailers, 'bar_mailer'
create_test_file :models, 'foo'
- run_test_mailers_command.tap do |output|
+ run_test_command("test/mailers").tap do |output|
assert_match "FooMailerTest", output
assert_match "BarMailerTest", output
assert_match "2 runs, 2 assertions, 0 failures", output
@@ -113,7 +112,7 @@ module ApplicationTests
create_test_file :jobs, 'foo_job'
create_test_file :jobs, 'bar_job'
create_test_file :models, 'foo'
- run_test_jobs_command.tap do |output|
+ run_test_command("test/jobs").tap do |output|
assert_match "FooJobTest", output
assert_match "BarJobTest", output
assert_match "2 runs, 2 assertions, 0 failures", output
@@ -121,6 +120,7 @@ module ApplicationTests
end
def test_run_functionals
+ skip "we no longer have the concept of functional tests. Just different directories..."
create_test_file :mailers, 'foo_mailer'
create_test_file :controllers, 'bar_controller'
create_test_file :functional, 'baz_functional'
@@ -136,7 +136,7 @@ module ApplicationTests
def test_run_integration
create_test_file :integration, 'foo_integration'
create_test_file :models, 'foo'
- run_test_integration_command.tap do |output|
+ run_test_command("test/integration").tap do |output|
assert_match "FooIntegration", output
assert_match "1 runs, 1 assertions, 0 failures", output
end
@@ -166,7 +166,7 @@ module ApplicationTests
end
RUBY
- run_test_command('test/unit/chu_2_koi_test.rb test_rikka').tap do |output|
+ run_test_command('-n test_rikka test/unit/chu_2_koi_test.rb').tap do |output|
assert_match "Rikka", output
assert_no_match "Sanae", output
end
@@ -187,7 +187,7 @@ module ApplicationTests
end
RUBY
- run_test_command('test/unit/chu_2_koi_test.rb /rikka/').tap do |output|
+ run_test_command('-p rikka test/unit/chu_2_koi_test.rb').tap do |output|
assert_match "Rikka", output
assert_no_match "Sanae", output
end
@@ -195,18 +195,18 @@ module ApplicationTests
def test_load_fixtures_when_running_test_suites
create_model_with_fixture
- suites = [:models, :helpers, [:units, :unit], :controllers, :mailers,
- [:functionals, :functional], :integration]
+ suites = [:models, :helpers, :controllers, :mailers, :integration]
suites.each do |suite, directory|
directory ||= suite
create_fixture_test directory
- assert_match "3 users", run_task(["test:#{suite}"])
+ assert_match "3 users", run_test_command("test/#{suite}")
Dir.chdir(app_path) { FileUtils.rm_f "test/#{directory}" }
end
end
def test_run_with_model
+ skip "These feel a bit odd. Not sure we should keep supporting them."
create_model_with_fixture
create_fixture_test 'models', 'user'
assert_match "3 users", run_task(["test models/user"])
@@ -214,6 +214,7 @@ module ApplicationTests
end
def test_run_different_environment_using_env_var
+ skip "no longer possible. Running tests in a different environment should be explicit"
app_file 'test/unit/env_test.rb', <<-RUBY
require 'test_helper'
@@ -228,7 +229,7 @@ module ApplicationTests
assert_match "development", run_test_command('test/unit/env_test.rb')
end
- def test_run_different_environment_using_e_tag
+ def test_run_different_environment
env = "development"
app_file 'test/unit/env_test.rb', <<-RUBY
require 'test_helper'
@@ -240,7 +241,7 @@ module ApplicationTests
end
RUBY
- assert_match env, run_test_command("test/unit/env_test.rb RAILS_ENV=#{env}")
+ assert_match env, run_test_command("-e #{env} test/unit/env_test.rb")
end
def test_generated_scaffold_works_with_rails_test
@@ -249,17 +250,8 @@ module ApplicationTests
end
private
- def run_task(tasks)
- Dir.chdir(app_path) { `bundle exec rake #{tasks.join ' '}` }
- end
-
def run_test_command(arguments = 'test/unit/test_test.rb')
- run_task ['test', arguments]
- end
- %w{ mailers models helpers units controllers functionals integration jobs }.each do |type|
- define_method("run_test_#{type}_command") do
- run_task ["test:#{type}"]
- end
+ Dir.chdir(app_path) { `bin/rails t #{arguments}` }
end
def create_model_with_fixture
@@ -296,15 +288,6 @@ module ApplicationTests
app_file 'db/schema.rb', ''
end
- def redirect_stderr(target_stream)
- previous_stderr = STDERR.dup
- $stderr.reopen(target_stream)
- yield
- target_stream.rewind
- ensure
- $stderr = previous_stderr
- end
-
def create_test_file(path = :unit, name = 'test')
app_file "test/#{path}/#{name}_test.rb", <<-RUBY
require 'test_helper'
diff --git a/railties/test/application/test_test.rb b/railties/test/application/test_test.rb
index c724c867ec..61652e5052 100644
--- a/railties/test/application/test_test.rb
+++ b/railties/test/application/test_test.rb
@@ -65,6 +65,7 @@ module ApplicationTests
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
end
test "ruby schema migrations" do
@@ -193,6 +194,98 @@ module ApplicationTests
assert_successful_test_run('models/user_test.rb')
end
+ # TODO: would be nice if we could detect the schema change automatically.
+ # For now, the user has to synchronize the schema manually.
+ # This test-case serves as a reminder for this use-case.
+ test "manually synchronize test schema after rollback" do
+ output = script('generate model user name:string')
+ version = output.match(/(\d+)_create_users\.rb/)[1]
+
+ app_file 'test/models/user_test.rb', <<-RUBY
+ require 'test_helper'
+
+ class UserTest < ActiveSupport::TestCase
+ test "user" do
+ assert_equal ["id", "name"], User.columns_hash.keys
+ end
+ end
+ RUBY
+ app_file 'db/schema.rb', <<-RUBY
+ ActiveRecord::Schema.define(version: #{version}) do
+ create_table :users do |t|
+ t.string :name
+ end
+ end
+ RUBY
+
+ assert_successful_test_run "models/user_test.rb"
+
+ # Simulate `db:rollback` + edit of the migration file + `db:migrate`
+ app_file 'db/schema.rb', <<-RUBY
+ ActiveRecord::Schema.define(version: #{version}) do
+ create_table :users do |t|
+ t.string :name
+ t.integer :age
+ end
+ end
+ RUBY
+
+ assert_successful_test_run "models/user_test.rb"
+
+ Dir.chdir(app_path) { `bin/rake db:test:prepare` }
+
+ assert_unsuccessful_run "models/user_test.rb", <<-ASSERTION
+Expected: ["id", "name"]
+ Actual: ["id", "name", "age"]
+ ASSERTION
+ end
+
+ test "hooks for plugins" do
+ output = script('generate model user name:string')
+ version = output.match(/(\d+)_create_users\.rb/)[1]
+
+ app_file 'lib/tasks/hooks.rake', <<-RUBY
+ task :before_hook do
+ has_user_table = ActiveRecord::Base.connection.table_exists?('users')
+ puts "before: " + has_user_table.to_s
+ end
+
+ task :after_hook do
+ has_user_table = ActiveRecord::Base.connection.table_exists?('users')
+ puts "after: " + has_user_table.to_s
+ end
+
+ Rake::Task["db:test:prepare"].enhance [:before_hook] do
+ Rake::Task[:after_hook].invoke
+ end
+ RUBY
+ app_file 'test/models/user_test.rb', <<-RUBY
+ require 'test_helper'
+ class UserTest < ActiveSupport::TestCase
+ test "user" do
+ User.create! name: "Jon"
+ end
+ end
+ RUBY
+
+ # Simulate `db:migrate`
+ app_file 'db/schema.rb', <<-RUBY
+ ActiveRecord::Schema.define(version: #{version}) do
+ create_table :users do |t|
+ t.string :name
+ end
+ end
+ RUBY
+
+ output = assert_successful_test_run "models/user_test.rb"
+ assert_includes output, "before: false\nafter: true"
+
+ # running tests again won't trigger a schema update
+ output = assert_successful_test_run "models/user_test.rb"
+ assert_not_includes output, "before:"
+ assert_not_includes output, "after:"
+ end
+
private
def assert_unsuccessful_run(name, message)
result = run_test_file(name)
@@ -208,23 +301,7 @@ module ApplicationTests
end
def run_test_file(name, options = {})
- ruby '-Itest', "#{app_path}/test/#{name}", options
- end
-
- def ruby(*args)
- options = args.extract_options!
- env = options.fetch(:env, {})
- env["RUBYLIB"] = $:.join(':')
-
- Dir.chdir(app_path) do
- `#{env_string(env)} #{Gem.ruby} #{args.join(' ')} 2>&1`
- end
- end
-
- def env_string(variables)
- variables.map do |key, value|
- "#{key}='#{value}'"
- end.join " "
+ Dir.chdir(app_path) { `bin/rails test "#{app_path}/test/#{name}" 2>&1` }
end
end
end
diff --git a/railties/test/application/url_generation_test.rb b/railties/test/application/url_generation_test.rb
index efbc853d7b..894e18cb39 100644
--- a/railties/test/application/url_generation_test.rb
+++ b/railties/test/application/url_generation_test.rb
@@ -15,7 +15,7 @@ module ApplicationTests
require "action_view/railtie"
class MyApp < Rails::Application
- config.secret_key_base = "3b7cd727ee24e8444053437c36cc66c4"
+ secrets.secret_key_base = "3b7cd727ee24e8444053437c36cc66c4"
config.session_store :cookie_store, key: "_myapp_session"
config.active_support.deprecation = :log
config.eager_load = false
@@ -42,5 +42,18 @@ module ApplicationTests
get "/"
assert_equal "/", last_response.body
end
+
+ def test_routes_know_the_relative_root
+ boot_rails
+ require "rails"
+ require "action_controller/railtie"
+ require "action_view/railtie"
+
+ relative_url = '/hello'
+ ENV["RAILS_RELATIVE_URL_ROOT"] = relative_url
+ app = Class.new(Rails::Application)
+ assert_equal relative_url, app.routes.relative_url_root
+ ENV["RAILS_RELATIVE_URL_ROOT"] = nil
+ end
end
end
diff --git a/railties/test/code_statistics_calculator_test.rb b/railties/test/code_statistics_calculator_test.rb
index b3eabf5024..46445a001a 100644
--- a/railties/test/code_statistics_calculator_test.rb
+++ b/railties/test/code_statistics_calculator_test.rb
@@ -6,6 +6,43 @@ class CodeStatisticsCalculatorTest < ActiveSupport::TestCase
@code_statistics_calculator = CodeStatisticsCalculator.new
end
+ test 'calculate statistics using #add_by_file_path' do
+ code = <<-RUBY
+ def foo
+ puts 'foo'
+ # bar
+ end
+ RUBY
+
+ temp_file 'stats.rb', code do |path|
+ @code_statistics_calculator.add_by_file_path path
+
+ assert_equal 4, @code_statistics_calculator.lines
+ assert_equal 3, @code_statistics_calculator.code_lines
+ assert_equal 0, @code_statistics_calculator.classes
+ assert_equal 1, @code_statistics_calculator.methods
+ end
+ end
+
+ test 'count number of methods in MiniTest file' do
+ code = <<-RUBY
+ class FooTest < ActionController::TestCase
+ test 'expectation' do
+ assert true
+ end
+
+ def test_expectation
+ assert true
+ end
+ end
+ RUBY
+
+ temp_file 'foo_test.rb', code do |path|
+ @code_statistics_calculator.add_by_file_path path
+ assert_equal 2, @code_statistics_calculator.methods
+ end
+ end
+
test 'add statistics to another using #add' do
code_statistics_calculator_1 = CodeStatisticsCalculator.new(1, 2, 3, 4)
@code_statistics_calculator.add(code_statistics_calculator_1)
@@ -45,30 +82,6 @@ class CodeStatisticsCalculatorTest < ActiveSupport::TestCase
assert_equal 6, @code_statistics_calculator.methods
end
- test 'calculate statistics using #add_by_file_path' do
- tmp_path = File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'tmp'))
- FileUtils.mkdir_p(tmp_path)
-
- code = <<-'CODE'
- def foo
- puts 'foo'
- # bar
- end
- CODE
-
- file_path = "#{tmp_path}/stats.rb"
- File.open(file_path, 'w') { |f| f.write(code) }
-
- @code_statistics_calculator.add_by_file_path(file_path)
-
- assert_equal 4, @code_statistics_calculator.lines
- assert_equal 3, @code_statistics_calculator.code_lines
- assert_equal 0, @code_statistics_calculator.classes
- assert_equal 1, @code_statistics_calculator.methods
-
- FileUtils.rm_rf(tmp_path)
- end
-
test 'calculate number of Ruby methods' do
code = <<-'CODE'
def foo
@@ -285,4 +298,17 @@ class Animal
assert_equal 0, @code_statistics_calculator.classes
assert_equal 0, @code_statistics_calculator.methods
end
+
+ private
+ def temp_file(name, content)
+ dir = File.expand_path '../fixtures/tmp', __FILE__
+ path = "#{dir}/#{name}"
+
+ FileUtils.mkdir_p dir
+ File.write path, content
+
+ yield path
+ ensure
+ FileUtils.rm_rf path
+ end
end
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/console_test.rb b/railties/test/commands/console_test.rb
index 4aea3e980f..de0cf0ba9e 100644
--- a/railties/test/commands/console_test.rb
+++ b/railties/test/commands/console_test.rb
@@ -46,28 +46,6 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
assert_match(/Loading \w+ environment in sandbox \(Rails/, output)
end
- if RUBY_VERSION < '2.0.0'
- def test_debugger_option
- console = Rails::Console.new(app, parse_arguments(["--debugger"]))
- assert console.debugger?
- end
-
- def test_no_options_does_not_set_debugger_flag
- console = Rails::Console.new(app, parse_arguments([]))
- assert !console.debugger?
- end
-
- def test_start_with_debugger
- stubbed_console = Class.new(Rails::Console) do
- def require_debugger
- end
- end
-
- rails_console = stubbed_console.new(app, parse_arguments(["--debugger"]))
- silence_stream(STDOUT) { rails_console.start }
- end
- end
-
def test_console_with_environment
start ["-e production"]
assert_match(/\sproduction\s/, output)
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index 2206e389b5..c0b88089b3 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -129,7 +129,7 @@ class ActionsTest < Rails::Generators::TestCase
run_generator
action :environment do
- '# This wont be added'
+ _ = '# This wont be added'# assignment to silence parse-time warning "unused literal ignored"
'# This will be added'
end
@@ -219,17 +219,41 @@ class ActionsTest < Rails::Generators::TestCase
assert_file 'config/routes.rb', /#{Regexp.escape(route_command)}/
end
+ def test_route_should_add_data_with_an_new_line
+ run_generator
+ action :route, "root 'welcome#index'"
+ route_path = File.expand_path("config/routes.rb", destination_root)
+ content = File.read(route_path)
+
+ # Remove all of the comments and blank lines from the routes file
+ content.gsub!(/^ \#.*\n/, '')
+ content.gsub!(/^\n/, '')
+
+ File.open(route_path, "wb") { |file| file.write(content) }
+ assert_file "config/routes.rb", /\.routes\.draw do\n root 'welcome#index'\nend\n\z/
+
+ action :route, "resources :product_lines"
+
+ routes = <<-F
+Rails.application.routes.draw do
+ resources :product_lines
+ root 'welcome#index'
+end
+F
+ assert_file "config/routes.rb", routes
+ end
+
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.rdoc")
+ assert_match "application up and running", action(:readme, "README.md")
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.rdoc")
+ assert_no_match "application up and running", action(:readme, "README.md")
end
def test_log
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index b7cbe04003..282e8cc4f9 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -5,7 +5,7 @@ require 'mocha/setup' # FIXME: stop using mocha
DEFAULT_APP_FILES = %w(
.gitignore
- README.rdoc
+ README.md
Gemfile
Rakefile
config.ru
@@ -18,6 +18,7 @@ DEFAULT_APP_FILES = %w(
app/mailers
app/models
app/models/concerns
+ app/jobs
app/views/layouts
bin/bundle
bin/rails
@@ -33,6 +34,7 @@ DEFAULT_APP_FILES = %w(
log
test/test_helper.rb
test/fixtures
+ test/fixtures/files
test/controllers
test/models
test/helpers
@@ -66,6 +68,11 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file("app/assets/javascripts/application.js")
end
+ def test_application_job_file_present
+ run_generator
+ assert_file("app/jobs/application_job.rb")
+ end
+
def test_invalid_application_name_raises_an_error
content = capture(:stderr){ run_generator [File.join(destination_root, "43-things")] }
assert_equal "Invalid application name 43-things. Please give a name which does not start with numbers.\n", content
@@ -160,6 +167,38 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file("#{app_root}/config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :json/)
end
+ def test_rails_update_does_not_create_callback_terminator_initializer
+ app_root = File.join(destination_root, 'myapp')
+ run_generator [app_root]
+
+ FileUtils.rm("#{app_root}/config/initializers/callback_terminator.rb")
+
+ Rails.application.config.root = app_root
+ Rails.application.class.stubs(:name).returns("Myapp")
+ Rails.application.stubs(:is_a?).returns(Rails::Application)
+
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_no_file "#{app_root}/config/initializers/callback_terminator.rb"
+ end
+
+ def test_rails_update_does_not_remove_callback_terminator_initializer_if_already_present
+ app_root = File.join(destination_root, 'myapp')
+ run_generator [app_root]
+
+ FileUtils.touch("#{app_root}/config/initializers/callback_terminator.rb")
+
+ Rails.application.config.root = app_root
+ Rails.application.class.stubs(:name).returns("Myapp")
+ Rails.application.stubs(:is_a?).returns(Rails::Application)
+
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_file "#{app_root}/config/initializers/callback_terminator.rb"
+ end
+
def test_rails_update_set_the_cookie_serializer_to_marchal_if_it_is_not_already_configured
app_root = File.join(destination_root, 'myapp')
run_generator [app_root]
@@ -176,6 +215,38 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file("#{app_root}/config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :marshal/)
end
+ def test_rails_update_does_not_create_active_record_belongs_to_required_by_default
+ app_root = File.join(destination_root, 'myapp')
+ run_generator [app_root]
+
+ FileUtils.rm("#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb")
+
+ Rails.application.config.root = app_root
+ Rails.application.class.stubs(:name).returns("Myapp")
+ Rails.application.stubs(:is_a?).returns(Rails::Application)
+
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_no_file "#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb"
+ end
+
+ def test_rails_update_does_not_remove_active_record_belongs_to_required_by_default_if_already_present
+ app_root = File.join(destination_root, 'myapp')
+ run_generator [app_root]
+
+ FileUtils.touch("#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb")
+
+ Rails.application.config.root = app_root
+ Rails.application.class.stubs(:name).returns("Myapp")
+ Rails.application.stubs(:is_a?).returns(Rails::Application)
+
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_file "#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb"
+ end
+
def test_application_names_are_not_singularized
run_generator [File.join(destination_root, "hats")]
assert_file "hats/config/environment.rb", /Rails\.application\.initialize!/
@@ -259,15 +330,44 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_generator_without_skips
+ run_generator
+ assert_file "config/application.rb", /\s+require\s+["']rails\/all["']/
+ assert_file "config/environments/development.rb" do |content|
+ assert_match(/config\.action_mailer\.raise_delivery_errors = false/, content)
+ end
+ assert_file "config/environments/test.rb" do |content|
+ assert_match(/config\.action_mailer\.delivery_method = :test/, content)
+ end
+ assert_file "config/environments/production.rb" do |content|
+ assert_match(/# config\.action_mailer\.raise_delivery_errors = false/, content)
+ end
+ end
+
def test_generator_if_skip_active_record_is_given
run_generator [destination_root, "--skip-active-record"]
assert_no_file "config/database.yml"
+ assert_no_file "config/initializers/active_record_belongs_to_required_by_default.rb"
assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/
assert_file "test/test_helper.rb" do |helper_content|
assert_no_match(/fixtures :all/, helper_content)
end
end
+ def test_generator_if_skip_action_mailer_is_given
+ run_generator [destination_root, "--skip-action-mailer"]
+ assert_file "config/application.rb", /#\s+require\s+["']action_mailer\/railtie["']/
+ assert_file "config/environments/development.rb" do |content|
+ assert_no_match(/config\.action_mailer/, content)
+ end
+ assert_file "config/environments/test.rb" do |content|
+ assert_no_match(/config\.action_mailer/, content)
+ end
+ assert_file "config/environments/production.rb" do |content|
+ assert_no_match(/config\.action_mailer/, content)
+ end
+ end
+
def test_generator_if_skip_sprockets_is_given
run_generator [destination_root, "--skip-sprockets"]
assert_no_file "config/initializers/assets.rb"
@@ -340,23 +440,15 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_inclusion_of_a_debugger
run_generator
- if defined?(JRUBY_VERSION)
+ if defined?(JRUBY_VERSION) || RUBY_ENGINE == "rbx"
assert_file "Gemfile" do |content|
assert_no_match(/byebug/, content)
- assert_no_match(/debugger/, content)
end
- elsif RUBY_VERSION < '2.0.0'
- assert_gem 'debugger'
else
assert_gem 'byebug'
end
end
- def test_inclusion_of_doc
- run_generator
- assert_file 'Gemfile', /gem 'sdoc',\s+'~> 0.4.0',\s+group: :doc/
- end
-
def test_template_from_dir_pwd
FileUtils.cd(Rails.root)
assert_match(/It works from file!/, run_generator([destination_root, "-m", "lib/template.rb"]))
@@ -381,13 +473,13 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file 'lib/test_file.rb', 'heres test data'
end
- def test_test_unit_is_removed_from_frameworks_if_skip_test_unit_is_given
- run_generator [destination_root, "--skip-test-unit"]
+ def test_tests_are_removed_from_frameworks_if_skip_test_is_given
+ run_generator [destination_root, "--skip-test"]
assert_file "config/application.rb", /#\s+require\s+["']rails\/test_unit\/railtie["']/
end
- def test_no_active_record_or_test_unit_if_skips_given
- run_generator [destination_root, "--skip-test-unit", "--skip-active-record"]
+ def test_no_active_record_or_tests_if_skips_given
+ run_generator [destination_root, "--skip-test", "--skip-active-record"]
assert_file "config/application.rb", /#\s+require\s+["']rails\/test_unit\/railtie["']/
assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/
assert_file "config/application.rb", /\s+require\s+["']active_job\/railtie["']/
@@ -420,6 +512,24 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_gem 'web-console'
end
+ def test_web_console_with_dev_option
+ run_generator [destination_root, "--dev"]
+
+ assert_file "Gemfile" do |content|
+ assert_match(/gem 'web-console',\s+github: "rails\/web-console"/, content)
+ assert_no_match(/gem 'web-console', '~> 2.0'/, content)
+ end
+ end
+
+ def test_web_console_with_edge_option
+ run_generator [destination_root, "--edge"]
+
+ assert_file "Gemfile" do |content|
+ assert_match(/gem 'web-console',\s+github: "rails\/web-console"/, content)
+ assert_no_match(/gem 'web-console', '~> 2.0'/, content)
+ end
+ end
+
def test_spring
run_generator
assert_gem 'spring'
diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb
index 6cc91f166b..62ca0ecb4b 100644
--- a/railties/test/generators/generators_test_helper.rb
+++ b/railties/test/generators/generators_test_helper.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'active_support/core_ext/module/remove_method'
+require 'active_support/testing/stream'
require 'rails/generators'
require 'rails/generators/test_case'
@@ -23,6 +24,8 @@ require 'action_dispatch'
require 'action_view'
module GeneratorsTestHelper
+ include ActiveSupport::Testing::Stream
+
def self.included(base)
base.class_eval do
destination File.join(Rails.root, "tmp")
@@ -42,11 +45,4 @@ module GeneratorsTestHelper
FileUtils.cp routes, destination
end
- def quietly
- silence_stream(STDOUT) do
- silence_stream(STDERR) do
- yield
- end
- end
- end
end
diff --git a/railties/test/generators/job_generator_test.rb b/railties/test/generators/job_generator_test.rb
new file mode 100644
index 0000000000..7fd8f2062f
--- /dev/null
+++ b/railties/test/generators/job_generator_test.rb
@@ -0,0 +1,29 @@
+require 'generators/generators_test_helper'
+require 'rails/generators/job/job_generator'
+
+class JobGeneratorTest < Rails::Generators::TestCase
+ include GeneratorsTestHelper
+
+ def test_job_skeleton_is_created
+ run_generator ["refresh_counters"]
+ assert_file "app/jobs/refresh_counters_job.rb" do |job|
+ assert_match(/class RefreshCountersJob < ApplicationJob/, job)
+ end
+ end
+
+ def test_job_queue_param
+ run_generator ["refresh_counters", "--queue", "important"]
+ assert_file "app/jobs/refresh_counters_job.rb" do |job|
+ assert_match(/class RefreshCountersJob < ApplicationJob/, job)
+ assert_match(/queue_as :important/, job)
+ end
+ end
+
+ def test_job_namespace
+ run_generator ["admin/refresh_counters", "--queue", "admin"]
+ assert_file "app/jobs/admin/refresh_counters_job.rb" do |job|
+ assert_match(/class Admin::RefreshCountersJob < ApplicationJob/, job)
+ assert_match(/queue_as :admin/, job)
+ end
+ end
+end
diff --git a/railties/test/generators/mailer_generator_test.rb b/railties/test/generators/mailer_generator_test.rb
index 25649881eb..f01e8cd2d9 100644
--- a/railties/test/generators/mailer_generator_test.rb
+++ b/railties/test/generators/mailer_generator_test.rb
@@ -7,94 +7,114 @@ class MailerGeneratorTest < Rails::Generators::TestCase
def test_mailer_skeleton_is_created
run_generator
- assert_file "app/mailers/notifier.rb" do |mailer|
- assert_match(/class Notifier < ActionMailer::Base/, mailer)
+ assert_file "app/mailers/notifier_mailer.rb" do |mailer|
+ assert_match(/class NotifierMailer < ApplicationMailer/, mailer)
+ assert_no_match(/default from: "from@example.com"/, mailer)
+ assert_no_match(/layout :mailer_notifier/, mailer)
+ end
+ end
+
+ def test_application_mailer_skeleton_is_created
+ run_generator
+ assert_file "app/mailers/application_mailer.rb" do |mailer|
+ assert_match(/class ApplicationMailer < ActionMailer::Base/, mailer)
assert_match(/default from: "from@example.com"/, mailer)
+ assert_match(/layout 'mailer'/, mailer)
end
end
def test_mailer_with_i18n_helper
run_generator
- assert_file "app/mailers/notifier.rb" do |mailer|
- assert_match(/en\.notifier\.foo\.subject/, mailer)
- assert_match(/en\.notifier\.bar\.subject/, mailer)
+ assert_file "app/mailers/notifier_mailer.rb" do |mailer|
+ assert_match(/en\.notifier_mailer\.foo\.subject/, mailer)
+ assert_match(/en\.notifier_mailer\.bar\.subject/, mailer)
end
end
def test_check_class_collision
- Object.send :const_set, :Notifier, Class.new
+ Object.send :const_set, :NotifierMailer, Class.new
content = capture(:stderr){ run_generator }
- assert_match(/The name 'Notifier' is either already used in your application or reserved/, content)
+ assert_match(/The name 'NotifierMailer' is either already used in your application or reserved/, content)
ensure
- Object.send :remove_const, :Notifier
+ Object.send :remove_const, :NotifierMailer
end
def test_invokes_default_test_framework
run_generator
- assert_file "test/mailers/notifier_test.rb" do |test|
- assert_match(/class NotifierTest < ActionMailer::TestCase/, test)
+ assert_file "test/mailers/notifier_mailer_test.rb" do |test|
+ assert_match(/class NotifierMailerTest < ActionMailer::TestCase/, test)
assert_match(/test "foo"/, test)
assert_match(/test "bar"/, test)
end
- assert_file "test/mailers/previews/notifier_preview.rb" do |preview|
- assert_match(/\# Preview all emails at http:\/\/localhost\:3000\/rails\/mailers\/notifier/, preview)
- assert_match(/class NotifierPreview < ActionMailer::Preview/, preview)
- assert_match(/\# Preview this email at http:\/\/localhost\:3000\/rails\/mailers\/notifier\/foo/, preview)
+ assert_file "test/mailers/previews/notifier_mailer_preview.rb" do |preview|
+ assert_match(/\# Preview all emails at http:\/\/localhost\:3000\/rails\/mailers\/notifier_mailer/, preview)
+ assert_match(/class NotifierMailerPreview < ActionMailer::Preview/, preview)
+ assert_match(/\# Preview this email at http:\/\/localhost\:3000\/rails\/mailers\/notifier_mailer\/foo/, preview)
assert_instance_method :foo, preview do |foo|
- assert_match(/Notifier.foo/, foo)
+ assert_match(/NotifierMailer.foo/, foo)
end
- assert_match(/\# Preview this email at http:\/\/localhost\:3000\/rails\/mailers\/notifier\/bar/, preview)
+ assert_match(/\# Preview this email at http:\/\/localhost\:3000\/rails\/mailers\/notifier_mailer\/bar/, preview)
assert_instance_method :bar, preview do |bar|
- assert_match(/Notifier.bar/, bar)
+ assert_match(/NotifierMailer.bar/, bar)
end
end
end
def test_check_test_class_collision
- Object.send :const_set, :NotifierTest, Class.new
+ Object.send :const_set, :NotifierMailerTest, Class.new
content = capture(:stderr){ run_generator }
- assert_match(/The name 'NotifierTest' is either already used in your application or reserved/, content)
+ assert_match(/The name 'NotifierMailerTest' is either already used in your application or reserved/, content)
ensure
- Object.send :remove_const, :NotifierTest
+ Object.send :remove_const, :NotifierMailerTest
end
def test_check_preview_class_collision
- Object.send :const_set, :NotifierPreview, Class.new
+ Object.send :const_set, :NotifierMailerPreview, Class.new
content = capture(:stderr){ run_generator }
- assert_match(/The name 'NotifierPreview' is either already used in your application or reserved/, content)
+ assert_match(/The name 'NotifierMailerPreview' is either already used in your application or reserved/, content)
ensure
- Object.send :remove_const, :NotifierPreview
+ Object.send :remove_const, :NotifierMailerPreview
end
def test_invokes_default_text_template_engine
run_generator
- assert_file "app/views/notifier/foo.text.erb" do |view|
- assert_match(%r(\sapp/views/notifier/foo\.text\.erb), view)
+ assert_file "app/views/notifier_mailer/foo.text.erb" do |view|
+ assert_match(%r(\sapp/views/notifier_mailer/foo\.text\.erb), view)
assert_match(/<%= @greeting %>/, view)
end
- assert_file "app/views/notifier/bar.text.erb" do |view|
- assert_match(%r(\sapp/views/notifier/bar\.text\.erb), view)
+ assert_file "app/views/notifier_mailer/bar.text.erb" do |view|
+ assert_match(%r(\sapp/views/notifier_mailer/bar\.text\.erb), view)
assert_match(/<%= @greeting %>/, view)
end
+
+ assert_file "app/views/layouts/mailer.text.erb" do |view|
+ assert_match(/<%= yield %>/, view)
+ end
end
def test_invokes_default_html_template_engine
run_generator
- assert_file "app/views/notifier/foo.html.erb" do |view|
- assert_match(%r(\sapp/views/notifier/foo\.html\.erb), view)
+ assert_file "app/views/notifier_mailer/foo.html.erb" do |view|
+ assert_match(%r(\sapp/views/notifier_mailer/foo\.html\.erb), view)
assert_match(/<%= @greeting %>/, view)
end
- assert_file "app/views/notifier/bar.html.erb" do |view|
- assert_match(%r(\sapp/views/notifier/bar\.html\.erb), view)
+ assert_file "app/views/notifier_mailer/bar.html.erb" do |view|
+ assert_match(%r(\sapp/views/notifier_mailer/bar\.html\.erb), view)
assert_match(/<%= @greeting %>/, view)
end
+
+ assert_file "app/views/layouts/mailer.html.erb" do |view|
+ assert_match(%r{<html>\n <body>\n <%= yield %>\n </body>\n</html>}, view)
+ end
end
def test_invokes_default_template_engine_even_with_no_action
run_generator ["notifier"]
- assert_file "app/views/notifier"
+ assert_file "app/views/notifier_mailer"
+ assert_file "app/views/layouts/mailer.text.erb"
+ assert_file "app/views/layouts/mailer.html.erb"
end
def test_logs_if_the_template_engine_cannot_be_found
@@ -104,23 +124,23 @@ class MailerGeneratorTest < Rails::Generators::TestCase
def test_mailer_with_namedspaced_mailer
run_generator ["Farm::Animal", "moos"]
- assert_file "app/mailers/farm/animal.rb" do |mailer|
- assert_match(/class Farm::Animal < ActionMailer::Base/, mailer)
- assert_match(/en\.farm\.animal\.moos\.subject/, mailer)
+ assert_file "app/mailers/farm/animal_mailer.rb" do |mailer|
+ assert_match(/class Farm::AnimalMailer < ApplicationMailer/, mailer)
+ assert_match(/en\.farm\.animal_mailer\.moos\.subject/, mailer)
end
- assert_file "test/mailers/previews/farm/animal_preview.rb" do |preview|
- assert_match(/\# Preview all emails at http:\/\/localhost\:3000\/rails\/mailers\/farm\/animal/, preview)
- assert_match(/class Farm::AnimalPreview < ActionMailer::Preview/, preview)
- assert_match(/\# Preview this email at http:\/\/localhost\:3000\/rails\/mailers\/farm\/animal\/moos/, preview)
+ assert_file "test/mailers/previews/farm/animal_mailer_preview.rb" do |preview|
+ assert_match(/\# Preview all emails at http:\/\/localhost\:3000\/rails\/mailers\/farm\/animal_mailer/, preview)
+ assert_match(/class Farm::AnimalMailerPreview < ActionMailer::Preview/, preview)
+ assert_match(/\# Preview this email at http:\/\/localhost\:3000\/rails\/mailers\/farm\/animal_mailer\/moos/, preview)
end
- assert_file "app/views/farm/animal/moos.text.erb"
- assert_file "app/views/farm/animal/moos.html.erb"
+ assert_file "app/views/farm/animal_mailer/moos.text.erb"
+ assert_file "app/views/farm/animal_mailer/moos.html.erb"
end
def test_actions_are_turned_into_methods
run_generator
- assert_file "app/mailers/notifier.rb" do |mailer|
+ assert_file "app/mailers/notifier_mailer.rb" do |mailer|
assert_instance_method :foo, mailer do |foo|
assert_match(/mail to: "to@example.org"/, foo)
assert_match(/@greeting = "Hi"/, foo)
@@ -132,4 +152,35 @@ class MailerGeneratorTest < Rails::Generators::TestCase
end
end
end
+
+ def test_mailer_on_revoke
+ run_generator
+ run_generator ["notifier"], behavior: :revoke
+
+ assert_no_file "app/mailers/notifier.rb"
+ assert_no_file "app/views/notifier/foo.text.erb"
+ assert_no_file "app/views/notifier/bar.text.erb"
+ assert_no_file "app/views/notifier/foo.html.erb"
+ assert_no_file "app/views/notifier/bar.html.erb"
+
+ assert_file "app/mailers/application_mailer.rb"
+ assert_file "app/views/layouts/mailer.text.erb"
+ assert_file "app/views/layouts/mailer.html.erb"
+ end
+
+ def test_mailer_suffix_is_not_duplicated
+ run_generator ["notifier_mailer"]
+
+ assert_no_file "app/mailers/notifier_mailer_mailer.rb"
+ assert_file "app/mailers/notifier_mailer.rb"
+
+ assert_no_file "app/views/notifier_mailer_mailer/"
+ assert_file "app/views/notifier_mailer/"
+
+ assert_no_file "test/mailers/notifier_mailer_mailer_test.rb"
+ assert_file "test/mailers/notifier_mailer_test.rb"
+
+ assert_no_file "test/mailers/previews/notifier_mailer_mailer_preview.rb"
+ assert_file "test/mailers/previews/notifier_mailer_preview.rb"
+ end
end
diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb
index 72f5fe29ca..57bc220558 100644
--- a/railties/test/generators/migration_generator_test.rb
+++ b/railties/test/generators/migration_generator_test.rb
@@ -85,6 +85,19 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_remove_migration_with_references_removes_foreign_keys
+ migration = "remove_references_from_books"
+ run_generator [migration, "author:belongs_to", "distributor:references{polymorphic}"]
+
+ assert_migration "db/migrate/#{migration}.rb" do |content|
+ assert_method :change, content do |change|
+ assert_match(/remove_reference :books, :author,.*\sforeign_key: true/, change)
+ assert_match(/remove_reference :books, :distributor/, change) # sanity check
+ assert_no_match(/remove_reference :books, :distributor,.*\sforeign_key: true/, change)
+ end
+ end
+ end
+
def test_add_migration_with_attributes_and_indices
migration = "add_title_with_index_and_body_to_posts"
run_generator [migration, "title:string:index", "body:text", "user_id:integer:uniq"]
@@ -171,6 +184,19 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_add_migration_with_references_adds_foreign_keys
+ migration = "add_references_to_books"
+ run_generator [migration, "author:belongs_to", "distributor:references{polymorphic}"]
+
+ assert_migration "db/migrate/#{migration}.rb" do |content|
+ assert_method :change, content do |change|
+ assert_match(/add_reference :books, :author,.*\sforeign_key: true/, change)
+ assert_match(/add_reference :books, :distributor/, change) # sanity check
+ assert_no_match(/add_reference :books, :distributor,.*\sforeign_key: true/, change)
+ end
+ end
+ end
+
def test_create_join_table_migration
migration = "add_media_join_table"
run_generator [migration, "artist_id", "musics:uniq"]
@@ -205,7 +231,7 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
end
end
end
-
+
def test_properly_identifies_usage_file
assert generator_class.send(:usage_path)
end
@@ -250,6 +276,30 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_create_table_migration_with_token_option
+ run_generator ["create_users", "token:token", "auth_token:token"]
+ assert_migration "db/migrate/create_users.rb" do |content|
+ assert_method :change, content do |change|
+ assert_match(/create_table :users/, change)
+ assert_match(/ t\.string :token/, change)
+ assert_match(/ t\.string :auth_token/, change)
+ assert_match(/add_index :users, :token, unique: true/, change)
+ assert_match(/add_index :users, :auth_token, unique: true/, change)
+ end
+ end
+ end
+
+ def test_add_migration_with_token_option
+ migration = "add_token_to_users"
+ run_generator [migration, "auth_token:token"]
+ assert_migration "db/migrate/#{migration}.rb" do |content|
+ assert_method :change, content do |change|
+ assert_match(/add_column :users, :auth_token, :string/, change)
+ assert_match(/add_index :users, :auth_token, unique: true/, change)
+ end
+ end
+ end
+
private
def with_singular_table_name
diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb
index c78597c81b..abd3ff50a4 100644
--- a/railties/test/generators/model_generator_test.rb
+++ b/railties/test/generators/model_generator_test.rb
@@ -223,7 +223,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
def test_migration_with_timestamps
run_generator
- assert_migration "db/migrate/create_accounts.rb", /t.timestamps null: false/
+ assert_migration "db/migrate/create_accounts.rb", /t.timestamps/
end
def test_migration_timestamps_are_skipped
@@ -287,18 +287,18 @@ class ModelGeneratorTest < Rails::Generators::TestCase
def test_fixtures_use_the_references_ids
run_generator ["LineItem", "product:references", "cart:belongs_to"]
- assert_file "test/fixtures/line_items.yml", /product_id: \n cart_id: /
+ assert_file "test/fixtures/line_items.yml", /product: \n cart: /
assert_generated_fixture("test/fixtures/line_items.yml",
- {"one"=>{"product_id"=>nil, "cart_id"=>nil}, "two"=>{"product_id"=>nil, "cart_id"=>nil}})
+ {"one"=>{"product"=>nil, "cart"=>nil}, "two"=>{"product"=>nil, "cart"=>nil}})
end
def test_fixtures_use_the_references_ids_and_type
run_generator ["LineItem", "product:references{polymorphic}", "cart:belongs_to"]
- assert_file "test/fixtures/line_items.yml", /product_id: \n product_type: Product\n cart_id: /
+ assert_file "test/fixtures/line_items.yml", /product: \n product_type: Product\n cart: /
assert_generated_fixture("test/fixtures/line_items.yml",
- {"one"=>{"product_id"=>nil, "product_type"=>"Product", "cart_id"=>nil},
- "two"=>{"product_id"=>nil, "product_type"=>"Product", "cart_id"=>nil}})
+ {"one"=>{"product"=>nil, "product_type"=>"Product", "cart"=>nil},
+ "two"=>{"product"=>nil, "product_type"=>"Product", "cart"=>nil}})
end
def test_fixtures_respect_reserved_yml_keywords
@@ -319,6 +319,16 @@ class ModelGeneratorTest < Rails::Generators::TestCase
assert_no_file "test/fixtures/accounts.yml"
end
+ def test_fixture_without_pluralization
+ original_pluralize_table_name = ActiveRecord::Base.pluralize_table_names
+ ActiveRecord::Base.pluralize_table_names = false
+ run_generator
+ assert_generated_fixture("test/fixtures/account.yml",
+ {"one"=>{"name"=>"MyString", "age"=>1}, "two"=>{"name"=>"MyString", "age"=>1}})
+ ensure
+ ActiveRecord::Base.pluralize_table_names = original_pluralize_table_name
+ end
+
def test_check_class_collision
content = capture(:stderr){ run_generator ["object"] }
assert_match(/The name 'Object' is either already used in your application or reserved/, content)
@@ -407,6 +417,48 @@ class ModelGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_foreign_key_is_not_added_for_non_references
+ run_generator ["account", "supplier:string"]
+
+ assert_migration "db/migrate/create_accounts.rb" do |m|
+ assert_method :change, m do |up|
+ assert_no_match(/foreign_key/, up)
+ end
+ end
+ end
+
+ def test_foreign_key_is_added_for_references
+ run_generator ["account", "supplier:belongs_to", "user:references"]
+
+ assert_migration "db/migrate/create_accounts.rb" do |m|
+ assert_method :change, m do |up|
+ assert_match(/t\.belongs_to :supplier,.*\sforeign_key: true/, up)
+ assert_match(/t\.references :user,.*\sforeign_key: true/, up)
+ end
+ end
+ end
+
+ def test_foreign_key_is_skipped_for_polymorphic_references
+ run_generator ["account", "supplier:belongs_to{polymorphic}"]
+
+ assert_migration "db/migrate/create_accounts.rb" do |m|
+ assert_method :change, m do |up|
+ assert_no_match(/foreign_key/, up)
+ end
+ end
+ end
+
+ def test_token_option_adds_has_secure_token
+ run_generator ["user", "token:token", "auth_token:token"]
+ expected_file = <<-FILE.strip_heredoc
+ class User < ActiveRecord::Base
+ has_secure_token
+ has_secure_token :auth_token
+ end
+ FILE
+ assert_file "app/models/user.rb", expected_file
+ end
+
private
def assert_generated_fixture(path, parsed_contents)
fixture_file = File.new File.expand_path(path, destination_root)
diff --git a/railties/test/generators/named_base_test.rb b/railties/test/generators/named_base_test.rb
index 4199e00b0d..18a26fde05 100644
--- a/railties/test/generators/named_base_test.rb
+++ b/railties/test/generators/named_base_test.rb
@@ -2,16 +2,6 @@ require 'generators/generators_test_helper'
require 'rails/generators/rails/scaffold_controller/scaffold_controller_generator'
require 'mocha/setup' # FIXME: stop using mocha
-# Mock out what we need from AR::Base.
-module ActiveRecord
- class Base
- class << self
- attr_accessor :pluralize_table_names
- end
- self.pluralize_table_names = true
- end
-end
-
class NamedBaseTest < Rails::Generators::TestCase
include GeneratorsTestHelper
tests Rails::Generators::ScaffoldControllerGenerator
@@ -59,11 +49,13 @@ class NamedBaseTest < Rails::Generators::TestCase
end
def test_named_generator_attributes_without_pluralized
+ original_pluralize_table_names = ActiveRecord::Base.pluralize_table_names
ActiveRecord::Base.pluralize_table_names = false
+
g = generator ['admin/foo']
assert_name g, 'admin_foo', :table_name
ensure
- ActiveRecord::Base.pluralize_table_names = true
+ ActiveRecord::Base.pluralize_table_names = original_pluralize_table_names
end
def test_scaffold_plural_names
diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb
index 7eeb084eab..e839b67960 100644
--- a/railties/test/generators/namespaced_generators_test.rb
+++ b/railties/test/generators/namespaced_generators_test.rb
@@ -146,26 +146,26 @@ class NamespacedMailerGeneratorTest < NamespacedGeneratorTestCase
def test_mailer_skeleton_is_created
run_generator
- assert_file "app/mailers/test_app/notifier.rb" do |mailer|
+ assert_file "app/mailers/test_app/notifier_mailer.rb" do |mailer|
assert_match(/module TestApp/, mailer)
- assert_match(/class Notifier < ActionMailer::Base/, mailer)
- assert_match(/default from: "from@example.com"/, mailer)
+ assert_match(/class NotifierMailer < ApplicationMailer/, mailer)
+ assert_no_match(/default from: "from@example.com"/, mailer)
end
end
def test_mailer_with_i18n_helper
run_generator
- assert_file "app/mailers/test_app/notifier.rb" do |mailer|
- assert_match(/en\.notifier\.foo\.subject/, mailer)
- assert_match(/en\.notifier\.bar\.subject/, mailer)
+ assert_file "app/mailers/test_app/notifier_mailer.rb" do |mailer|
+ assert_match(/en\.notifier_mailer\.foo\.subject/, mailer)
+ assert_match(/en\.notifier_mailer\.bar\.subject/, mailer)
end
end
def test_invokes_default_test_framework
run_generator
- assert_file "test/mailers/test_app/notifier_test.rb" do |test|
+ assert_file "test/mailers/test_app/notifier_mailer_test.rb" do |test|
assert_match(/module TestApp/, test)
- assert_match(/class NotifierTest < ActionMailer::TestCase/, test)
+ assert_match(/class NotifierMailerTest < ActionMailer::TestCase/, test)
assert_match(/test "foo"/, test)
assert_match(/test "bar"/, test)
end
@@ -173,20 +173,20 @@ class NamespacedMailerGeneratorTest < NamespacedGeneratorTestCase
def test_invokes_default_template_engine
run_generator
- assert_file "app/views/test_app/notifier/foo.text.erb" do |view|
- assert_match(%r(app/views/test_app/notifier/foo\.text\.erb), view)
+ assert_file "app/views/test_app/notifier_mailer/foo.text.erb" do |view|
+ assert_match(%r(app/views/test_app/notifier_mailer/foo\.text\.erb), view)
assert_match(/<%= @greeting %>/, view)
end
- assert_file "app/views/test_app/notifier/bar.text.erb" do |view|
- assert_match(%r(app/views/test_app/notifier/bar\.text\.erb), view)
+ assert_file "app/views/test_app/notifier_mailer/bar.text.erb" do |view|
+ assert_match(%r(app/views/test_app/notifier_mailer/bar\.text\.erb), view)
assert_match(/<%= @greeting %>/, view)
end
end
def test_invokes_default_template_engine_even_with_no_action
run_generator ["notifier"]
- assert_file "app/views/test_app/notifier"
+ assert_file "app/views/test_app/notifier_mailer"
end
end
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index 4329c6e1a4..a0f244da28 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -28,11 +28,11 @@ class PluginGeneratorTest < Rails::Generators::TestCase
include SharedGeneratorTests
def test_invalid_plugin_name_raises_an_error
- content = capture(:stderr){ run_generator [File.join(destination_root, "things-43")] }
- assert_equal "Invalid plugin name things-43. Please give a name which use only alphabetic or numeric or \"_\" characters.\n", content
+ content = capture(:stderr){ run_generator [File.join(destination_root, "my_plugin-31fr-extension")] }
+ assert_equal "Invalid plugin name my_plugin-31fr-extension. Please give a name which does not contain a namespace starting with numeric characters.\n", content
content = capture(:stderr){ run_generator [File.join(destination_root, "things4.3")] }
- assert_equal "Invalid plugin name things4.3. Please give a name which use only alphabetic or numeric or \"_\" characters.\n", content
+ assert_equal "Invalid plugin name things4.3. Please give a name which uses only alphabetic, numeric, \"_\" or \"-\" characters.\n", content
content = capture(:stderr){ run_generator [File.join(destination_root, "43things")] }
assert_equal "Invalid plugin name 43things. Please give a name which does not start with numbers.\n", content
@@ -44,7 +44,14 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_equal "Invalid plugin name Digest, constant Digest is already in use. Please choose another plugin name.\n", content
end
- def test_camelcase_plugin_name_underscores_filenames
+ def test_correct_file_in_lib_folder_of_hyphenated_plugin_name
+ run_generator [File.join(destination_root, "hyphenated-name")]
+ assert_no_file "hyphenated-name/lib/hyphenated-name.rb"
+ assert_no_file "hyphenated-name/lib/hyphenated_name.rb"
+ assert_file "hyphenated-name/lib/hyphenated/name.rb", /module Hyphenated\n module Name\n # Your code goes here...\n end\nend/
+ end
+
+ def test_correct_file_in_lib_folder_of_camelcase_plugin_name
run_generator [File.join(destination_root, "CamelCasedName")]
assert_no_file "CamelCasedName/lib/CamelCasedName.rb"
assert_file "CamelCasedName/lib/camel_cased_name.rb", /module CamelCasedName/
@@ -57,6 +64,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "test/test_helper.rb" do |content|
assert_match(/require.+test\/dummy\/config\/environment/, content)
assert_match(/ActiveRecord::Migrator\.migrations_paths.+test\/dummy\/db\/migrate/, content)
+ assert_match(/Minitest\.backtrace_filter = Minitest::BacktraceFilter\.new/, content)
end
assert_file "test/bukkits_test.rb", /assert_kind_of Module, Bukkits/
end
@@ -70,13 +78,10 @@ class PluginGeneratorTest < Rails::Generators::TestCase
def test_inclusion_of_a_debugger
run_generator [destination_root, '--full']
- if defined?(JRUBY_VERSION)
+ if defined?(JRUBY_VERSION) || RUBY_ENGINE == "rbx"
assert_file "Gemfile" do |content|
assert_no_match(/byebug/, content)
- assert_no_match(/debugger/, content)
end
- elsif RUBY_VERSION < '2.0.0'
- assert_file "Gemfile", /# gem 'debugger'/
else
assert_file "Gemfile", /# gem 'byebug'/
end
@@ -114,7 +119,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
def test_ensure_that_test_dummy_can_be_generated_from_a_template
FileUtils.cd(Rails.root)
- run_generator([destination_root, "-m", "lib/create_test_dummy_template.rb", "--skip-test-unit"])
+ run_generator([destination_root, "-m", "lib/create_test_dummy_template.rb", "--skip-test"])
assert_file "spec/dummy"
assert_no_file "test"
end
@@ -139,6 +144,20 @@ class PluginGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_app_generator_without_skips
+ run_generator
+ assert_file "test/dummy/config/application.rb", /\s+require\s+["']rails\/all["']/
+ assert_file "test/dummy/config/environments/development.rb" do |content|
+ assert_match(/config\.action_mailer\.raise_delivery_errors = false/, content)
+ end
+ assert_file "test/dummy/config/environments/test.rb" do |content|
+ assert_match(/config\.action_mailer\.delivery_method = :test/, content)
+ end
+ assert_file "test/dummy/config/environments/production.rb" do |content|
+ assert_match(/# config\.action_mailer\.raise_delivery_errors = false/, content)
+ end
+ end
+
def test_active_record_is_removed_from_frameworks_if_skip_active_record_is_given
run_generator [destination_root, "--skip-active-record"]
assert_file "test/dummy/config/application.rb", /#\s+require\s+["']active_record\/railtie["']/
@@ -152,6 +171,20 @@ class PluginGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_action_mailer_is_removed_from_frameworks_if_skip_action_mailer_is_given
+ run_generator [destination_root, "--skip-action-mailer"]
+ assert_file "test/dummy/config/application.rb", /#\s+require\s+["']action_mailer\/railtie["']/
+ assert_file "test/dummy/config/environments/development.rb" do |content|
+ assert_no_match(/config\.action_mailer/, content)
+ end
+ assert_file "test/dummy/config/environments/test.rb" do |content|
+ assert_no_match(/config\.action_mailer/, content)
+ end
+ assert_file "test/dummy/config/environments/production.rb" do |content|
+ assert_no_match(/config\.action_mailer/, content)
+ end
+ end
+
def test_ensure_that_database_option_is_passed_to_app_generator
run_generator [destination_root, "--database", "postgresql"]
assert_file "test/dummy/config/database.yml", /postgres/
@@ -159,13 +192,11 @@ class PluginGeneratorTest < Rails::Generators::TestCase
def test_generation_runs_bundle_install_with_full_and_mountable
result = run_generator [destination_root, "--mountable", "--full", "--dev"]
+ assert_match(/run bundle install/, result)
+ assert $?.success?, "Command failed: #{result}"
assert_file "#{destination_root}/Gemfile.lock" do |contents|
assert_match(/bukkits/, contents)
end
- assert_match(/run bundle install/, result)
- assert_match(/Using bukkits \(?0\.0\.1\)?/, result)
- assert_match(/Your bundle is complete/, result)
- assert_equal 1, result.scan("Your bundle is complete").size
end
def test_skipping_javascripts_without_mountable_option
@@ -226,6 +257,40 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "lib/bukkits.rb", /require "bukkits\/engine"/
end
+ def test_creating_engine_with_hyphenated_name_in_full_mode
+ run_generator [File.join(destination_root, "hyphenated-name"), "--full"]
+ assert_file "hyphenated-name/app/assets/javascripts/hyphenated/name"
+ assert_file "hyphenated-name/app/assets/stylesheets/hyphenated/name"
+ assert_file "hyphenated-name/app/assets/images/hyphenated/name"
+ assert_file "hyphenated-name/app/models"
+ assert_file "hyphenated-name/app/controllers"
+ assert_file "hyphenated-name/app/views"
+ assert_file "hyphenated-name/app/helpers"
+ assert_file "hyphenated-name/app/mailers"
+ assert_file "hyphenated-name/bin/rails"
+ assert_file "hyphenated-name/config/routes.rb", /Rails.application.routes.draw do/
+ assert_file "hyphenated-name/lib/hyphenated/name/engine.rb", /module Hyphenated\n module Name\n class Engine < ::Rails::Engine\n end\n end\nend/
+ assert_file "hyphenated-name/lib/hyphenated/name.rb", /require "hyphenated\/name\/engine"/
+ assert_file "hyphenated-name/bin/rails", /\.\.\/\.\.\/lib\/hyphenated\/name\/engine/
+ end
+
+ def test_creating_engine_with_hyphenated_and_underscored_name_in_full_mode
+ run_generator [File.join(destination_root, "my_hyphenated-name"), "--full"]
+ assert_file "my_hyphenated-name/app/assets/javascripts/my_hyphenated/name"
+ assert_file "my_hyphenated-name/app/assets/stylesheets/my_hyphenated/name"
+ assert_file "my_hyphenated-name/app/assets/images/my_hyphenated/name"
+ assert_file "my_hyphenated-name/app/models"
+ assert_file "my_hyphenated-name/app/controllers"
+ assert_file "my_hyphenated-name/app/views"
+ assert_file "my_hyphenated-name/app/helpers"
+ assert_file "my_hyphenated-name/app/mailers"
+ assert_file "my_hyphenated-name/bin/rails"
+ assert_file "my_hyphenated-name/config/routes.rb", /Rails.application.routes.draw do/
+ assert_file "my_hyphenated-name/lib/my_hyphenated/name/engine.rb", /module MyHyphenated\n module Name\n class Engine < ::Rails::Engine\n end\n end\nend/
+ assert_file "my_hyphenated-name/lib/my_hyphenated/name.rb", /require "my_hyphenated\/name\/engine"/
+ assert_file "my_hyphenated-name/bin/rails", /\.\.\/\.\.\/lib\/my_hyphenated\/name\/engine/
+ end
+
def test_being_quiet_while_creating_dummy_application
assert_no_match(/create\s+config\/application.rb/, run_generator)
end
@@ -251,6 +316,63 @@ class PluginGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_create_mountable_application_with_mountable_option_and_hypenated_name
+ run_generator [File.join(destination_root, "hyphenated-name"), "--mountable"]
+ assert_file "hyphenated-name/app/assets/javascripts/hyphenated/name"
+ assert_file "hyphenated-name/app/assets/stylesheets/hyphenated/name"
+ assert_file "hyphenated-name/app/assets/images/hyphenated/name"
+ assert_file "hyphenated-name/config/routes.rb", /Hyphenated::Name::Engine.routes.draw do/
+ assert_file "hyphenated-name/lib/hyphenated/name/version.rb", /module Hyphenated\n module Name\n VERSION = "0.0.1"\n end\nend/
+ assert_file "hyphenated-name/lib/hyphenated/name/engine.rb", /module Hyphenated\n module Name\n class Engine < ::Rails::Engine\n isolate_namespace Hyphenated::Name\n end\n end\nend/
+ assert_file "hyphenated-name/lib/hyphenated/name.rb", /require "hyphenated\/name\/engine"/
+ assert_file "hyphenated-name/test/dummy/config/routes.rb", /mount Hyphenated::Name::Engine => "\/hyphenated-name"/
+ assert_file "hyphenated-name/app/controllers/hyphenated/name/application_controller.rb", /module Hyphenated\n module Name\n class ApplicationController < ActionController::Base\n end\n end\nend/
+ assert_file "hyphenated-name/app/helpers/hyphenated/name/application_helper.rb", /module Hyphenated\n module Name\n module ApplicationHelper\n end\n end\nend/
+ assert_file "hyphenated-name/app/views/layouts/hyphenated/name/application.html.erb" do |contents|
+ assert_match "<title>Hyphenated name</title>", contents
+ assert_match(/stylesheet_link_tag\s+['"]hyphenated\/name\/application['"]/, contents)
+ assert_match(/javascript_include_tag\s+['"]hyphenated\/name\/application['"]/, contents)
+ end
+ end
+
+ def test_create_mountable_application_with_mountable_option_and_hypenated_and_underscored_name
+ run_generator [File.join(destination_root, "my_hyphenated-name"), "--mountable"]
+ assert_file "my_hyphenated-name/app/assets/javascripts/my_hyphenated/name"
+ assert_file "my_hyphenated-name/app/assets/stylesheets/my_hyphenated/name"
+ assert_file "my_hyphenated-name/app/assets/images/my_hyphenated/name"
+ assert_file "my_hyphenated-name/config/routes.rb", /MyHyphenated::Name::Engine.routes.draw do/
+ assert_file "my_hyphenated-name/lib/my_hyphenated/name/version.rb", /module MyHyphenated\n module Name\n VERSION = "0.0.1"\n end\nend/
+ assert_file "my_hyphenated-name/lib/my_hyphenated/name/engine.rb", /module MyHyphenated\n module Name\n class Engine < ::Rails::Engine\n isolate_namespace MyHyphenated::Name\n end\n end\nend/
+ assert_file "my_hyphenated-name/lib/my_hyphenated/name.rb", /require "my_hyphenated\/name\/engine"/
+ assert_file "my_hyphenated-name/test/dummy/config/routes.rb", /mount MyHyphenated::Name::Engine => "\/my_hyphenated-name"/
+ assert_file "my_hyphenated-name/app/controllers/my_hyphenated/name/application_controller.rb", /module MyHyphenated\n module Name\n class ApplicationController < ActionController::Base\n end\n end\nend/
+ assert_file "my_hyphenated-name/app/helpers/my_hyphenated/name/application_helper.rb", /module MyHyphenated\n module Name\n module ApplicationHelper\n end\n end\nend/
+ assert_file "my_hyphenated-name/app/views/layouts/my_hyphenated/name/application.html.erb" do |contents|
+ assert_match "<title>My hyphenated name</title>", contents
+ assert_match(/stylesheet_link_tag\s+['"]my_hyphenated\/name\/application['"]/, contents)
+ assert_match(/javascript_include_tag\s+['"]my_hyphenated\/name\/application['"]/, contents)
+ end
+ end
+
+ def test_create_mountable_application_with_mountable_option_and_multiple_hypenates_in_name
+ run_generator [File.join(destination_root, "deep-hyphenated-name"), "--mountable"]
+ assert_file "deep-hyphenated-name/app/assets/javascripts/deep/hyphenated/name"
+ assert_file "deep-hyphenated-name/app/assets/stylesheets/deep/hyphenated/name"
+ assert_file "deep-hyphenated-name/app/assets/images/deep/hyphenated/name"
+ assert_file "deep-hyphenated-name/config/routes.rb", /Deep::Hyphenated::Name::Engine.routes.draw do/
+ assert_file "deep-hyphenated-name/lib/deep/hyphenated/name/version.rb", /module Deep\n module Hyphenated\n module Name\n VERSION = "0.0.1"\n end\n end\nend/
+ assert_file "deep-hyphenated-name/lib/deep/hyphenated/name/engine.rb", /module Deep\n module Hyphenated\n module Name\n class Engine < ::Rails::Engine\n isolate_namespace Deep::Hyphenated::Name\n end\n end\n end\nend/
+ assert_file "deep-hyphenated-name/lib/deep/hyphenated/name.rb", /require "deep\/hyphenated\/name\/engine"/
+ assert_file "deep-hyphenated-name/test/dummy/config/routes.rb", /mount Deep::Hyphenated::Name::Engine => "\/deep-hyphenated-name"/
+ assert_file "deep-hyphenated-name/app/controllers/deep/hyphenated/name/application_controller.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationController < ActionController::Base\n end\n end\n end\nend/
+ assert_file "deep-hyphenated-name/app/helpers/deep/hyphenated/name/application_helper.rb", /module Deep\n module Hyphenated\n module Name\n module ApplicationHelper\n end\n end\n end\nend/
+ assert_file "deep-hyphenated-name/app/views/layouts/deep/hyphenated/name/application.html.erb" do |contents|
+ assert_match "<title>Deep hyphenated name</title>", contents
+ assert_match(/stylesheet_link_tag\s+['"]deep\/hyphenated\/name\/application['"]/, contents)
+ assert_match(/javascript_include_tag\s+['"]deep\/hyphenated\/name\/application['"]/, contents)
+ end
+ end
+
def test_creating_gemspec
run_generator
assert_file "bukkits.gemspec", /s.name\s+= "bukkits"/
@@ -295,7 +417,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
end
def test_creating_dummy_without_tests_but_with_dummy_path
- run_generator [destination_root, "--dummy_path", "spec/dummy", "--skip-test-unit"]
+ run_generator [destination_root, "--dummy_path", "spec/dummy", "--skip-test"]
assert_file "spec/dummy"
assert_file "spec/dummy/config/application.rb"
assert_no_file "test"
@@ -307,14 +429,14 @@ class PluginGeneratorTest < Rails::Generators::TestCase
def test_ensure_that_gitignore_can_be_generated_from_a_template_for_dummy_path
FileUtils.cd(Rails.root)
- run_generator([destination_root, "--dummy_path", "spec/dummy", "--skip-test-unit"])
+ run_generator([destination_root, "--dummy_path", "spec/dummy", "--skip-test"])
assert_file ".gitignore" do |contents|
assert_match(/spec\/dummy/, contents)
end
end
- def test_skipping_test_unit
- run_generator [destination_root, "--skip-test-unit"]
+ def test_skipping_test_files
+ run_generator [destination_root, "--skip-test"]
assert_no_file "test"
assert_file "bukkits.gemspec" do |contents|
assert_no_match(/s.test_files = Dir\["test\/\*\*\/\*"\]/, contents)
diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb
index ca972a3bdd..34e752cea1 100644
--- a/railties/test/generators/scaffold_controller_generator_test.rb
+++ b/railties/test/generators/scaffold_controller_generator_test.rb
@@ -106,8 +106,8 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
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, user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \}/, content)
- assert_match(/patch :update, id: @user, user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \}/, 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)
end
end
@@ -117,8 +117,8 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
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, user: \{ \}/, content)
- assert_match(/patch :update, id: @user, user: \{ \}/, content)
+ assert_match(/post :create, params: \{ user: \{ \} \}/, content)
+ assert_match(/patch :update, params: \{ id: @user, user: \{ \} \}/, content)
end
end
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index 637bde2a44..ee06802874 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -58,15 +58,25 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_file "test/controllers/product_lines_controller_test.rb" do |test|
assert_match(/class ProductLinesControllerTest < ActionController::TestCase/, test)
- assert_match(/post :create, product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \}/, test)
- assert_match(/patch :update, id: @product_line, product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \}/, 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)
end
# Views
- %w(index edit new show _form).each do |view|
+ assert_no_file "app/views/layouts/product_lines.html.erb"
+
+ %w(index show).each do |view|
assert_file "app/views/product_lines/#{view}.html.erb"
end
- assert_no_file "app/views/layouts/product_lines.html.erb"
+
+ %w(edit new).each do |view|
+ assert_file "app/views/product_lines/#{view}.html.erb", /render 'form', product_line: @product_line/
+ end
+
+ assert_file "app/views/product_lines/_form.html.erb" do |test|
+ assert_match 'product_line', test
+ assert_no_match '@product_line', test
+ end
# Helpers
assert_file "app/helpers/product_lines_helper.rb"
@@ -83,8 +93,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_file "test/controllers/product_lines_controller_test.rb" do |content|
assert_match(/class ProductLinesControllerTest < ActionController::TestCase/, content)
assert_match(/test "should get index"/, content)
- assert_match(/post :create, product_line: \{ \}/, content)
- assert_match(/patch :update, id: @product_line, product_line: \{ \}/, content)
+ assert_match(/post :create, params: \{ product_line: \{ \} \}/, content)
+ assert_match(/patch :update, params: \{ id: @product_line, product_line: \{ \} \}/, content)
end
end
@@ -235,6 +245,29 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_file "config/routes.rb", /\.routes\.draw do\s*\|map\|\s*$/
end
+ def test_scaffold_generator_on_revoke_does_not_mutilate_routes
+ run_generator
+
+ route_path = File.expand_path("config/routes.rb", destination_root)
+ content = File.read(route_path)
+
+ # Remove all of the comments and blank lines from the routes file
+ content.gsub!(/^ \#.*\n/, '')
+ content.gsub!(/^\n/, '')
+
+ File.open(route_path, "wb") { |file| file.write(content) }
+ assert_file "config/routes.rb", /\.routes\.draw do\n resources :product_lines\nend\n\z/
+
+ run_generator ["product_line"], :behavior => :revoke
+
+ assert_file "config/routes.rb", /\.routes\.draw do\nend\n\z/
+ end
+
+ def test_scaffold_generator_ignores_commented_routes
+ run_generator ["product"]
+ assert_file "config/routes.rb", /\.routes\.draw do\n resources :products\n/
+ end
+
def test_scaffold_generator_no_assets_with_switch_no_assets
run_generator [ "posts", "--no-assets" ]
assert_no_file "app/assets/stylesheets/scaffold.css"
@@ -249,13 +282,27 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_no_file "app/assets/stylesheets/posts.css"
end
- def test_scaffold_generator_no_assets_with_switch_resource_route_false
+ def test_scaffold_generator_with_switch_resource_route_false
run_generator [ "posts", "--resource-route=false" ]
assert_file "config/routes.rb" do |route|
assert_no_match(/resources :posts$/, route)
end
end
+ def test_scaffold_generator_no_helper_with_switch_no_helper
+ output = run_generator [ "posts", "--no-helper" ]
+
+ assert_no_match(/error/, output)
+ assert_no_file "app/helpers/posts_helper.rb"
+ end
+
+ def test_scaffold_generator_no_helper_with_switch_helper_false
+ output = run_generator [ "posts", "--helper=false" ]
+
+ assert_no_match(/error/, output)
+ assert_no_file "app/helpers/posts_helper.rb"
+ end
+
def test_scaffold_generator_no_stylesheets
run_generator [ "posts", "--no-stylesheets" ]
assert_no_file "app/assets/stylesheets/scaffold.css"
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index b998fef42e..68f07f29d7 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -47,8 +47,8 @@ module SharedGeneratorTests
assert_match(/Invalid value for \-\-database option/, content)
end
- def test_test_unit_is_skipped_if_required
- run_generator [destination_root, "--skip-test-unit"]
+ def test_test_files_are_skipped_if_required
+ run_generator [destination_root, "--skip-test"]
assert_no_file "test"
end
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index bf2992005b..63209559d7 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -11,6 +11,7 @@ require 'fileutils'
require 'bundler/setup' unless defined?(Bundler)
require 'active_support'
require 'active_support/testing/autorun'
+require 'active_support/testing/stream'
require 'active_support/test_case'
RAILS_FRAMEWORK_ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../..")
@@ -69,7 +70,8 @@ module TestHelpers
def assert_welcome(resp)
assert_equal 200, resp[0]
- assert resp[1]["Content-Type"] = "text/html"
+ assert_match 'text/html', resp[1]["Content-Type"]
+ assert_match 'charset=utf-8', resp[1]["Content-Type"]
assert extract_body(resp).match(/Welcome aboard/)
end
@@ -143,6 +145,7 @@ module TestHelpers
config.active_support.deprecation = :log
config.active_support.test_order = :random
config.action_controller.allow_forgery_protection = false
+ config.log_level = :info
RUBY
end
@@ -162,6 +165,8 @@ module TestHelpers
app.secrets.secret_key_base = "3b7cd727ee24e8444053437c36cc66c4"
app.config.session_store :cookie_store, key: "_myapp_session"
app.config.active_support.deprecation = :log
+ app.config.active_support.test_order = :random
+ app.config.log_level = :info
yield app if block_given?
app.initialize!
@@ -272,12 +277,6 @@ module TestHelpers
end
end
- def gsub_app_file(path, regexp, *args, &block)
- path = "#{app_path}/#{path}"
- content = File.read(path).gsub(regexp, *args, &block)
- File.open(path, 'wb') { |f| f.write(content) }
- end
-
def remove_file(path)
FileUtils.rm_rf "#{app_path}/#{path}"
end
@@ -306,35 +305,10 @@ class ActiveSupport::TestCase
include TestHelpers::Paths
include TestHelpers::Rack
include TestHelpers::Generation
+ include ActiveSupport::Testing::Stream
self.test_order = :sorted
- private
-
- def capture(stream)
- stream = stream.to_s
- captured_stream = Tempfile.new(stream)
- stream_io = eval("$#{stream}")
- origin_stream = stream_io.dup
- stream_io.reopen(captured_stream)
-
- yield
-
- stream_io.rewind
- return captured_stream.read
- ensure
- captured_stream.close
- captured_stream.unlink
- stream_io.reopen(origin_stream)
- end
-
- def quietly
- silence_stream(STDOUT) do
- silence_stream(STDERR) do
- yield
- end
- end
- end
end
# Create a scope and build a fixture rails app
diff --git a/railties/test/path_generation_test.rb b/railties/test/path_generation_test.rb
index 13bf29d3c3..e3dfcdfb7d 100644
--- a/railties/test/path_generation_test.rb
+++ b/railties/test/path_generation_test.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'abstract_unit'
require 'active_support/core_ext/object/with_options'
require 'active_support/core_ext/object/json'
diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb
index 8d61af4972..c51503c2b7 100644
--- a/railties/test/rails_info_controller_test.rb
+++ b/railties/test/rails_info_controller_test.rb
@@ -1,5 +1,4 @@
require 'abstract_unit'
-require 'mocha/setup' # FIXME: stop using mocha
module ActionController
class Base
@@ -15,26 +14,26 @@ class InfoControllerTest < ActionController::TestCase
get '/rails/info/properties' => "rails/info#properties"
get '/rails/info/routes' => "rails/info#routes"
end
- @controller.stubs(:local_request? => true)
@routes = Rails.application.routes
- Rails::InfoController.send(:include, @routes.url_helpers)
+ Rails::InfoController.include(@routes.url_helpers)
+
+ @request.env["REMOTE_ADDR"] = "127.0.0.1"
end
test "info controller does not allow remote requests" do
- @controller.stubs(local_request?: false)
+ @request.env["REMOTE_ADDR"] = "example.org"
get :properties
assert_response :forbidden
end
test "info controller renders an error message when request was forbidden" do
- @controller.stubs(local_request?: false)
+ @request.env["REMOTE_ADDR"] = "example.org"
get :properties
assert_select 'p'
end
test "info controller allows requests when all requests are considered local" do
- @controller.stubs(local_request?: true)
get :properties
assert_response :success
end
@@ -54,4 +53,29 @@ class InfoControllerTest < ActionController::TestCase
assert_response :success
end
+ test "info controller returns exact matches" do
+ exact_count = -> { JSON(response.body)['exact'].size }
+
+ get :routes, params: { path: 'rails/info/route' }
+ assert exact_count.call == 0, 'should not match incomplete routes'
+
+ get :routes, params: { path: 'rails/info/routes' }
+ assert exact_count.call == 1, 'should match complete routes'
+
+ get :routes, params: { path: 'rails/info/routes.html' }
+ assert exact_count.call == 1, 'should match complete routes with optional parts'
+ end
+
+ test "info controller returns fuzzy matches" do
+ fuzzy_count = -> { JSON(response.body)['fuzzy'].size }
+
+ get :routes, params: { path: 'rails/info' }
+ assert fuzzy_count.call == 2, 'should match incomplete routes'
+
+ get :routes, params: { path: 'rails/info/routes' }
+ assert fuzzy_count.call == 1, 'should match complete routes'
+
+ get :routes, params: { path: 'rails/info/routes.html' }
+ assert fuzzy_count.call == 0, 'should match optional parts of route literally'
+ end
end
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 6239af2066..79bd7a8241 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -498,17 +498,12 @@ YAML
boot_rails
initializers = Rails.application.initializers.tsort
- index = initializers.index { |i| i.name == "dummy_initializer" }
- selection = initializers[(index-3)..(index)].map(&:name).map(&:to_s)
+ dummy_index = initializers.index { |i| i.name == "dummy_initializer" }
+ config_index = initializers.rindex { |i| i.name == :load_config_initializers }
+ stack_index = initializers.index { |i| i.name == :build_middleware_stack }
- assert_equal %w(
- load_config_initializers
- load_config_initializers
- engines_blank_point
- dummy_initializer
- ), selection
-
- assert index < initializers.index { |i| i.name == :build_middleware_stack }
+ assert config_index < dummy_index
+ assert dummy_index < stack_index
end
class Upcaser
@@ -518,7 +513,7 @@ YAML
def call(env)
response = @app.call(env)
- response[2].each { |b| b.upcase! }
+ response[2].each(&:upcase!)
response
end
end
@@ -746,8 +741,8 @@ YAML
assert_equal "bukkits_", Bukkits.table_name_prefix
assert_equal "bukkits", Bukkits::Engine.engine_name
assert_equal Bukkits.railtie_namespace, Bukkits::Engine
- assert ::Bukkits::MyMailer.method_defined?(:foo_path)
- assert !::Bukkits::MyMailer.method_defined?(:bar_path)
+ assert ::Bukkits::MyMailer.method_defined?(:foo_url)
+ assert !::Bukkits::MyMailer.method_defined?(:bar_url)
get("/bukkits/from_app")
assert_equal "false", last_response.body
@@ -1160,10 +1155,10 @@ YAML
assert_equal "App's bar partial", last_response.body.strip
get("/assets/foo.js")
- assert_equal "// Bukkit's foo js\n;", last_response.body.strip
+ assert_equal "// Bukkit's foo js", last_response.body.strip
get("/assets/bar.js")
- assert_equal "// App's bar js\n;", last_response.body.strip
+ assert_equal "// App's bar js", last_response.body.strip
# ensure that railties are not added twice
railties = Rails.application.send(:ordered_railties).map(&:class)
@@ -1210,7 +1205,7 @@ YAML
test "engine can be properly mounted at root" do
add_to_config("config.action_dispatch.show_exceptions = false")
- add_to_config("config.serve_static_assets = false")
+ add_to_config("config.serve_static_files = false")
@plugin.write "lib/bukkits.rb", <<-RUBY
module Bukkits
diff --git a/railties/test/test_unit/reporter_test.rb b/railties/test/test_unit/reporter_test.rb
new file mode 100644
index 0000000000..77883612f5
--- /dev/null
+++ b/railties/test/test_unit/reporter_test.rb
@@ -0,0 +1,74 @@
+require 'abstract_unit'
+require 'rails/test_unit/reporter'
+
+class TestUnitReporterTest < ActiveSupport::TestCase
+ class ExampleTest < Minitest::Test
+ def woot; end
+ end
+
+ setup do
+ @output = StringIO.new
+ @reporter = Rails::TestUnitReporter.new @output
+ end
+
+ test "prints rerun snippet to run a single failed test" do
+ @reporter.record(failed_test)
+ @reporter.report
+
+ assert_match %r{^bin/rails test .*test/test_unit/reporter_test.rb:6$}, @output.string
+ assert_rerun_snippet_count 1
+ end
+
+ test "prints rerun snippet for every failed test" do
+ @reporter.record(failed_test)
+ @reporter.record(failed_test)
+ @reporter.record(failed_test)
+ @reporter.report
+
+ assert_rerun_snippet_count 3
+ end
+
+ test "does not print snippet for successful and skipped tests" do
+ @reporter.record(passing_test)
+ @reporter.record(skipped_test)
+ @reporter.report
+ assert_rerun_snippet_count 0
+ end
+
+ test "prints rerun snippet for skipped tests if run in verbose mode" do
+ verbose = Rails::TestUnitReporter.new @output, verbose: true
+ verbose.record(skipped_test)
+ verbose.report
+
+ assert_rerun_snippet_count 1
+ end
+
+ private
+ def assert_rerun_snippet_count(snippet_count)
+ assert_equal snippet_count, @output.string.scan(%r{^bin/rails test }).size
+ end
+
+ def failed_test
+ ft = ExampleTest.new(:woot)
+ ft.failures << begin
+ raise Minitest::Assertion, "boo"
+ rescue Minitest::Assertion => e
+ e
+ end
+ ft
+ end
+
+ def passing_test
+ ExampleTest.new(:woot)
+ end
+
+ def skipped_test
+ st = ExampleTest.new(:woot)
+ st.failures << begin
+ raise Minitest::Skip
+ rescue Minitest::Assertion => e
+ e
+ end
+ st
+ end
+end
diff --git a/railties/test/test_unit/runner_test.rb b/railties/test/test_unit/runner_test.rb
new file mode 100644
index 0000000000..9ea8b2c114
--- /dev/null
+++ b/railties/test/test_unit/runner_test.rb
@@ -0,0 +1,111 @@
+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/tasks/release.rb b/tasks/release.rb
index de05dfad99..d8c1390eef 100644
--- a/tasks/release.rb
+++ b/tasks/release.rb
@@ -1,4 +1,4 @@
-FRAMEWORKS = %w( activesupport activemodel activerecord actionview actionpack actionmailer railties activejob )
+FRAMEWORKS = %w( activesupport activemodel activerecord actionview actionpack activejob actionmailer railties )
root = File.expand_path('../../', __FILE__)
version = File.read("#{root}/RAILS_VERSION").strip
@@ -102,7 +102,7 @@ namespace :all do
abort "[ABORTING] `git status` reports a dirty tree. Make sure all changes are committed"
end
- unless ENV['SKIP_TAG'] || `git tag | grep '^#{tag}$`.strip.empty?
+ unless ENV['SKIP_TAG'] || `git tag | grep '^#{tag}$'`.strip.empty?
abort "[ABORTING] `git tag` shows that #{tag} already exists. Has this version already\n"\
" been released? Git tagging can be skipped by setting SKIP_TAG=1"
end
diff --git a/tools/line_statistics b/tools/line_statistics
index bfa921b095..d0b3557d7d 100644
--- a/tools/line_statistics
+++ b/tools/line_statistics
@@ -1,4 +1,4 @@
-# Class used to calculates LOC for a provided file list.
+# Class used to calculate LOC for a provided file list.
#
# Example:
# files = FileList["lib/active_record/**/*.rb"]
diff --git a/version.rb b/version.rb
index 8abed99f2c..7d74b1bfe5 100644
--- a/version.rb
+++ b/version.rb
@@ -5,10 +5,10 @@ module Rails
end
module VERSION
- MAJOR = 4
- MINOR = 2
+ MAJOR = 5
+ MINOR = 0
TINY = 0
- PRE = "beta4"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end